Глава 3. Тонкая настройка нашего образа Rail

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

Однако, как вы могли запомнить, мы сказали что этот Dockerfile был "достаточно хорош", но "не прекрасен" (Определение нашего первого пользовательского образа). Правда состоит в том, что для упрощения мы срезали пару углов. Теперь, когда у вас имеются за поясом некоторые основы Docker, мы можем вернуться к решению этих проблем.

К окончанию данной главы мы будем иметь свой Dockerfile аккуратно заправленным и готовым для нашего глубокого погружения в окончательные фрагменты нашей головоломной разработки - Docker Compose - но я забегаю вперёд.

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

Именование и сопровождение версий для нашего образа

Когда мы запускали свой сервер Rails при помощи этой команды:


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

мы ссылались на свой персональный образ по его идентификатору, a1df0eddba18 (у вас должно быть нечто иное). Не существует способа как это запомнить. В точности как вы не будете в здравом уме ссылаться на некую ветвь Git, применяющую SHA-1 хэширование своей последней фиксации, то же самое справедливо и для образов. Вместо этого мы даём своим образам дружественные людям названия, выполняя их маркировку (tagging). Допустим, мы желаем назвать свой образ railsapp. Мы можем сделать это при помощи:


​ 	​$ ​​docker​​ ​​tag​​ ​​a1df0eddba18​​ ​​railsapp​
		

что инструктирует, "Пометьте мой образ с идентификатором 'a1df0eddba18' как эrailsapp'". Чтобы проверить что это сработало, давайте перечислим свои образы с помощью:


​​ 	​$ ​​docker​​ ​​images​
		

Наш вывод подтверждает, что имеющееся название этого образа (также именуемое как репозиторий - repository) было установлено в railsapp:


​ 	REPOSITORY      TAG         IMAGE ID          CREATED            SIZE
​ 	railsapp        latest      a1df0eddba18      8 minutes ago      1.01GB
​ 	...
		

Обратите внимание, что поле "tag" перечисляется как latest. Это происходит по той причине, что команда docker tag на самом деле по некоторой ссылке образа (image reference), которая составляется из двух частей: самого названия (repo) образа и не обязательного тега:


​ 	<image_name>[:<tag>] 
		

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

К несчатью понятие tag слишком перегружено. Я предлагаю представлять сеье команду docker tag как помечающую (tagging) сразу обоими, как названием образа/ репозитория (в нашем случае railsapp), так и неким тегом (в нашем случае значением по умолчанию, latest).

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


​ 	​$ ​​docker​​ ​​tag​​ ​​railsapp​​ ​​railsapp:1.0​
		

Здесь мы ссылаемся на свой образ как railsapp, так как мы уже пометили его этим названием (railsapp:latest также сработало бы). Наш новый тег railsapp:1.0 создаёт соответствующее название образа railsapp и версию 1.0. Быстрое перечисление показывает как это работает:


​ 	​$ ​​docker​​ ​​images​
​ 	REPOSITORY      TAG         IMAGE ID          CREATED            SIZE
​ 	railsapp        1.0         a1df0eddba18      8 minutes ago      1.01GB
​ 	railsapp        latest      a1df0eddba18      8 minutes ago      1.01GB
​ 	...
		

Хотя имеются две различные строки, которые отображают наш образ railsapp и как latest, и как 1.0, основное поле "IMAGE ID" подтверждает что оба они по сути один и тот же образ.

Вместо того чтобы помечать образы после их построения, мы можем осуществлять это при самом построении такого образа при помощи параметра -t. Множество пометок может быть предоставлено определением дополнительных параметров -t, тем самым мы могли бы достичь того же самого результата что и две наши предыдущие команды docker tag если бы построили свой образ при помощи команды:


​​ 	​$ ​​docker​​ ​​build​​ ​​-t​​ ​​railsapp​​ ​​-t​​ ​​railsapp:1.0​​ ​​.​
		

Получив название своего образа мы теперь способны запускать свой сервере Rails применяя название образа подобно такому:


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

Ах. Намного лучше.

Запуск конкретной версии некого образа в точности тот что мы и ожидали увидеть: при помощи той же самой нотации с двоеточием, которую мы применяли ранее. Например, для определения в явном виде номера версии 1.0 для нашего образа мы бы осуществили запуск:


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

Команда по умолчанию

На данный момент, всякий раз когда мы желаемм запустить некий сервер Rails в контейнере, нам приходится в явном виде определять соответствующую команду bin/rails s -b 0.0.0.0 как часть своей команды docker run:


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

Это полная стыдоба, потому как самая основная цель нашего персонального образа состоит в том чтобы запускать некий сервер Rails. Было бы намного лучше если бы мы могли встроить своё знание того как запускать конкретный сервер Rails в сам имеющийся образ.

Мы можем сделать это добавляя некую новую инструкцию в свойDockerfile. Именно инструкция CMD, произносимая как "команда", определяет ту команду по умолчанию, которую надлежит исполнить при запуске некого контейнера из данного образа. Давайте воспользуемся ею в своём 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
​ 	
»	​CMD​​ ["bin/rails", "s", "-b", "0.0.0.0"]​
		

рассматривая эту вновь добавленную строку вы можете обратить внимание на наличие странной нотации массива в определении данной команды. Эта форма - именуемая как исполнительная (Exec) форма - необходима для того, чтобы наш сервер Rails запускался как самый первый процесс в этом контейнере (PID 1) и правильно получал бы сигналы Unix, такие как сигнал прекращения. Это рекомендуемая и наиболее часто применяемая форма.

Другая форма данной инструкции CMD, которая применяется реже, опускает такую нотацию массива в пользу непосредственной записи данной команды:


​ 	​CMD​​ bin/rails s -b 0.0.0.0​
		

Она именуется формой оболочки (shell), потому что Docker исполняет такую команду через некую оболочку команд, с префиксом /bin/sh -c - поэтому в нашем случае он запустит bin/rails s -b 0.0.0.0. Основная проблема состоит в том, что именно /bin/sh -c вместо самого сервера Rails является самым первым процессом внутри данного контейнера; следовательно /bin/sh -c не передаст сигналы в свой подпроцесс, а это может вызывать проблемы при попытке прекращения данного сервера. Говоря в целом, вам следует вовсе избегать такой формы запуска.

Ладно, давайте построим свой образ с помощью новой инструкции CMD, помня про пометку своей версии railsapp в качестве latest:


​ 	​$ ​​docker​​ ​​build​​ ​​-t​​ ​​railsapp​​ ​​.​
​ 	Sending build context to Docker daemon  138.8kB
​ 	...
​ 	Successfully built f87ad761cd0f
​ 	Successfully tagged railsapp:latest
		

При помощи своего вновь построенного образа мы способны запустить этот новый сервер Rails - опуская bin/rails s -b 0.0.0.0 - просто как:


​ 	​$ ​​docker​​ ​​run​​ ​​-p​​ ​​3000:3000​​ ​​railsapp​
		

Важно отметить, что наша инструкция CMD просто предоставляется как некая команда по умолчанию - вы можете определить некую другую при вызове своей команды docker run. Например, чтобы перечислить свои задачи Rake, мы бы выполнили:


​ 	​$ ​​docker​​ ​​run​​ ​​--rm​​ ​​railsapp​​ ​​bin/rails​​ ​​-T​
		

Обратите внимание, что мы применяем --rm для удаления этого контейнера после его исполнения. Мы применяем это здесь, а не когда запускаем сам сервер Rails, в то время как контейнер для запуска сервера Rails можно применять повторно.

Игнорирование ненужных файлов

Вы должно быть помните, что имеется различие в CLI Docker, который мы применяем для запуска команд, а также что имеющийся демон Docker выполняет основную часть реальной работы (как мы это уже видели в своих схемах архитектуры для Схемы архитектуры Dockere для Linux и Схемы архитектуры Dockere для Mac/Windows. Построение образа ничем не отличается - именно наш демон Docker в реальности выполняет построение необходимого образа.

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

Когда запускается команда docker build, имеющийся инструментарий CLI получает все те файлы, которые определены в нашем каталоге сборки - которые все вместе именуются контекстом сборки (build context) - и отправляет их своему демону Docker. Затем этот демон имеет возможность обработать соответствующий Dockerfile и позаботиться обо всех присутствующих там инструкциях для выработки необходимого образа.

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

Для исключения файлов и каталогов от их отправки в качестве части такого контекста сборки, мы перечисляем их в неком файле .dockerignore в своём каталоге сборки. Этот файл .dockerignore работает на той же основе, что и файл .gitignore, с которым вы скорее всего знакомы, хотя сам синтаксис их шаблонов соответствия слегка разнится.

Давайте создадим некий базовый файл .dockerignore для своего проекта:


​1: 	​# Git​
​- 	.git            
​- 	.gitignore      
​- 	
​5: 	​# Logs​
​- 	log/*           
​- 	
​- 	​# Temp files​
​- 	tmp/*           
​10: 	
​- 	​# Editor temp files​
​- 	*.swp           
​- 	*.swo
		

Мы исключаем свой каталог .git (строка 2), который содержит всю историю Git и все настройки, так как наш образ нуждается только в самых последних версиях этих файлов. За исключением одной мелочи, пока мы этим занимаемся, мы также исключили сам файл .gitignore (строка 3).

Аналогично мы иключаем все журналы (строка 6) или временные файлы (9), поскольку они вырабатываются и с целью безопасности их можно игнорировать. Наконец, я исключаю временные файлы Vim .swp и .swo (строки 12 и 13) - не стесняйте себя делать то же самое и для своего любимого редактора.

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

Давайте повторно построим свой образ вооружившись данным файлом .dockerignore.


​ 	​$ ​​docker​​ ​​build​​ ​​-t​​ ​​railsapp​​ ​​.​
​ 	Sending build context to Docker daemon  102.9kB
​ 	...
​ 	Successfully built 577a1a5a2d2c
​ 	Successfully tagged railsapp:latest
		

В полученном выводе мы получаем отчёт о размере контекста сборки - 102.9 кБ - что меньще чем ранее до добавления вашего .dockerignore Docker (138.8 кБ). Такое сбережение увеличивается со временем, в особенности по мере роста истории Git.

Кэширование собранного образа

В течении разработки мы достточно часто перестраиваем свой образ, либо устанавливая новые gem (bundle install является одним из необходимых этапов нашего Dockerfile), либо обновляя свои зависимости, такие как Node.js.

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

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

Давайте пересоберём свой образ прямо сейчас:


​ 	​$ ​​docker​​ ​​build​​ ​​-t​​ ​​railsapp​​ ​​.​
​ 	Sending build context to Docker daemon  102.9kB
​ 	Step 1/7 : FROM ruby:2.6
​ 	​ --->​​ ​​f28a9e1d0449​
​ 	Step 2/7 : RUN apt-get update -yqq
​ 	​ --->​​ ​​Using​​ ​​cache​
​ 	​ --->​​ ​​761da319d69a​
​ 	Step 3/7 : RUN apt-get install -yqq --no-install-recommends nodejs
​ 	​ --->​​ ​​Using​​ ​​cache​
​ 	​ --->​​ ​​145b025f550c​
​ 	Step 4/7 : COPY . /usr/src/app/
​ 	​ --->​​ ​​Using​​ ​​cache​
​ 	​ --->​​ ​​045a92afdc82​
​ 	Step 5/7 : WORKDIR /usr/src/app
​ 	​ --->​​ ​​Using​​ ​​cache​
​ 	​ --->​​ ​​1d89cb7f0720​
​ 	Step 6/7 : RUN bundle install
​ 	​ --->​​ ​​Using​​ ​​cache​
​ 	​ --->​​ ​​81ad2d531548​
​ 	Step 7/7 : CMD ["bin/rails", "s", "-b", "0.0.0.0"]
​ 	​ --->​​ ​​Using​​ ​​cache​
​ 	​ --->​​ ​​577a1a5a2d2c​
​ 	Successfully built 577a1a5a2d2c
​ 	Successfully tagged railsapp:latest
		

Этот образ должен быть собран очень быстро. Если вы взглянете на полученный вывод, вы увидите, что все шаги (отличающиеся от инструкции FROM) в точности сообщают Using cache. Это указывает на то, что Docker не требуется создавать некий новый образ для каждого шага: он просто повторно применяет некий кэшированный с предыдущей сборки промежуточный образ.

Значение кэширвание для некого определённого шага является недействительным при изменении вами инструкции своего Dockerfile на нечто, что не было построено ранее. Кроме того, инструкции COPY могут иметь свои несоответствия кэширования если копируемые файлы были изменены с момента соответствующего этапа последнего построения. Такое сопоставление выполняется имеющимся демоном Docker на основе файлов в его контексте сборки - следовательно игнорирование ненужных файлов из вашего файла .dockerignore также может предотвращать недействительность кэширования.

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

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

На самом деле у нашего Dockerfile уже имеется небольшая проблема с тем как работает само кэширование...

Проблема кэширования 1: Обновление пакетов

На данный момент наш Dockerfile имеет две такие строки:


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

Хотя это и работает, имеется некая скрытая проблем отслеживания. Допустим, мы переходим к некому более позднему этапу и понимаем что нам требуется установить некий дополнительный пакет - к примеру, редактор Vim. Мы добавляем соответствующий пакет vim в свою инструкцию apt-get update RUN, разрушая имеющийся кэш и приводя к возврату в правке этой инструкции:


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

Однако сама инструкция apt-get update для RUN остаётся без изменений и будут использованы все имеющиеся подробности кэшированного репозитория. Вместо того чтобы получить текущую, самую последнюю версию своего нового только что добавленного пакета, мы полоучим нечто, что было самой последней версией на тот момент, когда мы строили свой образ в последний раз. Такое поведение почти никогда не является тем что нам требуется.

По этой причине рекомендуется всегда сочетать ваши команды apt-get update и apt-get install в единую инструкцию RUN подобную следующей:


​ 	​RUN ​apt-get update -yqq && ​\​
​ 	  apt-get install -yqq --no-install-recommends nodejs vim
		

Это обеспечит гарантию того что при всяком изменении устанавливаемых вами пакетов вы также получаете в то же самое время и самую последнюю информацию информацию о репозитории.

Наконец, хорошим практическим приёмом является форматировать вашу команду apt-get install следующим образом:


​ 	​RUN ​apt-get update -yqq && apt-get install -yqq --no-install-recommends ​\​
​ 	  nodejs ​\​
​ 	  vim
		

Применение одного пакета в строке и соблюдение алфавитного порядка делает более простым просмотр устанавливаемых пакетов и выявления того какие из них следует изменять если приходится устанавливать множество пакетов.

Давайте теперь исправим эту проблему в своём Dockerfile. В настоящий момент нам не требуется устанавливать Vim, следовательно наши две инструкции RUN для apt-get update и apt-get install превращаются в:


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

Давайте повторно соберём свой образ чтобы включить это изменение:


​​ 	​$ ​​docker​​ ​​build​​ ​​-t​​ ​​railsapp​​ ​​.​
​ 	Sending build context to Docker daemon  102.9kB
​ 	Step 1/6 : FROM ruby:2.6
​ 	​ --->​​ ​​f28a9e1d0449​
​ 	...
​ 	Successfully built 621ceaca3298
​ 	Successfully tagged railsapp:latest
		

Проблема кэширования 2: Ненужные установки Gem

Допустим, что мы пожелаем внести изменения в свой файл README.md и замените имеющееся установленное по умолчанию определение версии Rails на следующее:


​ 	# README
​ 	
​ 	This is a sample Rails application from Docker for Rails Developers (PragProg).
​ 	It was generated using Docker without Ruby installed on the local machine.
​ 	
​ 	We're using the app to discover the wonderful world of Rails with Docker.

 	Это образец приложения Rails из книги Docker for Rails Developers (PragProg).
 	Он был сгенерирован при помощи Docker без установленного в вашей локальной машине Ruby.

 	Мы применяем данное прикладное приложение для исследования прекрасного мира Rails с использованием Docker.
		

Давайте попробуем что- нибудь. Что произойдёт если мы повторно соберём свой образ:


​ 	​$ ​​docker​​ ​​build​​ ​​-t​​ ​​railsapp​​ ​​.​
​ 	Sending build context to Docker daemon  102.9kB
​ 	Step 1/6 : FROM ruby:2.6
​ 	​ --->​​ ​​f28a9e1d0449​
​ 	Step 2/6 : RUN apt-get update -yqq && apt-get install -yqq --no-install-
​ 	recommends   nodejs
​ 	​ --->​​ ​​Using​​ ​​cache​
​ 	​ --->​​ ​​29c3dee2b8c7​
​ 	Step 3/6 : COPY . /usr/src/app/
​ 	​ --->​​ ​​fff98079f6ac​
​ 	Step 4/6 : WORKDIR /usr/src/app
​ 	​ --->​​ ​​Running​​ ​​in​​ ​​3e36b19fecbf​
​ 	Removing intermediate container 3e36b19fecbf
​ 	​ --->​​ ​​34e46dae43ab​
​ 	Step 5/6 : RUN bundle install
​ 	​ --->​​ ​​Running​​ ​​in​​ ​​f4528be7eb2b​
​ 	...
​ 	Bundle complete! 15 Gemfile dependencies, 68 gems now installed.
​ 	Bundled gems are installed into `/usr/local/bundle`
​ 	...
​ 	Removing intermediate container f4528be7eb2b
​ 	​ --->​​ ​​5965a3004093​
​ 	Step 6/6 : CMD ["bin/rails", "s", "-b", "0.0.0.0"]
​ 	​ --->​​ ​​Running​​ ​​in​​ ​​fe59ed9392a7​
​ 	Removing intermediate container fe59ed9392a7
​ 	​ --->​​ ​​1fbb2af53579​
​ 	Successfully built 1fbb2af53579
​ 	Successfully tagged railsapp:latest
		

Ух ты, это потребовало много времени. Основной причиной этой медленности было то, что все наши gems собирались с нуля, и всё это произошло потому, что мы изменили свой файл README.md - что произошло?

Если вы рассмотрите полученный вывод, вы обнаружите, что все шаги с 1 по 3 сообщили Using cache. Docker не приходится собирать по новой эти уровни, поскольку он сопоставил инструкции вашего Dockerfile с кэшированными промежуточными уровнями для этих шагов и увидел их неизменность.

Однако это не так для шага 4 (COPY . /usr/src/app) - этот шаг не использует имеющийся кэш. Хотя сама инструкция Dockerfile осталось той же самой, поскольку данная инструкция является инструкцией COPY, Docker выполняет проверку всех подлежащих копированию файлов (за исключением файлов .dockerignore) и сравнивает их с теми, что копировались ранее. Он понимает, что README.md был изменён, следовательно он знает о необходимости повторной сборки на данном шаге.

Нельзя обойти вниманием тот факт, что если файлы изменены, необходимо создать некий новый образ, содержащий эти файлы. Однако в данном случае, к сожалению, наше изменение README.md вызывает повторное исполнение bundle install. Это одновременно и длительно, и совершенно не является необходимым: наше изменение README.md не оказало воздействия на зависимости имеющихся gem. Единственная причина, по которой он запускается состоит в том, был изменён предыдущий шаг в нашем Dockerfile.

Давайте посмотрим имеется ли у нас нечто для решения этой задачи.

  Трюк с кэшированием Gemfile

Оказывается, существует некий действенный способ предотвращения изменений в несвязанных файлах, разрушающих наш кэш и приводящих к повторной сборке всех наших gems с нуля. Фокус состоит в том, чтобы обособленно копировать файлы, которые приводят к необходимости повторной сборки наших gem от тех, которые не вызывают этого.

Давайте обновим свой Dockerfile чтобы он делал это:


​1: 	FROM ruby:2.6
​- 	
​- 	RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \
​- 	  nodejs
​5: 	
​- 	COPY Gemfile* /usr/src/app/   
​- 	WORKDIR /usr/src/app
​- 	RUN bundle install            
​- 	
​10: 	COPY . /usr/src/app/
​- 	
​- 	CMD [​"bin/rails"​, ​"s"​, ​"-b"​, ​"0.0.0.0"​]
 	   

Первые три инструкции остаются прежними, однако строка 6 новая. Она копирует наши Gemfile и Gemfile.lock в наш образ до всего остального нашего кода.


​​ 	COPY Gemfile* /usr/src/app/
 	   

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

Имея теперь скопированными в наш образ свои Gemfile и Gemfile.lock, мы теперь можем изменить тот каталог, в которм мы находимся и где мы устанавливаем свои gems:


​ 	​WORKDIR​​ /usr/src/app​
​ 	​RUN ​bundle install
 	   

Наконец, имея установленными свои gems, мы можем скопировать все оставшиеся исходные файлы в этот образ:


​ 	​COPY​​ . /usr/src/app/​
 	   

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

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


​ 	​$ ​​docker​​ ​​build​​ ​​-t​​ ​​railsapp​​ ​​.​
 	   

Имея свой вновь собранный образ, давайте посмотрим что произойдёт когда мы изменим свой файл README.md снова. Поойдите далее и внесите какие- нибудь незначительные исправления в README.md, сохраните эти изменения и заново соберите свой образ:


​ 	​$ ​​docker​​ ​​build​​ ​​-t​​ ​​railsapp​​ ​​.​
​ 	Sending build context to Docker daemon  102.9kB
​ 	Step 1/7 : FROM ruby:2.6
​ 	​ --->​​ ​​f28a9e1d0449​
​ 	Step 2/7 : RUN apt-get update -yqq && apt-get install -yqq --no-install-
​ 	recommends   nodejs
​ 	​ --->​​ ​​Using​​ ​​cache​
​ 	​ --->​​ ​​29c3dee2b8c7​
​ 	Step 3/7 : COPY Gemfile* /usr/src/app/
​ 	​ --->​​ ​​Using​​ ​​cache​
​ 	​ --->​​ ​​050a87002be1​
​ 	Step 4/7 : WORKDIR /usr/src/app
​ 	​ --->​​ ​​Using​​ ​​cache​
​ 	​ --->​​ ​​d227daeedb1e​
​ 	Step 5/7 : RUN bundle install
​ 	​ --->​​ ​​Using​​ ​​cache​
​ 	​ --->​​ ​​616b88058c4b​
​ 	Step 6/7 : COPY . /usr/src/app/
​ 	​ --->​​ ​​b189758b9ded​
​ 	Step 7/7 : CMD ["bin/rails", "s", "-b", "0.0.0.0"]
​ 	​ --->​​ ​​Running​​ ​​in​​ ​​fad4be04ab20​
​ 	Removing intermediate container fad4be04ab20
​ 	​ --->​​ ​​9be0cf184e64​
​ 	Successfully built 9be0cf184e64
​ 	Successfully tagged railsapp:latest
		

Это было намного быстрее чем когда мы меняли свой README.md раньше. На этот раз это не привело к повторному построению наших gem. Строки с 6 по 7 в нашем Dockerfile могут применять имеющийся кэш потому что в них ничего не изменено. Docker всего лишь повторно собирает окончательные два этапа, причём оба они быстрые.

Последний штрих

Наш Dockerfile неотразим, не так ли? Шедевр. Это маленькое дитя станет толчком к тому что наша разработка приложения Rails на самом деле состоится. Итак, давайте сделаем то, что делают все настоящие творцы и подпишем свою работу.

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

Мы установим некую метку при помощи соответствующей инструкции LABEL, которая обладает таким форматом:


​ 	​LABEL​​ <key​>=<value​>
 	   

Она снабжает наш образ некой меткой с названием key установленной в value.

Для указания того кто отвечает за сопровождение данного файла мы изменим свой Dockerfile чтобы определить его адрес электронной почты подобно следующему (вы можете подставить адрес своей электронной почты вместо моего):


​ 	FROM ruby:2.6
​ 	
»	LABEL maintainer=​"rob@DockerForRailsDevelopers.com"​
​ 	
​ 	RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \
​ 	  nodejs
​ 	
​ 	COPY Gemfile* /usr/src/app/  
​ 	WORKDIR /usr/src/app
​ 	RUN bundle install           
​ 	
​ 	COPY . /usr/src/app/
​ 	
​ 	CMD [​"bin/rails"​, ​"s"​, ​"-b"​, ​"0.0.0.0"​]
 	   

И на этом наш Dockerfile завершён.

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


​ 	​LABEL​​ <key​>=<value​>​​ <key​>=<value​>​​ <key​>=<value​> ...
 	   

Выбор за вами.

Прежде чем завершить, не забудьте окончательно собрать свой образ с этим изменением:


​ 	​$ ​​docker​​ ​​build​​ ​​-t​​ ​​railsapp​​ ​​.​
		

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

Беглый обзор

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

Давайте повторим что мы изучили:

  1. Мы рассмотрели как именовать и поддерживать версии своего образа задавая в нём теги либо после его построения:

    
    ​ 	​$ ​​docker​​ ​​tag​​ ​​a1df0eddba18​​ ​​railsapp​
     	   

    либо в момент сборки (здесь устанавливаются два тега):

    
    ​ 	​$ ​​docker​​ ​​build​​ ​​-t​​ ​​railsapp​​ ​​-t​​ ​​railsapp:1.0​​ ​​.​
     	   
  2. Мы добавили некую команду по умолчанию в свой образ при помощи инструкции CMD:

    
    ​ 	​CMD​​ ["bin/rails", "s", "-b", "0.0.0.0"]​
     	   
  3. Мы ускорили построение своего образа воспользовавшись .dockerignore для предотвращения отправки в демон Docker ненужных файлов в качестве контекста нашей сборки.

  4. Мы обеспечили гарантию того что мы всегда применяем современную информацию репозитория пакетов при изменении тех пакетов, которые мы устанавливаем скомбинировав apt-get update и apt-get install в единую инструкцию RUN:

    
    ​ 	RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \
    ​ 	  nodejs
     	   
  5. Мы предотвратили то, что изменения в файлах вызывают повторное построение наших gems скопировав Gemfiles раньше в своём Dockerfile с тем, чтобы они могли кэшироваться отдельно:

    
    ​ 	COPY Gemfile* /usr/src/app/   
    ​ 	WORKDIR /usr/src/app
    ​ 	RUN bundle install
     	   
  6. Наконец, мы указали кто отвечает за наш образ, установив значение maintainer в соответствующей инструкции LABEL:

    
    ​ 	​LABEL​​ maintainer="rob@DockerForRailsDevelopers.com"​
     	   

Неплохо для дня работы.

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