Pull to refresh
95.39
Слёрм
Учебный центр для тех, кто работает в IT

Как работает Docker Desktop Networking

Reading time7 min
Views12K
Original author: David Scott

Современные приложения активно используют сети. Обычное дело, когда во время сборки apt-get/dnf/yum/apk install устанавливает пакет из репозитория пакетов дистрибутива Linux. При выполнении команды приложение может захотеть подключиться к внутренней базе данных postgres или mysql, чтобы сохранить определённое состояние при вызове listen() и accept(). При этом разработчик должен иметь возможность работать отовсюду — из дома или офиса, с мобильного устройства или через VPN. Docker Desktop помогает сделать так, чтобы сеть «просто работала» в каждом из сценариев. В статье разбираем инструменты и методы, которые обеспечивают это, начиная с всеми любимого набора протоколов: TCP/IP.

TCP/IP

TCP/IP — набор протоколов, который задаёт стандарты связи между компьютерами и содержит подробные соглашения о маршрутизации и межсетевом взаимодействии. Когда контейнер хочет подключиться к внешнему миру, он используют TCP/IP. Поскольку для Linux-контейнеров требуется ядро Linux, Docker Desktop включает вспомогательную виртуальную машину Linux. В результате трафик из контейнеров идёт от виртуальной машины Linux, а не от хоста, что вызывает серьёзную проблему.

Многие IT-отделы создают политики VPN, где говорится что-то вроде «перенаправлять через VPN только трафик, исходящий от хоста». Смысл в том, чтобы предотвратить случайное действие хоста в качестве маршрутизатора, перенаправляющего небезопасный трафик из интернета в защищенные корпоративные сети. Если программа VPN увидит трафик с виртуальной машины Linux, он не будет маршрутизироваться через VPN, что не позволит контейнерам получить доступ к внутренним ресурсам. 

Docker Desktop помогает избежать этой проблемы, перенаправляя весь трафик на уровне пользователя через vpnkit и стек TCP/IP, написанный на OCaml поверх библиотек сетевых протоколов проекта MirageOS. На диаграмме показан поток пакетов от вспомогательной виртуальной машины через vpnkit и в Интернет:

При загрузке виртуальная машина запрашивает адрес с помощью DHCP. Ethernet-фрейм, содержащий запрос, передаётся от виртуальной машины к хосту через общую память, либо через virtio на Mac, либо через AF_VSOCK на Windows. Vpnkit содержит виртуальный коммутатор ethernet (mirage-vnetif), который перенаправляет запрос на сервер DHCP (mirage/charrua).

Как только виртуальная машина получает ответ DHCP, содержащий IP-адрес виртуальной машины и IP-адрес шлюза, она отправляет запрос ARP для определения адреса сетевого шлюза (mirage/arp). После получения ответа ARP он сможет отправить пакет в Интернет.

Когда vpnkit видит исходящий пакет с новым IP-адресом, он создаёт виртуальный стек TCP/IP для удалённой машины (mirage/mirage-tcpip). Этот стек действует как одноранговый стек в Linux — принимает и обменивается пакетами. Когда контейнер вызывает connect() для установления TCP-соединения, Linux отправляет TCP-пакет с установленным флагом SYNchronize. Vpnkit наблюдает за флагом SYNchronize и сам вызывает connect() с хоста. Если connect() завершается успешно, vpnkit отвечает Linux пакетом TCP SYNchronize. В Linux connect() выполняется успешно, и данные проксируются в обоих направлениях (mirage/mirage-flow). Если connect() отклоняется, vpnkit отвечает пакетом TCP RST (reset), который заставляет connect() внутри Linux возвращать ошибку. UDP и ICMP обрабатываются аналогичным образом.

Помимо низкоуровневого TCP/IP, vpnkit имеет ряд встроенных высокоуровневых сетевых служб, например, DNS-сервер (mirage/ocaml-dns) и HTTP-прокси (mirage/cohttp). К этим службам можно обращаться напрямую — через виртуальный IP-адрес или DNS-имя, и косвенно — путем сопоставления исходящего трафика и динамического перенаправления в зависимости от конфигурации.

«Docker для админов и разработчиков»

С адресами TCP/IP трудно работать напрямую. В следующем разделе разберём, как Docker Desktop использует DNS для присвоения удобочитаемых имён сетевым службам.

DNS

Внутри Docker Desktop есть несколько DNS-серверов:

DNS-запросы от контейнеров сначала обрабатываются сервером внутри dockerd, который распознаёт имена других контейнеров в той же внутренней сети. Это позволяет контейнерам легко взаимодействовать друг с другом даже без знания внутренних IP-адресов. Каждый раз, когда приложение запускается, внутренние IP-адреса могут быть разными, но контейнеры по-прежнему будут легко подключаться друг к другу по удобочитаемому имени благодаря внутреннему DNS-серверу внутри dockerd.

Остальные поисковые запросы отправляются в CoreDNS (из CNCF). Затем в зависимости от доменного имени запросы перенаправляются на один из двух DNS-серверов на хосте. Домен docker.internal  считается особенным и включает в себя DNS-имя host.docker.internal, которое преобразуется в IP-адрес для текущего хоста. Хотя предпочтительнее, когда всё контейнеризировано, иногда имеет смысл запускать часть приложения как обычный сервис хостинга. Имя host.docker.internal позволяет контейнерам связываться с этими хост-сервисами и не беспокоиться о хардкодинге IP-адресов.

Второй DNS-сервер на хосте обрабатывает остальные запросы с помощью стандартных системных библиотек ОС. Это гарантирует, что, если имя правильно разрешится в веб-браузере разработчика, оно также будет правильно разрешаться в контейнерах. Это особенно важно при сложных настройках, например, когда одни запросы отправляются через корпоративный VPN (internal.registry.mycompany), в то время как другие — через обычный интернет (docker.com).

HTTP(S)-прокси

Некоторые организации блокируют прямой доступ в интернет и требуют, чтобы весь трафик направлялся через HTTP-прокси для фильтрации и логирования. Это влияет на извлечение образов во время сборки, а также на исходящий сетевой трафик, генерируемый контейнерами.

Самый простой способ использования HTTP-прокси — указать движку Docker на прокси-сервер c помощью переменных среды. Единственный недостаток: при необходимости изменения прокси-сервера придётся перезапустить Docker для обновления переменных, что приведёт к сбою. Docker Desktop позволяет избежать этого. Он запускает собственный HTTP-прокси внутри vpnkit, который перенаправляет на восходящий прокси-сервер. При изменении восходящего прокси-сервера внутренний прокси-сервер динамически перенастраивается, что позволяет избежать перезапуска.

На Mac Docker Desktop отслеживает параметры прокси-сервера, сохраненные в системных настройках. Когда компьютер переключает сеть (например, между сетями Wi-Fi или на сотовую связь), Docker Desktop автоматически обновляет внутренний HTTP-прокси, поэтому всё продолжает работать без каких-либо действий со стороны разработчика.

Port forwarding 

Порты позволяют сетевым и подключенным к интернету устройствам взаимодействовать через указанные каналы. Хотя серверы с назначенными IP-адресами могут подключаться к интернету напрямую и делать порты публично доступными, система, находящаяся за пределами локальной сети, может оказаться недоступной из интернета. Port Forwarding — технология проброса портов, которая позволяет преодолеть это ограничение и сделать устройства публично доступными. Доступ предоставляется с помощью перенаправления трафика определённых портов с внешнего адреса маршрутизатора на адрес выбранного компьютера в локальной сети.

Поскольку Docker Desktop запускает Linux-контейнеры внутри виртуальной машины Linux, возникает разрыв: порты на виртуальной машине открыты, но инструменты работают на хосте. Нам нужно что-то для перенаправления соединений с хоста на виртуальную машину.

Рассмотрим отладку веб-приложения: разработчик вводит docker run -p 80:80, чтобы порт 80 контейнера был открыт на порту 80 хоста (и чтобы сделать его доступным через http://localhost). Вызов Docker API записывается в /var/run/docker.sock  на хосте, как обычно. Когда Docker Desktop запускает Linux-контейнеры, движок Docker представляет собой программу Linux, работающую внутри вспомогательной виртуальной машины Linux, а не на хосте. Поэтому Docker Desktop включает в себя прокси-сервер Docker API, который пересылает запросы с хоста на виртуальную машину. В целях безопасности запросы не пересылаются напрямую по протоколу TCP по сети. Вместо этого Docker Desktop перенаправляет соединения с доменными сокетами Unix по защищенному низкоуровневому пути через процессы, обозначенные на схеме выше как vpnkit-bridge.

Прокси Docker API может делать больше, чем просто пересылать запросы туда и обратно. Он также может декодировать и преобразовывать запросы и ответы, чтобы улучшить работу разработчика. Когда разработчик предоставляет порт с помощью docker run -p 80:80, прокси Docker API декодирует запрос и использует внутренний API для переадресации порта через процесс com.docker.backend. Если что-то на хосте уже прослушивает этот порт, разработчику возвращается удобочитаемое сообщение об ошибке. Если порт свободен, процесс com.docker.backend начинает принимать соединения и перенаправлять их в контейнер через vpnkit-forwarder, запущенный поверх vpnkit-bridge.

Docker Desktop не запускается с «root» или «Administrator» на хосте. Разработчик может использовать docker run –privileged , чтобы получить права root внутри вспомогательной виртуальной машины, но гипервизор гарантирует, что хост всегда будет защищён. Это хорошо с точки зрения безопасности, но вызывает проблему удобства использования в macOS — как разработчик может открыть порт 80 (docker run -p 80:80), когда он считается «привилегированным портом» в Unix, то есть номер порта < 1024? Решение состоит в том, что Docker Desktop включает в себя вспомогательную привилегированную службу, которая запускается от имени root из launchd  и которая говорит API «пожалуйста, привяжите этот порт». В связи с этим возникает вопрос: безопасно ли разрешать пользователю без полномочий root привязывать привилегированные порты?

Привилегированные порты изначально были функцией безопасности. Они появились во времена, когда порты использовались для аутентификации сервисов: можно было с уверенностью предположить, что вы разговариваете с HTTP-демоном хоста, потому что он привязан к порту 80, для которого требуется root. Современный способ аутентификации — с помощью сертификатов TLS и отпечатков пальцев SSH. Поэтому пока системные службы связывают свои порты до запуска Docker Desktop, macOS связывает порты при загрузке через launchd, благодаря чему не может быть путаницы или отказа в обслуживании. Соответственно, современная macOS сделала привязку привилегированных портов ко всем IP-адресам (0.0.0.0 или INADDR_ANY) непривилегированной операцией. Есть только один случай, когда Docker Desktop все ещё нуждается в использовании привилегированного помощника для привязки портов: когда запрашивается определенный IP (например, docker run -p 127.0.0.1:80:80), для которого требуется root в macOS.

Коротко о главном

Извлечение образов Docker, установка пакетов Linux, взаимодействие с серверными частями базы данных — всё это ежедневные задачи, для выполнения которых приложениям нужны надёжные сетевые подключения. Docker Desktop работает в самых разных средах: в офисе, дома и даже в поездках с нестабильным Wi-Fi. Однако на каких-то компьютерах могут быть установлены ограничительные политики брандмауэра, на каких-то — сложные конфигурации VPN. В таких случаях Docker Desktop стремится «просто работать», чтобы разработчик мог сосредоточиться на создании и тестировании своего приложения (а не на отладке Docker).

«Docker для админов и разработчиков»

Tags:
Hubs:
Total votes 11: ↑11 and ↓0+11
Comments35

Articles

Information

Website
slurm.io
Registered
Founded
Employees
51–100 employees
Location
Россия
Representative
Антон Скобин