Git Branching Stratejisi Nedir? GitOps İçin GitFlow, GitHub Flow ve Trunk-Based Development, Image Promotion Nasıl Yapılır

Aynı 10 değişikliği GitFlow, GitHub Flow ve trunk ile denedim: 5, 1 ve 0 merge çakışması. Sonra tek bir image digest'ini Argo CD ile prod'a taşıdım.

Yazan: Mirac Can Yılmaz 20 dk okuma
Git Branching Stratejisi Nedir? GitOps İçin GitFlow, GitHub Flow ve Trunk-Based Development, Image Promotion Nasıl Yapılır - DevOps yazısının kapak görseli

Branch tartışması her ekipte bir gün açılır. Biri develop ve release/* ister, biri her şeyin main'de olmasını savunur, bir başkası da staging'de test edilenle prod'a çıkanın neden farklı olduğunu sorar. Branching stratejisi çoğu zaman bir kere seçilir, bir blog yazısından kopyalanır ve maliyetini kimse ölçmez. Bu yazıda branching stratejisinin ne olduğunu anlatıyorum; GitFlow, GitHub Flow ve trunk-based development'ı avantaj ve dezavantajlarıyla karşılaştırıyorum; GitOps'ta hangisini seçmek gerektiğini gösteriyorum. Sonra image promotion'a giriyorum: aynı container image'ı dev'den staging'e, oradan prod'a nasıl taşınmalı? Her şeyi yerelde, gerçek komutlarla çalıştırdım. Bunun için GitHub'da ya da GitLab'da repo açmadım.

Git branching stratejisi nedir?

Branching stratejisi, ekibin üç konuda yaptığı anlaşmadır:

  • İş nerede yapılıyor: uzun ömürlü branch'lerde mi, kısa ömürlü branch'lerde mi, yoksa doğrudan main'de mi?
  • İş nasıl birleştiriliyor: merge mi, rebase mi, squash mı ve ne sıklıkla?
  • Ne yayına çıkıyor: bir tag mi, bir release branch'i mi, yoksa main'deki her commit mi?

"Branching stratejisi" denince akla gelen üç model var:

  • GitFlow: iki kalıcı branch (main yayındaki kod, develop bir sonraki sürüm), yanında feature/*, release/* ve hotfix/* branch'leri.
  • GitHub Flow: tek kalıcı branch (main). Her değişiklik kısa bir branch ve bir pull request. main'e merge etmek deploy etmek demek.
  • Trunk-based development: herkes en az günde bir kez main'e (trunk) commit atar. Branch ya çok kısa ömürlüdür ya da hiç yoktur. Bitmemiş iş feature flag'in arkasında kapalı durur. Release branch'i isteğe bağlıdır; trunk'tan açılır ve geri merge edilmez.

Branching stratejisi hangi sorunu çözüyor?

Entegrasyon acısı. İki kişi aynı dosyayı değiştiriyor. Branch'leri ne kadar uzun ayrı kalırsa, birleştiklerinde çıkan sürpriz o kadar büyük oluyor. Branching stratejisi bu sürenin ne kadar olabileceğine karar veriyor.

Prod'da ne olduğunu bilmek. "Fiyat hatasının düzeltmesi canlıda mı?" sorusunun tek satırlık bir cevabı olmalı: bir tag, main'de bir commit ya da bir dosyadaki digest. Strateji yoksa cevap "ekibe bir sorayım" oluyor.

Birden fazla sürümü desteklemek. Müşterilerin bir kısmı 2.3'ü, bir kısmı 2.4'ü kullanıyorsa (on-prem yazılım, mobil uygulama, kütüphane), 2.4'ü göndermeden 2.3'ü düzeltebileceğin bir yer gerekiyor. Release branch'leri bunun için var.

GitFlow, GitHub Flow ve trunk-based development: avantajlar ve dezavantajlar

Diyagram: aynı 10 değişiklik üç branching modelinde. develop, feature, release ve hotfix branch'leriyle GitFlow: 5 merge çakışması, 15 merge commit, prod'a ortalama 9,4 gün. main'e kısa pull request'lerle GitHub Flow: 1 çakışma, 11 merge commit, 2 gün. main'e aynı gün commit'lerle trunk-based: 0 çakışma, 0 merge commit, aynı gün

GitFlow

Nasıl çalışıyor: feature'lar develop'tan açılıyor ve bitince oraya merge ediliyor. Yeterince feature birikince develop'tan release/x.y açılıyor, stabilize ediliyor, sonra main'e merge edilip tag'leniyor ve develop'a geri merge ediliyor. Prod'daki bir hata main'den açılan hotfix/x.y.z ile düzeltiliyor; bu branch da main'e ve develop'a (açık bir release branch'i varsa ona da) merge ediliyor.

git switch -c feature/wishlist develop        # günlerce çalışma
git switch develop && git merge --no-ff feature/wishlist
git switch -c release/1.0 develop             # stabilizasyon burada
git switch main && git merge --no-ff release/1.0 && git tag -a v1.0 -m v1.0
git switch develop && git merge --no-ff release/1.0   # geri merge
git switch -c hotfix/1.0.1 v1.0               # prod düzeltmesi
git switch main && git merge --no-ff hotfix/1.0.1 && git tag -a v1.0.1 -m v1.0.1
git switch develop && git merge --no-ff hotfix/1.0.1  # yine geri merge

Avantajları: sürümler açık ve isimli. develop'ta yeni iş devam ederken bir sürüm ayrıca stabilize edilebiliyor. Planlı sürüm çıkaran ve aynı anda birkaç sürümü destekleyen yazılıma uyuyor.

Dezavantajları: uzun ömürlü branch'ler geç entegrasyon demek, çakışmalar birikiyor. Her release ve hotfix iki kez merge ediliyor; ikinci merge unutulursa hata geri geliyor. CI beş farklı branch türüyle uğraşmak zorunda. Değişiklikler bir sonraki sürümü beklediği için prod'a çıkış süresi haftalarla ölçülüyor. GitFlow'u 2010'da yazan Vincent Driessen de 2020'de yazısına bir not ekledi: sürekli deploy edilen web uygulamaları için GitHub Flow gibi daha basit bir akışı öneriyor.

GitHub Flow

Nasıl çalışıyor: main'den branch aç, pull request aç, CI koşsun, biri review etsin, merge et, deploy et. Branch birkaç saat ya da birkaç gün yaşıyor.

git switch -c fix-timeout main
git commit -am "timeout 800ms"
git push -u origin fix-timeout      # PR aç; CI + review
# onaydan sonra main'e merge, main pipeline'ı deploy ediyor

Avantajları: tek kural var, öğretmesi kolay. Her değişiklik review'dan geçiyor. main her zaman deploy edilebilir durumda. Hotfix de sıradan küçük bir PR.

Dezavantajları: tek bir prod sürümü olduğunu varsayıyor; eski bir sürümü yamalaman gerekiyorsa işe yaramıyor. Günlerce açık kalan PR'lar sessizce GitFlow'un feature branch'lerine dönüşüyor. "Merge = deploy" iyi testler ve hızlı bir rollback gerektiriyor.

Trunk-based development

Nasıl çalışıyor: küçük commit'ler en az günde bir kez main'e gidiyor (doğrudan ya da birkaç saat yaşayan bir PR ile). Bitmemiş iş feature flag'in arkasında kapalı olarak yayına çıkıyor. Release branch'i gerekiyorsa main'den açılıyor ve içine sadece düzeltmeler cherry-pick ediliyor; geri hiçbir şey merge edilmiyor.

git pull --rebase origin main
git commit -am "wishlist: api flag arkasında (kapalı)"
git push origin main                # her push'ta CI koşuyor
# desteklenen bir sürüm için isteğe bağlı release branch'i:
git switch -c release/2.4 main && git cherry-pick <fix-sha>

Avantajları: entegrasyon sürekli olduğu için çakışmalar küçük kalıyor. Tek bir doğru sürüm var. Prod'a çıkış süresi saatlerle ölçülüyor. DORA araştırmalarının yüksek performanslı ekiplerle ilişkilendirdiği yöntem bu.

Dezavantajları: disiplin ve hızlı, güvenilir bir CI istiyor. Feature flag'ler sonradan temizlenmesi gereken kod. Her commit review'dan geçmiyorsa (ya da PR'lar çok kısa değilse) kalite testlere kalıyor. Yeni başlayan ekipler ilk haftalarda main'i sık sık bozuyor.

Karşılaştırma tablosu

GitFlow GitHub Flow Trunk-based
Kalıcı branch'ler main, develop main main
Branch ömrü günler, haftalar saatler, en fazla 2 gün saatler ya da hiç
Yayın birimi release branch'i + tag her merge her commit (ya da açılan release branch'i)
Hotfix hotfix/*, iki kez merge sıradan PR main'e commit, release branch'i varsa cherry-pick
Aynı anda birkaç sürüm evet hayır evet, release branch'leriyle
CI karmaşıklığı yüksek (5 branch türü) düşük düşük, ama CI hızlı olmalı
Kime uygun sürümlü ürünler, on-prem, mobil, kütüphaneler tek prod sürümü olan web uygulamaları ve servisler günde birçok kez deploy eden ekipler
Benim lab'ımda (aynı 10 değişiklik) 5 çakışma, prod'a 9,4 gün 1 çakışma, 2 gün 0 çakışma, aynı gün

CI/CD pipeline'ı branching modelini nasıl takip ediyor?

Pipeline'ın neyle tetikleneceğini branching modeli belirliyor. Aşağıdakiler kullandığım kalıplar. Bu yazı için bunları GitHub'da ya da GitLab'da koşmadım; lab aynı adımları yerel script'lerle çalıştırıyor.

GitHub Actions, trunk-based ya da GitHub Flow. Tek workflow: pull request'te test, main'de tek seferlik build, dev'e otomatik deploy, prod ise zorunlu reviewer'ı olan bir environment'ın arkasında.

on:
  pull_request:
  push:
    branches: [main]
concurrency:
  group: deploy-${{ github.ref }}
  cancel-in-progress: false
jobs:
  test:
    strategy:
      matrix: { python: ["3.12", "3.13"] }      # matrix build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "${{ matrix.python }}", cache: pip }   # bağımlılık cache'i
      - run: pip install -r requirements.txt && pytest
  build:
    if: github.ref == 'refs/heads/main'
    needs: test
    uses: ./.github/workflows/build-image.yml   # reusable workflow (on: workflow_call), digest'i döndürüyor
  promote-dev:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: ./.github/actions/bump-digest     # composite action: GitOps reposunda envs/dev'i değiştir, push et
        with: { env: dev, digest: "${{ needs.build.outputs.digest }}" }

Reusable workflow image'ı layer cache ile (cache-from: type=gha) build edip push ediyor ve digest'i döndürüyor. Composite action ise GitOps reposunda tek satırı değiştiren beş satırlık shell. Staging ve prod burada job bile değil: GitOps reposuna açılan pull request'ler. Bu da beni bir sonraki bölüme getiriyor. Reusable workflow ve composite action'ı serinin ilerleyen bir yazısında ayrıca anlatacağım.

GitLab CI'da aynı fikir. Branch filtrelerinin yerini rules alıyor. Monorepo'da pipeline'ı çalışma anında dinamik child pipeline ile üretmek mümkün:

workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
test:
  parallel:
    matrix: [{ PYTHON: ["3.12", "3.13"] }]
  cache: { key: { files: [requirements.txt] }, paths: [.cache/pip] }
  script: [pip install -r requirements.txt, pytest]
generate:
  script: ./ci/generate-child.sh > child.yml    # değişen her servis için bir job
  artifacts: { paths: [child.yml] }
services:
  trigger:
    include: [{ artifact: child.yml, job: generate }]
    strategy: depend

GitFlow'da aynı dosyaya develop, release/*, hotfix/*, main ve tag'ler için ayrı kurallar gerekiyor ve her kural farklı bir ortama karar veriyor. Beş branch türünün CI'daki maliyeti bu.

GitOps'ta hangi branching stratejisi?

GitOps'ta iki repo var ve ikisinin cevabı farklı.

Uygulama reposu (kaynak kod, Dockerfile, testler): kısa PR'larla trunk-based development ya da GitHub Flow. main'e her merge, commit SHA'sıyla tag'lenmiş tek bir image üretiyor. Release branch'ini yalnızca eski sürümleri gerçekten paralel destekliyorsan kullan.

GitOps (config) reposu (Argo CD ya da Flux'ın uyguladığı Kubernetes manifest'leri): tek branch, yani main, ve her ortam için bir klasör. Her ortam için ayrı branch açma.

apps/shop/base/            deployment, service
envs/dev/kustomization.yaml       namespace, 1 replica, debug log, image digest
envs/staging/kustomization.yaml   namespace, 2 replica, image digest
envs/prod/kustomization.yaml      namespace, 3 replica, image digest

Ortam başına branch ilk bakışta mantıklı geliyor ("dev branch'i dev'e deploy olur"); GitLab Flow bile bunu anlatıyor. GitOps config'inde ise belirli bir şekilde bozuluyor, aşağıda ölçtüm. Merge her şeyi taşıyor, tek bir ortamda kalması gereken ayarları da. Cherry-pick'ler branch'leri öyle ayırıyor ki sonunda git diff neyin farklı olduğunu söyleyemiyor. Klasör yapısında staging ile prod arasındaki fark, aynı commit'teki iki klasörün diff'i. Argo CD tek bir branch'i izliyor, her ortam için sadece path değişiyor.

Image promotion nasıl olmalı?

Image promotion, bir kez build edilmiş image'ı hiç değiştirmeden bir ortamdan diğerine taşımak. Uyduğum kurallar:

  1. Bir kez build et. CI image'ı tek bir commit'ten bir kez build ediyor ve her ortam o artifact'ı çalıştırıyor. Prod için yeniden build etmek, prod'da kimsenin test etmediği bir şeyin çalışması demek. Lab'da aynı commit'i iki kez build ettim, iki farklı digest çıktı.
  2. Tag ile değil, digest ile deploy et. :stable ya da :1.1.0 gibi bir tag herkesin taşıyabileceği bir işaretçi. Digest (@sha256:…) ise içeriğin kendisi. İnsanlar için commit SHA'sıyla tag'le, makineler için Git'e digest'i yaz.
  3. Promotion bir Git değişikliği. Staging'e geçmek, envs/staging'deki tek satırı (digest'i) değiştirmek demek. Commit, kimin neyi ne zaman taşıdığını söylüyor; rollback da git revert.
  4. Her ortamın kapısı farklı. dev: CI otomatik commit atıyor. staging: otomatik ya da tek onay. prod: zorunlu reviewer'ı olan bir pull request (envs/prod/ için CODEOWNERS, protected environment).
  5. Klasörü değil, digest'i kopyala. Bir önceki ortamın digest'ini al; klasörün tamamını merge etme ya da kopyalama. Böylece ortam ayarları yerinde kalıyor.
  6. Registry'de tag'leri değiştirilemez yap (ECR tag immutability, Harbor immutable tag kuralları). Registry'ler arası kopyalamada digest'i koruyan araçları kullan (crane copy, skopeo copy).
  7. Prod'dan önce doğrula. Digest'i imzala (cosign), prod'da imzasız image'ı bir admission policy reddetsin. Policy tarafını Kyverno yazısında anlatmıştım.
  8. Sıkıcı kısmı otomatikleştir. Argo CD Image Updater ya da Flux image automation dev'in digest'ini yazabilir; Kargo da dev → staging → prod promotion'ını kapılarıyla birlikte modelliyor. Hepsinin sonucu aynı: GitOps reposunda bir commit.

Diyagram: bir kez build et, digest ile taşı. Uygulama reposu main'de tek image build ediyor, sha-8b41e07 olarak push ediyor, CI a04d1186 digest'ini envs/dev'e yazıyor. Promotion aynı digest'i envs/staging'e (tek satırlık commit) ve review edilmiş bir merge ile envs/prod'a kopyalıyor. Argo CD main'i üç path ile izliyor. Altta yanlış yol: dev, staging ve prod branch'leri; bir merge debug log'u sızdırdı, merge ve cherry-pick iki kez çakıştı, üçü de 1.3.0 çalıştırırken git 7 commit fark gösterdi

Lab: ne ölçtüm

Üç kısım, hepsi yerelde:

  • A. Küçük bir Python reposu ve üç geliştiriciden gelen aynı 10 değişiklik; her modelde gerçek git ile uyguladım. N. değişiklik N. gün başlıyor. GitFlow'da feature branch'leri 5 gün, GitHub Flow'da PR'lar 2 gün yaşıyor, trunk'ta commit aynı gün giriyor. Dört değişiklik çifti aynı ya da komşu satırlara dokunuyor; gerçek hayatta config dosyalarında olduğu gibi.
  • B. Ortam başına branch'i olan bir GitOps reposu ve git merge ile git cherry-pick kullanılarak yapılan beş promotion.
  • C. Argo CD'li bir kind cluster, localhost:5001'de yerel bir registry ve GitHub yerine kind ağının içinde git daemon ile sunulan GitOps reposu. Her ortam için bir klasör, digest ile promotion.

A. Aynı 10 değişiklik, üç branching modeli

make models: GitFlow conflicts=5 merge_commits=15 avg_lead_days=9.4 max 13; çakışmalar feature/c3'ten config.yaml, release/1.0, feature/c6 ve hotfix/1.0.1'den pricing.py, feature/c9'dan cart.py; GitHub Flow conflicts=1 merge_commits=11 ortalama 2.0; Trunk-based conflicts=0 merge_commits=0 ortalama 0

GitFlow'da 5 çakışma çıktı. Üçü aynı anda açık olan feature'lardan geldi (C3 ile C2, C6 ile C4, C9 ile C5). Diğer ikisi sürecin kendisinden: release fix'i ve hotfix, arada aynı fonksiyonun değiştiği develop'a geri merge edilmek zorundaydı. GitHub Flow'da sadece C2 ile C3 aynı anda açık olduğu için 1 çakışma oldu. Trunk'ta 0. Son kod üç modelde de birebir aynı; çakışma çözümünün bir değişikliği düşürmediğini kontrol ettim.

Bu kısım için dürüst olayım: çakışma sayısı doğrudan branch'lerin ne kadar yaşadığına bağlı ve 5 ile 2 günü ben seçtim. PR'lar 1 gün yaşasaydı GitHub Flow'da da 0 olurdu. Önemli olan tam sayı değil, mekanizma.

Asıl büyük fark prod'a çıkış süresinde. GitFlow'da her değişiklik bir günde bitmesine rağmen bir sonraki sürümü bekliyor: ortalama 9,4 gün, en fazla 13 gün.

B. Ortam başına branch

make env-branches: dev'den staging'e merge LOG_LEVEL debug'ı sızdırdı; staging'den prod'a merge çakıştı; üç branch da shop 1.3.0 çalıştırıyor ama git log prod..staging 2 commit, staging..prod 5 commit; git diff staging prod replicas'ı ve staging'e ya da dev'e hiç ulaşmayan TIMEOUT_MS hotfix'ini gösteriyor

Beş promotion yaptım ve her hata türü bir kez ortaya çıktı:

  • Staging'e git merge dev yeni image'ı ve sadece dev'de olması gereken LOG_LEVEL=debug ayarını getirdi. Birinin fark edip geri alması gerekti.
  • Prod'a git merge staging, replicas satırında çakıştı; her branch'in kendi değeri vardı.
  • Doğrudan prod branch'inde yapılan bir hotfix (TIMEOUT_MS=700) staging'e ve dev'e hiç ulaşmadı. Prod'a yapılan sonraki cherry-pick de onunla çakıştı.

Sonunda üç branch da shop:1.3.0 çalıştırıyor. Buna rağmen git log staging'de prod'da olmayan 2 commit, ters yönde de 5 commit gösteriyor. Git geçmişi artık "staging ile prod arasında ne fark var?" sorusuna cevap vermiyor.

C. Bir kez build, Argo CD ile digest üzerinden promotion

Aynı commit'i iki kez build ettim:

build.sh 1.0.0 3f9c2a1 sha256:02e25761 veriyor; build.sh 1.1.0 8b41e07 sha256:f9cc2737 veriyor; aynı commit tekrar build edilince sha256:a04d1186; aynı digest mi? hayır

Aynı commit ve aynı Dockerfile iki farklı digest verdi. Image config'inde oluşturulma zamanı var, RUN ile oluşan katmanlar da yeni zaman damgası alıyor. Daha kötüsü, ikinci push sha-8b41e07 tag'ini hiçbir uyarı vermeden yeni image'a taşıdı. "Prod için yeniden build" bu yüzden aynı artifact değil; Git'e yazılan şey bu yüzden digest.

dev → staging → prod promotion:

# dev: CI az önce build ettiği digest'i yazıyor
./scripts/promote.sh dev sha256:a04d1186…
# staging: dev'deki digest'i kopyala, başka hiçbir şeyi değil
D=$(awk '/digest:/{print $2}' envs/dev/kustomization.yaml)
./scripts/promote.sh staging "$D"
# prod: review edilen bir değişiklik (GitHub/GitLab'da PR, burada branch + --no-ff merge)
git switch -c promote/prod-1.1.0
sed -i '' "s#digest: .*#digest: $D#" envs/prod/kustomization.yaml
git commit -am "promote shop 1.1.0 staging -> prod"
git switch main && git merge --no-ff promote/prod-1.1.0 && git push

promote.sh tek satırı değiştiriyor, commit'liyor, push ediyor ve o namespace'teki her pod o digest için oluşturulup hazır olana kadar bekliyor.

promotion: tüm ortamlar 1.0.0; dev git push'tan 80 sn sonra a04d11861a6c çalıştırıyor; render edilmiş dev ve staging klasörlerinin diff'i sadece namespace, replicas, log seviyesi ve image farkını gösteriyor; staging push'tan 56 sn sonra a04d11861a6c; git show --stat: 1 dosya, 1 ekleme, 1 silme

Render edilmiş dev ve staging klasörlerinin diff'i tam olarak istenen farkları gösteriyor: namespace, replicas, debug log seviyesi ve image. Staging promotion'ı 1 dosya, 1 satır.

review edilmiş merge ile prod promotion: prod push'tan 47 sn sonra 3 pod'da da a04d11861a6c; tüm ortamlar 1.1.0; merge'ün git revert -m 1 ile geri alınması prod'u 54 sn'de 02e257619b2b'ye döndürüyor; git log kimin neyi taşıdığını gösteriyor

Prod, merge push edildikten 47 saniye sonra yeni sürümdeydi. git revert -m 1 ile 54 saniyede geri döndü. git log artık deploy geçmişi: kim hangi digest'i taşıdı, kim onayladı, kim geri aldı.

Bu 47–80 saniye nereye gidiyor?

zamanlama: polling ile Argo CD commit'i 34 sn ve 13 sn sonra gördü, tüm pod'lar 67 sn ve 47 sn sonra hazır; refresh ile (webhook'un yaptığı) Argo CD commit'i 1 sn ve 2 sn sonra gördü, pod'lar 34 sn ve 35 sn sonra hazır

İki parça var. İlki Argo CD'nin commit'i fark etmesi. Polling ile (timeout.reconciliation'ı 10 sn yaptım, varsayılanı 3 dakika artı jitter) 13 ile 34 saniye sürdü. Git webhook'unun yaptığı gibi anında refresh ile 1 ile 2 saniye. İkincisi rollout: yaklaşık 33 saniye, çünkü eski pod'ların durması 30 saniye sürüyor. Bu bir Dockerfile sorunu (kubectl delete pod 31 sn sürdü) ve serideki CMD ile ENTRYPOINT yazısının konusu tam olarak bu.

Nerede patladı: 4 gerçek sürpriz

1. Yerel git daemon ile Argo CD failed to list refs: timeout exceeded

Argo CD GitOps reposunu okuyamadı. Asıl sebep git daemon'ın log'undaydı: fatal: detected dubious ownership in repository at '/srv/gitops.git'. Repo Mac'ten farklı bir sahiple mount edilmişti ve git onu sunmayı reddetti. Daemon container'ına safe.directory '*' ekleyince düzeldi. Ondan önce alpine/git image'ı daha erken bir yerde git: 'daemon' is not a git command hatası verdi: içinde git-daemon yok. Düz alpine'a apk add git-daemon ile kurmak gerekiyor.

2. Değişebilen bir tag, Git'te commit olmadan prod'daki kodu değiştirdi

tag kayması: deployment localhost:5001/shop:stable kullanıyor; Argo CD Synced/Healthy, sürüm 1.0.0, git HEAD d4cc98e; crane tag stable'ı a04d1186'ya taşıyor; bir pod siliniyor; Argo CD hâlâ Synced/Healthy, sürüm 1.1.0, git HEAD hâlâ d4cc98e; staging için crane tag sha-8b41e07, staging ve stable'da aynı digest a04d11861a6c'yi koruyor

Bir ortam shop:stable'ı imagePullPolicy: Always ile deploy ediyordu. Registry'de tag'i taşıdım ve bir pod'u sildim; node drain ya da eviction da tam olarak bunu yapar. Yeni pod 1.1.0 sürümüyle ayağa kalktı. Argo CD bu süre boyunca Synced/Healthy gösterdi, git log'da yeni commit yoktu. GitOps, doğrunun Git olduğunu vaat ediyor. Tag kullanınca doğru registry oluyor, Git sadece öyleymiş gibi görünüyor.

3. Retag için kullandığım docker buildx imagetools create digest'i değiştirdi

imagetools create -t shop:retagged shop@sha256:a04d1186; mediaType manifest.list.v2+json, içinde sha256:a04d1186; üstteki digest sha256:646898bd; prod pod spec'i @sha256:a04d1186 diyor ama status.imageID sha256:646898bd gösteriyor

Image'a ikinci bir tag vermek için önce docker buildx imagetools create -t kullandım. Bu komut sadece tag vermiyor; orijinal manifest'i içine alan yeni bir manifest list yazıyor. Tag de yeni bir digest'i gösteriyor (a04d1186…, 646898bd… oldu). crane tag digest'i koruyor; promotion adımında artık onu kullanıyorum.

4. status.imageID hiç deploy etmediğim bir digest gösterdi

İlk bekleme script'im pod'un status.imageID'sini taşıdığım digest'le karşılaştırıyordu. 3. sürprizden sonra hiç eşleşmedi: pod spec'i @sha256:a04d1186… diyordu, kubelet ise retag'den gelen manifest list'i, yani 646898bd…'yi raporluyordu. containerd aynı image için birden fazla repo digest'i tutuyor ve birini raporluyor. Script artık pod spec'ine (istenen şeye) ve hazır olma durumuna bakıyor. "Prod'da hangi digest çalışıyor" sorusunu imageID'den denetliyorsan bunu aklında tut.

Hangi durumda hangisi

Durum Seçim
Web servisi ya da API, tek prod sürümü, her gün deploy Trunk-based (ya da birkaç saat yaşayan PR'larla GitHub Flow), bitmemiş iş için feature flag
CI'a yeni başlayan, her değişikliğe review isteyen ekip GitHub Flow, PR'ların 2 günden fazla açık kalmaması kuralıyla
Aynı anda birkaç sürüm destekleniyor (on-prem, mobil, SDK) main'den açılan trunk + release branch'leri, düzeltmeler cherry-pick ile. Ayrı bir entegrasyon branch'ine de ihtiyaç varsa GitFlow
GitOps config reposu Tek main branch'i, her ortam için bir klasör. Ortam başına branch asla
Image'ı ortamlar arasında taşımak Bir kez build et, digest'i sabitle, tek satırlık commit ile taşı. Prod için reviewer'lı PR

Özet

  • Branching stratejisi işin nerede yapıldığına, ne sıklıkla birleştirildiğine ve neyin yayına çıktığına karar veriyor.
  • Aynı 10 değişiklik GitFlow'da 5, GitHub Flow'da 1, trunk-based development'ta 0 merge çakışması çıkardı. Prod'a ortalama 9,4, 2 ve 0 günde ulaştı.
  • GitOps'ta uygulama reposunda trunk ya da GitHub Flow, config reposunda tek main ve her ortama bir klasör kullan. Ortam başına branch, dev'e özel bir ayarı sızdırdı, iki kez çakıştı ve tüm ortamlar aynı image'ı çalıştırırken 7 commit fark gösterdi.
  • Bir kez build et, digest ile taşı. Aynı commit iki kez build edilince iki digest çıktı; taşınan bir tag, Argo CD Synced derken prod'daki kodu değiştirdi.
  • Promotion tek satırlık bir Git commit'i, rollback git revert. Webhook ile Argo CD değişikliği 1–2 saniyede görüyor.

Kaynaklar: Vincent Driessen, A successful Git branching model (2010, 2020 notuyla); GitHub Docs GitHub flow, Reusing workflows, Creating a composite action, Caching dependencies; GitLab Docs CI/CD pipelines, Downstream pipelines (dinamik child pipeline), Merge request pipelines; trunkbaseddevelopment.com; DORA Accelerate State of DevOps; Argo CD dokümanları Automated Sync, Git Webhook Configuration, Reconciliation; Kustomize images alanı; go-containerregistry crane dokümanları.

Serinin devamı

Bu yazı Temel Konular, Ölçerek serisinin ikincisi. Bir önceki yazıda, Zero-Downtime Deployment Nedir?, yeni sürüm başlarken ne olduğunu ölçmüştüm; bu yazıda oraya nasıl ulaştığını ölçtüm. Sıradaki yazı rootless container'lar. Argo CD'nin kendisini HA çalıştırmayı da Argo CD HA yazısında anlatmıştım.

Bu lab'ın kodu (üç branching simülasyonu, ortam başına branch senaryosu, kind + registry + git daemon + Argo CD kurulumu, promotion ve tag kayması script'leri ve Makefile) Buy Me a Coffee'deki Lab Access üyeleriyle paylaşılıyor; üye olduktan sonra repo daveti e-postayla geliyor: https://buymeacoffee.com/miraccanyilmaz/membership

İnsanın en büyük hatalarından biri de doğru zamanı yanlış kişilerle doldurmaktır.
Charles Bukowski