Mert Özen Her Satırda Daha İleri
DevOps GitOps ve Argo CD — #02

Argo CD Kurulumu ve Mimarisi: İlk Application ile Bookstore'u kind Cluster'a Deploy Etmek

Mert Özen 15 Kas 2026 11 dk 4 görüntülenme
Argo CD Kurulumu ve Mimarisi: İlk Application ile Bookstore'u kind Cluster'a Deploy Etmek

Argo CD, cluster içinde çalışıp bir Git repo'sundaki manifest'leri render eden, canlı nesnelerle karşılaştıran ve uygulayan bir controller grubu. Argo CD 3.6'yı kind cluster'ına kuruyor, repo-server, application-controller ve diğer component'lerin rolünü, CLI ile giriş ve ilk parolayı, Application CRD'sinin alanlarını ve Bookstore'u Git'ten deploy etmeyi görüyoruz; Synced görünüp hiçbir şey değiştirmeyen bir commit'i de yakalıyoruz.

Sürüm notu: Bu yazıdaki örnekler kind 0.33 ile Kubernetes 1.37, Argo CD 3.6.1 (argocd CLI 3.6.1), Kustomize 5.8 ve .NET 10 ile denenmiştir.

Önce Tahmin Et

Bookstore API'si Argo CD ile dev ortamında çalışıyor ve image tag'i 1.0.0. Dev overlay'ine kustomize edit set image ile 1.1.0 yazıp commit atıyorsun. Argo CD birkaç saniye sonra uygulamayı "Synced to main (e771436)" ve "Healthy" gösteriyor; yani son commit'in cluster'da olduğunu söylüyor. Pod'a sorduğunda hangi sürümü duyarsın? Synced kelimesine ne kadar güvenebileceğini bu yazının sonundaki tuzakta gerçek çıktıyla göreceğiz.

Lab: Argo CD'nin İzleyeceği Git Repo'su

Bir önceki yazıda sitedeki serilerin ortak örnek uygulaması Bookstore'u (bir online kitapçı) push tabanlı bir script'le deploy etmiştik. Şimdi aynı config repo'sunu Argo CD'ye izleteceğiz. Gerçek hayatta bu repo GitHub, GitLab ya da Azure Repos'ta durur ve Argo CD ona bir deploy key ya da token ile bağlanır. Lab'de dışarıya hiçbir şey push etmemek için repo'yu kind node'una read-only mount ediyor ve cluster içinde küçük bir Git HTTP server'ıyla (nginx + git http-backend) sunuyoruz:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    # Config repo'su node içinde /srv/git/bookstore-config altında görünür.
    # hostPath'i kendi clone'unla değiştir (Windows'ta ör. C:/work/bookstore-config).
    extraMounts:
      - hostPath: /path/to/bookstore-config
        containerPath: /srv/git/bookstore-config
        readOnly: true

Git server'ın Dockerfile'ı ve Deployment'ı snapshot'taki lab/git-server/ klasöründe. Böylece Argo CD'nin göreceği adres http://git-server.git-server.svc.cluster.local/bookstore-config. Lokal klasörde attığın her commit Argo CD'nin göreceği bir commit; push adımı yok. Bunun dışında her şey gerçek bir Git sağlayıcısıyla aynı çalışıyor.

Kurulum: install.yaml ile Argo CD 3.6

Argo CD'nin üç kurulum yolu var. Resmî manifest'ler (install.yaml, HA (High Availability; birden çok replica ile yüksek erişilebilirlik) için ha/install.yaml), topluluğun bakımını yaptığı Helm chart'ı (argo-helm repo'sundaki argo-cd chart'ı) ve UI ile API server'ı içermeyen core install (core-install.yaml). Biz lab'de non-HA install.yaml'ı sürümüyle sabitleyerek kullanıyoruz. Dokümana göre 3.6 Kubernetes 1.34–1.37 ile test ediliyor, kind 0.33'ün varsayılan node'u da 1.37:

kubectl create namespace argocd
kubectl apply -n argocd --server-side --force-conflicts \
    -f https://raw.githubusercontent.com/argoproj/argo-cd/v3.6.1/manifests/install.yaml
kubectl -n argocd wait --for=condition=Available deployment --all --timeout=600s
customresourcedefinition.apiextensions.k8s.io/applications.argoproj.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/applicationsets.argoproj.io serverside-applied
customresourcedefinition.apiextensions.k8s.io/appprojects.argoproj.io serverside-applied
serviceaccount/argocd-application-controller serverside-applied
...
deployment.apps/argocd-server condition met

Manifest 60 resource içeriyor: üç CRD (Custom Resource Definition; Kubernetes'e eklenen özel nesne tipi), ServiceAccount'lar, Role'ler, ClusterRole'ler, ConfigMap'ler, NetworkPolicy'ler, altı Deployment ve bir StatefulSet. --server-side önemli: CRD'ler büyük olduğu için client-side apply'ın tuttuğu last-applied annotation'ı boyut sınırını aşabiliyor, 3.6'nın sürüm notu da kurulumu bu bayraklarla veriyor. Kurulum lab'de 43 saniyede hazır oldu.

Component'ler: Kim Ne Yapıyor?

kubectl -n argocd get pods
NAME                                                READY   STATUS    RESTARTS   AGE
argocd-application-controller-0                     1/1     Running   0          46s
argocd-applicationset-controller-794bb95c9d-7dbc5   1/1     Running   0          47s
argocd-dex-server-66cf64bfcf-b9285                  1/1     Running   0          47s
argocd-notifications-controller-5878c8d644-74jrl    1/1     Running   0          47s
argocd-redis-5869b7547-s8cb7                        1/1     Running   0          47s
argocd-repo-server-7d55cc466b-rf2px                 1/1     Running   0          46s
argocd-server-85b849c688-6l5jz                      1/1     Running   0          46s

02-reconcile-loop-tr.png

argocd-repo-server repo'yu clone eder ve bir Application'ın path'ini render eder: Kustomize için kustomize build, Helm için helm template, düz klasör için YAML (manifest'lerin yazıldığı metin formatı) dosyalarını okur. Sonucu commit SHA'sı (commit'in benzersiz hash değeri) ile cache'ler; aynı commit için tekrar render etmez. Bizim kurulumdaki repo-server'ın içinde Kustomize 5.8.3 ve Helm 4.3.1 var; CI'da ya da lokalde aynı sürümleri kullanmak "bende çalışıyordu" farklarını önler.

argocd-application-controller serinin kalbi. Kubernetes API'sini watch eder, repo-server'dan gelen manifest'lerle canlı nesneleri karşılaştırır, sync status'u (Synced/OutOfSync) ve health'i hesaplar, sync işlemini yürütür. StatefulSet olarak kurulur; çok sayıda cluster yönetirken shard'lanabilir. argocd-server UI'ı, CLI'ın konuştuğu gRPC/REST API'yi ve Git webhook'larını sunar; kendisi deploy yapmaz, Application nesnelerini okur ve yazar. argocd-redis cache içindir; kaybolursa yeniden doldurulur. argocd-applicationset-controller ApplicationSet'lerden Application üretir (beşinci yazı), argocd-dex-server SSO (Single Sign-On; şirket hesabıyla tek seferde giriş) için kimlik köprüsüdür, argocd-notifications-controller sync ve health olaylarını Slack, Teams ya da webhook'a taşır (son yazı).

Varsayılan bekleme süresi de bilinmeli: Argo CD Git'i 120 saniyede bir, üzerine en fazla 60 saniyelik rastgele bir gecikme (jitter) ekleyerek yoklar; yani yeni bir commit en geç yaklaşık üç dakikada görülür. Daha hızlısı için Git sağlayıcısından argocd-server'a webhook gönderilir ya da argocd app get --refresh ile elle yenilenir. Lab'de webhook olmadığı için refresh kullandık.

CLI ile Giriş ve İlk Parola

argocd-server'ı dışarı açmadan, yalnız kendi makinemizde kullanmak için port-forward yeterli. Paylaşımlı makinelerde varsayılan port'lar dolu olabilir; adresi 127.0.0.1 ile sınırla ve boş bir port seç:

kubectl -n argocd port-forward --address 127.0.0.1 svc/argocd-server 52611:443
argocd admin initial-password -n argocd
<redacted>

 This password must be only used for first time login. We strongly recommend you update the password using `argocd account update-password`.

Kurulum admin kullanıcısı için rastgele bir parola üretip argocd-initial-admin-secret Secret'ına yazar. Bu parola yalnız ilk giriş içindir:

argocd login 127.0.0.1:52611 --username admin --password "$INITIAL_PASSWORD" --insecure
argocd account update-password --account admin \
    --current-password "$INITIAL_PASSWORD" --new-password "$NEW_PASSWORD"
'admin:login' logged in successfully
Context '127.0.0.1:52611' updated
Password updated
Context '127.0.0.1:52611' updated

--insecure lab'deki self-signed sertifika yüzünden var; gerçek kurulumda argocd-server bir Ingress ya da Gateway arkasında geçerli bir sertifikayla durur. Parolayı değiştirdikten sonra argocd-initial-admin-secret'ı sil; doküman da bunu öneriyor. Production'da ise admin hesabı SSO kurulunca tamamen kapatılır; onu serinin son yazısına bırakıyoruz.

İlk Application: CLI ile

Önce repo'yu tanıtıyoruz. Public bir repo için bu adım zorunlu değil, ama bağlantıyı hemen doğrular; private repo'da credential'ı da burada verirsin:

argocd repo add http://git-server.git-server.svc.cluster.local/bookstore-config
argocd repo list
Repository 'http://git-server.git-server.svc.cluster.local/bookstore-config' added
TYPE  NAME  REPO                                                             INSECURE  OCI    LFS    CREDS  STATUS      MESSAGE  PROJECT
git         http://git-server.git-server.svc.cluster.local/bookstore-config  false     false  false  false  Successful

Veritabanı şifresini tutan api-db Secret'ını lab script'i Git'in dışında, bookstore-dev namespace'ine yazıyor; Git'te secret konusu serinin yedinci yazısında. Sonra Application:

argocd app create bookstore-api-dev \
    --repo http://git-server.git-server.svc.cluster.local/bookstore-config \
    --path apps/api/overlays/dev \
    --revision main \
    --dest-server https://kubernetes.default.svc \
    --dest-namespace bookstore-dev \
    --project default
argocd app get bookstore-api-dev
application 'bookstore-api-dev' created
Name:               argocd/bookstore-api-dev
Project:            default
Server:             https://kubernetes.default.svc
Namespace:          bookstore-dev
Source:
- Repo:             http://git-server.git-server.svc.cluster.local/bookstore-config
  Target:           main
  Path:             apps/api/overlays/dev
Sync Policy:        Manual
Sync Status:        OutOfSync from main (df25766)
Health Status:      Missing

GROUP  KIND         NAMESPACE      NAME      STATUS     HEALTH   HOOK  MESSAGE
       Service      bookstore-dev  api       OutOfSync  Missing
       Service      bookstore-dev  postgres  OutOfSync  Missing
apps   Deployment   bookstore-dev  api       OutOfSync  Missing
apps   StatefulSet  bookstore-dev  postgres  OutOfSync  Missing        

Argo CD repo'yu okudu, path'i render etti ve dört resource'un cluster'da olmadığını gördü: hepsi OutOfSync, health'leri Missing. Sync policy Manual olduğu için kendiliğinden bir şey yapmıyor. Sync'i biz başlatıyoruz:

argocd app sync bookstore-api-dev
Operation:          Sync
Sync Revision:      df25766057841b5ee038d5ce25ef47012850cef1
Phase:              Succeeded
Duration:           0s
Message:            successfully synced (all tasks run)

GROUP  KIND         NAMESPACE      NAME      STATUS  HEALTH       HOOK  MESSAGE
       Service      bookstore-dev  api       Synced  Healthy            service/api created
       Service      bookstore-dev  postgres  Synced  Healthy            service/postgres created
apps   Deployment   bookstore-dev  api       Synced  Progressing        deployment.apps/api created
apps   StatefulSet  bookstore-dev  postgres  Synced  Progressing        statefulset.apps/postgres created

Sync bir saniyede bitti ama Deployment ve StatefulSet Progressing: manifest'ler uygulandı, pod'lar henüz ready değil. argocd app wait bookstore-api-dev --health ile 11 saniye sonra uygulama Healthy oldu. Sync ve health iki ayrı soru: sync "Git'teki cluster'da mı?", health "cluster'daki çalışıyor mu?" diye sorar. Üçüncü yazının konusu bu ikisinin ayrımı.

02-argocd-ui-tree-tr.png

UI aynı bilgiyi resource ağacı olarak gösteriyor. Argo CD yalnız Git'te yazdığımız dört nesneyi değil, onların ürettiği ReplicaSet, Pod ve PVC (PersistentVolumeClaim; pod'un istediği kalıcı disk) nesnelerini de ağaca ekliyor; Kubernetes'in owner reference'larını takip ediyor. Hangi nesnenin bu uygulamaya ait olduğunu ise bir annotation'dan biliyor:

kubectl -n bookstore-dev get deploy api -o jsonpath='{.metadata.annotations.argocd\.argoproj\.io/tracking-id}'
bookstore-api-dev:apps/Deployment:bookstore-dev/api

3.0'dan beri varsayılan resource tracking yöntemi bu annotation; 2.x'te app.kubernetes.io/instance label'ıydı. Label yöntemi Helm chart'larının da kullandığı aynı label'la çakışabiliyordu; annotation'da bu sorun yok. Eski bir kurulumu yükseltirken bu değişikliğin etkisini son yazıda ele alacağız.

Application CRD: Alanlar ve Declarative Hâli

argocd app create aslında bir Kubernetes nesnesi yarattı. Onu elle yazıp config repo'suna koyalım; bu dosya, CLI'da yazdığımız bayrakların karşılığı:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: bookstore-api-dev
  namespace: argocd
  # Application silinince oluşturduğu resource'lar da silinir.
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    repoURL: http://git-server.git-server.svc.cluster.local/bookstore-config
    targetRevision: main
    path: apps/api/overlays/dev
  destination:
    server: https://kubernetes.default.svc
    namespace: bookstore-dev

source nereden okunacağını söyler: repoURL, path ve targetRevision (branch, tag ya da commit SHA'sı). destination nereye uygulanacağını söyler: cluster (https://kubernetes.default.svc Argo CD'nin kurulu olduğu cluster'ın kendisi) ve namespace'i olmayan resource'lar için varsayılan namespace. Bizim overlay'imiz namespace: bookstore-dev yazdığı için ikisi zaten aynı. project bu Application'ın hangi kurallara tabi olduğunu belirler; default project her şeye izin verir, beşinci yazıda kendi AppProject'imizi yazacağız. Kaynak Helm chart'ı da olabilir: source'ta chart, chart repo'su ve helm.valueFiles verilir, Argo CD chart'ı helm template ile render edip aynı karşılaştırmayı yapar; helm install çalıştırmaz, yani cluster'da Helm release'i oluşmaz. Chart yazımı ilerideki Helm serisinin konusu.

Dosyayı commit'leyip uyguladık:

kubectl apply -f argocd/applications/bookstore-api-dev.yaml
Warning: resource applications/bookstore-api-dev is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
Warning: metadata.finalizers: "resources-finalizer.argocd.argoproj.io": prefer a domain-qualified finalizer name including a path (/) to avoid accidental conflicts with other finalizer writers
application.argoproj.io/bookstore-api-dev configured

İki uyarı da zararsız. Birincisi nesnenin CLI ile yaratılmış olmasından. İkincisi Kubernetes 1.37'nin finalizer adlarında "/" görmek istemesinden; Argo CD dokümanı bu adı ve arka planda silme için resources-finalizer.argocd.argoproj.io/background varyantını veriyor. Neden bu kadar uğraş? UI'da ya da CLI'da elle yaratılan Application'lar "ClickOps" olur: kim yarattı, hangi parametrelerle, nasıl geri kurulur, hiçbirinin cevabı Git'te değildir. Argo CD'nin kendi tanımları da Git'te olmalı. Şimdilik bu dosyayı kubectl apply ile uyguluyoruz; beşinci yazıda App of Apps ile onu da Argo CD'ye uygulatacağız.

Bir Değişikliği Git'ten Deploy Etmek

Önce Tahmin Et'teki commit:

cd apps/api/overlays/dev
kustomize edit set image bookstore-api=gitopsseries-bookstore-api:1.1.0
git commit -am "dev: bookstore-api 1.1.0"
argocd app get bookstore-api-dev --refresh
Sync Status:        Synced to main (e771436)
Health Status:      Healthy

Synced, ama argocd app diff boş döndü ve pod hâlâ 1.0.0'dı. kustomize build sebebi gösteriyor:

        image: gitopsseries-bookstore-api:1.0.0
        image: postgres:18-alpine

Base'in images alanı placeholder adı bookstore-api'yi gitopsseries-bookstore-api:1.0.0'a çeviriyordu. Overlay'in transformer'ı base'in çıktısına uygulanır; o çıktıda artık bookstore-api diye bir image yok, overlay'deki kural hiçbir şeyle eşleşmedi ve sessizce boşa gitti. Argo CD'ye göre her şey doğru: Git'in render ettiği hâl ile cluster aynı. Önce Tahmin Et'in cevabı bu: Synced "son commit'in render edilmiş hâli cluster'da" demek, "commit'te niyet ettiğin şey oldu" demek değil. Düzeltme, image adını base'de değiştirmemek; deployment.yaml'da gerçek ad, base'de yalnız newTag:

 images:
-- name: bookstore-api
-  newName: gitopsseries-bookstore-api
+- name: gitopsseries-bookstore-api
   newTag: 1.1.0
Sync Status:        OutOfSync from main (94b6b13)
Health Status:      Healthy

===== apps/Deployment bookstore-dev/api ======
182c182
<         image: gitopsseries-bookstore-api:1.0.0
---
>         image: gitopsseries-bookstore-api:1.1.0

Bu sefer Argo CD farkı gördü. argocd app sync'ten sonra pod'a sorduk ve geçmişe baktık:

{"version":"1.1.0","environment":"Production","pod":"api-85d44bd785-skjdw","database":"Postgres"}
argocd app history bookstore-api-dev
SOURCE  http://git-server.git-server.svc.cluster.local/bookstore-config
ID      DATE                           REVISION
0       2026-11-15 06:08:13 +0300 +03  main (df25766)
1       2026-11-15 06:09:33 +0300 +03  main (94b6b13)

Geçmişte e771436 yok, çünkü o commit cluster'da hiçbir şeyi değiştirmedi ve ayrı bir sync yapılmadı. Her sync'in commit SHA'sı status.sync.revision'da ve bu geçmişte duruyor; "prod'da hangi commit var?" sorusunun cevabı tek komut.

Üretimde Dikkat

Lab'deki non-HA kurulum production için değil; doküman da bunu açıkça söylüyor. Production'da ha/install.yaml ya da Helm chart'ının HA değerleriyle birden çok repo-server ve api-server replica'sı, Redis için HA kurulumu kullanılır. Kurulum manifest'ini sürümüyle sabitle (v3.6.1, stable değil) ve Argo CD'nin kendisini de bir Application olarak Git'ten yönet. Repo-server her render'da Git'e gider ve Kustomize/Helm çalıştırır; yüzlerce Application'da en çok CPU harcayan component odur, replica ve cache ayarları oradan başlar. Argo CD'nin sürüm temposu hızlı: üç ayda bir minor çıkıyor ve yalnız son üç minor yama alıyor. 3.6.0 3 Kasım 2026'da çıktı; 3.3 ailesi 2 Kasım'daki 3.3.19 ile son yamasını aldı ve destek dışı kaldı. Upgrade planı ilk günden takvimde olmalı.

Sık Düşülen Tuzaklar

targetRevision'ı HEAD ya da bir branch bırakıp hangi commit'in canlıda olduğunu bilmemek. Branch takip etmek dev için doğru seçim; ama cevap her zaman argocd app get'teki SHA'dadır, branch adında değil. Prod'da bir tag ya da commit SHA'sına sabitlemek bilinçli bir promotion adımı yaratır.

Argo CD'yi uygulamayla aynı namespace'e kurmak. Bookstore'un namespace'ini silen ya da prune eden bir hata Argo CD'yi de götürür; Argo CD kendi namespace'inde (argocd) dursun.

Synced'i "deploy oldu" diye okumak. Yukarıdaki image tuzağında Argo CD dürüsttü, hatalı olan bizim manifest'imizdi. Pull request'te kustomize build çıktısını diff'lemek bunu merge'den önce yakalar.

İlk admin parolasını kullanmaya devam etmek ve argocd-initial-admin-secret'ı cluster'da bırakmak.

Application'ları UI'dan yaratmak. Bir gün Argo CD'yi yeniden kurman gerektiğinde elinde hiçbir tanım kalmaz.

Küçük Bir Deneme

★☆☆ argocd app manifests bookstore-api-dev çıktısını kustomize build apps/api/overlays/dev ile karşılaştır. Fark var mı? Varsa hangi annotation'dan geliyor?

★★☆ Application'ın targetRevision'ını bir commit SHA'sına çevir, sonra overlay'de bir değişiklik commit'le. Argo CD bu commit'i görüyor mu? argocd app get neyi gösteriyor?

★★★ Argo CD'yi core-install.yaml ile ayrı bir kind cluster'ına kur ve aynı Application'ı yalnız kubectl ile yönet. UI ve API server olmadan neleri kaybettiğini, hangi işleri argocd CLI'ın --core moduyla hâlâ yapabildiğini listele.

İlk Application çalışıyor ama her şeyi biz tetikliyoruz. Bir sonraki yazıda sync'i Argo CD'ye bırakıyoruz: automated sync, prune, self-heal, health hesabı ve diff'in neyi görüp neyi görmediği.