Docker registry jest kolejnym komponentem, który będzie użyteczny w moim mozolnie budowanym labie. Jest to rejestr obrazów dokerowych, które ściągnę lub zbuduję, a które będę miał dostępne niejako pod ręką. Co więcej, lokalny GitHub runner też będzie miał do niego dostep, aby zbudować aplikację i wypchnąć jej obraz do tegoż rejestru.
Rejestr sam jest kontenerem dockerowym z obrazu registry:2
, dostępnego na dockerhubie.
Mając już przygotowaną platformę CD - Nomada, Consula i Vaulta, przygotowałem plik, który instaluje i uruchamia rejestr przy pomocy orkiestracji Nomada. Co tu się zadziało?
job "registry" {
datacenters = ["dc1"]
type = "service"
priority = 10
constraint {
attribute = "${attr.unique.hostname}"
value = "powernuke"
}
Powyższy kawałek otwiera nam całą definicję zadania nomadowego o nazwie registry
, który zostanie zrealizowany przez jeden z węzłów klastra Nomada - o nazwie “powernuke”. Komenda i definicja constraint
służy do decydowania co i gdzie ma być zrealizowane w klastrze. Pamiętając, że klaster Nomadowy może mieć węzły ogólnego jak i szczególnego przeznaczenia. Mogą to być węzły (workers
) linuxowe, MacOSowe, windowsowe, z runtime-em dockera, z Javą, systemowym itd. (lista jest spora).
update {
stagger = "10s"
max_parallel = 1
min_healthy_time = "30s"
healthy_deadline = "5m"
auto_revert = true
}
Ten kawałek odpowiada za strategię aktualizacji zadania. Tutaj mamy tzw. safe rollback strategy
. Każda nowa realizacja (nomad job run
) podbija wersję zadania. Jeśli poprzednie wciąż pracuje, to nomad czeka 10 sekund (stagger
) i realizuje nowe zadanie, stopując poprzednią wersję. Jeśli z jakiegoś powodu po 5 minutach (healthy_deadline
) nowe zadanie nie może uzyskać statusu “running”, to auto_revert
powie Nomadowi, że ma wrócić do poprzedniej, działającej wersji zadania. W ten sposób zachowujemy ciągłość, na wypadek, gdyby nowe zadanie miało jakieś problemy, czy będzie zadanie instalujące nam skomplikowaną aplikację z wieloma komponentami, czy pojedynczy kontener jak w przypadku rejestru. Bez strategii roll-back
możemy znaleźć się w sytuacji, gdzie nowe, a wadliwe zadanie zatrzyma nam działające poprzednie, a samo się nie wykona. Co spowoduje dłuższy przestój.
group "registry" {
count = 1
restart {
attempts = 10
interval = "5m"
delay = "30s"
mode = "delay"
}
Tutaj mamy całe sterowanie zadaniem - ile replik aplikacji ma zostać zainstalowane (count
) i jakie mają zajść warunki, aby nastąpił restart
.
network {
port "secreg" {
static = 5443
to = 5443
}
dns { servers = ["192.168.120.231"] }
}
W sekcji network
odpowiedzialnej za konfigurację spraw sieciowych, ustawiamy mapowanie portu (lub portów) i ustawiamy serwer DNS. Odpowiednik dockerowego docker run -p 5443:5443..
task "registry" {
driver = "docker"
config {
image = "registry:2"
volumes = [
"/data/registry:/var/lib/registry",
"/etc/pki/tls/certs/powernuke-peer-fullchain.pem:/certs/powernuke-peer-fullchain.pem",
"/etc/pki/tls/private/powernuke-peer-key.pem:/private/powernuke-peer-key.pem"
]
ports = ["secreg"]
}
env {
REGISTRY_HTTP_ADDR="0.0.0.0:5443"
REGISTRY_HTTP_TLS_CERTIFICATE = "/certs/powernuke-peer-fullchain.pem"
REGISTRY_HTTP_TLS_KEY = "/private/powernuke-peer-key.pem"
PORT = 5443
}
Tutaj zaczynamy zabawę z docker volumes
. W sekcji config
najpierw podajemy które definiujemy listę miejsc na dysku hosta, które chcemy “podmontować” naszemu kontenerowi (odpowiednik: docker ... -v /path/host:/path/container
). Bardzo ważne jest, aby mieć włączoną opcję docker.volumes.enabled = true
. Przy czym, niestety, w dokumentacji źródłowej ciężko znaleźć JAK konkretnie i gdzie to ustawić. A należy to ustawić w konfiguracji agenta nomada (czyli np. /etc/nomad.d/nomad.hcl):
plugin "docker" {
config {
volumes {
enabled = true
}
}
}
W UI Nomada, dla każdego węzła klastra powinniśmy widzieć, coś podobnego:
Dalej mamy podsekcję env
- zmiennych środowiskowych (znowu, odpowiednik docker ... -e
), gdzie ustawiam wymaganą konfigurację, aby nasz rejestr był osiągalny przez HTTPS. W tym wypadku, podaję po prostu cert i klucz hosta, bo sam rejestr będzie osiągalny przez https://powernuke.nukelab.home:5443
service {
name = "registry"
tags = ["global", "cache"]
check {
name = "HTTPS check"
type = "http"
protocol = "https"
port = "secreg"
path = "/v2/_catalog"
interval = "30s"
timeout = "5s"
method = "GET"
}
}
resources {
cpu = 200 # 200Mhz
memory = 128 # 128 MB
[..]
A co to za serwis? Otóż, chodzi o zdefiniowanie w jaki sposób Consul (zintegrowany z Nomadem - będący, w sumie częścią klastra Nomada, odpowiadającej za service mesh
) po zarejestrowaniu usługi registry
ma sprawdzać zdrowie tejże usługi. A więc - rodzaj (type
), protokół, port, ścieżka, metoda HTTP, interwał i czas po którym Consul zamelduje, że coś jest nie tak. Poniżej - przykład co zamelduje Consul po udanym wykonaniu zadania odpalenia kontenera z rejestrem:
Na samym końcu, podajemy alokację zasobów (CPU i pamięć) klastra dla tego konkretnego zadania.
Wypadałoby to teraz zautomatyzować, poprzez jakąś GH Action. Tak, aby lintery w post-commit’cie sprawdziły poprawność HCLa, a GH Action
poprzez lokalnego runnera po prostu odpalił nomad job run <....>
. To tyle.