Часть 1. Разработка

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

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

Глава 1. Новый мир храбрых

В этой главе мы намерены убедится что вы вооружены установкой рабочей версии Docker в вашей машине. Это важно, поэтому вы действительно должны попробовать самостоятельно и следовать приводимым примерам.

Далее мы сразу же погрузимся в пучину и выполним свою первую в истории команду Docker - запустив базовый скрипт на Ruby. Однако вместо того, чтобы полагаться на установленную на вашем локальном компьютере версию Ruby мы воспользуемся версей, поставляемой Docker.

Вы ознакомитесь с основами того как работает Docker, в том числе что такое образы и контейнеры и зачем они нам потребовались. Мы освоим основы анатомии docker run - вероятно самой центральной команды для понимания Docker

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

Устанавливаем Docker

Давайте приступим к установке Docker на вашей машине.

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

Мы намерены воспользоваться свободно распространяемой Community Edition (CE, Редакцию сообщества), вместо того чтобы применять Enterprise Edition (EE, Корпоративную редакцию). Сам по себе Docker CE поступает в двух качествах: Edge (находящемся на переднем крае), который содержит самые последние только что разработанные свойства, и Stable, который, ну, вы понимаете, ещё более стабильный. Убедитесь что вы устанавливаете последнюю версию, так как нам не нужны неожиданные сюрпризы, возникающие по ходу обучения пока вы следуете по данной книге.

Пройдите далее и прочтите инструкции по установки именно для вашей ОС, затем установите Docker и вернитесь ко мне обратно когда завершите. Не беспокойтесь, я подожду - ничто не будет плавать на моём борту лучше чем совершенно новая, резвая установка Docker.

  macOS

Docker предоставляет выгружаемую установку с названием Docker for Mac, в которой имеется всё что вам нужно в одном чётком пакете (это примерно 115.6 МБ для выгрузки). Пройдите далее и установите его следуя инструкциям по установке.

После установки Docker for Mac добавляет некую панель меню прикладного приложения в правом верхнем углу вашего экрана, снабжённое логотипом Docker, которым является талисман кита, нежно именуемый "Моби Док". Эта панель меню не только сообщает вам запущен ли Docker, но также и предоставляет прочие полезные информацию и настройки. В можете найти дополнительные подробности о расширенных настройках доступными в документации.

  Linux

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

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

Вам также потребуется пересмотреть инструкции пост- установки Docker, чтобы убедиться что вы всё сделали верно. Это поможет вам избежать какие бы то ни было проблемы, с которыми вы бы столкнулись.

В последующих главах мы будем полагаться на некий инструментарий с названием Docker Compose (компоновщик Docker), который в Linux устанавливается отдельно. Пройдите далее и установите его воспользовавшись надлежащей документацией. Если так получится, что он уже установлен, я рекомендую обновить его до самой последней, стабильной версии.

  Windows

То как устанавливать Docker зависит от того поддерживает ли ваша система Hyper-V, доморощенную технологию виртуализации Microsoft. Редакции Professional, Enterprise или Education (в своих 64- битных версиях) Windows 8 и выше имеют его поддержку - если это допускают аппаратные средства - однако, в частности, редакция Windows Home нет. Подробнее....

Если ваша система поддерживает Hyper-V, выгрузите установщик Docker for Windows, запустите его и следуйте инструкциям Docker for Windows устанавливает некий виджет в области уведомлений вашего Windows в правом нижнем углу вашего экрана (вам может понадобиться кликнуть туда чтобы раскрыть его). Кликнув по данному виджету вы откроете некое меню в котором вы обнаружите дополнительную информацию относительно регулировок различных настроек.

Если ваша система не поддерживает Hyper-V, вам может потребоваться выгрузить и установить Docker Toolbox, некий наследуемый способ запуска Docker on Windows. {Прим. пер.: или, например, установить VirtualBox. Также отметим, что начиная с Windows Server 2019 Microsoft имеет поддержку MobyVM и LCOW, которые позволяют контейнерам Linux работать на хосте контейнеров Windows Server, даже работая бок о бок с контейнерами Windows!}

Проверьте свою установку

Давайте убедимся что Docker установлен и запущен правильно. Так как Docker является неким инструментарием работающим из командной строки, пройдите далее и вскройте свой любимый терминал {Прим. пер.: например, 64- битный File Commander Брайана Хэйварда}, а затем введите такую команду:


​$ ​docker​ ​version
		

Если всё хорошо, вы должны получить что- то подобное следующему:


​​ 	Client: Docker Engine - Community
​ 	 Version:           18.09.0
​ 	 API version:       1.39
​ 	 Go version:        go1.10.4
​ 	 Git commit:        4d60db4
​ 	 Built:             Wed Nov  7 00:47:43 2018
​ 	 OS/Arch:           darwin/amd64
​ 	 Experimental:      false
​ 	
​ 	Server: Docker Engine - Community
​ 	 Engine:
​ 	  Version:          18.09.0
​ 	  API version:      1.39 (minimum version 1.12)
​ 	  Go version:       go1.10.4
​ 	  Git commit:       4d60db4
​ 	  Built:            Wed Nov  7 00:55:00 2018
​ 	  OS/Arch:          linux/amd64
​ 	  Experimental:     false
		

Не беспокойтесь, если у вас более новая версия, чем это показано здесь. У вас всё готово.

Прежде чем вы начнёте

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

  Что такое контейнер?

На понятийном уровне некий контейнер представляет собой некую изолированную или "помещённую в песочницу" среду исполнения - какую- то пустую ёмкость для исполнения в ней программное обеспечение. Контейнеры полагаются на свойства виртуализации встроенного в него ядра Linux (а в последнее время и Windows), что позволяет вам создавать полностью изолированный набор процессов, которые не знают (или не озабочены) относительно всей прочей системы. На самом деле, внутри некого контейнера, всё представляется таким образом, как будто это полная система Linux (или - {Прим. пер.: а вас это волнует?} - Windows), хотя все его ресурсы и возможности исходят от той машины хоста, в которой он исполняется.

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

  Что такое образ?

Как мы только что сказали, некий контейнер, чисто абстрактно, это всего лишь пустая ёмкость для исполнения в ней программного обеспечения. Чтобы запустить некий контейнер, вам понадобится снабдить его некой особой средой или контекстом - то, что вам потребуется для запуска некого веб сервера NGINX в одном контейнере, будет совершенно отличаться от того, что вам понадобится для, допустим, MySQL, в другом контейнере.

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

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

 

Рисунок 1-1



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

Исполнение некого сценария Rails без установленного Ruby

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

Давайте взглянем на это:


​ 	​$ ​​docker​​ ​​run​​ ​​ruby:2.6​​ ​​ruby​​ ​​-e​​ ​​"puts :hello"​
​ 	Unable to find image 'ruby:2.6' locally
​ 	2.6: Pulling from library/ruby
​ 	cd8eada9c7bb: Pull complete
​ 	c2677faec825: Pull complete
​ 	fcce419a96b1: Pull complete
​ 	045b51e26e75: Pull complete
​ 	3b969ad6f147: Pull complete
​ 	f2db762ad32e: Pull complete
​ 	708e57760f1b: Pull complete
​ 	06478b05a41b: Pull complete
​ 	Digest: sha256:ad724f6982b4a7c2d2a8a4ecb67267a1961a518029244ed943e2d448d6fb7
​ 	994
​ 	Status: Downloaded newer image for ruby:2.6
​ 	hello
		

Фига себе! Это что сейчас было?

Если вы взглянете на самую последнюю строку вывода, вы обнаружите, что это тот самый вывод, который мы бы ожидали получить из своего сценария Ruby: hello. Итак, это каким- то образом работает. Но как? Почему?

Наша команда docker run имеет следующий формат:


$ ​docker​ ​run​ ​[OPTIONS]​ ​<image> ​<command> 
		

Эта команда запускает некий новый контейнер, который основан на ​<image> и исполняет <command> внутри этого контейнера. Вы можете найти полезным представлять себе это в двух частях: docker run [OPTIONS] ​<image> сообщает какой тип контейнера мы намерены запустить, в то время как <command> сообщает что мы желаем сделать внутри этого контейнера.

Итак, вернёмся обратно к своей команде, у нас есть:

 

Рисунок 1-2



Наша первая часть сообщает что мы желаем запустить некий контейнер на основе образа ruby:2.6. Как мы уже обсуждали в Прежде чем вы начнёте, некий образ является полностью экипированным пакетом всего что требуется для запуска (какого- то особенного) контейнера. Наш образ ruby:2.6 не является исключением; он обладает предварительно установленным Ruby 2.6, причём со всеми его зависимостями, что позволяет нам созлавать контейнеры, способные запускать эту версию Ruby.

Вторая имеющаяся часть нашей команды определяет что мы желаем запустить внутри этого контейнера. В данном случае мы сообщаем, что хотели бы запустить соответствующий интерпретатор Ruby с неким сценарием, который передаётся при помощи параметра командной строки -e. Этот сценарий это самое простое что только можно себе представить: он всего- навсего выводит слово "hello".

[Замечание]Терминология: Исполнение образа

Может так случиться, что мы произносим исполнение некого образа, однако строго говоря, это неверно. Образы не могут исполняться непосредственно; они являются неизменными фабриками для создания контейнеров. Вместо этого мы имеем в виду, что мы создаём некий контейнер на основе данного образа, а уже именно этот контейнер может быть исполнен.

Наша команда docker run будет работать в любой машине которая имеет установленным Docker - даже без самого Ruby.

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

На самом деле мы этого не делали.

Когда мы исполнили свою команду docker run, вы могли заметить, что он нам сообщил Unable to find image ’ruby:2.6’ locally. Затем Docker осуществил выгрузку нужного нам образа ruby:2.6, и по этой причине данная команда потребовала какого- то времени для исполнения. Вместо того чтобы выгрузить этот образ за один шаг, выгрузка была выполнена по частям - именуемым уровнями - и это создало нам необходимый образ. Таким образом, Docker снабдил нас бесшовным механизмом для доставки в точности тех образов что нам нужны, когда он нам требуются.

  А что так долго?

Если вы самостоятельно выполняли нашу предыдущую команду, имелся один небольшой изъян, который вы могли заметить: она потребовала многоооо времени. Я знаю, языки с интерпретацией, такие как Ruby, достаточно медленные, но это нелепо.

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

Хотя образы обычно намного меньше чем ВМ - МБ вместо ГБ - дожидаться по 20 секунд для каждой команды Docker было бы достаточно разочаровывающим. К счастью, нам не нужно делать этого. Docker сохраняет выгружаемые образы локально, поэтому когда вы в следующий раз запускаете некий контейнер на основе того же самого образа, он запускается с почти естественной скоростью. Docker способен кэшировать индивидуальные уровни необходимого образа, делая возможным повторное использование уровней между образами, как мы это вскорости увидим.

Давайте рассмотрим это самостоятельно. Попробуем запустить свою команду повторно.


$ ​docker​ ​run​ ​ruby:2.6​ ​ruby​ ​-e​ ​"puts :hello"​
​hello
		

Ух ты. На этот раз намного быстрее - не было вывода о подлежащих выгрузке образах.

  Убираем за собой

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

Перечислив запущенные контейнеры мы имеем:


​ 	​$ ​​docker​​ ​​ps​
​ 	CONTAINER ID    IMAGE    COMMAND    CREATED    STATUS    PORTS    NAMES
		

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

Давайте перечислим все контейнеры, включая остановленные, добавив соответствующий параметр -a:


​​ 	$ ​​docker​​ ​​ps​​ ​​-a​
​ 	CONTAINER ID  IMAGE     COMMAND  CREATED  STATUS   PORTS  NAMES
​ 	974e2bcb8266  ruby:2.6  "ruby …  1 seco…  Exited…         dazzling_ba…
​ 	7f8d7dddd6b5  ruby:2.6  "ruby …  3 seco…  Exited…         hungry_heis…
		

Здесь у нас имеются два контейнера: по одному для каждого нашего запуска соответствующей команды Ruby. Темс не менее, они нам больше не нужны; мы можем удалить их:


​​ 	$ ​​docker​​ ​​rm​​ ​​<container​​ ​​id> ​​[<container​​ ​​id2>​​ ​​...]​
		

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


​​ 	$ ​​docker​​ ​​rm​​ 974e2bcb8266​ ​7f8d7dddd6b5
		

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

На будущее, когда мы создаём некий контейнер, который мы делее не будем применять, мы можем воспользоваться соответствующим параметром -rm, который сообщит Doccker о необходимости удаления такого контейнера по его завершению. Модным словечком для контейнером с коротким временем жизни, которые подлежат удалению после того как они сослужили своё, это эфемерный (throwaway), но я предпочитаю своё слово одноразовый. Вот как мы выполнили свой сценарий Ruby в неком одноразовом контейнере:


​​ 	$ ​docker​ ​run​ ​--rm​ ​ruby:2.6​ ​ruby​ ​-e​ ​"puts :hello"
		

Это достаточно распространённый шаблон, и вы будете его наблюдать на протяжении всей нашей книги.

Генерация нового прикладного приложения Rails без установленного Ruby

Выполнение некого сценария Ruby это было круто, но что ещё мы можем делать?

Было бы неплохо начать применять Docker для каких- нибудь задач "реального мира"? Давайте представим себе, что мы пожелали бы создать некий новый проект Rails (это не так уж и надуманно... ведь мы помимо всего прочего разработчики Ruby). Можем ли мы это сделать? Ставьте на кон.

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

Вместо этого мы можем сделать нечто слегка иное. Мы можем запустить некий контейнер, исполняющий какую- то интерактивную оболочку bash. Сделав это мы буквально получаем некий терминальный сеанс, исполняемый внутри самого контейнера. Отсюда мы можем запускать столько команд сколько пожелаем, во многом аналогично тому как если бы у нас имелся некий локальный сеанс Bash. Это очень полезный трюк чтобы развязать вам руки.

Давайте раскрутим это.

Однако, прежде чем мы начнём, вам понадобится найти некий каталог в своей машине, в котором вы желаете создать файлы своего проекта Rails. Поскольку наши предстоящие команды Docker будут оказывать воздействие на наши локальные файлы, (вскоре мы в точности разберёмся насколько близко), я рекомендую вам создать некую новую пустую папку, в которой вы будете исполнять свои шаги. Например:


​ 	​$ ​​mkdir​​ ​​~/docker_for_rails_developers​
​ 	​$ ​​cd​​ ​​~/docker_for_rails_developers​
		

Всё установлено? Отлично. Теперь мы готовы запустить некую интерактивную оболочку внутри контейнера на основе теперь уже знакомого нам образа ruby:2.6:


​ 	​$ ​docker​ ​run​ ​-i​ ​-t​ ​--rm​ ​-v​ ​${PWD}:/usr/src/app​ ​ruby:2.6​ ​bash
		

Как вы можете видеть, мы применяем соответствующий параметр --rm для создания одноразового контейнера, который будет удалён когда мы покончим с ним. Тут также имеются и новые параметры (-i, -t и -v ${PWD}:/usr/src/app), которые нам не встречались ранее. Через мгновение мы вернёмся к ним. Теперь же, когда мы запустили эту команду, вас должно приветствовать некое приглашение терминала, которое выглядит как- то так:


	​root@0c286e8bda42:/#
		

Это иное приглашение указывает теперь что мы теперь успешно запустили некую оболочку Bash внутри контейнера. соответствующие root@ и # указывают, что мы являемся пользователем root - это пользователь по умолчанию внутри некого контейнера.

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


	​root@0c286e8bda42:/# cd​ ​/usr/src/app
		

Теперь давайте установим gem Rails:


	​root@0c286e8bda42:/usr/src/app# ​gem​ ​install​ ​rails
		
[Замечание]Версии Rails

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

Вы должны обнаружить установленными соответствующий gem Rails и все его зависимости. Это означает, что мы теперь готовы выработать свой новый проект:


	​root@0c286e8bda42:/usr/src/app# ​rails​ ​new​ ​myapp​ ​--skip-test​ ​--skip-bundle
		

Мы воспользовались соответствующим параметром --skip-test чтобы сообщить Rails не применять его установки по умолчанию Minitest. Это обусловлено тем, что в Главе 7 мы применяем RSpec для демонстрации того как настроить свои проверки в среде Docker.

Мы также применяем параметр --skip-bundle. Он сообщает Rails не запускать bundle install после выработки данного проекта. Наш контейнер это всего лишь временное средство передвижения для нас по выработке надлежащего проекта Rails - так как мы намерены избавиться от него, нет накакой необходимости устанавливать все зависимости проекта.

Когда мы запустим свою команду rails new, мы получим приводимый ниже вывод, который отобразит создаваемые файлы нашего проекта Rails, в точности как мы и ожидали:


​ 	​create
​ 	create  README.md
​ 	create  Rakefile
​ 	create  .ruby-version
​ 	create  config.ru
​ 	create  .gitignore
​ 	create  Gemfile
​ 	...
​ 	create  vendor
​ 	create  vendor/.keep
​ 	create  storage
​ 	create  storage/.keep
​ 	create  tmp/storage
​ 	create  tmp/storage/.keep
​ 	remove  config/initializers/cors.rb
​ 	remove  config/initializers/new_framework_defaults_5_2.rb
		

Отлично! Мы можем обнаружить созданными свои файлы Rails. Однако, помните, что мы находимся внутри своего контейнера, а нам необходимо получить эти файлы в своей локальной машине. Как нам это сделать?

Прежде всего прекратим свою оболочку Bash, что остановит наш контейнер:


​ 	​root@0c286e8bda42:/usr/src/app# ​​exit​
		

Это вернёт нас к нашему знакомому приглашению терминала: $

Теперь давайте бросим взгляд вовнутрь своего текущего каталога в своей локальной машине:


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

Уф. Каким- то образом все выработанные внутри нашего контейнера файлы оказались здесь, в нашей локальной файловой системе. Разме контейнер не полностью изолирован? Как это произошло?

Наш ответ состоит в том параметре -v, который мы проигнорировали в своей команде docker run. В стиле речи Docker, это монтирует некий том - действенно разделяет некую часть нашей локальной файловой системы с используемым контейнером. В частности, -v ${PWD}:/usr/src/app просит: "Смонтируйте наш текущий каталог внутри самого контейнера в /usr/src/app" (${PWD} является некоторой переменной среды Unix для указания на значение текущего каталога). Это означает, что все файлы в нашем локальном каталоге будут видимыми в /usr/src/app внутри этого контейнера. Аналогично, если мы создаём, удаляем или изменяем файлы в каталоге этого контейнера, все изменения будут отражаться в нашей локальной файловой системе.

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

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

[Замечание]Только для пользователей Linux: смена владельца файла

Вы обратите внимание, что наши файлы вновь создаваемого проекта Rails находятся во владении root. Это происходит по той причине, что по умолчанию контейнеры исполняются от имени root (UID 1). Для изменения этих файлов вам необходимо сменить их владельца:


​ 	​$ ​​sudo​​ ​​chown​​ ​​:​​ ​​-R​​ ​​myapp/​
		

Вам придётся делать это всякий раз при выработке файлов внутри некого контейнера. За дополнительными подробностями обратитесь к Дополнение A. Владельцы файлов и полномочия.

Наконец, мы возвращаемся к брошенным нами параметрам -i и -t. Для их понимания нам вначале придётся разобраться с архитектурой Docker.

Само сердце Docker - механизм (engine) Docker - является неким приложением клиент- сервер. CLI Docker (а именно, команда docker) это всего лишь некий тонкий клиент, который общается с отдельной программой - самим демоном Docker - относительно того что мы просим. Этот демон отвечает за выполнение самого тяжёлого поднятия в терминах запуска, останова и прочих распоряжений относительно наших контейнеров.

Наш следующий рисунок отображает архитектуру Docker в Linux на верхнем уровне:

 

Рисунок 1-3



Однако Docker строится на основе технологий контейнеризации Linux, которые не доступны естественным путём в Mac и Windows. Docker обходит это устанавливая виртуальную машину Linux с малым весом, которая исполняет необходимого демона Docker. Это приводит к слегка различимой архитектуре Docker for Mac/Windows, что отображено на следующей схеме.

 

Рисунок 1-4



Итак, как нам это поможет пояснит зачем нам требуется применять параметры -i и -t?

Процессы Unix имеют три канала ввода/ вывода: стандартный ввод (stdin), стандартный вывод (stdout) и стандартные ошибки (stderr). Так как наш демон Docker запущен в некотором отдельном процессе, Docker будет вынужден предпринимать некие активные действия для продвижения нашего ввода CLI в сам демон Docker.

По умолчанию, тем не менее, docker run просто передаёт вывод самого контейнера в нашего клиента. Это отлично когда мы желаем запускать некий контейнер, который не требует никакого ввода. Однако, порой мы запускаем процессы, которые требуют ввод. Некий интерактивный сеанс Bash это прекрасный пример этого - он ожидает получения вводимых нами команд. В данном случае нам требуется в явном виде сообщить Docker передавать наш CLI ввод в имеющегося у нас демона Docker. Мы делаем это при помощи своего параметра -i - "i" для input (ввода). Если мы не определим его, наш контейнер немедленно завершится, из- за Bash - отсутствие получения на ввод - влечёт за собой прекращение исполнения.

Однако только этого недостаточно. Некий интерактивный сеанс Bash должен исполняться внутри некого эмулятора терминала, который отвечает за таки е моменты как отображение некого приглашения и интерпретации escape- последовательностей, таких как Ctrl-C. Если мы запускаем некий контейнер для исполнения bash, по умолчанию это исполнение происходит не в интерактивном режиме, выполняя только предоставляемые команды и завершаясь по их исполнению. Для достижения длительного времени жизни, интерактивного сеанса Bash внутри некого контейнера Docker, нам придётся сообщить Docker о необходимости установки для на некого эмулятора терминала (технически, какого- то псевдотерминала, иди pty), который сядет перед Bash. Мы осуществляем это определяя соответствующий параметр -i для docker run.

Теперь, если всё это звучит слишком сложно, просто запомните что всякий раз когда вам требуется длительное время жизни при интерактивном сеансе, вам необходимо определять оба параметра, и -i, и -t. На практике мы обычно соединяем их в сокращённый вид -it, который, как вы могли догадаться, означает "i" -n- "t" -eractive. Круто.

И на этом вся наша работа здесь завершена.

Беглый обзор

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

В этой главе:

  1. Мы установили на своей машине Docker.

  2. Мы запустили свою самую первую команду Docker, некий сценарий Ruby helloworld, причём без необходимости установки Ruby в вашей машине:

    
    ​ 	​$ ​​docker​​ ​​run​​ ​​ruby:2.6​​ ​​ruby​​ ​​-e​​ ​​"puts :hello"​
     	   
  3. Мы увидели как перечислять свои запущенные контейнеры при помощи docker ps, а также все контейнеры (включая остановленные) через docker ps -a.

  4. Мы удалили свои старые контейнеры при помощи docker rm <container id> и увидели как создавать одноразовый контейнер при помощи параметра docker run --rm.

  5. Мы выработали некий новый проект Rails при помощи контейнеров посредством:

    • Запуска некой интерактивной оболочки Bash исполняемым внутри контейнера

      
      ​ 	​$ ​​docker​​ ​​run​​ ​​-i​​ ​​-t​​ ​​--rm​​ ​​-v​​ ​​${PWD}:/usr/src/app​​ ​​ruby:2.6​​ ​​bash​
       	   
    • Установки gem Rails внутри этого контейнера

      
      ​ 	​root@0c286e8bda42:/usr/src/app# ​​gem​​ ​​install​​ ​​rails​
       	   
    • Применения нашего вновь установленного gem Rails для выработки нашего проекта

      
      ​ 	​root@0c286e8bda42:/usr/src/app# ​​rails​​ ​​new​​ ​​myapp​​ ​​--skip-test​​ ​​\​
      ​ 	​                                                ​​--skip-bundle​
       	   

Замечательно. Мы на пути к мастерству в Docker. В следующей главе мы узнаем, как запускать наше новое приложение Rails.