Pager o gece 02:17’de çaldı. Frankfurt’taki VPS üzerinde çalışan edge proxy bir anda TCP yeniden denemeleriyle boğulmuştu, Singapur’daki iş katmanı düğümü ise replikaya konuşamıyordu. Latency p95 280 ms’e zıplamış, hatalı retry’lar RDS bağlantı havuzunu şişirmişti. O ana kadar “internet üzerinden güvenli tüneller yeter” diyorduk; sonra DNS cache’lerinin TTL’leri birbirini ısırdı, wildcard SSL yenilemesi de aynı gece geldi. Eğer siz de downtime riski, DNS kaosu ya da SSL yenileme paniği arasında sıkıştıysanız ve Tailscale/ZeroTier ile özel ağ kurarak bunu kalıcı çözmek istiyorsanız, doğru yerdesiniz. Bu yazıda çok sağlayıcılı VPS’ler arasında site‑to‑site mesh’i, gerçek operasyon notları, ölçülebilir metrikler ve çalışır runbook’larla adım adım anlatacağım: mimari tasarım, kurulum, güvenlik, gözlemlenebilirlik ve değişiklik yönetimi. Sonunda, gece alarmı çalsa dahi “bu ağ ayağa kalkar” diyebileceğiniz bir yapı kurmuş olacağız.
İçindekiler
- 1 Incident Retrosu: Neden Overlay Mesh?
- 2 Mimari İnceleme: IP Planı, ACL ve Site‑to‑Site Seçenekleri
- 3 Tailscale ile Kurulum: Runbook ve Komutlar
- 4 ZeroTier ile Kurulum: Esnek Sanal Anahtar
- 5 Site‑to‑Site Köprüleme: Rota İlanı, MTU ve NAT
- 6 Gözlemlenebilirlik: Metrikler, Loglar ve Paneller
- 7 Güvenlik: Kimlik, Anahtar Döndürme, Segmentasyon
- 8 CI/CD ve IaC: Tekrarlanabilirlik
- 9 Gerçek Hikayeler: İki Post‑Mortem Notu
- 10 Değişiklik Yönetimi: Canary mi Blue/Green mi?
- 11 Operasyon Runbook’u: Gece Alarmına Kısa Cevap
- 12 Maliyet ve Performans: Gerçekçi Beklentiler
- 13 Kapanış: Ayağa Kalkan, Yönetilebilir Bir Mesh
Incident Retrosu: Neden Overlay Mesh?
O incident’te kök neden, iki bölgede farklı sağlayıcıların outboud NAT davranışları ve ara sıra tetiklenen asymmetric routing oldu. Frankfurt edge, Singapur app’e TCP açıyordu; dönüş trafiği ise farklı çıkış IP’sinden geldiği için stateful firewall bayılıyordu. VPC peering yoktu, IPsec siteler arası tüneller ise bakım sırasında yeniden bağlanmayı başaramadı. Üstüne bir de DNS TTL kısa tutulduğu için (10s) rota dalgalanmalarında client tarafında “servis dansı” yaşandı ve p95 latency 280 ms, paket kaybı %3,5’e vurdu. Audit loglarında 01:59–02:23 arasında TLS handshake timeout sayısı 112’ydi.
Overlay mesh ile hedeflediğimiz şuydu: heterojen sağlayıcılarda (hetzner, DO, OVH, AWS lightsail fark etmez) WireGuard temelli, otomatik NAT traversal yapan, yönetimi basit bir site‑to‑site ağ. Tailscale ve ZeroTier bu noktada iki güçlü seçenek. İkisi de Layer‑3 overlay kuruyor; Tailscale, WireGuard üstünde key management ve DERP relay’lerle pratikleşiyor. ZeroTier, esnek sanal switch + policy motoru gibi; L2/L3 melezi bir yaklaşım sunabiliyor. Metrikler açısından hedefimiz netti: p95 latency < 120 ms (kıta‑lararası), paket kaybı < %0,5, handshake recovery < 2s ve mevcut deployment pipeline’larına dokunmadan erişim.
Trade‑off notu: Klasik IPsec tünellerde full‑mesh için her noktayı her noktaya tanımlamak gerekir; yönetim yükü katlanır. Burada mesh kontrol düzlemi dışarıda (Tailscale control plane/DERP, ZeroTier controller) ve rotalar dinamik. Dezavantaj: 3. parti kontrol katmanı bağımlılığı ve veri akışının (normalde direct, NAT delme başarısızsa relay) nadiren relay üzerinden geçebilmesi; bu da latency sapması yaratabilir. Ancak SLA hedefleriyle kıyasladığımızda risk kabul edilebilirdi.
Mimari İnceleme: IP Planı, ACL ve Site‑to‑Site Seçenekleri
IP Planlaması ve Alanlar
Overlay mesh’te en kritik tasarım alanı IP planı. Tailscale default 100.64.0.0/10 CGNAT aralığını kullanırken, ZeroTier kendi tanımlı RFC4193 (fd00::/8) + 10.0.0.0/8 gibi atamalarla çalışabilir. Çakışmaları engellemek için her lokasyona /24 alt ağlar verdik:
– eu‑fra: 10.77.10.0/24
– ap‑sin: 10.77.20.0/24
– us‑nyc: 10.77.30.0/24
Tailscale’de her lokasyon için bir subnet router seçtik; ZeroTier’de ise managed routes ile aynı alt ağları ilan ettik. Amaç, host‑to‑host ve site‑to‑site erişimi aynı düzlemde yönetebilmekti.
DNS, MagicDNS ve Split‑Horizon
DNS tarafında iki önemli karar vardı: servis keşfi için MagicDNS (Tailscale) ve ZeroTier’ın adlandırma seçenekleri, ayrıca split‑horizon DNS. Public DNS kayıtlarında TTL 60s; overlay içi servislerde (örn. api.mesh.local) TTL 300s. Düşük TTL hızlı failover sağlarken kontrol uçları etrafında fırtına yaratabilir; SLA’nızda “özelleşmiş internal domain” varsa TTL’i biraz daha yüksek tutup health‑check’leri agresif yapmak daha sağlıklı.
Güvenlik ve ACL Modeli
Erişim kontrolünü “default deny” prensibiyle kurduk. Tailscale’de policy dosyası (ACLs) ile servis rollerine izin verdik; ZeroTier’da flow rules ile port/protokol bazlı yetki. Prod ve staging’i overlay seviyesinde segment ettik; ortak araçlar (CI runner, log forwarder) için sadece belirli portları açtık.
Tailscale ile Kurulum: Runbook ve Komutlar
Önkoşullar ve Organizasyon
Şirket alanı ile SSO (SAML/OIDC) bağladık. Pre‑auth key kullanarak headless sunucuların otomatik katılımını sağladık. DERP’leri varsayılan bıraktık ancak EU ve APAC için yakın noktaları seçtik. SSH yerine tailscale ssh’ı devreye aldık ki bastion ihtiyacı ve public portlar azalsın.
Kurulum Adımları (Host Başına)
- Kernel: WireGuard modülü (5.x) kontrollü.
- Paket: tailscale kurulumu (repo üzerinden).
- Auth: pre‑auth key ile otomatik katılım.
- Subnet router: ilgili /24’ü advertise et.
- ACL ve DNS: policy güncelle, MagicDNS doğrula.
- Gözlem: metrics exporter ve logs streaming.
# Debian/Ubuntu
curl -fsSL https://tailscale.com/install.sh | sh
# Headless join (örnek preauth key)
sudo tailscale up
--authkey=tskey-auth-kQ3...
--hostname=fra-edge-01
--advertise-tags=tag:edge,tag:prod
--ssh
--accept-dns=true
--accept-routes=true
# Subnet router olarak 10.77.10.0/24 ilan et
sudo tailscale up
--advertise-routes=10.77.10.0/24
--snat-subnet-routes=false
# Durum
sudo tailscale status
sudo tailscale status --json | jq '.'
Örnek JSON çıktısı:
{
"Self": {
"DNSName": "fra-edge-01.tailnet-abc.ts.net",
"TailscaleIPs": ["100.96.12.34", "fd7a:115c:a1e0::abcd"],
"UserID": 101,
"Hostinfo": {"OS": "linux", "Hostname": "fra-edge-01"}
},
"Peers": [
{
"DNSName": "sin-app-02.tailnet-abc.ts.net",
"TailscaleIPs": ["100.98.22.11"],
"RxBytes": 12900322,
"TxBytes": 9388821,
"Latency": {"p50": 172000000, "p90": 198000000},
"Relay": false
}
],
"MagicDNSEnabled": true,
"CurrentTailnet": "acme-corp"
}
Policy (ACL) Örneği
{
"ACLs": [
{"Action": "accept", "Users": ["group:platform"], "Ports": ["tag:edge:443", "tag:app:5432"]},
{"Action": "accept", "Users": ["group:sre"], "Ports": ["tag:edge:22", "tag:infra:9100"]}
],
"Groups": {
"group:platform": ["[email protected]", "[email protected]"],
"group:sre": ["[email protected]"]
},
"TagOwners": {
"tag:edge": ["group:sre"],
"tag:app": ["group:platform"],
"tag:infra": ["group:sre"]
},
"AutoApprovers": {
"routes": {"100.64.0.0/10": ["group:sre"]}
}
}
Bu projede şöyle çözdük: Subnet router’ları iki node’da çalıştırıp ECMP yerine aktif/pasif yaptık. Neden? Çünkü bazı sağlayıcılarda MTU farkı (1500 vs 1450) ECMP ile MSS uyumsuzluğu doğurdu. Aktif/pasif rota ile p95 latency’de %12 iyileşme, TCP retransmit’lerde %0,7 azalma gördük.
ZeroTier ile Kurulum: Esnek Sanal Anahtar
Controller ve Ağ Oluşturma
ZeroTier’da ya yönetilen cloud controller’ı ya da kendi controller’ınızı kullanabilirsiniz. Ağ oluşturduktan sonra node’ları join edip managed routes ve flow rules ile yetkiyi yönetirsiniz.
# Kurulum
curl -s https://install.zerotier.com | sudo bash
sudo zerotier-cli info
# > 200 info 9c3d1a2b3c 1.12.2 ONLINE
# Ağa katıl
sudo zerotier-cli join 8056c2e21c000001
sudo zerotier-cli listnetworks
# Routes ve assigned addresses'i kontrol et
Network ayar örneği (controller tarafı):
{
"config": {
"name": "acme-mesh",
"v4AssignMode": {"zt": true},
"v6AssignMode": {"rfc4193": true},
"routes": [
{"target": "10.77.10.0/24", "via": null},
{"target": "10.77.20.0/24", "via": null},
{"target": "10.77.30.0/24", "via": null}
]
},
"rules": [
{"rule": "drop not ethertype ipv4"},
{"rule": "accept ipprotocol tcp dport 22 from 10.77.0.0/16"},
{"rule": "accept ipprotocol tcp dport 443"},
{"rule": "accept ipprotocol tcp dport 5432 from 10.77.20.0/24 to 10.77.10.10/32"},
{"rule": "drop"}
]
}
ZeroTier’ın avantajı, L2’ye yakın davranabilmesi ve aynı ağda discovery gerektiren bazı protokollerde (örn. konsensus cluster management araçları) işleri kolaylaştırması. Dezavantaj, yanlış kurgulanırsa geniş yayın (broadcast) ve ARP gibi L2 davranışları overlay üzerinde gürültü yaratabilir. Bunu rules ile sıkılaştırmak şart.
Site‑to‑Site Köprüleme: Rota İlanı, MTU ve NAT
Tailscale Subnet Router ve Route Approval
Prod’da autoApprovers.routes ile ilan edilen ağları otomatik onaylattık. Geriye sadece sağlıklılık kontrolü kaldı. Bir rota düştüğünde alert üretiyoruz, failover node “advertise” etmeye hazır bekliyor.
# Pasif node hazır beklerken (advertise yok)
sudo tailscale up --authkey=tskey-auth-... --hostname=fra-edge-02 --accept-routes=true
# Failover anında
sudo tailscale up --advertise-routes=10.77.10.0/24 --snat-subnet-routes=false
ZeroTier Managed Routes ve Policy
ZeroTier tarafında aynı alt ağlar için managed routes kullanıyoruz. Health check script’i RTT ve packet loss eşiklerini aştığında controller API’si ile route “via” güncelleniyor. Bu, saniye mertebesinde failover sağlıyor.
MTU ve MSS Clamping
Farklı sağlayıcılar, farklı MTU. WireGuard üzerinde genelde 1420 civarı güvenli. Aksi halde “ICMP Fragmentation Needed” düşmüyor ve sessizce performans çöküyor. TCP için MSS clamp, UDP için Path MTU Discovery’ye güvenmek bazen hayal kırıklığı yaratır. Aşağıdaki kural, overlay arayüzünde MSS’i sınırlar:
# nftables örneği
sudo nft add table inet mangle
sudo nft add chain inet mangle prerouting { type filter hook prerouting priority -150; }
sudo nft add rule inet mangle prerouting tcp flags syn tcp option maxseg size set 1360
Bu projede bu ayarla p95 latency’de 18–25 ms arası iyileşme ve yük altında TCP retransmit oranında %0,4 azalma gördük.
Gözlemlenebilirlik: Metrikler, Loglar ve Paneller
Hangi Metrikleri İzledik?
İlk hafta “güzel çalışıyor” hissine güvenmedik. Overlay mesh için şu metrikleri topladık:
– Handshake süresi (p50/p95)
– NAT traversal başarı oranı (% direct vs % DERP/relay)
– RTT ve jitter (p50/p95)
– Packet loss
– Bytes in/out, connections/s
– Route health (ilan edilen prefix’ler ve reachable state)
Toplama ve Paneller
Tailscale’de tailscale status --json ve tailscale netcheck çıktıları export edildi. ZeroTier’da zerotier-cli listpeers ve controller API’leri kullanıldı. Exporter’lar Prometheus’a scrape edildi; Grafana’da “Mesh Health” panelleri kurduk.
# Örnek netcheck
sudo tailscale netcheck
# Report:
# * UDP: true
# * IPv4: yes, 203.0.113.10:41641
# * Latency: 24ms (direct), 41ms (via derp-eu)
# * MappingVariesByDestIP: false
Alert kuralları:
# PrometheusRule (basitleştirilmiş)
- alert: MeshRttDegraded
expr: histogram_quantile(0.95, sum(rate(mesh_rtt_bucket[5m])) by (le, link)) > 200
for: 10m
labels: {severity: warning}
annotations:
summary: "p95 RTT yavaş"
- alert: MeshRelayUsageSpike
expr: increase(mesh_relay_bytes_total[15m]) / increase(mesh_bytes_total[15m]) > 0.3
for: 15m
labels: {severity: critical}
annotations:
summary: "Relay kullanım oranı arttı"
Güvenlik: Kimlik, Anahtar Döndürme, Segmentasyon
Kimlik ve SSO
Makine kimlikleri SSO ile bağlandı. Tailscale’de tag’leri sahiplik modeline bağladık; ZeroTier’da member authorization’ı GitOps’a taşıdık. Her node’un katılımı code review’dan geçti; “join‑request” PR kapanmadan prod ağa erişim yok.
Anahtar Yaşam Döngüsü
Pre‑auth key’ler kısa ömürlü (24h) ve tek kullanımlık. Dönüşüm pipeline’ı her deploy’da anahtarları yeniliyor. Tailscale’de ephemeral node’lar test için kullanışlı; prod’da kalıcı kimlik tercih ettik. ZeroTier node secret’ları SOPS ile şifreli olarak repo’da tutuldu.
Segmentasyon
Prod/staging/ops ağlarını ayrı overlay ID’lerine böldük. Cross‑env erişimler sadece belirli port/protokollere, belirli etiketlere izinli. Bu sayede lateral movement yüzeyini daralttık.
CI/CD ve IaC: Tekrarlanabilirlik
Terraform ile Policy Yönetimi
Policy dosyaları ve ZeroTier network tanımları Terraform ile yönetildi. Plan/apply öncesi dry‑run ve canary stage’leri var.
# Terraform (özet örnek)
provider "tailscale" {}
provider "zerotier" {}
resource "tailscale_acl" "main" {
acl = file("acl.json")
}
resource "zerotier_network" "mesh" {
name = "acme-mesh"
assign_ipv4 = true
route {
target = "10.77.10.0/24"
}
}
GitOps Akışı
# GitHub Actions
name: mesh-policy
on:
pull_request:
paths: ["infra/mesh/**"]
push:
branches: ["main"]
paths: ["infra/mesh/**"]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform fmt -check
- run: terraform init
- run: terraform validate
- run: terraform plan -out tfplan
apply:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform apply -auto-approve tfplan
Ansible ile Host Hazırlığı
---
- hosts: vps
become: yes
tasks:
- name: Install Tailscale
shell: curl -fsSL https://tailscale.com/install.sh | sh
args:
warn: false
- name: Up tailscale
command: tailscale up --authkey={{ ts_authkey }} --ssh --accept-dns=true
- hosts: zerotier
become: yes
tasks:
- name: Install ZeroTier
shell: curl -s https://install.zerotier.com | bash
- name: Join network
command: zerotier-cli join {{ zt_network_id }}
Gerçek Hikayeler: İki Post‑Mortem Notu
PM‑01: DERP Üzerinden Sessiz Yavaşlama
Belirli saatlerde Avrupa düğümleri arası RTT 24 ms’den 55 ms’ye çıkıyordu. Paket kaybı yoktu. tailscale netcheck raporlarında direct “true” görünürken akış verisinde relay bytes oranı %28’e vurmuş. Kök neden: Bir sağlayıcı inbound UDP rate limit’ini gece yedeklemesi sırasında sıkılaştırıyordu; NAT mapping süreleri kısalıyor, bağlantılar relay’e düşüyordu. Çözüm: Health check’e “relay ratio” metriğini dahil ettik; oran %10’u geçerse alternatif çıkış IP’siyle yeni mapping oluşturduk. Maliyet: DERP trafiği düştü, p95 RTT 27 ms’ye indi.
PM‑02: MTU ve Parçalanma Kabusu
APAC kullanıcılarının bazı istekleri 2–3 saniye bekledikten sonra zaman aşımına düşüyordu. ICMP bloklu olduğu için PMTUD başarısızdı. Overlay üstünde büyük TLS paketleri parçalanamıyordu. Kök neden: Bir sağlayıcı 1500 MTU, diğeri 1472; WireGuard overhead’i ile efektif MTU 1420’nin altına inmişti. Çözüm: MSS clamping ve tailscale up --mtu=1280 sınırı. Sonuç: p95 2.3s → 410ms; retransmit %1.9 → %0.6.
Değişiklik Yönetimi: Canary mi Blue/Green mi?
Network policy değişikliklerinde canary yaklaşımını tercih ettim. Bir/iki node’u yeni ACL ve rota seti ile güncelliyoruz, panellerde 30 dakika gözlüyoruz. Eğer p95 RTT +%10’dan fazla artarsa ya da relay ratio %20’ye yaklaşırsa otomatik geri alıyoruz. DNS tarafında TTL tartışması hep çıkar: düşük TTL hızlı toparlar ama kontrol düzlemi yükünü ve cache sapmalarını artırır. Biz overlay içi domainlerde 300s, public yüzlerde 60s kullandık ve SLA’yı bunu varsayarak yazdık.
Operasyon Runbook’u: Gece Alarmına Kısa Cevap
Bağlantı Sorununda İlk 10 Dakika
- Durum:
tailscale status --jsonveyazerotier-cli listpeers. - Netcheck:
tailscale netcheck; relay ratio’yu not al. - MTU: test ping:
ping -M do -s 1372 100.x.x.x. - Rotalar: Tailscale’de
--advertise-routesaktif mi; ZeroTier managed routes up mı? - Failover: pasif subnet router’ı advertise et.
- Log: sağlayıcı firewall/NAT değişikliklerini teyit et.
Planlı Değişiklik
- PR: Terraform plan + policy diff.
- Canary: 1–2 node, 30 dk gözlem.
- Rollout: batch ile genişlet, her batch arası 10 dk.
- Rollback: tek komutla eski policy ve rota setine dön.
Maliyet ve Performans: Gerçekçi Beklentiler
Overlay maliyeti çoğunlukla “zaman kazanma”dır. Relay kullanım oranı düşükse ek latency ihmal edilebilir. Ancak yüksek throughput (örn. veri replikasyonu) senaryolarında doğrudan peering veya site‑to‑site IPsec hâlâ ekonomik olabilir. Bizim vakada replikasyon trafiğini overlay dışına aldık (dedike tüneller), uygulama trafiğini overlay’de tuttuk; toplam bant genişliği maliyeti %18 düştü, operasyon karmaşıklığı ise ciddi azaldı.
Kapanış: Ayağa Kalkan, Yönetilebilir Bir Mesh
O geceki pager olayı bize şunu öğretti: Farklı sağlayıcılar arasında istikrarlı bir yol bulmak, tek tek tünelleri idare etmekten daha önemli. Tailscale/ZeroTier ile özel ağ kurduğunuzda, kimlik temelli erişim, otomatik NAT traversal ve merkezî policy yönetimi birlikte çalışıyor. Metriklere yaslanın: RTT, relay ratio, packet loss ve handshake süresi; panellerde bunlar yeşilse rahat uyursunuz. Operasyonel olarak runbook’ları netleştirin; “ilk 10 dakika” ve “planlı değişiklik” adımlarını ekibin cebine koyun. Güvenlikte default deny ve kısa ömürlü anahtarlar sizi ileri taşır. Değişiklikleri canary ile yaymak, DNS TTL ve SLA tartışmalarını somut veriye bağlar. Bugün küçük başlayın: iki VPS ve bir subnet router. Yarın üç kıtada beş sağlayıcıya yayılmak, aynı disiplinle sadece bir pipeline işi olacak. Ekibinize söyleyin: Bu mesh, sizin kadar iyi; runbook’ları okuyun, panellere bakın, küçük değişiklikleri sık sık yapın. Gece pager çalarsa, ne yapacağınızı biliyorsunuz.
