systemd
det, der starter alt andet på Linux
Hvad det er
Når Linux-kernen er færdig med sin egen opstart, giver den styringen videre til én proces med proces-id 1, og på næsten alle udbredte distributioner er den proces systemd. Det er en system- og servicemanager: den bringer maskinen op, starter og overvåger tjenesterne, mens den kører, og lukker det hele ned i den rigtige rækkefølge. Projektet staver navnet med små bogstaver, aldrig SystemD eller System D, og det afsluttende d er det gamle Unix-tegn for en dæmon. Det hører til familien af init-systemer sammen med SysV-init-scripts og Upstart, som det har afløst på de fleste distributioner, OpenRC og runit, som nogle mindre distributioner stadig vælger, og launchd, der løser samme opgave på macOS.
Alt, hvad systemd styrer, er en unit, beskrevet i en kort tekstfil med sektioner og nøgle-værdi-par. Manualen tæller 11 typer: service, socket, target, device, mount, automount, timer, swap, path, slice og scope. En service kører et program, og en timer starter en anden unit efter en tidsplan. En socket holder en lyttende port og starter sin service, når den første forbindelse kommer, og et target samler units til en tilstand, maskinen kan nå, for eksempel et flerbrugersystem med netværk. Krav og rækkefølge er to forskellige indstillinger, så »det her kræver det dér« og »det her starter efter det dér« står på hver sin linje, og manageren samler dem til én transaktion af start- og stopjob, som kan kontrolleres, før den udføres.
To valg i designet adskiller det fra de scriptbaserede init-systemer, der kom før. Hver tjeneste kører i sin egen kontrolgruppe i kernen, så manageren kender hver eneste proces, tjenesten har startet, og kan stoppe dem alle, begrænse deres hukommelse og gøre deres CPU-forbrug op. Og projektet er en hel samling og ikke kun en manager: dets eget website nævner en logdæmon, journalen, og komponenter til logins, netværkskonfiguration, tidssynkronisering og navneopslag. Distributionerne vælger selv, hvilke af dem de slår til; manageren på PID 1 er den del, der findes næsten overalt.
Hvorfor det betyder noget
På en Linux-server er spørgsmålet sjældent, om man skal køre systemd, for distributionen har allerede valgt det. Den egentlige beslutning er, om man bruger det som supervisor og planlægger, eller om man lægger en anden procesmanager, en containerdæmons genstartspolitik eller cron ovenpå.
Bruger man det direkte, samles opstartsrækkefølge, genstartspolitik, logning,
ressourcegrænser og sandboxing i én fil pr. tjeneste, læst af en proces, der
allerede kører og ikke koster ekstra hukommelse. En unit kan sætte loft over
hukommelsen med MemoryMax=, gøre styresystemets egne mapper skrivebeskyttede
for tjenesten med ProtectSystem= og køre under en midlertidig bruger med
DynamicUser=, og systemd-analyze security giver hver tjeneste en
eksponeringsscore på en skala fra 0,0 til 10,0. Et job, som en timer starter,
logger i modsætning til en crontab-linje til journalen, får samme sandboxing som
enhver anden tjeneste og viser sin seneste og næste kørsel i
systemctl list-timers. Kompetencen kan også flyttes: en ingeniør, der kan læse
en unit-fil, kan drive de fleste Linux-servere, uanset distribution.
Omkostningerne er også reelle. Units virker kun på Linux, så et team, der også leverer til macOS, skriver property lists til launchd, og et, der leverer til BSD-systemerne, skriver rc-scripts. I de fleste containere findes systemd slet ikke: applikationen er selv PID 1, og genstartspolitikken hører til det, der kører containeren. Hvor containere og almindelige tjenester deler vært, mindsker Podmans Quadlet afstanden ved at gøre en containerbeskrivelse til en almindelig service-unit, så begge slags arbejde står under den samme supervisor.
Sådan ser det ud i praksis
Virksomhedens egen platform læner sig op ad systemd tre steder.
Produktionsværten bag hele virksomheden på én vært med 512 MB har én kerne og omkring 464 MB brugbar hukommelse, og den bærer webserveren for syv domæner, git-forgen, en Tor-onion-tjeneste, servere til Gemini og Gopher, krypterede backups og blokering af indtrængningsforsøg. På den størrelse skal hver fast proces gøre sig fortjent til sin hukommelse, så systemd er supervisoren, uden en ekstra supervisorproces lagt ovenpå, og containerne kører som Quadlet-units under den samme manager i stedet for under en containerdæmon med sin egen. Da spørgsmålet var, om værten også kunne bære en onion-tjeneste, viste et døgns målinger, at den ledige hukommelse aldrig faldt under cirka 310 MB af 464.
Krypterede backups med en månedlig gendannelsesøvelse kører på en systemd-timer: et databasedump, hvor der er en database, et krypteret og deduplikeret snapshot sendt over SFTP til lager hos en anden udbyder, en oprydning efter opbevaringsreglerne og et integritetstjek. En dead man’s switch får kun et ping, når det hele er lykkedes. Arbejdet ramte også en af de skarpere kanter i systemd, som er beskrevet nedenfor: adgangsfrasen ligger i en fil, som units læser, fordi en værdi, der sendes gennem unit’ens miljø, får sine escape-sekvenser fortolket, og en backslash i den ville i al stilhed have ændret adgangsfrasen.
Teknologisiden trækker den generelle linje: almindelige systemd-units som udgangspunkt og Docker og Kubernetes til de arbejdsbyrder, der gør sig fortjent til dem.
Hvor det går galt
En redigeret unit kører videre med de gamle indstillinger. Manageren læser
unit-filerne ind i hukommelsen, så en ændring på disken har ingen virkning, før
systemctl daemon-reload genopbygger dens billede af dem, og systemctl cat
kan vise en fil, der afviger fra det, den kørende manager bruger. Lav ændringer
med systemctl edit, der selv genindlæser, eller genindlæs og genstart derefter
tjenesten.
En hemmelighed når frem til tjenesten med et tegn for lidt. Værdier i
Environment= følger de citationsregler, systemd selv bruger, hvor
escape-sekvenser i C-stil fortolkes, og en bogstavelig backslash skal skrives
dobbelt. Dokumentationen advarer også om, at miljøvariabler er synlige for
uprivilegerede klienter over D-Bus. Læg hemmeligheder i en fil, og giv dem
videre med LoadCredential=, der giver tjenesten en skrivebeskyttet kopi, som
kun dens bruger og root kan læse.
En pakkeopdatering fjerner i stilhed en lokal rettelse. Ændringer i
leverandørens fil under /usr/lib/systemd/system/ bliver overskrevet ved næste
opdatering af pakken. Læg i stedet ændringerne i en drop-in under
/etc/systemd/system/<unit>.d/, som lægges oven på originalen og overlever
opgraderinger. Kommandolister lægges sammen i stedet for at blive erstattet, så
en drop-in, der skifter startkommandoen, først rydder den med en tom tildeling:
# /etc/systemd/system/app.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/local/bin/app --config /etc/app/app.toml
Restart=on-failure
Et natligt job springer den nat over, hvor serveren var slukket. En
kalendertimer, der skulle have kørt, mens maskinen var nede, venter blot på sit
næste tidspunkt, fordi Persistent= som standard er false. Sæt
Persistent=true på backups og andre job, der skal køre, så den forsømte kørsel
sker, så snart timeren er aktiv igen.
En tjeneste, der er gået ned, bliver nede. Restart= er som standard no,
så en tjeneste, der dør, forbliver død, til nogen opdager det. Sæt
Restart=on-failure på tjenester, der skal køre hele tiden, og vær klar over,
at når en unit rammer sin startgrænse, opgiver manageren den, og en manuel start
må vente, til intervallet er gået, eller til nogen kører
systemctl reset-failed.
Læs mere
- systemd(1) — selve manageren: unit-typerne, opstartsforløbet og de signaler, den reagerer på.
- systemd.unit(5) — unit-filer, afhængigheder, startgrænser og de drop-in-mapper, der tilsidesætter dem.
- systemd.service(5) — servicetyper, genstartspolitik og hvordan kommandolister nulstilles.
- systemd.exec(5) — kørselsmiljøet, sandboxing og credentials til tjenester.
- systemd.timer(5)
— kalendertimere og monotone timere, herunder
Persistent=. - systemctl(1) — kommandolinjen til at starte, redigere, genindlæse og undersøge units.