Предоставление порта в активном контейнере Docker

409

Я пытаюсь создать контейнер Docker, который действует как полноценная виртуальная машина. Я знаю, что могу использовать инструкцию EXPOSE внутри Dockerfile, чтобы выставить порт, и я могу использовать -pфлаг с, docker runчтобы назначить порты, но как только контейнер действительно запущен, есть ли команда, чтобы открыть / отобразить дополнительные порты?

Например, допустим, у меня есть контейнер Docker, на котором запущен sshd. Кто-то еще использует контейнер ssh и устанавливает httpd. Есть ли способ выставить порт 80 на контейнере и сопоставить его с портом 8080 на хосте, чтобы люди могли посещать веб-сервер, работающий в контейнере, без перезапуска?

reberhardt
источник
1
Контейнеру можно присвоить маршрутизируемый IP-адрес , поэтому сопоставление портов не требуется.
Мэтт

Ответы:

331

Вы не можете сделать это через Docker, но вы можете получить доступ к незакрытому порту контейнера с хост-машины.

если у вас есть контейнер, который работает с портом 8000, вы можете запустить

wget http://container_ip:8000

Чтобы получить IP-адрес контейнера, выполните 2 команды:

docker ps

docker inspect container_name | grep IPAddress

Внутренне Docker выдает вызов iptables при запуске образа, так что, возможно, сработают некоторые варианты.

выставить порт контейнера 8000 на ваш локальный порт 8001:

 iptables -t nat -A  DOCKER -p tcp --dport 8001 -j DNAT --to-destination 172.17.0.19:8000

Один из способов решить эту проблему - настроить другой контейнер с желаемым отображением портов и сравнить выходные данные команды iptables-save (хотя мне пришлось удалить некоторые другие параметры, которые заставляют трафик проходить через докер прокси).

ПРИМЕЧАНИЕ: это подрывной докер, поэтому следует делать с осознанием того, что он может хорошо создавать синий дым

ИЛИ

Другой альтернативой является поиск опции (new? Post 0.6.6?) -P, которая будет использовать произвольные порты хоста, а затем подключать их.

ИЛИ

с 0.6.5, вы могли бы использовать функцию LINKs, чтобы вызвать новый контейнер, который общается с существующим, с некоторыми дополнительными ответами на флаги -p этого контейнера? (Я еще не использовал ССЫЛКИ)

ИЛИ

с докером 0,11? Вы можете использовать его docker run --net host ..для непосредственного присоединения вашего контейнера к сетевым интерфейсам хоста (т. е. net не является пространством имен) и, таким образом, все порты, которые вы открываете в контейнере, открыты.

SvenDowideit
источник
6
Похоже, это не работает с докером 1.3.0. Правило DOCKER DNAT создается при запуске docker с параметром -p, но добавление его вручную не позволяет устанавливать соединения. Странное удаление правила во время работы контейнера, похоже, не мешает ему работать ...
silasdavis
4
Спасибо. Я был уверен в безопасности, что незащищенные порты были в безопасности.
seanmcl
Автоматизация вещей и использование jq + sed могут быть полезны: CONTAINER_IP=$(docker inspect container_name | jq .[0].NetworkSettings.IPAddress | sed -r 's/\"([^\"]+)\"/\1/''])'); iptables -t nat -A DOCKER -p tcp --dport 8001 -j DNAT --to-destination ${CONTAINER_IP}:8000
ericson.cepeda
5
@ ericson.cepeda Вместо того, чтобы вызывать, jqи sedвы можете использовать -fопцию docker inspect:CONTAINER_IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' container_name)
pwes
для тех, кто предпочитает kmkeen.com/jshon jshon -e 0 -e NetworkSettings -e Networks -e bridge -e IPAddress -u
slf
138

Вот что я бы сделал:

  • Зафиксируйте живой контейнер.
  • Снова запустите контейнер с новым образом с открытыми портами (я бы порекомендовал смонтировать общий том и открыть порт ssh)
sudo docker ps 
sudo docker commit <containerid> <foo/live>
sudo docker run -i -p 22 -p 8000:80 -m /data:/data -t <foo/live> /bin/bash
bosky101
источник
31
Ключевая часть моего вопроса заключается в том, что это должно происходить без перезапуска контейнера ... Переход на новый контейнер может сохранить файлы, но эффективно уничтожит все запущенные процессы и будет аналогичен перезагрузке на физической машине. Мне нужно сделать это без этого. Однако, спасибо!
Реберхардт
хорошо. Я бы сравнил это больше с началом параллельного экземпляра. так как оба теперь запущены (старый и новый), может быть проще прокси к новому контейнеру, как только необходимые миграции выполнены.
bosky101
Да, параллельные экземпляры и обратное проксирование являются одними из главных причин, по которым я люблю Docker. Однако в этом сценарии мне нужно сохранить все запущенные процессы в контейнере, которые могли быть запущены через SSH. Хотя исполняемые файлы будут сохранены при фиксации образа и запуске параллельного экземпляра, сами исполняемые файлы не будут запущены и что-либо в ОЗУ будет потеряно.
Реберхардт
6
Почему ты бежишь, sudo dockerа не только docker?
Тьяго Фигейро
Этот совет мне очень помог, потому что я установил тонны пакетов в медленной сети. После запуска docker commitя был готов снова протестировать приложение, а не тратить часы на переустановку всего.
густавохенке
49

Хотя вы не можете открыть новый порт существующего контейнера, вы можете запустить новый контейнер в той же сети Docker и заставить его перенаправлять трафик в исходный контейнер.

# docker run \
  --rm \
  -p $PORT:1234 \
  verb/socat \
    TCP-LISTEN:1234,fork \
    TCP-CONNECT:$TARGET_CONTAINER_IP:$TARGET_CONTAINER_PORT

Работал Пример

Запустите веб-сервис, который прослушивает порт 80, но не открывает его внутренний порт 80 (упс!):

# docker run -ti mkodockx/docker-pastebin   # Forgot to expose PORT 80!

Найдите свой IP-адрес сети Docker:

# docker inspect 63256f72142a | grep IPAddress
                    "IPAddress": "172.17.0.2",

Запустите verb/socatс открытым портом 8080 и заставьте его перенаправлять трафик TCP на порт 80 этого IP:

# docker run --rm -p 8080:1234 verb/socat TCP-LISTEN:1234,fork TCP-CONNECT:172.17.0.2:80

Теперь вы можете получить доступ к pastebin по адресу http: // localhost: 8080 / , и ваши запросы направляются туда, socat:1234куда он направляется pastebin:80, и ответ проходит по тому же пути в обратном направлении.

RobM
источник
7
Довольно умно! Тем не менее, вероятно, лучше использовать verb/socat:alpine, так как его изображение занимает 5% занимаемой площади (если только вы не столкнулись с несовместимостью с libc или DNS ).
jpaugh
3
Там такжеalpine/socat
Джемби
3
Отличный ответ. Простой, один вкладыш, который сделает это без каких-либо грязных хаков.
Бруно Брант
2
Это отлично сработало для меня, спасибо! Мне пришлось добавить --net myfoldername_defaultк моей verb/socatкоманде запуска, так как я запустил неэкспонированный контейнер в составе докера, который создает сеть.
emazzotta
35

Взломы IPtables не работают, по крайней мере, на Docker 1.4.1.

Лучший способ - запустить другой контейнер с открытым портом и ретранслировать с помощью socat. Вот что я сделал, чтобы (временно) подключиться к базе данных с помощью SQLPlus:

docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus

Dockerfile:

FROM debian:7

RUN apt-get update && \
    apt-get -y install socat && \
    apt-get clean

USER nobody

CMD socat -dddd TCP-LISTEN:1521,reuseaddr,fork TCP:db:1521
Рикардо Бранко
источник
2
Хаки iptables должны запускаться с хост-компьютера, а не с док-контейнера. По сути, он перенаправляет запросы на определенные порты хоста в соответствующие порты док-контейнера. Это совсем не специфично для докера, и вы можете сделать это на совершенно разных хостах. Можете ли вы добавить форматирование кода в файл Docker? Это кажется довольно полезным.
AusIV
1
Февраль 2016: Запуск Docker 1.9.1, это было единственное решение, которое успешно сработало для меня. Ни одно из решений IPTables не сработало. FROMДля эффективного использования ресурсов стоит порекомендовать использовать тот же базовый образ, что и ваш контейнер БД.
Экскалибур
4
Вы можете попробовать apline / socat . Он поставляется с предустановленным socat и принимает параметры socat в качестве команды, поэтому вам вообще не нужно писать Dockerfile.
trkoch
35

Вот еще одна идея. Используйте SSH для переадресации портов; это также дает преимущество работы в OS X (и, вероятно, в Windows), когда ваш хост Docker является виртуальной машиной.

docker exec -it <containterid> ssh -R5432:localhost:5432 <user>@<hostip>
Скотт Карлсон
источник
4
в моем случае на запущенном образе докера нет бинарного ssh
Radu Toader,
Нужно ли мне создавать SSH-ключ на моем компьютере и помещать его в контейнер докера для этого?
Октябрь
@octavian ssh-keygen -t rsa внутри контейнера. добавить ключ
Люк W
8

Мне пришлось иметь дело с этой же проблемой, и я смог ее решить, не останавливая ни один из моих работающих контейнеров. Это актуальное решение по состоянию на февраль 2016 года с использованием Docker 1.9.1. В любом случае, этот ответ является подробной версией ответа @ ricardo-branco, но более подробно для новых пользователей.

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

Поскольку я хотел бы получить доступ к базе данных MySQL извне (из Sequel Pro через SSH-туннелирование), я собираюсь использовать порт 33306на хост-машине. (Нет 3306, на случай, если запущен внешний экземпляр MySQL.)

Около часа настройки iptables оказались бесплодными, хотя:

Шаг за шагом вот что я сделал:

mkdir db-expose-33306
cd db-expose-33306
vim Dockerfile

Отредактируйте dockerfile, разместив это внутри:

# Exposes port 3306 on linked "db" container, to be accessible at host:33306
FROM ubuntu:latest # (Recommended to use the same base as the DB container)

RUN apt-get update && \
    apt-get -y install socat && \
    apt-get clean

USER nobody
EXPOSE 33306

CMD socat -dddd TCP-LISTEN:33306,reuseaddr,fork TCP:db:3306

Затем создайте изображение:

docker build -t your-namespace/db-expose-33306 .

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

docker run -it --rm --name=db-33306 --link the_live_db_container:db -p 33306:33306  your-namespace/db-expose-33306
Excalibur
источник
Вы можете использовать изображение глагол / сокат или альпийский / сокат напрямую. Смотрите глагол /
сокат
7

Чтобы добавить к принятому iptables решению ответа , мне нужно было запустить еще две команды на хосте, чтобы открыть его для внешнего мира.

HOST> iptables -t nat -A DOCKER -p tcp --dport 443 -j DNAT --to-destination 172.17.0.2:443
HOST> iptables -t nat -A POSTROUTING -j MASQUERADE -p tcp --source 172.17.0.2 --destination 172.17.0.2 --dport https
HOST> iptables -A DOCKER -j ACCEPT -p tcp --destination 172.17.0.2 --dport https

Примечание. Я открывал порт https (443), внутренний IP-адрес докера был 172.17.0.2

Примечание 2: Эти правила временны и будут действовать только до перезапуска контейнера.

dstj
источник
Это сработало для меня ... однако были некоторые Кават позже. Согласно примечанию 2, это остановит, если контейнеры перезапустятся, это может быть на другом IP, чем тогда. Но что более важно, docker не имеет представления об этих iptable-записях, поэтому не удалит их, когда вы позже полностью перезапустите службу и заставите docker сделать это правильно. В результате было получено несколько одинаковых записей iptable, которые привели к сбою практически без ошибок или указаний на причину. Как только были приняты дополнительные правила, проблема исчезла. Другими словами, просматривайте свои IP-таблицы ОЧЕНЬ внимательно после любого изменения.
Энтони
5

Вы можете использовать SSH, чтобы создать туннель и выставить свой контейнер на хосте.

Вы можете сделать это обоими способами, от контейнера к хосту и от хоста к контейнеру. Но вам нужен SSH инструмент, такой как OpenSSH в обоих (клиент в одном и сервер в другом).

Например, в контейнере вы можете сделать

$ yum install -y openssh openssh-server.x86_64
service sshd restart
Stopping sshd:                                             [FAILED]
Generating SSH2 RSA host key:                              [  OK  ]
Generating SSH1 RSA host key:                              [  OK  ]
Generating SSH2 DSA host key:                              [  OK  ]
Starting sshd:                                             [  OK  ]
$ passwd # You need to set a root password..

Вы можете найти IP-адрес контейнера из этой строки (в контейнере):

$ ifconfig eth0 | grep "inet addr" | sed 's/^[^:]*:\([^ ]*\).*/\1/g'
172.17.0.2

Затем на хосте вы можете просто сделать:

sudo ssh -NfL 80:0.0.0.0:80 root@172.17.0.2
тонна
источник
Это сработало для меня, с одним небольшим исправлением ... эта команда сработала: sudo ssh -NfL 25252: 172.17.0.50: 22 $ USER @ localhost
Александр Го,
3

Если у кого-то нет ответа - проверьте, работает ли целевой контейнер в Docker-сети:

CONTAINER=my-target-container
docker inspect $CONTAINER | grep NetworkMode
        "NetworkMode": "my-network-name",

Сохраните его на потом в переменной $NET_NAME:

NET_NAME=$(docker inspect --format '{{.HostConfig.NetworkMode}}' $CONTAINER)

Если да, вы должны запустить прокси-контейнер в той же сети.

Затем найдите псевдоним для контейнера:

docker inspect $CONTAINER | grep -A2 Aliases
                "Aliases": [
                    "my-alias",
                    "23ea4ea42e34a"

Сохраните его на потом в переменной $ALIAS:

ALIAS=$(docker inspect --format '{{index .NetworkSettings.Networks "'$NET_NAME'" "Aliases" 0}}' $CONTAINER)

Теперь запустите socatконтейнер в сети, $NET_NAMEчтобы подключиться к $ALIASоткрытому (но не опубликованному) порту контейнера ed:

docker run \
    --detach --name my-new-proxy \
    --net $NET_NAME \
    --publish 8080:1234 \
    alpine/socat TCP-LISTEN:1234,fork TCP-CONNECT:$ALIAS:80
ktalik
источник
2

Вы можете использовать оверлейную сеть, такую ​​как Weave Net , которая будет назначать уникальный IP-адрес каждому контейнеру и неявно выставлять все порты каждому контейнеру в сети.

Weave также обеспечивает интеграцию с хост-сетью . По умолчанию он отключен, но если вы хотите также получить доступ к IP-адресам контейнера (и всем его портам) с хоста, вы можете просто запуститьweave expose .

Полное раскрытие: я работаю в Weaveworks.

Fons
источник
2

Есть удобная оболочка HAProxy.

docker run -it -p LOCALPORT:PROXYPORT --rm --link TARGET_CONTAINER:EZNAME -e "BACKEND_HOST=EZNAME" -e "BACKEND_PORT=PROXYPORT" demandbase/docker-tcp-proxy

Это создает HAProxy для целевого контейнера. очень просто.

kwerle
источник
1

Сначала прочитайте ответ Рикардо . Это сработало для меня.

Однако существует сценарий, в котором это не будет работать, если запущенный контейнер был запущен с помощью docker-compose. Это потому, что docker-compose (я использую docker 1.17) создает новую сеть. Способ решения этого сценария будет

docker network ls

Затем добавьте следующее docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus --net network_name

ice.nicer
источник
0

Невозможно выполнить прямое сопоставление портов, но есть несколько способов дать контейнеру Docker то, что равносильно реальному интерфейсу, который будет иметь виртуальная машина.

Интерфейсы Macvlan

Docker теперь включает сетевой драйвер Macvlan . Это присоединяет сеть Docker к интерфейсу «реального мира» и позволяет назначать адреса этих сетей непосредственно контейнеру (как в режиме моста виртуальных машин).

docker network create \
    -d macvlan \
    --subnet=172.16.86.0/24 \
    --gateway=172.16.86.1  \
    -o parent=eth0 pub_net

pipeworkМожно также отобразить реальный интерфейс в контейнер или настроить подчиненный интерфейс в более старых версиях Docker.

Маршрутизация IP

Если у вас есть контроль над сетью, вы можете направить дополнительные сети на хост Docker для использования в контейнерах.

Затем вы назначаете эту сеть контейнерам и настраиваете свой хост Docker для маршрутизации пакетов через сеть Docker.

Общий хост-интерфейс

--net hostОпция позволяет хост - интерфейс для совместного использования в контейнер , но это, вероятно , не очень хорошая установка для запуска нескольких контейнеров на одном хосте из - за общего характера.

Matt
источник