Kubernetes Uygulama Deployment: Ansible ile Tam Rehber
Kubernetes cluster’ınız var, uygulamalarınız hazır ama her deployment’ta aynı kubectl apply komutlarını tekrar tekrar yazmaktan bıktınız mı? Ya da staging’de çalışan bir şeyi production’a taşırken “acaba bir şeyi unuttu muyum” diye endişe duyuyor musunuz? İşte bu noktada Ansible devreye giriyor. Ansible’ın idempotent yapısı ve Kubernetes modülleriyle birlikte, deployment süreçlerinizi tekrarlanabilir, güvenilir ve otomatik hale getirebilirsiniz.
Bu yazıda gerçek dünya senaryoları üzerinden Ansible ile Kubernetes deployment’ı nasıl yönetirsiniz, adım adım anlatacağım.
Neden Ansible + Kubernetes?
Kubectl direkt kullanmak başlangıçta yeterli görünüyor. Ama ekip büyüdükçe, ortam sayısı arttıkça ve deployment sıklığı yükseldikçe manuel süreçler kaos yaratmaya başlıyor. Ansible bu noktada birkaç kritik avantaj sağlıyor.
İdempotency en büyük kazanım. Aynı playbook’u 10 kez çalıştırsanız da sonuç aynı. Deployment’ın yarıda kesilmesi durumunda tekrar başlatabilirsiniz, sistem tutarlı kalır.
Secret yönetimi bir diğer önemli nokta. Ansible Vault ile hassas bilgileri şifreleyip playbook’larınıza dahil edebilirsiniz. Kubernetes secret’larını düz metin olarak Git’e push etme günleri geride kalır.
Multi-environment yönetimi de çok kolaylaşıyor. Tek bir playbook ile dev, staging ve production ortamlarına farklı parametrelerle deployment yapabilirsiniz.
Ön Gereksinimler ve Kurulum
Ansible kontrol makinenizde şunların kurulu olması gerekiyor:
# Ansible kurulumu (Python 3.8+ gerekli)
pip3 install ansible kubernetes
# Kubernetes koleksiyonunu kur
ansible-galaxy collection install kubernetes.core
# Kurulumu doğrula
ansible --version
python3 -c "import kubernetes; print(kubernetes.__version__)"
Ansible’ın Kubernetes cluster’ınıza erişebilmesi için kubeconfig dosyasının doğru konumda olması gerekiyor:
# Kubeconfig konumunu test et
kubectl cluster-info
# Ansible'ın hangi kubeconfig'i kullanacağını belirt
export KUBECONFIG=/home/your_user/.kube/config
# Ya da inventory dosyasında belirtin
# Bu yaklaşımı aşağıda göreceğiz
Proje Dizin Yapısı
Düzenli bir Ansible projesi için şu yapıyı öneriyorum. Gerçek dünyada bu yapı hem okunabilirliği artırıyor hem de CI/CD pipeline’ına entegrasyonu kolaylaştırıyor:
k8s-ansible/
├── inventory/
│ ├── dev/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ │ └── all.yml
│ ├── staging/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ │ └── all.yml
│ └── production/
│ ├── hosts.yml
│ └── group_vars/
│ └── all.yml
├── roles/
│ ├── k8s-namespace/
│ ├── k8s-deployment/
│ ├── k8s-service/
│ └── k8s-ingress/
├── playbooks/
│ ├── deploy-app.yml
│ ├── rollback.yml
│ └── cleanup.yml
├── vault/
│ └── secrets.yml
└── ansible.cfg
İlk Playbook: Namespace ve Temel Kaynaklar
Bir e-ticaret uygulamasını deploy ettiğimizi düşünelim. Önce namespace ve temel kaynakları oluşturalım:
# playbooks/setup-namespace.yml
---
- name: Kubernetes Namespace ve Temel Kaynaklar
hosts: localhost
connection: local
gather_facts: false
vars:
app_namespace: "ecommerce"
environment: "production"
kubeconfig_path: "{{ lookup('env', 'KUBECONFIG') }}"
tasks:
- name: Namespace olustur
kubernetes.core.k8s:
kubeconfig: "{{ kubeconfig_path }}"
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ app_namespace }}"
labels:
environment: "{{ environment }}"
managed-by: ansible
- name: Resource Quota tanimla
kubernetes.core.k8s:
kubeconfig: "{{ kubeconfig_path }}"
state: present
definition:
apiVersion: v1
kind: ResourceQuota
metadata:
name: "{{ app_namespace }}-quota"
namespace: "{{ app_namespace }}"
spec:
hard:
requests.cpu: "4"
requests.memory: 8Gi
limits.cpu: "8"
limits.memory: 16Gi
pods: "20"
- name: Namespace durumunu dogrula
kubernetes.core.k8s_info:
kubeconfig: "{{ kubeconfig_path }}"
kind: Namespace
name: "{{ app_namespace }}"
register: namespace_info
- name: Sonucu goster
debug:
msg: "Namespace {{ namespace_info.resources[0].metadata.name }} durumu: {{ namespace_info.resources[0].status.phase }}"
Secret ve ConfigMap Yönetimi
Bu kısım genellikle en çok hata yapılan yer. Production secret’larını Ansible Vault ile yönetmek hem güvenli hem de pratik:
# Vault dosyası oluştur
ansible-vault create vault/secrets.yml
# İçeriği şu şekilde olacak (vault editor'da):
# db_password: "super_secret_password_123"
# redis_password: "redis_secret_456"
# jwt_secret: "jwt_very_long_secret_key"
# docker_registry_password: "registry_token_xyz"
Şimdi bu secret’ları Kubernetes’e aktaran playbook:
# playbooks/deploy-secrets.yml
---
- name: Kubernetes Secrets ve ConfigMap Deploy
hosts: localhost
connection: local
gather_facts: false
vars_files:
- ../vault/secrets.yml
vars:
app_namespace: "ecommerce"
app_name: "web-store"
db_host: "postgres-service.ecommerce.svc.cluster.local"
redis_host: "redis-service.ecommerce.svc.cluster.local"
app_replicas: 3
tasks:
- name: Database secret olustur
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Secret
metadata:
name: "{{ app_name }}-db-secret"
namespace: "{{ app_namespace }}"
type: Opaque
stringData:
DB_PASSWORD: "{{ db_password }}"
REDIS_PASSWORD: "{{ redis_password }}"
JWT_SECRET: "{{ jwt_secret }}"
- name: ConfigMap olustur
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: ConfigMap
metadata:
name: "{{ app_name }}-config"
namespace: "{{ app_namespace }}"
data:
DB_HOST: "{{ db_host }}"
REDIS_HOST: "{{ redis_host }}"
APP_ENV: "production"
LOG_LEVEL: "warn"
MAX_CONNECTIONS: "100"
- name: Docker registry secret olustur
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Secret
metadata:
name: registry-credentials
namespace: "{{ app_namespace }}"
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: "{{ {'auths': {'registry.example.com': {'username': 'deploy_user', 'password': docker_registry_password}}} | to_json | b64encode }}"
Ana Deployment Playbook’u
İşte asıl iş burada başlıyor. Gerçek bir uygulama deployment’ı için kapsamlı bir örnek:
# playbooks/deploy-app.yml
---
- name: Web Store Uygulama Deployment
hosts: localhost
connection: local
gather_facts: false
vars_files:
- ../vault/secrets.yml
vars:
app_name: "web-store"
app_namespace: "ecommerce"
image_tag: "{{ lookup('env', 'IMAGE_TAG') | default('latest') }}"
image_repo: "registry.example.com/ecommerce/web-store"
app_replicas: "{{ lookup('env', 'APP_REPLICAS') | default(3) | int }}"
min_ready_seconds: 30
deployment_timeout: 300
pre_tasks:
- name: Image tag kontrolu
fail:
msg: "IMAGE_TAG environment variable tanimlanmamis! Ornek: export IMAGE_TAG=v1.2.3"
when: image_tag == "latest" and ansible_env.CI is defined
- name: Mevcut deployment durumunu kaydet
kubernetes.core.k8s_info:
kind: Deployment
name: "{{ app_name }}"
namespace: "{{ app_namespace }}"
register: current_deployment
ignore_errors: true
- name: Rollback icin mevcut image'i kaydet
set_fact:
previous_image: "{{ current_deployment.resources[0].spec.template.spec.containers[0].image | default('none') }}"
when: current_deployment.resources | length > 0
tasks:
- name: Deployment manifest uygula
kubernetes.core.k8s:
state: present
definition:
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ app_name }}"
namespace: "{{ app_namespace }}"
labels:
app: "{{ app_name }}"
version: "{{ image_tag }}"
annotations:
deployment.kubernetes.io/revision: "1"
ansible/last-deployed: "{{ ansible_date_time.iso8601 }}"
spec:
replicas: "{{ app_replicas }}"
minReadySeconds: "{{ min_ready_seconds }}"
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
selector:
matchLabels:
app: "{{ app_name }}"
template:
metadata:
labels:
app: "{{ app_name }}"
version: "{{ image_tag }}"
spec:
imagePullSecrets:
- name: registry-credentials
containers:
- name: "{{ app_name }}"
image: "{{ image_repo }}:{{ image_tag }}"
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: "{{ app_name }}-config"
- secretRef:
name: "{{ app_name }}-db-secret"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 20
failureThreshold: 3
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- "{{ app_name }}"
topologyKey: kubernetes.io/hostname
- name: Deployment'in tamamlanmasini bekle
kubernetes.core.k8s_rollout_status:
name: "{{ app_name }}"
namespace: "{{ app_namespace }}"
kind: Deployment
timeout: "{{ deployment_timeout }}"
register: rollout_status
- name: Deployment basarili mesaji
debug:
msg: "Deployment basarili! {{ app_name }}:{{ image_tag }} {{ app_replicas }} replica ile calisıyor."
when: rollout_status is succeeded
Service ve Ingress Yapılandırması
Deployment sonrasında servis ve ingress kaynaklarını da aynı playbook zincirinde yönetebilirsiniz:
# playbooks/deploy-networking.yml
---
- name: Service ve Ingress Yapilandirma
hosts: localhost
connection: local
gather_facts: false
vars:
app_name: "web-store"
app_namespace: "ecommerce"
domain_name: "store.example.com"
tls_secret_name: "store-tls-cert"
tasks:
- name: ClusterIP Service olustur
kubernetes.core.k8s:
state: present
definition:
apiVersion: v1
kind: Service
metadata:
name: "{{ app_name }}-service"
namespace: "{{ app_namespace }}"
labels:
app: "{{ app_name }}"
spec:
selector:
app: "{{ app_name }}"
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
type: ClusterIP
- name: HorizontalPodAutoscaler tanimla
kubernetes.core.k8s:
state: present
definition:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: "{{ app_name }}-hpa"
namespace: "{{ app_namespace }}"
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: "{{ app_name }}"
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
- name: Ingress olustur
kubernetes.core.k8s:
state: present
definition:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: "{{ app_name }}-ingress"
namespace: "{{ app_namespace }}"
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- "{{ domain_name }}"
secretName: "{{ tls_secret_name }}"
rules:
- host: "{{ domain_name }}"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: "{{ app_name }}-service"
port:
number: 80
- name: Ingress IP adresini al
kubernetes.core.k8s_info:
kind: Ingress
name: "{{ app_name }}-ingress"
namespace: "{{ app_namespace }}"
register: ingress_info
retries: 10
delay: 15
until: ingress_info.resources[0].status.loadBalancer.ingress is defined
- name: Erisim bilgisini goster
debug:
msg: "Uygulama erisim adresi: https://{{ domain_name }} (IP: {{ ingress_info.resources[0].status.loadBalancer.ingress[0].ip | default('pending') }})"
Rollback Mekanizması
Production’da bir şeyler ters gittiğinde hızlı rollback yapabilmek hayat kurtarır. İşte bunun için hazır bir playbook:
# playbooks/rollback.yml
---
- name: Uygulama Rollback
hosts: localhost
connection: local
gather_facts: false
vars:
app_name: "web-store"
app_namespace: "ecommerce"
rollback_timeout: 180
tasks:
- name: Mevcut deployment revision bilgisini al
kubernetes.core.k8s_info:
kind: Deployment
name: "{{ app_name }}"
namespace: "{{ app_namespace }}"
register: current_deploy
- name: Revision bilgisini goster
debug:
msg: |
Mevcut Image: {{ current_deploy.resources[0].spec.template.spec.containers[0].image }}
Mevcut Revision: {{ current_deploy.resources[0].metadata.annotations['deployment.kubernetes.io/revision'] | default('bilinmiyor') }}
- name: Rollback onayini iste
pause:
prompt: "Rollback yapilacak. Devam etmek icin ENTER, iptal icin Ctrl+C"
when: not ansible_check_mode
- name: Kubectl rollout undo calistir
kubernetes.core.k8s_json_patch:
kind: Deployment
name: "{{ app_name }}"
namespace: "{{ app_namespace }}"
patch:
- op: replace
path: /spec/template/metadata/annotations
value:
rollback-triggered: "{{ ansible_date_time.iso8601 }}"
when: rollback_to_image is not defined
- name: Belirli bir image'a rollback
kubernetes.core.k8s:
state: present
definition:
apiVersion: apps/v1
kind: Deployment
metadata:
name: "{{ app_name }}"
namespace: "{{ app_namespace }}"
spec:
template:
spec:
containers:
- name: "{{ app_name }}"
image: "{{ rollback_to_image }}"
when: rollback_to_image is defined
- name: Rollback tamamlanmasini bekle
kubernetes.core.k8s_rollout_status:
name: "{{ app_name }}"
namespace: "{{ app_namespace }}"
kind: Deployment
timeout: "{{ rollback_timeout }}"
- name: Pod durumlarini kontrol et
kubernetes.core.k8s_info:
kind: Pod
namespace: "{{ app_namespace }}"
label_selectors:
- "app={{ app_name }}"
register: pods_after_rollback
- name: Rollback sonrasi pod ozeti
debug:
msg: "Pod {{ item.metadata.name }}: {{ item.status.phase }}"
loop: "{{ pods_after_rollback.resources }}"
loop_control:
label: "{{ item.metadata.name }}"
Multi-Environment Yönetimi
Aynı kodu birden fazla ortamda kullanmak için inventory yapısından faydalanabilirsiniz:
# inventory/production/group_vars/all.yml
---
app_replicas: 5
environment: production
image_tag: "{{ lookup('env', 'IMAGE_TAG') }}"
domain_name: store.example.com
db_host: postgres-prod.ecommerce.svc.cluster.local
resource_cpu_request: "500m"
resource_memory_request: "512Mi"
resource_cpu_limit: "1000m"
resource_memory_limit: "1Gi"
kubeconfig_path: /home/deploy/.kube/production-config
# inventory/staging/group_vars/all.yml
---
app_replicas: 2
environment: staging
image_tag: "{{ lookup('env', 'IMAGE_TAG') | default('develop') }}"
domain_name: store-staging.example.com
db_host: postgres-staging.ecommerce.svc.cluster.local
resource_cpu_request: "250m"
resource_memory_request: "256Mi"
resource_cpu_limit: "500m"
resource_memory_limit: "512Mi"
kubeconfig_path: /home/deploy/.kube/staging-config
Bu yapıyla playbook’u ortama göre çalıştırmak son derece basit:
# Staging deploy
ansible-playbook -i inventory/staging playbooks/deploy-app.yml --ask-vault-pass
# Production deploy (image tag ile)
IMAGE_TAG=v2.1.5 ansible-playbook -i inventory/production playbooks/deploy-app.yml --ask-vault-pass
# Dry run (ne yapacagini gormek icin)
ansible-playbook -i inventory/production playbooks/deploy-app.yml --check --diff
# Sadece belirli task'lari calistir
ansible-playbook -i inventory/production playbooks/deploy-app.yml --tags "deployment,service"
CI/CD Pipeline Entegrasyonu
GitLab CI ile bu playbook’ları birleştirmek için basit ama etkili bir yaklaşım:
# .gitlab-ci.yml (ilgili kisimlar)
stages:
- build
- test
- deploy-staging
- deploy-production
variables:
ANSIBLE_HOST_KEY_CHECKING: "False"
ANSIBLE_FORCE_COLOR: "True"
.ansible-base:
image: python:3.11-slim
before_script:
- pip install ansible kubernetes
- ansible-galaxy collection install kubernetes.core
- echo "$VAULT_PASSWORD" > .vault_pass
- chmod 600 .vault_pass
- mkdir -p ~/.kube
deploy-staging:
extends: .ansible-base
stage: deploy-staging
environment: staging
script:
- echo "$KUBECONFIG_STAGING" | base64 -d > ~/.kube/config
- chmod 600 ~/.kube/config
- IMAGE_TAG=$CI_COMMIT_SHORT_SHA
ansible-playbook
-i inventory/staging
playbooks/deploy-app.yml
--vault-password-file .vault_pass
-e "image_tag=$CI_COMMIT_SHORT_SHA"
only:
- develop
deploy-production:
extends: .ansible-base
stage: deploy-production
environment: production
script:
- echo "$KUBECONFIG_PRODUCTION" | base64 -d > ~/.kube/config
- chmod 600 ~/.kube/config
- ansible-playbook
-i inventory/production
playbooks/deploy-app.yml
--vault-password-file .vault_pass
-e "image_tag=$CI_COMMIT_TAG"
only:
- tags
when: manual
Sık Yapılan Hatalar ve Çözümleri
kubeconfig yolu problemi: Ansible farklı bir kullanıcı olarak çalışıyorsa kubeconfig bulunamıyor. Bunu playbook’ta açıkça kubeconfig parametresiyle belirtmek çözüyor.
Vault şifresi CI/CD’de: --ask-vault-pass yerine --vault-password-file kullanın ve şifre dosyasını environment variable’dan oluşturun. Yukarıdaki GitLab CI örneğinde bu yaklaşımı görebilirsiniz.
Rollout timeout: Büyük cluster’larda default timeout yetersiz kalabiliyor. deployment_timeout değişkenini ortama göre artırın.
Image pull hatası: Registry credential’larını deployment’tan önce oluşturduğunuzdan emin olun. Bu yüzden pre_tasks bloğuna taşımanızı öneririm.
İdempotency sorunu: kubernetes.core.k8s modülü genellikle idempotent çalışır ama bazı annotation güncellemelerinde gereksiz restart’a neden olabilir. Sadece değişmesi gereken alanları patch ile güncelleyin.
Performans İpuçları
- Büyük cluster’larda
asyncvepollkullanarak paralel deployment yapabilirsiniz. kubernetes.core.k8s_infoile sık durum kontrolü yapmak API server’a yük bindiriyor.retriesvedelaydeğerlerini makul tutun.- Prod’da
--diffflag’ini kullanarak tam olarak neyin değiştiğini loglayın. Audit trail için çok değerli. - Playbook’larınızı rollere bölün. Uzun tek dosya playbook’lar zaman içinde yönetilemez hale geliyor.
Sonuç
Ansible ile Kubernetes deployment yönetimi başlangıçta biraz kurulum gerektiriyor ama uzun vadede ciddi zaman ve hata tasarrufu sağlıyor. Özellikle birden fazla ortamı yöneten küçük ve orta ölçekli ekipler için Helm kadar karmaşık olmadan aynı işi yapıyor.
Bu yazıda anlattığım yapıyı kendi ortamınıza adapte ederken en önemli şu noktalara dikkat edin: secret’larınızı her zaman Vault’ta tutun, rollback mekanizmasını production’a geçmeden test edin ve --check modunu CI pipeline’ınıza dahil edin.
Bir sonraki adım olarak Ansible rollerini Ansible Galaxy’ye yükleyip ekipler arasında paylaşmayı veya AWX/Ansible Tower ile web tabanlı deployment yönetimine geçişi düşünebilirsiniz. Ama önce temellerinizi sağlam atın; karmaşık araçlar basit temelin üstüne inşa edilir.
