Docker - podstawy zarządzania obrazami

Red Hat 7.x / CentOS 7.x
Jak wspomniałem w poprzedniej części (Docker - instalacja i zarządzanie kontenerami) podstawą uruchomienia kontenera w naszym środowisku są obrazy (images). W tej części zajmiemy się tym aspektem architektury Dockera.

Zarządzanie obrazami Dockera

Podstawowym, domyślnie skonfigurowanym repozytorium obrazów dockera jest hub.docker.com. Dostępne dla nas obrazy możemy przeglądać poprzez stronę www pod tym adresem. Znajdziemy tam opisy obrazów, informacje o wersji, tagi oraz komentarze i opinie użytkowników dockera na temat danego systemu.

Wizyta na stronie nie jest jednak jedyną metodą na wyszukiwanie w repozytorium interesujących nas obrazów. Możemy to robić również bezpośrednio z poziomu serwera przy pomocu polecenia docker search

[docker@docker-base ~]$ docker search ubuntu
INDEX       NAME                                        DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
docker.io   docker.io/ubuntu                            Ubuntu is a Debian-based Linux operating s...   3489      [OK]
docker.io   docker.io/ubuntu-upstart                    Upstart is an event-based replacement for ...   60        [OK]
docker.io   docker.io/torusware/speedus-ubuntu          Always updated official Ubuntu docker imag...   25                   [OK]
docker.io   docker.io/ubuntu-debootstrap                debootstrap --variant=minbase --components...   24        [OK]
docker.io   docker.io/rastasheep/ubuntu-sshd            Dockerized SSH service, built on top of of...   23                   [OK]
docker.io   docker.io/neurodebian                       NeuroDebian provides neuroscience research...   20        [OK]
docker.io   docker.io/nickistre/ubuntu-lamp             LAMP server on Ubuntu                           6                    [OK]
docker.io   docker.io/nickistre/ubuntu-lamp-wordpress   LAMP on Ubuntu with wp-cli installed            5                    [OK]
docker.io   docker.io/nuagebec/ubuntu                   Simple always updated Ubuntu docker images...   4                    [OK]
docker.io   docker.io/nimmis/ubuntu                     This is a docker images different LTS vers...   3                    [OK]
docker.io   docker.io/maxexcloo/ubuntu                  Docker base image built on Ubuntu with Sup...   2                    [OK]
docker.io   docker.io/avatao/ubuntu                     Ubuntu for challenges                           1                    [OK]
docker.io   docker.io/darksheer/ubuntu                  Base Ubuntu Image -- Updated hourly             1                    [OK]
docker.io   docker.io/jordi/ubuntu                      Ubuntu Base Image                               1                    [OK]
docker.io   docker.io/esycat/ubuntu                     Ubuntu LTS                                      0                    [OK]
docker.io   docker.io/konstruktoid/ubuntu               Ubuntu base image                               0                    [OK]
docker.io   docker.io/life360/ubuntu                    Ubuntu is a Debian-based Linux operating s...   0                    [OK]
docker.io   docker.io/lynxtp/ubuntu                     https://github.com/lynxtp/docker-ubuntu         0                    [OK]
docker.io   docker.io/rallias/ubuntu                    Ubuntu with the needful                         0                    [OK]
docker.io   docker.io/suzlab/ubuntu                     ubuntu                                          0                    [OK]
docker.io   docker.io/teamrock/ubuntu                   TeamRock's Ubuntu image configured with AW...   0                    [OK]
docker.io   docker.io/ustclug/ubuntu                    ubuntu image for docker with USTC mirror        0                    [OK]
docker.io   docker.io/uvatbc/ubuntu                     Ubuntu images with unprivileged user            0                    [OK]
docker.io   docker.io/webhippie/ubuntu                  Docker images for ubuntu                        0                    [OK]
docker.io   docker.io/widerplan/ubuntu                  Our basic Ubuntu images.                        0                    [OK]

Wynik polecenia zwraca nam w postaci kolumnowej informacje o nazwie repozytorium w którym znajduje się dany obraz, jego nazwę, krótki opis, ocenę użytkowników (STARS), a także informację o tym, czy dany obraz jest oficjalnie wspierany przez zespół dockera (OK w kolumnie OFFICIAL). Na tej podstawie możemy pobierać obrazy do lokalnego repozytorium serwera. Niestety póki co polecenie to nie umożliwia nam podglądania tagów obrazów, które są przydatne, jeśli zależy nam na konkretnej wersji.

Wspomniałem wcześniej o repozytorium lokalnym. Trafia do niego każdy obraz, na podstawie którego tworzony jest kontener. W dowolnej chwili możemy przejrzeć jego zawartość i zweryfikować jakie obrazy aktualnie posiadamy:

[docker@docker-base ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
docker.io/busybox   latest              bc744c4ab376        6 days ago          1.113 MB
docker.io/httpd     latest              ee4a5faa57f7        3 weeks ago         193.3 MB

Znajdziemy tu informacje z jakiego repozytorium obraz pochodzi oraz jego nazwę, tag danego obrazu, ID obrazu, kiedy został utworzony oraz jaki zajmuje obszar dyskowy.

Wiemy jak sprawdzać repozytoria, czas zatem poznać metody pracy z nimi.

W pierwszej kolejności zależeć nam będzie na pobieraniu obrazów. Będziemy potrzebowali do tego znajomości nazwy obrazu oraz ewentualnie taga wersji. Konwencja nazewnicza przy operacjach na obrazach wygląda następująco: nazwa_repo/nazwa_obrazu:tag (w docker search wyświetlane jako docker.io/nazwa_repo/nazwa_obrazu). Jeśli pominiemy nazwę repo obraz będzie pobrany z domyślnego repozytorium (library, czyli obraz widniejący w docker search jako docker.io/nazwa_obrazu). Jeśli pominiemy tag operować będziemy na pakietach otagowanych jako latest.

[docker@docker-base ~]$ docker pull ubuntu
Using default tag: latest
Trying to pull repository docker.io/library/ubuntu ... latest: Pulling from library/ubuntu

808ef855e5b6: Pull complete
267903aa9bd1: Pull complete
d28d8a6a946d: Pull complete
ab035c88d533: Pull complete
Digest: sha256:1bea66e185d3464fec1abda32ffaf2a11de69833cfcf81bd2b9a5be147776814
Status: Downloaded newer image for docker.io/ubuntu:latest

Tym sposobem pobraliśmy do lokalnego repo obraz ubuntu w wersji latest. Na potwierdzenie sprawdzamy zawartość repo lokalnego:

[docker@docker-base ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
docker.io/ubuntu    latest              ab035c88d533        6 days ago          187.9 MB
docker.io/busybox   latest              bc744c4ab376        6 days ago          1.113 MB
docker.io/httpd     latest              ee4a5faa57f7        3 weeks ago         193.3 MB

Możemy pobrać konkretną wersję (sprawdzając tagi na stronie hub.docker.com), np:

[docker@docker-base ~]$ docker pull ubuntu:14.04
Trying to pull repository docker.io/library/ubuntu ... 14.04: Pulling from library/ubuntu

808ef855e5b6: Already exists
267903aa9bd1: Already exists
d28d8a6a946d: Already exists
ab035c88d533: Already exists
Digest: sha256:1c8b813b6b6656e9a654bdf29a7decfcc73b92a62b934adc4253b0dc2be9d0a2
Status: Downloaded newer image for docker.io/ubuntu:14.04

Hmmm... niespodzianka - otrzymujemy komunikat "Alredy exists" i nic się nie pobiera, sprawdźmy zatem nasze lokalne repo:

[docker@docker-base ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
docker.io/ubuntu    latest              ab035c88d533        6 days ago          187.9 MB
docker.io/ubuntu    14.04               ab035c88d533        6 days ago          187.9 MB
docker.io/busybox   latest              bc744c4ab376        6 days ago          1.113 MB
docker.io/httpd     latest              ee4a5faa57f7        3 weeks ago         193.3 MB

Okazuje się, że wersja otagowana jako 14.04 to ten sam obraz (taki sam IMAGE ID) co wersja latest - obraz się nie pobrał bo faktycznie jest już na dysku. W lokalnym repo pojawił się jednak dodatkowy wpis otagowany jako 14.04.

Kolejnym poleceniem, które pomoże nam w zarządzaniu obrazami będzie docker rmi. Polecenie pozwala usunąć z dysku niepotrzebne już obrazy, zwalniając przy tym zajmowane przez nie miejsce. Do usunięcia obrazu potrzebować będziemy jego nazwy oraz taga, lub IMAGE ID. Jedna uwaga: nie uda się nam usunąć obrazu, jeśli istnieje korzystający z niego kontener! Przy czym nie jest ważne czy kontener jest aktywny czy nie - jeśli obraz istnieje w liście docker ps -a usuwanie zakończy się błędem.

[docker@docker-base ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
docker.io/ubuntu    latest              ab035c88d533        6 days ago          187.9 MB
docker.io/ubuntu    14.04               ab035c88d533        6 days ago          187.9 MB
docker.io/busybox   latest              bc744c4ab376        6 days ago          1.113 MB
docker.io/httpd     latest              ee4a5faa57f7        3 weeks ago         193.3 MB
[docker@docker-base ~]$ docker rmi bc744c4ab376
Untagged: docker.io/busybox:latest
Deleted: bc744c4ab376115cc45c610d53f529dd2d4249ae6b35e5d6e7a96e58863545aa
Deleted: 56ed16bd6310cca65920c653a9bb22de6b235990dcaa1742ff839867aed730e5
[docker@docker-base ~]$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
docker.io/ubuntu    14.04               ab035c88d533        6 days ago          187.9 MB
docker.io/ubuntu    latest              ab035c88d533        6 days ago          187.9 MB
docker.io/httpd     latest              ee4a5faa57f7        3 weeks ago         193.3 MB

Jak widać obraz zniknął z naszego repo.

Muszę tu jeszcze wspomnieć o jednym przypadku - a mianowicie naszym obrazie ubuntu. W repozytorium widnieje dwukrotnie, ale ma ten sam image id. Usunięcie obrazu z wykorzystaniem tego id poprzez docker rmi ab035c88d533 usunie z repo plik obrazu, więc znikną obie pozycje. Jeśli chcemy usunąć tylko jedną z nich należy posłużyć się ich nazwą oraz tagiem: docker rmi ubuntu:14.04.

TIP: jeśli chcemy szybko wyczyścić nasze lokalne repo usuwając wszystkie dostępne obrazy, możemy to zrobić przez docker rmi $(docker images -q)

Tworzenie własnych obrazów

Kontenery tworzone na bazie obrazów, póki istnieją w systemie są tworami trwałymi, jeśli chodzi o zmiany wewnątrz nich. Tzn. jeśli utworzysz kontener na bazie obrazu i zmienisz w nim konfigurację, przetrwa ona restarty kontenera. Będzie ona jednak zmieniona tylko w jego obrębie, więc jeśli utworzysz drugi kontener, na bazie tego samego obrazu - konfiguracja będzie domyślną konfiguracją obrazu.
Dlatego szybko odkryjesz potrzebę tworzenia własnych obrazów. Choćby taka sytuacja: chcesz uruchomić 20 systemów ubuntu. W każdym z nich, dajmy na to, ma istnieć 5 Twoich userów, ma on korzystać z customowego pliku /etc/hosts i ma mieć doinstalowany jakiś pakiet. Możesz rzecz jasna logować się do każdego kontenera i wprowadzać te zmiany... ale po co?

Przydatna będzie tu pierwsza z metod tworzenia własnych obrazów dockera - obraz na podstawie kontenera. Tworzysz kontener bazowy, na którym wprowadzasz wymagane zmiany. Może to być dodanie plików, pakietów, użytkowników, zmiany w konfiguracji, cokolwiek innego. Po zakończeniu konfiguracji tworzysz obraz, na podstawie którego możesz uruchomić kolejne kontenery, i będą one dziedziczyły konfigurację z Twojego obrazu.

Szybki przykład. Uruchamiamy kontener na bazie obrazu ubuntu:

[root@docker-base ~]# docker run -itd ubuntu
5be36e595f63dec02677fc9f43068274eb58abb5de8b341c16848a917b19ad3d
[root@docker-base ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
5be36e595f63        ubuntu              "/bin/bash"         5 seconds ago       Up 3 seconds                            adoring_heisenberg

W kontenerze tworzymy usera, np. o nazwie docker:

[root@docker-base ~]# docker exec -it adoring_heisenberg bash
root@5be36e595f63:/# id docker
id: docker: no such user
root@5be36e595f63:/# useradd docker
root@5be36e595f63:/# id docker
 uid=1000(docker) gid=1000(docker) groups=1000(docker)

Jak widać, domyślnie usera nie było, ale udało się go utworzyć. Stwórzmy zatem nasz obraz. Użyjemy do tego polecenia docker commit nazwa_kontenera nazwa_nowego_obrazu:

[root@docker-base ~]# docker commit adoring_heisenberg ubuntu_z_userem
bdb8fa2baf1462bba28cb481db4310cf7f93e4b30d157a55d913e24c0ba11021
[root@docker-base ~]# docker images
REPOSITORY                TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu_z_userem           latest              bdb8fa2baf14        5 seconds ago       188.3 MB
docker.io/ubuntu          latest              ab035c88d533        2 weeks ago         187.9 MB
docker.io/busybox         latest              bc744c4ab376        2 weeks ago         1.113 MB

Polecenie utworzyło nam nowy obraz... Tworzymy na jego podstawie kolejny kontener:

[root@docker-base ~]# docker run -itd ubuntu_z_userem
4c3b3a0891921da5b659add59d35d07d3e4b492cb4c873a3bed8ce927349f437
[root@docker-base ~]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
4c3b3a089192        ubuntu_z_userem     "/bin/bash"         4 seconds ago       Up 2 seconds                            reverent_jones
5be36e595f63        ubuntu              "/bin/bash"         8 minutes ago       Up 8 minutes                            adoring_heisenberg
[root@docker-base ~]# docker exec -it reverent_jones bash
root@4c3b3a089192:/# id docker
uid=1000(docker) gid=1000(docker) groups=1000(docker)

Nasz nowy kontener posiada prekonfigurowanego usera docker.
Metoda ta pozwala nam na stworzenie obrazu bazowego do dalszych zabaw, ale również możemy ją wykorzystywać do zapisywania kamieni milowych naszej pracy :)

Druga z metod polega na budowaniu obrazów na podstawie plików konfiguracyjnych Dockerfile, ale temat jest na tyle obszerny i zaawansowany, że nie będę go poruszał na etapie zaznajamiania się z Dockerem. Jest również na tyle interesujący, że z pewnością popełnię o nim jakiś tekst.

Dodatkowe informacje o obrazach

Docker pozwala nam uzyskać pewne informacje na temat dostępnych obrazów. Pierwszym poleceniem, które możemy zastosować również w odniesieniu do kontenera jest docker inspect. Polecenie wyświetla nam w formie tablicy json-a parametry konfiguracyjne, jakie zostaną użyte do uruchomienia kontenera. Przykładowy output komendy dla obrazu ubuntu:

[root@docker-base ~]# docker inspect ubuntu
[
{
    "Id": "ab035c88d533b656f25574a9f6f6dde8e8a9badf004d748690e9ee0b17205781",
    "Parent": "d28d8a6a946d1a1b25a6f4b438d1e92858a17bc58e15c5945d3ae12753a2883d",
    "Comment": "",
    "Created": "2016-03-18T18:24:28.818918568Z",
    "Container": "85c4705d113ecf7f3ae704f1b7597c5b6bc919fb8ac83e3fa5987ab49cc8153e",
    "ContainerConfig": {
        "Hostname": "f5ee59d47428",
        "Domainname": "",
        "User": "",
        "AttachStdin": false,
        "AttachStdout": false,
        "AttachStderr": false,
        "ExposedPorts": null,
        "PublishService": "",
        "Tty": false,
        "OpenStdin": false,
        "StdinOnce": false,
        "Env": [],
        "Cmd": [
            "/bin/sh",
            "-c",
            "#(nop) CMD [\"/bin/bash\"]"
        ],
        "Image": "d28d8a6a946d1a1b25a6f4b438d1e92858a17bc58e15c5945d3ae12753a2883d",
        "Volumes": null,
        "VolumeDriver": "",
        "WorkingDir": "",
        "Entrypoint": null,
        "NetworkDisabled": false,
        "MacAddress": "",
        "OnBuild": null,
        "Labels": {}
    },
    "DockerVersion": "1.9.1",
    "Author": "",
    "Config": {
        "Hostname": "f5ee59d47428",
        "Domainname": "",
        "User": "",
        "AttachStdin": false,
        "AttachStdout": false,
        "AttachStderr": false,
        "ExposedPorts": null,
        "PublishService": "",
        "Tty": false,
        "OpenStdin": false,
        "StdinOnce": false,
        "Env": [],
        "Cmd": [
            "/bin/bash"
        ],
        "Image": "d28d8a6a946d1a1b25a6f4b438d1e92858a17bc58e15c5945d3ae12753a2883d",
        "Volumes": null,
        "VolumeDriver": "",
        "WorkingDir": "",
        "Entrypoint": null,
        "NetworkDisabled": false,
        "MacAddress": "",
        "OnBuild": null,
        "Labels": {}
    },
    "Architecture": "amd64",
    "Os": "linux",
    "Size": 0,
    "VirtualSize": 187939858,
    "GraphDriver": {
        "Name": "devicemapper",
        "Data": {
            "DeviceId": "357",
            "DeviceName": "docker-253:1-8396148-ab035c88d533b656f25574a9f6f6dde8e8a9badf004d748690e9ee0b17205781",
            "DeviceSize": "107374182400"
        }
    }
}
]

Możemy zobaczyć tu jakie parametry były zdefiniowane dla obrazu - ustawienia sieciowe, komendy inicjacyjne kontenera, informacje o autorze, konfiguracji obrazu itd. Większość z nich definiowana jest w pliku Dockerfile użytym do stworzenia obrazu. Na początku znajomości z dockerem informacje te nie koniecznie muszą okazać się przydatne (przynajmniej w odniesieniu do obrazów).

Kolejnym poleceniem, które dostarczy nam informacji będzie docker history. Wyświetla ono historię (przynajmniej w teorii) poleceń, które zostały wykonane przed zacommitowaniem obrazu. Celowo piszę, że "przynajmniej w teorii", bo wpisy te są dla mnie zrozumiałe do momentu, kiedy tyczą się oryginalnego obrazu. Dobrze widać to na przykładzie tworzonego wcześniej "ubuntu_z_userem":

[root@docker-base ~]# docker history ubuntu_z_userem
IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
bdb8fa2baf14        About an hour ago   /bin/bash                                       329.3 kB
ab035c88d533        2 weeks ago         /bin/sh -c #(nop) CMD ["/bin/bash"]             0 B
d28d8a6a946d        2 weeks ago         /bin/sh -c sed -i 's/^#\s*\(deb.*universe\)$/   1.895 kB
267903aa9bd1        2 weeks ago         /bin/sh -c set -xe                                                  && echo '#!/bin/sh' > /u   194.5 kB
808ef855e5b6        2 weeks ago         /bin/sh -c #(nop) ADD file:e01d51d39ea04c8efb   187.7 MB

Nasze dodanie usera przedstawia się tu jako /bin/bash. Dla mnie totalnie niezrozumiałe, ale pewnie to kwestia poziomu zaawansowania.

Docker - instalacja i podstawy zarządzania kontenerami

Red Hat 7.x / CentOS 7.x
Docker to nowe spojrzenie na wirtualizację. Zamiast tradycyjnych maszyn wirtualnych wprowadza on niezależnie działające kontenery usług oparte o środowisko gospodarza, co zwiększa wydajność i elastyczność środowiska. Teorię można sobie doczytać w internecie, a nic tak nie przybliża do technologii, jak ćwiczenia praktyczne, więc bez zbędnych wstępów zaczynamy.

Instalacja

Jako środowiska bazowego używać będę systemu CentOS 7.2. Poza lokalizacją plików konfiguracyjnych dockera i procesem instalacji samo używanie go w tym srodowisku nie róźni się od innych platform.

Instalację możemy wykonać na trzy sposoby
- używając yum oraz systemowego repozytorium - yum install docker - w ten sposób instalujemy wersję zatwierdzoną przez CentOSa/Red Hata
- używając yum i konfigurując repozytorium dockera
- korzystając ze skryptu instalacyjnego dockera, który doda nam repozytorium i zainstaluje binaria curl -fsSL https://get.docker.com/ | sh

Jeśli zależy nam na aktualnej wersji najlepiej będzie użyć metody trzeciej, a jeśli wystarczy nam wersja wcześniejsza, ale "wygrzana" skorzystać możemy z metody pierwszej. Ja skorzystam z pierwszej.

[root@docker-base ~]# yum install docker

Możemy zweryfikować wersję zainstalowanego pakietu poprzez:

[root@docker-base ~]# docker --version
Docker version 1.8.2-el7.centos, build a01dc02/1.8.2

 Aby nie działać na koncie root-a, ale z drugiej strony nie robić wszystkiego przez sudo możemy dodać swojego użytkownika do grupy docker poprzez usermod -aG docker nasz_user. Jeśli grupa nie istnieje, należy ją utworzyć (groupadd docker).
Domyślnie serwis dockera nie jest wystartowany, więc trzeba do wystartować i zadbać, żeby startował wraz z systemem:

[root@docker-base ~]# systemctl enable docker
[root@docker-base ~]# systemctl start docker

I tu w zasadzie kończy się pierwszy etap - docker jest gotowy do użycia.

Zarządzanie kontenerami

Podstawowym poleceniem dockera, którego używać będziemy najczęściej jest wyświetlanie kontenerów działających na naszym hoście:

[docker@docker-base ~]$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

W wyniku jego wywołania uzyskamy listę działających w środowisku kontenerów. Lista jest wyświetlana w postaci kolumn, które oznaczają odpowiednio:
CONTAINER_ID - identyfikator uruchomionego kontenera
IMAGE - identyfikator obrazu z którego kontener został uruchomiony (o obrazach później)
COMMAND - polecenie wykonywane wewnątrz kontenera podczas jedo inicjalizacji
CREATED - czas utworzenia kontenera
STATUS - obecny status kontenera
PORTS - wykaz mapowanych do kontenera portów (opiszę przy okazji sieci w ramach kontenerów)
NAMES - nazwa naszego kontenera

Polecenie to posiada jeszcze jeden często używany przełącznik docker ps -a, który wyświetla wszystkie kontenery w systemie - również te z różnych powodów nieaktywne.

Jako, że temat zarządzania kontenerami jest ściśle powiązany z innym aspektem - zarządzaniem obrazami - na pierwszy rzut oka polecenia mogą nasuwać wiele pytań. Póki co przyjmij składnię jako pewnego rodzaju kanon, a wszystko rozjaśni się po poznaniu tematu obrazów. Temat ten jest szeroki, więc pewnie pojawi się w osobnym wpisie.

Na początek całą naszą pracę oprzemy o kontener, który dostarczy nam niewielki system operacyjny o nazwie busybox. Rozpocznijmy więc naszą przygodę.

Uruchamianie nowego kontenera


Jako, że tekst jest początkiem początków przygody z Dockerem nie będę tu przedstawiał wszystkich przydatnych opcji uruchomienia kontenera (pewnie później pojawi się zbiorówka tych opcji w osobnym tekście), a jedynie te niezbędne do uruchomienia najprostrzego funkcjonalnego kontenera.

Każdy kontener uruchamiany jest w naszym systemie na bazie posiadanych przez nas obrazów. Obrazy takie - jeśli jeszcze nie istnieją w lokalnym repozytorium są pobierane przez serwer dockera z oficjalnego repozytorium. Proces ten powoduje, że uruchomienie pierwszej instancji kontenera może trwać dość długo, ale nie należy się tym zrażać.
Podstawowa składnia uruchamiająca kontener wyglądanastępująco: docker run nazwa_obrazu. Zastosujmy ją więc praktycznie, opierając się o rzeczywisy obraz systemu wymienianego wcześniej:

[docker@docker-base ~]$ docker run busybox
Unable to find image 'busybox:latest' locally
Trying to pull repository docker.io/library/busybox ... latest: Pulling from library/busybox

56ed16bd6310: Extracting [==================================================>]   676 kB/676 kB
bc744c4ab376: Download complete

Jak zaobserwujemy docker pobiera sobie obraz busybox, aby uruchomić kontener. Po zakończeniu procesu moglibyśmy się spodziewać, że zastaniemy uruchomiony kontener z systemem busybox - sprawdźmy zatem:

[docker@docker-base ~]$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Pusto. Pomimo tego co mogłoby się wydawać, proces uruchomienia kontenera przebiegł właściwie. Wyjaśnienie sytuacji jest proste i widoczne w listingu wszystkich kontenerów:

[docker@docker-base ~]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
cf7e33398159        busybox             "sh"                3 minutes ago       Exited (0) 3 minutes ago                       dreamy_morse

Zrzut ten pokazuje nam, że kontener został utworzony 3 minuty wcześniej, a w chwili obecnej ma status Exited(0). Kontener wyłączył się, ponieważ operacją jaką miał za zadanie wykonać była egzekucja komendy sh. Udało się ją wykonać, więc kontener zrobił co miał do zrobienia i przestał być potrzebny.
Mój pierwszy kontakt z kontenerami opraty był na tym przykładzie, i szczerze mówiąc bardziej oddalał mnie od tej technologii niż do niej zbliżał. Po co komu "twór", który po uruchomieniu wchodzi do sh i się wyłącza? Kolejny wykopany przykład użycia był równie bezsensowny:

[docker@docker-base ~]$ docker run busybox echo "Echo kontrolne"
Echo kontrolne
[docker@docker-base ~]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                      PORTS               NAMES
2033115ffe8c        busybox             "echo 'Echo kontrolne"   3 minutes ago       Exited (0) 3 minutes ago                        berserk_brown
cf7e33398159        busybox             "sh"                     13 minutes ago      Exited (0) 13 minutes ago                       dreamy_morse

Przykład demonstruje jak przekazywać parametry do kontenera - nakazujemy tu odpalenie kontenera i wykonanie w nim echa... po co to komu? i jak to się ma do maszyny wirtualnej? Kontener znów się wyłączył - wykonał swoje zadanie, więc nie jest już do niczego przydatny....
Przykłady te są chyba najsłabszą reklamą technologii jaką widziałem, dlatego warto o nich szybko zapomnieć. Zróbmy więc coś, co przekona nas do Dockera, i udowodni słuszność koncepcji. Założeniem działania Dockera jest szybkie i niskozasobowe dostarczanie usługi, a dobrym przykładem jego realizacji może być prosty serwer www.
Warto tu poznać pierwsze przydatne opcje komendy docker run, a mianowicie -d - pozwala ona uruchomić kontener w tzw. detached mode, czyli oddzielić działanie  kontenera od naszej powłoki shell, oraz -P, które mapuje wszystkie porty kontenera na porty naszego hosta.
Stwórzmy zatem wspomiany kontener hostujący stronę www.

[docker@docker-base ~]$ docker run -d -P httpd
Unable to find image 'httpd:latest' locally
Trying to pull repository docker.io/library/httpd ... latest: Pulling from library/httpd

d8bd0657b25f: Pull complete
a582cd499e0f: Pull complete
4d035354d707: Pull complete
152d136f2b33: Pull complete
36c9fec13bf5: Pull complete
2e56fcbe3fad: Pull complete
ceef08ecfd76: Pull complete
a023717bbace: Pull complete
1fcf3155d7dc: Pull complete
698af7c24a15: Pull complete
cca3d6822dae: Pull complete
c7c39c3750a6: Pull complete
ee4a5faa57f7: Pull complete
Digest: sha256:4aba8de9c396e4f20d682fbc9ee8e06fd91700fdbb88ed36157d6f6487a99da7
Status: Downloaded newer image for docker.io/httpd:latest

22b8aba2db983f1ef6340118ab797a3f8e88680a24e680d3e77c1090264e5357
[docker@docker-base ~]$ docker ps
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                   NAMES
22b8aba2db98        httpd               "httpd-foreground"   8 seconds ago       Up 6 seconds        0.0.0.0:32768->80/tcp   jolly_meitner

Analogicznie do poprzedniego kontenera, obrazu httpd nie było w lokalnym repozytorium, więc został pobrany z sieci. Co się zmieniło w stosunku do poprzedniego przykładu? Po pierwsze, kontener działa, po drugie mamy wpis w kolumnie PORTS.
Kontener działa, więc powinien dostarczać nam usługę httpd. Do sprawdzenia tej teorii potrzebny jest nam drugi element - mapowanie portów. Powyższa forma wpisu oznacza, że port 32768 naszego serwera dockerowego jest mapowany na port 80 kontenera. Weryfikacja jest prosta - otwórz przeglądarkę i udaj się pod adres <ip_serwera_docker>:<port_mapowany> - u mnie to 192.168.1.150:32768. Voila. Apache działa!.

Interakcja z kontenerem


Pierwszym pytaniem jakie mi się nasunęło po uruchomieniu kontenera z httpd było mniej więcej "jak się dostać do środka i zmienić domyślną zawartość index.html". Okazuje się, że są przynajmniej dwie metody podłączenia się z systemem w kontenerze (patrząc z poziomu dockera).
Pierwszą z nich jest docker attach - metoda działa kiedy kontener uruchamia się z aktywnym shellem, i szybko przestałem z niej korzystać.
Druga, to docker exec.
Do podłączenia się do kontenera musimy znać jego id lub nazwę - z pomocą przychodzi nam tu docker ps .

[docker@docker-base ~]$ docker ps
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                   NAMES
b399b5f6b2ce        httpd               "httpd-foreground"   5 seconds ago       Up 4 seconds        0.0.0.0:32769->80/tcp   silly_sinoussi
[docker@docker-base ~]$ docker exec -it silly_sinoussi bash
root@b399b5f6b2ce:/usr/local/apache2#

Polecenie docker exec pozwala na wywołanie wenątrz kontenera komendy podawanej jako argument. Przy interaktywnym podłączeniu istotne są przełączniki użyte powyżej: -i pozwala na interakcję podłączając do kontenera STDIN, -t natomiast uruchamia pseudo terminal TTY dla kontenera. Wewnątrz kontenera poruszamy się tak, jak w zwykłym systemie operacyjnym.
Muszę tu wspomnieć o jednej właściwości dockera w odniesieniu do interakcji z kontenerem. Przy podłączeniu interaktywnym poprzez docker attach, lub uruchomieniu kontenera od razu z opcjami -it, który przeniesie nas do shella kontenera wyjście z niego przy użyciu standardowego exit lub kombinacji ctrl+d zabije nasz kontener - jeśli tego nie chcemy trzeba wyrobić sobie nawyk używania kombinacji wyjścia dockera ctrl+p ctrl+q

Uruchamianie i zatrzymywanie kontenerów


Pierwsze uruchomienie kontenera dodaje go do naszego inwentarza, który przeglądamy poprzez docker ps -a. Znajdujące się tu kontenery możemy zatrzymywać lub uruchamiać, zależnie od potrzeb.

[docker@docker-base ~]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                   NAMES
b399b5f6b2ce        httpd               "httpd-foreground"   10 minutes ago      Up 3 minutes        0.0.0.0:32770->80/tcp   silly_sinoussi
[docker@docker-base ~]$ docker stop b399b5f6b2ce
b399b5f6b2ce
[docker@docker-base ~]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS                     PORTS               NAMES
b399b5f6b2ce        httpd               "httpd-foreground"   11 minutes ago      Exited (0) 4 seconds ago                       silly_sinoussi
[docker@docker-base ~]$ docker start silly_sinoussi
silly_sinoussi
[docker@docker-base ~]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                   NAMES
b399b5f6b2ce        httpd               "httpd-foreground"   11 minutes ago      Up 1 seconds        0.0.0.0:32771->80/tcp   silly_sinoussi

Jak widać powyżej, stosowanie ID i nazwy kontenera jest zamienne i uwarunkowane jedynie preferencjami użytkownika. Dodatkowo, dostępne jest również polecenie docker restart restartujące kontener, a także, w przypadku tworów opornych na polecenie stop - docker kill.

Usuwanie kontenerów


Polecenie docker stop zatrzymuje kontener, ale nie usuwa go z naszego systemu. Kontener taki zajmuje ciągle zasoby dyskowe naszego hosta. Oczywiście istnieje możliwość permanentnego usunięcia nieużywanego już kontenera - wykonać to możemy poprzez

[docker@docker-base ~]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS                     PORTS                   NAMES
96d2915d0302        busybox             "sh"                 3 seconds ago       Exited (0) 3 seconds ago                           jolly_fermat
b399b5f6b2ce        httpd               "httpd-foreground"   19 minutes ago      Up 4 minutes               0.0.0.0:32772->80/tcp   silly_sinoussi
[docker@docker-base ~]$ docker rm 96d2915d0302
96d2915d0302
[docker@docker-base ~]$ docker ps -a
CONTAINER ID        IMAGE               COMMAND              CREATED             STATUS              PORTS                   NAMES
b399b5f6b2ce        httpd               "httpd-foreground"   19 minutes ago      Up 4 minutes        0.0.0.0:32772->80/tcp   silly_sinoussi

Tu również możemy zamiennie stosować id oraz nazwę kontenera.

Przydatne opcje i polecenia

Jednym z pierwszych przełączników jakich szukałem, był ten umożliwiający nadawanie konkretnych nazw kontenerom. Jeśli chcemy postawić kilka maszyn opartych o ten sam obraz, to nazwy typu "jolly_fermat" i "silly_sinoussi" niewiela nam pomogą przy identyfikacji konkretnej maszyny.
Do nadawania własnych nazw kontenerów używamy przełącznika --name nazwa_kontenera.

Przydatnymi poleceniami mogą okazać się dwa, pozwalające na zatrzymywanie oraz usuwanie WSZYSTKICH maszyn z dockera.
Do zatrzymania wszystkich kontenerów możemy użyć polecenia docker stop $(docker ps -q).
Usunięcie wszystkich kontenerów zrealizujemy w sposób analogiczny: docker rm $(docker ps -qa)

Osobiście w linuksie bardzo lubię fakt, że jeśli czegoś zapomnę, mogę to w bardziej lub mniej łatwy sposób znaleźć bez korzystania za każdym razem z google. Oczywiście mówię tu o helpach i man-ach dla wszelakich poleceń. Trzeba przyznać, że deweloperzy dockera wpisują się bardzo dobrze w zwyczaje dokumentowania binariów. Szczególnie przydatny jest help do dockera, który skonstruowany jest wielopoziomowo:
docker help wyświetli nam ogólnego helpa
docker help komenda daje helpa dla konkretnej opcji (np. docker help run)

[RH7] Konfiguracja fensowania nodów klastra

Red Hat 7.x / CentOS 7.x
Fensowanie jest wymogiem każdego klastra wysokiej dostępności. Zapobiega ono uszkodzeniom struktury danych spowodowanych przez błędnie działający node.
Zależnie od hardware'u na jakim działa klaster procers fensowania może wyłączyć komunikację sieciową uszkodzonego node'a ze współdzielonym zasobem storage'owym, lub zrestartować problematyczny węzeł klastra.

Pierwszym krokiem konfiguracji fensowania jest konfiguracja urządzenia fensującego - może to być np. UPS pod który node jest podłączony,  jego PDU (Power Distribution Unit), urządzenie kontrolujące zasilanie Blade'a.

Listę dostępnych modułów fensowania uzyskamy wydając polecenie:

[root@clusternode1 ~]# pcs stonith list
fence_apc - Fence agent for APC over telnet/ssh
fence_apc_snmp - Fence agent for APC, Tripplite PDU over SNMP
fence_bladecenter - Fence agent for IBM BladeCenter
fence_brocade - Fence agent for HP Brocade over telnet/ssh
fence_cisco_mds - Fence agent for Cisco MDS
fence_cisco_ucs - Fence agent for Cisco UCS
fence_compute - Fence agent for the automatic resurrection of OpenStack compute instances
fence_drac5 - Fence agent for Dell DRAC CMC/5
fence_eaton_snmp - Fence agent for Eaton over SNMP
fence_emerson - Fence agent for Emerson over SNMP
fence_eps - Fence agent for ePowerSwitch
fence_hpblade - Fence agent for HP BladeSystem
fence_ibmblade - Fence agent for IBM BladeCenter over SNMP
fence_idrac - Fence agent for IPMI
fence_ifmib - Fence agent for IF MIB
fence_ilo - Fence agent for HP iLO
fence_ilo2 - Fence agent for HP iLO
fence_ilo3 - Fence agent for IPMI
fence_ilo3_ssh - Fence agent for HP iLO over SSH
fence_ilo4 - Fence agent for IPMI
fence_ilo4_ssh - Fence agent for HP iLO over SSH
fence_ilo_moonshot - Fence agent for HP Moonshot iLO
fence_ilo_mp - Fence agent for HP iLO MP
fence_ilo_ssh - Fence agent for HP iLO over SSH
fence_imm - Fence agent for IPMI
fence_intelmodular - Fence agent for Intel Modular
fence_ipdu - Fence agent for iPDU over SNMP
fence_ipmilan - Fence agent for IPMI
fence_kdump - Fence agent for use with kdump
fence_mpath - Fence agent for multipath persistent reservation
fence_rhevm - Fence agent for RHEV-M REST API
fence_rsa - Fence agent for IBM RSA
fence_rsb - I/O Fencing agent for Fujitsu-Siemens RSB
fence_scsi - Fence agent for SCSI persistentl reservation
fence_virt - Fence agent for virtual machines
fence_vmware_soap - Fence agent for VMWare over SOAP API
fence_wti - Fence agent for WTI
fence_xvm - Fence agent for virtual machines

Każdy z modułów posiada specyficzne opcje, które możemy sprawdzić poprzez:

[root@clusternode1 ~]# pcs stonith describe fence_virt
fence_virt - Fence agent for virtual machines

fence_virt is an I/O Fencing agent which can be used withvirtual machines.

Stonith options:
  debug: Specify (stdin) or increment (command line) debug level
  serial_device: Serial device (default=/dev/ttyS1)
  serial_params: Serial Parameters (default=115200,8N1)
  channel_address: VM Channel IP address (default=10.0.2.179)
  ipport: TCP, Multicast, or VMChannel IP port (default=1229)
  port: Virtual Machine (domain name) to fence
  action: Fencing action (null, off, on, [reboot], status, list, monitor, metadata)
  timeout: Fencing timeout (in seconds; default=30)
  ipaddr: IP address to connect to in TCP mode (default=127.0.0.1 / ::1)
  auth: Authentication (none, sha1, [sha256], sha512)
  hash: Packet hash strength (none, sha1, [sha256], sha512)
  key_file: Shared key file (default=/etc/cluster/fence_xvm.key)
  delay: Fencing delay (in seconds; default=0)
  domain: Virtual Machine (domain name) to fence (deprecated; use port)
  priority: The priority of the stonith resource. Devices are tried in order of highest priority to lowest.
  pcmk_host_map: A mapping of host names to ports numbers for devices that do not support host names.
  pcmk_host_list: A list of machines controlled by this device (Optional unless pcmk_host_check=static-list).
  pcmk_host_check: How to determine which machines are controlled by the device.
  pcmk_delay_max: Enable random delay for stonith actions and specify the maximum of random delay

W przypadku środowiska wirtualnego opartego o VirtualBox problematyczne (jeśli nie niemożliwe) jest skonfigurowanie fensowania opartego o zasilanie maszyny, dlatego użyjemy zasobu dyskowego, który będzie naszym urządzeniem fensującym. W tym celu utworzyłem na dodatkowej maszynie zasób iSCSI, który podłączyłem do wszystkich nodów klastra. (tutaj znajdziesz jak udostępniać zasób iSCSI oraz jak podłączyć serwer do zasobu).
iSCSI pojawiło się na wszystkich maszynach jako /dev/sdb. Teraz musimy zdefiniować w stonith nasze urządzenie fensujące.

[root@clusternode1 ~]# pcs stonith create iscsi-shooter fence_scsi devices=/dev/sdb meta provides=unfencing

Wpis meta provides=unfencing jest opcją, którą należy dodawać w przypadku korzystania z urządzeń fensujących nie opartych o zasilanie. Dba ona, aby przed restartem fensowanego node'a został on "odfensowany", czyli gwarantuje start usług klastrowych i pełne przyłączneie do klastra.

Po dodaniu urządzenia, sprawdzimy, czy utworzyło się ono poprawnie:

[root@clusternode1 ~]# pcs stonith show
 iscsi-shooter  (stonith:fence_scsi):   Started clusternode1

Jak widać, urządzenie działa, więc teoretycznie powinniśmy być w stanie wymusić fensowanie poszczególnych nodów klastra, sprawdźmy zatem:

[root@clusternode1 ~]# pcs cluster status
Cluster Status:
 Last updated: Tue Mar 15 18:30:03 2016         Last change: Tue Mar 15 18:16:10 2016 by root via cibadmin on clusternode3
 Stack: corosync
 Current DC: clusternode1 (version 1.1.13-10.el7_2.2-44eb2dd) - partition with quorum
 3 nodes and 1 resource configured
 Online: [ clusternode1 clusternode2 clusternode3 ]

PCSD Status:
  clusternode1: Online
  clusternode2: Online
  clusternode3: Online
[root@clusternode1 ~]# pcs stonith fence clusternode2
Node: clusternode2 fenced
[root@clusternode1 ~]# pcs stonith fence clusternode3
Node: clusternode3 fenced
[root@clusternode1 ~]# pcs cluster status
Cluster Status:
 Last updated: Tue Mar 15 18:30:18 2016         Last change: Tue Mar 15 18:16:10 2016 by root via cibadmin on clusternode3
 Stack: corosync
 Current DC: clusternode1 (version 1.1.13-10.el7_2.2-44eb2dd) - partition with quorum
 3 nodes and 1 resource configured
 Node clusternode2: pending
 Node clusternode3: pending
 Online: [ clusternode1 ]

PCSD Status:
  clusternode1: Online
  clusternode2: Online
  clusternode3: Online

Jak widać dwa nody odłączyły się od klastra, czyli fensowanie działa. Na takim zfensowanym węźle, próba wyświetlenia statusu klastra zakończy się tak:

[root@clusternode2 ~]# pcs cluster status
Error: cluster is not currently running on this node

W przypadku, kiedy fensowanie nie jest oparte o zasilanie, czyli wadliwa maszyna nie jest restartowana, po usunięciu problemów należy przywrócić działanie takiej maszyny startując usługę klastrową:

[root@clusternode2 ~]# pcs cluster start
Starting Cluster...
[root@clusternode2 ~]# pcs cluster status
Cluster Status:
 Last updated: Tue Mar 15 18:35:12 2016         Last change: Tue Mar 15 18:16:10 2016 by root via cibadmin on clusternode3
 Stack: corosync
 Current DC: clusternode1 (version 1.1.13-10.el7_2.2-44eb2dd) - partition with quorum
 3 nodes and 1 resource configured
 Node clusternode3: pending
 Online: [ clusternode1 clusternode2 ]

PCSD Status:
  clusternode1: Online
  clusternode2: Online
  clusternode3: Online

Testując ten wariant fensowania, zetknąłem się z jednym błędem. Po fensowaniu maszyn clusternode2 i clusternode3 wykonanym z maszyny clusternode1, a następnie ponownym ich przywróceniu do klastra, nie mogłem fensować maszyny clusternode1 z pozostałych maszyn. Problem objawiał się następująco:

[root@clusternode2 ~]# pcs stonith fence clusternode1
Error: unable to fence 'clusternode1'
Command failed: No route to host

Przyczyną okazały się problemy z odświeżaniem informacji o konfiguracji urządzenia fensującego scsi. Rozwiązaniem natomiast okazało się wymuszenie jej odświeżenia:

[root@clusternode2 ~]# pcs stonith cleanup
Waiting for 1 replies from the CRMd. OK

[root@clusternode2 ~]# pcs stonith fence clusternode1
Node: clusternode1 fenced

Okazuje się, że komenda pcs stonith show pokazuje status urządzenia fensującego jako Stopped, gdy którykolwiek z nodów klastra jest niedostępny.
Testowo utworzyłem na każdym z nodów inne urządzenie fensujące, oparte o ten sam dysk - jeden zasób iscsi, i na każdym z nodów wydane polecenie pcs stonith create ze wskazaniem na dysk /dev/sdb, ale różnymi nazwami urządzenia - w rezultacie pcs stonith show pokazywał jako zastopowane jedynie urządzenie na zfensowanym nodzie...