Глава 2. Исполнение прикладного приложения Rail в контейнере

Теперь вы должны начать знакомиться с понятиями Docker, такими как образы и исполняемые на основе этих образов контейнеры. Не беспокойтесь если вы не запомнили все команды и их разнообразные параметры. Самый важный момент состоит в том что вы начинаете осваивать все понятия на верхнем уровне - всё остальное придёт когда вы начнёте применять Docker снова и снова.

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

В этой главе мы собираемся подхватить с того места где мы остановились чтобы запустить своё новое приложение. Итак, вы жарили, перемалывали, варили и иным образом готовили домашний напиток по своему выбору? (Мне только воду, спасибо.) Ну ладно, давай натрескаемся ...

Как нам исполнять своё прикладное приложение Rails?

К сожалению мы не можем запустить некий сервер Rails просто своим образом ruby:2.6 - Rails имеет некоторые дополнительные зависимости. Например, нам потребуется интерпретатор JavaScript (скадем, Node.js) в помощь нашим конвейерам ресурсов, плюс нам потребуется установить свои зависимости gem. Как нам запустить некий сервер Rails в контейнере и при этом гарантировать соблюдение этих требований?

Существует несколько подходов, которые мы можем предпринять. Мы можем сделать то, что мы делали в своей предыдущей главе: запустить bash внутри контейнера на основе уже применявшегося образа ruby:2.6 и установить оттуда то что нам требуется. Однако исполнение всех команд вручную не самый простой способ для повторяемых действий. Мы бы желали повторяемый способ раскрутки серверов влево, направо и по центру. Необходимость запускать различные команды вручную каждый раз когда мы пожелаем запускать некий сервер Rails никак не сокращает работу.

Другой способ, которым мы можем получить контейнер исполняющим Rails состоит в получении того же самого набора команд, требуемых для установки требований Rails и соединения их воедино в некую длинную составную команду docker run. Однако не просто эта команда будет гигантской, неуклюжей и сложной для запоминания, но и, что хуже всего, она была бы медленной. Эти инструкции установки приходилось бы выполнять с нуля всякий раз, в том числе установку Node.js или наших зависимостей gem/ Мы бы не хотели ожидать минуты просто чтобы запустить некий сервер Rails.

[Замечание]Компоновка команд docker run

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

Основной трюк при обходе данного ограничения состоит в использовании параметра -c команды bash, что запустит некую оболочку Bash и немедленно исполнит всё что вы передадите в как строку. Это позволит сделать вам следующее:


​ 	​$ ​​docker​​ ​​run​​ ​​<options>> ​​[image:version]​​ ​​\​
​ 	​    ​​bash​​ ​​-c​​ ​​"command1 && command2 && command3..."​
		

Старый добрый bash.

Итак, каково же настоящее решение?

Ладно, мы хотим иметь возможность запускать контейнеры на основе некого предварительно настроенного состояния, которое имеет всё что необходимо для запуска Rails. Всё что нам понадобится, так это фабрика - подсказка, подсказка - для создания таких исключительных контейнеров сервера Rails. Вы уловили тонкий намёк? В своей предыдущей главе мы уже говорили, что некий образ это "фабрика для создания конкретных контейнеров". Похоже, это именно то что нам требуется.

Определение нашего первого пользовательского образа

В реальной жизни фабрика не берётся из ниоткуда. Она должна быть построена в соответствии с чертежами: подробными планами и инструкциями, которые в точности описывают как именно она должна выглядеть. Фабрики контейнеров Docker - иначе говоря, образы - ничем не отличаются. Они требуют особого файла кальки, так и именуемого, Dockerfile. Некий Dockerfile использует специальный синтаксис для описания в точности как следует собирать соответствующий образ. Если вы уже знакомы с выражением инфраструктуры в качестве кода (IaaC), это именно её пример: Dockerfile описывает как настраивается некий образ машины, как это показано на схеме ниже.

 

Рисунок 2-1



Некий Dockerfile создаётся из различных инструкций - таких как FROM, RUN, COPY и WORKDIR - по соглашению всё заглавными. Вместо того чтобы абстрактно рассуждать обо всём этом, давайте рассмотрим конкретный пример.

Вот некий базовый Dockerfile для исполнения нашего прикладного приложения Rails. Оно пока не совершенно - мы сделаем некие улучшения в Главе 3, Тонкая настройка нашего образа Rail - но пока нам хватит и этого. Пока нет необходимости создfвать этот файл; пока мы просто поговорим.


​ 	FROM ruby:2.6                                           
​ 	
​ 	RUN apt-get update -yqq                                 
​ 	RUN apt-get install -yqq --no-install-recommends nodejs 
​ 	
​ 	COPY . /usr/src/app/                                    
​ 	
​ 	WORKDIR /usr/src/app                                    
​ 	RUN bundle install
 	   

Всякий образ должен с чего- то начинаться: с другого, предварительно имевшегося образа. По этой причине всякий Dockerfile начинается с некой инструкции FROM, которая и определяет тот образ, который применять в качестве его отправной точки. Обычно мы ищем для некого стартового момента то что больше всего подходит. Таким образом мы можем расширять его и настраивать под свои потребности.

Самой первой строкой нашего Dockerfile является:


​ 	FROM ruby:2.6                                           
​ 	   

Она сообщает, что наш образ будет основываться на образе ruby:2.6, который, как вы вероятно догадались, имел предварительно установленным Ruby 2.6. Мы выбрали старт с этого образа потому как самым основным нашим требованием является наличие установленным Ruby, тем самым, данный образ уже проделал большую часть нашего пути.

[Замечание]Справа вверху: базовые образы

Существует несколько особых образов без каких бы то ни было образов родителей - они именуются как некий базовый образ - именно на них основаны все образы. Они содержат минимальную файловую систему пользователя для некой операционной системы.

Если вы пожелаете построить свой собственный урезанный до минимума образ, вы можете строить свой образ FROM scratch (с нуля, читается неплохо, не так ли?) где scratch это некий минимальный базовый образ.

Даже имеется возможность создавать ваши собственные базовые образы, хотя это и несколько более продвинутая тема. Мы не намерены обсуждать её, так как имеется вероятность что вам это никогда не понадобится.

Следующими двумя строками в Dockerfile являются инструкции RUN, которые указывают Docker на необходимость выполнения неких команд:


​ 	RUN apt-get update -yqq                                 
​ 	RUN apt-get install -yqq --no-install-recommends nodejs 
​ 	   

Здесь мы сообщаем Docker о необходимости исполнить apt-get update -yqq с последующим apt-get install -yqq --no-install-recommends nodejs - но чего достигают для нас эти две команды?

Как мы уже могли знать, apt-get это команда, применяемая для установки программного обеспечения в Debian (и неких прочих) дистрибутивах Linux. Мы применяем её в своём Dockerfile по той причине, что наш официальный образ Ruby, поверх которого мы строим совй собственный образ, основан на Debian - в частности на версии с названием Stretch.

Следующая команда apt-get update сообщает имеющемуся диспетчеру пакетов выгрузить самую последнюю информацию этого пакета. Многие Dockerfile будут иметь аналогичную строку, так как без неё apt вовсе не имеет никакой информации о пакете, а следовательно не будет иметь возможности установить что бы то ни было. Её параметр -yqq является сочетанием параметра -y, который указывает на необходимость отвечать "yes" во всех приглашениях и параметр -qq, который включает "quiet" (тихий) режим для снижения вывода на печать.

Идущая следующей, команда apt-get install устанавливает Node.js, предварительно требующийся для Rails. Соответствующий параметр --no-install-recommends сообщает о том что не нужно устанавливать прочие рекомендуемые, но не существенные пакеты - они нам не нужны, и мы желаем поддерживать размер своего образа настолько маленьким, насколько это возможно не устанавливая не являющиеся необходимыми файлы.

Если вы знакомы с apt-get в Linux, вы могли бы удивиться что мы не запускаем свои команды от имени root при помощи sudo. Это обусловлено тем, что по умолчанию команды внутри контейнера исполняются самим пользователем root, поэтому sudo не требуется (хотя, как мы обсудим, это имеет последствия для промышленных приложений).

Давайте слегка переключимся перед тем как обсуждать следующую строку своего Dockerfile.

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

Мы уже наблюдали в Генерации нового прикладного приложения Rails без установленного Ruby, что мы имеем возможность монтировать некий локальный каталог в своём исполняемом контейнере. Некий монтируемый том действует как какой- то совместный для самого контейнера и его хоста каталог и это один из способов, которым мы можем выполнять доступ к локальным файлам внутри своего контейнера.

Тем не менее, монтирование некого тома имеет существенную тёмную сторону если это единственный способ, коим вы получаете доступ внутри некого контейнера. Файлы в неком томе не являются частью самого этого образа; они вкладываются в этот образ поверх во время исполнения (runtime, когда вы запускаете свой контейнер). Если такие монтируемые файлы являются существенными, данный образ не будет без них работать, но основным моментом образа является задача упаковать всё что ему требуется для исполнения. Таким образом, хорошим практическим приёмом будет снабдить все необходимые файлы вовпутрь самого образа.

Следующая строка в нашем Dockerfile обслуживает именно эту цель:


​ 	FROM ruby:2.6                                           
​ 	
​ 	RUN apt-get update -yqq                                 
​ 	RUN apt-get install -yqq --no-install-recommends nodejs 
​ 	
​ 	COPY . /usr/src/app/                                    
​ 	   

Это сообщает Docker о необходимости скопировать все файлы и нашего локального, текущего каталога (.) в /usr/src/app в файловую систему нашего нового образа. Так как нашим локальным текущим каталогом является корень нашего Rails, на самом деле мы просим: "Скопируй наше приложение Rails в папку /usr/src/app этого контейнера." Значение нашего пути источника всегда относительное к тому где расположен используемый Dockerfile.

Добавив наши файлы Rails в /usr/src/app своего образа мы хотим исполнить различные команды, которые требуется отработать в том каталоге гди и расположены эти файлы. Например, вскорости мы пожелаем запустить своё приложение сервера Rails в неком контейнере при помощи команды, подобной следующей:


​ 	$ ​docker​ ​run​ ​[OPTIONS]​ ​<our​ ​custom​ ​image>> ​bin/rails​ ​server
​ 	   

К сожалению, эта команда завершится неудачно ибо, по умолчанию, рабочим каталогом контейнера является /, который не содержит файлов нашего приложения Rails - мы скопировали их в /usr/src/app.

Тем не менее, нам может помочь с исправлением данного положения вещей соответствующая инструкция WORKDIR. На самом деле она выполняет команду смены каталога (cd), изменяя то, что наш образ рассматривает своим текущим каталогом. Следующая строка в нашем Dockerfile применяет её для установки в качестве своего рабочего каталога /usr/src/app:


​ 	WORKDIR /usr/src/app                                    
​ 	   

Теперь, запускаемые команды bin/rails server (и аналогичные) отработают, так как они будут исполнены в правильном каталоге.

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

Наконец, мы достигаем самой последней строки в своём Dockerfile:


​ 	RUN bundle install
 	   

Эта команда исполняется в текущем рабочем каталоге, который предыдущей командой был установлен в /usr/src/app. Таким образом это установит необходимые gem, определяемые Gemfile нашего проекта Rails, которые необходимы для запуска данного приложения.

  Собираем всё воедино

Вооружившись этими знаниями, наш Dockerfile должен быть теперь намного более понятным. Давайте взглянем на него ещё разок:


​1: 	FROM ruby:2.6                                           
​2: 	
​3: 	RUN apt-get update -yqq                                 
​4: 	RUN apt-get install -yqq --no-install-recommends nodejs 
​5: 	
​6: 	COPY . /usr/src/app/                                    
​7: 	
​8: 	WORKDIR /usr/src/app                                    
​9: 	RUN bundle install
 	   

Во- первых, в строке 1 мы сообщаем своему персональному образу о необходимости применять общий образ ruby:2.6 в качестве его отправной точки. Затем обновляем информацию о пакетах в своём диспетчере пакетов apt (строка 3) с тем, чтобы знать чо устанавливать. Затем мы применяем его для установки nodejs (строка 4), который нам требуется для конвейера ресурсов Rails.

Позаботившись о предварительных требованиях для Rails, мы затем скопировали файлы своего приложения из нашего текущего каталога в /usr/src/app (строка 6) с тем чтобы они были выпечены в самом образе. Мы делаем его текущим рабочим каталогом для своего образа (строка 8), с тем чтобы мы могли исполнять команды Rails для своего образа из верного каталога.

Наконец, мы bundle install (строка 9) для установки тех gem, которые требуются нашему проекту Rails.

Теперь, когда в этом больше смысла, давайте продолжим и создадим этот Dockerfile. Вначале мы убедимся что мы находимся в самом верхнем уровне (в корне) каталога своего приложения Rails:


​ 	​$ ​​ls​
​ 	Gemfile    Rakefile  bin     config.ru  lib  package.json  storage  vendor
​ 	README.md  app       config  db         log  public        tmp
		

Затем вскройте свой любимый редактор и создайте некий файл с названием Dockerfile с показанным нами содержанием. Я настоятельно рекомендую набрать его вручную вместо копирования и вставки - при обучении новым навыкам физический набор изучаемого помогает зацементировать это в вашем сознании и построить вам память на кончиках пальцев.

Имея в своих руках роскошный Dockerfile, давайте переключим своё внимание на то мы применим его для создания некого реального образа.

Построение нашего образа

Процесс выработки некого образа по Dockerfile именуется построением образа. Мы выполняем его при помощи команды docker build, которая имеет следующий формат:


​ 	​$ ​docker​ ​build​ ​[options]​ ​path/to/build/directory
		

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


​ 	​​$ ​​docker​​ ​​build​​ ​​.​
​ 	Sending build context to Docker daemon  138.8kB
​ 	Step 1/6 : FROM ruby:2.6
​ 	​ --->​​ ​​f28a9e1d0449​
​ 	Step 2/6 : RUN apt-get update -yqq
​ 	​ ---> ​​Running​​ ​​in​​ ​​29677ed71d2b​
​ 	Removing intermediate container 29677ed71d2b
​ 	​ ---> ​​761da319d69a​
​ 	...
​ 	Step 6/6 : RUN bundle install
​ 	​ ---> ​​Running​​ ​​in​​ ​​4550030ac412​
​ 	...
​ 	Bundle complete! 15 Gemfile dependencies, 68 gems now installed.
​ 	Bundled gems are installed into `/usr/local/bundle`
		

Ух ты, достаточно большой вывод. Что же произошло на самом деле?

Docker обрабатывает наш файл Dockerfile по одной инструкции за раз. Самая первая инструкция - FROM - трактуется иным образом нежели все остальные. Docker проверяет имеется ли уже определяемый образ в нашей локальной системе. Если данный образ не доступен, Docker выгружает этот образ, в точности как это имело место в Исполнение некого сценария Rails без установленного Ruby, когда мы исполнили docker run с указанием ruby:2.6 в самый первый раз.

Все прочие инструкции в данном Dockerfile обрабатываются тем же самым манером. Docker запускает одноразовый контейнер на основе тог образа, который был создае на предыдущем шаге, и исполняет текущую инструкцию Dockerfile внутри него. Затем Docker фиксирует (commit) все только что сделанные в результате исполнения данной инструкции изменения, создавая некий промежуточный образ для данного шага.

Вы можете всё что происходит в получаемом выводе, который разделяется на секции: по одной секции для каждой инструкции из Dockerfile. Получаемый вывод для каждого этапа имеет достаточно упорядоченный формат:


​1: 	Step <current step>/<total steps> : <Dockerfile instruction>         
​2: 	---> Running in <container ID>                   
​3: 	[Any output from running the instruction]        
​4: 	Removing intermediate container <container ID>   
​5: 	---> <image ID>
		

Для указания на контекст нам выдаётся сама инструкция Dockerfile, а также какой этап выполняется в данном процессе сборки (строка 1). Затем мы видим значение идентификатора того одноразового контейнера, который применяется для исполнения текущей инструкции Dockerfile (строка 2), за которой следует весь вывод от исполнения данной инструкции (строка 3).

Всякий создаваемый образ - даже промежуточный - получает некий уникальный, случайно вырабатываемый идентификатор образа. Именно им Docker идентифицирует образы внутри себя, причём он предоставляет нам некий способ ссылаться на него. Само значение создаваемого на данном этапе образа отображено в строке 5.

Наконец, в строке 4 Docker сообщает нам об удалении им того одноразового контейнера, который он применял на данном этапе.

При построении некого индивидуального образа мы в действительности интересуемся самим окончательным образом. Он представлен на самом последнем этапе после окончания всех инструкций по окончанию исполнения данного Dockerfile. Именно по этой причине весь вывод завершается выдачей нам самого последнего идентификатора образа (если вы следуете за нами, ваш идентификатор образа будет иным):


​ 	​Successfully built a1df0eddba18
		

Всё это кажется достаточно разумным, но где находится сам только что собранный нами образ?

Сама команда docker build не выполняет вывод некого файла; она всего лишь делает доступным такой новый образ, добавляя его в имеющийся перечень образов, о которых известно Docker. Docker управляет тем где и как сохраняются образы в вашей файловой системе. Приводимой ниже командой мы перечисляем имеющиеся в нашей системе образы:


​ 	​$ ​​docker​​ ​​images​
​ 	REPOSITORY     TAG        IMAGE ID        CREATED         SIZE
​ 	<none>         <none>      a1df0eddba18    1 second ago    1.01GB
​ 	ruby           2.6        f28a9e1d0449    6 days ago      868MB
		

Самый первый элемент представляет наш персональный образ, который мы только что создали - его идентификатор образа совпадает с тем, что определён по окончанию выполненной нами команды docker build.

Запуск сервера Rails при помощи нашего образа

Теперь, когда мы создали свой собственный сделанный на заказ образ, мы должны иметь возможность запустить некий сервер Rails для исполнения нашего приложения.

Давайте попробуем сделать это сейчас.

Мы можем ссылаться на свой образ по его идентификатору. Тем не менее, идентификаторы образа достаточно длинные и трудные для запоминания, поэтому обычным делом является назначение некому образу осмысленного названия. Позднее в Именовании и сопровождении версий для нашего образа мы увидим как это делается. На данном этапе, однако будем ссылаться на свой образ по его идентификатору, чего нам вполне хватит для старта при помощи данного образа. Давайте вскроем и сделаем это прямо сейчас.

Имея на руках идентификатор образа, мы можем запустить своё приложение Rails внутри некого контейнера на основе своего персонального образа при помощи приводимой далее команды. Давайте сделаем это:


​ 	​$ ​​docker​​ ​​run​​ ​​-p​​ ​​3000:3000​​ ​​a1df0eddba18​​ ​​\​
​ 	​    ​​bin/rails​​ ​​s​​ ​​-b​​ ​​0.0.0.0​
		

Помимо отображённого нового параметра -p 3000:3000, который мы вкратце обсудим в разделе Добираемся до прикладного приложения: публикация портов, это обычная старая команда docker run. Она сообщает: "Запусти некий котейнер на основе нашего персонального образа (a1df0eddba18) и выполни внутри него bin/rails s -b 0.0.0.0." Если вы никогда ранее не видели параметр -b, мы поясняем зачем он требуется в разделе Связывание сервера Rails с IP адресом.

Мы должны обнаружить что Rails запустился должным образом:


​ 	=>  Booting Puma
​ 	=>  Rails 5.2.2 application starting in development
​ 	=>  Run `rails server -h` for more startup options
​ 	Puma starting in single mode...
​ 	* Version 3.12.0 (ruby 2.6.0-p0), codename: Llamas in Pajamas
​ 	* Min threads: 5, max threads: 5
​ 	* Environment: development
​ 	* Listening on tcp://0.0.0.0:3000
​ 	Use Ctrl-C to stop
		

Дело сделано! Полёт идёт нормально.

Теперь давайте двинемся далее и посетим в вашем браузере http://localhost:3000. Вы должны увидеть знакомую страницу приветствия Rails.

 

Рисунок 2-2



Дай пять! Мы достигли своего приложения.

В своём терминале вы обнаружите обновление вывода журнала Rails показом нашего запроса:


​ 	Started GET "/" for 172.17.0.1 at 2019-01-15 09:49:45 +0000
​ 	...
​ 	  Rendering /usr/local/bundle/gems/railties-5.2.2/lib/rails/templates/rails/
​ 	welcome/index.html.erb
​ 	  Rendered /usr/local/bundle/gems/railties-5.2.2/lib/rails/templates/rails/
​ 	welcome/index.html.erb (2.7ms)
​ 	Completed 200 OK in 17ms (Views: 10.0ms | ActiveRecord: 0.0ms)
		

Мы в деле. Теперь вы можете остановить свой сервер Rails нажав Ctrl - C.

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

Добираемся до прикладного приложения: публикация портов

Насколько нам известно,контейнеры изолированы в песочницах. Как так произошло, что мы оказались способными достичь свой контейнер посетив http://localhost:3000 в своей локальной машине?

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

Однако по умолчанию некий контейнер может быть доступен только изнутри сетевой среды Docker, к которой он подключён (подробнее об этом в Как контейнеры общаются друг с другом), мы же можем сделать его доступным извне опубликовав один или более портов при помощи параметра -p в docker run.

В своей команде мы определили -p 3000:3000; это опубликовало соответствующий порт контейнера 3000 (второе значение) по порту 3000 в нашей локальной машине. Именно таким образом запрос к http://localhost:3000 и достигает наш сервер Rails, запущенный в имеющемся контейнере с портом 3000.

Как это работает на практике?

Как мы уже видели в Схеме архитектуры Dockere для Linux, в Linux наш демон Docker запускается напрямую в вашей локальной машине. В этом случае публикация порта просто устанавливает соответствие (через некое правило iptables), что и переправляет запросы к http://localhost:3000 в имеющийся Механизм Docker, который знает как выполнять маршрутизацию соответствующего запроса в той сетевой среде, к которой подключён ваш контейнер (как это показано на следующей схеме).

 

Рисунок 2-3



Docker для Mac/Windows имеет некую дополнительную сложность. Помните, здесь демон Docker запускается внутри некоторой ВМ Linux с малым весом, как мы это видели на Схеме архитектуры Dockere для Mac/Windows. Внутри такой ВМ всё работает в точности как для Docker в Linux - имеющееся соответствие портов будет выполнять маршрутизацию в ваш контейнер. Однако требуется ещё немного магии для направления запросов от http://localhost:3000 в сам порт 3000 ВМ; Docker для Mac/Windows настраивает для достижения этого пересылку порта, которая отображена на следующей схеме.

 

Рисунок 2-4



При публикации некого порта вы не обязаны использовать тот же самый внешний порт, который ваша служба применяет внутри самого контейнера. Если бы мы определили -p 80:3000, это бы выполнило установку соответствия порта 80 в вашей локальной машине тому серверу Rails, который осуществляет ожидание по порту 3000 внутри вашего контейнера. Это предоставляет нам гибкость в терминах того как выставлять службы во внешний мир.

Связывание сервера Rails с IP адресом

Обычно для запуска некого сервера Rails мы просто исполняем bin/rails, всё же когда мы запускаем сервер Rails при помощи docker run, мы применяем bin/rails s -b 0.0.0.0. Зачем мы так поступаем?

Когда мы запускаем сервер Rails с помощью bin/rails s, по умолчанию, он выполняет ожидание запросов только в localhost ( или в 127.0.0.1) на каждой машине, в которой он запущен. Это предоставляет некую безопасность по умолчанию, предотвращая доступ извне к серверу вашего приложения Rails. Однако в нашем случае этот сервер работает внутри некого контейнера, а запросы поступают извне.

Когда мы делаем запрос http://localhost:3000 в своей локальной машине, как мы это уже видели в Добираемся до прикладного приложения: публикация портов, такой запрос перенаправляется в имеющийся Механизм Docker. Тот в свою очередь выполняет маршрутизацию в тот контейнер, в котором запущен наш сервер Rails, выполняя транслюцию запроса в [IP address of container]:3000. Однако поскольку наш сервер Rails выполняет ожидание только запросов от localhost, ничто не выдаст ответ на такие запросы, поступающие с иного IP адреса.

Для исправления этого нам придётся сообщить своему серверу Rails выполнить привязку ко всем IP адресам, а не только к localhost, воспользовавшись параметром -b 0.0.0.0. Значение IP адреса 0.0.0.0 является особым адресом, который означает "все IPv4 адреса в данной машине."

[Замечание]Обнаружение IP адреса запущенного контейнера

Если вас интересует как отыскать значение реального адреса IP некого исполняемого контейнера, вы можете проделать следующее:

  1. Получите идентификатор искомого контейнера:

    
    ​ 	​$ ​​docker​​ ​​ps​
    ​ 	CONTAINER ID        IMAGE               more info
    ​ 	d7230c4b0595        e28cf982ae39
     	   
  2. Воспользуйтесь им в команде docker inspect определив полученное значение идентификатора контейнера:

    
    ​ 	​$ ​​docker​​ ​​inspect​​ ​​--format​​ ​​\​
    ​ 	​    ​​'{{ .NetworkSettings.IPAddress }}'​​ ​​d7230c4b0595​
    ​ 	172.17.0.2
     	   

Беглый обзор

Уф! Это была насыщенная действиями глава! Давайте повторим основное:

  1. Мы рассмотрели свой первый, сшитый на скорую руку Dockerfile, который позволил нам запустить своё приложение с неким сервером Rails:

    
    ​​ 	FROM ruby:2.6                                           
    ​ 	
    ​ 	RUN apt-get update -yqq                                 
    ​ 	RUN apt-get install -yqq --no-install-recommends nodejs 
    ​ 	
    ​ 	COPY . /usr/src/app/                                    
    ​ 	
    ​ 	WORKDIR /usr/src/app                                    
    ​ 	RUN bundle install
     	   
  2. Мы построили свой персональный образ из Dockerfile при помощи:

    
    ​​ 	​$ ​​docker​​ ​​build​​ ​​.​
     	   
  3. Мы перечислили все образы в своей системе вызвав:

    
    ​​ 	​$ ​​docker​​ ​​images​
     	   
  4. Мы запустили некий сервер Rails для исполнения нашего приложения с помощью:

    
    ​​ 	​$ ​​docker​​ ​​run​​ ​​-p​​ ​​3000:3000​​ ​​a1df0eddba18​​ ​​\​
    ​ 	​ ​​bin/rails​​ ​​s​​ ​​-b​​ ​​0.0.0.0​
     	   

А также мы увидели его исполняющимся из своего браузера по http://localhost:3000.

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