Контейнеры Linux (Linux containers) – это группа процессов, изолированных от остальной системы с помощью функций безопасности ядра Linux (пространств имён, контрольных групп и т. п.). Эта конструкция похожа на очень лёгкую виртуальную машину; в контейнерах нет расходов на запуск дополнительного ядра или симулирование аппаратного обеспечения. Это значит, что один сервер может легко поддерживать несколько контейнеров. С помощью контейнеров Linux можно запустить несколько экземпляров разных операционных систем на одном сервере, а также объединить приложение и его зависимости и изолировать их от остальной среды.
Предположим, у вас есть сервер, на котором нужно запустить несколько сервисов и веб-сайтов. В стандартной установке все сайты будут представлены виртуальными хостами, принадлежащими одному экземпляру Apache или Nginx. А контейнеры Linux позволяют изолировать каждый сайт и обеспечить ему собственный экземпляр веб-сервера.
Для создания и работы с контейнерами используется сервис LXD – гипервизор для управления всем жизненным циклом контейнеров Linux.
Данное руководство научит использовать LXD для обслуживания двух сайтов на основе Nginx в рамках одного сервера; каждый сайт будет помещен в отдельный контейнер. Также вы узнаете, как запустить обратный прокси-сервер HAProxy в третьем контейнере и перенаправить на него трафик.
Требования
- Сервер Ubuntu 16.04.
- Пользователь с доступом к sudo (вся работа выполняется в сессии этого пользователя).
- Настроенный брандмауэр (инструкции – здесь).
- Два FQDN и две записи A, направленные на IP-адрес сервера.
- Опционально: блочное хранилище в 20GB (здесь можно хранить данные контейнеров).
1: Добавление пользователя в группу lxd
Подключитесь к серверу как пользователь с доступом к sudo. Чтобы этот пользователь смог управлять контейнерами, его нужно добавить в группу lxd. Введите:
sudo usermod --append --groups lxd 8host
Выйдите из системы и войдите снова, чтобы обновить SSH-сессию.
2: Настройка LXD
Самым важным здесь является выбор типа бэкэнда для хранения контейнеров. Данные LXD рекомендуется хранить в файловой системе ZFS, размещенной в предварительно выделенном файле или в блочном хранилище. Чтобы включить поддержку ZFS в LXD, установите zfsutils-linux:
sudo apt-get update
sudo apt-get install zfsutils-linux
После этого можно инициализировать LXD. Во время инициализации вам будет предложено указать детали хранилища ZFS. Ниже вы найдете два подраздела для настройки предварительно выделенного файла или блочного хранилища. Следуйте соответствующему подразделу, а затем приступайте к настройкам сети.
Настройка предварительно выделенного файла
Чтобы сервис LXD мог использовать предварительно выделенный файл для хранения данных, сначала нужно инициализировать LXD.
sudo lxd init
Команда предложит ввести некоторую информацию (как показано ниже). Можно оставить значения по умолчанию (размер указывается в строке loop device).
Name of the storage backend to use (dir or zfs) [default=zfs]: zfs
Create a new ZFS pool (yes/no) [default=yes]? yes
Name of the new ZFS pool [default=lxd]: lxd
Would you like to use an existing block device (yes/no) [default=no]? no
Size in GB of the new loop device (1GB minimum) [default=15]: 15
Would you like LXD to be available over the network (yes/no) [default=no]? no
Do you want to configure the LXD bridge (yes/no) [default=yes]? yes
Warning: Stopping lxd.service, but it can still be activated by:
lxd.socket
LXD has been successfully configured.
После этого указанный объём будет автоматически выделен из дискового пространства сервера.
Настройка блочного хранилища
Найдите устройство, которое указывает на созданный том хранилища, чтобы добавить его в конфигурации LXD.
Примечание: В данном случае устройство будет называться /dev/disk/by-id/scsi-0D0_Volume_volume-fra1-01. Название устройства может отличаться.
Определив том, вернитесь в терминал и выполните следующую команду, чтобы инициализировать LXD:
sudo lxd init
Команда задаст ряд вопросов. Ответы на них указаны ниже:
Name of the storage backend to use (dir or zfs) [default=zfs]: zfs
Create a new ZFS pool (yes/no) [default=yes]? yes
Name of the new ZFS pool [default=lxd]: lxd
Когда команда предложит указать блочное устройство, выберите yes и укажите путь к этому устройству:
Would you like to use an existing block device (yes/no) [default=no]? yes
Path to the existing block device: /dev/disk/by-id/scsi-0DO_Volume_volume-fra1-01
На остальные вопросы можно ответить по умолчанию:
Would you like LXD to be available over the network (yes/no) [default=no]? no
Do you want to configure the LXD bridge (yes/no) [default=yes]? yes
Warning: Stopping lxd.service, but it can still be activated by:
lxd.socket
LXD has been successfully configured.
Настройка сети
Процесс инициализации представит ряд экранов, которые помогут настроить сетевой мост для контейнеров, чтобы они могли получить внутренние IP-адреса, взаимодействовать друг с другом и подключаться к Интернету.
Используйте значение по умолчанию для каждого параметра, кроме сети IPv6: здесь нужно выбрать No (в этом руководстве IPv6 не используется).
3: Создание контейнеров
Настройка LXD успешно завершена. Теперь можно создать контейнеры с помощью команды lxc.
Запросите список доступных установленных контейнеров:
lxc list
Команда вернёт:
Generating a client certificate. This may take a minute...
If this is your first time using LXD, you should also run: sudo lxd init
To start your first container, try: lxc launch ubuntu:16.04
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+
Сейчас команда lxc взаимодействует с гипервизором LXD впервые; вывод сообщает, что команда автоматически создала сертификат клиента для безопасной связи с LXD. Далее можно найти информацию о том, как запустить контейнер. В конце вывода можно найти пустой список контейнеров.
Создайте три контейнера: два для веб-серверов и один для обратного прокси-сервера. Цель обратного прокси-сервера – перенаправить входящие из Интернета соединения на правильный веб-сервер в контейнере.
С помощью команды lxc launch создайте и запустите контейнер Ubuntu 16.04 по имени web1. Символ x в части команды ubuntu:x – это сокращение от Xenial, кодового имени дистрибутива Ubuntu 16.04; ubuntu: – это идентификатор предварительно настроенного репозитория образов LXD.
Примечание: Полный список доступных образов для Ubuntu можно получить с помощью команды:
lxc image list ubuntu:
Список образов для других дистрибутивов покажет команда:
lxc image list images:
Чтобы создать необходимые контейнеры, введите:
lxc launch ubuntu:x web1
lxc launch ubuntu:x web2
lxc launch ubuntu:x haproxy
При создании первого контейнера команда загружает образ контейнера из Интернета и кэширует его. Следующие два контейнера будут созданы значительно быстрее.
Вывод после создания первого контейнера:
Creating web1
Retrieving image: 100%
Starting web1
Теперь у вас есть три пустых контейнера. Чтобы получить информацию о них, введите:
lxc list
На экране появится таблица, в которой указано имя, состояние, IP и тип каждого контейнера, а также наличие снапшотов контейнеров.
+---------+---------+-----------------------+------+------------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+---------+---------+-----------------------+------+------------+-----------+
| haproxy | RUNNING | 10.10.10.10 (eth0) | | PERSISTENT | 0 |
+---------+---------+-----------------------+------+------------+-----------+
| web1 | RUNNING | 10.10.10.100 (eth0) | | PERSISTENT | 0 |
+---------+---------+-----------------------+------+------------+-----------+
| web2 | RUNNING | 10.10.10.200 (eth0) | | PERSISTENT | 0 |
+---------+---------+-----------------------+------+------------+-----------+
Запишите имена контейнеров и их адреса IPv4.
4: Настройка контейнеров Nginx
Подключитесь к контейнеру web1 и настройте первый веб-сервер. Для этого используйте команду lxc exec. В ней укажите имя контейнера и команду, которую в нём нужно выполнить.
lxc exec web1 -- sudo --login --user ubuntu
Строка — указывает, где заканчиваются параметры lxc и начинаются команды, которые нужно выполнить в контейнере. Команда sudo –login –user ubuntu предоставит оболочку входа в предварительно настроенный аккаунт ubuntu в контейнере.
Примечание: Чтобы подключиться к контейнеру как root, введите:
lxc exec web1 -- /bin/bash
Командная строка в контейнере выглядит так:
ubuntu@web1:~$
Пользователь ubuntu в контейнере имеет доступ к sudo и может запускать команды с sudo, не указывая пароля.
Эта оболочка ограничена пределами контейнера. Все команды, запущенныев этой оболочке, останутся в контейнере и не повлияют на хост-сервер.
Обновите индекс пакетов в контейнере и установите Nginx:
sudo apt-get update
sudo apt-get install nginx
Отредактируйте веб-страницу по умолчанию для этого сайта и добавьте текст, который сообщит, что этот сайт размещен в контейнере web1. Откройте файл /var/www/html/index.nginx-debian.html:
sudo nano /var/www/html/index.nginx-debian.html
Внесите в файл следующие изменения:
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx on LXD container web1!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx on LXD container web1!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
...
Теперь в файле есть строка:
on LXD container web1
Сохраните и закройте файл.
Чтобы выйти из контейнера, введите:
logout
Повторите этот процесс в контейнере web2. Войдите в контейнер, установите веб-сервер и отредактируйте файл /var/www/html/index.nginx-debian.html (укажите web2 вместо web1). Затем закройте контейнер web2.
С помощью curl протестируйте веб-серверы в контейнерах. Для этого понадобятся IP-адреса контейнеров.
curl http://10.10.10.100/
http://10.10.10.100/" command
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx on LXD container web1!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx on LXD container web1!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
...
Аналогичным образом проверьте работу второго контейнера. Для этого укажите в команде curl его IP-адрес.
5: Настройка контейнера HAProxy
Теперь нужно настроить контейнер обратного прокси-сервера HAProxy.
Читайте также: Основы работы с HAProxy и базовые понятия балансировки нагрузки
Направьте трафик в контейнеры с помощью доменных имен.
Примечание: В руководстве используются условные домены. Первый веб-сайт доступен по доменам example.com и www.example.com, а второй сайт – по www2.example.com.
Войдите в контейнер haproxy:
lxc exec haproxy -- sudo --login --user ubuntu
Обновите список пакетов и установите HAProxy:
sudo apt-get update
sudo apt-get install haproxy
Затем нужно настроить HAProxy. Конфигурационный файл HAProxy находится в /etc/haproxy/haproxy.cfg.
sudo nano /etc/haproxy/haproxy.cfg
Внесите пару поправок в раздел defaults. Добавьте опцию forwardfor, чтобы сохранить исходный IP клиента. Опция http-server-close позволяет повторно использовать сессии и уменьшить задержку.
global
...
defaults
log global
mode http
option httplog
option dontlognull
option forwardfor
option http-server-close
timeout connect 5000
timeout client 50000
timeout server 50000
...
Затем нужно направить фронтэнд на контейнеры бэкэнда. Добавьте раздел www_frontend:
frontend www_frontend
bind *:80 # Bind to port 80 (www) on the container
# It matches if the HTTP Host: field mentions any of the hostnames (after the '-i').
acl host_web1 hdr(host) -i example.com www.example.com
acl host_web2 hdr(host) -i web2.example.com
# Redirect the connection to the proper server cluster, depending on the match.
use_backend web1_cluster if host_web1
use_backend web2_cluster if host_web2
Команды acl указывают имена хостов веб-серверов и перенаправляют запросы в соответствующий раздел backend.
Затем добавьте разделы backend для веб-серверов (они будут называться web1_cluster и web2_cluster).
backend web1_cluster
balance leastconn
# We set the X-Client-IP HTTP header. This is useful if we want the web server to know the real client IP.
http-request set-header X-Client-IP %[src]
# This backend, named here "web1", directs to container "web1.lxd" (hostname).
server web1 web1.lxd:80 check
backend web2_cluster
balance leastconn
http-request set-header X-Client-IP %[src]
server web2 web2.lxd:80 check
Параметр balance определяет стратегию балансировки нагрузки. В этом случае выбрано наименьшее количество подключений. Опция http-request устанавливает HTTP-заголовок с реальным IP-адресом веб-клиента. Без этого заголовка веб-сервер записывал бы IP-адрес HAProxy в качестве исходного IP всех соединений, что затрудняло бы анализ происхождения трафик. Опция server задает имя сервера (web1), за которым следует имя хоста и порт сервера.
LXD предоставляет DNS-сервер для контейнеров. Потому web1.lxd разрешается на IP-адрес, связанный с контейнером web1. У других контейнеров есть свои имена хостов (web2.lxd и haproxy.lxd).
С помощью параметра check HAPRoxy поддерживает проверки состояния веб-серверов.
Убедитесь, что настройка работает должным образом:
/usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -c
Команда должна вернуть:
Configuration file is valid
Перезапустите HAProxy:
sudo systemctl reload haproxy
Закройте контейнер и вернитесь в среду хоста.
logout
Теперь HAProxy работает как обратный прокси-сервер, который передаёт соединения, поступающие на порт 80, соответствующему контейнеру. Убедитесь, что haproxy может перенаправлять соединения.
curl --verbose --header 'Host: web2.example.com' http://10.10.10.10
Эта команда создаст запрос HAProxy и установит HTTP-заголовок host, который HAProxy должен использовать для перенаправления соединения на соответствующий веб-сервер.
Команда должна вернуть:
'Host: web2.example.com' http://10.10.10.10" command
...
> GET / HTTP/1.1
> Host: web2.example.com
> User-Agent: curl/7.47.0
> Accept: */*
>
...
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx on LXD container web2!</title>
<style>
...
Прокси-сервер HAProxy правильно понял запрос и отправил его в контейнер web2. Там веб-сервер обслужил страницу по умолчанию, которую вы отредактировали ранее, и вывел соответствующий текст. Теперь нужно направить внешние запросы в HAProxy, чтобы внешние клиенты могли получить доступ к веб-сайтам.
6: Перенаправление входящих подключений в контейнер HAProxy
Осталось только подключить обратный прокси-сервер к сети Интернет.
Сервер должен передавать все запросы из Интернета на порт 80 контейнера haproxy.
HAProxy установлен в контейнере и по умолчанию недоступен из Интернета. Чтобы открыть доступ к контейнеру, создайте правило iptables для перенаправления соединений.
В команде iptables нужно указать два IP-адреса: внешний IP сервера (укажите его вместо your_server_ip) и внутренний IP контейнера haproxy (вместо your_haproxy_ip), который можно узнать с помощью команды lxc list.
Чтобы добавить правило, введите:
sudo iptables -t nat -I PREROUTING -i eth0 -p TCP -d your_server_ip/32 --dport 80 -j DNAT --to-destination your_haproxy_ip:80
- -t nat определяет используемую таблицу (в данном случае nat).
- -I PREROUTING указывает, что правило нужно добавить в цепочку PREROUTING.
- -i eth0 определяет интерфейс eth0, стандартный открытый интерфейс серверов.
- -p TCP задаёт протокол TCP.
- -d your_server_ip/32 определяет целевой IP-адрес правила.
- –dport 80: целевой порт.
- -j DNAT переключается на DNAT.
- –to-destination your_haproxy_ip:80 отправляет запрос на IP-адрес контейнера HAProxy.
Читайте также:
Чтобы сохранить правила iptables, установите пакет iptables-persistent.
sudo apt-get install iptables-persistent
Во время установки будет предложено сохранить текущие правила.
Если у вас есть два FQDN, теперь вы можете подключиться к каждому из них в браузере.
Чтобы убедиться, что оба веб-сервера действительно доступны из Интернета, попробуйте получить доступ к каждому из них с помощью команды curl:
curl --verbose --header 'Host: example.com' 'http://your_server_ip'
curl --verbose --header 'Host: web2.example.com' 'http://your_server_ip'
Эти команды выполняют HTTP-подключение к внешнему IP-адресу сервера и добавляют поле заголовка HTTP с помощью опции –header, которую HAProxy будет использовать для обработки запроса.
Вывод первой команды выглядит так:
* Trying your_server_ip...
* Connected to your_server_ip (your_server_ip) port 80 (#0)
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.10.0 (Ubuntu)
...
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx on LXD container web1!</title>
<style>
body {
...
Вывод второй команды:
* Trying your_server_ip...
* Connected to your_server_ip (your_server_ip) port 80 (#0)
> GET / HTTP/1.1
> Host: web2.example.com
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.10.0 (Ubuntu)
...
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx on LXD container web2!</title>
<style>
body {
...
Заключение
Вы поместили два сайта в индивидуальные контейнеры, а также настроили контейнер HAProxy, который управляет трафиком. Чтобы добавить на сервер другие веб-сайты в индивидуальных контейнерах, достаточно повторить вышеописанный процесс.
Также вы можете добавить контейнер MySQL и установить CMS (например, WordPress) для управления каждым сайтом.
LXD предоставляет возможность делать снапшоты – быстрые снимки полного состояния контейнеров, что упрощает создание резервных копий и откатов контейнеров.
Если установить LXD на двух разных серверах, вы сможете подключить их и перемещать контейнеры между серверами.