24. září 2024

GitOps a ArgoCD

V tomto článku se podíváme na GitOps (🚨 buzzword alert! 🚨) a nástroj ArgoCD, který je jedním z nástrojů pro GitOps.

Co je to GitOps

GitOps je způsob, jak nasazovat změny do prostředí pomocí Gitu, kde jsou všechny změny zaznamenány jako commity v Git repozitáři.

GitOps se začal hodně rozšiřovat až v posledních letech, společně s Kubernetes. Protože vše v Kubernetes je deklarativní a je možné popsat v konfiguračních souborech (YAML, jsonnet,...) a to i s nástroji jako Kustomize nebo Helm.

Kustomize prakticky pouze přepisuje YAML na základě hierarchie. A i Helm, který funguje jako šablonovací systém YAML souborů potřebuje odněkud brát hodnoty (values), které je možné uložit do souboru, opět se dostáváme k deklarativnímu zápisu.

Dál se tedy budu věnovat GitOps a Kubernetes, ale GitOps je možné potkat i jinde.

GitOps je založen na několika principech:

Hlavní výhodou GitOps oproti jiným metodám, je že GitOps jednak zaručuje korektnost nastavení v Kubernetes clusteru, protože jakmile se změna dostane do repozitáře, automaticky se aplikuje. Nebo když se stav clusteru změní a je jiný, než má být, GitOps automaticky znovu-aplikuje konfiguraci a vrátí se do správného stavu, brání tedy tzv. config-driftu. Například ArgoCD kontroluje změny každé dvě minuty (ve výchozím nastavení). Druhým velkým plusem je, že vše je uloženo v repozitáři a je možné tedy repozitář s konfigurací považovat i za zálohu a stejně jako běžnou aplikaci, je možné obnovit prostředí z repozitáře.

Protože vše je uloženo v repozitáři a "aktivní část" systému funguje jako agent v clusteru, kde si pravidelně stahuje repozitář a hledá změny v repozitáři vůči aktuálnímu stavu clusteru. A pokud nějakou změnu najde, automaticky ji aplikuje.

GitOps vs CI/CD

GitOps nám tedy řeší automatizaci nasazování aplikace do clusteru (když se bavíme o Kubernetes), což je často stejný cíl jako u CI/CD.

Proč tedy další paradigma, které řeší to samé?

CI/CD pipeliny fungují na push principu. Tj. jakmile vyjde nová verze nebo se změní konfigurace, spustí se pipeline, které spouští testy atd. A pokud je vše v pořádku, na konci pipeline dochází k nasazení změn.

To je velice přímočarý postup, ale pokud se rozbije cluster jako takový, pravděpodobně nebude možné beze změny nasadit novou verzi. Pravděpodobně budete potřebovat upravit nějakou konfiguraci CI/CD pipeline. Což nás blokuje a tvoří manuální úkol, který někdo musí udělat.

Dalším rozdílem je případ, kdy máte více Kubernetes clusterů. V CI/CD pipeline je to celkem snadné pro dva, tři clustery, ale pokud jich máte desítky nebo stovky, je to extrémě obtížné spravovat.

V případě GitOps/ArgoCD máme stále jeden identický repozitář ze kterého může dělat pull libovolný počet ArgoCD instancí napříč různými clustery i napříč cloudy. Pokud bychom případně chtěli nebo potřebovali vytvořit nějak specifickou konfiguraci pro cluster, region nebo třeba dle země, stačí vytvořit konfigurační soubor a načítat takovou konfiguraci například pomocí ApplicationSetu. Ale takové řešení se lépe dělá s jednou instancí ArgoCD nebo alespoň ne s ArgoCD instancí v každém clusteru.

Slepice a vejce problém

Jeden zatím neuniverzálě vyřešený problém s GitOps:

Co bylo dřív slepice, nebo vejce?

Tedy s GitOps postupy a nástroji, je vlastně nemožné dostat ty samé GitOps nástroje do clusteru, než je tam nainstalujeme.

Dnes je mnoho různých postupů a nástrojů, které se snaží tento problém řešit. Typicky se jedná o "bootstrap skript" nebo nástroj do příkazové řádky, kterým můžeme nainstalovat GitOps nástroje do clusteru. Případně pokud vaše Kubernetes clustery spravujete Terraformem, můžete využít například Helm provideru a nainstalovat ArgoCD do každého clusteru právě Terraformem. Což je vcelku elegantní řešení:

provider "helm" {
  kubernetes {
    config_path = "~/.kube/config"
  }
}

resource "helm_release" "argocd" {
  name             = "argocd"
  repository       = "https://argoproj.github.io/argo-helm"
  chart            = "argo-cd"
  namespace        = "argocd"
  create_namespace = true
  version          = "7.6.1"  # Verze Helm chartu

  # pro konfiguraci Helm chartu pak vyuzijte
  # values = [] pole, kam muzete vlozit YAML snippety
  # values = [
  #   file("${path.module}/values.yaml"),
  # ]
  # nebo set {} objekt, kde muzete nastavit hodnoty
  # set {
  #   name  = "server.service.type"
  #   value = "LoadBalancer"
  # }
}

ArgoCD

Argo logo

ArgoCD je open-source nástroj, který právě staví nad GitOps paradigmem. Jedná se o jeden z nejoblíbenějších nástrojů pro GitOps v Kubernetes ekosystému. Je primárně vyvíjený firmou Akuity, i když dnes už patří pod CNCF a projekt je ve stavu Graduated.

Argo je dokonce celá rodina nástrojů, které se integrují a rozšiřují Kubernetes.

ArgoCD podporuje jednak obyčejné YAML manifesty, ale i zmiňovaný Kustomize, jsonnet a Helm. Zároveň je bezpotíží možné používat Kuberentes operátory a custom resources, které přinášejí.

ArgoCD má několik svých vlastních resources, kterými popisuje aplikace, ale i projekty a jejich dělení.

Populární alternativou k ArgoCD je FluxCD.

Jak začít s ArgoCD?

V první řadě je třeba ArgoCD nainstalovat do clusteru 😄

K tomu máme několik možností, Kustomize, Helm nebo ArgoCD Autopilot (o tom píšu později), který nástroj si vyberete nechám na vaší preferenci.

Kustomize:

# kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: argocd
resources:
  - https://raw.githubusercontent.com/argoproj/argo-cd/v2.7.2/manifests/install.yaml

Helm:

# argocd-values.yaml
global:
  domain: argocd.example.com

certificate:
  enabled: true

server:
  ingress:
    enabled: true
    ingressClassName: nginx
    annotations:
      nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
      nginx.ingress.kubernetes.io/ssl-passthrough: "true"
    tls: true
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd --namespace argocd --create-namespace --values ./argocd-values.yaml

Tím máme ArgoCD v clusteru a můžeme přidat první repozitář, vytvořit první aplikaci a začít nasazovat aplikace s ArgoCD a pomužívat GitOps.

Repozitář můžeme přidat přes argocd CLI, o tom později, nebo v UI. Repozitář v UI přidáme v Settings > Repositories > +Connect Repository.

Tam potom vyplníme URL, uživatele a heslo (nebo token místo hesla), popř. můžeme použít SSH.

Po přidání repozitáře potom vytvoříme první aplikaci, která bude koukat právě do našeho repozitáře.

# hello-world-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: hello-world
  namespace: argocd
spec:
  destination:
    namespace: default
    server: https://kubernetes.default.svc
  project: testing
  source:
    repoURL: https://git.example.com/nase/repo
    targetRevision: HEAD
    path: hello-world

Protože aplikace je Kubernetes resource, musíme ji dostat do clusteru, což uděláme pomocí kubectl.

kubectl apply -f hello-world-app.yaml

A v ArgoCD UI bychom nyní měli vidět naši hello-world aplikaci!

Abychom nemuseli přidávat každou aplikaci zvlášť ručně do clusteru přes kubectl, používá se tzv. app-of-apps, tedy aplikace aplikací.

Jedná se o ArgoCD aplikaci, která ukazuje do repozitáře a do složky, kde jsou uloženy všechny manifesty všech aplikací.

Pak je ale potřeba používat finalizery, aby když nějakou aplikaci odstraníme, aby se smazala z clusteru. A nebo naopak ne, abychom nic automaticky nemazali a zabránili tak případné chybě a problémům.

Taková app-of-apps typicky vypadá třeba takto:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: app-of-apps
  namespace: argocd
  annotations:
    argocd.argoproj.io/sync-wave: "-1"
spec:
  project: cd
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
  source:
    path: apps/
    directory:
      # recurse=true, jak název naznačuje, znamená, že ArgoCD hledá manifesty ve všech podsložkách
      # což se občas může hodit
      recurse: true
    repoURL: [email protected]:infra/gitops.git
    targetRevision: HEAD
  destination:
    server: "https://kubernetes.default.svc"
    namespace: default

Pokud bychom chtěli si v ArgoCD UI udělat větší pořádek, což třeba při větším počtu aplikací je dobrý nápad. A nebo pokud nasazujete dedikované instance aplikace pro jednotlivé tenanty například, je dobré používat projekty.

apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: testing
  namespace: argocd
spec:
  description: Testing project
  destinations:
    - namespace: "*"
      server: "*"
  sourceRepos:
    - "*"
  clusterResourceWhitelist:
    - group: "*"
      kind: "*"
  namespaceResourceWhitelist:
    - group: "*"
      kind: "*"

V projektu mimo jiné definujeme, do jakých clusterů, namespace a jaké druhy resources můžou aplikace v projektu spravovat. Čímž můžeme například globálně zakazovat ClusterRole resources apod. A díky Kyvernu do každého namespace vkládat ResourceQuota a NetworkPolicy a omezovat tak oprávnění a zamezovat jednotlivým instancím interagovat s jinými.

ArgoCD CLI

argocd je CLI (program pro příkazovou řádku) nástroj, který vám usnadní práci s ArgoCD. Můžete s ním vytvářet aplikace, projekty, získávat informace o aplikacích, spravovat v ArgoCD clustery, spravovat repozitáře (Git, Helm,...) a další věci.

Pokud jste příznivci terminálu, určitě se na argocd podívejte. Je to super nástroj, díky kterému můžete začít s ArgoCD během pár minut a nemusíte se učit CRD a psát je ručně, i když s tím vám dnes hbitě poradí ChatGPT nebo GitHub Copilot.

  1. port-forwarding na ArgoCD Service

    kubectl port-forward svc/argocd-server -n argocd 8080:443
    
  2. získání výchozího hesla pro uživatele admin

    argocd admin initial-password -n argocd
    
  3. otevřete svůj webový prohlížeč a jděte na adresu http://localhost:8080 a přihlaste se

ArgoCD Autopilot

ArgoCD Autopilot je CLI nástroj, který vám pomůže jednat nasadit ArgoCD do clusteru a dále spravovat všechny aplikace, projekty apod. ArgoCD Autopilot má vybraný pohled (opinionated) jak by měl vypadat Gitový repozitář, kam ArgoCD kouká a řeší jak má takový repozitář vypadat a jak má být uspořádaný (složky, soubory, aplikace, projekty,...).

Takže vám dokáže velmi usnadnit práci a nemusíte přemýšlet, kam daná věc patří, ArgoCD Autopilot to vyřeší za vás. Na druhou stranu, jako každý opinionated nástroj není moc flexibilní a je možné, že vám jeho přístup nebude vyhovovat. 🤷‍♂️

# Git token je třeba pro přístup do repozitáře
# kde token získáte, záleží na vašem Git serveru
# GitHub - Personal Access Token pod vaším účtem: Settings > Developer Settings > Classic Token
# GitLab - Personal Access Token nebo Deploy Token skupiny
export GIT_TOKEN="xxx"

# HTTPS cesta k repozitáři
export GIT_REPO=https://gitlab.example.com/<group>/<repo>

# Bootstrap ArgoCD do clusteru a setup repozitáře
# Poznámka: argocd-autopilot automaticky udělá push do repozitáře
argocd-autopilot repo bootstrap

V ArgoCD pak uvidíte tři aplikace:

# vytvoření nového projektu
argocd-autopilot project create testing

# vytvoření nové aplikace
argocd-autopilot app create hello-world \
  --app github.com/argoproj-labs/argocd-autopilot/examples/demo-app/ \
  -p testing \
  --wait-timeout 2m

ArgoCD Image Updater plugin

ArgoCD Image Updater řeší automatické sledování (watch) Docker/OCI registrů, zda není k dispozici nová verze obrazu (image) odpovídající například regulárnímu výrazu.

Pokud je nový image k dispozici, automaticky upraví manifest ArgoCD aplikace, čímž dojde k automatickému nasazení nové verze. A také udělá commit do Git repozitáře, abychom měli vše aktuální.

Je to tedy jednoduché a přímočaré řešení, ale vlastně je až příliž jednoduché a nejde s ním dělat složitější věci. Pokud chcete něco sofistikovanjšího, podívete se na Kargo.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  annotations:
    # Jediné co potřebujete, je tato anotace na ArgoCD aplikaci
    # a nainstalovaný a nastavený Image Updater plugin, vše pak funguje automaticky (automagicky :D)
    argocd-image-updater.argoproj.io/image-list: gcr.io/heptio-images/ks-guestbook-demo:^0.1
    # pro aktualizaci Git repozitáře je třeba ale přidat ještě jednu anotaci:
    argocd-image-updater.argoproj.io/write-back-method: git
    # případně je k dispozici ještě hodnota `arogcd`, v ten moment se nedělá push (write-back) do repozitáře
    # a změny se drží pouze v ArgoCD, což jde ale proti GitOps principu: co je v repozitáři je nasazené
  name: guestbook
  namespace: argocd
spec:
  destination:
    namespace: guestbook
    server: https://kubernetes.default.svc
  project: default
  source:
    path: helm-guestbook
    repoURL: https://github.com/argocd-example-apps/argocd-example-apps
    targetRevision: HEAD

Kargo

Kargo Guestbook demo screenshot

Kargo screenshot pro demo "guestbook" aplikaci

Kargo je open-source projekt od Akuity, který staví nad Argo CD a Rollouts a snaží se vyplnit chybějící článek řetězu, jak se dodává souftware z Gitu do produkce.

Kargo staví vlastní "pipeline", které řeší nasazení aplikace do prostředí (stage) a zároveň i progresivní postup jednotlivých verzí (freight) napříč prostředímy.

Díky Kargu zároveň získáváme velice pěknou vizualizaci, kde se právě nachází jaká verze aplikace a v jakém prostředí.

Pojďme si rozebrat jednotlivé Kargo pojmy:

Kargo pak využívá ArgoCD ApplicationSet, kterým tvoří aplikace pro jednotlivé Stages:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: kargo-demo
  namespace: argocd
spec:
  generators:
    - list:
        elements:
          - stage: test
          - stage: uat
          - stage: prod
  template:
    metadata:
      name: kargo-demo-{{stage}}
      annotations:
        kargo.akuity.io/authorized-stage: kargo-demo:{{stage}}
    spec:
      project: default
      source:
        repoURL: ${GITOPS_REPO_URL}
        targetRevision: stage/{{stage}}
        path: stages/{{stage}}
      destination:
        server: https://kubernetes.default.svc
        namespace: kargo-demo-{{stage}}
      syncPolicy:
        syncOptions:
          - CreateNamespace=true

Pak vytvoříme všechny potřebné resources, tedy Stages, Project a Warehouse:

# kargo-demo.yaml
apiVersion: kargo.akuity.io/v1alpha1
kind: Project
metadata:
  name: kargo-demo
---
apiVersion: v1
kind: Secret
type: Opaque
metadata:
  name: kargo-demo-repo
  namespace: kargo-demo
  labels:
    kargo.akuity.io/cred-type: git
stringData:
  repoURL: ${GITOPS_REPO_URL}
  username: ${GITHUB_USERNAME}
  password: ${GITHUB_PAT}
---
apiVersion: kargo.akuity.io/v1alpha1
kind: Warehouse
metadata:
  name: kargo-demo
  namespace: kargo-demo
spec:
  subscriptions:
    - image:
        repoURL: public.ecr.aws/nginx/nginx
        semverConstraint: ^1.26.0
        discoveryLimit: 5
---
apiVersion: kargo.akuity.io/v1alpha1
kind: Stage
metadata:
  name: test
  namespace: kargo-demo
spec:
  requestedFreight:
    - origin:
        kind: Warehouse
        name: kargo-demo
      sources:
        direct: true
  promotionMechanisms:
    gitRepoUpdates:
      - repoURL: ${GITOPS_REPO_URL}
        writeBranch: stage/test
        kustomize:
          images:
            - image: public.ecr.aws/nginx/nginx
              path: stages/test
    argoCDAppUpdates:
      - appName: kargo-demo-test
        appNamespace: argocd
---
apiVersion: kargo.akuity.io/v1alpha1
kind: Stage
metadata:
  name: uat
  namespace: kargo-demo
spec:
  requestedFreight:
    - origin:
        kind: Warehouse
        name: kargo-demo
      sources:
        stages:
          - test
  promotionMechanisms:
    gitRepoUpdates:
      - repoURL: ${GITOPS_REPO_URL}
        writeBranch: stage/uat
        kustomize:
          images:
            - image: public.ecr.aws/nginx/nginx
              path: stages/uat
    argoCDAppUpdates:
      - appName: kargo-demo-uat
        appNamespace: argocd
---
apiVersion: kargo.akuity.io/v1alpha1
kind: Stage
metadata:
  name: prod
  namespace: kargo-demo
spec:
  requestedFreight:
    - origin:
        kind: Warehouse
        name: kargo-demo
      sources:
        stages:
          - uat
  promotionMechanisms:
    gitRepoUpdates:
      - repoURL: ${GITOPS_REPO_URL}
        writeBranch: stage/prod
        kustomize:
          images:
            - image: public.ecr.aws/nginx/nginx
              path: stages/prod
    argoCDAppUpdates:
      - appName: kargo-demo-prod
        appNamespace: argocd

Abychom tyto resoureces dostali do clusteru, použijeme kargo CLI, ne kubectl!:

kargo apply -f kargo-demo.yaml

Potom bychom měli v Kargo dashboardu vidět něco podobného jako na screenshotu výše.

Kargo je mocný, ale komplexní nástroj. Pokud vás zajímá víc, doporučuji si vyzkoušet jejich dema kargo-simple a kargo-advanced na GitHubu, tady by to vydalo na samotný blog post nebo i víc. Pokud byste o to stáli, tak tweetněte na Twitteru/Xku na @vojtechmares_ 😉.

ArgoCD školení

Už vás nebaví do nekonečna psát CI/CD pipeliny a nasazovat každou aplikaci do Kubernetes skrz pipeline? Vyzkoušejte GitOps přístup s ArgoCD: všechny informace o nasazených aplikacích jsou přehledně verzované v Git repozitáři. A zároveň to slouží i jako záloha, kdyby vám Kubernetes cluster spadnul... Přijďte na veřejný termín školení nebo se domluvme na firemním školení.