Лабораторная работа №7 «Создание Doker Image» Цель работы: получение базовых навыков по работе с Docker. Теоретическая часть: Контейнеры Docker получают из образов Docker. По умолчанию Docker загружает эти образы из Docker Hub, реестр Docker, контролируемые Docker, т.е. компанией, реализующей проект Docker. Любой может размещать свои образы Docker на Docker Hub, поэтому большинство приложений и дистрибутивов Linux, которые вам потребуется, хранят там свои образы. Чтобы проверить, можно ли получить доступ к образам из Docker Hub и загрузить их, введите следующую команду: sudo docker run hello-world Данный вывод говорит о том, что Docker работает корректно: Docker первоначально не смог найти локальный образ hello-world, поэтому он загрузил образ из Docker Hub, который является репозиторием по умолчанию. После того как образ был загружен, Docker создал контейнер из образа, а приложение внутри контейнера было исполнено, отобразив сообщение. Вы можете выполнять поиск доступных на Docker Hub с помощью команды docker с субкомандой search. Например, чтобы найти образ Ubuntu, введите: sudo docker search ubuntu Скрипт пробежится по Docker Hub и вернет список всех образов с именами, совпадающими со строкой запроса. В данном случае вывод будет выглядеть примерно следующим образом: В столбце OFFICIAL OK указывает на образ, созданный и поддерживаемый компанией, реализующей проект. После того как вы определили образ, который хотели бы использовать, вы можете загрузить его на свой компьютер с помощью субкоманды pull. Запустите следующую команду, чтобы загрузить официальный образ ubuntu на свой компьютер: sudo docker pull ubuntu Вывод должен выглядеть следующим образом: После того как образ будет загружен, вы сможете запустить контейнер с помощью загруженного образа с помощью субкоманды run. Как вы уже видели на примере hello-world, если образ не был загружен, когда docker выполняется с субкомандой run, клиент Docker сначала загружает образ, а затем запускает контейнер с этим образом. Чтобы просмотреть образы, которые были загружены на ваш компьютер, введите: sudo docker images Вывод команды должен выглядеть примерно следующим образом: Как вы увидите далее в этом обучающем руководстве, образы, которые вы используете для запуска контейнеров, можно изменить и использовать для создания новых образов, которые затем могут быть загружены (помещены) на Docker Hub или другие реестры Docker. Давайте более подробно рассмотрим, как запускаются контейнеры. Контейнер hello-world, который вы запустили на предыдущем шаге, служит примером контейнера, который запускается и завершает работу после отправки тестового сообщения. Контейнеры могут быть гораздо более полезными, чем в примере выше, а также могут быть интерактивными. В конечном счете они очень похожи на виртуальные машины, но более бережно расходуют ресурсы. В качестве примера мы запустим контейнер с самым последним образом образ Ubuntu. Сочетание переключателей -i и -t предоставляет вам доступ к интерактивной командной оболочке внутри контейнера: sudo docker run -it ubuntu Необходимо изменить приглашение к вводу команды, чтобы отразить тот факт, что вы работаете внутри контейнера, и должны иметь следующую форму: Обратите внимание на идентификатор контейнер в запросе команды. В данном примере это 1f322bb26657. Вам потребуется этот идентификатор для определения контейнера, когда вы захотите его удалить. Теперь вы можете запустить любую команду внутри контейнера. Например, сейчас мы обновим базу данных пакетов внутри контейнера. Вам не потребуется начинать любую команду с sudo, потому что вы работаете внутри контейнера как root-пользователь: apt update После этого вы можете установите любое приложение внутри контейнера. Давайте установим Node.js: apt install nodejs Эта команда устанавливает Node.js внутри контейнера из официального репозитория Ubuntu. После завершения установки проверьте, что Node.js был установлен успешно: node -v Любые изменения, которые вы вносите внутри контейнера, применяются только к контейнеру. Чтобы выйти из контейнера, введите exit. Далее мы рассмотрим управление контейнерами в нашей системе. После использования Docker в течение определенного времени, у вас будет много активных (запущенных) и неактивных контейнеров на компьютере. Чтобы просмотреть активные, используйте следующую команду: sudo docker ps Вывод будет выглядеть примерно следующим образом: Вы запустили два контейнера: один из образа hello-world и другой из образа ubuntu. Оба контейнера больше не запущены, но все еще существуют в вашей системе. Чтобы просмотреть все контейнеры — активные воспользуйтесь командой docker ps с переключателем -a: sudo docker ps -a Вывод будет выглядеть следующим образом: и неактивные, Чтобы просмотреть последний созданный вами контейнер, передайте переключатель -l: sudo docker ps -l Чтобы запустить остановленный контейнер, воспользуйтесь docker start с идентификатором контейнера или именем контейнера. Давайте запустим контейнер на базе Ubuntu с идентификатором 1f322bb26657: sudo docker start 1f322bb26657 Контейнер будет запущен, а вы сможете использовать docker ps, чтобы просматривать его статус: Чтобы остановить запущенный контейнер, используйте docker stop с идентификатором или именем контейнера. На этот раз мы будем использовать имя, которое Docker присвоил контейнеру, т.е. beautiful_thompson: sudo docker stop beautiful_thompson После того как вы решили, что вам больше не потребуется контейнер, удалите его с помощью команды docker rm, снова добавив идентификатор контейнера или его имя. Используйте команду docker ps -a, чтобы найти идентификатор или имя контейнера, связанного с образом hello-world, и удалить его. sudo docker rm lucid_margulis Вы можете запустить новый контейнер и присвоить ему имя с помощью переключателя --name. Вы также можете использовать переключатель --rm, чтобы создать контейнер, который удаляется после остановки. Изучите команду docker run help, чтобы получить больше информации об этих и прочих опциях. Контейнеры можно превратить в образы, которые вы можете использовать для создания новых контейнеров. Давайте посмотрим, как это работает. После запуска образа Docker вы можете создавать, изменять и удалять файлы так же, как и с помощью виртуальной машины. Эти изменения будут применяться только к данному контейнеру. Вы можете запускать и останавливать его, но после того как вы уничтожите его с помощью команды docker rm, изменения будут утрачены навсегда. Данный раздел показывает, как сохранить состояние контейнера в виде нового образа Docker. После установки Node.js внутри контейнера Ubuntu у вас появился контейнер, запускающий образ, но этот контейнер отличается от образа, который вы использовали для его создания. Но позже вам может снова потребоваться этот контейнер Node.js в качестве основы для новых образов. Затем внесите изменения в новый экземпляр образа Docker с помощью следующей команды. sudo docker commit -m "What you did to the image" -a "Author Name" container_id repository/new_image_name Переключатель -m используется в качестве сообщения о внесении изменений, которое помогает вам и остальным узнать, какие изменения вы внесли, в то время как -a используется для указания автора. container_id — это тот самый идентификатор, который вы отмечали ранее в этом руководстве, когда запускали интерактивную сессию Docker. Если вы не создавали дополнительные репозитории на Docker Hub, repository, как правило, является вашим именем пользователя на Docker Hub. Например, для пользователя sammy с идентификатором контейнера 1f322bb26657 команда будет выглядеть следующим образом: Когда вы вносите образ, новый образ сохраняется локально на компьютере. Позже в этом обучающем руководстве вы узнаете, как добавить образ в реестр Docker, например, на Docker Hub, чтобы другие могли получить к нему доступ. Список образов Docker теперь будет содержать новый образ, а также старый образ, из которого он будет получен: sudo docker images В данном примере ubuntu-nodejs является новым образом, который был получен из образа ubuntu на Docker Hub. Разница в размере отражает внесенные изменения. В данном примере изменение состояло в том, что NodeJS был установлен. В следующий раз, когда вам потребуется запустить контейнер, использующий Ubuntu с предустановленным NodeJS, вы сможете использовать новый образ. Вы также можете создавать образы из Dockerfile, что позволяет автоматизировать установку программного обеспечения в новом образе. Создаём файл Dockerfile sudo nano Dockerfile С содержимым: FROM ubuntu:latest RUN apt-get -y update && apt-get install -y fortunes CMD /usr/games/fortune -a | cowsay После создания файла Dockerfile выполняем команду docker build -t docker-whale Образ создан, теперь мы можем его использовать. Самостоятельная работа: Собрать свой собственный образ с ОС Alpine Linux и его развернуть в контейнере. В созданном контейнере выполнить простейшие команды: создание директории, файла, изменения файла и так далее. Дополнительная информация: FROM — задаёт родительский (главный) образ; LABEL — добавляет метаданные для образа. Хорошее место для размещения информации об авторе; ENV — создаёт переменную окружения; RUN — запускает команды, создаёт слой образа. Используется для установки пакетов и библиотек внутри контейнера; COPY — копирует файлы и директории в контейнер; ADD — делает всё то же, что и инструкция COPY. Но ещё может распаковывать локальные .tar файлы; CMD — указывает команду и аргументы для выполнения внутри контейнера. Параметры могут быть переопределены. Использоваться может только одна инструкция CMD; WORKDIR — устанавливает рабочую директорию для инструкции CMD и ENTRYPOINT; ARG — определяет переменную для передачи Docker’у во время сборки; ENTRYPOINT — предоставляет команды и аргументы для выполняющегося контейнера. Суть его несколько отличается от CMD, о чём мы поговорим ниже; EXPOSE — открывает порт; VOLUME — создаёт точку подключения директории для добавления и хранения постоянных данных. FROM Docker-файл должен начинаться с инструкции FROM или ARG, за которой следует FROM. Команда FROM говорит Docker’у использовать базовый образ, который соответствует репозиторию и тегу. В этом примере хранилище образов — Ubuntu. Ubuntu — название официального Docker-репозитория, в котором и содержится данная ОС. Заметьте, что этот Docker-файл содержит тег для базового образа: 18.04, который указывает Docker’у, какую именно версию образа нужно использовать. Если тег не указан, по умолчанию берётся последняя версия образа. Но лучше всё же указывать тег базового образа. Когда Docker-файл, приведённый выше, используется для создания локального Docker-образа впервые, он загружает слои, указанные в образе Ubuntu. При создании Docker-контейнера, вы помещаете наверх слой, который впоследствии можно будет изменить. LABEL Следующая инструкция — LABEL. LABEL добавляет метаданные к образу, предоставляет контактную информацию. Она не замедляет процесс запуска и не занимает много места, наоборот, обеспечивает образ полезной информацией, так что обязательно используйте её. Больше про LABEL читайте здесь. ENV ENV создаёт переменную окружения, которая становится доступной во время запуска контейнера. В примере выше вы могли видеть использование переменной ADMIN при создании контейнера. ENV удобна для обозначения констант. Если константа используется в нескольких местах файла Dockerfile, и вам понадобится изменить её значение позднее, это можно будет сделать в одном месте. Docker-файл зачастую предоставляет несколько путей решения одной задачи. Будет хорошо, если в вашем решении будет учитываться баланс Docker- соглашений, прозрачность и скорость. К примеру, RUN, CMD и ENTRYPOINT служат различным целям и могут использоваться для выполнения команд. RUN RUN создаёт слой во время запуска. Docker фиксирует состояние образа после каждой инструкции RUN. Чаще всего используется для установки нужных пакетов внутрь контейнера. В примере выше RUN apk update && apk upgrade говорит Docker’у обновить пакеты из базового образа. && apk add bash указывает на то, что для базового образа нужно установить bash. apk — это сокращение от Alpine Linux package manager. Если вы используете базовый образ не Alpine Linux, то установка пакетов производится командой RUN apt-get. RUN и её родственные инструкции: CMD, ENTRYPOINT — могут быть как форме оболочки, так и в форме shell-скрипта. Во втором случае используют JSON-синтаксис: RUN ["my_executable", "my_first_param1", "my_second_param2"]. А в примере выше использовалась форма оболочки: RUN apk update && apk upgrade && apk add bash. Позднее в вашем Docker-файле вы будете создавать новую директорию, используя ["mkdir", "/a_directory"]. Не забывайте, что в JSON нужно использовать двойные кавычки! COPY Инструкция COPY . ./app говорит Docker’у, что нужно скопировать файлы и папки из вашей локальной сборки в рабочую директорию образа. COPY создаст все нужные папки, если они отсутствуют. ADD ADD делает то же самое, что и COPY, но с двумя отличиями. ADD может загружать файлы по URL, а также извлекать локальные TAR-файлы. В примере выше ADD копировала файлы по URL внутрь директории контейнера. Но официальныя документация не рекомендует использовать ADD так, потому что потом вы попросту не сможете удалить файлы. А дополнительные файлы увеличивают размер образа. Ещё официальная документация для ясности рекомендует использовать, когда это возможно, COPY вместе ADD. Жаль только, что в Docker’е невозможно использовать ADD и COPY в одной команде. Заметьте, инструкция содержит символ \. Это нужно для лучшей читаемости — так вы разбиваете длинную инструкцию на несколько строк. CMD CMD — инструкция для запуска чего-либо во время запуска самого контейнера. По ходу сборки она не фиксирует никакого результата. В примере выше во время сборки запускался скрипт my_script.py. Ещё пара моментов о CMD: Только одна CMD-инструкция на весь Docker-файл. Иначе все кроме последней будут проигнорированы; CMD может включать исполняемый файл; Если же CMD не содержит никакого файла, обязательно должна быть инструкция ENTRYPOINT. В этом случает обе инструкции должны быть в формате JSON; Аргументы командной строки для запуска Docker переопределяют аргументы, предоставленные CMD в Docker-файле. WORKDIR Меняет текущую рабочую директорию в контейнере для инструкций: COPY, ADD, RUN и ENTRYPOINT. Несколько замечаний: Предпочтительно задать абсолютный путь с помощью WORKDIR, а не перемещаться по файловой системе с помощью команд cd в Docker-файле; WORKDIR автоматически создаёт директорию, если её ещё нет; Можно использовать несколько WORKDIR-инструкций. Если используются относительные пути — каждая инструкция поменяет рабочую директорию. ARG Определяет переменную для передачи из командной строки в образ. Для ARG можно указать значение по умолчанию: ARG my_var=my_default_value. В отличие от ENV-переменных, ARG-переменные не доступны для запущенных контейнеров. Однако вы можете использовать их для установки дефолтных значений для ENV-переменных, когда вы создаёте образ. И затем ENV-переменные сохраняются. Больше про это вы найдёте здесь. ENTRYPOINT ENTRYPOINT тоже позволяет вам задавать дефолтные команды и аргументы во время запуска контейнера. Она похожа на CMD, но параметры ENTRYPOINT не переопределяются, если контейнер запущен с параметрами командной строки. Вместо этого аргументы командной строки, передаваемые docker run myimagename, добавляются к аргументам инструкции ENTRYPOINT. Например, docker run my_image bash добавляет аргумент bash в конец, ко всем другим аргументам ENTRYPOINT. Docker-файл обязательно должен содержать либо CMD-инструкцию, либо ENTRYPOINT-инструкцию. В официальной документации есть несколько советов, которые помогут сделать выбор между CMD и ENTRYPOINT для начальной команды: Если вам нужно запускать одну и туже команду несколько раз, выбирайте ENTRYPOINT; Используйте ENTRYPOINT, когда ваш контейнер выступает в роли исполняющейся программы; При наличии дополнительных дефолтных аргументов, которые могут быть изменены через командную строку, лучше подойдёт CMD. В примере выше, ENTRYPOINT ["python", "my_script.py", "my_var"] запускает в контейнере Python-скрипт my_script.py с аргументом my_var. Затем переменная my_var может быть использована в my_script argparse. Заметьте, у my_var есть дефолтное значение, ранее установленное в Docker-файле при помощи ARG. Так что, если аргумент не будет задан через командную строку, возьмётся его значение по умолчанию. Как правило, Docker рекомендует вам использовать исполняемую форму с JSON-синтаксисом ENTRYPOINT ["executable", "param1", "param2"]. EXPOSE Инструкция EXPOSE показывает, какой порт пробрасывать из контейнера. Используйте команду docker run с флагом -p для пробрасывания и сопоставления нескольких портов во время запуска. Флаг в верхнем регистре -P будет пробрасывать все открытые порты. VOLUME VOLUME определяет, где контейнер будет хранить постоянные данные и получать к ним доступ.