Как получить настоящий IP-адрес клиента для сервиса, работающего внутри Docker.

Вопрос или проблема

Мой сценарий: я хотел бы, чтобы мои сервисы на базе 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 режим:

  1. Реверс-прокси: Использование веб-сервера, такого как 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}
  2. Докер-сетевые плагины: Могут использоваться для предоставления дополнительных возможностей сетевого взаимодействия и настройки, чтобы избежать ограничения bridge режима.

  3. Использование сетевых правил iptables: Вы можете настроить iptables на уровне хоста, чтобы добавлять или изменять заголовки.

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

Эти решения обеспечивают возможность определения реального IP-адреса клиента вашим docker-сервисом без необходимости отказываться от преимуществ изолированного сетевого взаимодействия, предоставляемого bridge сетью.

Оцените материал
Добавить комментарий

Капча загружается...