Ansible ile Çoklu Ortam Yönetimi: Staging ve Production Ayrımı

Üretim ortamına yanlış bir playbook çalıştırmak, kariyerinizin en uzun gecesini yaşamanıza neden olabilir. “Staging’de test ettim, production’da da çalışır” mantığı teoride güzel görünse de, iki ortamın birbirinden net biçimde ayrılmadığı bir yapıda bu cümle çok tehlikeli. Ansible ile çoklu ortam yönetimi, sadece bir iyileştirme değil, operasyonel olgunluğun temel göstergelerinden biridir.

Bu yazıda gerçek dünya senaryolarına dayanan, production’ı korurken staging’i özgürce kullanabileceğiniz bir Ansible yapısını baştan sona kuruyoruz.

Neden Ortam Ayrımı Şart?

Çoğu ekip başlangıçta tek bir inventory dosyasıyla çalışır. Her şey küçükken bu yönetilebilir görünür. Ama ekip büyüdükçe, sunucu sayısı arttıkça ve değişiklik sıklığı yükseldikçe şu sorular kaçınılmaz hale gelir:

  • Bu değişkeni staging için mi yoksa production için mi set ettim?
  • Bu playbook hangi ortamda çalışacak?
  • Staging’deki veritabanı şifresi production’dakiyle aynı mı?

Ansible’ın güçlü yanı, bu soruları mimariye gömerek ortadan kaldırabilmesidir. Doğru bir inventory yapısı, değişken hiyerarşisi ve vault kullanımı bir araya geldiğinde, yanlış ortamda bir şey çalıştırmak neredeyse imkansız hale gelir.

Proje Dizin Yapısı

İyi bir çoklu ortam yapısının temeli, dizin organizasyonudur. Ansible’ın resmi best practice dökümanında önerilen yapıyı biraz daha genişleterek başlayalım:

ansible-project/
├── inventories/
│   ├── staging/
│   │   ├── hosts.ini
│   │   ├── group_vars/
│   │   │   ├── all.yml
│   │   │   ├── webservers.yml
│   │   │   └── dbservers.yml
│   │   └── host_vars/
│   │       └── staging-web-01.yml
│   └── production/
│       ├── hosts.ini
│       ├── group_vars/
│       │   ├── all.yml
│       │   ├── webservers.yml
│       │   └── dbservers.yml
│       └── host_vars/
│           └── prod-web-01.yml
├── roles/
│   ├── common/
│   ├── webserver/
│   └── database/
├── playbooks/
│   ├── site.yml
│   ├── deploy.yml
│   └── maintenance.yml
├── ansible.cfg
└── Makefile

Bu yapının güzelliği şu: inventories/staging ve inventories/production birbirinden tamamen bağımsız. Aynı role, aynı playbook, farklı değişkenlerle farklı ortamlarda çalışıyor.

Inventory Dosyalarını Yapılandırmak

Staging Inventory

# inventories/staging/hosts.ini

[webservers]
staging-web-01 ansible_host=10.0.1.10
staging-web-02 ansible_host=10.0.1.11

[dbservers]
staging-db-01 ansible_host=10.0.1.20

[loadbalancers]
staging-lb-01 ansible_host=10.0.1.5

[staging:children]
webservers
dbservers
loadbalancers

[all:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3
env=staging

Production Inventory

# inventories/production/hosts.ini

[webservers]
prod-web-01 ansible_host=10.1.1.10
prod-web-02 ansible_host=10.1.1.11
prod-web-03 ansible_host=10.1.1.12

[dbservers]
prod-db-primary ansible_host=10.1.1.20
prod-db-replica ansible_host=10.1.1.21

[loadbalancers]
prod-lb-01 ansible_host=10.1.1.5
prod-lb-02 ansible_host=10.1.1.6

[production:children]
webservers
dbservers
loadbalancers

[all:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3
env=production

Dikkat edin: env değişkeni her iki inventory’de de tanımlı ama farklı değerlere sahip. Bu değişkeni playbook ve role içinde koruyucu mantık kurmak için kullanacağız.

Group Vars ile Ortam Bazlı Konfigürasyon

Group vars, aynı rolün farklı ortamlarda farklı davranmasını sağlamanın en temiz yoludur.

Staging All Vars

# inventories/staging/group_vars/all.yml

# Ortam kimliği
environment_name: staging
environment_color: yellow    # Bazı ekipler banner rengi kullanır

# Uygulama ayarları
app_version: latest
app_debug: true
app_log_level: debug

# Veritabanı bağlantısı (vault ile şifrelenmiş değerler ayrı dosyada)
db_host: staging-db-01
db_port: 5432
db_name: myapp_staging
db_pool_size: 5

# Web sunucusu
nginx_worker_processes: 2
nginx_worker_connections: 512

# Monitoring
enable_monitoring: false
datadog_enabled: false

# Backup
backup_enabled: false
backup_retention_days: 3

# SSL
ssl_enabled: false
domain_name: staging.myapp.example.com

Production All Vars

# inventories/production/group_vars/all.yml

# Ortam kimliği
environment_name: production
environment_color: red

# Uygulama ayarları
app_version: "{{ lookup('env', 'APP_VERSION') | default('1.0.0') }}"
app_debug: false
app_log_level: warning

# Veritabanı
db_host: prod-db-primary
db_port: 5432
db_name: myapp_production
db_pool_size: 20

# Web sunucusu
nginx_worker_processes: auto
nginx_worker_connections: 2048

# Monitoring
enable_monitoring: true
datadog_enabled: true

# Backup
backup_enabled: true
backup_retention_days: 30

# SSL
ssl_enabled: true
domain_name: myapp.example.com

Bu yapıda aynı deploy.yml playbook’u çalıştırdığınızda, staging’de debug açık, monitoring kapalı, küçük pool size’larla çalışırken; production’da tam tersi konfigürasyon devreye girer.

Ansible Vault ile Gizli Bilgileri Yönetmek

Şifreler, API anahtarları ve sertifikalar hiçbir zaman düz metin olarak repository’e girmemeli. Her ortam için ayrı vault dosyası kullanmak en doğru yaklaşım.

# Staging vault dosyası oluşturma
ansible-vault create inventories/staging/group_vars/vault.yml

# Production vault dosyası oluşturma  
ansible-vault encrypt inventories/production/group_vars/vault.yml

Vault dosyalarının içeriği şöyle görünmeli (encrypt edilmeden önce):

# inventories/staging/group_vars/vault.yml (şifrelenmemiş hali)

vault_db_password: "staging-sifre-123"
vault_db_user: "myapp_staging"
vault_redis_password: "redis-staging-pass"
vault_app_secret_key: "staging-secret-key-buraya"
vault_datadog_api_key: ""    # Staging'de kullanılmıyor

# inventories/production/group_vars/vault.yml (şifrelenmemiş hali)
vault_db_password: "{{ uretim_sifreniz }}"
vault_db_user: "myapp_prod"
vault_redis_password: "guclu-redis-sifresi"
vault_app_secret_key: "production-secret-key-buraya"
vault_datadog_api_key: "dd-api-anahtariniz"

Vault şifrelerini ortam değişkenlerinden okumak için:

# .vault_pass_staging dosyasına staging vault şifresi
# .vault_pass_production dosyasına production vault şifresi
# Her ikisi de .gitignore'a eklenmeli!

echo "staging-vault-password" > .vault_pass_staging
echo "production-vault-password" > .vault_pass_production
chmod 600 .vault_pass_staging .vault_pass_production
# ansible.cfg içinde
[defaults]
inventory = inventories/staging
vault_password_file = .vault_pass_staging
roles_path = roles
retry_files_enabled = False
stdout_callback = yaml

Production Koruyucu Mekanizmalar

En kritik nokta burasıdır. Production’ı yanlışlıkla değiştirmek için ekstra engeller koymalısınız.

Playbook Seviyesinde Koruma

# playbooks/deploy.yml

---
- name: Deployment öncesi kontrol
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Ortam değişkenini kontrol et
      fail:
        msg: |
          HATA: Production deployment için onay gerekli!
          Lütfen DEPLOY_CONFIRM=yes ortam değişkenini set edin.
          Örnek: DEPLOY_CONFIRM=yes ansible-playbook ...
      when:
        - hostvars[groups['all'][0]]['env'] == 'production'
        - lookup('env', 'DEPLOY_CONFIRM') != 'yes'

    - name: Production deployment uyarısı
      pause:
        prompt: |
          *** PRODUCTION DEPLOYMENT ***
          Ortam: {{ hostvars[groups['all'][0]]['env'] }}
          Versiyon: {{ hostvars[groups['all'][0]]['app_version'] }}
          Devam etmek için ENTER'a basın, iptal için Ctrl+C
      when: hostvars[groups['all'][0]]['env'] == 'production'

- name: Uygulama deployment
  hosts: webservers
  become: true
  roles:
    - common
    - webserver
    - application

Role İçinde Ortam Kontrolü

# roles/database/tasks/main.yml

---
- name: Veritabanı rol kontrolü
  block:
    - name: Staging veritabanı işlemleri
      include_tasks: staging_db.yml
      when: env == 'staging'

    - name: Production veritabanı işlemleri
      include_tasks: production_db.yml
      when: env == 'production'

- name: Production'da migration onayı
  pause:
    prompt: "Migration çalıştırılacak. Onaylıyor musunuz? (yes/no)"
  register: migration_confirm
  when: env == 'production'

- name: Migration onayı kontrol
  fail:
    msg: "Migration iptal edildi."
  when:
    - env == 'production'
    - migration_confirm.user_input != 'yes'

Makefile ile Komut Standardizasyonu

Farklı geliştiricilerin farklı komutlar çalıştırması kaosa yol açar. Makefile, tüm ekibin aynı dili konuşmasını sağlar:

# Makefile

STAGING_INVENTORY = inventories/staging
PRODUCTION_INVENTORY = inventories/production
STAGING_VAULT = .vault_pass_staging
PRODUCTION_VAULT = .vault_pass_production

.PHONY: help staging-deploy production-deploy staging-check production-check

help:
	@echo "Kullanılabilir komutlar:"
	@echo "  make staging-check       - Staging dry-run"
	@echo "  make staging-deploy      - Staging'e deploy et"
	@echo "  make production-check    - Production dry-run"
	@echo "  make production-deploy   - Production'a deploy et (onay gerekir)"
	@echo "  make staging-ping        - Staging sunucularını kontrol et"
	@echo "  make production-ping     - Production sunucularını kontrol et"

staging-ping:
	ansible all -i $(STAGING_INVENTORY) --vault-password-file $(STAGING_VAULT) -m ping

production-ping:
	ansible all -i $(PRODUCTION_INVENTORY) --vault-password-file $(PRODUCTION_VAULT) -m ping

staging-check:
	ansible-playbook -i $(STAGING_INVENTORY) 
		--vault-password-file $(STAGING_VAULT) 
		--check --diff 
		playbooks/site.yml

staging-deploy:
	ansible-playbook -i $(STAGING_INVENTORY) 
		--vault-password-file $(STAGING_VAULT) 
		playbooks/site.yml

production-check:
	ansible-playbook -i $(PRODUCTION_INVENTORY) 
		--vault-password-file $(PRODUCTION_VAULT) 
		--check --diff 
		playbooks/site.yml

production-deploy:
	@echo "Production deployment başlatılıyor..."
	DEPLOY_CONFIRM=yes ansible-playbook -i $(PRODUCTION_INVENTORY) 
		--vault-password-file $(PRODUCTION_VAULT) 
		playbooks/site.yml

Artık ekipteki herkes make staging-deploy veya make production-deploy yazarak standart bir şekilde deployment yapabilir.

Gerçek Dünya Senaryosu: Nginx Konfigürasyonu

Aynı Nginx rolünün iki ortamda nasıl farklı davrandığını görelim:

# roles/webserver/templates/nginx.conf.j2

user www-data;
worker_processes {{ nginx_worker_processes }};
pid /run/nginx.pid;

events {
    worker_connections {{ nginx_worker_connections }};
    multi_accept on;
}

http {
    # Temel ayarlar
    sendfile on;
    tcp_nopush on;
    keepalive_timeout 65;

    # Loglama - ortama göre değişir
    {% if env == 'production' %}
    access_log /var/log/nginx/access.log combined buffer=16k;
    error_log /var/log/nginx/error.log warn;
    {% else %}
    access_log /var/log/nginx/access.log combined;
    error_log /var/log/nginx/error.log debug;
    {% endif %}

    # SSL - sadece production'da zorunlu
    {% if ssl_enabled %}
    server {
        listen 80;
        server_name {{ domain_name }};
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name {{ domain_name }};
        ssl_certificate /etc/ssl/certs/{{ domain_name }}.crt;
        ssl_certificate_key /etc/ssl/private/{{ domain_name }}.key;
        ssl_protocols TLSv1.2 TLSv1.3;

        location / {
            proxy_pass http://app_upstream;
        }
    }
    {% else %}
    server {
        listen 80;
        server_name {{ domain_name }};

        # Staging banner
        add_header X-Environment "STAGING" always;

        location / {
            proxy_pass http://app_upstream;
        }
    }
    {% endif %}
}

Bu template, staging’de HTTP üzerinden çalışırken ve response header’a X-Environment: STAGING eklerken, production’da HTTPS’e yönlendirme yapıyor.

CI/CD Pipeline ile Entegrasyon

GitLab CI örneği üzerinden ortam ayrımının pipeline’a nasıl yansıdığını görelim:

# .gitlab-ci.yml

stages:
  - test
  - staging-deploy
  - production-deploy

variables:
  ANSIBLE_HOST_KEY_CHECKING: "False"

# Syntax kontrolü her branch'te
ansible-lint:
  stage: test
  image: cytopia/ansible:latest
  script:
    - ansible-lint playbooks/
    - ansible-playbook --syntax-check -i inventories/staging playbooks/site.yml

# Staging: develop branch'e merge edilince otomatik
deploy-staging:
  stage: staging-deploy
  image: cytopia/ansible:latest
  environment:
    name: staging
    url: https://staging.myapp.example.com
  only:
    - develop
  before_script:
    - echo "$STAGING_VAULT_PASS" > .vault_pass_staging
    - chmod 600 .vault_pass_staging
  script:
    - ansible-playbook -i inventories/staging
        --vault-password-file .vault_pass_staging
        playbooks/deploy.yml
        -e "app_version=$CI_COMMIT_SHA"
  after_script:
    - rm -f .vault_pass_staging

# Production: main branch'e merge edilince manuel onay ile
deploy-production:
  stage: production-deploy
  image: cytopia/ansible:latest
  environment:
    name: production
    url: https://myapp.example.com
  only:
    - main
  when: manual    # Manuel tetikleme zorunlu
  before_script:
    - echo "$PRODUCTION_VAULT_PASS" > .vault_pass_production
    - chmod 600 .vault_pass_production
  script:
    - DEPLOY_CONFIRM=yes ansible-playbook
        -i inventories/production
        --vault-password-file .vault_pass_production
        playbooks/deploy.yml
        -e "app_version=$CI_COMMIT_TAG"
  after_script:
    - rm -f .vault_pass_production

Bu pipeline’da staging otomatik deploy edilirken, production için GitLab UI’dan manuel onay gerekiyor. when: manual satırı, birinin kasıtlı olarak “Deploy” butonuna basmasını zorunlu kılıyor.

Ortam Doğrulama Playbook’u

Deployment sonrası her şeyin doğru çalıştığını kontrol eden bir validation playbook’u çok işe yarar:

# playbooks/validate.yml

---
- name: Ortam doğrulama
  hosts: webservers
  gather_facts: true
  tasks:
    - name: Nginx servis durumu
      service_facts:

    - name: Nginx çalışıyor mu?
      assert:
        that:
          - ansible_facts.services['nginx.service'].state == 'running'
        fail_msg: "HATA: Nginx çalışmıyor!"
        success_msg: "OK: Nginx aktif"

    - name: Uygulama portu açık mı?
      wait_for:
        port: "{{ app_port | default(8080) }}"
        host: "{{ ansible_host }}"
        timeout: 10
        msg: "Uygulama portu kapalı!"

    - name: HTTP health check
      uri:
        url: "http://{{ ansible_host }}/health"
        status_code: 200
        return_content: true
      register: health_check
      retries: 3
      delay: 5

    - name: Ortam bilgisi doğrulama
      assert:
        that:
          - "'{{ environment_name }}' in health_check.content"
        fail_msg: "Ortam bilgisi response'da bulunamadı!"
      when: env == 'staging'

    - name: Validation özeti
      debug:
        msg: |
          Doğrulama tamamlandı
          Ortam: {{ environment_name }}
          Sunucu: {{ inventory_hostname }}
          Durum: BAŞARILI

Sık Yapılan Hatalar

Ortam yönetimi konusunda sahada sıkça karşılaştığım hataları paylaşayım:

  • Tek inventory dosyasında [staging] ve [production] grupları kullanmak: Vault şifrelerini ayırmak imkansız hale geliyor, değişken çakışmaları kaçınılmaz oluyor.
  • ansible.cfg içinde sabit inventory yolu tanımlamak: Birisi varsayılan komutu çalıştırdığında hangi ortamın etkileneceği belirsiz kalıyor.
  • Vault dosyalarını tek bir merkezi yerde tutmak: Her ortamın kendi vault’u olmalı. Staging ekibinin production vault’una erişimi olmamalı.
  • --check modunu production öncesinde atlamamak: Dry-run, production’da sürprizleri önlemenin en basit yolu. Makefile’daki production-check hedefini bir alışkanlık haline getirin.
  • env değişkenini role’lerde kontrol etmemek: Kritik işlemler için (veri silme, migration) her zaman ortam kontrolü ekleyin.

Sonuç

Ansible ile çoklu ortam yönetimi aslında birkaç temel prensibe dayanıyor: ayrı inventory dizinleri, ortam bazlı group_vars, vault ile gizli bilgi yönetimi ve koruyucu mekanizmalar. Bu yapıyı bir kez kurduğunuzda, ekibinizdeki her kişi güvenle staging’de deneme yapabilir, production’a ise ancak kasıtlı olarak geçilebilir.

Makefile yaklaşımı küçük bir detay gibi görünse de ekip standardizasyonu için kritik. Yeni bir ekip üyesi geldiğinde make help çalıştırıp ne yapacağını anlayabilmeli.

CI/CD pipeline entegrasyonuyla bu yapı tamamlanıyor: develop’a merge edilince staging’e otomatik git, main’e merge edilince production için birisinin kasıtlı onay vermesini zorunlu kıl. Bu basit fark, gece yarısı “staging’de test ettim production’da da çalışır” diye yapılan hatalı deployment’ların önüne geçer.

Son olarak, validation playbook’unu deployment sürecinize mutlaka ekleyin. Deploy ettikten sonra otomatik kontrol çalışırsa, sorunları kullanıcılar bulmadan siz bulursunuz. Bu fark, iyi bir sysadmin’i mükemmel bir sysadmin’den ayıran şeylerden biridir.

Yorum yapın