Caer en una madriguera de conejo: una historia sobre un error de reinicio de barniz - Parte 1

ghostinushanka , después de haber apretado los botones durante los últimos 20 minutos, como si su vida dependiera de ello, se vuelve hacia mí con una expresión medio salvaje en sus ojos y una sonrisa maliciosa: "Amigo, creo que lo entiendo".


"Mira aquí", dice, señalando uno de los símbolos en la pantalla, "apuesto a mi sombrero rojo que si agregamos aquí lo que acabo de enviarte", señalando a otro código, "el error ya no es se mostrará ".


Un poco desconcertado y cansado, cambio la expresión sed en la que hemos estado trabajando durante algún tiempo, systemctl varnish reload el archivo y ejecuto la systemctl varnish reload . El mensaje de error ha desaparecido ...


"Los correos que intercambié con el candidato", continuó mi colega, mientras su sonrisa se convirtió en una sonrisa genuina llena de alegría, "¡De repente me di cuenta de que este es exactamente el mismo problema!"


Como empezó todo


Este artículo asume una comprensión de cómo funcionan bash, awk, sed y systemd. El conocimiento del barniz es bienvenido pero no obligatorio.
Las marcas de tiempo del fragmento han cambiado.
Escrito con ghostinushanka .
Este texto es una traducción del original publicado en inglés hace dos semanas; Traducción boikoden .


El sol brilla a través de las ventanas panorámicas en otra cálida mañana de otoño, la taza de la bebida con cafeína recién preparada descansa lejos del teclado, la sinfonía favorita de sonidos y sonidos en los auriculares, superponiendo el susurro de los teclados mecánicos, y el fatídico título "Investigar varnishre" brilla juguetonamente la primera entrada en la lista de boletos pendientes en el tablero de Kanban. sh: echo: error de E / S en la puesta en escena "(Investigue el" varnishreload sh: echo: error de E / S "en la etapa). Cuando se trata de barniz, no hay errores y no puede haber lugar, incluso si no se traducen en ningún problema, como en este caso.


Para aquellos que no están familiarizados con varnishreload , este es un script de shell simple que se utiliza para recargar una configuración de barniz , también llamado VCL.


Como sugiere el nombre del ticket, se produjo un error en uno de los servidores en el escenario, y como estaba seguro de que el enrutamiento de barniz en el escenario funciona correctamente, supuse que sería un error menor. Entonces, solo un mensaje que entró en una secuencia de salida ya cerrada. Me llevo el boleto a mí mismo, con plena confianza de que lo dejaré listo en menos de 30 minutos, me doy una palmada en el hombro para limpiar el tablero de la próxima basura y volver a los asuntos más importantes.


Chocar contra una pared a una velocidad de 200 km / h


Después de abrir el archivo varnishreload , en uno de los servidores que ejecuta Debian Stretch, vi un script de shell con una longitud de menos de 200 líneas.


Después de ejecutar el script, no noté nada que pudiera causar problemas cuando se ejecutó varias veces directamente desde la terminal.


Al final, esta es una etapa, incluso si se rompe, nadie se quejará, bueno ... no demasiado. Ejecuto el script y veo lo que se escribirá en el terminal, pero no puedo ver ningún error.


Un par más comienza a asegurarme de que no puedo reproducir el error sin ningún esfuerzo adicional, y empiezo a descubrir cómo cambiar este script y hacer que siga dando un error.


¿Puede un script anular STDOUT (usando > &- )? O STDERR? Ninguno de los dos trabajó como resultado.


Obviamente, systemd de alguna manera modifica el entorno de inicio, pero ¿cómo y por qué?
varnishreload vim y edito varnishreload , agregando set -x directamente debajo del shebang, con la esperanza de que la salida del script de depuración arroje un poco de luz.


El archivo está arreglado, así que reinicio el barniz y veo que el cambio lo rompió por completo ... El escape es un completo desastre, en el que hay toneladas de código tipo C. Incluso desplazarse en la terminal no es suficiente para encontrar dónde comienza. Estoy completamente confundido ¿Puede el modo de depuración afectar el trabajo de los programas lanzados en un script? No, tonterías. ¿Un error en el caparazón? Varios escenarios posibles pasan por mi cabeza como cucarachas en diferentes direcciones. Una taza de bebida llena de cafeína se vació al instante, un viaje rápido a la cocina para reponer el caldo y ... vamos. Abro el script y miro el shebang: #!/bin/sh .


/bin/sh es simplemente bash symlink, por lo que el script se interpreta en modo compatible con POSIX, ¿verdad? Ahí estaba! El shell predeterminado en Debian es dash, y eso es exactamente a lo que se refiere /bin/sh .


 # ls -l /bin/sh lrwxrwxrwx 1 root root 4 Jan 24 2017 /bin/sh -> dash 

En aras de la prueba, cambié el shebang a #!/bin/bash , eliminé set -x e intenté nuevamente. Finalmente, durante el reinicio posterior del barniz, apareció un error tolerable en la salida:


 Jan 01 12:00:00 hostname varnishreload[32604]: /usr/sbin/varnishreload: line 124: echo: write error: Broken pipe Jan 01 12:00:00 hostname varnishreload[32604]: VCL 'reload_20190101_120000_32604' compiled 

¡Línea 124, ahí está!


 114 find_vcl_file() { 115 VCL_SHOW=$(varnishadm vcl.show -v "$VCL_NAME" 2>&1) || : 116 VCL_FILE=$( 117 echo "$VCL_SHOW" | 118 awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}' | { 119 # all this ceremony to handle blanks in FILE 120 read -r DELIM VCL_SHOW INDEX SIZE FILE 121 echo "$FILE" 122 } 123 ) || : 124 125 if [ -z "$VCL_FILE" ] 126 then 127 echo "$VCL_SHOW" >&2 128 fail "failed to get the VCL file name" 129 fi 130 131 echo "$VCL_FILE" 132 } 

Pero resultó que la línea 124 está bastante vacía y no tiene ningún interés. Solo podría suponer que el error surgió como parte de una línea múltiple que comienza en la línea 116.
¿Qué se escribe finalmente en la variable VCL_FILE como resultado de la ejecución de la VCL_FILE mencionada anteriormente?


Al principio, envía el contenido de la variable VLC_SHOW creada en la línea 115 al siguiente comando a través de la tubería. ¿Y entonces qué pasa entonces?


Primero, usa varnishadm , que es parte del paquete de instalación de barniz, para configurar el barniz sin reiniciar.


El vcl.show -v usa para generar la configuración completa de VCL especificada en ${VCL_NAME} en STDOUT.


Para mostrar la configuración VCL activa actual, así como varias versiones anteriores de las configuraciones de enrutamiento de barniz que todavía están en la memoria, puede usar el varnishadm vcl.list , cuyo resultado será similar al siguiente:


 discarded cold/busy 1 reload_20190101_120000_11903 discarded cold/busy 2 reload_20190101_120000_12068 discarded cold/busy 16 reload_20190101_120000_12259 discarded cold/busy 16 reload_20190101_120000_12299 discarded cold/busy 28 reload_20190101_120000_12357 active auto/warm 32 reload_20190101_120000_12397 available auto/warm 0 reload_20190101_120000_12587 

El valor de la variable ${VCL_NAME} establece en otra parte del script varnishreload con el nombre del VCL actualmente activo, si lo hay. En este caso, será "reload_20190101_120000_12397".


Genial, la variable ${VCL_SHOW} contiene la configuración completa para el barniz, hasta ahora está claro. Ahora, finalmente entendí por qué la salida del guión con set -x resultó estar tan rota: incluía el contenido de la configuración resultante.


Es importante comprender que una configuración completa de VCL a menudo puede combinarse a partir de múltiples archivos. Los comentarios de estilo C se utilizan para determinar dónde se incluyeron algunos archivos de configuración en otros, y esto es exactamente de lo que se trata toda la línea de fragmento de código a continuación.
La sintaxis de los comentarios que describen los archivos incluidos tiene el siguiente formato:


 // VCL.SHOW <NUM> <NUM> <FILENAME> 

Los números en este contexto no son importantes, estamos interesados ​​en el nombre del archivo.


Entonces, ¿qué está pasando en el pantano de equipos que comienzan en la línea 116?
Vamos a resolverlo.
El equipo consta de cuatro partes:


  1. Un echo simple que muestra el valor de la variable ${VCL_SHOW}
     echo "$VCL_SHOW" 
  2. awk , que busca una línea (registro), donde el primer campo, después de romper el texto, será "//", y el segundo "VCL.SHOW".
    Awk escribirá la primera línea que coincida con estos patrones y luego detendrá el procesamiento inmediatamente.
     awk '$1 == "//" && $2 == "VCL.SHOW" {print; exit}' 
  3. Un bloque de código que se almacena en cinco valores de campo variable separados por espacios. La quinta variable FILE obtiene el resto de la cadena. Finalmente, el último eco escribe el contenido de la variable ${FILE} .
     { read -r DELIM VCL_SHOW INDEX SIZE FILE; echo "$FILE" } 
  4. Dado que todos los pasos del 1 al 3 están encerrados en un sub-shell, la salida del valor $FILE se escribirá en la variable VCL_FILE .

Como se desprende del comentario en la línea 119, esto tiene un único propósito: manejar de manera confiable los casos en los que el VCL se referirá a archivos con caracteres de espacio en el nombre.


${VCL_FILE} lógica de procesamiento original para ${VCL_FILE} e intenté cambiar la secuencia de comandos, pero esto no condujo a nada. Todo funcionó limpiamente para mí y, en el caso de iniciar el servicio, me dio un error.


Parece que el error simplemente no es reproducible cuando ejecuta el script manualmente, mientras que los 30 minutos esperados ya han terminado seis veces y, en el apéndice, ha aparecido una tarea de mayor prioridad, dejando a un lado el resto de los casos. El resto de la semana estuvo lleno de una variedad de tareas y solo se diluyó ligeramente con un informe sobre sed y una entrevista con el candidato. El problema con el varnishreload se perdió irremediablemente en las arenas del tiempo.


Tu llamado sed-fu ... realmente ... basura


La semana siguiente resultó ser un día bastante libre, así que nuevamente decidí tomar este boleto. Tenía la esperanza de que en mi cerebro, algún proceso en segundo plano todo este tiempo estaba buscando una solución a este problema, y ​​esta vez ciertamente entiendo lo que es.


Desde la última vez que un simple cambio de código no ayudó, decidí volver a escribirlo a partir de la línea 116. En cualquier caso, el código existente era pésimo. Y no hay absolutamente ninguna necesidad de usar read .


Mirando el error nuevamente:
sh: echo: broken pipe : en este comando echo está en dos lugares, pero sospecho que el primero es el culpable más probable (bueno, o al menos un cómplice). Awk tampoco es creíble. Y en caso de que realmente sea awk | {read; echo} awk | {read; echo} awk | {read; echo} construcción lleva a todos estos problemas, ¿por qué no reemplazarlo? Este comando de una línea no usa todas las características de awk, e incluso esta read adicional en el apéndice.


Como hubo un informe sobre sed semana pasada, quería probar mis habilidades recién adquiridas y simplificar echo | awk | { read; echo} echo | awk | { read; echo} echo | awk | { read; echo} en un echo | sed más comprensible echo | sed echo | sed . Aunque definitivamente este no es el mejor enfoque para detectar un error, pensé que al menos probaría mi sed-fu y tal vez aprendería algo nuevo sobre el problema. En el proceso, le pedí a mi colega, el autor del informe sobre sed, que me ayudara a encontrar un script sed más efectivo.


varnishadm vcl.show -v "$VCL_NAME" contenido de varnishadm vcl.show -v "$VCL_NAME" en el archivo, para poder concentrarme en escribir un script sed sin ninguna molestia asociada con la recarga del servicio.


Una breve descripción de cómo sed maneja la entrada se puede encontrar en su manual GNU . En las fuentes sed, el carácter \n se especifica explícitamente como un separador de línea.


En varios pases y con las recomendaciones de mi colega, escribimos un script sed que dio el mismo resultado que toda la línea fuente 116.


El siguiente es un archivo de entrada de muestra:


 > cat vcl-example.vcl Text // VCL.SHOW 0 1578 file with 3 spaces.vcl More text // VCL.SHOW 0 1578 file.vcl Even more text // VCL.SHOW 0 1578 file with TWOspaces.vcl Final text 

Esto puede no ser obvio a partir de la descripción anterior, pero solo estamos interesados ​​en el primer comentario // VCL.SHOW , y puede haber varios de ellos en la entrada. Es por eso que el awk original termina su trabajo después del primer partido.


 #  ,      #   sed,  -    '\#'    '/',           #    “// VCL.SHOW”,       #  -n   ,  sed     ,       (.  ) # -E      > cat vcl-processor-1.sed \#// VCL.SHOW#p > sed -En -f vcl-processor-1.sed vcl-example.vcl // VCL.SHOW 0 1578 file with 3 spaces.vcl // VCL.SHOW 0 1578 file.vcl // VCL.SHOW 0 1578 file with TWOspaces.vcl #  ,     #   “substitute”,     ,    a #      ,    > cat vcl-processor-2.sed \#// VCL.SHOW# { s#.* [0-9]+ [0-9]+ (.*)$#\1# p } > sed -En -f vcl-processor-2.sed vcl-example.vcl file with 3 spaces.vcl file.vcl file with TWOspaces.vcl #  ,      #      awk,         > cat vcl-processor-3.sed \#// VCL.SHOW# { s#.* [0-9]+ [0-9]+ (.*)$#\1# p q } > sed -En -f vcl-processor-3.sed vcl-example.vcl file with 3 spaces.vcl #  ,    ,      > sed -En -e '\#// VCL.SHOW#{s#.* [0-9]+ [0-9]+ (.*)$#\1#p;q;}' vcl-example.vcl file with 3 spaces.vcl 

Entonces, el contenido de la secuencia de comandos varnishreload se verá así:


 VCL_FILE="$(echo "$VCL_SHOW" | sed -En '\#// VCL.SHOW#{s#.*[0-9]+ [0-9]+ (.*)$#\1#p;q;};')" 

La lógica anterior se puede resumir de la siguiente manera:
Si la línea coincide con la expresión regular // VCL.SHOW , entonces comed con avidez el texto que incluye ambos números en esta línea y guarda todo lo que queda después de esta operación. Dé el valor guardado y termine el programa.


Simple, ¿verdad?


Quedamos satisfechos con el script sed y el hecho de que reemplaza todo el código original. Todas mis pruebas dieron los resultados deseados, así que cambié la "varnishreload" en el servidor y ejecuté systemctl reload varnish nuevamente. El sucio error echo: write error: Broken pipe rió de nuevo en nuestras caras. Un cursor parpadeante esperaba que se ingresara un nuevo comando en el oscuro vacío del terminal ...

Source: https://habr.com/ru/post/477968/


All Articles