
Prerequisite
- Basic knowledge of GitOps
- ArgoCD setup installation, ArgoCD Rollout
Section needs to understand
What is the purpose of the argocd-repo-server component?
Short answer: git clone and generate manifests
The argocd-repo-server has two main responsibilities:
- Repository Management: It clones and maintains a local cache of the Git repository.
- Manifest Generation: It renders the application manifests (YAML) from the source code using configuration management tools (e.g., Helm, Kustomize).
So in context Rendered Manifests Pattern (I generated manifest and push to GitOps repo) how it will work?.
- Clone/Fetch
- Detect & Parse: without
Chart.yamlorkustomization.yaml,repo-serverwill trigger logic ofDirectory Application. - Return Manifests:
repo-serverwill read content of manifests then return it toApplication Controller(argocd-application-controller)
Document here: https://argo-cd.readthedocs.io/en/stable/user-guide/directory/
What is Argo Workflows?
Wow, I never heard about Argo Workflows before, only 2 component that I know before were ArgoCD and Argo Rollouts. So let get into work!
Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes, native for K8S, Argo Workflows is implemented as a Kubernetes CRD.
Each step in the workflow is a container. Hmm, this seems not correct. Let me correct it:
Each step calls a template. Templates of type container/script create pods for execution, while other types (dag, steps, resource, suspend) handle orchestration without creating pods
Example: It works same as Jenkins, GitHub Actions, or GitLab CI. It does Checkout code -> Build Docker Image -> Run Test -> Push into Registry.
Document: https://argoproj.github.io/workflows/
How do steps in a workflow pass data to each other?
The section above is for this question!
Short answer: There are 2 types of data you can pass to each other
- Parameters (Variable, string)
- Artifacts (File, Folder)
So let take an example from here for better understanding:
https://argo-workflows.readthedocs.io/en/latest/walk-through/output-parameters/
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: output-parameter-
spec:
entrypoint: output-parameter
templates:
- name: output-parameter
steps:
- - name: generate-parameter
template: hello-world-to-file
- - name: consume-parameter
template: print-message
arguments:
parameters:
# Pass the hello-param output from the generate-parameter step as the message input to print-message
- name: message
value: "{{steps.generate-parameter.outputs.parameters.hello-param}}"
- name: hello-world-to-file
container:
image: busybox
command: [sh, -c]
args: ["echo -n hello world > /tmp/hello_world.txt"] # generate the content of hello_world.txt
outputs:
parameters:
- name: hello-param # name of output parameter
valueFrom:
path: /tmp/hello_world.txt # set the value of hello-param to the contents of this hello-world.txt
- name: print-message
inputs:
parameters:
- name: message
container:
image: busybox
command: [echo]
args: ["{{inputs.parameters.message}}"]
First, look at hello-world-to-file template
- Action: Container run command
echo -n hello world > /tmp/hello_world.txt - After container finished, but not deleted, Argo Agent will read content of file
/tmp/hello_world.txtthen set that content into variable calledhello-param. And look at thatvalueFromis where it's data comes from!
Second, pass data, look at main template output-parameter in steps, this is where it passes data from outputs to steps
- - name: consume-parameter
template: print-message
arguments:
parameters:
# Pass the hello-param output from the generate-parameter step as the message input to print-message
- name: message
value: "{{steps.generate-parameter.outputs.parameters.hello-param}}"
In this step call the template print-message, but it said I need to load data for the fucking variable message.
Syntax: {{steps.<step_name>.outputs.parameters.<variable_name>}}
- It pointed to step
generate-parameter - It gets the
hello-param - Then it put into input
messageof current step
Third, look at template print-message
Define: I need the fucking input called message to run!
inputs:
parameters:
- name: message
Usage:
args: ["{{inputs.parameters.message}}"]
- At this moment:
{{inputs.parameters.message}}will be replaced with "hello world" - Command will be
echo "hello world"
So note for the CAPA exam: Where is outputs located?
outputsdefined in template of who created that data.- Must use
valueFrom: path: ...to tell Argo Workflow read from which file.
Reference?
- In
steps, when we want to get data, we must use prefixsteps. Example:steps.generate-parameter... - If you write with word
steps, ex:outputs.parametersit will be resulted as invalid syntax.
Almost 90 lines for simple explanation xD. But its worth!
What is the primary CRD for Argo Workflows?
As for the name and what we have go through, it is Workflow.
In Argo Workflows, what is a 'Suspend' template?
We can simply understand it as Pause of Workflow!
Document: https://argo-workflows.readthedocs.io/en/latest/walk-through/suspending/
The suspend template is a template type that will suspend the workflow for a `duration` or `indefinitely`.
For exam, I think it can be best remember with Suspend = Manual Approval
Let's learn about Argo Events
This is 4th project from Argo Project which I have no idea before xD
Argo Events is an event-based dependency manager for Kubernetes which helps you define multiple dependencies from a variety of event sources like webhook, s3, schedules, streams etc. and trigger Kubernetes objects after successful event dependencies resolution
So for exam note:
- Argo Events = Event-Driven / Dependency Manager.
- Source = where events raised (GitHub, Slack, SNS, Cron...)
- Trigger = Trigger something, commonly trigger Argo Workflows
- Sensor = Dependency Manager. Always have 2 parts: dependencies and triggers
So this is important section, I will try to understand via real example:
Source: https://github.com/argoproj/argo-events/blob/master/examples/sensors/redis.yaml
Purpose: This Sensor acts as a bridge between Redis and Argo Workflows.
- Dependency: It listens for the specific event from the Redis EventSource defined in dependencies.
{
"header": { "timestamp": "123456" },
"body": {
"user": "admin",
"message": "Hello World" <-- I want to get this!!!
}
}
- Parameterization: It dynamically extracts the body.message from the Redis event payload (src) and injects it into the Workflow's arguments (dest). You can understand it as concept "Find & Replace" data from event into Workflow
parameters:
- src:
dependencyName: test-dep # 1. Get from where?
dataKey: body.message # 2. Get what?
dest: spec.arguments.parameters.0.value # 3. Put where?
- Trigger: Once the dependency is met, it creates the Argo Workflow resource in the cluster.
Tell me about AnalysisRun in Argo Rollouts
AnalysisRun is the initialization of AnalysisTemplate. It is responsible for querying the metrics provider and deciding whether the deployment should continue or be rolled back.
What is Sync Phases and Waves in ArgoCD?
Document: https://argo-cd.readthedocs.io/en/stable/user-guide/sync-waves/#sync-phases-and-waves
Sync Waves (The "Order"):
- Purpose: Controls the sequence of deployment between resources.
- Mechanism: Uses integers (e.g., sync-wave: "1"). Lower numbers apply first. (Accepted number lower than 0 also xD)
- Key Rule: Argo CD waits for the current wave to be Healthy before starting the next wave.
- Use Case: Start Database (Wave 0) -> Start Backend (Wave 1).
Sync Phases (The "Timing/Hooks"):
- Purpose: Controls the lifecycle steps of the synchronization.
- Mechanism: Three main buckets: PreSync -> Sync (Main) -> PostSync.
- Key Rule: If a hook (like PreSync) fails, the sync stops.
- Use Case: Run DB Migration Job (PreSync) -> Deploy App (Sync) -> Send Slack Notification (PostSync).
Summary Relationship:
Phases define the Strategy (When), and Waves define the Order (Who goes first) within those phases. (Note: You can even have Waves inside a Phase, e.g., multiple PreSync jobs running in order).
Example with sync waves
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-backend
annotations:
argocd.argoproj.io/sync-wave: "1" # <--- wait for wave 0 finished then run
Example with sync phases: database migration
- PreSync: Database Migration Job
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration-job
annotations:
# This is how phase definition
argocd.argoproj.io/hook: PreSync
# important for CAPA: delete this fucking job job after run to avoid trash
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
containers:
- name: migrate
image: my-app:v2
command: ["./migrate-db.sh"]
restartPolicy: Never
- Sync (Main): Application Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-backend-app
# Without annotation hook, default is Sync phase
spec:
replicas: 3
template:
spec:
containers:
- name: app
image: my-app:v2 # new code need new DB structure
- PostSync: Notification Job This only run after second step is Healthy
apiVersion: batch/v1
kind: Job
metadata:
name: slack-notification
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
template:
spec:
containers:
- name: slack-curl
image: curlimages/curl
command: ["curl", "-X", "POST", "https://hooks.slack.com/...", "-d", "'Deploy Success!'"]
restartPolicy: Never
CAPA exam tip: remember annotation argocd.argoproj.io/hook-delete-policy. Without this line, next deploy will error because duplicated job name. So there are some policies for hook-delete-policy
HookSucceeded: Delete after success (Recommend)BeforeHookCreation: Delete old before create new oneHookFailed: Delete hook if fails
Argo Workflows - DAG templates
As an alternative to specifying sequences of steps, you can define a workflow as a directed-acyclic graph (DAG) by specifying the dependencies of each task. DAGs can be simpler to maintain for complex workflows and allow for maximum parallelism when running tasks.
Document: https://argo-workflows.readthedocs.io/en/latest/walk-through/dag/
Which CLI tool is used for Argo Rollouts?
Document: https://argo-rollouts.readthedocs.io/en/stable/features/kubectl-plugin/
Answer: kubectl argo rollouts
What is the 'Executor' in Argo Workflows?
Document: https://argo-workflows.readthedocs.io/en/latest/workflow-executors/
The executor is a process that conforms to a specific interface that allows Argo to perform certain actions like capturing artifacts, logs, etc... Emissary is the default executor (From version 3.3+, before was Docker!)
Which CLI tool is used for Argo Workflows?
Answer: argo
Document: https://argo-workflows.readthedocs.io/en/latest/walk-through/argo-cli/
Which command deletes all completed workflows?
argo delete --completed
Related Document: https://github.com/argoproj/argo-workflows/blob/main/docs/cli/argo_delete.md
How do you run a script (e.g., Python) directly inside a workflow step without building a custom image?
Short Answer: Use the script template type instead of container
Example:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: scripts-python-
spec:
entrypoint: python-script-example
templates:
- name: python-script-example
steps:
- - name: generate
template: gen-random-int
- name: gen-random-int
script: # <--- use 'script' instead of 'container'
image: python:alpine3.6
command: [python]
source: |
import random
i = random.randint(1, 100)
print(i)
Document where I copy code xD:
https://argo-workflows.readthedocs.io/en/latest/walk-through/scripts-and-results/
Which annotation allows you to delete a resource from the cluster but keep it in Git without Argo CD recreating it immediately?
Resource Exclusion or annotation
argocd.argoproj.io/compare-options: IgnoreExtraneous
Document: https://argo-cd.readthedocs.io/en/stable/user-guide/compare-options/
What is argocd-image-updater?
Another "WoW, what is this?"
So basically it is a tool to automatically update the container images of Kubernetes workloads that are managed by Argo CD as the name of it "ArgoCD image updater" xD
Document: https://argocd-image-updater.readthedocs.io/en/stable/
Example:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
annotations:
# 1. Enable feature for image 'my-image'
argocd-image-updater.argoproj.io/image-list: my-image=my-registry/my-app
# 2. Select strategy to update (ex: only update latest version)
# semver: update to highest allowed version according to given image constraint
# More strategy: https://argocd-image-updater.readthedocs.io/en/stable/#features
argocd-image-updater.argoproj.io/my-image.update-strategy: semver
# 3. Write back into Git (Fuck yeah)
argocd-image-updater.argoproj.io/write-back-method: git
spec:
# ...
Note for CAPA Exam:
- Git Write-back: it commits into gitops repo (create new commit with message like: "Update image to v1.1")
- Argo CD API (Imperative): It updates the Application resource directly in the cluster via Argo CD API, without modifying Git.
ArgoCD Notifications
Argo CD Notifications is a controller that continuously monitors Argo CD applications and sends alerts to external services (like Slack, Email, Telegram, Discord, or Webhooks) when specific events occur.
It relies on three main concepts:
- Triggers: Defines when to send a notification (e.g.,
on-sync-failed,on-health-degraded). - Templates: Defines what to say (the message content/format).
- Services: Defines where to send it (the destination credentials).
Document: Argo CD Docs - Notifications
What are Application URLs (External URLs)?
This feature allows you to access your deployed application directly from the Argo CD UI (via a small "launch" icon on the Service or Ingress resource).
How it works:
- Auto-Discovery: By default, Argo CD tries to automatically find the URL by looking at the
hostfield in your Ingress. - Manual Configuration: If auto-discovery fails (or if you use
NodePort), you can manually specify the URL by adding this specific annotation to your Ingress, Service, or Route:
link.argocd.argoproj.io/external-link: http://my-app.example.com
Document: External URL
Argo Workflows: Artifacts - Passing Files/Folders between Steps
Different from Parameters (string), Artifacts used to pass files/folders between steps.
- name: create-file
container:
image: alpine:latest
command: [sh, -c]
args: ["echo 'hello' > /tmp/message.txt"]
outputs:
artifacts:
- name: message-file
path: /tmp/message.txt # Argo grabs this file
- name: print-file
inputs:
artifacts:
- name: message-file # Same name to receive
path: /tmp/received.txt # Where to put in this container
container:
image: alpine:latest
command: [cat, /tmp/received.txt]
CAPA exam note:
- Parameters = small strings, use
{{steps.xxx.outputs.parameters.yyy}} - Artifacts = files/folders, stored in S3/GCS/Minio (configurable)
- Artifact storage options: s3, gcs, azure, oss, hdfs, artifactory
Argo Workflows: WorkflowTemplate vs ClusterWorkflowTemplate
Same concept as ConfigMap vs ClusterRole!
- WorkflowTemplate: Namespaced, only usable within same namespace
- ClusterWorkflowTemplate: Cluster-wide, usable from any namespace
How to reference:
steps:
- - name: call-template
templateRef:
name: my-template # Template name
template: main # Which template inside
# clusterScope: true # Add this for ClusterWorkflowTemplate
Argo Workflows: CronWorkflow - Scheduled Workflows
apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
name: nightly-backup
spec:
schedule: "0 2 * * *" # Cron syntax
timezone: "Asia/Ho_Chi_Minh"
concurrencyPolicy: Replace # What if previous still running?
workflowSpec:
entrypoint: backup-job
templates:
- name: backup-job
container:
image: backup-tool:latest
concurrencyPolicy options (CAPA exam!):
- Allow: Run concurrent (default)
- Forbid: Skip if previous still running
- Replace: Kill previous, start new
Argo Workflows: Retry Strategy & Timeout
templates:
- name: flaky-step
retryStrategy:
limit: 3
retryPolicy: "OnFailure" # Only retry on non-zero exit
backoff:
duration: "5s"
factor: 2 # 5s -> 10s -> 20s
container:
image: my-app
retryPolicy options:
- Always: Any failure
- OnFailure: Non-zero exit code only
- OnError: Argo errors only (not container failures)
- OnTransientError: Network issues
Timeout:
spec:
activeDeadlineSeconds: 3600 # Workflow level: 1 hour max
templates:
- name: task
activeDeadlineSeconds: 600 # Template level: 10 min max
Argo Workflows: Parallelism - withItems, withParam, withSequence
# Static list
- name: process
template: process-file
withItems: ["file1.csv", "file2.csv", "file3.csv"]
# Dynamic list from previous step (JSON array)
- name: process
template: process-file
withParam: "{{steps.get-files.outputs.result}}"
# Generate sequence
- name: process
template: work
withSequence:
count: 5 # 0,1,2,3,4
# OR start: 1, end: 10
CAPA exam note:
- withItems: Static list in YAML
- withParam: Dynamic JSON array from previous step
- withSequence: Generate numbers
Argo Workflows: Exit Handler - Finally block
spec:
entrypoint: main
onExit: cleanup-handler # Always run at the end!
templates:
- name: cleanup-handler
container:
image: alpine
command: [echo, "Cleaning up..."]
CAPA exam note: onExit runs regardless of success/failure - like finally in code.
ArgoCD: Application Source Types
ArgoCD supports multiple source types. When no Chart.yaml or kustomization.yaml found, it defaults to Directory (plain YAML).
spec:
source:
repoURL: https://github.com/my-org/my-repo
targetRevision: HEAD
path: manifests/
# For Helm
helm:
valueFiles:
- values-prod.yaml
parameters:
- name: replicas
value: "3"
# For Kustomize
kustomize:
namePrefix: prod-
images:
- my-app:v2.0
CAPA exam note: Know the difference between helm.values (inline) vs helm.valueFiles (from file)
ArgoCD: Sync Options
Common sync options via annotation or Application spec:
metadata:
annotations:
argocd.argoproj.io/sync-options: |
Prune=true
SelfHeal=true
CreateNamespace=true
CAPA exam note: SelfHeal + Prune = fully automated GitOps (no manual intervention)
ArgoCD: Health Status vs Sync Status
Two different things!
- Sync Status: Is cluster state = Git state? (Synced, OutOfSync)
- Health Status: Is application actually working? (Healthy, Progressing, Degraded, Suspended)
Example: App can be Synced but Degraded (manifests applied but pods crashing)
CAPA exam note: Refresh = check, Sync = apply
ArgoCD: AppProject - RBAC for Applications
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production
spec:
description: Production apps only
# Allowed Git sources
sourceRepos:
- 'https://github.com/my-org/*'
# Allowed destinations
destinations:
- namespace: 'prod-*'
server: https://kubernetes.default.svc
# Allowed/Denied resources
clusterResourceWhitelist:
- group: ''
kind: Namespace
namespaceResourceBlacklist:
- group: ''
kind: Secret
CAPA exam note: default project allows everything. Create custom projects to restrict source repos, destinations, and resource types.
ArgoCD: ApplicationSet - Generate Multiple Apps
Instead of creating 10 Applications manually, use ApplicationSet:
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: my-apps
spec:
generators:
# List generator - static list
- list:
elements:
- env: dev
namespace: dev
- env: prod
namespace: prod
template:
metadata:
name: 'myapp-{{env}}'
spec:
project: default
source:
repoURL: https://github.com/org/repo
path: 'envs/{{env}}'
destination:
server: https://kubernetes.default.svc
namespace: '{{namespace}}'
Common generators (CAPA exam!):
list: Static list of valuesclusters: Generate app per clustergit: Generate from directories/files in Gitmatrix: Combine multiple generators
CAPA exam note: App of Apps = manual setup, ApplicationSet = dynamic/automated generation
Argo Rollouts: AnalysisTemplate vs ClusterAnalysisTemplate
Same pattern again - namespaced vs cluster-wide:
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate # or ClusterAnalysisTemplate
metadata:
name: success-rate
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 1m
count: 3 # Run 3 times
successCondition: result[0] >= 0.95
failureLimit: 1 # Fail after 1 bad result
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(http_requests_total{status=~"2.*", app="{{args.service-name}}"}[5m]))
/
sum(rate(http_requests_total{app="{{args.service-name}}"}[5m]))
CAPA exam note:
- successCondition: Expression must be true to pass
- failureLimit: How many failures before rollback
- count: How many times to run the measurement
- interval: Time between measurements
Argo Events: EventBus - Transport Layer
EventBus is the backbone that connects EventSources to Sensors. Think of it as message queue.
apiVersion: argoproj.io/v1alpha1
kind: EventBus
metadata:
name: default
spec:
# Option 1: NATS (default, simplest)
nats:
native:
replicas: 3
auth: token
# Option 2: Jetstream (NATS with persistence)
# jetstream:
# version: latest
# Option 3: Kafka
# kafka:
# url: kafka-broker:9092
CAPA exam note:
- EventBus must exist before EventSource and Sensor work
- Default name is default - if you name it differently, must reference in EventSource/Sensor
- NATS = simple, Jetstream = persistent, Kafka = enterprise scale
Argo Events: Common EventSource Types
apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
name: my-sources
spec:
# Webhook - receive HTTP POST
webhook:
github-webhook:
port: "12000"
endpoint: /github
method: POST
# Calendar/Cron - scheduled events
calendar:
daily-trigger:
schedule: "0 9 * * *" # 9 AM daily
timezone: "Asia/Ho_Chi_Minh"
# AWS SNS
sns:
my-sns:
topicArn: arn:aws:sns:us-east-1:123456789:my-topic
region: us-east-1
# AWS SQS
sqs:
my-sqs:
queue: my-queue
region: us-east-1
# GitHub (native integration)
github:
repo-events:
repositories:
- owner: my-org
names: [my-repo]
events: [push, pull_request]
webhook:
endpoint: /github
port: "13000"
CAPA exam note: One EventSource can have multiple sources of same or different types.
Argo Events: Sensor - Trigger Conditions & Filters
Filter events before triggering:
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
name: my-sensor
spec:
dependencies:
- name: github-dep
eventSourceName: my-sources
eventName: github-webhook
filters:
data:
# Only trigger for 'main' branch
- path: body.ref
type: string
value:
- "refs/heads/main"
# Time filter - only during work hours
time:
start: "09:00:00"
stop: "18:00:00"
triggers:
- template:
name: run-workflow
k8s:
operation: create
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
generateName: ci-build-
spec:
entrypoint: build
# ... workflow spec
Filter types (CAPA exam!):
- data: Filter by event payload content
- time: Filter by time of day
- context: Filter by event metadata
- exprs: CEL expressions for complex logic
Argo Events: Multiple Dependencies - AND/OR Logic
spec:
dependencies:
- name: webhook-dep
eventSourceName: webhook-source
eventName: my-webhook
- name: calendar-dep
eventSourceName: calendar-source
eventName: daily
triggers:
- template:
name: my-trigger
conditions: "webhook-dep && calendar-dep" # AND - both must fire
# conditions: "webhook-dep || calendar-dep" # OR - either one
CAPA exam note: Default behavior (no conditions) = AND logic (all dependencies must be satisfied)
Argo Events: Trigger Types
Sensor can trigger different resources:
triggers:
# 1. Create Argo Workflow
- template:
name: workflow-trigger
k8s:
operation: create
source:
resource:
apiVersion: argoproj.io/v1alpha1
kind: Workflow
# 2. Create any K8s resource
- template:
name: job-trigger
k8s:
operation: create
source:
resource:
apiVersion: batch/v1
kind: Job
# 3. HTTP request (webhook out)
- template:
name: http-trigger
http:
url: https://api.example.com/webhook
method: POST
# 4. AWS Lambda
- template:
name: lambda-trigger
awsLambda:
functionName: my-function
region: us-east-1
CAPA exam note: Most common trigger = Argo Workflow, but can trigger any K8s resource or external services.
Argo Events: Complete Flow Diagram
┌─────────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
│ EventSource │ --> │ EventBus │ --> │ Sensor │ --> │ Workflow │
│ (webhook, │ │ (NATS/ │ │ (filter │ │ (or any │
│ calendar, │ │ Kafka) │ │ +match)│ │ K8s res)│
│ SNS, etc) │ │ │ │ │ │ │
└─────────────┘ └──────────┘ └─────────┘ └──────────┘
CAPA exam note summary:
- EventSource: WHERE events come from
- EventBus: HOW events are transported
- Sensor: WHAT to do when events match (filter + trigger)
My secret weapon: Mock Exam
Udemy practice test:
https://www.udemy.com/course/complete-certified-argo-project-associate-capa-exam-prep/
Exam note:
- Exam duration: 90 minutes
- Exam questions: 60 questions
- The exam required 75/100 to pass, and I scored 80/100!
- You should memorize and understand everything in
kind: Application - Another you should care about is ignoreDifferences
Good luck with the exam