werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

27 de maio no salón principal da conferencia DevOpsConf 2019, celebrada no marco do festival RIT++ 2019, como parte da sección "Entrega continua", entregouse un informe "werf - a nosa ferramenta para CI/CD en Kubernetes". Fala deses problemas e retos aos que se enfrontan todos cando se implementan en Kubernetes, así como sobre matices que quizais non se noten inmediatamente. Analizando as posibles solucións, mostramos como esta se implementa nunha ferramenta de código aberto werf.

Desde a presentación, a nosa utilidade (anteriormente coñecida como dapp) alcanzou un fito histórico de 1000 estrelas en GitHub — Esperamos que a súa crecente comunidade de usuarios facilite a vida a moitos enxeñeiros de DevOps.

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

Entón, imos presentar vídeo do informe (~47 minutos, moito máis informativo que o artigo) e o extracto principal del en forma de texto. Vaia!

Entregando código a Kubernetes

A charla xa non será sobre werf, senón sobre CI/CD en Kubernetes, o que implica que o noso software está empaquetado en contedores Docker (Falei disto en Informe 2016), e os K8 empregaranse para executalo en produción (máis sobre isto en Ano 2017).

Como é a entrega en Kubernetes?

  • Hai un repositorio Git co código e as instrucións para crealo. A aplicación está integrada nunha imaxe de Docker e publicada no Rexistro de Docker.
  • O mesmo repositorio tamén contén instrucións sobre como implementar e executar a aplicación. Na fase de implantación, estas instrucións envíanse a Kubernetes, que recibe a imaxe desexada do rexistro e lánzaa.
  • Ademais, normalmente hai probas. Algúns destes pódense facer ao publicar unha imaxe. Tamén pode (seguindo as mesmas instrucións) implementar unha copia da aplicación (nun espazo de nomes K8s ou un clúster separado) e realizar probas alí.
  • Finalmente, necesitas un sistema CI que reciba eventos de Git (ou clics no botón) e chame a todas as etapas designadas: construír, publicar, implementar, probar.

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

Aquí hai algunhas notas importantes:

  1. Porque temos unha infraestrutura inmutable (infraestrutura inmutable), a imaxe da aplicación que se utiliza en todas as fases (escenificación, produción, etc.), debe haber un. Falei disto con máis detalle e con exemplos. aquí.
  2. Porque seguimos a infraestrutura como enfoque de código (IaC), o código da aplicación, as instrucións para a súa montaxe e posta en marcha debe ser exactamente nun repositorio. Para obter máis información sobre isto, consulte o mesmo informe.
  3. Cadea de entrega (entrega) normalmente vémolo así: a aplicación foi montada, probada, liberada (etapa de lanzamento) e iso é todo: a entrega tivo lugar. Pero, en realidade, o usuario obtén o que lanzou, non entón cando o entregaches á produción, e cando puido ir alí e esta produción funcionou. Entón creo que a cadea de entrega remata só na fase operativa (correr), ou máis precisamente, mesmo no momento en que o código foi eliminado da produción (substituíndoo por un novo).

Volvamos ao esquema de entrega anterior en Kubernetes: non só inventámolo nós, senón literalmente todos os que trataron con este problema. De feito, este patrón chámase agora GitOps (Podes ler máis sobre o termo e as ideas detrás del aquí). Vexamos as fases do esquema.

Etapa de construción

Parece que podes falar de construír imaxes de Docker en 2019, cando todos saben escribir e executar Dockerfiles. docker build?.. Aquí están os matices aos que me gustaría prestar atención:

  1. Peso da imaxe importa, así que usa en varias fasespara deixar na imaxe só a aplicación que é realmente necesaria para o funcionamento.
  2. Número de capas debe minimizarse combinando cadeas de RUN-Comandos segundo o significado.
  3. Non obstante, isto engade problemas depuración, porque cando a montaxe falla, tes que atopar o comando correcto da cadea que causou o problema.
  4. Velocidade de montaxe importante porque queremos implementar cambios rapidamente e ver os resultados. Por exemplo, non quere reconstruír dependencias nas bibliotecas de idiomas cada vez que constrúe unha aplicación.
  5. Moitas veces necesitas dun repositorio Git moitas imaxes, que se pode resolver mediante un conxunto de Dockerfiles (ou etapas nomeadas nun ficheiro) e un script Bash coa súa montaxe secuencial.

Esta foi só a punta do iceberg ao que se enfrontan todos. Pero hai outros problemas, en particular:

  1. Moitas veces na fase de montaxe necesitamos algo montar (por exemplo, almacena na caché o resultado dun comando como apt nun directorio de terceiros).
  2. Queremos Ansible en lugar de escribir en shell.
  3. Queremos construír sen Docker (por que necesitamos unha máquina virtual adicional na que hai que configurar todo para iso, cando xa temos un clúster de Kubernetes no que podemos executar contedores?).
  4. Montaxe paralela, que se pode entender de diferentes xeitos: diferentes comandos do Dockerfile (se se usan varias etapas), varios commits do mesmo repositorio, varios Dockerfiles.
  5. Montaxe distribuída: Queremos recoller cousas en vainas que sexan "efémeras" porque a súa caché desaparece, o que significa que debe almacenarse nalgún lugar por separado.
  6. Finalmente, nomeei o cumio dos desexos automaxia: O ideal sería ir ao repositorio, escribir algún comando e obter unha imaxe preparada, montada cunha comprensión de como e que facer correctamente. Non obstante, persoalmente non estou seguro de que todos os matices se poidan prever deste xeito.

E aquí están os proxectos:

  • moby/kit de construción — un constructor de Docker Inc (xa integrado nas versións actuais de Docker), que está tentando resolver todos estes problemas;
  • kaniko — un creador de Google que che permite construír sen Docker;
  • Buildpacks.io — O intento de CNCF de facer maxia automática e, en particular, unha solución interesante con rebase para capas;
  • e unha morea de outras utilidades, como constrúe, ferramentas xenuínas/img...

...e mira cantas estrelas teñen en GitHub. É dicir, por unha banda, docker build existe e pode facer algo, pero en realidade o problema non está completamente resolto - proba diso é o desenvolvemento paralelo de colectores alternativos, cada un dos cales resolve algunha parte dos problemas.

Montaxe en werf

Así que chegamos werf (antes famoso como dapp) — Unha utilidade de código aberto da empresa Flant, que levamos moitos anos facendo. Todo comezou hai 5 anos con scripts Bash que optimizaron a montaxe de Dockerfiles, e durante os últimos 3 anos realizouse un desenvolvemento completo no marco dun proxecto co seu propio repositorio Git. (primeiro en Ruby e despois reescrito para ir e, ao mesmo tempo, renomeada). Que problemas de montaxe se solucionan en werf?

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

Os problemas sombreados en azul xa se implementaron, a construción paralela fíxose dentro do mesmo host e está previsto que os problemas destacados en amarelo estean rematados a finais do verán.

Fase de publicación no rexistro (publicar)

Marcamos docker push... - que podería ser difícil de subir unha imaxe ao rexistro? E entón xorde a pregunta: "Que etiqueta debo poñer na imaxe?" Xorde pola razón que temos Gitflow (ou outra estratexia de Git) e Kubernetes, e a industria está intentando garantir que o que ocorre en Kubernetes siga o que ocorre en Git. Despois de todo, Git é a nosa única fonte de verdade.

Que ten de difícil isto? Garantir a reproducibilidade: dun commit en Git, que é de natureza inmutable (inmutable), a unha imaxe de Docker, que debería manterse igual.

Tamén é importante para nós determinar a orixe, porque queremos entender a partir de que commit se construíu a aplicación que se executa en Kubernetes (despois podemos facer diferenzas e cousas similares).

Estratexias de etiquetado

O primeiro é sinxelo etiqueta git. Temos un rexistro cunha imaxe etiquetada como 1.0. Kubernetes ten escenario e produción, onde se carga esta imaxe. En Git facemos commits e nalgún momento etiquetamos 2.0. Recopilámolo segundo as instrucións do repositorio e colócao no rexistro coa etiqueta 2.0. Estirámola á escena e, se todo está ben, á produción.

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

O problema con este enfoque é que primeiro puxemos a etiqueta, e só despois probámola e desenvolvémola. Por que? En primeiro lugar, é simplemente ilóxico: estamos a emitir unha versión de software que aínda non probamos (non podemos facer doutro xeito, porque para comprobar hai que poñer unha etiqueta). En segundo lugar, este camiño non é compatible con Gitflow.

A segunda opción - git commit + tag. A rama mestra ten unha etiqueta 1.0; para iso no rexistro - unha imaxe despregada na produción. Ademais, o clúster de Kubernetes ten contornos de vista previa e de posta en escena. A continuación seguimos Gitflow: na rama principal para o desenvolvemento (develop) creamos novas funcións, o que resulta nunha confirmación co identificador #c1. Recopilámolo e publicámolo no rexistro usando este identificador (#c1). Co mesmo identificador lanzamos a vista previa. Facemos o mesmo cos commits #c2 и #c3.

Cando nos demos conta de que hai funcións suficientes, comezamos a estabilizar todo. Crea unha rama en Git release_1.1 (na base #c3 de develop). Non é necesario recoller esta versión, porque... isto fíxose no paso anterior. Polo tanto, podemos simplemente poñelo en escena. Reparamos erros #c4 e, do mesmo xeito, estenderse á posta en escena. Ao mesmo tempo, o desenvolvemento está en marcha develop, de onde se toman periodicamente os cambios release_1.1. Nalgún momento, obtemos un commit compilado e cargado para a posta en escena, co que estamos satisfeitos (#c25).

Despois fusionamos (con avance rápido) a rama de lanzamento (release_1.1) en mestre. Poñemos unha etiqueta coa nova versión neste commit (1.1). Pero esta imaxe xa está recollida no rexistro, polo que para non recollela de novo, simplemente engadimos unha segunda etiqueta á imaxe existente (agora ten etiquetas no rexistro). #c25 и 1.1). Despois diso, lanzámolo á produción.

Hai un inconveniente de que só se carga unha imaxe na posta en escena (#c25), e na produción é algo diferente (1.1), pero sabemos que "fisicamente" estas son a mesma imaxe do rexistro.

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

A verdadeira desvantaxe é que non hai soporte para os commits de fusión, hai que facer un avance rápido.

Podemos ir máis aló e facer un truco... Vexamos un exemplo dun ficheiro Dockerfile sinxelo:

FROM ruby:2.3 as assets
RUN mkdir -p /app
WORKDIR /app
COPY . ./
RUN gem install bundler && bundle install
RUN bundle exec rake assets:precompile
CMD bundle exec puma -C config/puma.rb

FROM nginx:alpine
COPY --from=assets /app/public /usr/share/nginx/www/public

Imos construír un ficheiro a partir del segundo o seguinte principio:

  • SHA256 a partir dos identificadores das imaxes utilizadas (ruby:2.3 и nginx:alpine), que son sumas de verificación do seu contido;
  • todos os equipos (RUN, CMD etcétera.);
  • SHA256 dos ficheiros que se engadiron.

... e toma a suma de verificación (de novo SHA256) dese ficheiro. Isto sinatura todo o que define o contido da imaxe de Docker.

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

Volvamos ao diagrama e en lugar de commits usaremos tales sinaturas, é dicir. etiquetar imaxes con sinaturas.

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

Agora, cando sexa necesario, por exemplo, fusionar os cambios dunha versión a unha mestra, podemos facer unha confirmación de combinación real: terá un identificador diferente, pero a mesma sinatura. Co mesmo identificador lanzaremos a imaxe a produción.

A desvantaxe é que agora non será posible determinar que tipo de compromiso foi impulsado á produción: as sumas de verificación só funcionan nunha dirección. Este problema resólvese cunha capa adicional con metadatos; contarei máis tarde.

Etiquetado en werf

En werf fomos aínda máis alá e preparámonos para facer unha compilación distribuída cunha caché que non está almacenada nunha máquina... Entón, estamos construíndo dous tipos de imaxes Docker, chamámolas etapa и imaxe.

O repositorio werf Git almacena instrucións específicas para a compilación que describen as diferentes etapas da compilación (antes de instalar, instalar, antes de configurar, instalación). Recollemos a imaxe da primeira etapa cunha sinatura definida como a suma de verificación dos primeiros pasos. Despois engadimos o código fonte, para a nova imaxe de etapa calculamos a súa suma de verificación... Estas operacións repítense para todas as etapas, como resultado do cal obtemos un conxunto de imaxes de etapa. Despois elaboramos a imaxe final, que tamén contén metadatos sobre a súa orixe. E etiquetamos esta imaxe de diferentes xeitos (detalles máis adiante).

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

Supoñamos que despois disto aparece unha nova confirmación na que só se cambiou o código da aplicación. Que pasará? Para os cambios de código, crearase un parche e prepararase unha nova imaxe de escenario. A súa sinatura determinarase como a suma de verificación da antiga imaxe do escenario e do novo parche. A partir desta imaxe formarase unha nova imaxe final. Un comportamento semellante ocorrerá con cambios noutras etapas.

Así, as imaxes de escenario son unha caché que se pode almacenar de forma distribuída e as imaxes xa creadas a partir del cárganse no Rexistro Docker.

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

Limpeza do rexistro

Non estamos falando de eliminar capas que quedaron colgadas despois das etiquetas eliminadas: esta é unha característica estándar do propio Rexistro Docker. Estamos a falar dunha situación na que se acumulan moitas etiquetas Docker e entendemos que xa non necesitamos algunhas delas, pero que ocupan espazo (e/ou pagamos por iso).

Cales son as estratexias de limpeza?

  1. Non podes facer nada non limpes. Ás veces é realmente máis fácil pagar un pouco por espazo extra que desentrañar unha enorme maraña de etiquetas. Pero isto só funciona ata certo punto.
  2. Restablecemento completo. Se eliminas todas as imaxes e reconstruíches só as actuais no sistema CI, pode xurdir un problema. Se o contedor se reinicia en produción, cargarase unha nova imaxe para el, que aínda non foi probada por ninguén. Isto mata a idea de infraestrutura inmutable.
  3. Verde-azul. Un rexistro comezou a desbordarse: cargamos imaxes a outro. O mesmo problema que no método anterior: en que momento pode borrar o rexistro que comezou a desbordarse?
  4. Polo tempo. Queres eliminar todas as imaxes de máis de 1 mes? Pero definitivamente haberá un servizo que non se actualice durante un mes...
  5. A man determinar o que xa se pode eliminar.

Hai dúas opcións realmente viables: non limpar ou unha combinación de azul-verde + manualmente. Neste último caso, estamos a falar do seguinte: cando entendes que é hora de limpar o rexistro, crea un novo e engádese todas as novas imaxes ao longo de, por exemplo, un mes. E despois dun mes, vexa cales pods en Kubernetes seguen usando o antigo rexistro e transfireos tamén ao novo rexistro.

A que chegamos werf? Recollemos:

  1. Cabeza de Git: todas as etiquetas, todas as ramas - supoñendo que necesitamos todo o que está etiquetado en Git nas imaxes (e se non, entón necesitamos borralo no propio Git);
  2. todos os pods que actualmente se envían a Kubernetes;
  3. ReplicaSets antigos (o que foi lanzado recentemente), e tamén pensamos analizar as versións de Helm e seleccionar alí as imaxes máis recentes.

... e fai unha lista branca deste conxunto: unha lista de imaxes que non eliminaremos. Limpamos todo o demais, despois atopamos imaxes orfas do escenario e tamén as eliminamos.

Fase de implantación

Declaración fiable

O primeiro punto sobre o que me gustaría chamar a atención no despregamento é o lanzamento da configuración de recursos actualizada, declarada declarativamente. O documento YAML orixinal que describe os recursos de Kubernetes sempre é moi diferente do resultado que se está a executar no clúster. Porque Kubernetes engade á configuración:

  1. identificadores;
  2. información do servizo;
  3. moitos valores predeterminados;
  4. sección co estado actual;
  5. cambios realizados como parte do webhook de admisión;
  6. o resultado do traballo de varios controladores (e do planificador).

Polo tanto, cando aparece unha nova configuración de recurso (novo), non podemos simplemente tomar e sobrescribir a configuración actual "en directo" con ela (Vivir). Para iso teremos que comparar novo coa última configuración aplicada (último aplicado) e rodar Vivir parche recibido.

Este enfoque chámase fusión bidireccional. Úsase, por exemplo, en Helm.

Tamén hai fusión bidireccional, que se diferencia en que:

  • comparando último aplicado и novo, miramos o que se eliminou;
  • comparando novo и Vivir, miramos o que se engadiu ou cambiou;
  • aplícase o parche sumado Vivir.

Implementamos máis de 1000 aplicacións con Helm, polo que en realidade vivimos cunha combinación bidireccional. Non obstante, ten unha serie de problemas que solucionamos cos nosos parches, que axudan a Helm a funcionar con normalidade.

Estado de lanzamento real

Despois de que o noso sistema de CI xera unha nova configuración para Kubernetes baseada no seguinte evento, transmítea para usala (aplicar) a un clúster - usando Helm ou kubectl apply. A continuación, prodúcese a fusión N-way xa descrita, á que a API de Kubernetes responde con aprobación ao sistema CI, e iso ao seu usuario.

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

Non obstante, hai un gran problema: despois de todo aplicación exitosa non significa implantación exitosa. Se Kubernetes entende que cambios hai que aplicar e aplícao, aínda non sabemos cal será o resultado. Por exemplo, actualizar e reiniciar pods no frontend pode ter éxito, pero non no backend, e obteremos diferentes versións das imaxes da aplicación en execución.

Para facer todo correctamente, este esquema require unha ligazón adicional: un rastreador especial que recibirá información de estado da API de Kubernetes e transmitirá para unha análise posterior do estado real das cousas. Creamos unha biblioteca de código aberto en Go - cubedog (ver o seu anuncio aquí), que resolve este problema e está integrado en werf.

O comportamento deste rastreador a nivel de werf configúrase mediante anotacións que se colocan en Implementos ou StatefulSets. Anotación principal - fail-mode - Entende os seguintes significados:

  • IgnoreAndContinueDeployProcess — ignoramos os problemas de implantación deste compoñente e continuamos coa implantación;
  • FailWholeDeployProcessImmediately — un erro neste compoñente detén o proceso de implantación;
  • HopeUntilEndOfDeployProcess — Esperamos que este compoñente funcione ao final do despregamento.

Por exemplo, esta combinación de recursos e valores de anotación fail-mode:

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

Cando implementamos por primeira vez, é posible que a base de datos (MongoDB) non estea lista aínda; as implantacións fallarán. Pero pode agardar o momento para que comece, e o despregamento aínda terá lugar.

Hai dúas anotacións máis para kubedog en werf:

  • failures-allowed-per-replica — o número de caídas permitidas para cada réplica;
  • show-logs-until — regula o momento ata o que werf mostra (en stdout) rexistros de todos os pods desenvolvidos. O predeterminado é PodIsReady (para ignorar mensaxes que probablemente non queremos cando o tráfico comeza a chegar ao pod), pero os valores tamén son válidos: ControllerIsReady и EndOfDeploy.

Que máis queremos do despregue?

Ademais dos dous puntos xa descritos, gustaríanos:

  • para ver rexistros - e só os necesarios, e non todo seguido;
  • pista progreso, porque se o traballo colga "en silencio" durante varios minutos, é importante entender o que está a pasar alí;
  • ter retroceso automático no caso de que algo saíu mal (e polo tanto é fundamental coñecer o estado real do despregamento). O lanzamento debe ser atómico: ou vai ata o final, ou todo volve ao seu estado anterior.

Resultados de

Para nós como empresa, para implementar todos os matices descritos en diferentes etapas de entrega (construír, publicar, implementar), un sistema de CI e unha utilidade son suficientes werf.

No canto dunha conclusión:

werf - a nosa ferramenta para CI / CD en Kubernetes (descrición xeral e informe de vídeo)

Coa axuda de werf, logramos un bo progreso na resolución dunha gran cantidade de problemas para os enxeñeiros de DevOps e estaríamos encantados de que a comunidade máis ampla probase polo menos esta utilidade en acción. Será máis doado conseguir un bo resultado xuntos.

Vídeos e diapositivas

Vídeo da actuación (~47 minutos):

Presentación do informe:

PS

Outros informes sobre Kubernetes no noso blog:

Fonte: www.habr.com

Engadir un comentario