Подробное изучение пространства имен контейнерной сети

Общие сведения о пространстве имен 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

Заключение

Я обобщил инструменты/команды для обновления параметров сетевого пространства имен на следующей диаграмме: