NixOS unutar Hetzner cloud instance na Ubuntu hostu s Incusom
Zašto Incus, a ne KVM?
Hetzner cloud serveri nižih kategorija ne podržavaju KVM (nested virtualization). To znači da klasični alati poput QEMU/KVM, VirtualBox ili libvirt ne mogu kreirati prave virtuelne mašine. Ostaje nam kontejnerizacija.
Incus (nasljednik LXD projekta) omogućava pokretanje sistemskih kontejnera koji simuliraju kompletno Linux okruženje — sa vlastitim init procesom (systemd), mrežnim stackom, korisnicima i servisima. Za razliku od Docker kontejnera koji pakuju jednu aplikaciju, Incus kontejner se ponaša gotovo identično pravoj virtuelnoj mašini: ima sopstveni /etc, systemd, SSH server i IPv4/IPv6 adresu.
Ubuntu host (Hetzner cloud)
└── Incus
└── nixos-1 (NixOS 26.05 kontejner)
├── systemd kao PID 1
├── dhcpcd → 10.9*.*.35 (incusbr0 bridge)
└── SSH, vim, htop, git...
Priprema: Instalacija slike
Na Ubuntu hostu (root@167.233.x.y) s instaliranim Incusom, preuzimamo NixOS 26.05 sliku:
incus image list images: | grep -i nixos
# Nixos 26.05 amd64 (20260602_01:02) — hash: 595f499fba00
Pokretanje kontejnera mora biti s privilegiranim načinom (objašnjenje slijedi):
incus launch 595f499fba00 nixos-1 -c security.privileged=true
Problemi — jedan za drugim
Problem 1: systemd-networkd se vješa
Odmah pri prvom pokretanju kontejnera, IPv4 adresa nije se pojavila. Istraživanjem unutar kontejnera:
incus exec nixos-1 -- systemctl status systemd-networkd
Servis je bio zaglavljen u stanju activating, a u cgroupima se neprestano rađale nove instance procesa sd-mkuserns.
Uzrok: NixOS 26.05 koristi systemd 260 koji uvodi PrivateUsers=true u systemd-networkd. Ova opcija zahtijeva user namespaces (izolacija korisničkih ID-ova). U neprivilegiranim LXC kontejnerima, kernel ne dozvoljava kreiranje user namespacea, pa systemd-networkd beskonačno pokušava pokrenuti sd-mkuserns helper procese — i nikad ne uspije.
Pokušano rješenje: Prebacivanje na dhcpcd umjesto systemd-networkd:
networking.useNetworkd = false;
networking.interfaces.eth0.useDHCP = true;
Problem 2: Kontejner zaglavljen u “initializing”
Nakon prebacivanja na dhcpcd, kontejner je ostajao u stanju systemctl is-system-running → initializing čak i nakon 60 sekundi. Servisi poput dhcpcd bili su u redu čekanja (job queued), ali nikad se nisu pokrenuli.
Uzrok: Mnogi core sistemski servisi bili su neuspješni još pri startu, blokira li basic.target i sve što ovisi o njemu.
Problem 3: Exit code 243/CREDENTIALS — pravi krivac
Detaljnijom analizom:
systemctl --failed
Otkrivamo niz servisa koji padaju s greškom status=243/CREDENTIALS:
systemd-tmpfiles-setup-dev-early.servicesystemd-journald.servicesystemd-sysctl.service
Uzrok: systemd 260 uveo je ImportCredential= direktive u ove servise. Mehanizam učitavanja kredencijala koristi kernel keyring i memfd_create() sistemske pozive. U LXC kontejnerima, ova infrastruktura nije dostupna, pa servisi izlaze s kodom 243 (EXIT_CREDENTIALS) — još prije nego što počnu raditi.
Ovo je kaskadni kvar: bez systemd-tmpfiles-setup-dev-early, ne može se montirati /dev; bez systemd-journald, nema logiranja; bez ovih, dhcpcd i svi drugi servisi koji o njima ovise ne mogu se pokrenuti.
Problem 4: Greška pri prebacivanju na privilegirani kontejner
U pokušaju da riješimo user namespace problem, probali smo promijeniti kontejner iz neprivilegiranog u privilegirani nakon što je već bio pokrenut:
incus config set nixos-1 security.privileged=true
Greška: Kada kontejner mijenja UID mapiranje (iz 1000000:root u direktni 0:root), svi fajlovi koji su bili kreirani u starom mapiranju postaju nečitljivi. Kontejner se u potpunosti pokvari.
Rješenje: Uvijek kreirati kontejner od početka s privilegiranim načinom:
incus launch 595f499fba00 nixos-1 -c security.privileged=true
Problem 5: Nedostaje /nix/var/nix/profiles/system
Svježi kontejneri iz NixOS 26.05 slike nemaju postavljenu sistemsku profilnu vezu sve dok ne završi uspješan boot. Budući da boot pada (zbog 243/CREDENTIALS), profil se nikad ne kreira.
Posljedica: pri svakom restartovanju, systemctl i nixos-rebuild ne mogu naći trenutni sistem, a bash nije u PATH-u jer /run/current-system/sw/bin ne postoji.
Ručno rješenje (privremeno, da se dođe do mreže):
incus exec nixos-1 -- /bin/sh << 'EOF'
SYSTEM=/nix/store/<hash>-nixos-system-nixos-lxc-26.05.889.b51242d7d436
$SYSTEM/activate
export PATH=/run/current-system/sw/bin
EOF
Problem 6: nixos-rebuild switch pada na D-Bus koraku
Čak i kada uspijemo izgraditi novi sistem, nixos-rebuild switch završava greškom:
Failed to connect to system scope bus via local transport: No such file or directory
Command 'systemd-run ... switch-to-configuration switch' returned non-zero exit status 1.
Uzrok: switch-to-configuration koristi systemd-run koji zahtijeva aktivni D-Bus. U degradiranom stanju boot procesa, D-Bus nije pokrenut.
Rješenje — zaobilaznica:
# Samo izgradnja, bez prebacivanja:
nixos-rebuild build --option sandbox false
# Ručna aktivacija:
NEW=/nix/store/<novi-hash>-nixos-system-nixos-1-lxc-...
nix-env -p /nix/var/nix/profiles/system --set $NEW
$NEW/activate
ln -sf $NEW/init /sbin/init
# Restart kontejnera:
incus restart nixos-1
Problem 7: Konflikt environment.etc s Nix store-om
Pokušaj direktnog dodavanja drop-in fajlova putem:
environment.etc."systemd/system/service.d/fix.conf".text = ''
[Service]
ImportCredential=
'';
Rezultira greškom pri build-u:
mkdir: cannot create directory '.../service.d': Permission denied
Uzrok: /etc/systemd/system/ je simbolički link na direktorij u Nix storeu (read-only). Nije moguće dodati fajlove unutar njega putem environment.etc mehanizma.
Problem 8: Incusov zzz-lxc-service.conf kasni
Incus automatski kreira runtime drop-in:
/run/systemd/system/service.d/zzz-lxc-service.conf
Ovaj fajl ispravno onemogućuje sandboxing za LXC, ali se kreira tek nakon što su servisi već propali pri bootu. Nije prisutan kada systemd prvi put čita konfiguraciju.
Konačno rješenje
Arhitektura fix-a
Koristimo systemd.packages u NixOS konfiguraciji da dodamo vendorski nivo globalnog drop-in fajla. Vendorski fajlovi (u lib/systemd/system/) su dio systemd search path-a i čitaju se pri svim boot ciklusima, uključujući prvi.
systemd.packages = [
(pkgs.writeTextDir "lib/systemd/system/service.d/zz-lxc-cred-fix.conf" ''
[Service]
ImportCredential=
LoadCredential=
PrivateMounts=no
PrivateIPC=no
PrivateTmp=no
...
'')
];
Kompletna configuration.nix
{ modulesPath, lib, pkgs, ... }:
{
imports = [
"${modulesPath}/virtualisation/lxc-container.nix"
];
networking.hostName = "nixos-1";
# Perzistentni ekvivalent Incusovog zzz-lxc-service.conf.
# Kreira se pri prvom bootu, PRIJE nego što systemd pokuša pokrenuti servise.
systemd.packages = [
(pkgs.writeTextDir "lib/systemd/system/service.d/zz-lxc-cred-fix.conf" ''
[Service]
ImportCredential=
LoadCredential=
PrivateMounts=no
PrivateIPC=no
PrivateTmp=no
PrivateDevices=no
PrivateNetwork=no
ProtectSystem=no
ProtectHome=no
ProtectClock=no
ProtectHostname=no
ProtectKernelTunables=no
ProtectKernelModules=no
ProtectKernelLogs=no
ProtectControlGroups=no
ProtectProc=default
ProcSubset=all
RestrictNamespaces=no
NoNewPrivileges=no
ReadWritePaths=
'')
];
# Dodatno osiguranje za tmpfiles servise
systemd.services."systemd-tmpfiles-setup-dev-early".serviceConfig.ImportCredential = lib.mkForce "";
systemd.services."systemd-tmpfiles-setup-dev".serviceConfig.ImportCredential = lib.mkForce "";
systemd.services."systemd-tmpfiles-setup".serviceConfig.ImportCredential = lib.mkForce "";
# Nix buildovi bez kernel namespace sandboxinga (LXC ograničenje)
nix.settings.sandbox = false;
services.openssh.enable = true;
environment.systemPackages = with pkgs; [ vim git htop net-tools curl ];
time.timeZone = "Europe/Sarajevo";
system.stateVersion = "26.05";
}
Rezultat
Nakon incus restart nixos-1 i čekanja ~45 sekundi:
incus list nixos-1
+---------+---------+---------------------+---...---+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE |
+---------+---------+---------------------+---...---+-----------+
| nixos-1 | RUNNING | 10.9*.*.35 (eth0) | ... | CONTAINER |
+---------+---------+---------------------+---...---+-----------+
Preostala samo 2 “failed” jedinice — obje bezopasne, inherentna LXC ograničenja:
dev-hugepages.mount— Huge Pages nije dostupan u kontejnerimarun-wrappers.mount— tmpfs konflikt s Incus mount-om
Sve ostalo radi: SSH, ping 8.8.8.8, htop, ifconfig, vim, DNS rezolucija.
Sažetak problema i rješenja
| # | Problem | Uzrok | Rješenje |
|---|---|---|---|
| 1 | systemd-networkd beskonačna petlja | PrivateUsers=true + nema user NS u LXC | security.privileged=true |
| 2 | Boot zaglavljen u “initializing” | Kaskadni kvar servisa | Fix 243/CREDENTIALS |
| 3 | 243/CREDENTIALS exit | ImportCredential= + LXC bez kernel keyring-a | Globalni drop-in s ImportCredential= |
| 4 | UID mapping kvar | Promjena privilegija na živom kontejneru | Kreirati kontejner odmah kao privilegirani |
| 5 | Nedostaje /nix/var/nix/profiles/system | Neuspješan prvi boot | Ručna aktivacija + ponovni pokušaj |
| 6 | nixos-rebuild switch pada | systemd-run treba D-Bus | nixos-rebuild build + ručna aktivacija |
| 7 | environment.etc konflikt | /etc/systemd/system/ je symlink u Nix store | systemd.packages s vendor drop-in-om |
| 8 | zzz-lxc-service.conf kasni | Kreira se runtime, nakon pada servisa | systemd.packages kreira perzistentni ekvivalent |
Napomena
Generisano od strane Claude 🤖
Ernad Husremović, hernad@bring.out.ba