NixOS ile yeniden üretilebilir servis kurulumu
Bildirimsel yapılandırma, atomik geçiş ve flakes ile sürüm sabitleme gerçekte ne veriyor, nerede Docker ve klasik yapılandırma yönetiminin önüne geçiyor, hangi tuzaklar ilk ayda karşınıza çıkıyor ve NixOS ne zaman yanlış seçim oluyor.
Aynı yapılandırma, üç makine, üç farklı sonuç
Bir servisi üç sunucuya kurup üçünde de birebir aynı davranışı elde etmek çoğu ekibin tahmininden zordur. Kurulum betiği aynıdır, paket adları aynıdır; ama bir makinede depo indeksi eskidir, ötekinde biri aylar önce bir ayar dosyasını elle düzenlemiştir, üçüncüsünde bağımlılık çözümü farklı bir yan sürüm çekmiştir. Asıl sıkıntı adımların yanlış olması değil, adımların bir sonucu değil bir geçmişi tarif etmesidir. Klasik bir yapılandırma yönetimi görevi "şu paketi kur, şu satırı ekle" der ve makinenin o satırdan önceki hâlini bilmez. NixOS'un teklifi tam buraya bakar: sisteme ne yapılacağını değil, sistemin ne olacağını yazarsınız.
Bildirimsel olmak, idempotent olmaktan farklı bir şey
Yapılandırma yönetimi araçlarının çoğu yakınsayan (convergent) çalışır. Makinenin mevcut durumunu okur, hedefe göre eksikleri tamamlar. Bu ilişki tek yönlüdür: playbook'tan bir görevi silmek, o görevin aylar önce kurduğu paketi makineden kaldırmaz. Zamanla elinizde, hiçbir kaynak dosyada karşılığı olmayan yazılımlarla dolu bir sunucu kalır. Kimse silmeye cesaret edemez, çünkü neyin neye bağlı olduğunu kimse bilmez.
NixOS'ta yapılandırma bir betik değil, bir ifadedir. Değerlendirildiğinde sistemin tamamını tarif eden bir sonuç üretir: hangi paketler hangi sürümleriyle, hangi systemd birimleri, hangi kullanıcılar, hangi çekirdek parametreleri. Bir satırı silerseniz o satırın ürettiği her şey bir sonraki nesilde yoktur. Çalışan sistem, kaynak dosyaların ürettiğinden ne fazlasını ne eksiğini içerir.
Bunu mümkün kılan yapı depodur. Her paket, bağımlılık grafiğinin tamamının özetinden türeyen bir yola kurulur; kabaca /nix/store/<özet>-nginx-1.28.0 gibi. Aynı kütüphanenin iki farklı sürümü yan yana durabildiği için "bu paketi güncellersem şu servis bozulur mu" sorusu ilginç olmaktan çıkar. Küresel bir /usr/lib yoktur, dolayısıyla küresel bir çakışma da yoktur.
configuration.nix okumak
Tek makinelik bir kurulumda her şey bir dosyada başlar:
{ config, pkgs, ... }:
{
imports = [ ./hardware-configuration.nix ];
networking.hostName = "app01";
time.timeZone = "Europe/Istanbul";
services.openssh = {
enable = true;
settings.PasswordAuthentication = false;
};
services.postgresql = {
enable = true;
package = pkgs.postgresql_16;
ensureDatabases = [ "appdb" ];
};
networking.firewall.allowedTCPPorts = [ 80 443 ];
system.stateVersion = "26.05";
}
Burada yaptığınız şey bir modül sistemine seçenek ataması. services.postgresql.enable = true bir kurulum komutu tetiklemiyor; nixpkgs içindeki PostgreSQL modülünün kendi systemd birimini, servis kullanıcısını, veri dizinini ve ilklendirme betiğini üretmesini sağlıyor. Modülü açıp hangi seçeneğin ne ürettiğini okuyabilirsiniz. Gizli bir kısım yok.
Bir servis ve önündeki ters vekil
Kendi uygulamanızı çalıştırmak için hazır bir modül gerekmez, systemd birimini doğrudan tarif edersiniz:
{ config, pkgs, ... }:
{
systemd.services.myapp = {
description = "Uygulama sunucusu";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "postgresql.service" ];
serviceConfig = {
ExecStart = "${pkgs.myapp}/bin/myapp --listen 127.0.0.1:8080";
DynamicUser = true;
Restart = "on-failure";
ProtectSystem = "strict";
PrivateTmp = true;
};
};
services.nginx = {
enable = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
virtualHosts."app.example.com" = {
enableACME = true;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:8080";
proxyWebsockets = true;
};
};
};
security.acme = {
acceptTerms = true;
defaults.email = "[email protected]";
};
}
enableACME sertifika alma işini, yenileme zamanlayıcısını ve nginx ile paylaşılan dizin izinlerini kendisi kurar. Alan adı doğrulaması için 80 ve 443 portlarının dışarıdan erişilebilir olması gerekir; güvenlik duvarı satırını unutursanız sertifika hiç gelmez ve hata mesajı bunu açıkça söylemez. Yeni başlayanların ilk hafta en sık takıldığı yerlerden biri budur.
Atomik geçiş ve geri alma
nixos-rebuild switch yeni sistemin tamamını kurar, bitirdikten sonra bir sembolik bağı çevirir ve yalnızca değişen birimleri yeniden başlatır. Yarım uygulanmış bir durum yoktur: ya eski nesildesinizdir ya yeni nesilde. Her nesil önyükleyici menüsünde ayrı bir girdi olarak kalır, yani çekirdek güncellemesi bile geri alınabilir.
Pratikte üç komut ekmeğini çıkarır. nixos-rebuild test yapılandırmayı çalışan sisteme uygular ama önyükleme varsayılanına dokunmaz; makine yeniden başlarsa eski hâline döner. Kendi erişim yolunuzu kesebilecek her değişiklikte bunu kullanın. nixos-rebuild boot tersini yapar, bir sonraki açılışta devreye girer. nixos-rebuild switch --rollback bir önceki nesle döner.
Bir yükseltmenin gerçekte neyi değiştireceğini uygulamadan önce görmek için:
nixos-rebuild build --flake .#app01
nix store diff-closures /run/current-system ./result
İkinci komut iki sistem arasında eklenen, kaldırılan ve sürümü değişen paketleri listeler. Kod incelemesinde bir Nix diff'ine bakmak genelde az şey anlatır; asıl bilgi bu çıktıdadır.
Bir noktada dürüst olmak gerek: geri alma sistemi geri alır, veriyi değil. Bir şema göçü koştuysa, uygulama diske yeni bir biçimde yazmaya başladıysa, önceki nesle dönmek sizi kurtarmaz. Nesiller yedek değildir.
Flakes ile sürüm sabitleme
Kanal kullanan bir kurulumda nixos-rebuild switch --upgrade, o makinede o an güncel olan nixpkgs anlık görüntüsünü çeker. "Aynı yapılandırma aynı sonucu verir" iddiasını delen en yaygın yol budur. Flakes deliği kapatır: her girdinin tam commit özetini depoda duran bir flake.lock dosyasına yazar.
{
description = "Sunucu yapılandırmaları";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
outputs = { self, nixpkgs }: {
nixosConfigurations.app01 = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./hosts/app01/configuration.nix ];
};
};
}
Kurulum nixos-rebuild switch --flake .#app01 ile yapılır. Güncelleme bilinçli bir eyleme dönüşür: nix flake update nixpkgs kilidi tazeler, değişiklik gözden geçirilebilir bir commit olarak görünür, ancak ondan sonra bir makineye ulaşır. Aynı kilitle altı ay sonra kurduğunuz sunucu bugünküyle aynı paket sürümlerini alır.
Flakes hâlâ resmen deneysel bir özellik. Günlük karşılığı, yapılandırmanıza nix.settings.experimental-features = [ "nix-command" "flakes" ]; satırını eklemeniz. Yıllardır üretimde kullanılıyor, ama bazı komutların hâlâ ek bayrak istemesinin sebebi o deneysel etiket.
Docker ve klasik yapılandırma yönetimiyle karşılaştırma
Konteyner imajları göründükleri kadar yeniden üretilebilir değildir. İçinde FROM debian:12 ve apt-get install geçen bir Dockerfile'ı üç ay arayla iki kez derleyin, iki farklı imaj alırsınız. Bu çoğu zaman canınızı yakmaz, çünkü etiketli imajın kendisi sabitlenmiş bir yapıttır. Ama sabitlediğiniz şey tarif değil, çıktıdır. Yeniden derlemek zorunda kaldığınız an, mesela bir güvenlik yaması için, elinize ne geçeceğini bilmezsiniz. Nix tarifi sabitler, çıktıyı ondan türetir.
Konteynerlerin kazandığı yerler açık: dağıtım kolaylığı, her mühendisin bildiği bir arayüz, olgun bir çalışma zamanı ekosistemi. İkisi birbirini dışlamıyor da; Nix konteyner imajı da üretebilir ve bu imajlar katman sırasına bağlı olmadan yeniden üretilebilir çıkar.
Klasik yapılandırma yönetimi araçları başka bir problemi çözer: kurmadığınız, tamamını değiştiremediğiniz, çoğu zaman birden fazla işletim sistemine yayılmış bir filoyu idare etmek. Kendi kurduğunuz makinelerde ise sürüklenmenin çaresi değil, kaynağıdırlar. Karar kuralı basit. Makineleri siz sağlıyorsanız ve hepsini değiştirebiliyorsanız NixOS kazanır. Devraldığınız heterojen bir mevcudu yönetiyorsanız kazanmaz.
Tuzaklar
Sırlar depoya konmaz. Nix deposu dünyaya okunabilir, dolayısıyla yapılandırmanıza yazdığınız düz metin bir parola, dosya izinlerinden bağımsız olarak makinedeki her kullanıcının okuyabileceği bir yola düşer; üstelik sürüm kontrolünde de commit'lidir. Doğrusu, şifreli dosyaları etkinleştirme anında çözmek: agenix ya da sops-nix. Bunu ilk günden kurun, sonradan eklemek her sırrı döndürmek demektir.
system.stateVersion bir sürüm numarası değildir. O makineye ilk kurduğunuz NixOS sürümünü kaydeder ve verisini kendi göç ettiremeyen yazılımların hangi varsayılanları kullanacağını belirler. Yükseltme sırasında artırmak hiçbir şeyi güncellemez, sadece o veri biçimi varsayımlarını değiştirir. Dokunmayın.
Diskler sessizce dolar. Her nesil kendi kapanışını canlı tutar, birkaç ay ilgilenmeyince depo onlarca gigabayta ulaşır. nix.gc.automatic ve --delete-older-than 30d benzeri bir seçenek işi görür, ama açıkça etkinleştirmeniz gerekir.
Nixpkgs dışındaki yazılım pahalıdır. pip, npm, cargo gibi dil düzeyi paket yöneticileri kendi ikili dosyalarını indirmeye alışkındır ve NixOS standart bir dosya sistemi hiyerarşisi sunmadığı için o ikili dosyalar dinamik bağlayıcıyı bulamaz. Geçici çözümler var, her biri ek iş. Bağımlılıklarınızın büyük kısmı nixpkgs dışındaysa bu maliyeti karara dahil edin.
Hata mesajları serttir. Küçük bir yazım hatası, sizin dosyanızla ilgisi olmayan bir yığın izi ve tepesinde "infinite recursion encountered" gibi bir cümle üretebilir. Öğrenme eğrisinin en dik yeri burasıdır. Dilin kendisi küçüktür ve birkaç günde öğrenilir; modül sisteminin nasıl değerlendirildiğini anlamak epey daha uzun sürer.
Ekip birden fazla kişi olunca
Tek kişilik bir kurulumda NixOS keyiflidir. Ekipte ise şu desen belirir: bir kişi öğrenir, herkes ona sorar. Değişiklikler o kişinin incelemesini beklemeye başlar, o kişi izne çıktığında altyapıya kimse dokunmaz. Bunu bilerek yönetmek gerekir. Gerçekten işe yarayan üç önlem var: bütün makine yapılandırmalarını ortak modüllerin paylaşıldığı tek bir depoda tutmak, kod incelemelerine diff-closures çıktısını iliştirmek, ve yeni gelenin ilk haftasında gerçek bir servisi baştan sona kendisinin eklemesini sağlamak. Dokümantasyon bu üçünün hiçbirinin yerini tutmuyor.
Nerede seçmemeli
Kısa ömürlü ya da tek seferlik makinelerde karşılığı yoktur; bir sanal makineyi bir kez kurup atacaksanız öğrenme maliyeti geri dönmez. Zaten bir konteyner düzenleyicisi üzerinde yaşayan ve düğüm işletim sistemine neredeyse hiç dokunmayan ekiplerde de kazanç sınırlıdır, çünkü değişkenlik artık işletim sisteminde değildir. Satıcı destek matrisi belirli bir dağıtımı şart koşuyorsa tartışma açılmadan biter. Ve en önemlisi: ekipte kimse bu yaklaşımı benimsemek istemiyorsa yapmayın. Yarısı NixOS, yarısı elle düzenlenmiş dosyalarla yaşayan bir sunucu, düz bir dağıtımdan daha kötü bir yerdir; hem sürüklenmeyi yaşarsınız hem de size onu göstermesi gereken araçlar yalan söyler.
Karar genelde tek bir şeye iner. Makine sayınız artıyor ve her yenisini kurmak bir arkeoloji seansına dönüşüyorsa, ödemeniz gereken bedel birkaç haftalık öğrenme süresidir. Onu bir kez ödersiniz. Sürüklenmenin bedelini her ay yeniden ödersiniz.