A 03-06 et vaig ensenyar ps, kill i nohup, i et vaig dir amb totes les lletres que res d'això no serveix per gestionar un servei de debò. A 03-07 vas muntar la còpia nocturna amb cron i et vaig avisar que existia alguna cosa millor. Aquesta és la lliçó on se salden les dues promeses, i és el cor del mòdul. Avui Tramontana Reserves s'arrenca a mà: si srv-tramontana es reinicia, l'aplicació no torna; si el procés 1284 mor, ningú no l'aixeca; desplegar.sh canvia l'enllaç simbòlic i no reinicia res; i la còpia de les 4:20 depèn d'una línia de cron sense control real de solapament ni registre decent. En acabar tindràs tramontana.service escrit, enfortit i habilitat, i copia_tramontana.sh convertit en un servei amb el seu timer.

Contingut

  1. Què és un sistema d'init i què resol systemd
  2. PID 1, cgroups i els tipus d'unitat
  3. systemctl: consultar, controlar i habilitar
  4. On viuen les unitats i quina precedència tenen
  5. Escriure una unitat .service secció per secció
  6. Enfortir la unitat i posar-li sostre amb cgroups v2
  7. Targets i nivells d'execució
  8. Temporitzadors: el substitut de cron
  9. Analitzar l'arrencada i llançar treballs transitoris
  10. Cas Tramontana: el servei, el temporitzador i el desplegament complet

  1. Què és un sistema d'init i què resol systemd

El nucli arrenca, munta l'arrel i executa un procés: l'init, amb PID 1, del qual descendeix tota la resta. La seva feina és aixecar el sistema, mantenir els serveis vius i apagar-lo amb ordre.

Aspecte SysV init systemd
Arrencada Scripts seqüencials /etc/init.d/* Paral·lela, guiada per dependències declarades
Descripció Codi de shell que reinventa arrencar, parar i recarregar Fitxer declaratiu de poques línies
Supervisió i seguiment Cap; fitxers PID que menteixen Reinici automàtic amb política i límits; cgroups, que el nucli sí que coneix
Registre / tasques Cadascú al seu fitxer; cron a part Journal estructurat (05-06); temporitzadors integrats
Aïllament Res Espais de noms, ProtectSystem, capacitats

La conseqüència pràctica dels cgroups és enorme: si la teva aplicació llança cinc fills i un queda orfe, systemctl stop els mata tots, perquè el nucli els comptabilitza al mateix grup de control. Amb un fitxer PID, aquell orfe continuava escrivint mentre tu et pensaves haver aturat el servei.

  1. PID 1, cgroups i els tipus d'unitat

$ ps -p 1 -o comm= ; systemd-cgls --no-pager | sed -n '4,5p'
systemd
│ ├─tramontana.service
│ │ └─1284 /opt/tramontana/app/bin/tramontana --config ...

Tot a systemd és una unitat, i el sufix del fitxer diu de quin tipus:

Tipus Per a què serveix Exemple
.service Un procés gestionat: arrencar, parar, supervisar tramontana.service
.socket / .path Activa el servei a la primera connexió / en canviar un fitxer ssh.socket
.target / .timer Punt de sincronització que agrupa unitats / dispara una altra unitat a una hora multi-user.target, tramontana-copia.timer
.mount / .slice Punt de muntatge (un per línia de fstab) / grup que reparteix CPU, memòria i E/S srv-tramontana-backups.mount

  1. systemctl: consultar, controlar i habilitar

$ systemctl status tramontana.service
● tramontana.service - Tramontana Reserves (aplicació web)
     Loaded: loaded (/etc/systemd/system/tramontana.service; enabled; preset: enabled)
     Active: active (running) since Tue 2026-08-18 04:31:02 CEST; 7h 12min ago
   Main PID: 1284 (tramontana)
      Tasks: 12 (limit: 4571)
     Memory: 214.8M (max: 512.0M available: 297.1M)
     CGroup: /system.slice/tramontana.service
             └─1284 /opt/tramontana/app/bin/tramontana --config /etc/tramontana/app.conf
Aug 18 11:02:17 srv-tramontana tramontana[1284]: db_timeout després de 30s (connexions=200)

Cal llegir la sortida sencera:

  • Loaded: de quin fitxer ve i si està enabled (arrenca sol en iniciar la màquina) o disabled. Un servei pot estar active i disabled: funciona ara, però no tornarà després d'un reinici.
  • Active: active (running) per a un dimoni, active (exited) per a un oneshot que va acabar bé, failed amb el codi de sortida i el senyal si va morir.
  • Tasks, Memory, CGroup: consum real del cgroup i arbre complet de processos, fills inclosos, no una estimació. Les últimes línies són registre del journal, que exprimiràs a 05-06.
sudo systemctl start|stop|restart tramontana.service
sudo systemctl reload-or-restart tramontana.service # recarrega (si hi ha ExecReload) o reinicia
systemctl is-active|is-enabled|is-failed tramontana.service   # scripts: codi 0/3
systemctl --failed ; systemctl list-units --type=service --state=running

enable és literalment un enllaç simbòlic

Aquí es tanca el cercle amb 02-06:

$ sudo systemctl enable tramontana.service
Created symlink /etc/systemd/system/multi-user.target.wants/tramontana.service → /etc/systemd/system/tramontana.service.
$ sudo systemctl mask tramontana.service
Created symlink /etc/systemd/system/tramontana.service → /dev/null.

enable crea l'enllaç dins del .wants del target que indiqui [Install] i disable l'esborra: res més. Per això enable no arrenca el servei (per a això, enable --now) i disable no l'atura. mask enllaça la unitat a /dev/null, i així no es pot arrencar en absolut —ni a mà, ni com a dependència, ni per un sòcol—, que és el que vols durant un manteniment; unmask ho reverteix.

Estat Arrenca en iniciar? A mà?
enabled / disabled Sí / no Sí en tots dos casos
masked / static No / només com a dependència (no té [Install]) No, de cap manera / sí

sudo systemctl daemon-reload és obligatori després de crear o editar qualsevol fitxer d'unitat: si no, systemd continua treballant amb la versió anterior en memòria i et tornaràs boig. I recarregar la configuració no reinicia el servei: això és un restart a part.

systemctl cat tramontana.service       # la unitat efectiva, amb els seus drop-ins
systemctl show tramontana.service -p Restart -p User -p MemoryMax
systemctl list-dependencies tramontana.service
sudo systemctl edit tramontana.service # crea un DROP-IN sense tocar l'original

edit obre /etc/systemd/system/tramontana.service.d/override.conf, on escrius només la secció i les directives que cal canviar. Un drop-in és millor que editar la unitat original: sobreviu a l'actualització del paquet que la va portar, deixa explícit què has canviat tu i s'elimina esborrant un fitxer. Avís amb les directives de llista (ExecStart, Environment): per substituir cal buidar-les abans amb ExecStart= en blanc, o s'acumulen.

  1. On viuen les unitats i quina precedència tenen

Per prioritat decreixent: /etc/systemd/system/ (on escrius tu), /run/systemd/system/ (unitats transitòries, a la RAM) i /lib/systemd/system/ (les dels paquets). Les teves unitats van a /etc/systemd/system/; les dels paquets no es toquen, s'ajusten amb drop-ins. Un fitxer amb el mateix nom a /etc substitueix completament el de /lib.

  1. Escriure una unitat .service secció per secció

[Unit]: què és i amb qui es relaciona

Aquí s'hi embolica tothom, perquè hi ha dos eixos independents: l'ordre (quan arrenca respecte d'una altra unitat) i la dependència (si la necessita per existir).

Directiva Eix Significat
After= / Before= Ordre «Arrenca'm després/abans de X», sense obligar que X existeixi
Wants= Dependència feble Intenta arrencar X; si X falla, jo arrenco igualment
Requires= / BindsTo= Dependència forta Si X falla, jo no arrenco; amb BindsTo, a més m'aturo si X s'atura
Conflicts= Exclusió X i jo no podem estar actius alhora

L'error clàssic és posar Requires=postgresql.service i creure que garanteix que la base de dades estigui llesta abans que tu. No ho garanteix: Requires sense After arrenca les dues en paral·lel, així que calen les dues directives. Com a norma fes servir Wants=: si el servei de registres remot falla, prefereixes que la teva aplicació arrenqui igualment.

[Service]: com s'executa

Type= Quan es considera arrencat Quan fer-lo servir
simple / exec En llançar ExecStart / quan l'execve() té èxit Procés en primer pla; exec detecta a més un binari inexistent
forking Quan el pare surt i deixa el fill Dimonis clàssics; requereix PIDFile=
oneshot Quan el procés acaba Còpies i migracions; amb RemainAfterExit=yes queda com a actiu
notify Quan el procés avisa per sd_notify() Serveis que saben dir «ja estic llest»

Un servei modern en Go, Java o Python gairebé sempre és simple o exec: no l'enviïs al fons amb & ni facis servir nohup, perquè systemd ja se n'encarrega. La resta de directives del dia a dia:

ExecStartPre=/usr/bin/test -r /etc/tramontana/app.conf  # si falla, no arrenca
ExecStart=/opt/tramontana/app/bin/tramontana --config /etc/tramontana/app.conf
ExecReload=/bin/kill -HUP $MAINPID    # què fa 'systemctl reload'
ExecStop=/opt/tramontana/app/bin/tramontana --parada-neta
User=svc-tramontana
Group=tramontana
Environment=TRAMONTANA_ENTORN=produccio
EnvironmentFile=-/etc/tramontana/entorn   # el '-': si no existeix, continua igual
Restart=on-failure
RestartSec=5
StartLimitBurst=5
TimeoutStopSec=30
KillMode=mixed
  • Restart=: no (per defecte), on-failure (codi diferent de zero o senyal fatal: l'opció correcta per a una aplicació), always, on-abnormal.
  • RestartSec + StartLimitBurst + StartLimitIntervalSec: reinicia als 5 s, i si ho fa 5 vegades en 300 s es rendeix i queda failed. Sense aquest límit, una aplicació que no arrenca entra en un bucle infinit que consumeix CPU i omple el registre; per tornar-hi, systemctl reset-failed.
  • TimeoutStopSec i KillMode=mixed: quant s'espera després del SIGTERM abans del SIGKILL, i que aquest arribi a tot el cgroup. És la seqüència «SIGTERM → esperar → verificar → -9» de 03-06, automatitzada.

[Install]: quan arrencar sol

Sol tenir una línia, WantedBy=multi-user.target, i és el que llegeix enable per saber en quin directori .wants ha de crear l'enllaç. Una unitat sense [Install] no es pot habilitar: apareixerà com a static.

  1. Enfortir la unitat i posar-li sostre amb cgroups v2

És una de les coses més valuoses de systemd, i és de franc: unes línies deixen el teu servei amb moltíssima menys superfície d'atac de la que aconseguiries a mà.

Directiva Què fa
NoNewPrivileges=yes El procés no pot guanyar privilegis: anul·la SUID i setcap de 05-02
PrivateTmp= / PrivateDevices= / ProtectHome= / ProtectKernelTunables= /tmp propi; només dispositius bàsics; /home i /root invisibles; /proc/sys de només lectura
ProtectSystem=strict + ReadWritePaths= Tot el FS en només lectura tret de la llista mínima que obris
RestrictAddressFamilies= Quines famílies de sòcols pot fer servir (AF_INET AF_INET6 AF_UNIX)
CapabilityBoundingSet= / AmbientCapabilities= Sostre de capacitats (buit = cap) / les que sí que concedeix, p. ex. CAP_NET_BIND_SERVICE
LockPersonality=yes, MemoryDenyWriteExecute=yes Tanquen vies d'explotació conegudes

systemd-analyze security tramontana.service puntua el resultat: la unitat que escriurem al final de la lliçó treu 2.9 OK, davant del 9.6 UNSAFE que treia sense enfortir. Nota pràctica: ProtectSystem=strict trencarà el teu servei la primera vegada, perquè escriu en algun lloc que no has declarat. No ho desactivis: mira el registre, afegeix la ruta a ReadWritePaths i torna-ho a provar. Aquell exercici t'obliga a saber exactament on escriu la teva aplicació.

A la mateixa secció [Service] pertanyen els límits de recursos, que systemd aplica amb cgroups v2:

MemoryMax=512M   # dur: en superar-lo, l'OOM killer actua DINS del servei
MemoryHigh=384M  # a partir d'aquí es pressiona perquè alliberi, sense matar
CPUQuota=150%    # com a molt, 1,5 nuclis dels 2; TasksMax=64 conté una fork bomb
IOWeight=50      # prioritat relativa d'E/S
$ systemd-cgtop --iterations=1 | sed -n '3p'
system.slice/tramontana.service     12    2.8   214.8M    12.0K   840.0K

Aquell MemoryMax impedeix que una fuita de memòria s'endugui per davant la base de dades i el mateix SSH: en superar-lo, el nucli mata dins del cgroup del servei i Restart=on-failure l'aixeca.

  1. Targets i nivells d'execució

Target Què és
multi-user.target (runlevel 3) Sistema complet en xarxa sense entorn gràfic: el normal en un servidor; graphical.target hi afegeix l'escriptori
rescue.target / emergency.target Monousuari amb FS locals / només un shell amb / en lectura: on t'envia un fstab trencat

També existeix network-online.target, el punt de sincronització que indica que hi ha xarxa utilitzable. systemctl get-default diu quin és el predeterminat, set-default el canvia i sudo systemctl isolate rescue.target canvia de target ara mateix, tallant els serveis que sobren.

  1. Temporitzadors: el substitut de cron

Un temporitzador és una unitat que n'activa una altra: per convenció, tramontana-copia.timer dispara tramontana-copia.service (mateix nom, un altre sufix).

Aspecte cron Temporitzador de systemd
Sintaxi Cinc camps críptics OnCalendar= llegible i verificable
Execució perduda (màquina apagada) Es perd Persistent=true l'executa en arrencar
Registre i entorn El que redirigeixis; PATH mínim Journal (journalctl -u); l'entorn de la unitat
Solapament i dependències flock a mà; cap dependència No es rellança si continua actiu; After=, Requires= i l'enfortiment de la secció 6
Veure el programat crontab -l per usuari systemctl list-timers, global
# /etc/systemd/system/tramontana-copia.timer
[Unit]
Description=Còpia diària de Tramontana Reserves
[Timer]
OnCalendar=*-*-* 04:20:00
Persistent=true
RandomizedDelaySec=180
Unit=tramontana-copia.service
[Install]
WantedBy=timers.target

Verifica sempre l'expressió abans de confiar-hi:

$ systemd-analyze calendar '*-*-* 04:20:00'
    Next elapse: Wed 2026-08-19 04:20:00 CEST   (From now: 17h left)

Altres formes de disparament: OnBootSec=5min (després d'arrencar la màquina), OnUnitActiveSec=1h (cada hora des de l'última execució) i OnStartupSec=. I expressions llegibles: daily, weekly, Mon..Fri 08:00, *-*-01 03:00 (dia 1 de cada mes).

$ systemctl list-timers --all | tail -1
Wed 2026-08-19 04:20:00 CEST 17h left Tue 2026-08-18 04:21:43 CEST tramontana-copia.timer

Persistent=true és la raó principal per migrar de cron: si el servidor estava apagat a les 4:20, la còpia s'executa tan bon punt arrenqui en lloc de perdre's fins l'endemà.

  1. Analitzar l'arrencada i llançar treballs transitoris

$ systemd-analyze
Startup finished in 3.412s (kernel) + 11.807s (userspace) = 15.219s
$ systemd-analyze blame | head -1
6.104s [email protected]
$ systemd-analyze critical-chain tramontana.service | head -3
tramontana.service +412ms
└─postgresql.service @5.201s +6.104s
$ sudo systemd-run --unit=purga --property=MemoryMax=256M /home/operador/scripts/purgar_releases.sh --dry-run

blame ordena per durada; critical-chain mostra la cadena que de debò retarda l'arrencada d'una unitat concreta, que és el que importa: un servei lent del qual no depèn ningú no retarda res. I systemd-run llança alguna cosa puntual com a unitat transitòria, registrada al journal i amb els seus límits aplicats, a diferència d'un nohup.

  1. Cas Tramontana: el servei, el temporitzador i el desplegament complet

tramontana.service

# /etc/systemd/system/tramontana.service
[Unit]
Description=Tramontana Reserves (aplicació web)
Documentation=https://intranet.tramontana.example/runbook
Wants=network-online.target
After=network-online.target postgresql.service
[Service]
Type=exec
User=svc-tramontana
Group=tramontana
UMask=0027
WorkingDirectory=/opt/tramontana/app
Environment=TRAMONTANA_ENTORN=produccio
ExecStartPre=/usr/bin/test -r /etc/tramontana/app.conf
ExecStart=/opt/tramontana/app/bin/tramontana --config /etc/tramontana/app.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
StartLimitBurst=5
TimeoutStopSec=30
KillMode=mixed
# Enfortiment
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/log/tramontana /opt/tramontana/shared/uploads
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
CapabilityBoundingSet=
MemoryMax=512M
[Install]
WantedBy=multi-user.target

Decisions que convé entendre: Type=exec perquè l'aplicació es queda en primer pla i així un binari inexistent es detecta en arrencar; User=svc-tramontana amb Group=tramontana, les identitats de 05-01, i UMask=0027 per convenció del curs; ReadWritePaths amb només el registre i les pujades, perquè tota la resta —inclòs /opt/tramontana— ha de ser immutable per a l'aplicació; i CapabilityBoundingSet= buit perquè escolta al 8080 (si passés al 80 s'hi afegiria AmbientCapabilities=CAP_NET_BIND_SERVICE, substituint el setcap de 05-02, que es perdia a cada desplegament).

$ sudo systemctl daemon-reload && sudo systemctl enable --now tramontana.service
$ systemctl is-active tramontana.service && curl -sf -o /dev/null -w '%{http_code}\n' http://10.0.2.15:8080/salut
active
200

El temporitzador de la còpia

# /etc/systemd/system/tramontana-copia.service
[Unit]
Description=Còpia de seguretat de Tramontana Reserves
RequiresMountsFor=/srv/tramontana/backups
[Service]
Type=oneshot
User=operador
Group=tramontana
UMask=0027
ExecStart=/home/operador/scripts/copia_tramontana.sh -q
TimeoutStartSec=30min
Nice=10
IOSchedulingClass=idle
ProtectSystem=strict
ReadWritePaths=/srv/tramontana/backups /var/log/tramontana

Type=oneshot perquè l'script acaba, i sense [Install] perquè no s'habilita: el dispara el temporitzador, i per això apareixerà com a static. RequiresMountsFor= és la joia d'aquesta unitat: si el volum LVM de 05-04 no està muntat, la còpia no s'executa en lloc d'escriure alegrement al directori buit de /, una fallada silenciosa que només descobriries el dia de la restauració. Nice=10 i IOSchedulingClass=idle fan que la còpia cedeixi el pas a l'aplicació, cosa que a 05-07 resultarà important.

$ sudo systemctl daemon-reload && sudo systemctl enable --now tramontana-copia.timer
$ sudo systemctl start tramontana-copia.service  # provar-la JA, sense esperar a les 4:20
$ systemctl status tramontana-copia.service | sed -n '3p'
     Active: inactive (dead) since Tue 2026-08-18 11:41:09 CEST; 8s ago
$ sudo sed -i.bak-$(date +%F) '/copia_tramontana.sh/s/^/#MIGRAT A TIMER /' /etc/cron.d/tramontana

inactive (dead) després d'un oneshot és èxit: l'script va acabar amb codi 0; un failed mostraria el codi de sortida que vas definir a 04-03. I aquell sed final retira la línia de cron, que és la meitat de la feina que la gent oblida: deixar les dues coses actives significa dues còpies simultànies barallant-se pel flock cada matinada.

desplegar.sh reinicia per fi

El deute del Mòdul 4: després de l'ln -sfn atòmic, l'script comprovava la salut contra un procés que continuava executant el release anterior, perquè el binari ja estava carregat en memòria. Ara:

    ln -sfn "releases/${versio}" "${TRAMONTANA_BASE}/app.nou"
    mv -T "${TRAMONTANA_BASE}/app.nou" "${TRAMONTANA_BASE}/app"
    sudo systemctl restart tramontana.service || morir 74 "systemctl restart ha fallat"
    for _ in 1 2 3 4 5 6; do   # esperar que estigui amunt abans de mesurar la salut
        systemctl is-active --quiet tramontana.service && break; sleep 2
    done
    if ! curl -fsS --max-time 5 "http://127.0.0.1:8080/salut" >/dev/null; then
        error "la ${versio} no respon; revertint"
        ln -sfn "releases/${previ}" "${TRAMONTANA_BASE}/app.nou"
        mv -T "${TRAMONTANA_BASE}/app.nou" "${TRAMONTANA_BASE}/app"
        sudo systemctl restart tramontana.service
        morir 75 "reversió a ${previ} completada"
    fi

El sudo systemctl restart funciona sense contrasenya interactiva gràcies a la regla d'/etc/sudoers.d/tramontana que vas escriure a 05-02: aquí és on les tres lliçons encaixen. I systemctl is-active --quiet en bucle evita la fallada clàssica de comprovar la salut abans que el procés hagi obert el port.

Errors Comuns i Consells

  • Oblidar el daemon-reload. Edites la unitat, reinicies i no canvia res: si has tocat un fitxer d'unitat, daemon-reload abans que res. I enable no arrenca el servei, només crea l'enllaç: fes servir enable --now.
  • Requires= sense After=. No garanteix cap ordre: les dues unitats arrenquen en paral·lel. Prefereix Wants= tret que de debò no puguis funcionar sense l'altra. I en migrar a un temporitzador, comenta la línia de cron en el mateix canvi o tindràs dues execucions simultànies.
  • Enviar el procés al fons amb & o nohup a ExecStart. systemd el perd de vista i el dona per mort: deixa que corri en primer pla.
  • Restart=always sense StartLimitBurst. Una aplicació que no arrenca entra en un bucle que consumeix CPU i omple el disc de registres. I no et rendeixis davant de ProtectSystem=strict: la fallada et diu on escriu la teva aplicació, així que afegeix la ruta a ReadWritePaths en lloc de treure la protecció.
  • Editar la unitat d'un paquet a /lib/systemd/system. La propera actualització s'endú la teva feina: drop-in a /etc/systemd/system/<unitat>.d/.
  • Consell: guarda les teves unitats a git al costat dels scripts. Una unitat és codi de producció i es revisa com a tal.

Exercicis

  1. Diagnòstic d'un servei que no arrenca. tramontana.service està en failed. Enumera, en ordre, les cinc ordres que executaries per esbrinar-ne la causa, i digues què t'explica cadascuna.
  2. Un temporitzador amb recuperació. Escriu el parell .service + .timer que executi /home/operador/scripts/purgar_releases.sh tots els dilluns a les 05:10, que es recuperi si el servidor estava apagat, que no se solapi amb la còpia i que quedi registrat. Verifica l'expressió de calendari.
  3. Drop-in en lloc d'edició. El proveïdor demana que Tramontana arrenqui amb TRAMONTANA_MAX_FILS=8 i amb 768 MiB de límit de memòria, sense modificar tramontana.service. Fes-ho i demostra que ha fet efecte.

Solucions

1.

systemctl status tramontana.service      # 1. Estat, codi de sortida i últimes línies
journalctl -u tramontana.service -n 50   # 2. El registre complet de l'arrencada fallida
systemctl cat tramontana.service         # 3. La unitat EFECTIVA, amb els seus drop-ins
sudo -u svc-tramontana /opt/tramontana/app/bin/tramontana --config /etc/tramontana/app.conf
systemd-analyze verify /etc/systemd/system/tramontana.service   # 5. Sintaxi i referències

La quarta ordre llança l'aplicació a mà amb el seu usuari: falla l'aplicació o la unitat?

És el pas que més temps estalvia. Si a mà funciona i per systemd no, el culpable sol ser l'enfortiment —una ruta que falta a ReadWritePaths— o l'entorn, perquè la unitat no hereta les teves variables.

2.

# /etc/systemd/system/tramontana-purga.service
[Unit]
Description=Purga de releases antics de Tramontana
After=tramontana-copia.service
Conflicts=tramontana-copia.service
[Service]
Type=oneshot
User=operador
Group=tramontana
ExecStart=/home/operador/scripts/purgar_releases.sh
TimeoutStartSec=15min
ProtectSystem=strict
ReadWritePaths=/opt/tramontana/releases /var/log/tramontana

# /etc/systemd/system/tramontana-purga.timer
[Unit]
Description=Purga setmanal de releases
[Timer]
OnCalendar=Mon 05:10
Persistent=true
RandomizedDelaySec=300
Unit=tramontana-purga.service
[Install]
WantedBy=timers.target
$ systemd-analyze calendar 'Mon 05:10' | grep 'Next elapse'
    Next elapse: Mon 2026-08-24 05:10:00 CEST
$ sudo systemctl daemon-reload && sudo systemctl enable --now tramontana-purga.timer

Conflicts= més After= garanteixen que no coincideixin, el registre és automàtic al journal i no cal flock: systemd no rellança una unitat que ja està activa.

3. Amb sudo systemctl edit tramontana.service, que crea el drop-in:

### /etc/systemd/system/tramontana.service.d/override.conf
[Service]
Environment=TRAMONTANA_MAX_FILS=8
MemoryMax=768M
$ sudo systemctl daemon-reload && sudo systemctl restart tramontana.service
$ systemctl show tramontana.service -p MemoryMax -p Environment
MemoryMax=805306368
Environment=TRAMONTANA_ENTORN=produccio TRAMONTANA_MAX_FILS=8

systemctl show dona el valor efectiu (en bytes) i systemctl cat mostraria el drop-in al final de la unitat sense haver tocat l'original. I Environment= és acumulativa: la variable nova se suma a les que ja definia la unitat.

Conclusió

srv-tramontana ja es governa sol. Saps què és un init i què aporta systemd davant de SysV —arrencada paral·lela per dependències, supervisió real, cgroups que no menteixen, registre estructurat i aïllament—; coneixes els tipus d'unitat i manegues systemctl de punta a punta: llegeixes un status línia a línia, controles amb start/restart/reload, distingeixes enabled d'active i disable de mask, saps que enable és literalment crear un enllaç simbòlic i que daemon-reload és obligatori després de tocar qualsevol fitxer d'unitat. Ajustes amb drop-ins en lloc d'editar unitats alienes.

Escrius una unitat des de zero i entens les dues coses on tothom ensopega: la diferència entre ordre (After/Before) i dependència (Wants/Requires/BindsTo), i quin Type= correspon a cada manera d'arrencar. Enforteixes amb NoNewPrivileges, ProtectSystem=strict i ReadWritePaths, ho comproves amb systemd-analyze security, poses sostre amb MemoryMax, CPUQuota i TasksMax vigilant-ho amb systemd-cgtop, manegues targets, analitzes l'arrencada amb blame i critical-chain i llances treballs transitoris amb systemd-run. I els deutes estan saldats: tramontana.service existeix, enfortit, amb reinici automàtic i límits, i arrenca sol després d'un reinici del servidor; copia_tramontana.sh és ara tramontana-copia.service disparat per un temporitzador a les 04:20 amb Persistent=true i RequiresMountsFor sobre el volum de 05-04, amb la línia de cron ja retirada; i desplegar.sh fa el systemctl restart que li faltava, recolzant-se en la regla de sudoers de 05-02. Falta la memòria del sistema. El teu servei ja escriu al journal, però no saps consultar-lo; /var/log/tramontana/acces.log porta 412 línies i creixent sense que ningú el roti, que va ser el primer deute que vaig anunciar en tancar el Mòdul 4 i continua sense pagar; i quan la Marta pregunti «què va passar ahir a la nit a les 03:00?», ara mateix no sabries respondre amb precisió. A Registres del Sistema: journald i syslog veuràs les dues capes de registre que conviuen a Ubuntu, exprimiràs journalctl amb tots els seus filtres, faràs el journal persistent, escriuràs un /etc/logrotate.d/tramontana correcte i provat en simulació, connectaràs lib/comuns.sh amb el journal mitjançant logger, i aprendràs què mai no ha d'acabar escrit en un registre.

Curs de Linux: De Principiant a Administrador de Sistemes

Mòdul 1: Introducció a Linux

Mòdul 2: Comandes Bàsiques de Linux

Mòdul 3: Habilitats Avançades en la Línia de Comandes

Mòdul 4: Scripting en Shell

Mòdul 5: Administració del Sistema

Mòdul 6: Xarxes i Seguretat

Mòdul 7: Temes Avançats

Mòdul 8: Projectes Pràctics

© Copyright 2026. Tots els drets reservats