├── .prettierignore ├── clusters ├── prod │ ├── flux-system │ │ ├── gotk-sync.yaml │ │ ├── gotk-components.yaml │ │ └── kustomization.yaml │ ├── apps.yaml │ ├── cloud.yaml │ └── infrastructure.yaml ├── staging │ ├── flux-system │ │ ├── gotk-sync.yaml │ │ ├── gotk-components.yaml │ │ └── kustomization.yaml │ ├── apps.yaml │ ├── cloud.yaml │ └── infrastructure.yaml ├── dev │ ├── apps.yaml │ ├── flux-system │ │ ├── gotk-sync.yaml │ │ └── kustomization.yaml │ ├── cloud.yaml │ └── infrastructure.yaml └── README.md ├── .github ├── screens │ ├── flux-ui-apps.png │ └── flux-ui-depends-on.png └── workflows │ ├── json.yaml │ ├── shell.yaml │ ├── pr.yaml │ ├── yaml.yaml │ ├── validate.yaml │ └── e2e.yaml ├── apps ├── prod │ ├── kustomization.yaml │ └── podinfo │ │ └── imagepolicy.yaml ├── staging │ └── kustomization.yaml ├── dev │ ├── kustomization.yaml │ ├── fastapi-example │ │ ├── controller.yaml │ │ ├── imageupdateautomation.yaml │ │ ├── kustomization.yaml │ │ └── git-secret.enc.yaml │ └── podinfo │ │ ├── kustomization.yaml │ │ └── podinfo-values.yaml ├── base │ ├── podinfo │ │ ├── namespace.yaml │ │ ├── kustomization.yaml │ │ ├── repository.yaml │ │ └── release.yaml │ └── fastapi-example │ │ ├── namespace.yaml │ │ ├── kustomization.yaml │ │ ├── imagerepository.yaml │ │ ├── gitrepository.yaml │ │ ├── controller.yaml │ │ ├── imagepolicy.yaml │ │ └── imageupdateautomation.yaml └── README.md ├── cloud ├── providers │ ├── base │ │ ├── kustomization.yaml │ │ └── gcp.yaml │ ├── dev │ │ └── kustomization.yaml │ ├── prod │ │ └── kustomization.yaml │ └── staging │ │ └── kustomization.yaml ├── resources │ ├── dev │ │ └── kustomization.yaml │ ├── prod │ │ └── kustomization.yaml │ ├── staging │ │ └── kustomization.yaml │ └── base │ │ ├── kustomization.yaml │ │ ├── gcp-buckets.yaml │ │ └── gcp-gke.yaml └── README.md ├── infrastructure ├── configs │ ├── prod │ │ └── kustomization.yaml │ ├── staging │ │ └── kustomization.yaml │ ├── base │ │ ├── flux-receivers │ │ │ ├── kustomization.yaml │ │ │ ├── ingress.yaml │ │ │ └── receiver.yaml │ │ ├── cert-manager │ │ │ ├── kustomization.yaml │ │ │ └── cluster-issuer.yaml │ │ ├── weave-gitops │ │ │ ├── kustomization.yaml │ │ │ └── network-policy.yaml │ │ └── monitoring │ │ │ ├── kustomization.yaml │ │ │ ├── podmonitor.yaml │ │ │ └── dashboards │ │ │ ├── logs.json │ │ │ ├── cluster.json │ │ │ └── control-plane.json │ └── dev │ │ ├── flux-receivers │ │ ├── kustomization.yaml │ │ └── receiver-secret.enc.yaml │ │ ├── kustomization.yaml │ │ └── docker-secret.enc.yaml ├── controllers │ ├── prod │ │ └── kustomization.yaml │ ├── staging │ │ └── kustomization.yaml │ ├── base │ │ ├── sso │ │ │ ├── namespace.yaml │ │ │ ├── dex │ │ │ │ ├── kustomization.yaml │ │ │ │ ├── repository.yaml │ │ │ │ └── release.yaml │ │ │ ├── oauth2-proxy │ │ │ │ ├── kustomization.yaml │ │ │ │ ├── repository.yaml │ │ │ │ └── release.yaml │ │ │ └── kustomization.yaml │ │ ├── rabbitmq │ │ │ ├── namespace.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── repository.yaml │ │ │ └── release.yaml │ │ ├── cert-manager │ │ │ ├── namespace.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── repository.yaml │ │ │ └── release.yaml │ │ ├── external-dns │ │ │ ├── namespace.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── repository.yaml │ │ │ └── release.yaml │ │ ├── ingress-nginx │ │ │ ├── namespace.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── repository.yaml │ │ │ └── release.yaml │ │ ├── monitoring │ │ │ ├── loki-stack │ │ │ │ ├── kustomization.yaml │ │ │ │ ├── repository.yaml │ │ │ │ └── release.yaml │ │ │ ├── kustomization.yaml │ │ │ ├── namespace.yaml │ │ │ └── kube-prometheus-stack │ │ │ │ ├── kustomization.yaml │ │ │ │ ├── repository.yaml │ │ │ │ └── release.yaml │ │ └── weave-gitops │ │ │ ├── kustomization.yaml │ │ │ ├── repository.yaml │ │ │ └── release.yaml │ └── dev │ │ ├── kustomization.yaml │ │ ├── weave-gitops │ │ ├── kustomizeconfig.yaml │ │ ├── kustomization.yaml │ │ ├── release.yaml │ │ └── values-secret.enc.yaml │ │ ├── cert-manager │ │ ├── kustomization.yaml │ │ └── release.yaml │ │ └── ingress-nginx │ │ ├── kustomization.yaml │ │ └── release.yaml └── README.md ├── .gitignore ├── .sourceignore ├── k3d.config.yaml ├── scripts ├── decrypt.sh ├── utils.sh ├── encrypt.sh ├── set-hosts.sh ├── create-cluster.sh ├── validate.sh └── provision-cluster.sh ├── kind.config.yaml ├── .editorconfig ├── .envrc.example ├── .githooks ├── pre-commit └── commit-msg ├── renovate.json ├── .sops.yaml ├── Makefile ├── README.md └── LICENSE /.prettierignore: -------------------------------------------------------------------------------- 1 | gotk-components.yaml 2 | gotk-sync.yaml 3 | -------------------------------------------------------------------------------- /clusters/prod/flux-system/gotk-sync.yaml: -------------------------------------------------------------------------------- 1 | # This file will be generated automatically by flux boostrap. 2 | -------------------------------------------------------------------------------- /clusters/prod/flux-system/gotk-components.yaml: -------------------------------------------------------------------------------- 1 | # This file will be generated automatically by flux boostrap. 2 | -------------------------------------------------------------------------------- /clusters/staging/flux-system/gotk-sync.yaml: -------------------------------------------------------------------------------- 1 | # This file will be generated automatically by flux boostrap. 2 | -------------------------------------------------------------------------------- /.github/screens/flux-ui-apps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darioblanco/gitops/HEAD/.github/screens/flux-ui-apps.png -------------------------------------------------------------------------------- /clusters/staging/flux-system/gotk-components.yaml: -------------------------------------------------------------------------------- 1 | # This file will be generated automatically by flux boostrap. 2 | -------------------------------------------------------------------------------- /.github/screens/flux-ui-depends-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darioblanco/gitops/HEAD/.github/screens/flux-ui-depends-on.png -------------------------------------------------------------------------------- /apps/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../dev 5 | -------------------------------------------------------------------------------- /apps/staging/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../dev 5 | -------------------------------------------------------------------------------- /cloud/providers/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - gcp.yaml 5 | -------------------------------------------------------------------------------- /cloud/providers/dev/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | -------------------------------------------------------------------------------- /cloud/providers/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | -------------------------------------------------------------------------------- /cloud/resources/dev/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | -------------------------------------------------------------------------------- /cloud/resources/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | -------------------------------------------------------------------------------- /cloud/providers/staging/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | -------------------------------------------------------------------------------- /cloud/resources/staging/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base 5 | -------------------------------------------------------------------------------- /infrastructure/configs/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../dev 5 | -------------------------------------------------------------------------------- /infrastructure/configs/staging/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../dev 5 | -------------------------------------------------------------------------------- /infrastructure/controllers/prod/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../dev 5 | -------------------------------------------------------------------------------- /apps/dev/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - fastapi-example 5 | - podinfo 6 | -------------------------------------------------------------------------------- /infrastructure/controllers/staging/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../dev 5 | -------------------------------------------------------------------------------- /apps/base/podinfo/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: podinfo 5 | labels: 6 | toolkit.fluxcd.io/tenant: dev-team 7 | -------------------------------------------------------------------------------- /cloud/resources/base/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - gcp-buckets.yaml 5 | - gcp-gke.yaml 6 | -------------------------------------------------------------------------------- /infrastructure/configs/base/flux-receivers/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - receiver.yaml 5 | -------------------------------------------------------------------------------- /apps/base/fastapi-example/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: fastapi-example 5 | labels: 6 | toolkit.fluxcd.io/tenant: dev-team 7 | -------------------------------------------------------------------------------- /infrastructure/configs/base/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - cluster-issuer.yaml 5 | -------------------------------------------------------------------------------- /infrastructure/configs/base/weave-gitops/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - network-policy.yaml 5 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/sso/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: sso 5 | labels: 6 | toolkit.fluxcd.io/tenant: sre-team 7 | -------------------------------------------------------------------------------- /apps/base/podinfo/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - namespace.yaml 5 | - repository.yaml 6 | - release.yaml 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/rabbitmq/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: rabbitmq-system 5 | labels: 6 | toolkit.fluxcd.io/tenant: sre-team 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/sso/dex/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - repository.yaml 5 | - release.yaml 6 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/cert-manager/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: cert-manager 5 | labels: 6 | toolkit.fluxcd.io/tenant: sre-team 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/external-dns/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: external-dns 5 | labels: 6 | toolkit.fluxcd.io/tenant: sre-team 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/ingress-nginx/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: ingress-nginx 5 | labels: 6 | toolkit.fluxcd.io/tenant: sre-team 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/sso/oauth2-proxy/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - repository.yaml 5 | - release.yaml 6 | -------------------------------------------------------------------------------- /infrastructure/controllers/dev/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - cert-manager 5 | - ingress-nginx 6 | - weave-gitops 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # GitHub actions binaries 2 | bin/ 3 | 4 | # Environment variables with secrets 5 | .envrc 6 | 7 | # Other 8 | output.yaml 9 | 10 | # Private keys 11 | *.agekey 12 | *secret.yaml 13 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/monitoring/loki-stack/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - repository.yaml 5 | - release.yaml 6 | -------------------------------------------------------------------------------- /apps/dev/fastapi-example/controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1 2 | kind: Kustomization 3 | metadata: 4 | name: fastapi-example 5 | namespace: flux-system 6 | spec: 7 | path: ./deploy/dev 8 | -------------------------------------------------------------------------------- /infrastructure/configs/dev/flux-receivers/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../../base/flux-receivers 5 | - receiver-secret.enc.yaml 6 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - namespace.yaml 5 | - release.yaml 6 | - repository.yaml 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/external-dns/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - namespace.yaml 5 | - release.yaml 6 | - repository.yaml 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/rabbitmq/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - namespace.yaml 5 | - release.yaml 6 | - repository.yaml 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/dev/weave-gitops/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | nameReference: 2 | - kind: Secret 3 | version: v1 4 | fieldSpecs: 5 | - path: spec/valuesFrom/adminUser 6 | kind: HelmRelease 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/ingress-nginx/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - namespace.yaml 5 | - release.yaml 6 | - repository.yaml 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/monitoring/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - namespace.yaml 5 | - kube-prometheus-stack 6 | # - loki-stack 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/weave-gitops/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: flux-system 4 | resources: 5 | - release.yaml 6 | - repository.yaml 7 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/monitoring/namespace.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: monitoring 5 | labels: 6 | app.kubernetes.io/component: monitoring 7 | toolkit.fluxcd.io/tenant: sre-team 8 | -------------------------------------------------------------------------------- /.sourceignore: -------------------------------------------------------------------------------- 1 | # Flux ignore 2 | # https://fluxcd.io/flux/components/source/gitrepositories/#excluding-files 3 | 4 | # Exclude all 5 | /* 6 | 7 | # Include manifest directories 8 | !/apps/ 9 | !/clusters/ 10 | !/infrastructure/ 11 | -------------------------------------------------------------------------------- /apps/dev/podinfo/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../../base/podinfo 5 | patches: 6 | - path: podinfo-values.yaml 7 | target: 8 | kind: HelmRelease 9 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/monitoring/kube-prometheus-stack/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: monitoring 4 | resources: 5 | - repository.yaml 6 | - release.yaml 7 | -------------------------------------------------------------------------------- /apps/base/podinfo/repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: HelmRepository 3 | metadata: 4 | name: podinfo 5 | namespace: podinfo 6 | spec: 7 | interval: 5m 8 | url: https://stefanprodan.github.io/podinfo 9 | -------------------------------------------------------------------------------- /infrastructure/configs/dev/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../base/cert-manager 5 | - ../base/weave-gitops 6 | - flux-receivers 7 | - docker-secret.enc.yaml 8 | -------------------------------------------------------------------------------- /apps/dev/fastapi-example/imageupdateautomation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: image.toolkit.fluxcd.io/v1beta1 2 | kind: ImageUpdateAutomation 3 | metadata: 4 | name: fastapi-example 5 | namespace: flux-system 6 | spec: 7 | update: 8 | path: ./deploy/dev 9 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/weave-gitops/repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: HelmRepository 3 | metadata: 4 | name: weave-gitops 5 | spec: 6 | type: oci 7 | interval: 60m0s 8 | url: oci://ghcr.io/weaveworks/charts 9 | -------------------------------------------------------------------------------- /infrastructure/controllers/dev/cert-manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../../base/cert-manager 5 | patches: 6 | - path: release.yaml 7 | target: 8 | kind: HelmRelease 9 | -------------------------------------------------------------------------------- /infrastructure/controllers/dev/ingress-nginx/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../../base/ingress-nginx 5 | patches: 6 | - path: release.yaml 7 | target: 8 | kind: HelmRelease 9 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/cert-manager/repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: HelmRepository 3 | metadata: 4 | name: cert-manager 5 | namespace: cert-manager 6 | spec: 7 | interval: 24h 8 | url: https://charts.jetstack.io 9 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/sso/dex/repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: HelmRepository 3 | metadata: 4 | name: dex 5 | namespace: sso 6 | spec: 7 | interval: 120m 8 | type: default 9 | url: https://charts.dexidp.io 10 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/sso/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - namespace.yaml 5 | # Dex is disabled until the "install retries exhausted" error is fixed 6 | # - dex 7 | - oauth2-proxy 8 | -------------------------------------------------------------------------------- /apps/dev/podinfo/podinfo-values.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 2 | kind: HelmRelease 3 | metadata: 4 | name: podinfo 5 | namespace: podinfo 6 | spec: 7 | chart: 8 | spec: 9 | version: ">=1.0.0-alpha" 10 | test: 11 | enable: false 12 | -------------------------------------------------------------------------------- /k3d.config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: k3d.io/v1alpha2 2 | kind: Cluster 3 | nodes: 4 | - role: server 5 | count: 1 6 | kubernetesVersion: latest 7 | extraPortMappings: 8 | - containerPort: 6443 9 | hostPort: 6443 10 | - role: agent 11 | count: 3 12 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/external-dns/repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: HelmRepository 3 | metadata: 4 | name: external-dns 5 | namespace: external-dns 6 | spec: 7 | interval: 24h 8 | url: https://kubernetes-sigs.github.io/external-dns/ 9 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/ingress-nginx/repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: HelmRepository 3 | metadata: 4 | name: ingress-nginx 5 | namespace: ingress-nginx 6 | spec: 7 | interval: 24h 8 | url: https://kubernetes.github.io/ingress-nginx 9 | -------------------------------------------------------------------------------- /apps/prod/podinfo/imagepolicy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: image.toolkit.fluxcd.io/v1beta2 2 | kind: ImagePolicy 3 | metadata: 4 | name: podinfo 5 | namespace: flux-system 6 | spec: 7 | imageRepositoryRef: 8 | name: podinfo 9 | policy: 10 | semver: 11 | range: ">=1.0.0-0" 12 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/monitoring/loki-stack/repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: HelmRepository 3 | metadata: 4 | name: grafana-charts 5 | namespace: monitoring 6 | spec: 7 | interval: 120m0s 8 | url: https://grafana.github.io/helm-charts 9 | -------------------------------------------------------------------------------- /apps/base/fastapi-example/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - namespace.yaml 5 | - gitrepository.yaml 6 | - imagerepository.yaml 7 | - controller.yaml 8 | - imagepolicy.yaml 9 | - imageupdateautomation.yaml 10 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/rabbitmq/repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: HelmRepository 3 | metadata: 4 | name: rabbitmq 5 | namespace: rabbitmq-system 6 | spec: 7 | type: oci 8 | interval: 60m0s 9 | url: oci://registry-1.docker.io/bitnamicharts 10 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/sso/oauth2-proxy/repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: HelmRepository 3 | metadata: 4 | name: oauth2-proxy 5 | namespace: sso 6 | spec: 7 | interval: 120m 8 | type: default 9 | url: https://oauth2-proxy.github.io/manifests 10 | -------------------------------------------------------------------------------- /infrastructure/controllers/dev/cert-manager/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://artifacthub.io/packages/helm/cert-manager/cert-manager 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: cert-manager 6 | spec: 7 | chart: 8 | spec: 9 | version: "1.x" 10 | -------------------------------------------------------------------------------- /.github/workflows/json.yaml: -------------------------------------------------------------------------------- 1 | name: json 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - "**.json" 8 | pull_request: 9 | paths: 10 | - "**.json" 11 | workflow_dispatch: 12 | jobs: 13 | lint: 14 | uses: darioblanco/.github/.github/workflows/json.yaml@v1 15 | -------------------------------------------------------------------------------- /.github/workflows/shell.yaml: -------------------------------------------------------------------------------- 1 | name: shell 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - "**.sh" 8 | pull_request: 9 | paths: 10 | - "**.sh" 11 | workflow_dispatch: 12 | jobs: 13 | lint: 14 | uses: darioblanco/.github/.github/workflows/shell.yaml@v1 15 | -------------------------------------------------------------------------------- /apps/base/fastapi-example/imagerepository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: image.toolkit.fluxcd.io/v1beta2 2 | kind: ImageRepository 3 | metadata: 4 | name: fastapi-example 5 | namespace: flux-system 6 | spec: 7 | image: ghcr.io/darioblanco/fastapi-example 8 | interval: 1h 9 | secretRef: 10 | name: github-docker 11 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/monitoring/kube-prometheus-stack/repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1beta2 2 | kind: HelmRepository 3 | metadata: 4 | name: prometheus-community 5 | namespace: monitoring 6 | spec: 7 | interval: 120m 8 | type: default 9 | url: https://prometheus-community.github.io/helm-charts 10 | -------------------------------------------------------------------------------- /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: pr 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - edited 7 | - ready_for_review 8 | - reopened 9 | - synchronize 10 | jobs: 11 | pr: 12 | uses: darioblanco/.github/.github/workflows/pr.yaml@v1 13 | secrets: 14 | repoAccessToken: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /apps/base/fastapi-example/gitrepository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: source.toolkit.fluxcd.io/v1 2 | kind: GitRepository 3 | metadata: 4 | name: fastapi-example 5 | namespace: flux-system 6 | spec: 7 | interval: 5m 8 | url: https://github.com/darioblanco/fastapi-example 9 | ref: 10 | branch: main 11 | secretRef: 12 | name: fastapi-example-repo 13 | -------------------------------------------------------------------------------- /apps/dev/fastapi-example/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - ../../base/fastapi-example 5 | - git-secret.enc.yaml 6 | patches: 7 | - path: controller.yaml 8 | target: 9 | kind: Kustomization 10 | - path: imageupdateautomation.yaml 11 | target: 12 | kind: ImageUpdateAutomation 13 | -------------------------------------------------------------------------------- /clusters/prod/apps.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1 2 | kind: Kustomization 3 | metadata: 4 | name: apps 5 | namespace: flux-system 6 | spec: 7 | interval: 10m0s 8 | dependsOn: 9 | - name: infra-configs 10 | sourceRef: 11 | kind: GitRepository 12 | name: flux-system 13 | path: ./apps/prod 14 | prune: true 15 | wait: true 16 | timeout: 5m0s 17 | -------------------------------------------------------------------------------- /apps/base/fastapi-example/controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1 2 | kind: Kustomization 3 | metadata: 4 | name: fastapi-example 5 | namespace: flux-system 6 | spec: 7 | interval: 1m 8 | prune: true 9 | sourceRef: 10 | kind: GitRepository 11 | name: fastapi-example 12 | decryption: 13 | provider: sops 14 | secretRef: 15 | name: sops-age 16 | -------------------------------------------------------------------------------- /apps/base/fastapi-example/imagepolicy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: image.toolkit.fluxcd.io/v1beta2 2 | kind: ImagePolicy 3 | metadata: 4 | name: fastapi-example 5 | namespace: flux-system 6 | spec: 7 | imageRepositoryRef: 8 | name: fastapi-example 9 | filterTags: 10 | pattern: "^main-[a-fA-F0-9]+-(?P.*)" 11 | extract: "$ts" 12 | policy: 13 | numerical: 14 | order: asc 15 | -------------------------------------------------------------------------------- /clusters/staging/apps.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1 2 | kind: Kustomization 3 | metadata: 4 | name: apps 5 | namespace: flux-system 6 | spec: 7 | interval: 10m0s 8 | dependsOn: 9 | - name: infra-configs 10 | sourceRef: 11 | kind: GitRepository 12 | name: flux-system 13 | path: ./apps/staging 14 | prune: true 15 | wait: true 16 | timeout: 5m0s 17 | -------------------------------------------------------------------------------- /infrastructure/configs/base/weave-gitops/network-policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: NetworkPolicy 3 | metadata: 4 | name: weave-gitops-ingress 5 | namespace: flux-system 6 | spec: 7 | policyTypes: 8 | - Ingress 9 | ingress: 10 | - from: 11 | - namespaceSelector: {} 12 | podSelector: 13 | matchLabels: 14 | app.kubernetes.io/name: weave-gitops 15 | -------------------------------------------------------------------------------- /.github/workflows/yaml.yaml: -------------------------------------------------------------------------------- 1 | name: yaml 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths: 7 | - "**.yml" 8 | - "**.yaml" 9 | - "!.github/workflows/*.yaml" 10 | pull_request: 11 | paths: 12 | - "**.yml" 13 | - "**.yaml" 14 | - "!.github/workflows/*.yaml" 15 | workflow_dispatch: 16 | jobs: 17 | lint: 18 | uses: darioblanco/.github/.github/workflows/yaml.yaml@v1 19 | -------------------------------------------------------------------------------- /cloud/resources/base/gcp-buckets.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: gcp-buckets 6 | labels: 7 | toolkit.fluxcd.io/tenant: sre-team 8 | --- 9 | apiVersion: storage.gcp.upbound.io/v1beta1 10 | kind: Bucket 11 | metadata: 12 | name: crossplane-bucket-example 13 | labels: 14 | docs.crossplane.io/example: provider-gcp 15 | spec: 16 | forProvider: 17 | location: US 18 | providerConfigRef: 19 | name: default 20 | -------------------------------------------------------------------------------- /clusters/dev/apps.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.toolkit.fluxcd.io/v1 2 | kind: Kustomization 3 | metadata: 4 | name: apps 5 | namespace: flux-system 6 | spec: 7 | interval: 10m0s 8 | dependsOn: 9 | - name: infra-configs 10 | sourceRef: 11 | kind: GitRepository 12 | name: flux-system 13 | path: ./apps/dev 14 | prune: true 15 | wait: true 16 | timeout: 5m0s 17 | decryption: 18 | provider: sops 19 | secretRef: 20 | name: sops-age 21 | -------------------------------------------------------------------------------- /infrastructure/configs/base/flux-receivers/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: webhook-receiver 5 | namespace: flux-system 6 | spec: 7 | rules: 8 | - host: flux-webhook.local 9 | http: 10 | paths: 11 | - pathType: Prefix 12 | path: / 13 | backend: 14 | service: 15 | name: webhook-receiver 16 | port: 17 | number: 80 18 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/external-dns/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://artifacthub.io/packages/helm/external-dns/external-dns 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: external-dns 6 | namespace: external-dns 7 | spec: 8 | interval: 30m 9 | chart: 10 | spec: 11 | chart: external-dns 12 | version: "1.x" 13 | sourceRef: 14 | kind: HelmRepository 15 | name: external-dns 16 | interval: 12h 17 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/cert-manager/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://artifacthub.io/packages/helm/cert-manager/cert-manager 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: cert-manager 6 | namespace: cert-manager 7 | spec: 8 | interval: 30m 9 | chart: 10 | spec: 11 | chart: cert-manager 12 | version: "*" 13 | sourceRef: 14 | kind: HelmRepository 15 | name: cert-manager 16 | interval: 12h 17 | values: 18 | installCRDs: true 19 | -------------------------------------------------------------------------------- /infrastructure/configs/base/monitoring/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - podmonitor.yaml 5 | configMapGenerator: 6 | - name: flux-grafana-dashboards 7 | namespace: monitoring 8 | files: 9 | - dashboards/cluster.json 10 | - dashboards/control-plane.json 11 | - dashboards/logs.json 12 | options: 13 | labels: 14 | grafana_dashboard: "1" 15 | app.kubernetes.io/part-of: flux 16 | app.kubernetes.io/component: monitoring 17 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/rabbitmq/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://artifacthub.io/packages/helm/bitnami/rabbitmq 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: rabbitmq 6 | namespace: rabbitmq-system 7 | spec: 8 | interval: 60m 9 | chart: 10 | spec: 11 | chart: rabbitmq 12 | version: "12.x" 13 | sourceRef: 14 | kind: HelmRepository 15 | name: rabbitmq 16 | interval: 12h 17 | # https://github.com/bitnami/charts/blob/main/bitnami/rabbitmq/values.yaml 18 | # values: 19 | -------------------------------------------------------------------------------- /scripts/decrypt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the directory of the current script 4 | script_dir="$(dirname "${BASH_SOURCE[0]}")" 5 | 6 | # shellcheck disable=SC1091 7 | source "${script_dir}"/utils.sh 8 | 9 | # Exit if no filepath is provided 10 | if [ $# -eq 0 ]; then 11 | print_red "Error: No encrypted file provided" 12 | echo "Usage: $0 " 13 | exit 1 14 | fi 15 | 16 | file="$1" 17 | output_filepath="${file%%.enc.yaml}.yaml" 18 | 19 | # Decrypt the file 20 | sops -d "$file" > "${output_filepath}" 21 | 22 | print_green "Decrypted file saved to $output_filepath" 23 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/sso/oauth2-proxy/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://artifacthub.io/packages/helm/oauth2-proxy/oauth2-proxy 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: oauth2-proxy 6 | namespace: sso 7 | spec: 8 | interval: 5m 9 | chart: 10 | spec: 11 | version: "6.16.x" 12 | chart: oauth2-proxy 13 | sourceRef: 14 | kind: HelmRepository 15 | name: oauth2-proxy 16 | interval: 60m 17 | # https://github.com/oauth2-proxy/manifests/blob/main/helm/oauth2-proxy/values.yaml 18 | # values: 19 | -------------------------------------------------------------------------------- /infrastructure/configs/base/cert-manager/cluster-issuer.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: letsencrypt 5 | namespace: cert-manager 6 | spec: 7 | acme: 8 | # Replace the email address with your own contact email 9 | email: fluxcdbot@users.noreply.github.com 10 | # The server is replaced in /clusters/production/infrastructure.yaml 11 | server: https://acme-staging-v02.api.letsencrypt.org/directory 12 | privateKeySecretRef: 13 | name: letsencrypt-nginx 14 | solvers: 15 | - http01: 16 | ingress: 17 | class: nginx 18 | -------------------------------------------------------------------------------- /infrastructure/controllers/dev/weave-gitops/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | namespace: flux-system 4 | resources: 5 | - ../../base/weave-gitops 6 | patches: 7 | - path: release.yaml 8 | target: 9 | kind: HelmRelease 10 | # See https://fluxcd.io/flux/guides/helmreleases/#refer-to-values-in-secret-generated-with-kustomize-and-sops 11 | generatorOptions: 12 | disableNameSuffixHash: true 13 | secretGenerator: 14 | - name: weave-gitops 15 | files: 16 | - values.yaml=values-secret.enc.yaml 17 | configurations: 18 | - kustomizeconfig.yaml 19 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/ingress-nginx/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://artifacthub.io/packages/helm/ingress-nginx/ingress-nginx 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: ingress-nginx 6 | namespace: ingress-nginx 7 | spec: 8 | interval: 30m 9 | chart: 10 | spec: 11 | chart: ingress-nginx 12 | version: "*" 13 | sourceRef: 14 | kind: HelmRepository 15 | name: ingress-nginx 16 | interval: 12h 17 | values: 18 | controller: 19 | service: 20 | type: "NodePort" 21 | admissionWebhooks: 22 | enabled: false 23 | -------------------------------------------------------------------------------- /infrastructure/configs/base/flux-receivers/receiver.yaml: -------------------------------------------------------------------------------- 1 | # See https://fluxcd.io/flux/components/notification/receiver/#example 2 | apiVersion: notification.toolkit.fluxcd.io/v1 3 | kind: Receiver 4 | metadata: 5 | name: flux-system-receiver 6 | namespace: flux-system 7 | spec: 8 | type: github 9 | events: 10 | - "ping" 11 | - "push" 12 | secretRef: 13 | name: receiver-token 14 | resources: 15 | - apiVersion: source.toolkit.fluxcd.io/v1 16 | kind: GitRepository 17 | name: flux-system 18 | - apiVersion: source.toolkit.fluxcd.io/v1 19 | kind: GitRepository 20 | name: fastapi-example 21 | -------------------------------------------------------------------------------- /kind.config.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.x-k8s.io/v1alpha4 3 | nodes: 4 | - role: control-plane 5 | extraPortMappings: 6 | - containerPort: 6443 7 | hostPort: 6443 8 | listenAddress: "127.0.0.1" 9 | - role: worker 10 | extraPortMappings: 11 | - containerPort: 80 12 | hostPort: 8080 13 | protocol: TCP 14 | - containerPort: 443 15 | hostPort: 4430 16 | protocol: TCP 17 | - role: worker 18 | extraPortMappings: 19 | - containerPort: 80 20 | hostPort: 8081 21 | protocol: TCP 22 | - containerPort: 443 23 | hostPort: 4431 24 | protocol: TCP 25 | -------------------------------------------------------------------------------- /infrastructure/controllers/dev/weave-gitops/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://github.com/weaveworks/weave-gitops/tree/main/charts/gitops-server 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: weave-gitops 6 | namespace: flux-system 7 | spec: 8 | chart: 9 | spec: 10 | version: "4.x" 11 | # https://github.com/weaveworks/weave-gitops/blob/main/charts/gitops-server/values.yaml 12 | valuesFrom: 13 | - kind: Secret 14 | name: weave-gitops 15 | values: 16 | ingress: 17 | hosts: 18 | - host: flux.local 19 | paths: 20 | - path: / 21 | pathType: ImplementationSpecific 22 | -------------------------------------------------------------------------------- /scripts/utils.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | reset_color='\033[0m' 4 | 5 | function print_blue() { 6 | echo -e "⌛️ \033[34m${1}${reset_color}" 7 | } 8 | 9 | function print_cyan() { 10 | echo -e "🎉 \033[36m${1}${reset_color}" 11 | } 12 | 13 | function print_green() { 14 | echo -e "✅ \033[32m${1}${reset_color}" 15 | } 16 | 17 | function print_magenta() { 18 | echo -e "🤨 \033[35m${1}${reset_color}" 19 | } 20 | 21 | function print_red() { 22 | echo -e "🚨 \033[31m${1}${reset_color}" 23 | } 24 | 25 | function print_yellow() { 26 | echo -e "📣 \033[33m${1}${reset_color}" 27 | } 28 | 29 | function exit_gracefully() { 30 | print_red "** Exit" 31 | exit 32 | } 33 | -------------------------------------------------------------------------------- /clusters/dev/flux-system/gotk-sync.yaml: -------------------------------------------------------------------------------- 1 | # This manifest was generated by flux. DO NOT EDIT. 2 | --- 3 | apiVersion: source.toolkit.fluxcd.io/v1 4 | kind: GitRepository 5 | metadata: 6 | name: flux-system 7 | namespace: flux-system 8 | spec: 9 | interval: 1m0s 10 | ref: 11 | branch: main 12 | secretRef: 13 | name: flux-system 14 | url: ssh://git@github.com/darioblanco/gitops 15 | --- 16 | apiVersion: kustomize.toolkit.fluxcd.io/v1 17 | kind: Kustomization 18 | metadata: 19 | name: flux-system 20 | namespace: flux-system 21 | spec: 22 | interval: 10m0s 23 | path: ./clusters/dev 24 | prune: true 25 | sourceRef: 26 | kind: GitRepository 27 | name: flux-system 28 | -------------------------------------------------------------------------------- /.github/workflows/validate.yaml: -------------------------------------------------------------------------------- 1 | name: validate 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "**.yaml" 7 | - "**.yml" 8 | push: 9 | paths: 10 | - "**.yaml" 11 | - "**.yml" 12 | workflow_dispatch: 13 | 14 | jobs: 15 | manifests: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | - name: Setup yq 21 | uses: fluxcd/pkg/actions/yq@main 22 | - name: Setup kubeconform 23 | uses: fluxcd/pkg/actions/kubeconform@main 24 | - name: Setup kustomize 25 | uses: fluxcd/pkg/actions/kustomize@main 26 | - name: Validate manifests 27 | run: make validate 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | indent_size = 4 6 | tab_width = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | max_line_length = 80 12 | 13 | [{*.gyp,*.md,*.json,*.tf,*.tpl,*.yml,*.yaml}] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [{*.py,*.asm}] 18 | indent_style = space 19 | 20 | [*.asm] 21 | indent_size = 8 22 | 23 | [{*.md,*.tsv}] 24 | trim_trailing_whitespace = false 25 | 26 | [{*.c,*.cc,*.h,*.hh,*.cpp,*.hpp,*.m,*.mm,*.mpp,*.js,*.java,*.go,*.rs,*.php,*.ng,*.jsx,*.ts,*.d,*.cs,*.swift}] 27 | curly_bracket_next_line = false 28 | spaces_around_operators = true 29 | spaces_around_brackets = outside 30 | -------------------------------------------------------------------------------- /apps/base/podinfo/release.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 2 | kind: HelmRelease 3 | metadata: 4 | name: podinfo 5 | namespace: podinfo 6 | spec: 7 | releaseName: podinfo 8 | chart: 9 | spec: 10 | chart: podinfo 11 | sourceRef: 12 | kind: HelmRepository 13 | name: podinfo 14 | interval: 50m 15 | install: 16 | remediation: 17 | retries: 3 18 | # Default values 19 | # https://github.com/stefanprodan/podinfo/blob/master/charts/podinfo/values.yaml 20 | values: 21 | redis: 22 | enabled: true 23 | repository: public.ecr.aws/docker/library/redis 24 | tag: 7.0.6 25 | ingress: 26 | enabled: true 27 | className: nginx 28 | -------------------------------------------------------------------------------- /.envrc.example: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The token with admin permissions in the ${GITHUB_REPO} repository (so it can create deploy keys) 4 | # as defined in https://fluxcd.io/flux/installation/#github-and-github-enterprise 5 | export GITHUB_TOKEN=secret 6 | 7 | # The github organization of the GitOps repo 8 | export GITHUB_OWNER=myorg 9 | 10 | # The GitOps repo 11 | export GITHUB_REPO=myrepo 12 | 13 | # The list of age secret keys that allow to decrypt sops secrets locally 14 | export SOPS_AGE_KEY_FILE=./clusters/dev/sops.agekey 15 | 16 | # It is also possible to provide multiple files in a single one. SOPS will try all the keys until one works. 17 | # SOPS_AGE_KEY_FILE=$(cat ./clusters/dev/sops.agekey; echo; cat ./clusters/staging/sops.agekey; echo; cat ./clusters/prod/sops.agekey) 18 | # export SOPS_AGE_KEY_FILE 19 | -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Running pre-commit hook: make format" 4 | 5 | make format 6 | 7 | # Check the exit status of the make format command 8 | if [ $? -ne 0 ]; then 9 | echo "Unable to automatically format repo files." 10 | exit 1 11 | fi 12 | 13 | echo "Files automatically formatted." 14 | 15 | echo "Running pre-commit hook: make validate" 16 | 17 | # If there is at least one YAML file, run `make validate` 18 | if [ "$(git diff --cached --name-only --diff-filter=d | grep -c -E '\.yaml$')" -gt 0 ]; then 19 | echo "Running make validate due to staged YAML files..." 20 | 21 | make validate 22 | 23 | # Check the exit status of the make validate command 24 | if [ $? -ne 0 ]; then 25 | echo "Validation failed." 26 | exit 1 27 | fi 28 | 29 | echo "Validation passed." 30 | fi 31 | -------------------------------------------------------------------------------- /clusters/dev/flux-system/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - gotk-components.yaml 5 | - gotk-sync.yaml 6 | labels: 7 | - pairs: 8 | toolkit.fluxcd.io/tenant: sre-team 9 | patches: 10 | - patch: | 11 | - op: add 12 | path: /spec/template/spec/containers/0/args/- 13 | value: --concurrent=20 14 | - op: add 15 | path: /spec/template/spec/containers/0/args/- 16 | value: --requeue-dependency=5s 17 | target: 18 | kind: Deployment 19 | name: "(kustomize-controller|helm-controller|source-controller)" 20 | - patch: | 21 | - op: add 22 | path: /metadata/labels/toolkit.fluxcd.io~1tenant 23 | value: sre-team 24 | target: 25 | kind: Namespace 26 | name: flux-system 27 | -------------------------------------------------------------------------------- /clusters/prod/flux-system/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - gotk-components.yaml 5 | - gotk-sync.yaml 6 | labels: 7 | - pairs: 8 | toolkit.fluxcd.io/tenant: sre-team 9 | patches: 10 | - patch: | 11 | - op: add 12 | path: /spec/template/spec/containers/0/args/- 13 | value: --concurrent=20 14 | - op: add 15 | path: /spec/template/spec/containers/0/args/- 16 | value: --requeue-dependency=5s 17 | target: 18 | kind: Deployment 19 | name: "(kustomize-controller|helm-controller|source-controller)" 20 | - patch: | 21 | - op: add 22 | path: /metadata/labels/toolkit.fluxcd.io~1tenant 23 | value: sre-team 24 | target: 25 | kind: Namespace 26 | name: flux-system 27 | -------------------------------------------------------------------------------- /clusters/staging/flux-system/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - gotk-components.yaml 5 | - gotk-sync.yaml 6 | labels: 7 | - pairs: 8 | toolkit.fluxcd.io/tenant: sre-team 9 | patches: 10 | - patch: | 11 | - op: add 12 | path: /spec/template/spec/containers/0/args/- 13 | value: --concurrent=20 14 | - op: add 15 | path: /spec/template/spec/containers/0/args/- 16 | value: --requeue-dependency=5s 17 | target: 18 | kind: Deployment 19 | name: "(kustomize-controller|helm-controller|source-controller)" 20 | - patch: | 21 | - op: add 22 | path: /metadata/labels/toolkit.fluxcd.io~1tenant 23 | value: sre-team 24 | target: 25 | kind: Namespace 26 | name: flux-system 27 | -------------------------------------------------------------------------------- /clusters/dev/cloud.yaml: -------------------------------------------------------------------------------- 1 | # --- 2 | # apiVersion: kustomize.toolkit.fluxcd.io/v1 3 | # kind: Kustomization 4 | # metadata: 5 | # name: cloud-providers 6 | # namespace: flux-system 7 | # spec: 8 | # interval: 10m0s 9 | # sourceRef: 10 | # kind: GitRepository 11 | # name: flux-system 12 | # path: ./cloud/providers/dev 13 | # prune: true 14 | # wait: true 15 | # timeout: 5m0s 16 | # --- 17 | # apiVersion: kustomize.toolkit.fluxcd.io/v1 18 | # kind: Kustomization 19 | # metadata: 20 | # name: cloud-resources 21 | # namespace: flux-system 22 | # spec: 23 | # interval: 1h 24 | # dependsOn: 25 | # - name: cloud-providers 26 | # retryInterval: 1m 27 | # timeout: 5m 28 | # sourceRef: 29 | # kind: GitRepository 30 | # name: flux-system 31 | # path: ./cloud/resources/dev 32 | # prune: true 33 | # wait: true 34 | -------------------------------------------------------------------------------- /clusters/prod/cloud.yaml: -------------------------------------------------------------------------------- 1 | # --- 2 | # apiVersion: kustomize.toolkit.fluxcd.io/v1 3 | # kind: Kustomization 4 | # metadata: 5 | # name: cloud-providers 6 | # namespace: flux-system 7 | # spec: 8 | # interval: 10m0s 9 | # sourceRef: 10 | # kind: GitRepository 11 | # name: flux-system 12 | # path: ./cloud/providers/prod 13 | # prune: true 14 | # wait: true 15 | # timeout: 5m0s 16 | # --- 17 | # apiVersion: kustomize.toolkit.fluxcd.io/v1 18 | # kind: Kustomization 19 | # metadata: 20 | # name: cloud-resources 21 | # namespace: flux-system 22 | # spec: 23 | # interval: 1h 24 | # dependsOn: 25 | # - name: cloud-providers 26 | # retryInterval: 1m 27 | # timeout: 5m 28 | # sourceRef: 29 | # kind: GitRepository 30 | # name: flux-system 31 | # path: ./cloud/resources/prod 32 | # prune: true 33 | # wait: true 34 | -------------------------------------------------------------------------------- /clusters/staging/cloud.yaml: -------------------------------------------------------------------------------- 1 | # --- 2 | # apiVersion: kustomize.toolkit.fluxcd.io/v1 3 | # kind: Kustomization 4 | # metadata: 5 | # name: cloud-providers 6 | # namespace: flux-system 7 | # spec: 8 | # interval: 10m0s 9 | # sourceRef: 10 | # kind: GitRepository 11 | # name: flux-system 12 | # path: ./cloud/providers/staging 13 | # prune: true 14 | # wait: true 15 | # timeout: 5m0s 16 | # --- 17 | # apiVersion: kustomize.toolkit.fluxcd.io/v1 18 | # kind: Kustomization 19 | # metadata: 20 | # name: cloud-resources 21 | # namespace: flux-system 22 | # spec: 23 | # interval: 1h 24 | # dependsOn: 25 | # - name: cloud-providers 26 | # retryInterval: 1m 27 | # timeout: 5m 28 | # sourceRef: 29 | # kind: GitRepository 30 | # name: flux-system 31 | # path: ./cloud/resources/staging 32 | # prune: true 33 | # wait: true 34 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "assigneesFromCodeOwners": true, 5 | "assigneesSampleSize": 1, 6 | "branchPrefix": "chore/renovate/", 7 | "commitBodyTable": true, 8 | "commitMessageAction": "Pin", 9 | "commitMessageTopic": "{{depName}}", 10 | "packageRules": [ 11 | { 12 | "matchDepTypes": ["devDependencies"], 13 | "matchUpdateTypes": ["patch", "minor"], 14 | "commitMessageSuffix": "dev", 15 | "automerge": true, 16 | "groupName": "devDependencies (non-major)" 17 | } 18 | ], 19 | "prConcurrentLimit": 5, 20 | "prHourlyLimit": 3, 21 | "rangeStrategy": "bump", 22 | "semanticCommits": "enabled", 23 | "semanticCommitType": "chore", 24 | "separateMinorPatch": true, 25 | "vulnerabilityAlerts": { 26 | "labels": ["security"] 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /infrastructure/configs/base/monitoring/podmonitor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: PodMonitor 3 | metadata: 4 | name: flux-system 5 | namespace: flux-system 6 | labels: 7 | app.kubernetes.io/part-of: flux 8 | app.kubernetes.io/component: monitoring 9 | spec: 10 | namespaceSelector: 11 | matchNames: 12 | - flux-system 13 | selector: 14 | matchExpressions: 15 | - key: app 16 | operator: In 17 | values: 18 | - helm-controller 19 | - source-controller 20 | - kustomize-controller 21 | - notification-controller 22 | - image-automation-controller 23 | - image-reflector-controller 24 | podMetricsEndpoints: 25 | - port: http-prom 26 | relabelings: 27 | # https://github.com/prometheus-operator/prometheus-operator/issues/4816 28 | - sourceLabels: [__meta_kubernetes_pod_phase] 29 | action: keep 30 | regex: Running 31 | -------------------------------------------------------------------------------- /clusters/dev/infrastructure.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.toolkit.fluxcd.io/v1 3 | kind: Kustomization 4 | metadata: 5 | name: infra-controllers 6 | namespace: flux-system 7 | spec: 8 | interval: 1h 9 | retryInterval: 1m 10 | timeout: 5m 11 | sourceRef: 12 | kind: GitRepository 13 | name: flux-system 14 | path: ./infrastructure/controllers/dev 15 | prune: true 16 | wait: true 17 | decryption: 18 | provider: sops 19 | secretRef: 20 | name: sops-age 21 | --- 22 | apiVersion: kustomize.toolkit.fluxcd.io/v1 23 | kind: Kustomization 24 | metadata: 25 | name: infra-configs 26 | namespace: flux-system 27 | spec: 28 | dependsOn: 29 | - name: infra-controllers 30 | interval: 1h 31 | retryInterval: 1m 32 | timeout: 5m 33 | sourceRef: 34 | kind: GitRepository 35 | name: flux-system 36 | path: ./infrastructure/configs/dev 37 | prune: true 38 | decryption: 39 | provider: sops 40 | secretRef: 41 | name: sops-age 42 | -------------------------------------------------------------------------------- /clusters/prod/infrastructure.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.toolkit.fluxcd.io/v1 3 | kind: Kustomization 4 | metadata: 5 | name: infra-controllers 6 | namespace: flux-system 7 | spec: 8 | interval: 1h 9 | retryInterval: 1m 10 | timeout: 5m 11 | sourceRef: 12 | kind: GitRepository 13 | name: flux-system 14 | path: ./infrastructure/controllers/prod 15 | prune: true 16 | wait: true 17 | decryption: 18 | provider: sops 19 | secretRef: 20 | name: sops-age 21 | --- 22 | apiVersion: kustomize.toolkit.fluxcd.io/v1 23 | kind: Kustomization 24 | metadata: 25 | name: infra-configs 26 | namespace: flux-system 27 | spec: 28 | dependsOn: 29 | - name: infra-controllers 30 | interval: 1h 31 | retryInterval: 1m 32 | timeout: 5m 33 | sourceRef: 34 | kind: GitRepository 35 | name: flux-system 36 | path: ./infrastructure/configs/prod 37 | prune: true 38 | decryption: 39 | provider: sops 40 | secretRef: 41 | name: sops-age 42 | -------------------------------------------------------------------------------- /clusters/staging/infrastructure.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: kustomize.toolkit.fluxcd.io/v1 3 | kind: Kustomization 4 | metadata: 5 | name: infra-controllers 6 | namespace: flux-system 7 | spec: 8 | interval: 1h 9 | retryInterval: 1m 10 | timeout: 5m 11 | sourceRef: 12 | kind: GitRepository 13 | name: flux-system 14 | path: ./infrastructure/controllers/staging 15 | prune: true 16 | wait: true 17 | decryption: 18 | provider: sops 19 | secretRef: 20 | name: sops-age 21 | --- 22 | apiVersion: kustomize.toolkit.fluxcd.io/v1 23 | kind: Kustomization 24 | metadata: 25 | name: infra-configs 26 | namespace: flux-system 27 | spec: 28 | dependsOn: 29 | - name: infra-controllers 30 | interval: 1h 31 | retryInterval: 1m 32 | timeout: 5m 33 | sourceRef: 34 | kind: GitRepository 35 | name: flux-system 36 | path: ./infrastructure/configs/staging 37 | prune: true 38 | decryption: 39 | provider: sops 40 | secretRef: 41 | name: sops-age 42 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/sso/dex/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://artifacthub.io/packages/helm/dex/dex 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: dex 6 | namespace: sso 7 | spec: 8 | interval: 5m 9 | chart: 10 | spec: 11 | version: "0.15.x" 12 | chart: dex 13 | sourceRef: 14 | kind: HelmRepository 15 | name: dex 16 | interval: 60m 17 | # https://github.com/dexidp/helm-charts/blob/master/charts/dex/values.yaml 18 | values: 19 | config: 20 | # Set it to a valid URL 21 | issuer: http://my-issuer-url.com 22 | 23 | # See https://dexidp.io/docs/storage/ for more options 24 | storage: 25 | type: memory 26 | 27 | # Enable at least one connector 28 | # See https://dexidp.io/docs/connectors/ for more options 29 | enablePasswordDB: true 30 | ingress: 31 | enabled: true 32 | 33 | hosts: 34 | - host: my-issuer-url.com 35 | paths: 36 | - path: / 37 | -------------------------------------------------------------------------------- /scripts/encrypt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the directory of the current script 4 | script_dir="$(dirname "${BASH_SOURCE[0]}")" 5 | 6 | # shellcheck disable=SC1091 7 | source "${script_dir}"/utils.sh 8 | 9 | # Exit if no filepath is provided 10 | if [ $# -eq 0 ]; then 11 | print_red "Error: No file provided" 12 | echo "Usage: $0 " 13 | exit 1 14 | fi 15 | 16 | # Get the input file path 17 | input_filepath="$1" 18 | 19 | # Get the filename without the extension 20 | filename=$(basename -- "${input_filepath}") 21 | name="${filename%.*}" 22 | 23 | # Get the directory path 24 | dirpath=$(dirname -- "${input_filepath}") 25 | 26 | # Construct the output file path 27 | output_filepath="${dirpath}/${name}.enc.yaml" 28 | 29 | # Encrypt the file into a new file with the `.enc.yaml` extension 30 | sops -e "${input_filepath}" > "${output_filepath}" 31 | 32 | # Format the sops generated file to follow our file conventions 33 | prettier --write "${output_filepath}" 34 | 35 | print_green "Encrypted file saved to ${output_filepath}" 36 | -------------------------------------------------------------------------------- /cloud/providers/base/gcp.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: gcp.upbound.io/v1beta1 3 | kind: ProviderConfig # customizes the settings of the GCP Provider 4 | metadata: 5 | name: default 6 | namespace: crossplane-system 7 | spec: 8 | projectID: # find your GCP project ID from the project_id field of the gcp-credentials.json file 9 | credentials: 10 | source: Secret 11 | secretRef: 12 | namespace: crossplane-system 13 | name: gcp-secret 14 | key: creds 15 | --- 16 | apiVersion: v1 17 | kind: Secret 18 | metadata: 19 | name: gcp-secret 20 | namespace: crossplane-system 21 | type: Opaque 22 | data: 23 | credentials: # gcp-credentials.json file content goes here in base64 format 24 | --- 25 | apiVersion: pkg.crossplane.io/v1 26 | kind: Provider # uses the Crossplane Provider CRD to connect your Kubernetes cluster to your cloud provider. 27 | metadata: 28 | name: upbound-provider-gcp 29 | namespace: crossplane-system 30 | spec: 31 | package: xpkg.upbound.io/upbound/provider-gcp:v0.28.0 32 | -------------------------------------------------------------------------------- /scripts/set-hosts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get the directory of the current script 4 | script_dir="$(dirname "${BASH_SOURCE[0]}")" 5 | 6 | # shellcheck disable=SC1091 7 | source "${script_dir}"/utils.sh 8 | 9 | # Array of hostnames that are exposed with an ingress 10 | hostnames=( 11 | "auth.local" 12 | "flux.local" 13 | "fastapi-example.local" 14 | "grafana.local" 15 | "podinfo.local" 16 | "prometheus.local" 17 | ) 18 | 19 | # Local IP address where the cluster will be listening 20 | ip="127.0.0.1" 21 | 22 | # Variable to hold the string of hostnames that will appear in /etc/hosts linked ot the IP 23 | hostnames_str="${hostnames[*]}" 24 | 25 | print_yellow "Reading/writing to the /etc/hosts requires sudo privileges!" 26 | # Check if the entry already exists 27 | if grep -q "$ip $hostnames_str" /etc/hosts; then 28 | print_blue "The entry $ip $hostnames_str already exists in /etc/hosts" 29 | else 30 | # Append the entry to /etc/hosts 31 | echo "$ip $hostnames_str" | sudo tee -a /etc/hosts 32 | print_green "Done. $ip $hostnames_str added to /etc/hosts" 33 | fi 34 | -------------------------------------------------------------------------------- /.githooks/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | message=$(cat "$1") 4 | 5 | # Regex to validate commit for the pattern type(scope): subject 6 | # e.g. feat(user-login): add login functionality 7 | pattern="^([a-z]+)(\([[a-z_-]*\))?: .+" 8 | 9 | if [[ ! $message =~ $pattern ]]; then 10 | echo "Your commit message does not match the conventional commit format." 11 | echo "Commit message: $message" 12 | echo "Please ensure your commit message matches one of the following patterns:" 13 | echo " type(scope): subject" 14 | echo " e.g. feat(user-login): add login functionality" 15 | echo " type: subject" 16 | echo " e.g. fix: change logout functionality" 17 | exit 1 18 | fi 19 | 20 | commit_type="${BASH_REMATCH[1]}" 21 | valid_types=("build" "chore" "ci" "docs" "feat" "fix" "perf" "refactor" "revert" "style" "test") 22 | valid=false 23 | 24 | for type in "${valid_types[@]}"; do 25 | if [[ $commit_type == "$type" ]]; then 26 | valid=true 27 | break 28 | fi 29 | done 30 | 31 | if [ "$valid" = true ]; then 32 | exit 0 33 | else 34 | echo "Invalid commit type. Allowed types are: ${valid_types[*]}" 35 | exit 1 36 | fi 37 | -------------------------------------------------------------------------------- /apps/base/fastapi-example/imageupdateautomation.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: image.toolkit.fluxcd.io/v1beta1 2 | kind: ImageUpdateAutomation 3 | metadata: 4 | name: fastapi-example 5 | namespace: flux-system 6 | spec: 7 | interval: 30m 8 | sourceRef: 9 | kind: GitRepository 10 | name: fastapi-example 11 | git: 12 | checkout: 13 | ref: 14 | branch: main 15 | commit: 16 | author: 17 | email: fluxcdbot@users.noreply.github.com 18 | name: fluxcdbot 19 | messageTemplate: | 20 | chore: automated image update 21 | 22 | Automation name: {{ .AutomationObject }} 23 | 24 | Files: 25 | {{ range $filename, $_ := .Updated.Files -}} 26 | - {{ $filename }} 27 | {{ end -}} 28 | 29 | Objects: 30 | {{ range $resource, $_ := .Updated.Objects -}} 31 | - {{ $resource.Kind }} {{ $resource.Name }} 32 | {{ end -}} 33 | 34 | Images: 35 | {{ range .Updated.Images -}} 36 | - {{.}} 37 | {{ end -}} 38 | push: 39 | branch: main 40 | update: 41 | path: ./deploy/base 42 | strategy: Setters 43 | -------------------------------------------------------------------------------- /infrastructure/controllers/dev/ingress-nginx/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://artifacthub.io/packages/helm/ingress-nginx/ingress-nginx 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: ingress-nginx 6 | spec: 7 | chart: 8 | spec: 9 | version: "4.7.x" 10 | # Compatibility with kind clusters 11 | # See https://github.com/kubernetes/ingress-nginx/blob/main/hack/manifest-templates/provider/kind/values.yaml 12 | values: 13 | controller: 14 | updateStrategy: 15 | type: RollingUpdate 16 | rollingUpdate: 17 | maxUnavailable: 1 18 | hostPort: 19 | enabled: true 20 | terminationGracePeriodSeconds: 0 21 | service: 22 | type: NodePort 23 | watchIngressWithoutClass: true 24 | # nodeSelector: 25 | # ingress-ready: "true" 26 | # tolerations: 27 | # - key: "node-role.kubernetes.io/master" 28 | # operator: "Equal" 29 | # effect: "NoSchedule" 30 | # - key: "node-role.kubernetes.io/control-plane" 31 | # operator: "Equal" 32 | # effect: "NoSchedule" 33 | publishService: 34 | enabled: false 35 | extraArgs: 36 | publish-status-address: localhost 37 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/monitoring/loki-stack/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://artifacthub.io/packages/helm/grafana/loki-stack 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: loki-stack 6 | namespace: monitoring 7 | spec: 8 | interval: 5m 9 | dependsOn: 10 | - name: kube-prometheus-stack 11 | chart: 12 | spec: 13 | version: "2.x" 14 | chart: loki-stack 15 | sourceRef: 16 | kind: HelmRepository 17 | name: grafana-charts 18 | interval: 60m 19 | # https://github.com/grafana/helm-charts/blob/main/charts/loki-stack/values.yaml 20 | # https://github.com/grafana/loki/blob/main/production/helm/loki/values.yaml 21 | values: 22 | grafana: 23 | enabled: false 24 | sidecar: 25 | datasources: 26 | enabled: true 27 | maxLines: 1000 28 | promtail: 29 | enabled: true 30 | loki: 31 | enabled: true 32 | isDefault: false 33 | serviceMonitor: 34 | enabled: true 35 | additionalLabels: 36 | app.kubernetes.io/part-of: kube-prometheus-stack 37 | config: 38 | chunk_store_config: 39 | max_look_back_period: 0s 40 | table_manager: 41 | retention_deletes_enabled: true 42 | retention_period: 12h 43 | -------------------------------------------------------------------------------- /infrastructure/controllers/dev/weave-gitops/values-secret.enc.yaml: -------------------------------------------------------------------------------- 1 | adminUser: 2 | passwordHash: ENC[AES256_GCM,data:9perX/E92La7I+CO9jirh0n9aB0kl5I7H9Wmtk6aGrWq9TQ4Lya6rJ5FtVfEWZWyna0ch8tv0/hSTW6t,iv:gC4h33cOrcmA3O3rDLySWXBkucqxivH5thwPuk77S6s=,tag:Smop2c2EYf8rIYSJ7MgmWw==,type:str] 3 | sops: 4 | kms: [] 5 | gcp_kms: [] 6 | azure_kv: [] 7 | hc_vault: [] 8 | age: 9 | - recipient: age1qvesyd4zyqs5p40n8gr2ngjvsg6surf9e37h3xv7rm7m5lsgz5jsetg3ql 10 | enc: | 11 | -----BEGIN AGE ENCRYPTED FILE----- 12 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlR0RjdHJYMkhVbnpnZ0Fv 13 | Mms2dGVoVnpmaU5Rd000MjFtOWttODRjY1FvCnVicXQ3cnRGSVhtbjZEOUtRemRm 14 | Qm4wRHNCZjc2MFppK1VZbi9iWTk2MkkKLS0tIDM2QnY0cmhOSjJNV3ZFQlNGaXdt 15 | MmxyRlh6V1RnaU1sUkxqYktxQWh3QUUK3xOFl5ZI2xOJJJpgxEhKl3SpNaqtLT3H 16 | 9GqOAy4CErF6HkY5LrzetEPAeYykro6XejTYqlQxw88XslGXd4HpDg== 17 | -----END AGE ENCRYPTED FILE----- 18 | lastmodified: "2023-07-18T16:56:11Z" 19 | mac: ENC[AES256_GCM,data:QEVeCGPpEq1pA41wxVh9uKT88DH7Pqt0Y/bLM5wr0eEcNF/tCin5v+HMOdBmvnBChXczTEnohFXV2Kl4SOAcvTvAqRulWqHtYE680TFOqsdsKw66cfwnEyb2U7wPvD50vNL2sB+OTBJZvoyFQS3RBELCmSMbMiJ9iWwGa9w6oSs=,iv:TnjkiOtJNOPTm892zX9zr5zrr7viBRqIAOwqZF8ZlRM=,tag:rmjsjSUK8rnP2ZFGLdxuZg==,type:str] 20 | pgp: [] 21 | unencrypted_suffix: _unencrypted 22 | version: 3.7.3 23 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/weave-gitops/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://github.com/weaveworks/weave-gitops/tree/main/charts/gitops-server 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: weave-gitops 6 | spec: 7 | interval: 60m 8 | chart: 9 | spec: 10 | chart: weave-gitops 11 | version: "*" 12 | sourceRef: 13 | kind: HelmRepository 14 | name: weave-gitops 15 | interval: 12h 16 | # https://github.com/weaveworks/weave-gitops/blob/main/charts/gitops-server/values.yaml 17 | values: 18 | resources: 19 | requests: 20 | cpu: 100m 21 | memory: 64Mi 22 | limits: 23 | cpu: 1 24 | memory: 512Mi 25 | securityContext: 26 | capabilities: 27 | drop: 28 | - ALL 29 | readOnlyRootFilesystem: true 30 | runAsNonRoot: true 31 | runAsUser: 1000 32 | adminUser: 33 | create: true 34 | username: admin 35 | # # Change password by generating a new hash with: 36 | # # https://docs.gitops.weave.works/docs/configuration/securing-access-to-the-dashboard/#login-via-a-cluster-user-account 37 | # # bcrypt hash for password "flux" 38 | # passwordHash: "$2a$10$P/tHQ1DNFXdvX0zRGA8LPeSOyb0JXq9rP3fZ4W8HGTpLV7qHDlWhe" 39 | ingress: 40 | enabled: true 41 | className: nginx 42 | -------------------------------------------------------------------------------- /.sops.yaml: -------------------------------------------------------------------------------- 1 | # This config applies recursively to all sub-directories. 2 | 3 | # Multiple directories can use separate SOPS configs. 4 | 5 | # Contributors using the sops CLI to create and encrypt files won’t have to worry 6 | # about specifying the proper key for the target cluster or namespace. 7 | # Therefore, they can just encrypt files with `sops -e file.yaml` 8 | 9 | # Private key should be in ./cluster/{name}/sops.agekey 10 | 11 | # creation rules are evaluated sequentially, the first match wins 12 | creation_rules: 13 | # Dev secrets 14 | - path_regex: .*dev/.*values-secret.yaml 15 | age: age1qvesyd4zyqs5p40n8gr2ngjvsg6surf9e37h3xv7rm7m5lsgz5jsetg3ql 16 | - path_regex: .*dev/.*.yaml 17 | encrypted_regex: ^(data|stringData)$ 18 | age: age1qvesyd4zyqs5p40n8gr2ngjvsg6surf9e37h3xv7rm7m5lsgz5jsetg3ql 19 | # Staging secrets 20 | - path_regex: .*staging/.*values-secret.yaml 21 | age: age1fapwknfa6lm0rmpxe4dkuyjpcz9wwju73ghw53f74yqjhrevtyxs43h2yg 22 | - path_regex: .*staging/.*.yaml 23 | encrypted_regex: ^(data|stringData)$ 24 | age: age1fapwknfa6lm0rmpxe4dkuyjpcz9wwju73ghw53f74yqjhrevtyxs43h2yg 25 | # Production secrets 26 | - path_regex: .*prod/.*values-secret.yaml 27 | age: age1us3r24et6a5kn8e4plqtvghchpuyj56n7errv72stqhgxq2l8dhsrem3u7 28 | - path_regex: .*prod/.*.yaml 29 | encrypted_regex: ^(data|stringData)$ 30 | age: age1us3r24et6a5kn8e4plqtvghchpuyj56n7errv72stqhgxq2l8dhsrem3u7 31 | -------------------------------------------------------------------------------- /infrastructure/configs/dev/flux-receivers/receiver-secret.enc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | type: Opaque 4 | metadata: 5 | name: receiver-token 6 | namespace: flux-system 7 | data: 8 | token: ENC[AES256_GCM,data:ojyei5+L7yhI13vPBzJb78Tyoo/FZHjte06HL9qPsX5ifhPw1inhmnWvDiFnMAKEKPbm3aJoiMw=,iv:gtO86hci8Br/eY2wlz+wCrJFPbTJX8N7W84QD/eiP20=,tag:sgMMa3IGKEu9+NO0W9raAQ==,type:str] 9 | sops: 10 | kms: [] 11 | gcp_kms: [] 12 | azure_kv: [] 13 | hc_vault: [] 14 | age: 15 | - recipient: age1qvesyd4zyqs5p40n8gr2ngjvsg6surf9e37h3xv7rm7m5lsgz5jsetg3ql 16 | enc: | 17 | -----BEGIN AGE ENCRYPTED FILE----- 18 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjQis0ODJYZGMwMzhFcEJt 19 | Z3A4Zis0OUJBMlVpWGpRMmsxVCtGTjI3LzJNCldVTjNucU1WckUrQ2xncGxyZmtW 20 | ZkZnbFNCZU1lTVdOTUEyQ0puOFE0VmsKLS0tIGZpSWxNVndiY3ZyS08vK3dhNWVC 21 | OTVrL3RJb1J1R2YrMVNQajJYaVo0cVUKQrF1EnmiKz7j2eJ/P+eshUFA0fdBjfab 22 | Ews3y84Q1JBfSmDcORwijLBkqMmVbXYbwAq3RYVMGYBtNg1i1eYZcw== 23 | -----END AGE ENCRYPTED FILE----- 24 | lastmodified: "2023-07-20T07:35:09Z" 25 | mac: ENC[AES256_GCM,data:ab3KnNnwrtsHGG9RtPBvXYEnBRqqttNF+ft5JknAQ3PU5KYs8Rd4dVKKK3q9kJf4jUcrU/3wy5NdrZK6ctn0K2BPsQfV/mdwylLagWmM9/6xifrM3q4lkKMdaLWF0arNABQXLoAhqwmc91ydH7yhdlr5OOXvdksHaTA9riP4Btg=,iv:lwYZKIouDkrdVWe43uUClaswiDI3NpH/kL+Dvw+ih5k=,tag:R+bKROFRjEoDVJkBqUlR0g==,type:str] 26 | pgp: [] 27 | encrypted_regex: ^(data|stringData)$ 28 | version: 3.7.3 29 | -------------------------------------------------------------------------------- /apps/dev/fastapi-example/git-secret.enc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: fastapi-example-repo 5 | namespace: flux-system 6 | data: 7 | password: ENC[AES256_GCM,data:RpTQCaIIuip5/ZmiGRv5rOgF5IJPF0Pn4FHIN4Dlf16w5M2kOyZ1S3tAUYiewDAwi3osh0ZzbkOaR+DM1vrk4HQTTjjuipPd9FDOwDGUh0JG9/aheRgu7Xh68tjJ+jtDDLqJEzprQQSa2KAQJ3mnCIujbjyAEBm3GNLZcg==,iv:Dv6zvEEZA1OBUNclPQqZnVv2Xc/iKZXxlsBaXsc49rQ=,tag:3nE8z5zl0tP/yFxcYt4M7g==,type:str] 8 | username: ENC[AES256_GCM,data:k9kpjj2uZd+iNK+zhDAheA==,iv:azvHCBEYmA8ht/pobQEozKeg43UBF+9rkqN+XRI3AMI=,tag:jU0NItL8nymUPuyf6+O8/A==,type:str] 9 | type: Opaque 10 | sops: 11 | kms: [] 12 | gcp_kms: [] 13 | azure_kv: [] 14 | hc_vault: [] 15 | age: 16 | - recipient: age1qvesyd4zyqs5p40n8gr2ngjvsg6surf9e37h3xv7rm7m5lsgz5jsetg3ql 17 | enc: | 18 | -----BEGIN AGE ENCRYPTED FILE----- 19 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEQkFhcFV5eEEveGFxbGs4 20 | SnV6bHdTTUdJandvS1J6NGhwUkdKRDlzVUc4CllUS0o0Tmh2QkNvbnZGYTRrZ3hB 21 | bDFtei9YNnJSN1FXVkZHYWpJeWs1TWcKLS0tIDRCRUNRK2w1SWdXTENTREZLcS96 22 | ZUxjSjVLRlAzcVJmV3Q5amMrY0R6a2MKy0xliHASx6Qc9DGgH/c1c4iBYakFx/al 23 | jRY4TAWwLHscW6sv4ZYyWbL+5zvJJ3t0lrbU8ZqTo1rGWNG/iduGTg== 24 | -----END AGE ENCRYPTED FILE----- 25 | lastmodified: "2023-07-20T20:14:54Z" 26 | mac: ENC[AES256_GCM,data:J1dny5xMv1U8qkWMLywVFoh+JG5nRL1emjnNQbquOL2xbLHmHDRy1SK3hpp9MBFBn5VCwS8Kh8EggG85mGaRfDze86YDu+GmihM8vM7ypoio6L6pkYTiQfMR9ZzHvlL5v94IC0YSsP3L/LjgI7cvg/iHaZcPtsnEs9Gf5dfW0JI=,iv:j3G9IohPMAqrNx+iBhyO9vYSwmtjKE/d6NKTal3yUxg=,tag:K6F5Hs7g5bJppS8OgHbEqg==,type:str] 27 | pgp: [] 28 | encrypted_regex: ^(data|stringData)$ 29 | version: 3.7.3 30 | -------------------------------------------------------------------------------- /infrastructure/configs/dev/docker-secret.enc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | .dockerconfigjson: ENC[AES256_GCM,data:WQXv22ym23gCUfDodvOConcVI7N5dRIm9omq2kR7UNI+eUEQaBhgbri2uRrlm9nuZQlvByPi6AVPKzUPaOQE/9BgTlwBzq150x2s/wDIRdL/8MzojzJeU6P1oju3OSbNGOTzTEJF6OjzZLb0dxA9D+MPQnrAjD+PlBpRsHwTBs+iWxGJn4bosjV1DFHylwE7tqerf8DPrlXtOBmEhbBBws5PePbg9Nne/j6MK+MfDxexHCE5XjBPZGNb6Cf/paKC5WfQiWnt+XRMCJnkFScycoyZQFQbAaV5Qd/7cOroTQEefVStymge8b0p8o6NytKPgXAKIR4UB00=,iv:DTJvvy6VombNLu1WldJ4vTUcuEY340kSMgr4cqeiuhM=,tag:a7t6PXOdIZXxYoAnG1MCgA==,type:str] 4 | kind: Secret 5 | metadata: 6 | name: github-docker 7 | namespace: flux-system 8 | type: kubernetes.io/dockerconfigjson 9 | sops: 10 | kms: [] 11 | gcp_kms: [] 12 | azure_kv: [] 13 | hc_vault: [] 14 | age: 15 | - recipient: age1qvesyd4zyqs5p40n8gr2ngjvsg6surf9e37h3xv7rm7m5lsgz5jsetg3ql 16 | enc: | 17 | -----BEGIN AGE ENCRYPTED FILE----- 18 | YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJN3NBZVFrUFpqWVJlS2ZM 19 | SzJ6RVp1WGVrKzlTNFY3dDRqZjlXdUFHS1JrCkplN1Z3YmRqSm9ORlFFeENaMWh3 20 | bTVFYXNEeG9PeHZ5VXd1V3VyY1c1RHcKLS0tIHNISW1iRDFlOUN3Sk9kYlZPcjF5 21 | SS9nNU8xVTdiRHRWWkVUR0xabzVxYTQKNO8dttnyQbsA8nYMXGAeYjtWILqhaRTz 22 | 8DU//gwDgtOmTOuQJtC3jA2fYAE/oj5mhMGuUSBMPfGbnkumqB+YMQ== 23 | -----END AGE ENCRYPTED FILE----- 24 | lastmodified: "2023-07-20T08:05:44Z" 25 | mac: ENC[AES256_GCM,data:FneM0yVTbuanNdbd8BuPw148jEsdKW95gtiUNfctchAy0OE4lpJiS3FTUVslXlkZmbDbEySIPijJNumgLo4G+7yAm9lGZCvUSvNBPST53J10oitf7yEzPezynyZGL0VYrSuAmexEcevq10zyqFgKdXT+4bCOsSi3rWHDozWCXeM=,iv:TPqyRgOSwkyuun2YaYGi4NDdppYeUH1ZxQcaTGZBtcw=,tag:ZTVjURuPux/fPSWjzxIibw==,type:str] 26 | pgp: [] 27 | encrypted_regex: ^(data|stringData)$ 28 | version: 3.7.3 29 | -------------------------------------------------------------------------------- /infrastructure/controllers/base/monitoring/kube-prometheus-stack/release.yaml: -------------------------------------------------------------------------------- 1 | # See https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack 2 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 3 | kind: HelmRelease 4 | metadata: 5 | name: kube-prometheus-stack 6 | namespace: monitoring 7 | spec: 8 | interval: 5m 9 | chart: 10 | spec: 11 | version: "48.x" 12 | chart: kube-prometheus-stack 13 | sourceRef: 14 | kind: HelmRepository 15 | name: prometheus-community 16 | interval: 60m 17 | install: 18 | crds: Create 19 | upgrade: 20 | crds: CreateReplace 21 | # https://github.com/prometheus-community/helm-charts/blob/main/charts/kube-prometheus-stack/values.yaml 22 | values: 23 | alertmanager: 24 | enabled: false 25 | grafana: 26 | ingress: 27 | enabled: true 28 | hosts: 29 | - grafana.local 30 | prometheus: 31 | ingress: 32 | enabled: true 33 | hosts: 34 | - prometheus.local 35 | prometheusSpec: 36 | retention: 24h 37 | resources: 38 | requests: 39 | cpu: 200m 40 | memory: 200Mi 41 | podMonitorNamespaceSelector: {} 42 | podMonitorSelector: 43 | matchLabels: 44 | app.kubernetes.io/component: monitoring 45 | postRenderers: 46 | - kustomize: 47 | patches: 48 | - target: 49 | # Ignore these objects from Flux diff as they are mutated from chart hooks 50 | kind: (ValidatingWebhookConfiguration|MutatingWebhookConfiguration) 51 | name: kube-prometheus-stack-admission 52 | patch: | 53 | - op: add 54 | path: /metadata/annotations/helm.toolkit.fluxcd.io~1driftDetection 55 | value: disabled 56 | - target: 57 | # Ignore these objects from Flux diff as they are mutated at apply time but not at dry-run time 58 | kind: PrometheusRule 59 | patch: | 60 | - op: add 61 | path: /metadata/annotations/helm.toolkit.fluxcd.io~1driftDetection 62 | value: disabled 63 | -------------------------------------------------------------------------------- /scripts/create-cluster.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit if one of the scripts fail 4 | set -e 5 | 6 | # Get the directory of the current script 7 | script_dir="$(dirname "${BASH_SOURCE[0]}")" 8 | 9 | # shellcheck disable=SC1091 10 | source "${script_dir}"/utils.sh 11 | 12 | if [ -z "$1" ] 13 | then 14 | print_red "Error: No cluster type provided." 15 | echo "Usage: $0 " 16 | exit 1 17 | fi 18 | 19 | if [ -z "$2" ] 20 | then 21 | print_red "Error: No cluster name provided." 22 | echo "Usage: $0 " 23 | exit 1 24 | fi 25 | 26 | cluster_type="$1" 27 | cluster_name="$2" 28 | 29 | context_name="" 30 | case "$cluster_type" in 31 | kind) 32 | print_blue "Creating kind cluster '${cluster_name}'..." 33 | if kind get clusters | grep -q "${cluster_name}"; then 34 | print_yellow "Cluster '${cluster_name}' already exists. Context will be switched to the current cluster." 35 | else 36 | print_magenta "Cluster '${cluster_name}' does not exist. It will be created." 37 | # Create the cluster using the configuration file 38 | kind create cluster --name "${cluster_name}" --config "${script_dir}"/../kind.config.yaml 39 | print_green "Created kind cluster '${cluster_name}'" 40 | fi 41 | context_name="kind-${cluster_name}" 42 | ;; 43 | 44 | k3d) 45 | print_blue "Creating k3d cluster '${cluster_name}'..." 46 | if k3d cluster list | grep -q "${cluster_name}"; then 47 | print_yellow "Cluster '${cluster_name}' already exists. Context will be switched to the current cluster." 48 | else 49 | print_magenta "Cluster '${cluster_name}' does not exist. It will be created." 50 | # Create the cluster using the configuration file 51 | k3d cluster create --config "${script_dir}"/../k3d.config.yaml "${cluster_name}" 52 | print_green "Created k3d cluster '${cluster_name}'" 53 | fi 54 | context_name="k3d-${cluster_name}" 55 | ;; 56 | 57 | *) 58 | # Unsupported cluster types 59 | print_red "Error: Unsupported cluster type. Only 'kind' and 'k3d' are supported." 60 | exit 1 61 | ;; 62 | esac 63 | 64 | # Get cluster info so that the context is defined 65 | echo "" 66 | kubectl cluster-info --context "${context_name}" 67 | -------------------------------------------------------------------------------- /apps/README.md: -------------------------------------------------------------------------------- 1 | # Apps 2 | 3 | ## Folder structure 4 | 5 | The apps configuration is structured into: 6 | 7 | - **apps/base/** contains namespaces and Helm release definitions 8 | - **apps/dev/** contains the production Helm release values 9 | - **apps/prod/** contains the production Helm release values 10 | - **apps/staging/** contains the staging values 11 | 12 | ```text 13 | └── apps 14 | ├── base 15 | ├── dev 16 | ├── prod 17 | └── staging 18 | ``` 19 | 20 | ## Helm releases 21 | 22 | The podinfo example shows how a helm release is managed in different environments. 23 | 24 | In `apps/base/podinfo/` we have a Flux `HelmRelease` with common values for both clusters: 25 | 26 | ```yaml 27 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 28 | kind: HelmRelease 29 | metadata: 30 | name: podinfo 31 | namespace: podinfo 32 | spec: 33 | releaseName: podinfo 34 | chart: 35 | spec: 36 | chart: podinfo 37 | sourceRef: 38 | kind: HelmRepository 39 | name: podinfo 40 | namespace: flux-system 41 | interval: 50m 42 | values: 43 | ingress: 44 | enabled: true 45 | className: nginx 46 | ``` 47 | 48 | In `apps/staging/` dir we have a Kustomize patch with the staging specific values: 49 | 50 | ```yaml 51 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 52 | kind: HelmRelease 53 | metadata: 54 | name: podinfo 55 | spec: 56 | chart: 57 | spec: 58 | version: ">=1.0.0-alpha" 59 | test: 60 | enable: true 61 | values: 62 | ingress: 63 | hosts: 64 | - host: podinfo.staging 65 | ``` 66 | 67 | Note that with `version: ">=1.0.0-alpha"` we configure Flux to automatically upgrade 68 | the `HelmRelease` to the latest chart version including alpha, beta and pre-releases. 69 | 70 | In `apps/production/` dir we have a Kustomize patch with the production specific values: 71 | 72 | ```yaml 73 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 74 | kind: HelmRelease 75 | metadata: 76 | name: podinfo 77 | namespace: podinfo 78 | spec: 79 | chart: 80 | spec: 81 | version: ">=1.0.0" 82 | values: 83 | ingress: 84 | hosts: 85 | - host: podinfo.production 86 | ``` 87 | 88 | Note that with `version: ">=1.0.0"` we configure Flux to automatically upgrade 89 | the `HelmRelease` to the latest stable chart version (alpha, beta and pre-releases will be ignored). 90 | -------------------------------------------------------------------------------- /cloud/README.md: -------------------------------------------------------------------------------- 1 | # Cloud 2 | 3 | ## Folder structure 4 | 5 | The folder contains the following top directories: 6 | 7 | - `providers` contains Crossplane [providers](https://docs.crossplane.io/v1.12/concepts/providers/) 8 | - **resources** dir contains Crossplane [managed resources](https://docs.crossplane.io/v1.12/concepts/managed-resources/) and [composite resources](https://docs.crossplane.io/v1.12/concepts/composition/). 9 | 10 | ```text 11 | └── cloud 12 | ├── providers 13 | │ ├── base 14 | │ ├── dev 15 | │ ├── prod 16 | │ └── staging 17 | └── resources 18 | ├── base 19 | ├── dev 20 | ├── prod 21 | └── staging 22 | ``` 23 | 24 | ## Prerequisites 25 | 26 | - A Kubernetes cluster with at least 6 GB of RAM permissions to create pods and secrets in the Kubernetes cluster 27 | - [Helm](https://helm.sh/) version v3.2.0 or later 28 | - A GCP account with permissions to create a storage bucket 29 | - GCP [account keys](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) 30 | - GCP [Project ID](https://support.google.com/googleapi/answer/7014113?hl=en) 31 | 32 | ## Provision a GKE cluster 33 | 34 | Steps to provision a Crossplane cluster so that it can manage GKE resources. 35 | 36 | ### 1. Install the GCP provider 37 | 38 | A provider installs their own Kubernetes Custom Resource Definitions (CRDs). These CRDs allow you to create GCP resources directly inside Kubernetes. 39 | 40 | You can view the new CRDs with kubectl get crds. Every CRD maps to a unique GCP service Crossplane can provision and manage. 41 | 42 | ### 2. Create the Kubernetes secret for GCP 43 | 44 | The provider requires credentials to create and manage GCP resources. 45 | Providers use a Kubernetes Secret to connect the credentials to the provider. 46 | 47 | For basic user authentication, use a Google Cloud service account JSON file. 48 | See the [GCP Docs](https://cloud.google.com/iam/docs/creating-managing-service-account-keys). 49 | 50 | Save the JSON file as `gcp-credentials.json`. Its base64 encoded value is the one that will go 51 | to the secret. 52 | 53 | ### 3. Create a ProviderConfig 54 | 55 | A `ProviderConfig` customizes the settings of the GCP Provider. 56 | 57 | ### 4. Create a Managed Resource (MR) 58 | 59 | Now that the provider is configured we can create Kubernetes resources so Crossplane 60 | defines the required state in our target cloud provider. 61 | 62 | The `./resources/` folder has some managed resource examples. 63 | 64 | ## Resources 65 | 66 | [GCP Quickstart](https://docs.crossplane.io/v1.12/getting-started/provider-gcp/) 67 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | SHELL := /usr/bin/env bash 3 | MAKEFLAGS += --no-builtin-rules 4 | MAKEFLAGS += --no-builtin-variables 5 | 6 | .PHONY: bootstrap e2e format flux-ui \ 7 | help hosts init validate wait-apps 8 | 9 | bootstrap: init ## create a local dev cluster with kind and bootstrap with flux via gitops 10 | ./scripts/create-cluster.sh kind dev 11 | ./scripts/provision-cluster.sh dev kind-dev --gitops 12 | @$(MAKE) wait-apps 13 | 14 | clean: ## remove any locally created test resources 15 | kind delete cluster --name dev 16 | 17 | e2e: init ## create a local dev cluster with kind, sync with flux without gitops and clean it up after the checks pass 18 | ./scripts/create-cluster.sh kind dev 19 | ./scripts/provision-cluster.sh dev kind-dev 20 | @$(MAKE) wait-apps 21 | kind delete cluster --name dev 22 | 23 | format: init ## format yaml and json files 24 | prettier --write "**/*.{json,yaml,yml}" --log-level error 25 | 26 | flux-ui: init ## port-forward to the current kubernetes cluster so flux UI can be accessed in http://localhost:9001 27 | kubectl -n flux-system port-forward svc/weave-gitops 9001:9001 28 | 29 | help: ## list available commands 30 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) 31 | 32 | hosts: ## Define local hostnames that will point to the cluster IP in relation to the ingresses 33 | ./scripts/set-hosts.sh 34 | 35 | init: ## verify that all the required commands are already installed 36 | @if [ -z "$$CI" ]; then \ 37 | function cmd { \ 38 | if ! command -v "$$1" &>/dev/null ; then \ 39 | echo "error: missing required command in PATH: $$1" >&2 ;\ 40 | return 1 ;\ 41 | fi \ 42 | } ;\ 43 | cmd age-keygen ;\ 44 | cmd flux ;\ 45 | cmd kind ;\ 46 | cmd kubeconform ;\ 47 | cmd kubectl ;\ 48 | cmd prettier ;\ 49 | cmd sops ;\ 50 | cmd yq ;\ 51 | cp .githooks/* .git/hooks/ ;\ 52 | git config diff.sopsdiffer.textconv "sops -d" ;\ 53 | fi 54 | 55 | validate: init # validate the flux custom resources and kustomize overlays using kubeconform 56 | ./scripts/validate.sh 57 | 58 | wait-apps: init # wait for resources provisioned in the current context app cluster 59 | kubectl -n flux-system wait kustomization/infra-controllers --for=condition=ready --timeout=5m 60 | kubectl -n flux-system wait kustomization/infra-configs --for=condition=ready --timeout=5m 61 | kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=5m 62 | kubectl -n flux-system wait kustomization/fastapi-example --for=condition=ready --timeout=5m 63 | kubectl -n podinfo wait helmrelease/podinfo --for=condition=ready --timeout=5m 64 | -------------------------------------------------------------------------------- /scripts/validate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script downloads the Flux OpenAPI schemas, then it validates the 4 | # Flux custom resources and the kustomize overlays using kubeconform. 5 | # This script is meant to be run locally and in CI before the changes 6 | # are merged on the main branch that's synced by Flux. 7 | 8 | # Copyright 2023 The Flux authors. All rights reserved. 9 | # 10 | # Licensed under the Apache License, Version 2.0 (the "License"); 11 | # you may not use this file except in compliance with the License. 12 | # You may obtain a copy of the License at 13 | # 14 | # http://www.apache.org/licenses/LICENSE-2.0 15 | # 16 | # Unless required by applicable law or agreed to in writing, software 17 | # distributed under the License is distributed on an "AS IS" BASIS, 18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | # See the License for the specific language governing permissions and 20 | # limitations under the License. 21 | 22 | # Prerequisites 23 | # - yq v4.34 24 | # - kustomize v5.0 25 | # - kubeconform v0.6 26 | 27 | set -o errexit 28 | set -o pipefail 29 | 30 | # Get the directory of the current script 31 | script_dir="$(dirname "${BASH_SOURCE[0]}")" 32 | 33 | # shellcheck disable=SC1091 34 | source "${script_dir}"/utils.sh 35 | 36 | # mirror kustomize-controller build options 37 | kustomize_flags=("--load-restrictor=LoadRestrictionsNone") 38 | kustomize_config="kustomization.yaml" 39 | 40 | # skip Kubernetes Secrets due to SOPS fields failing validation 41 | kubeconform_flags=("-skip=Secret") 42 | kubeconform_config=("-strict" "-ignore-missing-schemas" "-schema-location" "default" "-schema-location" "/tmp/flux-crd-schemas" "-verbose") 43 | 44 | print_blue "Downloading Flux OpenAPI schemas" 45 | mkdir -p /tmp/flux-crd-schemas/master-standalone-strict 46 | curl -sL https://github.com/fluxcd/flux2/releases/latest/download/crd-schemas.tar.gz | tar zxf - -C /tmp/flux-crd-schemas/master-standalone-strict 47 | 48 | find . -type f -name '*.yaml' -print0 | while IFS= read -r -d $'\0' file; 49 | do 50 | print_blue "Validating $file" 51 | yq e 'true' "$file" > /dev/null 52 | done 53 | 54 | print_blue "Validating clusters" 55 | find ./clusters -maxdepth 2 -type f -name '*.yaml' -print0 | while IFS= read -r -d $'\0' file; 56 | do 57 | kubeconform "${kubeconform_flags[@]}" "${kubeconform_config[@]}" "${file}" 58 | if [[ ${PIPESTATUS[0]} != 0 ]]; then 59 | exit 1 60 | fi 61 | done 62 | 63 | print_blue "Validating kustomize overlays" 64 | find . -type f -name $kustomize_config -print0 | while IFS= read -r -d $'\0' file; 65 | do 66 | print_blue "Validating kustomization ${file/%$kustomize_config}" 67 | kustomize build "${file/%$kustomize_config}" "${kustomize_flags[@]}" | \ 68 | kubeconform "${kubeconform_flags[@]}" "${kubeconform_config[@]}" 69 | if [[ ${PIPESTATUS[0]} != 0 ]]; then 70 | exit 1 71 | fi 72 | done 73 | -------------------------------------------------------------------------------- /cloud/resources/base/gcp-gke.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: gcp-gke 6 | labels: 7 | toolkit.fluxcd.io/tenant: sre-team 8 | --- 9 | # API Reference: https://doc.crds.dev/github.com/crossplane/provider-gcp/container.gcp.crossplane.io/Cluster/v1beta2@v0.22.0 10 | apiVersion: container.gcp.crossplane.io/v1beta1 11 | kind: GKECluster 12 | metadata: 13 | name: gke-crossplane-cluster 14 | spec: 15 | forProvider: 16 | initialClusterVersion: "1.27" 17 | network: "projects/development-labs/global/networks/opsnet" 18 | subnetwork: "projects/development-labs/regions/us-central1/subnetworks/opsnet" 19 | ipAllocationPolicy: 20 | useIpAliases: true 21 | defaultMaxPodsConstraint: 22 | maxPodsPerNode: 110 23 | addonsConfig: 24 | cloudRunConfig: 25 | disabled: true 26 | dnsCacheConfig: 27 | enabled: false 28 | gcePersistentDiskCsiDriverConfig: 29 | enabled: true 30 | horizontalPodAutoscaling: 31 | disabled: true 32 | httpLoadBalancing: 33 | disabled: false 34 | istioConfig: 35 | disabled: true 36 | auth: "AUTH_NONE" 37 | kalmConfig: 38 | enabled: false 39 | kubernetesDashboard: 40 | disabled: true 41 | networkPolicyConfig: 42 | disabled: false 43 | location: us-central1-a 44 | binaryAuthorization: 45 | enabled: false 46 | legacyAbac: 47 | enabled: false 48 | loggingService: "none" 49 | masterAuth: 50 | clientCertificateConfig: 51 | issueClientCertificate: false 52 | monitoringService: "none" 53 | --- 54 | # API Reference: https://doc.crds.dev/github.com/crossplane/provider-gcp/container.gcp.crossplane.io/NodePool/v1beta1@v0.22.0 55 | apiVersion: container.gcp.crossplane.io/v1alpha1 56 | kind: NodePool 57 | metadata: 58 | name: gke-crossplane-node-pool 59 | spec: 60 | forProvider: 61 | autoscaling: 62 | autoprovisioned: false 63 | enabled: true 64 | maxNodeCount: 2 65 | minNodeCount: 1 66 | clusterRef: 67 | name: gke-crossplane-cluster 68 | config: 69 | # sandboxConfig: 70 | # sandboxType: gvisor 71 | diskSizeGb: 100 72 | # diskType: pd-ssd 73 | imageType: cos_containerd 74 | labels: 75 | test-label: crossplane-created 76 | machineType: n1-standard-4 77 | oauthScopes: 78 | - "https://www.googleapis.com/auth/devstorage.read_only" 79 | - "https://www.googleapis.com/auth/logging.write" 80 | - "https://www.googleapis.com/auth/monitoring" 81 | - "https://www.googleapis.com/auth/servicecontrol" 82 | - "https://www.googleapis.com/auth/service.management.readonly" 83 | - "https://www.googleapis.com/auth/trace.append" 84 | initialNodeCount: 2 85 | locations: 86 | - us-central1-a 87 | management: 88 | autoRepair: true 89 | autoUpgrade: true 90 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yaml: -------------------------------------------------------------------------------- 1 | name: e2e 2 | 3 | on: 4 | push: 5 | paths: 6 | - "apps/**" 7 | - "clusters/dev/**" 8 | - "infrastructure/**" 9 | - ".github/workflows/e2e.yaml" 10 | branches: ["*"] 11 | tags-ignore: ["*"] 12 | workflow_dispatch: 13 | 14 | jobs: 15 | kubernetes: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | - name: Setup Flux 21 | uses: fluxcd/flux2/action@main 22 | - name: Setup Kubernetes 23 | uses: helm/kind-action@v1.8.0 24 | with: 25 | cluster_name: dev 26 | config: kind.config.yaml 27 | - name: Install Flux in Kubernetes Kind 28 | run: flux install --components-extra=image-reflector-controller,image-automation-controller 29 | - name: Setup cluster reconciliation 30 | run: | 31 | flux create source git flux-system \ 32 | --url=${{ github.event.repository.html_url }} \ 33 | --branch=${GITHUB_REF#refs/heads/} \ 34 | --ignore-paths=clusters/**/flux-system/ 35 | 36 | flux create kustomization flux-system \ 37 | --source=flux-system \ 38 | --path=./clusters/dev 39 | 40 | echo "$E2E_AGEKEY" | kubectl create secret generic sops-age \ 41 | --namespace=flux-system \ 42 | --from-file=sops.agekey=/dev/stdin 43 | env: 44 | E2E_AGEKEY: ${{ secrets.E2E_AGEKEY }} 45 | - name: Verify infrastructure reconciliation 46 | run: | 47 | kubectl -n flux-system wait kustomization/infra-controllers --for=condition=ready --timeout=5m 48 | kubectl -n flux-system wait kustomization/infra-configs --for=condition=ready --timeout=5m 49 | kubectl -n flux-system wait kustomization/apps --for=condition=ready --timeout=5m 50 | - name: Verify app reconciliation 51 | run: | 52 | kubectl -n flux-system wait kustomization/fastapi-example --for=condition=ready --timeout=5m 53 | kubectl -n podinfo wait helmrelease/podinfo --for=condition=ready --timeout=5m 54 | - name: Debug failure 55 | if: failure() 56 | run: | 57 | kubectl get namespaces 58 | 59 | echo "" 60 | echo "***** flux-system namespace *****" 61 | kubectl -n flux-system get all 62 | kubectl -n flux-system logs deploy/source-controller 63 | kubectl -n flux-system logs deploy/kustomize-controller 64 | kubectl -n flux-system logs deploy/helm-controller 65 | 66 | echo "" 67 | echo "***** fastapi-example namespace *****" 68 | kubectl -n fastapi-example get all 69 | 70 | echo "" 71 | echo "***** podinfo namespace *****" 72 | kubectl -n podinfo get all 73 | 74 | echo "" 75 | echo "***** flux custom resources *****" 76 | flux get all --all-namespaces 77 | kubectl describe -n flux-system gitrepository flux-system 78 | -------------------------------------------------------------------------------- /infrastructure/README.md: -------------------------------------------------------------------------------- 1 | # Infrastructure 2 | 3 | Infrastructure components for Kubernetes clusters. 4 | They provide essential functionality or services that are shared among multiple applications 5 | or services running on the cluster, and they can be seen often in multiple clusters as they are 6 | foundational services. 7 | 8 | ## Folder structure 9 | 10 | The infrastructure addons is structured into: 11 | 12 | - `infrastructure/controllers/` contains namespaces and Helm release definitions for Kubernetes controllers 13 | - `infrastructure/configs/` contains Kubernetes custom resources such as cert issuers and networks policies 14 | 15 | ```text 16 | └── infrastructure 17 | ├── configs 18 | │ ├── base 19 | │ ├── dev 20 | │ ├── prod 21 | │ └── staging 22 | └── controllers 23 | ├── base 24 | ├── dev 25 | ├── prod 26 | └── staging 27 | ``` 28 | 29 | In `infrastructure/controllers/` we have the Flux `HelmRepository` and `HelmRelease` definitions such as: 30 | 31 | ```yaml 32 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 33 | kind: HelmRelease 34 | metadata: 35 | name: cert-manager 36 | namespace: cert-manager 37 | spec: 38 | interval: 30m 39 | chart: 40 | spec: 41 | chart: cert-manager 42 | version: "1.x" 43 | sourceRef: 44 | kind: HelmRepository 45 | name: cert-manager 46 | namespace: cert-manager 47 | interval: 12h 48 | values: 49 | installCRDs: true 50 | ``` 51 | 52 | Note that with `interval: 12h` we configure Flux to pull the Helm repository index every twelfth hours to check for updates. 53 | If the new chart version that matches the `1.x` semver range is found, Flux will upgrade the release. 54 | 55 | In `infrastructure/configs/` dir we have Kubernetes custom resources, such as the Let's Encrypt issuer: 56 | 57 | ```yaml 58 | apiVersion: cert-manager.io/v1 59 | kind: ClusterIssuer 60 | metadata: 61 | name: letsencrypt 62 | spec: 63 | acme: 64 | # Replace the email address with your own contact email 65 | email: fluxcdbot@users.noreply.github.com 66 | server: https://acme-staging-v02.api.letsencrypt.org/directory 67 | privateKeySecretRef: 68 | name: letsencrypt-nginx 69 | solvers: 70 | - http01: 71 | ingress: 72 | class: nginx 73 | ``` 74 | 75 | In `clusters/prod/infrastructure.yaml` we replace the Let's Encrypt server value to point to the production API: 76 | 77 | ```yaml 78 | apiVersion: kustomize.toolkit.fluxcd.io/v1 79 | kind: Kustomization 80 | metadata: 81 | name: infra-configs 82 | namespace: flux-system 83 | spec: 84 | # ...omitted for brevity 85 | dependsOn: 86 | - name: infra-controllers 87 | patches: 88 | - patch: | 89 | - op: replace 90 | path: /spec/acme/server 91 | value: https://acme-v02.api.letsencrypt.org/directory 92 | target: 93 | kind: ClusterIssuer 94 | name: letsencrypt 95 | ``` 96 | 97 | Note that with `dependsOn` we tell Flux to first install or upgrade the controllers and only then the configs. 98 | This ensures that the Kubernetes CRDs are registered on the cluster, before Flux applies any custom resources. 99 | 100 | ## Monitoring 101 | 102 | Monitoring is provisioned with the [kube-prometheus-stack](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack) chart (using the Prometheus Operator) and the [loki-stack](https://github.com/grafana/helm-charts/tree/main/charts/loki-stack). 103 | 104 | See [Flux's monitoring guide](https://fluxcd.io/flux/guides/monitoring/) 105 | -------------------------------------------------------------------------------- /scripts/provision-cluster.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Exit if one of the scripts fail 4 | set -e 5 | 6 | # Get the directory of the current script 7 | script_dir="$(dirname "${BASH_SOURCE[0]}")" 8 | 9 | # shellcheck disable=SC1091 10 | source "${script_dir}"/utils.sh 11 | 12 | # Initialize flags 13 | gitops_flag=false 14 | 15 | # Parse flags 16 | while (( "$#" )); do 17 | case "$1" in 18 | --gitops) 19 | gitops_flag=true 20 | shift 21 | ;; 22 | *) # preserve positional arguments 23 | PARAMS="$PARAMS $1" 24 | shift 25 | ;; 26 | esac 27 | done 28 | 29 | # Set positional arguments in their proper place 30 | eval set -- "$PARAMS" 31 | 32 | # Check that cluster_name is provided 33 | if [ -z "$1" ] 34 | then 35 | print_red "Error: No kubernetes cluster name provided." 36 | echo "Usage: $0 [--gitops] " 37 | exit 1 38 | fi 39 | 40 | # Check that context_name is provided 41 | if [ -z "$2" ] 42 | then 43 | print_red "Error: No kubernetes context name provided." 44 | echo "Usage: $0 [--gitops] " 45 | exit 1 46 | fi 47 | 48 | cluster_name="$1" 49 | context_name="$2" 50 | private_key_path="./clusters/${cluster_name}/sops.agekey" 51 | 52 | # Source the .envrc file to load the GITHUB_USER and GITHUB_REPO environment variables 53 | # shellcheck source=/dev/null 54 | source .envrc 55 | 56 | if [ "$gitops_flag" = true ] ; then 57 | # Provision flux the "gitops" way (https://fluxcd.io/flux/cmd/flux_bootstrap/) 58 | echo "" 59 | print_blue "Provisioning flux with gitops..." 60 | flux bootstrap github \ 61 | --branch=main \ 62 | --components-extra=image-reflector-controller,image-automation-controller \ 63 | --context="${context_name}" \ 64 | --owner="${GITHUB_OWNER}" \ 65 | --path=clusters/"${cluster_name}" \ 66 | --personal \ 67 | --read-write-key \ 68 | --repository="${GITHUB_REPO}" 69 | else 70 | echo "" 71 | print_blue "Provisioning flux without gitops..." 72 | flux install \ 73 | --components-extra=image-reflector-controller,image-automation-controller \ 74 | --context="${context_name}" 75 | flux create source git flux-system \ 76 | --context="${context_name}" \ 77 | --url=https://github.com/"${GITHUB_OWNER}"/"${GITHUB_REPO}" \ 78 | --branch=main \ 79 | --ignore-paths=clusters/"${cluster_name}"/flux-system/ 80 | flux create kustomization flux-system \ 81 | --context="${context_name}" \ 82 | --source=flux-system \ 83 | --path=./clusters/"${cluster_name}" 84 | fi 85 | print_green "Cluster provisioned successfully" 86 | 87 | if [ ! -f "$private_key_path" ]; then 88 | echo "" 89 | print_yellow "The private key does not exist in ${private_key_path}." 90 | 91 | read -r -p "👉 Do you want to generate a new one? You will later need to update the to update the ./.sops.yaml file with its public key for ${cluster_name} [y/n]: " generate_age_key 92 | if [[ $generate_age_key =~ ^[yY] ]]; then 93 | age-keygen -o "${private_key_path}" 94 | print_green "New age key generated in ${private_key_path}, do not forget to update the ./.sops.yaml file with the public key for ${cluster_name}" 95 | else 96 | print_yellow "Skipped cluster provision, a private key needs to be provided. You might want to set ${private_key_path} with an age key from a password manager or any other external source. " 97 | exit_gracefully 98 | fi 99 | fi 100 | 101 | # Provision the key that will be used to decrypt sops secrets 102 | echo "" 103 | print_blue "🔑 Creating private sops-age key for global secret management..." 104 | 105 | if kubectl get secret sops-age --context="${context_name}" --namespace=flux-system > /dev/null 2>&1; then 106 | print_yellow "Secret 'sops-age' already exists. Skipping creation." 107 | else 108 | kubectl create secret generic sops-age \ 109 | --context="${context_name}" \ 110 | --namespace=flux-system \ 111 | --from-file=./clusters/"${cluster_name}"/sops.agekey 112 | print_green "Secret 'sops-age' created." 113 | fi 114 | echo "" 115 | -------------------------------------------------------------------------------- /clusters/README.md: -------------------------------------------------------------------------------- 1 | # clusters 2 | 3 | ## Bootstrap staging and production 4 | 5 | The clusters dir contains the following Flux configuration structure: 6 | 7 | ```text 8 | ./clusters/ 9 | ├── dev 10 | │ ├── apps.yaml 11 | │ ├── cloud.yaml 12 | │ └── infrastructure.yaml 13 | ├── prod 14 | │ ├── apps.yaml 15 | │ ├── cloud.yaml 16 | │ └── infrastructure.yaml 17 | └── staging 18 | ├── apps.yaml 19 | │ ├── cloud.yaml 20 | └── infrastructure.yaml 21 | ``` 22 | 23 | In **clusters/staging/** dir we have the Flux Kustomization definitions, for example: 24 | 25 | ```yaml 26 | apiVersion: kustomize.toolkit.fluxcd.io/v1 27 | kind: Kustomization 28 | metadata: 29 | name: apps 30 | namespace: flux-system 31 | spec: 32 | interval: 10m0s 33 | dependsOn: 34 | - name: infra-configs 35 | sourceRef: 36 | kind: GitRepository 37 | name: flux-system 38 | path: ./apps/staging 39 | prune: true 40 | wait: true 41 | ``` 42 | 43 | Note that with `path: ./apps/staging` we configure Flux to sync the staging Kustomize overlay and 44 | with `dependsOn` we tell Flux to create the infrastructure items before deploying the apps. 45 | 46 | Export your GitHub access token, username and repo name: 47 | 48 | ```sh 49 | export GITHUB_TOKEN= 50 | export GITHUB_USER= 51 | export GITHUB_REPO= 52 | ``` 53 | 54 | Alternatively: 55 | 56 | ```sh 57 | cp .envrc.example .envrc 58 | # Fill the .envrc with your GITHUB_* variables 59 | source .envrc 60 | # Or if you have `direnv` 61 | direnv allow 62 | ``` 63 | 64 | Verify that your staging cluster satisfies the prerequisites with: 65 | 66 | ```sh 67 | flux check --pre 68 | ``` 69 | 70 | Set the kubectl context to your staging cluster and bootstrap Flux 71 | (use `kind-staging` in the context flag if running a cluster locally with kind): 72 | 73 | ```sh 74 | flux bootstrap github \ 75 | --context=staging \ 76 | --owner=${GITHUB_USER} \ 77 | --repository=${GITHUB_REPO} \ 78 | --branch=main \ 79 | --personal \ 80 | --path=clusters/staging 81 | ``` 82 | 83 | The bootstrap command commits the manifests for the Flux components in `clusters/staging/flux-system` dir 84 | and creates a deploy key with read-only access on GitHub, so it can pull changes inside the cluster. 85 | 86 | Watch for the Helm releases being installed on staging: 87 | 88 | ```console 89 | $ watch flux get helmreleases --all-namespaces 90 | 91 | NAMESPACE NAME REVISION SUSPENDED READY MESSAGE 92 | cert-manager cert-manager v1.11.0 False True Release reconciliation succeeded 93 | flux-system weave-gitops 4.0.12 False True Release reconciliation succeeded 94 | ingress-nginx ingress-nginx 4.4.2 False True Release reconciliation succeeded 95 | podinfo podinfo 6.3.0 False True Release reconciliation succeeded 96 | ``` 97 | 98 | Verify that the demo app can be accessed via ingress: 99 | 100 | ```console 101 | $ kubectl -n ingress-nginx port-forward svc/ingress-nginx-controller 8080:80 & 102 | 103 | $ curl -H "Host: podinfo.staging" http://localhost:8080 104 | { 105 | "hostname": "podinfo-59489db7b5-lmwpn", 106 | "version": "6.2.3" 107 | } 108 | ``` 109 | 110 | Bootstrap Flux on production by setting the context and path to your production cluster 111 | (use `kind-prod` in the context flag if running a cluster locally with kind): 112 | 113 | ```sh 114 | flux bootstrap github \ 115 | --context=production \ 116 | --owner=${GITHUB_USER} \ 117 | --repository=${GITHUB_REPO} \ 118 | --branch=main \ 119 | --personal \ 120 | --path=clusters/production 121 | ``` 122 | 123 | Watch the production reconciliation: 124 | 125 | ```console 126 | $ flux get kustomizations --watch 127 | 128 | NAME REVISION SUSPENDED READY MESSAGE 129 | apps main/696182e False True Applied revision: main/696182e 130 | flux-system main/696182e False True Applied revision: main/696182e 131 | infra-configs main/696182e False True Applied revision: main/696182e 132 | infra-controllers main/696182e False True Applied revision: main/696182e 133 | ``` 134 | 135 | ### Access the Flux UI 136 | 137 | To access the Flux UI on a cluster, first start port forwarding with: 138 | 139 | ```sh 140 | kubectl -n flux-system port-forward svc/weave-gitops 9001:9001 141 | ``` 142 | 143 | Navigate to http://localhost:9001 and login using the username `admin` and the password `flux`. 144 | 145 | [Weave GitOps](https://docs.gitops.weave.works/) provides insights into your application deployments, 146 | and makes continuous delivery with Flux easier to adopt and scale across your teams. 147 | The GUI provides a guided experience to build understanding and simplify getting started for new users; 148 | they can easily discover the relationship between Flux objects and navigate to deeper levels of information as required. 149 | 150 | ![flux-ui-depends-on](.github/screens/flux-ui-depends-on.png) 151 | 152 | You can change the admin password bcrypt hash in **infrastructure/controllers/weave-gitops.yaml**: 153 | 154 | ```yaml 155 | apiVersion: helm.toolkit.fluxcd.io/v2beta1 156 | kind: HelmRelease 157 | metadata: 158 | name: weave-gitops 159 | namespace: flux-system 160 | spec: 161 | # ...omitted for brevity 162 | values: 163 | adminUser: 164 | create: true 165 | username: admin 166 | # bcrypt hash for password "flux" 167 | passwordHash: "$2a$10$P/tHQ1DNFXdvX0zRGA8LPeSOyb0JXq9rP3fZ4W8HGTpLV7qHDlWhe" 168 | ``` 169 | 170 | To generate a bcrypt hash please see Weave GitOps 171 | [documentation](https://docs.gitops.weave.works/docs/configuration/securing-access-to-the-dashboard/#login-via-a-cluster-user-account). 172 | 173 | Note that on production systems it is recommended to expose Weave GitOps over TLS with an ingress controller and 174 | to enable OIDC authentication for your organisation members. 175 | To configure OIDC with Dex and GitHub please see this [guide](https://docs.gitops.weave.works/docs/guides/setting-up-dex/). 176 | 177 | ## Add clusters 178 | 179 | If you want to add a cluster to your fleet, first clone your repo locally: 180 | 181 | ```sh 182 | git clone https://github.com/${GITHUB_USER}/${GITHUB_REPO}.git 183 | cd ${GITHUB_REPO} 184 | ``` 185 | 186 | Create a dir inside `clusters` with your cluster name: 187 | 188 | ```sh 189 | mkdir -p clusters/dev 190 | ``` 191 | 192 | Copy the sync manifests from staging: 193 | 194 | ```sh 195 | cp clusters/staging/infrastructure.yaml clusters/dev 196 | cp clusters/staging/apps.yaml clusters/dev 197 | ``` 198 | 199 | You could create a dev overlay inside `apps`, make sure 200 | to change the `spec.path` inside `clusters/dev/apps.yaml` to `path: ./apps/dev`. 201 | 202 | Push the changes to the main branch: 203 | 204 | ```sh 205 | git add -A && git commit -m "add dev cluster" && git push 206 | ``` 207 | 208 | Set the kubectl context and path to your dev cluster and bootstrap Flux 209 | (use `kind-dev` in the context flag if running a cluster locally with kind): 210 | 211 | ```sh 212 | flux bootstrap github \ 213 | --context=dev \ 214 | --owner=${GITHUB_USER} \ 215 | --repository=${GITHUB_REPO} \ 216 | --branch=main \ 217 | --personal \ 218 | --path=clusters/dev 219 | ``` 220 | 221 | ## Identical environments 222 | 223 | If you want to spin up an identical environment, you can bootstrap a cluster 224 | e.g. `production-clone` and reuse the `production` definitions. 225 | 226 | Bootstrap the `production-clone` cluster: 227 | 228 | ```sh 229 | flux bootstrap github \ 230 | --context=production-clone \ 231 | --owner=${GITHUB_USER} \ 232 | --repository=${GITHUB_REPO} \ 233 | --branch=main \ 234 | --personal \ 235 | --path=clusters/production-clone 236 | ``` 237 | 238 | Pull the changes locally: 239 | 240 | ```sh 241 | git pull origin main 242 | ``` 243 | 244 | Create a `kustomization.yaml` inside the `clusters/production-clone` dir: 245 | 246 | ```yaml 247 | apiVersion: kustomize.config.k8s.io/v1beta1 248 | kind: Kustomization 249 | resources: 250 | - flux-system 251 | - ../production/infrastructure.yaml 252 | - ../production/apps.yaml 253 | ``` 254 | 255 | Note that besides the `flux-system` kustomize overlay, we also include 256 | the `infrastructure` and `apps` manifests from the production dir. 257 | 258 | Push the changes to the main branch: 259 | 260 | ```sh 261 | git add -A && git commit -m "add production clone" && git push 262 | ``` 263 | 264 | Tell Flux to deploy the production workloads on the `production-clone` cluster: 265 | 266 | ```sh 267 | flux reconcile kustomization flux-system \ 268 | --context=production-clone \ 269 | --with-source 270 | ``` 271 | -------------------------------------------------------------------------------- /infrastructure/configs/base/monitoring/dashboards/logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_LOKI", 5 | "label": "Loki", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "loki", 9 | "pluginName": "Loki" 10 | } 11 | ], 12 | "annotations": { 13 | "list": [ 14 | { 15 | "builtIn": 1, 16 | "datasource": "-- Grafana --", 17 | "enable": true, 18 | "hide": true, 19 | "iconColor": "rgba(0, 211, 255, 1)", 20 | "name": "Annotations & Alerts", 21 | "target": { 22 | "limit": 100, 23 | "matchAny": false, 24 | "tags": [], 25 | "type": "dashboard" 26 | }, 27 | "type": "dashboard" 28 | }, 29 | { 30 | "datasource": { 31 | "type": "datasource", 32 | "uid": "grafana" 33 | }, 34 | "enable": true, 35 | "iconColor": "red", 36 | "name": "flux events", 37 | "target": { 38 | "limit": 100, 39 | "matchAny": false, 40 | "tags": ["flux"], 41 | "type": "tags" 42 | } 43 | } 44 | ] 45 | }, 46 | "description": "Flux logs collected from Kubernetes, stored in Loki", 47 | "editable": true, 48 | "gnetId": null, 49 | "graphTooltip": 0, 50 | "id": 29, 51 | "iteration": 1653748775696, 52 | "links": [], 53 | "liveNow": false, 54 | "panels": [ 55 | { 56 | "datasource": "${DS_LOKI}", 57 | "description": "", 58 | "fieldConfig": { 59 | "defaults": { 60 | "color": { 61 | "mode": "palette-classic" 62 | }, 63 | "custom": { 64 | "axisLabel": "", 65 | "axisPlacement": "auto", 66 | "barAlignment": 0, 67 | "drawStyle": "bars", 68 | "fillOpacity": 0, 69 | "gradientMode": "none", 70 | "hideFrom": { 71 | "legend": false, 72 | "tooltip": false, 73 | "viz": false 74 | }, 75 | "lineInterpolation": "linear", 76 | "lineWidth": 1, 77 | "pointSize": 5, 78 | "scaleDistribution": { 79 | "type": "linear" 80 | }, 81 | "showPoints": "auto", 82 | "spanNulls": false, 83 | "stacking": { 84 | "group": "A", 85 | "mode": "none" 86 | }, 87 | "thresholdsStyle": { 88 | "mode": "off" 89 | } 90 | }, 91 | "mappings": [], 92 | "thresholds": { 93 | "mode": "absolute", 94 | "steps": [ 95 | { 96 | "color": "green", 97 | "value": null 98 | }, 99 | { 100 | "color": "red", 101 | "value": 80 102 | } 103 | ] 104 | } 105 | }, 106 | "overrides": [] 107 | }, 108 | "gridPos": { 109 | "h": 4, 110 | "w": 24, 111 | "x": 0, 112 | "y": 0 113 | }, 114 | "id": 4, 115 | "options": { 116 | "legend": { 117 | "calcs": [], 118 | "displayMode": "hidden", 119 | "placement": "bottom" 120 | }, 121 | "tooltip": { 122 | "mode": "single", 123 | "sort": "none" 124 | } 125 | }, 126 | "targets": [ 127 | { 128 | "datasource": "${DS_LOKI}", 129 | "expr": "sum(count_over_time({namespace=~\"$namespace\", stream=~\"$stream\", app =~\"$controller\"} | json | __error__!=\"JSONParserErr\" | level=~\"$level\" |= \"$query\" [$__interval]))", 130 | "instant": false, 131 | "legendFormat": "Log count", 132 | "range": true, 133 | "refId": "A" 134 | } 135 | ], 136 | "type": "timeseries" 137 | }, 138 | { 139 | "datasource": "${DS_LOKI}", 140 | "description": "Logs from services running in Kubernetes", 141 | "gridPos": { 142 | "h": 25, 143 | "w": 24, 144 | "x": 0, 145 | "y": 4 146 | }, 147 | "id": 2, 148 | "options": { 149 | "dedupStrategy": "numbers", 150 | "enableLogDetails": false, 151 | "prettifyLogMessage": true, 152 | "showCommonLabels": false, 153 | "showLabels": false, 154 | "showTime": false, 155 | "sortOrder": "Descending", 156 | "wrapLogMessage": false 157 | }, 158 | "targets": [ 159 | { 160 | "datasource": "${DS_LOKI}", 161 | "expr": "{namespace=~\"$namespace\", stream=~\"$stream\", app =~\"$controller\"} | json | __error__!=\"JSONParserErr\" | level=~\"$level\" |= \"$query\"", 162 | "refId": "A" 163 | } 164 | ], 165 | "type": "logs" 166 | } 167 | ], 168 | "refresh": "10s", 169 | "schemaVersion": 36, 170 | "style": "light", 171 | "tags": ["flux"], 172 | "templating": { 173 | "list": [ 174 | { 175 | "current": { 176 | "selected": false, 177 | "text": "", 178 | "value": "" 179 | }, 180 | "description": "String to search for", 181 | "hide": 0, 182 | "label": "Search Query", 183 | "name": "query", 184 | "options": [ 185 | { 186 | "selected": true, 187 | "text": "", 188 | "value": "" 189 | } 190 | ], 191 | "query": "", 192 | "skipUrlSync": false, 193 | "type": "textbox" 194 | }, 195 | { 196 | "allValue": "info|error", 197 | "current": { 198 | "selected": false, 199 | "text": "All", 200 | "value": "$__all" 201 | }, 202 | "hide": 0, 203 | "includeAll": true, 204 | "multi": false, 205 | "name": "level", 206 | "options": [ 207 | { 208 | "selected": true, 209 | "text": "All", 210 | "value": "$__all" 211 | }, 212 | { 213 | "selected": false, 214 | "text": "info", 215 | "value": "info" 216 | }, 217 | { 218 | "selected": false, 219 | "text": "error", 220 | "value": "error" 221 | } 222 | ], 223 | "query": "info,error", 224 | "queryValue": "", 225 | "skipUrlSync": false, 226 | "type": "custom" 227 | }, 228 | { 229 | "allValue": ".+", 230 | "current": { 231 | "selected": true, 232 | "text": ["All"], 233 | "value": ["$__all"] 234 | }, 235 | "datasource": "${DS_LOKI}", 236 | "definition": "label_values(app)", 237 | "hide": 0, 238 | "includeAll": true, 239 | "multi": true, 240 | "name": "controller", 241 | "options": [], 242 | "query": "label_values(app)", 243 | "refresh": 1, 244 | "regex": "", 245 | "skipUrlSync": false, 246 | "sort": 0, 247 | "type": "query" 248 | }, 249 | { 250 | "allValue": ".+", 251 | "current": { 252 | "selected": true, 253 | "text": ["flux-system"], 254 | "value": ["flux-system"] 255 | }, 256 | "datasource": "${DS_LOKI}", 257 | "definition": "label_values(namespace)", 258 | "hide": 0, 259 | "includeAll": true, 260 | "multi": true, 261 | "name": "namespace", 262 | "options": [], 263 | "query": "label_values(namespace)", 264 | "refresh": 1, 265 | "regex": "", 266 | "skipUrlSync": false, 267 | "sort": 0, 268 | "type": "query" 269 | }, 270 | { 271 | "allValue": ".+", 272 | "current": { 273 | "selected": false, 274 | "text": "All", 275 | "value": "$__all" 276 | }, 277 | "datasource": "${DS_LOKI}", 278 | "definition": "label_values(stream)", 279 | "hide": 0, 280 | "includeAll": true, 281 | "multi": true, 282 | "name": "stream", 283 | "options": [], 284 | "query": "label_values(stream)", 285 | "refresh": 1, 286 | "regex": "", 287 | "skipUrlSync": false, 288 | "sort": 0, 289 | "type": "query" 290 | }, 291 | { 292 | "current": { 293 | "selected": false, 294 | "text": "Loki", 295 | "value": "Loki" 296 | }, 297 | "hide": 0, 298 | "includeAll": false, 299 | "label": "Datasource", 300 | "multi": false, 301 | "name": "DS_LOKI", 302 | "options": [], 303 | "query": "loki", 304 | "refresh": 1, 305 | "regex": "", 306 | "skipUrlSync": false, 307 | "type": "datasource" 308 | } 309 | ] 310 | }, 311 | "time": { 312 | "from": "now-6h", 313 | "to": "now" 314 | }, 315 | "timepicker": {}, 316 | "timezone": "", 317 | "title": "Flux Logs", 318 | "uid": "flux-logs", 319 | "version": 2 320 | } 321 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gitops 2 | 3 | [![e2e](https://github.com/darioblanco/gitops/actions/workflows/e2e.yaml/badge.svg)](https://github.com/darioblanco/gitops/actions/workflows/e2e.yaml) 4 | [![json](https://github.com/darioblanco/gitops/actions/workflows/json.yaml/badge.svg)](https://github.com/darioblanco/gitops/actions/workflows/json.yaml) 5 | [![validate](https://github.com/darioblanco/gitops/actions/workflows/validate.yaml/badge.svg)](https://github.com/darioblanco/gitops/actions/workflows/validate.yaml) 6 | [![yaml](https://github.com/darioblanco/gitops/actions/workflows/yaml.yaml/badge.svg)](https://github.com/darioblanco/gitops/actions/workflows/yaml.yaml) 7 | 8 | Apply GitOps to everything with [Flux](https://fluxcd.io/) and [Crossplane](https://www.crossplane.io/). 9 | 10 | See my blogpost [Streamline Kubernetes Management: GitOps with Flux and Kustomize](https://medium.com/@dariobit/streamline-kubernetes-management-gitops-with-flux-and-kustomize-52300168c1c8) for more details. 11 | 12 | Run `make help` for a list of commands. 13 | 14 | ## Architecture 15 | 16 | The idea is to have a single Kubernetes cluster for everything 17 | (e.g. your applications, infrastructure that support them and cloud configuration) 18 | but in different environments, so changes can be thoroughly testing locally in a 19 | dev environment or in your cloud provider of choice, using a `staging` environment. 20 | 21 | Therefore, the `./clusters` folder hold the flux resources with kustomize that will 22 | bootstrap each environment cluster. Those resources point to the respective `./apps`, `./cloud` and 23 | `./infrastructure` folders respectively: 24 | 25 | ```mermaid 26 | graph LR; 27 | ./clusters-->./apps 28 | ./clusters-->./cloud 29 | ./clusters-->./infrastructure 30 | ``` 31 | 32 | Each cluster bootstraps those components in the following order: 33 | 34 | ```mermaid 35 | graph LR; 36 | cloud-providers-->cloud-resources 37 | infra-controllers-->infra-configs 38 | infra-configs-->apps 39 | ``` 40 | 41 | Once the components are bootstrapped, they will reconcile with the folders from the 42 | repo they point at, to keep the configuration from git consistent with the state 43 | of the cluster, which is the essence of GitOps. 44 | 45 | ## Repository structure 46 | 47 | The Git repository contains the following top directories: 48 | 49 | - [apps](./apps/README.md): `helm` and `kustomize` releases with a custom configuration and selection per environment. They depend on the addons and configs. 50 | - [cloud](./cloud/README.md): any cloud resource that does not belong to Kubernetes and is managed via Crossplane. 51 | - [clusters](./clusters/README.md): `flux` configuration per Kubernetes cluster environment that are used for their bootstrapping. 52 | - [infrastructure](./infrastructure/README.md) common Kubernetes-specific infrastructure tools that are likely to be seen in multiple kubernetes clusters (e.g. `ingress-nginx` or `cert-manager`), and its configurations. 53 | - scripts: utilities used by the `Makefile` and CI. 54 | 55 | ```text 56 | ├── apps 57 | │ ├── base 58 | │ ├── dev 59 | │ ├── prod 60 | │ └── staging 61 | ├── cloud 62 | │ ├── providers 63 | │ │ ├── base 64 | │ │ ├── dev 65 | │ │ ├── prod 66 | │ │ └── staging 67 | │ └── resources 68 | │ ├── base 69 | │ ├── dev 70 | │ ├── prod 71 | │ └── staging 72 | ├── clusters 73 | │ ├── dev 74 | │ ├── prod 75 | │ └── staging 76 | ├── infrastructure 77 | │ ├── configs 78 | │ │ ├── base 79 | │ │ ├── dev 80 | │ │ ├── prod 81 | │ │ └── staging 82 | │ └── controllers 83 | │ ├── base 84 | │ ├── dev 85 | │ ├── prod 86 | │ └── staging 87 | └── scripts 88 | ``` 89 | 90 | ## Prerequisites 91 | 92 | You can see the tooling needed to develop with this repository in the Makefile's `init` target. 93 | Run `make init` to make sure that all prerequisites are fulfilled, and feel free to remove 94 | prerequisites based on your personal needs. 95 | 96 | - `age-keygen`: to provide an encryption backend to `sops` for securing secrets in the git repository. 97 | - `flux`: the core requirement to enable GitOps in a Kubernetes cluster. 98 | - `kind`: to provision a local Kubernetes cluster for testing. 99 | - `kubeconform`: to lint Kubernetes manifests. 100 | - `kubectl`: to interact with a Kubernetes cluster. 101 | - `prettier`: to automatically format YAML and JSON files and ensure their style consistency. 102 | - `sops`: to allow Flux the encryption and decryption of secrets. 103 | - `yq`: to validate and interact with YAML files via the command line. 104 | 105 | ## Testing 106 | 107 | Any change to the Kubernetes manifests or to the repository structure should be validated in CI before 108 | a pull requests is merged into the main branch and synced on the cluster. 109 | 110 | This repository contains the following GitHub CI workflows: 111 | 112 | - the [validate](./.github/workflows/validate.yaml) workflow validates the Kubernetes manifests and Kustomize overlays with [kubeconform](https://github.com/yannh/kubeconform) 113 | - the [e2e](./.github/workflows/e2e.yaml) workflow starts a Kubernetes cluster in CI and tests the `dev` setup (`dev`) by running Flux in Kubernetes Kind. 114 | 115 | ## Secrets 116 | 117 | Secrets encryption is done with `Mozilla SOPS` and `age` as its backend, at client level: 118 | 119 | - [Using SOPS with age and git like a pro](https://devops.datenkollektiv.de/using-sops-with-age-and-git-like-a-pro.html) 120 | - [Encrypted GitOps secrets with flux and age](https://major.io/p/encrypted-gitops-secrets-with-flux-and-age/) 121 | 122 | To be able to decrypt secrets, you need to have a private file per cluster. The private file 123 | has to be stored in `./cluster/{clusterName}/sops.agekey`. 124 | 125 | ### Generate a private key per cluster 126 | 127 | Each cluster folder in `./clusters/` should have a git ignored `sops.agekey` file, whose public key 128 | is listed in `./.sops.yaml` with a path_regex that involves files that only belong to that cluster. 129 | 130 | You can generate a key like this: 131 | 132 | ```sh 133 | # One key per file 134 | age-keygen > clusters/dev/sops.agekey 135 | # You can also append multiple keys to a single file, that also works 136 | age-keygen >> clusters/dev/sops.agekey 137 | ``` 138 | 139 | You should have a file there with a format like this: 140 | 141 | ```sh 142 | $ cat sops.agekey 143 | # created: 2023-07-17T14:07:50+02:00 144 | # public key: age1v6q8sylunaq9m08rwxq702enmmh9lama7sp47vkcw3z8wm74z39q846s3y 145 | AGE-SECRET-KEY-THIS_IS_A_SECRET_THAT_SHOULD_NEVER_BE_PUSHED 146 | ``` 147 | 148 | Normally, you would need to put an `AGE-SECRET-*` value that is shared within your team. The 149 | `sops.agekey` file will never be pushed to the repo as it is git ignored. 150 | 151 | The public key of this file should be added to the relevant `.sops.yaml` entries. 152 | 153 | ### Encrypt Kubernetes secrets 154 | 155 | The encrypt command with `sops` is easy because the `.sops.yaml` configuration file already 156 | points to the age public key based on the path of the target file. As the files to be encrypted 157 | are always divided by cluster, `sops` know which public key to use thanks to that config. 158 | 159 | In addition, the `sops` configuration defines an `encrypted_regex` so it will only encrypt the 160 | `data` and `stringData` attributes, that are only found in Kubernetes secrets. 161 | 162 | Therefore, to encrypt a secret resource so it can be pushed to the repo: 163 | 164 | ```sh 165 | sops -e secret.yaml > secret.enc.yaml 166 | ``` 167 | 168 | Always make sure that the secrets you push to the repo are encrypted! 169 | 170 | It is safe to run this command because `secret-values.yaml` are always git ignored. 171 | 172 | NOTE: make format has to be run because SOPS create yaml files that do not follow the .editorconfig standard. 173 | You can run it manually or the git hook will. 174 | 175 | Alternatively, you can achieve the same (encryption + formatting) through the `encrypt.sh` script: 176 | 177 | ```sh 178 | $ ./scripts/encrypt.sh secret.yaml 179 | secret.enc.yaml 20ms 180 | ✅ Encrypted file saved to secret.enc.yaml 181 | ``` 182 | 183 | ### Decrypt Kubernetes secrets 184 | 185 | With the environment variables loaded (`source .envrc`), you can decrypt specific attributes from the YAML: 186 | 187 | ```sh 188 | $ sops -d --extract '["data"]' secret.yaml 189 | foo: ValueThatWasEncrypted 190 | ``` 191 | 192 | Alternatively, you can decrypt and store the decrypted files with this script: 193 | 194 | ```sh 195 | $ ./scripts/decrypt.sh secret.enc.yaml 196 | ✅ Decrypted file saved to secret.yaml 197 | ``` 198 | 199 | ## Run Kubernetes locally 200 | 201 | Kind allows you to create production-like Kubernetes cluster in your own computer. 202 | 203 | ### Creating local Kubernetes clusters 204 | 205 | See [creating a cluster](https://kind.sigs.k8s.io/docs/user/quick-start/#creating-a-cluster) in Kind's documentation. 206 | 207 | For instance, to create two local clusters 208 | (`production` and `staging` with the `kind-prod` and `kind-staging` contexts respectively) 209 | 210 | ```sh 211 | $ kind create cluster --name crossplane --config kind.config.yaml 212 | $ kind create cluster --name production --config kind.config.yaml 213 | $ kind create cluster --name staging --config kind.config.yaml 214 | $ kind get clusters 215 | crossplane 216 | production 217 | staging 218 | ``` 219 | 220 | To switch between clusters: 221 | 222 | ```sh 223 | # kubectl will connect to the crossplane cluster and create its context 224 | kubectl cluster-info --context kind-crossplane 225 | # kubectl will connect to the production cluster and create its context 226 | kubectl cluster-info --context kind-prod 227 | # kubectl will connect to the staging cluster and create its context 228 | kubectl cluster-info --context kind-staging 229 | ``` 230 | 231 | To view the available contexts: 232 | 233 | ```sh 234 | $ kubectl config get-contexts 235 | CURRENT NAME CLUSTER AUTHINFO NAMESPACE 236 | * kind-staging kind-staging kind-staging flux-system 237 | ``` 238 | 239 | Alternatively, you can use a tool like `kubectx` to manage contexts (`brew install kubectx`): 240 | 241 | ```sh 242 | $ kubectx 243 | kind-crossplane 244 | kind-prod 245 | kind-staging 246 | $ kubectx kind-prod 247 | Switched to context "kind-prod". 248 | ``` 249 | 250 | ### Accessing a local cluster 251 | 252 | You can access the cluster using `kubectl`. Make sure you have selected the proper context. 253 | 254 | For instance: 255 | 256 | ```sh 257 | kubectx kind-staging 258 | kubectl get namespaces 259 | ``` 260 | 261 | ### Using the ingress from a local cluster 262 | 263 | Due to current limitations with `kind`, the local port in which you can access the 264 | ingress controller from the cluster might vary, depending on the node in which 265 | the `ingress-nginx` controller ends up. 266 | 267 | You can check the forwarded ports in `kind.config.yaml`. 268 | 269 | In addition, to ease the management of virtual hostnames you can run `make hosts`, which 270 | will add entries from the exposed services in the cluster into your `/etc/hosts` file. 271 | Once added, you can browse (or port 8081, 8082, depending on the 272 | worker node in which the `ingress-nginx` controller is). 273 | 274 | For instance: 275 | 276 | ### Deleting a local cluster 277 | 278 | To delete a `kind` cluster: 279 | 280 | ```sh 281 | kind delete cluster --name myname 282 | ``` 283 | 284 | ## References 285 | 286 | - [Flux2 Example](https://github.com/fluxcd/flux2-kustomize-helm-example) 287 | - [How to apply GitOps to everything with Crossplane and Flux](https://www.cncf.io/blog/2022/07/26/how-to-apply-gitops-to-everything-with-crossplane-and-flux/) 288 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /infrastructure/configs/base/monitoring/dashboards/cluster.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | }, 13 | { 14 | "datasource": { 15 | "type": "datasource", 16 | "uid": "grafana" 17 | }, 18 | "enable": true, 19 | "iconColor": "red", 20 | "name": "flux events", 21 | "target": { 22 | "limit": 100, 23 | "matchAny": false, 24 | "tags": ["flux"], 25 | "type": "tags" 26 | } 27 | } 28 | ] 29 | }, 30 | "editable": true, 31 | "gnetId": null, 32 | "graphTooltip": 0, 33 | "iteration": 1652337714814, 34 | "links": [], 35 | "panels": [ 36 | { 37 | "datasource": "${DS_PROMETHEUS}", 38 | "description": "", 39 | "fieldConfig": { 40 | "defaults": { 41 | "decimals": 0, 42 | "mappings": [], 43 | "thresholds": { 44 | "mode": "absolute", 45 | "steps": [ 46 | { 47 | "color": "blue", 48 | "value": null 49 | }, 50 | { 51 | "color": "red", 52 | "value": 100 53 | } 54 | ] 55 | }, 56 | "unit": "short" 57 | }, 58 | "overrides": [] 59 | }, 60 | "gridPos": { 61 | "h": 5, 62 | "w": 6, 63 | "x": 0, 64 | "y": 0 65 | }, 66 | "id": 24, 67 | "options": { 68 | "colorMode": "value", 69 | "graphMode": "none", 70 | "justifyMode": "auto", 71 | "orientation": "auto", 72 | "reduceOptions": { 73 | "calcs": ["last"], 74 | "fields": "", 75 | "values": false 76 | }, 77 | "text": {}, 78 | "textMode": "value" 79 | }, 80 | "pluginVersion": "7.5.5", 81 | "targets": [ 82 | { 83 | "exemplar": true, 84 | "expr": "count(gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"True\",kind=~\"Kustomization|HelmRelease\"})\n-\nsum(gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"Deleted\",kind=~\"Kustomization|HelmRelease\"})", 85 | "interval": "", 86 | "legendFormat": "", 87 | "refId": "A" 88 | } 89 | ], 90 | "timeFrom": null, 91 | "timeShift": null, 92 | "title": "Cluster Reconcilers", 93 | "type": "stat" 94 | }, 95 | { 96 | "datasource": "${DS_PROMETHEUS}", 97 | "description": "", 98 | "fieldConfig": { 99 | "defaults": { 100 | "decimals": 0, 101 | "mappings": [], 102 | "thresholds": { 103 | "mode": "absolute", 104 | "steps": [ 105 | { 106 | "color": "red", 107 | "value": null 108 | } 109 | ] 110 | }, 111 | "unit": "short" 112 | }, 113 | "overrides": [] 114 | }, 115 | "gridPos": { 116 | "h": 5, 117 | "w": 6, 118 | "x": 6, 119 | "y": 0 120 | }, 121 | "id": 28, 122 | "options": { 123 | "colorMode": "value", 124 | "graphMode": "area", 125 | "justifyMode": "auto", 126 | "orientation": "auto", 127 | "reduceOptions": { 128 | "calcs": ["last"], 129 | "fields": "", 130 | "values": false 131 | }, 132 | "text": {}, 133 | "textMode": "value" 134 | }, 135 | "pluginVersion": "7.5.5", 136 | "targets": [ 137 | { 138 | "exemplar": true, 139 | "expr": "sum(gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"Kustomization|HelmRelease\"})", 140 | "interval": "", 141 | "legendFormat": "", 142 | "refId": "A" 143 | } 144 | ], 145 | "timeFrom": null, 146 | "timeShift": null, 147 | "title": "Failing Reconcilers", 148 | "type": "stat" 149 | }, 150 | { 151 | "datasource": "${DS_PROMETHEUS}", 152 | "description": "", 153 | "fieldConfig": { 154 | "defaults": { 155 | "decimals": 0, 156 | "mappings": [], 157 | "thresholds": { 158 | "mode": "absolute", 159 | "steps": [ 160 | { 161 | "color": "blue", 162 | "value": null 163 | }, 164 | { 165 | "color": "red", 166 | "value": 100 167 | } 168 | ] 169 | }, 170 | "unit": "short" 171 | }, 172 | "overrides": [] 173 | }, 174 | "gridPos": { 175 | "h": 5, 176 | "w": 6, 177 | "x": 12, 178 | "y": 0 179 | }, 180 | "id": 29, 181 | "options": { 182 | "colorMode": "value", 183 | "graphMode": "none", 184 | "justifyMode": "auto", 185 | "orientation": "auto", 186 | "reduceOptions": { 187 | "calcs": ["last"], 188 | "fields": "", 189 | "values": false 190 | }, 191 | "text": {}, 192 | "textMode": "value" 193 | }, 194 | "pluginVersion": "7.5.5", 195 | "targets": [ 196 | { 197 | "exemplar": true, 198 | "expr": "count(gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"True\",kind=~\"GitRepository|HelmRepository|Bucket\"})\n-\nsum(gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"Deleted\",kind=~\"GitRepository|HelmRepository|Bucket\"})", 199 | "interval": "", 200 | "legendFormat": "", 201 | "refId": "A" 202 | } 203 | ], 204 | "timeFrom": null, 205 | "timeShift": null, 206 | "title": "Kubernetes Manifests Sources", 207 | "type": "stat" 208 | }, 209 | { 210 | "datasource": "${DS_PROMETHEUS}", 211 | "description": "", 212 | "fieldConfig": { 213 | "defaults": { 214 | "decimals": 0, 215 | "mappings": [], 216 | "thresholds": { 217 | "mode": "absolute", 218 | "steps": [ 219 | { 220 | "color": "red", 221 | "value": null 222 | } 223 | ] 224 | }, 225 | "unit": "short" 226 | }, 227 | "overrides": [] 228 | }, 229 | "gridPos": { 230 | "h": 5, 231 | "w": 6, 232 | "x": 18, 233 | "y": 0 234 | }, 235 | "id": 30, 236 | "options": { 237 | "colorMode": "value", 238 | "graphMode": "area", 239 | "justifyMode": "auto", 240 | "orientation": "auto", 241 | "reduceOptions": { 242 | "calcs": ["last"], 243 | "fields": "", 244 | "values": false 245 | }, 246 | "text": {}, 247 | "textMode": "value" 248 | }, 249 | "pluginVersion": "7.5.5", 250 | "targets": [ 251 | { 252 | "exemplar": true, 253 | "expr": "sum(gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"GitRepository|HelmRepository|Bucket\"})", 254 | "interval": "", 255 | "legendFormat": "", 256 | "refId": "A" 257 | } 258 | ], 259 | "timeFrom": null, 260 | "timeShift": null, 261 | "title": "Failing Sources", 262 | "type": "stat" 263 | }, 264 | { 265 | "datasource": "${DS_PROMETHEUS}", 266 | "description": "", 267 | "fieldConfig": { 268 | "defaults": { 269 | "mappings": [], 270 | "thresholds": { 271 | "mode": "absolute", 272 | "steps": [ 273 | { 274 | "color": "green", 275 | "value": null 276 | }, 277 | { 278 | "color": "#EAB839", 279 | "value": 1 280 | }, 281 | { 282 | "color": "red", 283 | "value": 61 284 | } 285 | ] 286 | }, 287 | "unit": "s" 288 | }, 289 | "overrides": [] 290 | }, 291 | "gridPos": { 292 | "h": 4, 293 | "w": 12, 294 | "x": 0, 295 | "y": 5 296 | }, 297 | "id": 8, 298 | "options": { 299 | "displayMode": "gradient", 300 | "minVizHeight": 10, 301 | "minVizWidth": 0, 302 | "orientation": "horizontal", 303 | "reduceOptions": { 304 | "calcs": ["mean"], 305 | "fields": "", 306 | "values": false 307 | }, 308 | "showUnfilled": true, 309 | "text": {} 310 | }, 311 | "pluginVersion": "7.5.5", 312 | "targets": [ 313 | { 314 | "exemplar": true, 315 | "expr": " sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind)", 316 | "interval": "", 317 | "legendFormat": "{{kind}}", 318 | "refId": "A" 319 | } 320 | ], 321 | "timeFrom": null, 322 | "timeShift": null, 323 | "title": "Reconciler ops avg. duration", 324 | "type": "bargauge" 325 | }, 326 | { 327 | "datasource": "${DS_PROMETHEUS}", 328 | "description": "", 329 | "fieldConfig": { 330 | "defaults": { 331 | "mappings": [], 332 | "thresholds": { 333 | "mode": "absolute", 334 | "steps": [ 335 | { 336 | "color": "green", 337 | "value": null 338 | }, 339 | { 340 | "color": "#EAB839", 341 | "value": 1 342 | }, 343 | { 344 | "color": "red", 345 | "value": 61 346 | } 347 | ] 348 | }, 349 | "unit": "s" 350 | }, 351 | "overrides": [] 352 | }, 353 | "gridPos": { 354 | "h": 4, 355 | "w": 12, 356 | "x": 12, 357 | "y": 5 358 | }, 359 | "id": 31, 360 | "options": { 361 | "displayMode": "gradient", 362 | "minVizHeight": 10, 363 | "minVizWidth": 0, 364 | "orientation": "horizontal", 365 | "reduceOptions": { 366 | "calcs": ["mean"], 367 | "fields": "", 368 | "values": false 369 | }, 370 | "showUnfilled": true, 371 | "text": {} 372 | }, 373 | "pluginVersion": "7.5.5", 374 | "targets": [ 375 | { 376 | "exemplar": true, 377 | "expr": " sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind)", 378 | "interval": "", 379 | "legendFormat": "{{kind}}", 380 | "refId": "A" 381 | } 382 | ], 383 | "timeFrom": null, 384 | "timeShift": null, 385 | "title": "Source ops avg. duration", 386 | "type": "bargauge" 387 | }, 388 | { 389 | "collapsed": false, 390 | "datasource": "${DS_PROMETHEUS}", 391 | "gridPos": { 392 | "h": 1, 393 | "w": 24, 394 | "x": 0, 395 | "y": 9 396 | }, 397 | "id": 15, 398 | "panels": [], 399 | "title": "Status", 400 | "type": "row" 401 | }, 402 | { 403 | "datasource": "${DS_PROMETHEUS}", 404 | "description": "", 405 | "fieldConfig": { 406 | "defaults": { 407 | "custom": { 408 | "displayMode": "auto", 409 | "filterable": true, 410 | "inspect": false 411 | }, 412 | "mappings": [ 413 | { 414 | "options": { 415 | "0": { 416 | "text": "Ready" 417 | }, 418 | "1": { 419 | "text": "Not Ready" 420 | } 421 | }, 422 | "type": "value" 423 | } 424 | ], 425 | "thresholds": { 426 | "mode": "absolute", 427 | "steps": [ 428 | { 429 | "color": "blue", 430 | "value": null 431 | }, 432 | { 433 | "color": "blue", 434 | "value": 0 435 | }, 436 | { 437 | "color": "red", 438 | "value": 1 439 | } 440 | ] 441 | } 442 | }, 443 | "overrides": [ 444 | { 445 | "matcher": { 446 | "id": "byName", 447 | "options": "Status" 448 | }, 449 | "properties": [ 450 | { 451 | "id": "custom.displayMode", 452 | "value": "color-background" 453 | } 454 | ] 455 | } 456 | ] 457 | }, 458 | "gridPos": { 459 | "h": 11, 460 | "w": 12, 461 | "x": 0, 462 | "y": 10 463 | }, 464 | "id": 33, 465 | "options": { 466 | "footer": { 467 | "fields": "", 468 | "reducer": ["sum"], 469 | "show": false 470 | }, 471 | "showHeader": true, 472 | "sortBy": [ 473 | { 474 | "desc": true, 475 | "displayName": "Status" 476 | } 477 | ] 478 | }, 479 | "pluginVersion": "7.5.5", 480 | "targets": [ 481 | { 482 | "exemplar": true, 483 | "expr": "gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"Kustomization|HelmRelease\"}", 484 | "format": "table", 485 | "instant": true, 486 | "interval": "", 487 | "legendFormat": "", 488 | "refId": "A" 489 | } 490 | ], 491 | "timeFrom": null, 492 | "timeShift": null, 493 | "title": "Cluster reconciliation readiness ", 494 | "transformations": [ 495 | { 496 | "id": "organize", 497 | "options": { 498 | "excludeByName": { 499 | "Time": true, 500 | "__name__": true, 501 | "app": true, 502 | "container": true, 503 | "endpoint": true, 504 | "exported_namespace": false, 505 | "instance": true, 506 | "job": true, 507 | "kubernetes_namespace": true, 508 | "kubernetes_pod_name": true, 509 | "namespace": true, 510 | "pod": true, 511 | "pod_template_hash": true, 512 | "status": true, 513 | "type": true 514 | }, 515 | "indexByName": {}, 516 | "renameByName": { 517 | "Value": "Status", 518 | "exported_namespace": "Namespace", 519 | "kind": "Kind", 520 | "name": "Name", 521 | "namespace": "Operator Namespace" 522 | } 523 | } 524 | } 525 | ], 526 | "type": "table" 527 | }, 528 | { 529 | "datasource": "${DS_PROMETHEUS}", 530 | "description": "", 531 | "fieldConfig": { 532 | "defaults": { 533 | "custom": { 534 | "displayMode": "auto", 535 | "filterable": true, 536 | "inspect": false 537 | }, 538 | "mappings": [ 539 | { 540 | "options": { 541 | "0": { 542 | "text": "Ready" 543 | }, 544 | "1": { 545 | "text": "Not Ready" 546 | } 547 | }, 548 | "type": "value" 549 | } 550 | ], 551 | "thresholds": { 552 | "mode": "absolute", 553 | "steps": [ 554 | { 555 | "color": "blue", 556 | "value": null 557 | }, 558 | { 559 | "color": "blue", 560 | "value": 0 561 | }, 562 | { 563 | "color": "red", 564 | "value": 1 565 | } 566 | ] 567 | } 568 | }, 569 | "overrides": [ 570 | { 571 | "matcher": { 572 | "id": "byName", 573 | "options": "Status" 574 | }, 575 | "properties": [ 576 | { 577 | "id": "custom.displayMode", 578 | "value": "color-background" 579 | } 580 | ] 581 | } 582 | ] 583 | }, 584 | "gridPos": { 585 | "h": 11, 586 | "w": 12, 587 | "x": 12, 588 | "y": 10 589 | }, 590 | "id": 34, 591 | "options": { 592 | "footer": { 593 | "fields": "", 594 | "reducer": ["sum"], 595 | "show": false 596 | }, 597 | "showHeader": true, 598 | "sortBy": [ 599 | { 600 | "desc": true, 601 | "displayName": "Status" 602 | } 603 | ] 604 | }, 605 | "pluginVersion": "7.5.5", 606 | "targets": [ 607 | { 608 | "exemplar": true, 609 | "expr": "gotk_reconcile_condition{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",type=\"Ready\",status=\"False\",kind=~\"GitRepository|HelmRepository|Bucket\"}", 610 | "format": "table", 611 | "instant": true, 612 | "interval": "", 613 | "legendFormat": "", 614 | "refId": "A" 615 | } 616 | ], 617 | "timeFrom": null, 618 | "timeShift": null, 619 | "title": "Source acquisition readiness ", 620 | "transformations": [ 621 | { 622 | "id": "organize", 623 | "options": { 624 | "excludeByName": { 625 | "Time": true, 626 | "__name__": true, 627 | "app": true, 628 | "container": true, 629 | "endpoint": true, 630 | "exported_namespace": false, 631 | "instance": true, 632 | "job": true, 633 | "kubernetes_namespace": true, 634 | "kubernetes_pod_name": true, 635 | "namespace": true, 636 | "pod": true, 637 | "pod_template_hash": true, 638 | "status": true, 639 | "type": true 640 | }, 641 | "indexByName": {}, 642 | "renameByName": { 643 | "Value": "Status", 644 | "exported_namespace": "Namespace", 645 | "kind": "Kind", 646 | "name": "Name", 647 | "namespace": "Operator Namespace" 648 | } 649 | } 650 | } 651 | ], 652 | "type": "table" 653 | }, 654 | { 655 | "collapsed": false, 656 | "datasource": "${DS_PROMETHEUS}", 657 | "gridPos": { 658 | "h": 1, 659 | "w": 24, 660 | "x": 0, 661 | "y": 21 662 | }, 663 | "id": 17, 664 | "panels": [], 665 | "title": "Timing", 666 | "type": "row" 667 | }, 668 | { 669 | "aliasColors": {}, 670 | "bars": false, 671 | "dashLength": 10, 672 | "dashes": false, 673 | "datasource": "${DS_PROMETHEUS}", 674 | "description": "", 675 | "fieldConfig": { 676 | "defaults": {}, 677 | "overrides": [] 678 | }, 679 | "fill": 1, 680 | "fillGradient": 0, 681 | "gridPos": { 682 | "h": 8, 683 | "w": 24, 684 | "x": 0, 685 | "y": 22 686 | }, 687 | "hiddenSeries": false, 688 | "id": 27, 689 | "legend": { 690 | "alignAsTable": true, 691 | "avg": true, 692 | "current": false, 693 | "hideEmpty": true, 694 | "hideZero": true, 695 | "max": false, 696 | "min": false, 697 | "rightSide": true, 698 | "show": true, 699 | "total": false, 700 | "values": true 701 | }, 702 | "lines": true, 703 | "linewidth": 1, 704 | "nullPointMode": "null", 705 | "options": { 706 | "alertThreshold": true 707 | }, 708 | "percentage": false, 709 | "pluginVersion": "7.5.5", 710 | "pointradius": 2, 711 | "points": false, 712 | "renderer": "flot", 713 | "seriesOverrides": [], 714 | "spaceLength": 10, 715 | "stack": false, 716 | "steppedLine": false, 717 | "targets": [ 718 | { 719 | "exemplar": true, 720 | "expr": " sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind, name)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"Kustomization|HelmRelease\"}[5m])) by (kind, name)", 721 | "hide": false, 722 | "interval": "", 723 | "legendFormat": "{{kind}}/{{name}}", 724 | "refId": "B" 725 | } 726 | ], 727 | "thresholds": [], 728 | "timeFrom": null, 729 | "timeRegions": [], 730 | "timeShift": null, 731 | "title": "Cluster reconciliation duration", 732 | "tooltip": { 733 | "shared": true, 734 | "sort": 0, 735 | "value_type": "individual" 736 | }, 737 | "type": "graph", 738 | "xaxis": { 739 | "buckets": null, 740 | "mode": "time", 741 | "name": null, 742 | "show": true, 743 | "values": [] 744 | }, 745 | "yaxes": [ 746 | { 747 | "format": "s", 748 | "label": null, 749 | "logBase": 1, 750 | "max": null, 751 | "min": null, 752 | "show": true 753 | }, 754 | { 755 | "format": "short", 756 | "label": null, 757 | "logBase": 1, 758 | "max": null, 759 | "min": null, 760 | "show": true 761 | } 762 | ], 763 | "yaxis": { 764 | "align": false, 765 | "alignLevel": null 766 | } 767 | }, 768 | { 769 | "aliasColors": {}, 770 | "bars": false, 771 | "dashLength": 10, 772 | "dashes": false, 773 | "datasource": "${DS_PROMETHEUS}", 774 | "description": "", 775 | "fieldConfig": { 776 | "defaults": {}, 777 | "overrides": [] 778 | }, 779 | "fill": 1, 780 | "fillGradient": 0, 781 | "gridPos": { 782 | "h": 8, 783 | "w": 24, 784 | "x": 0, 785 | "y": 30 786 | }, 787 | "hiddenSeries": false, 788 | "id": 35, 789 | "legend": { 790 | "alignAsTable": true, 791 | "avg": true, 792 | "current": false, 793 | "hideEmpty": true, 794 | "hideZero": true, 795 | "max": false, 796 | "min": false, 797 | "rightSide": true, 798 | "show": true, 799 | "total": false, 800 | "values": true 801 | }, 802 | "lines": true, 803 | "linewidth": 1, 804 | "nullPointMode": "null", 805 | "options": { 806 | "alertThreshold": true 807 | }, 808 | "percentage": false, 809 | "pluginVersion": "7.5.5", 810 | "pointradius": 2, 811 | "points": false, 812 | "renderer": "flot", 813 | "seriesOverrides": [], 814 | "spaceLength": 10, 815 | "stack": false, 816 | "steppedLine": false, 817 | "targets": [ 818 | { 819 | "exemplar": true, 820 | "expr": " sum(rate(gotk_reconcile_duration_seconds_sum{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind, name)\n/\n sum(rate(gotk_reconcile_duration_seconds_count{namespace=~\"$operator_namespace\",exported_namespace=~\"$namespace\",kind=~\"GitRepository|HelmRepository|Bucket\"}[5m])) by (kind, name)", 821 | "hide": false, 822 | "interval": "", 823 | "legendFormat": "{{kind}}/{{name}}", 824 | "refId": "B" 825 | } 826 | ], 827 | "thresholds": [], 828 | "timeFrom": null, 829 | "timeRegions": [], 830 | "timeShift": null, 831 | "title": "Source acquisition duration", 832 | "tooltip": { 833 | "shared": true, 834 | "sort": 0, 835 | "value_type": "individual" 836 | }, 837 | "type": "graph", 838 | "xaxis": { 839 | "buckets": null, 840 | "mode": "time", 841 | "name": null, 842 | "show": true, 843 | "values": [] 844 | }, 845 | "yaxes": [ 846 | { 847 | "format": "s", 848 | "label": null, 849 | "logBase": 1, 850 | "max": null, 851 | "min": null, 852 | "show": true 853 | }, 854 | { 855 | "format": "short", 856 | "label": null, 857 | "logBase": 1, 858 | "max": null, 859 | "min": null, 860 | "show": true 861 | } 862 | ], 863 | "yaxis": { 864 | "align": false, 865 | "alignLevel": null 866 | } 867 | } 868 | ], 869 | "refresh": "30s", 870 | "schemaVersion": 36, 871 | "style": "light", 872 | "tags": ["flux"], 873 | "templating": { 874 | "list": [ 875 | { 876 | "allValue": "", 877 | "current": { 878 | "selected": true, 879 | "text": ["All"], 880 | "value": ["$__all"] 881 | }, 882 | "datasource": "$DS_PROMETHEUS", 883 | "definition": "label_values(gotk_reconcile_condition, namespace)", 884 | "description": null, 885 | "error": null, 886 | "hide": 0, 887 | "includeAll": true, 888 | "label": null, 889 | "multi": true, 890 | "name": "operator_namespace", 891 | "options": [], 892 | "query": { 893 | "query": "label_values(gotk_reconcile_condition, namespace)", 894 | "refId": "StandardVariableQuery" 895 | }, 896 | "refresh": 2, 897 | "regex": "", 898 | "skipUrlSync": false, 899 | "sort": 5, 900 | "tagValuesQuery": "", 901 | "tags": [], 902 | "tagsQuery": "", 903 | "type": "query", 904 | "useTags": false 905 | }, 906 | { 907 | "allValue": null, 908 | "current": { 909 | "selected": true, 910 | "tags": [], 911 | "text": ["All"], 912 | "value": ["$__all"] 913 | }, 914 | "datasource": "$DS_PROMETHEUS", 915 | "definition": "label_values(gotk_reconcile_condition, exported_namespace)", 916 | "description": null, 917 | "error": null, 918 | "hide": 0, 919 | "includeAll": true, 920 | "label": null, 921 | "multi": true, 922 | "name": "namespace", 923 | "options": [], 924 | "query": { 925 | "query": "label_values(gotk_reconcile_condition, exported_namespace)", 926 | "refId": "StandardVariableQuery" 927 | }, 928 | "refresh": 2, 929 | "regex": "", 930 | "skipUrlSync": false, 931 | "sort": 0, 932 | "tagValuesQuery": "", 933 | "tags": [], 934 | "tagsQuery": "", 935 | "type": "query", 936 | "useTags": false 937 | }, 938 | { 939 | "current": { 940 | "selected": false, 941 | "text": "Prometheus", 942 | "value": "Prometheus" 943 | }, 944 | "hide": 0, 945 | "includeAll": false, 946 | "label": "Datasource", 947 | "multi": false, 948 | "name": "DS_PROMETHEUS", 949 | "options": [], 950 | "query": "prometheus", 951 | "refresh": 1, 952 | "regex": "", 953 | "skipUrlSync": false, 954 | "type": "datasource" 955 | } 956 | ] 957 | }, 958 | "time": { 959 | "from": "now-15m", 960 | "to": "now" 961 | }, 962 | "timepicker": { 963 | "refresh_intervals": [ 964 | "10s", 965 | "30s", 966 | "1m", 967 | "5m", 968 | "15m", 969 | "30m", 970 | "1h", 971 | "2h", 972 | "1d" 973 | ] 974 | }, 975 | "title": "Flux Cluster Stats", 976 | "uid": "flux-cluster", 977 | "version": 3 978 | } 979 | -------------------------------------------------------------------------------- /infrastructure/configs/base/monitoring/dashboards/control-plane.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "target": { 12 | "limit": 100, 13 | "matchAny": false, 14 | "tags": [], 15 | "type": "dashboard" 16 | }, 17 | "type": "dashboard" 18 | }, 19 | { 20 | "datasource": { 21 | "type": "datasource", 22 | "uid": "grafana" 23 | }, 24 | "enable": true, 25 | "iconColor": "red", 26 | "name": "flux events", 27 | "target": { 28 | "limit": 100, 29 | "matchAny": false, 30 | "tags": ["flux"], 31 | "type": "tags" 32 | } 33 | } 34 | ] 35 | }, 36 | "editable": true, 37 | "fiscalYearStartMonth": 0, 38 | "gnetId": null, 39 | "graphTooltip": 0, 40 | "id": 29, 41 | "iteration": 1639041352219, 42 | "links": [], 43 | "liveNow": false, 44 | "panels": [ 45 | { 46 | "datasource": "${DS_PROMETHEUS}", 47 | "description": "", 48 | "fieldConfig": { 49 | "defaults": { 50 | "decimals": 0, 51 | "mappings": [], 52 | "thresholds": { 53 | "mode": "absolute", 54 | "steps": [ 55 | { 56 | "color": "blue", 57 | "value": null 58 | }, 59 | { 60 | "color": "red", 61 | "value": 100 62 | } 63 | ] 64 | }, 65 | "unit": "short" 66 | }, 67 | "overrides": [] 68 | }, 69 | "gridPos": { 70 | "h": 5, 71 | "w": 6, 72 | "x": 0, 73 | "y": 0 74 | }, 75 | "id": 24, 76 | "options": { 77 | "colorMode": "value", 78 | "graphMode": "none", 79 | "justifyMode": "auto", 80 | "orientation": "auto", 81 | "reduceOptions": { 82 | "calcs": ["last"], 83 | "fields": "", 84 | "values": false 85 | }, 86 | "text": {}, 87 | "textMode": "value" 88 | }, 89 | "pluginVersion": "8.2.3", 90 | "targets": [ 91 | { 92 | "expr": "sum(go_info{namespace=\"$namespace\",pod=~\".*-controller-.*\"})", 93 | "interval": "", 94 | "legendFormat": "pods", 95 | "refId": "A" 96 | } 97 | ], 98 | "timeFrom": null, 99 | "timeShift": null, 100 | "title": "Controllers", 101 | "type": "stat" 102 | }, 103 | { 104 | "datasource": "${DS_PROMETHEUS}", 105 | "description": "", 106 | "fieldConfig": { 107 | "defaults": { 108 | "mappings": [], 109 | "thresholds": { 110 | "mode": "absolute", 111 | "steps": [ 112 | { 113 | "color": "blue", 114 | "value": null 115 | }, 116 | { 117 | "color": "#EAB839", 118 | "value": 50 119 | }, 120 | { 121 | "color": "red", 122 | "value": 100 123 | } 124 | ] 125 | }, 126 | "unit": "s" 127 | }, 128 | "overrides": [] 129 | }, 130 | "gridPos": { 131 | "h": 5, 132 | "w": 6, 133 | "x": 6, 134 | "y": 0 135 | }, 136 | "id": 23, 137 | "options": { 138 | "colorMode": "value", 139 | "graphMode": "area", 140 | "justifyMode": "auto", 141 | "orientation": "auto", 142 | "reduceOptions": { 143 | "calcs": ["lastNotNull"], 144 | "fields": "", 145 | "values": false 146 | }, 147 | "text": {}, 148 | "textMode": "auto" 149 | }, 150 | "pluginVersion": "8.2.3", 151 | "targets": [ 152 | { 153 | "expr": "max(workqueue_longest_running_processor_seconds{namespace=\"$namespace\",pod=~\".*-controller-.*\"})", 154 | "hide": false, 155 | "interval": "", 156 | "legendFormat": "seconds", 157 | "refId": "B" 158 | } 159 | ], 160 | "timeFrom": null, 161 | "timeShift": null, 162 | "title": "Max Work Queue", 163 | "type": "stat" 164 | }, 165 | { 166 | "datasource": "${DS_PROMETHEUS}", 167 | "description": "", 168 | "fieldConfig": { 169 | "defaults": { 170 | "mappings": [], 171 | "thresholds": { 172 | "mode": "absolute", 173 | "steps": [ 174 | { 175 | "color": "blue", 176 | "value": null 177 | }, 178 | { 179 | "color": "#EAB839", 180 | "value": 500000000 181 | }, 182 | { 183 | "color": "red", 184 | "value": 900000000 185 | } 186 | ] 187 | }, 188 | "unit": "decbits" 189 | }, 190 | "overrides": [] 191 | }, 192 | "gridPos": { 193 | "h": 5, 194 | "w": 6, 195 | "x": 12, 196 | "y": 0 197 | }, 198 | "id": 25, 199 | "options": { 200 | "orientation": "auto", 201 | "reduceOptions": { 202 | "calcs": ["lastNotNull"], 203 | "fields": "", 204 | "values": false 205 | }, 206 | "showThresholdLabels": false, 207 | "showThresholdMarkers": true, 208 | "text": {} 209 | }, 210 | "pluginVersion": "8.2.3", 211 | "targets": [ 212 | { 213 | "expr": "sum(go_memstats_alloc_bytes{namespace=\"$namespace\",pod=~\".*-controller-.*\"})", 214 | "interval": "", 215 | "legendFormat": "", 216 | "refId": "A" 217 | } 218 | ], 219 | "timeFrom": null, 220 | "timeShift": null, 221 | "title": "Memory", 222 | "type": "gauge" 223 | }, 224 | { 225 | "datasource": "${DS_PROMETHEUS}", 226 | "description": "", 227 | "fieldConfig": { 228 | "defaults": { 229 | "mappings": [], 230 | "thresholds": { 231 | "mode": "absolute", 232 | "steps": [ 233 | { 234 | "color": "blue", 235 | "value": null 236 | }, 237 | { 238 | "color": "#EAB839", 239 | "value": 50 240 | }, 241 | { 242 | "color": "red", 243 | "value": 100 244 | } 245 | ] 246 | } 247 | }, 248 | "overrides": [] 249 | }, 250 | "gridPos": { 251 | "h": 5, 252 | "w": 6, 253 | "x": 18, 254 | "y": 0 255 | }, 256 | "id": 26, 257 | "options": { 258 | "colorMode": "value", 259 | "graphMode": "area", 260 | "justifyMode": "auto", 261 | "orientation": "auto", 262 | "reduceOptions": { 263 | "calcs": ["mean"], 264 | "fields": "", 265 | "values": false 266 | }, 267 | "text": {}, 268 | "textMode": "auto" 269 | }, 270 | "pluginVersion": "8.2.3", 271 | "targets": [ 272 | { 273 | "expr": "sum(rate(rest_client_requests_total{namespace=\"$namespace\",pod=~\".*-controller-.*\"}[1m]))", 274 | "interval": "", 275 | "legendFormat": "requests", 276 | "refId": "A" 277 | } 278 | ], 279 | "timeFrom": null, 280 | "timeShift": null, 281 | "title": "API Requests", 282 | "type": "stat" 283 | }, 284 | { 285 | "aliasColors": {}, 286 | "bars": false, 287 | "dashLength": 10, 288 | "dashes": false, 289 | "datasource": "${DS_PROMETHEUS}", 290 | "decimals": null, 291 | "description": "", 292 | "fill": 1, 293 | "fillGradient": 0, 294 | "gridPos": { 295 | "h": 8, 296 | "w": 24, 297 | "x": 0, 298 | "y": 5 299 | }, 300 | "hiddenSeries": false, 301 | "id": 21, 302 | "legend": { 303 | "alignAsTable": true, 304 | "avg": true, 305 | "current": true, 306 | "max": false, 307 | "min": false, 308 | "rightSide": false, 309 | "show": true, 310 | "total": false, 311 | "values": true 312 | }, 313 | "lines": true, 314 | "linewidth": 1, 315 | "nullPointMode": "null", 316 | "options": { 317 | "alertThreshold": true 318 | }, 319 | "percentage": false, 320 | "pluginVersion": "8.2.3", 321 | "pointradius": 2, 322 | "points": false, 323 | "renderer": "flot", 324 | "seriesOverrides": [], 325 | "spaceLength": 10, 326 | "stack": false, 327 | "steppedLine": false, 328 | "targets": [ 329 | { 330 | "expr": "sum(rate(rest_client_requests_total{namespace=\"$namespace\"}[1m]))", 331 | "hide": false, 332 | "interval": "", 333 | "legendFormat": "total", 334 | "refId": "A" 335 | }, 336 | { 337 | "expr": "sum(rate(rest_client_requests_total{namespace=\"$namespace\",code!~\"2..\"}[1m]))", 338 | "hide": false, 339 | "interval": "", 340 | "legendFormat": "errors", 341 | "refId": "B" 342 | } 343 | ], 344 | "thresholds": [], 345 | "timeFrom": null, 346 | "timeRegions": [], 347 | "timeShift": null, 348 | "title": "Kubernetes API Requests", 349 | "tooltip": { 350 | "shared": true, 351 | "sort": 0, 352 | "value_type": "individual" 353 | }, 354 | "type": "graph", 355 | "xaxis": { 356 | "buckets": null, 357 | "mode": "time", 358 | "name": null, 359 | "show": true, 360 | "values": [] 361 | }, 362 | "yaxes": [ 363 | { 364 | "$$hashKey": "object:912", 365 | "decimals": null, 366 | "format": "short", 367 | "label": "", 368 | "logBase": 1, 369 | "max": null, 370 | "min": null, 371 | "show": true 372 | }, 373 | { 374 | "$$hashKey": "object:913", 375 | "format": "short", 376 | "label": null, 377 | "logBase": 1, 378 | "max": null, 379 | "min": null, 380 | "show": true 381 | } 382 | ], 383 | "yaxis": { 384 | "align": false, 385 | "alignLevel": null 386 | } 387 | }, 388 | { 389 | "collapsed": false, 390 | "datasource": "${DS_PROMETHEUS}", 391 | "fieldConfig": { 392 | "defaults": {}, 393 | "overrides": [] 394 | }, 395 | "gridPos": { 396 | "h": 1, 397 | "w": 24, 398 | "x": 0, 399 | "y": 13 400 | }, 401 | "id": 15, 402 | "panels": [], 403 | "title": "Resource Usage", 404 | "type": "row" 405 | }, 406 | { 407 | "aliasColors": {}, 408 | "bars": false, 409 | "dashLength": 10, 410 | "dashes": false, 411 | "datasource": "${DS_PROMETHEUS}", 412 | "fill": 1, 413 | "fillGradient": 0, 414 | "gridPos": { 415 | "h": 11, 416 | "w": 12, 417 | "x": 0, 418 | "y": 14 419 | }, 420 | "hiddenSeries": false, 421 | "id": 11, 422 | "legend": { 423 | "alignAsTable": true, 424 | "avg": true, 425 | "current": true, 426 | "max": false, 427 | "min": false, 428 | "show": true, 429 | "total": false, 430 | "values": true 431 | }, 432 | "lines": true, 433 | "linewidth": 1, 434 | "nullPointMode": "null", 435 | "options": { 436 | "alertThreshold": true 437 | }, 438 | "percentage": false, 439 | "pluginVersion": "8.2.3", 440 | "pointradius": 2, 441 | "points": false, 442 | "renderer": "flot", 443 | "seriesOverrides": [], 444 | "spaceLength": 10, 445 | "stack": true, 446 | "steppedLine": false, 447 | "targets": [ 448 | { 449 | "expr": "rate(process_cpu_seconds_total{namespace=\"$namespace\",pod=~\".*-controller-.*\"}[1m])", 450 | "interval": "", 451 | "legendFormat": "{{pod}}", 452 | "refId": "A" 453 | } 454 | ], 455 | "thresholds": [], 456 | "timeFrom": null, 457 | "timeRegions": [], 458 | "timeShift": null, 459 | "title": "CPU Usage", 460 | "tooltip": { 461 | "shared": true, 462 | "sort": 0, 463 | "value_type": "individual" 464 | }, 465 | "type": "graph", 466 | "xaxis": { 467 | "buckets": null, 468 | "mode": "time", 469 | "name": null, 470 | "show": true, 471 | "values": [] 472 | }, 473 | "yaxes": [ 474 | { 475 | "$$hashKey": "object:93", 476 | "format": "s", 477 | "label": null, 478 | "logBase": 1, 479 | "max": null, 480 | "min": null, 481 | "show": true 482 | }, 483 | { 484 | "$$hashKey": "object:94", 485 | "format": "short", 486 | "label": null, 487 | "logBase": 1, 488 | "max": null, 489 | "min": null, 490 | "show": true 491 | } 492 | ], 493 | "yaxis": { 494 | "align": false, 495 | "alignLevel": null 496 | } 497 | }, 498 | { 499 | "aliasColors": {}, 500 | "bars": false, 501 | "dashLength": 10, 502 | "dashes": false, 503 | "datasource": "${DS_PROMETHEUS}", 504 | "fill": 1, 505 | "fillGradient": 0, 506 | "gridPos": { 507 | "h": 11, 508 | "w": 12, 509 | "x": 12, 510 | "y": 14 511 | }, 512 | "hiddenSeries": false, 513 | "id": 13, 514 | "legend": { 515 | "alignAsTable": true, 516 | "avg": true, 517 | "current": true, 518 | "max": false, 519 | "min": false, 520 | "show": true, 521 | "total": false, 522 | "values": true 523 | }, 524 | "lines": true, 525 | "linewidth": 1, 526 | "nullPointMode": "null", 527 | "options": { 528 | "alertThreshold": true 529 | }, 530 | "percentage": false, 531 | "pluginVersion": "8.2.3", 532 | "pointradius": 2, 533 | "points": false, 534 | "renderer": "flot", 535 | "seriesOverrides": [], 536 | "spaceLength": 10, 537 | "stack": true, 538 | "steppedLine": false, 539 | "targets": [ 540 | { 541 | "expr": "sum(container_memory_working_set_bytes{namespace=\"$namespace\",container!=\"POD\",container!=\"\",pod=~\".*-controller-.*\"}) by (pod)", 542 | "hide": false, 543 | "interval": "", 544 | "legendFormat": "{{pod}}", 545 | "refId": "A" 546 | } 547 | ], 548 | "thresholds": [], 549 | "timeFrom": null, 550 | "timeRegions": [], 551 | "timeShift": null, 552 | "title": "Memory Usage", 553 | "tooltip": { 554 | "shared": true, 555 | "sort": 0, 556 | "value_type": "individual" 557 | }, 558 | "type": "graph", 559 | "xaxis": { 560 | "buckets": null, 561 | "mode": "time", 562 | "name": null, 563 | "show": true, 564 | "values": [] 565 | }, 566 | "yaxes": [ 567 | { 568 | "$$hashKey": "object:93", 569 | "decimals": 0, 570 | "format": "bytes", 571 | "label": "", 572 | "logBase": 1, 573 | "max": null, 574 | "min": null, 575 | "show": true 576 | }, 577 | { 578 | "$$hashKey": "object:94", 579 | "format": "short", 580 | "label": null, 581 | "logBase": 1, 582 | "max": null, 583 | "min": null, 584 | "show": true 585 | } 586 | ], 587 | "yaxis": { 588 | "align": false, 589 | "alignLevel": null 590 | } 591 | }, 592 | { 593 | "collapsed": false, 594 | "datasource": "${DS_PROMETHEUS}", 595 | "fieldConfig": { 596 | "defaults": {}, 597 | "overrides": [] 598 | }, 599 | "gridPos": { 600 | "h": 1, 601 | "w": 24, 602 | "x": 0, 603 | "y": 25 604 | }, 605 | "id": 17, 606 | "panels": [], 607 | "title": "Reconciliation Stats", 608 | "type": "row" 609 | }, 610 | { 611 | "aliasColors": {}, 612 | "bars": false, 613 | "dashLength": 10, 614 | "dashes": false, 615 | "datasource": "${DS_PROMETHEUS}", 616 | "fill": 1, 617 | "fillGradient": 0, 618 | "gridPos": { 619 | "h": 8, 620 | "w": 24, 621 | "x": 0, 622 | "y": 26 623 | }, 624 | "hiddenSeries": false, 625 | "id": 27, 626 | "legend": { 627 | "alignAsTable": true, 628 | "avg": true, 629 | "current": true, 630 | "max": false, 631 | "min": false, 632 | "rightSide": false, 633 | "show": true, 634 | "total": false, 635 | "values": true 636 | }, 637 | "lines": true, 638 | "linewidth": 1, 639 | "nullPointMode": "null", 640 | "options": { 641 | "alertThreshold": true 642 | }, 643 | "percentage": false, 644 | "pluginVersion": "8.2.3", 645 | "pointradius": 2, 646 | "points": false, 647 | "renderer": "flot", 648 | "seriesOverrides": [], 649 | "spaceLength": 10, 650 | "stack": false, 651 | "steppedLine": false, 652 | "targets": [ 653 | { 654 | "expr": "workqueue_longest_running_processor_seconds{name=\"kustomization\"}", 655 | "hide": false, 656 | "interval": "", 657 | "legendFormat": "kustomizations", 658 | "refId": "B" 659 | } 660 | ], 661 | "thresholds": [], 662 | "timeFrom": null, 663 | "timeRegions": [], 664 | "timeShift": null, 665 | "title": "Cluster Reconciliation Duration", 666 | "tooltip": { 667 | "shared": true, 668 | "sort": 0, 669 | "value_type": "individual" 670 | }, 671 | "type": "graph", 672 | "xaxis": { 673 | "buckets": null, 674 | "mode": "time", 675 | "name": null, 676 | "show": true, 677 | "values": [] 678 | }, 679 | "yaxes": [ 680 | { 681 | "$$hashKey": "object:912", 682 | "format": "s", 683 | "label": null, 684 | "logBase": 1, 685 | "max": null, 686 | "min": null, 687 | "show": true 688 | }, 689 | { 690 | "$$hashKey": "object:913", 691 | "format": "short", 692 | "label": null, 693 | "logBase": 1, 694 | "max": null, 695 | "min": null, 696 | "show": true 697 | } 698 | ], 699 | "yaxis": { 700 | "align": false, 701 | "alignLevel": null 702 | } 703 | }, 704 | { 705 | "aliasColors": {}, 706 | "bars": true, 707 | "dashLength": 10, 708 | "dashes": false, 709 | "datasource": "${DS_PROMETHEUS}", 710 | "decimals": 2, 711 | "description": "", 712 | "fill": 1, 713 | "fillGradient": 0, 714 | "gridPos": { 715 | "h": 9, 716 | "w": 12, 717 | "x": 0, 718 | "y": 34 719 | }, 720 | "hiddenSeries": false, 721 | "id": 2, 722 | "legend": { 723 | "alignAsTable": true, 724 | "avg": true, 725 | "current": true, 726 | "max": false, 727 | "min": false, 728 | "rightSide": false, 729 | "show": true, 730 | "total": false, 731 | "values": true 732 | }, 733 | "lines": false, 734 | "linewidth": 1, 735 | "nullPointMode": "null", 736 | "percentage": false, 737 | "pluginVersion": "8.2.3", 738 | "pointradius": 2, 739 | "points": false, 740 | "renderer": "flot", 741 | "seriesOverrides": [], 742 | "spaceLength": 10, 743 | "stack": false, 744 | "steppedLine": true, 745 | "targets": [ 746 | { 747 | "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"kustomization\",result!=\"error\"}[1m])) by (controller)", 748 | "format": "time_series", 749 | "interval": "", 750 | "legendFormat": "successful reconciliations ", 751 | "refId": "A" 752 | }, 753 | { 754 | "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"kustomization\",result=\"error\"}[1m])) by (controller)", 755 | "format": "time_series", 756 | "interval": "", 757 | "legendFormat": "failed reconciliations ", 758 | "refId": "B" 759 | } 760 | ], 761 | "thresholds": [], 762 | "timeFrom": null, 763 | "timeRegions": [], 764 | "timeShift": null, 765 | "title": "Cluster Reconciliations ops/min", 766 | "tooltip": { 767 | "shared": true, 768 | "sort": 0, 769 | "value_type": "individual" 770 | }, 771 | "type": "graph", 772 | "xaxis": { 773 | "buckets": null, 774 | "mode": "time", 775 | "name": null, 776 | "show": true, 777 | "values": [] 778 | }, 779 | "yaxes": [ 780 | { 781 | "$$hashKey": "object:1171", 782 | "format": "opm", 783 | "label": null, 784 | "logBase": 1, 785 | "max": null, 786 | "min": null, 787 | "show": true 788 | }, 789 | { 790 | "$$hashKey": "object:1172", 791 | "format": "short", 792 | "label": null, 793 | "logBase": 1, 794 | "max": null, 795 | "min": null, 796 | "show": true 797 | } 798 | ], 799 | "yaxis": { 800 | "align": false, 801 | "alignLevel": null 802 | } 803 | }, 804 | { 805 | "aliasColors": {}, 806 | "bars": true, 807 | "dashLength": 10, 808 | "dashes": false, 809 | "datasource": "${DS_PROMETHEUS}", 810 | "decimals": 2, 811 | "description": "", 812 | "fill": 1, 813 | "fillGradient": 0, 814 | "gridPos": { 815 | "h": 9, 816 | "w": 12, 817 | "x": 12, 818 | "y": 34 819 | }, 820 | "hiddenSeries": false, 821 | "id": 4, 822 | "legend": { 823 | "alignAsTable": true, 824 | "avg": true, 825 | "current": true, 826 | "max": false, 827 | "min": false, 828 | "rightSide": false, 829 | "show": true, 830 | "total": false, 831 | "values": true 832 | }, 833 | "lines": false, 834 | "linewidth": 1, 835 | "nullPointMode": "null", 836 | "percentage": false, 837 | "pluginVersion": "8.2.3", 838 | "pointradius": 2, 839 | "points": false, 840 | "renderer": "flot", 841 | "seriesOverrides": [], 842 | "spaceLength": 10, 843 | "stack": false, 844 | "steppedLine": true, 845 | "targets": [ 846 | { 847 | "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"gitrepository\",result!=\"error\"}[1m]))", 848 | "format": "time_series", 849 | "interval": "", 850 | "legendFormat": "successful git pulls", 851 | "refId": "A" 852 | }, 853 | { 854 | "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"gitrepository\",result=\"error\"}[1m]))", 855 | "format": "time_series", 856 | "interval": "", 857 | "legendFormat": "failed git pulls", 858 | "refId": "B" 859 | } 860 | ], 861 | "thresholds": [], 862 | "timeFrom": null, 863 | "timeRegions": [], 864 | "timeShift": null, 865 | "title": "Git Sources ops/min", 866 | "tooltip": { 867 | "shared": true, 868 | "sort": 0, 869 | "value_type": "individual" 870 | }, 871 | "type": "graph", 872 | "xaxis": { 873 | "buckets": null, 874 | "mode": "time", 875 | "name": null, 876 | "show": true, 877 | "values": [] 878 | }, 879 | "yaxes": [ 880 | { 881 | "$$hashKey": "object:285", 882 | "format": "opm", 883 | "label": null, 884 | "logBase": 1, 885 | "max": null, 886 | "min": null, 887 | "show": true 888 | }, 889 | { 890 | "$$hashKey": "object:286", 891 | "format": "short", 892 | "label": null, 893 | "logBase": 1, 894 | "max": null, 895 | "min": null, 896 | "show": true 897 | } 898 | ], 899 | "yaxis": { 900 | "align": false, 901 | "alignLevel": null 902 | } 903 | }, 904 | { 905 | "collapsed": false, 906 | "datasource": "${DS_PROMETHEUS}", 907 | "fieldConfig": { 908 | "defaults": {}, 909 | "overrides": [] 910 | }, 911 | "gridPos": { 912 | "h": 1, 913 | "w": 24, 914 | "x": 0, 915 | "y": 43 916 | }, 917 | "id": 19, 918 | "panels": [], 919 | "title": "Helm Stats", 920 | "type": "row" 921 | }, 922 | { 923 | "aliasColors": {}, 924 | "bars": false, 925 | "dashLength": 10, 926 | "dashes": false, 927 | "datasource": "${DS_PROMETHEUS}", 928 | "fill": 1, 929 | "fillGradient": 0, 930 | "gridPos": { 931 | "h": 8, 932 | "w": 24, 933 | "x": 0, 934 | "y": 44 935 | }, 936 | "hiddenSeries": false, 937 | "id": 9, 938 | "legend": { 939 | "alignAsTable": true, 940 | "avg": true, 941 | "current": true, 942 | "max": false, 943 | "min": false, 944 | "rightSide": true, 945 | "show": false, 946 | "total": false, 947 | "values": true 948 | }, 949 | "lines": true, 950 | "linewidth": 1, 951 | "nullPointMode": "null as zero", 952 | "percentage": false, 953 | "pluginVersion": "8.2.3", 954 | "pointradius": 2, 955 | "points": false, 956 | "renderer": "flot", 957 | "seriesOverrides": [], 958 | "spaceLength": 10, 959 | "stack": false, 960 | "steppedLine": false, 961 | "targets": [ 962 | { 963 | "expr": "histogram_quantile(0.50, sum(rate(controller_runtime_reconcile_time_seconds_bucket{controller=\"helmrelease\"}[5m])) by (le))", 964 | "hide": true, 965 | "interval": "", 966 | "legendFormat": "P50", 967 | "refId": "A" 968 | }, 969 | { 970 | "expr": "histogram_quantile(0.90, sum(rate(controller_runtime_reconcile_time_seconds_bucket{controller=\"helmrelease\"}[5m])) by (le))", 971 | "hide": true, 972 | "interval": "", 973 | "legendFormat": "P90", 974 | "refId": "B" 975 | }, 976 | { 977 | "expr": "histogram_quantile(0.99, sum(rate(controller_runtime_reconcile_time_seconds_bucket{controller=\"helmrelease\"}[5m])) by (le))", 978 | "hide": false, 979 | "interval": "", 980 | "legendFormat": "P99", 981 | "refId": "C" 982 | } 983 | ], 984 | "thresholds": [], 985 | "timeFrom": null, 986 | "timeRegions": [], 987 | "timeShift": null, 988 | "title": "Helm Release Duration", 989 | "tooltip": { 990 | "shared": true, 991 | "sort": 0, 992 | "value_type": "individual" 993 | }, 994 | "type": "graph", 995 | "xaxis": { 996 | "buckets": null, 997 | "mode": "time", 998 | "name": null, 999 | "show": true, 1000 | "values": [] 1001 | }, 1002 | "yaxes": [ 1003 | { 1004 | "$$hashKey": "object:1612", 1005 | "format": "s", 1006 | "label": null, 1007 | "logBase": 1, 1008 | "max": null, 1009 | "min": null, 1010 | "show": true 1011 | }, 1012 | { 1013 | "$$hashKey": "object:1613", 1014 | "format": "short", 1015 | "label": null, 1016 | "logBase": 1, 1017 | "max": null, 1018 | "min": null, 1019 | "show": true 1020 | } 1021 | ], 1022 | "yaxis": { 1023 | "align": false, 1024 | "alignLevel": null 1025 | } 1026 | }, 1027 | { 1028 | "aliasColors": {}, 1029 | "bars": true, 1030 | "dashLength": 10, 1031 | "dashes": false, 1032 | "datasource": "${DS_PROMETHEUS}", 1033 | "decimals": 2, 1034 | "description": "", 1035 | "fill": 1, 1036 | "fillGradient": 0, 1037 | "gridPos": { 1038 | "h": 9, 1039 | "w": 12, 1040 | "x": 0, 1041 | "y": 52 1042 | }, 1043 | "hiddenSeries": false, 1044 | "id": 5, 1045 | "legend": { 1046 | "alignAsTable": true, 1047 | "avg": true, 1048 | "current": true, 1049 | "max": false, 1050 | "min": false, 1051 | "rightSide": false, 1052 | "show": true, 1053 | "total": false, 1054 | "values": true 1055 | }, 1056 | "lines": false, 1057 | "linewidth": 1, 1058 | "nullPointMode": "null", 1059 | "percentage": false, 1060 | "pluginVersion": "8.2.3", 1061 | "pointradius": 2, 1062 | "points": false, 1063 | "renderer": "flot", 1064 | "seriesOverrides": [], 1065 | "spaceLength": 10, 1066 | "stack": false, 1067 | "steppedLine": true, 1068 | "targets": [ 1069 | { 1070 | "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmrelease\",result!=\"error\"}[1m])) by (controller)", 1071 | "format": "time_series", 1072 | "interval": "", 1073 | "legendFormat": "successful reconciliations ", 1074 | "refId": "A" 1075 | }, 1076 | { 1077 | "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmrelease\",result=\"error\"}[1m])) by (controller)", 1078 | "format": "time_series", 1079 | "interval": "", 1080 | "legendFormat": "failed reconciliations ", 1081 | "refId": "B" 1082 | } 1083 | ], 1084 | "thresholds": [], 1085 | "timeFrom": null, 1086 | "timeRegions": [], 1087 | "timeShift": null, 1088 | "title": "Helm Releases ops/min", 1089 | "tooltip": { 1090 | "shared": true, 1091 | "sort": 0, 1092 | "value_type": "individual" 1093 | }, 1094 | "type": "graph", 1095 | "xaxis": { 1096 | "buckets": null, 1097 | "mode": "time", 1098 | "name": null, 1099 | "show": true, 1100 | "values": [] 1101 | }, 1102 | "yaxes": [ 1103 | { 1104 | "$$hashKey": "object:1102", 1105 | "format": "opm", 1106 | "label": null, 1107 | "logBase": 1, 1108 | "max": null, 1109 | "min": null, 1110 | "show": true 1111 | }, 1112 | { 1113 | "$$hashKey": "object:1103", 1114 | "format": "short", 1115 | "label": null, 1116 | "logBase": 1, 1117 | "max": null, 1118 | "min": null, 1119 | "show": true 1120 | } 1121 | ], 1122 | "yaxis": { 1123 | "align": false, 1124 | "alignLevel": null 1125 | } 1126 | }, 1127 | { 1128 | "aliasColors": {}, 1129 | "bars": true, 1130 | "dashLength": 10, 1131 | "dashes": false, 1132 | "datasource": "${DS_PROMETHEUS}", 1133 | "decimals": 2, 1134 | "description": "", 1135 | "fill": 1, 1136 | "fillGradient": 0, 1137 | "gridPos": { 1138 | "h": 9, 1139 | "w": 12, 1140 | "x": 12, 1141 | "y": 52 1142 | }, 1143 | "hiddenSeries": false, 1144 | "id": 6, 1145 | "legend": { 1146 | "alignAsTable": true, 1147 | "avg": true, 1148 | "current": true, 1149 | "max": false, 1150 | "min": false, 1151 | "rightSide": false, 1152 | "show": true, 1153 | "total": false, 1154 | "values": true 1155 | }, 1156 | "lines": false, 1157 | "linewidth": 1, 1158 | "nullPointMode": "null", 1159 | "percentage": false, 1160 | "pluginVersion": "8.2.3", 1161 | "pointradius": 2, 1162 | "points": false, 1163 | "renderer": "flot", 1164 | "seriesOverrides": [], 1165 | "spaceLength": 10, 1166 | "stack": false, 1167 | "steppedLine": true, 1168 | "targets": [ 1169 | { 1170 | "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmchart\",result!=\"error\"}[1m])) by (controller)", 1171 | "format": "time_series", 1172 | "interval": "", 1173 | "legendFormat": "successful chart pulls", 1174 | "refId": "A" 1175 | }, 1176 | { 1177 | "expr": "sum(increase(controller_runtime_reconcile_total{controller=\"helmchart\",result=\"error\"}[1m])) by (controller)", 1178 | "format": "time_series", 1179 | "interval": "", 1180 | "legendFormat": "failed chart pulls", 1181 | "refId": "B" 1182 | } 1183 | ], 1184 | "thresholds": [], 1185 | "timeFrom": null, 1186 | "timeRegions": [], 1187 | "timeShift": null, 1188 | "title": "Helm Charts ops/min", 1189 | "tooltip": { 1190 | "shared": true, 1191 | "sort": 0, 1192 | "value_type": "individual" 1193 | }, 1194 | "type": "graph", 1195 | "xaxis": { 1196 | "buckets": null, 1197 | "mode": "time", 1198 | "name": null, 1199 | "show": true, 1200 | "values": [] 1201 | }, 1202 | "yaxes": [ 1203 | { 1204 | "$$hashKey": "object:1892", 1205 | "format": "opm", 1206 | "label": null, 1207 | "logBase": 1, 1208 | "max": null, 1209 | "min": null, 1210 | "show": true 1211 | }, 1212 | { 1213 | "$$hashKey": "object:1893", 1214 | "format": "short", 1215 | "label": null, 1216 | "logBase": 1, 1217 | "max": null, 1218 | "min": null, 1219 | "show": true 1220 | } 1221 | ], 1222 | "yaxis": { 1223 | "align": false, 1224 | "alignLevel": null 1225 | } 1226 | } 1227 | ], 1228 | "refresh": "10s", 1229 | "schemaVersion": 31, 1230 | "style": "light", 1231 | "tags": ["flux"], 1232 | "templating": { 1233 | "list": [ 1234 | { 1235 | "current": { 1236 | "selected": false, 1237 | "text": "Prometheus", 1238 | "value": "Prometheus" 1239 | }, 1240 | "description": null, 1241 | "error": null, 1242 | "hide": 2, 1243 | "includeAll": false, 1244 | "label": null, 1245 | "multi": false, 1246 | "name": "DS_PROMETHEUS", 1247 | "options": [], 1248 | "query": "prometheus", 1249 | "refresh": 1, 1250 | "regex": "", 1251 | "skipUrlSync": false, 1252 | "type": "datasource" 1253 | }, 1254 | { 1255 | "allValue": null, 1256 | "current": { 1257 | "selected": false, 1258 | "text": "flux-system", 1259 | "value": "flux-system" 1260 | }, 1261 | "datasource": "${DS_PROMETHEUS}", 1262 | "definition": "workqueue_work_duration_seconds_count", 1263 | "description": null, 1264 | "error": null, 1265 | "hide": 0, 1266 | "includeAll": false, 1267 | "label": null, 1268 | "multi": false, 1269 | "name": "namespace", 1270 | "options": [], 1271 | "query": { 1272 | "query": "workqueue_work_duration_seconds_count", 1273 | "refId": "Prometheus-namespace-Variable-Query" 1274 | }, 1275 | "refresh": 2, 1276 | "regex": "/.*namespace=\"([^\"]*).*/", 1277 | "skipUrlSync": false, 1278 | "sort": 0, 1279 | "tagValuesQuery": "", 1280 | "tagsQuery": "", 1281 | "type": "query", 1282 | "useTags": false 1283 | } 1284 | ] 1285 | }, 1286 | "time": { 1287 | "from": "now-15m", 1288 | "to": "now" 1289 | }, 1290 | "timepicker": { 1291 | "refresh_intervals": [ 1292 | "10s", 1293 | "30s", 1294 | "1m", 1295 | "5m", 1296 | "15m", 1297 | "30m", 1298 | "1h", 1299 | "2h", 1300 | "1d" 1301 | ] 1302 | }, 1303 | "timezone": "", 1304 | "title": "Flux Control Plane", 1305 | "uid": "flux-control-plane", 1306 | "version": 2 1307 | } 1308 | --------------------------------------------------------------------------------