This shows you the differences between two versions of the page.
cc:laboratoare:02 [2020/10/16 11:41] radu.ciobanu [Laboratorul 02.] |
cc:laboratoare:02 [2022/10/15 20:16] (current) florin.mihalache |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Laboratorul 02 - Elemente adiționale în Docker ===== | + | ====== Laboratorul 02 - Introducere în Kubernetes ====== |
- | ==== Publicarea unei imagini într-un registru ==== | + | ==== Despre Kubernetes ==== |
- | În [[cc:laboratoare:01|laboratorul 1]], am creat o imagine de Docker pe care am rulat-o local într-un container. Pentru a putea rula imaginea creată în orice alt sistem, este necesar să o publicăm, deci să o urcăm într-un registru pentru a putea să facem deploy de containere cu imaginea noastră în producție. Un registru este o colecție de repository-uri, iar un repository este o colecție de imagini (similar cu GitHub, cu diferența că, într-un registru Docker, codul este deja construit și se rețin modificările făcute în straturile imaginilor de Docker, nu în cod). Există numeroase registre pentru imagini Docker (Docker Hub, Gitlab Registry, etc.), iar la laborator vom folosi registrul public Docker, pentru că este gratuit și pre-configurat. | + | Kubernetes (prescurtat K8s) este cel mai popular orchestrator, folosit la scara largă și oferit ca și CaaS (Container as a Service) de toți vendorii de infrastructură (Amazon, Google, Microsoft). Este considerat standard în materie de deployment al serviciilor orchestrate. |
- | Pentru exemplificare, vom porni de la aplicația prezentată în laboratorul 1, care afișează o poză aleatoare într-o pagină Web și pe care o puteți găsi în {{:cc:laboratoare:lab1.zip|această arhivă}}. Primul pas în publicarea unei imagini este crearea unui cont la [[https://hub.docker.com]]. Mai departe, logarea în registru de pe mașina locală se realizează prin următoarea comandă: | + | Varianta originală este dezvoltată de Google, este open source și face parte din Cloud Native Foundation (CNF). Fiecare vendor adaugă pachete în plus, pentru a îl adapta la ecosistemul proprietar (de exemplu, Azure adaugă comenzi și straturi în plus peste varianta de baza). |
- | <code bash> | + | Este mai complicat decât Docker Swarm, dar în multe situații nu veți fi nevoiți să rulați propriul vostru cluster de Kubernetes, ci doar să rulați aplicații peste un cluster de Kubernetes gata oferit (de exemplu, Azure Kubernetes Service (AKS)). |
- | $ docker login | + | |
- | Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one. | + | Avantajul major asupra Docker Swarm îl oferă utilizarea sa în industrie, cât și faptul că este mai flexibil și oferă mai multe funcționalități, însă nu out of the box. Faptul că este customizabil implică, bineînțeles, și un factor de dificultate mai mare. Kubernetes este mai greu de învățat decât Swarm, dar are și mai multe capabilități. |
- | Username: | + | |
- | Password: | + | |
- | Login Succeeded | + | |
- | </code> | + | |
- | Putem specifica numele de utilizator și parola direct în comandă, iar varianta generică a acesteia este (unde serverul implicit, dacă alegem să omitem acel parametru, este Docker Hub): | + | ==== Instalare ==== |
- | <code bash> | + | Kubernetes se poate seta în multe moduri: |
- | $ docker login [–u <UTILIZATOR> –p <PAROLĂ>] [SERVER] | + | |
- | </code> | + | |
- | Înainte de a publica imaginea în registru, ea trebuie tag-uită după formatul **//username/repository:tag//**. Tag-ul este opțional, dar este util pentru că denotă versiunea unei imagini Docker. Se folosește următoarea comandă pentru tag-uirea unei imagini (în exemplul de mai jos, unde vrem să tag-uim imaginea pe care am creat-o în laboratorul anterior, utilizatorul se numește **//raduioanciobanu//**, repository-ul este **//cloudcomputing//**, iar tag-ul este **//example//**): | + | === Bootstrap la propriul cluster === |
- | <code bash> | + | Este metoda cea mai dificilă, în care se leagă mai multe mașini în rețea folosind [[https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/|Kubeadm]]. Se folosește pentru a crea un cluster de la 0 și necesită un nivel de cunoștințe mediu-avansat. |
- | $ docker tag testapp raduioanciobanu/cloudcomputing:example | + | |
- | </code> | + | |
- | <code bash> | + | === Folosirea unui cluster deja setat === |
- | $ docker images | + | |
- | REPOSITORY TAG IMAGE ID CREATED SIZE | + | |
- | testapp latest 74254b15e6ba About a minute ago 62.9MB | + | |
- | raduioanciobanu/cloudcomputing example 74254b15e6ba About a minute ago 62.9MB | + | |
- | alpine edge f96c4363411f 4 weeks ago 5.58MB | + | |
- | </code> | + | |
- | Odată tag-uită imaginea, ea poate fi publicată în registru: | + | Asa cum am precizat în introducere, sunt puține cazurile cand veți trebui să setați un cluster de la 0, deoarece vendorii principali de infrastructură au versiunile lor de Kubernetes și sistemele de CaaS deja pregătite. |
- | <code bash> | + | === Instalare locală === |
- | $ docker push raduioanciobanu/cloudcomputing:example | + | |
- | </code> | + | |
- | Din acest moment, imaginea va fi vizibilă pe [[https://hub.docker.com]], de unde poate fi descărcată și rulată pe orice mașină, server sau sistem Cloud: | + | Cea mai buna metodă de a învăța este să vă setați local un cluster cu un singur nod de Kubernetes. Acest lucru se poate face în mai multe moduri: |
+ | * Pentru utilizatorii Windows PRO care au și Docker instalat, Kubernetes vine preinstalat | ||
+ | * Pentru utilizatorii Windows Non-Pro se folosește [[https://minikube.sigs.k8s.io/docs/start/|Minikube]] | ||
+ | * Pentru utilizatorii Linux, se folosește [[https://microk8s.io/|MicroK8s]] | ||
- | <code bash> | + | ==== Arhitectura Kubernetes ==== |
- | $ docker run -p 8888:5000 raduioanciobanu/cloudcomputing:example | + | |
- | Unable to find image 'raduioanciobanu/cloudcomputing:example' locally | + | {{:cc:laboratoare:kubernetesarhitecture.png?800|}} |
- | example: Pulling from raduioanciobanu/cloudcomputing | + | |
- | cc5efb633992: Pull complete | + | |
- | cd0af7ebab8a: Pull complete | + | |
- | 41c55a3da379: Pull complete | + | |
- | a779b27637f8: Pull complete | + | |
- | dfaeccf28d0c: Pull complete | + | |
- | 805843c75452: Pull complete | + | |
- | Digest: sha256:25af18fb4ffa9bf439e90bd4baee9adf0ab1e2999a44aeaa216ebf0454201ce8 | + | |
- | Status: Downloaded newer image for raduioanciobanu/cloudcomputing:example | + | |
- | * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) | + | |
- | [...] | + | |
- | </code> | + | |
- | Alternativ, în loc să rulăm comanda de publicare a unei imagini de fiecare dată când modificăm ceva la codul sursă, putem să configurăm [[https://docs.docker.com/docker-hub/builds/|build-uri automate]] din contul de Docker Hub. Pașii necesari sunt descriși în continuare. | + | Precum Docker Swarm, Kubernetes este un sistem format din mai multe [[https://kubernetes.io/docs/concepts/overview/components/#master-components|componente]]: |
- | În primul rând, este necesară existența unui repository Docker Hub și a unui repository pe GitHub (Docker Hub funcționează și cu BitBucket, dar în acest exemplu ne vom concentra pe GitHub). Toate fișierele necesare creării unei imagini Docker (adică Dockerfile-ul și toate fișierele sursă și de configurare) trebuie să fie prezente în repository-ul GitHub. Mai departe, de pe pagina repository-ului de Docker Hub, se selectează tab-ul Builds și apoi opțiunea „Configure Automated Builds”, așa cum se poate observa în imaginea de mai jos. | + | - **Kubectl** - CLI pentru configurarea clusterului și managementului aplicațiilor. Asemănător cu comanda //docker// |
+ | - **Node** - nodul fizic din cadrul unui cluster | ||
+ | - **Kubelet** - agentul (daemon) de Kubernetes care rulează pe fiecare nod | ||
+ | - **Control Plane** - colecția de noduri care fac management la cluster. Include API Server, Scheduler, Controller Manager, CoreDNS, Etcd. Asemănător cu nodurile "master" din Docker Swarm. | ||
- | {{:cc:laboratoare:dockerhub.png?direct&600|}} | + | {{:cc:laboratoare:controlplane.png?800|}} |
- | În continuare, va fi necesară completarea unor informații despre repository-ul GitHub și opțiuni de testare automată înainte de build, după care trebuie specificate regulile de build. O regulă de build conține informații despre: tipul build-ului (bazat pe un branch sau pe un tag Git), sursa (numele branch-ului sau tag-ului de pe care se face build-ul), tag-ul care va fi asignat noii imagini Docker construite, numele și adresa fișierului Dockerfile în repository-ul GitHub, calea către sursele ce vor fi compilate, opțiuni de auto-build (dacă se va face build automat la fiecare push pe branch-ul sau cu tag-ul specificat), opțiuni de build caching (dacă se vor cache-ui fișiere la build în cazul unor repository-uri de dimensiuni mari). În exemplul de mai jos, atunci când are loc un push pe branch-ul **//master//**, se va crea automat o imagine Docker cu tag-ul **//latest//** folosindu-se fișierul Dockerfile aflat în rădăcina repository-ului de GitHub. | + | ==== Componentele unei aplicații Kubernetes ==== |
- | {{:cc:laboratoare:dockerhub2.png?direct&500|}} | + | Kubernetes folosește o ierarhie logică de componente pentru aplicații: |
- | În continuare, pe pagina de Builds de pe Docker Hub vor exista opțiuni pentru pornirea unui nou build, precum și informații despre build-urile precedente și statusurile lor. | + | * **Pod** - unitatea fundamentală de lucru. Un pod conține întotdeauna containere. |
+ | * **Controllere** - creează, actualizează și menține starea de rulare a pod-urilor. Echivalentul unui serviciu din Docker Swarm. | ||
+ | <note tip>Exemple de controllere: **Deployment**, ReplicaSet, StatefulSet, DaemonSet, Job, CronJob</note> | ||
+ | * **Service** - endpoint de conectare în rețea. Se atașeaza unui pod. Echivalentul configurației de rețea din Docker Swarm. | ||
+ | <note tip>Tipurile de services sunt: NodePort, ClusterIP și LoadBalancer.</note> | ||
+ | * **Storage** - obiecte care se ocupă de persistarea datelor. PersistentVolume (PV) și PersistentVolumeClaim (PVC). Asemanător cu Docker mounts. | ||
+ | <note tip>Datorită PV și PVC, în Kubernetes se poate realiza persistarea datelor chiar și între mai multe noduri. În cadrul Docker Swarm eram limitați la utilizarea unui NFS extern.</note> | ||
- | ==== Networking ==== | + | * **Namespace** - grup de obiecte într-un cluster. Asemanător cu stack din Docker Swarm |
- | Subsistemul de networking Docker este de tip pluggable și folosește drivere. Mai multe astfel de drivere există implicit, ele oferind funcționalitate de bază pentru componenta de rețea. Driverul de rețea implicit este **bridge**, și presupune crearea unui bridge software care permite containerelor conectate la aceeași rețea de acest tip să comunice între ele, oferind totodată izolare față de containerele care nu sunt conectate la această rețea bridge. Driverul de bridge Docker instalează automat reguli pe mașina gazdă astfel încât containerele de pe rețele bridge diferite nu pot comunica direct unele cu altele. Rețelele de tip bridge se aplică doar containerelor care rulează pe aceeași mașină Docker. | + | * **Configurations** - obiecte de configurație. Exemple de obiecte de configurație: Secrets, ConfigMaps |
- | Pentru comunicație între containere care rulează pe mașini Docker diferite, se poate gestiona rutarea la nivel de sistem de operare, sau se poate folosi o rețea de tip **overlay**. Așa cum se va detalia în [[cc:laboratoare:03|laboratorul 3]], rețelele de tip overlay conectează mai multe mașini Docker și permit serviciilor dintr-un swarm să comunice între ele. Rețelele overlay se mai pot folosi și pentru a facilita comunicația între un serviciu swarm și un container de sine stătător, sau între două containere care rulează pe mașini Docker diferite. | + | MicroK8s reprezintă o versiune de Kubernetes folosită pe scară mică, pentru testarea aplicațiilor în medii offline (pe local, pe mașini virtuale). Pentru laboratoarele de Cloud Computing, acesta poate fi folosit (nu este obligatoriu, puteți să alegeți ce doriți). Instrucțiuni legate de setup-ul MicroK8s le aveți [[https://microk8s.io/docs/getting-started|aici]]. |
- | Alte drivere de rețea Docker mai sunt **host** (pentru containere de sine stătătoare, eliminând izolarea de rețea dintre container și gazda Docker, folosindu-se astfel infrastructura de rețea a gazdei direct), **macvlan** (permite asignarea de adrese MAC unui container, făcându-l să apară ca un dispozitiv fizic pe rețea), sau **none**. | + | ==== Crearea și rularea unui cluster Kubernetes ==== |
- | Containerele din aceeași rețea pot comunica fără să expună porturi, prin intermediul **named DNS**. Acest lucru înseamnă că putem să accesam un container nu prin IP, ci prin numele său. Pentru comunicarea cu lumea exterioară (gazda, containere din afara rețelei, etc.) trebuie expuse porturi. | + | Pentru pornirea unui cluster de Kubernetes pe local se poate folosi următoarea comandă: ''minikube start''. |
- | Pentru a demonstra modul în care funcționează rețelele de tip bridge în Docker, întâi vom porni două containere ce vor rula Alpine. În mod implicit, orice container Docker nou-creat se va afla într-o rețea numită „bridge”, așa că, pentru a demonstra faptul că două containere care nu sunt în aceeași rețea nu pot comunica, va trebui întâi să le scoate din acea rețea. | + | O altă modalitate de a crea un cluster Kubernetes este folosind ''kind''. Instrucțiunile de setup pentru acest utilitar le aveți [[https://kind.sigs.k8s.io/ | aici]]. ''kind'' rulează Kubernetes în Docker, simulând câte un nod din cluster cu câte un container. |
- | <code bash> | + | Pentru crearea unui cluster folosind ''kind'' ne putem folosi de un fișier de configurare: |
- | $ docker container run --name c1 -d -it alpine | + | <code yaml> |
- | + | # Configurare de cluster cu trei noduri (dintre care doi workeri) | |
- | f5a8653a325e8092151614d5a6a80b04b9410ea8b8a5fcfc4028f1ad33239ad9 | + | kind: Cluster # precizăm ce dorim să creăm: cluster, pod, etc. |
+ | apiVersion: kind.x-k8s.io/v1alpha4 | ||
+ | nodes: | ||
+ | - role: control-plane | ||
+ | - role: worker | ||
+ | - role: worker | ||
</code> | </code> | ||
- | <code bash> | + | Pentru a crea un cluster folosind kind și un fișier de configurare, folosim următoarea comandă: ''kind create cluster --config kind-config.yaml'', unde ''kind-config.yaml'' reprezintă numele fișierului de configurare a cluster-ului. |
- | $ docker container run --name c2 -d -it alpine | + | |
- | b063ad1ef7bd0ae82a7385582415e78938f7df531cef9eefc33e065af09cf92c | + | Dacă dorim să aflăm informații despre cluster-ul curent folosim următoarea comandă: ''kubectl cluster-info [dump]'' |
- | </code> | + | |
- | <code bash> | + | Pentru a afișa date despre componentele din cluster, folosim comanda ''kubectl get all''. |
- | $ docker network disconnect bridge c1 | + | |
- | </code> | + | |
- | <code bash> | + | Dacă dorim să aflăm informații despre nodurile din cluster folosim următoarea comandă: ''kubectl get nodes''. Pentru a vedea detalii despre nodurile dintr-un cluster folosim comanda ''kubectl describe nodes <node>''. |
- | $ docker network disconnect bridge c2 | + | |
- | </code> | + | |
- | În acest moment, containerele **c1** și **c2** nu fac parte din nicio rețea. Mai departe, vom încerca să dăm **//ping//** dintr-un container în altul. | + | Pentru ștergerea cluster-ului în care ne aflăm folosim comanda ''kind delete cluster''. |
- | <code bash> | + | ==== Crearea și rularea unui pod Kubernetes ==== |
- | $ docker exec -it c1 ash | + | |
- | / # ifconfig | + | Putem crea și rula pod-uri în două maniere: imperativă (prin comenzi cu parametri) și declarativă (folosind fișiere de configurare). |
- | lo Link encap:Local Loopback | + | |
- | inet addr:127.0.0.1 Mask:255.0.0.0 | + | |
- | UP LOOPBACK RUNNING MTU:65536 Metric:1 | + | |
- | RX packets:0 errors:0 dropped:0 overruns:0 frame:0 | + | |
- | TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 | + | |
- | collisions:0 txqueuelen:1000 | + | |
- | RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) | + | |
- | + | ||
- | / # ping c2 | + | |
- | ping: bad address 'c2' | + | |
- | + | ||
- | / # exit | + | |
- | </code> | + | |
- | + | ||
- | Se poate observa mai sus că **c1** nu are decât o adresă IP loopback și că nu poate accesa **c2**. De asemenea, ar fi interesant de menționat faptul că ne-am atașat la container folosind comanda **//docker exec//** rulând un shell (**//ash//** este shell-ul de pe Alpine). | + | |
- | + | ||
- | Pentru a crea o rețea de tip bridge în Docker, putem folosi următoarea comandă: | + | |
+ | Dacă dorim să rulăm un pod în mod declarativ, folosim următoarea comandă: ''kubectl run <pod-name> --image <image name>''. Exemple practice de folosire: | ||
<code bash> | <code bash> | ||
- | $ docker network create -d bridge c1-c2-bridge | + | kubectl run my-nginx --image=nginx |
- | + | kubectl run alpine --image=alpine | |
- | 8644b8accd2a14d10c9911c36635ca6b161449b3aa527db878a727ec1bf980d0 | + | |
</code> | </code> | ||
- | Mai departe, putem vizualiza rețele existente astfel: | + | Dacă dorim ca un pod să ruleze interactiv (mai sus aveți exemple de pod-uri care rulează în background), folosim comanda ''kubectl run -i --tty --rm <pod-name> --image=<image name>''. Exemplu de folosire: ''kubectl run -i --tty --rm alpine --image=alpine'' |
- | <code bash> | + | Un pod poate rula în cadrul unui namespace. Dacă dorim acest lucru, creăm mai întâi un namespace folosind comanda ''kubectl create namespace <namespace-name>''. Pentru a rula un pod în cadrul unui namespace: ''kubectl run alpine --image=alpine -n <namespace-name>'' |
- | $ docker network ls | + | |
- | NETWORK ID NAME DRIVER SCOPE | + | Dacă dorim să afișăm toate pod-urile folosim comanda ''kubectl get pods'' și dacă dorim să listăm toate namespace-urile folosim ''kubectl get namespaces''. De asemenea, având în vedere că un pod poate rula în cadrul unui namespace, putem să afișăm toate pod-urile din cadrul unui namespace: ''kubectl get pods -n <namespace>'' |
- | ecd72738aa59 bridge bridge local | + | |
- | 8644b8accd2a c1-c2-bridge bridge local | + | |
- | 615363cafefa host host local | + | |
- | 1e3b8e49b20d none null local | + | |
- | </code> | + | |
- | Putem adăuga un container într-o rețea fie atunci când pornim containerul, fie atunci când el deja a fost pornit. Pentru cazul de mai sus, unde **c1** și **c2** erau deja pornite, le putem adăuga în rețea astfel: | + | Pentru a obține mai multe informații cu ajutorul comenzii get, putem să folosim următoarea sintaxă: ''kubectl get pods -o wide'' |
+ | Pentru a rula o comanda in interiorul unui pod, folosim subcomanda exec: | ||
<code bash> | <code bash> | ||
- | $ docker network connect c1-c2-bridge c1 | + | kubectl exec -it <podname> <command> |
+ | kubectl exec -it mypod ls # (mypod trebuie sa existe deja in cluster) | ||
</code> | </code> | ||
- | <code bash> | + | <note> |
- | $ docker network connect c1-c2-bridge c2 | + | Task: creați un namespace cu numele ''my-first-namespace'', rulați două pods (unul cu imaginea alpine, altul cu imaginea nginx) în cadrul namespace-ului creat, afișati namespace-urile și pod-urile din cadrul acelui namespace. Rulați comanda ls în ambele pod-uri. |
- | </code> | + | </note> |
- | Dacă **c1** și **c2** nu ar fi fost deja pornite, le-am fi putut porni deja atașate la rețeaua **c1-c2-bridge** astfel: | + | După cum s-a putut observa mai sus, pentru crearea unui cluster am folosit un fișier de configurare de tip YAML (aici am folosit metoda declarativă). Orice fișier YAML are 4 [[https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/|componente]] importante: |
- | <code bash> | + | * **apiVersion** - versiunea de comenzi folosită (asemănător cu //version// din Docker Compose / Docker Swarm) |
- | $ docker container run --name c2 -d -it --network=c1-c2-bridge alpine | + | <note important>apiVersion diferă în funcție de kind (exemplu: în cazul deployment-ului este apps/v1, în cazul pod-ului este v1)</note> |
+ | * **kind** - tipul de obiect (e.g.: Pod, Deployment, etc...) | ||
+ | * **metadata** - informatii aditionale atasate unui obiect (e.g.: name, labels) | ||
+ | * **spec** - continutul obiectului (asemanator cu ce se scrie in directiva //service// din docker compose/docker swarm) | ||
- | 67dde5da9b793de63903ac85ff46574da77f0031df9b49acf44d58062687729c | + | Pentru a face deploy la unul (sau mai multe) obiecte dintr-un fișier YAML folosim următoarea comandă: ''kubectl apply -f myfile.yaml'' |
- | </code> | + | <note tip>Obiectul va avea tipul definit în YAML (de exemplu: cluster, pod, deployment, etc.)</note> |
- | + | <note tip>Se pot folosi și comenzile ''kubectl create'', ''kubectl update'', ''kubectl delete'', dar este indicat sa folositi direct ''kubectl apply'', care combină ''create'', ''replace'' și ''delete''. Aceasta reprezintă cel mai avansat model de aplicare a unei configurații declarative.</note> | |
- | <code bash> | + | <note tip>''kubectl apply'' se poate da și pe un folder care conține fișiere YAML sau pe un URL care point-ează către un fișier YAML</note> |
- | $ docker container run --name c1 -d -it --network=c1-c2-bridge alpine | + | |
- | 4de3e000700f81d31e0458dbd034abe90dfce6b1b992d23d760a44f748c0de0d | + | Putem crea pod-uri și folosind metoda declarativă, mai precis prin fișiere de configurare. Creăm un fișier de configurare, cu numele ''nginxpod.yaml'': |
+ | <code yaml> | ||
+ | apiVersion: v1 | ||
+ | kind: Pod | ||
+ | metadata: | ||
+ | name: nginx01 # numele pod-ului | ||
+ | spec: | ||
+ | containers: | ||
+ | - image: nginx | ||
+ | name: nginx | ||
</code> | </code> | ||
- | Putem vedea containerele dintr-o rețea astfel: | + | Pentru a crea acest pod, folosim comanda apply și specificăm fișierul: ''kubectl apply -f nginxpod.yaml''. Se obține un pod cu numele ''nginx01''. |
- | <code bash> | + | <note> |
- | $ docker network inspect c1-c2-bridge | + | Task: Creați un pod în mod declarativ folosind acest [[https://gitlab.com/mobylab-cc/laborator-4/-/blob/main/testapp-pod.yaml|fișier de configurare]]. |
- | + | </note> | |
- | [...] | + | |
- | "Containers": { | + | |
- | "b063ad1ef7bd0ae82a7385582415e78938f7df531cef9eefc33e065af09cf92c": { | + | |
- | "Name": "c2", | + | |
- | "EndpointID": "a76463662d110804205e9211537e541eb0de2646fa90e8760d3419a6dc7d32c7", | + | |
- | "MacAddress": "02:42:ac:12:00:03", | + | |
- | "IPv4Address": "172.18.0.3/16", | + | |
- | "IPv6Address": "" | + | |
- | }, | + | |
- | "f5a8653a325e8092151614d5a6a80b04b9410ea8b8a5fcfc4028f1ad33239ad9": { | + | |
- | "Name": "c1", | + | |
- | "EndpointID": "95d9061b47f73f9b4cc7a82111924804bdc73d0b496549dec834216ee58c64ed", | + | |
- | "MacAddress": "02:42:ac:12:00:02", | + | |
- | "IPv4Address": "172.18.0.2/16", | + | |
- | "IPv6Address": "" | + | |
- | } | + | |
- | } | + | |
- | [...] | + | |
- | </code> | + | |
- | + | ||
- | În acest moment, cele două containere fac parte din aceeași rețea și pot comunica: | + | |
+ | De asemenea, în lucrul cu pods putem face următoarele lucruri: | ||
<code bash> | <code bash> | ||
- | $ docker exec -it c1 ash | + | kubectl port-forward <nume-pod> 8888:5000 # realizeaza mapare de porturi |
- | + | kubectl logs <nume-pod> # afiseaza loguri | |
- | / # ping -c2 c2 | + | kubectl exec <nume-pod> -- ls # executa o comanda intr-un pod |
- | PING c2 (172.18.0.3): 56 data bytes | + | kubectl cp file.txt <nume-pod>:/file.txt # copiaza un fisier intr-un pod |
- | 64 bytes from 172.18.0.3: seq=0 ttl=64 time=6.258 ms | + | kubectl delete pods/<nume-pod> # sterge un pod |
- | 64 bytes from 172.18.0.3: seq=1 ttl=64 time=0.109 ms | + | |
- | + | ||
- | --- c2 ping statistics --- | + | |
- | 2 packets transmitted, 2 packets received, 0% packet loss | + | |
- | round-trip min/avg/max = 0.109/3.183/6.258 ms | + | |
- | + | ||
- | / # exit | + | |
</code> | </code> | ||
- | <code bash> | + | <note> |
- | $ docker exec -it c2 ash | + | Task: aplicați comenzile menționate mai sus pe pod-ul creat în task-ul anterior. |
+ | </note> | ||
- | / # ping -c2 c1 | + | ==== Generarea de fișiere YAML ==== |
- | PING c1 (172.18.0.2): 56 data bytes | + | Fișierele YAML pot fi scrise de la 0 sau pot fi generate prin rularea //uscată// a pod-urilor. |
- | 64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.111 ms | + | |
- | 64 bytes from 172.18.0.2: seq=1 ttl=64 time=0.268 ms | + | |
- | --- c1 ping statistics --- | + | <code yaml> |
- | 2 packets transmitted, 2 packets received, 0% packet loss | + | #Exemplu oficial de fisier YAML |
- | round-trip min/avg/max = 0.111/0.189/0.268 ms | + | apiVersion: v1 |
- | + | kind: Pod | |
- | / # exit | + | metadata: |
+ | name: nginx | ||
+ | labels: | ||
+ | run: nginx | ||
+ | whatever: dude | ||
+ | spec: | ||
+ | containers: | ||
+ | - name: nginx | ||
+ | image: nginx:1.14.2 | ||
+ | ports: | ||
+ | - containerPort: 80 | ||
</code> | </code> | ||
- | ==== Volume și bind mounts ==== | ||
- | |||
- | În Docker, datele dintr-un container nu sunt persistate în exterior. Pentru a ilustra acest lucru, putem rula un container simplu de Alpine, în care creăm un fișier apoi ieșim. | ||
- | |||
- | <code bash> | ||
- | $ docker container run --name c1 -ti alpine sh | ||
- | Unable to find image 'alpine:latest' locally | + | Pentru a genera fișiere YAML plecând de la o comandă imperativă putem folosi flag-urile ''--dry-run=client -o yaml''. Exemplu: ''kubectl run nginx - -image=nginx - -dry-run=client -o yaml'' |
- | latest: Pulling from library/alpine | + | |
- | 88286f41530e: Pull complete | + | |
- | Digest: sha256:f006ecbb824d87947d0b51ab8488634bf69fe4094959d935c0c103f4820a417d | + | |
- | Status: Downloaded newer image for alpine:latest | + | |
- | / # mkdir /test && echo hello > /test/hello.txt | + | ==== Labels și Selectors ==== |
- | / # exit | + | În fișierul YAML generat de comanda anterioară, putem observa că în câmpul ''metadata'', pe lângă atributul ''name'', avem și atributul ''labels'': |
+ | <code yaml> | ||
+ | metadata: | ||
+ | creationTimestamp: null | ||
+ | labels: | ||
+ | run: nginx | ||
+ | name: nginx | ||
</code> | </code> | ||
- | <code bash> | + | Aceste labels sunt perechi de tipul cheie-valoare care sunt folosite pentru identificarea obiectelor din Kubernetes. În exemplul de mai sus, avem perechea ''run=nginx''. Această pereche poate fi folosită de către un label selector pentru a referi acest obiect (mai multe detalii în continuare). |
- | $ docker container ls -a | + | |
- | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES | + | === Label Selectors === |
- | 97492cd1349b alpine "sh" 15 minutes ago Exited (0) 15 minutes ago c1 | + | Spre deosebire de nume, label-urile nu asigură unicitate. În general, ne așteptăm ca mai multe obiecte să aibă aceleași label-uri. |
- | </code> | + | |
- | Odată ce un container a fost rulat, chiar dacă execuția sa s-a oprit, layer-ele sale pot fi accesate până când containerul este șters cu comanda **//docker container rm//** (sau **//docker system prune//**). În mod implicit, Docker folosește [[https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt|OverlayFS]] sau [[https://www.thegeekstuff.com/2013/05/linux-aufs/|AUFS]] (ambele sisteme de fișiere de tip union) ca driver de storage pentru gestiunea imaginilor. Putem verifica acest lucru folosind **//docker info//**: | + | Asa cum am spus anterior, cu ajutorul unor Label Selectors putem identifica numite seturi de obiecte în Kubernetes. Un exemplu relevant este expunerea unui pod printr-un serviciu. |
- | <code bash> | + | Un exemplu comun de utilizare a labels și labels selectors prin intermediul fișierelor de configurare YAML este următorul: |
- | $ docker info | grep -i storage | + | ''kubectl run nginx --image=nginx --port=8080 --dry-run=client -o yaml >vtest.yaml'' |
- | Storage Driver: overlay2 | + | Adăugați podului următorul label: |
+ | <code yaml> | ||
+ | app: myapp | ||
</code> | </code> | ||
- | Pentru fiecare layer dintr-un container Docker, se vor stoca în AUFS/OverlayFS informații despre cum arăta inițial și despre ce fișiere s-au modificat (au fost adăugate, șterse sau schimbate). Aceste informații se găsesc în **///var/lib/docker/aufs/diff//** (pentru AUFS) sau **///var/lib/docker/overlay2//** (pentru OverlayFS), unde există directoare pentru fiecare layer al fiecărui container care a fost rulat pe sistem fără să fi fost șters. Putem astfel să vedem din exteriorul containerului, după ce acesta a terminat de rulat, fișierul care a fost creat în interiorul containerului: | + | Creați pod-ul: |
<code bash> | <code bash> | ||
- | $ cd /var/lib/docker/overlay2/ | + | kubectl apply -f test.yaml. |
+ | kubectl describe pod nginx # analizate label-urile podului. | ||
</code> | </code> | ||
- | <code bash> | + | Pentru a expune acest pod trebuie creat un serviciu de tipul ClusterIP care să selecteze podul nostru prin intermediul label selectors. Un fișier de configurare YAML pentru acest serviciu este următorul: |
- | $ ls -latr | + | <code yaml> |
- | + | apiVersion: v1 | |
- | [...] | + | kind: Service |
- | drwx------ 4 root root 4096 Oct 21 07:12 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc-init | + | metadata: |
- | drwx------ 4 root root 4096 Oct 21 07:12 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc | + | creationTimestamp: null |
+ | name: nginx | ||
+ | spec: | ||
+ | ports: | ||
+ | - port: 80 | ||
+ | protocol: TCP | ||
+ | targetPort: 80 | ||
+ | selector: | ||
+ | app: myapp #select the pod/pods | ||
+ | status: | ||
+ | loadBalancer: {} | ||
</code> | </code> | ||
- | <code bash> | + | Observați că nu am definit tipul de serviciu, astfel Kubernetes a creat by default un serviciu de tipul ClusterIP. Verificați faptul că podul nginx este expus. |
- | $ ls 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc/diff/ | + | |
- | root test | + | ==== ReplicaSets ==== |
- | </code> | + | Un ReplicaSet are rolul de a menține un număr stabil de replici ale unui pod. Acest obiect este definit prin anumite câmpuri, ca de exemplu un label selector care specifică modul în care pot fi controlate pod-urile, un număr de replici care indică numarul de pod-uri pe care le vrem up and running și un template al pod-urile pe care le orchestrează. |
- | <code bash> | + | Exemplu de ReplicaSet: |
- | $ cat 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc/diff/test/hello.txt | + | <code yaml> |
- | + | apiVersion: apps/v1 | |
- | hello | + | kind: ReplicaSet |
+ | metadata: | ||
+ | name: nume | ||
+ | spec: | ||
+ | replicas: 4 # numărul de replici ale pod-ului | ||
+ | selector: | ||
+ | matchLabels: | ||
+ | app: containerlabel | ||
+ | template: | ||
+ | metadata: | ||
+ | name: pod-template | ||
+ | labels: | ||
+ | app: containerlabel | ||
+ | spec: | ||
+ | containers: | ||
+ | - name: container | ||
+ | image: nginx | ||
</code> | </code> | ||
- | Totuși, aceste date nu sunt persistente, ci sunt șterse împreuna cu layer-ul. Astfel, dacă se șterge containerul, datele vor fi pierdute: | + | Pentru detalii legate de ReplicaSets putem folosi următoarele comenzi: |
- | + | <code yaml> | |
- | <code bash> | + | kubectl apply -f testapp-rs.yaml # creează un ReplicaSet dintr-un fisier |
- | $ docker container rm 97492cd1349b | + | kubectl get replicasets # afișează lista de ReplicaSet-uri |
+ | kubectl describe rs <nume-replicaset> # afișează detalii despre un ReplicaSet | ||
+ | kubectl delete rs <nume-replicaset> # sterge un ReplicaSet | ||
</code> | </code> | ||
- | <code bash> | + | <note> |
- | $ ls 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc/ | + | Task: creați un ReplicaSet pe baza [[https://gitlab.com/mobylab-cc/laborator-4/-/blob/main/testapp-rs.yaml|acestui fișier]]. Aplicați comenzile de mai sus. |
+ | </note> | ||
- | ls: 5b3f2aeff7a90abd5c1a2eb50e5bbf9bde38983bda84728ab3788a12ea2399dc: No such file or directory | + | Un ReplicaSet poate fi scalat prin două moduri: |
- | </code> | + | - deschidem fișierul de configurare, modificăm numărul de replici și aplicăm comanda de apply asupra fișierului de configurare |
+ | - folosind comanda de scalare: ''kubectl scale replicasets <nume-replicaset> --replicas=4'' | ||
- | Pentru persistența datelor dintr-un container, în Docker se folosesc mecanisme de persistență numite **volume**, care sunt o mapare între fișierele din cadrul unui container și fișiere de pe sistemul gazdă sau NFS (Network File Storage). Volumele Docker au câteva proprietăți și beneficii: | + | <note> |
+ | Task: scalați ReplicaSet-ul creat anterior la 4 noduri (folosind oricare din metode), apoi ștergeti ReplicaSet-ul. | ||
+ | </note> | ||
- | * sunt ușor de salvat și migrat | + | ==== Deployments ==== |
- | * pot fi controlate și configurate cu comenzi CLI sau cu API-ul de Docker | + | Un deployment ne dă opțiunea declarativă de a updata pod-uri și ReplicaSets. Într-un deployment descriem starea dorită, apoi un Deployment Controller are grijă ca clusterul să ajungă în starea descrisă. Putem folosi deployment-uri pentru a crea noi ReplicaSets sau chiar pentru a șterge un deployment existent și a adopta toate resursele sale. |
- | * funcționează pe containere Linux și Windows | + | |
- | * pot fi partajate între containere | + | |
- | * prin driverele de volume, se pot stoca date persistente pe gazde remote sau pe provideri de cloud, se pot cripta datele, etc. | + | |
- | * conținutul unui volum nou poate fi pre-populat de un container | + | |
- | * utilizarea unui volum nu crește dimensiunea unui container care îl folosește, pentru că un volum există în afara ciclului de viață al containerului. | + | |
- | Volumele se mai numesc și „named volumes” și sunt gestionate de Docker. Există mai multe metode pentru a defini și utiliza un volum atunci când se rulează un singur container de Linux. Dacă se creează o imagine custom, atunci volumul se poate defini în fișierul Dockerfile, prin comanda **//VOLUME//**. Dacă se rulează, de exemplu, un container bazat pe o imagine existentă (cum ar fi Alpine în exemplul din [[cc:laboratoare:01|laboratorul 1]]), atunci se poate defini un volum la runtime. În exemplul de mai jos, rulăm o imagine de Alpine în background care face **//ping//** într-un fișier localizat într-un volum **///test//**, pe care îl creăm folosind flag-ul **//-v//**: | + | Exemplu de deployment (varianta declarativă): |
- | + | <code yaml> | |
- | <code bash> | + | apiVersion: apps/v1 |
- | $ docker container run --name c2 -d -v /test alpine sh -c 'ping 8.8.8.8 > /test/ping.txt' | + | kind: Deployment |
+ | metadata: | ||
+ | name: nginx-deployment | ||
+ | labels: | ||
+ | app: nginx | ||
+ | spec: | ||
+ | replicas: 3 | ||
+ | selector: | ||
+ | matchLabels: | ||
+ | app: nginx | ||
+ | template: | ||
+ | metadata: | ||
+ | labels: | ||
+ | app: nginx | ||
+ | spec: | ||
+ | containers: | ||
+ | - name: nginx | ||
+ | image: nginx:1.14.2 | ||
+ | ports: | ||
+ | - containerPort: 80 | ||
</code> | </code> | ||
- | <code bash> | + | <note> |
- | $ docker container ls | + | Task: |
+ | * Creați deployment-ul definit în fișierul de mai sus, știm deja cum să folosim comanda apply. | ||
+ | * Verificați câte noduri există: ''kubectl get pods''. | ||
+ | * Verificați câte ReplicaSets există: ''kubectl get rs # am folosit un shortcut aici'' | ||
+ | * Verificați că deployment-ul este up and running: ''kubectl get deploy'' | ||
+ | </note> | ||
- | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES | + | ==== ConfigMaps și Secrets ==== |
- | 59d0785188a6 alpine "sh -c 'ping 8.8.8..." About a minute ago Up About a minute c2 | + | |
- | </code> | + | |
- | În timp ce containerul rulează, putem să îl inspectăm și observăm că este legat de o componentă de tip **//Volume//** cu destinația **///test//**. Astfel, putem afla unde este localizat volumul. Dacă ne uităm în acel director, vom vedea fișierul în care se face **//ping//** din container: | + | Un ConfigMap este un obiect folosit pentru a stoca într-un format de tipul cheie-valoare date care **nu** sunt sensitive. Un pod poate consuma un ConfigMap ca o variabilă de mediu, ca un argument în linie de comandă sau ca un fișier de configurare într-un volum. |
+ | Un astfel de obiect oferă opțiunea de a decupla configurația specifica unui mediu de imaginile de container și de codul aplicației, ceea ce sporește portabilitatea aplicațiilor. | ||
- | <code bash> | + | Un exemplu simplu este separarea mediilor. Pentru development, o să folosim calculatorul local, iar pentru producție o să folosim un provider de cloud. Configurăm codul astfel încât acesta se conectează la o bază de date folosind o variabilă de mediu, de ex ''DATABASE_HOST''. Pe mediul local o să setăm variabila de mediu la localhost (presupunând că avem un server de bază de date pe localhost), iar în cloud o să setăm variabila la un serviciu de Kubernetes prin care este expusă o bază de date (o să învățăm despre servicii în următorul laborator). |
- | $ docker container inspect -f "{{ json .Mounts }}" c2 | python -m json.tool | + | |
- | [ | + | Un obiect de tipul ConfigMap nu este folosit pentru a stoca cantități mari de date. Pentru o dimensiune mai mare de 1MB se vor folosi volume (despre care învățăm în laboratorul următor). |
- | { | + | |
- | "Destination": "/test", | + | |
- | "Driver": "local", | + | |
- | "Mode": "", | + | |
- | "Name": "2afac5683222a3435549131a931a4c0628b775ecd3d79cb3fd597b3501418288", | + | |
- | "Propagation": "", | + | |
- | "RW": true, | + | |
- | "Source": "/var/lib/docker/volumes/2afac5683222a3435549131a931a4c0628b775ecd3d79cb3fd597b3501418288/_data", | + | |
- | "Type": "volume" | + | |
- | } | + | |
- | ] | + | |
- | </code> | + | |
- | <code bash> | + | Fișier pentru configurarea unui ConfigMap: |
- | $ ls /var/lib/docker/volumes/2afac5683222a3435549131a931a4c0628b775ecd3d79cb3fd597b3501418288/_data | + | <code yaml> |
- | + | apiVersion: v1 | |
- | ping.txt | + | kind: ConfigMap |
+ | metadata: | ||
+ | name: game-demo | ||
+ | data: | ||
+ | # property-like keys; each key maps to a simple value | ||
+ | player_initial_lives: "3" | ||
</code> | </code> | ||
- | <code bash> | + | <note> |
- | $ cat ping.txt | + | Task: |
+ | * aplicați ConfigMap-ul de mai sus, folosind comanda apply: ''kubectl apply -f <configmapfile>'' | ||
+ | * verificați că a fost creat obiectul: ''kubectl get configmap'' | ||
+ | </note> | ||
- | PING 8.8.8.8 (8.8.8.8): 56 data bytes | + | Un ConfigMap poate fi folosit într-un pod în mai multe moduri, nu doar ca în exemplul de mai sus, într-o variabilă de mediu. Mai există și opțiunea de a folosi un ConfigMap într-un volum (cum înca nu știm să lucrăm cu volume, o să învățăm în laboratorul următor). |
- | 64 bytes from 8.8.8.8: seq=0 ttl=38 time=58.619 ms | + | |
- | 64 bytes from 8.8.8.8: seq=1 ttl=38 time=58.498 ms | + | |
- | </code> | + | |
- | Dacă oprim și ștergem containerul, volumul va exista în continuare: | + | Fișier pentru configurarea unui pod folosind ConfigMap (în acest exemplu folosind ConfigMap-ul definit anterior): |
- | + | <code yaml> | |
- | <code bash> | + | apiVersion: v1 |
- | $ docker container stop c2 | + | kind: Pod |
- | + | metadata: | |
- | c2 | + | name: configmap-demo-pod |
+ | spec: | ||
+ | containers: | ||
+ | - name: demo | ||
+ | image: alpine | ||
+ | command: ["sleep", "3600"] | ||
+ | env: | ||
+ | # Define the environment variable | ||
+ | - name: PLAYER_INITIAL_LIVES # Notice that the case is different here | ||
+ | # from the key name in the ConfigMap. | ||
+ | valueFrom: | ||
+ | configMapKeyRef: | ||
+ | name: game-demo # The ConfigMap this value comes from. | ||
+ | key: player_initial_lives # The key to fetch. | ||
</code> | </code> | ||
- | <code bash> | + | <note> |
- | $ docker container rm c2 | + | Task: Creați acest pod-ul definit mai sus. |
- | + | </note> | |
- | c2 | + | |
- | </code> | + | |
- | + | ||
- | <code bash> | + | |
- | $ ls /var/lib/docker/volumes/2afac5683222a3435549131a931a4c0628b775ecd3d79cb3fd597b3501418288/_data | + | |
- | + | ||
- | ping.txt | + | |
- | </code> | + | |
- | + | ||
- | O a treia metodă de a lucra cu volume în Docker este direct prin API-ul de volume, adică prin comenzi CLI de genul **//docker volume create//**, **//docker volume ls//**, etc. Dacă vrem să creăm volume pentru o stivă de servicii, acest lucru poate fi făcut în fișierul YAML folosit pentru Docker Compose, așa cum vom vedea în [[cc:laboratoare:03|laboratorul 3]]. | + | |
- | + | ||
- | Pe lângă volume, mai există și noțiunea de **bind mounts**. Acestea sunt similare cu volumele, dar nu sunt gestionate de Docker, ci se pot afla oriunde în sistemul de fișiere al gazdei pe care rulăm containerele, și pot fi modificate extern de orice proces non-Docker. Diferența principală dintre un bind mount si un volum este că bind mount-ul este o cale fizică de pe mașina gazdă, în timp ce volumul este o entitate Docker care utilizează, în spate, un bind mount abstractizat. În imaginea de mai jos (preluată din [[https://docs.docker.com/storage/bind-mounts/|documentația oficială]]), se poate observa în mod grafic diferența dintre volume și bind mounts. | + | |
- | + | ||
- | {{:cc:laboratoare:volumebind.png?direct&500|}} | + | |
- | + | ||
- | Atunci când pornim un container prin comanda **//docker container run//**, atât argumentul **//-v//** (sau **//--volume//**), cât și **//--mount//** permit utilizarea de bind mounts și volume. Totuși, în cadrul serviciilor (așa cum vom vedea în [[cc:laboratoare:03|laboratorul 3]]), nu putem folosi decât **//--mount//**. Acesta este totuși considerat oricum mai expresiv, pentru că necesită specificarea efectivă tipului de legătura (volum sau bind mount). Astfel, exemplul anterior unde atașam un volum **///test//** containerului pe care îl rulam ar arăta în felul următor: | + | |
- | + | ||
- | <code bash> | + | |
- | $ docker container run --name c2 -d --mount source=test,target=/test alpine sh -c 'ping 8.8.8.8 > /test/ping.txt' | + | |
- | </code> | + | |
- | + | ||
- | Pentru a verifica efectul acestei comenzi, putem rula comanda de inspectare: | + | |
- | + | ||
- | <code bash> | + | |
- | $ docker container inspect -f "{{ json .Mounts }}" c2 | python -m json.tool | + | |
- | + | ||
- | [ | + | |
- | { | + | |
- | "Destination": "/test", | + | |
- | "Driver": "local", | + | |
- | "Mode": "z", | + | |
- | "Name": "test", | + | |
- | "Propagation": "", | + | |
- | "RW": true, | + | |
- | "Source": "/var/lib/docker/volumes/test/_data", | + | |
- | "Type": "volume" | + | |
- | } | + | |
- | ] | + | |
- | </code> | + | |
- | + | ||
- | ==== Docker pe Windows și pe MacOS ==== | + | |
- | + | ||
- | Docker a fost creat nativ pentru Linux, utilizând componente de kernel specifice Linux, cum ar fi **//cgroups//** sau **//namespaces//**, folosite pentru a izola procese și alte componente ale sistemului de operare. Începând din 2016, el poate rula nativ și pe Windows, dar doar pentru versiunile Windows Server 2016 și Windows 10. De aceea, pentru a rula pe un sistem de operare desktop precum un Windows mai vechi sau MacOS, Docker necesită rularea virtualizată. | + | |
- | + | ||
- | Pe Windows, se folosește izolare Hyper-V pentru a rula un kernel Linux cu un set minimal de componente suficiente pentru a executa Docker. Pe MacOS, Docker for Mac este o aplicație nativă care conține un hypervizor bazat pe [[https://github.com/machyve/xhyve|xhyve]] și o distribuție minimală de Linux, peste care rulează Docker. Astfel, se oferă o experiență mult mai apropiată de utilizarea Docker pe Linux, sistemul de operare pentru care a fost creat. | + | |
- | + | ||
- | Ca un exemplu, pentru a avea acces în mașina virtuală de Docker pentru MacOS, se poate folosi **//screen//** (pentru a se termina sesiunea, se folosește combinația de taste **//Ctrl+a, k//**): | + | |
- | + | ||
- | <code bash> | + | |
- | $ screen /Users/<UID>/Library/Containers/com.docker.docker/Data/vms/0/tty | + | |
- | + | ||
- | linuxkit-025000000001:~# pwd | + | |
- | /root | + | |
- | </code> | + | |
- | + | ||
- | ==== Comenzi utile ==== | + | |
- | + | ||
- | TODO | + | |
- | ==== Exerciții ==== | + | S-a creat în interiorul pod-ului o variabilă de mediu numită ''PLAYER_INITIAL_LIVES'' care o să își ia valoarea din cheia ''player_initial_lives'' al ConfigMap-ului nostru. |
- | TODO | + | <note> |
+ | Pentru a verifica că totul a mers cum ne așteptam, afișați toate variabilele de mediu din interiorul podului proaspăt creat. | ||
+ | </note> |