Docker from zero to hero – ITAD 2018 PW

Opublikowane przez Piotr Wachulec w dniu

Hej, ha, ho! No to teraz się pewnie zdziwiliście. Ostatni artykuł wyszedł… 1.01.2018 roku. Od tamtej pory na blogu była cisza za co Cię z całego serduszka przepraszam. Ale! Nadarzyła się świetna okazja, żeby wrócić do tego miejsca, odświeżyć je trochę, odkurzyć, zmienić i poprawić to i owo. Mam nadzieję, że Ci się podoba! (Jeśli zostawisz jakiś komentarz, to będę wdzięczny!) 

No dobra Mistrzu. Powiedz, co to za okazja?! A no taka, że dzięki uprzejmości i zaproszeniu Grupy .NET działającej przy Politechnice Warszawskiej oraz jej szefa Michała Chęcińskiego miałem (chyba, że odpaliłeś/aś ten wpis w trakcie to mam) niebywałą przyjemność podzielić się kawałkiem swojej wiedzy i opowiedzieć to i owo o kontenerach i narzędziu Docker na konferencji IT Academic Days odbywającej się 28. listopada 2018 roku na Politechnice Warszawskiej.

On

Pomyślmy sobie o tym, jak możemy uruchamiać aplikacje. U siebie lokalnie, czy na jakiś środowiskach produkcyjnych, UAT, QA, czy zwał jak zwał. Obojętnie. Weźmy do tego pod uwagę, że w sumie, to nie chcemy uruchamiać jednej aplikacji (co, zabronisz mi?! MI?! MNIE?!), tylko różne, napisane w .Necie, Javie, Pythonie, Ruscie, Elixirze, C++, o tych wszystkich DżawaSkriptach nie wspominając… A no i jeszcze weźmy pod uwagę to, że jeśli to nie jest nasza lokalna maszynka, to gdzieś tam… właśnie gdzieś tam za rogiem czai się on. Tak, dokładnie on – Andrzej Administrator.

Na co dzień świetny gościu, w piłkarzyki zasuwa, jak dzik po lesie, na kawie rozbawia pół biura, a przy obiedzie pokazuje foteczki, jak z wnukiem łowi rekiny w weekendy. Człowiek ideał rzekłbyś/rzekłabyś. Jednak kiedy przychodzi temat serwerów, wirtualnych maszyn… “Panie Andrzeju, bo byśmy tam doinstalowali jakąś paczuszkę, czy może jakiś upgrade zrobili…”. To wtedy jest jak jak Cerber strzegący wejścia do krainy zmarłych. Wtedy jest taki, jak miał być Komisarz Ryba w Kilerze…

Dziwne, u mnie działa

No właśnie. Sami widzicie jak jest. Ciężkie jest życie programisty. A rozpatrzmy taką sytuację. Wchodzisz w rozwijany już projekt. “Słuchaj, tutaj mamy Dżawę ósemkę, do tego na bazie Postgresa 9 i…”. Dobra, no to bierzesz tego biednego, spracowanego laptopa i instalujesz sobie wszystkie po kolei rzeczy, jak Ci uberarchitekt, czy inny lider powiedział. 
– Coś jeszcze potrzebuję?
– Nie, nie, będzie grało, tam masz konfigurację zdefiniowaną, będzie hulać.
No to ochoczo robisz git clone repozytorium, odpalasz środowisko, projekt, PLAY

I lipa.

Sypie errorami jak Pan Karol Strasburger sucharami o 14.00 w niedzielę w Familiadzie (oczywiście mocno ściskamy Pana Karola!).

Dobra, halo. Jestem turbo devem, gdzie jest moja pizza i kawa? Potrzymaj mi piwo, zaraz będzie chodzić. No i siedzisz, uprawiasz StackOverflow Driven Development, bo przecież u innych działa i nikt nie wie, gdzie można zacząć w ogóle szukać. Po 3 dniach wychodzisz z biura z tarczą i oczywiście padasz z wycieńczenia zaraz za drzwiami. 

Żarty żartami, ale

Dobra, musicie mi wybaczyć, trochę mnie poniosło. Jednak wydaje mi się, że przekazałem to, co tak często nas trapi, tzn. problem konfiguracji środowiska i uruchomienia aplikacji. Oczywiście, to nie jedyna rzecz, bo to możemy rozpykać poprzez przygotowanie odpowiednio skonfigurowanej maszyny wirtualnej, na której wiemy, że aplikacja będzie chodzić, jednak, w przypadku środowisk lokalnych do dewelopmentu jest co najmniej mało wygodne, nie mówiąc już o pożeraniu zasobów.

A i na serwerach nie jest różowo. Tak, ja wiem, że w dzisiejszych czasach sprzęt praktycznie nie stanowi ograniczeń przy odpowiednim portfelu gotówki, ale zwróć uwagę na to, że maszyny wirtualne dają bardzo duży narzut ze względu na wirtualizację całego ekosytemu (tak naprawdę przecież przy odpaleniu maszyny wirtualnej przechodzimy całą procedurę bootowania systemu, jak na maszynie fizycznej, mamy cały narzut pamięciowy i czasowy związany z wirtualizacją). Podniesienie maszyny wirtualnej po prostu jest kosztowne czasowo i pamięciowo. Kolejna sprawa, to procesy CI & CD.

Załóżmy, że mamy właśnie napisaną aplikację w Javie, no i jak tutaj wdrożyć nową wersję? No bo trzeba jakoś automatycznie ubić to, co chodzi aktualnie i podnieść nową wersję. Jeszcze najlepiej zrobić to wszystko z modelu 0 downtime. Ja nie mówię, że się nie da tylko, że trzeba trochę zakombinować. Mało przyjemne.

Remedium

Na szczęście przeżyjemy wszystkie te kryzysy i nasze życie stanie się (a właściwie już się staje) prostsze. Z pomocą na te i inne bolączki przychodzą nam kontenery oraz narzędzie z nimi związane, czyli najpopularniejszy i najbardziej znany aktualnie na rynku Docker.

Logo Docker
Źródło: https://www.vectorlogo.zone/logos/docker/index.html

Co to są te kontenery?

Jak sam Docker podaje, kontener jest to 

ustandaryzowana jednostka oprogramowania

https://www.docker.com/resources/what-container

Z polskiego na nasze – po prostu chodzi o to, że jest to taka paczka, która zawiera wszystkie potrzebne zależności, narzędzia, biblioteki itd. potrzebne do tego, aby uruchomić aplikację w takim modelu czarnej skrzynki – dostajesz pudełeczko z wielkim czerwonym guzikiem WCIŚNIJ MNIE – wciskasz i działa 🙂 Tak najlepiej sobie to wyobrażać i rozumieć. 

A jak się to ma do wirtualnych maszyn?

Spójrzmy na schemat klasycznego podejścia

Model serwera w podejściu klasycznym

To o czym mówiliśmy wcześniej – system na systemie i cały narzut wirtualizacyjny. Słabo. A czasem jest jeszcze tak z różnych przyczyn i polityk, że na maszynie wirtualnej są jeszcze uruchomione maszyny wirtualne i to się zagłębia… Każda z nich ma swoją kopię systemu, oprogramowania itd. Straszna sprawa. A jak to wygląda, kiedy mamy Docker i kontenery?

Model serwera w podejściu kontenerowym

Zobacz, mamy tutaj infrastrukturę, sprzęt na którym chodzi system operacyjny, na nim mamy zainstalowane oprogramowanie, które pozwala na uruchamianie (i nie tylko, ale o tym zaraz) kontenerów i od razu aplikacje w kontenerach. Obojętnie w czym. Java, PHP, C#, Ruby, łotewer. Działa z pudełka. Nie bawisz się z konfiguracją, uruchamiasz i działa. Kontenery współdzielą pomiędzy sobą zasoby i każdy z nich chodzi jako osobny, odizolowany proces w systemie operacyjnym. Dlatego jest możliwe takie działanie i są nieporównywalnie lepsze od maszyn wirtualnych jeśli chodzi o czasy uruchomienia i zużywane zasoby.

Wejdź głębiej!

Mamy już pogląd na to, jakie problemy trapiły nas wszystkich i jakie znaleziono na to panaceum. Przyszedł teraz, by zapoznać się z samym Dockerem jako takim. 

Budowa

Żeby wejść do świata Dockera, trzeba zapoznać się z pewnymi pojęciami i uporządkować je sobie w głowie. 

  1. Rozpocznijmy od tego, że jesteśmy w posiadaniu kodu aplikacji, bądź jest już ona skompilowana/spakowana (np. do *.jar).
  2. By móc zacząć myśleć w ogóle o Dockerze, należy przygotować plik Dockerfile. Jest to plik tekstowy, w którym definiujemy z czego będzie składał się kontener oraz jak ma zostać skonfigurowany. Przykład Dockerfile omówimy sobie w części praktycznej.
  3. Kiedy już mamy aplikację i plik Dockerfile jesteśmy w stanie zbudować obraz (ang. image). Obraz nie jest niczym więcej jak tą paczką, o której wcześniej mówiliśmy. Zawiera wszystkie pliki, by uruchomić aplikację standalone.
    • W tym miejscu jeszcze warto wspomnieć, że obraz zbudowany jest z warstw (ang. layers).
  4. Obraz po zbudowaniu przechowywany jest w repozytorium (ang. repository). I teraz może być przechowywany w naszym lokalnym repozytorium uruchomionym przez demon Dockera, który po zainstalowaniu Dockera wstaje razem z systemem i jest w stanie właśnie zbudować czy przechować obraz lub też może być przechowywany w repozytorium zdalnym. Najbardziej znanym repozytorium dla obrazów jest Docker Hub. To jest taki Github tylko właśnie dla Dockera. Każdy może sobie założyć konto i w darmowej wersji można mieć jedno prywatne repozytorium – więcej informacji znajdziecie w Billing Information & Pricing Plans. Oprócz tego możemy sobie uruchomić także, załóżmy na jakimś serwerze zdalnym swoje prywatne repozytorium obrazów lub skorzystać z gotowych usług, np. Azure Container Registry.
  5. Z demonem komunikuje się klient poprzez zapytania HTTP.
  6. No to jak mamy obraz, to już możemy się bawić, bo uwaga, możemy każdy obraz uruchomić tyle razy, ile nam się podoba i “ile fabryka dała” w sprzęt, na którym to uruchamiamy. Uruchomioną instancję obrazu nazywamy kontenerem (ang. container).

Przeczytaj powyższą listę jeszcze raz, na spokojnie. Upewnij się, że wszytko zrozumiałeś/aś i poczułeś/aś, ponieważ te pojęcia są kluczowe do dalszej pracy z Dockerem.

Po co ten cały zachód?

Już wcześniej powiedzieliśmy sobie o kilku zaletach używania Dockera i kontenerów:

  • Optymalizacja czasowa i pamięciowa
  • Uruchamianie procesów w odizolowanych środowiskach

Jednak na tym nie koniec. Konteneryzacja przede wszystkim umożliwia podział dużych systemów informatycznych na całkowicie niezależne komponenty, co w dzisiejszym modelu architektury i pracy przy dewelopmencie aplikacji ma nie lada znaczenie. 

Kolejną kwestią jest łatwość zarządzania kontenerami (nie będę wchodził w szczegóły teraz ze względu na to, że na przykładzie sobie zobaczymy, jak to działa). 

Skoro powiedzieliśmy sobie, że łatwo się nimi zarządza, to warto zasygnalizować istnienie narzędzi takich jak Kubernetes czy wbudowany natywnie w Dockera Docker Swarm. Są to tak zwane orkiestratory, których zadaniem jest zarządzanie kontenerami – sprawdzanie, “czy kontener żyje” i w razie potrzeby zabicie go i uruchomienie na nowo, uruchamianie dodatkowych kontenerów w razie wykrycia wzmożonego ruchu, bądź wyłączanie ich w przypadku małego ruchu (skalowanie w poziomie), czy automatyzacja procesów wdrażania nowych wersji oprogramowania (uruchamianie kontenerów z nową wersją przy jednocześnie uruchomionych kontenerach ze starą wersją i kiedy wykryte zostanie prawidłowe działanie kontenera, to przełączenie na niego ruchu i zamknięcie kontenera ze starą wersją itd.)

Instalacja

Oczywiście zanim zaczniemy cokolwiek robić, trzeba zainstalować Dockera na naszej maszynce. Najłatwiej to przyjdzie na Ubunciaku. Po prostu robisz kroki z instrukcji i śmiga.

Dla Windowsa i Maca istnieje coś takiego jak Docker Desktop. Na Macu nie wiem jak to działa, bo nie posiadam sprzętu i nie testowałem, ale łyndołs…

Problem Hyper-V Docker Desktop

Tak. Mam wirtualizator i nie mogę korzystać z Dockera. Dlatego też, w sumie zaleca się takie podejście (mówili nawet o tym ostatnio w Microsofcie jak byłem na Forum Architektów i Programistów), żeby postawić sobie właśnie maszynkę wirtualną z Linuxem i na niej zainstalować Dockera. Działa #potwierdzoneinfo

Life coding!

Aplikacja w Pythonie i Flasku

No to czas, na pierwsze nasze użycie. Mamy zainstalowanego Dockera, więc można działać. Weźmiemy sobie na tapetę najprostszą możliwą do testów napisaną w Pythonie i Flasku. na początku poleceniem mkdir stworzyłem sobie katalog ~/Documents/itad2018/docker-python, a później do niego wszedłem.

Wewnątrz niego stworzyłem katalog src na nasz kod źródłowy oraz plik requirements.txt, w którym zapiszemy sobie zależności, z których będzie korzystała nasza aplikacja. Plik requirements.txt ma następującą treść:

Flask==1.0.2

Zatem po powyższych operacjach nasz katalog docker-python powinien mieć następującą strukturę:

piotr@ubuntu-desktop-18-04-1:~/Documents/itad2018/docker-python$ ls
requirements.txt src

Pozwolicie, że już nie będę wrzucał screenów, a zrzuty z konsoli. Będzie łatwiej 🙂

No to teraz przyszedł czas na stworzenie ubertrudnego kodu źródłowego. Zobaczcie, jak on wygląda:

from flask import Flask
app = Flask(name)

@app.route("/")
def hello():
return "Hello world\n"

if __name__=="__main__":
app.run(host="0.0.0.0")

[Optional] Uruchom apkę lokalnie

Jeśli masz zainstalowanego Pythona i Python-venv, to możesz spróbować uruchomić ten kod wywołując następujące polecenia, ale nie musisz tego kroku wykonywać. 

python3 -m venv venv
source venv/bin/activate
pip3 install -r requirements.txt
python3 src/main.py

Jeśli jednak zdecydowałeś/aś się uruchomić kod bez kontenera, powinieneś/powinnaś zobaczyć coś takiego w konsoli:

(venv) piotr@ubuntu-desktop-18-04-1:~/Desktop/itad/docker-python$ python3 src/main.py
* Serving Flask app "main" (lazy loading)
* Environment: production
* WARNING: Do not use the development server in a production environment.
* Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

Po wykonaniu zapytania GET z drugiej konsoli otrzymasz coś takiego

piotr@ubuntu-desktop-18-04-1:~$ curl -X GET http://localhost:5000
Hello world
piotr@ubuntu-desktop-18-04-1:~$

Zaś w tej gdzie uruchomiona jest aplikacja

(venv) piotr@ubuntu-desktop-18-04-1:~/Desktop/itad/docker-python$ python3 src/main.py
* Serving Flask app "main" (lazy loading)
* Environment: production
* WARNING: Do not use the development server in a production environment.
* Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [28/Nov/2018 09:55:02] "GET / HTTP/1.1" 200 -

Teraz jesteśmy 100% pewni, że działa, jednak podkreślę, nie trzeba tego robić.

Dockerfile

Aby uruchomić kontener potrzebujemy już wcześniej wspomnianego pliku Dockerfile. Na razie zobacz, jak on w naszym przypadku będzie wyglądał, zaraz sobie go przeanalizujemy linia po linii.

FROM python:3.7-alpine
COPY requirements.txt .
RUN pip3 install -r requirements.txt
CMD ["python3", "/src/main.py"]

Analiza

Obraz bazowy

Jak widać nasz Dockerfile jest uber trudny i rozbudowany. Przejdźmy go zatem linia po linii.

FROM python:3.7-alpine

W tej linii mamy słowo kluczowe FROM i potem coś o Pythonie. From informuje Docker z jakiego obrazu bazowego ma skorzystać do budowania obrazu. Ogólnie zasada jest prosta – nie tworzymy koła na nowo. Jeżeli możemy skorzystać z czegoś, co już istnieje (a w 99,999% przypadków możemy), to korzystamy, dlatego w zdecydowanej większości będziemy budować swoje obrazy na bazie innych. Oczywiście da się zbudować swój obraz od zera, jednak bardziej to jest stosowane do budowania obrazów systemów operacyjnych, zresztą na stronie Dockera w dokumentacji można znaleźć informację o tym w jaki sposób to zrobić. Ogólnie nie polecam, szczególnie na początku.

No a co z tym dalej? Jak szukać obrazów bazowych? Wchodzimy na wspomnianego już Docker Hub i tam w szukajce wpisujemy interesujące nas hasło, w naszym przypadku “python”.

Wynik wyszukiwania obrazu w Docker Hub

Teraz trzeba sobie poklikać i poczytać, co dany obraz robi fajnego. Nas będzie interesował ten pierwszy, oficjalny (to niebieskie, to nazwa obrazu). Otwórzmy stronę tego obrazu. 

Strona obrazu Python

I teraz tak, mamy w sekcji  Full description różne tagi. Tagi to po prostu rozróżnienie różnych wersji tego samego obrazu, na końcu znajdziesz opis, co znaczy co. Ja wybrałem obraz Pythona w wersji 3.7 (chodzi oczywiście o wersję języka) i wersję alpine, żeby ważyło mało. Nie będziemy robić żadnej wybitnej konfiguracji, więc damy sobie radę 🙂

COPY

Mam nadzieję, że już wiesz, dlaczego pierwsza linijka wygląda jak wygląda. Czas na rozpykanie drugiej, ale zanim, to przypomnijmy sobie strukturę plików w katalogu docker-python poleceniem ls

piotr@ubuntu-desktop-18-04-1:~/Documents/itad2018/docker-python$ ls
Dockerfile requirements.txt src

oraz jak wygląda druga linijka

COPY requirements.txt .

Polecenie COPY kopiuje wskazany plik we wskazane miejsce w obrazie. Czyli w naszym przypadku, bierze plik requirements.txt i kopiuje go bez zmiany nazwy do katalogu w którym jest – w przypadku tego obrazu jest to korzeń /.

RUN

W przedostatniej już linijce pliku Dockerfile mamy polecenie RUN

RUN pip3 install -r requirements.txt

Po krótce – to polecenie zostanie wywołane podczas budowania obrazu i zostanie utworzona nowa warstwa (zobacz definicję warstwy w dokumentacji). Tutaj manager pakietów zainstaluje pakiety opisane w pliku requirements.txt, dzięki czemu będziemy w stanie wewnątrz kontenera uruchomić  aplikację.

Last but not least – CMD

Przyszedł czas na ostatnią linijkę

CMD ["python3", "/src/main.py"]

Polecenie CMD definiuje jakie ma zostać wykonane domyślnie polecenie, kiedy kontener zostanie uruchomiony, czyli zostanie uruchomiony skrypt w naszym przypadku. 

Ciężko było? Trudne? Mam nadzieję, że nie 🙂 Ogólnie o tym, co może być w Dockerfile i jak go budować możesz poczytać w dokumentacji. Znalazłem też ciekawy artykuł, który porównuje RUN, CMD i ENTRYPOINT.

Zbuduj swój pierwszy obraz

Uff, przeszliśmy. Mamy wszystko, co jest potrzebne, by zbudować swój pierwszy obraz a potem go uruchomić. Przejdźmy zatem do rzeczy.

Sprawdzenie aktualnie istniejących obrazów

Będziemy chcieli zbudować obraz. Możemy sprawdzić, czy jakieś obrazy mamy u siebie lokalnie. Ogólnie jeżeli, to świeża instalacja, to nie powinniście mieć żadnego obrazu, bądź hello-world z instrukcji instalacji. Aby to sprawdzić należy wywołać polecenie

sudo docker images

Ogólnie do pracy z Dockerem potrzebujemy admina. Są dwie opcje, albo przełączamy się na roota (sudo -s) albo wszystkie komendy wyołujemy z sudo. Jeśli coś wywołasz bez sudo, otrzymasz mniej więcej taki komunikat:

piotr@ubuntu-desktop-18-04-1:~/Documents/itad2018/docker-python$ docker images
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.39/images/json: dial unix /var/run/docker.sock: connect: permission denied

Po poprawnym wywołaniu komendy dostaniesz listę obrazów. U mnie ona jest aktualnie pusta (w dalszych krokach przełączę się na roota, żeby nie podawać ciągle hasła):

piotr@ubuntu-desktop-18-04-1:~/Documents/itad2018/docker-python$ sudo docker images
[sudo] password for piotr:
REPOSITORY TAG IMAGE ID CREATED SIZE

Budujemy

Obraz zbudujemy następującym poleceniem

docker build -t docker-python-example .

Fragment docker build to komenda odpowiadająca za budowanie obrazu. Dalej następuje flaga -t z wartością docker-python-example co znaczy ni mniej, ni więcej, że tak chcemy nazwać tworzony obraz. I ostatnie miejsce, w naszym przypadku kropka, to ścieżka do tzw. build contextu, czyli miejsca, gdzie znajdują się wszystkie pliki, z których ma zostać utworzony obraz. Tutaj chcemy, by był to katalog, w którym aktualnie się znajdujemy, dlatego wpisałem tam kropkę.

Po wywołaniu powyższego polecenia, Twoim oczom powinno się ukazać mniej więcej coś takiego:

root@ubuntu-desktop-18-04-1:~/Documents/itad2018/docker-python# docker build -t docker-python-example .
Sending build context to Docker daemon 3.584kB
Step 1/4 : FROM python:3.7
3.7: Pulling from library/python
54f7e8ac135a: Pull complete
d6341e30912f: Pull complete
087a57faf949: Pull complete
5d71636fb824: Pull complete
0c1db9598990: Pull complete
bfb904e99f24: Pull complete
78a3d3a96a32: Pull complete
885a0ed92c89: Pull complete
dd7cc9ace242: Pull complete
Digest: sha256:3870d35b962a943df72d948580fc66ceaaee1c4fbd205930f32e0f0760eb1077
Status: Downloaded newer image for python:3.7
---> 1e80caffd59e
Step 2/4 : COPY requirements.txt .
---> 935b88a6a9b5
Step 3/4 : RUN pip3 install -r requirements.txt
---> Running in c88b8ccd44b3
Collecting Flask==1.0.2 (from -r requirements.txt (line 1))
Downloading https://files.pythonhosted.org/packages/7f/e7/08578774ed4536d3242b14dacb4696386634607af824ea997202cd0edb4b/Flask-1.0.2-py2.py3-none-any.whl (91kB)
Collecting click>=5.1 (from Flask==1.0.2->-r requirements.txt (line 1))
Downloading https://files.pythonhosted.org/packages/fa/37/45185cb5abbc30d7257104c434fe0b07e5a195a6847506c074527aa599ec/Click-7.0-py2.py3-none-any.whl (81kB)
Collecting itsdangerous>=0.24 (from Flask==1.0.2->-r requirements.txt (line 1))
Downloading https://files.pythonhosted.org/packages/76/ae/44b03b253d6fade317f32c24d100b3b35c2239807046a4c953c7b89fa49e/itsdangerous-1.1.0-py2.py3-none-any.whl
Collecting Werkzeug>=0.14 (from Flask==1.0.2->-r requirements.txt (line 1))
Downloading https://files.pythonhosted.org/packages/20/c4/12e3e56473e52375aa29c4764e70d1b8f3efa6682bef8d0aae04fe335243/Werkzeug-0.14.1-py2.py3-none-any.whl (322kB)
Collecting Jinja2>=2.10 (from Flask==1.0.2->-r requirements.txt (line 1))
Downloading https://files.pythonhosted.org/packages/7f/ff/ae64bacdfc95f27a016a7bed8e8686763ba4d277a78ca76f32659220a731/Jinja2-2.10-py2.py3-none-any.whl (126kB)
Collecting MarkupSafe>=0.23 (from Jinja2>=2.10->Flask==1.0.2->-r requirements.txt (line 1))
Downloading https://files.pythonhosted.org/packages/e4/c4/adcc2d6f2ac2146cc04e076f14f1006c1de8e1e747fa067668b6573000b8/MarkupSafe-1.1.0-cp37-cp37m-manylinux1_x86_64.whl
Installing collected packages: click, itsdangerous, Werkzeug, MarkupSafe, Jinja2, Flask
Successfully installed Flask-1.0.2 Jinja2-2.10 MarkupSafe-1.1.0 Werkzeug-0.14.1 click-7.0 itsdangerous-1.1.0
Removing intermediate container c88b8ccd44b3
---> 3589c381f8b9
Step 4/4 : CMD ["python3", "/src/main.py"]
---> Running in 588a917589b7
Removing intermediate container 588a917589b7
---> ccc59fc6cd20
Successfully built ccc59fc6cd20
Successfully tagged docker-python-example:latest

Sprawdźmy teraz nasz zbiorek obrazów poleceniem docker image.

root@ubuntu-desktop-18-04-1:~/Documents/itad2018/docker-python# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-python-example latest ccc59fc6cd20 6 minutes ago 932MB
python 3.7 1e80caffd59e 11 days ago 923MB

Co tutaj widzimy? Otóż zbudował nam się obraz! Super! Ale zaraz, zaraz, co to jest to drugie…? No to jest ten obraz, który znajduje się w pierwszej linii pliku Dockerfile – obraz bazowy. Docker sam wykrył, że jest on potrzebny i pobrał go z Dockerhuba.

Gdybyś chciał/a dowiedzieć się więcej na temat polecenia docker build, to oczywiście sprawdź w dokumentacji.

Uruchommy!

Tak, to właśnie ta chwila! Wywołaj w konsoli następujące polecenie, proszę:

docker run -p 5000:5000 -v $PWD/src:/src docker-python-example

W konsoli powinieneś dostać następującą informację:

root@ubuntu-desktop-18-04-1:~/Documents/itad2018/docker-python# docker run  -p 5000:5000 -v $PWD/src:/src docker-python-example
* Serving Flask app "main" (lazy loading)
* Environment: production
WARNING: Do not use the development server in a production environment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

Gratulacje! Uruchomiłeś swój pierwszy kontener! Spróbuj do niego uderzyć:

piotr@ubuntu-desktop-18-04-1:~$ curl -X GET http://localhost:5000
Hello world

No i działa 🙂 Możesz ubić teraz kontener wciskając Ctrl+C.

Rozbijmy uruchomienie na czynniki

Podstawowym uruchomieniem kontenera jest komenda docker run <nazwa obrazu>, czyli w naszym przypadku 
docker run docker-python-example. I mamy dodatkowo, dwie flagi:

  • -p 5000:5000 – zmapuj port 5000 w kontenerze na 5000 na hoście
  • -v <ścieżka lokalna>:<ścieżka w kontenerze> – zamontuj do kontenera następujący katalog

Nie wiem, czy zauważyliście, ale specjalnie wcześniej nic nie mówiłem o wrzucaniu kodu źródłowego. Teraz po prostu miejsce, gdzie go składujemy, podmontowaliśmy do kontenera. Jaka jest tego zaleta? Kiedy będziemy rozwijać oprogramowanie, nie trzeba będzie przy zmianach przebudowywać obrazu, wystarczy go wyłączyć i podnieść ponownie.

Kurczę, Pjoter, to chodzi w konsoli…

No dobra, a czy możemy sprawdzić, czy jakieś kontenery są uruchomione? Jasne, że tak. Wpisz proszę w konsoli polecenie 

docker ps

Zwraca on listę uruchomionych kontenerów. U mnie na razie żadne nie chodzi:

root@ubuntu-desktop-18-04-1:~/Documents/itad2018/docker-python/ITAD2018_CodingTimeDocker# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Uruchom proszę ponownie kontener dodając flagę -d do polecenia. (Od razu też wyświetlmy ponownie listę uruchomionych kontenerów):

root@ubuntu-desktop-18-04-1:~/Documents/itad2018/docker-python/ITAD2018_CodingTimeDocker/docker-python# docker run -d -p 5000:5000 -v $PWD/src:/src docker-python-example
ae283f420e8252949a8757fb308047e0daf54e785ef13eaf595a480fa57eb89e
root@ubuntu-desktop-18-04-1:~/Documents/itad2018/docker-python/ITAD2018_CodingTimeDocker/docker-python# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ae283f420e82 docker-python-example "python3 /src/main.py" 4 seconds ago Up 2 seconds 0.0.0.0:5000->5000/tcp infallible_neumann

Kontener jest uruchomiony. Możemy go wyłączyć poprzez polecenie

docker kill <NAMES>

U nas będzie to wyglądało 

docker kill infallible_neumann

To tyle. Oczywiście kod źródłowy znajdziesz na moim Githubie. Znalazłem też fajny film na YouTube, gdzie możesz sobie przepracować podobny przykład, tylko że z PHP.

Co dalej?

Widzimy już, jakie kontenery są fajne i jak prosto w sumie w nie wejść. Jak to często bywa, próg wejścia jest niski, za to potem robią się schody. Teraz postaramy się w kilku słowach powiedzieć sobie na co należy zwracać uwagę, aby zwiększać poziom naszych umiejętności oraz by nie narobić sobie biedy przy korzystaniu z Dockera.

Pierwsza i podstawowa zasada – wszelkie dane wchodzące do niego i wychodzące powinny być przechowywane poza nim, czyli np.jeśli bazę danych stawiamy na Dockerze (bo można, czemu nie, w Docker Hub dostępne są obrazy np. mySQL, jednak z opowieści kolegów wiem, że czasem warto się zastanowić, jeśli chodzi o bazy danych – mieli taki case, że przy obsłudze klastrowej bazy dany opartej właśnie o mySQL były problemy własnie z obsługą tego klastrowania), to do kontenera powinno być podmontowane miejsce na fizycznym dysku. Czy logi – jeśli po barbarzyńsku zrzucamy je do plików, to niech te pliki lądują od razu poza kontenerem, choć są fajniejsze rozwiązania, takie chociażby jak Logstash. W każdym razie – dane z kontenera out!

A powyższe wynika z faktu, że z kontenerami pracujemy w takim modelu “ubij/podnieś”, tzn. na przykład wywala nam się aplikacja w kontenerze z różnych przyczyn, leci jakiś null pointer exception, to wtedy z automatu, bez zastanowienia powinniśmy taki kontener wyłączyć i podnieść go na nowo (a do sprawdzania, czy kontener pracuje dobrze możemy wykorzystać coś takiego jak healthcheck, co też jest wykorzystywane przez orkiestratory przy zarządzaniu kontenerami).

Wynika to także z faktu, że nasz kontener jest tylko maszynką, silnikiem do wykonywania kodu, zatem powinien być bezstanowy, ezoteryczny. Jeśli musisz w kontenerze grzebać, by odzyskać jakieś dane, to wiedz, że coś się dzieje…

I pojawia się kolejna rzecz – staraj się nie grzebać w pracującym kontenerze, a już w żadnym wypadku nie doinstalowywać żadnych pakietów z palca. Po to jest Dockerfile, żeby w nim opisać całą konfigurację kontenera. Jeżeli coś brakuje, to weź go dziewczyno/chłopaku ubij, zedytuj Dockerfile i przebuduj obraz na nowo. No bo jak zaczniesz sobie tak grzebać w kontenerze i modyfikować go w trakcie działania, to co zrobisz w czasie crashu? No uruchamiasz na nowo i znowu grzebiesz? Czyli tracisz jedną z zalet korzystania z kontenerów – brak konieczności ręcznej konfiguracji?

Obrazy

A jak już jesteśmy przy budowaniu obrazów… Na początku pewnie do wszystkich budowanych przez nas obrazu bierzemy pełne wersje innych obrazów – pythona, ubuntu, debiana, javy, czy innych. Jednak wiadomo jak to jest, wersja pełna, a korzystamy z 5% całego software. Dlatego sztuką jest budowanie naszych obrazów w taki sposób, by zawierały absolutne minimum tego, czego potrzebujemy do poprawnego działania programu i kontenera. Prodockerowcy zaczynają korzystać potem z bazowych obrazów z budowanych na Linuxie Alpine – jest to taka turbo mała i lekka dystrybucja – cały system ma chyba 5 MB? Ale jeśli chcemy z niego zacząć korzystać, to nie ma tam absolutnie nic. Trzeba wszystko sobie samemu zainstalować i skonfigurować, co często może stanowić wyzwanie.

Ale obraz nie kończy się tylko na obrazie bazowym. Poprawne i w pełni świadome korzystanie z Alpine to chyba ostatni krok. Bo jakby się tak zastanowić to to, że korzystamy z pełnych bazowych dystrybucji wynika właśnie z faktu świadomości – kiedy zaczynamy swoją przygodę czy to z kontenerami, czy z programowaniem, instalujemy wszystko jak leci, bo tak naprawdę jesteśmy jak dzieci we mgle i nie wiemy, co jak działa, do czego służy i co się nam przyda. Dlatego też możemy zacząć optymalizację naszych obrazów od odchudzania warstw.

Jak wiemy już, warstwa w Dockerze jest zapisywana jako delta pomiędzy obrazem n-1 a n. Od tego, co w niej się znajduje, będzie zależał jej rozmiar. Dlatego może warto po jakiś instalacjach czyścić cache, bądź usuwać pobrane pliki? 

Brzmi to trudno. W sumie jest. Ale przecież jesteśmy tutaj po to, by się skillować 😉 Ja sam gdzieś raczkuję w tych tematach i staram się z dnia na dzień rozwijać. Bo wiecie jak to jest, Docker jest naprawdę fajnym i pożytecznym narzędziem, pozamiatał na dzisiejszym rynku. Ale nikt nie powiedział, że rozwiązuje całe zło tego świata i nie można używać go źle. Bo można, chociażby w ten sposób, że olewamy co napisałem w powyższych paragrafach. Warto być świadomym już od początku i starać się chociażby zwracać na to uwagę

Choć z drugiej strony patrząc – czasem, szczególnie przy bardzo małych aplikacjach, to nie ma takiego znaczenia. Narzut czasu i kosztów na optymalizację budowania obrazu będzie niewspółmierny do zysków. Takie zabiegi (odchudzanie warstw i obrazy “alpine” mają sens wtedy, gdy tych kontenerów są setki czy tysiące i potrzebujemy naprawdę dużej responsywności i mądrego zużywania zasobów.

Jeśli chcesz jeszcze poczytać o wadach i zaletach używania Dockera, to łap dwa artykuły: jeden na Runnable.com, drugi na Linode.com

Summary

Dobrnęliśmy do końca. Dużo tego wyszło w sumie. Jeżeli byłeś/byłaś na tej prezentacji, to mam nadzieję, że Ci się podobało. Mam także nadzieję, że ten wpis pozwoli Ci szybko, łatwo i bezboleśnie wejść w świat kontenerów. Przejrzyj jeszcze linki, które dla Ciebie zebrałem.

Jeżeli spodobał Ci się mój artykuł, masz pytania, chcesz, żebym o czymś napisał, to zostaw proszę swój komentarz!

Slajdy z prezentacji

Linkownia

Podczas tworzenia mojej prezentacji i tego wpisu czerpałem z wielu ciekawych źródeł. Gdybyś chciał/a się rozwinąć dalej, to poniżej lista linków, które możesz przejrzeć. Have fun!