Homework 14 Solutions — GitOps with ArgoCD
Lesson 14 · Cloud Infrastructure & DevOps
Solution to Task 1 — ArgoCD Notifications child Application
Create cloud-devops/sample-app/argocd/children/argocd-notifications.yaml:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argocd-notifications
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "-1"
spec:
project: default
source:
repoURL: https://argoproj.github.io/argo-helm
chart: argocd-notifications
targetRevision: 1.8.1
helm:
values: |
notifiers:
service.webhook.test-hook: |
url: https://webhook.site/<your-tempid>
headers:
- name: Content-Type
value: application/json
subscriptions:
- recipients: [test-hook]
triggers: [on-sync-succeeded, on-sync-failed, on-health-degraded]
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated: {prune: true, selfHeal: true}
Then add the subscription annotation to app-url-shortener.yaml (and to children/url-shortener.yaml):
metadata:
annotations:
argocd.argoproj.io/sync-wave: "0"
notifications.argoproj.io/subscribe.on-sync-succeeded.test-hook: ""
notifications.argoproj.io/subscribe.on-sync-failed.test-hook: ""
on-sync-running instead of on-sync-succeeded. Sync-running fires on every reconciliation — you will receive a webhook every 3 minutes even when nothing changed. Subscribe to succeeded and failed only. The on-health-degraded trigger is useful for alerting when a Deployment’s pods are crash-looping.
Solution to Task 2 — Sealed Secrets migration plan
Sample plan using the Sealed Secrets path:
- Install Sealed Secrets via an ArgoCD child Application (Helm chart
sealed-secretsfromhttps://bitnami-labs.github.io/sealed-secrets), wave −1 so it precedes the app. - CRD:
SealedSecret(groupbitnami.com/v1alpha1) — a custom resource that wraps an encrypted blob. The bitnami Sealed Secrets controller decrypts it inside the cluster using the cluster’s private key and creates a regularSecret. - Where unencrypted values live: only on the developer’s laptop, never in git. The developer runs
kubesealCLI, which reads a plaintextSecretYAML on stdin, encrypts it with the controller’s public key, and prints aSealedSecretYAML to stdout. TheSealedSecretis what goes in git. - Migrate
values-prod.yamlby extracting each secret value into a separate KubernetesSecretYAML, sealing it (kubeseal < secret.yaml > sealed-secret.yaml), committing theSealedSecret, and referencing the resulting in-cluster Secret name from the Deployment viaenvFrom.secretRef. Thevalues-prod.yamlthen only references secret names, not values. - Rotation: generate a new plaintext Secret, seal it, commit the new
SealedSecret. ArgoCD syncs. The Sealed Secrets controller sees the updated resource and updates the in-clusterSecret. No plaintext ever reaches git. - The cluster controller’s public key is the only material the developer needs locally (fetched via
kubeseal --fetch-cert --controller-namespace kube-system). The cluster’s private key never leaves the cluster. SealedSecrets are namespace-scoped by default — aSealedSecretcreated for theurl-shortenernamespace cannot be decrypted intokube-system. This is deliberate; respect the boundary.
SealedSecret YAML between two clusters. Each cluster has a unique private key — the SealedSecret can only be decrypted by the cluster whose public key was used for encryption. Run kubeseal --fetch-cert per cluster.
Solution to Task 3 — Argo Rollouts vs blue/green comparison
Rollout CR vs Deployment. Rollout (argoproj.io/v1alpha1) is a CRD that replaces Deployment for the canary use-case. The pod spec (spec.template.spec) is identical to a Deployment’s. The difference is spec.strategy.canary with steps:
spec:
strategy:
canary:
steps:
- setWeight: 10
- pause: {duration: 5m}
- analysis:
templates:
- templateName: success-rate
- setWeight: 50
- pause: {duration: 5m}
- setWeight: 100
Each setWeight updates a traffic split (via the ingress controller’s annotations or a service mesh). Each analysis step runs an AnalysisTemplate — a CRD containing a Prometheus query that must return a passing result; failure aborts the rollout and triggers a rollback.
AnalysisTemplate example. A template that asserts the canary’s error rate is below 1%:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata: {name: success-rate}
spec:
metrics:
- name: success-rate
interval: 30s
successCondition: result[0] >= 0.99
provider:
prometheus:
address: http://kps-kube-prometheus-stack-prometheus.monitoring.svc:9090
query: |
sum(rate(http_requests_total{code!~"5..",service="{{args.service-name}}"}[2m]))
/ sum(rate(http_requests_total{service="{{args.service-name}}"}[2m]))
The Rollout controller polls this query every 30 seconds and gates the next setWeight step on the result. If the canary’s error rate exceeds 1%, the analysis fails, the rollout halts, and traffic is routed back to the stable version automatically.
When to stick with blue/green from L13. When you do not yet have a meaningful metric-comparison story. Argo Rollouts is “blue/green plus automated metric analysis”. If you cannot write a PromQL query that meaningfully separates a healthy canary from an unhealthy one (because you have too little traffic, or your metrics are not instrumented at the right granularity), canary buys you nothing — it just slows down the deploy. For a 2-service URL shortener with ≤10 rps in development, blue/green with a manual 30-second observation window is correct. For a multi-service product with ≥1000 rps and SRE-owned SLIs, Argo Rollouts pays for itself in the first incident it prevents.
Rollout as “Deployment + traffic split” and omitting the analysis block entirely. A canary without metric analysis is just a slower blue/green — you get the complexity of Argo Rollouts without the automated safety gate that justifies it.
Course complete. You built the platform around an app, then watched ArgoCD make the platform self-managing. From here: pick a real app you own; run it through the same stack; operate it for 30 days; come back and re-read your own dashboards and runbook. That’s the loop. Everything else is taste.