Wstęp
Kontenery to temat na pewno nie nowy, natomiast w ostatnich latach bijący chyba rekordy popularności. Podczas tej rosnącej fali ja również postanowiłem sprawdzić, z czym to się je i w czym Docker może pomóc mi. W niniejszym serii postów chciałbym podzielić się z własnymi doświadczeniami, notatkami, fragmentami kodów oraz pokazać, w jaki sposób można wykorzystać Docker’a. Będzie to Docker z perspektywy osoby, która pracuje z danymi. Począwszy od ich gromadzenia, po ich przechowywanie, aż po ich przetwarzanie. Na pewno nie zabraknie samego Docker’a, SQL Server’a i Python’a przedstawionych w jak najbardziej praktyczny sposób. Zapraszam!
Wprowadzenie
Docker to, jak możemy przeczytać na Wikipedii, “otwarte oprogramowanie służące do realizacji wirtualizacji na poziomie systemu operacyjnego (tzw. konteneryzacji), działające jako platforma dla programistów i administratorów do tworzenia, wdrażania i uruchamiania aplikacji rozproszonych” źródło W skrócie jest to zatem jedno z narzędzi do zarządzania kontenerami, a same kontenery można rozumieć jako odizolowaną aplikację lub zbiór aplikacji. Kontenery są odizolowane od siebie, natomiast mogą współdzielić pomiędzy sobą zasoby czy też nawet komunikować się ze sobą. Współdzielą one również system operacyjny, ponieważ w przeciwieństwie do tradycyjnej wirtualizacji w tym przypadku nie ma potrzeby tworzenia całego środowiska.
Źródło grafiki: docs.docker.com
Zgodnie ze schematem w przypadku tradycyjnej wirtualizacji “stawiana” jest maszyna wirtualna, która emuluje pełnoprawny fizyczny serwer z pełnoprawnym systemem operacyjnym. Maszyna wirtualna jest zarządzana oraz uruchamiana za pośrednictwem hypervisor’a, dzięki któremu można również nadać określone zasoby dla konkretnej maszyny. W przypadku konteneryzacji zasada jest inna. System operacyjny zainstalowany na serwerze dostarcza kernel, który następnie używany jest przez wszystkie kontenery tworząc dla każdego z nich odseparowane środowisko. Do konkretnych kontenerów (takich środowisk) możemy doinstalować natomiast szereg dodatkowych bibliotek czy nawet całe dystrybucje systemu Linuks (będzie to zależało od przeznaczenia kontenera). Będą one jednak cały czas korzystały i współdzieliły ten sam kernel. Kontenerami oraz współpracą pomiędzy systemem operacyjnym hosta i kontenerami zarządza silnikiem, w tym przypadku będzie to “Docker”. Współpracę pomiędzy kontenerami, a systemem operacyjnym hosta dość dobrze opisano w niniejszym poście na blogu: floydhilton.com Warto dodać, że Docker to najpopularniejsze rozwiązanie tego typu, natomiast na rynku istnieje wiele innych propozycji.
Istotną kwestią w przypadku Dockera i konteneryzacji jest fakt, że będziemy operować na de facto dwóch bytach – kontenerze (container) oraz obrazach (image). Kontener powinien odpowiadać konkretnej, działającej aplikacji, natomiast obraz będzie szablonem tego konteneru, czyli aplikacji. Wydaje mi się, że można by to porównać do programowania obiektowego i wtedy obiektem byłaby aplikacja, klasą byłby obraz, natomiast instancją byłby właśnie kontener. Warto dodać, że obrazy można tworzyć samemu, lub pobierać z tak zwanych repozytoriów, a nawet tworzyć obrazy na podstawie innych obrazów. W przypadku Dockera repozytorium znajduje się na hub.docker.com Korzystanie z gotowych, jak i tworzenie własnych obrazów zostanie opisane w dalszej części.
Zaczynamy pracę z dockerem
Instalacja
Docker’a można pobrać ze strony projektu www.docker.com. Dostępny jest na każdą platformę, czyli Linux, Mac oraz Windows. Sama instalacja nie zostanie tutaj opisana, ponieważ ogranicza się do skorzystania z kreatora, natomiast warto pamiętać o tym, iż przed pobraniem pliku instalacyjnego należy się zarejestrować na stornie projektu. Konto umożliwia również dostęp wielu dodatkowych funkcjonalności jak własne repozytoria, gdzie można przechowywać własne obrazy kontenerów (w wersji darmowej można utworzyć jedno repozytorium). Dodatkowe funkcjonalności i zalety zostaną opisane ewentualnie w innym wpisie.
Dodatkowo warto pamiętać, że aby zainstalować Docker na serwerze musi być włączona obsługa wirtualizacji w BIOS oraz urządzeniach z systemem Windows niezbędna jest licencja Pro, Enterprise lub Education. W przeciwnym wypadku można zainstalować “Docker Toolbox”, który według mojej wiedzy to dokładnie to samo, natomiast o ile pierwszy wykorzystuje Hyper-V to ten drugi wykorzystuje Oracle Virtual Box. Wirtualizacja oraz Hyper-V/Oracle Virtual Box będą zapewniać opisany wyżej dostęp do kernela.
Więcej informacji odnośnie do instalacji znajduje się na docker.com – link
Podstawy
Po zainstalowaniu Dockera powinien on być dostępny z linii poleceń danego systemu. W tej serii zrzuty ekranu będą bazowały na Dockerze, który jest zainstalowany na Linuksie, natomiast zamiennie mogą pojawić się zrzuty ekranu z Dockerem zainstalowanym na systemie Windows. Każdy kod/kontener/obraz powinien jednak działać na obu systemach. Warto nadmienić, że poza linią poleceń dostępnych jest szereg dodatkowych narzędzi, łącznie z oficjalnymi i nieoficjalnymi interfejsami GUI do zarządzania Dockerem i kontenerami, natomiast tutaj skupię się na linii poleceń. Jeżeli Docker został prawidłowo zainstalowany po wpisaniu słowa kluczowego “Docker” w terminalu zostanie wyświetlona “pomoc” oraz podstawowe komendy dla tego narzędzia.
docker
Jak już zostało to zauważone wcześniej najbardziej podstawowymi oraz najważniejszymi komendami będą:
- docker image – zarządzanie obrazami
- docker container – zarządzanie kontenerami
Te i inne komendy zostaną wkrótce opisane i przedstawione na przykładach.
Praca z Dockerem
Podstawowym elementem jest obraz kontenera. Obrazy, zarówno utworzone własnoręcznie, jak i te pobrane z internetu można zobaczyć używając komendy “docker image ls”. Naszym oczom ukaże się wówczas lista wszystkich dostępnych obrazów (lokalnie), ich tag (opisujący wersję), ID, datę utworzenia oraz rozmiar obrazu. Warto pamiętać, że ten sam obraz może występować w wielu wersjach. Na zrzucie ekranu poniżej Python występuje w wersji ostatniej (latest) oraz 2.7-slim. Na tym etapie pewnie nie jest to tak istotne, natomiast powinniśmy świadomie wybierać oraz tworzyć obrazy pamiętając, że im mniejszy rozmiar obrazu tym najczęściej lepiej (po pierwsze z założenia kontener powinien zawierać tylko to, co jest mu faktycznie potrzebne. Osobną kwestią są oczywiście również zasoby i szybkość tworzenia kontenerów z danego obrazu).
docker image ls
Same kontenery można natomiast zobaczyć używając komendy “docker container ls”, która zwróci wszystkie czynne kontenery. Zatrzymane kontenery można zobaczyć korzystając z flagi “–all”, czyli “docker container ls –all”. Przydatnym argumentem jest również “–format”, który pozwala określić format zwracanych do terminala informacji. Istotne szczególnie dlatego, że podstawowy wynik może nie mieścić się w jednym oknie (oczywiście zależnie od ustawień) i określenie formatu zwłaszcza przy dużej ilości kontenerów ułatwia czytelność.
docker container ls docker container ls --all docker container ls --format="{{.ID}} {{.Names}}"
Dwie powyższe komendy to absolutne minimum, które pozwoli sprawdzić bieżący stan oraz status kontenerów i ich obrazów. Warto pamiętać, że opcji formatowania jest znacznie więcej oraz istnieje więcej atrybutów i właściwości, które można wyświetlić. Oczywiście jeżeli wykonamy powyższe komendy zaraz po zainstalowaniu Dockera to rezultat tych komend będzie pusty, ponieważ nie będzie ani jednego obrazu czy kontenera, ale o tym następny akapit.
Docker pull
W sieci istnieją repozytoria, które udostępniają gotowe obrazy kontenerów, które można pobrać na własny komputer. Domyślnym repozytorium Dockera jest hub.docker.com Obecnie znajduje się tam ponad 2 miliony obrazów (stan na czerwiec 2019). Część z nich stanowi oficjalne obrazy tworzone przez duże organizacje jak Microsoft, Oracle czy IBM, a część po prostu udostępniona przez innych programistów. Lista obrazów zawiera również filtr pozwalający wybrać obrazy przygotowane przez sam zespół Dockera. Obrazy te mogą zostać wykorzystane jako gotowe obrazy aplikacji (np. obraz SQL Server, z którego po prostu zostanie utworzony kontener) lub jako podstawa do nowych aplikacji (np. Python, który zawiera podstawowe biblioteki tego języka i na podstawie którego przygotowany zostanie nowy obraz, który zainstaluje dodatkowe biblioteki oraz kod aplikacji). Tworzenie własnych obrazów zostanie omówione później. Do pobrania kontenera na lokalny komputer służy polecenie “docker pull”, a obrazy można pobierać zarówno po nazwie, jak i jego ID.
docker pull python
Po pobraniu obrazu będzie on widoczny na liście dostępnych obrazów oraz będzie gotowy do użycia.
Docker run
Obraz kontenera można uruchomić, a właściwie uruchomić jego instancję, korzystając polecanie “docker run”. Polecenie to będzie tworzyć kontener na podstawie wybranego obrazu. Jednym z pierwszych obrazów, którego można użyć, aby przetestować tę technologię jest obraz o nazwie “hello-world”, który po uruchomieniu wyświetli tekst na ekranie. Aby uruchomić demo, należy najpierw pobrać obraz kontenera:
docker pull hello-world
A następnie uruchomić kontener:
docker run hello-world
Nazwa kontenera nie jest wymagana i w przypadku braku jej specyfikacji nazwa zostanie wygenerowana automatycznie. Po uruchomieniu komendy powinien zostać wyświetlony komunikat.
Po sprawdzeniu listy kontenerów zobaczymy, że nazwa rzeczywiście została wygenerowana, oraz że kontener z takiego obrazu został utworzony, natomiast, że zakończył już on swoje działanie.
docker container ls -a
Kontener żyje bowiem tak długo, jak jest to wymagane i po zakończeniu pracy, którą powinien wykonać się wyłącza. Oczywiście nie oznacza to, że kontenery nie mogą działać znacznie dłużej lub nawet nieprzerwanie.
Jako drugi test można spróbować uruchomić kontener z obrazu Python, na przykład:
docker run python:latest
Po wykonaniu powyższej komendy kontener zostanie uruchomiony, natomiast od razu zakończy swoje działanie. Jak zostało wspomniane kontener został utworzony z obrazu “python”, który to zawiera po prostu środowisko oraz podstawowe biblioteki Python, natomiast nie przekazujemy do niego żadnego skryptu ani nie określiliśmy żadnego zadania/procesu, a więc tak naprawdę nie ma żadnej pracy do wykonania i domyślnie po uruchomieniu kontener kończy swoje działanie. Warto dodać, że w tym przypadku podany został również konkretny tag obrazu (“latest”). Istnieje bowiem możliwość, że jeden obraz (o takiej samej nazwie) będzie posiadał wiele wersji oznaczonych konkretnymi tagami. Aby uruchomić taki kontener i powstrzymać go od zamknięcia można użyć następującej komendy:
docker container ls --all --format "{{.ID}} \t {{.Names}} \t {{.Command}} \t {{.Status}}" docker run -t -d --name="run-sample-python" python:latest docker container ls --all --format "{{.ID}} \t {{.Names}} \t {{.Command}} \t {{.Status}}"
Jak widać w tym przypadku kontener cały czas działa i nie wyłączył się od razu po starcie. O dodatkowych parametrach i uruchamianiu bardziej skomplikowanych scenariuszy jeszcze będzie mowa, natomiast w powyższym przykładzie został uruchomiony kontener na podstawie obrazu “Python” o konkretnej nazwie. Dodatkowo wykorzystano parametr “-t” przygotuje terminal tty dla tego kontenera oraz parametr “-d” (detach mode), który uruchamia kontener w tle. Ponadto dodanie terminala spowoduje, że kontener od razu nie zakończy swojego działania. Dodanie terminala powoduje, że taki terminal zostanie uruchomiony i kontener będzie aktywnie oczekiwał połączenia do tego terminala. Nie jest to jedyna metoda, w jaki sposób można powstrzymać “pusty” kontener przed zamknięciem, natomiast przykład ten miał na celu wyłącznie pokazać, dlaczego kontener zaraz po uruchomieniu się wyłącza (oczywiście pomijając inne przyczyny jak błędy).
Przykład ten mam nadzieje, że obrazuje również wskazówkę widoczną na komunikacie pochodzącą z przykładu z “Hello world” odnośnie uruchomienia kontenera Ubuntu z podobnymi parametrami. Takie uruchomienie kontenera może służyć na przykład do przygotowania testowego środowiska python czy też testowego środowiska Ubuntu (testowego w kwestii raczej nauki aniżeli testowego środowiska wykorzystywanego w projekcie).
Docker exec
Ciekawą funkcjonalnością jest możliwość uruchamiania komend na uruchomionym kontenerze, czy nawet korzystając z tej możliwości uruchomienie terminala i połączenia się z konkretnym z jego pomocą do kontenera. Służy do tego komenda “docker exec”, która w opisywanym przypadku pozwoli na połączenie się za pomocą konkretnej powłoki do kontenera. Dzięki temu można wykorzystywać wcześniej wspomniane środowisko testowe. W tym celu należy uruchomić następujące polecenie:
docker container ls --all --format "{{.ID}} \t {{.Names}} \t {{.Status}}" docker exec -it <container ID> bash
gdzie <container ID> można pobrać za pomocą wcześniej opisanego polecenia “docker container ls”
W powyższym przykładzie połączono się do terminala na kontenerze utworzonym z obrazu Python, korzytając z bash’a. Po połączeniu został uruchomiony Python oraz przykładowa komenda w tym języku. Tak samo można połączyć się do kontenera utworzonego z obrazu dowolnej dystrybucji Linux czy jakiegokolwiek innego obrazu (jeżeli w chwili uruchamiania terminal zostanie uruchomiony). Dodatkowo może to się okazać przydatne w krytycznych sytuacjach lub sprawdzaniu dodatkowych logów.
Docker rm, rmi i system prune – usuwanie obrazów i kontenerów
Kiedy obraz lub kontener nie jest już potrzebny można go usunąć. Do usuwania kontenerów służy komenda “docker rm”, natomiast do usuwania obrazów służy komenda “docker rmi”. Zarówno obrazy, jak i kontenery można usuwać za pomocą nazwy lub ID.
docker container ls --all --format "{{.ID}} \t {{.Names}} \t {{.Command}} \t {{.Status}}" docker rm <container ID> docker container ls --all --format "{{.ID}} \t {{.Names}} \t {{.Command}} \t {{.Status}}"
Pierwszy przykład pokazuje usunięcie kontenera za pomocą jego ID. (Uważne oko może zauważyć, że w tym przypadku zrzut został zrobiony na maszynie Windows na lokalnym komputerze)
docker image ls docker rmi <image ID> docker image ls
Drugi przykład pokazuje usunięcie obrazu. Nie zawsze (a raczej rzadko) jeden obraz to jeden byt co wynika z mechanizmu budowania oraz aktualizacji obrazu. Na zrzucie powyżej po komendzie “docker rmi” można zauważyć, że zostało usuniętych kilka obiektów (nazywanych warstwami). Nie jest to kluczowe na tym etapie i zostanie omówione w dalszych postach. Warto również pamiętać, że można usuwać więcej niż jeden kontener lub oraz na raz. Wystarczy do jednej z tych komend po spacji wymienić kilka nazw lub kilka ID kontenerów lub obrazów. Poniższa komenda usunie oba wymienione kontenery.
docker rm 889f466e1ab4 8559620b5b0d
Kontenery i obrazy można również usunąć korzystając z komendy “docker system prune”. Komenda ta automatycznie usunie nieużywane kontenery oraz niepotrzebne obrazy (takie, które zostały zaktualizowane, nazywane “dangling images”). Dodatkowo usunie między innymi nieużywane sieci oraz wyczyści cache buildów.
docker container ls --all --format "{{.ID}} \t {{.Names}} \t {{.Command}} \t {{.Status}}" docker image ls docker system prune docker container ls --all --format "{{.ID}} \t {{.Names}} \t {{.Command}} \t {{.Status}}" docker image ls
Korzystając z parametru “all” razem z “docker system prune” dodatkowo zostaną usunięte wszystkie nieużywane obrazy.
docker container ls --all --format "{{.ID}} \t {{.Names}} \t {{.Command}} \t {{.Status}}" docker image ls docker system prune -all docker container ls --all --format "{{.ID}} \t {{.Names}} \t {{.Command}} \t {{.Status}}" docker image ls
Korzystanie z tej komendy w środowisku produkcyjnym wydaje się chyba kontrowersyjne, natomiast przynajmniej na własnym środowisku testowym, a szczególnie podczas nauki, myślę, że może się przydać.
Zakończenie
Kontenery i Docker dla “amatora danych” mogą okazać się bardzo przydatne, nawet do zastosować w domu (nieprodukcyjnych). Osobiście aplikacje piszę najpierw na komputerze (zwykle język Python), następnie w chwili gdy już są przetestowane oraz sprawnie działają przygotowuję obraz w Dockerze dla takiej aplikacji i następnie uruchamiam aplikację w kontenerze w NAS albo na VPS, który można stosunkowo tanio znaleźć w sieci. Według mnie ułatwia to pracę osoby, która pracuje (hobbystycznie, ale nie tylko) z danymi pomijając już kwestię bardzo szerokiego wykorzystania Dockera w zastosowaniach czysto projektowych i cyklu rozwijania różnego rodzaju aplikacji. Oczywiście jest to tylko wstęp do tej tematyki, a kolejne tematy zostaną poruszone już wkrótce.
Uwaga
Nie jestem z całą pewnością ekspertem w zakresie Dockera oraz Pythona, a wpis ten traktuję bardziej jako zapis moich notatek i prób, które być może przydadzą się również Tobie. W przypadku jakichkolwiek błędów i usprawnień bardzo zapraszam do komentowania lub kontaktu.
- Docker dla amatora danych – Tworzenie środowiska (VM) w Azure i VirtualBox (skrypt) - April 20, 2020
- Power-up your BI project with PowerApps – materiały - February 5, 2020
- Docker dla “amatora” danych – kod źródłowy do prezentacji - November 18, 2019
Świetnie wytłumaczone 😉 Pozdrawiam!
Dzięki! Również pozdrawiam! 🙂