Я не буду вас обманывать и говорить, что контейнеры – это очень просто, потому что в реальности это не совсем так. Я постараюсь рассказать все максимально просто и надеюсь, что у меня получиться проще некуда.
Нужно говорить, что такое контейнеры и зачем они нужны? Раз уж начинать с самых основ, то наверно действительно стоит попробовать объяснить моими словами, и я надеюсь, что у меня получиться проще некуда.
Для сайта очень часто нужна для работы база данных, нужен какой-то язык программирования, библиотеки и так далее. Когда мы работаем локально, то программисты все это настраивают вручную. Но вот настал момент, когда нужно доставить приложение сервера для тестирования, на сервера для показа клиенту или даже на рабочие сервера. Прошли времена, когда программист сразу же выкладывал свою работу на рабочий сервер, разве только это не домашняя страничка или блог. Более-менее серьезные проекты нужно доставлять на различные сервера.
Когда есть несколько мест доставки, нужно каждый раз убеждаться, что в каждом из них используется одна и та же конфигурация. Если сайт протестировали на PHP 5, то без специального тестирования рисковать выкладывать на сервер код, где может стоять PHP 7 это большой риск.
Хуже обстоят дела с микросервисной архитектурой, где один сервис может требовать Python 3.6, другой сервис может требовать PHP 7, а где-то может лежать стары код на втором питоне или пятом PHP, которые не будут работать с новой версией. В рабочем окружении вы можете выделить для каждого из них отдельную ОС или виртуальную машину. Только из-за того, что сервисы требуют различную версию PHP или Python вы вынуждены будете поднимать целую ОС в виртуальной машине, а запуск каждой из них будет недешевым.
Это же как на мерседесе ездить за хлебом. У вас получается виртуальная машина с огромным количеством возможностей, а вам всего лишь нужно за хлебом.
А что если не загружать целую ОС, а запускать нужные нам сервисы с нужными им зависимостями в виде интерпретаторов или баз данных в основной ОС, просто изолировано друг от друга.
И вот пришла идея контейнеров, в которые складывают все необходимое программе и такой контейнер изолирован. В основной ОС может стоять PHP 5, а в контейнере может находиться PHP 7. Контейнер изолирован и версия интерпретатора внутри него не будет конфликтовать с вервиями снаружи.
Все говорят о том, что контейнеры запускаются очень быстро. Это действительно так, но почему? Потому что внутри находятся только необходимые сервисы и программы, и если находятся компоненты ОС, то готовые к запуску. Если сайту нужен MySQL, то внутри контейнера будут только он. Как долго стартует MySQL? Да практически мгновенно и именно в этом кроется секрет. Тут нет никакой другой магии. В отличии от виртуальных машин в контейнере нет ОС, поэтому не нужно тратить время на ее загрузку.
В отличии от программ установки контейнеры – это не архив библиотек, которые требуют копирования в ОС, а всегда готовая к запуску сущность. Причем каждая эта сущность будет выполняться изолировано и независимо.
В жизни контейнеры доставляю на кораблях или тягачах. Тягач или корабль – это как раз ОС, а контейнер, который они переводят – это необходимые нам сервисы в большом изолированном ящике.
Сегодня железный контейнер мы можем вести на грузовике, а завтра можем перегрузить его на корабль. Точно так же и с докерами, их контейнеры мы можем сегодня запускать на своём локальном сервере, а завтра перегрузить в облако и запускать там без изменений.
Итак, контейнер - хранилище для кода, его зависимостей и конфигурации. В него можно помещать и статичные данные, но вот динамические данные, с которыми мы можем захотеть работать должны быть, но это уже отдельная история. Мы берём все это, объединяем вместе и держим готовыми к запуску в любой момент.
Нужно ли переходить на контейнеры? У них есть преимущества, но от них выиграют не все. Например, если говорить о моем блоге Flenov.info и вообще о большинстве блогов в интернете, то я мало выиграю от контейнера. У меня простой код, который не сильно придирчив к ОС и версии PHP/MySQL, потому что не использую ничего специфичного. Мне не нужно ничего особо конфигурировать и только один сервер. Я не планирую мигрировать на другие хостинги и мне не нужно поднимать 10-ки серверов.
А вот для более серьезных проектов и тем более с микросервисами контейнеры предоставляют могут стать серьезным преимуществом.
Итак, поехали знакомиться с докером.
Сначала нужно идем на сайт www.docker.com и здесь скачиваем программу установки для докеров. Я под Windows его уже давно не ставил, но кажется Домашнюю версия поддерживается как-то криво. Сейчас погуглил и похоже полноценная поддержка есть в Pro и Enterprise, а Home версия начала поддерживаться через WSL и подсистему Linux.
Итак, скачиваем последнюю версию Docker и устанавливаем, тут ничего сложного нет.
Теперь нам понадобиться какое-то приложение, которое мы будем заворачивать в контейнер. Чаще всего в контейнерах доставляют Web приложения, и мы тоже можем на Web приложениях пробовать, к тому же их создавать не так уж и сложно. Я покажу примеры контейнера PHP и Python приложения. Разница небольшая, но для разнообразия можно показать оба.
Начнем с PHP, создадим папку phptest и в ней можно создать файл helloworld.php с двумя строчками:
<? echo "Hello\r\n";
Ничего особого, просто выводим строку Hello. Теперь нам нужно как-то рассказать докеру, что должно быть в образе и как можно запустить наше приложение из него.
Прежде чем начать мы должны познакомиться с еще одним термином – образ или image. Образ – это как раз сущность, которая хранит программу и все конфигурации. Контейнер – это что-то, чему нужен образ, чтобы существовать и его мы сможем выполнять. Так что мы должны начать с создания образа, в котором будет находиться программа.
Образ имеет слоеную архитектуру и каждый из этих слоев неизменяемых, то есть из них можно только читать. Когда мы создаем контейнер, то поверх образа добавляется еще один слой контейнера и вот в него уже писать можно.
Для описания содержимого образа нужно создать специальный файл описания Dockerfile без расширения. Когда вы будете генерировать образ на основе этого файла описания, докер читает инструкции из него и формирует файл.
Создайте Dockerfile файл поместите внутрь следующее содержимое:
FROM php:7.0 RUN mkdir -p /usr/app/myphpapp COPY helloworld.php /usr/app/myphpapp WORKDIR /usr/app/myphpapp CMD php helloworld.php
Раньше каждая из этих команд создавала отдельный слой, а сейчас с целью оптимизации размера только RUN, COPY, ADD создают слой, а остальные создают промежуточные образы.
Для нашего запуска нашего PHP приложения нужен интерпретатор PHP. Основу можно указать с помощью инструкции FROM. В данном случае это:
FROM php:7.0
Инструкции нужно указать имя базы (в данном случае php) и метку (в данном случае версия 7.0). При выполнении этой строки докер попытается найти в своем репозитории образ с именем php 7-й версии, скачает его и добавит к нашему будущему образу.
Нам нужна папка, в которой будет находиться программа. В nix системах новую папку можно создать выполнением команды:
mkdir -p /usr/app/myphpapp
Но это команда и нам ее нужно выполнить, а для этого в Dockerfile можно использовать инструкцию RUN, которой мы должны указать Linux команду:
RUN mkdir -p /usr/app/myphpapp
Docker не может изменять слои и тем более те, которые скачал с репозитория, поэтому выполняя команду RUN он создаст новый слой и после выполнения команды выше в этом слое появиться папка.
Отлично, теперь у нас есть папка для программы, и мы можем поместить в нее программу. Казалось бы, мы должны снова выполнить команду RUN и в ней Linux команду COPY, но тогда команда выполниться внутри image, а нам нужно скопировать файл, который сейчас находиться снаружи внутрь. Специально для этого есть команду COPY:
COPY helloworld.php /usr/app/myphpapp
Отлично, мы скопировали файл в папку и в этот момент был создан новый слой с файлом внутри папки.
Этого достаточно, чтобы создать образ, который содержит приложение и зависимость в виде PHP. Но мы должны еще сказать, что именно нашу программу нужно запускать при старте контейнера. Для этого сделаем папку программы рабочей:
WORKDIR /usr/app/myphpapp
Для нашего примера этот пункт не является обязательным, потому что наше приложение состоит из одного файла, и оно не обращается к другим файлам. Но очень часто у вас будет больше годного файла и приложения часто ищут файлы в текущей рабочей директории и поэтому очень часто нам эта команда будет необходима.
И укажем команду для запуска с помощью инструкции CMD:
CMD php helloworld.php
Если не указывать workdir, придется указывать полный путь к файлу для запуска:
CMD php /usr/app/myphpapp/helloworld.php
Теперь мы готовы создать свой образ командой:
docker build -t test:v1 .
Мы просим docker собрать образ, а с помощью ключа -t задаем имя репозитория и метки test:v1. Точно так же, как у образа php:7.0 на основе которого мы создали наш образ.
Указывать репозиторий и метку не обязательно, просто если этого не будет, то в качестве имени будет сгенерированный код, который не очень удобен к использованию. Поэтому рекомендую указывать как минимум репозиторий, то есть как минимум слово test.
В конце команды есть точка, которая указывает, что образ должен быть создан на основе содержимого текущей папки. В этой папке должен быть Dockerfile. Docker build начинает читать содержимое этого файла и строить образ.
Имея новый образ мы можем создать еще один образ на его основе и возможно добавить в него еще какие-то файлы и программы:
FROM test:v1 ...
Но мы этого делать не будем, это я так, для примера.
Итак, у нас появился образ, который мы можем выполнить. Чтобы посмотреть список образов можно выполнить команду:
docker images
Результат:
REPOSITORY TAG IMAGE ID CREATED SIZE phptest v1 5beee598f4ba 57 seconds ago 357MB php 7.0 cd45c80ef616 23 months ago 357MB
Пройдёмся сразу по колонкам:
Обратите внимание, у нас тут два образа. Просто когда мы создавали наш образ, мы указали PHP 7.0 и docker скачал его, прежде чем создавать на его основе новый образ. Конечно же удалять PHP он не стал.
Обратите внимание, что размеры идентичны, потому что мой php скрипт не на столько большой, чтобы повлиять на размер, но по размеру можно сказать, что phptest включает в себя php.
Если бы мы не указали тэг, то docker воспринимал бы этот образ как последний, и мы увидели бы во второй колонке слово latest. Если бы мы не указали -t вовсе, то в первой и во второй колонке были бы <none>. Если создать два образа без метки, то работать с этим будет не очень удобно.
Отлично, образ есть, и мы можем запустить его:
docker run phptest:v1
В результате в консоли вы должны будете увидеть слово Hello.
Во время запуска на основе образа был создан контейнер, и он уже был выполнен. Вот тот самый момент, когда наконец появился контейнер. Когда выполнение закончилось, то контейнер заканчивает работу, но он все же продолжает существовать. Чтобы просмотреть работающие контейнеры нужно выполнить команду
docker ps
Мы ничего не должны увидеть, потому что наш контейнер остановился уже. А чтобы увидеть и остановленные контейнеры, нужно добавить ключ -a:
docker ps -a
Пройдемся по колонкам:
С пустым именем управлять контейнером будет проблематично, поэтому что-то нужно и поэтому Docker сгенерировал имя. Чтобы при запуске указать имя нужно использовать параметр -- name:
docker run --name phptestcont phptest:v1
Давайте еще рассмотрим, как можно удалить контейнер, чтобы закончить базовый курс. Удалять можно контейнеры и образы. Образ нельзя будет удалить, пока не удалены все контейнеры, которые созданы на основе образа, так что начнем удалять с контейнеров.
Для удаления контейнара используется команда rm и ей указывается ID контейнера или имя:
docker rm phptestcont
Хотите удалить все контейнеры, можно выполнить команду:
docker rm $(docker ps -aq)
В качестве параметра команде передается результат вывода docker ps -aq. В нем мы знаем практически все, кроме ключа q, который заставляет выводить не все колонки, а только идентификаторы контейнеров.
Удаление образа – это команда image rm, которой нужно передать идентификатор образа IMAGE ID или имя в формате репозиторий:тэг:
docker image rm phptest:v1
Очень редко приходиться удалять все образы, но на всякий случай это можно сделать так:
docker image rm $(docker images -q)
На этом наверно завершим рассматривать команды, потому что для базового процесса создания/запуска/удаления рассмотренного уже достаточно. Лучше закрепим материал и быстро создадим ещё один образ и контейнер на базе Python.
Давайте теперь попробуем сделать образ и контейнер для приложения на питоне. Создаем новую папку для теста и в ней создаем два файла helloworld.py и Dockerfile. В первый из них достаточно поместить строку:
print("Hello")
Как и в случае с PHP примером мы тут будем выводить просто одно слово Hello.
FROM python:3.8 RUN mkdir -p /usr/app/myphpapp COPY helloworld.py /usr/app/myphpapp WORKDIR /usr/app/myphpapp CMD python helloworld.py
Для выполнения Python приложения нам нужен конечно же соответствующий интерпретатор, я выбрал 3.8. Все остальное практически идентично PHP контейнеру – мы создаем папку, копируем наш файл, изменяем рабочую папку и запускаем выполнение.
Дальше создаем и выполняем образ:
docker build -t pytest:v1 . docker run pytest:v1
Если вы не удалили php образы и выполните сейчас команду images, то вы должны увидеть оба образа и к сожалению для Python его образ на много больше:
REPOSITORY TAG IMAGE ID CREATED SIZE pytest v1 f390d1c6b470 39 seconds ago 882MB phptest v1 5beee598f4ba 24 hours ago 357MB
Внимание!!! Если ты копируешь эту статью себе на сайт, то оставляй ссылку непосредственно на эту страницу. Спасибо за понимание
Спасибо за труды!
Наконец-то, нашел нормальную понятную статью. Формат верный, так держать.
Невнятно описаны моменты с созданием и копированием, какие команды прописываются для запуска. Нигде нет человекоориентированной логично развернуто изложенной информации по работе с докером даже на самом докере, банально инструкции даже нормально не описаны - три слова и типа тебе должно быть все понятно. Ясности не возникает также при осмыслении таких сущностей как image и dockerfile, сплошной бездумный копипаст между ресурсами - нет осмысления материала. Та же история с гитом и еще больший гемор с галпом - работает через одно место.