Налаштування кластера Nomad за допомогою Consul та інтеграція з Gitlab

Запровадження

Останнім часом стрімко зростає популярність Kubernetes — дедалі більше проектів запроваджують його у себе. Я ж хотів торкнутися такого оркестратора, як Nomad: він чудово підійде проектам, де вже використовуються інші рішення від компанії HashiCorp, наприклад Vault і Consul, а самі проекти не є складними в плані інфраструктури. У даному матеріалі буде інструкція з встановлення Nomad, об'єднання двох нід у кластер, а також інтеграції Nomad з Gitlab.

Налаштування кластера Nomad за допомогою Consul та інтеграція з Gitlab

Тестовий стенд

Трохи про тестовий стенд: використовуються три віртуальні сервери з характеристиками 2 CPU, 4 RAM, 50 Gb SSD, об'єднаних у загальну локальну мережу. Їх назви та IP адреси:

  1. nomad-livelinux-01: 172.30.0.5
  2. nomad-livelinux-02: 172.30.0.10
  3. consul-livelinux-01: 172.30.0.15

Установка Nomad, Consul. Створення кластера Nomad

Приступимо до базової установки. Незважаючи на простоту установки, я опишу її для цілісності статті: по суті вона була створена з чернеток і нотаток для швидкого доступу у разі потреби.

Перш, ніж приступити до практики, обговоримо теоретичну частину, тому що на даному етапі важливе розуміння майбутньої структури.

У нас існує дві ноди номада і ми хочемо об'єднати їх у кластер, також на майбутнє нам знадобиться автоматичний скейлінг кластера — для цього нам знадобиться Consul. За допомогою цього інструменту кластеризація та додавання нових нід стає дуже простим завданням: створена нода Nomad підключається до Consul агенту, після чого здійснює підключення до існуючого Nomad кластера. Тому на початку ми встановимо Consul сервер, налаштуємо базову http авторизацію для веб-панелі (вона за замовчуванням без авторизації і може бути доступна за зовнішньою адресою), а також самі Consul агенти на серверах Nomad, після чого тільки приступимо до Nomad.

Установка інструментів компанії HashiCorp дуже проста: по суті, ми просто переміщуємо бінарний файл у bin директорію, налаштовуємо конфігураційний файл інструменту та створюємо його сервіс.

Завантажуємо бінарний файл Consul та розпаковуємо його в домашню директорію користувача:

root@consul-livelinux-01:~# wget https://releases.hashicorp.com/consul/1.5.0/consul_1.5.0_linux_amd64.zip
root@consul-livelinux-01:~# unzip consul_1.5.0_linux_amd64.zip
root@consul-livelinux-01:~# mv consul /usr/local/bin/

Тепер у нас є готовий бінарний файл consul для подальшого налаштування.

Для роботи з Consul нам потрібно створити унікальний ключ за допомогою команди keygen:

root@consul-livelinux-01:~# consul keygen

Перейдемо до конфігурації Consul, створюємо директорію /etc/consul.d/ з наступною структурою:

/etc/consul.d/
├── bootstrap
│   └── config.json

У директорії bootstrap буде конфігураційний файл config.json - в ньому ми задамо налаштування Consul. Його вміст:

{
"bootstrap": true,
"server": true,
"datacenter": "dc1",
"data_dir": "/var/consul",
"encrypt": "your-key",
"log_level": "INFO",
"enable_syslog": true,
"start_join": ["172.30.0.15"]
}

Розберемо окремо основні директиви та їх значення:

  • завантажувач: true. Включаємо автоматичне додавання нових нод у разі їхнього підключення. Зазначу, що ми не вказуємо тут точну кількість очікуваних нід.
  • сервер: true. Вмикаємо режим сервера. Consul на цій віртуальній машині виступатиме на даний момент єдиним сервером і майстром, ВМ Nomad'a буду клієнтами.
  • центр обробки даних: DC1. Вказуємо назву датацентру для створення кластеру. Він має бути ідентичним і на клієнтах, і на серверах.
  • шифрувати: your-key. Ключ, який також має бути унікальним та збігатися на всіх клієнтах та серверах. Генерується за допомогою команди consul keygen.
  • start_join. У цьому списку ми вказуємо список IP-адрес, до яких буде здійснюватися підключення. На даний момент залишаємо лише власну адресу.

На цьому етапі ми можемо запустити consul за допомогою командного рядка:

root@consul-livelinux-01:~# /usr/local/bin/consul agent -config-dir /etc/consul.d/bootstrap -ui

Це непоганий спосіб налагодження зараз, однак на постійній основі використовувати цей спосіб не вийде з очевидних причин. Створимо сервіс файл для керування Consul через systemd:

root@consul-livelinux-01:~# nano /etc/systemd/system/consul.service

Вміст файлу consul.service:

[Unit]
Description=Consul Startup process
After=network.target
 
[Service]
Type=simple
ExecStart=/bin/bash -c '/usr/local/bin/consul agent -config-dir /etc/consul.d/bootstrap -ui' 
TimeoutStartSec=0
 
[Install]
WantedBy=default.target

Запускаємо Consul через systemctl:

root@consul-livelinux-01:~# systemctl start consul

Перевіряємо: у нас сервіс має бути запущений, а виконавши команду consul members ми маємо побачити наш сервер:

root@consul-livelinux:/etc/consul.d# consul members
consul-livelinux    172.30.0.15:8301  alive   server  1.5.0  2         dc1  <all>

Наступний етап: встановлення Nginx та налаштування проксіювання, http авторизації. Встановлюємо nginx через пакетний менеджер та в директорії /etc/nginx/sites-enabled створюємо конфігураційний файл consul.conf з таким вмістом:

upstream consul-auth {
    server localhost:8500;
}

server {

    server_name consul.doman.name;
    
    location / {
      proxy_pass http://consul-auth;
      proxy_set_header Host $host;
      auth_basic_user_file /etc/nginx/.htpasswd;
      auth_basic "Password-protected Area";
    }
}

Не забудьте створити .htpasswd файл та згенерувати для нього логін та пароль. Цей пункт потрібний для того, щоб веб-панель не була доступна всім, хто знає наш домен. Однак, при налаштуванні Gitlab нам доведеться від цього відмовитися - інакше ми не зможемо задеплоїти в Nomad наш додаток. У моєму проекті і Gitlab, і Nomad знаходяться лише у сірій мережі, тож такої проблеми тут немає.

На двох серверах встановлюємо Consul агенти за наступною інструкцією. Повторюємо дії з бінарним файлом:

root@nomad-livelinux-01:~# wget https://releases.hashicorp.com/consul/1.5.0/consul_1.5.0_linux_amd64.zip
root@nomad-livelinux-01:~# unzip consul_1.5.0_linux_amd64.zip
root@nomad-livelinux-01:~# mv consul /usr/local/bin/

За аналогією з попереднім сервером створюємо директорію конфігураційних файлів /etc/consul.d з наступною структурою:

/etc/consul.d/
├── client
│   └── config.json

Вміст файлу config.json:

{
    "datacenter": "dc1",
    "data_dir": "/opt/consul",
    "log_level": "DEBUG",
    "node_name": "nomad-livelinux-01",
    "server": false,
    "encrypt": "your-private-key",
    "domain": "livelinux",
    "addresses": {
      "dns": "127.0.0.1",
      "https": "0.0.0.0",
      "grpc": "127.0.0.1",
      "http": "127.0.0.1"
    },
    "bind_addr": "172.30.0.5", # локальный адрес вм
    "start_join": ["172.30.0.15"], # удаленный адрес консул сервера
    "ports": {
      "dns": 53
     }

Зберігаємо зміни та переходимо до налаштування сервіс-файлу, його вміст:

/etc/systemd/system/consul.service:

[Unit]
Description="HashiCorp Consul - A service mesh solution"
Documentation=https://www.consul.io/
Requires=network-online.target
After=network-online.target

[Service]
User=root
Group=root
ExecStart=/usr/local/bin/consul agent -config-dir=/etc/consul.d/client
ExecReload=/usr/local/bin/consul reload
KillMode=process
Restart=on-failure

[Install]
WantedBy=multi-user.target

Запускаємо consul на сервері. Тепер, після запуску ми повинні у nsul members побачити налагоджений сервіс. Це означатиме, що він успішно підключився до кластера як клієнт. Повторіть те саме на другому сервері і після цього ми зможемо приступити до встановлення та налаштування Nomad.

Більш детальна установка Nomad описана у його офіційній документації. Є два традиційні способи встановлення: завантаження бінарного файлу та компіляція з вихідних джерел. Я оберу перший спосіб.

Примітка: проект дуже швидко розвивається, часто виходять нові оновлення. Можливо, на момент завершення статті вийде нова версія. Тому перед прочитанням рекомендую перевірити актуальну версію Nomad на даний момент і завантажувати саме її.

root@nomad-livelinux-01:~# wget https://releases.hashicorp.com/nomad/0.9.1/nomad_0.9.1_linux_amd64.zip
root@nomad-livelinux-01:~# unzip nomad_0.9.1_linux_amd64.zip
root@nomad-livelinux-01:~# mv nomad /usr/local/bin/
root@nomad-livelinux-01:~# nomad -autocomplete-install
root@nomad-livelinux-01:~# complete -C /usr/local/bin/nomad nomad
root@nomad-livelinux-01:~# mkdir /etc/nomad.d

Після розпакування ми отримаємо бінарний файл Nomad'a вагою 65 Мб - його необхідно перемістити в /usr/local/bin.

Створимо data директорію для Nomad'a і відредагуємо його service файл (його, швидше за все, не існуватиме на початку):

root@nomad-livelinux-01:~# mkdir --parents /opt/nomad
root@nomad-livelinux-01:~# nano /etc/systemd/system/nomad.service

Вставляємо туди такі рядки:

[Unit]
Description=Nomad
Documentation=https://nomadproject.io/docs/
Wants=network-online.target
After=network-online.target

[Service]
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/usr/local/bin/nomad agent -config /etc/nomad.d
KillMode=process
KillSignal=SIGINT
LimitNOFILE=infinity
LimitNPROC=infinity
Restart=on-failure
RestartSec=2
StartLimitBurst=3
StartLimitIntervalSec=10
TasksMax=infinity

[Install]
WantedBy=multi-user.target

Однак, не поспішаємо запускати nomad - ми ще не створили його файл конфігурації:

root@nomad-livelinux-01:~# mkdir --parents /etc/nomad.d
root@nomad-livelinux-01:~# chmod 700 /etc/nomad.d
root@nomad-livelinux-01:~# nano /etc/nomad.d/nomad.hcl
root@nomad-livelinux-01:~# nano /etc/nomad.d/server.hcl

Підсумкова структура директорії буде такою:

/etc/nomad.d/
├── nomad.hcl
└── server.hcl

Файл nomad.hcl повинен містити таку конфігурацію:

datacenter = "dc1"
data_dir = "/opt/nomad"

Вміст файлу server.hcl:

server {
  enabled = true
  bootstrap_expect = 1
}

consul {
  address             = "127.0.0.1:8500"
  server_service_name = "nomad"
  client_service_name = "nomad-client"
  auto_advertise      = true
  server_auto_join    = true
  client_auto_join    = true
}

bind_addr = "127.0.0.1" 

advertise {
  http = "172.30.0.5"
}

client {
  enabled = true
}

Не забудьте змінити конфігураційний файл на другому сервері - там потрібно змінити значення директиви http.

Останнім на даному етапі залишається налаштування Nginx для проксіювання та встановлення http авторизації. Вміст файлу nomad.conf:

upstream nomad-auth {
        server 172.30.0.5:4646;
}

server {

        server_name nomad.domain.name;
        
        location / {
	        proxy_pass http://nomad-auth;
	        proxy_set_header Host $host;
	        auth_basic_user_file /etc/nginx/.htpasswd;
		   auth_basic "Password-protected Area";
        }
        
}

Тепер ми можемо отримати доступ до веб-панелі зовнішньої мережі. Підключаємося та переходимо на сторінку servers:

Налаштування кластера Nomad за допомогою Consul та інтеграція з Gitlab
Зображення 1. Список серверів у кластері Nomad

Обидва сервери успішно відображаються в панелі, теж ми побачимо у виведенні команди nomad node status:

Налаштування кластера Nomad за допомогою Consul та інтеграція з Gitlab
Зображення 2. Виведення команди nomad node status

Що ж із боку Consul? Давайте подивимося. Переходимо в панель керування Consul, на сторінку nodes:
Налаштування кластера Nomad за допомогою Consul та інтеграція з Gitlab
Зображення 3. Список нод у кластері Consul

Тепер у нас є підготовлений Nomad, який працює у зв'язці з Consul. У завершальному етапі ми приступимо до найцікавішого: налаштуємо доставку Docker контейнерів з Gitlab в Nomad, а також поговоримо про деякі інші його особливості.

Створення Gitlab Runner

Для деплою докер образів в Номад ми будемо використовувати окремий раннер з бінарним файлом Nomad'а всередині (тут, до речі, можна відзначити ще одну особливість додатків Hashicorp — окремо вони являють собою єдиний бінарний файл). Завантажте його в директорію раннера. Для нього створимо найпростіший Dockerfile з таким вмістом:


FROM alpine:3.9
RUN apk add --update --no-cache libc6-compat gettext
COPY nomad /usr/local/bin/nomad

У цьому проекті створюємо .gitlab-ci.yml:

variables:
  DOCKER_IMAGE: nomad/nomad-deploy
  DOCKER_REGISTRY: registry.domain.name
 

stages:
  - build

build:
  stage: build
  image: ${DOCKER_REGISTRY}/nomad/alpine:3
  script:
    - tag=${DOCKER_REGISTRY}/${DOCKER_IMAGE}:latest
    - docker build --pull -t ${tag} -f Dockerfile .
    - docker push ${tag}

У результаті буде доступний образ раннера Номада в Gitlab Registry, тепер ми можемо перейти безпосередньо в репозиторій проекту, створимо Pipeline і налаштуємо nomad job Nomad'a.

Налаштування проекту

Почнемо з job's файлу для Nomad. Мій проект у цій статті буде досить примітивним: він складатиметься з однієї таски. Вміст .gitlab-ci буде наступним:

variables:
  NOMAD_ADDR: http://nomad.address.service:4646
  DOCKER_REGISTRY: registry.domain.name
  DOCKER_IMAGE: example/project

stages:
  - build
  - deploy

build:
  stage: build
  image: ${DOCKER_REGISTRY}/nomad-runner/alpine:3
  script:
    - tag=${DOCKER_REGISTRY}/${DOCKER_IMAGE}:${CI_COMMIT_SHORT_SHA}
    - docker build --pull -t ${tag} -f Dockerfile .
    - docker push ${tag}


deploy:
  stage: deploy
  image: registry.example.com/nomad/nomad-runner:latest
  script:
    - envsubst '${CI_COMMIT_SHORT_SHA}' < project.nomad > job.nomad
    - cat job.nomad
    - nomad validate job.nomad
    - nomad plan job.nomad || if [ $? -eq 255 ]; then exit 255; else echo "success"; fi
    - nomad run job.nomad
  environment:
    name: production
  allow_failure: false
  when: manual

Тут деплой відбувається в ручному режимі, але ви можете налаштувати його на зміну вмісту директорії проекту. Pipeline ж складається з двох етапів: зі збирання образу та його деплою в номад. На першому етапі ми збираємо докер образ і пушимо його в наш Registry, на другому ж запускаємо нашу job в Nomad.

job "monitoring-status" {
    datacenters = ["dc1"]
    migrate {
        max_parallel = 3
        health_check = "checks"
        min_healthy_time = "15s"
        healthy_deadline = "5m"
    }

    group "zhadan.ltd" {
        count = 1
        update {
            max_parallel      = 1
            min_healthy_time  = "30s"
            healthy_deadline  = "5m"
            progress_deadline = "10m"
            auto_revert       = true
        }
        task "service-monitoring" {
            driver = "docker"

            config {
                image = "registry.domain.name/example/project:${CI_COMMIT_SHORT_SHA}"
                force_pull = true
                auth {
                    username = "gitlab_user"
                    password = "gitlab_password"
                }
                port_map {
                    http = 8000
                }
            }
            resources {
                network {
                    port "http" {}
                }
            }
        }
    }
}

Зверніть увагу, що у мене закритий Registry і для успішного пулла докер-образу мені потрібно авторизуватися в ньому. Найкращим рішенням у разі є висновок логіна і пароля в Vault з наступною інтеграцією його з Nomad. Nomad нативно підтримує Vault. Але для початку в Vault встановимо необхідні policy для Nomad, їх можна завантажити:

# Download the policy and token role
$ curl https://nomadproject.io/data/vault/nomad-server-policy.hcl -O -s -L
$ curl https://nomadproject.io/data/vault/nomad-cluster-role.json -O -s -L

# Write the policy to Vault
$ vault policy write nomad-server nomad-server-policy.hcl

# Create the token role with Vault
$ vault write /auth/token/roles/nomad-cluster @nomad-cluster-role.json

Тепер, створивши необхідні policy, ми в блоці task у файлі job.nomad додамо інтеграцію з Vault:

vault {
  enabled = true
  address = "https://vault.domain.name:8200"
  token = "token"
}

Я використовую авторизацію по токену та прописую його безпосередньо тут, також є варіант вказівки токена як змінної при запуску nomad agent:

$ VAULT_TOKEN=<token> nomad agent -config /path/to/config

Тепер ми можемо використовувати ключі з Vault. Принцип роботи простий: ми створюємо файл у Nomad job, який зберігатиме в собі значення змінних, наприклад:

template {
                data = <<EOH
{{with secret "secrets/pipeline-keys"}}
REGISTRY_LOGIN="{{ .Data.REGISTRY_LOGIN }}"
REGISTRY_PASSWORD="{{ .Data.REGISTRY_LOGIN }}{{ end }}"

EOH
    destination = "secrets/service-name.env"
    env = true
}

Ось таким нескладним підходом можна налаштувати доставку контейнерів у кластер Nomad та працювати з ним надалі. Я ж скажу, що певною мірою я симпатизую Nomad — він більше підходить для невеликих проектів, де Kubernetes може спричинити додаткові складнощі і не реалізовуватиме свого потенціалу до кінця. До того ж, Nomad відмінно підійде початківцям – він просто встановлюється та конфігурується. Однак при тестуванні на деяких проектах я стикаюся з проблемою його ранніх версій — багатьох базових функцій просто немає або вони працюють некоректно. Тим не менш, я вважаю, що Nomad буде розвиватися далі і в майбутньому обросте потрібними всіма функціями.

Автор: Ілля Андрєєв, під редакцією Олексія Жадан та команди «Лайв Лінукс»


Джерело: habr.com

Додати коментар або відгук