This is an old revision of the document!
Făcând o analogie cu lumea sistemelor de operare, putem considera un Docker image ca fiind similar unui binar - o componenta statică ce poate fi lansată în execuție. Un Docker container este asemănător procesului, o componenta dinamică care este o instanță aflată în execuție a unei imagini Docker.
Pentru început, să listăm imaginile stocate local. În lista desfășurată veți vedea imaginile pe care le-am utilizat în secțiunea 1:
student@aldebaran:~$ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE alpine latest 965ea09ff2eb 6 weeks ago 5.55MB hello-world latest fce289e99eb9 11 months ago 1.84kB
Ca strategie, Docker își propune să ofere o bibliotecă extrem de largă de servicii gata configurate, împachetate sub formă de containere. În cazul în care ne dorim să căutăm o aplicație, un middleware, un runtime etc. care ne-ar fi utile pentru a rezolva o cerință sau a dezvolta o soluție complexă, putem căuta în upstream dacă există deja o astfel de imagine dezvoltată. Pentru a face acest lucru avem diferite metode, atât din CLI cât și din browser. Implicit, engine-ul docker va căuta în DockerHub registry, echivalentul GitHub pentru imagini Docker. În terminologia Docker, un registry este un repository de imagini Docker: docker search <nume imagine cautata>
docker search alpine
docker search httpd
docker search tomcat
docker search --filter is-official=true golang
Online putem accesa: https://hub.docker.com Pentru informații despre imagine de Alpine: https://hub.docker.com/_/alpine Meniul tags este utilizat pentru diferențierea imaginilor pe versiuni/pe releases: https://hub.docker.com/_/alpine?tab=tags
Să creăm o imagine pornind de la imaginea oficială Alpine cu versiunea de release latest. În folder-ul home al utilizatorului curent creați un fișier numit Dockerfile
și scrieți următoarele 2 linii:
student@aldebaran:~$ cat Dockerfile FROM alpine:latest CMD ["echo", "Am facut si bune am facut si RL"]
Se poate observa ușor forma declarativă, auto-descriptivă, a instrucțiunilor Docker.
Urmând același flux ca în cazul construcției de software, vom compila codul Docker anterior scris, rezultatul fiind un obiect de tip Docker Image. Timpul de execuție poate varia și poate dura chiar și câteva minute.
student@aldebaran:~$ docker build -t rlrules/rlhello:v1.0 .
Înainte de a merge mai departe, să disecăm succint convenția de nume anterior folosită. Numele imaginii Docker are în general următorul format:
<DockerHub User (Repository) >/<Nume Aplicație (Nume Imagine)>:<tag (versiune, release)>
rlrules/rlhello:v1.0
Respectarea unui astfel de format ne permite ulterior să putem face operațiuni de push sau pull asemănător acțiunilor pe care le putem face în GitHub/git server pentru un repository în care putem versiona și stoca codul.
În continuare vom rula imaginea anterior creată:
student@aldebaran:~$ docker run rlrules/rlhello:v1.0 Am facut si bune am facut si RL
Utilizarea instucțiunii CMD, din Dockerfile, ne oferă, suplimentar, capabilitatea suprascrierii la runtime a comenzii specificate în declarație. Să testăm:
student@aldebaran:~$ docker run rlrules/rlhello:v1.0 "hello, world" docker: Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "exec: \"hello, world\": executable file not found in $PATH": unknown.
Eroarea apărută se datorează faptului că “hello, world” nu este o comandă viabilă în Linux și deci nu este interpretată de către shell-ul utilizat (ex: bash, sh etc.)
student@aldebaran:~$ docker run rlrules/rlhello:v1.0 cat /etc/issue Welcome to Alpine Linux 3.10 Kernel \r on an \m (\l) student@aldebaran:~$ docker run rlrules/rlhello:v1.0 echo "hello, world" hello, world
Cele două comenzi vor rula cu succes, prima afișând o informație de pe container în timp ce a doua afișează mesajul transmis comenzii echo
: hello, world
Să modificăm imaginea anterioară înlocuind instrucțiunea CMD cu instrucțiunea ENTRYPOINT.
student@aldebaran:~$ cat Dockerfile FROM alpine:latest ENTRYPOINT ["echo", "Am facut si bune am facut si RL"]
În etapa de build vom construi un nou release v2.0:
student@aldebaran:~$ docker build -t rlrules/rlhello:v2.0 .
Rularea release-ului v2.0 va avea același outcome ca în cazul versiunii anterioare :
student@aldebaran:~$ docker run rlrules/rlhello:v2.0
Rezultatul utilizării celor 2 instrucțiuni (CMD și ENTRYPOINT) este același! Care este totuși diferența între CMD și ENTRYPOINT?
Pentru a vedea acest lucru, să încercăm să rulăm imaginea nouă utilizând, de data aceasta, argumente:
student@aldebaran:~$ docker run rlrules/rlhello:v2.0 "hello, world" Am facut si bune am facut si RL hello, world student@aldebaran:~$ docker run rlrules/rlhello:v2.0 cat /etc/issue Am facut si bune am facut si RL cat /etc/issue student@aldebaran:~$ docker run rlrules/rlhello:v2.0 echo "hello, world" Am facut si bune am facut si RL echo hello, world
Vedem că în cazul ENTRYPOINT, indiferent de argumentul adăugat la finalul comenzii docker run, acesta va fi anexat la comanda setată în instrucțiunea din Dockerfile. Întrucât comanda configurată este echo <string>
, șirurile de caractere adăugate ulterior vor fi atașate stringului “Am făcut și bune, am făcut și RL”. Într-o descriere mai pragmatică, ENTRYPOINT imprimă rolul unui executabil clasic asupra imaginii docker.
Un mecanism frecvent utilizat este conjuncția dintre cele două instrucțiuni. Putem folosi ENTRYPOINT pentru a desemna comanda fixă, explicit atașată containerului, în timp ce instrucțiunea CMD oferă argumentul default, interschimbabil, al comenzii definite de ENTRYPOINT.
Vom modifica imaginea astfel încât să reflecte scenariul definit anterior:
student@aldebaran:~$ cat Dockerfile FROM alpine:latest ENTRYPOINT ["echo"] CMD ["Am facut si bune, am facut si RL"]
Comanda echo este fixă, imutabilă, în timp ce string-ul Am făcut și bune, am făcut și RL
devine valoare implicită în cazul în care niciun argument suplimentar nu este specificat în comanda docker run.
Compilăm și rulăm noua imagine:
student@aldebaran:~$ docker build -t rlrules/rlhello:v3.0 . student@aldebaran:~$ docker run rlrules/rlhello:v3.0 Am facut si bune, am facut si RL student@aldebaran:~$ docker run rlrules/rlhello:v3.0 "hello, world" hello, world student@aldebaran:~$ docker run rlrules/rlhello:v3.0 cat /etc/issue cat /etc/issue student@aldebaran:~$ docker run rlrules/rlhello:v3.0 echo "hello, world" echo hello, world
Lista instrucțiunilor Docker nu se rezumă doar la FROM sau CMD/ENTRYPOINT. Pentru configurații mai complexe există o colecție largă de comenzi ce pot fi declarate la nivelul unui Dockerfile. Referințe despre aceste opțiuni se pot găsi pe site-ul Docker (https://docs.docker.com/engine/reference/builder/)
În continuare vom crește complexitatea și vom construi un Dockerfile extins:
student@aldebaran:~$ cat Dockerfile FROM ubuntu:latest MAINTAINER rl-awesome-students-team RUN apt-get update && apt-get -y install cowsay ENV RL_EXEC_PATH "/usr/games/" ENV PATH=${PATH}:${RL_EXEC_PATH} ENTRYPOINT ["cowsay"] CMD ["Am facut si bune, am facut si RL"]
Vom compila și rula imaginea nou definită:
student@aldebaran:~$ docker build -t rlrules/rlhello:v4.0 .
student@aldebaran:~$ docker run rlrules/rlhello:v4.0
student@aldebaran:~$ docker run rlrules/rlhello:v4.0 "Hello, world"
În cadrul configurației curente, plecând de la imaginea de Ubuntu:latest, la build time am instalat pachetul cowsay, am setat variabile de mediu, am specificat maintainer-ul imaginii și am configurat un parametrul implicit, în cazul în care imaginea este rulată fără parametrii adiționali. Complexitatea poate crește, în funcție de acțiunile pe care trebuie să le facem pentru a prepara containerul cu toate dependințele de care are nevoie.
Un aspect important și interesant legat de imagini este reprezentat de construcția pe layere/straturi. Fiecare modificare adusă imaginii anterioare poate rezulta în construcția unui strat suplimentar. Dintr-o perspectivă simplificată, putem spune că fiecare instrucțiune din Dockerfile construiește o imagine nouă, intermediară, care se diferențiază de cea anterioară prin modificările aduse de instrucțiunea curentă. Plecând de la această premisă, un layer este exact acest delta dintre 2 imagini succesive. Dintr-o perspectivă mai complexă, nu orice instrucțiune rezultă într-un strat nou. Comenzi precum CMD, MAINTAINER, ENTRYPOINT nu aduc o modificare substanțială imaginii, ci alterează aspecte legate de metadatele atașate unei imagini.
Să validăm această informație pentru imaginea rlrules/rlhello:v4.0:
student@aldebaran:~$ docker image inspect rlrules/rlhello:v4.0
În output-ul comenzii anterioare, căutați secțiunea RootFS. Pentru simplitate puteți rula:
student@aldebaran:~$ docker image inspect --format "{{json .RootFS}}" rlrules/rlhello:v4.0 | json_pp
Să vedem care au fost acele instrucțiuni care au dus la construcția celor 5 straturi:
student@aldebaran:~$ docker image history rlrules/rlhello:v4.0
Toate instrucțiunile care au un SIZE > 0
sunt responsabile de crearea unui layer suplimentar în imagine:
FROM ubuntu:latest
)RUN apt-get update && apt-get install
ce creează noi fișiere în sistemul de fișiere
În cazul în care ne dorim să ștergem o imagine de pe sistem, mai întâi trebuie să obținem identificatorul unic al imaginii (IMAGE ID
):
student@aldebaran:~$ docker image ls
Să încercăm să ștergem imaginea rlrules/rlhello:v1.0:
student@aldebaran:~$ docker image rm <id imagine rlrules/rlhello:v1.0>
O soluție a problemei anterioare o reprezintă ștergerea containerului care utilizează imaginea vizată:
student@aldebaran:~$ docker container ps -a
student@aldebaran:~$ docker container rm <id container oprit care utilizeaza imaginea v1.0>
Acum putem efectua ștergerea imaginii:
student@aldebaran:~$ docker image rm <id imagine rlrules/rlhello:v1.0>
Voila!
Până în acest moment, ne-am familiarizat cu Docker Engine și cu obiectele cele mai importante din lumea Docker. În continuare, în cadrul laboratorului, vom începe să punem server-ul nostru de imagini în contextul de rulare cerut. Întrucât aplicația este slab cuplată, un prim pas care trebuie realizat este să stabilim comunicarea între serviciile componente. În acest context ne vom muta atenția asupra conceptelor de Networking din Docker.