Docker Swarm в LXC контейнерах
Настройка Docker Swarm в LXC контейнерах.
Предисловие
Docker Swarm - это простой кластер. Для его создания нужно несколько машин. Чтобы развернуть кластер на локалке, нужно сначала запустить виртуальные машины, а в них настроить кластер. Для этой задачи хорошо подойдут LXC контейнеры. LXC контейнеры в отличии от VirtualBox не будут требовать резервирования оперативной памяти. Также LXC выполняется на том же ядре, что и хост, это ускорит работу Docker Swarm.
Инструкция по запуску Docker Swarm на локальной машине
Для запуска Docker swarm на локальной машине необходимо создать два и более LXC контейнера, установить туда Docker и иницировать Swarm. Контейнеры с докер должны работать в привелигированном режиме.
Перейдите по ссылке, если вас интересует обычная настройка Docker Swarm.
Перейдите по ссылке, если вас интересует настройка Docker в LXC без Swarm.
Перед тем как настривать Docker Swarm в LXC установите:
Настройка сети
Рекомендуется использовать сеть 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/lxc/dnsmasq.conf и пропишите в нем:
port=53
listen-address=172.30.0.1
resolv-file=/etc/resolv.conf
domain-needed
А также в /etc/hosts пропишите IP адреса контейнеров
172.30.0.20 docker0
172.30.0.21 docker1
Установка драйвера 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
2) Установитк два контейнера:
lxc-create -t download -n docker0 -- --dist ubuntu --release focal --arch amd64
lxc-create -t download -n docker1 -- --dist ubuntu --release focal --arch amd64
3) Раскоментируйте обратнр строчки
lxc.idmap = u 0 100000 65536
lxc.idmap = g 0 100000 65536
4) Внесите изменения в файл /var/lib/lxc/docker0/config
Раскоментируйте строку
lxc.include = /usr/share/lxc/config/nesting.conf
Это позволит создать Nested контейнер — возможность запустить контейнер в контейнере (вложенные контейнеры).
Добавьте в файл следующие строки
lxc.net.0.ipv4.address = 172.30.0.20/24
lxc.net.0.ipv4.gateway = 172.30.0.1
lxc.mount.auto = cgroup-full:rw
lxc.apparmor.profile = unconfined
lxc.cgroup.devices.allow = a
lxc.cap.drop =
В файле /var/lib/lxc/docker0/rootfs/etc/netplan/10-lxc.yaml укажите:
network:
version: 2
ethernets:
eth0:
dhcp4: no
dhcp6: no
addresses: [172.30.0.20/24]
gateway4: 172.30.0.1
nameservers:
addresses: [172.30.0.1]
5) Пересоздайте 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.
6) Скопируйте ssh ключ. Вместо /home/user укажите вашу домашнюю папку.
mkdir /var/lib/lxc/docker0/rootfs/root/.ssh
chmod 700 /var/lib/lxc/docker0/rootfs/root/.ssh
cat /home/user/.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/user/.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 101000:101000 /var/lib/lxc/docker0/rootfs/home/ubuntu/.ssh
7) Запустите контейнер и подключитесь к нему:
lxc-start docker0
lxc-attach docker0
8) Установите программы:
apt update
apt install aptitude mc nano htop iftop bwm-ng iperf iperf3 iotop tmux screen openntpd sshfs net-tools dnsutils bind9-utils
9) Установите локаль. Раскоментируйте строки в файле /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
10) Установите ssh сервер:
aptitude install openssh-server
11) Далее нужно переподключиться к контейнеру через 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) Добавьте группу www
groupadd -g 800 -r www
useradd -u 800 -g 800 -r www
mkdir /home/www
chown www:www /home/www
usermod -d /home/www www
usermod -s /bin/bash www
5) Установите Docker
curl -sSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker
apt-get install docker-compose
Для хранения логов рекомендуется journald. В файле /etc/docker/daemon.json пропишите:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "1"
}
}
6) Отключите apparmor в LXC контейнере
systemctl stop apparmor
systemctl disable apparmor
7) Проверьте запущен ли докер:
docker ps
Если выдает ошибку, значит проблема в файле /etc/docker/daemon.json. Пересоздайте его. Возможно в нем скопировались невидимые символы.
8) Добавьте в группу docker пользователя ubuntu чтобы он мог управлять docker
usermod -a -G docker ubuntu
usermod -a -G docker www
Установка контейнера 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, т.к. на нем должен запуститься контейнер.
Исправление ошибок при запуске 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 при проброске портов.
DNS Round Robin - это способ адресации к сервисам. В докере существуют два способа через виртуальные ip адреса (по умолчанию) и через DNS Round Robin. При использовании виртуальных IP адресов, для каждого сервиса создается IP адрес и все контейнеры его получают при поиске сервиса. При отправке пакета на этот IP адрес, докер сам разруливает к какому контейнеру отправить запрос. Получается виртуальный IP адрес - это промежуточный IP адрес. При использовании DNS Round Robin будут другие контейнеры будут получать прямые IP адреса контейнеров.
mode: host отвечает за проброс портов. Существует два варианта ingress (по умолчанию) и host. При использовании ingress обращение на порт любого серверу в Docker кластере будет переадресовано в контейнер, где указан проброс этого порта. Если поставить mode: host, то проброс потров будет работать только на том сервер, где запустился этот контейнер.