Wdrożenie 20 węzłów Docker Swarm przy użyciu Terraform i Ansible
Przetłumaczone automatycznie z angielskiego. Przeczytaj oryginał

Jeśli zastanawiasz się, jak automatycznie wdrożyć 20 węzłów Docker Swarm i uruchomić na nich 100 kontenerów Docker, czytaj dalej. Pokażę, jak to osiągnąć przy użyciu Terraform, Ansible i Portainera.
Plan działania #
- Terraform: 20 maszyn wirtualnych Ubuntu
- Instalacja Docker Swarm przy użyciu Ansible
- Instalacja Portainera
- Wdrożenie 100 kontenerów w klastrze Swarm
Czym jest Docker Swarm i po co mi 20 takich węzłów? #
Docker to zestaw narzędzi do konteneryzacji wykorzystujący cgroups i namespaces, które pozwalają kontrolować i współdzielić zasoby procesora oraz systemu operacyjnego. Docker Swarm to specjalny tryb pracy, który pozwala uruchomić wiele węzłów w klastrze, przy czym mogą to być osobne fizyczne komputery lub maszyny wirtualne. Daje nam to skalowalność i separację zasobów, a jednocześnie wszystko pozostaje w obrębie tych samych narzędzi do zarządzania. Pracę można sobie znacznie ułatwić, instalując Portainer CE, czyli interfejs do zarządzania orkiestracją kontenerów (de facto do zarządzania operacyjnego).
Wracając do pytania: dlaczego 20 węzłów? Można mieć pojedynczy węzeł Docker Swarm, który jest jednocześnie managerem i workerem, i przydzielić mu mnóstwo posiadanych zasobów, takich jak CPU i RAM. Jednak dla łatwiejszego utrzymania, równomiernego wykorzystania zasobów i ich zabezpieczenia korzysta się z trybu klastrowego, takiego jak Docker Swarm, z więcej niż jednym węzłem.
Czym są Terraform i Ansible i o co chodzi z całą tą automatyzacją? #
Terraform to narzędzie do automatyzacji budowy systemów, na przykład provisioningu maszyn wirtualnych. Można to zrobić również za pomocą Ansible, ale jego rola polega tu raczej na zarządzaniu już utworzonymi systemami niż na ich tworzeniu. Oba narzędzia mogłyby więc posłużyć do wszystkich zadań, jednak Terraform z pluginem Telmate Proxmox robi to najprościej. Ansible używam do automatyzacji zadań na zasobach utworzonych przez Terraform. To jest mój sposób, Twój może być inny.
Po co wdrażać 100 kontenerów tej samej aplikacji? #
Jeśli Twoja aplikacja doskonale obsługuje błędy i awarie narzędzi, potrafi działać w wielu procesach z wieloma wątkami i wiesz, że nigdy nie będzie ponownie wdrażana, to zostań przy 1 kontenerze. Jednak w każdym innym przypadku posiadanie wielu kontenerów, czyli instancji, tej samej aplikacji będzie korzystne. Zwiększasz odporność na awarie i ułatwiasz wydawanie nowych wersji oprogramowania. Nie musisz aż tak bardzo przejmować się konfiguracją serwera aplikacji itp., ponieważ wszystko to jest łagodzone przez wdrożenie w N > 1 instancjach.
Możesz mieć 2 kontenery dla aplikacji frontendowej, 2 kontenery dla aplikacji backendowej, 1 aplikację do przetwarzania w tle i wiele innych kombinacji z jednego lub wielu repozytoriów. Możesz mieć 50 frontendów i 50 backendów, zależy to od przypadku. Możesz wprowadzić autoskalowanie, które, nawiasem mówiąc, jest dostępne w OKD, OpenShift i Kubernetes, ale Docker Swarm i Portainer nie mają takiej funkcji. To niefortunne, ale wciąż można to zrobić samodzielnie albo planować i monitorować wykorzystanie zasobów. W przypadku dedykowanego sprzętu autoskalowanie nie jest aż tak ważne, wystarczy przydzielić zasoby z zapasem na przyszłe szczyty. W przypadku dostawców chmury publicznej, gdy płacisz za to, czego używasz, opracowanie autoskalowania będzie istotne.
Terraform: 20 maszyn wirtualnych Ubuntu #
Aby wdrożyć więcej niż jedną maszynę wirtualną przy użyciu Terraform i pluginu providera Telmate Proxmox, trzeba albo skopiować sekcję resource wiele razy, albo użyć notacji count. Zdefiniowałem sekcje terraform i provider oraz dwie sekcje resource, każdą dla innego docelowego serwera Proxmox. Dzięki count zyskujesz możliwość interpolacji ${count.index} przy każdym kolejnym wykonaniu zasobu. Użyłem tego dla nazwy i adresu IP. Serwer docelowy rozróżniany jest za pomocą target_node. Upewnij się, że używasz właściwej nazwy klonu i nazwy magazynu dyskowego, na którym zostaną umieszczone Twoje maszyny wirtualne.
terraform {
required_providers {
proxmox = {
source = "telmate/proxmox"
version = "2.9.0"
}
}
}
provider "proxmox" {
pm_api_url = "https://192.168.2.11:8006/api2/json"
pm_user = "root@pam"
pm_password = "xxx"
pm_tls_insecure = true
pm_timeout = 300
pm_parallel = 2
}
resource "proxmox_vm_qemu" "ubuntu_vm_xx" {
count = 10
name = "z10-ubuntu-22-from-terraform-2-${count.index}"
target_node = "lab2"
clone = "new-template"
full_clone = true
memory = 4000
cores = 2
bootdisk = "virtio0"
network {
bridge = "vmbr0"
model = "virtio"
}
disk {
storage = "single-dir-ssd-256GB"
size = "10G"
type = "virtio"
}
os_type = "cloud-init"
ipconfig0 = "ip=192.168.2.${count.index}/22,gw=192.168.1.1"
ciuser = "xxx"
cipassword = "xxx"
}
resource "proxmox_vm_qemu" "ubuntu_vm_lab" {
count = 10
name = "z10-ubuntu-22-from-terraform-3-${count.index}"
target_node = "lab"
clone = "z10-ubuntu-22-template-RAW"
full_clone = true
memory = 4000
cores = 2
bootdisk = "virtio0"
network {
bridge = "vmbr0"
model = "virtio"
}
disk {
storage = "vms1"
size = "10G"
type = "virtio"
}
os_type = "cloud-init"
ipconfig0 = "ip=192.168.3.${count.index}/22,gw=192.168.1.1"
ciuser = "xxx"
cipassword = "xxx"
}Powyższa notacja utworzy 10+10 maszyn wirtualnych Ubuntu. Możesz to uruchomić poleceniem:
terraform apply -parallelism=1Po utworzeniu maszyn wirtualnych trzeba poczekać, aż cloud-init zakończy pracę. Jeśli nie masz pewności, czy nadal działa, zrestartuj taką maszynę, aby nie zostały zawieszone procesy, które mogłyby kolidować z kolejnym krokiem, czyli instalacją Dockera za pomocą Ansible.
Instalacja usługi Docker za pomocą Ansible #
Najpierw definiujemy inwentarz:
[lab]
192.168.2.0
192.168.2.1
192.168.2.2
192.168.2.3
192.168.2.4
192.168.2.5
192.168.2.6
192.168.2.7
192.168.2.8
192.168.2.9
192.168.3.0
192.168.3.1
192.168.3.2
192.168.3.3
192.168.3.4
192.168.3.5
192.168.3.6
192.168.3.7
192.168.3.8
192.168.3.9
[lab:vars]
ansible_user=xxx
ansible_password=xxxNastępnie definiujemy i uruchamiamy instalację wymaganych pakietów:
---
- name: Docker reqs installation
hosts: all
vars:
ansible_ssh_common_args: '-o ServerAliveInterval=60'
tasks:
- name: Install aptitude
apt:
name: aptitude
state: latest
update_cache: true
- name: Install required system packages
apt:
pkg:
- apt-transport-https
- ca-certificates
- curl
- software-properties-common
- python3-pip
- virtualenv
- python3-setuptools
state: latest
update_cache: true
I na koniec instalacja samego Dockera:
---
- name: Docker installation
hosts: all
vars:
ansible_ssh_common_args: '-o ServerAliveInterval=60'
tasks:
- name: Add Docker GPG apt Key
apt_key:
url: https://download.docker.com/linux/ubuntu/gpg
state: present
- name: Add Docker Repository
apt_repository:
repo: deb https://download.docker.com/linux/ubuntu jammy stable
state: present
- name: Update apt and install docker-ce
apt:
name: docker-ce
state: latest
update_cache: true
- name: Install Docker Module for Python
pip:
name: dockerMoglibyśmy umieścić te kroki w konfiguracji Packera i w ten sposób Docker wraz z wymaganiami byłby dołączony domyślnie. Dobrze jest jednak znać nie tylko Packera i Terraform, ale też wiedzieć, jak zrobić to z poziomu Ansible.
Konfiguracja Docker Swarm i dołączenie do klastra #
Zdecydowałem się skonfigurować pojedynczego managera z Portainerem, więc wybrałem do tego zadania 192.168.2.0:
sudo docker swarm init
curl -L https://downloads.portainer.io/ce2-21/portainer-agent-stack.yml -o portainer-agent-stack.yml
sudo docker stack deploy -c portainer-agent-stack.yml portainer
Mamy teraz zainicjalizowany Docker Swarm i zainstalowany stack Portainera. Po zainicjalizowaniu trybu Swarm otrzymujesz token do dołączania węzłów. Możesz dołączyć więcej węzłów typu manager, ale na potrzeby prostej demonstracyjnej instalacji wystarczy jeden, do którego dołączamy dodatkowe 19 węzłów typu worker za pomocą polecenia Ansible:
ansible all -i inventory -m shell -a "sudo docker swarm join --token SWMTKN-1-4ph10h6ck7aqonnr7bunok3be63nl7itnjkua4owm0cvmyh2z1-754wirc3y36igf86porgwtbjh 192.168.2.0:2377" --become -o -f 1

W Portainerze jest wizualizator klastra, w którym widać wszystkie węzły i to, co się na nich znajduje:

Uruchamianie obciążeń kontenerowych #
W Portainer CE możesz skalować instancje usługi, czyli kontenery, po prostu wpisując liczbę kontenerów Docker do uruchomienia. Możesz użyć trybu replicated, w którym jawnie określasz, ile kontenerów chcesz uruchomić, albo trybu global, w którym liczba kontenerów automatycznie będzie równa liczbie węzłów w klastrze Docker Swarm.

Scheduler Docker Swarm będzie próbował rozmieszczać kontenery równomiernie zgodnie z definicją usługi i możliwościami sprzętu. Możesz stopniowo zwiększać liczbę instancji i monitorować wykorzystanie zasobów sprzętowych. Osobnym tematem są webhooki wdrożeniowe, strategie wdrożeń itp.

Scenariusz porównania wydajności #
Instalację inicjuje polecenie terraform init -parallel=1. Na starszym sprzęcie sugeruję strategię jeden po drugim zamiast wysokiego poziomu równoległości, który mógłby prowadzić do nieoczekiwanych zachowań, takich jak timeout klonowania dysku czy inne problemy.

Po wykonaniu tego kroku widzimy, jak te dwie sekcje resource Terraform przełożyły się na 5+5 maszyn wirtualnych na dwóch węzłach Proxmox. Terraform śledzi stan bazowy (state) Twojego wdrożenia, jednak poleganie wyłącznie na nim nie jest w 100% bezpieczne. Dobrze jest dodatkowo sprawdzić wyniki. W moich testach zdarzyła się sytuacja, gdy Terraform zgłosił, że usunął całą zawartość, choć w rzeczywistości tego nie zrobił. Podobnie z tworzeniem zasobów: nawet jeśli dostajesz informację, że wszystko zostało zrobione, koniecznie to sprawdź. Problem może tkwić w co najmniej 4 miejscach: w samym Terraform, w providerze Telmate Proxmox, w API Proxmoxa w golangu i wreszcie w samym Proxmoxie wraz z jego sprzętem i oprogramowaniem.

Zarówno przy apply, jak i destroy zostaną Ci pokazane proponowane zmiany wynikające z konfiguracji; możesz wtedy przejrzeć, co się wydarzy i czy odpowiada to Twoim potrzebom:

Kluczowe jest, aby wiedzieć, na jakim sprzęcie się pracuje. Przynajmniej z perspektywy wydajności. W przypadku Proxmox VE i sprzętu bare-metal dostajesz dokładnie to, co widzisz. Tę strategię można jednak zastosować także u wielu innych dostawców platform, możesz sprawdzić, co dają konkretne maszyny wirtualne w AWS czy Azure. Aby to zilustrować, porównałem obciążenie serwera między 2 węzłami Proxmox w tym samym klastrze. Oto jeden z 2 x Intel Xeon X5560:

A tutaj masz 2 x Intel Xeon E5645:

Widać dokładnie, że różnica w teoretycznej wydajności CPU znajduje potwierdzenie w rzeczywistym scenariuszu. Pierwszy serwer osiąga obciążenie do 14, a drugi do 16. Jest też różnica w zużyciu pamięci RAM. To samo dotyczy wydajności dysków. Wszystkie te czynniki mogą mieć znaczenie przy równoległym uruchamianiu wielu zadań Ansible lub Terraform.
Podsumowanie #
Dzięki Terraform, Ansible, a nawet Packerowi łatwo wdrożysz wiele zasobów wirtualnych i przeskalujesz wdrożenie swoich aplikacji.