Mark Ku's Blog
Podcast ConversationAI dialogue version of this article · Mandarin audio
Audio for this article is powered by VoAIVoAI

Why Argo CD + GitOps?

If you only have one K8s cluster with low update frequency, manual deployment with kubectl apply is fine. But once the architecture grows and you hit the following scenarios, manual operations start to fall short:

  • Need to manage 10–20 K8s clusters (Dev/Staging/Prod and other environments)
  • Frequently switching kubectl context is error-prone
  • After manual Apply, hard to confirm whether the cluster's current state matches version control

This is where introducing GitOps + Argo CD effectively addresses these pain points.

The core idea is intuitive: "automate the deployment operation, reduce human intervention."

  • Git as the single source of truth: All YAML and Helm values must go into Git version control.
  • Argo CD handles auto-sync: Continuously monitors the Git repo; when code changes, it automatically syncs cluster state to the desired state.
  • Fast rollback: If a deploy goes wrong, just Revert in Git and Argo CD will help switch back to the previous version, ensuring stability.

These notes record the key steps for setting up this flow: installing Argo CD via Helm, configuring GitLab SSO, and how to plan permission management properly.


Core GitOps concept

I won't go too deep into theory, but one concept is essential.

Traditional deployment often lacks standardization — what each person manually applies may differ, and over time the environment "drifts" and becomes hard to trace. GitOps treats Git as the "standard SOP," and Argo CD is the executor. It ensures the cluster's actual state (Live State) always matches the definition in the Git repo (Desired State). If someone manually modifies cluster resources, Argo CD detects the difference and marks it as OutOfSync — you can even configure self-healing to revert it.

Once you understand this, the configuration steps below have more context.

(Prerequisites like adding the Helm Repo are skipped here — let's jump to the main course of SSO and permissions.)

Implementation: settle authentication and installation

Step 1: Prepare the Helm environment

Easy step — add the Helm repo and export the Argo CD default values.yaml for editing.

# Add Helm repository
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update

# Export default values for editing
helm show values argo/argo-cd --version 8.3.2 > values.yaml

Step 2: Create a GitLab OAuth Application

This step establishes trust between Argo CD and GitLab. By configuring an Application in GitLab, users can log in to Argo CD with their GitLab account — for unified account management.

  1. Log in to GitLab → top-right avatar → Settings → Applications
  2. Add an Application:
  • Name: ArgoCD SSO
  • Redirect URI: http://<your-node-ip>:32009/api/dex/callback
  • Scopes: check openid, read_user, email
  1. After saving, get the Application ID and Secret

⚠️ Don't commit the Client ID and Secret to a Git repository.

Two configuration methods:

A. Write directly into values.yaml (recommended — easy to automate) B. Configure inside Argo CD UI after install (good for ad-hoc adjustments)

Step 3: Create a GitLab Group Access Token

Argo CD needs permission to access the Git repo to pull code. Strongly recommend not using a personal account's Token — staff changes or password updates would interrupt production deployments. Best practice: get a dedicated Group Access Token.

  1. Go to your GitLab Group page → SettingsAccess Tokens.
  2. Create a Token:
  • Name: something recognizable, e.g., argocd-group-readonly
  • Scopes: just check read_repository and read_api — least privilege is safest.
  • Role: pick Reporter (read-only suffices).
  1. Critical: copy the Token immediately after creation — GitLab only displays it once.

Step 4: Write values.yaml

This is the key step. We'll create a custom values.yaml filling in the SSO and Repo info gathered above, overriding Helm's default settings.

configs:
  cm:
    # Argo CD's entrypoint URL — used by SSO callback
    url: http://<your-node-ip>:32009
    
    # The most likely place to get stuck: Dex (SSO) configuration
    dex.config: |
      connectors:
        - type: gitlab
          id: gitlab
          name: GitLab
          config:
            baseURL: https://gitlab.example.com
            clientID: <your-gitlab-oauth-client-id>
            clientSecret: <your-gitlab-oauth-client-secret>
            # Make sure the Callback URL in GitLab Application settings matches this
            redirectURI: http://<your-node-ip>:32009/api/dex/callback
  
  # Plug in the Token you just created so Argo CD recognizes your GitLab
  credentialTemplates:
    gitlab-group-token:
      url: https://gitlab.example.com
      username: oauth2  # always 'oauth2'
      password: <your-gitlab-group-access-token>
  
  # Pre-register repos to save adding them one-by-one in the UI later
  repositories:
    kong-api-gateway:
      url: https://gitlab.example.com/your-group/kong-api-gateway.git
      type: git
    argocd-deployment:
      url: https://gitlab.example.com/your-group/argocd-deployment.git
      type: git

# Server network setup (Lab uses NodePort for laziness; production should use Ingress)
server:
  service:
    type: NodePort
    nodePortHttp: 32009
    nodePortHttps: 32010
  
  # Allow HTTP in dev — otherwise certificate errors will keep popping up
  extraArgs:
    - --insecure

💡 Production recommendation: Ingress + TLS to replace NodePort, and remove --insecure.

Step 5: Install Argo CD

Once configs are ready, fire away:

# Install Argo CD
helm upgrade --install homelab-argo argo/argo-cd \
  --version 8.3.2 \
  -f values.yaml \
  -n argocd --create-namespace

# Wait for Pods to come up
kubectl wait --for=condition=ready pod \
  -l app.kubernetes.io/name=argocd-server \
  -n argocd --timeout=300s

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

Change the admin password right after first login.


Permission management

Step 6: Configure RBAC and AppProject

After installing Argo CD, you still need to define "who can manage what resources" and "which clusters can be deployed to." The AppProject mechanism is essentially a permission whitelist — without proper config, Argo CD won't be able to deploy anything.

Create argocd-projects.yaml:

apiVersion: v1
kind: List
items:
# Default Project — basic permissions
- apiVersion: argoproj.io/v1alpha1
  kind: AppProject
  metadata:
    name: default
    namespace: argocd
  spec:
    # Allow all source repos
    sourceRepos:
    - '*'
    
    # Allow deploy to all clusters and namespaces
    destinations:
    - namespace: '*'
      server: '*'
    
    # Allow operating cluster-level resources
    clusterResourceWhitelist:
    - group: '*'
      kind: '*'
    
    # Allow operating namespace-level resources
    namespaceResourceWhitelist:
    - group: '*'
      kind: '*'

# Custom Project — tune to your needs
- apiVersion: argoproj.io/v1alpha1
  kind: AppProject
  metadata:
    name: your-project
    namespace: argocd
  spec:
    description: Your project description
    
    sourceRepos:
    - 'https://gitlab.example.com/your-group/*'
    
    destinations:
    - namespace: '*'
      server: https://kubernetes.default.svc
    
    # Cluster-level resource whitelist
    clusterResourceWhitelist:
    - group: ''
      kind: Namespace
    - group: 'rbac.authorization.k8s.io'
      kind: ClusterRole
    - group: 'rbac.authorization.k8s.io'
      kind: ClusterRoleBinding
    - group: 'apiextensions.k8s.io'
      kind: CustomResourceDefinition
    
    # Namespace-level resource whitelist
    namespaceResourceWhitelist:
    - group: ''
      kind: ConfigMap
    - group: ''
      kind: Secret
    - group: ''
      kind: Service
    - group: ''
      kind: ServiceAccount
    - group: 'apps'
      kind: Deployment
    - group: 'apps'
      kind: StatefulSet
    - group: 'batch'
      kind: Job
    - group: 'batch'
      kind: CronJob
    - group: 'networking.k8s.io'
      kind: Ingress

# Extra RBAC permissions for Argo CD Controller (supports metrics-server, etc.)
- apiVersion: rbac.authorization.k8s.io/v1
  kind: ClusterRole
  metadata:
    name: argocd-application-controller-auth-delegator
    labels:
      app.kubernetes.io/component: application-controller
      app.kubernetes.io/name: argocd-application-controller
      app.kubernetes.io/part-of: argocd
  rules:
  # Auth delegation for metrics-server
  - apiGroups: ["authentication.k8s.io"]
    resources: ["tokenreviews"]
    verbs: ["create"]
  - apiGroups: ["authorization.k8s.io"]
    resources: ["subjectaccessreviews"]
    verbs: ["create"]
  # API Services management
  - apiGroups: ["apiregistration.k8s.io"]
    resources: ["apiservices"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

- apiVersion: rbac.authorization.k8s.io/v1
  kind: ClusterRoleBinding
  metadata:
    name: argocd-application-controller-auth-delegator
  roleRef:
    apiGroup: rbac.authorization.k8s.io
    kind: ClusterRole
    name: argocd-application-controller-auth-delegator
  subjects:
  - kind: ServiceAccount
    name: argocd-application-controller
    namespace: argocd

Apply the config

# Apply AppProject and RBAC config
kubectl apply -f argocd-projects.yaml -n argocd

# Verify Projects are created
kubectl get appprojects -n argocd

# Check RBAC permissions too
kubectl get clusterrole | grep argocd
kubectl get clusterrolebinding | grep argocd

Permission structure cheat sheet

When integrating Argo CD with GitLab, permissions split into three layers:

  1. SSO login (GitLab OAuth): manages user identity — decides who can log in to the Argo CD UI.
  2. Repo access (Group Access Token): manages code access — lets Argo CD read GitLab repos.
  3. Registry (optional): manages image-pull permissions — pulling private images requires an additional Deploy Token.

💡 SSO manages "people," Group Token manages "code," Registry Token manages "images."


Worked example

Application status overview

This is the Argo CD Application status page. Key indicators to watch:

  • Application name: testapp
  • APP HEALTH:Healthy — all resources running normally.
  • SYNC STATUS:Synced to HEAD — Git config is fully synced to the cluster.
  • LAST SYNC:Sync OK — most recent sync succeeded.
  • Auto sync: ❌ Auto-sync not enabled.

🌳 Application resource tree

A K8s resource tree deployed via GitOps roughly looks like this:

  1. testapp (Application)
    • nginx-content (ConfigMap)
    • test-app (Namespace)
    • nginx-test-service (Service)
    • nginx-test (Deployment)
      • nginx-test-554867cd4b (ReplicaSet)
        • nginx-test-554867cd4b-65gtp (Pod)
        • nginx-test-554867cd4b-xgs9x (Pod)

📌 As long as the top-right shows ✅ green status, the deployment succeeded.


Quick command reference

A quick reference for common ops commands.

Helm

# Install Helm (Windows)
choco install kubernetes-helm

# Install/upgrade Argo CD
helm upgrade --install argocd argo/argo-cd \
  --namespace argocd --create-namespace \
  -f values.yaml

# Export current settings
helm get values argocd -n argocd -o yaml > current-values.yaml

K8s debug and config commands

# Inspect Argo CD Service
kubectl get svc argocd-server -n argocd

# Patch Service to NodePort
kubectl patch svc argocd-server -n argocd -p '{
  "spec": {
    "type": "NodePort",
    "ports": [
      {
        "port": 80,
        "targetPort": 8080,
        "nodePort": 32009
      }
    ]
  }
}'

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

# Export AppProject config
kubectl get appprojects -n argocd -o yaml > argocd-projects.yaml

⚠️ Once you have the admin password, change it immediately.


References

Author

Mark Ku

擁有 10+ 年經驗的資深軟體工程師,現為 AI 應用 Builder,專注於大型平台架構與簡化複雜系統設計,從電商系統到訂閱與收費平台,結合 AI Agent、AI 整合與自動化開發,打造高效率且可持續演進的產品技術基礎。Read More

Found this useful?

The author's free tools, daily podcasts and newsletter are all here.

Mark Ku · This article is licensed under CC BY 4.0. Credit the author and link back to the original when reusing it.

Comments

Subscribe to Newsletter

Subscribe to get new posts delivered instantly — never miss a tech share.

By submitting, you agree to receive emails. You can anytime.

Popular Posts

View all
Mark Ku
··602

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution
Mark Ku
··490

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.
Mark Ku
··333

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki
Mark Ku
··264

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning
Mark Ku
··221

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1
Mark Ku
··215

Setting Up Samba on Ubuntu to Share Folders with Windows 11

Setting Up Samba on Ubuntu to Share Folders with Windows 11