Dominik Miklaszewski
by Dominik Miklaszewski

Categories

Tags

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:

center-aligned-image

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:

center-aligned-image

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.