Niedawno zacząłem prowadzić Grupę .NET na Uniwersytecie Warszawskim i, ponieważ niedługo zaczniemy pierwszy projekt, postanowiłem założyć serwis Git. Dotychczas pracowałem z GitLabem, ale nie przypadł mi specjalnie do gustu. Dlatego gdy odkryłem Go Git Service postanowiłem go wdrożyć.
Gogs ma kilka metod instalacji, a jedna z nich to użycie kontenerów Dockera. Od pewnego czasu słyszałem o tej technologii, ale nie wiedziałem jak działa, ani do czego dokładnie służy. Teraz kiedy udało mi się opanować ją w stopniu jaki potrzebny był do postawienia serwera opartego o kontenery, postanowiłem opisać tu jak jej używać.
Jak to działa?
Docker posługuje się technologią konteneryzacji zaimplementowaną w kernelu Linuxa. Każdy obraz to zbiór plików user-space’u Linuxa. Tworząc kontener bierzemy pliki z obrazu i używamy ich jako ,,wirtualnej maszyny”, która korzysta z naszego działającego kernela, który zapewnia odseparowanie procesów tego kontenera od procesów hosta, czyli naszego systemu. Zaletą tego rozwiązania w stosunku do używania maszyn wirtualnych jest to, że nie mamy duplikacji kernela - całego systemu zarządzania pamięcią i dyskiem. Więc nasze kontenery są mniejsze i szybsze.
Dodatkowo, każdy obraz jest skonstruowany z warstw połączonych przez UnionFS, więc pobierając dwie aplikacje oparte o Ubuntu, ściągniemy warstwę zawierającą pliki Ubuntu tylko raz. W trakcie działania aplikacji wszelkie modyfikacje plików i folderów systemowych są kolejną warstwą, więc nie dochodzi do kopiowania wszystkich plików do nowego kontenera, tylko ich współdzielenia.
Instalacja
Po pierwsze chcemy zainstalować Docker. Jest on dostępny przede wszystkim na Linuxie, ale także na Windowsie i Mac OS X, gdzie jest oparty o VirtualBox. Ponieważ ja pracowałem na maszynie wirtualnej z Debianem, będę opisywał proces ze strony Linuxa.
Postępując zgodnie z Getting Started: Step 1, uruchomiłem
curl -fsSL https://get.docker.com/ | sh
i po chwili miałem zainstalowany Docker. Aby uruchamiał się on ze startem systemu, zrobiłem
sudo systemctl enable docker
sudo systemctl start docker
Normalnie Docker potrzebuje uprawnień administratora do działania i żeby nie musieć ciągle wpisywać sudo
, należy dodać użytkownika do grupy docker
sudo usermod -aG docker user
Uruchamianie aplikacji
Zaczniemy od standardowej aplikacji hello-world
. Aby uruchomić aplikację użyjemy
docker run [OPTIONS] image[:version] [COMMAND]
W naszym przypadku będzie to po prostu
docker run hello-world
Jeśli nie podamy wersji obrazu to automatycznie będzie to latest
. Jeśli nie posiadamy obrazu w naszym systemie zobaczymy komunikat
Unable to find image `hello-world:latest` locally
Po czym zostanie od ściągnięty z domyślnego rejestru hub.docker.com i uruchomiony.
Kilka opcji
Po pierwsze, wiele aplikacji, które będziemy uruchamiać to będą daemony i nie będziemy chcieli aby zajmowały nasz terminal. Aby aplikacja działała w tle, należy ją uruchomić z opcją -d
, czyli ,,detached”. W kolejnych przykładach będziemy posługiwali się obrazem Ubuntu.
docker run -d ubuntu
Jak sprawdzić czy aplikacja działa?
docker ps
Powyższe polecenie pokaże nam działające kontery
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Ale jest pusto. Czy Ubuntu się nie uruchomił? Uruchomił się, nie miał nic do roboty więc się zakończył. A co może się dziać? Możemy wejść w tryb interaktywny, o czym niżej, lub wywołać jedno polecenie.
$ docker run ubuntu ls /dev
core fd full fuse mqueue null ptmx pts random shm stderr stdin stdout tty urandom zero
Dodając parametr -a
do polecenia ps
zobaczymy wszystkie kontenery, także te które się zatrzymały.
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6d27601a1401 ubuntu "/bin/bash" About a minute ago Exited (0) About a minute ago dreamy_franklin
3d8188660fe2 hello-world "/hello" 11 minutes ago Exited (0) 11 minutes ago jovial_yalow
Jeśli będziemy chcieli zrobić coś więcej w aplikacji, będziemy potrzebowali dwóch innych argumentów
-i
- ,,interactive” przekazuje dane z klawiatury na STDIN-t
- ,,tty” uruchamia terminal
Spróbujmy teraz
$ docker run -it ubuntu
root@a278d4ca6959:/ #
Żeby przekonać się, że naprawdę mamy do czynienia z systemem zróbmy
root@a278d4ca6959:/ # ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
Dalej przekonajmy się, że pracujemy na tym samym kernelu
root@a278d4ca6959:/ # uname -a
Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt25-1 (2016-03-06) x86_64 x86_64 x86_64 GNU/Linux
I to samo na hoście
$ uname -a
Linux 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt25-1 (2016-03-06) x86_64 GNU/Linux
Jeśli uruchomiliśmy kontener w trybie detached i chcemy jednak uzyskać nad nim kontrolę to możemy posłużyć się
docker attach [OPTIONS] CONTAINER
Natomiast jeśli nie chcemy uzyskiwać pełnej kontroli, bo np. aplikacja uniemożliwia nam zrobienie czegokolwiek, możemy wywołać jakieś polecenie w kontenerze przez
docker exec [OPTIONS] CONTAINER COMMAND [ARGS...]
Usuwanie i nazwy
Mamy w tej chwili 3 wyłączone kontenery i chcielibyśmy się ich pozbyć. Do tego służy polecenie rm
, któremu należy podać nazwę lub hash kontenera
docker rm 3d8188660fe2 #usuwamy hello-world
docker rm dreamy_franklin #usuwamy pierwszy kontener z Ubuntu
Co to za nazwa kontenera? Domyślnie jest to losowa nazwa złożona z przymiotnika i nazwiska jakiegoś znanego naukowca. Ale możemy nazywać samemu nasze kontenery
docker run --name my_ubuntu ubuntu
Aby usunąć wszystkie nasze kontenery posłużymy się
docker rm $(docker ps -aq)
Gdzie polecenie w nawiasach wypisuje nam wszystkie kontenery, ale tylko ich hashe (-q
).
Dlaczego chcielibyśmy nazywać nasze kontenery? Bo łatwiej się ich wtedy używa. Za pomocą polecenia inspect
możemy zobaczyć ustawienia naszego kontenera, a za pomocą polecenia logs
przejrzeć wyjście naszej aplikacji. Oba potrzebują nazwy lub hashu kontenera.
Wolumeny
Czasem możemy mieć potrzebę aby ten nasz kontener nie był całkowicie odizolowany od naszego systemu. Więc możemy współdzielić z nim foldery lub pliki. Powiedzmy, że mam folder ~/html
i chciałbym go mieć w moim Ubuntu w folderze /var/www
.
$ docker run -it -v ~/html:/var/www/html ubuntu
/ # cd /var/www/html; touch file.html
/ # exit
$ ls ~/html
file.html
Publiczne porty
Możemy być zainteresowani aby udostępnić pewne porty kontenera na zewnątrz. Np chcemy opublikować stronę internetową. Pobierzemy aplikację NGINX
docker pull nginx
następnie utworzymy plik konfiguracyjny ~/nginx.conf
, w którym wpiszemy
events {}
http {
server {
listen 80;
location / {
root /var/www/html;
}
}
}
A następnie uruchamiamy
docker run -d -p 8080:80 -v ~/nginx.conf:/etc/nginx/nginx.conf -v ~/html:/var/www/html --name webserver nginx
Parametr -p
, czyli publish, określa mapowanie portu 80 kontenera do portu 8080 hosta. Następnie mamy dwa połączenia wolumenów: pliku konfiguracyjnego i folderu z zawartością strony. Teraz wystarczy utworzyć prosty plik index.html
, wrzucić go do ~/html
, wejść w przeglądarce na http://localhost:8080/
i voila.
Gy będziemy chcieli zatrzymać nasz serwer wystarczy
docker stop webserver
A jeśli będziemy chcieli uruchomić go ponownie
docker start webserver
Łączenie aplikacji
Każdy kontener w trakcie tworzenie ma przypisany adres IP, który możemy sprawdzić przez docker inspect
. Używanie go nie jest jednak specjalnie wygodne, szczególnie jeśli ten adres będzie się zmieniał.
Jako przykład wezmę dwa kontenery: serwer http z php i serwer smtp.
docker run -d --name smtp namshi/smtp
docker run -d --name webserver [...] --link smtp:smtp nginx
--link {name|hash}:alias
tworzy połączenie sieciowe między kontenerami i dodaje wpis do pliku /etc/hosts
o nazwie alias
i wskazuje na adres IP wskazanego kontenera.
Dzięki temu nie musimy ustawiać portów jako publicznych dla aplikacji, które porozumiewają się lokalnie.
Więcej o możliwościach sieciowych.
Praktyczne zastosowanie
Kiedy warto korzystać z kontenerów? Jeśli chcemy szybko zainstalować aplikację wraz z jej zależnościami. Kiedy musimy korzystać z dwóch niekompatybilnych ze sobą aplikacji. Kiedy to jedyna droga.
Odkąd usłyszałem, że Microsoft udostępnia open-source’owy .NET Core, chciałem wypróbować jak to działa. Ponieważ na co dzień pracuję na Manjaro opartym o Arch Linux, który nie jest wspierany przez MS, miałem z tym trudności. Dzięki Dockerowi wystarczyło jedno polecenie aby ściągnąć gotowy obraz oparty o Ubuntu z zainstalowanym .NET Core
docker pull microsoft/dotnet
I mogę teraz bawić bez konieczności instalacji drugiego systemu lub wirtualizacji.
Podsumowanie
Kontenery ułatwiają życie głównie ze względu na prostotę użycia. Jeśli skonfiguruję sobie kontener mogę utworzyć własny obraz i zuploadować go do rejestru, aby inni mogli z niego korzystać.
Cały ten blog post to tylko zalążek możliwości i API Dockera, który jest wystarczający aby zacząć się kontenerami bawić. Nazwałbym go minimalistycznym szerokim tutorialem :) Większość informacji jest ze strony docs.docker.com, w której wszystkie funkcje Dockera są podzielone na tematy i omówione dokładniej.