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
- Les quatre capes que poden fallar
- Connectivitat:
pingi el seu codi de sortida - Resolució de noms
- Ports i sòcols:
ss - Comprovar si un port respon:
nci/dev/tcp - Interfícies, rutes i la IP pròpia
curldavant dewget- Comprovar disponibilitat i mesurar amb
curl - Espera activa amb reintents i retrocés
- Transferències i sessió remota
- Tallafocs, a grans trets
- Seguretat: credencials i certificats
- Aplicació:
veloz_port_obertal toolkit
- 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.
- Connectivitat:
ping i el seu codi de sortida
ping i el seu codi de sortidaping 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):
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.
- 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.
- Ports i sòcols:
ss
ssss 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.
- Comprovar si un port respon:
nc i /dev/tcp
nc i /dev/tcpss 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:
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 |
Sí | No (bashisme, 08-07) |
| Control del temps d'espera | -w propi |
Necessita timeout |
| Variants incompatibles | Sí: -z no hi és a totes |
No |
- 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 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—.
curl davant de wget
curl davant de wgetTots 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.tgzI 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.
- Comprovar disponibilitat i mesurar amb
curl
curlPer 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:
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—.
- 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".
- 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à.
- 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.
- 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), apsmentre dura l'ordre i als registres del servidor. Fes servir--netrc, o una variable d'entorn llegida d'un fitxer amb permisos 600 comveloz-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.
- Aplicació:
veloz_port_obert al toolkit
veloz_port_obert al toolkitLa 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
pingsense-c. En un script es queda penjat per sempre. I unpingfallit 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/tcpsensetimeout. Davant d'una màquina que descarta paquets, l'script es penja més d'un minut. /dev/tcpamb#!/bin/sh. És un bashisme: falla adash(08-07). Si necessites portabilitat, fes servirnc.curlsense-f. Retorna 0 encara que el servidor contesti 500: la teva comprovació sempre dirà que tot va bé.digper saber a quina IP anirà l'aplicació.digignora/etc/hosts; fes servirgetent hosts.- Interpretar malament
127.0.0.1:8080ass. Significa que només s'atén la mateixa màquina, per molt enLISTENque estigui. - Consell: posa
--max-time(otimeout) 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"
fiCinc 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"
fiss -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
- Què és Bash?
- Configurar el teu Entorn
- Navegació Bàsica per la Línia d'Ordres
- Entendre el Shell
- Trobar Ajuda: man, help i --help
Mòdul 2: Ordres Bàsiques de Bash
- Operacions amb Fitxers i Directoris
- Ordres de Processament de Text
- Permisos i Propietat dels Fitxers
- Redirecció i Canonades
- Comodins i Expansió de Rutes
- Historial i Dreceres de Teclat
Mòdul 3: Fonaments de Scripting
- Crear i Executar un Script
- Variables i Constants
- Operadors Bàsics
- Sentències Condicionals
- Arguments i Entrada de l'Usuari
- Cometes, Expansió i Substitució
Mòdul 4: Scripting Intermedi
- Bucles en Bash
- Funcions en Bash
- Arrays i Arrays Associatius
- Manipulació de Cadenes
- La Sentència case i els Menús Interactius
- Aritmètica i Càlculs Numèrics
Mòdul 5: Tècniques Avançades de Scripting
- Operacions Avançades amb Fitxers
- Gestió de Processos
- Gestió d'Errors i Depuració
- Expressions Regulars
- Entrada/Sortida Avançada: Descriptors i Here-Documents
- Scripts Modulars i Llibreries Reutilitzables
Mòdul 6: Treballar amb Eines Externes
Mòdul 7: Automatització i Programació
- Tasques Cron
- Automatitzar Tasques
- Scripts de Còpia i Restauració
- Monitoratge i Registre
- Serveis i Temporitzadors amb systemd
- Automatització Remota amb SSH
Mòdul 8: Bones Pràctiques i Optimització
- Escriure Codi Llegible
- Optimitzar Scripts en Bash
- Consideracions de Seguretat
- Control de Versions amb Git
- Anàlisi Estàtica amb ShellCheck i shfmt
- Proves Automatitzades amb Bats
- Portabilitat: POSIX sh enfront de Bashismes
