Решаем вопрос с портами раз и навсегда

Вы когда-нибудь были на собеседовании на должность младшего разработчика, где от вас ожидали, что вы знаете, как работает Docker? Сегодня интервьюеры ожидают, что вы хорошо знаете восемь, десять или даже более технологий. Это сумасшествие. Скорее всего, вы не изучали Docker в университете. Если у вас есть знания в этой области, вы отличаетесь от других людей.

Одна из первых проблем, с которой вы столкнетесь, когда начнёте использовать Docker, в том, что вы не сможете просто так подключиться к контейнеру. Эта статья о том, почему так происходит. Я также объясню на практическом примере, как в Docker работает привязка портов.

Даже если вы уже опытный разработчик, вы должны знать, что такое привязка портов. В противном случае во время собеседования вы можете выглядеть глупо. Если у вас нет об этом ни малейшего представления, возьмите чашку кофе и устраивайтесь поудобнее. Я постараюсь рассказать всё, что вам нужно знать об этом. Всего за несколько минут!

Начнём с контейнера Nginx Docker

Ничего страшного, если вы ничего не знаете о Docker. Я попробую подробно всё объяснить. Во-первых, мне нужно убедиться, что вы понимаете разницу между контейнером Docker и образом Docker.

Образ Docker  —  это файл со всеми зависимостями и конфигурациями для запуска определённой программы. Одна из проблем, решаемых с помощью Docker — кошмар установки.

Мы все устанавливали программы на Windows, Mac или Linux. Когда программе чего-то не хватает, она сообщает об этом и всё. Это раздражает. Наверное, можно тоже просто установить её, да? Тогда не хотите ли вы установить и эту программу? Ох… В конце концов вы получаете множество программ. При этом часто нужно настроить системные переменные и так далее. В худшем случае это может повредить систему. Вы же не хотите, чтобы все ваши коллеги прошли через это? Так вот, образ Docker содержит все необходимое для установки программы, а контейнер Docker  —  это запущенный экземпляр образа.

Итак, Docker решает проблему установки. Контейнер содержит все необходимое для работы и ничего больше. Вы можете запустить его на Windows, Linux или Mac и, в принципе, везде, где установлен Docker. Но хватит о преимуществах. Мы здесь для того, чтобы поднять и запустить контейнер.

Давайте, наконец, начнём с Nginx Docker. Nginx  —  это веб-сервер, работающий на 80 порту. Я сразу же запущу контейнер Docker (в отсоединенном режиме), используя образ Nginx Docker:

docker container run -d nginx

Создается контейнер. Вы увидите его UUID. Если вы не знаете, что такое UUID. С помощью команды container ps вы получаете список активных контейнеров:

Теперь, если вы попытаетесь подключиться к контейнеру напрямую с помощью curl или браузера, у вас не получится. То есть вы не можете подключиться непосредственно к контейнеру. 

 —  Но почему? В Docker-файле говорится, что порт 80 открыт… я его не понимаю. 

 —  Не волнуйтесь, я объясню это в следующей части статьи!

curl -I 127.0.0.1:80
curl: (7) Failed to connect to 127.0.0.1 port 80: Connection refused

Почему нельзя подключиться напрямую?

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

 —  А, понятно. То есть не наоборот. Как же тогда мы должны подключиться? 

 —  Есть несколько вариантов сделать это. Изучим их.

1. Предоставить все порты Docker’а.

docker container run -P -d nginx

Параметр -P открывает порты, предоставленные контейнеру. Docker идентифицирует каждый порт, предоставленный в Dockerfile, а также те, что предоставляются с помощью команды и параметра docker container build --expose. Каждый открытый порт напрямую привязан к “случайному” порту хоста. 

—  Хорошо звучит, но как теперь найти этот порт? 

 —  Не волнуйтесь, мы найдем ваши порты. Для этого есть несколько способов. Я покажу два из них:

docker container port

netstat

Начнём с команды Docker. Просто допишите к ней UUID контейнера. Вы увидите, что порт контейнера 80 привязан к IP-адресу хоста 0.0.0.0 и порту хоста 32768 (или другому порту при самостоятельном выполнении команды).

docker container port вставьте UUID контейнера
80/tcp -> 0.0.0.0:32768

Другой вариант  —  команда netstat. Чтобы найти все открытые порты, вы также можете выполнить команду ниже. Заметьте, что вы найдёте свой любимый порт среди других портов. Искомый порт находится в третьей строке.

netstat -ntlp

Все открытые порты, найденные netstat.

2. Доступ к определённому порту

Пример привязки портов: привязка порта 80 контейнера Docker к 8080 порту хост-машины.

Предоставление всех портов Docker обычно не очень хорошая идея: по умолчанию не выставляется вообще ни один порт. Думаю, это сделано в целях безопасности. Вы же не хотите открывать всё: это не имеет никакого смысла. Верно? Чтобы открыть только один порт, выполните эту команду:

docker container run -p 8080:80 -d nginx

Порт 80 контейнера Nginx открыт внешнему миру на порту хоста 8080. Теперь вы можете подключиться к контейнеру несколькими способами. Например, с помощью curl или вашего браузера. Это потрясающе!

Поздравляю, теперь вы понимаете самую важную часть привязки портов в Docker! Я покажу результат выполнения curl:

curl -I 0.0.0.0:8080
HTTP/1.1 200 OK
Server: nginx/1.17.9
Date: Sun, 08 Mar 2020 11:38:47 GMT
Content-Type: text/html
Content-Length: 612
Last-Modified: Tue, 03 Mar 2020 14:32:47 GMT
Connection: keep-alive
ETag: “5e5e6a8f-264”
Accept-Ranges: bytes

Позвольте мне также показать вам, как это будет выглядеть в вашем браузере при посещении 0.0.0.0:8080. Подключитесь с помощью браузера к 0.0.0.0:8080:

Ещё одна вещь

По умолчанию Docker предоставляет контейнерные порты IP-адресу 0.0.0.0 (это соответствует любому IP-адресу в системе). Вы также можете указать Docker, к какому IP привязываться. Это может быть 127.0.0.1 или другой IP. Чтобы привязать 80 порт контейнера к порту 8000 хоста и IP-адресу 127.0.0.1, также известному как localhost, просто выполните команду ниже:

docker run -d -p 127.0.0.1:8000:80 nginx

Заключение

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

Я надеюсь, эта статья прояснила для вас привязку портов.

Спасибо за чтение!

Читайте также:


Перевод статьи Dieter Jordens: How Does Docker Port Binding Work?

Предыдущая статьяИзменение типа с помощью typealias
Следующая статьяКакие десять книг про науку о данных и искусственный интеллект стоит прочитать в 2020