werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

27 de maig a la sala principal de la conferència DevOpsConf 2019, celebrada en el marc del festival RIT++ 2019, com a part de la secció "Enviament continu", es va donar un informe "werf - la nostra eina per CI/CD a Kubernetes". Parla d'aquests problemes i reptes als quals s'enfronta tothom quan es desplega a Kubernetes, així com sobre matisos que potser no es noten immediatament. Analitzant possibles solucions, mostrem com això s'implementa en una eina de codi obert werf.

Des de la presentació, la nostra utilitat (abans coneguda com a dapp) ha assolit una fita històrica 1000 estrelles a GitHub — Esperem que la seva creixent comunitat d'usuaris faci la vida més fàcil a molts enginyers de DevOps.

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

Així doncs, us presentem vídeo del reportatge (~ 47 minuts, molt més informatiu que l'article) i l'extracte principal en forma de text. Va!

Lliurament de codi a Kubernetes

La xerrada ja no serà sobre werf, sinó sobre CI/CD a Kubernetes, la qual cosa implica que el nostre programari està empaquetat en contenidors Docker. (Vaig parlar d'això a Informe 2016), i els K8 s'utilitzaran per executar-lo en producció (més sobre això a 2017 anys).

Com és el lliurament a Kubernetes?

  • Hi ha un repositori Git amb el codi i les instruccions per construir-lo. L'aplicació està integrada en una imatge de Docker i es publica al registre de Docker.
  • El mateix dipòsit també conté instruccions sobre com desplegar i executar l'aplicació. En l'etapa de desplegament, aquestes instruccions s'envien a Kubernetes, que rep la imatge desitjada del registre i la llança.
  • A més, normalment hi ha proves. Alguns d'aquests es poden fer quan es publica una imatge. També podeu (seguint les mateixes instruccions) desplegar una còpia de l'aplicació (en un espai de noms K8s o un clúster separat) i executar proves allà.
  • Finalment, necessiteu un sistema CI que rebi esdeveniments de Git (o clics de botons) i cridi a totes les etapes designades: crear, publicar, desplegar, provar.

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

Aquí hi ha algunes notes importants:

  1. Perquè tenim una infraestructura immutable (infraestructura immutable), la imatge de l'aplicació que s'utilitza en totes les etapes (escenificació, producció, etc.), n'hi ha d'haver un. En vaig parlar amb més detall i amb exemples. aquí.
  2. Perquè seguim l'enfocament de la infraestructura com a codi (IaC), el codi de l'aplicació, les instruccions per muntar-la i llançar-la hauria de ser exactament en un dipòsit. Per obtenir més informació sobre això, vegeu el mateix informe.
  3. Cadena de lliurament (entrega) normalment ho veiem així: l'aplicació s'ha muntat, provat, llançat (etapa de llançament) i això és tot: el lliurament s'ha produït. Però, en realitat, l'usuari obté el que heu llançat, no després quan el vas lliurar a la producció, i quan va poder anar-hi i aquesta producció va funcionar. Així que crec que s'acaba la cadena de lliurament només en l'etapa operativa (correr), o més precisament, fins i tot en el moment en què el codi es va retirar de la producció (substituint-lo per un de nou).

Tornem a l'esquema de lliurament anterior a Kubernetes: no només el vam inventar nosaltres, sinó també literalment tots els que van tractar aquest problema. De fet, aquest patró s'anomena ara GitOps (Podeu llegir més sobre el terme i les idees que hi ha darrere aquí). Vegem les etapes de l'esquema.

Etapa de construcció

Sembla que es pot parlar de la creació d'imatges de Docker el 2019, quan tothom sap com escriure Dockerfiles i executar docker build?.. Aquests són els matisos als quals m'agradaria parar atenció:

  1. Pes de la imatge importa, així que utilitza d'etapes múltiplesdeixar a la imatge només l'aplicació realment necessària per al funcionament.
  2. Nombre de capes s'ha de minimitzar combinant cadenes de RUN-ordres segons el significat.
  3. Tanmateix, això afegeix problemes depuració, perquè quan el conjunt falla, heu de trobar l'ordre correcte de la cadena que va causar el problema.
  4. Velocitat de muntatge important perquè volem implementar canvis ràpidament i veure'n els resultats. Per exemple, no voleu reconstruir les dependències a les biblioteques d'idiomes cada vegada que creeu una aplicació.
  5. Sovint, necessiteu des d'un dipòsit Git moltes imatges, que es pot resoldre mitjançant un conjunt de Dockerfiles (o etapes anomenades en un fitxer) i un script Bash amb el seu muntatge seqüencial.

Aquesta va ser només la punta de l'iceberg a què s'enfronta tothom. Però hi ha altres problemes, en particular:

  1. Sovint en l'etapa de muntatge necessitem alguna cosa muntar (per exemple, emmagatzemeu a la memòria cau el resultat d'una ordre com apt en un directori de tercers).
  2. Volem Ansible en lloc d'escriure en shell.
  3. Volem construir sense Docker (per què necessitem una màquina virtual addicional en la qual hem de configurar tot per a això, quan ja tenim un clúster Kubernetes en el qual podem executar contenidors?).
  4. Muntatge paral·lel, que es pot entendre de diferents maneres: diferents ordres del Dockerfile (si s'utilitza multietapa), diverses commits del mateix repositori, diversos Dockerfiles.
  5. Muntatge distribuït: Volem recollir coses en beines que siguin "efímeres" perquè la seva memòria cau desapareix, la qual cosa significa que s'ha d'emmagatzemar en algun lloc per separat.
  6. Finalment, vaig anomenar el cim dels desitjos automàgia: Seria ideal anar al repositori, escriure alguna ordre i obtenir una imatge ja feta, muntada amb una comprensió de com i què fer correctament. Tanmateix, personalment no estic segur que tots els matisos es puguin preveure d'aquesta manera.

I aquí teniu els projectes:

  • moby/kit de construcció — un constructor de Docker Inc (ja integrat a les versions actuals de Docker), que intenta resoldre tots aquests problemes;
  • kaniko — un constructor de Google que us permet construir sense Docker;
  • Buildpacks.io — L'intent de CNCF de fer màgia automàtica i, en particular, una solució interessant amb rebase per a capes;
  • i un munt d'altres utilitats, com ara construeix, genuinetools/img...

...i mira quantes estrelles tenen a GitHub. És a dir, d'una banda, docker build existeix i pot fer alguna cosa, però en realitat el problema no està completament resolt - una prova d'això és el desenvolupament paral·lel de col·lectors alternatius, cadascun dels quals resol una part dels problemes.

Muntatge en werf

Així que vam arribar werf (prèviament famós com dapp) — Una utilitat de codi obert de l'empresa Flant, que fa molts anys que fabriquem. Tot va començar fa 5 anys amb scripts Bash que optimitzen el muntatge de Dockerfiles, i durant els últims 3 anys s'ha dut a terme un desenvolupament complet en el marc d'un projecte amb el seu propi repositori Git. (primer en Ruby, i després reescrit per anar, i al mateix temps canviat de nom). Quins problemes de muntatge es resolen a werf?

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

Els problemes ombrejats en blau ja s'han implementat, la construcció paral·lela es va fer dins del mateix amfitrió i es preveu que les qüestions destacades en groc s'acabin a finals de l'estiu.

Fase de publicació en el registre (publicar)

Vam marcar docker push... - què pot ser difícil de pujar una imatge al registre? I llavors sorgeix la pregunta: "Quina etiqueta he de posar a la imatge?" Neix per la raó que tenim Gitflow (o una altra estratègia de Git) i Kubernetes, i la indústria està intentant assegurar-se que el que passa a Kubernetes segueixi el que passa a Git. Després de tot, Git és la nostra única font de veritat.

Què té de difícil això? Garantir la reproductibilitat: d'un commit a Git, que és de naturalesa immutable (immutable), a una imatge de Docker, que s'hauria de mantenir igual.

També és important per a nosaltres determinar l'origen, perquè volem entendre a partir de quina confirmació es va crear l'aplicació que s'executa a Kubernetes (llavors podem fer diferències i coses semblants).

Estratègies d'etiquetatge

El primer és senzill etiqueta git. Tenim un registre amb una imatge etiquetada com a 1.0. Kubernetes té escenari i producció, on es penja aquesta imatge. A Git fem commits i en algun moment etiquetem 2.0. El recollim segons les instruccions del repositori i el col·loquem al registre amb l'etiqueta 2.0. L'extrem a l'escenari i, si tot va bé, a la producció.

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

El problema d'aquest enfocament és que primer vam posar l'etiqueta i només després la vam provar i la vam desplegar. Per què? En primer lloc, és simplement il·lògic: estem emetent una versió de programari que encara no hem provat (no podem fer d'altra manera, perquè per comprovar-ho hem de posar una etiqueta). En segon lloc, aquest camí no és compatible amb Gitflow.

La segona opció: git commit + etiqueta. La branca mestra té una etiqueta 1.0; per a això al registre: una imatge desplegada a producció. A més, el clúster de Kubernetes té contorns de previsualització i escenificació. A continuació seguim Gitflow: a la branca principal per al desenvolupament (develop) fem noves característiques, donant lloc a una confirmació amb l'identificador #c1. El recollim i el publiquem al registre mitjançant aquest identificador (#c1). Amb el mateix identificador estem desplegant per a una vista prèvia. Fem el mateix amb els commits #c2 и #c3.

Quan ens vam adonar que hi ha prou característiques, comencem a estabilitzar-ho tot. Creeu una branca a Git release_1.1 (a la base #c3 d' develop). No cal recollir aquesta publicació, perquè... això es va fer al pas anterior. Per tant, simplement podem posar-lo en escena. Arreglem errors #c4 i de la mateixa manera es desplega a la posada en escena. Al mateix temps, el desenvolupament està en marxa develop, d'on es treuen els canvis periòdicament release_1.1. En algun moment, obtenim una confirmació compilada i pujada a la posada en escena, amb la qual estem contents (#c25).

A continuació, fusionem (amb avançament ràpid) la branca de llançament (release_1.1) en màster. Posem una etiqueta amb la nova versió en aquest commit (1.1). Però aquesta imatge ja està recollida al registre, així que per no tornar-la a recollir, simplement afegim una segona etiqueta a la imatge existent (ara té etiquetes al registre). #c25 и 1.1). Després d'això, el tirem a producció.

Hi ha un inconvenient que només es penja una imatge a la posada en escena (#c25), i en producció és una mica diferent (1.1), però sabem que "físicament" són la mateixa imatge del registre.

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

El veritable desavantatge és que no hi ha suport per a les confirmacions de fusió, heu de fer un avançament ràpid.

Podem anar més enllà i fer un truc... Vegem un exemple d'un Dockerfile simple:

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

Construïm un fitxer a partir d'ell segons el principi següent:

  • SHA256 a partir dels identificadors de les imatges utilitzades (ruby:2.3 и nginx:alpine), que són sumes de control del seu contingut;
  • tots els equips (RUN, CMD etcètera.);
  • SHA256 dels fitxers que s'han afegit.

... i agafeu la suma de comprovació (de nou SHA256) d'aquest fitxer. Això signatura tot el que defineix el contingut de la imatge de Docker.

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

Tornem al diagrama i en comptes de commits utilitzarem aquestes signatures, és a dir etiqueta imatges amb signatures.

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

Ara, quan calgui, per exemple, fusionar els canvis d'una versió a una mestra, podem fer una confirmació de combinació real: tindrà un identificador diferent, però la mateixa signatura. Amb el mateix identificador desplegarem la imatge a producció.

El desavantatge és que ara no serà possible determinar quin tipus de compromís es va impulsar a la producció: les sumes de control només funcionen en una direcció. Aquest problema es resol amb una capa addicional amb metadades; més endavant us explicaré més.

Etiquetat en werf

A werf hem anat encara més enllà i ens estem preparant per fer una compilació distribuïda amb una memòria cau que no s'emmagatzema en una màquina... Per tant, estem construint dos tipus d'imatges Docker, les anomenem etapa и imatge.

El repositori werf Git emmagatzema instruccions específiques de compilació que descriuen les diferents etapes de la compilació (abans d'instal·lar, instal · lar, abans de la configuració, disposició). Recollim la imatge de la primera etapa amb una signatura definida com la suma de control dels primers passos. Després afegim el codi font, per a la nova imatge de l'etapa calculem la seva suma de control... Aquestes operacions es repeteixen per a totes les etapes, com a resultat de les quals obtenim un conjunt d'imatges d'escenari. Després fem la imatge final, que també conté metadades sobre el seu origen. I etiquetem aquesta imatge de diferents maneres (detalls més endavant).

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

Suposem que després d'això apareix una nova confirmació en la qual només s'ha canviat el codi de l'aplicació. Què passarà? Per als canvis de codi, es crearà un pedaç i es prepararà una nova imatge d'escenari. La seva signatura es determinarà com la suma de control de l'antiga imatge de l'escenari i el nou pegat. A partir d'aquesta imatge es formarà una nova imatge final. Un comportament similar es produirà amb canvis en altres etapes.

Així, les imatges d'escenari són una memòria cau que es pot emmagatzemar de manera distribuïda i les imatges ja creades a partir d'ella es pengen al registre Docker.

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

Neteja del registre

No estem parlant d'eliminar les capes que van quedar penjades després de les etiquetes suprimides: aquesta és una característica estàndard del propi registre Docker. Estem parlant d'una situació en què s'acumulen moltes etiquetes Docker i entenem que ja no en necessitem algunes, però que ocupen espai (i/o paguem per això).

Quines són les estratègies de neteja?

  1. Simplement no pots fer res no netejar. De vegades és molt més fàcil pagar una mica per espai addicional que desentranyar un enorme embolic d'etiquetes. Però això només funciona fins a un cert punt.
  2. Reinici complet. Si suprimiu totes les imatges i reconstruïu només les actuals al sistema CI, pot sorgir un problema. Si el contenidor es reinicia en producció, se'n carregarà una imatge nova, que encara no ha estat provada per ningú. Això mata la idea d'una infraestructura immutable.
  3. Blau verd. Un registre va començar a desbordar-se: pengem imatges a un altre. El mateix problema que en el mètode anterior: en quin moment podeu esborrar el registre que ha començat a desbordar-se?
  4. Per temps. Vols suprimir totes les imatges de més d'1 mes? Però sens dubte hi haurà un servei que no s'ha actualitzat durant un mes...
  5. Manualment determinar què ja es pot esborrar.

Hi ha dues opcions realment viables: no netejar o una combinació de blau-verd + manualment. En aquest darrer cas, estem parlant del següent: quan enteneu que és el moment de netejar el registre, en creeu un de nou i hi afegiu totes les imatges noves durant, per exemple, un mes. I al cap d'un mes, mireu quins pods de Kubernetes encara fan servir el registre antic i transferiu-los també al registre nou.

A què hem arribat werf? Recollim:

  1. Cap de Git: totes les etiquetes, totes les branques, suposant que necessitem tot el que s'etiqueta a Git a les imatges (i si no, hem d'esborrar-ho al mateix Git);
  2. tots els pods que actualment s'envien a Kubernetes;
  3. ReplicaSets antics (el que s'ha llançat recentment) i també tenim previst escanejar les versions de Helm i seleccionar-hi les últimes imatges.

... i feu una llista blanca d'aquest conjunt: una llista d'imatges que no suprimirem. Netegem tota la resta, després trobem imatges d'escenari orfe i també les suprimim.

Etapa de desplegament

Declaració fiable

El primer punt sobre el qual m'agradaria cridar l'atenció en el desplegament és el llançament de la configuració de recursos actualitzada, declarada declarativa. El document YAML original que descriu els recursos de Kubernetes sempre és molt diferent del resultat que s'executa realment al clúster. Com que Kubernetes afegeix a la configuració:

  1. identificadors;
  2. informació del servei;
  3. molts valors per defecte;
  4. secció amb l'estat actual;
  5. els canvis realitzats com a part del webhook d'admissió;
  6. el resultat del treball de diversos controladors (i del planificador).

Per tant, quan apareix una nova configuració de recursos (nou), no podem simplement agafar i sobreescriure la configuració actual "en directe" amb ella (viure). Per fer-ho haurem de comparar nou amb l'última configuració aplicada (l'últim aplicat) i roda cap amunt viure pegat rebut.

Aquest enfocament s'anomena fusió de 2 vies. S'utilitza, per exemple, a Helm.

També hi ha fusió de 3 vies, que es diferencia en què:

  • comparant l'últim aplicat и nou, mirem el que s'ha esborrat;
  • comparant nou и viure, mirem el que s'ha afegit o canviat;
  • s'aplica el pegat sumat viure.

Despleguem més de 1000 aplicacions amb Helm, de manera que vivim amb una combinació bidireccional. Tanmateix, té una sèrie de problemes que hem resolt amb els nostres pedaços, que ajuden a Helm a funcionar amb normalitat.

Estat real de llançament

Després que el nostre sistema CI generi una nova configuració per a Kubernetes basada en el següent esdeveniment, la transmet per utilitzar-la (aplicar) a un clúster - utilitzant Helm o kubectl apply. A continuació, es produeix la fusió N-way ja descrita, a la qual l'API de Kubernetes respon amb aprovació al sistema CI, i això al seu usuari.

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

Tanmateix, hi ha un gran problema: després de tot una aplicació reeixida no significa un llançament reeixit. Si Kubernetes entén quins canvis s'han d'aplicar i els aplica, encara no sabem quin serà el resultat. Per exemple, actualitzar i reiniciar pods a la interfície pot tenir èxit, però no a la part posterior, i obtindrem diferents versions de les imatges de l'aplicació en execució.

Per fer-ho tot correctament, aquest esquema requereix un enllaç addicional: un rastrejador especial que rebrà informació d'estat de l'API de Kubernetes i la transmetrà per a una anàlisi posterior de l'estat real de les coses. Hem creat una biblioteca de codi obert a Go - cubedog (vegeu el seu anunci aquí), que resol aquest problema i està integrat a werf.

El comportament d'aquest rastrejador a nivell de werf es configura mitjançant anotacions que es col·loquen a Deployments o StatefulSets. Anotació principal - fail-mode - entén els significats següents:

  • IgnoreAndContinueDeployProcess — ignorem els problemes de desplegament d'aquest component i continuem amb el desplegament;
  • FailWholeDeployProcessImmediately — un error en aquest component atura el procés de desplegament;
  • HopeUntilEndOfDeployProcess — Esperem que aquest component funcioni al final del desplegament.

Per exemple, aquesta combinació de recursos i valors d'anotació fail-mode:

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

Quan despleguem per primera vegada, és possible que la base de dades (MongoDB) encara no estigui preparada; els desplegaments fallaran. Però podeu esperar el moment perquè comenci i el desplegament encara es durà a terme.

Hi ha dues anotacions més per a kubedog a werf:

  • failures-allowed-per-replica — el nombre de caigudes permeses per a cada rèplica;
  • show-logs-until — regula el moment fins al qual werf mostra (en stdout) els registres de totes les beines desplegades. El valor predeterminat és PodIsReady (per ignorar missatges que probablement no volem quan el trànsit comença a arribar al pod), però els valors també són vàlids: ControllerIsReady и EndOfDeploy.

Què més volem del desplegament?

A més dels dos punts ja descrits, ens agradaria:

  • veure registres - i només els necessaris, i no tot seguit;
  • pista progrés, progressar, perquè si la feina es penja “en silenci” durant uns quants minuts, és important entendre què hi passa;
  • иметь retrocés automàtic en cas que alguna cosa va sortir malament (i per tant és fonamental conèixer l'estat real del desplegament). El desplegament ha de ser atòmic: o va fins al final, o tot torna al seu estat anterior.

Resultats de

Per a nosaltres com a empresa, per implementar tots els matisos descrits en diferents etapes de lliurament (construir, publicar, desplegar), un sistema CI i una utilitat són suficients werf.

En lloc d'una conclusió:

werf: la nostra eina per a CI/CD a Kubernetes (visió general i informe de vídeo)

Amb l'ajuda de werf, hem avançat molt en la resolució d'un gran nombre de problemes per als enginyers de DevOps i ens agradaria que la comunitat més àmplia com a mínim intentés aquesta utilitat en acció. Serà més fàcil aconseguir un bon resultat junts.

Vídeos i diapositives

Vídeo de l'actuació (~47 minuts):

Presentació de l'informe:

PS

Altres informes sobre Kubernetes al nostre bloc:

Font: www.habr.com

Afegeix comentari