Przygody z dockerem #2: Praca z kontenerami
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 :) Wejdźmy 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 domyślnej nazwy tagu (latest). Sprawdźmy 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ędzy 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ągnięty obraz, ale jak uruchomić to wszystko? 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.
Kontener 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, sprawdźmy, 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/
z ambitnymi ludźmi i projektami