Ansible – klaster na Docker Swarm + Portainer

Czym jest Docker Swarm?

Swarm jest natywnym rozwiązaniem klastrowym dostarczanym wraz z Dockerem od Docker Engine 1.12. W obecnym świecie, gdzie królują takie rozwiązania jak Kubernetes, czy Mesos, Docker Swarm jest często pomijany, nawet przez osoby, które zaczynają się dopiero uczyć samego Dockera. Jest to dość dużym błędem. Warto poznać to rozwiązanie już podczas nauki, tym bardziej, że stawia ono na maksymalną prostotę, jeśli chodzi o konfiguracje i użytkowanie. W przypadku Swarma, z racji tego, że jest częścią ekosystemu Dockera, rozwijaną i wspieraną przez samego producenta, możemy liczyć na ciekawe rozwiązania, które bez wątpienia czynią narzędzie konkurencyjnym w porównaniu z innymi tego typu klastrami.

Zalety Docker Swarm

Swarm jest domyślnie przeznaczony dla systemów produkcyjnych, możemy znaleźć w nim takie zalety jak:

  • Prosta konfiguracja i zarządzanie klastrem
  • Bezpieczeństwo – po zainicjowaniu klastra komunikacja w Swarmie jest szyfrowana TLS (nie da się zrezygnować, naturalnie o szyfrowanie naszych aplikacji w kontenerach musimy zadbać sami)
  • Load Balancer – Swarm bazuje na wudowanym rozwiązaniu „Routing Mesh” dzięki czemu po przypisaniu do nodów domeny, nawet jeśli zapytanie trafi na serwer, który nie posiada danego kontenera, ruch zostanie skierowany do najbliższej instancji na której taki kontener się znajduje.
  • Wysoka dostępność – klaster działa w strukturze managerów i workerów z aktywnym leaderem, dzięki czemu po awarii leadera, kolejny manager zostaje na niego promowany, sam klaster działa nadal bez awarii. Oczywiście przy zachowaniu „best practice” dla HA czyli 3 managerów w tym jeden działający jako leader.
  • Prosty do konfiguracji za pomocą narzędzi do zarządzania konfiguracją jak Puppet, czy Ansible
  • Wsparcie producenta – Swarm jest rozwijany przez Dockera.
  • Działa z innymi narzędziami Dockera np. Docker-Compose, Docker Engine
  • Wysokiej jakości dokumentacja – często się zdarza, że producenci po stworzeniu dokumentacji do danego narzędzia wraz z jego rozwojem zapominają o niej. Nie w tym przypadku, każda najmniejsza zmiana w Swarmie, momentalnie jest wyczerpująco opisywana w dokumentacji.
  • Skalowalność – możemy wystawiać kontenery jako repliki i podawać samemu ilość w jakiej chcemy dany kontener, lub globalnie, czyli każdy z nodów wpięty do klastra będzie posiadał z automatu dany kontener
  • Uzgadnianie stanu – managerowie stale monitorują stan klastra, między stanem rzeczywistym, a stanem pożądanym. Jeśli mieliśmy skonfigurowany kontener w 5 replikach, a nody obsługujące 2 z tych replik uległyby awarii, manager utworzy dwie nowe repliki, aby zastąpić uszkodzone, przypisując je do działających nodów.
  • Aktualizacje bez przerwy – klaster daje nam kontrolę nad opóźnieniem jakie ma być między wdrażanymi serwisami na inne nody, dzięki temu jeśli coś pójdzie źle, możemy w łatwy sposób przywrócić zadanie do poprzedniej wersji, bez wpływu na produkcję

Czym jest Portainer?

Portainer jest open-soruce’owym, bazującym na aplikacji www, GUI dla Dockera i świetnie nadaje się do zarządzania Swarmem. Możemy z jego pomocą zrobić wszystko to, co za pomocą CLI z tym, że okraszone bardzo ładną i przyjemną dla oka grafiką.
Portainer działa na zasadzie agent-serwer, co oznacza, że będziemy potrzebowali agenta na każdym z nodów, który będziemy posiadać. Nie musimy się tym jednak przejmować, w dalszej części zobaczymy jak łatwo i prosto zrobimy to za pomocą Swarma.

Zalety Portainera

  • Prosta instalacja (gotowy stack Docker-Compose)
  • Przejrzyste GUI
  • Pełna integracja z Docker i Docker Swarm
  • Można spiąć wiele klastrów
  • Tworzenie serwisów Swarma jak i samych kontenerów
  • Zarządzanie wolumenami razem z NFS
  • Gotowe w pełni konfigurowalne szablony dla kontenerów jaki klastra
  • Zarządzanie użytkownikami
  • LDAP
  • Możliwość wystawiania stacków za pomocą Docker-Compose
  • Zarządzanie siecią
  • Bezpośredni dostęp do konsoli danego kontenera bezpośrednio z GUI
  • Logi z kontenerów
  • Graficzne przedstawienie naszego klastra
  • Informacje o zasobach zużywanych przez dany kontener
  • Zarządzanie obrazami
  • Dodawanie i zarządzanie własnymi registrami

Infrastruktura dla Docker Swarm

Playbooka będziemy pisać pod maszyny wystawiane na CentOS 7, a nasza Infrastruktura, będzie wyglądała następująco:

Zgodnie z najmniejszym dopuszczalnym modelem wysokiej dostępności przez Swarma (czyli, przy trzech managerach), mamy tolerancję, że jeden może nie działać. W takim wypadku możemy, albo naprawić problematycznego managera, albo wypromować workera na managera.

A więc potrzebujemy 5 jednakowych maszyn, pod kontrolą czystego systemu CentOS 7, aby zbudować powyższy model. W moim przypadku będzie to wyglądało następująco:

  • Manager1 (Leader): 192.168.56.10
  • Manager2: 192.168.56.13
  • Manager3: 192.168.56.14
  • Worker1: 192.168.56.11
  • Worker2: 192.168.56.12

Środowisko dla Swarma zostało wystawione za pomocą doskonałego narzędzia, jakim jest Vagrant i znaleźć je można na końcu artykułu.

Piszemy playbooka

Podstawowa konfiguracja

Gdy mamy już nasze środowisko pod klaster, czas zabrać się za pisanie playbooka, wykorzystamy gotowy moduł docker_swarm do zarządzania naszym Swarmem. Struktura katalogów wygląda następująco:

swarm_playbook/
 ├── ansible.cfg
 ├── group_vars
 ├── hosts
 ├── roles
 └── site.yml

Wraz z stworzeniem struktury jak powyżej (UWAGA!!! podświetlone linie to katalogi!), możemy przejść do edycji ansible.cfg który będzie wyglądał następująco:

[defaults]
inventory = hosts

# uzytkownik
remote_user = vagrant

# wylaczenie sprawdzania klucza
host_key_checking = False

# przyspieszenie
pipelining = True
gathering = smart
fact_caching = jsonfile
fact_caching_timeout = 86400
fact_caching_connection = /tmp/ansible_fact_cache
forks = 20

Tak wygląda config Ansible dla naszego playbooka. Kolejno od góry mamy ustawienie w jakim pliku mamy przechowywane nasze hosty na których będzie pracował Ansible, czyli hosts. Ustawiamy użytkownika, abyśmy nie musieli go podawać przy uruchamianiu playbooka, w moim przypadku jest to vagrant, naturalnie można go podmienić na każdego innego. Następnie wyłączamy sprawdzanie klucza ssh, z racji tego, że musielibyśmy mieć dany host już w ~/.ssh/known_hosts aby się podłączyć. Blok przyspieszenie odpowiada za ustawienie Ansible, aby mógł łączyć się wieloma wątkami do hostów oraz przechowywać raz pobrane fakty przez określony czas, co ominie nam pobieranie faktów za każdym razem gdy będziemy wykonywać playbooka i zaoszczędzi trochę czasu.

Teraz zajmiemy się konfiguracją pliku hosts:

[leader]
192.168.56.10

[managers]
192.168.56.10
192.168.56.13
192.168.56.14

[workers]
192.168.56.11
192.168.56.12

Bardzo prosta sprawa, tworzomy trzy grupy [leader], [managers] i [workers] po czym umieszczamy w nich nody, które im będą odpowiadać. W tym momęcie możemy przejść do tworzenia grup.

Tworzymy grupy

Grupy będą nam potrzebne, abyśmy mogli wykonać odpowiednie taski Ansible dla poszczególnych rodzajów nodów. Struktura przedstawia się następująco:

swarm_playbook/group_vars/
├── all
├── leader
├── managers
└── workers

Cała zawartość swarm_playbook/group_vars/to na tę chwile puste pliki. Teraz możemy przejść do tworzenia pierwszej roli.

Rola – repo

Będąc ciągle w swarm_playbook/ wykonujemy następujące polecenie ansible-galaxy init roles/repo powinniśmy zobaczyć informacje o udanym utworzeniu roli - roles/repo was created successfully. Z racji tego, że instalowaliśmy praktycznie czyste CentOS’y zakładamy, że systemy posiadają zainstalowane tylko domyślne paczki z wersji minimalnej. Rola repo posłuży nam w tym momencie do zainstalowania repozytorium Epel. Edytujemy zatem vim roles/repo/tasks/main.yml i wrzucamy zawartość:

---
# tasks file for repo

- name: Instalujemy repo z .rpm
  yum:
    name: "{{ repo_install }}"
    state: present

Używamy modułu yum i tworzymy zmienną repo_install. Teraz użyjemy tej zmiennej w grupie all, ponieważ repozytorium Epel będziemy potrzebować na każdym z nodów. Przechodzimy do vim group_vars/alli deklarujemy naszą zmienną:

---

repo_install:
  - https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

Grupa alloznacza, że wszystko co się w niej znajdzie, będzie stosowane na wszystkich hostach, które posiadamy w pliku hosts. Repozytorium epelbędziemy potrzebować do instalacji jednej z paczek wymaganych do używania modułu docker_swarm.

Rola – packages

Tworzymy rolę ansible-galaxy init roles/packages jak poprzednio za pomocą ansible-galaxy i dzielimy ją na taski, ponieważ będziemy musieli skorzystać z modułu yum oraz pip. Tworzymy task dla instalacji paczek z yum vim roles/packages/tasks/yum_common_packages.yml o zawartości:

---

- name: instalacja paczek z YUM
  yum:
    name: "{{ yum_packages  }}"
    update_cache: yes
    state: present

Kod praktycznie się nie różni od taska z roli repo, ale tutaj zainstalujemy python-pip (dlatego potrzebowaliśmy repozytorium epel), dzięki któremu będziemy mogli użyć modułu pip i zainstalować najważniejszą paczkę dla modułu docker_swarm. Utwórzmy więc kolejny task, który zajmie się instalacją modułów z pip. Tworzym i Edytujemy następujący plik vim roles/packages/tasks/pip_common_packages.yml i wklejamy:

---

- name: instalacja paczek z PIP
  pip:
    name: "{{ pip_packages }}"
    state: present

W tym momencie mamy już utworzone oba potrzebne taski, które zajmą się instalacją dla nas modułów potrzebnych dla docker_swarm, więc zainicjujmy ich użycie w vim roles/packages/tasks/main.yml za include:

---
# tasks file for packages

- include: yum_common_packages.yml

- include: pip_common_packages.yml

Teraz jeszcze tylko musimy dodać nasze nowo powstałe zmienne yum_packagesi pip_packages do vim group_vars/all, te paczki będziemy potrzebować na wszystkich nodach stąd ponownie grupa all:

yum_packages:
  - python-pip

pip_packages:
  - docker

Wszystkie role z instalacją podstawowych repozytoriów oraz paczek mamy stworzone, przejdźmy do instalacji samego Dockera.

Rola – Docker

Inicjujemy nową rolę ansible-galaxy init roles/docker i przechodzimy do pisania pierwszego taska, którym będzie pobranie repozytorium Dockera i wrzucenie do /etc/yum.repos.d/ na naszych nodach. Tworzymy i edytujemy vim roles/docker/tasks/docker_repo.yml:

---

- name: sciagamy repo docker
  get_url:
    url: "{{ docker_repo }}"
    dest: /etc/yum.repos.d/

Tym razem używając modułu get_url, dzięki któremu ściągniemy repo Dockera.

Kolejnym taskiem będzie instalacja paczek Dockera, a więc tworzymy vim roles/docker/tasks/docker_package.yml i używamy ponownie modułu yum:

---

- name: instalacja paczek Dockera
  yum:
    name: "{{ docker_packages }}"
    update_cache: yes
    state: present

Tutaj może pojawić się pytanie, dlaczego nie zainstalowaliśmy tych paczek wcześniej używając do tego roli packages? Dlatego, że tworzymy tutaj mały playbook, który w przyszłości można rozbudowywać o kolejne paczki i repozytoria. Dobrą praktyką natomiast jest, podczas instalacji aplikacji którą możemy konfigurować na poziomie repozytorium, instalacja paczek i zarządzanie serwisem, aby całość komponentów znajdowała się w jednej roli.

Tym sposobem przechodzimy do utworzenia ostatniego taska dla tej roli, dzięki któremu, będziemy mogli zarządzać serwisem Dockera. Tworzymy i edytujemy vim roles/docker/tasks/docker_service.yml:

---
# tasks file for docker

- name: uruchomienie daemona Docker
  service:
    name: docker
    state: "{{ item.docker_status }}"
    enabled: "{{ item.docker_autostart }}"
  with_items:
    - "{{ docker }}"

Użyliśmy modułu service, a dzięki zmiennej docker będziemy mogli zarządzać stanem serwisu i auto-startem.

Pozostaje nam tylko dodać utworzone taski dla roli docker do głównego taska vim roles/docker/tasks/main.yml:

---
# tasks file for docker

- include: docker_repo.yml

- include: docker_package.yml

- include: docker_service.yml

I tak ukończyliśmy bardzo podstawowa role dla Dockera, która w przyszłości może być rozbudowywana o kolejną konfigurację, jak również może służyć jako pełnoprawna rola do instalacji tylko samego Dockera. Jako że wszystko co się w tej roli znalazło chcemy aby było instalowane na wszystkich nodach, edytujemy ponownie naszą grupę all i dodajemy użyte zmienne z tej roli vim group_vars/all:

docker_repo: https://download.docker.com/linux/centos/docker-ce.repo

docker_packages:
  - docker-ce
  - docker-ce-cli
  - containerd.io

docker:
  - docker_status: started
    docker_autostart: yes

W ten sposób zakończyliśmy także dopisywanie zmiennych do grupy all, a całość tego pliku powinna prezentować się następująco:

---

repo_install:
  - https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm


yum_packages:
  - python-pip

pip_packages:
  - docker

docker_repo: https://download.docker.com/linux/centos/docker-ce.repo

docker_packages:
  - docker-ce
  - docker-ce-cli
  - containerd.io

docker:
  - docker_status: started
    docker_autostart: yes

Rola – swarm

Tworzymy rolę ansible-galaxy init roles/swarm, będzie ona odpowiadała za inicjację samego klastra, oraz wpięcie do niego naszych managerów i workerów. Tym razem utworzymy dwa taski. Pierwszy do inicjacji Swarma i leadera. Edytujemy vim roles/swarm/tasks/swarm_init.yml:

---

- name: Inicjacja Lidera
  docker_swarm:
    state: "{{ item.swarm_init }}"
    advertise_addr: "{{ item.adv_addr }}"
  with_items:
    - "{{ swarm_claster }}"

- name: rejestracja faktow
  docker_swarm:
    state: inspect
  register: swarm_tokens

W tym momencie w końcu dotarliśmy do użycia modułu docker_swarm. Pierwsza część to standardowe użycie modułu, które wykorzystamy do inicjacji leadera i klastra, a za pomocą zmiennej swarm_claster przekażemy takie informacje jak stan klastra i IP, pod którym będzie on dostępny. Natomiast drugą część potrzebujemy do zarejestrowania faktu, z którego będziemy mogli wyciągnąć takie informacje jak token dla workera i token dla managera. Tę zmienną wrzucimy do grupy leader i tylko tam będzie wykorzystywane. Przejdźmy więc do edycji vim group_vars/leader:

---

swarm_claster:
  - swarm_init: present
    adv_addr: "{{ hostvars[groups['leader'][0]]['inventory_hostname'] }}"


swarm_init: present oznacza, że Swarm został zainicjowany, natomiast adv_addr: "{{ hostvars[groups['leader'][0]]['inventory_hostname'] }}" mówi nam o tym, że klaster jest dostępny pod nodem znajdującym się w hosts w grupie leader na pozycji 0.

Utwórzmy kolejny task dla wpięcia workerów i managerów, który nieznacznie będzie się różnił od pierwszego vim roles/swarm/tasks/swarm_node.yml:

---

- name: wpiecie nodow
  docker_swarm:
    state: "{{ item.join_state  }}"
    advertise_addr:
    join_token: "{{ item.token  }}"
    remote_addrs: [ '{{ item.join_remote_addrs }}' ]
  with_items:
    - "{{ add_node  }}"

Jak widzimy, doszły nam pozycje advertise_addr, join_token oraz remote_addrs, dzięki którym będziemy mogli wybrać rodzaj tokena, który chcemy użyć, oraz wskazać gdzie znajduje się klaster. Jeśli advertise_addr pozostawimy bez wartości, wybiera on domyślne IP, aby pod nim rozgłaszać się w klastrze. Zmienne z tego taska, będziemy deklarować w dwóch grupach managers i workers.

Zaczniemy od grupy managers, przechodzmy do edycji vim group_vars/managers:

---

add_node:
  - join_state: join
    token: "{{ hostvars[groups['leader'][0]]['swarm_tokens']['swarm_facts']['JoinTokens']['Manager'] }}"
    join_remote_addrs: "{{ hostvars[groups['leader'][0]]['inventory_hostname'] }}"

join_state: join– oznacza, że node ma zostać przyłączony do klastra.

token: dzieki wcześniejszej rejestracji faktu swarm_tokens za pomocą inspekcji w tasku swarm_init.yml jesteśmy w stanie wyciągnąć z ledera konkretny token, w tym przypadku token managera.

join_remote_addrs – wskazujemy gdzie znajduje się klaster.

W ten sposób będziemy wpinać każdą maszynę, która znajdzie się w grupie managers w pliku hosts

Dodajemy zmienne do grupy workers, aby przyłączyć maszyny, które mają taką rolę pełnić

---

add_node:
  - join_state: join
    token: "{{ hostvars[groups['leader'][0]]['swarm_tokens']['swarm_facts']['JoinTokens']['Worker'] }}"
    join_remote_addrs: "{{ hostvars[groups['leader'][0]]['inventory_hostname'] }}"

Jak widzimy, jedyne co się zmieniło to zmienna token, tym razem wskazaliśmy token dla workerów.

Jeszcze pozostało nam zainicjowanie naszych tasków w vim roles/swarm/tasks/main.yml:

---
# tasks file for swarm

- include: swarm_init.yml
  when: swarm_claster is defined

- include: swarm_node.yml
  when: add_node is defined

Ponieważ chcemy, aby poszczególne taski wykonały się tylko w danych grupach, użyliśmy when, aby zostały użyte tylko tam, gdzie są zadeklarowane konkretne zmienne. Tym sposobem przechodzimy do stworzenia ostatniej roli.

Rola – portainer

Inicjujemy rolę jak poprzednie ansible-galaxy init roles/portainer, będziemy tworzyć trzy taski. Z racji tego, że na ta chwilę nie ma jeszcze w Ansible modułu, który odpowiada za deployowanie stacków utworzonych w Docker-Compose dla Swrma, będziemy musieli poradzić sobie w inny sposób 😉 Najpierw zrobimy task, który utworzy nam katalog dla pliku Docker-Compose vim roles/portainer/tasks/portainer_stack_dir.yml:

---

- name: Tworzenie katalogu dla docker compose portainera
  file:
    path: "{{ item.stack_dir }}"
    state: directory
  with_items:
    - "{{ portainer_conf }}"

Użyliśmy modułu file dzięki któremu na nodzie na którym będzie, zadeklarowana zmienna portainer_conf, wskażemy katalog w którym ma wylądować nasz stack portainera w formacie Docker-Compose. Teraz stworzymy task, odpowiadający za ściągnięcie samego pliku do instalacji Portainera wykorzystując do tego moduł, który już znamy get_url vim roles/portainer/tasks/portainer_download.yml:

---

- name: Pobieranie portainer-agent-stack.yml
  get_url:
    url: "{{ item.portainer_url }}"
    dest: "{{ item.stack_dir }}/"
  with_items:
    - "{{ portainer_conf }}"

Po podaniu URL, pod którym znajduje się „przepis” Docker-Compose, zostanie on ściągnięty do katalogu z taska portainer_stack_dir.yml. Został nam ostatni task, który będzie odpowiadał za uruchomienie instalacji Portainera + jego agentów. Utwórzmy więc go vim roles/portainer/tasks/portainer_run.yml:

---

- name: Uruchamiany portainera
  shell: docker stack deploy --compose-file={{ item.portainer_run }} portainer && touch portainer_on.txt
  args:
    chdir: "{{ item.stack_dir }}/"
    creates: portainer_on.txt
  with_items:
    - "{{ portainer_conf }}"

Co tu się konkretnie zadziało? Dzięki modułowi shell użyliśmy standardowej komendy z Docker Swarm do deployowania stacków zapisanych w formacie Docker-Compose. Do tego w momencie zdeployowania stacka utworzyliśmy plik portainer_on.txt, dzięki któremu będziemy sprawdzać, czy stack został już uruchomiony, czy nie, a całość ma się zadziać w katalogu, z naszego pierwszego taska w tej roli. Zmienną portainer_conf będziemy inicjować w grupie leader, więc przejdźmy do jej edycji vim group_vars/leader i zobaczmy, jak to będzie wyglądało:

portainer_conf:
  - stack_dir: /opt/docker
    portainer_url: https://downloads.portainer.io/portainer-agent-stack.yml
    portainer_run: portainer-agent-stack.yml

Tworzymy katalog na hoście z grupy leader, podajemy adres URL, skąd ma zostać ściągnięty nasz plik zawierający gotowy stack i podajemy jego nazwę w celu uruchomienia stacka. Nasz plik odpowiadający za grupę leader po wszystkich zmianach powinien wyglądać następująco:

---

swarm_claster:
  - swarm_init: present
    adv_addr: "{{ hostvars[groups['leader'][0]]['inventory_hostname'] }}"

portainer_conf:
  - stack_dir: /opt/docker
    portainer_url: https://downloads.portainer.io/portainer-agent-stack.yml
    portainer_run: portainer-agent-stack.yml

W tej roli, zostało nam jeszcze dodanie naszych wszystkich tasków do roles/portainer/tasks/main.yml:

---
# Rola dla instalacji Portainera

- name: Instalacja serwera Portainer + Agenta
  block:
  - include: portainer_stack_dir.yml
  - include: portainer_download.yml
  - include: portainer_run.yml
  when: portainer_conf is defined

W związku z tym, że każdy task należy do jednej zmiennej portainer_confwrzuciliśmy je w blok i ponownie użyliśmy when, aby rola wykonała się tylko tam, gdzie ta zmienna jest zadeklarowana. To już wszystkie role, jakie mieliśmy do utworzenia, czas skonfigurować nasz główny plik playbooka.

Konfiguracja site.yml

Serce naszego playbooka, czyli plik z którego wszystko wykonamy, znajduje się w swarm_playbook/site.ymlprzejdzmy do jego edycji:

---

- name: Inicjowanie klastra docker swarm
  hosts: all
  become: yes

  roles:
    - repo
    - packages
    - docker
    - swarm
    - portainer

Nadajemy nazwę, wskazujemy, że playbook ma zostać uruchomiony na grupie hostów all, oraz po połączeniu ma działać jako sudo, za co odpowiada become: yes. Następnie dołączamy role, które mają zostać uruchomione i w jakiej kolejności. Całość wyzwalamy z serwera, na którym mamy Ansible i w katalogu playbooka za pomocą komendy ansible-playbook site.yml -k co oznacza:

ansible-playbook – uruchom playbooka
site.yml – z pliku site.yml
-k – zapytaj o hasło do ssh

Powinno wyglądać to następująco:

Portainer

Po wykonaniu playbooka powinniśmy mieć możliwość dostania się do Portainera z przeglądarki pod adresem http://<ip_leadera>:9000 w moim przypadku http://192.168.56.15:9000. Powinien nas przywitać następujący widok:

Tworzymy hasło dla admina min 8 znaków, po czym ukazuje nam się strona domowa:

Z tego miejsca widzimy od razu ile mamy zdeployowanych stacków, ile serwisów działa, ile łącznie mamy kontenerów, oraz ile posiadamy wolumenów i obrazów.
W Docker Swarm stackiem nazywane jest wszystko, co mamy zapisane w postaci Docker-Compose i zrobimy deploy za pomocą komendy docker stack deploy --compose-file, bądź w samym Portainer za pomocą paska bocznego i pozycji stack. Pokażę teraz, w jaki sposób możemy wystawić i zreplikować Nginx za pomocą Portainer.

Deploy serwisu

Klikamy po lewej w Services, następnie nadajemy nazwę, pod jaką nasz serwis ma się prezentować; wybieramy obraz, z którego chcemy zrobić deploy i w ten sposób docieramy do „trybu planowania” określającego, w jaki sposób ma być wystawiony nasz kontener. Mamy do wyboru dwa tryby – Global i Replicated, Czym się różnią? Jeśli zaznaczymy Global, to nasz kontener zostanie wystawiony po jednym na każdym z nodów, które mamy wpięte do klastra oraz na wszystkich kolejnych nodach, które zostaną do Swarma przyłączone. Natomiast wybór Replicated oznacza, że to my decydujemy, w ilu replikach nasz kontener ma zostać wystawiony i rozłożony po klastrze. Następnie mapujemy, pod jakim portem możemy się na niego dostać poprzez hosta oraz port, pod jakim działa aplikacja w samym kontenerze i na koniec pozostaje nam kliknąć utworzenie serwisu.

Teraz możemy wrócić na zakładkę Services i po chwili powinniśmy zobaczyć:

Co oznacza, że nasz serwis nginx-www został zdeployowany z obrazu nginx:latest, i jest zreplikowany na 4 nody (takiej replikacji sobie zażyczyliśmy), oraz dostęp do niego mamy po porcie 8080. Automatycznie widzimy, na których nodach konkretnie nasz Nginx jest zreplikowany i czy działa. W ten oto prosty sposób wystawiliśmy swój pierwszy serwis.

Słowem zakończenia

Tym sposobem dotarliśmy do końca tego przydługiego, lecz (mam nadzieję) wartościowego wpisu, Nie chciałem w nim „przeklikać” całego Portainera, gdyż wpis byłby 3x dłuższy, ale uważam, że jest to tak proste i intuicyjne narzędzie, że każdy bez problemu odkryje jego tajniki. Myślę, że nic nie daje takiej radości jak samodzielna eksploracja nowego narzędzia.

Na koniec mogę poradzić, aby od razu po skonfigurowaniu klastra, przypisać do każdego noda tę samą domenę, dzięki czemu będziemy mogli wykorzystać olbrzymi potencjał trybu „routing mesh” i poczuć moc wbudowanego load-balancera. Nody automatycznie będą rozdzielały między sobą zapytania i kierowały w odpowiednie miejsce na podstawie domeny.

ŹRÓDŁA

Vagrant – stack 5 maszyn pod klaster Docker Swarm + 1 maszyna z Ansible >> TUTAJ <<

Playbook – cały playbook dla Ansible >> TUTAJ <<