Els tres projectes anteriors miren cap endins del servidor: el seu estat, els seus registres, les seves dades. Aquest mira cap enfora. Quan algú a Veloz Envíos diu «la xarxa va malament», normalment vol dir que l'aplicació triga, i la feina d'operacions és convertir aquesta frase en una mesura. monitor-xarxa.sh fa exactament això: defineix de manera mesurable què significa que la xarxa i el servei estan bé, ho comprova en paral·lel cada pocs minuts, alerta només quan alguna cosa canvia i desa un històric del qual surt la disponibilitat del mes.
Contingut
- Què significa «la xarxa va bé», de forma mesurable
- Comprovació puntual davant de tendència
- Disseny: catàleg d'objectius i una funció per tipus
- Les comprovacions, una a una
- El despatxador i el resultat normalitzat
- Paral·lelisme: per què aquí sí que compensa
- Reintents amb retrocés
- Estat i alertes només en les transicions
- Finestres de manteniment
- Històric i subcomandament
resum - Mode directe, timer i posada en producció
- Què significa «la xarxa va bé», de forma mesurable
«Va bé» no és monitoritzable. Aquestes cinc frases sí:
| Afirmació mesurable | Com es comprova | Llindar a Veloz Envíos |
|---|---|---|
| L'amfitrió respon | ping -c1 -W2 |
Pèrdua 0 %, RTT < 50 ms |
| El nom resol | dig +short |
Retorna com a mínim una A, < 500 ms |
| El port està obert | nc -z -w2 o /dev/tcp |
Connexió en < 2 s |
| L'API respon correctament | curl -w '%{http_code} %{time_total}' |
200 i < 800 ms |
| El certificat no ha caducat | openssl s_client |
Més de 15 dies de marge |
Cada fila és una comprovació independent, amb el seu llindar i la seva criticitat. La distinció importa: un ping que respon no diu res sobre si l'API funciona, i una API que retorna 200 en quatre segons està trencada encara que el codi sigui correcte. Un servei es declara sa per la seva resposta útil, no per la seva accessibilitat.
- Comprovació puntual davant de tendència
Són dos productes diferents que comparteixen dades:
- La comprovació puntual respon ara: està caigut? La seva sortida és un estat i la seva conseqüència possible és una alerta.
- La tendència respon al llarg del mes: quant ha estat disponible, com evoluciona la latència? La seva sortida és un número d'informe i la seva conseqüència és una decisió d'arquitectura.
Confondre-les produeix les dues fallades clàssiques: alertar per una latència que fa tres setmanes que puja (tard) o dibuixar gràfiques sense que ningú s'assabenti d'una caiguda (inútil). El disseny les separa: cada execució alerta si escau i afegeix una línia a l'històric; el subcomandament resum només llegeix l'històric.
- Disseny: catàleg d'objectius i una funció per tipus
Igual que els perfils de còpia (09-03), els objectius viuen en un fitxer:
# etc/monitor-xarxa.d/objectius.conf # nom|tipus|desti|llindar|criticitat api-salut|http|http://localhost:8080/salud|800|critica api-enviaments|http|http://localhost:8080/envios?ciudad=Valencia|1500|alta db-port|port|10.0.0.20:5432|2000|critica dns-intern|dns|api.veloz.local|500|mitjana srv-02|ping|srv-veloz-02|50|alta passarella|ping|10.0.0.1|30|critica
El separador és | i no pas la coma perquè les URL porten comes amb facilitat i les barres verticals no. Es llegeix amb IFS='|' read (03-06), ignorant comentaris i línies buides:
llegeix_objectius() {
local nom tipus desti llindar crit
while IFS='|' read -r nom tipus desti llindar crit; do
[[ $nom == \#* || -z $nom ]] && continue
printf '%s|%s|%s|%s|%s\n' "$nom" "$tipus" "$desti" "$llindar" "${crit:-mitjana}"
done < "$OBJECTIUS"
}Cada tipus té la seva funció comprova_<tipus> i retorna el conveni dels connectors clàssics (07-04): 0 = OK, 1 = AVÍS, 2 = CRÍTIC, 3 = DESCONEGUT. Aquest conveni és el que permet que la resta de l'script no sàpiga res de ping ni de curl.
- Les comprovacions, una a una
comprova_ping() { # $1 desti, $2 llindar ms -> imprimeix "ms missatge"
local sortida rtt
sortida=$(ping -c1 -W2 -n "$1" 2>/dev/null) || { printf '0 sense resposta\n'; return 2; }
rtt=${sortida#*time=}; rtt=${rtt%% *}
printf '%s rtt=%sms\n' "$rtt" "$rtt"
awk -v r="$rtt" -v u="$2" 'BEGIN {exit !(r+0 > u)}' && return 1 || return 0
}
comprova_dns() {
local ini final ip
ini=$(date +%s%3N); ip=$(timeout 3 dig +short +time=2 +tries=1 "$1" A | head -1)
final=$(date +%s%3N)
[[ -n $ip ]] || { printf '0 no resol\n'; return 2; }
printf '%s resol a %s\n' "$((final - ini))" "$ip"
(( final - ini > $2 )) && return 1 || return 0
}
comprova_port() {
local host=${1%:*} port=${1##*:} ini final
ini=$(date +%s%3N)
timeout 2 bash -c "exec 3<>/dev/tcp/$host/$port" 2>/dev/null ||
{ printf '0 tancat o filtrat\n'; return 2; }
final=$(date +%s%3N); printf '%s obert\n' "$((final - ini))"
}
comprova_http() {
local resp cod ms
resp=$(curl -sS -o /dev/null --max-time 5 -w '%{http_code} %{time_total}' "$1" 2>/dev/null) \
|| { printf '0 sense connexio\n'; return 2; }
read -r cod ms <<< "$resp"
ms=$(awk -v t="$ms" 'BEGIN {printf "%d", t * 1000}')
[[ $cod == 200 ]] || { printf '%s codi %s\n' "$ms" "$cod"; return 2; }
printf '%s 200 en %sms\n' "$ms" "$ms"
(( ms > $2 )) && return 1 || return 0
}Detalls que vénen de 06-04 i 06-05. ping -n evita la resolució inversa, que pot afegir segons d'espera per un DNS lent i falsejar la mesura. /dev/tcp amb timeout no necessita nc instal·lat, però és un bashisme pur (08-07): si l'script hagués de córrer en dash, aquí hi aniria nc -z -w2. curl -w dóna el codi i el temps en una sola invocació, en lloc de mesurar-ho des de fora amb date; --max-time és obligatori, perquè una comprovació sense límit pot penjar tot el cicle. I l'HTTP 200 s'exigeix explícitament: un 302 cap a una pàgina de manteniment no és servei. Cada funció imprimeix <mil·lisegons> <missatge> i retorna l'estat pel codi de sortida: separar la dada (stdout) del veredicte (codi) és la mateixa divisió de 09-01 entre recollir i decidir.
- El despatxador i el resultat normalitzat
executa_objectiu() { # nom|tipus|desti|llindar|criticitat -> linia TSV
local nom tipus desti llindar crit
IFS='|' read -r nom tipus desti llindar crit <<< "$1"
local fn=comprova_$tipus sortida estat
declare -F "$fn" >/dev/null || { veloz_log_error "tipus desconegut: $tipus"; return 3; }
sortida=$(amb_reintents "$fn" "$desti" "$llindar"); estat=$?
printf '%s\t%s\t%s\t%s\t%s\t%s\n' \
"$(date -Is)" "$nom" "$tipus" "$estat" "${sortida%% *}" "${sortida#* }"
}declare -F com a llista blanca (08-03): el tipus del fitxer es converteix en nom de funció només si aquesta funció existeix. Un case explícit seria igual de vàlid (04-05) i més llegible per a qui no conegui el truc; l'avantatge de declare -F és que afegir un tipus nou és escriure una funció i res més. El resultat normalitzat —data ISO, nom, tipus, estat, mil·lisegons, missatge— és el format intern que consumeixen l'informe, les alertes i l'històric: un altre cop la mateixa arquitectura dels tres projectes anteriors.
- Paral·lelisme: per què aquí sí que compensa
Amb dotze objectius i una mitjana de 700 ms cadascun, en sèrie són vuit segons i mig. I no són vuit segons de CPU: són vuit segons d'espera. Aquest és el criteri de 08-02 per paral·lelitzar: quan el procés està bloquejat esperant la xarxa, llançar-ne vint alhora no costa pràcticament res.
executa_tots() {
local tmp; tmp=$(mktemp -d); trap 'rm -rf "$tmp"' RETURN
local i=0 obj
while IFS= read -r obj; do
executa_objectiu "$obj" > "$tmp/$(printf '%03d' "$i")" &
((i++))
(( $(jobs -rp | wc -l) >= PARALLELISME )) && wait -n
done < <(llegeix_objectius)
wait
cat "$tmp"/*
}Tres coses fan que això sigui correcte i no un caos. Cada treball escriu en el seu propi fitxer temporal numerat (05-02): si tots escrivissin en el mateix, les línies s'entrellaçarien. El nom porta un índex amb zeros a l'esquerra (%03d) perquè el cat final retorni l'ordre del catàleg, no l'ordre d'acabament —un informe les files del qual ballen a cada execució és illegible i fa impossible comparar dues sortides—. I wait -n limita els treballs simultanis: espera que n'acabi un qualsevol abans de llançar el següent, que és l'equivalent d'xargs -P amb processos de fons. Amb xargs -0 -P "$PARALLELISME" el codi seria més curt, a canvi d'haver d'exportar les funcions; amb vint objectius, qualsevol de les dues serveix.
- Reintents amb retrocés
Un paquet perdut no és una caiguda. Alertar per la primera comprovació fallida genera el soroll que fa que la gent ignori les alertes, que és l'avaria pitjor possible d'un sistema de monitoratge.
amb_reintents() { # $1 = funcio, resta = arguments
local fn=$1 intent espera=1 sortida estat; shift
for intent in 1 2 3; do
sortida=$("$fn" "$@"); estat=$?
(( estat == 0 )) && { printf '%s' "$sortida"; return 0; }
(( intent < 3 )) && { sleep "$espera"; espera=$((espera * 2)); }
done
printf '%s' "$sortida"; return "$estat"
}Retrocés exponencial (05-03): 1 s, 2 s. Només es reintenta la fallada i se surt al primer èxit, així que el cas normal no costa res. Tres intents i un màxim de tres segons d'espera són suficients per filtrar el soroll sense endarrerir el cicle. Compte amb multiplicar: tres reintents per dotze objectius seqüencials serien un cicle de minut i mig en una apagada general; amb el paral·lelisme de l'apartat anterior, continuen sent quatre segons.
- Estat i alertes només en les transicions
El fitxer d'estat desa l'última situació coneguda de cada objectiu, i l'alerta es dispara únicament quan canvia (07-04):
processa_estats() {
declare -A previ
[[ -r $ESTAT ]] && while IFS=$'\t' read -r n e; do previ[$n]=$e; done < "$ESTAT"
local nou=$ESTAT.parcial ant; : > "$nou"
local data nom tipus estat ms msg
while IFS=$'\t' read -r data nom tipus estat ms msg; do
printf '%s\t%s\n' "$nom" "$estat" >> "$nou"
ant=${previ[$nom]:-0}
(( ant == 0 && estat != 0 )) && alerta FALLADA "$nom" "$msg" "$ms"
(( ant != 0 && estat == 0 )) && alerta RECUPERAT "$nom" "$msg" "$ms"
done
mv "$nou" "$ESTAT"
}Només dues transicions generen avís: OK→FALLADA i FALLADA→OK. Un objectiu que fa sis hores que és caigut no torna a avisar cada cinc minuts, i l'avís de recuperació és tan important com el de fallada, perquè tanca l'incident sense que ningú hagi d'anar a mirar. L'escriptura de l'estat fa servir un altre cop .parcial + mv (08-03): si l'script mor a mitges, el fitxer d'estat anterior queda intacte, i un estat corrupte provocaria una tempesta de falses transicions a l'execució següent.
L'enviament fa servir jq -n per construir el cos (06-05), mai printf amb el missatge interpolat:
alerta() {
local classe=$1 objectiu=$2 detall=$3 ms=$4
veloz_log_info "alerta $classe $objectiu: $detall"
en_manteniment "$objectiu" && { veloz_log_info "silenciada (manteniment)"; return 0; }
[[ -n ${WEBHOOK:-} ]] || return 0
jq -n --arg c "$classe" --arg o "$objectiu" --arg d "$detall" --arg h "$(hostname -s)" \
'{text: "[\($c)] \($o) a \($h): \($d)"}' |
curl -sS --max-time 10 -X POST -H 'Content-Type: application/json' -d @- "$WEBHOOK" >/dev/null ||
veloz_log_error "no s'ha pogut enviar l'alerta"
}La fallada del webhook es registra però no avorta el monitor: el sistema d'avisos caigut no s'ha d'endur per davant la vigilància. I el missatge sempre s'escriu primer al registre local, perquè en quedi constància encara que no surti.
- Finestres de manteniment
# etc/monitor-xarxa.d/manteniment.conf -> objectiu|inici ISO|final ISO
en_manteniment() {
local obj=$1 ara o ini final; ara=$(date +%s)
[[ -r $MANTENIMENT ]] || return 1
while IFS='|' read -r o ini final; do
[[ $o == "$obj" || $o == '*' ]] || continue
(( ara >= $(date -d "$ini" +%s) && ara <= $(date -d "$final" +%s) )) && return 0
done < "$MANTENIMENT"
return 1
}El silenciament suprimeix l'alerta, no la comprovació: l'històric continua registrant l'estat real, així que la disponibilitat del mes no es falseja. És una distinció que molts sistemes comercials fan malament, i la diferència entre «no em molestis ara» i «fem veure que no ha passat».
- Històric i subcomandament
resum
resumCada execució afegeix les seves línies a logs/xarxa-AAAA-MM.tsv. Un fitxer per mes manté la mida acotada sense necessitat de logrotate i fa trivial l'informe mensual.
resum() { # $1 = fitxer mensual
awk -F'\t' '
{ n[$2]++; if ($4 == 0) ok[$2]++; if ($5 + 0 > 0) { suma[$2] += $5; m[$2]++ }
if ($4 != 0) fallades[$2]++ }
END {
printf "%-14s %8s %8s %10s %8s\n", "OBJECTIU", "MOSTRES", "DISPON.", "LAT.MITJ.", "FALLADES"
for (k in n)
printf "%-14s %8d %7.2f%% %8.0fms %8d\n", k, n[k], ok[k]*100/n[k],
(m[k] ? suma[k]/m[k] : 0), fallades[k]+0
}' "$1" | (read -r capc; printf '%s\n' "$capc"; sort -k3 -n)
}La disponibilitat és un quocient entre mostres, no entre minuts: amb un cicle de cinc minuts, cada mostra fallida representa cinc minuts d'indisponibilitat, i convé dir-ho a l'informe perquè ningú confongui la precisió amb l'exactitud. La latència mitjana exclou les mostres amb 0 ms, que són fallades i no mesures —incloure-les abaixaria artificialment la mitjana justament quan el servei va pitjor, l'error d'interpretació més comú en monitoratge—. El (read -r capc; ...) conserva la capçalera a dalt mentre ordena la resta per disponibilitat ascendent, és a dir, els pitjors primer.
- Mode directe, timer i posada en producció
Per a una sessió de diagnòstic va bé un bucle propi, que és preferible a watch perquè conserva l'històric i les alertes:
mode_directe() { # \033[H\033[J neteja la pantalla a cada volta
while true; do printf '\033[H\033[J'; executa_tots | formata_text; sleep "${INTERVAL:-30}"; done
}Però en producció mana un timer de systemd (07-05) cada cinc minuts, per tres raons concretes: sobreviu al tancament de la sessió SSH, journalctl -u conserva la traça de cada execució, i systemctl list-timers mostra si de debò s'està executant. Un bucle en un terminal oblidat sembla monitoratge fins al dia que algú tanca el portàtil.
Errors Habituals i Consells
- Monitoritzar el
pingi dir-ne servei. L'amfitrió respon i l'API retorna 500: la comprovació que importa és la que fa el que fa l'usuari. - Alertar a cada execució mentre dura la fallada. És la via ràpida perquè s'ignorin les alertes. Només transicions, i avís de recuperació sempre.
- Comprovacions sense
timeout. Un objectiu que no respon mai bloqueja el cicle sencer; amb paral·lelisme, esgota els processos.--max-time,-W,timeout: sempre. - Paral·lelitzar escrivint tots al mateix fitxer. Les línies s'entrellacen: un temporal per treball i consolidació ordenada. I durant el manteniment, silencia l'alerta, mai la comprovació, o falsejaràs la disponibilitat de l'informe mensual.
- Consell: monitoritza també el mateix monitor. Si
veloz-monitor.timerno s'executa, ningú no se n'adona; una comprovació d'«antiguitat de l'última línia de l'històric» avigilant.shtanca aquest forat.
Exercicis
- Caducitat del certificat TLS. Afegeix el tipus
tls, que avisi quan faltin menys de 15 dies per a la caducitat i sigui crític per sota de 5. - Traça de ruta davant d'una fallada. Quan un objectiu
pingpassi a FALLADA, adjunta automàticament al registre una traça de ruta cap al destí, sense endarrerir el cicle normal. - Informe de tendència. Estén
resumamb--comparar MES, que mostri la variació de disponibilitat i latència respecte al mes anterior, marcant els empitjoraments.
Solucions
1. openssl s_client necessita -servername per a SNI i una entrada per stdin que tanqui la connexió:
comprova_tls() {
local host=${1%:*} port=${1##*:} caducitat dies
caducitat=$(echo | timeout 5 openssl s_client -connect "$host:$port" -servername "$host" 2>/dev/null |
openssl x509 -noout -enddate 2>/dev/null) || { printf '0 sense certificat\n'; return 2; }
caducitat=${caducitat#notAfter=}; dies=$(( ( $(date -d "$caducitat" +%s) - $(date +%s) ) / 86400 ))
printf '%s caduca en %s dies (%s)\n' "$dies" "$dies" "$caducitat"
(( dies < 5 )) && return 2; (( dies < 15 )) && return 1; return 0
}L'echo | del principi és imprescindible: sense ell, s_client deixa la connexió oberta esperant entrada i el timeout acaba matant-lo. L'aritmètica de dates és la de 04-06, restant segons d'època.
2. La traça no ha de bloquejar el cicle, així que es llança en segon pla i amb límit:
diagnostic_extra() { { timeout 20 traceroute -n -w1 -q1 "$1" 2>&1 | veloz_log_info_stdin; } & disown; }S'invoca des d'alerta només en la transició a FALLADA —mai mentre dura— per no llançar una traça cada cinc minuts durant una caiguda de dues hores.
3. S'executa resum sobre els dos fitxers mensuals, es carreguen en dos arrays associatius (04-03) indexats per objectiu i es recorren comparant. La regla de presentació: mostrar la diferència amb signe i marcar només el que empitjora més d'un llindar (per exemple, 0,5 punts de disponibilitat o un 20 % de latència), perquè un informe que ho marca tot no marca res.
Conclusió
monitor-xarxa.sh converteix «la xarxa va bé» en cinc afirmacions mesurables amb el seu llindar, i les comprova des d'un catàleg en fitxer que creix sense tocar codi. Les idees transferibles són quatre. El conveni 0/1/2/3 dels connectors clàssics, que permet que el despatxador no sàpiga res de ping ni de curl i que afegir un tipus sigui escriure una funció. Paral·lelitzar el que espera: dotze objectius en quatre segons en lloc de vuit i mig, amb un temporal per treball i consolidació en ordre fix perquè l'informe sigui comparable. Alertar en les transicions, amb avís de recuperació i finestres que silencien l'avís però mai la mesura. I separar la comprovació puntual de la tendència: la primera alerta, la segona informa, i totes dues surten de la mateixa línia TSV. Pel camí: reintents amb retrocés (05-03), curl -w i /dev/tcp amb timeout (06-04), jq -n per al cos del webhook (06-05), wait -n per limitar la concurrència (05-02), escriptura atòmica de l'estat (08-03) i awk per al resum mensual (06-01).
Queden cinc scripts que funcionen, cadascun amb les seves opcions, la seva configuració i la seva manera d'invocar-se. A 09-05, el tancament del curs, deixen de ser una col·lecció i es converteixen en un producte: veloz-ops, una única ordre amb subordres, configuració unificada, instal·lador idempotent, versionat amb Git, bateria de proves, desplegament a la flota, tornada enrere i documentació.
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
