Сап, коллеги!
В этот раз я сидел, работал с докером и решил, а что если я попробую запустить 2 веб сервера в разных контейнерах, где контейнер A будет лишь проксировать всё в контейнер B, а веб сервер в контейнере B будет возвращать html файл.
Сделать это в докере? Сущий пустяк.
Что если воспользоваться тем, что нам даёт ядро линукса.
Для начала подготовим к этому нашу файловую систему:
mkdir -p /srv/containers
cd /srv/containers
Я захотел, чтобы наши контейнеры были на базе arch linux. Вы же можете скачать bootstrap версию другого дистрибутива на ваш вкус
wget https://mirror.i3d.net/pub/archlinux/iso/2025.08.01/archlinux-bootstrap-x86_64.tar.zst
tar -xf archlinux-bootstrap-x86_64.tar.zst
mv root.x86_64 arch-fs
Так, мы подготовили общую среду для наших контейнеров.
Теперь займёмся подготовкой тех директорий, которые не будут общими для наших контейнеров:
/var/log/access.log
/var/log/error.log
/var/www/html
/etc/nginx
Создадим раздельные директории:
mkdir -p container{1,2}/{var/{log,www/html},etc/nginx}
touch container{1,2}/var/log/access.log
touch container{1,2}/var/log/error.log
echo "Hello friend!" > container2/var/www/html/index.html
На этом наша файловая система почти готова, осталось доработать общую директорию arch-fs и дописать сами nginx.conf файлы
cp /usr/sbin/nginx arch-fs/usr/sbin
mkdir -p arch-fs/{var/{log,lib/nginx/body,www},etc/nginx}
Теперь пропишем минимальный nginx конфиг для второго контейнера.
В файле по директории container2/etc/nginx/nginx.conf:
events {
}
http {
server {
listen 80;
server_name localhost;
location / {
root /var/www/html;
index index.html;
try_files $uri $uri/ =404;
}
}
}
А затем и для первого контейнера.
В файле по директории container1/etc/nginx/nginx.conf:
events {
}
http {
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://10.0.1.2:80;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
Теперь перейдём к настройки сети.
Начнём с создания двух сетевых пространств имён:
ip netns add n_1
ip netns add n_2
Создадим виртуальный Ethernet интерфейс и пробросим один из его концов в n_1
ip link add nginx_out type veth peer name nginx_in
ip link set nginx_in netns n_1
После каждого этапа можно проверять резултат настройки через:
ip a
И в нужном сетевом пространстве имён:
ip netns exec n_1 ip a
Выдадим получившейся паре ipv4 адреса:
ip addr add 10.0.0.1/24 dev nginx_out
ip netns exec n_1 ip addr add 10.0.0.2/24 dev nginx_in
Вот мы и настроили наш туннель в n_1. Осталось всё это дело поднять:
ip link set nginx_out up
ip netns exec n_1 ip link set nginx_in up
ip netns exec n_1 ip link set lo up # почему бы и нет
Если всё корректно настроено, то теперь мы можем пингануть адрес 10.0.0.2:
ping 10.0.0.2
Проделаем все те же действия, но новый пара у нас будет уже между n_1 и n_2:
ip netns exec n_1 ip link add nginx_out_2 type veth peer name nginx_in_2
ip netns exec n_1 ip link set nginx_in_2 netns n_2
ip netns exec n_1 ip addr add 10.0.1.1/24 dev nginx_out_2
ip netns exec n_2 ip addr add 10.0.1.2/24 dev nginx_in_2
ip netns exec n_1 ip link set nginx_out_2 up
ip netns exec n_2 ip link set nginx_in_2 up
ip netns exec n_2 ip link set lo up
Если всё получилось корректно, то должен теперь из n_1 пинговаться 10.0.1.2:
ip netns exec n_1 ping 10.0.1.2
Ура! С настройкой сети мы закончили.
Последний штрих, создание shell скриптов для запуска наших контейнеров:
В файле /srv/container/start_1.sh
#!/bin/bash
mount --bind ./container1/var/log/nginx ./arch-fs/var/log/nginx
mount --bind ./container1/etc/nginx ./arch-fs/etc/nginx
chroot ./arch-fs /sbin/nginx -g 'daemon off;'
И в файле /srv/container/start_2.sh
#!/bin/bash
mount --bind ./container2/var/log/nginx ./arch-fs/var/log/nginx
mount --bind ./container2/var/www/html ./arch-fs/var/www/html
mount --bind ./container2/etc/nginx ./arch-fs/etc/nginx
chroot ./arch-fs /sbin/nginx -g 'daemon off;'
Готово! Осталось это всё запустить:
ip netns exec n_2 unshare -m -i -p -u -f --mount-proc ./start2.sh &
ip netns exec n_1 unshare -m -i -p -u -f --mount-proc ./start1.sh &
И теперь если мы попробуем отправить http запрос по адресу 10.0.0.2:80/ то должны получить содержимое файла index.html, т.е. Hello friend!
curl http://10.0.0.2:80/
Hello friend!
Фух, это было утомительно. Однако у нас получилось запустить 2 ведь сервера в раздельных контейнерах без докера. А ведь с использованием докера это всё бы заняло считанные минуты!