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
Kasım 8 2024

Dijital Göçebe Olmak: Çalışırken Dünyayı Gezmenin Gerçek Yüzü

O meşhur fotoğrafı mutlaka görmüşsünüzdür: Kumsalda, şezlonga uzanmış, kucağında bilgisayarıyla çalışan güler yüzlü bir insan. Arkada palmiyeler, masmavi bir deniz… Şimdi size bir sır vereyim: O fotoğraf tamamen bir yalan. Açık havada, doğrudan güneş ışığı altında o ekranda hiçbir şey göremezsiniz. Klavyenizin arasına kaçacak kum taneleri ise sonraki üç ay boyunca canınızı sıkar. Ancak bu durum, hem dünyayı gezip hem de çalışmanın imkansız olduğu anlamına gelmiyor. Doğru stratejiyle, gerçek bir dijital göçebe olmak, hayatınızda vereceğiniz en iyi karar olabilir. Bu yazıda, remote work hayatını sürdürülebilir kılan, bütçenizi sarsmayacak ve sizi yollarda yarı yolda bırakmayacak pratik ipuçlarını kendi deneyimlerimle harmanlayarak anlatıyorum.

Ucuz, Hızlı ve İlham Verici: En İyi Coworking Şehirleri

Her seyahat severin hayalinde popüler metropoller vardır ancak bir nomad için öncelikler farklıdır. Londra veya New York harika şehirler olabilir, ama aylık yaşam maliyetleri belinizi büker. Bütçe dostu ve gerçekten üretken olabileceğiniz yerleri seçmek bu işin ilk kuralıdır. Benim bu konudaki favori sığınağım Bulgaristan’ın küçük bir dağ kasabası olan Bansko. Sofia’dan yaklaşık iki buçuk saatlik konforlu bir otobüs yolculuğuyla (bilet fiyatı sadece 10-12 Euro civarında) ulaşabileceğiniz bu kasaba, Avrupa’nın en büyük dijital göçebe komünitelerinden birine ev sahipliği yapıyor. Kışın kayak yapıp yazın dağ yürüyüşlerine çıkarken, aylık yaklaşık 130 Euro gibi komik bir fiyata harika coworking alanlarına erişebiliyorsunuz. Üstelik stüdyo daire kiraları da Avrupa standartlarına göre oldukça makul.

Bansko gibi göçebe dostu yerlerde konaklama ararken Airbnb yerine yerel Facebook gruplarını veya doğrudan coworking alanlarının kendi ağlarını kullanın. Bu yöntemle komisyonsuz ve çok daha uygun fiyatlı uzun dönemli kiralama anlaşmaları yapabilirsiniz.

Asya tarafına göz kırpmak isterseniz, herkesin gittiği Bali’nin aşırı kalabalık ve pahalılaşmış bölgeleri yerine Tayland’ın sakin adası Koh Lanta’yı öneririm. Adadaki KoHub, denize sıfır ofis ortamıyla çalışırken gerçekten tropikal bir adada olduğunuzu hissettiriyor. Koh Lanta’da aylık motor kiralama bedeli 80-100 Dolar civarında ve yerel pazarlarda öğün başı 2-3 Dolar harcayarak nefis yemekler yiyebiliyorsunuz. Önemli olan popüler akıntılara kapılmak değil, bütçenizi ve zihinsel huzurunuzu koruyabileceğiniz gizli cennetleri bulmaktır.

Vize Labirentinde Yolunu Bulmak

İşin en can sıkıcı ama kaçınılmaz kısmı bürokrasidir. Pasaportunuzun gücü ne olursa olsun, vize süreçleri her zaman bir parça baş ağrıtır. Ancak son yıllarda birçok ülke remote work yapanlar için özel vizeler sunmaya başladı. Eğer freelance çalışıyorsanız veya düzenli bir geliriniz varsa, her yıl kapılarını daha fazla açan ülkeleri hedeflemelisiniz. Örneğin, Gürcistan bu konuda tam bir cennet. Birçok ülke vatandaşına 365 gün boyunca vizesiz kalma hakkı tanıyorlar. Tiflis, harika kafeleri, hızlı interneti ve inanılmaz misafirperver yerel halkıyla bütçe dostu bir üs arayanlar için biçilmiş kaftan.

Avrupa Birliği sınırları içinde kalmak istiyorsanız, Hırvatistan ve Karadağ gibi ülkelerin sunduğu dijital göçebe vizelerini inceleyebilirsiniz. Genellikle aylık belirli bir gelir sınırını (Hırvatistan için bu sınır şu sıralar yaklaşık 2.500 Euro civarında) kanıtlamanızı istiyorlar. Eğer bu gelir seviyesine sahipseniz, bir yıl boyunca Adriyatik kıyılarında yaşama şansını yakalayabiliyorsunuz. Başvuru yaparken acele etmeyin, tüm evraklarınızı İngilizceye çevirtip apostil yaptırmayı unutmayın.

İnternet Bağlantısı Garantisi: Zoom Görüşmesinin Ortasında Kalmamak

Bir nomad için en büyük kabus, önemli bir müşteri toplantısının tam ortasında internetin kopmasıdır. “Hızlı Wi-Fi var” diyen otellere veya kafelere asla gözü kapalı güvenmeyin. Seyahat ettiğim her yerde, konaklama rezervasyonu yapmadan önce ev sahibinden mutlaka bir hız testi ekran görüntüsü talep ederim. Upload hızının en az 10 Mbps olduğundan emin olmadan ödeme yapmam.

Ayrıca her zaman bir B planınız olmalı. Gittiğiniz ülkeye varır varmaz havaalanında turist kazığı yemeden, şehir merkezindeki yerel bir bayiden fiziksel bir SIM kart veya telefonunuz destekliyorsa Airalo gibi uygulamalardan bir eSIM edinin. İnternet hızınızı ve paket kaybınızı test etmek için terminal üzerinden hızlıca şu komutu çalıştırarak bağlantı kalitenizi manuel olarak kontrol edebilirsiniz:

# Bağlantının kararlılığını ve ping süresini test etmek için terminali açın
ping -c 50 8.8.8.8

Bu komut sayesinde paket kaybı (packet loss) olup olmadığını net bir şekilde görürsünüz. Eğer kayıp oranı sıfır ise ve gecikme süresi (latency) 80ms’nin altındaysa, o kafede gönül rahatlığıyla çalışabilir ve Zoom toplantılarınıza katılabilirsiniz.

Yalnız Ama Asla Tek Başına Değil: Sosyalleşme Sanatı

Sürekli yolda olmanın en karanlık tarafı yalnızlıktır. Her birkaç ayda bir arkadaş çevrenizi sıfırdan kurmak zorunda kalırsınız. Bu yüzden, sıradan oteller veya tek başınıza kalacağınız soğuk Airbnb daireleri yerine coliving alanlarını tercih edin. Coliving, ortak mutfak ve çalışma alanlarına sahip, sizin gibi çalışan insanların bir arada yaşadığı modern yurt sistemleridir. Akşam mutfakta makarna pişirirken yanınızdaki Alman yazılımcıyla veya Kolombiyalı tasarımcıyla arkadaş olabilirsiniz.

Gittiğiniz şehirlerdeki yerel toplulukları bulmak için Meetup ve Facebook Groups platformlarını aktif kullanın. “Digital Nomads Tbilisi” veya “Bansko Nomads” gibi aramalarla haftalık buluşmalardan, masa tenisi turnuvalarından ve ortak doğa yürüyüşlerinden anında haberdar olabilirsiniz.

Unutmayın, dijital göçebelik sadece bilgisayar başında çalışıp ucuza yaşamak demek değildir. Bu yaşam tarzı, yerel kültürlerle bağ kurmak, sabah kahvesini içerken yerel esnafla selamlaşmak ve dünyanın farklı köşelerinden dostlar edinmektir. Kendinize zaman tanıyın, ilk haftalarda yaşayacağınız adaptasyon sorunlarından korkmayın ve her şeyden önemlisi, o bilgisayar ekranının ötesindeki dünyayı gerçekten keşfedin.

Category: Genel | LEAVE A COMMENT
Kasım 8 2024

Docker Multi-Stage Build ile İmaj Boyutunu %80 Küçültme

Modern devops süreçlerinde, CI/CD pipeline’larının hızını doğrudan etkileyen en kritik unsurlardan biri optimize edilmiş container imajlarıdır. Üretim ortamına (production) her deployment çıktığımızda, şişkin bir docker imajının registry’ye push edilmesi ve ardından Kubernetes node’ları tarafından pull edilmesi ciddi bir zaman ve network maliyeti yaratır. İşte tam bu noktada, multi-stage build yaklaşımı imaj boyutlarını radikal bir şekilde düşürerek deployment süreçlerimizi hızlandırır, cold-start sürelerini minimize eder ve güvenlik açıklarını (vulnerabilities) büyük oranda azaltır. Bu yazıda, sadece teoriden bahsetmeyeceğiz; hantal bir imajı alıp adım adım ameliyat masasına yatıracağız.

Acı Gerçek: 800 MB’lık Hello World İmajı Nasıl Yapılır?

Pek çok yazılım ekibinde, “çalışıyor işte kurcalama” mantığıyla yazılmış Dockerfile’lar görürüz. Özellikle Go, Rust gibi statik olarak derlenen dillerde veya Node.js gibi devasa node_modules klasörüne sahip ortamlarda bu durum tam bir faciaya dönüşebilir. Gelin, basit bir Go uygulaması üzerinden durumu simüle edelim.

Aşağıda, sadece HTTP 200 dönen basit bir Go web sunucusunun Dockerfile’ı yer alıyor:

FROM golang:1.21

WORKDIR /app

COPY . .

RUN go build -o main .

EXPOSE 8080

CMD ["./main"]

Bu Dockerfile ile build aldığınızda, karşınıza çıkacak imaj boyutu yaklaşık 850 MB civarında olacaktır. Peki neden? Çünkü Go derleyicisine, paket yöneticisine, Debian tabanlı işletim sisteminin tüm araçlarına (curl, git, apt vb.) sadece uygulamayı çalıştırmak için ihtiyacımız yok. Bunlar sadece derleme (build-time) aşamasında lazım olan araçlar. Production runtime’ında bu araçların durması hem gereksiz bir disk yükü hem de potansiyel birer güvenlik açığıdır.

Multi-Stage Build Nedir ve Neden Hayat Kurtarır?

Multi-stage build, tek bir Dockerfile içerisinde birden fazla FROM ifadesi kullanarak imaj oluşturma sürecini aşamalara (stage) bölmemizi sağlar. Derleme araçlarını ilk aşamada bırakır, sadece ortaya çıkan nihai çıktıyı (artifact) bir sonraki temiz ve hafif aşamaya kopyalarız.

Bu yöntemle, ilk aşamada (builder) tüm ağır SDK’leri kullanabilir, işimiz bittiğinde ise sadece binary dosyasını alıp minimalist bir base image üzerine koyabiliriz. Böylece, yüzlerce megabaytlık derleme araçları nihai imajın katmanlarında (layer) yer almaz.

Dockerfile’ı Ameliyat Ediyoruz

Şimdi yukarıdaki kötü senaryoyu multi-stage kullanarak optimize edelim. Dockerfile’ımızı iki aşamaya bölüyoruz: “builder” ve “runner”.

# 1. Aşama: Derleme (Build Stage)
FROM golang:1.21-alpine AS builder

WORKDIR /app

# Bağımlılıkları kopyala ve indir
COPY go.mod go.sum ./
RUN go mod download

# Kaynak kodları kopyala ve derle
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /app/main .

# 2. Aşama: Çalıştırma (Runtime Stage)
FROM alpine:3.18

WORKDIR /app

# Sadece derlenmiş binary'yi ilk aşamadan kopyalıyoruz
COPY --from=builder /app/main .

EXPOSE 8080

# Non-root user tanımlayarak güvenliği artırıyoruz
RUN adduser -D appuser
USER appuser

CMD ["./main"]

Sonuç inanılmaz: İmaj boyutu 850 MB’tan 20 MB seviyelerine düştü! Yaklaşık %97 oranında bir küçülme elde ettik. Peki burada tam olarak ne yaptık? Neden bu kadar büyük bir fark oluştu?

  • AS builder: İlk satırda bu aşamaya bir isim verdik. Böylece sonraki aşamalarda bu isme referans verebiliyoruz.
  • Alpine Base Image: Derleme aşamasında Debian yerine hafif bir Alpine imajı seçtik.
  • CGO_ENABLED=0: Go uygulamasını derlerken C kütüphanelerine statik bağımlılık olmamasını sağladık. Bu sayede binary dosyamız tamamen taşınabilir hale geldi.
  • COPY –from=builder: İşte sihirli an! İkinci aşamada, ilk aşamada ürettiğimiz /app/main dosyasını çekip aldık. Geri kalan tüm Go SDK ve intermediate katmanlar çöpe gitti.

Layer Caching: Docker’ı Akıllıca Kandırmak

Multi-stage build kullanmak harika, ancak CI/CD pipeline’larımızın saniyeler içinde tamamlanmasını istiyorsak layer caching mekanizmasını doğru kullanmalıyız. Docker, Dockerfile’daki her bir satırı (instruction) yukarıdan aşağıya doğru sırayla çalıştırır ve her satır için bir cache katmanı oluşturur.

Eğer bir satırda değişiklik tespit edilirse (örneğin kopyalanan kodlar değiştiyse), Docker o satırdan sonraki tüm adımların cache’ini geçersiz kılar (cache invalidation).

Sık yapılan bir hata şudur:

COPY . .
RUN go mod download

Bu senaryoda, projedeki herhangi bir .go dosyasında tek bir satır değiştirdiğinizde, Docker tüm bağımlılıkları (go dependencies) internetten tekrar indirmeye başlar. Çünkü COPY . . satırı cache’i bozmuştur. Bunu önlemek için bağımlılık tanımlarını (go.mod, package.json vb.) kaynak koddan önce kopyalamalıyız:

COPY go.mod go.sum ./
RUN go mod download  # Bu satır, bağımlılıklar değişmediği sürece cache'den gelir!
COPY . .

Bu basit yer değişimi, CI/CD pipeline’larınızdaki build sürelerini dakikalardan saniyelere indirecektir. “Neden her seferinde npm install bekliyoruz?” sorusunun yanıtı tam olarak buradadır.

Distroless Base Image: İçeride Kimse Var mı?

İmaj boyutunu Alpine kullanarak 20 MB’a düşürdük, ancak daha da ileriye gidebiliriz. Alpine imajları her ne kadar küçük olsa da içinde hala bir shell (sh, ash) ve bir paket yöneticisi (apk) barındırır. Eğer bir saldırgan container içerisine sızmayı başarırsa, bu araçları kullanarak container içinde exploit çalıştırabilir veya internal network’ünüzü tarayabilir.

Google tarafından geliştirilen distroless imajlar, sadece uygulamanızın çalışması için gereken minimum çalışma zamanı (runtime) bağımlılıklarını içerir. İçinde shell yoktur, paket yöneticisi yoktur, temel linux komutları (ls, cd, ping) bile yoktur.

Şimdi runtime stage kısmını distroless static imajı ile güncelleyelim:

# 2. Aşama: Distroless Runtime
FROM gcr.io/distroless/static-debian12:latest

WORKDIR /

# Binary'yi kopyala
COPY --from=builder /app/main /main

# Distroless imajlarında varsayılan olarak nonroot kullanıcısı hazırdır
USER nonroot:nonroot

EXPOSE 8080

ENTRYPOINT ["/main"]

Bu değişiklikten sonra imaj boyutumuz yaklaşık 10-12 MB bandına çekilir. En önemlisi, container’ın saldırı yüzeyini (attack surface) neredeyse sıfıra indirmiş olduk. Container içinde /bin/sh olmadığı için uzaktan kod çalıştırma (RCE) açıklarının sömürülmesi imkansıza yakın hale gelir.

Güvenlik Tarama (Security Scanning) Testi

Yaptığımız bu optimizasyonların güvenlik tarafındaki yansımasını görmek için popüler açık kaynaklı tarama aracı Trivy kullanabiliriz. İlk yazdığımız 850 MB’lık imajı tarattığımızda muhtemelen düzinelerce “High” ve “Critical” seviyeli işletim sistemi açığı ile karşılaşacaktık.

Distroless imajımızı taratmak için terminalde şu komutu çalıştırabiliriz:

trivy image my-distroless-app:latest

Çıktıda göreceğiniz üzere, işletim sistemi seviyesindeki açık sayısı (Vulnerabilities) sıfıra yakın çıkacaktır. Bu, DevSecOps süreçlerinizi otomatize ederken security gate’lerden (güvenlik bariyerleri) takılmadan geçmenizi sağlar.

Özet ve Best Practice’ler

Özetlemek gerekirse, production seviyesinde container imajları hazırlarken şu altın kuralları asla unutmamalıyız:

  • Her zaman multi-stage build kullanın. Derleme ortamı ile çalışma ortamını birbirinden kesin çizgilerle ayırın.
  • Değişme sıklığı en az olan adımları (bağımlılık yükleme) Dockerfile’ın en üstüne, sık değişenleri (application code) en altına yazarak layer caching avantajını maksimize edin.
  • Çalışma ortamında Alpine veya daha iyisi distroless imajları tercih edin.
  • Container’ları asla root kullanıcısı ile çalıştırmayın; Dockerfile içinde mutlaka yetkisiz bir kullanıcı (non-root) tanımlayın.

Bu pratikleri uygulayarak hem bulut maliyetlerinizi (network egress ve storage) düşürebilir hem de çok daha güvenli ve hızlı deployment süreçlerine sahip olabilirsiniz. Unutmayın, en iyi imaj, içinde ihtiyacınız olmayan hiçbir şey barındırmayan imajdır!

Category: Genel | LEAVE A COMMENT
Kasım 1 2024

SLO/SLI/SLA: DevOps Ekiplerine Güvenilirlik Mühendisliği Rehberi

Gecenin 3’ünde, sırf bir API gateway endpoint’i anlık olarak 500ms yavaş yanıt verdi diye PagerDuty alarmıyla uyanıp monitöre boş gözlerle baktıysanız, tebrikler: Yanlış kurgulanmış bir alarm stratejisinin kurbanı oldunuz. Modern mikroservis mimarilerinde sistemlerin kararlılığını ve reliability (güvenilirlik) seviyesini ölçmek, her saniye CPU veya memory değerlerini izleyip eşik değer aşılınca ortalığı ayağa kaldırmakla olmaz. Modern sre (Site Reliability Engineering) dünyasında, sistem sağlığını ve mühendislik eforumuzu yönetmek için kullandığımız üç kutsal kısaltmamız var: sli, slo ve sla. Bu yazıda teorik tanımların ötesine geçecek, production ortamında işinize yarayacak gerçekçi error budget hesaplamalarını ve Prometheus üzerinde burn rate tabanlı dinamik alarmları nasıl kuracağımızı öğreneceğiz.

Kavram Karmaşasını Çözelim: SLI vs. SLO vs. SLA

Bu üç kavram genellikle birbirine karıştırılır veya aynı şeymiş gibi kullanılır. İşleri basitleştirmek için aralarındaki ilişkiyi netleştirelim:

  • SLI (Service Level Indicator): “Sistemimiz şu an ne kadar iyi çalışıyor?” sorusunun cevabıdır. Doğrudan ölçülebilir, anlık teknik metriklerdir. Örneğin; “Son 5 dakikadaki başarılı HTTP isteklerinin (2xx/3xx/4xx), toplam isteklere oranı.”
  • SLO (Service Level Objective): “Sistemin ne kadar iyi çalışmasını hedefliyoruz?” sorusunun yanıtıdır. SLI metriğine bağlı bir hedef ve zaman aralığı tanımlar. Örneğin; “Gelen tüm isteklerin %99.9’u, 30 günlük kayan pencerede (rolling window) 200ms’in altında yanıt vermelidir.”
  • SLA (Service Level Agreement): “Eğer sistemi hedeflediğimiz kadar ayakta tutamazsak ne kadar ceza ödeyeceğiz?” sorusunun ticari ve hukuki cevabıdır. Mühendislerden ziyade hukuk, ürün yönetimi (Product Management) ve müşterileri ilgilendirir. Genellikle SLO değerinden daha düşük bir taahhüt içerir (örneğin SLO %99.9 ise, SLA %99.5 olabilir).

Kısacası: SLI ölçer, SLO hedefler, SLA ise patlarsa fatura keser.

Hata Bütçesi (Error Budget): Neden Kusursuzluk Aramıyoruz?

SRE felsefesinin en büyük kabullerinden biri şudur: %100 güvenilirlik (reliability) yanlış bir hedeftir. Kullanıcılarınızın internet bağlantısı bile %100 kesintisiz değilken, altyapınız için %100 uptime hedeflemek sadece bütçenizi tüketir ve ekibin hızını (velocity) sıfırlar.

Bunun yerine Error Budget kavramını kullanırız. Formül oldukça basittir:

Error Budget = 100% - SLO%

Eğer bir servis için 30 günlük SLO hedefimizi %99.9 olarak belirlediysek, bu bizim 30 günde %0.1 oranında hata yapma veya kesinti yaşama hakkımız olduğu anlamına gelir. 30 günün saniye karşılığı 2.592.000 saniyedir. Bu durumda hata bütçemiz tam olarak 2.592 saniye, yani yaklaşık 43.2 dakikadır.

Bu bütçe sadece sistemin çökmesiyle tükenmez; yeni deployment’lar sırasındaki yavaşlıklar, geçici veritabanı bağlantı kayıpları da bu bütçeden yer. Eğer ay sonuna doğru hata bütçeniz duruyorsa, production ortamına korkusuzca riskli güncellemeler (feature deployment) gönderebilirsiniz. Bütçe tükendiyse, yeni deployment’ları durdurur ve sadece reliability/bug-fix işlerine odaklanırsınız.

Prometheus ile SLI Tanımlama (Pratik Uygulama)

İşleri biraz daha pratikleştirelim. Elimizde Prometheus tarafından scrape edilen bir HTTP servisinin metrikleri olduğunu varsayalım. En popüler SLI türlerinden biri olan Availability (Kullanılabilirlik) metriğini hesaplayalım.

Burada kritik bir SRE detayı var: Genellikle 5xx durum kodlarını sunucu hatası (unavailability) kabul ederken, 4xx durum kodlarını (örneğin 404 veya 401) kullanıcı hatası olarak kabul eder ve sistemin sağlığını bozmadığı için SLI hesabına dahil etmeyiz (tabii 4xx’ler aniden tavan yapmadıysa).

Yazacağımız PromQL sorgusu şu şekildedir:

sum(rate(http_requests_total{job="my-service", status!~"5.."}[5m]))
/
sum(rate(http_requests_total{job="my-service"}[5m]))

Bu sorgu, son 5 dakika içinde 5xx dışındaki isteklerin, toplam isteklere oranını verir. Çıktı 0 ile 1 arasında olacaktır (örneğin 0.9995, yani %99.95 başarı).

Burn Rate Alerts: CPU Alarmlarını Çöpe Atın

Geleneksel alert mekanizmaları genellikle “Anlık CPU %90’ı geçerse Slack’e mesaj at” veya “Hata oranı %1’i geçerse nöbetçiyi ara” şeklindedir. Bu yaklaşımlar iki büyük sorun yaratır:

  1. Gürültü (Alert Fatigue): Anlık bir trafik spike’ı nedeniyle hata oranı 1 dakikalığına %2’ye çıkabilir ve kendi kendine düzelir. Boşu boşuna uykunuz bölünür.
  2. Yavaş Ölümler (Slow Burns): Hata oranınız saatlerce %0.2’de kalabilir. Bu oran geleneksel alarmları tetiklemez ancak 30 günlük %99.9’luk SLO bütçenizi sinsice sıfırlar.

SRE pratikleri bu sorunu Burn Rate (Tüketim Hızı) alarmları ile çözer. Burn rate, hata bütçenizi ne kadar hızlı tükettiğinizi gösteren katsayıdır.

  • Burn Rate = 1: Bütçeniz tam olarak planlanan sürede (örneğin 30 günde) tükenecek demektir.
  • Burn Rate = 14.4: Bütçenizin %2’sini sadece 1 saat içinde tüketeceğiniz anlamına gelir. Acil müdahale (Pager) gerekir!
  • Burn Rate = 2.9: Bütçenizin %5’ini 6 saatte tüketeceksiniz demektir. Bu durumda bilet (Jira ticket) açılması yeterlidir.

Prometheus Kural Dosyası (Prometheus Alert Rules)

Aşağıdaki konfigürasyon bloğu, Google SRE Workbook standartlarına uygun olarak yazılmış, çoklu zaman pencereli (multi-window, multi-burn-rate) bir alert kuralı örneğidir. Hem anlık gürültüleri engeller hem de yavaş bütçe tüketimlerini kaçırmaz.

groups:
  - name: service-slo-alerts
    rules:
      # Critical Alert: 1 saatlik burn rate > 14.4 VE son 5 dakikada da bu eğilim devam ediyorsa
      - alert: ServiceAvailabilityCriticalBurnRate
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[1h]))
            /
            sum(rate(http_requests_total}[1h]))
          ) > (1 - 0.999) * 14.4
          and
          (
            sum(rate(http_requests_total{status=~"5.."}[5m]))
            /
            sum(rate(http_requests_total}[5m]))
          ) > (1 - 0.999) * 14.4
        for: 2m
        labels:
          severity: critical
          tier: platform
        annotations:
          summary: "Hızlı Hata Bütçesi Tüketimi (Burn Rate 14.4x)"
          description: "Hata bütçesi son 1 saatte çok hızlı tükeniyor! Current error rate: {{ $value | humanizePercentage }}"

      # Warning Alert: 6 saatlik burn rate > 6 (Bütçenin %5'i 6 saatte bitiyor)
      - alert: ServiceAvailabilityWarningBurnRate
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[6h]))
            /
            sum(rate(http_requests_total}[6h]))
          ) > (1 - 0.999) * 6
          and
          (
            sum(rate(http_requests_total{status=~"5.."}[30m]))
            /
            sum(rate(http_requests_total}[30m]))
          ) > (1 - 0.999) * 6
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "Yavaş Hata Bütçesi Tüketimi (Burn Rate 6x)"
          description: "Son 6 saattir hata bütçesi eriyor. Nöbetçi ekibin mesai saatlerinde incelemesi önerilir."

Yukarıdaki kurallarda dikkat ederseniz (1 - 0.999) * 14.4 gibi dinamik bir matematiksel formül kullandık. Buradaki 0.999 bizim SLO hedefimiz olan %99.9’dur. Eğer yarın bir gün bu hedefi %99.5’e çekmek isterseniz sadece bu katsayıları değiştirmeniz yeterli olacaktır.

Bu Yapıyı Kurduktan Sonra Sizi Ne Bekliyor?

Bu sisteme geçiş yapmak sadece teknik bir araç değişimi değil, ciddi bir kültürel dönüşümdür. Burn rate alarmlarına geçtiğinizde:

  • Gecenin yarısında uyanma oranınız (false-positive paging) dramatik şekilde düşecek.
  • Ekipler “Bu hafta çok downtime oldu, deployment yapmayalım” kararını hissiyatla değil, doğrudan Grafana paneline bakarak somut verilerle (Error Budget Dashboard) verecek.
  • Yazılım geliştiriciler ve sistem yöneticileri aynı dile konuşmaya başlayacak: “Hata bütçemiz ne kadar kaldı?”

Kendi sistemlerinizde bu dönüşümü başlatmak için ilk adım olarak en kritik 3 servisinizi belirleyin, basit birer kullanılabilirlik SLI’ı yazın ve error budget grafiğinizi oluşturun. Unutmayın, mükemmel bir sistem kesintisiz çalışan sistem değil, ne zaman ve ne kadar hata yapabileceğini bilen ve bunu kontrol altında tutan sistemdir.

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
    Ekim 11 2024

    Linux Namespace ve Cgroup: Container’ların Altındaki Mekanizma

    Eğer bir teknik mülakatta ya da kahve molasında “Container nedir?” sorusuna “Hizmetleri izole eden hafifletilmiş sanal makinelerdir (VM)” yanıtını verdiyseniz, bugün bu ezberi bozuyoruz. Çünkü aslında işletim sistemi seviyesinde “container” diye bir nesne veya teknoloji bulunmuyor. Bizim docker, containerd ya da podman adını verdiğimiz araçların yaptığı her şey, linux çekirdeğinin (kernel) sunduğu iki temel özelliğin etrafına örülmüş şık birer kullanıcı arayüzü (wrapper) olmaktan ibaret: namespace ve cgroup.

    Bu makalede, işin kolayına kaçıp hazır CLI araçları kullanmak yerine, modern bir container runtime’ın (örneğin containerd) yaptığı işi tamamen elle (manual) yapacağız. Sıfırdan izole bir root filesystem (rootfs) hazırlayacak, kendi ağ geçidimizi kuracak, prosesleri izole edecek ve cgroup v2 ile kaynak sınırları koyacağız. Arkanıza yaslanın, terminalinizi açın ve container dünyasının arka bahçesine hoş geldiniz.

    1. Hazırlık: Minimal Bir Root Filesystem (rootfs) Oluşturmak

    Bir container’ın kendi dünyasında yaşayabilmesi için öncelikle bağımsız bir dosya sistemine ihtiyacı vardır. Docker imajları aslında katmanlaştırılmış (layered) tar dosyalarından başka bir şey değildir. Biz de işe minimal bir Alpine Linux rootfs indirerek başlayacağız.

    # Çalışma dizinimizi oluşturalım
    mkdir -p /tmp/kerten-container/rootfs
    cd /tmp/kerten-container
    
    # Alpine rootfs indiriyoruz
    curl -sSL https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/alpine-minirootfs-3.18.4-x86_64.tar.gz -o alpine.tar.gz
    tar -xzf alpine.tar.gz -C rootfs/
    rm alpine.tar.gz

    Artık elimizde izole bir işletim sisteminin sahip olması gereken tüm temel dizin yapısı (bin, sbin, etc, lib, proc…) mevcut. Sıradaki adım, bu dizini yeni dünyamızın kök dizini (root) haline getirmek.

    2. Chroot Değil, Pivot Root: Güvenli Mount Namespace

    Dosya sistemi izolasyonu denince akla gelen ilk syscall (sistem çağrısı) genellikle chroot olur. Ancak chroot esnektir ve root yetkilerine sahip bir proses kolayca bu hapishaneden kaçabilir (jailbreak). Modern container dünyası bunun yerine çok daha güvenli olan pivot_root sistem çağrısını kullanır.

    pivot_root, mevcut mount namespace’in root mount noktasını yeni bir dizine taşır ve eski root’u başka bir dizine bind eder. Bunu elle simüle etmek için öncelikle yeni bir mount namespace oluşturmamız gerekir.

    İşte sihirli komutumuz: unshare. Bu komut, belirtilen namespace türlerini sıfırdan oluşturarak yeni bir proses başlatır.

    # Mount, UTS (hostname) ve IPC namespace'lerini izole ederek yeni bir bash oturumu açıyoruz
    unshare --mount --uts --ipc --fork /bin/bash

    Şu andan itibaren açılan bu yeni shell oturumunda yaptığımız mount işlemleri host sistemimizi etkilemeyecek. Şimdi rootfs dizinimizi bir mount noktasına dönüştürelim ve pivot_root için hazırlayalım:

    # rootfs dizinimizi bir bind mount olarak işaretliyoruz (pivot_root bunu şart koşar)
    mount --bind /tmp/kerten-container/rootfs /tmp/kerten-container/rootfs
    
    # Eski root dizinini koyacağımız geçici bir klasör oluşturuyoruz
    mkdir -p /tmp/kerten-container/rootfs/put_old
    
    # pivot_root komutunu çalıştırıyoruz: [yeni_root] [eski_root_un_duracagi_yer]
    cd /tmp/kerten-container/rootfs
    pivot_root . put_old
    
    # Artık yeni root içerisindeyiz. Eski sisteme ait mount noktalarını temizleyelim
    cd /
    umount -l /put_old
    rmdir /put_old
    
    # Hostname'i değiştirelim (UTS namespace sayesinde host etkilenmez)
    hostname kerten-container

    Harika! Şu an sadece kendi hazırladığımız rootfs içerisindeki ikili dosyaları (binaries) görebilen, izole edilmiş bir dosya sistemindeyiz. Ancak eksik bir şeyler var: Prosesler nerede?

    3. PID Namespace: “Ben Kimim?” Sorusu

    Eğer az önce oluşturduğumuz ortamda ps aux çalıştırmayı denerseniz, hata alırsınız. Çünkü proseslerin yönetildiği ve kernel bilgilerinin okunduğu sanal dosya sistemi olan /proc henüz mount edilmedi. Daha da önemlisi, hala host sistemin PID (Process ID) uzayını paylaşıyoruz.

    Gelin, yeni bir PID namespace oluşturalım. Bunun için yeni bir terminal penceresi açıp host üzerinde çalışmaya devam edeceğiz. Bu sayede hem host hem de container arasındaki ilişkiyi daha net görebiliriz.

    Bu sefer PID namespace’i de işin içine katarak unshare ile yeni bir container başlatalım (dosya sistemi adımlarını bu yeni namespace içinde hızlıca tekrarladığımızı varsayalım veya doğrudan aşağıdaki komutla temiz bir başlangıç yapalım):

    # PID ve Mount namespace izole edilmiş şekilde başlatıyoruz
    unshare --mount --pid --fork --mount-proc /bin/bash

    --mount-proc parametresi, bizim için otomatik olarak yepyeni ve izole bir /proc dosya sistemi mount eder. Şimdi bu oturumda prosesleri listeleyelim:

    ps aux

    Çıktıya dikkat edin:

    PID   USER     TIME  COMMAND
        1 root      0:00 /bin/bash
        2 root      0:00 ps aux

    İşte container dünyasının kutsal kasesi! Host üzerinde binlerce çalışan proses varken, bizim container’ımız kendisini dünyadaki tek proses (PID 1) olarak görüyor. PID 1 olmak büyük bir sorumluluktur; eğer bu proses ölürse, kernel tüm namespace’i sonlandırır.

    4. Network Namespace: Kablolama İşlemleri

    Şu ana kadar dosya sistemini ve prosesleri izole ettik ancak container’ımızın dış dünya ile bağlantısı yok (loopback arayüzü bile kapalı). Docker arkada bu işi sanal bir switch (docker0 bridge) ve veth pair (sanal ethernet kablosu) kullanarak çözer.

    Gelin bu kablolamayı host üzerinde elle yapalım. Bu senaryo için host üzerinde root yetkileriyle yeni bir terminal açın.

    Öncelikle container’ımızın namespace’ini kalıcı hale getirmemiz gerekir ki host üzerinden oraya erişebilelim. Linux’ta her namespace /proc/[PID]/ns/ altında bir dosya olarak temsil edilir.

    # Container'ımızın PID'sini host üzerinde bulalım (örneğin 12345 olsun)
    # Host üzerinde:
    ip netns attach kerten-netns 12345

    Şimdi sanal ethernet çiftimizi oluşturalım. Bu işlem, bir ucu hostta, diğer ucu container içinde olan sanal bir kablo yaratacaktır:

    # veth çiftini oluştur
    ip link add veth-host type veth peer name veth-container
    
    # Kablonun container ucunu container'ın network namespace'ine taşıyalım
    ip link set veth-container netns kerten-netns
    
    # Host tarafındaki uca IP verelim ve ayağa kaldıralım
    ip addr add 10.200.0.1/24 dev veth-host
    ip link set veth-host up
    
    # Container tarafındaki uca IP verelim ve ayağa kaldıralım
    ip netns exec kerten-netns ip addr add 10.200.0.2/24 dev veth-container
    ip netns exec kerten-netns ip link set veth-container up
    ip netns exec kerten-netns ip link set lo up # Loopback'i unutmayalım
    
    # Container için default gateway tanımlayalım
    ip netns exec kerten-netns ip route add default via 10.200.0.1

    Artık container içinden host tarafındaki 10.200.0.1 IP adresine ping atabilirsiniz. Container runtime’ların her container için saniyeler içinde yaptığı o karmaşık ağ konfigürasyonunun temel mekanizması tam olarak budur.

    5. Cgroup v2 Sihri: Kaynakları Sınırlandırmak

    Güzel, izole bir ortamımız ve ağımız var. Peki ya bu container içerisindeki bir proses çıldırır ve host sistemin tüm CPU ve RAM kaynaklarını tüketmeye çalışırsa? İşte burada devreye cgroup (Control Groups) giriyor.

    Modern Linux dağıtımları artık varsayılan olarak cgroup v2 kullanıyor. Cgroup v2, v1’deki dağınık yapıyı tek bir hiyerarşik ağaç altında birleştirerek işleri inanılmaz derecede kolaylaştırdı. Cgroup v2 hiyerarşisi varsayılan olarak /sys/fs/cgroup dizininde yaşar.

    Şimdi elle “kerten-limit” adında bir kontrol grubu oluşturalım ve container’ımızın bellek kullanımını 100 MB ile sınırlayalım:

    # Host üzerinde cgroup dizinine gidelim
    cd /sys/fs/cgroup
    
    # Yeni bir grup oluşturmak sadece bir dizin oluşturmaktan ibarettir!
    mkdir kerten-limit
    cd kerten-limit
    
    # Kernel, bu dizini oluşturduğumuz an içine kontrol dosyalarını otomatik olarak yerleştirir.
    ls -la

    Şimdi bellek sınırımızı (memory limit) 100MB (104857600 bytes) olarak ayarlayalım:

    echo "104857600" > memory.max

    Peki bu sınırı container prosesimize nasıl uygulayacağız? Çok basit: Container prosesimizin host üzerindeki PID’sini (örneğin 12345) cgroup altındaki cgroup.procs dosyasına yazmamız yeterli:

    echo "12345" > cgroup.procs

    Artık bu proses veya bu prosesten türeyecek (fork) olan tüm alt süreçler toplamda 100 MB bellek sınırını aşamazlar. Sınırı aşmaya çalıştıkları anda kernel’ın meşhur OOM-Killer (Out of Memory Killer) mekanizması devreye girecek ve o prosesi acımasızca sonlandıracaktır. Tıpkı Kubernetes ortamında aldığınız o meşhur OOMKilled hatası gibi!

    Özet: Container Aslında Bir İllüzyondur

    Gördüğünüz gibi, arka planda çalışan gizemli hipervizörler, sanal donanımlar veya ağır sanallaştırma katmanları yok. Yaptığımız her şey, Linux kernel’ına “Bu prosese sadece şu dizini göster (mount namespace), sadece şu prosesleri görmesine izin ver (PID namespace), ağ trafiğini şu sanal kabloya yönlendir (net namespace) ve şu kadar kaynak tüketmesine izin ver (cgroups)” demekten ibaretti.

    Docker ve containerd gibi araçlar, bu karmaşık syscall ve CLI yönetimini otomatize ederek bize pratik birer imaj paketleme ve dağıtım standardı sunar. Bu temel mekanizmayı kavramak, Kubernetes ortamlarında veya büyük ölçekli altyapılarda karşılaştığınız ağ, performans ve izolasyon sorunlarını (troubleshooting) çok daha hızlı ve profesyonelce çözmenizi sağlayacaktır.

    Bir sonraki derin dalış makalemizde görüşmek üzere, sistemleriniz ayakta, container’larınız hafif kalsın!

    Category: Genel | LEAVE A COMMENT
    Ekim 4 2024

    Cron İşlerini Bash ile Monitör Etme ve Alerting

    Modern altyapılarda Kubernetes cronjob’ları, serverless scheduler’lar havada uçuşsa da, günün sonunda hepimizin bir yerlerde tıkır tıkır çalışan (ya da çalıştığını umduğu) emektar bir linux sunucusu ve içinde barınan klasik cron işleri vardır. Ancak cron’un en büyük problemi “sessiz ölüm” (silent failure) dediğimiz durumdur. Bir backup script’i patladığında, disk dolduğunda veya API key’in süresi geçtiğinde cron bunu size söylemez. Bu yazıda, kendi yazacağımız esnek bir bash wrapper script ile tüm cron işlerinizi merkezi olarak monitoring dünyasına entegre edecek, hata durumlarında anında alert üretecek ve üçüncü parti servislerle nasıl haberleştireceğimizi göreceğiz.

    Klasik Cron Yaklaşımlarının Sorunu Nedir?

    Hepimiz o yollardan geçtik. Bir cron işinin çıktısını almak için satırın sonuna efsanevi >> /var/log/myjob.log 2>&1 yönlendirmesini yazarız. Sonra ne mi olur? O log dosyası gigabaytlarca büyür, diski doldurur ve bir gün script hata verdiğinde kimsenin haberi olmaz. Çünkü logları aktif olarak okuyan kimse yoktur.

    Eski usul Linux yöneticileri size “MTA (Mail Transfer Agent) kurun, cron hata aldığında lokal mail atsın” diyecektir. Yıl 2024+. ephemeral VM’lerin, cloud instance’larının cirit attığı bu çağda localhost mail kuyruklarıyla uğraşmak, postfix yapılandırmak ve spame düşen lokal mailleri takip etmek tam bir operasyonel işkencedir. Bizim daha proaktif, modern ve DevOps pratiklerine uygun bir çözüme ihtiyacımız var.

    Wrapper Script Pattern Neden En İyi Çözümdür?

    Elinizde 20 farklı cron script’i olduğunu düşünün. Her birinin içine tek tek Slack webhook entegrasyonu, hata yakalama (error handling) ve çalışma süresi ölçümü eklemek tam bir anti-pattern’dir. Kod tekrarı yaratır ve yarın öbür gün Slack yerine Teams’e geçildiğinde 20 farklı script’i update etmeniz gerekir.

    Wrapper Script Pattern, çalıştırılacak asıl komutu sarmalayan, onun exit code’unu yakalayan, standart output (stdout) ve standart error (stderr) akışlarını yöneten merkezi bir aracıdır. Crontab içerisindeki tanımımız şuna benzer:

    0 4 * * * /usr/local/bin/cron-wrapper.sh --job "db-backup" --cmd "/opt/scripts/backup.sh --force"

    Bu yaklaşım sayesinde, asıl işi yapan script’in monitoring mantığından tamamen bağımsız (decoupled) kalmasını sağlarız. Script sadece işini yapar ve exit code döner; wrapper ise raporlamayı üstlenir.

    Adım 1: Savaşçı Ruhlu Bash Wrapper Script

    Lafı uzatmadan production ortamında güvenle kullanabileceğiniz, hata durumunda exit code’u ve logları yakalayan, zaman aşımı (timeout) kontrolü yapabilen wrapper script’imizi yazalım. Bu script’i /usr/local/bin/cron-wrapper.sh olarak kaydedebilirsiniz.

    #!/usr/bin/env bash
    
    # Robust Bash Ayarları
    set -o nounset
    set -o pipefail
    
    # Değişken Tanımları
    JOB_NAME=""
    COMMAND=""
    SLACK_WEBHOOK_URL="${SLACK_WEBHOOK_URL:-""}"
    HEALTHCHECK_URL="${HEALTHCHECK_URL:-""}"
    
    # Kullanım Yardımcısı
    usage() {
        echo "Kullanım: $0 --job [JOB_NAME] --cmd [COMMAND]"
        exit 1
    }
    
    # Parametreleri Parse Etme
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --job)
                JOB_NAME="$2"
                shift 2
                ;;
            --cmd)
                COMMAND="$2"
                shift 2
                ;;
            *)
                usage
                ;;
        esac
    done
    
    if [[ -z "$JOB_NAME" || -z "$COMMAND" ]]; then
        usage
    fi
    
    # Geçici Log Dosyası Oluşturma (Güvenli yöntem)
    LOG_FILE=$(mktemp /tmp/cron_wrapper_${JOB_NAME}.XXXXXX)
    
    # Her durumda temizlik yapılması için trap kullanımı
    cleanup() {
        rm -f "$LOG_FILE"
    }
    trap cleanup EXIT
    
    # Zamanı Ölçmeye Başla
    START_TIME=$(date +%s)
    
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Job '$JOB_NAME' başlatılıyor..." >> "$LOG_FILE"
    echo "Komut: $COMMAND" >> "$LOG_FILE"
    echo "--------------------------------------------------" >> "$LOG_FILE"
    
    # Komutu çalıştır ve tüm çıktıyı log dosyasına yönlendir
    # set +e kullanıyoruz çünkü komut hata verirse wrapper script'in hemen çökmesini istemiyoruz,
    # hatayı yakalayıp alert üreteceğiz.
    set +e
    eval "$COMMAND" >> "$LOG_FILE" 2>&1
    EXIT_CODE=$?
    set -e
    
    # Zamanı Hesapla
    END_TIME=$(date +%s)
    DURATION=$((END_TIME - START_TIME))
    
    echo "--------------------------------------------------" >> "$LOG_FILE"
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Job bitti. Süre: ${DURATION}sn, Exit Code: $EXIT_CODE" >> "$LOG_FILE"
    
    # Sonuçları Değerlendir
    if [ $EXIT_CODE -eq 0 ]; then
        echo "Başarılı: $JOB_NAME sorunsuz tamamlandı."
        # Eğer tanımlıysa Healthcheck ping'i gönder (Mavi Tik)
        if [ -n "$HEALTHCHECK_URL" ]; then
            curl -fsS --retry 3 "$HEALTHCHECK_URL" > /dev/null 2>&1 || true
        fi
    else
        echo "HATA: $JOB_NAME başarısız oldu! Exit code: $EXIT_CODE" >&2
        
        # Slack Alert Tetikleme fonksiyonunu çağır
        if [ -n "$SLACK_WEBHOOK_URL" ]; then
            # Log dosyasının son 20 satırını alalım ki Slack mesajı devasa olmasın
            LAST_LOGS=$(tail -n 20 "$LOG_FILE" | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\\n/g')
            
            PAYLOAD=$(cat <<EOF
    {
      "text": "🚨 *Cron Job Hatası!*",
      "attachments": [
        {
          "color": "danger",
          "fields": [
            { "title": "Job Adı", "value": "${JOB_NAME}", "short": true },
            { "title": "Exit Code", "value": "${EXIT_CODE}", "short": true },
            { "title": "Süre", "value": "${DURATION} saniye", "short": true },
            { "title": "Sunucu", "value": "$(hostname)", "short": true }
          ],
          "text": "*Son Loglar:*\n\`\`\`${LAST_LOGS}\`\`\`"
        }
      ]
    }
    EOF
    )
            curl -X POST -H 'Content-type: application/json' --data "$PAYLOAD" "$SLACK_WEBHOOK_URL" > /dev/null 2>&1 || true
        fi
        
        # Wrapper kendisi de hata koduyla çıksın ki pipeline'lar veya üst sistemler anlasın
        exit $EXIT_CODE
    fi
    

    Neden “set +e” ve “set -o pipefail” Kullandık?

    Bash’te hata yönetimi hassas bir konudur. set -o pipefail komutu, bir pipe zincirindeki herhangi bir komut hata verirse tüm satırın hata kodu dönmesini sağlar. Wrapper script’in başında bunu aktif ettik. Ancak, sarmaladığımız asıl komut hata verdiğinde script’imizin patlayıp Slack alert’ini gönderemeden sonlanmasını engellemek için, çalıştırma aşamasında geçici olarak set +e ile esneklik sağladık, exit code’u güvenli bir şekilde $? ile lokal değişkene aldık ve ardından set -e ile güvenli modumuza geri döndük.

    Ayrıca mktemp kullanımı, paralel çalışan cron işlerinin aynı log dosyası üzerine yazarak yarış durumuna (race condition) girmesini engeller.

    Adım 2: “Dead Man’s Snitch” Mantığı ile Healthcheck Entegrasyonu

    Peki ya sunucunun kendisi çökerse? Ya da cron daemon durursa? Bu durumlarda “hata aldığımda Slack’e yaz” mantığı tamamen devre dışı kalır. Çünkü çalışmayan bir sistem hata mesajı da gönderemez. Buna monitoring dünyasında silent failure denir.

    Çözüm: Inverted Monitoring (Ters İzleme) ya da bilinen adıyla Heartbeat / Dead Man’s Snitch pattern. Sistem çalışmayı durdurduğunda alarm çalmasını istiyorsak, sistemin düzenli aralıklarla “Ben hayattayım!” sinyali göndermesi gerekir. Eğer sinyal gelmezse, monitoring sunucumuz bizi uyarır.

    Bu iş için healthchecks.io veya kendi host ettiğiniz open-source alternatiflerini (örneğin Uptime Kuma) kullanabilirsiniz. Script’imizdeki HEALTHCHECK_URL tam olarak bunu yapar. Cron başarılı biterse ilgili adrese bir curl isteği atar. Eğer o istek zamanında gitmezse, dış dünya sunucunun patladığını anlar.

    Adım 3: Slack Webhook ile Gerçek Zamanlı Alerting

    Yukarıdaki script’imizde Slack için JSON payload’unu dinamik olarak oluşturduk. Bash içinde JSON oluştururken kaçış karakterleri (escaping) her zaman baş ağrıtır. Bu yüzden log dosyasından okuduğumuz son 20 satırı (LAST_LOGS) temizlemek için sed ile yeni satır karakterlerini (\n) ve çift tırnakları escape ettik.

    Slack’te görünecek mesaj, hata anında debug yapmanızı inanılmaz kolaylaştıracaktır. Hangi sunucuda, ne kadar sürede patladığını ve hatanın son satırlarını terminale girmeden telefonunuzdan görebilirsiniz:

    Slack Notification Alerting

    Adım 4: Crontab Yapılandırması

    Şimdi yazdığımız bu sistemi gerçek bir senaryoda test edelim. Sunucumuzdaki env variable’ları crontab ortamına geçirmek bazen can sıkıcı olabilir. En temiz yöntem webhook url gibi hassas verileri wrapper script içine hardcode etmek yerine wrapper’ı çağırırken bir konfigürasyondan beslemek veya crontab’ın üst kısmında tanımlamaktır.

    # /etc/cron.d/my-monitored-crons veya crontab -e
    
    SLACK_WEBHOOK_URL="https://hooks.slack.com/services/T00/B00/X00"
    HEALTHCHECK_URL="https://hc-ping.com/your-uuid-here"
    
    # Her gece 03:00'te yedek al, hata olursa Slack'e bildir, başarılı olursa ping at
    0 3 * * * root /usr/local/bin/cron-wrapper.sh --job "database-backup" --cmd "/opt/backups/pg_dump.sh"
    

    Dikkat Edilmesi Gereken İpuçları

    • Mutlak Yol (Absolute Paths) Kullanın: Cron ortamında $PATH değişkeni kısıtlıdır. Hem wrapper script’in içinde çağırdığınız tool’ların (curl, sed vb.) hem de crontab içindeki komutların tam yollarını yazmaya özen gösterin.
    • Yetkilendirme: Wrapper script’in çalıştırılabilir olduğundan emin olun: chmod +x /usr/local/bin/cron-wrapper.sh
    • Log Rotasyonu: Biz script içinde trap kullanarak geçici logları işlem sonunda siliyoruz. Ancak işlem yarıda kesilirse (örneğin kill -9) loglar kalabilir. Periyodik olarak /tmp dizininin temizlendiğinden emin olun (çoğu modern Linux dağıtımı bunu otomatik yapar).

    Özet

    Cron işlerini kendi haline bırakmak, prod ortamında saatli bir bomba üzerinde oturmak gibidir. Bu makalede yazdığımız esnek bash wrapper script sayesinde, mevcut hiçbir altyapınızı bozmadan, tek bir merkezi mekanizma ile tüm linux cron işlerinizi modern birer microservice gibi monitoring ve alert süreçlerine dahil ettik. Unutmayın, en iyi sistem tıkır tıkır çalışan değil, bozulduğunda neresinin bozulduğunu size ilk söyleyen sistemdir.

    Category: Genel | LEAVE A COMMENT
    Eylül 27 2024

    Linux Sistem Performans Analizi: perf, strace ve eBPF Araçları

    Bir sabah Slack’ten gelen o korkunç alarm sesiyle uyandınız: “Production veritabanı sunucusunda latency uçtu, CPU %99!” Hemen sunucuya SSH ile bağlandınız, top komutunu çalıştırdınız ve evet, CPU gerçekten can çekişiyor. Ancak top veya htop gibi geleneksel araçlar size sadece “yangın olduğunu” söyler; yangının hangi odada, hangi kibritle başladığını göstermez. İşte bu noktada modern bir SRE (Site Reliability Engineer) gibi düşünmeli ve linux çekirdeğinin (kernel) derinliklerine inmeliyiz. Bu yazıda, modern altyapılarda performance sorunlarını iğne deliğinden geçirir gibi analiz etmenizi sağlayacak üç silahşörü inceleyeceğiz: perf, strace ve devrim niteliğindeki ebpf tabanlı bpftrace.

    1. Donanımın Nabzını Tutmak: perf (CPU & Hardware Profiling)

    perf, Linux çekirdeği ile doğrudan entegre çalışan inanılmaz güçlü bir profil çıkarma (profiling) aracıdır. CPU döngülerini (cycles), cache miss (önbellek kaçırma) oranlarını ve CPU talimatlarını (instructions) donanımsal sayaçları (PMU – Performance Monitoring Unit) kullanarak analiz eder.

    Neden Sadece CPU Yüzdesine Bakmak Yanıltıcıdır?

    Çoğu mühendis CPU %100 olduğunda uygulamanın çok yoğun hesaplama yaptığını düşünür. Ancak durum her zaman bu değildir. CPU, RAM’den veri beklerken de (stall cycles) %100 meşgul görünebilir. Biz buna Memory-Bound (bellek sınırlandırılmış) deriz. Eğer CPU gerçekten matematiksel işlemler yapıyorsa buna da Compute-Bound denir.

    Bu ayrımı görmek için ilk yapmamız gereken şey perf stat komutunu çalıştırmaktır:

    # Belirli bir PID'yi 5 saniye boyunca analiz edelim
    perf stat -p 1234 sleep 5

    Karşımıza şöyle bir çıktı gelecektir:

     Performance counter stats for process id '1234':
    
           2001.45 msec task-clock                #    0.400 CPUs utilized          
                  1234  context-switches          #    0.617 K/sec                  
                    45  cpu-migrations            #    0.022 K/sec                  
                   105  page-faults               #    0.052 K/sec                  
            4002938102  cycles                    #    2.000 GHz                    
            2001467291  instructions              #    0.50  insn per cycle         
             120495839  branches                  #   60.204 M/sec                  
               4958102  branch-misses             #    4.11% of all branches        
    
           5.002345123 seconds time elapsed

    Burada odaklanmamız gereken en kritik metrik insn per cycle (IPC) değeridir. IPC (Instructions Per Cycle), CPU’nun her döngüde kaç talimat çalıştırdığını gösterir.

    • IPC < 1.0 ise: Uygulamanız muhtemelen I/O veya memory erişimi bekliyor (Memory-Bound). CPU boşta (stall) bekliyor demektir.
    • IPC > 1.5 ise: CPU gerçekten yoğun şekilde kodunuzu çalıştırıyor demektir (Compute-Bound).

    Sıcak Noktaları Bulmak: perf record ve perf report

    Uygulamanın tam olarak hangi fonksiyonunun CPU’yu sömürdüğünü bulmak için örnekleme (sampling) yapmamız gerekir. Bu işlem production ortamlarında genellikle %1 ila %5 arasında çok düşük bir overhead (ek yük) ile yapılabilir.

    # Saniyede 99 frekansla (overkill olmaması için idealdir) 10 saniye boyunca kayıt alalım
    # -g parametresi call-graph (çağrı ağacı) kaydetmesini sağlar
    perf record -F 99 -p 1234 -g -- sleep 10

    Bu komut geçerli dizinde perf.data adında bir dosya oluşturur. Bu dosyayı analiz etmek için terminalden şu komutu veririz:

    perf report -n --stdio

    Karşınıza çıkan interaktif arayüzde hangi fonksiyonun (sembolün) CPU döngülerinin yüzde kaçını harcadığını hiyerarşik bir şekilde görebilirsiniz. Eğer fonksiyon isimleri yerine [hexadecimal] adresler görüyorsanız, uygulamanızın “debug symbols” (hata ayıklama sembolleri) eksiktir demektir. Go kullanıyorsanız binary’nizi strip etmeyin, C++/Rust kullanıyorsanız -g flag’i ile derlediğinizden emin olun.

    2. Karanlıkta Kalan Sistem Çağrıları: strace

    Uygulamanız çalışıyor ama hiçbir şey yapmıyor gibi mi görünüyor? Dosya okumaya çalışırken kilitlenmiş olabilir mi? Yoksa DNS çözümlemesi yaparken timeout mu yaşıyor? Bu gibi durumlarda, uygulamanın kernel ile olan iletişimini yani System Calls (syscalls) trafiğini izlememiz gerekir.

    İşte strace bu işin mutfağıdır.

    Production Uyarısı: strace’i Dikkatli Kullanın!

    Geliştirme ortamında strace my_app yazıp geçmek harikadır ancak bunu canlı production ortamında sakın yapmayın! strace, izlediği process’in her syscall yaptığında durdurulmasını (ptrace ile) sağlar. Bu durum, uygulamanın performansını %100 ila %1000 oranında düşürebilir.

    Bunun yerine, production dostu parametrelerle nokta atışı yapmalıyız:

    # Uygulamanın en çok hangi sistem çağrısında ne kadar zaman harcadığını özetleyelim
    # Bu işlem ham log akıtmaya göre çok daha az overhead yaratır
    strace -c -p 1234

    Çıktı bize muhteşem bir özet sunacaktır:

    % time     seconds  usecs/call     calls    errors syscall
    ------ ----------- ----------- --------- --------- ----------------
     89.45    1.204958         120     10041           read
     10.12    0.136120          13     10200      1050 openat
      0.43    0.005789          11       500           write
    ------ ----------- ----------- --------- --------- ----------------
    100.00    1.346867                 20741      1050 total

    Yukarıdaki tabloda net bir şekilde görüyoruz ki, uygulama zamanının %89’unu read sistem çağrısında harcıyor ve ciddi miktarda openat (dosya açma hatası) alıyor. Hangi dosyayı açamadığını bulmak için sadece hatalı çağrıları filtreleyebiliriz:

    # Sadece başarısız olan (errors) openat çağrılarını göster
    strace -e trace=openat -Z -p 1234

    3. Modern Çağın Sihirli Değneği: eBPF ve bpftrace

    Geleneksel araçların kısıtlamalarından sıkıldıysanız, sahneye eBPF (Extended Berkeley Packet Filter) çıkıyor. eBPF, Linux çekirdeğinin kodunu değiştirmeden veya kernel modülü yüklemeden, güvenli sandbox’lar içinde doğrudan çekirdekte kod çalıştırmamızı sağlar.

    bpftrace ise eBPF dünyasının “awk” dilidir. Son derece hafif, pratik ve neredeyse sıfır overhead ile çalışan tek satırlık (one-liners) script’ler yazmamıza olanak tanır.

    Örnek 1: Disk I/O Latency Analizi (Biolatency)

    Diskinizin yavaş olduğunu düşünüyorsunuz ama hangi process’in ne kadarlık bir gecikmeye sebep olduğunu bulamıyorsunuz. Geleneksel iostat size sadece ortalama değerler verir. bpftrace ile gerçek zamanlı bir histogram çizelim:

    # Disk I/O tamamlanma sürelerini mikrosaniye cinsinden histogram olarak gösterir
    bpftrace -e '
    kprobe:vfs_read { @start[tid] = nsecs; } 
    kretprobe:vfs_read /@start[tid]/ { 
        @latency = hist((nsecs - @start[tid]) / 1000); 
        delete(@start[tid]); 
    }'

    Bu script, her vfs_read (sanal dosya sistemi okuma) başladığında bir zaman damgası alır ve bittiğinde aradaki farkı hesaplayıp logaritmik bir grafik çizer:

    @latency: 
    [2, 4)                12 |@@@@                                        |
    [4, 8)                85 |@@@@@@@@@@@@@@@@@@@@@@@@                    |
    [8, 16)              142 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
    [16, 32)              45 |@@@@@@@@@@@@                                |
    [32, 64)               8 |@@                                          |

    Bu grafiğe bakarak, okuma işlemlerinin büyük çoğunluğunun 8-16 mikrosaniye arasında tamamlandığını, yani diskimizin sağlıklı çalıştığını saniyeler içinde anlayabiliriz.

    Örnek 2: Bellek Sızıntısı (Memory Leak) Avı

    Sisteminizde gizemli bir bellek şişmesi var. Hangi uygulamanın sürekli malloc çağırıp free etmediğini bulmak istiyorsunuz. eBPF ile kullanıcı alanındaki (user-space) bellek allocation çağrılarını takip edebiliriz:

    # libc içindeki malloc çağrılarının byte boyutlarına göre dağılımını izleyelim
    bpftrace -e 'usdt:/lib/x86_64-linux-gnu/libc.so.6:libc:memory_malloc_retry { @[arg0] = count(); }'

    Bu sayede production ortamında çalışan uygulamanıza hiçbir kütüphane enjekte etmeden veya onu durdurmadan bellek tüketim kalıplarını yakalayabilirsiniz.

    Özet ve Doğru Aracı Seçme Rehberi

    Sistem tıkandığında hangi aracı ne zaman kullanacağınıza karar vermek, tecrübeli bir mühendisi amatörden ayıran en önemli özelliktir. İşte size pratik bir başucu tablosu:

    Senaryo / Belirti Kullanılacak Araç Neden?
    Yüksek CPU kullanımı var, hangi kod satırının yavaş olduğunu bulmak istiyorum. perf record / report Düşük overhead ile CPU çağrı ağacını (call graph) çıkarır.
    Uygulama kilitlendi (stuck), log üretmiyor, ne yaptığını göremiyorum. strace -p <PID> Çekirdeğe gönderdiği sistem çağrılarını (network, dosya erişimi) anlık listeler.
    Disk performansından şüpheleniyorum ama genel istatistikler yetersiz kalıyor. bpftrace (eBPF) Kernel seviyesinde I/O kuyruk gecikmesini histogram olarak gösterir.
    Sistemin donanımsal düzeyde (L1/L2 Cache, CPU Cycles) analizine ihtiyacım var. perf stat İşlemcinin donanımsal sayaçlarına doğrudan erişim sağlar.

    Performans analizi yaparken her zaman en az invaziv (sisteme en az müdahale eden) yöntemden başlayın. Önce perf stat ve bpftrace ile genel resmi görün, gerekirse ve güvenliyse strace ile derinlemesine inceleme yapın. Unutmayın, iyi bir SRE sadece sorunu çözen değil, sorunu çözerken production sistemini ayakta tutabilen kişidir!

    Category: Genel | LEAVE A COMMENT
    Eylül 13 2024

    Elastic Fleet ile Merkezi Agent Yönetimi: Elveda Config Karmaşası!

    DevOps dünyasında monitoring ve log yönetimi denince akla gelen ilk isimlerden biri şüphesiz Elastic Stack. Ancak her sunucuya ayrı Filebeat, Metricbeat, Heartbeat kurup, Ansible playbook’ları içinde YAML indentasyon hatalarıyla boğuştuğumuz günler geride kaldı. Modern altyapılarda artık “single binary” felsefesi ve merkezi yönetim ön planda. İşte bu noktada sahneye elastic stack’in kurtarıcısı olan fleet ve onun her işe koşan askeri agent çıkıyor.

    Bu makalede, production ortamlarında hayat kurtaran Elastic Fleet mimarisini, Fleet Server kurulumunu, policy yönetimini ve sisteme dinamik entegrasyonlar eklemeyi derinlemesine inceleyeceğiz. Çayınızı kahvenizi alın; Ansible playbook’larınızdaki Beats rollerini silmeye hazırlanıyoruz.

    Neden Elastic Fleet? Eski Usul Beats vs. Modern Agent

    Eski günleri hatırlayalım. Bir web sunucunuz var ve hem sistem metriklerini (CPU, RAM) hem de Nginx loglarını toplamak istiyorsunuz. Süreç kabaca şöyle işliyordu:

    • Sunucuya Metricbeat indir, kur, metricbeat.yml konfigüre et, servisi başlat.
    • Filebeat indir, kur, Nginx modülünü aktif et, filebeat.yml içinde Elasticsearch adresini gir, servisi başlat.
    • Elasticsearch cluster adresi veya şifresi değiştiğinde, tüm sunuculardaki o meşhur YAML dosyalarını CM (Configuration Management) araçlarıyla güncelle.

    Bu yaklaşım ölçeklendikçe tam bir kabusa dönüşüyor. Elastic Agent ise bu karmaşayı tek bir binary altında topluyor. Agent arka planda gereken tüm “Beat” işlevlerini barındırıyor ve en önemlisi, konfigürasyonunu lokal bir dosyadan değil, doğrudan Kibana üzerinde koşan Fleet Server’dan dinamik olarak çekiyor. Siz Kibana arayüzünden tek bir tıkla yeni bir log kaynağı eklediğinizde, hedef sunucudaki Elastic Agent saniyeler içinde yeni policy’yi uyguluyor. Ne restart gerekiyor, ne de sunucuya SSH atmak.

    Fleet Server Mimarisi: Olayın Arkasındaki Beyin

    Fleet yapısını kurmadan önce mimariyi anlamak şart. Fleet, merkezi yönetim katmanıdır ve Kibana ile entegre çalışır. Ancak Kibana, binlerce agent ile doğrudan ham veri alışverişi yapacak şekilde tasarlanmamıştır. Bu yüzden araya Fleet Server girer.

    Fleet Server, Elastic Agent’ların bağlandığı, policy güncellemelerini aldığı ve durum raporladığı bir kontrol merkezidir. Kendisi de aslında özel bir policy ile çalışan bir Elastic Agent’tır. Agent’lar Fleet Server’a HTTPS üzerinden bağlanır (genellikle port 8220). Fleet Server ise aldığı durumları ve metrikleri doğrudan Elasticsearch’e yazar.

    Adım Adım Fleet Server Kurulumu (Self-Hosted)

    Eğer Elastic Cloud kullanıyorsanız Fleet Server sizin için otomatik olarak yönetilir. Ancak gerçek DevOps mühendisleri olarak biz self-hosted yapıları severiz (veya mecbur kalırız). Gelin, kendi altyapımızda Fleet Server’ı Docker üzerinde ayağa kaldıralım.

    Adım 1: Elasticsearch ve Kibana Hazırlığı

    Öncelikle halihazırda HTTPS korumalı bir Elasticsearch cluster’ınız ve Kibana’nız olduğunu varsayıyoruz. Fleet Server’ın Elasticsearch ile haberleşebilmesi için bir “Service Token” üretmemiz gerekiyor. Kibana Console (Dev Tools) üzerinden şu komutla token’ımızı alalım:

    POST _security/service/elastic/fleet-server/token/my-fleet-token

    Dönen yanıttaki value değerini güvenli bir yere kaydedin. Bu bizim FLEET_SERVER_ELASTICSEARCH_SERVICE_TOKEN değerimiz olacak.

    Adım 2: Fleet Server’ı Docker ile Çalıştırma

    Aşağıdaki Docker komutu ile Fleet Server’ı ayağa kaldırabiliriz. Üretim ortamında mutlaka geçerli SSL sertifikaları kullanmalısınız, ancak test ortamı için doğrulama adımlarını esnetebiliriz.

    docker run -d \
      --name fleet-server \
      -p 8220:8220 \
      -e FLEET_SERVER_ENABLE=1 \
      -e FLEET_SERVER_ELASTICSEARCH_HOST=https://elasticsearch.local:9200 \
      -e FLEET_SERVER_ELASTICSEARCH_SERVICE_TOKEN=AAEAAWVsYXN0aWMvZmxlZXQtc2VydmVyL3Rva2VuLTE2N... \
      -e FLEET_URL=https://fleet-server.local:8220 \
      -e FLEET_SERVER_CERT=/usr/share/elastic-agent/certs/fleet.crt \
      -e FLEET_SERVER_KEY=/usr/share/elastic-agent/certs/fleet.key \
      -v /path/to/certs:/usr/share/elastic-agent/certs \
      docker.elastic.co/beats/elastic-agent:8.11.1

    Bu komutla Fleet Server, 8220 portundan agent bağlantılarını kabul etmeye hazır hale gelecektir. Kibana arayüzünde Management > Fleet sekmesine gittiğinizde Fleet Server’ın “Healthy” durumuna geçtiğini görmelisiniz.

    Elastic Agent Kurulumu ve Fleet’e Enroll Etme

    Sırada log ve metrik toplayacağımız hedef sunuculara (örneğin bir Linux VM) Elastic Agent kurmak var. Agent kurulumu son derece basittir çünkü tek bir binary indirip çalıştırmaktan ibarettir.

    Adım 1: Agent Paketini İndirme

    Hedef sunucuda terminale çıkıp işletim sistemimize uygun paketi indiriyoruz:

    curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.11.1-linux-x86_64.tar.gz
    tar xzvf elastic-agent-8.11.1-linux-x86_64.tar.gz
    cd elastic-agent-8.11.1-linux-x86_64

    Adım 2: Enrollment (Kayıt) İşlemi

    Agent’ı sisteme kurarken onu Fleet Server’a “enroll” etmemiz, yani kaydetmemiz gerekir. Bunun için Kibana > Fleet > Enrollment Tokens sekmesinden hedef policy için üretilmiş token’ı alıyoruz. Ardından şu komutla kurulumu tetikliyoruz:

    sudo ./elastic-agent install \
      --url=https://fleet-server.local:8220 \
      --enrollment-token=U0ZVMWhvMEJGUnp6eE5iNUp0eXU6cUpnZXA3V... \
      --certificate-authorities=/path/to/ca.crt

    Bu komut arka planda şunları yapar:

    • Agent’ı sistem servisi olarak kaydeder (systemd).
    • Gerekli dizin yapılarını (/etc/elastic-agent, /var/log/elastic-agent) oluşturur.
    • Fleet Server ile el sıkışarak ilk policy’sini indirir ve servisi başlatır.

    Servisin durumunu doğrulamak için:

    sudo systemctl status elastic-agent

    Policy Yönetimi: Tek Merkezden Gücü Dağıtmak

    Elastic Fleet’in en güçlü yanı policy tabanlı yönetimdir. Bir policy; hangi logların toplanacağını, hangi metriklerin hangi sıklıkla çekileceğini belirten bir kurallar bütünüdür. Sunucularınızı rollerine göre gruplayabilirsiniz (örn: web-servers-policy, db-servers-policy).

    Bir sunucunun davranışını değiştirmek istediğinizde, tek yapmanız gereken Kibana arayüzünden ilgili policy’yi editlemektir. Örneğin, tüm web sunucularında Apache log takibini kapatıp yerine Nginx log takibini açmak istiyorsunuz. Bu değişikliği policy üzerinde yaptığınız an, o policy’ye bağlı olan 500 sunucudaki Elastic Agent saniyeler içinde kendini günceller.

    Pratik Örnek: Nginx Entegrasyonu Ekleme

    Gelin, çalışan bir Elastic Agent’a Kibana üzerinden nasıl dinamik entegrasyon ekleyeceğimize bakalım:

    1. Kibana > Fleet > Agent Policies sayfasına gidin.
    2. Agent’ınızın bağlı olduğu policy’yi seçin.
    3. Add Integration butonuna tıklayın.
    4. Arama çubuğuna Nginx yazın ve entegrasyonu seçin.
    5. Nginx log dosyalarının yollarını (varsayılan: /var/log/nginx/access.log) ve metrik endpoint’ini (stub_status) konfigüre edin.
    6. Save Integration butonuna basarak kaydedin.

    Arkanıza yaslanın. Hedef sunucudaki agent, konfigürasyon değişikliğini algılayacak, arka planda Nginx loglarını okumaya başlayacak ve Kibana’da hazır gelen “Nginx Overview” dashboard’larını verilerle dolduracaktır. Sıfır konfigürasyon dosyası editlendi, sıfır servis restart edildi!

    Prodüksiyon Tavsiyeleri ve Savaş Hikayeleri

    Her şey kağıt üzerinde harika görünse de, production ortamlarında Fleet koştururken dikkat etmeniz gereken bazı can alıcı noktalar var:

    1. Resource Limits (Kaynak Sınırları)

    Elastic Agent, eski Beats’lere göre biraz daha fazla memory tüketebilir çünkü arka planda birden fazla süreci yönetir. Özellikle çok sayıda log dosyası (wildcard ile binlerce log) izleniyorsa, agent memory limitlerine takılabilir. Sunucularınızda elastic-agent‘ın CPU ve RAM tüketimini mutlaka monitor edin.

    2. SSL/TLS Sertifikaları

    Fleet Server ile Agent arasındaki iletişim kesinlikle HTTPS olmalıdır. Self-signed sertifika kullanıyorsanız, agent kurulumu sırasında --certificate-authorities parametresiyle CA sertifikanızı belirtmeyi unutmayın. Aksi takdirde agent’lar “x509: certificate signed by unknown authority” hatasıyla kaydolmayacaktır.

    3. Fleet Server Ölçekleme

    Tek bir Fleet Server kabaca 1000 ila 2000 agent’a kadar sorunsuz hizmet verebilir. Ancak binlerce agent’ın olduğu büyük altyapılarda, Fleet Server’ı arkasında bir Load Balancer (HAProxy, AWS ALB vb.) olacak şekilde multi-node mimaride kurmalısınız.

    Fleet Server’ları ölçeklerken şu parametre ile JVM heap boyutunu ve max connection limitlerini optimize edebilirsiniz:

    # Örnek environment değişkeni
    -e FLEET_SERVER_MAX_CONNECTIONS=5000

    Özet

    Elastic Fleet ve Agent ikilisi, modern DevOps dünyasındaki monitoring operasyonlarını büyük ölçüde kolaylaştırıyor. Altyapınızı kodla yönetirken (IaC), sunuculara sadece tek bir agent kurup geri kalan tüm konfigürasyonu UI veya Fleet API’leri üzerinden yönetmek, operasyonel yükü ciddi oranda azaltır. Eğer hala eski usul Beats konfigürasyonlarıyla uğraşıyorsanız, Fleet’e geçiş planını hemen sprint’inize eklemenizi öneririm.

    Category: Genel | LEAVE A COMMENT