02. [20p] Docker Images

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.

În cadrul secțiunii curente vom parcurge etapele construcției unei imagini Docker scoțând în evidență câteva particularități de implementare.

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

Un aspect foarte important din output-ul anterior îl reprezintă coloana Official, atribut ce garantează faptul că imaginea vizată este întreținută și verificată de către o comunitate de încredere, validată de către Docker.

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

Convenție de nume extinsă

Convenție de nume extinsă

În sens mai extins, în cazul în care organizația în care lucrăm deține propriul repository de artefacte Docker (propriul Docker Registry), similar cu menținerea propriului Code Repository (ex: GitHub Enterprise, GitLab on premise, Atlassian Bitbucket etc.), convenția de nume este extinsă:

<adresă/hostname registry>:<port (oficial 5000)>/<user (repository)>/<Nume Aplicație (Nume Imagine)>:<tag (versiune, release)> mypersonalregistry:5000/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

Câte layere pot fi numărate? De ce?

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:

  • primele nivele sunt moștenite din construcția imaginii de bază Ubuntu cu tag-ul latest (FROM ubuntu:latest)
  • ultimul nivel este adăugat de noi prin instrucțiunea 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>

De ce returnează eroare? Ce putem face pentru a șterge imaginea?

O soluție a problemei anterioare o reprezintă ștergerea containerului care utilizează imaginea vizată:

student@aldebaran:~$ docker container ps -a

Care este containerul care utilizează imaginea vizată?

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.

rl/labs/11/contents/02.txt · Last modified: 2020/11/15 17:57 by jan.vaduva
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0