Przygody z dockerem #2: Praca z kontenerami

Obrazek użytkownika jsobiecki
Przygody z dockerem #2: Praca z kontenerami

Wprowadzenie

W poprzedniej części tego mini-cyklu opisałem narzędzie oraz ideę, jaka za nim stoi. W tej części zaprezentuję podstawy pracy z obrazami i kontenerami, podstawowymi cegiełkami pracy z Dockerem. Pokażę jak uruchomić przykładową aplikację, oraz podstawowe operacje, na które pozwala nam nasz interfejs.

Tworzenie kontenerów

Na sam początek, pokażemy, jak uruchomić jakąś aplikację w kontenerze. Na nasz warsztat idzie stary dobry Drupal. Co zrobić by zainstalować  ten CMS na naszym komputerze, ale w izolowanym, dedykowanym środowisku dostarczonym przez Dockera?

Najpierw musimy znaleźć obraz który zawiera Drupala. Ok, ale co to jest? To tak naprawdę paczka zawierająca biblioteki systemu bazowego. Np. może te być system CentOS, Ubuntu czy inny Debian, z zainstalowanym serwerem WWW. Dla projektu Docker powstało bogate repozytorium z obrazami, w którym użytkowcy mogą znaleźć coś dla siebie. Można tam także publikować swoje obrazy, ale ten temat wykracza poza zakres tego wpisu i zostanie opisany w innym terminie.

Z tej bogatej biblioteki trzeba wybrać to co nas interesuje. Możemy szukać na dwa sposoby: przez interfejs WWW lub korzystając z interfejsu CLI. Te drugie oferuje polecenie docker search, które pozwala w szybki sposób znaleźć coś dla siebie.



$ docker search drupal

NAME  DESCRIPTION               STARS     OFFICIAL   AUTOMATED
drupal  Drupal is an open sourc  37         [OK]           
centurylink/drupal  Drupal do    32         [OK]     [OK]


Zwróćmy uwagę na kolumnę Official. Oznacza to że obraz jest utrzymywany przez zaufany zespół, i jest to oficjalna dystrybucja tej aplikacji. Można użyć innych obrazów, ale ich jakość może być... różna :) Wejdzmy na stronę projektu. Jak widać, obraz występuje w kilku wariantach (tagach). Mechanizm tagów używany jest przez twórców obrazu by przygotować obrazy w różnych odmianach. Np.spróbujmy sprawdzić najnowszą wersję z gałęzi 8.x:


$ docker pull drupal:8

0c2770de12b2: Already exists 
53a7ad4bdbbd: Already exists 
a1cf2f9b3404: Already exists 
e59f37741eaf: Already exists 
d22e71516cef: Already exists 
9e4df0ca609b: Download complete 
180e25acc056: Download complete 
18cb893c6be4: Download complete 
88fa3a887be9: Download complete 
c163c71a0264: Download complete 
f4b4197daf7a: Already exists 
f598334d420b: Already exists 



Po wykonaniu tego polecenia, narzędzie pobierze obraz kontenera. Jeżeli nie dokleilibyśmy nazwy taga do nazwy obrazu (:8), docker użyłby by domyślnej nazwy tagu (latest). Sprawdzmy listę naszych obrazów:



$ docker images

REPOSITORY TAG IMAGE ID       CREATED VIRTUAL SIZE
drupal     8   6a3953fc175d   11 days ago     565.2 MB


Warto pamiętać, że ze względu na to że używany jest w Dockerze system plików AUFS, obrazy składają się z warstw, które często są współdzielone międy różnymi obrazami. Np, jeżeli kiedyś będziemy chcieli ściągnąć najnowszą wersję obrazu, ściągnięte zostaną tylko te warstwy, których nie mamy lokalnie.

OK, mamy ściągniety obraz, ale jak uruchomić to wszysko? Do tego służy docker run. Polecenie te wykorzystuje wybrany obraz, dokłada do systemu plików dodatkową warstwę, która może być modyfikowana (warstwy wchodzące w skład obrazu nigdy nie są modyfikowane!) i nakazuje w ramach tak utworzonego nowego kontenera, wykonanie wybranego polecenia. By było łatwiej, twórcy obrazów często w konfiguracji ustalają domyślne polecenie (np. start serwera WWW), więc nie trzeba się o nie martwić. Np, zakładając że mamy obraz bazowy centos, możemy np. uruchomić w nim basha:


$ docker run -t -i centos  /bin/bash
[root@7fddc7c2da5a /]#

Ok, zatrzymajmy się na chwilę przy tym poleceniu. Parametr -t mówi, by przydzielić dockerowi konsolę (której bash wymaga). -i mówi, by docker działał w trybie interaktywnym. centos to nazwa obrazu a /bin/bash to nazwa polecenia.

Ok, wróćmy do naszego Drupala. Jak go odpalić na naszym środowisku?


$ docker run --name dzialajacy_drupal -d -p 8080:80 drupal:8

02179c49e5e61de0ca5ea0868762eef1c27e6bc9f94773ffd31ce60081f0facc

Tutaj użyliśmy kilku innych opcji --name to nazwanie kontenera, po tej nazwie możemy się do niego odwoływać do innych operacji. -d to żądanie uruchomienia kontenera w tle. Bardzo ważną opcją jest -p 8080:80 Ta opcja mówi, że port 80 dostępny w kontenerze ma być dostępny jako port 8080 na naszym hoście (czyli w naszym kontekście, na naszej stacji roboczej). Ciąg liter i cyfr, którą otrzymamy po wykonaniu tej komendy, jest unikalnym identyfikatorem nowo utworzonego kontenera. Można odwołać się do niego później korzystając właśnie z tego identyfikatora lub nazwy którą przekazaliśmy w parametrze.

Konterner działa, teraz pozostaje tylko odwiedzić w naszej przeglądarce adres http://localhost:8080 i gotowe, mamy działającego Drupala :)

Praca z istniejącymi kontenerami

W poprzednim kroku uruchomiliśmy kontener, w którym działa Drupal. Ponieważ działa w tle, co można zrobić by go zatrzymać / usunąć / inne?

Wyświetlanie kontenerów

Do tego służy polecenie docker ps Wyświetla ono wszystkie działające kontenery.


$ docker ps

CONTAINER ID IMAGE    COMMAND              CREATED    STATUS        PORTS                NAMES
02179c...    drupal:8 "apache2-foreground" 3 days ago Up 17 minutes 0.0.0.0:8080->80/tcp dzialajacy_drupal 

Kolejne kolumny oznaczają odpowiedni, identyfikator kontenera, nazwę obrazu z którego został on utworzony, wykonywaną komendę, czas utworzenia, status, udostępnione porty i nazwę pod jaką można się do niego odwoływać Wykorzystajmy ten identyfikator, by zatrzymać kontener. Użyjemy do tego docker stop.


$ docker stop dzialajacy_drupal

Po wykonaniu tej komendy, kontener jest zatrzymany. Próba odwiedzenia adresu http://localhost:8080 zakończy się niepowodzeniem. Ok, włączmy kontener ponownie.


$ docker start dzialajacy_drupal

Spróbujmy odwiedzić http://localhost:8080 ponownie. Tym razem powinniśmy otrzymać ekran instalacyjny Drupala.

Wyświetlanie statusu kontenerów

Ok, sprawdzmy, jakie procesy działają w kontenerze (docker top:


$ docker top dzialajacy_drupal

UID      PID   PPID  C STIME TTY TIME     CMD
root     10211 2153  0 10:51 ?   00:00:00 apache2 -DFOREGROUND
www-data 10220 10211 0 10:51 ?   00:00:00 apache2 -DFOREGROUND
www-data 10221 10211 0 10:51 ?   00:00:00 apache2 -DFOREGROUND


Jak widać, jest to tylko kilka procesów apache2. Tutaj objawia się zaleta wykorzystania Dockera - narzut uruchomienia kolejnych kontenerów to tylko kwestia uruchomienia kilku dodatkowych serwerów. W pełnej wirtualizacji, mamy na karku kilkadziesiąt dodatkowych programów!

Jeśli chcemy sprawdzić, jakie porty udostępnia docker, sprawdzimy to za pomocą docker port


$ docker port dzialajacy_drupal
80/tcp -> 0.0.0.0:8080

Jeżeli chcemy podpatrzeć logi demonów, działających w kontenerze, sprawdzimy to za pomocą docker logs


$ docker logs dzialajacy_drupal 

AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.1. Set the 'ServerName' directive globally to suppress this message
AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.1. Set the 'ServerName' directive globally to suppress this message
PHP Warning:  Module 'PDO' already loaded in Unknown on line 0
[Tue Jul 14 08:43:21.632435 2015] [mpm_prefork:notice] [pid 1] AH00163: Apache/2.4.10 (Debian) PHP/5.6.10 configured -- resuming normal operations
[Tue Jul 14 08:43:21.632464 2015] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'
172.17.42.1 - - [14/Jul/2015:08:45:45 +0000] "GET / HTTP/1.1" 302 611 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:39.0) Gecko/20100101 Firefox/39.0"
172.17.42.1 - - [14/Jul/2015:08:45:45 +0000] "GET /core/install.php HTTP/1.1" 200 3750 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:39.0) Gecko/20100101 Firefox/39.0"
172.17.42.1 - - [14/Jul/2015:08:45:46 +0000] "GET /core/assets/vendor/domready/ready.min.js?v=1.0.8 HTTP/1.1" 200 690 "http://localhost:8080/core/install.php" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:39.0) Gecko/20100101 Firefox/39.0"

Zapisywanie zmian w kontenerze

Wspomniałem wcześniej, że obrazy nie są modyfikowane, modyfikowane są tylko kontenery tworzone przez komendę docker run. Oznacza to, że kiedy będziemy tworzyć kolejny kontener, nie będzie on zawierał zmian które zostały wykonane przez inne kontenery korzystające z tego samego obrazu. Co jednak zrobić, gdy chcemy zapamiętać zmiany w konterze i np. utworzyć nowy obraz bazując na tych zmianach? Najpierw, możemy podpatrzeć, jakie zmiany siedzą w konenerze. Służy do tego docker diff.


$ docker diff dzialajacy_drupal
C /run
C /run/apache2
A /run/apache2/apache2.pid
C /run/lock
C /run/lock/apache2
C /var
C /var/www
C /var/www/html
C /var/www/html/sites
C /var/www/html/sites/prod.test.ratioweb.pl
A /var/www/html/sites/prod.test.ratioweb.pl/files
A /var/www/html/sites/prod.test.ratioweb.pl/files/translations
A /var/www/html/sites/prod.test.ratioweb.pl/files/translations/drupal-8.0.0-beta12.pl.po

Jeżeli nasze zmiany są w porządku, możemy zatwierdzić te zmainy i zapisać je w formie nowego obrazu. Do tego służy polecenie commit.


$ docker commit dzialajacy_drupal drupal:8-installed

W ten sposób tworzymy nowy obraz, który możemy wykorzystać w przyszłości.

Sprzątanie kontenerów

Kiedy znudzimy się zabawą, i mamy zatrzymane kontenery - warto posprzątać po sobie:

Najpierw spróbujmy usunąć  obraz drupal:8


$ docker rmi drupal
Error response from daemon: Conflict, cannot delete 6a3953fc175d because the running container 02179c49e5e6 is using it, stop it and use -f to force
Error: failed to remove images: [drupal:8]

System nie pozwolił nam na usunięcie obrazu, ponieważ działają kontery który bazują na tym obrazie. Do usunięcia kontenera użyjemy komendy docker rm


$ docker rm dzialajacy_drupal 

Po tym kroku, mozemy usunąć bazowy obraz.

Komunikacja pomiędzy kontenerami

Warto zwrócić uwagę, że nasz kontener z Drupalem jest dosyć ubogi. Nie zawiera bazy danych, pozwala na instalację Drupala tylko korzystając z zagnieżdzonej bazy SQLlite. Co jeżeli chcielibyśmy dołożyć do kompletu np. bazę danych? Należy użyć mechanizmu linkowania kontenerów. Spróbujmy dodać obraz z najnowszą wersją serwera MariaDB:


$ docker pull mariadb:10
$ docker -e MYSQL_ROOT_PASSWORD=mysecretpassword -e MYSQL_DATABASE=drupal -d run  --name server_db mariadb

Ok, kilka słów wyjaśnienia. -e pozwala wstrzykiwać zmienne środowiskowe do kontenera. Zostaną one użyte przez obraz MariaDB tak, by ustawić hasło administratora oraz utworzyć instancję bazy danych z odpowiednią nazwą.



$ docker -d run --name drupal_server --link server_db:db drupal


--link jest tutaj kluczowe. Ta komenda mówi "server_db" będzie dostępny w tym kontenerze, a można się do niego odwoływać przez alias "db". Czyli jeżeli podamy w instalatorze Drupala nazwę db jako adres bazy danych, będzie to prawidłowy adres IP. Docker po prostu aktualizuje plik /etc/hosts. Pewną wadą rozwiązania jest fakt, że przy linkowaniu, musimy utworzyć kontener na nowo, nie jest możliwe zmiany konfiguracji działającego kontenera.

Współdzielenie katalogów między serwerem a kontenerami

Czasami istnieje potrzeba współdzielenia katalogu pomiędzy kontenerem a komputerem. Jeżeli rozwijacie własny projekt, można współdzielić kod projektu. W przypadku tradycyjnych rozwiązań, np. Virtualbox, spotkałem się z dużym narzutem wydajności na operacje IO, w przypadku Dockera na szczeście jest dużo lepiej.

Dla przykładu, jak współdzielić katalog z logami aplikacji, by mieć ręce na pulsie? Całkiem prosto:


$ docker -d run --name drupal_server -v /sciezka/na/serwerze:/sciezka/w/kontenerze

Polecenie -v mówi: /sciezka/na/serwerze ma być dostępna jako katalog "/sciezka/w/kontanerze" w kontenerze.

Źródła

  • Repozytorium obrazów Dockera: https://registry.hub.docker.com/
  • Dokumentacja referencyjna CLI Dockera: http://docs.docker.com/reference/commandline/cli/