Hlavní navigace

Orchestrujeme pragmaticky: bez zbytečných nástrojů, s pomocí rsync

Jan Molič

Usnadňovat si práci patřilo vždy k běžné praxi (dobrých) správců serverů. V současnosti se tomu říká automatizace. Všichni neustále něco automatizují. Zdá se, že lidstvo již brzy dosáhne plně automatického blahobytu.

Doba čtení: 8 minut

Sdílet

Zadání bylo: vytvořit záložní verzi (failover) webu na vyřazeném počítači, umístěném v kanceláři. Na něm poběží kopie webu, synchronizovaná v pravidelných intervalech s live serverem. To znamená, aby v případě havárie live serveru šlo změnit DNS domény (ručně) a web běžel aspoň nějak. A udělat to celé rychle a jednoduše.

Najít správný bod

Nabízelo se použití Dockeru, Puppetu, Ansible, Proxmoxu a dalších. Jelikož jsem ale neměl chuť sestavovat ani Dockerfile, natož psát konfigurační soubory pro konfigurační soubory, rozhodl jsem se použít rsync.

Podotýkám, že výše zmíněné nástroje mohou být užitečné, pokud jsou použity na správném místě. Člověk by se měl vždy pragmaticky ptát, který nástroj je nejvhodnější pro co a kde? Je s podivem, že většina lidí považuje otevírání piva vývrtkou za pošetilost, přesto dělají totéž, přijde-li na technologie.

Na druhou stranu existují lidé, kteří se snaží naopak zjednodušovat, ale fanaticky až do takové míry, že věci opět začnou obtěžovat. Pivo lze také otevřít kamenem, avšak při pití se můžete pořezat. To je druhý extrém. Pragmatik hledá bod, kdy věci obtěžují právě nejméně.

Smysl automatizace

Co má být smyslem automatizace? Z pragmatického hlediska zmenšení celkově investované lidské práce na minimum. Důležité je ono „celkově investované“, poněvadž nejde jen o prvotní instalaci a konfiguraci serveru, nýbrž také o následnou správu; množství upgradů všeho druhu a tak vůbec. Vše je nutno započítat: i „skákání“ okolo samotných automatizačních nástrojů. Třeba ten server chci mít hotový za pár hodin a pak už na něj nechci aspoň pět let ani sáhnout!

Smyslem automatizace není samoúčelné vrstvení vrstev nad vrstvy. Kupříkladu kombinace gitlab-ci, dockeru, jenkinsu či kubernetes může být fajn, pokud na aplikaci pracuje množství vývojářů a administrátorů s různými právy v systému. Vždycky však hrozí, že vznikne těžkopádná hromada navršených technologií, která se každou chvíli sesype. Pak celkové množství investované práce neustále narůstá.

Řečeno současnou terminologií: z existence orchestru ještě nevyplývá, že bude hrát libozvučně. A kvalitní hudbu dokáží produkovat také sólisté. Tím virtuosem zde bude rsync. Není trendy, zato jsem si jist, že bude hrát stejně spolehlivě i za deset let.

Automatizujeme rsyncem

Na UNIXU a (zatím ještě) Linuxu je skvělé, že všechno je soubor. Vlastností souborů je fakt, že je lze kopírovat. Kopírovat celé stromy souborů umí rsync a dovede to přes ssh. Právě tímto způsobem teď vyřešíme záložní server – jako periodicky synchronizovanou kopii živého serveru.

Někdo by namítl, že kopírovat běžící stroj na běžící stroj je ošklivé, nicméně pragmatik oponuje, že to je jednoduché a zároveň funkční. Hezké to samozřejmě není, ovšem navrstvené vrstvy nejsou o moc hezčí. Když aktualizujete nějaký software, tak to obvykle neprobíhá atomicky, navíc za běhu původní verze toho softwaru, takže kde je vlastně rozdíl?

Instalaci cílového (záložního) stroje jsem provedl tak, že jsem na něm nabootoval flashku s live Linuxem, nakonfiguroval sshd, abych se do stroje dostal z běžícího serveru, připravil disky, připojil vše do /mnt a nakonec tam zkopíroval kořenový souborový systém ze serveru pomocí rsyncu:

# rsync -av / root@backupserver:/mnt/ --one-file-system

Poté proběhly finální úpravy (zachrootování dovnitř /mnt, instalace zavaděče, konfigurace sítě, běžících služeb a podobně). Důležité bylo, že po rebootu naběhl téměř identický systém, jaký je na ostrém serveru.

Jelikož používám sshd na jiném portu než 22, budu nadále k rsyncu přidávat parametr --rsh. Namísto cílového hostitele se pak píše jen dvojtečka před cestou, například takto:

# RSH="ssh root@backupserver -p 1234"
# rsync -av --rsh="$RSH" / :/ --one-file-system

Parametr --one-file-system by měl zaručit, že se bude synchronizovat pouze jeden souborový systém. Není žádoucí, aby se společně s kořenovým oddílem kopírovaly /dev, /proc, /sys ani další připojené svazky. Jenže to nemusí vždy fungovat podle předpokladů.

Pokud někde používáte mount --bind, čili připojujete adresáře do jiných adresářů, pak se jedná o totožný souborový systém a rsync by data z namountovaných adresářů zkopíroval duplicitně. Problém se dá naštěstí vyřešit vyhnáním ďábla ďáblem: kořen pomocí mount --bind namountujeme jinam, například do /mnt/rootfs a budeme rsyncovat tento adresář namísto kořenového.

Uvnitř /mnt/rootfs budou tudíž jen soubory, které opravdu sídlí na kořenovém oddíle a nebude ani potřeba přidávat parametr --one-file-system. Namountování kořene je nejlepší udělat na obou strojích, aby nedošlo k nechtěnému smazání dat na cílovém stroji,

# mount --bind / /mnt/rootfs

Tip: jde to dát do /etc/fstab; jako option uveďte bind; jen pozor, na některých distribucích je potřeba umístit tento řádek až na konec fstabu, jinak se adresář nenamountuje; dále na některých distribucích to i přesto produkuje chybové hlášky, ačkoli to funguje.

Poté už stačí jen zavolat:

# rsync -av --rsh="$RSH" /mnt/rootfs/ :/mnt/rootfs/ --delete

Kdo by chtěl synchronizaci vylepšit, mohl by z /mnt/rootfs  dělat snapshoty a ty rsyncovat, čímž by nedocházelo k nekonzistencím v průběhu synchronizace. Taktéž by asi šlo na cílovém stroji vymyslet něco na způsob „reverzního snapshotu“. Nicméně zůstaneme u jednoduchosti, poněvadž ta je dostačující. Keep it stupid, simple, říká pravidlo. Zesložitit se dá všechno vždycky.

Výjimky a „orchestrace“

Pořád to ovšem není ono. Kopírovat výše uvedeným způsobem „server na server“ by sice fungovalo, jenže jenom jednou, neboť cílový stroj by příště už nenabootoval. Nelze kopírovat všechny soubory. Na cílovém stroji se liší minimálně nastavení sítě, hostname, konfigurace ssh, zavaděče, atd. Určitě taky nechceme kopírovat logy. Napíšeme proto seznam výjimek, které se z live serveru synchronizovat nebudou. Tento seznam pošleme rsyncu na standardní vstup:

# cat backupserver-exclude.txt | rsync -av --rsh="$RSH" --exclude-from=- --delete /mnt/rootfs/ :/mnt/rootfs/

Soubor s výjimkami má syntax: co řádka, to cesta. Cesty mohou obsahovat hvězdičky a otazníky (wildcards).

Zároveň potřebujeme ty soubory, které se na cílovém serveru liší, mít někde uloženy. Jde o to, aby se na cílový stroj vše „natlačilo“ a nikoli tam něco měnilo (to by pak nebyla žádná orchestrace). Vzhledem k tomu, že ty soubory, které se v cílovém systému liší, jsou zároveň výjimkami, tak je nejlepší specifické soubory mít právě na stroji, z nějž rsyncujeme a odtud je následně kopírovat.

Věc rozdělíme do dvou kroků. V prvním budeme synchronizovat kořenový oddíl kromě definovaných výjimek a kromě specifické konfigurace pro cílový systém. V druhém kroku zkopírujeme tuto specifickou konfiguraci.

Soubor backupserver-exclude.txt obsahuje (příklad):

- /etc/runit/runsvdir/*
- /etc/runit/sv/*/supervise
- /etc/runit/sv/*/log/supervise
- /root/.ssh
- /tmp/*
- /var/cache/nginx/*
- /var/cache/pacman/*
- /var/log/klog/*.s
- /var/log/klog/current
- /var/log/klog/lock
- /var/log/svlog/*/*.s
- /var/log/svlog/*/current
- /var/log/svlog/*/lock
- /var/log/socklog/*/*.s
- /var/log/socklog/*/current
- /var/log/socklog/*/lock
- /var/log/wtmp

Adresář backupserver-config obsahuje (příklad):

backupserver-config/
├── etc
│   ├── default
│   │   └── grub
│   ├── fstab
│   ├── hostname
│   ├── runit
│   │   └── runsvdir
│   │   ├── current -> default
│   │   ├── default
│   │   │   ├── agetty-tty1 -> /etc/runit/sv/agetty-tty1
│   │   │   ├── agetty-tty2 -> /etc/runit/sv/agetty-tty2
│   │   │   ├── agetty-tty3 -> /etc/runit/sv/agetty-tty3
│   │   │   ├── agetty-tty4 -> /etc/runit/sv/agetty-tty4
│   │   │   ├── agetty-tty5 -> /etc/runit/sv/agetty-tty5
│   │   │   ├── agetty-tty6 -> /etc/runit/sv/agetty-tty6
│   │   │   ├── crond -> /etc/runit/sv/crond
│   │   │   ├── dbus -> /etc/runit/sv/dbus
│   │   │   ├── dhcpcd-enp2s0 -> /etc/runit/sv/dhcpcd-enp2s0
│   │   │   ├── dnscache -> /etc/runit/sv/dnscache
│   │   │   ├── elogind -> /etc/runit/sv/elogind
│   │   │   ├── klog -> /etc/runit/sv/klog
│   │   │   ├── mysqld -> /etc/runit/sv/mysqld
│   │   │   ├── nginx -> /etc/runit/sv/nginx
│   │   │   ├── ntpd -> /etc/runit/sv/ntpd
│   │   │   ├── pgsql -> /etc/runit/sv/pgsql
│   │   │   ├── php72-fpm -> /etc/runit/sv/php72-fpm
│   │   │   ├── socklog -> /etc/runit/sv/socklog
│   │   │   ├── squid -> /etc/runit/sv/squid
│   │   │   ├── sshd -> /etc/runit/sv/sshd
│   │   │   └── udevd -> /etc/runit/sv/udevd
│   │   └── single
│   │   └── sulogin -> /etc/runit/sv/sulogin
│   └── ssh
│   ├── moduli
│   ├── ssh_config
│   ├── ssh_host_dsa_key
│   ├── ssh_host_dsa_key.pub
│   ├── ssh_host_ecdsa_key
│   ├── ssh_host_ecdsa_key.pub
│   ├── ssh_host_rsa_key
│   ├── ssh_host_rsa_key.pub
│   └── sshd_config
├── root
│   ├── .hushlogin
│   └── .ssh
│   ├── authorized_keys
│   ├── config
│   ├── id_rsa
│   └── id_rsa.pub
└── var
└── spool
└── cron
└── root

Krok 1: seznam výjimek vznikne sloučením obsahu statického souboru s vylistováním souborů a symlinků v adresáři s konfigurací:

( cat backupserver-exclude.txt ; \
  cd backupserver-config && find . -type f,l | sed s#^./##; \
) | rsync -av --rsh="$RSH" --exclude-from=- --delete /mnt/rootfs/ :/mnt/rootfs/

Krok 2: synchronizuje se specifická konfigurace

# rsync -av --rsh="$RSH" ./backupserver-config/ :/mnt/rootfs/

Pozor, nikoli s --delete, jinak by nechtěně zmizel obsah cílových adresářů!

MIF20 tip google

Výsledek

Pak už to stačí dát do cronu. V tomto bodě se tedy synchronizuje celý systém. Zbývá dořešit synchronizaci databází. Jistě by bývalo šlo, udělat dump/restore, ale podle mých zkušeností vůbec nevadí, když se současně s ostatními soubory zkopírují na cílový stroj i soubory běžících databází (jsou-li databáze v tu chvíli na cílovém stroji vypnuty). Není to pěkné, ale je to funkční! Konkrétně Postgres i MySQL jsou vůči této technice docela imunní. Takhle potom vypadá celá „orchestrace“:

$RSH <<< "sv stop pgsql mysqld"
( cat backupserver-exclude.txt ; \
  cd backupserver-config && find . -type f,l | sed s#^./##; \
) | rsync -av --rsh="$RSH" --exclude-from=- --delete /mnt/rootfs/ :/mnt/rootfs/
rsync -av --rsh="$RSH" ./backupserver-config/ :/mnt/rootfs/
$RSH <<< "sv start pgsql mysqld"

Výsledkem je, že netřeba dělat vůbec nic, ani když se na ostrém serveru změní verze databáze či čehokoli jiného. Podobným způsobem spravuji asi 40 serverů a řeknu vám, že je to naprosto suckless!

Odkazy