Creamos una tarea de implementación en GKE sin complementos, SMS ni registro. Echemos un vistazo debajo de la chaqueta de Jenkins.

Todo comenzó cuando el líder de uno de nuestros equipos de desarrollo nos pidió que probáramos su nueva aplicación, que había sido contenida en contenedores el día anterior. Lo publiqué. Después de unos 20 minutos, se recibió una solicitud para actualizar la aplicación, porque allí se había agregado algo muy necesario. Renové. Después de un par de horas más... bueno, puedes adivinar lo que empezó a pasar después...

Debo admitir que soy bastante vago (¿no lo admití antes? ¿No?), y dado que los líderes de equipo tienen acceso a Jenkins, en el que tenemos todo CI/CD, pensé: déjelo implementar como ¡Por mucho que quiera! Me acordé de un chiste: dale un pescado a un hombre y comerá por un día; llame a una persona alimentada y será alimentada toda su vida. Y fue jugar trucos en el trabajo, que podría implementar un contenedor que contenga la aplicación de cualquier versión construida exitosamente en Kuber y transferirle cualquier valor. ENV (mi abuelo, filólogo, en el pasado profesor de inglés, ahora se giraba el dedo en la sien y me miraba muy expresivamente después de leer esta frase).

Entonces, en esta nota te contaré cómo aprendí:

  1. Actualizar dinámicamente trabajos en Jenkins desde el trabajo mismo o desde otros trabajos;
  2. Conéctese a la consola en la nube (Cloud Shell) desde un nodo con el agente Jenkins instalado;
  3. Implementar carga de trabajo en Google Kubernetes Engine.


De hecho, por supuesto, estoy siendo algo falso. Se supone que tienes al menos parte de la infraestructura en la nube de Google y, por tanto, eres su usuario y, por supuesto, tienes una cuenta de GCP. Pero de eso no se trata esta nota.

Esta es mi próxima hoja de trucos. Solo quiero escribir esas notas en un caso: me enfrenté a un problema, inicialmente no sabía cómo resolverlo, la solución no fue buscada en Google lista para usar, así que la busqué en Google por partes y finalmente resolví el problema. Y para que en el futuro, cuando olvide cómo lo hice, no tenga que buscar todo en Google pieza por pieza y compilarlo, escribo esas hojas de trucos.

Cláusula de exención de responsabilidades: 1. La nota fue escrita “para mí”, para el papel. las mejores prácticas no se aplica. Me alegra leer las opciones "hubiera sido mejor hacerlo así" en los comentarios.
2. Si la parte aplicada de la nota se considera sal, entonces, como todas mis notas anteriores, esta es una solución salina débil.

Actualización dinámica de la configuración del trabajo en Jenkins

Preveo tu pregunta: ¿qué tiene que ver la actualización dinámica del trabajo? Ingrese el valor del parámetro de cadena manualmente y ¡listo!

Respondo: Soy muy vago, no me gusta cuando se quejan: Misha, el despliegue falla, ¡se acabó todo! Empiezas a buscar y hay un error tipográfico en el valor de algún parámetro de inicio de tarea. Por eso, prefiero hacer todo de la forma más eficiente posible. Si es posible evitar que el usuario ingrese datos directamente dándole en su lugar una lista de valores para elegir, entonces organizo la selección.

El plan es el siguiente: creamos un trabajo en Jenkins, en el que, antes del lanzamiento, podemos seleccionar una versión de la lista, especificar valores para los parámetros pasados ​​​​al contenedor a través de ENV, luego recopila el contenedor y lo envía al Registro de contenedores. Luego desde allí el contenedor se lanza en cuber como carga de trabajo con los parámetros especificados en el trabajo.

No consideraremos el proceso de creación y configuración de un trabajo en Jenkins, esto está fuera de tema. Asumiremos que la tarea está lista. Para implementar una lista actualizada con versiones, necesitamos dos cosas: una lista fuente existente con números de versión válidos a priori y una variable como Parámetro de elección en la tarea. En nuestro ejemplo, llamemos a la variable BUILD_VERSION, no nos detendremos en ello en detalle. Pero echemos un vistazo más de cerca a la lista de fuentes.

No hay tantas opciones. Inmediatamente me vinieron a la mente dos cosas:

  • Utilice la API de acceso remoto que Jenkins ofrece a sus usuarios;
  • Solicite el contenido de la carpeta del repositorio remoto (en nuestro caso es JFrog Artifactory, lo cual no es importante).

API de acceso remoto de Jenkins

Según la excelente tradición establecida, preferiría evitar largas explicaciones.
Me permitiré sólo una traducción libre de un fragmento del primer párrafo. primera página de la documentación API:

Jenkins proporciona una API para acceso remoto legible por máquina a su funcionalidad. <…> El acceso remoto se ofrece en un estilo similar a REST. Esto significa que no existe un único punto de entrada a todas las funciones, sino una URL como ".../api/", Dónde "..." significa el objeto al que se aplican las capacidades de la API.

En otras palabras, si la tarea de implementación de la que estamos hablando actualmente está disponible en http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build, entonces los silbidos API para esta tarea están disponibles en http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/

A continuación, podemos elegir en qué forma recibir el resultado. Centrémonos en XML, ya que la API sólo permite filtrar en este caso.

Intentemos obtener una lista de todas las ejecuciones de trabajos. Sólo nos interesa el nombre del ensamblado (nombre para mostrar) y su resultado (resultado):

http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]

¿Lo tienes?

Ahora filtremos solo aquellas ejecuciones que terminan con el resultado. ÉXITO. Usemos el argumento &excluir y como parámetro le pasaremos la ruta a un valor no igual a ÉXITO. Sí Sí. Una doble negativa es una afirmación. Excluimos todo lo que no nos interesa:

http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!='SUCCESS']

Captura de pantalla de la lista de exitosos.
Creamos una tarea de implementación en GKE sin complementos, SMS ni registro. Echemos un vistazo debajo de la chaqueta de Jenkins.

Bueno, solo por diversión, asegurémonos de que el filtro no nos engañó (¡los filtros nunca mienten!) y mostremos una lista de los que “no tuvieron éxito”:

http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result='SUCCESS']

Captura de pantalla de la lista de los que no tuvieron éxito
Creamos una tarea de implementación en GKE sin complementos, SMS ni registro. Echemos un vistazo debajo de la chaqueta de Jenkins.

Lista de versiones de una carpeta en un servidor remoto

Hay una segunda forma de obtener una lista de versiones. Me gusta incluso más que acceder a la API de Jenkins. Bueno, porque si la aplicación se creó correctamente, significa que se empaquetó y se colocó en el repositorio en la carpeta correspondiente. Por ejemplo, un repositorio es el almacenamiento predeterminado de versiones funcionales de aplicaciones. Como. Bueno, preguntémosle qué versiones hay almacenadas. Curvaremos, grep y awk la carpeta remota. Si alguien está interesado en el oneliner, entonces está debajo del spoiler.

Comando de una línea
Tenga en cuenta dos cosas: paso los detalles de la conexión en el encabezado y no necesito todas las versiones de la carpeta, y selecciono solo aquellas que se crearon dentro de un mes. Edite el comando para adaptarlo a sus realidades y necesidades:

curl -H "X-JFrog-Art-Api:VeryLongAPIKey" -s http://arts.myre.po/artifactory/awesomeapp/ | sed 's/a href=//' | grep "$(date +%b)-$(date +%Y)|$(date +%b --date='-1 month')-$(date +%Y)" | awk '{print $1}' | grep -oP '>K[^/]+' )

Configurar trabajos y archivos de configuración de trabajos en Jenkins

Descubrimos la fuente de la lista de versiones. Incorporamos ahora la lista resultante a la tarea. Para mí, la solución obvia era agregar un paso en la tarea de creación de la aplicación. El paso que se ejecutaría si el resultado fuera "éxito".

Abra la configuración de la tarea de ensamblaje y desplácese hasta el final. Haga clic en los botones: Agregar paso de compilación -> Paso condicional (único). En la configuración de pasos, seleccione la condición Estado de construcción actual, establezca el valor ÉXITO, la acción a realizar si tiene éxito Ejecutar comando de shell.

Y ahora la parte divertida. Jenkins almacena configuraciones de trabajo en archivos. En formato XML. Por el camino http://путь-до-задания/config.xml En consecuencia, puede descargar el archivo de configuración, editarlo según sea necesario y volver a colocarlo donde lo obtuvo.

Recuerde, acordamos anteriormente que crearemos un parámetro para la lista de versiones. BUILD_VERSION?

Descarguemos el archivo de configuración y echemos un vistazo a su interior. Solo para asegurarse de que el parámetro esté en su lugar y sea del tipo deseado.

Captura de pantalla bajo spoiler.

Su fragmento config.xml debería verse igual. Excepto que aún falta el contenido del elemento de opciones.
Creamos una tarea de implementación en GKE sin complementos, SMS ni registro. Echemos un vistazo debajo de la chaqueta de Jenkins.

¿Está seguro? Eso es todo, escribamos un script que se ejecutará si la compilación se realiza correctamente.
El script recibirá una lista de versiones, descargará el archivo de configuración, escribirá la lista de versiones en el lugar que necesitemos y luego lo devolverá. Sí. Así es. Escriba una lista de versiones en XML en el lugar donde ya hay una lista de versiones (lo será en el futuro, después del primer lanzamiento del script). Sé que todavía hay fanáticos acérrimos de las expresiones regulares en el mundo. No les pertenezco. Por favor instalar xmlstarler a la máquina donde se editará la configuración. Me parece que este no es un precio tan alto a pagar para evitar editar XML usando sed.

Debajo del spoiler, presento el código que realiza la secuencia anterior en su totalidad.

Escriba una lista de versiones desde una carpeta en el servidor remoto a la configuración

#!/bin/bash
############## Скачиваем конфиг
curl -X GET -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml -o appConfig.xml

############## Удаляем и заново создаем xml-элемент для списка версий
xmlstarlet ed --inplace -d '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' appConfig.xml

xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]' --type elem -n a appConfig.xml

xmlstarlet ed --inplace --insert '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a' --type attr -n class -v string-array appConfig.xml

############## Читаем в массив список версий из репозитория
readarray -t vers < <( curl -H "X-JFrog-Art-Api:Api:VeryLongAPIKey" -s http://arts.myre.po/artifactory/awesomeapp/ | sed 's/a href=//' | grep "$(date +%b)-$(date +%Y)|$(date +%b --date='-1 month')-$(date +%Y)" | awk '{print $1}' | grep -oP '>K[^/]+' )

############## Пишем массив элемент за элементом в конфиг
printf '%sn' "${vers[@]}" | sort -r | 
                while IFS= read -r line
                do
                    xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' --type elem -n string -v "$line" appConfig.xml
                done

############## Кладем конфиг взад
curl -X POST -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml --data-binary @appConfig.xml

############## Приводим рабочее место в порядок
rm -f appConfig.xml

Si prefieres la opción de obtener versiones de Jenkins y eres tan vago como yo, debajo del spoiler está el mismo código, pero una lista de Jenkins:

Escriba una lista de versiones de Jenkins en la configuración.
Solo tenga esto en cuenta: el nombre de mi ensamblado consta de un número de secuencia y un número de versión, separados por dos puntos. En consecuencia, awk corta la parte innecesaria. Por su cuenta, cambie esta línea para adaptarla a sus necesidades.

#!/bin/bash
############## Скачиваем конфиг
curl -X GET -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml -o appConfig.xml

############## Удаляем и заново создаем xml-элемент для списка версий
xmlstarlet ed --inplace -d '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' appConfig.xml

xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]' --type elem -n a appConfig.xml

xmlstarlet ed --inplace --insert '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a' --type attr -n class -v string-array appConfig.xml

############## Пишем в файл список версий из Jenkins
curl -g -X GET -u username:apiKey 'http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/xml?tree=allBuilds[displayName,result]&exclude=freeStyleProject/allBuild[result!=%22SUCCESS%22]&pretty=true' -o builds.xml

############## Читаем в массив список версий из XML
readarray vers < <(xmlstarlet sel -t -v "freeStyleProject/allBuild/displayName" builds.xml | awk -F":" '{print $2}')

############## Пишем массив элемент за элементом в конфиг
printf '%sn' "${vers[@]}" | sort -r | 
                while IFS= read -r line
                do
                    xmlstarlet ed --inplace --subnode '/project/properties/hudson.model.ParametersDefinitionProperty/parameterDefinitions/hudson.model.ChoiceParameterDefinition[name="BUILD_VERSION"]/choices[@class="java.util.Arrays$ArrayList"]/a[@class="string-array"]' --type elem -n string -v "$line" appConfig.xml
                done

############## Кладем конфиг взад
curl -X POST -u username:apiKey http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_k8s/config.xml --data-binary @appConfig.xml

############## Приводим рабочее место в порядок
rm -f appConfig.xml

En teoría, si ha probado el código escrito según los ejemplos anteriores, entonces en la tarea de implementación ya debería tener una lista desplegable con las versiones. Es como en la captura de pantalla debajo del spoiler.

Lista de versiones correctamente cumplimentada
Creamos una tarea de implementación en GKE sin complementos, SMS ni registro. Echemos un vistazo debajo de la chaqueta de Jenkins.

Si todo funcionó, copie y pegue el script en Ejecutar comando de shell y guardar los cambios.

Conexión a Cloud Shell

Disponemos de recolectores en contenedores. Usamos Ansible como nuestra herramienta de entrega de aplicaciones y administrador de configuración. En consecuencia, cuando se trata de crear contenedores, me vienen a la mente tres opciones: instalar Docker en Docker, instalar Docker en una máquina que ejecuta Ansible o crear contenedores en una consola en la nube. Acordamos guardar silencio sobre los complementos para Jenkins en este artículo. ¿Recordar?

Decidí: bueno, dado que los contenedores "listos para usar" se pueden recolectar en la consola en la nube, ¿por qué molestarse? Mantenlo limpio, ¿verdad? Quiero recopilar contenedores de Jenkins en la consola de la nube y luego ejecutarlos en Cuber desde allí. Además, Google tiene canales muy ricos dentro de su infraestructura, lo que tendrá un efecto beneficioso en la velocidad de implementación.

Para conectarse a la consola en la nube, necesita dos cosas: nube de gcloud y derechos de acceso a Google Cloud API para la instancia de VM con la que se realizará esta misma conexión.

Para aquellos que planean conectarse no desde la nube de Google
Google permite la posibilidad de desactivar la autorización interactiva en sus servicios. Esto le permitirá conectarse a la consola incluso desde una máquina de café, si ejecuta *nix y tiene una consola propia.

Si es necesario que cubra este tema con más detalle en el marco de esta nota, escriba en los comentarios. Si obtenemos suficientes votos, escribiré una actualización sobre este tema.

La forma más sencilla de otorgar derechos es a través de la interfaz web.

  1. Detenga la instancia de VM desde la que posteriormente se conectará a la consola en la nube.
  2. Abra Detalles de instancia y haga clic Изменить.
  3. En la parte inferior de la página, seleccione el alcance de acceso a la instancia. Acceso completo a todas las API de la nube.

    Captura de pantalla
    Creamos una tarea de implementación en GKE sin complementos, SMS ni registro. Echemos un vistazo debajo de la chaqueta de Jenkins.

  4. Guarde sus cambios y ejecute la instancia.

Una vez que la VM haya terminado de cargarse, conéctese a través de SSH y asegúrese de que la conexión se produzca sin errores. Utilice el comando:

gcloud alpha cloud-shell ssh

Una conexión exitosa se parece a esto
Creamos una tarea de implementación en GKE sin complementos, SMS ni registro. Echemos un vistazo debajo de la chaqueta de Jenkins.

Implementar en GKE

Dado que nos esforzamos por todos los medios para cambiar completamente a IaC (infraestructura como código), nuestros archivos acoplables se almacenan en Git. Esto es por un lado. Y la implementación en Kubernetes se describe mediante un archivo yaml, que solo se usa para esta tarea, que a su vez también es como un código. Esto es del otro lado. En general, quiero decir, el plan es este:

  1. Tomamos los valores de las variables. BUILD_VERSION y, opcionalmente, los valores de las variables que se pasarán ENV.
  2. Descargue el archivo acoplable de Git.
  3. Genere yaml para la implementación.
  4. Subimos ambos archivos a través de scp a la consola en la nube.
  5. Construimos un contenedor allí y lo insertamos en el registro de contenedores.
  6. Aplicamos el archivo de implementación de carga al cuber.

Seamos más específicos. Una vez que empezamos a hablar de ENV, entonces supongamos que necesitamos pasar los valores de dos parámetros: PARAM1 и PARAM2. Agregamos su tarea para la implementación, escribimos - Parámetro de cadena.

Captura de pantalla
Creamos una tarea de implementación en GKE sin complementos, SMS ni registro. Echemos un vistazo debajo de la chaqueta de Jenkins.

Generaremos yaml con una simple redirección echo archivar. Se supone, por supuesto, que tienes en tu dockerfile PARAM1 и PARAM2que el nombre de la carga será Impresionante aplicación, y el contenedor ensamblado con la aplicación de la versión especificada se encuentra en Registro de contenedores en el camino gcr.io/awesomeapp/awesomeapp-$BUILD_VERSIONDonde $BUILD_VERSION acaba de ser seleccionado de la lista desplegable.

Listado de equipos

touch deploy.yaml
echo "apiVersion: apps/v1" >> deploy.yaml
echo "kind: Deployment" >> deploy.yaml
echo "metadata:" >> deploy.yaml
echo "  name: awesomeapp" >> deploy.yaml
echo "spec:" >> deploy.yaml
echo "  replicas: 1" >> deploy.yaml
echo "  selector:" >> deploy.yaml
echo "    matchLabels:" >> deploy.yaml
echo "      run: awesomeapp" >> deploy.yaml
echo "  template:" >> deploy.yaml
echo "    metadata:" >> deploy.yaml
echo "      labels:" >> deploy.yaml
echo "        run: awesomeapp" >> deploy.yaml
echo "    spec:" >> deploy.yaml
echo "      containers:" >> deploy.yaml
echo "      - name: awesomeapp" >> deploy.yaml
echo "        image: gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION:latest" >> deploy.yaml
echo "        env:" >> deploy.yaml
echo "        - name: PARAM1" >> deploy.yaml
echo "          value: $PARAM1" >> deploy.yaml
echo "        - name: PARAM2" >> deploy.yaml
echo "          value: $PARAM2" >> deploy.yaml

Agente de Jenkins después de conectarse usando gcloud alfa nube-shell ssh el modo interactivo no está disponible, por lo que enviamos comandos a la consola en la nube usando el parámetro --dominio.

Limpiamos la carpeta de inicio en la consola en la nube del antiguo archivo docker:

gcloud alpha cloud-shell ssh --command="rm -f Dockerfile"

Coloque el archivo docker recién descargado en la carpeta de inicio de la consola en la nube usando scp:

gcloud alpha cloud-shell scp localhost:./Dockerfile cloudshell:~

Recopilamos, etiquetamos y enviamos el contenedor al registro de contenedores:

gcloud alpha cloud-shell ssh --command="docker build -t awesomeapp-$BUILD_VERSION ./ --build-arg BUILD_VERSION=$BUILD_VERSION --no-cache"
gcloud alpha cloud-shell ssh --command="docker tag awesomeapp-$BUILD_VERSION gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION"
gcloud alpha cloud-shell ssh --command="docker push gcr.io/awesomeapp/awesomeapp-$BUILD_VERSION"

Hacemos lo mismo con el archivo de implementación. Tenga en cuenta que los siguientes comandos utilizan nombres ficticios del clúster donde se produce la implementación (clúster awsm) y nombre del proyecto (proyecto impresionante), donde se encuentra el clúster.

gcloud alpha cloud-shell ssh --command="rm -f deploy.yaml"
gcloud alpha cloud-shell scp localhost:./deploy.yaml cloudshell:~
gcloud alpha cloud-shell ssh --command="gcloud container clusters get-credentials awsm-cluster --zone us-central1-c --project awesome-project && 
kubectl apply -f deploy.yaml"

Ejecutamos la tarea, abrimos la salida de la consola y esperamos ver el ensamblaje exitoso del contenedor.

Captura de pantalla
Creamos una tarea de implementación en GKE sin complementos, SMS ni registro. Echemos un vistazo debajo de la chaqueta de Jenkins.

Y luego el despliegue exitoso del contenedor ensamblado.

Captura de pantalla
Creamos una tarea de implementación en GKE sin complementos, SMS ni registro. Echemos un vistazo debajo de la chaqueta de Jenkins.

Ignoré deliberadamente el escenario. Ingreso. Por una sencilla razón: una vez que lo configuras carga de trabajo con un nombre determinado, permanecerá operativo, sin importar cuántas implementaciones con este nombre realice. Bueno, en general, esto está un poco más allá del alcance de la historia.

En lugar de conclusiones

Probablemente no se podrían haber realizado todos los pasos anteriores, sino simplemente instalar algún complemento para Jenkins, su muuulion. Pero por alguna razón no me gustan los complementos. Bueno, más precisamente, recurro a ellos sólo por desesperación.

Y simplemente me gusta retomar algún tema nuevo para mí. El texto anterior también es una forma de compartir los hallazgos que hice al resolver el problema descrito al principio. Comparte con aquellos que, como él, no son para nada un lobo huargo en devops. Si mis hallazgos ayudan al menos a alguien, estaré encantado.

Fuente: habr.com

Añadir un comentario