Back to top

Docker

Kontenery, kontenery - po horyzont. Nie tak dawno (kilka lat temu) cieszyłem się z możliwości i lekkości LXC, i szczerze mówiąc, w dyskusjach z kolegami Dockera raczej ignorowałem. Cóż, LXC jest całkiem fajnym narzędziem, czegóż można chcieć więcej?.. Otóż oczywiście można! I choć osobiście ciężko mi się jest zgodzić ze stwierdzeniem, że albo Docker, albo LXC (to, moim zdaniem, zależy w dużej mierze od rodzaju zadania), to jednak w pracy developera, Docker wydaje sprawdzać się zdecydowanie lepiej - LXC jest, cóż,"zbyt ciężkie". Warto tutaj zaznaczyć, że w przypadku Docker-a różnicę robi przede wszystkim „podejście”. Wszystko to z czym spotkałem się przed Dockerem (VMWare, ESXi, Xen, Hyper-V, VirtualPC, Qemu, Bochs, VirtualBox i oczywiście LXC) - wszystko to to jest jakiś rodzaj wirtualizacji (parawirtualizacji, bądź konteneryzacji) CAŁEGO systemu. Docker skupia się natomiast na aplikacji, bez dbałości o te części systemu, o które nie dba sama aplikacja. Mówiąc językiem Linuksowych guru: aplikacja uruchamiana jest z identyfikatorem procesu (PID) równym "1" (standardowo zarezerwowanym dla procesu "Init", jednak w kontenerze dockerowym nie ma ani procesu Init, ani żadnego jego odpowiednika) - czy to coś zmienia? Okazuje się, że wiele!

Koncepcja

Słowem wstępu muszę uprzedzić, że porównywanie projektów LXC i Docker będzie częste w tym artykule – wynika to po części z moich doświadczeń (znam LXC, pracowałem jako administrator przez jakiś czas i doskonale rozumiem podejście prezentowane przez LXC), po części z tego, że to po prostu świetny materiał do porównań – oba projekty wykorzystują w sumie tę samą technologię (nawet jeżeli Docker zrezygnował już z opierania się na LXC i od jakiegoś czasu, jako „execution environment”, wykorzystuje własną bibliotekę – libcontainer). Dla tych co nie znają LXC - przyjmijmy, że LXC to „standardowe” podejście – czyli kontener jako maszyna wirtualna, to powinno wystarczyć (w zawiłości techniczne wdawać się nie będziemy).

Powróćmy teraz do identyfikatora procesu (PID) – jak pisałem we wstępie, Docker uruchamia wskazaną aplikację z tym identyfikatorem równym „1”. Okazuje się, że to może niekiedy stwarzać problemy. Problemem może tkwić w samej aplikacji (przykład), jak również może on wynikać z tego jak proces z PID = 1 jest traktowany przez Linuksa (chodzi mi tutaj wyłącznie o jądro systemu GNU/Linux) – dokładniejszy opis tego problemu oraz proponowane rozwiązanie można znaleźć na stronie projektu dumb-init. Te problemy nie wyglądają jednak na jakieś sprawy krytyczne. Technicznie więc, mimo iż mogą występować jakieś tarcia, dużych trudności jednak chyba nie ma (a w przyszłości będzie pewnie tylko lepiej!). Koncepcyjnie natomiast, sprawa (wciąż myślę tutaj o procesie z PID = 1) jest trochę bardziej złożona. Choć nigdy nie rozbijałem procesu Init na czynniki pierwsze (zainteresowani znajdą pewnie mnóstwo informacji w Internecie), wierzę na słowo, że proces ten wykonuje wiele pożytecznych funkcji. Z ciekawości postanowiłem sprawdzić jak wygląda drzewko procesów w systemie Debian GNU/Linux, gdy przy starcie systemu pominiemy uruchomienie procesu Init, przechodząc bezpośrednio do powłoki. To prosty trick, który przydaje się jeżeli zapomnimy hasła root-a, bo w tak uruchomionym systemie mamy uprawnienia root-a bez konieczności podawania hasła – w końcu uruchomiliśmy powłokę systemu bezpośrednio! Standardowa procedura zakłada uruchomienie procesu Init, następnie uruchamiany (przez proces Init) jest proces getty ( w efekcie wypisywany jest „login prompt”), po wpisaniu nazwy użytkownika uruchamiany jest proces password (przyjmujący hasło i autoryzujący użytkownika) i wreszcie, po wpisaniu hasła i jego poprawnej weryfikacji (przez proces password) jest uruchamiany proces powłoki (np. /bin/bash) – długa droga! Przekazując odpowiednie parametry do jądra przy starcie systemu możemy pominąć całą tą sekwencję uruchamiając od razu proces powłoki - w tym przypadku: /bin/bash (powinno działać, jeżeli Linux (jądro systemu) nie został skompilowany z opcjami blokowania takiego uruchomienia - jak domyślny kernel w Debianie Jessie).

Jak wygląda drzewko procesów w tak uruchomionym systemie? Otóż proces „bash” uruchomiony jest oczywiście z PID = 1! (Choć na żywym systemie lista procesów wcale nie będzie ograniczona tylko do naszego procesu powłoki, bo będziemy mieli całkiem pokaźne drzewo procesów - w moim przypadku +/- 60 - wywodzących się z kthreadd, uruchomionym z PID = 2 – ale to „szczegóły techniczne”). Efekt – z jednej strony zastąpienie procesu Init procesem powłoki pozwoli nam pobuszować z uprawnieniami administratora, z drugiej - nasz system, przynajmniej na starcie, jest prawie zupełnie nieużywalny. W systemie uruchomionym standardowo od razu mamy około 180 działających procesów (oczywiście wliczając to ów +/- 60 podprocesów kthreadd) – i mimo, iż nie wydaje mi się aby wszystkie te procesy były naprawdę niezbędne, to jednak część z nich robi całkiem użyteczną robotę sprawiając, że nasz system działa tak jak tego oczekujemy (chodzi głównie o konfigurację sieci, montowanie zewnętrznych dysków, itp…).

Docker uruchamia w kontenerze system odpowiadający z grubsza przypadkowi pierwszemu – czyli przypadkowi w którym uruchomiamy systemu GNU/Linux z procesem „bash” (bądź wybraną aplikacją) w miejscu procesu Init. LXC reprezentuje podejście drugie.

Czas na pierwszą z Dockerowych zasad: jeden konterer - jedna aplikacja.

Reasumując: nie bawimy się w Init i wszystko co z nim związane (jeżeli potrzebujemy kontenerów z procesem Init, to wykorzystajmy LXC) - kontener Dockerowy z zasady jest podporządkowany konkretnej aplikacji, nawet jeżeli zawiera cały system plików systemu GNU/Linux. Ten "cały" system plików niech nas nie przeraża - dzięki zastosowaniu "warstw" (ang. layers) koszt przestrzeni dyskowej jest dość mocno zminimalizowany. LXC wymaga pełnego systemu plików per kontener, czyli na 10 kontenerów z systemem zajmującym 600 MB przestrzeni dyskowej potrzebujemy około 6 GB miejsca, Docker korzysta z mechanizmu warstw, pozwalającego "współużytkować" bazowy system plików przez wszystkie kontenery ograniczając koszt per kontener jedynie do zapisu różnicowego, co pozwala ów 10 kontenerów uruchomić przy wykorzystaniu mniej niż 1GB dysku – to naprawdę fajna cecha (do tej warstwowości jeszcze wrócę).

Oczywiście ów "jedna aplikacja" nie jest wymogiem, czy ograniczeniem, a jedynie zasadą - oznacza to, że jeżeli chcemy to możemy uruchomić więcej procesów wewnątrz dockerowego kontenera, możemy nawet uruchomić proces Init (choć to nie wydaje się sensowne – o czym jeszcze za chwilkę). Jedną z możliwości, w przypadku, gdy naprawdę potrzebujemy uruchomienia więcej niż jednej aplikacji w dockerowym kontenerze, jest wykorzystanie aplikacji supervisord.

I ostatnia z rzeczy powiązanych z „koncepcją” - zauważmy, że kontener LXC może zostać uruchomiony bez procesu zarządzającego (LXD) - polecenie "lxc start -n KONTENER" nie wymaga obecności serwisu LXD w systemie hosta - kontereny LXC są "samowystarczalne". Kontener dockerowy natomiast nie będzie mógł zostać uruchomiony bez obecności w systemie procesu dockerd (swego czasu LXD też wspierał kontenery dockerowe, ale nie wiem czy to ciągle działa). Na razie nie zagłębiałem się w powody techniczne takiego rozwiązania przyjmując, że jest to jedna z konsekwencji odejścia od procesu Init – choć to pewnie duże uproszczenie. Uruchamianie jednak procesu Init w dockerowym kontenerze może powodować, że część funkcji byłaby dublowana przez „asystujący” w tle dockerd, co z kolei może prowadzić do jakiś zgrzytów. Ale to tylko teoretyzowanie - sam nie próbowałem takich „sztuczek” i w sumie nie jestem pewien czy to byłby jedyny, i czy poważny, problem. Na razie trzymam się zasady: jeden kontener - jedna aplikacja (choć wykorzystuję również supervisord).

Budowanie

Znamy już koncepcje, czas poznać sposób budowania środowiska. W poprzednim artykule opisywałem jak wygląda budowanie środowiska w przypadku duetu Vagrant+VirtualBox - w przypadku Dockera podejście się zmienia, głównie ze względu na zasadę: „jeden kontener – jedna aplikacja”. Budując środowisko z wykorzystaniem Dockera możemy postawić na naprawdę dużą izolację procesów! Pozwalają nam na to możliwości techniczne rozwiązania, koszt budowy izolowanego środowiska (kontenera) jest dużo niższy, niż koszt budowy wirtualnej maszyny VirtualBoxa (ile VB moglibyśmy uruchomić równolegle w naszym lokalnym systemie?), ba, jest nawet niższy niż koszt budowy kontenera z wykorzystaniem LXC (np. ze względu na dużo większe zapotrzebowanie LXC na przestrzeń dyskową). Niestety, choć to chyba nieuniknione, zmieniają się również narzędzia, z których możemy korzystać. Ale zanim o tym dokładniej, to wspomnę krótko jeszcze o możliwości uruchomienia Dockera w VirtualBoxie (np, z wykorzystaniem boot2docker) - to nawet może mieć sens, np. przy lokalnym testowaniu dockerowego trybu Swarm.

Narzędzia. Ansible (i inne podobne narzędzia) mogą być przydatne zarówno w przypadku duetu Vagrant+VirtualBox, jak i w przypadku LXC (czy dowolnego innego systemu wirtualizacji/konteneryzacji, który umożliwia dostęp po ssh). Niestety w przypadku Dockera to nie zadziała – tutaj ssh to osobna bajka (ssh to w końcu osobny proces!). Musimy więc skupić się na rozwiązaniach dedykowanych, takich jak docker-compose.

Nasze środowisko deweloperskie to przeważnie zbiór kooperujących ze sobą procesów (baza danych, serwer http, jakiś cache, itd…) – jak tę całą menażerię skonteneryzować? Oczywiście każdy proces musimy umieścić w osobnym kontenerze. Liczba potrzebnych kontenerów może, w związku z tym, urosnąć do naprawdę sporej liczby. Nie będę w tym artykule opierał się na konkretnym przykładzie, ale niedługo postaram się podzielić doświadczeniami z przeniesienia jednego z moich prywatnych projektów z dwóch maszyn VirtualBoxowych na (jak na razie) pięć kontenerów dockerowych. W tej chwili chcę zwrócić tylko uwagę na trend – otóż, w przypadku Dockera, nie butujemy jednej maszyny będącej naszym środowiskiem deweloperskim - budujemy środowisko współpracujących ze sobą komponentów, nawet w przypadku prostych aplikacji (np. aplikacja wymagająca apache+php+mysql to będą już minimum dwa kontenery - jeden na apache+php, drugi na mysql - a nie jedna maszyna wirtualna). Z każdym kolejnym krokiem, dołożeniem kolejnej aplikacji, ta, początkowo drobna różnica będzie zamieniać się w ziejącą przepaść – ale to dobrze, bo to zbliża nas do kolejnego ostatnio modnego trendu w IT – mikroserwisów!

Wobec powyższego, można powiedzieć, że aplikacja w architekturze dockerowej to cały zespół współpracujących komponentów, stanowiących małą sieć komputerową na naszej lokalnej maszynie - bo przecież każdy kontener będzie wymagał indywidualnego adresu ip, ten indywidualny adres musi być przydzielany z jakiejś puli adresów, określających sieć, która musi mieć swoją bramę sieciową, serwery dns, itd... Przeważnie będziemy wszystkie kontenery umieszczać w jednej sieci, ale naprawdę nic nie stoi na przeszkodzie, aby stworzyć bardziej zróżnicowane środowisko sieciowe (jeżeli chcemy np. symulować pracę routerów BGP). Taka wyraźniejsza izolacja poszczególnych elementów niesie ze sobą kilka nowych wyzwań, przede wszystkim wymaga dokładniejszego określenia zakresu odpowiedzialności i zależności. Niekiedy może to przysparzać trochę problemów, jednak w ostatecznym rozrachunku, dostajemy w zamian wiele udogodnień - wymiana jakiegokolwiek elementu (na poziomie kontenera) jest wręcz banalną operacją!

Pisałem już, że w przypadku Dockera, sprawa sieci nie sprowadza się już do przypisania jednego adresu ip, jak w przypadku np. VirtualBox-a (oczywiście generalizuję). Tutaj musimy spojrzeć na to bardziej globalnie i ustalić konkretny zakres adresów ip z których będziemy korzystać. Sieć w Dockerze to więc temat istotny.

Aktualnie Docker wspiera dwa sieciowe podejścia:

  • legacy - predefiniowane sieci bridge, none i host - cały czas mogą być pomocne w testach i rozwiązaniach ad-hoc, poza tym jednak raczej powinniśmy ich unikać
  • user definied networks - sieci definiowane przez użytkownika - to powinno być nasze główne narzędzie!

Myślę, że do tematów sieciowych w Dockerze powrócę jeszcze w jakimś artykule, teraz polecam jedynie zaznajomić się z dokumentacją. W przypadku implementacji Dockera w systemie OS X, sprawa nabiera dodatkowego kolorytu ze względu na specyfikę tej implementacji i działanie (wykorzystywanego tam) Hyperkit-a. Powiem tu tylko, że mój sposób na "nieskrępowany" dostęp do kontenerów na OS X to wykorzystanie kontenera z OpenVPN-em (troszkę więcej o różnicach pomiędzy Dockerem na GNU/Linuksie a OS X napiszę jeszcze na zakończenie, w podsumowaniu).

Gdy już przygotujemy warstwę sieciową, czas na budowanie kontenerów! Ogólnie rzecz ujmując budowanie kontenera jest operacją dwuetapową:

  1. budowanie / pobranie obrazu (image)
  2. konfiguracja i uruchomienie kontenera

Bazę dla naszych kontenerów stanowią „obrazy". "Obrazy" możemy pobierać np. z serwisu dockerhub.com, lokalnego repozytorium, bądź budować we własnym zakresie. Czym jest "obraz" (image)? To po prostu system plików kontenera, przy czym pewne aspekty działania systemu są "nakładane" dopiero w momencie uruchamiania kontenera (np. konfiguracja sieciowa), dlatego nie powinniśmy raczej modyfikować plików związanych z tymi funkcjami w trakcie budowania naszego „obrazu”, powinniśmy się skupić jedynie na rzeczach ważnych z punku widzenia aplikacji, która w danym kontenerze będzie uruchomiona. Obraz budowany za pomocą pliku Dockerfile, jest zawsze budowany na bazie jakiegoś innego obrazu. Budując więc system plików dla naszej aplikacji zawsze wybieramy system bazowy, następnie, w pliku konfiguracyjnym obrazu (Dockerfile) określamy przekształcenia którym chcemy poddać dany obraz bazowy, aby otrzymać pożądany efekt. Te przekształcenia to np.:

  1. kopiowanie plików do systemu plików kontenera
  2. wykonywanie instalacji pakietów
  3. zmiany w plikach konfiguracyjnych

Mogłoby się wydawać, że to w zupełności wystarczy aby rozwiązać każdy problem, jednak prędzej czy później, zauważymy pewną... niedogodność. Otóż wszystkie te operacje odnoszą się tylko do systemu plików, nie możemy więc, np. w trakcie przygotowywania obrazu dla kontenera z serwerem MySQL, za pomocą powyższych mechanizmów zakładać kont użytkowników, czy odtworzyć z backupu bazy danych, chodzi mi o to, że polecenia:

 
RUN mysqladmin -u root -proot

czy

RUN mysql -u root -proot < create_my_db.sql

Nie będą działać bo... usługa MySQL nie jest jeszcze dostępna! Zostanie ona uruchomiona dopiero w momencie uruchomienia kontenera, to jednak będzie mogło nastąpić dopiero po zbudowaniu obrazu – heh, gonimy swój ogon? To może być trochę kłopotliwe, jednak oczywiście, nie jesteśmy w takich sytuacjach bezsilni - sposobów radzenia sobie jest kilka, koncentrują się jednak one na działaniach wykonywanych już w trakcie uruchamiania kontenera, a nie w trakcie budowania jego obrazu. Warto o tym pamiętać!

Każdy „obraz” budujemy na podstawie innego obrazu, a liczba obrazów pośrednich może być dowolna (przynajmniej teoretycznie). To znaczy, że nie każdy obraz musi być budowany z myślą o konkretnym kontenerze. Niektóre z obrazów możemy budować aby wyodrębnić, lub wyizolować, pewne operacje. Zależność pomiędzy dockerowymi obrazami przypomina trochę mechanizm dziedziczenia w programowaniu obiektowym - stety/niestety, nie jest możliwe dziedziczenie wielokrotne - tzn. dany kontener może mieć tylko jeden kontener bazowy.

Z obrazami związana jest jeszcze jedna ciekawa funkcjonalność wykorzystywana w Dockerze - warstwy (layers). Każdy obraz zbudowany jest z warstw, które są tworzone automatycznie w trakcie wykonywania poleceń z pliku Dockerfile.

Warstwy można „podglądać” wykorzystując polecenie:

$ docker history NAZWA_OBRAZU

Można je również scalać, redukując ich ilość w co bardziej złożonych obrazach. Warstwy to idea bardzo… ciekawa. Obsługa warstwowego systemu plików istnieje w Linuksie (jądrze systemu GNU/Linux) już od dawna, jednak nie była ona chyba wcześniej eksponowana w tak mocny sposób użytkownikowi (mi, w każdym razie, nie przychodzi teraz do głowy żaden projekt który korzystał by z tego mechanizmu). Idea jest prosta – każda wyższa warstwa zawiera tylko różnice (w obszarze systemu plików) w stosunku do warstwy niższej. Warstwy to „trzeci wymiar” konfiguracji (heh, jeszcze muszę przemyśleć to porównanie), organizując je odpowiednio możemy znacznie przyspieszyć budowanie obrazów. Obowiązuje tu jednak zasada, że jeżeli dokonamy zmian w jakiejś warstwie, to poza tą warstwą, „przebudowane” muszą zostać również wszystkie warstwy wyższe. Warstwy przypominają trochę mechanizm znany z programów graficznych, gdzie tło obrazu możemy mieć na najniższej warstwie, a w warstwach wyższych dokładamy szczegóły i ogólnie rzecz ujmując, tak to właśnie, mniej więcej, działa.

Ostatnim krokiem w tworzeniu obrazu jest wykonanie następującej komendy (zwróćcie uwagę na kropkę na końcu polecenia - wskazuje on że plik Dockerfile znajduje się w katalogu bieżącym, możemy również zastąpić kropkę ścieżką do katalogu zawierającego plik Dockerfile):

 
$ docker build -t NAZWA_OBRAZU .

Jeżeli wszystko poszło ok, to nasz obraz powinien być teraz widoczny po wpisaniu komendy:

$ docker images 

Na tej liście, poza obrazami stworzonymi przez nas lokalnie, widoczne są również obrazy pobrane ze zdalnego repozytorium (np. serwisu dockerhub.com), takie pobieranie wykonywane jest automatycznie przez nasz lokalny proces build w momencie gdy w pliku Dockerfile, w linii FROM, natrafi on na nazwę obrazu, który nie jest jeszcze dostępny lokalnie. Pobieranie obrazu ze zdalnego repozytorium może również zostać wykonane przez nas za pomocą komendy:

$ docker pull NAZWA_OBRAZU

Na przykład, aby pobrać oficjalny obraz serwera MySQL:

$ docker pull mysql

Usuwanie obrazów z listy "images" możliwe jest poprzez polecenie:

$ docker rmi NAZWA_OBRAZU

Kontenery

Uruchomienie kontenera wymaga, poza innymi parametrami, podania nazwy jednego z obrazów z naszej listy obrazów (choć istnieje możliwość wykonania automatycznej próby pobrania obrazu w razie jego braku na naszym lokalnym dysku). Zgodnie z dokumentacją systemu Docker zaleca się, aby kontenery były bezstanowe - czyli w wyniku ewentualnego usunięcia kontenera nie powinniśmy tracić żadnych danych / informacji, to zdecydowanie pozwala utrzymać porządek.

Zobaczmy jak wygląda uruchomienie przykładowego kontenera za pomocą instrukcji "run"

$ docker run --rm NAZWA_OBRAZU

Parametr "--rm" spowoduje usunięcie kontenera zaraz po zakończeniu jego pracy. Zanim zaczniemy się temu przyglądać dokładniej zobaczmy jak "przeglądać" kontenery, które istnieją / działają w naszym systemie.

$ docker ps

Pokaże nam listę wszystkich aktualnie uruchomionych kontenerów

 
$ docker ps -a

Pokaże nam listę wszystkich kontenerów istniejących w naszym systemie (czyli również tych których wykonywanie zostało aktualnie zakończone, ale które nie zostały usunięte). Jeżeli po uruchomieniu naszego kontenera sprawdzimy listę kontenerów ("ps") to powinien on być na niej widoczny. Zakończyć pracę kontenera możemy np. poleceniem:

$ docker stop NAZWA_KONTENERA

(NAZWA_KONTENERA znaleźć wśród wyników polecenia "ps" - domyślnie, jeżeli nie podamy tej nazwy jawnie w trakcie tworzenia kontenera, będzie to połączenie losowego przymiotnika z jakimś znanym nazwiskiem). Oczywiście, kontener uruchomiony z parametrem „—rm” nie będzie widoczny na liście "ps -a" po zakończeniu pracy - zostanie natychmiast usunięty.

Plecenie run łączy w sobie funkcje dwóch innych poleceń:

1. Utworzenia kontenera

 
$ docker create NAZWA_OBRAZU

2. Uruchomienia kontenera:

$ docker start NAZWA_KONTENERA

Polecenia "start" można używać również do ponownego uruchomienia wcześniej zatrzymanego kontenera, o ile kontener nie zostanie usunięty (co sprawia, że kontenery niekoniecznie muszą być bezstanowe ;)).

Jeżeli chcielibyśmy "ręcznie" usunąć kontener, możemy to zrobić za pomocą polecenia:

$ docker rm NAZWA_KONTENERA

(kontener musi zostać wcześniej zatrzymany!)

Wszystkie powyższe przykłady odnoszące się do zarządzania kontenerem zakładały wykorzystanie domyślnej sieci - czyli legacy "bridge", ta sieć zostanie wykorzystana zawsze, jeżeli nie sprecyzujemy inaczej.

Lista możliwych opcji/parametrów dla poleceń "create" oraz "run" jest dość długa - polecam przejrzenie stron podręcznika tych poleceń!

I znów słówko o warstwach (layers), tym razem w kontekście kontenerów - otóż, w momencie startu kontenera, Docker dokłada kolejną warstwę do warstw zdefiniowanych w uruchamianym obrazie - ta warstwa zawiera wszystkie modyfikacje systemu plików dokonane już w samym kontenerze i jest oczywiście niszczona wraz ze zniszczeniem kontenera. Zastosowanie takiego rozwiązania to chyba główny powód naciskania na bezstanowość kontenerów - to obraz powinien zawierać wszystkie dane, bo to obraz jest przechowywany w repozytorium - po danych "wygenerowanych" w samym kontenerze nie ma w repozytorium śladu. Czyli jeżeli nie będziemy dbać o bezstanowość naszych kontenerów, część ich konfiguracji będzie w plikach obrazów, a część w samych kontenerach – ten dualizm może być dość męczący na dłuższą metę. Oczywiście istnieje wiele typów danych tymczasowych, dla których "warstwa" kontenera to idealne miejsce, dlatego użycie opcji "--rm" nie zawsze jest idealnym rozwiązaniem. Istnieje jeszcze możliwość wykorzystania wolumenów do przechowywania danych mających „przetrwać” w przypadku zniszczenia kontenera (czyli dane serwerów DB, usług katalogowych, itp, mogą - powinny! - być zapisywane nie w obrębie systemu plików kontenera, a na wolumenie (zewnętrznym, w stosunku do samego kontenera), o którym informacje przekazywane są do kontenera w trakcie jego uruchamiania za pomocą parametru "-v").

I to chyba wszystko w temacie budowania i uruchamiana kontenerów, oczywiście to tylko informacje podstawowe, opcji i możliwości jest mnóstwo!

Podsumowanie

Z oczywistych względów niestety nie mogłem, w tym krótkim artykule, poruszyć wszystkich około dockerowych tematów - nie napisałem np. ani słowa o docker-compose, który znacznie ułatwia zarządzanie duża liczbą kontenerów i uwalnia nas od konieczności pamiętania i wpisywania długich poleceń budujących obrazy czy uruchamiających ich konkretne instancje (potrafi również zarządzać siecią i wolumenami). To bardzo istotne narzędzie, którego wykorzystanie nawet w małych projektach znacząco podnosi czytelność konfiguracji. Gorąco polecam zaznajomienie się z nim. Wykorzystuję docker-compose w swoich projektach i z pewnością napiszę o nim kilka słów w przyszłości!

Wolumeny - to kolejny pominięty temat (poza krótką wzmianką przy okazji opisywania procesu uruchamiania kontenera). W projektach lokalnych wystarcza jednak w tym zakresie wiedza podstawowa, sam specjalnie nie kombinowałem jeszcze z jakimiś bardziej zaawansowanymi, niż wykorzystanie lokalnego katalogu, możliwościami – temat, również z mojej strony, pozostaje więc jeszcze do zbadania.

Gdzieś w artykule wspomniałem o różnicach w implementacji dockerowego stacka w środowiskach GNU/Linux i w OS X (wersji dla Windows nie miałem okazji jeszcze testować). Ponieważ Docker wymaga do działania Linuksa (w znaczeniu jądra systemu GNU/Linux), dlatego implementacja w systemie OS X wykorzystuje pewien trick - Docker (wraz z Linuksem) uruchamiany jest w wirtualnym środowisku zarządzanym przed narzędzie Hyperkit - to stwarza pewne różnice w stosunku do GNU/Linuksowego odpowiednika. Dla mnie osobiście, różnicą najbardziej "widoczną" jest większa izolacja sieci dockerowej w środowisku OS X. W środowisku GNU/Linux dostęp do kontenerów możliwy jest bezpośrednio z systemu hosta przy wykorzystaniu adresu ip kontenera (jeżeli nasz kontener otrzymał ip 10.10.10.1, to polecenie "ping 10.10.10.1" z systemu hosta będzie działać) - w systemie OS X, Hyperkit izoluje sieć dockerową na tyle mocno, że bezpośrednia komunikacja host->kontener nie jest możliwa (choć nie jest to być może problem zbyt palący, gdyż taka bezpośrednia komunikacja przeważnie nie jest nam potrzebna - dostęp do usług hostowanych w kontenerach może być realizowany przez interfejs lokalny, co jest konfigurowane za pomocą opcji "-p" przy tworzeniu kontenera ). Chcąc móc na OS X pracować tak jak na GNU/Linuksie ja osobiście wykorzystuję OpenVPNa (kontener z serwerem uruchamiam wewnątrz dockerowej sieci) oraz Tunnelblick jako klienta w systemie lokalnym - to jednak może się wydawać trochę skomplikowane (w rzeczywistości to jest skomplikowane, ale prostszego sposobu jak dotąd nie znalazłem). Jako ciekawostkę zamieszczam ten link do artykułu, którego autor zwraca uwagę, iż taka izolacja to kwestia konfiguracji Hyperkit-a i podaje jak można tę konfigurację zmienić, to chyba jest jednak bardziej skomplikowane niż wykorzystanie OpenVPN-a ;)

Ciekawie z Dockerem jest również gdy chcemy uruchomić więcej niż jeden dockerowy host w ramach dockerwego "klastra". Mam okazję eksperymentować z taką konfiguracją w pracy - z pewnością będę się chciał podzielić doświadczeniami/spostrzeżeniami po wdrożeniu dockerowego trybu Swarm. Na razie, w aspekcie konfiguracji wielo-hostowej, eksperymentuję z dockerową implementacją protokołu VXLAN. Cel tego ćwiczenia to przygotowanie rozproszonego środowiska dockerowego połączonego protokołem VXLAN w jedną sieć (do tworzenia i zarządzania hostami wykorzystujemy inną ciekawą technologię: OpenStack). W przygotowywanym środowisku wszystkie kontenery, niezależnie od hosta na którym zostały uruchomione, będą pracować w jednej, wewnętrznej, sieci (co ma zapewnić protokół VXLAN) - to z pewnością będę chciał opisać dokładniej, ale pozostało jeszcze kilka rzeczy do sprawdzenia, aby być w pełni zadowolonym z efektu.

Kontenery, kontenery po horyzont! Na ostatniej jesieni linuksowej miałem okazję uczestniczyć w warsztatach, w których uczyliśmy się wykorzystywać funkcjonalność Linuksa (znów myślę tylko o jądrze systemu GNU/Limux) do tworzenia kontenerów. Bez żadnego "zewnętrznego" oprogramowania typu LXC czy Docker. Okazuje się, że się da! Że tak naprawdę wszystko jest zaszyte w kernelu (w Linuksie), nawet warstwowy system plików! To były naprawdę bardzo ciekawe warsztaty i mam nadzieję, że znajdę czas, aby odtworzyć przykłady takiego budowania kontenerów i podzielić się nimi na tym blogu, na razie jednak, ryzykując bycie trochę gołosłownym, powtórzę tylko - wszystko jest w Linuksie! Docker to idea realizowana za pomocą mechanizmów zatopionych w jądrze systemu GNU/Linux i choć dockerowi deweloperzy odwalili oczywiście kawał dobrej roboty, to, krótko mówiąc, niedługo pewnie będzie się roić od implementacji tej kontenerowej idei (już rozpędza się Rocket, którym ekipa CoreOS chce przeciwdziałać Enterprisowym zapędom Dockera).

Już na sam koniec chciałbym wspomnieć jeszcze jedną osobę, otóż, ku swemu zaskoczeniu, odkryłem kiedyś, że Ian Murdock zaangażowany był w projekt Docker. Niestety niezbyt długo, ledwie niespełna dwa miesiące, do czasu swojej samobójczej śmierci w grudniu 2015 roku. Wcześniej, w Sun Microsystems, Murdock przewodził projektowi Indiana, którego "produktem" był/jest OpenSolaris, a przed Sunem był CTO Linux Foundation – tego wszystkiego o nim nie wiedziałem, cóż, po prostu nigdy wcześniej nie sprawdzałem co o Murdocku mówi Wikipedia. Osobiście kojarzyłem Murdocka jedynie jako założyciela dystrybucji Debian (to od jego imienia - Ian - pochodzą trzy ostatnie litery w nazwie tej dystrybucji, pierwsza część "Deb" nawiązuje do imienia Debra). Szkoda, naprawdę szkoda pana Murdocka.

I to chyba wszystko, wyszło trochę przydługawo, jednak mam nadzieję, że nie nudno (no i, że nie zbyt chaotycznie) – Docker to temat rzeka!