Docker в LXC без Swarm

Иногда требуется запустить docker в lxc, например, для установки Docker Swarm, или для установки Kubernetos. LXC позволяет запускать Docker внутри контейнера. Рекомендуется использовать хост машину Ubuntu 18.04.

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

Перейдите по ссылке, если вас интересует настройка Docker Swarm в LXC.

Прежде чем начать:

  1. выполните базовую настройку Ubuntu
  2. обновите ядро системы до версии 5.3
  3. установите LXC на Ubuntu по инструкции

На хост машине добавьте драйвера br_netfilter в автозагрузку:

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

Для того, чтобы запустить Docker в LXC нужно выполнить следующие действия:

  1. запустить контейнер в privileged mode
  2. включить мод lxc nested containers

Запуск контейнера в privileged mode

Privileged mode - это привелигированный режим, запуск контейнера от рута. 

Перед установкой контейнера, нужно закоментировать строки в файле /etc/lxc/default.conf. Комментировать строки, нужно только при установке контейнера, который вы хотите, чтобы он работал в привелигированном режиме.

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

Это позволит установить контейнер под рутом.

Выполните команду установки контейнера для Centos 7:

lxc-create -t download -n docker0 -- --dist centos --release 7 --arch amd64

Для Ubuntu 20.04

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

Для Ubuntu 22.04:

lxc-create -t download -n test-ubuntu -- --dist ubuntu --release bionic --arch amd64

Для Ubuntu 24.04:

lxc-create -t download -n test-ubuntu -- --dist ubuntu --release noble --arch amd64

Затем нужно обязательно раскоментировать эти строки обратно в файле /etc/lxc/default.conf

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

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

Рекомендуется для 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
no-dhcp-interface=
interface=lxcbr0
except-interface=eth*
except-interface=enp*
except-interface=wlan*
except-interface=wlp*
except-interface=virbr0
except-interface=fan-*

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

Nested контейнер — это возможность запустить контейнер в контейнере (вложенные контейнеры). Чтобы включить данную опцию, нужно внести изменения в конфиг LXC контейнера.

Опция включается в файле конфига контейнера /var/lib/lxc/<название контейнера>/config

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

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

Пропишите параметры сети:

lxc.net.0.ipv4.address = 172.30.0.20/24
lxc.net.0.ipv4.gateway = 172.30.0.1

Также вам понадобится прописать параметры для монтирования файловой системы cgroup и отключения AppArmor в контейнере

lxc.mount.auto = cgroup-full:rw
lxc.apparmor.profile = unconfined
lxc.cgroup.devices.allow = a
lxc.cap.drop =

Для Ubuntu в файле /var/lib/lxc/<название контейнера>/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]

Настройка доступа к видеокарте

Для доступа к видеокарте nvidia в конфиге контейнера укажите строки:

# GPU
lxc.cgroup.devices.allow = c 195:* rwm
lxc.cgroup.devices.allow = c 243:* rwm
lxc.cgroup.devices.allow = c 510:* rwm
lxc.cgroup2.devices.allow = c 195:* rwm
lxc.cgroup2.devices.allow = c 243:* rwm
lxc.cgroup2.devices.allow = c 510:* rwm
lxc.mount.entry = /dev/nvidia0 dev/nvidia0 none bind,optional,create=file
lxc.mount.entry = /dev/nvidiactl dev/nvidiactl none bind,optional,create=file
lxc.mount.entry = /dev/nvidia-uvm dev/nvidia-uvm none bind,optional,create=file
lxc.mount.entry = /dev/nvidia-modeset dev/nvidia-modeset none bind,optional,create=file
lxc.mount.entry = /dev/nvidia-uvm-tools dev/nvidia-uvm-tools none bind,optional,create=file

Настройка ssh сервера

1) Вместо /home/user укажите вашу домашнюю папку.

mkdir /var/lib/lxc/<название контейнера>/rootfs/home/ubuntu/.ssh
cat /home/user/.ssh/id_rsa.pub >> /var/lib/lxc/<название контейнера>/rootfs/home/ubuntu/.ssh/authorized_keys
chmod 700 /var/lib/lxc/<название контейнера>/rootfs/home/ubuntu/.ssh
chmod 600 /var/lib/lxc/<название контейнера>/rootfs/home/ubuntu/.ssh/authorized_keys
chown -R 1000:1000 /var/lib/lxc/<название контейнера>/rootfs/home/ubuntu/.ssh

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

lxc-start <название контейнера>
lxc-attach <название контейнера>

3) Разрешите пинг

sudo setcap cap_net_raw+ep /bin/ping

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

apt-get update
apt-get install openssh-server

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

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

ssh ubuntu@172.30.0.20

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

Настройка контейнера Ubuntu 24.04

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

apt update
apt install aptitude mc nano htop iftop bwm-ng iperf iperf3 iotop tmux screen net-tools rsync jq

2) Настройка DNS

Выполните

rm /etc/resolv.conf
nano /etc/resolv.conf

Укажите следующие настройки

nameserver 127.0.0.53
options edns0 trust-ad
ndots:1
search .

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

systemctl stop apparmor
systemctl disable apparmor

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

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

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

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

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"

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

locale-gen

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

aptitude install openssh-server

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

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

SystemMaxUse=1G

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

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

systemctl daemon-reload

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

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

groupadd -r wheel
usermod -a -G wheel ubuntu

Создайте файл /etc/sudoers.d/wheel

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

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

8) Установите iptables

aptitude install iptables-persistent

Обязательно включите NAT, если вы хотите использовать Docker или LXC

echo 1 > /proc/sys/net/ipv4/ip_forward
echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/ip_forward.conf

При работе с iptables будьте осторожны.
Одно неверное движение и доступ к серверу может быть заблокирован!!!

Пример конфига /etc/iptables/rules.v4

*filter
:INPUT ACCEPT [19:913]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [39:3584]
:ALLOW-INPUT - [0:0]
:f2b-sshd - [0:0]

-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT

# Fail2Ban SSH
#-A INPUT -p tcp -m multiport --dports 22 -j f2b-sshd

# Разрешаем входящие соединения ssh
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT

# Перейти к цепочке ALLOW-INPUT
-A INPUT -j ALLOW-INPUT

# Запрещаем остальные входящие соединения
-A INPUT -j REJECT
-A FORWARD -j REJECT

# Раскомментируйте, если нужно запретить все исходящие соединения 
#-A OUTPUT -j REJECT

# Разрешить http
-A ALLOW-INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A ALLOW-INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A ALLOW-INPUT -j RETURN

# Fail2ban
-A f2b-sshd -j RETURN

COMMIT

Скопируйте этот же конфиг в /etc/iptables/rules.v6

cp /etc/iptables/rules.v4 /etc/iptables/rules.v6

9) Перезагрузите контейнер:

init 6

Установка Docker на Centos 7

Запустите контейнер и войдите в него:

lxc-start <название контейнера>
lxc-attach <название контейнера>

Установите docker и включите автозапуск:

yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y docker-ce docker-ce-cli containerd.io

systemctl enable docker
systemctl start docker

Установка Docker на Ubuntu 24.04

Установите Docker

sudo aptitude install docker.io

Альтернативный способ

curl -sSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker

Настройка Docker

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

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "1"
  }
}

Можно также добавить строчки

"max-concurrent-uploads": 1,
"max-concurrent-downloads": 1,

Они позволяют ограничить количество потоков на загрузку докер образов. По умолчанию 3 потока.

Перезапустите докер

service docker restart

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

docker ps

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

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

usermod -a -G docker ubuntu

В крон через команду sudo crontab -e пропишите команду, которая будет автоматически очищать контейнеры

0 */2 * * * docker system prune --filter "until=24h" -f -a > /dev/null
0 */2 * * * docker image prune --filter "until=168h" -f > /dev/null

После установки, скачайте тестовый контейнер и убедитесь что он работает:

docker pull alpine
docker run -it -d --name test alpine /bin/sh

Выполните команду docker ps, должно вам выдать:

root@docker0:/# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
93f0d96197e0        alpine              "/bin/sh"           28 seconds ago      Up 25 seconds                           test

Проверьте работу контейнера:

root@docker0:/# docker exec -it test ping google.com
PING google.com (64.233.164.101): 56 data bytes
64 bytes from 64.233.164.101: seq=0 ttl=42 time=222.140 ms
64 bytes from 64.233.164.101: seq=1 ttl=42 time=140.911 ms
^C
--- google.com ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 140.911/181.525/222.140 ms

root@docker0:/# docker exec -it test ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:34 errors:0 dropped:0 overruns:0 frame:0
          TX packets:33 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:3728 (3.6 KiB)  TX bytes:3293 (3.2 KiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

Возникаемые ошибки

Если возникают ошибки:

docker: Error response from daemon: Could not check if 
docker-default AppArmor profile was loaded: open 
/sys/kernel/security/apparmor/profiles: permission denied.
failed to register layer: Error processing tar file(exit status 1): 
remount /, flags: 0x84000: permission denied
docker: Error response from daemon: OCI runtime create failed: 
container_linux.go:345: starting container process caused 
"process_linux.go:430: container init caused \"process_linux.go:396: 
setting cgroup config for procHooks process caused \\\"
failed to write c 10:200 rwm to devices.allow: write /sys/fs/cgroup/devices/docker/27822e65d0ccd42267cd420309f1de3ea2ddfc353d18d9ab9d362b0549b43ed0/devices.allow: 
operation not permitted\\\"\"": unknown.

То это скорее всего связано с AppArmor. Сделайте tail -n 20 /var/log/syslog. Если в логе присутсвуют следующие строки:

Jul 16 23:49:04 alfabook kernel: 
[45843.968279] audit: type=1400 audit(1563299344.593:66): 
apparmor="DENIED" operation="mount" info="failed flags match" 
error=-13 profile="lxc-container-default-cgns" name="/" 
pid=5274 comm="exe" flags="rw, rslave"
Jul 17 00:00:05 alfabook kernel:
[46504.633713] audit: type=1400 audit(1563300005.284:68): 
apparmor="DENIED" operation="mount" info="failed flags match" 
error=-13 profile="lxc-container-default-with-nesting" 
name="/run/docker/runtime-runc/moby/0f36b9a62ec3234ad83b341a67f1a2f16c5db3dc55a6827720b85e30f0ecc547/runc.DcCoEi" 
pid=7011 comm="exe" flags="ro, remount, bind"

Jul 17 00:00:05 alfabook kernel:
[46504.676276] audit: type=1400 audit(1563300005.328:69):
apparmor="DENIED" operation="mount" info="failed flags match"
error=-13 profile="lxc-container-default-with-nesting"
name="/var/lib/docker/overlay2/f40a7cc6f04db2b5cd48ddf8ec82303e07d9a744241250852c4462c12aa58df1/merged/"
pid=7028 comm="runc:[2:INIT]" srcname="/var/lib/docker/overlay2/f40a7cc6f04db2b5cd48ddf8ec82303e07d9a744241250852c4462c12aa58df1/merged/"
flags="rw, rbind"

То, да причина в AppArmor. Обычно в конфиг LXC lxc.apparmor.profile, lxc.mount.auto, lxc.cgroup.devices.allow помогает. Но если не помогло, то выполните следующие команды:

Включите обучение AppArmor

aa-complain /etc/apparmor.d/*

Запустите LXC контейнеры, и попробуйте заново запустить docker. Затем, после выдачи ошибки, перейдите на хост машину и выполните команду aa-logprof. Данная комманда анализирует лог /var/log/syslog и предлагает вам варианты патчинга правил AppArmor. 

Пропатчите конфиг AppArmor и затем выключите обучение:

aa-enforce /etc/apparmor.d/*

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

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