Neden Terraform ve Ansible ile VPS Otomasyonu Kurmalısınız?
Yeni bir proje için VPS altyapısını planlarken genellikle aynı sorular etrafında dönüyoruz: Hangi boyutta VPS alacağız, hangi portlar açılacak, hangi paketler kurulacak, SSH anahtarları nasıl dağıtılacak, güvenlik duvarı kuralları nasıl standart olacak? Bir projede elle yaptığınız adımları birkaç ay sonra başka bir ortamda tekrarlarken küçük farklar büyük sorunlara dönüşebilir. Bir yerde ufw açık, diğerinde kapalı; birinde swap var, diğerinde yok; PHP ayarları iki ortamda da farklı…
Terraform, desteklenen API üzerinden altyapıyı; Ansible ise sunucudaki paket ve yapılandırmaları yönetir. İki aracı birleştirmek staging ve üretim farklarını azaltabilir. Tek komutluk akış ancak erişim, değişiklik incelemesi ve hata kontrolleri tamamlandığında anlamlıdır; uygulama verileri için ayrıca doğrulanmış yedek ve geri yükleme gerekir.
Bu rehberde Terraform ve Ansible ile VPS otomasyonu için gerçek bir OpenStack sağlayıcı örneğini, Ubuntu 24.04 üzerinde Ansible rollerini ve envanter aktarımını kuruyoruz. OpenStack bölümü yalnız yetkili olduğunuz, erişilebilir bir OpenStack API için geçerlidir; DCHost’un böyle bir API veya Terraform sağlayıcısı sunduğu varsayılmaz. Önceden açılmış bir DCHost VPS’yi doğrudan Ansible envanterine ekleyebilirsiniz. Örnek HTTP başlangıç sayfası ve PHP-FPM bağlantısını hazırlar; gerçek uygulama, TLS, izleme ve geri yükleme ayrı kabul kontrolleri ister.
Genel Mimarinin Resmi: Terraform Nerede, Ansible Nerede?
Önce rolleri netleştirelim. Terraform ve Ansible aynı problemi çözmez, birbirini tamamlar:
- Terraform: Desteklenen bir provider ve API üzerinden altyapıyı yönetir. VPS, ek disk, ağ, IP ve DNS kaydı gibi hangi kaynakların yönetilebileceği seçilen entegrasyonun kapsamına bağlıdır.
- Ansible: Kurulan VPS’in içini hazırlar. Paketleri kurar, ayar dosyalarını yerleştirir, servisleri başlatır, güvenlik sertleştirmesini yapar.
Bu ikili sayesinde üç katmanınız olur:
- Katman 1 – Altyapı: VPS, ağ, IP, DNS (Terraform)
- Katman 2 – İşletim sistemi ve temel servisler: SSH, firewall, log, izleme (Ansible rolleri)
- Katman 3 – Uygulama: PHP/Node.js/Java, veritabanı, cache, kodun deploy’u (Yine Ansible rolleri + CI/CD)
Terraform tarafındaki akışın daha derinine girmek isterseniz, blogda yer alan Terraform ile VPS ve DNS otomasyonu yazısında DNS ve çok bölgeli yapılarla birleştirilmiş daha gelişmiş bir senaryoyu da inceleyebilirsiniz.
Terraform ile VPS Altyapısını Kodlamak
Temel Kavramlar: Provider, Resource, State
Terraform’un üç temel kavramını netleştirelim:
- Provider: API ile konuşan eklenti. Burada
terraform-provider-openstack/openstack3.4.0 kullanılır; kaynak şeması bu sürüme aittir. - Resource: Sağlayıcının yönettiği nesne; örnekte SSH public key kaydı ve sanal makine. Ek disk ve DNS gibi kaynaklar ilgili API yetkisini de gerektirir.
- State: Kod ile yönetilen gerçek kaynaklar arasındaki eşleştirmeleri tutar. Plan; yapılandırma, state ve sağlayıcıdan okunan bilgilerle oluşur. State gerçek sistemin değişmez veya eksiksiz kopyası değildir.
Basit bir dizin yapısıyla başlayalım:
infra/
main.tf
variables.tf
outputs.tf
cloud-init.yml
terraform.tfvars
.terraform.lock.hcl
OpenStack ile Sürümü Sabitlenmiş Terraform Örneği
Örnek Terraform 1.17.1 ve OpenStack provider 3.4.0 için hazırlanmıştır. Yetkili clouds.yaml profilinizin adını ve bölgesini kullanın; kimlik bilgilerini bu depoya taşımayın. Ubuntu 24.04 cloud image, flavor, ağ ve güvenlik grubu sizin bulutunuzda mevcut olmalıdır. Ağın sabit IPv4 adresine Ansible çalıştırıcısından yol bulunmalıdır; bu örnek floating IP veya genel internet erişimi oluşturmaz. SSH portu 22 ve ubuntu kullanıcısı seçilen imajla ayrıca doğrulanmalıdır.
infra/main.tf
terraform {
required_version = "= 1.17.1"
required_providers {
openstack = {
source = "terraform-provider-openstack/openstack"
version = "= 3.4.0"
}
}
}
provider "openstack" {
cloud = var.cloud
region = var.region
}
resource "openstack_compute_keypair_v2" "runner" {
name = "${var.name_prefix}-runner"
public_key = file(pathexpand(var.ssh_public_key_file))
}
resource "openstack_compute_instance_v2" "app" {
count = var.vps_count
name = "${var.name_prefix}-${count.index + 1}"
image_id = var.image_id
flavor_id = var.flavor_id
key_pair = openstack_compute_keypair_v2.runner.name
security_groups = var.security_group_names
user_data = file("${path.module}/cloud-init.yml")
metadata = { environment = var.name_prefix, role = "app" }
network {
uuid = var.network_id
access_network = true
}
}
Değişkenleri variables.tf içinde tanımlayalım:
infra/variables.tf
variable "cloud" { type = string }
variable "region" { type = string }
variable "image_id" { type = string }
variable "flavor_id" { type = string }
variable "network_id" { type = string }
variable "security_group_names" { type = list(string) }
variable "ssh_public_key_file" { type = string }
variable "name_prefix" {
type = string
validation {
condition = can(regex("^[a-z][a-z0-9-]{0,30}$", var.name_prefix))
error_message = "Use a short lowercase host prefix."
}
}
variable "vps_count" {
type = number
default = 1
validation {
condition = var.vps_count >= 1 && var.vps_count <= 20 && floor(var.vps_count) == var.vps_count
error_message = "Use an integer between 1 and 20."
}
}
terraform.tfvars.example içeriğini infra/terraform.tfvars olarak kaydedip REPLACE_… değerlerinin tamamını kendi yetkili ortamınızdan doldurun. SSH dosyası yalnız public key içermelidir; kısaltılmış bir anahtar veya private key kullanmayın. Sağlayıcı kimlik bilgileri erişimi kısıtlanmış profilinizde kalsın. Her ortam için ayrı çalışma dizini ve ayrı state/erişim sınırı kullanın.
infra/terraform.tfvars.example
cloud = "REPLACE_AUTHORIZED_CLOUD_NAME"
region = "REPLACE_REGION"
image_id = "REPLACE_UBUNTU_24_04_IMAGE_UUID"
flavor_id = "REPLACE_FLAVOR_ID"
network_id = "REPLACE_RUNNER_REACHABLE_NETWORK_UUID"
security_group_names = ["REPLACE_REVIEWED_SECURITY_GROUP_NAME"]
ssh_public_key_file = "~/.ssh/id_ed25519.pub"
name_prefix = "app-staging"
vps_count = 1
Aşağıdaki komutları proje kökünden çalıştırın. cloud-init.yml dosyasını da bir sonraki bölümdeki içerikle oluşturmuş olun. Kaydedilen planı inceleyin; doğru hesap, bölge, kaynak sayısı, değiştirme ve silme işlemlerini doğruladıktan sonra apply komutunu çalıştırın. Kaydedilmiş planla apply ayrıca onay sormaz. Yeni bir değişiklikte yeni plan üretin; başarısız veya kısmen uygulanmış işlemden sonra eski planı tekrar kullanmayın.
umask 077
terraform -chdir=infra init
terraform -chdir=infra validate
terraform -chdir=infra plan -out=reviewed.tfplan
# Planı inceleyip uygulama kararını verdikten sonra:
terraform -chdir=infra apply reviewed.tfplan
Burada kritik nokta: Sır içermeyen Terraform tanımlarını Git deposunda tutarak kodda yapılan değişiklikleri takip edebilirsiniz. Ortama özel terraform.tfvars dosyasını ve hassas veri içerebilen state/plan dosyalarını depoya eklemeyin; kimlik bilgileri bu örnekte ayrı cloud profilinde tutulur. Commit geçmişi amaçlanan değişiklikleri gösterir; hangi değişikliğin gerçekten uygulandığını izlemek için ayrıca uygulama kayıtları gerekir.
cloud-init ile İlk Nefes: Ansible’a Hazırlık
Bu örnek, OpenStack metadata/config-drive desteğiyle uyumlu Ubuntu cloud image’in varsayılan kullanıcısını ve public key aktarımını kullanır. user_data dosyası Python ve sudo paketlerini hazırlar. Bir kullanıcı oluşturma, farklı anahtar dağıtımı veya başka bir imaj gerekiyorsa cloud-init’in users ve ssh_authorized_keys alanlarını o imajda test edin. User data içine sır koymayın; bu içerik state ve sağlayıcı tarafında görülebilir. Başarılı sanal makine oluşturma, cloud-init’in tamamlandığını göstermez.
infra/cloud-init.yml
#cloud-config
package_update: true
packages:
- python3
- python3-apt
- sudo
# Ubuntu cloud image's default user and OpenStack-injected public key are used.
# Verify the image's username, SSH policy and sudo access before using it.
Bu mimariyi daha derinlemesine anlatan cloud-init ve Ansible ile tekrar üretilebilir VPS kurulumu yazısına mutlaka göz atmanızı öneririm; burada ise daha çok Terraform + Ansible entegrasyonuna odaklanacağız.
Ansible ile VPS Konfigürasyonunu Kodlamak
Envanter, Playbook ve Roller
Ansible tarafında temel yapı taşlarımız:
- Inventory: Hangi sunucular var, IP adresleri, gruplar, değişkenler.
- Playbook: Hangi gruplara hangi rollerin uygulanacağını söyleyen orkestrasyon dosyası.
- Role: Belirli bir işi üstlenen, yeniden kullanılabilir görev paketleri (örneğin nginx, php, security_hardening rolü).
Aşağıdaki dizin yapısını proje boyunca koruyun. Yönetim makinesinde Python 3.13, Terraform 1.17.1, Bash, GNU Make ve SSH istemcisi bulunmalıdır. Ansible core 2.20.11 ile community.general 14.0.0 birlikte sabitlenir. Paket güncellemesinde destek matrisi ve porting notlarını inceleyip testleri tekrarlayın; bu sabitleme sürümleri süresiz güncel yapmaz.
infra-project/
Makefile
.gitignore
infra/
main.tf
variables.tf
outputs.tf
cloud-init.yml
terraform.tfvars
.terraform.lock.hcl
config/
ansible.cfg
requirements.yml
collections/
inventory/
hosts.json
group_vars/app.yml
playbooks/
site.yml
hardening.yml
roles/
common/tasks/main.yml
webserver/
tasks/main.yml
files/validate-site
templates/app.conf.j2
hardening/tasks/main.yml
scripts/generate_inventory.sh
config/ansible.cfg
[defaults]
inventory = ./inventory/hosts.json
roles_path = ./roles
collections_path = ./collections
host_key_checking = True
retry_files_enabled = False
config/requirements.yml
---
collections:
- name: community.general
version: 14.0.0
# From the repository root; Python 3.13 and Terraform 1.17.1 installed.
python3.13 -m venv .venv
. .venv/bin/activate
python -m pip install 'ansible-core==2.20.11'
(cd config && ansible-galaxy collection install -r requirements.yml -p collections)
ansible.cfg, config/ içinden okunduğunda roller için ./roles, koleksiyonlar için ./collections yolunu açıkça belirler. Depoyu dünya tarafından yazılabilir bir dizine koymayın. Komutlar bu nedenle cd config veya parantezli alt kabuk kullanır.
Basit Bir Inventory Örneği
Önceden açılmış bir DCHost VPS için Terraform adımlarını atlayıp bu envanteri doldurabilirsiniz. 192.0.2.10 yalnız dokümantasyon adresidir; gerçek, size ait VPS adresiyle değiştirin. SSH host key parmak izini güvenilir konsol veya sağlayıcı kaydıyla doğrulayın; host key kontrolünü kapatmayın. Non-root kullanıcı, anahtarlı SSH ve bu örneğin kullandığı parolasız sudo yetkisi çalışmalı; hedef Ubuntu 24.04 ve Python hazır olmalıdır. cloud_init_wait yalnız bu hizmeti kullanan imajlarda true olmalıdır.
config/inventory/hosts.json
{
"app": {
"hosts": {
"app-staging-1": {
"ansible_host": "192.0.2.10",
"ansible_user": "ubuntu",
"ansible_port": 22,
"ansible_python_interpreter": "/usr/bin/python3",
"cloud_init_wait": false
}
}
}
}
config/inventory/group_vars/app.yml
---
app_server_name: app.example.com
app_timezone: Etc/UTC
# Set to true only after the fresh-host preflight described in the article.
fresh_host_reviewed: false
app_server_name değerini size ait, aynı Nginx üzerinde başka siteye atanmamış alan adıyla değiştirin. fresh_host_reviewed ancak yeni/ayrılmış hedefte paket, port, dosya yolu, SSH ve sudo kontrollerini yaptıktan sonra true olmalıdır. /var/www/iac-app, validator ve site dosyası yollarını bu örneğe ayırın; cPanel/Plesk gibi mevcut bir panelin yönettiği Nginx’e bu rolü uygulamayın.
Ardından basit bir playbook:
config/playbooks/site.yml
---
- name: Configure the reviewed Ubuntu web hosts
hosts: app
become: true
gather_facts: false
serial: 1
any_errors_fatal: true
pre_tasks:
- name: Wait for SSH and a working Python module transport
ansible.builtin.wait_for_connection:
timeout: 300
- name: Wait for cloud-init before using APT
ansible.builtin.command: cloud-init status --wait
changed_when: false
when: cloud_init_wait | default(false) | bool
- name: Gather facts
ansible.builtin.setup:
- name: Require the documented target and completed preflight
ansible.builtin.assert:
that:
- ansible_facts.distribution == 'Ubuntu'
- ansible_facts.distribution_version == '24.04'
- ansible_user != 'root'
- fresh_host_reviewed | bool
- app_server_name is match('^[a-z0-9]+([.-][a-z0-9]+)*$')
fail_msg: Review SSH/sudo, network and existing services before enabling this example.
roles:
- common
- webserver
common rolü paket önbelleğini gerektiğinde yeniler, yardımcı araçları ve timezone bağımlılıklarını kurar. community.general.timezone ayrı koleksiyondandır; Ubuntu 24.04 için tzdata ve util-linux-extra eklenmiştir. Rol tam sistem yükseltmesi yapmaz. Güncelleme/reboot politikasını bakım penceresiyle planlayın; zaman dilimi değişiminden etkilenen cron görevlerini de kontrol edin.
config/roles/common/tasks/main.yml
---
- name: Install base tools and timezone dependencies
ansible.builtin.apt:
name: [curl, git, htop, tzdata, util-linux-extra]
state: present
update_cache: true
cache_valid_time: 3600
- name: Set the system timezone
community.general.timezone:
name: "{{ app_timezone }}"
# Package upgrades, SSH and firewall changes use a separate reviewed rollout.
Web rolü Ubuntu paketleriyle Nginx ve php8.3-fpm kurar. APT’nin servis başlatmasını policy_rc_d: 101 ile engeller; mevcut çalışan servislerin varlığı ayrıca değerlendirilir. Varsayılan ve diğer siteleri silmez. Yalnız sahiplik işaretli iac-app.conf dosyasını yönetir; önce aday şablonu, sonra tüm kurulu Nginx ağacını kontrol eder. İkinci kontrolde hata veya uyarı varsa bu role ait site değişikliklerini geri almaya çalışır ve durur. Paket kurulumu veya dışarıdan eşzamanlı değişiklikler bu geri dönüşün kapsamı dışındadır.
config/roles/webserver/templates/app.conf.j2
# Managed by iac-app example
server {
listen 80;
server_name {{ app_server_name }};
root /var/www/iac-app;
index index.html index.php;
location / {
try_files $uri $uri/ =404;
}
location ~ /\. {
deny all;
}
location ~ \.php$ {
try_files $uri =404;
include /etc/nginx/fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
}
config/roles/webserver/files/validate-site
#!/usr/bin/env bash
set -euo pipefail
candidate=${1:?candidate required}
work=$(mktemp -d)
trap 'rm -rf -- "$work"' EXIT
cp -- "$candidate" "$work/site.conf"
cat > "$work/nginx.conf" <<EOF
events {}
http {
include /etc/nginx/mime.types;
include $work/site.conf;
}
EOF
/usr/sbin/nginx -t -c "$work/nginx.conf"
config/roles/webserver/tasks/main.yml
---
- name: Install the Ubuntu packages without automatic service starts
ansible.builtin.apt:
name: [nginx, php8.3-fpm]
state: present
policy_rc_d: 101
- name: Check the existing complete Nginx configuration
ansible.builtin.command: /usr/sbin/nginx -t
changed_when: false
- name: Inspect the owned site file
ansible.builtin.stat:
path: /etc/nginx/sites-available/iac-app.conf
follow: false
register: old_site
- name: Inspect the owned enable link
ansible.builtin.stat:
path: /etc/nginx/sites-enabled/iac-app.conf
follow: false
register: old_link
- name: Reject path collisions
ansible.builtin.assert:
that:
- not old_site.stat.exists or old_site.stat.isreg | default(false)
- not old_link.stat.exists or (old_link.stat.islnk | default(false) and old_link.stat.lnk_source == '/etc/nginx/sites-available/iac-app.conf')
- name: Read the previous site if present
ansible.builtin.slurp:
src: /etc/nginx/sites-available/iac-app.conf
register: old_bytes
when: old_site.stat.exists
- name: Require the ownership marker before updating an existing file
ansible.builtin.assert:
that:
- (old_bytes.content | b64decode).startswith('# Managed by iac-app example\n')
when: old_site.stat.exists
- name: Install the candidate syntax validator
ansible.builtin.copy:
src: validate-site
dest: /usr/local/sbin/validate-iac-site
owner: root
group: root
mode: '0755'
- name: Create the example document root
ansible.builtin.file:
path: /var/www/iac-app
state: directory
owner: root
group: root
mode: '0755'
- name: Write the static bootstrap page
ansible.builtin.copy:
content: "IaC HTTP bootstrap ready\n"
dest: /var/www/iac-app/index.html
owner: root
group: root
mode: '0644'
- name: Install and validate the owned site
block:
- name: Validate the candidate before replacing the site
ansible.builtin.template:
src: app.conf.j2
dest: /etc/nginx/sites-available/iac-app.conf
owner: root
group: root
mode: '0644'
backup: true
validate: /usr/local/sbin/validate-iac-site %s
register: site_write
- name: Enable only this owned site
ansible.builtin.file:
src: /etc/nginx/sites-available/iac-app.conf
dest: /etc/nginx/sites-enabled/iac-app.conf
state: link
register: link_write
- name: Validate all installed sites together
ansible.builtin.command: /usr/sbin/nginx -t
register: nginx_complete_check
changed_when: false
failed_when: nginx_complete_check.rc != 0 or '[warn]' in nginx_complete_check.stderr
rescue:
- name: Restore the previous owned site content
ansible.builtin.copy:
content: "{{ old_bytes.content | b64decode }}"
dest: /etc/nginx/sites-available/iac-app.conf
owner: "{{ old_site.stat.uid }}"
group: "{{ old_site.stat.gid }}"
mode: "{{ old_site.stat.mode }}"
when: old_site.stat.exists
- name: Remove only the newly created owned link
ansible.builtin.file:
path: /etc/nginx/sites-enabled/iac-app.conf
state: absent
when: not old_link.stat.exists
- name: Remove only the newly created owned file
ansible.builtin.file:
path: /etc/nginx/sites-available/iac-app.conf
state: absent
when: not old_site.stat.exists
- name: Stop after the failed deployment
ansible.builtin.fail:
msg: Validation failed; owned site restored where possible. Inspect the failure before retrying.
- name: Validate the installed PHP-FPM configuration
ansible.builtin.command: /usr/sbin/php-fpm8.3 -t
changed_when: false
- name: Ensure PHP-FPM is running
ansible.builtin.service:
name: php8.3-fpm
state: started
enabled: true
- name: Read service state before start or enable
ansible.builtin.service_facts:
- name: Remember whether Nginx was already running
ansible.builtin.set_fact:
nginx_was_running: "{{ ansible_facts.services.get('nginx.service', {}).get('state', 'stopped') == 'running' }}"
- name: Ensure validated Nginx is running
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: Reload validated Nginx even after an earlier activation failure
ansible.builtin.service:
name: nginx
state: reloaded
when: nginx_was_running
Şablon statik başlangıç sayfasını sunar; mevcut gerçek PHP dosyalarını Ubuntu’nun /run/php/php8.3-fpm.sock soketine iletir. PHP uygulaması oluşturmaz. Validator geçici bir tam yapılandırma kurar; aday dosyanın tek başına geçmesi diğer sitelerle uyumu kanıtlamadığı için ikinci kontrol zorunludur. Nginx ve PHP-FPM doğrulamaları ile gerekli servis işlemleri başarılı olduktan sonra, önceden çalışan Nginx dosya değişmese de her çalıştırmada reload edilir. Böylece dosya kaydedildikten sonra PHP kontrolü, servis işlemi veya reload başarısız olmuşsa sonraki başarılı deneme yapılandırmayı devreye alır. Başlangıçta durmuş Nginx doğrulamadan sonra başlatılır; o çalıştırmada ek reload yapılmaz. Hatalı doğrulama veya servis işlemi akışı durdurur. Servis, dosya erişimi ve uygulama yanıtını dağıtımdan sonra ayrıca kontrol edin.
Proje kökünde sanal ortam etkin durumdayken envanteri, sözdizimini ve ardından gerçek hedefte sonucu kontrol edin. --syntax-check rol keşfini ve sözdizimini kontrol eder; SSH, sudo, APT, servis veya uygulama başarısını kanıtlamaz. --check de tam çalışma garantisi değildir. İlk kurulumdan sonra aynı hedefte ikinci çalıştırmanın değişikliklerini inceleyerek beklenen idempotence davranışını ölçün. Bu örnekte doğrulanmış, önceden çalışan Nginx ile doğrulanmış SSH için her başarılı çalıştırmada reload yapılması kasıtlıdır ve changed olarak raporlanır; tüm görevlerin ikinci çalıştırmada değişikliksiz kalması beklenmez.
# From the repository root, with .venv active.
(cd config && ansible-inventory --graph)
(cd config && ansible-playbook playbooks/site.yml --syntax-check)
(cd config && ansible-playbook playbooks/site.yml)
# Kendi VPS adresiniz ve alan adınızla gerçek yanıtı kontrol edin:
curl --fail --show-error --resolve app.example.com:80:192.0.2.10 http://app.example.com/
Güvenlik Sertleştirmesini Role Haline Getirmek
SSH ve firewall değişikliklerini web kurulumundan ayrı, tek hedefle başlayarak uygulayın. Önce yönetici hesabını oluşturup public key’i o hesaba dağıtın; gerekiyorsa sudoers kuralını visudo -cf ile doğrulayın. Örnekte mevcut ubuntu hesabı kullanılır: anahtarlı yeni bir SSH oturumunda sudo çalıştığını ve acil konsol erişimini gerçekten test etmeden aşağıdaki iki bayrağı true yapmayın. management_source_ip, NAT/bastion sonrasında sunucunun gördüğü gerçek yönetim adresidir; CIDR bu adresi kapsamalıdır. Bu rol değiştirilmeden kullanılacaksa ansible_host sunucunun bağlantıyı aldığı sayısal yerel IP adresi, ansible_port ise sunucudaki gerçek SSH portu olmalıdır. Hedef adresi veya portu NAT ile çevriliyorsa ya da envanterde hostname kullanılıyorsa, önce sshd -T -C içindeki laddr/lport değerlerini ve UFW portunu sunucunun gördüğü yerel değerlere göre ayrı değişkenlerle uyarlayın; dış bağlantı adresini yerel adres sanarak bu rolü çalıştırmayın. IPv4/IPv6 kurallarını, mevcut geniş izinleri ve diğer servis portlarını önceden inceleyin.
- SSH drop-in’i için tam yapılandırmayı ve yönetici/root bağlamındaki etkili ayarları kontrol edin. Include sırası veya Match bloğu hedef politikayı bozuyorsa reload yapılmadan durulsun.
- UFW’de mevcut yönetim CIDR/port iznini ekledikten sonra inbound default-deny uygulayın. Sayısal portlar, henüz kurulmamış bir uygulama profiline bağımlılığı kaldırır. Mevcut izinleri otomatik kaldırmaz.
- Fail2ban ve unattended-upgrades paketlerini kurmak tek başına etkin koruma/politika değildir. Jail, backend, yönetim istisnaları, güncelleme/reboot penceresi ve logları ayrı yapılandırıp doğrulayın.
config/inventory/group_vars/hardening-inputs.example.yml
# Merge reviewed values into inventory/group_vars/app.yml; this file is not loaded.
admin_access_verified: false
console_access_verified: false
management_source_ip: 192.0.2.50
management_source_hostname: runner.example.com
management_cidr: 192.0.2.50/32
config/playbooks/hardening.yml
---
- name: Apply the separately reviewed access policy
hosts: app
become: true
serial: 1
any_errors_fatal: true
roles:
- hardening
config/roles/hardening/tasks/main.yml
---
- name: Require real access checks, console recovery and explicit network inputs
ansible.builtin.assert:
that:
- ansible_facts.distribution == 'Ubuntu'
- ansible_facts.distribution_version == '24.04'
- ansible_user != 'root'
- admin_access_verified | default(false) | bool
- console_access_verified | default(false) | bool
- management_source_ip is defined
- management_source_hostname is defined
- management_cidr is defined
- management_source_hostname is match('^[a-zA-Z0-9.-]+$')
- ansible_user is match('^[a-z_][a-z0-9_-]*$')
- name: Check that the actual management source is allowed
ansible.builtin.command:
argv:
- python3
- -c
- 'import ipaddress,sys; assert ipaddress.ip_address(sys.argv[1]) in ipaddress.ip_network(sys.argv[2], strict=False)'
- "{{ management_source_ip }}"
- "{{ management_cidr }}"
delegate_to: localhost
become: false
changed_when: false
- name: Verify sudo before changing access
ansible.builtin.command: /usr/bin/true
changed_when: false
- name: Install hardening tools without activating services
ansible.builtin.apt:
name: [ufw, fail2ban, unattended-upgrades]
state: present
update_cache: true
cache_valid_time: 3600
policy_rc_d: 101
- name: Inspect the owned SSH drop-in
ansible.builtin.stat:
path: /etc/ssh/sshd_config.d/00-iac-access.conf
follow: false
register: old_ssh
- name: Reject non-file collisions
ansible.builtin.assert:
that:
- not old_ssh.stat.exists or old_ssh.stat.isreg | default(false)
- name: Preserve previous owned SSH bytes
ansible.builtin.slurp:
src: /etc/ssh/sshd_config.d/00-iac-access.conf
register: old_ssh_bytes
when: old_ssh.stat.exists
- name: Require ownership marker
ansible.builtin.assert:
that:
- (old_ssh_bytes.content | b64decode).startswith('# Managed by iac-access example\n')
when: old_ssh.stat.exists
- name: Install and check the effective SSH policy
block:
- name: Write the owned SSH drop-in
ansible.builtin.copy:
content: |
# Managed by iac-access example
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
dest: /etc/ssh/sshd_config.d/00-iac-access.conf
owner: root
group: root
mode: '0600'
backup: true
register: ssh_write
- name: Validate the complete SSH configuration
ansible.builtin.command: /usr/sbin/sshd -t
changed_when: false
- name: Read effective policy for the administrator and root
ansible.builtin.command:
argv:
- /usr/sbin/sshd
- -T
- -C
- "user={{ item }},addr={{ management_source_ip }},host={{ management_source_hostname }},laddr={{ ansible_host }},lport={{ ansible_port | default(22) }}"
loop: ["{{ ansible_user }}", root]
register: ssh_effective
changed_when: false
- name: Reject ignored or overridden authentication settings
ansible.builtin.assert:
that:
- "'permitrootlogin no' in item.stdout_lines"
- "'passwordauthentication no' in item.stdout_lines"
- "'kbdinteractiveauthentication no' in item.stdout_lines"
- "'pubkeyauthentication yes' in item.stdout_lines"
loop: "{{ ssh_effective.results }}"
rescue:
- name: Restore previous owned SSH content
ansible.builtin.copy:
content: "{{ old_ssh_bytes.content | b64decode }}"
dest: /etc/ssh/sshd_config.d/00-iac-access.conf
owner: "{{ old_ssh.stat.uid }}"
group: "{{ old_ssh.stat.gid }}"
mode: "{{ old_ssh.stat.mode }}"
when: old_ssh.stat.exists
- name: Remove a newly created owned SSH drop-in
ansible.builtin.file:
path: /etc/ssh/sshd_config.d/00-iac-access.conf
state: absent
when: not old_ssh.stat.exists
- name: Stop before reload
ansible.builtin.fail:
msg: SSH validation failed. Inspect includes, earlier settings and Match blocks.
- name: Reload validated SSH even after an earlier activation failure
ansible.builtin.service:
name: ssh
state: reloaded
- name: Reconnect after SSH policy change
ansible.builtin.meta: reset_connection
- name: Require fresh SSH access
ansible.builtin.wait_for_connection:
timeout: 60
- name: Allow the actual management CIDR and SSH port before enabling UFW
community.general.ufw:
rule: allow
from_ip: "{{ management_cidr }}"
port: "{{ ansible_port | default(22) }}"
proto: tcp
- name: Allow public HTTP and HTTPS by numeric port
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop: ['80', '443']
- name: Enable inbound default-deny policy
community.general.ufw:
state: enabled
direction: incoming
policy: deny
- name: Reconnect after firewall policy change
ansible.builtin.meta: reset_connection
- name: Verify management access again
ansible.builtin.wait_for_connection:
timeout: 60
# Substitute the verified host, account, key and actual SSH port.
ssh -o StrictHostKeyChecking=yes -o BatchMode=yes -o PasswordAuthentication=no -o KbdInteractiveAuthentication=no -i ~/.ssh/id_ed25519 -p 22 ubuntu@192.0.2.10 'sudo -n true'
# After merging reviewed inputs into inventory/group_vars/app.yml:
(cd config && ansible-playbook playbooks/hardening.yml --syntax-check)
(cd config && ansible-playbook playbooks/hardening.yml)
SSH tam ve etkili yapılandırma kontrolleri geçince, drop-in değişmese de her başarılı çalıştırmada reload edilir. Bu tekrar, dosya kaydedildikten sonra başarısız olan reload işleminin sonraki denemede atlanmasını önler; reload hatası akışı durdurur. Etkin ayar kontrolü verilen bağlantı bağlamlarını kapsar; tüm kullanıcı/ağ Match kombinasyonlarını ispatlamaz. SSH veya firewall sonrası bağlantı kaybında Ansible rescue her zaman çalışamaz; test edilmiş konsoldan geri dönüş gerekir. Yeni bağlantıyı doğrulamadan eski oturumu kapatmayın.
Bu konuları derinlemesine ele aldığımız VPS güvenlik sertleştirme kontrol listesi ve SSH güvenliğini sağlamlaştırma yazılarını, Ansible rolünüzü tasarlarken referans rehber gibi kullanabilirsiniz.
Terraform Çıktısını Ansible Envanterine Bağlamak
Terraform’un ürettiği sabit IPv4 adreslerini JSON envanterine aktaracağız. Bu aktarım yeni makine oluşturmaz, sunucuya erişimi de kanıtlamaz. Doğrudan DCHost envanteri kullanıyorsanız bu adımı atlayıp make configure çalıştırın.
Terraform Çıktısıyla Envanter Üretmek
Aşağıdaki outputs.tf hem adres listesini hem Ansible’ın okuyacağı nesneyi üretir. Adresler public IP garantisi taşımaz; çalışma makinenizin ağ rotası, SSH host key kaydı ve izinleri hazır olmalıdır.
infra/outputs.tf
output "app_ips" {
description = "Fixed IPv4 addresses; the runner must have a route to this network."
value = [for s in openstack_compute_instance_v2.app : s.access_ip_v4]
}
output "ansible_inventory" {
value = {
app = {
hosts = {
for s in openstack_compute_instance_v2.app : s.name => {
ansible_host = s.access_ip_v4
ansible_user = "ubuntu"
ansible_port = 22
ansible_python_interpreter = "/usr/bin/python3"
cloud_init_wait = true
}
}
}
}
}
Script’i scripts/generate_inventory.sh olarak kaydedip çalıştırılabilir yapın. Terraform hatası, boş envanter veya geçersiz adres durumunda var olan hosts.json korunur. Başarılı kontrol sonrası aynı dizindeki geçici dosya atomik rename ile yerini alır. Üretilen hedef listesini çalıştırmadan önce yine inceleyin; root yetkili bir otomasyonun girdileri güvenilir olmalıdır.
scripts/generate_inventory.sh
#!/usr/bin/env bash
set -euo pipefail
cd -- "$(dirname -- "$0")/.."
umask 077
mkdir -p config/inventory
raw=$(mktemp config/inventory/.terraform.XXXXXX)
candidate=$(mktemp config/inventory/.inventory.XXXXXX.json)
trap 'rm -f -- "$raw" "$candidate"' EXIT
terraform -chdir=infra output -json ansible_inventory > "$raw"
python3 - "$raw" "$candidate" <<'PYTHON'
import ipaddress, json, re, sys
from pathlib import Path
value = json.loads(Path(sys.argv[1]).read_text())
hosts = value['app']['hosts']
assert isinstance(hosts, dict) and hosts, 'Empty or invalid app inventory'
for name, host in hosts.items():
assert re.fullmatch(r'[a-z][a-z0-9-]{0,40}', name), 'Invalid host name'
ipaddress.IPv4Address(host['ansible_host'])
assert host['ansible_user'] == 'ubuntu'
assert host['ansible_port'] == 22
assert host['ansible_python_interpreter'] == '/usr/bin/python3'
assert host['cloud_init_wait'] is True
Path(sys.argv[2]).write_text(json.dumps(value, indent=2) + '\n')
PYTHON
(cd config && ansible-inventory -i "${candidate#config/}" --list > /dev/null)
mv -- "$candidate" config/inventory/hosts.json
chmod 0755 scripts/generate_inventory.sh
Artık akışımız şöyle olabilir:
- Doğru ortam dizininde yeni plan üretin ve değişiklikleri inceleyin.
- İncelenen planı uygulayın; başarılıysa envanteri oluşturun.
- Envanter/host key erişimini doğrulayın ve Ansible ile hedefi yapılandırın.
İncelenmiş adımları bir Makefile ile birleştirebilirsiniz. Ancak yeni veya yeniden oluşturulan hedeflerde ilk çalıştırmayı bölün: önce incelenmiş Terraform planını uygulayın ve envanteri üretin; sonra hedef listesini, host key’leri, SSH/sudo erişimini ve yeni sunucu ön kontrollerini doğrulayın. Ancak bundan sonra fresh_host_reviewed değerini true yapıp make configure çalıştırın. Aşağıdaki make all, apply ile Ansible arasında bu inceleme için durmaz; yalnız hedef kimliği ve erişim ön kontrolleri geçerliliğini koruyan değişikliklerde kullanın.
Makefile ile Tek Komutluk Akış
Makefile
SHELL := /bin/bash
.SHELLFLAGS := -eu -o pipefail -c
.NOTPARALLEL:
.PHONY: plan all configure destroy
plan:
umask 077; rm -f infra/reviewed.tfplan; terraform -chdir=infra init; terraform -chdir=infra validate; terraform -chdir=infra plan -out=reviewed.tfplan
all:
test -f infra/reviewed.tfplan
read -r -p 'Apply the reviewed plan in this checkout? Type APPLY: ' answer; test "$$answer" = APPLY
terraform -chdir=infra apply reviewed.tfplan
rm -- infra/reviewed.tfplan
./scripts/generate_inventory.sh
cd config && ansible-playbook playbooks/site.yml
configure:
cd config && ansible-playbook playbooks/site.yml
destroy:
umask 077; rm -f infra/destroy.tfplan; terraform -chdir=infra plan -destroy -out=destroy.tfplan
read -r -p 'Destroy resources in this reviewed plan? Type DESTROY: ' answer; test "$$answer" = DESTROY
terraform -chdir=infra apply destroy.tfplan
rm -- infra/destroy.tfplan
all: infra config gibi bağımsız hedefler paralel Make altında yarışabilir. Burada tek tarif ve .NOTPARALLEL kullanılır; hata sonraki komutu durdurur. Bu yalnız aynı Make sürecini sıralar: eşzamanlı başka çalıştırıcıları CI kilidi ve backend state kilidiyle engelleyin. Ortam seçimi için aynı checkout’ta yalnız tfvars değiştirerek üretime geçmeyin.
Önce yeni planı üretip okuyun; ardından aynı checkout’ta onaylı planı uygulayın. İkinci komut APPLY yanıtı ister:
make plan
# Planı inceleyin.
make all
Adım adım şu işlemler gerçekleşir:
- Yetkili OpenStack ortamında incelenmiş kaynak değişiklikleri uygulanır.
- Başarılı çıktıdan JSON envanteri oluşturulur; önceki dosya doğrulama tamamlanana kadar korunur.
- Ansible temel paket, zaman dilimi ve HTTP/PHP-FPM rolünü uygular. SSH/firewall politikası ayrı
hardening.ymlaşamasıdır.
Felaket kurtarmada altyapıyı yeniden oluşturmak yalnız bir adımdır. Yeni planın kaynak kimliklerini doğrulayın, verileri yedekten geri yükleyin, DNS/TLS ve uygulama kontrollerini tamamlayın. Gerçek RTO/RPO’yu bu işlemleri içeren bir tatbikatla ölçün; sabit bir saniye/dakika sonucu varsaymayın. Bu konuyu daha geniş ölçekteki felaket kurtarma planları ile birleştirmek isterseniz, felaket kurtarma planı nasıl yazılır yazımız size iyi bir çerçeve sunar.
Güvenlik, Secrets ve Erişim Yönetimi
API Anahtarları ve SSH Anahtarlarını Nasıl Saklamalı?
Terraform ve Ansible ile otomasyon kurarken en kritik konulardan biri, gizli anahtarların (secrets) yönetimidir. Hata yapmaya çok açık alanlar:
- Terraform veya Ansible dosyalarında API token’larını düz metin olarak bırakmak
- SSH özel anahtarlarını Git deposuna yanlışlıkla commit etmek
- Üretim ortamı şifrelerini staging ortamıyla karıştırmak
En azından şu temel önlemleri almanızı öneririz:
- Private key, provider kimlik bilgileri, state ve plan dosyalarını Git dışında, erişimi kısıtlı tutun.
.gitignoregeçmişte kaydedilmiş sırları silmez; açığa çıkan sırları değiştirin. sensitive = truebazı çıktıları maskeler; state/plan içeriğini şifrelemez.terraform output -jsonhassas çıktı değerlerini düz metin verebilir. Backend için erişim kontrolü, aktarım/depolama şifrelemesi, sürümleme ve kilitleme ayrı ayarlanmalıdır.- Ansible Vault saklanan dosyayı korur; çalışma sırasında açılan sırlar için dar yetki, uygun
no_logve güvenli CI günlük politikası gerekir. SOPS/age gibi araçlarda anahtar kurtarmasını da planlayın.
.gitignore
infra/.terraform/
infra/*.tfstate*
infra/*.tfplan
infra/terraform.tfvars
config/inventory/hosts.json
config/collections/
.venv/
# Store provider credentials and private SSH keys outside this repository.
Gizli bilgilerin yönetimini derinlemesine ele aldığımız VPS’te secrets yönetimi rehberi, Terraform/Ansible akışınızı güvenli hale getirirken size somut örnekler sağlayacaktır.
SSH Erişimini Standartlaştırmak
SSH politikasını kodlamak, hesap ve erişim kurallarını gözden geçirmenizi kolaylaştırabilir:
- Yönetim hesabı ve public key listesini kontrollü kaynaktan dağıtın; hesap ve sudo erişimini ayrı yeni oturumda sınayın.
- SSHD’de Include ve Match önceliğini dikkate alın. Yalnız metni değiştirmek yerine
sshd -tve gerçek bağlantı bağlamlarıylasshd -T -Ckontrolü yapın. - Değişiklik sonrası yeni bağlantı, firewall ve Fail2ban loglarını inceleyin; her hedefte aynı güvenlik sonucunu varsaymayın.
Ayrı sertleştirme rolü erişim politikasını gözden geçirilebilir hale getirir. Mevcut bir sunucunun imajı, PAM, Match blokları ve ağ yolu farklı olabilir; standartlaşmayı dosya benzerliğiyle değil etkili ayar ve bağlantı testleriyle doğrulayın.
Gerçekçi Senaryolar
1. Staging ve Üretim Ortamlarını Aynı Hale Getirmek
Staging ortamında çalışan bir özellik üretimde farklı davranabilir. Yazılım ve veriyle birlikte altyapı farklarını da inceleyin: farklı PHP sürümü, farklı Nginx ayarı, farklı kernel parametresi…
Terraform ve Ansible ile şu modeli kurabilirsiniz:
- Ortak Terraform modülünü kullanın; staging ve üretim için ayrı çalışma dizinleri, backend/state anahtarları ve erişim yetkileri ayırın. CLI workspace’leri state’i ayırabilir fakat tek başına güvenlik sınırı değildir.
- Aynı Ansible rol setini ayrı envanter ve ilgili
group_varsdeğerleriyle uygulayın. Komuttan önce hedef listesini kontrol edin. - Kaynak boyutu, PHP sürümü, imaj ve paket politikasındaki farkları kayıt altına alın. Daha küçük staging planı üretimin performansını birebir temsil etmez.
Bu yaklaşım staging ve üretim arasındaki farkları izlemeyi kolaylaştırabilir; test sonucunu üretime genellerken kapasite ve veri farklarını değerlendirin. Özellikle yüksek trafikli WordPress ve Laravel projelerinde, geliştirme–staging–canlı yolculuğu yazısında anlattığımız dağıtım akışları ile birleştiğinde, kod ve altyapı değişikliklerini birlikte gözden geçirebilirsiniz; veri, bağımlılık sürümleri ve dağıtım/geri dönüş testleri ayrıca doğrulanmalıdır.
2. Aynı Uygulamayı Farklı Bölgelerde Çoğaltmak
Yetkili API ve bölge kaynaklarınız varsa aynı modülden farklı bölgelerde VPS grupları oluşturabilirsiniz. Bölge başına imaj/flavor/ağ kimliklerini doğrulayın; gecikme, veri kopyalama, kapasite ve kesinti sınırlarını ölçün. Aynı Ansible rolü tutarlı yapılandırma sağlar, kendi başına düşük gecikme veya bölgesel felaket toleransı sağlamaz.
İleride DNS veya Anycast tabanlı bir çok bölgeli mimariye geçmek isterseniz, blogda detaylandırdığımız çok bölgeli mimariler rehberi ile Terraform/Ansible altyapınızı birleştirmeniz oldukça doğal bir adım olur.
3. Sıfırdan Kaldırmak Gerekebilen Riskli Denemeler
Bazen yeni bir veritabanı sürümü, yeni bir PHP versiyonu veya yeni bir cache mimarisi denemek istersiniz. Üretim sunucusu üzerinde doğrudan deneme yapmak yerine, Terraform + Ansible ile çöpe atılabilir test ortamları oluşturmak çok daha güvenlidir.
- Üretimden ayrı yetki/state kullanan test checkout’unda
make plançalıştırıp planı inceleyin. Yeni hedef için incelenmiş planı uygulayın, envanteri üretin ve hedef kimliği, host key, SSH/sudo ile yeni sunucu ön kontrollerini tamamlayın; ardındanfresh_host_revieweddeğerini true yapıpmake configureçalıştırın.make allyalnız bu kontrollerin geçerliliğini koruyan sonraki değişiklikler içindir. - Yetkili hedeflerde yük, sorgu ve yapılandırma denemelerini yürütün; yedek ve veri gereksinimini belirleyin.
make destroyönce silme planını gösterir veDESTROYister. State dışındaki DNS, yedek veya manuel kaynakları otomatik temizlediğini varsaymayın.
Test ortamları üretimden ayrıldığında deneme etkisi sınırlandırılabilir. Kota, çalışan süre, disk/IP ve saklanan yedek maliyetlerini izleyin; otomasyon tek başına maliyet düşürme garantisi değildir.
Sık Yapılan Hatalar ve İyileştirme İpuçları
Hata 1: Her Şeyi Tek Dev VPS Üzerine Yığmak
Veritabanı, uygulama ve cache’i ayırmak hata sınırlarını ve ölçeklemeyi kolaylaştırabilir; buna karşılık ağ gecikmesi, güvenlik, yedekleme ve işletim yükünü artırır. Küçük projede tek VPS uygun olabilir. Ayrıştırma kararını otomasyon kolaylığına göre değil yük, erişilebilirlik ve bakım gereksinimlerine göre verin.
Veritabanını ayrı bir VPS’e taşımanın ne zaman mantıklı olduğu sorusunu, blogdaki veritabanı sunucusunu uygulamadan ayırmak rehberinde detaylıca tartıştık. Altyapı kodu değişikliği destekleyebilir; veri taşıma, ağ ve uygulama geçişini ayrıca planlayın.
Hata 2: State Dosyasını Ciddiye Almamak
State, Terraform’un kaynak eşleştirmesi için kritik kayıttır; gerçek altyapının eksiksiz tek doğruluk kaynağı değildir. Rastgele yerel kopyalar aynı kaynak için farklı planlar üretmeye yol açabilir. Bu öğretici yerel state ile başlar; ekip/üretim kullanımına geçmeden backend ve erişim tasarımını tamamlayın.
Önerimiz:
- Seçilen backend’in gerçek desteklediği şifreleme, erişim kontrolü, sürümleme ve state kilitlemesini yapılandırın. “S3 uyumlu” etiketi tüm backend özelliklerinin aynı olduğunu kanıtlamaz.
- Aynı state için eşzamanlı apply işlemlerini backend kilidi ve CI eşzamanlılık kuralıyla engelleyin; kilidi zorla açmadan önce devam eden işlem olmadığını doğrulayın.
- State yedeklerini ve erişim anahtarlarının kurtarılmasını test edin. State yedeği uygulama/veritabanı yedeğinin yerine geçmez.
Hata 3: Ansible Rollerini Spagetti Hale Getirmek
Zamanla Ansible rolleri büyüyüp iç içe geçtiğinde, “bu sunucuya ne kuruldu” sorusunun cevabı zorlaşmaya başlar. Bunu engellemek için:
- Her rolün net bir sorumluluğu olsun:
nginx,php_fpm,mysql,security_hardeninggibi. - Rolleri yalnızca ihtiyaç duyan sunucu gruplarına atayın; her rolü her yere yüklemeye çalışmayın.
- Değişkenleri rollere özel
defaultsklasöründe tutun; environment farklarını group_vars ile yönetin.
Hata 4: İzleme ve Loglamayı Unutmak
CPU, disk ve 5xx izlemesini dağıtımın kabul adımlarına ekleyin. Ajanın kurulu olması, alarmın doğru hedefe ulaştığını kanıtlamaz; metrik, log, dış HTTP kontrolü ve kontrollü alarm/iyileşme testi yapın. Yeni log akışında ömrü dolmuş Promtail yerine desteklenen bir toplayıcı seçin; Prometheus metrikleri ve Loki benzeri log depolarının görevlerini ayırın.
Bu aşamada, blogda detaylandırdığımız VPS izleme ve alarm kurulumu rehberindeki ajanları ve dashboard kurulumlarını Ansible rolleri haline getirip, her yeni VPS’inize otomatik olarak ekleyebilirsiniz.
DCHost ile Terraform ve Ansible Otomasyonunu Hayata Geçirmek
Buraya kadar anlattığımız yapı, ilk bakışta kapsamlı görünebilir; ancak küçük adımlarla gittiğinizde her adımın somut bir getirisi oluyor. Özetle:
- Terraform, yetkili OpenStack API üzerinde örnekteki kaynakları yönetir; DCHost için bir API desteği varsayılmaz.
- Ansible, mevcut uygun DCHost VPS’de de açıkça tanımlanan OS/web görevlerini uygular. SSH/firewall için ayrı kontrollü aşama kullanılır.
- Plan, envanter, uygulama yanıtı, TLS, izleme ve geri yükleme kontrollerini birleştirerek yeniden çalıştırılabilir bir süreç oluşturun.
VPS, dedicated veya kendi sanallaştırmanızı çalıştırdığınız colocation altyapısında aynı yapılandırma ilkelerini kullanabilirsiniz; gerekli yönetim yetkileri ve OS desteği her platformda ayrıca doğrulanır. DCHost üzerindeki mevcut sunucular için Ansible ile başlamak, API entegrasyonu hakkında varsayım yapmadan ilerlemenizi sağlar.
Bir sonraki adım olarak şunları yapmanızı öneririm:
- Yeni/ayrılmış bir Ubuntu 24.04 hedef seçin; mevcut VPS için doğrudan envanter yolunu veya yetkili OpenStack için incelenmiş plan yolunu kullanın.
- Önce common/web rollerini ve gerçek HTTP/PHP davranışını, sonra ayrı erişim politikasını test edin.
- DNS/TLS, veritabanı, cache, izleme ve yedek rollerini kendi kabul ve geri dönüş adımlarıyla ekleyin. HTTP-01 için genel internetten port 80 ve challenge yolu erişilebilir olmalıdır; mevcut şablonun noktayla başlayan yolları engelleyen kuralını challenge yoluna izin verecek şekilde uyarlayın. Wildcard için DNS-01 gerekir.
Yedeklerde saklama, şifreleme ve geri yükleme tatbikatını kod kadar önemseyin. Restic’in S3 backend’i ile Borg’un SSH/yerel depo modeli aynı değildir; seçtiğiniz araç için desteklenen hedefi kullanın. DCHost altyapı seçiminizi kaynak, erişim, izleme ve kurtarma gereksinimleriyle birlikte değerlendirin. Sürüm ve rol güncellemelerini küçük bir test ortamından geçirerek ilerleyin.





