Подробное изучение пространства имен контейнерной сети
Общие сведения о пространстве имен Container Network
Что касается Network Namespace, если понимать его буквально, мы знаем, что это изоляция сети на узле Linux, но какую часть сетевых ресурсов он конкретно изолирует?
Начнем с Linux Programmer's Manual
. В этом руководстве есть краткое описание и перечислены некоторые основные ресурсы пространства имен сети.
Сетевые ресурсы пространства имен
- Сетевое оборудование: относится к
lo
,eth0
и другому сетевому оборудованию. Вы можете просмотреть их с помощью командыip link
. - Стеки протоколов IPv4 и IPv6. Из стеков протоколов мы знаем, что уровень IP и описанные выше стеки протоколов TCP и UDP также работают независимо для каждого пространства имен. Следовательно, для многих протоколов, таких как IP, TCP и UDP, связанные с ними параметры также не зависят от каждого пространства имен. Большинство этих параметров находятся в каталоге
/proc/sys/net/
, а также включают ресурсы портов TCP и UDP. - Таблица IP-маршрутизации Этот ресурс также относительно прост для понимания. Вы можете запустить команду
ip route
в разных пространствах сетевых имен, чтобы просмотреть разные таблицы маршрутизации. - Правила брандмауэра На самом деле я говорю о
iptables
правилах, аiptables
правил можно настроить независимо в каждом пространстве имен. - Информация о состоянии сети Вы можете получить эту информацию от
/proc/net
и/sys/class/net
. Статус здесь в основном включает информацию о статусе предыдущих четырех типов ресурсов.
Как создать сетевое пространство имен?
Мы можем создать новое сетевое пространство имен, вызвав функции clone()
или unshare()
.
функция клонирования()
Когда создается новый процесс, вместе с созданием нового процесса также создается новое сетевое пространство имен. Этот метод на самом деле реализован путем присоединения флага CLONE_NEWNET
к системному вызову clone()
. На всякий случай, если вас интересует исходный код:
int new_netns(void *para) { printf("New Namespace Devices:\n"); system("ip link"); printf("\n\n"); sleep(100); return 0; } int main(void) { pid_t pid; printf("Host Namespace Devices:\n"); system("ip link"); printf("\n\n"); pid = clone(new_netns, stack + STACK_SIZE, CLONE_NEWNET | SIGCHLD, NULL); if (pid == -1) errExit("clone"); if (waitpid(pid, NULL, 0) == -1) errExit("waitpid"); return 0; }
функция unshare()
Мы можем вызвать системный вызов unshare()
, чтобы напрямую изменить сетевое пространство имен текущего процесса. Опять же, я публикую исходный код для вашего интереса:
int main(void) { pid_t pid; printf("Host Namespace Devices:\n"); system("ip link"); printf("\n\n"); if (unshare(CLONE_NEWNET) == -1) errExit("unshare"); printf("New Namespace Devices:\n"); system("ip link"); printf("\n\n"); return 0; }
Примечание. Не только сетевое пространство имен, но и другие пространства имен также устанавливаются через вызовы функций clone()
или unshare()
. Даже программа создания контейнера, такая как runC
, также использует unshare()
для создания пространства имен для вновь созданного контейнера. runC
— это CLI-инструмент для создания и запуска контейнеров в Linux в соответствии со спецификацией OCI (https://github.com/opencontainers/runc).
После создания сетевого пространства имен мы можем запустить команду lsns -t net
на хосте, чтобы просмотреть существующее сетевое пространство имен в системе. Конечно, lsns
также можно использовать для просмотра других пространств имен.
Давайте запустим быстрый пример:
$ gcc -o clone-ns clone-ns.c $ ls clone-ns.c clone-ns $ ./clone-ns Host Namespace Devices: 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000 link/ether 0a:e7:39:38:d4:5b brd ff:ff:ff:ff:ff:ffclone: Operation not permitted [ec2-user@devops101 namespace]$ sudo ./clone-ns Host Namespace Devices: 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000 link/ether 0a:e7:39:38:d4:5b brd ff:ff:ff:ff:ff:ffNew Namespace Devices: 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
Скриншот:
Как установить параметр контейнерной сети?
Теперь мы немного познакомились с Network Namespace
, давайте посмотрим, как мы можем установить параметры контейнерной сети.
Но в первую очередь нужно понимать, что сетевые параметры Network Namespace
в контейнере не полностью наследуются от Host Namespace хоста, а также не полностью переинициализируются при установке нового Network Namespace
.
Давайте возьмем в качестве примера наш контейнер httpd
и попробуем обновить сетевые настройки внутри этого контейнера и посмотрим, что произойдет:
Ага, мы не могли этого сделать, так как /proc/sys
монтируется как read-only
. Мы можем проверить, выполнив следующую команду:
[root@b4e702116ae5 /]# cat /proc/mounts | grep "proc/sys" proc /proc/sys proc ro,nosuid,nodev,noexec,relatime 0 0 proc /proc/sysrq-trigger proc ro,nosuid,nodev,noexec,relatime 0 0
Почему /proc/sys
монтируется в контейнере только для чтения? Из соображений безопасности runC
обрабатывает все связанные с /proc и /sys каталоги в контейнере как монтируемые только для чтения по умолчанию.
Тогда как мы это делаем? Если у вас есть права root на хосте. Самый простой и грубый способ — использовать команду nsenter
, о которой мы упоминали ранее, для изменения сетевых параметров в контейнере. Однако этот метод, очевидно, не разрешен в производственной среде, потому что мы не позволим пользователям иметь разрешение на вход в систему хоста.
Вообще говоря, такие изменения следует вносить только до запуска приложения в контейнере. В противном случае многие tcp-ссылки уже будут установлены, поэтому даже при изменении новых параметров установленные ссылки не вступят в силу. Это требует перезапуска приложения. Все мы знаем, что в производственной среде обычно избегают перезапуска приложений, что явно неуместно.
Таким образом, лучшее время для изменения сетевых параметров, очевидно, когда контейнер только что запущен, а приложение в контейнере еще не запущено.
докер системный
Фактически, runC
также резервирует интерфейс модификации перед выполнением монтирования только для чтения в каталоге /proc/sys
, который используется для изменения параметров в «/proc/sys» в контейнере, которые также являются параметрами sysctl
.
Например:
[root@devops101 ~]# docker run -d --name httpd --sysctl net.ipv4.tcp_keepalive_time=600 registry/httpd:v1 bbba53b0deb4ca3c4221fbf6e8bd82aee4678da2c80844f5260a4c427e441ce9 [root@devops101 ~]# docker exec httpd cat /proc/sys/net/ipv4/tcp_keepalive_time 600
Заключение
Я обобщил инструменты/команды для обновления параметров сетевого пространства имен на следующей диаграмме: