Chapter 10.2: GitOps (ArgoCD)

Part 1: The "Why" - What is GitOps?

We've learned how to build CI/CD pipelines (Chapter 4) and how to deploy to Kubernetes (Chapter 6.2). The standard way to deploy is a **"push"** model. Your CI pipeline (e.g., GitHub Actions) runs a command at the end:

$ kubectl apply -f deployment.yml

This "pushes" the configuration to your cluster. This works, but it has serious problems in large-scale systems:

The Problems with the "Push" Model:

  • No Single Source of Truth: What is *actually* running in your cluster? To find out, you have to run kubectl get deployment. But what if a developer (with kubectl access) manually changes a running deployment using kubectl edit ...? Now your Git repo (which says image: v1) is **lying**. The cluster is running image: v1-hotfix. This is called **"Configuration Drift"** and it's a nightmare.
  • Security:** Your CI pipeline needs *admin-level credentials* (a kubeconfig file) to your production cluster. This is a massive security risk. If a hacker gets into your CI, they own your entire cluster.
  • Rollbacks are Hard: To roll back, you have to find the *old* YAML file and re-run kubectl apply on it. This is a slow, manual process during an emergency.

The Solution: GitOps (A "Pull" Model)

GitOps** is a modern, declarative way to manage and deploy to Kubernetes. It's built on one core principle:
**Git is the Single Source of Truth.**
The *only* way to make a change to production is to make a git push to a specific branch in a specific Git repository. No one (not even the senior-most admin) is allowed to use kubectl apply or kubectl edit.

How does this work? You install an "operator" (a robot) *inside* your Kubernetes cluster. The most popular one is **ArgoCD**.

  1. You create a Git repo dedicated to your K8s config (e.g., my-app-k8s-config).
  2. You install **ArgoCD** inside your cluster.
  3. You tell ArgoCD: "Your job is to constantly *watch* the main branch of the my-app-k8s-config repo."
  4. ArgoCD then enters a loop: It "pulls" the YAML from Git (the **Desired State**) and compares it to what's *actually* running in the cluster (the **Live State**).
  5. If it sees *any* difference ("Configuration Drift"), it marks the app as "OutOfSync" and (if you want) **automatically fixes it**.

If a developer runs kubectl edit and changes the image, ArgoCD will see the drift in 3 minutes and *automatically* change it back. **Git is the boss.**

To Deploy (v2):** A developer *never* touches kubectl. They just make a Pull Request to the `my-app-k8s-config` repo, changing the image from v1 to v2. Once that PR is merged, ArgoCD sees the change in Git and automatically runs the kubectl apply *for them*.
To Rollback:** You just run git revert. ArgoCD sees the "new" commit (which is actually the old state) and automatically rolls the app back to v1.

This gives you a **fully automated, 100% auditable, and secure** deployment system where every change is a Git commit.

Part 2: Installing & Setting Up ArgoCD

ArgoCD is a set of services you install directly into your Kubernetes cluster.

Step 1: Install ArgoCD on your Cluster (e.g., Minikube)

This will create a new argocd namespace and install all the necessary components (API Server, Controller, Repo Server).

# 1. Create the namespace
$ kubectl create namespace argocd

# 2. Apply the official installation YAML from GitHub
$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

service/argocd-server created
deployment.apps/argocd-repo-server created
... (and many more)

Step 2: Install the ArgoCD CLI (on your laptop)

This is the command-line tool (like kubectl) that you use to talk to the ArgoCD API server.

# On macOS (using Homebrew)
$ brew install argocd

# On 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
$ rm argocd-linux-amd64

Step 3: Access the ArgoCD UI (via Port-Forwarding)

By default, the ArgoCD server is not exposed to the internet (it's a ClusterIP service). For local development, you use kubectl port-forward to access it.

# This forwards your laptop's port 8080 to the argocd-server's port 80
# (Run this in a separate terminal and leave it running)
$ kubectl port-forward svc/argocd-server -n argocd 8080:80

You can now open http://localhost:8080 in your browser to see the ArgoCD UI!

Step 4: Log In (Get the Admin Password)

ArgoCD automatically generates a default admin password and stores it in a K8s Secret.

# 1. Get the name of the secret
$ kubectl get secrets -n argocd | grep argocd-initial-admin-secret

argocd-initial-admin-secret   ...


# 2. Get the 'password' field from that secret and decode it
$ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

MY_SECRET_PASSWORD_12345

You can now log in to the UI (http://localhost:8080) or the CLI with:
**Username:** admin
**Password:** (The password you just got)

# Log in with the CLI
$ argocd login localhost:8080

WARNING: server is not configured with TLS.
Username: admin
Password: ********************
'admin:login' logged in successfully

Read the Official ArgoCD Getting Started Guide →

Part 3: The `Application` CRD (The "What")

How do you tell ArgoCD to monitor your Git repo? You create a new Kubernetes object with kind: Application. This is a **Custom Resource Definition (CRD)** that ArgoCD installs.

This Application object is the *core* of GitOps. It tells ArgoCD: "This is the app. This is its Git repo. This is its target cluster."

Example: An `Application` YAML

Let's say you have a Git repo at `https://github.com/msmaxpro/my-app-k8s-config` that contains your deployment.yml and service.yml.

argocd-app.yml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-web-app
  namespace: argocd # This object must live in the 'argocd' namespace
spec:
  # 1. 'project': Which project this app belongs to
  project: default
  
  # 2. 'source': The "Desired State" (Your Git Repo)
  source:
    repoURL: https://github.com/msmaxpro/my-app-k8s-config.git
    targetRevision: HEAD # Track the latest commit
    path: prod # Look for YAMLs in the 'prod' folder of this repo
    
  # 3. 'destination': The "Live State" (Your K8s Cluster)
  destination:
    server: https://kubernetes.default.svc # 'kubernetes.default.svc' is the internal alias for "this cluster"
    namespace: production # Deploy the app into the 'production' namespace
    
  # 4. 'syncPolicy': How to sync (optional)
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

How to Apply This:

You apply this file *once* to your cluster:

$ kubectl apply -f argocd-app.yml -n argocd
application.argoproj.io/my-web-app created

As soon as you do this, ArgoCD's "Application Controller" sees the new object. It will:
1. Clone your `https://github.com/msmaxpro/my-app-k8s-config.git` repo.
2. Look in the `prod` folder.
3. Read all the YAML files (deployment.yml, service.yml).
4. Compare them to what's in the `production` namespace.
5. See that *nothing* is running, and declare the app "OutOfSync".
6. (Because we set automated: true) It will then automatically run kubectl apply on those files *for you*.
7. Your app is now deployed!

Part 4: Deep Dive - The `syncPolicy`

This is the "magic" of GitOps. It controls how ArgoCD fixes "Configuration Drift."

`syncPolicy: {}` (Manual Sync)

If you don't set an automated policy (or leave it empty), ArgoCD will be in **Manual Sync** mode.
- You merge a PR to update the `image: v2` in Git.
- ArgoCD detects this. It shows a big "OutOfSync" button in its UI.
- Your app is **still running v1**.
- A human must log in to the ArgoCD UI and click the **"SYNC"** button.
- *Then*, ArgoCD will run kubectl apply and deploy v2.
This is safe, and is a form of "Continuous Delivery" (with manual approval).

`syncPolicy: { automated: {} }` (Automated Sync)

This is **Continuous Deployment**.
- You merge a PR to update the `image: v2` in Git.
- ArgoCD detects this (within 3 minutes by default).
- ArgoCD *immediately* runs kubectl apply and deploys v2.
- No human intervention required.

`prune: true` (Auto-Deletion)

What if you *delete* service.yml from your Git repo?
- If prune: false (the default), ArgoCD will just show "OutOfSync." The Service will *keep running* in your cluster.
- If prune: true, ArgoCD will see the Service exists in the cluster but *not* in Git, and will **automatically run kubectl delete svc/my-service** to remove it.

`selfHeal: true` (The "Anti-kubectl" Shield)

This is the most important feature for enforcing Git as the source of truth.
- A developer logs in at 3 AM and runs kubectl edit deployment my-web-app --image=v3-hotfix.
- Your "Live State" (v3-hotfix) is now different from your "Desired State" (v2).
- If selfHeal: false (the default), ArgoCD will just show "OutOfSync" and do nothing.
- If selfHeal: true, ArgoCD will detect this drift and **automatically run kubectl apply** to change the image *back* to `v2`, "healing" the cluster and overriding the manual change.
This forces all changes to go through Git.

Part 5: The Full GitOps Workflow

Here is the complete, end-to-end loop for a professional DevOps team.

The Setup

  1. **Repo 1: `my-node-app` (The App Code)**
    • Contains your app.js, package.json, and Dockerfile.
  2. **Repo 2: `my-k8s-config` (The GitOps Repo)**
    • Contains your deployment.yml (which points to image: my-app:v1) and service.yml.
  3. **CI Pipeline (GitHub Actions on `my-node-app`)**
    • A workflow that triggers on `push` to main.
  4. **CD (ArgoCD)**
    • An Application object inside Kubernetes that "watches" the `my-k8s-config` repo.

The Workflow in Action:

  1. Dev:** A developer makes a code change (e.g., adds a new API endpoint) to my-node-app and pushes to a feature branch.
  2. PR:** The developer opens a Pull Request.
  3. CI (GitHub Actions): The CI pipeline on `my-node-app` triggers.
    • Builds the code.
    • Runs unit tests.
    • Runs SAST, SCA, and Secret scans.
  4. Merge:** The PR is approved and merged into main.
  5. CI/CD (GitHub Actions): A *second* pipeline on `my-node-app` (triggered by push to main) runs:
    • Builds the production Docker image.
    • Tags it with the Git commit hash (e.g., ghcr.io/msmaxpro/my-app:abc1234).
    • Scans the image with Trivy.
    • **Pushes** the image to the GitHub Container Registry (GHCR).
  6. (The Magic Step):** The *same* pipeline then:
    • Checks out the *other* repo (my-k8s-config).
    • Updates deployment.yml to change image: my-app:v1 to image: my-app:abc1234.
    • Commits and pushes this change to the my-k8s-config repo.
  7. GitOps (ArgoCD):**
    • ArgoCD (which is *always* watching) detects the new commit in my-k8s-config.
    • It sees the "Desired State" (image abc1234) is different from the "Live State" (image v1).
    • It automatically (because selfHeal: true) triggers a kubectl apply.
    • Kubernetes performs a rolling update, safely deploying your new code to production.

This entire process, from git push to production, is **100% automated** and **100% auditable** (every change is a Git commit).

The "App of Apps" Pattern

You don't want to run kubectl apply -f argocd-app.yml for all 500 of your apps. The "App of Apps" pattern is the solution.
1. You create one "root" Application object.
2. You point this *one* app at a Git repo (e.g., all-my-apps).
3. Inside *that* repo, there are no deployments, just 500 *other* Application.yml files.
Now, to add a new app to your company, you just add a new YAML file to the all-my-apps repo. ArgoCD sees it and *automatically creates the new ArgoCD Application*, which in turn automatically deploys your app.

Read the Official ArgoCD Documentation →

© 2025 CodeWithMSMAXPRO. All rights reserved.