ArgoCD Config Management Plugin Yazmak

Selamlar, bu yazımda ArgoCD'nin Helm ve Kustomize dışına çıkıp Jsonnet, CUE ya da kendi yazdığınız bir render script'i ile çalışmasını sağlayan Config Management Plugin (CMP) konusuna gireceğiz. Konu duyulduğunda 'eyvah, ArgoCD'nin içine kod gömeceğim' havası verir ama aslında işin özü çok temiz: bir sidecar container, küçük bir ConfigMap ve basit bir generate komutu. Hadi başlayalım.

Config Management Plugin nedir?

CMP, ArgoCD'nin repo-server'ına eşlik eden ve repodaki kaynaktan Kubernetes manifest'i üreten bir sidecar container'dır. Repo-server git'ten klasörü çekiyor, sonra discovery kuralına uyan plugin'e 'sen render et' diyor; plugin de stdout'a YAML basıyor. Bu kadar.

ArgoCD 2.4 öncesinde plugin'ler argocd-cm ConfigMap'inin içine gömülürdü. O dönem geçti, şimdi sidecar pattern'ı tercih ediliyor; çünkü her plugin kendi imajıyla, kendi bağımlılıklarıyla, kendi izolasyonuyla geliyor. Repo-server ile sidecar arasındaki konuşma Unix socket üzerinden gRPC ile, biz bunu görmüyoruz.

Plugin tanimi: ConfigMap

Önce plugin'in kendisini tanımlayalım. Aşağıdaki yaml, bir Jsonnet render plugin'inin minimal hali:

apiVersion: argoproj.io/v1alpha1
kind: ConfigManagementPlugin
metadata:
  name: jsonnet-cmp
spec:
  version: v1.0
  init:
    command: [sh, -c]
    args:
      - jb install
  generate:
    command: [sh, -c]
    args:
      - jsonnet -J vendor -J lib main.jsonnet | yq eval -P -
  discover:
    find:
      glob: '**/main.jsonnet'
  allowConcurrency: true
  lockRepo: false

Üç kritik alan var. init her render öncesi çalışır, biz burada jsonnet-bundler ile bağımlılıkları indiriyoruz. generate asıl iş: stdout'a Kubernetes YAML'ı bastığında ArgoCD bunu alıp cluster'a uyguluyor. discover ise plugin'in hangi repolarda devreye gireceğini söylüyor. Burada main.jsonnet dosyası varsa bu plugin uyanıyor diyoruz.

allowConcurrency: true aynı plugin'in birden fazla Application için paralel çalışmasına izin veriyor. CPU'ya değen bir tooling kullanıyorsanız (örneğin CUE) bunu false bırakmak isteyebilirsiniz.

Sidecar imaji

Plugin çalışacaksa içinde Jsonnet de olmalı, argocd-cmp-server binary'si de. Tipik Dockerfile şöyle:

FROM golang:1.22 AS tools
RUN go install github.com/google/go-jsonnet/cmd/jsonnet@latest && \
    go install github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb@latest

FROM alpine:3.19
RUN apk add --no-cache bash git curl yq
COPY --from=tools /go/bin/jsonnet /usr/local/bin/
COPY --from=tools /go/bin/jb /usr/local/bin/
COPY --from=quay.io/argoproj/argocd:v2.10.0 /usr/local/bin/argocd-cmp-server /usr/local/bin/
COPY plugin.yaml /home/argocd/cmp-server/config/plugin.yaml
RUN adduser -D -u 999 argocd
USER 999
ENTRYPOINT ['/usr/local/bin/argocd-cmp-server']

Bence buradaki en sık atlanan nokta argocd-cmp-server binary'sini ArgoCD imajından kopyalamayı unutmak; o olmadan socket açılmıyor ve repo-server plugin'i göremiyor. Sonra argocd-repo-server deployment'ına bu container'ı extraContainers olarak ekliyoruz. Helm chart kullanıyorsanız repoServer.extraContainers alanı işinizi görüyor.

Discovery ve parametre kullanimi

Discovery kuralı plugin'in hayatı. glob pattern'ı çok geniş tutarsanız her Application'da plugin uyanır, plugin uyanır ama dosya yoksa hata fırlatır. Tecrübeyle sabittir ki bu durumda repo-server log'larında 'plugin not applicable' satırlarına boğulursunuz. Mümkünse discovery için bir marker dosyası (.use-jsonnet gibi) tutmak ve bunu glob'la birlikte kullanmak temiz oluyor.

Parametreler de güzel: Application spec'i içinden plugin.parameters ile değer geçebilirsiniz. Örneğin environment'a göre values dosyası seçtirmek için:

source:
  plugin:
    name: jsonnet-cmp
    env:
      - name: ENVIRONMENT
        value: production

Plugin tarafında bu değerlere $ARGOCD_ENV_ENVIRONMENT ile ulaşıyorsunuz. ArgoCD ayrıca ARGOCD_APP_NAME, ARGOCD_APP_NAMESPACE, ARGOCD_APP_REVISION gibi değişkenleri otomatik enjekte ediyor.

RBAC ve guvenlik

CMP sidecar'ı argocd namespace'inde, repo-server'ın service account'u ile çalışır. Yani plugin'inize ek bir ServiceAccount tanımlamanıza gerek yok; ama plugin git dışında bir kaynağa erişecekse (örneğin Vault'tan secret çekecekse) o kaynağa ulaşmak için kendi credential'ını volume olarak mount etmeniz gerekir. Asla imajın içine secret gömmeyin.

securityContext:
  runAsNonRoot: true
  runAsUser: 999
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
    drop: [ALL]

Bu blok minimum hijyen. NetworkPolicy ile egress'i sadece git ve gerekli artifact registry'ye açmak da çok kıymetli; aksi halde plugin script'i istemeden public bir endpoint'e gider ve süpriz oluşur.

Sik karsilasilan tuzaklar

  • Stdout'a karisan log: echo 'building...' yazdıysanız o satır plugin çıktısının başına düşer ve ArgoCD invalid YAML hatası verir. Log'u stderr'e (>&2) yönlendirin.
  • Discovery glob'unu cok genis tutmak: **/*.yaml yazınca her Application'ı plugin'inize bağlarsınız. Marker dosyası kullanın.
  • Argocd-cmp-server binary'sini unutmak: Imaj çalışıyor görünür, repo-server hiçbir şey görmez. Socket açılmadan plugin'iniz yok demektir.
  • lockRepo: true bırakıp paralel render beklemek: Bu seçenek tüm repo'yu kilitler, monorepo'da Application sayınız kadar kuyruk oluşur. Stateless plugin'lerde false bırakın.

Kapanis

CMP, ArgoCD'yi gerçekten 'her tooling'i kabul eden bir GitOps motoru' haline getiriyor. Bence ekibinizde Jsonnet, CUE ya da kendi render script'i kullanan bir takım varsa bu yola erken girmek uzun vadede çok iş kurtarıyor; sonradan Helm'e zorlamaya çalışmak hem kodu kötüleştiriyor hem de güveni sarsıyor. Umarım faydalı olur, bir sonraki yazıda görüşmek üzere.