Featured Article GitOps Kubernetes

GitOps Workflow with ArgoCD and Flux: Declarative Kubernetes Management

Implement GitOps workflows using ArgoCD and Flux for declarative, version-controlled Kubernetes deployments with automated synchronization.

HA
Hari Prasad
October 07, 2024
5 min read ...
Financial Planning Tool

PPF Calculator

Calculate your Public Provident Fund returns with detailed projections and tax benefits. Plan your financial future with precision.

Try Calculator
Free Forever Secure
10K+
Users
4.9★
Rating
Career Tool

Resume Builder

Create professional DevOps resumes with modern templates. Showcase your skills, experience, and certifications effectively.

Build Resume
No Login Export PDF
15+
Templates
5K+
Created
Kubernetes Tool

EKS Pod Cost Calculator

Calculate Kubernetes pod costs on AWS EKS. Optimize resource allocation and reduce your cloud infrastructure expenses.

Calculate Costs
Accurate Real-time
AWS
EKS Support
$$$
Save Money
AWS Cloud Tool

AWS VPC Designer Pro

Design and visualize AWS VPC architectures with ease. Create production-ready network diagrams with subnets, route tables, and security groups in minutes.

Design VPC
Visual Editor Export IaC
Multi-AZ
HA Design
Pro
Features
Subnets Security Routing
Explore More

Discover My DevOps Journey

Explore my portfolio, read insightful blogs, learn from comprehensive courses, and leverage powerful DevOps tools—all in one place.

50+
Projects
100+
Blog Posts
10+
Courses
20+
Tools

GitOps revolutionizes how we manage infrastructure and applications by using Git as the single source of truth. This comprehensive guide explores GitOps principles and shows you how to implement them using ArgoCD and Flux CD.

What is GitOps?

GitOps is a operational framework that applies DevOps best practices like version control, collaboration, and CI/CD to infrastructure automation:

Core Principles:

  1. Declarative: System desired state expressed declaratively
  2. Versioned: Desired state stored in Git
  3. Pulled Automatically: Software agents automatically pull desired state
  4. Continuously Reconciled: Software agents ensure correct state

Why GitOps?

Benefits over traditional deployment methods:

  • Single Source of Truth: Git repo contains complete system state
  • Version Control: Full audit trail of changes
  • Easy Rollbacks: Simply revert Git commits
  • Security: Pull-based deployments, no external access needed
  • Developer Productivity: Use familiar Git workflows
  • Disaster Recovery: Entire cluster state in Git
  • Compliance: Automated enforcement of policies

GitOps vs Traditional CI/CD

Traditional CI/CD:
Git Push → CI Build → CD Tool Pushes → Cluster

GitOps:
Git Push → GitOps Operator Pulls → Cluster Syncs

ArgoCD: Getting Started

Installation

# Create namespace
kubectl create namespace argocd

# Install ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Wait for pods to be ready
kubectl wait --for=condition=ready pod --all -n argocd --timeout=300s

# Get admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

# Port forward to access UI
kubectl port-forward svc/argocd-server -n argocd 8080:443

# Access UI at https://localhost:8080
# Username: admin
# Password: (from command above)

# Install ArgoCD CLI
curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd
rm argocd-linux-amd64

# Login via CLI
argocd login localhost:8080 --username admin --password <password> --insecure

First Application

Create a Git repository with this structure:

my-app/
├── k8s/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── ingress.yaml
└── README.md
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: default
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: nginx:1.25
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
  name: my-app
  namespace: default
spec:
  selector:
    app: my-app
  ports:
  - port: 80
    targetPort: 80
  type: ClusterIP

Create ArgoCD Application:

# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
  namespace: argocd
spec:
  project: default
  
  source:
    repoURL: https://github.com/myorg/my-app
    targetRevision: main
    path: k8s
  
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
    - CreateNamespace=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

Apply the application:

# Using kubectl
kubectl apply -f application.yaml

# Or using ArgoCD CLI
argocd app create my-app \
  --repo https://github.com/myorg/my-app \
  --path k8s \
  --dest-server https://kubernetes.default.svc \
  --dest-namespace default \
  --sync-policy automated \
  --auto-prune \
  --self-heal

# View application status
argocd app get my-app

# Sync application
argocd app sync my-app

# View logs
argocd app logs my-app

Flux CD: Getting Started

Installation

# Install Flux CLI
curl -s https://fluxcd.io/install.sh | sudo bash

# Verify installation
flux --version

# Check prerequisites
flux check --pre

# Bootstrap Flux with GitHub
export GITHUB_TOKEN=<your-token>
export GITHUB_USER=<your-username>
export GITHUB_REPO=<your-repo-name>

flux bootstrap github \
  --owner=$GITHUB_USER \
  --repository=$GITHUB_REPO \
  --branch=main \
  --path=./clusters/production \
  --personal

# Check Flux components
flux check

# View all Flux resources
kubectl get all -n flux-system

First Application with Flux

# apps/my-app/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: my-app
---
# apps/my-app/source.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: my-app
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/myorg/my-app
  ref:
    branch: main
  secretRef:
    name: git-credentials
---
# apps/my-app/kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: my-app
  namespace: flux-system
spec:
  interval: 5m
  path: "./k8s"
  prune: true
  sourceRef:
    kind: GitRepository
    name: my-app
  healthChecks:
  - apiVersion: apps/v1
    kind: Deployment
    name: my-app
    namespace: my-app
  timeout: 2m
# Apply Flux resources
kubectl apply -f apps/my-app/

# Watch reconciliation
flux get kustomizations --watch

# View logs
flux logs --follow --level=info

# Trigger manual reconciliation
flux reconcile kustomization my-app --with-source

Advanced ArgoCD Patterns

Multi-Environment Setup

# apps/dev/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app-dev
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/my-app
    targetRevision: develop
    path: k8s/overlays/dev
  destination:
    server: https://kubernetes.default.svc
    namespace: dev
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
---
# apps/staging/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app-staging
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/my-app
    targetRevision: main
    path: k8s/overlays/staging
  destination:
    server: https://kubernetes.default.svc
    namespace: staging
  syncPolicy:
    automated:
      prune: true
      selfHeal: false  # Manual approval for staging
---
# apps/production/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app-production
  namespace: argocd
spec:
  project: production
  source:
    repoURL: https://github.com/myorg/my-app
    targetRevision: v1.0.0  # Use tags for production
    path: k8s/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: false  # Never auto-prune in production
      selfHeal: false  # Manual approval required
    syncOptions:
    - CreateNamespace=true
    - PruneLast=true
  # Ignore differences in specific fields
  ignoreDifferences:
  - group: apps
    kind: Deployment
    jsonPointers:
    - /spec/replicas  # Allow HPA to control replicas

App of Apps Pattern

# apps/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/gitops-config
    targetRevision: main
    path: apps
  destination:
    server: https://kubernetes.default.svc
    namespace: argocd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Directory structure:

gitops-config/
├── apps/
│   ├── dev/
│   │   ├── app1.yaml
│   │   └── app2.yaml
│   ├── staging/
│   │   ├── app1.yaml
│   │   └── app2.yaml
│   └── production/
│       ├── app1.yaml
│       └── app2.yaml
└── infrastructure/
    ├── ingress-nginx.yaml
    ├── cert-manager.yaml
    └── prometheus.yaml

ArgoCD with Helm

# apps/my-helm-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-helm-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/helm-charts
    targetRevision: main
    path: charts/my-app
    helm:
      releaseName: my-app
      valueFiles:
      - values.yaml
      - values-production.yaml
      parameters:
      - name: image.tag
        value: v1.0.0
      - name: replicas
        value: "3"
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

ArgoCD with Kustomize

# apps/kustomize-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: kustomize-app
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/my-app
    targetRevision: main
    path: k8s/overlays/production
    kustomize:
      images:
      - nginx:1.25=nginx:1.26
      commonLabels:
        environment: production
      commonAnnotations:
        managed-by: argocd
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Advanced Flux Patterns

Multi-Tenant Setup

# clusters/production/tenants/tenant-a/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: tenant-a
---
# clusters/production/tenants/tenant-a/sync.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: tenant-a
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/tenant-a/apps
  ref:
    branch: main
  secretRef:
    name: tenant-a-git-credentials
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: tenant-a
  namespace: flux-system
spec:
  interval: 5m
  path: "./apps"
  prune: true
  sourceRef:
    kind: GitRepository
    name: tenant-a
  targetNamespace: tenant-a
  serviceAccountName: tenant-a-reconciler
  validation: client

Flux with Helm Releases

# apps/my-app/helmrelease.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: bitnami
  namespace: flux-system
spec:
  interval: 1h
  url: https://charts.bitnami.com/bitnami
---
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: nginx
  namespace: default
spec:
  interval: 5m
  chart:
    spec:
      chart: nginx
      version: '13.x'
      sourceRef:
        kind: HelmRepository
        name: bitnami
        namespace: flux-system
  values:
    replicaCount: 3
    service:
      type: LoadBalancer
    resources:
      limits:
        cpu: 200m
        memory: 256Mi
      requests:
        cpu: 100m
        memory: 128Mi
  install:
    remediation:
      retries: 3
  upgrade:
    remediation:
      retries: 3
  test:
    enable: true

Flux Image Automation

# apps/my-app/image-policy.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: my-app
  namespace: flux-system
spec:
  image: myregistry.azurecr.io/my-app
  interval: 1m
  secretRef:
    name: acr-credentials
---
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: my-app
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: my-app
  policy:
    semver:
      range: '>=1.0.0 <2.0.0'
---
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: my-app
  namespace: flux-system
spec:
  interval: 1m
  sourceRef:
    kind: GitRepository
    name: my-app
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: fluxcdbot@example.com
        name: Flux CD Bot
      messageTemplate: |
        Update image to {{range .Updated.Images}}{{println .}}{{end}}
    push:
      branch: main
  update:
    path: ./k8s
    strategy: Setters

In your Kubernetes manifests:

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  template:
    spec:
      containers:
      - name: my-app
        image: myregistry.azurecr.io/my-app:1.0.0 # {"$imagepolicy": "flux-system:my-app"}

Progressive Delivery with Argo Rollouts

# Install Argo Rollouts
kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml

# Rollout manifest
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
spec:
  replicas: 5
  strategy:
    canary:
      steps:
      - setWeight: 20
      - pause: {duration: 1m}
      - setWeight: 40
      - pause: {duration: 1m}
      - setWeight: 60
      - pause: {duration: 1m}
      - setWeight: 80
      - pause: {duration: 1m}
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: myapp:1.0.0
        ports:
        - containerPort: 8080

Blue-Green Deployment

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: my-app
spec:
  replicas: 3
  revisionHistoryLimit: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        image: myapp:2.0.0
        ports:
        - containerPort: 8080
  strategy:
    blueGreen:
      activeService: my-app-active
      previewService: my-app-preview
      autoPromotionEnabled: false
      scaleDownDelaySeconds: 30

Monitoring and Observability

ArgoCD Metrics

# ServiceMonitor for Prometheus
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: argocd-metrics
  namespace: argocd
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: argocd-server
  endpoints:
  - port: metrics

Flux Monitoring

# Install Flux monitoring stack
flux create source git flux-monitoring \
  --url=https://github.com/fluxcd/flux2 \
  --branch=main

flux create kustomization monitoring \
  --source=flux-monitoring \
  --path="./manifests/monitoring/prometheus" \
  --prune=true \
  --interval=1h

Best Practices

Repository Structure

gitops-repo/
├── apps/
│   ├── base/
│   │   ├── deployment.yaml
│   │   ├── service.yaml
│   │   └── kustomization.yaml
│   └── overlays/
│       ├── dev/
│       │   ├── kustomization.yaml
│       │   └── replica-patch.yaml
│       ├── staging/
│       │   ├── kustomization.yaml
│       │   └── replica-patch.yaml
│       └── production/
│           ├── kustomization.yaml
│           └── replica-patch.yaml
├── infrastructure/
│   ├── nginx-ingress/
│   ├── cert-manager/
│   └── prometheus/
└── clusters/
    ├── dev/
    ├── staging/
    └── production/

Security Best Practices

Use SSH keys for Git access
Implement RBAC for ArgoCD/Flux
Use sealed secrets or external secret managers
Enable webhook signatures
Implement resource quotas per namespace
Use namespaced installations for multi-tenancy
Enable audit logging
Regularly update ArgoCD/Flux versions

Sealed Secrets

# Install Sealed Secrets controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml

# Install kubeseal CLI
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/kubeseal-0.24.0-linux-amd64.tar.gz
tar -xvzf kubeseal-0.24.0-linux-amd64.tar.gz
sudo install -m 755 kubeseal /usr/local/bin/kubeseal

# Create sealed secret
kubectl create secret generic my-secret \
  --from-literal=password=supersecret \
  --dry-run=client -o yaml | \
  kubeseal -o yaml > sealed-secret.yaml

# Apply sealed secret (safe to commit)
kubectl apply -f sealed-secret.yaml

Disaster Recovery

Backup ArgoCD

# Export all applications
kubectl get applications -n argocd -o yaml > argocd-apps-backup.yaml

# Export all projects
kubectl get appprojects -n argocd -o yaml > argocd-projects-backup.yaml

# Backup using Velero
velero backup create argocd-backup --include-namespaces argocd

Backup Flux

# Backup Flux resources
flux export source git --all > flux-sources-backup.yaml
flux export kustomization --all > flux-kustomizations-backup.yaml
flux export helmrelease --all > flux-helmreleases-backup.yaml

Troubleshooting

ArgoCD

# Check application sync status
argocd app get my-app

# View sync details
argocd app get my-app --show-operation

# Check diff
argocd app diff my-app

# View logs
argocd app logs my-app --tail 100 --follow

# Refresh application
argocd app get my-app --refresh

# Hard refresh (force sync)
argocd app sync my-app --force

Flux

# Check Git source status
flux get sources git

# Check kustomization status
flux get kustomizations

# View reconciliation logs
flux logs --kind=Kustomization --name=my-app --follow

# Suspend reconciliation
flux suspend kustomization my-app

# Resume reconciliation
flux resume kustomization my-app

# Force reconciliation
flux reconcile kustomization my-app --with-source

Conclusion

GitOps transforms infrastructure and application management by leveraging Git’s version control capabilities. Whether you choose ArgoCD or Flux CD, implementing GitOps provides better visibility, security, and reliability for your Kubernetes deployments. Both tools excel at different use cases—ArgoCD shines with its UI and application management, while Flux excels at flexibility and multi-tenancy.

Resources


Which GitOps tool do you prefer? Share your experiences in the comments!

HA
Author

Hari Prasad

Seasoned DevOps Lead with 11+ years of expertise in cloud infrastructure, CI/CD automation, and infrastructure as code. Proven track record in designing scalable, secure systems on AWS using Terraform, Kubernetes, Jenkins, and Ansible. Strong leadership in mentoring teams and implementing cost-effective cloud solutions.

Continue Reading

DevOps Tools & Calculators Free Tools

Power up your DevOps workflow with these handy tools

Enjoyed this article?

Explore more DevOps insights, tutorials, and best practices

View All Articles