This shows you the differences between two versions of the page.
cc:laboratoare:03 [2020/10/25 20:19] alexandru.hogea [Elemente Cheie] |
cc:laboratoare:03 [2022/10/23 19:39] (current) florin.mihalache |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Laboratorul 03. Docker Compose ===== | + | ====== Laboratorul 03 - Kubernetes deployments, rollouts și servicii ====== |
+ | ===== Deployments ===== | ||
+ | Despre deployments ați învățat în cadrul laboratorului trecut. În cadrul laboratorului curent veți învăța despre upgrade-ul unui deployment și despre rollback-ul unui deployment. | ||
- | ==== Introducere ==== | + | Pentru un fișier de deployment putem să avem următorul template: |
+ | <code yaml> | ||
+ | apiVersion: apps/v1 | ||
+ | kind: Deployment | ||
+ | metadata: | ||
+ | name: <> #name | ||
+ | spec: | ||
+ | replicas: <x> #no of replicas | ||
+ | selector: | ||
+ | matchLabels: | ||
+ | key: value | ||
+ | template: | ||
+ | metadata: | ||
+ | labels: | ||
+ | pod-key: pod-value | ||
+ | spec: | ||
+ | containers: | ||
+ | - name: nginx | ||
+ | image: <> #you can use the nginx image | ||
+ | </code> | ||
- | [[https://docs.docker.com/compose/|Docker Compose]] este un creat de catre Docker, folosit pentru **centralizarea configurarii** de rulare a containerelor in maniera **declarativa**. | + | Dorim, de exemplu, să facem următorul deploymentul, cu numele ''cc-dep-01'', cu 4 replici și cu un label-ul ''app: lab3cc'': |
+ | <code yaml> | ||
+ | kind: Deployment | ||
+ | metadata: | ||
+ | name: cc-dep-01 | ||
+ | spec: | ||
+ | replicas: 10 | ||
+ | selector: | ||
+ | matchLabels: | ||
+ | app: lab3cc | ||
+ | template: | ||
+ | metadata: | ||
+ | labels: | ||
+ | app: lab3cc | ||
+ | spec: | ||
+ | containers: | ||
+ | - name: nginx | ||
+ | image: nginx | ||
+ | </code> | ||
- | In mod clasic, pentru rularea unor containere, este nevoie sa rulati comanda aferenta de rulare (__docker run__) si sa dati toti parametri necesari. Acest proces poate deveni anevoios daca este repetat pentru pornirea mai multor containere. | + | <note> |
+ | Tasks: | ||
+ | * Creați deployment-ul cum știm deja: ''kubectl apply -f lab3dep.yaml'' | ||
+ | * Afișați obiectele de tipul ReplicaSet și Deployment pe care le aveți momentan în cluster. | ||
+ | * Afișați pod-urile care sunt up and running. | ||
+ | </note> | ||
- | Un mod primitiv de a "salva" configuratia de rulare este sa va creati scripturi. Problema in rularea mai multor scripturi este pierderea consistentei in configurare (Ce container la ce retea se conecteaza, cu cine comunica, etc...). | + | Ajungem la partea de upgrade a unui deployment. Un use-case relevant este unul des întâlnit în ziua de azi, presupunem că a apărut o nouă versiune a aplicației și vrem să folosim versiunea curentă. |
- | Utilizand fisiere de configurare **.yml**, Docker Compose centralizeaza procesul de configurare intr-o maniera naturala, declarativa. | + | În cazul deployment-ului nostru, vrem să folosim o nouă versiune a webserver-ului aplicației noastre, deci vrem să folosim o versiune mai nouă a nginx. |
- | Mai mult de atat, formatul pentru fisiere compose este utilizat si in cadrul **Docker Swarm**, orchestratorul creat de Docker pentru gestiunea serviciilor Docker. Despre Swarm vom discuta la laboratorul urmator. | + | <note> |
+ | Tasks: | ||
+ | * Creați un nou fișier plecând de la fișierul creat anterior. Schimbați imaginea folosită în ''nginx:1.21.1''. | ||
+ | * Aplicați modificările făcute: ''kubectl apply -f mynewdep.yaml'' | ||
+ | * Listați pod-urile din cluster. Ce se întâmplă? | ||
+ | </note> | ||
- | <note tip>O sa observati de mai multe ori ca folosim terminologia servicii/containere. Asta pentru ca Docker Swarm lucreaza cu servicii, in timp ce Docker Compose cu containere. Ne referim la amandoua in acelasi context deoarece configuratia, este, in proportie de 90%, identica, indiferent de utilizarea Swarm sau Compose</note> | + | În primul rând, un aspect foarte important este că pod-urile sunt upgraded secvențial, nu toate odată, după ce un număr de noi pod-uri au fost create, urmează să se șteargă cele vechi, procedeul se repetă până când toate pod-urile au fost updated. |
- | ==== Elemente Cheie ==== | + | <note tip>Așa cum puteți observa, numele unui pod are următoarea structură: ''numeDeployment-identificatorReplicaSet-identificatorPod''</note> |
+ | <note>Task: | ||
+ | * Listați toate obiectele de tip ReplicaSet din cluster: ''kubectl get rs # am folosit o prescurtare'' | ||
+ | </note> | ||
- | === Formatul unui fisier YAML === | + | Din câte puteți observa, avem două ReplicaSets care corespund aceluiași deployment. Motivul este pentru că în momentul în care am făcut upgrade deployment-ului, a fost creat un nou ReplicaSet cu nouă imagine de nginx folosită. Obiectul ReplicaSet vechi este și el păstrat pentru că există mereu o șansă să vrem să ne întoarcem la versiunea anterioară a aplicației. |
- | Fisierele YAML (Yet Another Markup Language), de obicei, sunt folosite pentru a scrie configurari declarativ. Formatul este unul foarte usor de inteles si folosit: | + | ===== Rollouts ===== |
+ | Am realizat că avem o greșeală în deploymentul nostru, pentru a reveni la versiunea anterioară folosim următoarea comandă: ''kubectl rollout history deployment <depname>'' | ||
- | * elemente de tip cheie:valoare | + | <note> |
- | * aliniatele **identate** reprezinta proprietati copii ale pargrafelor anterioare | + | Tasks: |
- | * listele se delimiteaza prin - | + | * Analizați din nou obiectele de ReplicaSet și Pod. |
+ | * Scalați deployment-ul la 2 replici: ''kubectl scale deployment <depname> –replicas=2'' | ||
+ | * Verificați numărul de pod-uri. | ||
+ | * Scalați din nou la 5 replici: ''kubectl scale deployment <depname> –replicas=5'' | ||
+ | </note> | ||
- | === Exemplu Fisier docker-compose.yml === | + | Pentru a modifica imaginea folosită de un deployment, avem următoarea comandă: ''kubectl set image deployment <depname> nginx=nginx:failTest'' |
- | <code yaml> | + | În comanda de mai sus am introdus intenționat o imagine care nu există. Dacă urmariți comportamentul deployment-ului, o să observați că noile pod-uri nu vor fi create deoarece nu poate fi gasită imaginea specificată. |
- | # docker-compose.yml | + | |
- | version: '3.8' | + | |
- | services: | + | |
- | api: | + | |
- | build: . # construieste imaginea dintr-un Dockerfile | + | |
- | image: nume-imagine-registru:versiune # foloseste o imagine de pe registrul curent | + | |
- | environment: | + | |
- | NODE_ENV: development | + | |
- | VARIABILA_DE_MEDIU: valoare | + | |
- | ports: | + | |
- | - "5000:80" | + | |
- | networks: | + | |
- | - network-laborator-3 | + | |
- | postgres: | + | |
- | image: postgres:12 | + | |
- | environment: | + | |
- | PGPASSWORD_FILE: /run/secrets/parola-mea-ultra-secreta | + | |
- | volumes: | + | |
- | - volum-laborator-3:/var/lib/postgresql/data | + | |
- | - ./scripturi-initializare/init-db.sql:/docker-entrypoint-init.d/init-db.sql | + | |
- | networks: | + | |
- | - network-laborator-3 | + | |
- | volumes: | + | Am ajuns în situația în care vrem să ne întoarcem la o versiunea anterioară. Pentru a vedea istoricul modificărilor făcute pe acest deployment, avem următoarea comandă: ''kubectl rollout history deoployment <depname>'' |
- | volum-laborator-3: | + | |
- | networks: | + | Observați ca avem mai multe "revizii" ale deployment-ului nostru. Pentru a ne întoarce la o anumită revizie, folosim următoarea comandă: |
- | network-laborator-3: | + | ''kubectl rollout history deployment cc-dep-01 --revision=2'' |
- | secrets: | + | ===== Servicii ===== |
- | parola-mea-ultra-secreta: | + | Care sunt serviciile cunoscute în Kubernetes? Care este diferența între ele? Ne interesează în mod special diferența dintre ClusterIP și NodePort. |
- | file: './parola-mea-nu-atat-de-secreta.txt' | + | Pentru a instanția o aplicație simplă care conține o bază de date și un API care face interogări, avem nevoie, asa cum intuiți, de 2 pod-uri. |
+ | Pentru a porni un pod de Postgres, avem nevoie de a seta variabilele ''POSTGRES_USER'' și ''POSTGRES_PASSWORD'', dar și numele bazei de date pe care urmează să o folosim - ''POSTGRES_DB''. Avem mai multe moduri prin care putem face asta, cel mai simplu exemplu (și cel mai nesigur) este cu un ConfigMap. | ||
+ | |||
+ | Exemplu: | ||
+ | <code yaml> | ||
+ | apiVersion: v1 | ||
+ | kind: ConfigMap | ||
+ | metadata: | ||
+ | name: db-config-map | ||
+ | data: | ||
+ | POSTGRES_USER: "admin" | ||
+ | POSTGRES_PASSWORD: "admin" | ||
+ | POSTGRES_DB: "books" | ||
</code> | </code> | ||
- | === Version === | + | Când o să creăm pod-ul / deployment-ul, o să folosim acest ConfigMap pentru a injecta variabilele de mediu. Pentru a avea datele persistente, avem nevoie, asa cum știm din labul trecut, de un PV și de un PVC: |
+ | * ''PersistentVolume'' (PV): | ||
+ | <code yaml> | ||
+ | apiVersion: v1 | ||
+ | kind: PersistentVolume | ||
+ | metadata: | ||
+ | name: postgres-pv-volume | ||
+ | labels: | ||
+ | type: local | ||
+ | spec: | ||
+ | storageClassName: manual | ||
+ | capacity: | ||
+ | storage: 1Gi | ||
+ | accessModes: | ||
+ | - ReadWriteOnce | ||
+ | hostPath: | ||
+ | path: "/mnt/data" | ||
+ | </code> | ||
+ | * ''PersistentVolumeClaim'' (PVC): | ||
+ | <code yaml> | ||
+ | apiVersion: v1 | ||
+ | kind: PersistentVolumeClaim | ||
+ | metadata: | ||
+ | name: posgress-pvc | ||
+ | spec: | ||
+ | storageClassName: manual | ||
+ | accessModes: | ||
+ | - ReadWriteOnce | ||
+ | resources: | ||
+ | requests: | ||
+ | storage: 1Gi | ||
+ | </code> | ||
- | **Version** descrie ce set de functionalitati va fi incarcat la rularea utilitarului Docker Compose | + | Pentru a crea pod-ul de Postgres, avem următorul fișier: |
- | <note important>Este **obligatoriu** sa treceti versiunea in orice fisier docker-compose.yml </note> | + | <code yaml> |
+ | apiVersion: v1 | ||
+ | kind: Pod | ||
+ | metadata: | ||
+ | name: db-pod | ||
+ | labels: | ||
+ | app: postgres | ||
+ | spec: | ||
+ | containers: | ||
+ | - image: postgres:10.4 | ||
+ | name: postgres | ||
+ | volumeMounts: | ||
+ | - name: myvol | ||
+ | mountPath: /etc/config | ||
+ | ports: | ||
+ | - containerPort: 5432 | ||
+ | envFrom: | ||
+ | - configMapRef: | ||
+ | name: db-config-map | ||
+ | volumes: | ||
+ | - name: myvol | ||
+ | persistentVolumeClaim: | ||
+ | claimName: posgress-pvc | ||
+ | </code> | ||
- | === Services === | + | Pentru ca podul nostru de API să comunice cu cel de baze de date, trebuie să expunem pod-ul de Postgres printr-un serviciu de tip ClusterIP: |
- | + | <code yaml> | |
- | **Services** descrie serviciile/containerele ce vor rula dupa ce configuratia este pornita de catre compose. Fiecare serviciu reprezinta un container care va avea **numele** si configuratia serviciului. In exemplul de mai sus, containerele se vor numi //api// si //postgres//. | + | apiVersion: v1 |
- | + | kind: Service | |
- | * **build** - mentioneaza folderul unde se afla Dockerfile-ul de la care se va efectua construirea containerului | + | metadata: |
- | * **image** - mentioneaza numele imaginii folosit pentru rularea containerului | + | name: postgres |
- | <note important>build si image sunt optiuni disjuncte</note> | + | labels: |
- | * **ports** - lista de intrari de tipul **PORT_HOST:PORT_SERVICIU** unde este realizata maparea de porturi | + | app: postgres |
- | * **volumes** - lista de intrari de tipul **VOLUM_HOST:CALE_SERVICIU** unde sunt precizate maparile de volume. Aceleasi reguli care se aplica la rularea clasica sunt mentinute si aici. VOLUM_HOST poate fi **named volume** sau **bind mount**. | + | spec: |
- | * **networks** - lista de retele din care face parte serviciul/containerul | + | type: ClusterIP |
- | * **secrets** - lista de secrete ce vor fi folosite in cadrul serviciului/containerului | + | ports: |
- | <note tip>Secretele trebuie trecute si in cadrul variabilelor de mediu, conform documentatiei. De exemplu, [[https://hub.docker.com/_/postgres|in configuratia Postgres]] trebuie trecute in variabile de mediu speciale, sufixate cu **_FILE**, impreuna cu calea lor completa (adica /run/secrets/NUME_SECRET)</note> | + | - port: 5432 |
- | * **environment** - obiect de intrari de tipul **VARIABILA_MEDIU_SERVICIU:valoare** care injecteaza variabilele de mediu specificate la rularea serviciului/containerului | + | selector: |
- | + | app: postgres | |
- | === Volumes === | + | </code> |
- | + | ||
- | **Volumes** descrie //NAMED VOLUMES// utilizate in cadrul configuratiei. | + | |
- | <note tip>Observati ca **volumes**, ca si proprietate top level, este scrisa pe acelasi nivel de identatie ca **services**. Nu trebuie confundata cu proprietatea copil **volumes** din interiorul configuratiei serviciilor</note> | + | |
- | Volumele se trec sub forma de obiecte. Daca nu se doreste schimbarea configuratiei default, valoarea este un camp gol. In laboratorul urmator vom lucra cu volume NFS, unde va trebui oferita o configuratie custom. | + | Pentru a porni podul de API, avem următorul fișier: |
- | + | ||
- | === Networks === | + | |
- | + | ||
- | **Networks** descrie //retelele// utilizate in cadrul configuratiei. | + | |
- | <note tip>Observati ca **networks**, ca si proprietate top level, este scrisa pe acelasi nivel de identatie ca **services**. Nu trebuie confundata cu proprietatea copil **networks** din interiorul configuratiei serviciilor</note> | + | |
- | + | ||
- | Retelele se trec sub forma de obiecte. Daca nu se doreste schimbarea configuratiei default, valoarea este un camp gol. Un exemplu de configuratie de retea este atunci cand se foloseste o retea care deja exista, de exemplu: | + | |
<code yaml> | <code yaml> | ||
- | networks: | + | apiVersion: v1 |
- | reteaua-mea-care-de-fapt-exista: | + | kind: Pod |
- | external: true | + | metadata: |
- | name: reteaua-originala-care-deja-exista | + | name: apipod |
+ | labels: | ||
+ | app: api | ||
+ | spec: | ||
+ | containers: | ||
+ | - image: andreidamian/lab3cc:first | ||
+ | name: apicontainer | ||
+ | env: | ||
+ | - name: PGUSER | ||
+ | value: "admin" | ||
+ | - name: PGPASSWORD | ||
+ | value: "admin" | ||
+ | - name: PGDATABASE | ||
+ | value: "books" | ||
+ | - name: PGHOST | ||
+ | value: "postgres" | ||
+ | - name: PGPORT | ||
+ | value: "5432" | ||
</code> | </code> | ||
- | In cazul de mai sus, //reteaua-mea-care-de-fapt-exista// este doar o "redenumire" a unei retele deja existente. | + | Dupa ce și acest pod este up and running, trebuie să îl expunem printr-un serviciu de tip NodePort pentru a putea comunica cu el de pe mașina noastră. Facem asta prin următoarea comandă (se poate face și printr-un fișier YAML): ''kubectl expose pod apipod --port 80 --type=NodePort'' |
- | === Secrets === | + | Apoi verificați portul asignat de noul serviciu (31598): |
+ | {{ :cc:laboratoare:image5.png |}} | ||
- | **Secrets** descrie //secretele// utilizate in cadrul configuratiei. | + | Folosiți Postman pentru a accesa API-ul deployed: |
- | <note tip>Observati ca **secrets**, ca si proprietate top level, este scrisa pe acelasi nivel de identatie ca **services**. Nu trebuie confundata cu proprietatea copil **secrets** din interiorul configuratiei serviciilor</note> | + | {{ :cc:laboratoare:image2.png?600 |}} |
- | Secretele retin informatii sensibile intr-o maniera securizata, criptata, in cadrul Swarm, despre care vom vorbi la laboratorul urmator. In compose nu sunt securizate, insa au fost introduse pentru a usura tranzitia catre Swarm. | + | Avem următoarea eroare: |
+ | {{ :cc:laboratoare:image1.png?500 |}} | ||
- | In cadrul compose, secretele pot proveni doar din fisiere externe. Fisierele externe sunt mentionate pentru fiecare secret in parte. | + | Avem această eroare pentru că baza de date nu a fost configurată. Pentru a configura baza de date avem mai multe variante, cea mai simpla (dar și cea mai ineficientă) este următoarea: |
+ | * obtineți un shell pe podul de Postgres: ''kubectl exec -it db-pod bash'' | ||
+ | * ne conectam la baza de date books cu username și parola definite în ConfigMap-ul pentru acest pod (admin/admin): ''psql -h localhost -U admin --password books'' | ||
+ | {{ :cc:laboratoare:image4.png?500 |}} | ||
+ | * rulați următorul script pentru a inițializa baza de date: | ||
+ | <code sql> | ||
+ | CREATE TABLE IF NOT EXISTS books ( | ||
+ | id uuid PRIMARY KEY NOT NULL , | ||
+ | title varchar NOT NULL, | ||
+ | author varchar NOT NULL | ||
+ | ); | ||
+ | </code> | ||
- | ==== Comenzi Docker Compose ==== | + | Incercați din nou comanda GET din Postman. Inserați ceva în baza de date: |
+ | {{ :cc:laboratoare:image3.png?500 |}} | ||
- | Comenzile pentru interactiunea cu docker-compose seamana, ca si sintaxa, cu cele clasice de docker. Mai jos va oferim cele mai utilizate comenzi. | + | Stergeți pod-ul de Postgres și incercați să faceți GET. Ar trebui să primiți următoarea eroare: |
+ | {{ :cc:laboratoare:image6.png?500 |}} | ||
- | <code bash> | + | Reporniți pod-ul de baza de date și executați un GET: |
- | # Porneste containerele | + | {{ :cc:laboratoare:image7.png?500 |}} |
- | docker-compose start | + | |
- | # Opreste containerele | + | |
- | docker-compose stop | + | |
- | # Pune in stare de pauza conainerele unui serviciu. Diferenta fata de stop e ca la procesul din container se trimite SIGPAUSE | + | |
- | docker-compose pause | + | |
- | # Scoate din stare de pauza containerele | + | |
- | docker-compose unpause | + | |
- | # Listeaza containerele active | + | |
- | docker-compose ps | + | |
- | # Face build, recreaza, porneste si ataseaza containere la un serviciu | + | |
- | docker-compose up | + | |
- | # Serviciile ruleaza in background detasat de terminalul care le-a initializat | + | |
- | docker-compose up -d | + | |
- | # Creaza imaginile inainte de pornire unde a fost declarata calea pentru un Dockerfile pentru un container | + | |
- | docker-compose up --build | + | |
- | # Se foloseste fisierul de docker-compose specificat in loc de cel implicit | + | |
- | docker-compose -f my-docker-compose.yml up | + | |
- | # Opreste containerele si le sterge pe acestea impreuna cu retelele, volumele si imagine create la up. | + | |
- | docker-compose down | + | |
- | # Sterge toate containerele oprite (se poate specifica la sfarsit si numele containerului care trebuie sters) | + | |
- | docker-compose rm | + | |
- | # Cu -s se opresc toate containerele si cu -v se sterg si volumele anonime atasate | + | |
- | docker-compose rm -s -v | + | |
- | </code> | + |