Skip to content

GitOps — Practical

Terminal window
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd -n argocd --create-namespace \
--set server.service.type=LoadBalancer \
--set configs.params."server\.insecure"=true # dev

Login:

Terminal window
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d
argocd login <host>
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: { name: bootstrap, namespace: argocd }
spec:
project: default
source:
repoURL: https://github.com/org/manifests
path: argocd-apps
targetRevision: main
destination: { server: https://kubernetes.default.svc, namespace: argocd }
syncPolicy: { automated: { prune: true, selfHeal: true } }

Then argocd-apps/*.yaml contains one Application per service.

manifests/
base/
api/
kustomization.yaml
deployment.yaml
service.yaml
overlays/
prod/
api/
kustomization.yaml
replicas-patch.yaml
staging/
api/
kustomization.yaml
image-patch.yaml
overlays/prod/api/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: app
resources:
- ../../../base/api
patches:
- path: replicas-patch.yaml
images:
- name: ghcr.io/org/api
newTag: 1.2.3
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: { name: nginx-ingress, namespace: argocd }
spec:
source:
repoURL: https://kubernetes.github.io/ingress-nginx
chart: ingress-nginx
targetRevision: 4.10.0
helm:
values: |
controller:
service:
type: LoadBalancer
metrics:
enabled: true
destination: { server: https://kubernetes.default.svc, namespace: ingress }
syncPolicy: { automated: { prune: true, selfHeal: true } }
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api
namespace: argocd
annotations:
argocd-image-updater.argoproj.io/image-list: api=ghcr.io/org/api
argocd-image-updater.argoproj.io/api.update-strategy: digest
argocd-image-updater.argoproj.io/write-back-method: git
argocd-image-updater.argoproj.io/git-branch: main
spec: ...
Terminal window
kubeseal --controller-namespace kube-system --controller-name sealed-secrets \
--format yaml < secret.yaml > sealed-secret.yaml

sealed-secret.yaml is safe to commit. Cluster’s controller decrypts in place.

External Secrets Operator (from AWS Secrets Manager)

Section titled “External Secrets Operator (from AWS Secrets Manager)”
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata: { name: aws-sm }
spec:
provider:
aws:
service: SecretsManager
region: eu-west-1
auth:
jwt:
serviceAccountRef: { name: eso, namespace: external-secrets }
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata: { name: db, namespace: app }
spec:
refreshInterval: 1h
secretStoreRef: { kind: ClusterSecretStore, name: aws-sm }
target: { name: db-secret }
data:
- secretKey: password
remoteRef: { key: prod/db, property: password }
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata: { name: api }
spec:
replicas: 10
strategy:
canary:
maxSurge: "25%"
maxUnavailable: 0
steps:
- setWeight: 10
- pause: { duration: 5m }
- setWeight: 25
- pause: { duration: 10m }
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100
analysis:
templates: [{ templateName: success-rate }]
startingStep: 1
selector: { matchLabels: { app: api } }
template: ...
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata: { name: success-rate }
spec:
args: [{ name: service-name }]
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.99
failureLimit: 3
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{service="{{args.service-name}}",status=~"2.."}[1m]))
/ sum(rate(http_requests_total{service="{{args.service-name}}"}[1m]))
spec:
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers: [/spec/replicas]
- name: Bump prod manifest
run: |
sed -i "s|newTag:.*|newTag: ${{ github.sha }}|" \
manifests/overlays/prod/api/kustomization.yaml
git config user.name "release-bot"
git config user.email "bot@example.com"
git checkout -b prod-bump-${{ github.sha }}
git commit -am "prod: api -> ${{ github.sha }}"
git push -u origin HEAD
gh pr create --base main --head prod-bump-${{ github.sha }} \
--title "prod: api -> ${{ github.sha }}" \
--reviewer @org/platform
manifests/
clusters/
prod-eu/
argocd-apps/{api,web,workers}.yaml
staging-eu/
argocd-apps/...
apps/
api/
base/
overlays/{prod,staging,dev}/
infra/
cert-manager/
ingress-nginx/
external-secrets/
  • Argo CD / Flux — controllers.
  • Argo Rollouts / Flagger — progressive delivery.
  • Argo Workflows / Tekton — CI on K8s.
  • Argo Events — event-driven workflows.
  • Image Updater / Renovate — version bumping.
  • Sealed Secrets / SOPS / External Secrets — secret mgmt.
  • kargo — promotion automation.
  • Lens / Headlamp — UI debugging.