estat-servei.sh ja sap dir si la màquina és sana i si el procés veloz-api existeix. Però «el procés existeix» i «el servei respon» no són el mateix: un procés pot ser viu amb el port 8080 tancat, escoltant només a 127.0.0.1 mentre la resta de la xarxa no l'abasta, o bloquejat sense acceptar connexions. Per distingir aquests casos cal sortir del sistema local i mirar la xarxa. Aquesta lliçó cobreix el diagnòstic i l'automatització de xarxa des d'un script: comprovar connectivitat, resoldre noms, veure quins ports escolten, provar si un respon —inclòs el /dev/tcp que va quedar apuntat a 05-05— i esperar amb reintents que un servei s'aixequi.

Contingut

  1. Les quatre capes que poden fallar
  2. Connectivitat: ping i el seu codi de sortida
  3. Resolució de noms
  4. Ports i sòcols: ss
  5. Comprovar si un port respon: nc i /dev/tcp
  6. Interfícies, rutes i la IP pròpia
  7. curl davant de wget
  8. Comprovar disponibilitat i mesurar amb curl
  9. Espera activa amb reintents i retrocés
  10. Transferències i sessió remota
  11. Tallafocs, a grans trets
  12. Seguretat: credencials i certificats
  13. Aplicació: veloz_port_obert al toolkit

  1. Les quatre capes que poden fallar

Quan «l'API no va», el diagnòstic útil és saber on es trenca, i per això convé comprovar en ordre:

Capa Pregunta Eina
Xarxa Arribo a la màquina? ping -c 2 host
Noms El nom es resol a la IP correcta? getent hosts, dig +short
Port Hi ha alguna cosa escoltant i accepta connexions? ss -tulpn, nc -z, /dev/tcp
Aplicació Contesta el que ha de contestar? curl -sfI, i el JSON de 06-05

Saltar-se una capa és la causa dels diagnòstics equivocats: culpar la xarxa quan el que falla és el DNS, o donar per caiguda una API que en realitat retorna un 500 perfectament lliurat. Un script de monitoratge seriós distingeix els quatre casos i ho diu al missatge d'error.

  1. Connectivitat: ping i el seu codi de sortida

ping interactiu es llança sense més i es talla amb Ctrl-C; en un script això és inacceptable, així que sempre porta -c (nombre de paquets) i convé -W (espera màxima per resposta, en segons):

ping -c 2 -W 2 -q srv-veloz-01 >/dev/null 2>&1 || veloz_log_error "srv-veloz-01 no respon"

La clau és que el que importa no és la sortida de ping, sinó el seu codi de retorn: 0 si ha rebut alguna resposta, diferent de 0 si no. Per això -q (mode silenciós) i la redirecció a /dev/null no perden res: tota la informació que necessita l'script és a $?. És exactament el mateix principi de 03-01 aplicat a la xarxa.

Dos avisos. Primer, ping fa servir ICMP, i molts tallafocs el bloquegen encara que el servei funcioni perfectament: un ping fallit no demostra que la màquina sigui caiguda, només que no contesta a ICMP. Segon, per veure per on es perd el camí existeixen traceroute (o tracepath) i mtr, que combina traça i estadística contínua; són eines de diagnòstic manual, no d'script.

  1. Resolució de noms

Abans de culpar la xarxa cal comprovar que el nom es tradueix a una IP. L'opció correcta en un script és getent hosts, la mateixa ordre de 06-03: consulta les fonts que el sistema té configurades a /etc/nsswitch.conf —inclòs /etc/hosts—, que és exactament el que farà l'aplicació en connectar-se.

getent hosts api.veloz.example retorna 10.20.0.15 api.veloz.example, mentre que dig +short api.veloz.example dona la mateixa IP però preguntant només al DNS, ignorant /etc/hosts. La diferència importa: si algú va posar una entrada a /etc/hosts, dig dirà una cosa i l'aplicació n'anirà a una altra. dig +short és en canvi insuperable per preguntar a un servidor concret (dig @8.8.8.8 +short api.veloz.example) i així distingir un problema del DNS intern d'un de general. host és una versió resumida de dig, i nslookup és el veterà que continua apareixent a manuals antics: funciona, però la seva sortida és la més incòmoda d'analitzar i el seu codi de sortida, poc fiable. Ordre de preferència: getent hosts als scripts, dig +short per diagnosticar.

  1. Ports i sòcols: ss

ss mostra els sòcols del sistema i ha substituït netstat, que a moltes distribucions ja ni s'instal·la. La combinació que cal memoritzar és ss -tulpn:

Lletra Significat
-t Sòcols TCP
-u Sòcols UDP
-l Només els que estan escoltant
-p Mostra el procés propietari (cal tenir privilegis per veure els aliens)
-n Números en comptes de noms: 8080 en lloc de http-alt
ss -tulpn | grep ':8080'
# LISTEN 0 511 127.0.0.1:8080 0.0.0.0:* users:(("veloz-api",pid=2114,fd=6))

Llegir aquesta línia sencera és el diagnòstic que faltava a 06-03. LISTEN confirma que accepta connexions; 127.0.0.1:8080 és la part reveladora: l'API escolta només a la interfície local, així que cap altre equip no s'hi podrà connectar encara que el procés estigui perfecte. Si hi posés 0.0.0.0:8080, escoltaria a totes les interfícies. I users:(("veloz-api",pid=2114)) diu quin procés el té, que és la resposta a «alguna cosa m'ocupa el port». Per al mateix des de l'angle dels fitxers oberts hi ha lsof -i :8080 (06-03), útil quan ss no està disponible.

  1. Comprovar si un port respon: nc i /dev/tcp

ss mira des de dins de la màquina. Per comprovar-ho des de fora —o simplement per verificar que una connexió s'estableix de debò— hi ha dues formes.

La primera és nc (netcat) amb -z, que intenta connectar sense enviar dades, i -w amb un temps màxim: nc -z -w2 localhost 8080 && echo obert. La segona no necessita instal·lar res, perquè és una prestació de Bash: els pseudofitxers /dev/tcp/host/port i /dev/udp/host/port. Obrir-los amb la redirecció de descriptors de 05-05 intenta una connexió TCP:

if timeout 2 bash -c 'exec 3<>/dev/tcp/localhost/8080' 2>/dev/null; then
    echo "port obert"
fi

Tres detalls fan que això funcioni. exec 3<> obre el descriptor 3 per a lectura i escriptura sobre la connexió; si el port és tancat, la redirecció falla i el bash -c retorna un codi diferent de 0. El timeout 2 de 05-02 és obligatori: sense ell, una màquina que descarta paquets en silenci deixaria l'script penjat durant el temps d'espera del sistema, que pot ser de més d'un minut. I el 2>/dev/null silencia el missatge Connection refused que Bash imprimeix.

L'avís de portabilitat és seriós: /dev/tcp no és un fitxer real ni una característica del sistema, és una prestació de Bash. No existeix a dash, que és el /bin/sh d'Ubuntu, així que un script amb #!/bin/sh que el faci servir fallarà amb No such file or directory. A més, algunes distribucions compilen Bash sense aquesta prestació. La comparació queda així:

nc -z -w2 /dev/tcp
Cal instal·lar alguna cosa Sí (netcat-openbsd) No, ve amb Bash
Funciona a sh/dash No (bashisme, 08-07)
Control del temps d'espera -w propi Necessita timeout
Variants incompatibles Sí: -z no hi és a totes No

  1. Interfícies, rutes i la IP pròpia

La família ip va substituir ifconfig i route. ip a (abreviatura d'ip address show) llista les interfícies amb les seves adreces, ip -4 a només les IPv4, i ip r mostra la taula de rutes, la primera línia de la qual default via ... és la porta d'enllaç.

Obtenir la IP pròpia en un script té un parany: una màquina pot tenir diverses interfícies, i hostname -I retorna totes separades per espais, sense dir quina es farà servir per sortir. La forma fiable és preguntar-ho a la taula de rutes:

ip -4 route get 1.1.1.1 | awk '{ for (i=1;i<=NF;i++) if ($i=="src") { print $(i+1); exit } }'

ip route get no envia cap paquet: consulta quina ruta i IP d'origen faria servir el nucli per arribar a aquell destí, que és justament la resposta correcta. L'awk de 06-01 busca la paraula src i pren el camp següent, en lloc de refiar-se d'una posició fixa que canvia segons la configuració —el mateix principi de robustesa que $NF—.

  1. curl davant de wget

Tots dos descarreguen per HTTP, però estan pensats per a coses diferents:

curl wget
Sortida per defecte La sortida estàndard (per a canonades) Un fitxer al disc
Fort en APIs: capçaleres, mètodes, cossos Descàrregues: reintents, recursivitat, reprendre
Segueix redireccions Només amb -L Per defecte
Descàrrega recursiva d'un lloc No Sí (-r)
Presència Gairebé universal Habitual a Linux, absent a macOS
Falla amb error HTTP Només amb -f Sí per defecte

Per a un script d'operacions, la regla és senzilla: curl per parlar amb serveis, wget per baixar-se un fitxer gran de forma desatesa. Descarregar amb tots dos:

curl -sSfL -o /tmp/veloz-cli.tgz https://descargas.veloz.example/cli.tgz
wget -q --tries=3 --timeout=20 -O /tmp/veloz-cli.tgz https://descargas.veloz.example/cli.tgz

I un advertiment d'higiene: curl ... | bash —l'«instal·lador d'una línia» que promocionen molts projectes— executa com a teu un codi que no has vist i que pot canviar entre dues execucions. Descarrega primer, revisa, executa després.

  1. Comprovar disponibilitat i mesurar amb curl

Per saber si un servei HTTP és viu sense descarregar el cos, -I demana només les capçaleres i -f fa que curl falli amb codi 22 davant d'un error HTTP en lloc de retornar 0 tan tranquil:

curl -sfI --max-time 5 http://localhost:8080/salud >/dev/null && veloz_log_info "api viva"

Aquest -sfI amb --max-time és la comprovació de disponibilitat estàndard: silenciós, fallant davant d'errors i sense poder penjar-se. El desglossament fi de -s, -S i -f i dels codis HTTP correspon a 06-05; aquí el que interessa és el codi de sortida.

Per mesurar, -w imprimeix variables en acabar, amb -o /dev/null per llençar el cos:

curl -s -o /dev/null -w 'codi=%{http_code} total=%{time_total}s connexio=%{time_connect}s\n' \
    http://localhost:8080/salud          # -> codi=200 total=0.043s connexio=0.001s

%{time_total} és la latència completa i %{time_connect} només l'establiment de la connexió: si el total és alt però la connexió és ràpida, el problema és a l'aplicació, no a la xarxa. Registrar aquest número a cada execució dona una sèrie temporal amb què detectar degradacions abans que es converteixin en caigudes —la base del projecte 09-04—.

  1. Espera activa amb reintents i retrocés

Després de reiniciar veloz-api, el port triga uns segons a acceptar connexions. Comprovar-ho immediatament dona un fals negatiu; dormir un temps fix és endevinar. La solució és l'espera activa amb retrocés exponencial de 05-03, aplicada a la xarxa:

# veloz_espera_servei — Espera que un port accepti connexions. Us: ... <host> <port> [intents]
veloz_espera_servei() {
    local host="${1:?}" port="${2:?}" intents="${3:-6}" espera=1 i
    for (( i = 1; i <= intents; i++ )); do
        if veloz_port_obert "$host" "$port"; then
            veloz_log_info "$host:$port disponible despres de $i intent(s)"
            return 0
        fi
        veloz_log_info "intent $i/$intents fallit; reintent en ${espera}s"
        sleep "$espera"
        espera=$(( espera * 2 ))
    done
    veloz_log_error "$host:$port no ha respost despres de $intents intents"
    return 1
}

El retrocés (1, 2, 4, 8…) és preferible a un interval fix per dos motius: reacciona ràpid si el servei s'aixeca de seguida i no atabala una màquina que ja va justa. Els sis intents per defecte cobreixen uns 63 segons, suficient per a un reinici normal. I el return 1 final és el que permet encadenar veloz_espera_servei localhost 8080 || veloz_morir 1 "l'API no s'ha aixecat".

  1. Transferències i sessió remota

Copiar fitxers entre màquines es fa amb scp origen usuari@desti:/cami per a còpies puntuals o amb rsync -az --delete origen/ usuari@desti:/cami/ quan cal sincronitzar directoris, ja que només transfereix les diferències. Executar ordres en una altra màquina és ssh usuari@desti 'ordre'. Els tres són la base de l'automatització remota i tenen la seva pròpia lliçó: claus sense contrasenya, ssh-agent, known_hosts, opcions no interactives i els perills d'executar a cegues en diverses màquines es veuen a 07-06. L'única cosa que convé retenir ara: en un script, ssh necessita -o BatchMode=yes perquè falli en comptes de quedar-se esperant una contrasenya que ningú no teclejarà.

  1. Tallafocs, a grans trets

Quan el port és en LISTEN a 0.0.0.0 però des d'una altra màquina no s'hi arriba, el sospitós habitual és el tallafocs. A Ubuntu, ufw status verbose (cal tenir privilegis) llista les regles actives; per sota hi ha iptables -L -n o nft list ruleset. Al núvol hi ha a més un segon tallafocs fora de la màquina —grups de seguretat— que cap ordre local no pot veure, i que explica molts diagnòstics impossibles. Regla mental: si ss diu que escolta a 0.0.0.0, nc funciona des de la mateixa màquina i no des de fora, el problema és al camí, no al servei.

  1. Seguretat: credencials i certificats

Tres regles que no es negocien, i que es desenvolupen a 08-03:

  • No incrustis mai credencials a l'URL. curl https://usuari:[email protected]/ deixa la contrasenya a l'historial (02-06), a ps mentre dura l'ordre i als registres del servidor. Fes servir --netrc, o una variable d'entorn llegida d'un fitxer amb permisos 600 com veloz-ops.conf.
  • No desactivis la verificació de certificats. curl -k (o --insecure) accepta qualsevol certificat, inclòs el de qui s'interposi a la connexió: converteix HTTPS en HTTP amb un cadenat fals. Si el certificat és intern, el correcte és instal·lar-lo o passar-lo amb --cacert /cami/ca.pem.
  • Fes servir sempre HTTPS, fins i tot a la xarxa interna. Una xarxa «de confiança» deixa de ser-ho el dia que algú hi connecta un portàtil compromès.

  1. Aplicació: veloz_port_obert al toolkit

La funció que faltava a lib/comu.sh, amb les dues implementacions i elecció automàtica:

# veloz_port_obert — Accepta connexions host:port? Us: veloz_port_obert <host> <port> [seg]
veloz_port_obert() {
    local host="${1:?falta el host}" port="${2:?falta el port}" seg="${3:-2}"
    [[ "$port" =~ ^[0-9]+$ ]] && (( port > 0 && port < 65536 )) || {
        veloz_log_error "port invalid: $port"; return 64; }
    if command -v nc >/dev/null 2>&1; then
        nc -z -w"$seg" "$host" "$port" >/dev/null 2>&1
    else
        timeout "$seg" bash -c "exec 3<>/dev/tcp/$host/$port" 2>/dev/null
    fi
}

La validació del port amb =~ (05-04) no és paranoia: el número acaba dins d'una cadena que bash -c executarà, així que un valor sense validar seria una injecció de codi en tota regla. command -v (06-03) tria la implementació disponible, i totes dues branques retornen directament el seu codi de sortida, que es converteix en el de la funció.

Amb ella, estat-servei.sh distingeix per fi els casos que abans confonia:

comprovar_api() {
    local host=localhost port=8080
    if ! pgrep -f veloz-api >/dev/null; then
        veloz_log_error "veloz-api: proces no trobat"; return 1
    elif ! veloz_port_obert "$host" "$port"; then
        veloz_log_error "veloz-api: proces viu pero $port no accepta connexions"; return 1
    elif ! curl -sfI --max-time 5 "http://$host:$port/salud" >/dev/null; then
        veloz_log_error "veloz-api: port obert pero /salud no respon correctament"; return 1
    fi
    veloz_log_info "veloz-api: correcte"
}

Aquests tres missatges són la lliçó sencera resumida: procés, port i aplicació són tres coses diferents, i dir quina ha fallat estalvia mitja hora de diagnòstic a qui llegeixi l'alerta a les tres de la matinada. Aquesta funció és el germen del projecte 09-04.

Errors Habituals i Consells

  • ping sense -c. En un script es queda penjat per sempre. I un ping fallit no prova que la màquina sigui caiguda: molts tallafocs bloquegen ICMP.
  • Confiar només en pgrep. Que el procés existeixi no significa que el port accepti connexions ni que l'aplicació contesti.
  • Fer servir /dev/tcp sense timeout. Davant d'una màquina que descarta paquets, l'script es penja més d'un minut.
  • /dev/tcp amb #!/bin/sh. És un bashisme: falla a dash (08-07). Si necessites portabilitat, fes servir nc.
  • curl sense -f. Retorna 0 encara que el servidor contesti 500: la teva comprovació sempre dirà que tot va bé.
  • dig per saber a quina IP anirà l'aplicació. dig ignora /etc/hosts; fes servir getent hosts.
  • Interpretar malament 127.0.0.1:8080 a ss. Significa que només s'atén la mateixa màquina, per molt en LISTEN que estigui.
  • Consell: posa --max-time (o timeout) a tota ordre de xarxa. Un script de monitoratge que es penja és pitjor que un que falla, perquè ningú no se n'assabenta.
  • Consell: diagnostica sempre en l'ordre xarxa → nom → port → aplicació, i fes que el missatge d'error digui en quina capa s'ha trencat.

Exercicis

Exercici 1. Escriu una funció veloz_dns_ok que comprovi que un nom resol i que la IP obtinguda coincideix amb l'esperada, registrant missatges diferents per a «no resol» i «resol a una IP inesperada».

Exercici 2. Escriu un fragment que, després de reiniciar veloz-api, esperi fins a 30 segons que /salud contesti correctament, mesurant quant ha trigat, i surti amb error si no ho aconsegueix.

Exercici 3. Escriu una comprovació que detecti el cas concret de «l'API escolta només a 127.0.0.1» i l'avisi com a problema de configuració, no com a caiguda.

Solucions

Solució 1.

# veloz_dns_ok — Us: veloz_dns_ok <nom> <ip_esperada>
veloz_dns_ok() {
    local nom="${1:?}" esperada="${2:?}" obtinguda
    obtinguda=$(getent hosts "$nom" | awk 'NR==1 { print $1 }') || true
    if [[ -z "$obtinguda" ]]; then
        veloz_log_error "DNS: $nom no resol"; return 1
    elif [[ "$obtinguda" != "$esperada" ]]; then
        veloz_log_error "DNS: $nom resol a $obtinguda, s'esperava $esperada"; return 2
    fi
    veloz_log_info "DNS: $nom -> $obtinguda"
}

getent hosts pot retornar diverses línies si hi ha diverses adreces, d'aquí el NR==1. El || true evita que set -e (05-03) mati l'script quan el nom no resol: aquí la fallada és un resultat esperat que volem tractar, no un error. Els codis 1 i 2 diferents permeten a qui la crida reaccionar de manera diferent a «no hi ha DNS» i «el DNS apunta malament», que són incidències molt diferents.

Solució 2.

inici=$(date +%s)
if veloz_espera_servei localhost 8080 5 &&
   curl -sfI --max-time 5 http://localhost:8080/salud >/dev/null; then
    veloz_log_info "veloz-api llesta en $(( $(date +%s) - inici ))s"
else
    veloz_morir 1 "veloz-api no s'ha aixecat en 30s"
fi

Cinc intents amb retrocés 1+2+4+8+16 cobreixen 31 segons. S'encadenen les dues comprovacions perquè són diferents: primer que el port accepti connexions, després que l'aplicació contesti bé —un servei pot obrir el port abans d'acabar d'arrencar—. La mesura amb date +%s és l'aritmètica d'època de 04-06.

Solució 3.

escoltes=$(ss -tuln | awk '$1=="tcp" && $5 ~ /:8080$/ { print $5 }')
if [[ -z "$escoltes" ]]; then
    veloz_log_error "ningu escolta al 8080"
elif [[ "$escoltes" == 127.0.0.1:* || "$escoltes" == "[::1]:"* ]]; then
    veloz_log_error "CONFIGURACIO: veloz-api nomes escolta en local ($escoltes)"
else
    veloz_log_info "veloz-api escolta a $escoltes"
fi

ss -tuln sense -p no necessita privilegis, que és el desitjable en un script de comprovació. L'awk filtra la columna d'adreça local que acabi en :8080, i les comparacions amb globs de 04-05 detecten tant 127.0.0.1 com la variant IPv6 [::1]. Distingir aquest cas importa molt: és una fallada de configuració que s'arregla en un fitxer, no una caiguda que s'arregla reiniciant.

Conclusió

Diagnosticar la xarxa des d'un script és recórrer quatre capes en ordre —xarxa, noms, port, aplicació— i dir en quina s'ha trencat. ping -c -W respon per la primera, i el que se'n fa servir és el seu codi de sortida, no la seva sortida; recorda que ICMP es bloqueja sovint. Els noms es resolen amb getent hosts als scripts (veu el mateix que l'aplicació, inclòs /etc/hosts) i amb dig +short per diagnosticar contra un servidor concret. ss -tulpn ensenya qui escolta i en quina adreça, i aquesta adreça distingeix un 0.0.0.0 accessible d'un 127.0.0.1 que condemna el servei a parlar només amb ell mateix. Per provar un port des de fora hi ha nc -z -w2 i el bashisme /dev/tcp/host/port, que no requereix instal·lar res però exigeix timeout i no existeix a sh. ip route get dona la IP pròpia de forma fiable, curl -sfI --max-time comprova disponibilitat HTTP fallant de debò davant d'un error, curl -w mesura latències, i l'espera activa amb retrocés exponencial substitueix els sleep a ull quan cal esperar que un servei s'aixequi. Tot amb temps d'espera, mai sense; i sense credencials a l'URL ni curl -k.

estat-servei.sh ja distingeix procés, port i aplicació. Però es queda a la porta: sap que /salud retorna un 200, no què diu. La veloz-api respon JSON —estat de la base de dades, enviaments a la cua, versió desplegada, mètriques—, i per a un script això és text que no s'ha de tocar amb grep ni amb sed. La lliçó següent (06-05) tanca el mòdul amb curl a fons per a APIs i amb jq, l'eina que converteix JSON en dades manejables i també en sortida: informe-diari.sh deixarà d'escriure només text per publicar el seu resum com a JSON.

Curs de Programació en Bash

Mòdul 1: Introducció a Bash

Mòdul 2: Ordres Bàsiques de Bash

Mòdul 3: Fonaments de Scripting

Mòdul 4: Scripting Intermedi

Mòdul 5: Tècniques Avançades de Scripting

Mòdul 6: Treballar amb Eines Externes

Mòdul 7: Automatització i Programació

Mòdul 8: Bones Pràctiques i Optimització

Mòdul 9: Projectes del Món Real

© Copyright 2026. Tots els drets reservats