[22/24] G is for GitOps: Modern Deployment Workflows


This is Post #22 in the Kubernetes A-to-Z Series

Reading Order: Previous: Zero-Downtime Deployments | Next: Best Practices

Series Progress: 22/24 complete | Difficulty: Advanced | Time: 40 min | Part 6/6: Security & Production

GitOps has revolutionized how teams deploy and manage applications on Kubernetes. By using Git as the single source of truth for declarative infrastructure and applications, GitOps brings versioning, collaboration, and automation to Kubernetes deployments.

This comprehensive guide explores GitOps principles and compares the two leading GitOps tools: ArgoCD and FluxCD.

What is GitOps?

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

Traditional Deployment:
┌──────────────────────────────────────────────┐
│  Developer → kubectl apply → Cluster         │
│  - No audit trail                            │
│  - Manual changes                            │
│  - Configuration drift                       │
│  - Hard to rollback                          │
└──────────────────────────────────────────────┘

GitOps Deployment:
┌──────────────────────────────────────────────┐
│  Developer → Git Push → GitOps Operator      │
│              ↓                               │
│         Git Repository                       │
│              ↓                               │
│         Automatic Sync                       │
│              ↓                               │
│         Kubernetes Cluster                   │
│  - Full audit trail                          │
│  - Declarative                               │
│  - Self-healing                              │
│  - Easy rollback                             │
└──────────────────────────────────────────────┘

Core GitOps Principles

  1. Declarative: Entire system described declaratively
  2. Versioned: Canonical state versioned in Git
  3. Pulled Automatically: Software agents pull desired state
  4. Continuously Reconciled: Agents ensure correctness

GitOps Benefits

  • Increased Velocity: Faster deployments through automation
  • Improved Reliability: Declarative approach reduces errors
  • Enhanced Security: No direct cluster access needed
  • Better Collaboration: Git workflow for infrastructure
  • Easy Rollback: Git history enables instant rollbacks
  • Audit Trail: Every change tracked in Git
  • Disaster Recovery: Cluster rebuilt from Git

ArgoCD vs FluxCD: Overview

ArgoCD

ArgoCD is a declarative, GitOps continuous delivery tool for Kubernetes developed by Intuit.

Key Characteristics:

  • User-friendly Web UI and CLI
  • Multi-cluster management
  • SSO integration
  • RBAC support
  • Application health assessment
  • Rich visualization

FluxCD

FluxCD is a GitOps operator for Kubernetes that synchronizes cluster state with Git, part of the CNCF.

Key Characteristics:

  • Lightweight and Kubernetes-native
  • Modular architecture (Toolkit)
  • Progressive delivery with Flagger
  • Multi-tenancy support
  • Notification system
  • CLI-focused workflow

Feature Comparison

FeatureArgoCDFluxCD
UIRich Web UINo built-in UI
ArchitectureMonolithicModular (Toolkit)
Helm SupportNativeNative
KustomizeNativeNative
Multi-tenancyGoodExcellent
SSOBuilt-inExternal
Progressive DeliveryArgo Rollouts (separate)Flagger (integrated)
NotificationsYesYes
Image AutomationLimitedExcellent
Webhook SupportYesYes
Learning CurveModerateSteeper

Installing ArgoCD

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 pods --all -n argocd --timeout=300s

# Get initial 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)

ArgoCD CLI Installation

# macOS
brew install argocd

# Linux
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

# Login via CLI
argocd login localhost:8080 --insecure
argocd account update-password

ArgoCD: Creating Applications

Application Manifest

# application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: guestbook
  namespace: argocd
spec:
  project: default

  # Source repository
  source:
    repoURL: https://github.com/argoproj/argocd-example-apps.git
    targetRevision: HEAD
    path: guestbook

  # Destination cluster
  destination:
    server: https://kubernetes.default.svc
    namespace: guestbook

  # Sync policy
  syncPolicy:
    automated:
      prune: true      # Delete resources not in Git
      selfHeal: true   # Force sync when cluster state changes
      allowEmpty: false
    syncOptions:
    - CreateNamespace=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

Helm Application

# helm-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: nginx-ingress
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://kubernetes.github.io/ingress-nginx
    chart: ingress-nginx
    targetRevision: 4.8.3
    helm:
      values: |
        controller:
          replicaCount: 3
          service:
            type: LoadBalancer
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
  destination:
    server: https://kubernetes.default.svc
    namespace: ingress-nginx
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
    - CreateNamespace=true

Kustomize Application

# kustomize-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: webapp
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/myapp.git
    targetRevision: main
    path: overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

ArgoCD CLI Commands

# Application Management
argocd app create -f application.yaml
argocd app list
argocd app get guestbook
argocd app sync guestbook
argocd app delete guestbook

# Manual sync
argocd app sync guestbook

# Diff between Git and cluster
argocd app diff guestbook

# View history
argocd app history guestbook

# Rollback
argocd app rollback guestbook 1

# View logs
argocd app logs guestbook

# Repository Management
argocd repo add https://github.com/myorg/myrepo.git --username myuser --password mytoken
argocd repo list

# Cluster Management
argocd cluster add my-cluster
argocd cluster list

# Project Management
argocd proj create myproject
argocd proj list

Installing FluxCD

Installation

# Install Flux CLI
# macOS
brew install fluxcd/tap/flux

# Linux
curl -s https://fluxcd.io/install.sh | sudo bash

# Windows
choco install flux

# Verify installation
flux --version

# Check cluster prerequisites
flux check --pre

# Bootstrap Flux (GitHub example)
export GITHUB_TOKEN=<your-token>
flux bootstrap github \
  --owner=myorg \
  --repository=fleet-infra \
  --branch=main \
  --path=clusters/production \
  --personal

Bootstrap with GitLab

export GITLAB_TOKEN=<your-token>
flux bootstrap gitlab \
  --owner=myorg \
  --repository=fleet-infra \
  --branch=main \
  --path=clusters/production

FluxCD: GitRepository and Kustomization

GitRepository Resource

# gitrepository.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: webapp
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/myorg/webapp
  ref:
    branch: main
  secretRef:
    name: git-credentials

Kustomization Resource

# kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: webapp
  namespace: flux-system
spec:
  interval: 5m
  path: ./deploy/production
  prune: true
  sourceRef:
    kind: GitRepository
    name: webapp
  healthChecks:
  - apiVersion: apps/v1
    kind: Deployment
    name: webapp
    namespace: production
  timeout: 2m

FluxCD: Helm Repositories and Releases

HelmRepository

# helmrepository.yaml
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: nginx
  namespace: flux-system
spec:
  interval: 10m
  url: https://kubernetes.github.io/ingress-nginx

HelmRelease

# helmrelease.yaml
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: nginx-ingress
  namespace: flux-system
spec:
  interval: 5m
  chart:
    spec:
      chart: ingress-nginx
      version: 4.8.3
      sourceRef:
        kind: HelmRepository
        name: nginx
        namespace: flux-system
  values:
    controller:
      replicaCount: 3
      service:
        type: LoadBalancer
  install:
    remediation:
      retries: 3
  upgrade:
    remediation:
      retries: 3

FluxCD CLI Commands

# Source Management
flux create source git webapp \
  --url=https://github.com/myorg/webapp \
  --branch=main \
  --interval=1m

flux get sources git
flux reconcile source git webapp

# Kustomization Management
flux create kustomization webapp \
  --source=webapp \
  --path="./deploy/production" \
  --prune=true \
  --interval=5m

flux get kustomizations
flux reconcile kustomization webapp

# Helm Management
flux create source helm nginx \
  --url=https://kubernetes.github.io/ingress-nginx \
  --interval=10m

flux create helmrelease nginx-ingress \
  --source=HelmRepository/nginx \
  --chart=ingress-nginx \
  --chart-version=4.8.3

flux get helmreleases
flux reconcile helmrelease nginx-ingress

# Image Automation
flux create image repository webapp \
  --image=myregistry/webapp \
  --interval=1m

flux create image policy webapp \
  --image-ref=webapp \
  --select-semver=">=1.0.0"

# Monitoring
flux logs
flux events
flux stats

Image Automation with FluxCD

FluxCD excels at automated image updates:

# imagerepository.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImageRepository
metadata:
  name: webapp
  namespace: flux-system
spec:
  image: myregistry/webapp
  interval: 1m
---
# imagepolicy.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta2
kind: ImagePolicy
metadata:
  name: webapp
  namespace: flux-system
spec:
  imageRepositoryRef:
    name: webapp
  policy:
    semver:
      range: ">=1.0.0"
---
# imageupdateautomation.yaml
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
  name: webapp
  namespace: flux-system
spec:
  interval: 1m
  sourceRef:
    kind: GitRepository
    name: webapp
  git:
    checkout:
      ref:
        branch: main
    commit:
      author:
        email: [email protected]
        name: fluxcdbot
      messageTemplate: "Update image to {{range .Updated.Images}}{{println .}}{{end}}"
    push:
      branch: main
  update:
    path: ./deploy/production
    strategy: Setters

Update deployment with image marker:

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

Multi-Environment Strategy

Repository Structure

fleet-infra/
├── base/
│   └── webapp/
│       ├── deployment.yaml
│       ├── service.yaml
│       └── kustomization.yaml
├── clusters/
│   ├── development/
│   │   ├── webapp-kustomization.yaml
│   │   └── flux-system/
│   ├── staging/
│   │   ├── webapp-kustomization.yaml
│   │   └── flux-system/
│   └── production/
│       ├── webapp-kustomization.yaml
│       └── flux-system/
└── environments/
    ├── development/
    │   └── webapp/
    │       ├── kustomization.yaml
    │       └── patches.yaml
    ├── staging/
    │   └── webapp/
    │       ├── kustomization.yaml
    │       └── patches.yaml
    └── production/
        └── webapp/
            ├── kustomization.yaml
            └── patches.yaml

Environment-Specific Kustomization

# clusters/production/webapp-kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: webapp
  namespace: flux-system
spec:
  interval: 5m
  path: ./environments/production/webapp
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
  validation: client
  healthChecks:
  - apiVersion: apps/v1
    kind: Deployment
    name: webapp
    namespace: production

Progressive Delivery with Flagger

Flagger automates canary deployments:

# Install Flagger
flux create source helm flagger \
  --url=https://flagger.app \
  --interval=1h

flux create helmrelease flagger \
  --source=HelmRepository/flagger \
  --chart=flagger \
  --chart-version=1.33.0 \
  --namespace=flagger-system \
  --create-target-namespace=true

Canary Release

# canary.yaml
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: webapp
  namespace: production
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: webapp
  service:
    port: 80
    targetPort: 8080
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
    - name: request-success-rate
      thresholdRange:
        min: 99
      interval: 1m
    - name: request-duration
      thresholdRange:
        max: 500
      interval: 1m
    webhooks:
    - name: load-test
      url: http://flagger-loadtester/
      timeout: 5s
      metadata:
        cmd: "hey -z 1m -q 10 -c 2 http://webapp-canary.production/"

Security Best Practices

Sealed Secrets (FluxCD)

# Install Sealed Secrets controller
flux create source helm sealed-secrets \
  --url=https://bitnami-labs.github.io/sealed-secrets

flux create helmrelease sealed-secrets \
  --source=HelmRepository/sealed-secrets \
  --chart=sealed-secrets

# Create sealed secret
echo -n mypassword | kubectl create secret generic db-password \
  --dry-run=client \
  --from-file=password=/dev/stdin \
  -o yaml | \
  kubeseal -o yaml > sealed-secret.yaml

RBAC Configuration (ArgoCD)

# argocd-rbac-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.default: role:readonly
  policy.csv: |
    p, role:developers, applications, get, */*, allow
    p, role:developers, applications, sync, */*, allow
    p, role:ops-team, applications, *, */*, allow
    p, role:ops-team, clusters, *, *, allow
    g, developers-group, role:developers
    g, ops-group, role:ops-team

Monitoring and Notifications

ArgoCD Notifications

# argocd-notifications-cm.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-notifications-cm
  namespace: argocd
data:
  service.slack: |
    token: $slack-token
  trigger.on-deployed: |
    - when: app.status.operationState.phase in ['Succeeded']
      send: [app-deployed]
  template.app-deployed: |
    message: Application {{.app.metadata.name}} deployed successfully
    slack:
      attachments: |
        [{
          "title": "{{.app.metadata.name}}",
          "color": "good",
          "fields": [{
            "title": "Sync Status",
            "value": "{{.app.status.sync.status}}",
            "short": true
          }]
        }]

FluxCD Notifications

# notification-provider.yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Provider
metadata:
  name: slack
  namespace: flux-system
spec:
  type: slack
  channel: deployments
  secretRef:
    name: slack-webhook
---
# alert.yaml
apiVersion: notification.toolkit.fluxcd.io/v1beta2
kind: Alert
metadata:
  name: webapp
  namespace: flux-system
spec:
  providerRef:
    name: slack
  eventSeverity: info
  eventSources:
  - kind: Kustomization
    name: webapp
  - kind: HelmRelease
    name: webapp

When to Choose ArgoCD vs FluxCD

Choose ArgoCD When:

UI is important for team visibility ✅ SSO integration is required ✅ Multi-cluster management from single control plane ✅ Application health visualization is valuable ✅ Quick onboarding for teams new to GitOps ✅ Rich RBAC requirements

Choose FluxCD When:

CLI-first workflow preferred ✅ Image automation is critical ✅ Multi-tenancy requirements ✅ Progressive delivery with Flagger ✅ Lightweight footprint desired ✅ CNCF ecosystem alignment important ✅ Modular architecture for customization

Best Practices

Repository Structure

gitops-repo/
├── apps/
│   ├── base/
│   └── overlays/
│       ├── dev/
│       ├── staging/
│       └── prod/
├── infrastructure/
│   ├── controllers/
│   ├── sources/
│   └── configs/
└── clusters/
    ├── dev/
    ├── staging/
    └── prod/

Security

  • Use private repositories for sensitive configs
  • Implement RBAC for repository access
  • Encrypt secrets (Sealed Secrets, SOPS, External Secrets)
  • Enable audit logging
  • Use signed commits

Operations

  • Monitor sync status and health
  • Set up alerts for sync failures
  • Use health checks for critical resources
  • Implement proper retry and backoff strategies
  • Test changes in lower environments first

Performance

  • Use sync windows for controlled deployments
  • Implement resource limits on GitOps operators
  • Optimize webhook delivery
  • Cache Git repositories appropriately
  • Monitor resource utilization

Troubleshooting

ArgoCD

# Check application status
argocd app get myapp

# View sync errors
argocd app get myapp --show-operation

# Force refresh
argocd app get myapp --refresh

# Hard refresh
argocd app get myapp --hard-refresh

# Check controller logs
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller

FluxCD

# Check reconciliation status
flux get all

# View logs
flux logs --all-namespaces

# Suspend/resume reconciliation
flux suspend kustomization webapp
flux resume kustomization webapp

# Force reconciliation
flux reconcile kustomization webapp --with-source

# Check events
kubectl events -n flux-system

Migration Path

From Traditional CI/CD to GitOps

  1. Start small: Pick one non-critical application
  2. Structure repository: Organize manifests properly
  3. Deploy GitOps tool: Install ArgoCD or FluxCD
  4. Create application: Point to Git repository
  5. Enable automation: Turn on auto-sync
  6. Monitor: Watch for issues, iterate
  7. Expand: Gradually add more applications

Key Takeaways

  • GitOps uses Git as single source of truth
  • ArgoCD provides user-friendly UI and visualization
  • FluxCD offers modular, lightweight architecture
  • Both tools support Helm and Kustomize
  • Progressive delivery available via Argo Rollouts or Flagger
  • Security through RBAC, secrets management, and audit trails
  • Choose based on team needs and workflow preferences

Resources for Further Learning


GitOps represents the future of Kubernetes deployments. Whether you choose ArgoCD for its rich UI and ease of use, or FluxCD for its modularity and GitOps purity, both tools will transform how you manage applications in Kubernetes.