Docker

Definicja - czym jest Docker?

Docker to złoty środek pomiędzy maszyną wirtualną a środowiskiem lokalnym. Maszyna wirtualna, tworzona na przykład za pomocą duetu Vagrant+Ansible daje nam całkowitą separację środowiska usługi i lokalnego środowiska naszego komputera. Jest to bardzo wygodne, ale:

  • zajmuje bardzo dużo miejsca (chcemy zainstalować cały system)

  • jest bardzo zasobożerne (znów, instalujemy cały system)

  • najchętniej chcemy, by wszystkie usługi dla danego projektu były zainstalowane na jednej maszynie (bo nie chcemy kilku maszyn na jednym projekcie), co zabiera nam elastyczność przy ich włączaniu i wyłączaniu.

Na te trzy problemy odpowiada Docker. Pozwala on, w skrócie, uruchomić domyślnie jedną komendę (np. usługę, ale może to być też komenda interaktywna) w dowolnym środowisku - zwykle minimalistycznej wersji Linuxa z doinstalowanymi paczkami, których zadaniem jest rozszerzyć funkcjonalność do tej, której potrzebujemy.

Takie usługi są uruchamiane jako kontenery i są do pewnego stopnia zwirtualizowane - ich zasoby nie są co prawda jasno oddzielone od zasobów maszyny-rodzica, ale “dyski” już tak (integracja, jak w przypadku maszyn wirtualnych następuje poprzez synchronizację konkretnych folderów). Podobnie działa współdzielenie portów - możemy przekierować niektóre porty maszyny-rodzica na porty kontenera.

Aby uruchomić usługę wystarczy mieć jej obraz, na którego podstawie zostanie stworzony kontener. Takie obrazy możemy budować za pomocą Dockerfile, ale można też korzystać z obrazów dostepnych publicznie na hub.docker.com.

Użyteczność w środowisku deweloperskim

W ramach pracy deweloperskiej (w znaczeniu - nie na produkcji) Docker spełnia kilka głównych zadań:

  1. Gdy wykorzystamy razem z nim docker-compose lub wesprzemy się Makefile’em za pomocą dockera możemy sprawić, by nasz zespół deweloperów odpalał wszystkie wykorzystywane przez nas bazy w ciągu kilkunastu minut. Jest to szczególnie użyteczne, gdy nie chcemy męczyć się z ujednoliceniem konfiguracji wszystkich maszyn deweloperów, lub gdy korzystamy ze złożonej konfiguracji środowiska i nie chcemy duplikować jej na maszynach deweloperów.

  2. Środowiska, których używamy są znacznie bardziej wymienne - możemy pracować jednocześnie nad dwoma projektami wykorzystującymi MySQLa i swobodnie przerzucać się między dwoma kontenerami Dockerowymi (nawet na tym samym porcie) - by swobodnie zmieniać środowisko tych dwóch projektów. Łatwo też całkowicie zresetować środowisko, na przykład gdy chcemy wyczyścić bazę i przetestować działanie migracji systemowych, a potem móc swobodnie wrócić do bazy wypełnionej danymi, na której zwykle pracujemy. Do tego, choć dockery zajmuję oczywiście więcej zasobów niż usługi zainstalowane lokalnie, wyłączenie dockerów po pracy nad nimi jest na tyle proste i naturalne, że zyskujemy bardzo dużo pamięci w czasie, gdy nie pracujemy akurat nad projektem.

  3. Wreszcie, dzięki dockerowi można znacznie łatwiej produkować kod nawet na bardzo słabym sprzęcie - na przykład wykorzystując rozwiązania typu docker-swarm (na jakimś serwerze) lub Ubuntu Docker na usługach jak DigitalOcean. Znacznie łatwiej w ten sposób pracować z usługami w chmurze bez szczególnego myślenia - a takie usługi można potem często po prostu wyłączyć, płacąc za nie jedynie koszt godzinowy.

Przykład Makefile uruchamiającego dockery kilku podstawowych usług

Poniższy Makefile może być wykorzystany na przykład do tego, by wspierać one-hour launch w projekcie programistycznym. Tutaj uruchamiane są MySQL, phpmyadmin do jego administracji, elasticsearch, redis:

dockerInit:    docker run -p 3306:3306 --name megaschema-mysql -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=b2c -d mariadb    docker run --name megaschema-myadmin -d --link megaschema-mysql:db -p 8080:80 phpmyadmin/phpmyadmin    docker run -p 6379:6379 --name megaschema-redis -d redis    docker run -i -t -d --name megaschema-onlyoffice -p 1001:80 onlyoffice/documentserver    docker run -p 9200:9200 -e ES_JAVA_OPTS="-Xms512m -Xmx512m" --name megaschema-elasticsearch -d elasticsearch:6.6.0docker:    docker start megaschema-mysql    docker start megaschema-myadmin    docker start megaschema-redis    docker start megaschema-elasticsearch

Przykład pliku docker-compose uruchamiającego wordpressa, na przykład do dewelopmentu motywów

version: '3.3'services:   db:     image: mysql:5.7     volumes:       - db_data:/var/lib/mysql     restart: always     environment:       MYSQL_ROOT_PASSWORD: somewordpress       MYSQL_DATABASE: wordpress       MYSQL_USER: wordpress       MYSQL_PASSWORD: wordpress   wordpress:     depends_on:       - db     image: wordpress:latest     ports:       - "8000:80"     restart: always     environment:       WORDPRESS_DB_HOST: db:3306       WORDPRESS_DB_USER: wordpress       WORDPRESS_DB_PASSWORD: wordpress       WORDPRESS_DB_NAME: wordpressvolumes:    db_data: {}

Testowanie i publikacja

Dockera łatwo można wykorzystać do testowania i publikacji kolejnych wersji oprogramowania. Po każdym commitcie lub release oprogramowania w Gitcie łatwo podłączyć oprogramowanie, które automatycznie wykona instrukcje w Dockerfile’u i wrzuci go do naszego registry (miejsca, które przechowuje obrazy dockerowe po ich stworzeniu). Jeśli szukacie open-sourcowego rozwiązania do tego, polecam miks Gitea i Drone - integrują sie ze sobą bez problemów.

Przykład najprostszego Dockerfile do aplikacji w NodeJS:

FROM node:12

ADD . /usr/src/appWORKDIR /usr/src/app

RUN yarnRUN yarn build

CMD node /usr/src/app/dist/index.js

Dzięki publikowaniu obrazów w taki sposób, łatwo można na przykład wrócić do poprzedniej wersji oprogramowania w razie problemów z publikacją, a także zawsze zachowujemy czyste środowisko w momencie publikacji.

Użyteczność na produkcji

  1. Po pierwsze, lenistwo - często nie chce nam się w pełni konfigurować jakiejś usługi, która jest dla nas kompletną paczką. Remark42, Redash, Metabase, Hasura, Huginn, przyhkłady można mnożyć.

  2. Po drugie - śledzenie i separacja - powiedzmy, że chcemy na naszym serwerze obslugiwać X organizacji. Każda z nich powinna mieć relatywnie odseparowane od siebie zasoby. W szczególności, jeśli jakaś organizacja zacznie zużywać zbyt wiele zasobów, chcemy zablokować jej dostęp i nie pozwolić, by wpłynęła na inne organizacje. Chcemy też dość wygodnie śledzić, jak wygląda zużycie zasobów między organizacjami. Do takich zastosowań przydatny jest Portainer

  3. Po trzecie - skala - docker pozwala nam łatwo skalować działanie dowolnego systemu. Oczywiście, jak w poprzednim przykładzie, możemy za jego pomocą rozłożyć usługi wielu organizacji po wielu serwerach i łatwo powiększać grono tych usług, kontrolując jednocześnie zużycie zasobów, ale też łatwo stworzyć na przykład farmę TORów, która pozwoli nam wykonywać mnóstwo operacji sieciowych jednocześnie, każdą z innym adresem IP ;).

Zastosowanie Dockera

Dockera instalujemy zgodnie z instrukcją na oficjalnej stronie Dockera - https://docs.docker.com/install/. Jeśli jesteście na Linuxie, nie zapomnijcie o dodaniu uprawnień do Waszego użytkownika, zgodnie z instrukcją na https://docs.docker.com/install/linux/linux-postinstall/.

Przyjrzyjmy się zatem podstawowym komendom.

docker ps

Pokazuje wszystkie aktualnie włączone kontenery dockerowe. Wraz z opcją –all pozwala pokazać wszystkie kontenery, również te wyłączone

docker run

Tworzy nowy kontener. Ważne opcje:

  • p PORT RODZICA:PORT KONTENERA - opcja pozwalająca przekierować port rodzica na port kontenera. W szczególności może być to ten sam port, na przykład by wygodnie przekierować bazę MySQL - p 3306:3306 tak, by system traktował ją “jak własną”

  • d - bardzo ważna funkcja - uruchamia kontener w stanie detached, czyli tak, że nie mamy do niego dostępu w bieżącej konsoli, uruchamia się on w tle. Najlepiej uruchamiać tak usługi

  • it - zbiór opcji, który podłącza nas do kontenera, dając możliwość kontrolowania go z pomocą basho-podobnego wiersza poleceń. Uzyteczne gdy uruchamiamy np. konsolę pythona.

  • –name - opcja umożliwiająca nadanie kontenerowi własnej nazwy - użyteczne na przykład po to, by potem wydawać temu kontenerowi polecenia, jak start czy stop.

  • e - opcja pozwalająca konfigurować obraz dockera (zmienne środowiskowe) - szczegóły na jej temat znajdziecie w przykładach wykorzystania wybranego obrazu dockerowego

  • – link KONTENER:NAZWA HOSTA - opcja pozwalająca połączyć ze sobą dwa kontenery. Wybrany kontener będzie dostępny z wewnątrz tworzonego kontenera jako NAZWA_HOSTA

Przykładowe usługi - MariaDB, phpmyadmin, elasticsearch, Redis, Metabase, czy Jupyter notebook

docker run -p 3306:3306 --name $NAME-mysql -e MYSQLROOTPASSWORD=root -e MYSQLDATABASE=$NAME -d mariadbdocker run --name $NAME-myadmin -d --link $NAME-mysql:db -p 8080:80 phpmyadmin/phpmyadmindocker run -p 9200:9200 -e ESJAVA_OPTS="-Xms512m -Xmx512m" --name $NAME-elasticsearch -d elasticsearchdocker run -p 6379:6379 --name $NAME-redis -d redisdocker run -d -p 10001:3000 --name metabase2 metabase/metabasedocker run -p 10000:8888 jupyter/scipy-notebook:17aba6048f44

docker start KONTENER i docker stop KONTENER

Odpowiednio uruchomienie wyłączonego kontenera i wyłączenie uruchomionego kontenera. Takie wyłączenie nie powoduje utraty danych, i zwalnia zasoby systemu

docker rm KONTENER

Usuwa kontener, i wszystkie dane nie umieszczone w nazwanych volume’ach danych.

docker exec KONTENER KOMENDA

Uruchamiamy komendę na danym kontenerze. W szczególności, można w ten sposób uruchomić na kontenerze basha.

docker-compowse up i down

Wspomniany wcześniej docker-compose pozwala konfigurować projekty w ramach jednego pliku i uruchamiać je komendą up i zamykać komendą down.

Persystencja danych

By zachować dane przy usunięciu kontenera (choćby dlatego, że każde uruchomienie docker-compose down usuwa kontenery), musimy wykorzystać wolumeny danych - dzięki nim możemy albo synchronizowac katalog kontenera z katalogiem na maszynie hosta:

// fragment docker-compose.ymlvolumes: - ./mysqldata:/var/lib/mysql

Lub jako named volumes - trzymane w głownym katalogu dockera - jak w przykładzie Wordpressa powyżej.

Użyteczne snippety

Podrzucam jeszcze kilka snippetów, które mogą się Was przydać.

docker stop $(docker ps -a -q)

Zatrzymanie wszystkich aktualnie uruchomionych kontenerów

docker rm $(docker ps -a -q)

Usunięcie wszystkich zatrzymanych kontenerów

docker exec -it /bin/bash

Wejście do danego kontenera bashem, by wykonać w nim jakiekolwiek manualne operacje.