Creamos unha tarefa de implementación en GKE sen complementos, SMS ou rexistro. Imos botar un ollo debaixo da chaqueta de Jenkins

Todo comezou cando o xefe de equipo dun dos nosos equipos de desenvolvemento pediunos que probaramos a súa nova aplicación, que fora contenedora o día anterior. publiqueino. Pasados ​​uns 20 minutos, recibiuse unha solicitude para actualizar a aplicación, porque alí se engadira algo moi necesario. renovei. Despois dun par de horas máis... ben, podes adiviñar o que comezou a pasar despois...

Debo recoñecer que son bastante preguiceiro (non admitín isto antes? Non?), e dado que os líderes do equipo teñen acceso a Jenkins, no que temos todos CI/CD, pensei: déixao que se despregue como por moito que el queira! Lembreime dunha broma: dálle un peixe a un home e comerá un día; Chama a unha persoa Fed e estará toda a vida. E foi xogar trucos no traballo, que sería capaz de despregar un contedor que contén a aplicación de calquera versión construída con éxito en Kuber e transferirlle calquera valor ENV (meu avó, filólogo, profesor de inglés no pasado, agora xiraba o dedo na súa sien e miraba para min moi expresivamente despois de ler esta frase).

Entón, nesta nota vouvos contar como aprendín:

  1. Actualizar de forma dinámica os traballos en Jenkins desde o propio traballo ou doutros traballos;
  2. Conéctese á consola da nube (Cloud shell) desde un nodo co axente Jenkins instalado;
  3. Implementa a carga de traballo en Google Kubernetes Engine.


De feito, estou sendo, por suposto, un pouco falseado. Suponse que tes polo menos parte da infraestrutura na nube de Google e, polo tanto, es o seu usuario e, por suposto, tes unha conta GCP. Pero non é do que trata esta nota.

Esta é a miña seguinte folla de trampas. Só quero escribir este tipo de notas nun caso: afrontei un problema, inicialmente non sabía como resolvelo, a solución non se buscou en Google xa preparada, polo que busquei en Google por partes e, finalmente, resolvín o problema. E para que no futuro, cando esqueza como o fixen, non teña que buscar todo en google de novo peza por peza e compilalo, escríbome eu mesmo tales follas de trucos.

Disclaimer: 1. A nota foi escrita “para min”, para o papel mellores prácticas non se aplica. Estou feliz de ler as opcións de "sería mellor facelo deste xeito" nos comentarios.
2. Se a parte aplicada da nota se considera sal, entón, como todas as miñas notas anteriores, esta é unha solución de sal débil.

Actualizando dinámicamente a configuración do traballo en Jenkins

Preveo a túa pregunta: que ten que ver a actualización dinámica de emprego? Introduza o valor do parámetro de cadea manualmente e listo!

Respondo: Son moi preguiceiro, non me gusta cando se queixan: Misha, o despregamento está fallando, todo desapareceu! Comeza a buscar e hai un erro de tipografía no valor dalgún parámetro de inicio da tarefa. Polo tanto, prefiro facer todo o máis eficiente posible. Se é posible evitar que o usuario introduza datos directamente dando no seu lugar unha lista de valores para escoller, entón eu organizo a selección.

O plan é o seguinte: creamos un traballo en Jenkins, no que, antes do lanzamento, poderiamos seleccionar unha versión da lista, especificar os valores dos parámetros pasados ​​ao contedor mediante ENV, entón recolle o contedor e empúxao ao Rexistro de contedores. Despois, dende alí lánzase o contedor en cuber as carga de traballo cos parámetros especificados no traballo.

Non imos considerar o proceso de creación e creación dun traballo en Jenkins, isto está fóra do tema. Asumiremos que a tarefa está lista. Para implementar unha lista actualizada con versións, necesitamos dúas cousas: unha lista de fontes existente con números de versión válidos a priori e unha variable como Parámetro de elección na tarefa. No noso exemplo, deixe o nome da variable BUILD_VERSION, non nos detendremos en detalle. Pero vexamos máis de cerca a lista de fontes.

Non hai tantas opcións. Inmediatamente se me ocorreron dúas cousas:

  • Use a API de acceso remoto que Jenkins ofrece aos seus usuarios;
  • Solicita o contido do cartafol do repositorio remoto (no noso caso é JFrog Artifactory, que non é importante).

API de acceso remoto de Jenkins

Segundo a excelente tradición establecida, preferiría evitar longas explicacións.
Permitirei só unha tradución libre dun anaco do primeiro parágrafo primeira páxina da documentación da API:

Jenkins ofrece unha API para o acceso remoto lexible pola máquina á súa funcionalidade. <…> O acceso remoto ofrécese nun estilo REST. Isto significa que non hai un único punto de entrada para todas as funcións, senón un URL como ".../api/", onde "..." significa o obxecto ao que se aplican as capacidades da API.

Noutras palabras, se a tarefa de implantación da que estamos a falar actualmente está dispoñible en http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build, entón os asubíos da API para esta tarefa están dispoñibles en http://jenkins.mybuild.er/view/AweSomeApp/job/AweSomeApp_build/api/

A continuación, temos a elección de que forma recibir a saída. Centrémonos no XML, xa que a API só permite filtrar neste caso.

Imos tentar obter unha lista de todos os traballos executados. Só nos interesa o nome da asemblea (displayName) e o seu resultado (resultar):

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

Entendeu?

Agora imos filtrar só aquelas carreiras que acaban co resultado Éxito. Usemos o argumento &excluír e como parámetro pasarémoslle o camiño a un valor non igual a Éxito. Si Si. Unha dobre negativa é unha afirmación. Excluímos todo o que non nos interese:

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

Captura de pantalla da lista de éxitos
Creamos unha tarefa de implementación en GKE sen complementos, SMS ou rexistro. Imos botar un ollo debaixo da chaqueta de Jenkins

Ben, só por diversión, asegurémonos de que o filtro non nos enganou (¡os filtros nunca menten!) e mostremos unha lista de "sen éxito":

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

Captura de pantalla da lista de non acertados
Creamos unha tarefa de implementación en GKE sen complementos, SMS ou rexistro. Imos botar un ollo debaixo da chaqueta de Jenkins

Lista de versións dun cartafol nun servidor remoto

Hai unha segunda forma de obter unha lista de versións. Gústame aínda máis que acceder á API de Jenkins. Ben, porque se a aplicación foi construída con éxito, significa que foi empaquetada e colocada no repositorio no cartafol apropiado. Como, un repositorio é o almacenamento predeterminado das versións de traballo das aplicacións. Gústame. Ben, preguntémoslle que versións hai almacenadas. Curl, grep e awk o cartafol remoto. Se alguén está interesado no oneliner, entón está baixo o spoiler.

Comando dunha liña
Teña en conta dúas cousas: paso os detalles da conexión na cabeceira e non necesito todas as versións do cartafol, e só selecciono as que se crearon nun mes. Edita o comando para adaptalo ás túas realidades e 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 traballos e ficheiro de configuración de traballos en Jenkins

Descubrimos a fonte da lista de versións. Imos agora incorporar a lista resultante na tarefa. Para min, a solución obvia era engadir un paso na tarefa de compilación da aplicación. O paso que se executaría se o resultado fose "éxito".

Abre a configuración da tarefa de montaxe e desprázate ata o final. Fai clic nos botóns: Engadir paso de compilación -> Paso condicional (único). Na configuración do paso, seleccione a condición Estado de construción actual, establece o valor Éxito, a acción que se realizará se ten éxito Executar o comando shell.

E agora a parte divertida. Jenkins almacena as configuracións de traballo en ficheiros. En formato XML. Polo camiño http://путь-до-задания/config.xml En consecuencia, pode descargar o ficheiro de configuración, editalo segundo sexa necesario e poñelo de novo onde o conseguiu.

Lembra que acordamos anteriormente que imos crear un parámetro para a lista de versións BUILD_VERSION?

Descarguemos o ficheiro de configuración e botemos un ollo no seu interior. Só para asegurarse de que o parámetro está no seu lugar e do tipo desexado.

Captura de pantalla baixo spoiler.

O teu fragmento config.xml debería ter o mesmo aspecto. Excepto que aínda falta o contido do elemento choices
Creamos unha tarefa de implementación en GKE sen complementos, SMS ou rexistro. Imos botar un ollo debaixo da chaqueta de Jenkins

Estás seguro? Iso é todo, imos escribir un script que se executará se a compilación é exitosa.
O script recibirá unha lista de versións, descargará o ficheiro de configuración, escribirá a lista de versións no lugar que necesitemos e, a continuación, volverá colocalo. Si. Correcto. Escribe unha lista de versións en XML no lugar onde xa hai unha lista de versións (será no futuro, despois do primeiro lanzamento do script). Sei que aínda hai fans feroces das expresións regulares no mundo. Non pertenzo a eles. Instala xmlstarler á máquina onde se editará a configuración. Paréceme que non é un prezo tan grande para evitar editar XML usando sed.

Baixo o spoiler, presento o código que realiza a secuencia anterior na súa totalidade.

Escribe unha lista de versións desde un cartafol do servidor remoto ata a 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

Se prefires a opción de obter versións de Jenkins e es tan preguiceiro coma min, debaixo do spoiler hai o mesmo código, pero unha lista de Jenkins:

Escribe unha lista de versións de Jenkins para a configuración
Teña en conta isto: o meu nome de conxunto consta dun número de secuencia e dun número de versión, separados por dous puntos. En consecuencia, awk corta a parte innecesaria. Por ti mesmo, cambia esta liña para que se adapte ás túas 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, se probou o código escrito baseándose nos exemplos anteriores, na tarefa de implantación xa debería ter unha lista despregable con versións. É como na captura de pantalla debaixo do spoiler.

Lista de versións correctamente completada
Creamos unha tarefa de implementación en GKE sen complementos, SMS ou rexistro. Imos botar un ollo debaixo da chaqueta de Jenkins

Se todo funcionou, copia e pega o script Executar o comando shell e gardar os cambios.

Conectando a Cloud Shell

Temos colectores en contedores. Usamos Ansible como a nosa ferramenta de entrega de aplicacións e xestor de configuración. En consecuencia, cando se trata de construír contedores, véñense á mente tres opcións: instalar Docker en Docker, instalar Docker nunha máquina que executa Ansible ou construír contedores nunha consola na nube. Acordamos permanecer en silencio sobre os complementos para Jenkins neste artigo. Lembras?

Decidín: ben, xa que os contedores "fóra da caixa" pódense recoller na consola da nube, entón por que molestarse? Mantéñase limpo, non? Quero recoller contedores Jenkins na consola da nube e, a continuación, lanzalas ao cuber desde alí. Ademais, Google conta con canles moi ricas dentro da súa infraestrutura, o que terá un efecto beneficioso na velocidade de implantación.

Para conectarse á consola na nube, necesitas dúas cousas: gcloud e dereitos de acceso a API de Google Cloud para a instancia de VM desde a que se realizará esta mesma conexión.

Para aqueles que planean conectarse non desde a nube de Google
Google permite a posibilidade de desactivar a autorización interactiva nos seus servizos. Isto permitirache conectarte á consola incluso desde unha máquina de café, se está a executar *nix e ten unha propia consola.

Se hai necesidade de tratar este tema con máis detalle no marco desta nota, escriba nos comentarios. Se conseguimos votos suficientes, escribirei unha actualización sobre este tema.

A forma máis sinxela de conceder dereitos é a través da interface web.

  1. Detén a instancia de VM desde a que te conectarás posteriormente á consola na nube.
  2. Abre Detalles da instancia e fai clic emendar.
  3. Na parte inferior da páxina, seleccione o ámbito de acceso á instancia Acceso completo a todas as API de Cloud.

    Captura de pantalla
    Creamos unha tarefa de implementación en GKE sen complementos, SMS ou rexistro. Imos botar un ollo debaixo da chaqueta de Jenkins

  4. Garda os cambios e inicia a instancia.

Unha vez que a máquina virtual remate de cargar, conéctate a ela a través de SSH e asegúrate de que a conexión se produce sen erros. Use o comando:

gcloud alpha cloud-shell ssh

Unha conexión exitosa parece algo así
Creamos unha tarefa de implementación en GKE sen complementos, SMS ou rexistro. Imos botar un ollo debaixo da chaqueta de Jenkins

Implementar en GKE

Dado que nos esforzamos de todos os xeitos posibles para cambiar completamente a IaC (Infraestructura como código), os nosos ficheiros docker almacénanse en Git. Isto é por unha banda. E o despregamento en kubernetes descríbese mediante un ficheiro yaml, que só é usado por esta tarefa, que tamén é como código. Isto é do outro lado. En xeral, quero dicir, o plan é o seguinte:

  1. Tomamos os valores das variables BUILD_VERSION e, opcionalmente, os valores das variables polas que se pasarán ENV.
  2. Descarga o dockerfile de Git.
  3. Xerar yaml para a súa implantación.
  4. Cargamos estes dous ficheiros a través de scp á consola da nube.
  5. Construímos alí un contedor e introducímolo no rexistro de contedores
  6. Aplicamos o ficheiro de implementación de carga ao cuber.

Sexamos máis específicos. Unha vez que empezamos a falar ENV, entón supoñamos que necesitamos pasar os valores de dous parámetros: PARAM 1 и PARAM 2. Engadimos a súa tarefa para a súa implantación, escriba - Parámetro de cadea.

Captura de pantalla
Creamos unha tarefa de implementación en GKE sen complementos, SMS ou rexistro. Imos botar un ollo debaixo da chaqueta de Jenkins

Xeraremos yaml cunha simple redirección perder para arquivar. Suponse, por suposto, que tes no teu ficheiro docker PARAM 1 и PARAM 2que o nome da carga será aplicación fantástica, e atópase o contedor montado coa aplicación da versión especificada Rexistro de contedores de camiño gcr.io/awesomeapp/awesomeapp-$BUILD_VERSIONonde $BUILD_VERSION foi seleccionado na lista despregable.

Listaxe do equipo

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

Axente de Jenkins despois de conectarse usando gcloud alpha cloud-shell ssh o modo interactivo non está dispoñible, polo que enviamos comandos á consola da nube mediante o parámetro --comando.

Limpamos o cartafol de inicio da consola da nube do antigo dockerfile:

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

Coloque o ficheiro docker recentemente descargado no cartafol de inicio da consola na nube usando scp:

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

Recollemos, etiquetamos e enviamos o contedor ao rexistro de contedores:

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"

Facemos o mesmo co ficheiro de despregamento. Teña en conta que os seguintes comandos usan nomes ficticios do clúster onde se produce a implantación (awsm-cluster) e nome do proxecto (incrible-proxecto), onde se atopa o 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"

Executamos a tarefa, abrimos a saída da consola e esperamos ver a montaxe exitosa do contedor.

Captura de pantalla
Creamos unha tarefa de implementación en GKE sen complementos, SMS ou rexistro. Imos botar un ollo debaixo da chaqueta de Jenkins

E despois o despregamento exitoso do contedor montado

Captura de pantalla
Creamos unha tarefa de implementación en GKE sen complementos, SMS ou rexistro. Imos botar un ollo debaixo da chaqueta de Jenkins

Ignorei a configuración deliberadamente Ingreso. Por unha simple razón: unha vez que o configuras carga de traballo cun nome dado, permanecerá operativo, sen importar cantas implantacións con este nome realices. Ben, en xeral, isto está un pouco máis aló do alcance da historia.

En vez de conclusións

Probablemente non se puideron facer todos os pasos anteriores, senón que simplemente instalou algún complemento para Jenkins, o seu muuulion. Pero por algún motivo non me gustan os complementos. Pois, máis precisamente, recóllo a eles só por desesperación.

E só me gusta recoller algún tema novo para min. O texto anterior tamén é unha forma de compartir os descubrimentos que fixen ao resolver o problema descrito ao principio. Comparte cos que, coma el, non son para nada un lobo terrible nos devops. Se os meus descubrimentos axudan polo menos a alguén, estarei feliz.

Fonte: www.habr.com

Engadir un comentario