Вопрос или проблема
Мой сценарий: я хотел бы, чтобы мои сервисы на базе Docker знали, какой IP обращается к ним из интернета (это требование моего приложения).
Я работаю на облачном VPS, debian jessie, с docker 1.12.2.
Я использую nc
в режиме подробного вывода для открытия порта 8080
.
docker run --rm -p "8080:8080" centos bash -c "yum install -y nc && nc -vv -kl -p 8080"
Скажем, у VPS есть домен example.com
, и с другой машины, у которой, допустим, есть IP dead:::::beef
, я выполняю команду
nc example.com 8080
Сервер говорит:
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Listening on :::8080
Ncat: Listening on 0.0.0.0:8080
Ncat: Connection from 172.17.0.1.
Ncat: Connection from 172.17.0.1:49799.
172.17.0.1
находится в локальной сети сервера и, конечно, не имеет отношения к моему клиенту. На самом деле:
docker0 Link encap:Ethernet HWaddr ....
inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0
Если я запускаю контейнер в режиме сетевого взаимодействия хоста
docker run --rm --net=host -p "8080:8080" centos bash -c "yum install -y nc && nc -vv -kl -p 8080"
и снова обращаюсь к своему серверу
nc example.com 8080
Я получаю ожидаемый результат:
Ncat: Connection from dead:::::beef.
Ncat: Connection from dead:::::beef:55650.
Мои вопросы:
- Почему docker “маскирует” IP-адреса, когда не находится в режиме сетевого взаимодействия хоста? Я думаю, что процесс демона docker, вероятно, именно тот, кто открывает порт, получает соединение, а затем передает его процессу контейнера, используя собственное внутреннее соединение сетевого интерфейса.
nc
, работающий в контейнере, таким образом видит, что вызов поступает с IP демона docker. - (Как) могу ли я сделать так, чтобы мой сервис docker знал о внешних IP, обращающихся к нему, без использования режима сетевого взаимодействия хоста?
Согласно документации, драйвер по умолчанию (т.е., когда вы не указываете --net=host
в docker run
, это драйвер сетевого взаимодействия bridge
.
Дело не в том, что docker “маскирует” IP-адреса, а в том, как режимы сетевого взаимодействия bridge
и host
отличаются.
В контексте Docker, сетевая сеть bridge использует программный мост, который
позволяет контейнерам, подключенным к одной и той же сетевой сети bridge, общаться
между собой, обеспечивая изоляцию от контейнеров, которые не подключены
к этой сетевой сети bridge. Драйвер моста Docker автоматически устанавливает
правила на хост-машине, чтобы контейнеры на разных сетевых сетях bridge не могли
напрямую общаться друг с другом.
Docker создает изолированную сеть, чтобы каждый контейнер в сетевой сети bridge мог общаться. В вашем случае это docker0
. Таким образом, по умолчанию контейнеры на вашем хосте docker общаются в этой сети.
Как вы, вероятно, уже догадались, да, 172.17.0.1
является маршрутом по умолчанию в сети docker0
, но это не действует как маршрутизатор, пересылающий пакеты до места назначения, поэтому вы видите его в качестве источника в выводе netcat.
На самом деле, вы можете проверить это, запустив ss -tulnp
на вашем хосте docker. Вы должны увидеть, что процесс, прослушивающий порт 8080, это docker.
С другой стороны, использование драйвера сетевого взаимодействия хоста означает, что нет изоляции между контейнером и хостом. Вы можете проверить это, запустив ss -tulnp
на вашем хосте docker; вы должны увидеть, что процесс, прослушивающий сокет, вместо Docker (в вашем случае это должен быть nc
).
У меня была точно такая же проблема, и это решение от @TosoBoso хорошо сработало для меня с обратным прокси Nginx перед моим веб-бэкендом. Документ Echo документ также помог мне лучше понять эту проблему.
В основном вам нужно переписать заголовки (по крайней мере, вам нужен X-Real-IP
) в вашей конфигурации Nginx, и затем вы сможете перехватить это позже в своей программе.
location /api/ {
proxy_pass http://127.0.0.1:7006/api/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Для пользователей Caddy вам нужна всего одна строка
header_up X-Real-IP {remote_host}
.
Ответ или решение
Для решения проблемы определения истинного IP-адреса клиента для службы, работающей внутри Docker, важно понять, как работают различные режимы сетевого взаимодействия Docker и как можно настроить систему, чтобы достичь желаемого результата. Используя сетевой режим Docker, по умолчанию (bridge), и хостовый режим, вы можете увидеть разницу в получаемых IP-адресах. Рассмотрим теорию, пример и применение этого знания.
Теория
Docker предоставляет несколько сетевых драйверов, из которых наиболее часто используются bridge и host. В режиме bridge контейнеры подключаются к виртуальной сети, созданной при помощи программного моста. Это позволяет контейнерам, находящимся на одной виртуальной сети, взаимодействовать друг с другом, обеспечивая при этом изоляцию от других контейнеров. В этом режиме, по умолчанию, если вы не указываете --net=host
в команде docker run
, Docker создает виртуальную сеть (например, docker0
), и все контейнеры, подключенные к этой сети, общаются между собой в изолированном пространстве.
Когда входящий трафик направляется в контейнер, Docker-привод создает виртуальное сетевое подключение. Таким образом, IP-адрес, который фиксируется внутри контейнера (например, в nc), будет IP-адресом этой виртуальной сети (например, 172.17.0.1
), а не исходным IP-адресом клиента.
Пример
Вы упомянули, что при использовании bridge режима IP-адрес, который получает сервер, отображается как 172.17.0.1
. Это связано с тем, что контейнер находится в виртуальной сети и видит IP-адрес Docker-моста. При запуске контейнера в host режиме Docker использует сетевые ресурсы хоста без изоляции, в результате чего вы увидите истинный IP клиента (например, dead:::::beef
).
Используя следующую команду:
docker run --rm --net=host -p "8080:8080" centos bash -c "yum install -y nc && nc -vv -kl -p 8080"
вы получаете ожидаемый результат, то есть реальный IP клиента (dead:::::beef
), так как контейнер использует сетевые ресурсы хоста напрямую.
Применение
Существует несколько способов получить истинный IP-адрес клиента без необходимости использовать host режим:
-
Реверс-прокси: Использование веб-сервера, такого как Nginx или Caddy, в качестве реверс-прокси может оказаться очень полезным. Реверс-прокси могут переписывать заголовки HTTP-запросов, добавляя заголовок
X-Real-IP
, который вы можете использовать для определения истинного IP-адреса источника запроса.Пример конфигурации Nginx:
location /api/ { proxy_pass http://127.0.0.1:7006/api/; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
Для Caddy:
header_up X-Real-IP {remote_host}
-
Докер-сетевые плагины: Могут использоваться для предоставления дополнительных возможностей сетевого взаимодействия и настройки, чтобы избежать ограничения bridge режима.
-
Использование сетевых правил iptables: Вы можете настроить iptables на уровне хоста, чтобы добавлять или изменять заголовки.
Выбор подхода зависит от специфики вашего приложения и архитектуры вашей системы. Использование реверс-прокси часто является наиболее распространенным и гибким решением, так как оно обеспечивает еще и дополнительный уровень защиты и может улучшить производительность, балансируя нагрузку между несколькими контейнерами.
Эти решения обеспечивают возможность определения реального IP-адреса клиента вашим docker-сервисом без необходимости отказываться от преимуществ изолированного сетевого взаимодействия, предоставляемого bridge сетью.