Kasım 15 2024

Ansible ile Idempotent Playbook Yazmanın İncelikleri

Sektörde beş yılı deviren her devops mühendisinin ortak kabuslarından biri, “benden önce yazılmış” ve her çalıştığında farklı bir sürpriz sunan ansible playbook’larıdır. Hepimiz o yollardan geçtik: Sadece bir config dosyasındaki parametreyi değiştirmek için çalıştırdığınız playbook, sunucudaki üç servisi yeniden başlatır, SSL sertifikalarını sıfırlar ve deployment pipeline’ını kilitler. İşte bu noktada, modern iac (Infrastructure as Code) felsefesinin kalbi olan idempotent (eşgüçlü) kavramı devreye giriyor. Gerçek bir altyapı otomasyon süreci, aynı playbook’u ister 1 ister 1000 kez çalıştırın, hedef sistemi her zaman tam olarak hedeflediğiniz kararlı durumda (desired state) bırakmalıdır. Bu makalede, işin “YAML yazmaktan” çıkıp gerçek bir yazılım mühendisliği disiplinine dönüştüğü o ince çizgiyi inceleyeceğiz.

1. “Her Şey Yolunda” İllüzyonu: changed_when ve failed_when

Ansible modüllerinin büyük çoğunluğu idempotent çalışacak şekilde tasarlanmıştır. Örneğin ansible.builtin.apt veya ansible.builtin.template modülleri, hedef sistemin durumunu kontrol eder ve bir değişiklik gerekmiyorsa yeşil yanarak yoluna devam eder. Ancak iş shell veya command modüllerine geldiğinde Ansible körleşir. Bu modüller doğası gereği her çalıştığında sisteme bir etki ettiklerini varsayarlar ve her zaman sarı (changed: true) dönerler.

Bu durum sadece göz zevkimizi bozmakla kalmaz; Ansible handler’larının (örneğin servis restart işlemleri) gereksiz yere tetiklenmesine yol açarak production ortamında kesintilere sebep olur. İşte bu kontrolsüzlüğü dizginlemek için elimizdeki en güçlü silahlar: changed_when ve failed_when.

İyi, Kötü ve Çirkin: shell Modülünü Terbiye Etmek

Farz edelim ki bir uygulamanın CLI aracıyla bir konfigürasyon yapacaksınız. Eğer bu konfigürasyon zaten yapılmışsa, komutu tekrar çalıştırmamalı veya çalıştırsak bile Ansible’a “hey, burada yeni bir şey yapmadın, sakin ol” demeliyiz.

Aşağıdaki kötü pratiğe bir göz atalım:

# KÖTÜ PRATİK (Her çalışmada "changed" döner, handler'ları tetikler)
- name: Enable custom application plugin
  ansible.builtin.shell: "myapp-cli plugin enable prometheus"
  register: plugin_output
  notify: Restart MyApp

Şimdi bunu profesyonelce revize edelim. Önce eklentinin durumunu sorgulayalım, ardından sadece eklenti aktif değilse işlem yapalım ve durumu Ansible’a doğru şekilde bildirelim:

# İYİ PRATİK (Idempotent ve güvenli)
- name: Check if prometheus plugin is already enabled
  ansible.builtin.shell: "myapp-cli plugin list | grep -E '^prometheus.*enabled'"
  register: plugin_status
  failed_when: false
  changed_when: false

- name: Enable custom application plugin if not enabled
  ansible.builtin.shell: "myapp-cli plugin enable prometheus"
  when: plugin_status.rc != 0
  register: enable_result
  changed_when: "'plugin successfully enabled' in enable_result.stdout"
  notify: Restart MyApp

Burada ne yaptık? İlk task’ta failed_when: false ve changed_when: false diyerek, sadece bir durum sorguladığımızı ve bu sorgunun sistemi değiştirmediğini, ayrıca grep başarısız olsa bile (yani plugin kurulu değilse) pipeline’ın kırılmaması gerektiğini belirttik. İkinci task’ta ise sadece plugin aktif değilse çalıştık ve çıktıdaki spesifik bir loga bakarak değişikliğin gerçekten gerçekleşip gerçekleşmediğini teyit ettik.

2. Sırları Sızdırmadan Yönetmek: Ansible Vault ve Best Practice’ler

IaC kodlarınızı Git reposunda tutuyorsanız (ki tutmalısınız), API key’ler, veritabanı şifreleri veya SSL private key’ler gibi hassas verileri (secrets) asla düz metin (plain text) olarak commit etmemelisiniz. Ansible bu problemi çözmek için dahili bir şifreleme mekanizması olan Ansible Vault‘u sunar.

Ancak projelerde sıklıkla yapılan hata, tüm group_vars/all.yml dosyasını toptan şifrelemektir. Bu, kod incelemelerinde (Code Review) hangi değişkenlerin değiştiğini görmeyi imkansız hale getirir. Bunun yerine, sadece hassas değerleri şifreleyip referans vermek çok daha temiz bir yaklaşımdır.

Tek Satır Şifreleme (vault_encrypted) Pratiği

Tüm dosyayı şifrelemek yerine, sadece gizli tutmak istediğiniz değeri terminalde şifreleyin:

ansible-vault encrypt_string 'super_secret_db_password' --name 'vault_db_password'

Bu komut size YAML formatında şifrelenmiş bir blok verecektir. Bu bloğu değişken dosyanıza güvenle yapıştırabilirsiniz:

# group_vars/production.yml
db_username: "app_user" # Düz metin olarak kalmasında sakınca yok
db_password: "{{ vault_db_password }}" # Şifrelenmiş değere referans

# Şifrelenmiş blok:
vault_db_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          3565393065646134373463326139613661646231363539303362333633643730306265353866
          62376663363236373961633535366430333766396662366264370a3736363339386333343632
          3431663030313534663831633633636636316262333066366432366164343164376435383561
          32303533663731306132373335623233390a3666363435303964313032393335396161613936
          653063616664653838323630

Playbook’u çalıştırırken Vault şifresini güvenli bir şekilde okutmak için --vault-password-file parametresini veya çevresel değişkenleri (environment variables) kullanabilirsiniz. CI/CD pipeline’larında bu şifreyi runner’a bir secret olarak tanımlamak en güvenli yoldur.

3. Altyapıyı Test Etmek: Molecule ile TDD Yaklaşımı

“Yazdığım playbook production’da çalışır mı?” sorusunun cevabı hiçbir zaman “deneyip görelim” olmamalıdır. Yazılım dünyasındaki Unit/Integration test kavramının IaC dünyasındaki karşılığı Molecule‘dur. Molecule; Ansible rollerinizi izole ortamlarda (genellikle Docker veya Podman üzerinde, bazen de Vagrant/AWS’te) otomatik olarak ayağa kaldırır, playbook’unuzu çalıştırır (idempotency testi dahil) ve ardından ortamı temizler.

Molecule Kurulumu ve Örnek Senaryo

Molecule’ü projenize dahil etmek için Docker driver’ı ile birlikte yükleyin:

pip install molecule molecule-plugins[docker] ansible-lint

Bir Ansible rolü içinde Molecule senaryosu başlatmak için:

molecule init scenario --driver-name docker

Bu komut, rolünüzün altında molecule/default adında bir klasör oluşturur. Buradaki en kritik dosya testlerin nasıl koşulacağını belirleyen molecule.yml dosyasıdır:

# molecule/default/molecule.yml
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: test-ubuntu-target
    image: geerlingguy/docker-ubuntu2204-ansible:latest
    pre_build_image: true
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
    cgroupns_mode: host
    privileged: true
provisioner:
  name: ansible
  playbooks:
    converge: ${MOLECULE_PLAYBOOK:-converge.yml}
verifier:
  name: ansible

molecule test komutunu çalıştırdığınızda şu adımlar sırasıyla işletilir:

  1. Dependency: Gerekli harici roller indirilir.
  2. Lint: Ansible-lint ile kod standartları taranır.
  3. Destroy/Create: Eski test container’ları silinir ve yenileri ayağa kaldırılır.
  4. Converge: Playbook’unuz ilk kez çalıştırılır (kurulumlar yapılır).
  5. Idempotence: Playbook ikinci kez çalıştırılır. Eğer herhangi bir task “changed” dönerse test başarısız sayılır! İşte gerçek idempotent testi budur.
  6. Verify: Belirlediğiniz test script’leri (örneğin servis gerçekten çalışıyor mu kontrolü) koşturulur.

4. Performans Optimizasyonu: Dakikaları Saniyelere İndirmek

Ansible, mimarisi gereği her bir task için hedef makineye SSH bağlantısı açar, modülü transfer eder, çalıştırır ve sonucu geri alır. Yüzlerce sunucudan oluşan bir envanterde bu durum ciddi bir performans darboğazı (bottleneck) yaratır. Neyse ki birkaç ince ayarla bu süreyi dramatik şekilde azaltabiliriz.

SSH Pipelining Aktifleştirme

Ansible’ın varsayılan davranışında, modül dosyaları önce hedef makineye kopyalanır ve ardından SSH üzerinden çalıştırılır. SSH Pipelining aktifleştirildiğinde ise bu işlemler kopyalama adımı atlanarak doğrudan SSH oturumu üzerinden (piped) gerçekleştirilir.

# ansible.cfg
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

Not: Pipelining kullanabilmek için hedef sunuculardaki /etc/sudoers dosyasında requiretty seçeneğinin aktif olmaması gerekir (modern dağıtımlarda varsayılan olarak pasiftir).

Fact Gathering Mekanizmasını Optimize Etmek

Ansible her playbook başlangıcında hedef sisteme dair tüm sistem bilgilerini (IP, CPU, RAM, disk durumları vb.) toplar (Gathering Facts). Eğer playbook’unuzda bu değişkenleri (örn: ansible_distribution) kullanmıyorsanız, bu adımı tamamen kapatın:

- hosts: webservers
  gather_facts: false
  tasks:
    # ...

Eğer bazı task’lar için bu bilgilere ihtiyacınız varsa, akıllı cache (fact caching) mekanizmasını devreye sokarak her çalıştırmada bu süreyi tekrar ödemekten kurtulabilirsiniz:

# ansible.cfg
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_fact_cache
fact_caching_timeout = 86400 # 24 saat cache'le

Özet ve Kapanış

Yazması kolay, yönetmesi zor bir araçtır Ansible. Onu sıradan bir shell script tetikleyicisinden profesyonel bir iac aracına dönüştüren şey, sizin yazdığınız playbook’lardaki detay seviyesidir. changed_when ile kontrolü elinizde tutmak, secrets yönetiminde hassas davranmak, Molecule ile test güvencesi sağlamak ve performans parametrelerini optimize etmek sizi ekipten bir adım öne çıkaracaktır. Altyapınızın her zaman tahmin edilebilir, kararlı ve hızlı kalması dileğiyle!

Category: Genel | LEAVE A COMMENT
Ekim 25 2024

Terraform State Yönetimi: Remote Backend ve State Locking

Modern devops dünyasında, Altyapıyı Kod Olarak Yönetmek (iac) denince akla gelen ilk araç tartışmasız terraform. Ancak projeler büyüdükçe ve ekip arkadaşlarınızla aynı cloud kaynakları üzerinde çalışmaya başladığınızda, o meşhur local terraform.tfstate dosyası tam bir kabusa dönüşebilir. Bu yazımızda, state yönetimini local’den kurtarıp AWS üzerinde S3 ve DynamoDB kullanarak nasıl kurumsal seviyede güvenli, kilitlenebilir (state locking) ve ölçeklenebilir bir yapıya kavuşturacağımızı adım adım inceleyeceğiz.

Eğer production ortamınızda hala local state kullanıyorsanız, muhtemelen adrenaline aşırı bağımlı bir hayat yaşıyorsunuzdur. Gelin, o state dosyasını ait olduğu yere, buluta taşıyalım ve işleri profesyonelleştirelim.

Local State’in Vedası: Neden Remote Backend?

Terraform ile çalışırken oluşturduğunuz her kaynak, default olarak projenizin kök dizinindeki terraform.tfstate dosyasına kaydedilir. Tek başınıza çalışırken bu durum idare edilebilir gibi görünse de, ekibe ikinci bir kişi dahil olduğu anda şu sorunlar baş gösterir:

  • Race Condition (Yarış Durumu): Aynı anda iki mühendisin terraform apply çalıştırması, kaynakların çakışmasına ve state dosyasının bozulmasına neden olur.
  • Güvenlik ve Hassas Veriler: State dosyası, oluşturulan kaynakların şifrelerini, private key’lerini ve tüm meta datasını plain-text (açık metin) olarak tutar. Bu dosyayı Git deposuna göndermek, güvenlik ekiplerinin kapınıza dayanması için en kestirme yoldur.
  • Tek Nokta Hatası (Single Point of Failure): Bilgisayarınızın diski bozulduğunda veya kahve döküldüğünde tüm altyapı geçmişiniz yok olur.

Bu sorunların çözümü, state dosyasını merkezi ve güvenli bir yerde tutmaktır. AWS ekosisteminde bunun endüstri standardı karşılığı S3 (Storage) + DynamoDB (Locking) ikilisidir.

Adım Adım S3 ve DynamoDB Backend Kurulumu

Buradaki en büyük ironi şudur: Remote backend’i oluşturmak için de Terraform kullanmak isteriz. Ancak backend henüz var olmadığı için, bootstrap aşamasında local state kullanıp, kaynakları oluşturduktan sonra state’i yeni oluşturduğumuz bu remote backend’e migrate ederiz (taşırız).

1. S3 Bucket ve DynamoDB Kaynaklarının Tanımlanması

Öncelikle backend kaynaklarımızı tanımlayacağımız bir main.tf dosyası oluşturalım. S3 bucket’ımızın versiyonlamaya (versioning) açık olması hayati önem taşır. Bu sayede olası bir state bozulmasında geçmişe dönebiliriz.

provider "aws" {
  region = "eu-west-1"
}

# State dosyasını saklayacağımız S3 Bucket
resource "aws_s3_bucket" "terraform_state" {
  bucket        = "kertenkerem-tf-state-bucket"
  force_destroy = false

  lifecycle {
    prevent_destroy = true
  }
}

# S3 Bucket versiyonlamasını aktif ediyoruz
resource "aws_s3_bucket_versioning" "state_versioning" {
  bucket = aws_s3_bucket.terraform_state.id
  versioning_configuration {
    status = "Enabled"
  }
}

# State dosyasının diskte şifrelenmesi (Encryption)
resource "aws_s3_bucket_server_side_encryption_configuration" "state_encryption" {
  bucket = aws_s3_bucket.terraform_state.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm = "AES256"
    }
  }
}

# State locking mekanizması için DynamoDB Tablosu
resource "aws_s3_bucket_public_access_block" "block_public" {
  bucket = aws_s3_bucket.terraform_state.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_dynamodb_table" "terraform_locks" {
  name         = "kertenkerem-tf-state-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

Kritik Detay: DynamoDB tablosunun partition key (hash_key) değeri tam olarak LockID (büyük-küçük harf duyarlı) olmalıdır. Terraform, kilit bilgisi yazarken bu spesifik anahtarı arar.

Bu aşamada projemizi initialize edip kaynakları AWS üzerinde oluşturalım:

terraform init
terraform apply

2. Backend Konfigürasyonunun Eklenmesi

Kaynaklarımız AWS üzerinde başarıyla oluşturulduktan sonra, projemize bu backend’i kullanmasını söyleme zamanı geldi. Proje dizininde bir backend.tf dosyası oluşturalım:

terraform {
  backend "s3" {
    bucket         = "kertenkerem-tf-state-bucket"
    key            = "global/s3/terraform.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "kertenkerem-tf-state-locks"
    encrypt        = true
  }
}

Büyük Göç: State Dosyasını Local’den S3’e Taşımak (Migration)

Backend tanımını ekledikten sonra Terraform’a bu değişikliği bildirmemiz gerekiyor. Terminale dönüp şu sihirli komutu çalıştırıyoruz:

terraform init

Terraform, local’de mevcut bir state dosyası olduğunu ve konfigürasyonda yeni bir S3 backend tanımlandığını algılayacaktır. Karşınıza şu şekilde bir uyarı gelecektir:

Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "local" backend to the
  newly configured "s3" backend. No existing state was found in the "s3" backend.
  Do you want to copy this state to the new "s3" backend? Enter "yes" to copy and "no"
  to start with an empty state.

  Enter a value: yes

Bu soruya yes yanıtını verdiğinizde, Terraform yerel diskinizdeki tüm state geçmişini güvenli bir şekilde AWS S3 bucket’ına yükler. Artık projenizin kök dizinindeki local terraform.tfstate dosyasını güvenle silebilirsiniz (yine de silmeden önce yedeklemek her zaman iyi bir refleksdir).

Kriz Anı: State Lock Çakışması ve “force-unlock” Sanatı

Remote backend’imizin en güzel yanı, birisi terraform apply veya terraform plan çalıştırdığında DynamoDB’ye bir lock (kilit) kaydı yazmasıdır. Bu esnada başka biri aynı işlemi yapmaya çalışırsa, Terraform işlemi durdurur ve şu hatayı fırlatır:

Error: Error acquiring the state lock
Error info:
   ID:     e9b72a44-dfc1-4b11-9a73-519280d8f0f0
   Path:   kertenkerem-tf-state-bucket/global/s3/terraform.tfstate
   Who:    ahmet@kertenkerem-macbook
   Version: 1.5.0
   Created: 2023-10-27 12:00:00 +0000 UTC
   Info:    org: kertenkerem-devops

Peki ya bu kilitlenme, Jenkins/GitLab pipeline’ınızın yarıda kesilmesi, internetinizin kopması veya Docker container’ının crash olması nedeniyle havada asılı kaldıysa? Kimse işlem yapamadığı için deployment’lar kilitlenir.

Çözüm: force-unlock

Böyle bir durumda, hatanın size verdiği Lock ID‘yi kullanarak kilidi manuel olarak kırmak zorundasınız. Ancak dikkatli olun: Bu komutu çalıştırmadan önce, kilidin gerçekten “sahipsiz” kaldığından ve arka planda başka bir mühendisin veya pipeline’ın canlı bir apply işlemi yürütmediğinden emin olmalısınız.

terraform force-unlock e9b72a44-dfc1-4b11-9a73-519280d8f0f0

Komut başarıyla çalıştığında DynamoDB üzerindeki kilit kaydı silinecek ve pipeline’larınız tekrar çalışabilir hale gelecektir.

Workspace Stratejileri: Hangisini Seçmeliyiz?

Birden fazla ortamı (dev, stage, prod) yönetirken state dosyalarını nasıl izole edeceğiz? Burada iki temel yaklaşım var: Terraform Workspace CLI ve Directory-based (Klasör Tabanlı) İzolasyon.

Yöntem A: Terraform Workspace CLI

Terraform’un built-in workspace özelliğidir. Tek bir konfigürasyon kodunu kullanarak arka planda farklı state dosyaları oluşturur.

terraform workspace new dev
terraform workspace new prod

# Geçiş yapmak için:
terraform workspace select dev

S3 backend tarafında Terraform, state dosyalarınızı otomatik olarak env:/dev/global/s3/terraform.tfstate ve env:/prod/global/s3/terraform.tfstate yollarına kaydeder.

Neden Dikkatli Olunmalı? Workspace’ler CLI üzerinden kolayca değiştirilebildiği için, yanlışlıkla prod ortamındayken dev zannedip yıkıcı bir apply komutu çalıştırmak çok kolaydır. Ayrıca her iki ortam da aynı AWS account’unu ve aynı yetki sınırlarını paylaşıyorsa, güvenlik zafiyeti doğurabilir.

Yöntem B: Klasör Tabanlı İzolasyon (Önerilen)

Büyük ölçekli ve multi-account (çoklu AWS hesabı) yapılarında en güvenli yol, ortamları dizin bazında tamamen ayırmaktır.

├── environments
│   ├── dev
│   │   ├── backend.tf  # Dev AWS Account S3 kovasını gösterir
│   │   ├── main.tf
│   │   └── variables.tf
│   └── prod
│       ├── backend.tf  # Prod AWS Account S3 kovasını gösterir
│       ├── main.tf
│       └── variables.tf

Bu yaklaşımda, Dev ve Prod ortamlarının state dosyaları tamamen farklı AWS hesaplarındaki S3 bucket’larında durur. Prod ortamına dokunmak için Prod AWS credentials’ına ihtiyacınız vardır. Dolayısıyla, local’deki bir kaza eseri tüm prod altyapısını silme ihtimaliniz sıfıra iner.

Son Sözler ve Altın Kurallar

Terraform ile profesyonel bir altyapı yönetmek istiyorsanız, state dosyanızı gözünüz gibi korumalısınız. Özetlemek gerekirse:

  • Asla ama asla local state ile production yönetmeyin.
  • S3 bucket’ınızda Versioning ve Encryption özelliklerini her zaman açık tutun.
  • State lock için mutlaka DynamoDB entegrasyonunu yapın.
  • Büyük ve kritik projelerde CLI workspace’leri yerine, dizin tabanlı (ve tercihen Terragrunt destekli) izole yapılar kurun.
  • .gitignore dosyanıza .terraform/, *.tfstate ve *.tfstate.backup maskelerini eklemeyi unutmayın.

State’iniz kilitli, pipeline’larınız yeşil kalsın!

Category: Genel | LEAVE A COMMENT
Ekim 18 2024

GitOps ile Infrastructure as Code: ArgoCD ve Flux Karşılaştırması

Modern yazılım geliştirme süreçlerinde “lokalde çalışıyordu, production’da neden patladı?” sendromunu aşmanın en asil yolu altyapımızı da uygulama kodlarımız gibi sürüm kontrol sistemlerinde tutmaktan geçiyor. İşte bu noktada, Infrastructure as Code (IaC) felsefesinin uygulama dağıtım süreçleriyle evlenmesinden doğan GitOps yaklaşımı devreye giriyor. Günümüzde Kubernetes cluster’larını yönetmenin fiili standardı haline gelen GitOps modelinde, Git repomuz sistemimizin “Single Source of Truth” (Tek Doğruluk Kaynağı) haline geliyor. Peki, bu ekosistemin iki dev oyuncusu olan ArgoCD ve Flux arasından hangisini seçmeliyiz? Bu makalede, iki aracı derinlemesine karşılaştıracak, Helm chart deployment pratiklerine bakacak ve işler ters gittiğinde can simidimiz olacak rollback stratejilerini inceleyeceğiz.

Push-Based vs. Pull-Based: Neden GitOps?

Geleneksel CI/CD pipeline süreçlerinde genellikle “Push-Based” (İtme Odaklı) bir yaklaşım benimseriz. Jenkins, GitLab CI veya GitHub Actions gibi bir araç, uygulamanın imajını build eder ve ardından kubectl apply veya helm upgrade komutlarıyla hedef Kubernetes cluster’ına bağlanıp değişikliği push eder. Kulağa kolay geliyor değil mi? Ancak bu yaklaşımın iki büyük karanlık noktası vardır:

  1. Güvenlik Açığı: CI/CD aracınıza cluster üzerinde yüksek yetkili (admin) erişim anahtarları (kubeconfig) vermeniz gerekir. Eğer CI sunucunuz hacklenirse, tüm cluster anahtarlarını teslim etmiş olursunuz.
  2. State Drift (Durum Sapması): Cluster üzerinde birisi manuel olarak (örneğin gece yarısı hotfix’i ile) bir deployment’ı değiştirirse, Git repomuzdaki kod ile canlıdaki durum birbirinden sapar. CI aracının bir sonraki tetiklenmesine kadar bu durumdan haberimiz olmaz.

Pull-Based GitOps ise bu problemi tersine çevirir. Cluster içerisine kurduğumuz bir operator (ArgoCD veya Flux), Git repomuzu sürekli izler (polling veya webhook ile). Repoda yeni bir commit gördüğünde, aradaki farkı algılar ve cluster’ı Git’teki durumla eşitlemek için “pull” eder. Dışarıya hiçbir credential sızmaz, cluster kendi kendini yönetir.

ArgoCD vs. Flux: Mimari ve Felsefe Farkları

İki araç da CNCF (Cloud Native Computing Foundation) bünyesinde mezun (graduated) projelerdir ve production ortamlarında rüştünü fazlasıyla ispatlamıştır. Ancak felsefeleri oldukça farklıdır.

1. ArgoCD: Görsel Güç ve Merkezi Yönetim

ArgoCD, kullanıcı dostu web arayüzü (UI) ve merkezi yönetim kabiliyetleriyle öne çıkar. Eğer organizasyonunuzda çok sayıda developer varsa ve onların Kubernetes kaynaklarını görsel olarak izlemesini, loglara erişmesini, senkronizasyon durumunu canlı grafiklerle görmesini istiyorsanız ArgoCD tam size göredir.

  • CRD Yapısı: Temel olarak Application ve AppProject Custom Resource Definition (CRD) yapılarını kullanır.
  • SSO Entegrasyonu: Dex aracılığıyla Keycloak, Okta, GitHub gibi kimlik sağlayıcılarla harika entegre olur. Multi-tenancy desteği mükemmeldir.
  • Yönetim Tarzı: Web UI üzerinden manuel tetikleme (Sync) yapılmasına izin verir (Opsiyonel olarak kapatılabilir).

2. Flux (Flux v2): Kubernetes Native ve Minimalist

Flux, Kubernetes felsefesine sıkı sıkıya bağlıdır. Bir web arayüzü barındırmaz (üçüncü parti CLI veya UI araçları hariç). Tamamen “Git-first” ve “declarative” bir yaklaşımla çalışır. Mikrofonu Kubernetes’in kendi controller mimarisine bırakır.

  • Modüler Mimari: Flux, GitOps Toolkit adı verilen bağımsız controller’lardan oluşur (Source Controller, Kustomize Controller, Helm Controller, Notification Controller).
  • Çoklu Cluster (Multi-cluster) Kolaylığı: Git repolarını hiyerarşik olarak tarayıp düzinelerce cluster’ı tek bir Flux instance’ı ile yönetmek oldukça esnektir.
  • CLI Gücü: fluxctl (artık sadece flux) CLI’ı, terminal bağımlısı mühendislerin rüyalarını süsleyecek seviyededir.

Hızlı Karşılaştırma Tablosu

  • Multi-Tenancy
  • Özellik ArgoCD Flux v2
    Kullanıcı Arayüzü (UI) Var (Çok güçlü ve detaylı) Yok (CLI odaklı / Üçüncü parti eklentiler var)
    Mimari Monolitik Operator / API Server Mikroservis / Modüler Controller’lar
    Helm Desteği Var (Client-side render eder) Var (Dedicated Helm Controller kullanır)
    Yerleşik (AppProject nesneleri ile) Kubernetes RBAC ve Namespace izolasyonu ile

    GitOps Dünyasında Helm Yönetimi

    GitOps yaparken Kubernetes manifestlerini doğrudan çiğ YAML olarak tutmak yerine genellikle Helm chart paketlerini kullanırız. Hem ArgoCD hem de Flux, Helm entegrasyonuna sahiptir ancak işleyişleri farklıdır.

    ArgoCD ile Helm Dağıtımı

    ArgoCD, Helm chart’ı Git’ten veya harici bir Helm repo’sundan okur. Arka planda helm template komutunu çalıştırarak saf manifestleri üretir ve bunları cluster’a uygular. Yani cluster’da gerçek bir helm list komutu çalıştırdığınızda bu release’i göremezsiniz; her şey standart Kubernetes kaynakları olarak görünür.

    İşte ArgoCD için örnek bir declarative Application manifesti:

    apiVersion: argoproj.io/v1alpha1
    kind: Application
    metadata:
      name: kertenkerem-api
      namespace: argocd
    spec:
      project: default
      source:
        repoURL: 'https://charts.bitnami.com/bitnami'
        chart: nginx
        targetRevision: 15.0.0
        helm:
          valueFiles:
            - values.yaml
          parameters:
            - name: service.type
              value: ClusterIP
      destination:
        server: 'https://kubernetes.default.svc'
        namespace: production
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
    

    Buradaki selfHeal: true parametresi kritik önem taşır. Eğer birisi cluster’da gidip kubectl edit ile Nginx deployment’ının replika sayısını değiştirirse, ArgoCD bunu anında yakalar ve saniyeler içinde Git’teki duruma (örneğin 3 replikaya) geri döndürür.

    Flux ile Helm Dağıtımı

    Flux ise işi Kubernetes native controller’lar aracılığıyla çözer. İlk olarak kaynağı (HelmRepository) tanımlarız, ardından bu kaynaktan beslenen bir HelmRelease kaynağı oluştururuz. Flux, Kubernetes içerisinde gerçek Helm release’leri oluşturur.

    İlk adım: Helm Repository Tanımlama

    apiVersion: source.toolkit.fluxcd.io/v1beta2
    kind: HelmRepository
    metadata:
      name: bitnami
      namespace: flux-system
    spec:
      interval: 1h
      url: https://charts.bitnami.com/bitnami
    

    İkinci adım: HelmRelease Kaynağı

    apiVersion: helm.toolkit.fluxcd.io/v2beta1
    kind: HelmRelease
    metadata:
      name: kertenkerem-api
      namespace: production
    spec:
      interval: 15m
      chart:
        spec:
          chart: nginx
          version: '15.x'
          sourceRef:
            kind: HelmRepository
            name: bitnami
            namespace: flux-system
          interval: 1h
      install:
        remediation:
          retries: 3
      upgrade:
        remediation:
          retries: 3
      values:
        service:
          type: ClusterIP
    

    Rollback Stratejileri: İşler Sarpasarınca Ne Yapıyoruz?

    Canlıya yeni bir sürüm çıktık ve loglar bir anda kırmızıya boyandı. Geleneksel dünyada panikle helm rollback yazarız. Ancak GitOps dünyasında bu bir antipattern’dir! Neden mi? Çünkü cluster üzerinde manuel yapacağınız her rollback, Git reposundaki durumla çelişecektir. GitOps operator’ınız canlı durumu tekrar Git’tekine eşitlemek için (eğer self-heal açıksa) uygulamanızı tekrar hatalı sürüme yükseltecektir. Kısır döngüye hoş geldiniz!

    Peki doğru rollback stratejileri nelerdir?

    1. “Git Revert” Stratejisi (The Pure GitOps Way)

    En temiz, en denetlenebilir rollback yöntemi Git geçmişini geriye almaktır.
    Hatalı deployment’a sebep olan commit belirlenir ve lokalde şu komut koşturulur:

    git revert <commit-hash>
    git push origin main
    

    GitOps operator’ı değişikliği algılar, yeni “eski” versiyonu cluster’a uygular. Bu sayede audit log’larımızda kimin, ne zaman, neden rollback yaptığının kaydı kalır.

    2. Otomatik Helm Rollback (Automated Remediation)

    Bazen uygulamanın ayağa kalkamaması (CrashLoopBackOff) durumunda Git commit’ini bekleyecek vaktimiz olmayabilir. Flux ve ArgoCD bu durumlar için “Auto-Rollback” mekanizmaları sunar.

    Flux’ta Rollback Yapılandırması:
    Yukarıda paylaştığımız Flux HelmRelease manifestindeki remediation bloğu tam olarak bu işe yarar. Eğer Helm upgrade işlemi başarısız olursa (örneğin readiness probe’lar geçemezse), Flux otomatik olarak bir önceki çalışan Helm release sürümüne geri döner (rollback).

    ArgoCD’de Rollback Yapılandırması:
    ArgoCD’de eğer senkronizasyon başarısız olursa, otomatik rollback doğrudan ArgoCD Application seviyesinde yönetilebilir. Ancak dikkat edin; eğer Git’teki kod hala hatalı sürümü işaret ediyorsa ve ArgoCD “Auto-Sync” modundaysa, sürekli bir loop oluşabilir. Bu yüzden ArgoCD’de genellikle Progressive Delivery araçları (Argo Rollouts) ile birlikte Canary veya Blue/Green deployment stratejileri tercih edilir.

    Hangisini Seçmelisiniz?

    Yol ayrımındaysanız şu basit kuralları göz önünde bulundurabilirsiniz:

    • Şu durumlarda ArgoCD seçin: Yazılım geliştirme ekipleriniz cluster durumunu izlemek için terminale mahkum kalmak istemiyorsa, SSO entegreli şık bir dashboard şartsa ve uygulama gruplarını mantıksal projeler halinde (multi-tenancy) izole etmek istiyorsanız.
    • Şu durumlarda Flux seçin: Kubernetes felsefesine sıkı sıkıya bağlı kalmak istiyorsanız, kaynak tüketimi (resource footprint) sizin için kritikse, cluster içinde ekstra bir web server/UI çalıştırmak istemiyor ve tamamen Git entegrasyonuna güveniyorsanız.

    Her iki araç da günün sonunda sizi manuel müdahalelerin getirdiği güvensiz hislerden kurtaracak ve altyapınızı gerçek anlamda kod olarak yönetmenizi sağlayacaktır. Cluster’ınız kararlı, pipeline’larınız yeşil kalsın!

    Category: Genel | LEAVE A COMMENT