Розкриття порту на реальному контейнері Docker


408

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

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


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 (хоча мені довелося видалити деякі інші параметри, які змушують трафік проходити через докер проксі).

ПРИМІТКА: це підривний докер, тому слід робити з усвідомленням того, що він цілком може створити синій дим

АБО

Ще одна альтернатива - шукати (новий? Пост 0.6.6?) -P варіант - який буде використовувати випадкові порти хоста, а потім з'єднати їх.

АБО

з 0.6.5, ви можете використовувати функцію LINKs, щоб вивести новий контейнер, який спілкується з існуючим, з деякими додатковими ретрансляціями на прапорці -p цього контейнера? (Я ще не використовував посилання)

АБО

з докером 0,11? ви можете використовувати docker run --net host ..для приєднання свого контейнера безпосередньо до мережевих інтерфейсів хоста (тобто мережа не розміщена по іменах), і таким чином всі порти, які ви відкриєте в контейнері, піддаються впливу.


6
Схоже, це не працює з docker 1.3.0 принаймні. Правило DOCKER DNAT створюється при запуску докера з -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 Замість invoing 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

31
Ключова частина мого питання полягає в тому, що це має відбутися без перезавантаження контейнера ... Перехід до нового контейнера може зберігати файли, але ефективно знищить будь-які запущені процеси і буде подібний до перезавантаження на фізичній машині. Мені потрібно це робити, не стаючи цього. Дякую, хоча!
reberhardt

добре Я порівняв би це більше, хоча запустити паралельний екземпляр. оскільки обидва зараз запущені (старий і новий), можливо, простіше проксі до нового контейнера, як тільки будуть виконані необхідні міграції.
bosky101

Так, паралельні випадки та зворотне проксі - це одна з головних причин, які я люблю Докер. Однак у цьому сценарії мені потрібно зберегти всі запущені процеси в контейнері, які, можливо, були запущені через SSH. Хоча виконувані файли зберігатимуться під час створення зображення та запуску паралельного екземпляра, самі виконувані файли не запускатимуться, і нічого в оперативній пам’яті не буде втрачено.
реберхардт

6
Чому ти біжиш, sudo dockerа не просто docker?
Тіаго Фігейро

Ця порада мені дуже допомогла, бо я встановив безліч пакетів у повільній мережі. Запустившись, docker commitя був готовий тестувати додаток ще раз, а не витрачати години на перевстановлення всього.
gustavohenke

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

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


7
Досить розумний! Але, мабуть, краще використовувати verb/socat:alpine, оскільки його зображення містить 5% відбитка (якщо ви не зіткнетеся з несумісністю libc або DNS ).
jpaugh

3
Там такожalpine/socat
Jamby

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

Докерфайл:

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. По суті, це переадресація запитів до певних портів хоста у відповідні порти контейнерів docker. Це зовсім не конкретний докер, і ви можете зробити це зовсім іншим хостам. Чи можете ви додати форматування коду до файлу Docker? Це здається досить корисним.
AusIV

1
Лютий 2016: Запуск Docker 1.9.1, це єдине рішення, яке успішно працювало для мене. Жодне рішення IPTables не працювало. FROMДля ефективного використання ресурсів варто порадити використовувати те саме базове зображення, як і ваш контейнер БД.
Екскалібур

4
Ви можете спробувати apline / socat . Він поставляється з попередньо встановленою socat і приймає параметри socat як команду, тому вам взагалі не потрібно писати Dockerfile.
trkoch

35

Ось ще одна ідея. Використовуйте SSH для переадресації порту; це має перевагу також працювати в OS X (і, можливо, в Windows), коли вашим хокером Docker є VM.

docker exec -it <containterid> ssh -R5432:localhost:5432 <user>@<hostip>

4
у моєму випадку на зображенні, що працює у докера, немає бінарного ssh
Radu Toader

Чи потрібно мені створити ключ SSH на своїй машині і помістити його в докерний контейнер для цього?
октавіан

@octavian ssh-keygen -t rsa всередині контейнера. додати паб-ключ до авторизованих ключів у хості
Luke 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

Ви можете використовувати зображення дієслова / socat або альпійського / socat безпосередньо. Дивіться дієслово / socat
pjotr_dolphin

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: Ці правила і тимчасові, і триватимуть лише до перезавантаження контейнера


Це працювало для мене ... однак пізніше були і деякі Каваті. Згідно із приміткою 2, це зупиняється, якщо контейнери перезавантажуються, можливо, вони знаходяться на іншому IP-адресі. Але більш важливий docker не має поняття про ці iptable записи, тому не буде їх видалено, коли згодом повністю перезапустить службу і змусить докера виконати її належним чином. Результатом було кілька записів 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

Якщо жодна відповідь не працює для когось - перевірте, чи не працює ваш цільовий контейнер у докерній мережі:

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

2

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

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

Повне розкриття: Я працюю у Weaveworks.


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 до цільового контейнера. Простенька.



1

Спочатку прочитайте відповідь Рікардо . Це працювало для мене.

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

docker network ls

Потім додайте наступне docker run -d --name sqlplus --link db:db -p 1521:1521 sqlplus --net network_name


0

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

Інтерфейси Macvlan

Тепер Докер включає мережевий драйвер 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 для маршрутизації пакетів через докерну мережу.

Спільний хост-інтерфейс

Ця --net hostопція дозволяє розділити інтерфейс хоста в контейнер, але це, мабуть, не є гарною настройкою для запуску декількох контейнерів на одному хості через спільного характеру.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.