Teknoloji

Bulutun İlk Nefesi: cloud‑init ve Ansible ile Tekrar Üretilebilir VPS Nasıl Kurulur?

Bir VPS, İlk Nefes ve Küçük Bir Aydınlanma

Geçen hafta, küçük bir VPS’i hızlıca ayağa kaldırmam gerekti. Akşamüstü güneş camdan kırılırken, “Bir kullanıcı aç, SSH’ı sıkılaştır, firewall’u yak, uygulamayı kaldır” diye diye klavyeye vuruyorum. Tam her şey bitti derken ufak bir ayar unutulmuş; root girişini kapatmamışım. Geri dön, düzelt, restart et. Hiç başınıza geldi mi? Aynı adımları defalarca tekrarlarken, bir detay gözden kaçar ve bütün heves söner. O an dedim ki, bu işin ilk nefeste düzgün ve tekrar üretilebilir olması şart.

Benim için çözüm, cloud‑init ile makine ilk kez boot eder etmez temel işleri hallettirmek, ardından Ansible ile tüm servisleri ve ince ayarları idempotent bir şekilde dağıtmak oldu. Bu yazıda, VPS’inize ilk nefesinde kullanıcı, güvenlik ve servis otomasyonunu nasıl vereceğinizi anlatacağım. Arada ufak hikayeler, bazı pratik kod örnekleri ve can sıkıcı tuzaklardan kaçınma yolları da var. Mesela şöyle düşünün: Tık diye bir butona basıyorsunuz ve beş dakika sonra aynı ayarlarla ikinci, üçüncü VPS’iniz de hazır. Güzel değil mi?

Neden İlk Boot’ta Otomasyon? Çünkü Zaman, Dikkat ve Tutarlılık

Bir sunucuyu elle kurarken ilk on dakikada yapılanlar genelde birbirine benzer. Kullanıcı açılır, sudo verilir, SSH anahtarları yüklenir, parolalı giriş kapatılır, ufw veya nftables ile kapılar ayarlanır. Yine de her seferinde ufak farklar çıkar. Bir defasında authorized_keys dosyasını yanlış izinle bırakmıştım; girişler tek tek patlamıştı. O gün anladım ki ilk boot’ta, tek bir kaynak dosyadan beslenen bir otomasyon bütün bu küçük hataları törpülüyor. Üstelik aynı adımlar, aynı sırayla uygulanınca sonuç hep aynı oluyor.

cloud‑init burada sihirli bir anahtar gibi. Sanal sunucu sağlayıcınızın kullanıcı verisi alanına bir YAML dosyası koyuyorsunuz; sunucu ilk ayağa kalktığında o dosyadaki her adımı uyguluyor. Ardından sahneyi Ansible’a bırakıyorsunuz. O da “Eğer bu paket kurulu değilse kur, bu dosya bu içerikte değilse düzelt” diyerek, tekrarladığınızda dahi aynı sonucu güvenle üretmeye devam ediyor. Yani makine ne yapacağını sizden öğreniyor, siz de klavye başında aynı komutları ezberlemekten kurtuluyorsunuz.

cloud‑init Temelleri: Küçük Bir YAML, Büyük Bir Başlangıç

cloud‑init’i ilk kez görünce insanın aklına “Bu kadarcık dosya ile tüm kurulum mu?” sorusu geliyor. Evet, çoğu zaman yetiyor. users bölümünde kullanıcıları tanımlarsınız, ssh_authorized_keys ile anahtarlarınızı yerleştirirsiniz, packages ile temel paketleri kurarsınız. Hatta write_files ile yapılandırma dosyalarını yazdırabilir, runcmd ile küçük komut dizileri çalıştırabilirsiniz. Hepsi, makine ilk kez nefes alırken.

Mesela şöyle düşünün: Deploy kullanıcısını oluşturup sudo yetkisi verelim, parolalı girişleri kapatalım, ufw ve fail2ban’ı kurup ayağa kaldıralım, sonra da Ansible ile daha ileri ayarları çekelim. Aşağıdaki kapsayıcı bir başlangıç olabilir:

#cloud-config
users:
  - name: deploy
    gecos: Deploy User
    groups: [sudo, docker]
    sudo: "ALL=(ALL) NOPASSWD:ALL"
    shell: /bin/bash
    ssh_authorized_keys:
      - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...kendi_anahtariniz...

package_update: true
package_upgrade: true
packages:
  - ufw
  - fail2ban
  - curl
  - git
  - python3
  - python3-pip

write_files:
  - path: /etc/ssh/sshd_config.d/01-hardening.conf
    permissions: '0644'
    content: |
      PasswordAuthentication no
      PermitRootLogin no
      PubkeyAuthentication yes

runcmd:
  - systemctl restart ssh
  - ufw allow OpenSSH
  - ufw allow 80,443/tcp
  - ufw --force enable
  - systemctl enable --now fail2ban
  - pip3 install --upgrade pip
  - pip3 install ansible
  - ansible-pull -U https://github.com/yourorg/infra.git -C main site.yml -i localhost, --extra-vars "env=prod"

Bu dosyayı VPS sağlayıcınızın “User data” alanına yapıştırdığınızda, sunucunuz açılır açılmaz tüm bu adımlar gerçekleşir. Ben işin “ansible-pull” kısmını çok seviyorum; sunucu, sanki mutfağa girip kendi tarif defterini raftan alıyor ve yemek yapmaya başlıyor. Tabii o defteri, yani Git deposunu düzenli tutmak da size düşüyor.

Daha fazla ayrıntı ve modül için cloud‑init belgelerine göz atmanızı öneririm. Orada sadece kullanıcı ve paketler değil, ağ ayarlarından dosya sistemine kadar pek çok konuyu ilk boot’a sığdırabileceğinizi görürsünüz.

İlk Boot’ta Kullanıcı ve SSH Güvenliği: Sakin, Net ve Tek Sefer

SSH güvenliği, tekrar üretilebilirliğin şefkatli ama titiz tarafı. Bir kere kurarsınız, sonra unutursunuz. Ama ilk kurulumda açık kapı bırakmak istemezsiniz. İlk boot’ta yeni bir kullanıcı oluşturup sudo verin, authorized_keys ile anahtarı girin, parolalı girişleri kapatın ve root girişini tamamen devre dışı bırakın. Küçük bir dosya ile koca bir riski sıfırlamış olursunuz.

SSH’ı biraz daha sevgiyle sıkılaştırmak isterseniz, bağlantı denemelerini sınırlayan ayarlar ve Fail2ban gibi ufak yardımcılar çok işe yarar. İlk günlerde bunlara gerek yokmuş gibi hissettiren şey, bir gece ansızın loglara düşen başarısız giriş denemeleriyle anlam kazanır. Kendi pratik notlarımı derlediğim “VPS’te SSH güvenliğini sıcak tutmanın yolları” başlıklı yazı da işin devamını merak edenlere fikir verebilir.

Ansible tarafında da bu işin bir rolü olur. Mesela “ssh_hardening” benzeri bir rol, belirli dosyaların içeriklerini, dosya izinlerini ve servis ayarlarını tek tek düzenler. Bir kez tanımlayıp tüm sunuculara yayarken, her seansta aynı sonucu alırsınız. Bu, beyin yorgunluğunu alıp yerine keyifli bir rutin koymak gibi.

Güvenlik Duvarı ve Fail2ban: Sessiz Kahramanları İlk Günden Sahneye Almak

Bir VPS, internete açıldığı an dikkat çeker. Bu kaçınılmaz. Bu yüzden ilk boot’ta güvenlik duvarını ayağa kaldırmak iyi hissettirir. Ufw ile başlamak kolay; sadece belli portları açar, diğerlerini kapatırsınız. Biraz daha ileri gidip kuralları detaylandırmak isterseniz, nftables ile daha zengin bir dil ve esneklik elde edersiniz. Ben, kuralların tekrar üretilebilir olmasını sevdiğim için, temel seti cloud‑init ile kurdurup, ince ayarları Ansible rolüne bırakıyorum.

nftables’ı daha keyifli kullanmak için pratik örneklerle hazırladığım “nftables ile VPS Güvenlik Duvarı Rehberi” yazısına göz atabilirsiniz. Orada rate limit ve IPv6 gibi detayları nazikçe elden geçiriyoruz. Ayrıca Fail2ban’ın ufak hapsetme hamleleri, kaba kuvvet denemelerini ilk dakikada yumuşatır. İlk boot’ta “systemctl enable –now fail2ban” demek bile çoğu durumda büyük fark yaratır.

Uygulama katmanında da küçük önlemler tatlı sonuç verir. Mesela web uygulamalarınızda aşırı istekleri biraz frenlemek isterseniz, “Nginx rate limiting ile Fail2ban’ı el ele yürütmek” hakkında anlattıklarımın, burada kurduğunuz temel güvenlik yaklaşımıyla güzelce birleştiğini göreceksiniz.

Ansible ile Servis Otomasyonu: Reçete Defterini Sunucuya Verin

cloud‑init’i, sahneye perde açan görevli gibi düşünün. Asıl oyunu oynayan genellikle Ansible oluyor. Ben “ansible-pull” yapısını seviyorum çünkü push için merkezi bir bağlantı açmak zorunda kalmıyorsunuz. Sunucu, Git deposuna gider, playbook’u çeker ve kendi kendine uygular. Mesela Nginx, Docker ve uygulama rolleri ile küçük bir platform şablonu kurmak çok doğal akıyor.

Örnek bir üst seviye playbook hayal edelim. Bu, tek makine için bile güzel; ama asıl gücünü benzer makinelere çoğalttığınızda hissediyorsunuz:

---
- name: VPS temel kurulum ve uygulama rolleri
  hosts: localhost
  connection: local
  become: true
  vars:
    domain: example.com
    env: prod
  roles:
    - role: common
    - role: ssh_hardening
    - role: docker
    - role: nginx
    - role: app_deploy

Rollerin her biri küçük, net ve idempotent olmalı. common rolü ile zaman dilimi, locale ve log rotasyonunu; ssh_hardening ile servis ayarlarını; docker ile runtime ve kullanıcı grubunu; nginx ile site tanımlarını; app_deploy ile de uygulamanızı ve environment dosyalarınızı sağlayabilirsiniz. Ansible’ın “şu dosya bu içerikte mi, değilse böyle yap” yaklaşımı sayesinde, playbook’u kaç kez çalıştırırsanız çalıştırın, sonuç aynı kalır. Bu, tekrar üretilebilirliğin en sevdiğim yanı.

Hangi modüller, hangi parametreler derken dalıp giderseniz, resmi Ansible dokümantasyonunu bir yer imi gibi kenarda tutmak çok yardımcı oluyor. Özellikle file, template, copy, service, ufw (community modüllerinde) ve docker_* modülleri bu yolculukta sık sık el sallıyor.

Servisleri İlk Boot’ta Kalıcılaştırmak: systemd ile Küçük Bir Dans

Bazen tek seferlik bir komut yetmez; bir servis tanımı gerekir. cloud‑init ile bir systemd service dosyası yazdırmak, ardından Ansible rolünde şablonla güncellemek iyi bir ikili. Mesela Docker Compose ile çalışan bir uygulamanız var; “docker compose up” demek yerine, bunu bir unit haline getirip sistem açıldığında çalışmasını istersiniz. O zaman write_files ile unit dosyasını yazdırıp, runcmd ile “systemctl enable –now” diyebilirsiniz.

write_files:
  - path: /etc/systemd/system/myapp.service
    permissions: '0644'
    content: |
      [Unit]
      Description=My App (Compose)
      After=network-online.target docker.service
      Wants=network-online.target

      [Service]
      Type=oneshot
      RemainAfterExit=yes
      WorkingDirectory=/opt/myapp
      ExecStart=/usr/bin/docker compose up -d
      ExecStop=/usr/bin/docker compose down
      Restart=on-failure
      RestartSec=5

      [Install]
      WantedBy=multi-user.target

runcmd:
  - systemctl daemon-reload
  - systemctl enable --now myapp.service

İlerleyen günlerde bu unit dosyasını Ansible’da bir template’e taşıyıp değişkenlerle beslemek daha konforlu oluyor. Versiyon yükseltirken sadece imaj etiketini değiştiriyor, playbook’u çalıştırıyorsunuz. Hepsi o. Detay sevenler için systemd service tanımları belgeleri, uzun bir akşamı keyifli bir öğrenme turuna çevirebilir.

Gizli Bilgiler, Ortam Değişkenleri ve Küçük Sırlar

Tekrar üretilebilirlik harika, ama gizli bilgiler işi biraz özen istiyor. API anahtarları, veritabanı parolaları ve benzeri sırları cloud‑init içine gömmek istemezsiniz. Bunun yerine, Ansible tarafında Vault kullanmak veya secrets’ları bir CI/CD sistemi üzerinden “ansible-pull” komutuna tek seferlik değişken olarak enjekte etmek iyi çalışıyor. Local’de test ederken sahte değerler, prod’da gerçek değerler… Hepsi aynı playbook’ta anlamlı bir akışa oturuyor.

Ortam değişkenlerini .env dosyalarında tutuyorsanız, template modülü ile owner/grup/izin ayarlarını titizlikle set edin. Ayrıca günlük dosyaları ve dökümler için logrotate veya sistemin kendi journal rotasyon ayarlarını gözden geçirmek iyi bir alışkanlık. İlk günden bu disipline girince, aylar sonra bile makine “ben hazırım” der gibi sakin çalışıyor.

Test Ederken Korkmadan Deneyin: NoCloud ve Yerel Emülasyon

“Peki bunu her seferinde gerçek bir VPS’te mi deneyeceğiz?” diye soran çok oluyor. Aslında hayır. cloud‑init’in NoCloud veri kaynağı sayesinde, user-data dosyanızı yerelde bir ISO’ya çevirip sanal bir makinede test edebilirsiniz. Küçük bir komut dizisiyle, sanki uzak bir sağlayıcıdaymış gibi ilk boot senaryosunu canlandırırsınız. Bu, özellikle yazım hatalarını yakalamak için şahane.

# user-data.yaml dosyanızı hazırlayın
cloud-localds user-data.iso user-data.yaml

# QEMU ile başlatın (örnek, minimal):
qemu-system-x86_64 
  -m 2048 
  -drive file=disk.qcow2,if=virtio 
  -cdrom user-data.iso 
  -nic user,model=virtio 
  -enable-kvm

Ben genelde küçük bir disk imajı oluşturup, user-data’yı değiştirerek tekrar tekrar deniyorum. İlk boot’ta beklediğim dosyalar ve servisler ayağa kalkınca içim rahat ediyor. cloud‑init’in NoCloud kullanımına dair örnekler için resmi belgeler yine en güvenli rehber oluyor. Test ettiğiniz akışı, canlıya aynen uyguladığınızda sürprizler azalıyor.

Küçük Tuzaklar ve Nazik Çözümler: Deneyimlerden Notlar

Bir kere, authorized_keys izinleri yüzünden anahtarlar görmezden gelinmişti. Dosyayı 0600 yapıp sahibi doğru kullanıcıya verince düzeldi. Bir başka sefer, runcmd sırasındaki bir komutun exit kodu yüzünden kalan adımlar çalışmamıştı; onu “|| true” ile yumuşatarak akışı bozmamayı seçtim. Yine de bu tür bypass’ları dikkatli kullanın; asıl hatayı saklamasın.

Bir de sıra meselesi var. cloud‑init aşamalarını düşünürken, paket kurmayı ve yazı dosyalarını yerleştirmeyi, servisleri başlatmadan önce bitirmek gerekiyor. Yoksa servis, eski ayarlarla başlıyor. Benim çözümüm: write_files biter bitmez “systemctl daemon-reload” ve ardından ilgili servislerin restart’ı. Küçük gibi görünen bu jest, gereksiz reboot’ları çöpe atıyor.

Uygulama tarafında hızla yayına çıkarken, oturum açma uç noktaları ve yönetim ekranları biraz ekstra sevgi ister. Nginx tarafında yumuşak bir rate limit, Fail2ban ile yaramaz girişimleri süzmek iyi hissettiriyor. Bu yaklaşımı, yukarıda paylaştığım kılavuzdaki pratiklerle birleştirince, “ilk gün tedbirleri” güzelce tamamlanıyor.

Güncellemeler, Rotasyon ve Küçük Bakım Törenleri

İlk boot’ta güncelleme yaptırmak iyi, ama orada kalmasın. Ansible ile periyodik görevler veya küçük bir CI pipeline’ı kurup playbook’u belirli aralıklarla yeniden çalıştırmak harika sonuç veriyor. Rollerde küçük düzeltmeler yaptıkça, tüm sunucular sessizce aynı standarda çekiliyor. Önemli nokta şu: idempotent davranış bozulmasın. Yani playbook tekrar çalışınca “yeniden yaratma” değil “aynı ayarı teyit etme” olsun.

SSH anahtarlarının rotasyonu, kullanıcı erişimlerinin gözden geçirilmesi ve sertifikaların yenilenmesi gibi işler de bu törene dahil. Bu akışı otomatikleştirdikçe, akşamüstü kahveleri gerçekten kahve tadında oluyor. Zor anlarda, ekstra güvenlik notlarına ihtiyacınız olursa “SSH güvenliğini sağlamlaştırma” yazısındaki pratik öneriler yine elinizin altında.

Kapanış: İlk Nefeste Doğan Standart

Şimdi başa dönelim. O akşamüstü, cloud‑init ve Ansible ikilisiyle kurduğum küçük düzen bana şöyle bir şey öğretti: İlk nefes ne kadar temiz ve düzenliyse, sonrası da o kadar huzurlu geçiyor. Kullanıcılar, SSH ayarları, güvenlik duvarı ve servisler ilk boot’ta yerini aldı mı, makine ikinci, üçüncü, dördüncü kez tekrarlandığında da aynı hisle ayağa kalkıyor. Bir tuşa basıp beklemek, ardından “tam oldu” demek… İnsana iyi geliyor.

Pratik bir öğütle bitireyim. cloud‑init dosyanızı küçük başlayın, çalıştıkça büyütün. Ansible rollerini minik parçalara ayırın, her birini tek bir amaca odaklayın. Test etmekten çekinmeyin; NoCloud ile yerelde prova yapmak çok rahatlatıyor. Ve unutmayın: Güvenlik ilk gün davranışıdır. Bir sonraki yazıda, bu temelin üzerine ufak bir CI/CD akışı ve sıfır kesintili dağıtım konuşalım mı? Umarım bu yazı yolunuzu aydınlatmıştır. Görüşmek üzere.

Sıkça Sorulan Sorular

VPS sağlayıcınızın "user data" alanına YAML içeriğini yapıştırırsınız. Sunucu ilk açıldığında cloud‑init bu adımları uygular. NoCloud ile yerelde ISO oluşturup test edebilirsiniz.

Sunucu Git’ten playbook’u kendi çeker, ekstra erişim açmanıza gerek kalmaz. Böylece ilk boot’ta internet erişimi olan her VPS kendini tarif defterinden kurabilir.

Önerilmez. Gizlileri Ansible Vault ile şifreleyin veya CI/CD üzerinden ansible‑pull’a çalışırken enjekte edin. Testte sahte, prod’da gerçek değer kullanarak akışı temiz tutun.