Docker Swarm в LXC контейнерах

Настройка Docker Swarm в LXC контейнерах.

Предисловие

Docker Swarm — это простой кластер. Для его создания нужно несколько машин. Чтобы развернуть кластер на локалке, нужно сначала запустить виртуальные машины, а в них настроить кластер. Для этой задачи хорошо подойдут LXC контейнеры. LXC контейнеры в отличии от VirtualBox не будут требовать резервирования оперативной памяти. Также LXC выполняется на том же ядре, что и хост, это ускорит работу Docker Swarm.

Исправление ошибок при запуске Docker Swarm

1) Если Docker swarm в LXC выдает ошибку:

WARNING: No swap limit support
WARNING: bridge-nf-call-iptables is disabled
WARNING: bridge-nf-call-ip6tables is disabled

или

Failed creating ingress network: error creating external connectivity network: cannot restrict inter-container communication: please ensure that br_netfilter kernel module is loaded

То это означает, что у вас установлено старое ядро. Обновите ядро на хост машине согласно инструкции.

И не загружен модуль ядра br_netfilter. Его нужно загрузить командой:

modprobe br_netfilter
modprobe overlay

Или прописать его в автозагрузку (об этом ниже в инструкции).

Более подробно об этой ошибке:
https://github.com/lxc/lxd/issues/5193#issuecomment-431693318
https://bugs.launchpad.net/ubuntu/+source/docker.io/+bug/1618283

2) Может возникнуть эта ошибка;

Starting container failed: container: endpoint create on GW Network failed: failed to create endpoint gateway_00555448fe8e on network docker_gwbridge: network does not exist

и syslog будет выдавать следующее

$ tail -n 20 /var/log/syslog

[ 3419.850480] br0: port 2(veth0) entered blocking state
[ 3419.850486] br0: port 2(veth0) entered forwarding state
[ 3419.943752] br0: port 2(veth0) entered disabled state

Oct 16 08:38:25 docker0 kernel: [ 3285.847755] veth0: renamed from veth945e509
Oct 16 08:38:25 docker0 kernel: [ 3285.964009] eth0: renamed from veth6d61c08
Oct 16 08:38:25 docker0 kernel: [ 3286.135717] br0: port 3(veth1) entered disabled state
Oct 16 08:38:25 docker0 kernel: [ 3286.136040] br0: port 3(veth1) entered blocking state
Oct 16 08:38:25 docker0 kernel: [ 3286.136043] br0: port 3(veth1) entered forwarding state

Это связано тоже с версией ядра и тем, что модуль br_netfilter не загружен.

3) Не пингуются контейнеры между собой и не пробрасывается порт. Это происходит по одной причине. Почему то виртуальные IP адреса и ingress в докер под LXC не работают. Решается это просто. Нужно включить dnsrr (DNS Round Robin) в секции для каждого сервиса и использовать mode: host при проброске портов. Ниже в самом конце статьи, я привожу пример yaml файла, где указаны эти параметры.

DNS Round Robin — это способ адресации к сервисам. В докере существуют два способа через виртуальные ip адреса (по умолчанию) и через DNS Round Robin. При использовании виртуальных IP адресов, для каждого сервиса создается IP адрес и все контейнеры его получают при поиске сервиса. При отправке пакета на этот IP адрес, докер сам разруливает к какому контейнеру отправить запрос. Получается виртуальный IP адрес — это промежуточный IP адрес. При использовании DNS Round Robin будут другие контейнеры будут получать прямые IP адреса контейнеров.

mode: host отвечает за проброс портов. Существует два варианта ingress (по умолчанию) и host. При использовании ingress обращение на порт любого серверу в Docker кластере будет переадресовано в контейнер, где указан проброс этого порта. Если поставить mode: host, то проброс потров будет работать только на том сервер, где запустился этот контейнер.

Инструкция по запуску Docker Swarm на локальной машине

Для запуска Docker swarm на локальной машине необходимо создать два и более LXC контейнера, установить туда Docker и иницировать Swarm. Контейнеры с докер должны работать в привелигированном режиме.

На Ubuntu 18.04 должно быть установлено новое ядро 5.3 и программа LXC.

Если вас интересует обычная настройка Docker Swarm, то перейдите по этой ссылке. Если вас интересует настройка Docker в LXC без Swarm, то перейдите по этой ссылке.

Настройка сети

Рекомендуется использовать сеть 172.30.0.1/24. Более подробный список указан в списке сетей.

В файле /etc/default/lxc-net пропишите

USE_LXC_BRIDGE="true"
LXC_BRIDGE="lxcbr0"
LXC_ADDR="172.30.0.1"
LXC_NETMASK="255.255.255.0"
LXC_NETWORK="172.30.0.0/24"
LXC_DHCP_RANGE="172.30.0.2,172.30.0.254"
LXC_DHCP_MAX="253"
#LXC_DHCP_CONFILE=/etc/lxc/dnsmasq.conf
#LXC_DOMAIN="lxc"

А также в /etc/hosts пропишите IP адреса контейнеров

172.30.0.20 docker0
172.30.0.21 docker0

Установка драйвера br_netfilter

На хост машине сделайте:

echo overlay >> /etc/modules-load.d/docker.conf
echo br_netfilter >> /etc/modules-load.d/docker.conf

Установка контейнера docker0

Все команды надо делать по рутом.

1) закоментировать строки в файле /etc/lxc/default.conf для создания контейнеров в привелигированном режиме. Потом нужно будет закоментировать обратно.

#lxc.idmap = u 0 100000 65536
#lxc.idmap = g 0 100000 65536

Установить два контейнера:

lxc-create -t download -n docker0 -- --dist ubuntu --release focal --arch amd64
lxc-create -t download -n docker1 -- --dist ubuntu --release focal --arch amd64

2) Раскоментировать обратно строки в файле строки в файле /etc/lxc/default.conf

lxc.idmap = u 0 100000 65536
lxc.idmap = g 0 100000 65536

3) Внесите изменения в файл /var/lib/lxc/docker0/config

Раскоментируйте строку

lxc.include = /usr/share/lxc/config/nesting.conf

Это позволит создать Nested контейнер — возможность запустить контейнер в контейнере (вложенные контейнеры).

Добавьте в файл следующие строки

lxc.mount.auto = cgroup-full:rw
lxc.apparmor.profile = unconfined
lxc.cgroup.devices.allow = a
lxc.net.0.ipv4.address = 172.30.0.20/24
lxc.net.0.ipv4.gateway = 172.30.0.1

В файле /var/lib/lxc/docker0/rootfs/etc/netplan/10-lxc.yaml укажите:

network:
  version: 2
  ethernets:
    dhcp4: no
    dhcp6: no
    addresses: [172.30.0.20/24]
    gateway4: 172.30.0.1
    nameservers:
      addresses: [172.30.0.1]

4) Пересоздайте resolv.conf

rm /var/lib/lxc/docker0/rootfs/etc/resolv.conf
nano /var/lib/lxc/docker0/rootfs/etc/resolv.conf

Укажите в нем новый адреса DNS серверов:

nameserver 172.30.0.1

Достаточно указать ДНС 172.30.0.1. На хост машине запускается dnsmasq на этом адресе и является проксирующим ДНС. Также он резолвит все домены из /etc/hosts. Поэтому при настройке сети на хосте нужно было в /etc/hosts прописать IP адреса контейнеров docker0 и docker1.

5) Скопируйте ssh ключ. Вместо /home/user укажите вашу домашнюю папку.

mkdir /var/lib/lxc/docker0/rootfs/root/.ssh
chmod 700 /var/lib/lxc/docker0/rootfs/root/.ssh
cat /home/alfa/.ssh/id_rsa.pub >> /var/lib/lxc/docker0/rootfs/root/.ssh/authorized_keys
chmod 600 /var/lib/lxc/docker0/rootfs/root/.ssh/authorized_keys

mkdir /var/lib/lxc/docker0/rootfs/home/ubuntu/.ssh
chmod 700 /var/lib/lxc/docker0/rootfs/home/ubuntu/.ssh
cat /home/alfa/.ssh/id_rsa.pub >> /var/lib/lxc/docker0/rootfs/home/ubuntu/.ssh/authorized_keys
chmod 600 /var/lib/lxc/docker0/rootfs/home/ubuntu/.ssh/authorized_keys
chown -R 1000:1000 /var/lib/lxc/docker0/rootfs/home/ubuntu/.ssh

6) Запустите контейнер и подключитесь к нему:

lxc-start docker0
lxc-attach docker0

7) Установите программы:

apt update
apt install aptitude mc nano htop iftop bwm-ng iperf iperf3 iotop tmux screen openntpd sshfs net-tools dnsutils bind9-utils

8) Установите локаль. Раскоментируйте строки в файле /etc/locale.gen

en_US.UTF-8 UTF-8
ru_RU.UTF-8 UTF-8

Пропишите локаль на уровне системы:

nano /etc/profile.d/0.locale.sh

export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/sbin:/bin"
export LANG="en_US.UTF-8"
export LANGUAGE="en_US:en"
export LC_CTYPE="en_US.UTF-8"
export LC_NUMERIC="en_US.UTF-8"
export LC_TIME="en_US.UTF-8"
export LC_COLLATE="en_US.UTF-8"
export LC_MONETARY="en_US.UTF-8"
export LC_MESSAGES="en_US.UTF-8"
export LC_PAPER="en_US.UTF-8"
export LC_NAME="en_US.UTF-8"
export LC_ADDRESS="en_US.UTF-8"
export LC_TELEPHONE="en_US.UTF-8"
export LC_MEASUREMENT="en_US.UTF-8"
export LC_IDENTIFICATION="en_US.UTF-8"
export EDITOR=nano

Пересоздайте локаль:

locale-gen

9) Установите ssh сервер:

aptitude install openssh-server

10) Далее нужно переподключиться к контейнеру через ssh.

Выйдите из контейнера:

exit

Настройка конфига ssh

Для упрощения подключения к контейнерам, рекомендуется в локальной домашней папке в файле ~/.ssh/config прописать параметры docker0 и docker1

Host *
  Protocol 2
  KeepAlive yes
  TCPKeepAlive yes
  ServerAliveInterval 5
  ServerAliveCountMax 100
  Compression no
  #CompressionLevel 9
  #ForwardX11 yes
  UseRoaming no

Host docker0
  Hostname 172.30.0.20
  User ubuntu
  Port 22

Host docker1
  Hostname 172.30.0.21
  User ubuntu
  Port 22

После внесенных изменений подключаться можно будет по командам:

ssh docker0
ssh docker1

или:

ssh root@docker0
ssh root@docker1

Настройка контейнера docker0

1) Подключитесь к контейнеру через ssh

Откройте терминал под текущим пользователем (не рут) и подключитесь к системе:

ssh docker0

Если сертификат установлен верно, то должно подключиться без пароля.

2) Ограничьте размер логов systemd

Пропишите в /etc/systemd/journald.conf строчку:

SystemMaxUse=1G

Это строчка ограничивает максимальный размер логов в 1 гигабайт. 

Перезагрузите конфигурацию systemd:

systemctl daemon-reload

3) Сделайте sudo su -l без ввода пароля

Добавьте группу wheel

groupadd -r wheel
usermod -a -G wheel ubuntu

в /etc/sudoers добавьте строчку

%wheel ALL=(ALL:ALL) NOPASSWD: ALL

Все пользователи, которые находятся в группе wheel будут иметь возможность выполнять команды рут без пароля

4) Установите Docker

apt install docker.io
systemctl enable docker

Для хранения логов рекомендуется journald. В файле /etc/docker/daemon.json пропишите:

{
  "log-driver": "journald"
}

5) Отключите apparmor в LXC контейнере

systemctl stop apparmor
systemctl disable apparmor

6) Проверьте запущен ли докер:

docker ps

Если выдает ошибку, значит проблема в файле /etc/docker/daemon.json. Пересоздайте его. Возможно в нем скопировались невидимые символы.

7) Добавьте в группу docker пользователя ubuntu чтобы он мог управлять docker

usermod -a -G docker ubuntu

Установка контейнера docker1

Зайдите по рут на хост машине. Остановите контейнер docker0.

lxc-stop docker0

Скопируйте docker0 в docker1

mkdir /var/lib/lxc/docker1
cp -rfpH /var/lib/lxc/docker0/* /var/lib/lxc/docker1

Поменяйте rootfs, hostname, IP и MAC адрес в файле /var/lib/lxc/docker1/config

lxc.rootfs.path = dir:/var/lib/lxc/docker1/rootfs
lxc.uts.name = docker1
lxc.net.0.hwaddr = 00:16:3e:c5:02:e9
lxc.net.0.ipv4.address = 172.30.0.21/24

Также в файле /var/lib/lxc/docker1/rootfs/etc/netplan/10-lxc.yaml

addresses: [172.30.0.21/24]

В файле /var/lib/lxc/docker1/rootfs/etc/hosts

127.0.1.1       docker1
127.0.0.1       localhost
::1             localhost ip6-localhost ip6-loopback
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters

В файле /var/lib/lxc/docker1/rootfs/etc/hostname

docker1

Настройка кластера

1) Запустите оба контейнера:

lxc-start docker0
lxc-start docker1

Подключитесь из двух терминалов к контейнерам:

ssh docker0
ssh docker1

2) На docker0 создайте кластер

docker swarm init --advertise-addr 172.30.0.20

3) Эта команда выдаст токен. Искользуйте его, чтобы на docker1 подключитесь к кластеру

docker swarm join --token TOKEN 172.30.0.20:2377

Кластер создан. Если вы забыли Token, то введите команду docker swarm join-token manager на primary node

Проверка работы кластера

Создайте файл compose.yaml

version: "3.3"

services:
    nginx:
        image: nginx:latest
        labels:
            name: nginx
            version: 1.0
        deploy:
            replicas: 1
            endpoint_mode: dnsrr
            update_config:
                parallelism: 1
                failure_action: rollback
                delay: 5s
            restart_policy:
                condition: "on-failure"
                delay: 10s
                window: 120s
            placement:
                constraints:
                    - "node.hostname == docker1"
        ports:
            - target: 80
              published: 80
              protocol: tcp
              mode: host
        networks:
            - backend
        logging:
            driver: journald
            
networks:

    backend:
        driver: overlay
        attachable: true

Запустите сервис

docker stack deploy --compose-file compose.yaml dev

Подождите некоторое время и проверьте запустился ли контейнер на хосте docker1 командой docker ps

Если он не запускается, узнайте ошибку

docker service ps --no-trunc dev_nginx

Обратите внимание, что указан параметр mode: host в секции проброса 80 порта. Это означает, что нужно по http обращаться к серверу docker1, т.к. на нем должен запуститься контейнер.

Дополнительные ссылки

  1. Более подробно о других полезных командах Docker
  2. Установка LXC в Ubuntu 18.04
  3. Установка контейнера LXC
  4. Docker в LXC
  5. Официальная инструкция установки Docker в Centos 7
  6. Справка по Docker Swarm