neděle 1. dubna 2018

DevOps s Kubernetes a Helm Charts


V poslední době jsem byl nucen se více zaměřit na integraci mezi vývojem a provozem. Poté, co jsem si prošlapal několik slepých uliček, jsem nakonec skončil u technologie, která se pomalu stává hlavním tématem ve spojení s Cloudem. Kubernetes. Platforma, kterou si zamilujete.

Než se dostaneme ke Kubernetes a Helm Charts, pojďme se podívat na svět, kde tyto technologie neexistují.

Cesta mezi zdrojáky v gitu a provozem je často dlouhá, krkolomná a křehká. Zažil jsem projekty, kde integrace znamenala, že se ručně nakopíroval build na ostrý provoz přes FTP. Projekty, kde díky absenci CI/CD se integrace vlastně nekonala, ale byla tvořena pouze z jednoho lokálního PC. A také projekty, kde integrace byla tak složitá, že bylo třeba zaměstnat několik lidí v roli DevOps, kteří tvořili křehké bash/python/perl skripty, které chápal pouze autor.

V takovém prostředí je vždy nutné mít několik programátorů/administrátorů, kteří jsou fulltime výtíženi jen tím, aby udrželi aktuální stav. Ve chvíli, kdy dochází produkčnímu nasazení, jedná se spíše o ruskou ruletu. Před takovou akcí dochází k modlitbám a lidem v hlavách běží několik otázek: "Bude to fungovat? Jak se jednoduše vrátíme zpět? Cože? Horizontálně škálovat?! Máme správné verze?" Tohle peklo se často ještě násobí počtem systémů, které musíme nasadit.

Vedle samotného nasazení na produkci se DevOps často stará i o to, aby vývoj, potažmo ostatní účastníci projektu, měli svá prostředí. Pokud nejsme naprostí ignoranti, je jasné, že pouze s jedním prostředím typu DEV si nemůžeme vystačit. Poté přichází na řadu prostředí pro testery, prostředí pro demo, prostředí na preprod simulaci, apod. Nedej bože, aby vývoj chtěl prostředí podle git větví. Není neobvyklé, že takových prostředí můžeme mít klidně i několik desítek. Často se nakonec dostáváme do stavu, kdy začneme ohýbat release management, protože jinak bychom do produkce nikdy nic nedostali. Chaos, který se nedá narovnat jinak, než zásadní změnou použitých technologií...

Pojďme se nyní pokusit z takového stavu vymanit a v podstatě se dostat do stavu, kdy samotná role DevOps se stane vlastně zbytečnou, popřípadě umožní nám zredukovat potřebné množství lidí, kteří by nám někde po serverech psali bash skripty. Aby následující popis nebyl příliš abstraktní, pojďme si navrhnout projekt, kterého se to bude týkat.

Návrh projektu


Náš projekt se skládá ze šesti systémů (každý systém je uložen ve vlastním git repozitáři):
  1. Frontend - hlavní UI našeho projektu
  2. Backend - hlavní backend pro UI a současně poskytuje veřejné API
  3. Admin - admin rozhraní
  4. Backend-admin - backend pro administraci
  5. Backend-int - integrační backend mezi naším projektem a ERP systémem pod námi
  6. Tool-ui - pomocný nástroj, který sleduje náš systém, slouží pouze pro vývoj

Běh projektu má následující předpoklady:
  • Databáze
  • Frontend, Backend a Admin musí být přístupný na doméně přes SSL, ostatní systémy nesmí být dostupné na veřejné doméně či IP adrese
  • Certifikáty pro komunikaci mezi Backend-int a ERP systémem
  • Projekt běží v Cloudu (Azure či Google Cloud)
  • CI/CD server - například CircleCI

Součástí vývoje je nutné mít i několik prostředí. Každé prostředí obsahuje vlastní databázi a je plně autonomní. Tvorba prostředí vlastně znamená tvorbu databáze a serverů, na kterých běží náš projekt. Pro naše účely si představme, že máme tyto prostředí:
  • dev - prostředí pro vývoj
  • test[0-9] - až 10 testovacích prostředí
  • uat - prostředí pro uživatelské akceptační testy
  • preprod - protředí simulující produkci, tedy na produkčních datech
  • prod - produkční prostředí

Krok první: Docker


O Dockeru jsem psal v minulém článku Vývoj aplikací přes Docker. V dnešní době si vývoj bez Dockeru neumím ani moc představit. Těch výhod, které Vám Docker nabízí je totiž tolik, že i přes prvotní krkolomné rozeběhnutí se nakonec budete ptát sami sebe: "Proč jsem sakra s Dockerem nezačal už dříve".

Součástí samotné "dockerizace" Vašich systémů je nutné myslet i na správné verzování. K verzování Vám slouží samotné Docker tagy. Zde Vás odkážu na článek, popisující samotné verzování.
Vedle verzování je možné Docker image ukládát i do privátních repozitářů. Ať už v rámci Cloudu, kde často najdete něco jako Container registry, tak třeba i v Docker Hubu.
Každopádně samotný Docker image NESMÍ obsahovat žádné citlivé informace. Pokud do běžícího Docker kontejneru potřebujeme dostat informace o připojení k databázi, nechte toto nastavení na Kubernetes.

Nyní Vám ukážu příklad, jak v CircleCI vytvořit Docker image a ten uložit do privátního Docker registry v Azure:
version: 2
jobs:
  create_docker_image:
    working_directory: ~/docker-image
    machine: true
    steps:
      - checkout
      - attach_workspace:
          at: ~/docker-image
      - run:
          name: Docker login
          command: docker login ${AZURE_DOCKER_URL_SERVER} -u ${AZURE_DOCKER_USERNAME} -p ${AZURE_DOCKER_PASSWORD}
      - run:
          name: Set docker image to workspace
          command: mkdir -p workspace && echo "${AZURE_DOCKER_URL_SERVER}/xxx/${CIRCLE_PROJECT_REPONAME}" > workspace/docker-image
      - run:
          name: Show docker image
          command: cat workspace/docker-image
      - run:
          name: Docker create image
          command: docker build --build-arg GIT_COMMIT=${CIRCLE_SHA1} --build-arg GIT_BRANCH=${CIRCLE_BRANCH} --build-arg BUILD_NUM=${CIRCLE_BUILD_NUM} --build-arg BUILD_AUTHOR=${CIRCLE_USERNAME} -t $(cat workspace/docker-image):latest .
      - run:
          name: Docker create tag CIRCLE_BRANCH
          command: docker tag $(cat workspace/docker-image) $(cat workspace/docker-image):${CIRCLE_BRANCH}
      - run:
          name: Docker create tag CIRCLE_SHA1
          command: docker tag $(cat workspace/docker-image) $(cat workspace/docker-image):${CIRCLE_SHA1}
      - run:
          name: Docker push
          command: docker push $(cat workspace/docker-image)
      - persist_to_workspace:
          root: .
          paths:
            - workspace
            - .circleci

workflows:
  version: 2
  build_and_deploy:
    jobs:
      - create_docker_image:
          context: xxx-context
          filters:
            branches:
              only:
                - dev

Job s názvem create_docker_image provede několik věcí:

  1. Přihlásí se pomocí Docker login do privátního Docker registry
  2. Vytvoří Docker image s tagem latest
  3. Do Docker image uloží informace jako: commit hash, git branch, číslo buildu, autora buildu
  4. Vytvoří další tag podle git větve
  5. Vytvoří další tag s git hash commitem
  6. Pushne dané tagy do privátní Docker registry
V ukázce je navíc použit workspace, což je vlastnost CircleCI, která umožňuje předat hodnoty dalším jobům v rámci workflow.

Výsledkem je, že v Docker repozitory budeme mít Docker image s tagy: latest, dev a commit hash. Důvod, proč verzujeme Docker image více tagy je jednoduchý. Představte si situaci, že používáte Docker image s tagem dev. Co když je ovšem onen image rozbitý a my rychle potřebujeme nasadit jeho předchozí verzi? Není nic jednoduššího, než si dohledat předchozí commit a nasadit image podle commit hash.

V případě releasu jsou Docker image tvořeny z master větve a verzovány podle git tagu. Pokud řeknu, že vytvářím novou verzi například: 2.3.7, tak se vytvoří či přepíší následující tagy: latest, 2, 2.3 a 2.3.7. Ale více se už dozvíte z výše odkazovaného článku o verzování Docker image.
Další variantou verzování vývoje je přes pre-release podle SemVer. Nicméně, zůstaňme u této zjednodušené varianty.

V této chvíli bychom mohli samotný Docker image nasadit a říci, že máme hotovo. Po pravdě, přímé nasazování samotných Docker images nám sice částečně pomůže, ale tím zdaleka nekončíme.

Stačí si jen zodpovědět otázku: "Jak vytvořím celé prostředí, když nasazuji pouze jeden systém?" Onen systém potřebuje databázi, potřebuje i okolní systémy a tím pádem bychom se stále museli zabývat tvorbou prostředí ala bash skripty v DevOps.

Krok druhý: Kubernetes


Když jsem přemýšlel, že napíši článek o Kubernetes, uvědomil jsem si, že vlastně nevím, kde přesně začít. Těch věcí je tolik, že na konci si řeknete: "Uff...". Na druhou stranu si ale zase uvědomíte, že to vlastně není nic složitého a vlastně jde jen o další abstrakci. Pojďme si Kubernetes vysvětlit trochu zjednodušeně.

Jednou větou se dá říci, že Kubernetes slouží na orchestraci běžících Docker kontejnerů. Zkusme si v několika bodech ukázat, kde všude nám může pomoci.

  • Tvorba bezvýpadkového systému
  • Škálování (horizontální i vertikální) za běhu
  • Load balancing
  • Správa celého prostředí
  • Možnost provést rollback, vrátit se k předchozím verzím
  • Uzavřít systémy uvnitř VNETu a vystavit ven pouze některé systémy
  • Globální nastavení proměných jako jsou hesla či hodnoty ovlivňující chod systémů
  • Jednoduše přes interní DNS provázat systémy mezi sebou
  • Před veřejné endpointy nasadit například nginx s automatickým Lets Encrypt SSL certifikáty
  • Automatické horizontální škálování, například v případě zátěže se po 5 minutách začnou automaticky vytvářet nové servery a případě snížení záteže se po dalších 5 minutách začnou mazat až do předem definovaného minimálního množství
  • Změnu provádět vždy deklarativně, tedy nebát se jako v případě imperativního zásahu, že shodím celý systém
  • Oddělit od sebe jednotlivá prostředí
  • Ideální platforma na tvorbu multitenantních systémů
  • Nebýt závislý na Cloud platformě

Kubernetes za Vás nevyřeší to, zda jste kvalitně otestovali svůj systém. Vyřeší ovšem za Vás to, že Vás odstíní od věcí, které byste si museli sami pracně udržovat pomocí různých podpůrných systémů.

Za největší výhodu považuji deklarativní ovládání celé platformy. Pomocí vlastních šablon říkáte, co se má v budoucnu stát. Příkladem je Deployment.

Deployment


Pomocí objektu Deployment definujete, jaký systém a v jaké konfiguraci se má nasadit. Součástí šablony pro Deployment jsou i další věci jako je například informace o tom, jaký Docker image se má spustit, s jakými parametry a třeba kolik se má vytvořit serverů.
Poté, co onen Deployment pošlete do Kubernetes, tak on si ho zařadí do své fronty požadavků a v budoucnu začně zpracovávat.

Pojďme se podívat na příklad Deployment objektu:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-deployment
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: frontend
    spec:
      containers:
      - name: frontend
        image: url/frontend-image:1.0.3-dev
        env:
        - name: PORT
          value: "8080"
        - name: NODE_ENV
          value: "production"
        ports:
        - containerPort: 8080
      imagePullSecrets:
      - name: image-secret

Tento plán nám říká, že do Kubernetes nasadíme systém s názvem frontend, který je z Docker image frontend-image:1.0.3-dev. Navíc onen Docker image je z privátní repozitory, k němuž najde přístup v Secret objektu s názvem image-secret. Secret je další objekt, který se definuje podobně jako objekt Deployment

Daný Docker kontejner bude mít dvě proměné a to PORT a NODE_ENV. Systém se nasadí ve třech Podech. Pojem Pod v Kubernetes znamená běžící jednotku. Zjednodušeně řečeno, můžeme na ní koukat jako na běžící Docker kontejner.

Abychom tento Deployment nasadili, stačí spustit následující příkaz:
kubectl apply -f deployment.yaml

V případě, že již takový Deployment existuje a tudíž i existují bežící Pody, tak Kubernetes neudělá to, že by předchozí zastavil a nasadil nové, ale nejprve si vytvoří nový Pod a v ramci něho se pokusí onen Pod spustit. Pokud by se mu to nepovedlo, ať už z důvodu neexistence Docker image, nebo pádu systému při startu, tak to neovlivní aktuální běh Podů.
Pokud se mu to podaří, tak po startu zastaví jeden z předchozích Podů a začne vytvářet další. Tímto způsobem postupně vymění všechny Pody, aniž by muselo dojít k výpadku.

V našem případě jsou systémy postaveny nad Node.js. Díky tomu je start takového Podu často v řádu několika milisekund. Pokud používáte nějaké šílenosti typu Weblogic server, který startuje cca týden, tak právě díky postupné výměně Podů, nemusíte zaznamenat žádný výpadek.

V Kubernetes existuje velká škála objektů. Zde je výčet těch, se kterými sám pracuji a považuji je za ty nejzásadnější:
  • Namespace - jmenný prostor, vhodný například pro určování prostředí
  • Deployment - definuje tvorbu Podů
  • Pod - běžící Docker kontejner
  • Service - služba, sloužící na sloučení Podů pod jeden vstupní bod, může sloužit jako Load Balancer
  • ConfigMap - objekt obsahující konfigurace, nejčastěji se tato konfigurace namapuje do Podů
  • Secret - stejný jako ConfigMap s tím rozdílem, že hodnota není na první pohled viditelná a je v ukládána v base64; nejčastěji se používá na uložení hesel a dalších citlivých informací
  • Ingress - objekt určený pro externí přístup, příkladem je třeba vystavení Vaší aplikace přes nginx server s SSL; Ingress je nad objektem Service a může tedy sloužit také jako Load Balancer


Kubernetes je úžásná technologie, která tady s námi několik let jistě bude. Informace o tom, jak použít Kubernetes třeba v Azure, najdete zde. Pokud byste si chtěli Kubernetes vyzkoušet pouze lokálně, není lepší volby než přes minikube.

Krok třetí: Helm


Pokud jsme zvládli Docker a Kubernetes, tak poslední třešničkou na dortu je Helm. A i když se Vám může zdát, že Helm je technologie, která už není tak potřebná, opak je pravdou Kubernetes bez Helmu je stejné, jako jezdit v rychlém autě se zataženou ruční brzdou.

Co je to tedy Helm? Jedná se o Package Manager pro Kubernetes. Vím, tohle je asi nic neříkající věta a proto pojďme si opět ukázat, kde by se nám Helm mohl hodit.

Vraťme se k našemu projektu. Projekt je složen ze 6ti systémů, které jsou mezi sebou provázané a navíc potřebují několik věcí, jako je ono připojení k databázi, připojení k privátní Docker repozitory, vystavení několik služeb přes nginx SSL, apod. Tím nám vznikají vcelku složité předpoklady k sestavení celého projektu. Co kdybychom ale řekli, že celý náš projekt vytvoříme od nuly a to pouze jedním příkazem?

A zde právě přichází na řadu Helm. Celý náš projekt můžeme zabalit do balíčku, který nainstalujeme jako kdybychom instalovali jakýkoli jiný program.

Zde je ukázka instalace v Helmu, která provede nasazení celého prostředí jako jeden balík:
helm install repo/projekt-x --namespace dev --name projekt-x-dev -f values-dev.yaml 

Tento příkaz vytvoří namespace dev a nainstaluje Kubernetes objekty, které jsou definovány v repozitáři repo/projekt-x. Onen balík bude pojmenován jako projekt-x-dev a navíc do jednotlivých definic vloží hodnoty ze souboru values-dev.yaml.

Stejně jako v případě Docker image bychom NEMĚLI do Helm Chart ukládat citlivé informace. K tomu právě slouží možnost při instalaci připojit soubor, který obsahuje ruzná hesla a další hodnoty, kterým nastavíme celý projekt.

Poté si můžeme nechat vypsat všechny balíčky, které jsme pomocí Helmu nainstalovali:
helm ls

Pomocí Helmu můžeme celý balíček smazat a tím smazat celé prostředí. Můžeme se pomocí Helmu i vracet k různým revizím. Představte si, že nasazujete novou verzi celého vašeho projektu. V testech bylo vše v pořádku a co čert nechtěl, na produkci je problém. Není nic snazšího, než pomocí Helm rollback se vrátit k předchozí verzi:
helm rollback system-x-dev 23

Pro představu si pojďme popsat třeba Wordpress. Wordpress je všeobecně známý redakční systém a na své fungování potřebuje několik věcí: apache, php a mysql. Co kdybychom ale Wordpress pomocí Helmu nainstalovali třeba takto:
helm install --name my-release stable/wordpress
Důvod, proč jsem si zvolil zrovna ukázku pro Wordpress, není náhoda. Existuje totiž oficiální balík Wordpress Chart.

Pojďme se nyní podívat na to, jak se takový Helm Chart tvoří. První základní věcí je soubor Chart.yaml, který obsahuje základní informace o našem balíku:
apiVersion: v1
appVersion: "1.0"
description: Nas pokusny system
name: system-x
version: 0.0.1

Nejdůležitější věcí je atribut name a version. Díky tomu jsme schopni identifikovat náš balík. Další součástí je adresář templates, ve kterém si již tvoříme vlastní definice Kubernetes objektů.

V případě, že máte již balík v Kubernetes nainstalován, máte možnost změnit jeho hodnoty. A to také pouze částečně. Představte si, že máte nainstalováné prostředí dev a chcete změnit verzi Docker image pro frontend. Ideální kandidát v CD/CI:
helm upgrade --reuse-values --wait system-x-dev repo/system-x --namespace dev --set frontend.image.tag=1.0.3-dev

Závěr


Stejně jako Kubernetes, tak i Helm je technologie, která se nedá popsat v rámci jednoho článku. I když se na první pohled může zdát, že je to příliš složité, tak opak je pravdou. Sám jsem toho důkazem. Stačí úvest jeden příklad. Kubernetes a Helm jsem si osvojil během několika dní. Postupnými kroky se dopracoval až k tomu, že dnes pouze vylepšuji současný stav.
V budoucnu se zkusím více zaměřit na samotný release management, který dost ovlivňuje to, jakým způsobem budeme s Dockerem, Kubernetes a Helmem pracovat.

"Happy helming"

Žádné komentáře:

Okomentovat

DevOps a Release management

Release management je proces, který je dnes přímo spojen s věcmi jako je Continuous Integration, Continuous Delivery (ve zkratce CI/CD), G...