Docker руководство на русском

Содержание

  • Вопросы и ответы
  • Введение
    • Пре-реквизиты
    • Настройка компьютера

  • 1.0 Играем с Busybox
    • 1.1 Docker Run
    • 1.2 Терминология

  • 2.0 Веб-приложения и Докер
    • 2.1 Статические сайты
    • 2.2 Образы
    • 2.3 Наш первый образ
    • 2.4 Dockerfile
    • 2.5 Docker на AWS

  • 3.0 Многоконтейнерные окружения
    • 3.1 SF Food Trucks
    • 3.2 Сети Docker
    • 3.3 Docker Compose
    • 3.4 AWS Elastic Container Service

  • 4.0 Заключение
    • 4.1 Следующие шаги
    • 4.2 Фидбек автору

Вопросы и ответы

Что такое Докер?

Определение Докера в Википедии звучит так:

программное обеспечение для автоматизации развёртывания и управления приложениями в среде виртуализации на уровне операционной системы; позволяет «упаковать» приложение со всем его окружением и зависимостями в контейнер, а также предоставляет среду по управлению контейнерами.

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

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

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

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

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

Почему я должен использовать их?

Взлет Докера был по-настоящему эпичным. Не смотря на то, что контейнеры сами по себе — не новая технология, до Докера они не были так распространены и популярны. Докер изменил ситуацию, предоставив стандартный API, который сильно упростил создание и использование контейнеров, и позволил сообществу вместе работать над библиотеками по работе с контейнерами. В статье, опубликованной в  The Register в середине 2014 говорится, что Гугл поддерживает больше двух миллиардов контейнеров в неделю.

Google Trends для слова ‘Docker’
interest

В дополнение к продолжительному росту Докера, компания-разработчик Docker Inc. была оценена в два с лишним миллиарда долларов! Благодаря преимуществам в эффективности и портативности, Докер начал получать все больше поддержки, и сейчас стоит во главе движения по контейнеризации (containerization). Как современные разработчики, мы должны понять этот тренд и выяснить, какую пользу мы можем получить из него.

Чему меня научит это пособие?

Это единое и полное пособие по всем аспектам работы с Докером. Кроме разъяснения мифов о Докере и его экосистеме, оно позволит вам получит небольшой опыт по сборке и деплою собственных веб-приложений в облаке. Мы будем использовать Amazon Web Services для деплоя статичных сайтов, и два динамических веб-приложения задеплоим на EC2 с использованием Elastic Beanstalk и Elastic Container Service. Даже если вы никогда ничего не деплоили, это пособие даст вам все необходимое.

Как использовать этот документ

Этот документ содержит несколько разделов, каждый из которых посвящен определенному аспекту Докера. В каждом разделе мы будем вводить команды или писать код. Весь код доступен в репозитории на Гитхабе.

Введение

Внимание: В этом пособии используется версия Докера 1.12.0-rc2. Если вы столкнулись с несовместимостью, пожалуйста, отправьте issue. Спасибо!

Пре-реквизиты

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

  • Amazon Web Services
  • Docker Hub

Настройка компьютера

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

Докер

Еще несколько релизов назад запуск Докера на OS X и Windows был был проблемным. Но команда разработчиков проделала огромную работу, и сегодня весь процесс — проще некуда. Этот туториал getting started включает в себя подробные инструкции по установке на Мак, Linux и Windows.

Проверим, все ли установлено корректно:

$ docker run hello-world

Hello from Docker.
This message shows that your installation appears to be working correctly.
...

Python

Python обычно предустановлен на OS X и на большинстве дистрибутивов Linux. Если вам нужно установить Питон, то скачайте установщик здесь.

Проверьте версию:

$ python --version
Python 2.7.11

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

Для проверки запустите такую команду:

$ pip --version
pip 7.1.2 from /Library/Python/2.7/site-packages/pip-7.1.2-py2.7.egg (python 2.7)

Java (не обязательно)

Разрабатываемое нами приложение будет использовать Elasticsearch для хранения и поиска. Для локального запуска Elasticsearch вам понадобится Java. В этом пособии все будет запускаться внутри контейнера, так что локально не обязательно иметь Java. Если Java установлена, то команда java -version должна сгенерировать подобный вывод:

$ java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)


1.0 Играем с Busybox

Теперь, когда все необходимое установлено, пора взяться за работу. В этом разделе мы запустим контейнер Busybox на нашей системе и попробуем запустить docker run.

Для начала, запустите следующую команду:

$ docker pull busybox

Внимание: в зависимости от того, как вы устанавливали Докер на свою систему, возможно появление сообщения permission denied. Если вы на Маке, то удостоверьтесь, что движок Докер запущен. Если вы на Линуксе, то запустите эту команду с sudo. Или можете создать группу docker чтобы избавиться от этой проблемы.

Команда pull скачивает образ busybox из регистра Докера и сохраняет его локально. Можно использовать команду docker images, чтобы посмотреть список образов в системе.

$ docker images
REPOSITORY              TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
busybox                 latest              c51f86c28340        4 weeks ago         1.109 MB

1.1 Docker Run

Отлично! Теперь давайте запустим Докер-контейнер с этим образом. Для этого используем волшебную команду docker run:

$ docker run busybox
$

Подождите, ничего не произошло! Это баг? Ну, нет. Под капотом произошло много всего. Докер-клиент нашел образ (в нашем случае, busybox), загрузил контейнер и запустил команду внутри этого контейнера. Мы сделали docker run busybox, но не указали никаких команд, так что контейнер загрузился, запустилась пустая команда и программа завершилась. Ну, да, как-то обидно, так что давайте сделаем что-то поинтереснее.

$ docker run busybox echo "hello from busybox"
hello from busybox

Ура, наконец-то какой-то вывод. В нашем случае клиент Докера послушно запустил команду echo внутри контейнера, а потом вышел из него. Вы, наверное, заметили, что все произошло очень быстро. А теперь представьте себе, как нужно загружать виртуальную машину, запускать в ней команду и выключать ее. Теперь ясно, почему говорят, что контейнеры быстрые!

Теперь давайте взглянем на команду docker ps. Она выводит на экран список всех запущенных контейнеров.

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

Контейнеров сейчас нет, поэтому выводится пустая строка. Не очень полезно, поэтому давайте запустим более полезный вариант: docker ps -a

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
305297d7a235        busybox             "uptime"            11 minutes ago      Exited (0) 11 minutes ago                       distracted_goldstine
ff0a5c3750b9        busybox             "sh"                12 minutes ago      Exited (0) 12 minutes ago                       elated_ramanujan

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

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

$ docker run -it busybox sh
/ # ls
bin   dev   etc   home  proc  root  sys   tmp   usr   var
/ # uptime
 05:45:21 up  5:58,  0 users,  load average: 0.00, 0.01, 0.04

 

Команда run с флагом -it подключает интерактивный tty в контейнер. Теперь можно запускать сколько угодно много команд внутри. Попробуйте.

Опасно!: Если хочется острых ощущений, то можете попробовать rm -rf bin в контейнере. Но удостоверьтесь, что запускаете ее внутри контейнера, а не снаружи. Если сделаете это снаружи, на своем компьютере, то будет очень плохо, и команды вроде lsecho перестанут работать. Когда внутри контейнера все перестанет работать, просто выйдете и запустите его заново командой docker run -it busybox sh. Докер создает новый контейнер при запуске, поэтому все заработает снова.

На этом захватывающий тур по возможностям команды docker run закончен. Скорее всего, вы будете использовать эту команду довольно часто. Так что важно, чтобы мы поняли как с ней обращаться. Чтобы узнать больше о run, используйте docker run --help, и увидите полный список поддерживаемых флагов. Скоро мы увидим еще несколько способов использования docker run.

Перед тем, как продолжать, давайте вкратце рассмотрим удаление контейнеров. Мы видели выше, что с помощью команды docker ps -a все еще можно увидеть остатки завершенных контейнеров. На протяжении этого пособия, вы будете запускать docker run несколько раз, и оставшиеся, бездомные контейнеры будут съедать дисковое пространство. Так что я взял за правило удалять контейнеры после завершения работы с ними. Для этого используется команда docker rm. Просто скопируйте ID (можно несколько) из вывода выше и передайте параметрами в команду.

$ docker rm 305297d7a235 ff0a5c3750b9
305297d7a235
ff0a5c3750b9

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

$ docker rm $(docker ps -a -q -f status=exited)

Эта команда удаляет все контейнеры, у которых статус exited. Флаг -q возвращает только численные ID, а флаг -f фильтрует вывод на основе предоставленных условий. Последняя полезная деталь — команде docker run можно передать флаг --rm, тогда контейнер будет автоматически удаляться при завершении. Это очень полезно для разовых запусков и экспериментов с Докером.

Также можно удалять ненужные образы командой docker rmi.

1.2 Терминология

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

  • Images (образы) — Схемы нашего приложения, которые являются основой контейнеров. В примере выше мы использовали команду docker pull чтобы скачать образ busybox.
  • Containers (контейнеры) — Создаются на основе образа и запускают само приложение. Мы создали контейнер командой docker run, и использовали образ busybox, скачанный ранее. Список запущенных контейнеров можно увидеть с помощью команды docker ps.
  • Docker Daemon (демон Докера) — Фоновый сервис, запущенный на хост-машине, который отвечает за создание, запуск и уничтожение Докер-контейнеров. Демон — это процесс, который запущен на операционной системе, с которой взаимодействует клиент.
  • Docker Client (клиент Докера) — Утилита командной строки, которая позволяет пользователю взаимодействовать с демоном. Существуют другие формы клиента, например, Kitematic, с графическим интерфейсом.
  • Docker Hub — Регистр Докер-образов. Грубо говоря, архив всех доступных образов. Если нужно, то можно содержать собственный регистр и использовать его для получения образов.

2.0 Веб-приложения и Докер

Супер! Теперь мы научились работать с  docker run, поиграли с несколькими контейнерами и разобрались в терминологии. Вооруженные этими знаниями, мы готовы переходить к реальным штукам: деплою веб-приложений с Докером!

2.1 Статические сайты

Давайте начнем с малого. Вначале рассмотрим самый простой статический веб-сайт. Скачаем образ из Docker Hub, запустим контейнер и посмотрим, насколько легко будет запустить веб-сервер.

Поехали. Для одностраничного сайта нам понадобится образ, который я заранее создал для этого пособия и разместил в регистре — prakhar1989/static-site. Можно скачать образ напрямую командой docker run.

$ docker run prakhar1989/static-site

Так как образа не существует локально, клиент сначала скачает образ из регистра, а потом запустит его. Если все без проблем, то вы увидите сообщение Nginx is running... в терминале. Теперь сервер запущен. Как увидеть сайт в действии? На каком порту работает сервер? И, что самое важное, как напрямую достучаться до контейнера из хост-контейнера?

В нашем случае клиент не открывает никакие порты, так что нужно будет перезапустить команду  docker run чтобы сделать порты публичными. Заодно давайте сделаем так, чтобы терминал не был прикреплен к запущенному контейнеру. В таком случае можно будет спокойно закрыть терминал, а контейнер продолжит работу. Это называется detached mode.

$ docker run -d -P --name static-site prakhar1989/static-site
e61d12292d69556eabe2a44c16cbd54486b2527e2ce4f95438e504afb7b02810

Флаг -d открепит (detach) терминал, флаг -P сделает все открытые порты публичными и случайными, и, наконец, флаг --name это имя, которое мы хотим дать контейнеру. Теперь можно увидеть порты с помощью команды docker port [CONTAINER].

$ docker port static-site
80/tcp -> 0.0.0.0:32769
443/tcp -> 0.0.0.0:32768

Откройте http://localhost:32769 в своем браузере.

Замечание: Если вы используете docker-toolbox, то, возможно, нужно будет использовать docker-machine ip default чтобы получить IP-адрес.

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

$ docker run -p 8888:80 prakhar1989/static-site
Nginx is running...

static

Чтобы остановить контейнер запустите docker stop и укажите идентификатор (ID) контейнера.

Согласитесь, все было очень просто. Чтобы задеплоить это на реальный сервер, нужно просто установить Докер и запустить команду выше. Теперь, когда вы увидели, как запускать веб-сервер внутри образа, вам, наверное, интересно — а как создать свой Докер-образ? Мы будем изучать эту тему в следующем разделе.

2.2 Образы

Мы касались образов ранее, но в этом разделе мы заглянем глубже: что такое Докер-образы и как создавать собственные образы. Наконец, мы используем собственный образ чтобы запустить приложение локально, а потом задеплоим его на AWS, чтобы показать друзьям. Круто? Круто! Давайте начнем.

Образы это основы для контейнеров. В прошлом примере мы скачали (pull) образ под названием Busybox из регистра, и попросили клиент Докера запустить контейнер, основанный на этом образе. Чтобы увидеть список доступных локально образов, используйте команду docker images.

$ docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
prakhar1989/catnip              latest              c7ffb5626a50        2 hours ago         697.9 MB
prakhar1989/static-site         latest              b270625a1631        21 hours ago        133.9 MB
python                          3-onbuild           cf4002b2c383        5 days ago          688.8 MB
martin/docker-cleanup-volumes   latest              b42990daaca2        7 weeks ago         22.14 MB
ubuntu                          latest              e9ae3c220b23        7 weeks ago         187.9 MB
busybox                         latest              c51f86c28340        9 weeks ago         1.109 MB
hello-world                     latest              0a6ba66e537a        11 weeks ago        960 B

Это список образов, которые я скачал из регистра, а также тех, что я сделал сам (скоро увидим, как это делать). TAG — это конкретный снимок или снэпшот (snapshot) образа, а IMAGE ID — это соответствующий уникальный идентификатор образа.

Для простоты, можно относиться к образу как к git-репозиторию. Образы можно коммитить с изменениями, и можно иметь несколько версий. Если не указывать конкретную версию, то клиент по умолчанию использует latest. Например, можно скачать определенную версию образа ubuntu:

$ docker pull ubuntu:12.04

Чтобы получить новый Докер-образ, можно скачать его из регистра (такого, как Docker Hub) или создать собственный. На Docker Hub есть десятки тысяч образов. Можно искать напрямую из командной строки с помощью docker search.

Важно понимать разницу между базовыми и дочерними образами:

  • Base images (базовые образы) — это образы, которые не имеют родительского образа. Обычно это образы с операционной системой, такие как ubuntu, busybox или debian.
  • Child images (дочерние образы) — это образы, построенные на базовых образах и обладающие дополнительной функциональностью.

Существуют официальные и пользовательские образы, и любые из них могут быть базовыми и дочерними.

  • Официальные образы — это образы, которые официально поддерживаются командой Docker. Обычно в их названии одно слово. В списке выше pythonubuntubusybox и hello-world — базовые образы.
  • Пользовательские образы — образы, созданные простыми пользователями вроде меня и вас. Они построены на базовых образах. Обычно, они называются по формату user/image-name.

2.3 Наш первый образ

Теперь, когда мы лучше понимаем, что такое образы и какие они бывают, самое время создать собственный образ. Цель этого раздела — создать образ с простым приложением на Flask. Для этого пособия я сделал маленькое приложение, которое выводит случайную гифку с кошкой. Ну, потому что, кто не любит кошек? Склонируйте этот репозиторий к себе на локальную машину.

Вначале давайте проверим, что приложение работает локально. Войдите в директорию flask-app командой cd и установите зависимости.

$ cd flask-app
$ pip install -r requirements.txt
$ python app.py
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

 

Если все хорошо, то вы увидите вывод как в примере выше. Зайдите на http://localhost:5000 чтобы увидеть приложение в действии.

Замечание: Если команда pip install падает с ошибками «permission denied», то попробуйте запустить ее с sudo. Если не хотите устанавливать пользовательские пакеты на уровне системы, то используйте команду pip install --user -r requirements.txt.

Выглядит отлично, правда? Теперь нужно создать образ с приложением. Как говорилось выше, все пользовательские образы основаны на базовом образе. Так как наше приложение написано на Питоне, нам нужен базовый образ Python 3. В частности, нам нужна версия python:3-onbuild базового образа с Питоном.

Что за версия onbuild, спросите вы?

Эти образы включают несколько триггеров ONBUILD, которых обычно достаточно чтобы быстро развернуть приложение. При сборке будет скопирован файл requirements.txt, будет запущен pip install с этим файлом, а потом текущая директория будет скопирована в /usr/src/app.

Другими словами, версия onbuild включает хелперы, которые автоматизируют скучные процессы запуска приложения. Вместо того, чтобы вручную выполнять эти задачи (или писать скрипты), образы делают все за вас. Теперь у нас есть все ингредиенты для создания своего образа: работающее веб-приложение и базовый образ. Как это сделать? Ответ: использовать Dockerfile.

2.4 Dockerfile

Dockerfile — это простой текстовый файл, в котором содержится список команд Докер-клиента. Это простой способ автоматизировать процесс создания образа. Самое классное, что команды в Dockerfile почти идентичны своим аналогам в Linux. Это значит, что в принципе не нужно изучать никакой новый синтаксис чтобы начать работать с докерфайлами.

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

Для начала укажем базовый образ. Для этого нужно использовать ключевое слово FROM.

FROM python:3-onbuild

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

EXPOSE 5000

Последний шаг — указать команду для запуска приложения. Это просто python ./app.py. Для этого используем команду CMD:

CMD ["python", "./app.py"]

Главное предназначение CMD — это сообщить контейнеру какие команды нужно выполнить при старте. Теперь наш Dockerfile готов. Вот как он выглядит:

# our base image
FROM python:3-onbuild

# specify the port number the container should expose
EXPOSE 5000

# run the application
CMD ["python", "./app.py"]

Теперь можно создать образ. Команда docker build занимается сложной задачей создания образа на основе Dockerfile.

Листинг ниже демонстрирует процесс. Перед тем, как запустите команду сами (не забудьте точку в конце), проверьте, чтобы там был ваш username вместо моего. Username должен соответствовать тому, что использовался при регистрации на Docker hub. Если вы еще не регистрировались, то сделайте это до выполнения команды. Команда docker build довольно проста: она принимает опциональный тег с флагом -t и путь до директории, в которой лежит Dockerfile.

$ docker build -t prakhar1989/catnip .
Sending build context to Docker daemon 8.704 kB
Step 1 : FROM python:3-onbuild
# Executing 3 build triggers...
Step 1 : COPY requirements.txt /usr/src/app/
 ---> Using cache
Step 1 : RUN pip install --no-cache-dir -r requirements.txt
 ---> Using cache
Step 1 : COPY . /usr/src/app
 ---> 1d61f639ef9e
Removing intermediate container 4de6ddf5528c
Step 2 : EXPOSE 5000
 ---> Running in 12cfcf6d67ee
 ---> f423c2f179d1
Removing intermediate container 12cfcf6d67ee
Step 3 : CMD python ./app.py
 ---> Running in f01401a5ace9
 ---> 13e87ed1fbc2
Removing intermediate container f01401a5ace9
Successfully built 13e87ed1fbc2

Если у вас нет образа python:3-onbuild, то клиент сначала скачает его, а потом возьмется за создание вашего образа. Так что, вывод на экран может отличаться от моего. Посмотрите внимательно, и найдете триггеры onbuild. Если все прошло хорошо, то образ готов! Запустите docker images и увидите свой образ в списке.

Последний шаг — запустить образ и проверить его работоспособность (замените username на свой):

$ docker run -p 8888:5000 prakhar1989/catnip
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

 

Зайдите на указанный URL и увидите приложение в работе.

static

Поздравляю! Вы успешно создали свой первый образ Докера!

2.5 Docker на AWS

Что хорошего в приложении, которое нельзя показать друзьям, правда? Так что в этом разделе мы научимся деплоить наше офигенное приложение в облако. Будем использовать AWS Elastic Beanstalk чтобы решить эту задачу за пару кликов. Мы увидим, как с помощью Beanstalk легко управлять и масштабировать наше приложение.

Docker push

Первое, что нужно сделать перед деплоем на AWS это опубликовать наш образ в регистре, чтобы можно было скачивать его из AWS. Есть несколько Docker-регистров (или можно создать собственный). Для начала, давайте используем Docker Hub. Просто выполните:

$ docker push prakhar1989/catnip

Если это ваша первая публикация, то клиент попросит вас залогиниться. Введите те же данные, что используете для входа в Docker Hub.

$ docker login
Username: prakhar1989
WARNING: login credentials saved in /Users/prakhar/.docker/config.json
Login Succeeded

Не забудьте заменить название образа на свое. Очень важно сохранить формат username/image_name, чтобы клиент понимал, куда публиковать образ.

После этого можете посмотреть на свой образ на Docker Hub. Например, вот страница моего образа.

Замечание: один важный момент, который стоит прояснить перед тем, как продолжить — не обязательно хранить образ в публичном регистре (или в любом другом регистре вообще) чтобы деплоить на AWS. Если вы пишете код для следующего многомиллионного стартапа-единорога, то можно пропустить этот шаг. Мы публикуем свой образ чтобы упростить деплой, пропустив несколько конфигурационных шагов.

Теперь наш образ онлайн, и любой докер-клиент может поиграться с ним с помощью простой команды:

$ docker run -p 8888:5000 prakhar1989/catnip

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

Beanstalk

AWS Elastic Beanstalk (EB) это PaaS (Platform as a Service — платформа как сервис) от Amazon Web Services. Если вы использовали Heroku, Google App Engine и т.д., то все будет привычно. Как разработчик, вы сообщаете EB как запускать ваше приложение, а EB занимается всем остальным, в том числе масштабированием, мониторингом и даже апдейтами. В апреле 2014 в EB добавили возможность запускать Докер-контейнеры, и мы будем использовать именно эту возможность для деплоя. У EB очень понятный интерфейс командной строки, но он требует небольшой конфигурации, поэтому для простоты давайте используем веб-интерфейс для запуска нашего приложения.

Чтобы продолжать, вам потребуется работающий аккаунт на AWS. Если у вас его нет, то создайте его. Для этого потребуется ввести данные кредитной карты. Но не волнуйтесь, эта услуга бесплатна, и все, что будет происходить в рамках этого пособия тоже бесплатно.

Давайте начнем:

  • Войдите в свою консоль AWS.
  • Нажмите на Elastic Beanstalk. Ссылка находится в секции compute, в левом верхнем углу. Или просто перейдите сюда.

static

  • Нажмите на «Create New Application» в верхнем правом углу.
  • Дайте своему приложению запоминающееся (но уникальное) имя и, если хотите, добавьте описание.
  • на экране New Environment выберите Web Server Environment.
  • Следующий экран показан ниже. Выберите Docker из готовых вариантов конфигурации. Можно оставить Environment type как есть. Нажмите Next.

static

  • Тут мы будем сообщать системе EB о нашем образе. Откройте файл Dockerrun.aws.json в директории flask-app и измените Name образа, чтобы оно соответствовало названию вашего образа. Не волнуйтесь, я опишу содержание файла попозже. Потом выберите вариант «upload your own» и выберите файл.
  • Далее, выберите название окружения и URL. Этот URL как раз можно будет давать друзьям, так что постарайтесь придумать что-нибудь попроще.
  • Пока не будем вносить никаких правок в секцию Additional Resources. Нажмите Next и переходите к Configuration Details.
  • В этой секции вам нужно выбрать тип инстанса t1.micro. Это очень важно, потому что это бесплатный тип от AWS. Если хотите, можно выбрать пару ключей для входа. Если вы не знаете, что это значит, то не волнуйтесь и просто пропустите эту часть. Все остальное можно оставить по умолчанию и продолжать.
  • Также не нужно указывать никакие Environment Tags and Permissions, так что просто жмите Next два раза подряд. В конце будет экран Review. Если все выглядит нормально, то нажимайте кнопку Launch.
  • На последнем экране будет несколько спиннеров. Это поднимается и настраивается ваше окружение. Обычно, нужно около пяти минут для первой настройки.

Пока ждем, давайте быстренько взглянем на файл Dockerrun.aws.json. Это файл для AWS, в котором находится информация о приложении конфигурации Докера. EB получает информацию из этого файла.

{
  "AWSEBDockerrunVersion": "1",
  "Image": {
    "Name": "prakhar1989/catnip",
    "Update": "true"
  },
  "Ports": [
    {
      "ContainerPort": "5000"
    }
  ],
  "Logging": "/var/log/nginx"
}

Файл довольно понятный, но всегда можно обратиться к официальной документации. Мы указываем название образа, и EB будет использовать его заодно с портом.

К этому моменту инстанс уже должен быть готов. Зайдите на страницу EB и увидите зеленый индикатор успешного запуска приложения.

static

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

Поздравляю! Вы задеплоили свое первое Докер-приложение! Может показаться, что было очень много шагов, но с командной утилитой EB можно имитировать функциональность Хероку несколькими нажатиями клавиш. Надеюсь, вы согласитесь, что Докер сильно упрощает процесс и минимизирует болезненные моменты деплоя в облако. Я советую вам почитать документацию AWS про single-container Docker environment чтобы понимать, какие существуют возможности в EB.

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

3.0 Многоконтейнерные окружения

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

Если у вас есть опыт управления сервисами в продакшене, то вы знаете, что современные приложения обычно не такие простые. Почти всегда есть база данных (или другой тип постоянного хранилища). Системы вроде Redis и Memcached стали практически обязательной частью архитектуры веб-приложений. Поэтому, в этом разделе мы научимся «докеризировать» приложения, которым требуется несколько запущенных сервисов.

В частности, мы увидим, как запускать и управлять многоконтейнерными Докер-окружениями. Почему нужно несколько контейнеров, спросите вы? Ну, одна из главных идей Докера в том, что он предоставляет изоляцию. Идея совмещения процесса и его зависимостей в одной песочнице (называемой контейнером) и делает Докер мощным инструментом.

Аналогично тому, как приложение разбивают на части, стоит содержать отдельные сервисы в отдельных контейнерах. Разным частям скорее всего требуются разные ресурсы, и требования могут расти с разной скоростью. Если мы разделим эти части и поместим в разные контейнеры, то каждую часть приложения можно строить, используя наиболее подходящий тип ресурсов. Это также хорошо совмещается с идеей микро сервисов. Это одна из причин, по которой Докер (и любая другая технология контейнеризации) находится на передовой современных микро сервисных архитектур.

3.1 SF Food Trucks

Приложение, которое мы переведем в Докер, называется SF Food Trucks (к сожалению, сейчас приложение уже не работает публично — прим. пер.). Моя цель была сделать что-то полезное (и похожее на настоящее приложение из реального мира), что-то, что использует как минимум один сервис, но не слишком сложное для этого пособия. Вот что я придумал.

sf food trucks

Бэкэнд приложения написано на Питоне (Flask), а для поиска используется Elasticsearch. Как и все остальное в этом пособии, код находится на Github. Мы используем это приложение, чтобы научиться запускать и деплоить много-контейнерное окружение.

Теперь, когда вы завелись (надеюсь), давайте подумаем, как будет выглядеть этот процесс. В нашем приложении есть бэкэнд на Flask и сервис Elasticsearch. Очевидно, что можно поделить приложение на два контейнера: один для Flask, другой для Elasticsearch (ES). Если приложение станет популярным, то можно будет добавлять новые контейнеры в нужном месте, смотря где будет узкое место.

Отлично, значит нужно два контейнера. Это не сложно, правда? Мы уже создавали Flask-контейнер в прошлом разделе. А для Elasticsearch… давайте посмотрим, есть ли что-нибудь в хабе:

$ docker search elasticsearch
NAME                              DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
elasticsearch                     Elasticsearch is a powerful open source se...   697       [OK]
itzg/elasticsearch                Provides an easily configurable Elasticsea...   17                   [OK]
tutum/elasticsearch               Elasticsearch image - listens in port 9200.     15                   [OK]
barnybug/elasticsearch            Latest Elasticsearch 1.7.2 and previous re...   15                   [OK]
digitalwonderland/elasticsearch   Latest Elasticsearch with Marvel & Kibana       12                   [OK]
monsantoco/elasticsearch          ElasticSearch Docker image                      9                    [OK]

Не удивительно, но существуют официальный образ для Elasticsearch. Чтобы запустить ES, нужно всего лишь выполнить docker run, и вскоре у нас будет локальный, работающий контейнер с одним узлом ES.

$ docker run -dp 9200:9200 elasticsearch
d582e031a005f41eea704cdc6b21e62e7a8a42021297ce7ce123b945ae3d3763

$ curl 0.0.0.0:9200
{
  "name" : "Ultra-Marine",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.1.1",
    "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
    "build_timestamp" : "2015-12-15T13:05:55Z",
    "build_snapshot" : false,
    "lucene_version" : "5.3.1"
  },
  "tagline" : "You Know, for Search"
}

Заодно давайте запустим контейнер с Flask. Но вначале нужен Dockerfile. В прошлой секции мы использовали образ python:3-onbuild в качестве базового. Однако, в этом раз, кроме установки зависимостей через pip, нам нужно, чтобы приложение генерировало минимизированный Javascript-файл для продакшена. Для этого понадобится Nodejs. Так что нужен свой билд с нуля, поэтому начнем с базового образа ubuntu.

Замечание: если оказывается, что существующий образ не подходит для вашей задачи, то спокойно создавайте свой образ на основе другого базового образа. В большинстве случаем, для образов на Docker Hub можно найти соответствующий Dockerfile на Github. Почитайте существующий Докерфайлы — это один из лучших способов научиться делать свои образы.

Наш Dockerfile для Flask-приложения выглядит следующим образом:

# start from base
FROM ubuntu:14.04
MAINTAINER Prakhar Srivastav <prakhar@prakhar.me>

# install system-wide deps for python and node
RUN apt-get -yqq update
RUN apt-get -yqq install python-pip python-dev
RUN apt-get -yqq install nodejs npm
RUN ln -s /usr/bin/nodejs /usr/bin/node

# copy our application code
ADD flask-app /opt/flask-app
WORKDIR /opt/flask-app

# fetch app specific deps
RUN npm install
RUN npm run build
RUN pip install -r requirements.txt

# expose port
EXPOSE 5000

# start app
CMD [ "python", "./app.py" ]

Тут много всего нового. Вначале указан базовый образ Ubuntu LTS, потом используется пакетный менеджер apt-get для установки зависимостей, в частности — Python и Node. Флаг yqq нужен для игнорирования вывода и автоматического выбора «Yes» во всех местах. Также создается символическая ссылка для бинарного файла node. Это нужно для решения проблем обратной совместимости.

Потом мы используем команду ADD для копирования приложения в нужную директорию в контейнере — /opt/flask-app. Здесь будет находиться весь наш код. Мы также устанавливаем эту директорию в качестве рабочей, так что следующие команды будут выполняться в контексте этой локации. Теперь, когда наши системные зависимости установлены, пора установить зависимости уровня приложения. Начнем с Node, установки пакетов из npm и запуска команды сборки, как указано в нашем файле package.json. В конце устанавливаем пакеты Python, открываем порт и определяем запуск приложения с помощь CMD, как в предыдущем разделе.

Наконец, можно собрать образ и запустить контейнер (замените prakhar1989 на свой username ниже).

$ docker build -t prakhar1989/foodtrucks-web .

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

$ docker run -P prakhar1989/foodtrucks-web
Unable to connect to ES. Retying in 5 secs...
Unable to connect to ES. Retying in 5 secs...
Unable to connect to ES. Retying in 5 secs...
Out of retries. Bailing out...

Упс! Наше приложение не смогло запуститься, потому что оно не может подключиться к Elasticsearch. Как сообщить одному контейнеру о другом и как заставить их взаимодействовать друг с другом? Ответ — в следующей секции.

3.2 Сети Docker

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

Ладно, давайте запустим docker ps, что тут у нас:

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                              NAMES
e931ab24dedc        elasticsearch       "/docker-entrypoint.s"   2 seconds ago       Up 2 seconds        0.0.0.0:9200->9200/tcp, 9300/tcp   cocky_spence

Итак, у нас есть контейнер ES по адресу и порту 0.0.0.0:9200, и мы можем напрямую обращаться к нему. Если можно было бы сообщить нашему приложению подключаться к этому адресу, то оно сможет общаться с ES, верно? Давайте взглянем на код на Питоне, туда, где описано подключение.

es = Elasticsearch(host='es')

Нужно сообщить Flask-контейнеру, что контейнер ES запущен на хосте 0.0.0.0 (порт по умолчанию 9200), и все заработает, да? К сожалению, нет, потому что IP 0.0.0.0 это адрес для доступа к контейнеру с  хост-машины, то есть с моего Мака. Другой контейнер не сможет обратиться по этому адресу. Ладно, если не этот адрес, то какой другой адрес нужно использовать для работы с контейнером ES? Рад, что вы спросили.

Это хороший момент, чтобы изучить работу сети в Докере. После установки, Докер автоматически создает три сети:

$ docker network ls
NETWORK ID          NAME                DRIVER
075b9f628ccc        none                null
be0f7178486c        host                host
8022115322ec        bridge              bridge

Сеть bridge — это сеть, в которой контейнеры запущены по умолчанию. Это значит, что когда я запускаю контейнер ES, он работает в этой сети bridge. Чтобы удостовериться, давайте проверим:

$ docker network inspect bridge
[
    {
        "Name": "bridge",
        "Id": "8022115322ec80613421b0282e7ee158ec41e16f565a3e86fa53496105deb2d7",
        "Scope": "local",
        "Driver": "bridge",
        "IPAM": {
            "Driver": "default",
            "Config": [
                {
                    "Subnet": "172.17.0.0/16"
                }
            ]
        },
        "Containers": {
            "e931ab24dedc1640cddf6286d08f115a83897c88223058305460d7bd793c1947": {
                "EndpointID": "66965e83bf7171daeb8652b39590b1f8c23d066ded16522daeb0128c9c25c189",
                "MacAddress": "02:42:ac:11:00:02",
                "IPv4Address": "172.17.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.bridge.default_bridge": "true",
            "com.docker.network.bridge.enable_icc": "true",
            "com.docker.network.bridge.enable_ip_masquerade": "true",
            "com.docker.network.bridge.host_binding_ipv4": "0.0.0.0",
            "com.docker.network.bridge.name": "docker0",
            "com.docker.network.driver.mtu": "1500"
        }
    }
]

Видно, что контейнер e931ab24dedc находится в секции Containers. Также виден IP-адрес, выданный этому контейнеру — 172.17.0.2. Именно этот адрес мы и искали? Давайте проверим: запустим Flask-приложение и попробуем обратиться к нему по IP:

$ docker run -it --rm prakhar1989/foodtrucks-web bash
root@35180ccc206a:/opt/flask-app# curl 172.17.0.2:9200
bash: curl: command not found
root@35180ccc206a:/opt/flask-app# apt-get -yqq install curl
root@35180ccc206a:/opt/flask-app# curl 172.17.0.2:9200
{
  "name" : "Jane Foster",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.1.1",
    "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
    "build_timestamp" : "2015-12-15T13:05:55Z",
    "build_snapshot" : false,
    "lucene_version" : "5.3.1"
  },
  "tagline" : "You Know, for Search"
}
root@35180ccc206a:/opt/flask-app# exit

Сейчас все должно быть понятно. Мы запустили контейнер в интерактивном режиме с процессом bash. Флаг --rm нужен для удобства, благодаря нему контейнер автоматически удаляется после выхода. Мы попробуем curl, но нужно сначала установить его. После этого можно удостовериться, что по адресу 172.17.0.2:9200 на самом деле можно обращаться к ES! Супер!

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

  1. Придется добавлять записи в файл /etc/hosts внутри Flask-контейнера, чтобы приложение понимало, что имя хоста es означает 172.17.0.2. Если IP-адрес меняется, то придется вручную менять запись.
  2. Так как сеть bridge используется всеми контейнерами по умолчанию, этот метод не безопасен.

Но есть хорошие новости: в Докере есть отличное решение этой проблемы. Докер позволяет создавать собственные изолированные сети. Это решение также помогает справиться с проблемой /etc/hosts, сейчас увидим как.

Во-первых, давайте создадим свою сеть:

$ docker network create foodtrucks
1a3386375797001999732cb4c4e97b88172d983b08cd0addfcb161eed0c18d89

$ docker network ls
NETWORK ID          NAME                DRIVER
1a3386375797        foodtrucks          bridge
8022115322ec        bridge              bridge
075b9f628ccc        none                null
be0f7178486c        host                host

Команда network create создает новую сеть bridge. Нам сейчас нужен именно такой тип. Существуют другие типы сетей, и вы можете почитать о них в официальной документации.

Теперь у нас есть сеть. Можно запустить наши контейнеры внутри сети с помощью флага --net. Давайте так и сделаем, но сначала остановим контейнер с ElasticSearch, который был запущен в сети bridge по умолчанию.

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                              NAMES
e931ab24dedc        elasticsearch       "/docker-entrypoint.s"   4 hours ago         Up 4 hours          0.0.0.0:9200->9200/tcp, 9300/tcp   cocky_spence

$ docker stop e931ab24dedc
e931ab24dedc

$ docker run -dp 9200:9200 --net foodtrucks --name es elasticsearch
2c0b96f9b8030f038e40abea44c2d17b0a8edda1354a08166c33e6d351d0c651

$ docker network inspect foodtrucks
[
    {
        "Name": "foodtrucks",
        "Id": "1a3386375797001999732cb4c4e97b88172d983b08cd0addfcb161eed0c18d89",
        "Scope": "local",
        "Driver": "bridge",
        "IPAM": {
            "Driver": "default",
            "Config": [
                {}
            ]
        },
        "Containers": {
            "2c0b96f9b8030f038e40abea44c2d17b0a8edda1354a08166c33e6d351d0c651": {
                "EndpointID": "15eabc7989ef78952fb577d0013243dae5199e8f5c55f1661606077d5b78e72a",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {}
    }
]

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

$ docker run -it --rm --net foodtrucks prakhar1989/foodtrucks-web bash
root@53af252b771a:/opt/flask-app# cat /etc/hosts
172.18.0.3  53af252b771a
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.2  es
172.18.0.2  es.foodtrucks

root@53af252b771a:/opt/flask-app# curl es:9200
bash: curl: command not found
root@53af252b771a:/opt/flask-app# apt-get -yqq install curl
root@53af252b771a:/opt/flask-app# curl es:9200
{
  "name" : "Doctor Leery",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "2.1.1",
    "build_hash" : "40e2c53a6b6c2972b3d13846e450e66f4375bd71",
    "build_timestamp" : "2015-12-15T13:05:55Z",
    "build_snapshot" : false,
    "lucene_version" : "5.3.1"
  },
  "tagline" : "You Know, for Search"
}
root@53af252b771a:/opt/flask-app# ls
app.py  node_modules  package.json  requirements.txt  static  templates  webpack.config.js
root@53af252b771a:/opt/flask-app# python app.py
Index not found...
Loading data in elasticsearch ...
Total trucks loaded:  733
 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
root@53af252b771a:/opt/flask-app# exit

Ура! Работает! Магическим образом Докер внес нужные правки в файл /etc/hosts, и поэтому es:9200 можно использовать в приложении — этот адрес корректно направляет запросы в контейнер ES. Отлично! Давайте теперь запустим Flask-контейнер по-настоящему:

$ docker run -d --net foodtrucks -p 5000:5000 --name foodtrucks-web prakhar1989/foodtrucks-web
2a1b77e066e646686f669bab4759ec1611db359362a031667cacbe45c3ddb413

$ docker ps
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                              NAMES
2a1b77e066e6        prakhar1989/foodtrucks-web   "python ./app.py"        2 seconds ago       Up 1 seconds        0.0.0.0:5000->5000/tcp             foodtrucks-web
2c0b96f9b803        elasticsearch                "/docker-entrypoint.s"   21 minutes ago      Up 21 minutes       0.0.0.0:9200->9200/tcp, 9300/tcp   es

$ curl -I 0.0.0.0:5000
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 3697
Server: Werkzeug/0.11.2 Python/2.7.6
Date: Sun, 10 Jan 2016 23:58:53 GMT

Зайдите на http://0.0.0.0:5000, и увидите приложение в работе. Опять же, может показаться, что было много работы, но на самом деле мы ввели всего 4 команды чтобы с нуля дойти до работающего приложения. Я собрал эти команды в bash-скрипт.

#!/bin/bash

# build the flask container
docker build -t prakhar1989/foodtrucks-web .

# create the network
docker network create foodtrucks

# start the ES container
docker run -d --net foodtrucks -p 9200:9200 -p 9300:9300 --name es elasticsearch

# start the flask app container
docker run -d --net foodtrucks -p 5000:5000 --name foodtrucks-web prakhar1989/foodtrucks-web

Теперь представьте, что хотите поделиться приложением с другом. Или хотите запустить на сервере, где установлен Докер. Можно запустить всю систему с помощью одной команды!

$ git clone https://github.com/prakhar1989/FoodTrucks
$ cd FoodTrucks
$ ./setup-docker.sh

Вот и все! По-моему, это невероятно крутой и мощный способ распространять и запускать приложения!

Docker Links

Перед тем, как завершить этот раздел, стоит отметить, что docker network это относительно новая фича, она входит в релиз Docker 1.9 .

До того, как появился network, ссылки были допустимым способом настройки взаимодействия между контейнерами. В соответствии с официальной документацией, linking вскоре будет переведены в статус deprecated. Если вам попадется туториал или статья, где используется link для соединения контейнеров, то просто не забывайте использовать вместо этого network (на момент публикации перевода links является legacy, — прим. пер.)

3.3 Docker Compose

До этого момента мы изучали клиент Докера. Но в экосистеме Докера есть несколько других инструментов с открытым исходным кодом, которые хорошо взаимодействуют с Докером. Некоторые из них это:

  1. Docker Machine позволяет создавать Докер-хосты на своем компьютере, облачном провайдере или внутри дата-центра.
  2. Docker Compose — инструмент для определения и запуска много-контейнерных приложений.
  3. Docker Swarm — нативное решение для кластеризации.

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

У Docker Compose довольно интересная предыстория. Примерно два года назад компания OrchardUp запустила инструмент под названием Fig. Идея была в том, чтобы создавать изолированные рабочие окружения с помощью Докера. Проект очень хорошо восприняли на Hacker News — я смутно помню, что читал о нем, но не особо понял его смысла.

Первый комментарий на самом деле неплохо объясняет, зачем нужен Fig и что он делает:

На самом деле, смысл Докера в следующем: запускать процессы. Сегодня у Докера есть неплохое API для запуска процессов: расшаренные между контейнерами (иными словами, запущенными образами) разделы или директории (shared volumes), перенаправление портов с хост-машины в контейнер, вывод логов, и так далее. Но больше ничего: Докер сейчас работает только на уровне процессов.
Не смотря на то, что в нем содержатся некоторые возможности оркестрации нескольких контейнеров для создания единого «приложения», в Докере нет ничего, что помогало бы с управлением такими группами контейнеров как одной сущностью. И вот зачем нужен инструмент вроде Fig: чтобы обращаться с группой контейнеров как с единой сущностью. Чтобы думать о «запуске приложений» (иными словами, «запуске оркестрированного кластера контейнеров») вместо «запуска контейнеров».

Оказалось, что многие пользователи Докера согласны с такими мыслями. Постепенно, Fig набрал популярность, Docker Inc. заметили, купили компанию и назвали проект Docker Compose.

Итак, зачем используется Compose? Это инструмент для простого определения и запуска многоконтейнерных Докер-приложений. В нем есть файл docker-compose.yml, и с его помощью можно одной командой поднять приложение с набором сервисов.

Давайте посмотрим, сможем ли мы создать файл docker-compose.yml для нашего приложения SF-Foodtrucks и проверим, способен ли он на то, что обещает.

Но вначале нужно установить Docker Compose. Есть у вас Windows или Mac, то Docker Compose уже установлен — он идет в комплекте с Docker Toolbox. На Linux можно установить Docker Compose следуя простым инструкциям на сайте документации. Compose написан на Python, поэтому можно сделать просто pip install docker-compose. Проверить работоспособность так:

$ docker-compose version
docker-compose version 1.7.1, build 0a9ab35
docker-py version: 1.8.1
CPython version: 2.7.9
OpenSSL version: OpenSSL 1.0.1j 15 Oct 2014

Теперь можно перейти к следующему шагу, то есть созданию файла docker-compose.yml. Синтаксис yml-файлов очень простой, и в репозитории уже есть пример, который мы будем использовать

version: "2"
services:
  es:
    image: elasticsearch
  web:
    image: prakhar1989/foodtrucks-web
    command: python app.py
    ports:
      - "5000:5000"
    volumes:
      - .:/code

      

Давайте я разберу это подробнее. На родительском уровне мы задали название неймспейса для наших сервисов: es и web. К каждому сервису можно добавить дополнительные параметры, среди которых image — обязательный. Для es мы указываем доступный на Docker Hub образ elasticsearch. Для Flask-приложения — тот образ, который мы создали самостоятельно в начале этого раздела.

С помощью других параметров вроде command и ports можно предоставить информацию о контейнере. volumes отвечает за локацию монтирования, где будет находиться код в контейнере web. Это опциональный параметр, он полезен, если нужно обращаться к логам и так далее. Подробнее о параметрах и возможных значениях можно прочитать в документации.

Замечание: Нужно находиться в директории с файлом docker-compose.yml чтобы запускать большую часть команд Compose.

Отлично! Файл готов, давайте посмотрим на docker-compose в действии. Но вначале нужно удостовериться, что порты свободны. Так что если у вас запущены контейнеры Flask и ES, то пора их остановить:

$ docker stop $(docker ps -q)
39a2f5df14ef
2a1b77e066e6

Теперь можно запускать docker-compose. Перейдите в директорию с приложением Foodtrucks и выполните команду docker-compose up.

$ docker-compose up
Creating network "foodtrucks_default" with the default driver
Creating foodtrucks_es_1
Creating foodtrucks_web_1
Attaching to foodtrucks_es_1, foodtrucks_web_1
es_1  | [2016-01-11 03:43:50,300][INFO ][node                     ] [Comet] version[2.1.1], pid[1], build[40e2c53/2015-12-15T13:05:55Z]
es_1  | [2016-01-11 03:43:50,307][INFO ][node                     ] [Comet] initializing ...
es_1  | [2016-01-11 03:43:50,366][INFO ][plugins                  ] [Comet] loaded [], sites []
es_1  | [2016-01-11 03:43:50,421][INFO ][env                      ] [Comet] using [1] data paths, mounts [[/usr/share/elasticsearch/data (/dev/sda1)]], net usable_space [16gb], net total_space [18.1gb], spins? [possibly], types [ext4]
es_1  | [2016-01-11 03:43:52,626][INFO ][node                     ] [Comet] initialized
es_1  | [2016-01-11 03:43:52,632][INFO ][node                     ] [Comet] starting ...
es_1  | [2016-01-11 03:43:52,703][WARN ][common.network           ] [Comet] publish address: {0.0.0.0} is a wildcard address, falling back to first non-loopback: {172.17.0.2}
es_1  | [2016-01-11 03:43:52,704][INFO ][transport                ] [Comet] publish_address {172.17.0.2:9300}, bound_addresses {[::]:9300}
es_1  | [2016-01-11 03:43:52,721][INFO ][discovery                ] [Comet] elasticsearch/cEk4s7pdQ-evRc9MqS2wqw
es_1  | [2016-01-11 03:43:55,785][INFO ][cluster.service          ] [Comet] new_master {Comet}{cEk4s7pdQ-evRc9MqS2wqw}{172.17.0.2}{172.17.0.2:9300}, reason: zen-disco-join(elected_as_master, [0] joins received)
es_1  | [2016-01-11 03:43:55,818][WARN ][common.network           ] [Comet] publish address: {0.0.0.0} is a wildcard address, falling back to first non-loopback: {172.17.0.2}
es_1  | [2016-01-11 03:43:55,819][INFO ][http                     ] [Comet] publish_address {172.17.0.2:9200}, bound_addresses {[::]:9200}
es_1  | [2016-01-11 03:43:55,819][INFO ][node                     ] [Comet] started
es_1  | [2016-01-11 03:43:55,826][INFO ][gateway                  ] [Comet] recovered [0] indices into cluster_state
es_1  | [2016-01-11 03:44:01,825][INFO ][cluster.metadata         ] [Comet] [sfdata] creating index, cause [auto(index api)], templates [], shards [5]/[1], mappings [truck]
es_1  | [2016-01-11 03:44:02,373][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:02,510][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:02,593][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:02,708][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
es_1  | [2016-01-11 03:44:03,047][INFO ][cluster.metadata         ] [Comet] [sfdata] update_mapping [truck]
web_1 |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)

Перейдите по IP чтобы увидеть приложение. Круто, да? Всего лишь пара строк конфигурации и несколько Докер-контейнеров работают в унисон. Давайте остановим сервисы и перезапустим в detached mode:

web_1 |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)
Killing foodtrucks_web_1 ... done
Killing foodtrucks_es_1 ... done

$ docker-compose up -d
Starting foodtrucks_es_1
Starting foodtrucks_web_1

$ docker-compose ps
      Name                    Command               State           Ports
----------------------------------------------------------------------------------
foodtrucks_es_1    /docker-entrypoint.sh elas ...   Up      9200/tcp, 9300/tcp
foodtrucks_web_1   python app.py                    Up      0.0.0.0:5000->5000/tcp

Не удивительно, но оба контейнера успешно запущены. Откуда берутся имена? Их Compose придумал сам. Но что насчет сети? Его Compose тоже делаем сам? Хороший вопрос, давайте выясним.

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

$ docker-compose stop
Stopping foodtrucks_web_1 ... done
Stopping foodtrucks_es_1 ... done

Заодно, давайте удалим сеть foodtrucks, которую создали в прошлый раз. Эта сеть нам не потребуется, потому что Compose автоматически сделает все за нас.

$ docker network rm foodtrucks
$ docker network ls
NETWORK ID          NAME                DRIVER
4eec273c054e        bridge              bridge
9347ae8783bd        none                null
54df57d7f493        host                host

Класс! Теперь в этом чистом состоянии можно проверить, способен ли Compose на волшебство.

$ docker-compose up -d
Recreating foodtrucks_es_1
Recreating foodtrucks_web_1
$ docker ps
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                    NAMES
f50bb33a3242        prakhar1989/foodtrucks-web   "python app.py"          14 seconds ago      Up 13 seconds       0.0.0.0:5000->5000/tcp   foodtrucks_web_1
e299ceeb4caa        elasticsearch                "/docker-entrypoint.s"   14 seconds ago      Up 14 seconds       9200/tcp, 9300/tcp       foodtrucks_es_1

Пока все хорошо. Проверим, создались ли какие-нибудь сети:

$ docker network ls
NETWORK ID          NAME                 DRIVER
0c8b474a9241        bridge               bridge              
293a141faac3        foodtrucks_default   bridge              
b44db703cd69        host                 host                
0474c9517805        none                 null  

Видно, что Compose самостоятельно создал сеть foodtrucks_default и подсоединил оба сервиса в эту сеть, так, чтобы они могли общаться друг с другом. Каждый контейнер для сервиса подключен к сети, и оба контейнера доступны другим контейнерам в сети. Они доступны по hostname, который совпадает с названием контейнера. Давайте проверим, находится ли эта информация в /etc/hosts.

$ docker ps
CONTAINER ID        IMAGE                        COMMAND                  CREATED             STATUS              PORTS                    NAMES
bb72dcebd379        prakhar1989/foodtrucks-web   "python app.py"          20 hours ago        Up 19 hours         0.0.0.0:5000->5000/tcp   foodtrucks_web_1
3338fc79be4b        elasticsearch                "/docker-entrypoint.s"   20 hours ago        Up 19 hours         9200/tcp, 9300/tcp       foodtrucks_es_1

$ docker exec -it bb72dcebd379 bash
root@bb72dcebd379:/opt/flask-app# cat /etc/hosts
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.2  bb72dcebd379

Упс! Оказывается, файл понятия не имеет о es. Как же наше приложение работает? Давайте попингуем его по названию хоста:

root@bb72dcebd379:/opt/flask-app# ping es
PING es (172.18.0.3) 56(84) bytes of data.
64 bytes from foodtrucks_es_1.foodtrucks_default (172.18.0.3): icmp_seq=1 ttl=64 time=0.049 ms
64 bytes from foodtrucks_es_1.foodtrucks_default (172.18.0.3): icmp_seq=2 ttl=64 time=0.064 ms
^C
--- es ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.049/0.056/0.064/0.010 ms

Вуаля! Работает! Каким-то магическим образом контейнер смог сделать пинг хоста es. Оказывается, Docker 1.10 добавили новую сетевую систему, которая производит обнаружение сервисов через DNS-сервер. Если интересно, то почитайте подробнее о предложении и release notes.

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

Надеюсь, я продемонстрировал как на самом деле просто управлять многоконтейнерной средой с Compose. В последнем разделе мы задеплоим все на AWS!

3.4 AWS Elastic Container Service

В прошлом разделе мы использовали docker-compose чтобы запустить наше приложение локально одной командой: docker-compose up. Теперь, когда приложение работает, мы хотим показать его миру, заполучить юзеров, поднять кучу денег и купить большой дом в Майами. Последние три шага выходят за пределы этого пособия, так что займемся выяснением деталей о деплое многоконтейнерного приложения в облако AWS.

Если вы дочитали до этого места, то скорее всего убедились, что Docker — довольно крутая технология. И вы не одиноки. Облачные провайдеры заметили взрывной рост популярности Докера и стали добавлять поддержку в свои сервисы. Сегодня, Докер-приложения можно деплоить на AWS, Azure,Rackspace, DigitalOcean и много других. Мы уже умеем деплоить приложение с одним контейнером на Elastic Beanstalk, а в этом разделе мы изучим AWS Elastic Container Service (или ECS).

AWS ECS — это масштабируемый и гибкий сервис по управлению контейнерами, и он поддерживает Докер. С его помощью можно управлять кластером на EC2 через простой API. В Beanstalk были нормальные настройки по умолчанию, но ECS позволяет настроить каждый аспект окружения по вашим потребностям. По этой причине ECS — не самый простой инструмент в начале пути.

К счастью, у ECS есть удобный инструмент командной строки (CLI) с поддержкой Docker Compose и автоматической провизией на ECS! Так как у нас уже есть рабочий файл docker-compose.yml, настройка и запуск на AWS должна быть достаточно легкой. Начнем!

Вначале нужно установить CLI. На момент написания этого пособия CLI-утилита не доступна на Windows. Инструкции по установке CLI на Mac и Linux хорошо описаны на сайте с официальной документацией. Установите утилиту, а потом проверьте ее работоспособность так:

$ ecs-cli --version
ecs-cli version 0.1.0 (*cbdc2d5)

Первый шаг — задать пару ключей для авторизации на инстансах. Зайдите на страницу EC2 Console и создайте новый keypair. Скачайте файл и держите его в безопасном месте. Еще один момент — имя региона. Я назвал свой ключ ecs и указал регион us-east-1. Я продолжу повествование с этим допущением.

keypair.png

Теперь настройте CLI:

$ ecs-cli configure --region us-east-1 --cluster foodtrucks
INFO[0000] Saved ECS CLI configuration for cluster (foodtrucks)

Команда configure с именем региона, в котором хотим разместить наш кластер, и название кластера. Нужно указать тот же регион, что использовался прри создании ключей. Если у вас не настроен AWS CLI, то следуйте руководству, которое подробно описывает все шаги.

Следующий шаг позволяет утилите создавать шаблон CloudFormation.

$ ecs-cli up --keypair ecs --capability-iam --size 2 --instance-type t2.micro
INFO[0000] Created cluster                               cluster=foodtrucks
INFO[0001] Waiting for your cluster resources to be created
INFO[0001] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0061] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0122] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0182] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0242] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS

Здесь мы указываем названия ключей, которые мы скачали (в моем случае ecs), количество инстансов (--size) и тип инстансов, на которых хотим запускать контейнеры. Флаг --capability-iam говорит утилите, что мы понимаем, что эта команда может создать ресурсы IAM.

В последнем шаге мы используем файл docker-compose.yml. Требуется небольшое изменение, так что вместо модификации файла, давайте сделаем копию и назовем ее aws-compose.yml. Содержание этого файла (после изменений):

es:
  image: elasticsearch
  cpu_shares: 100
  mem_limit: 262144000
web:
  image: prakhar1989/foodtrucks-web
  cpu_shares: 100
  mem_limit: 262144000
  ports:
    - "80:5000"
  links:
    - es

    

Единственные отличия от оригинального файла docker-compose.yml это параметры mem_limit и cpu_shares для каждого контейнера.

Также, мы убрали version и services, так как AWS еще не поддерживает версию 2 файлового формата Compose. Так как наше приложение будет работать на инстансах типа t2.micro, мы задали 250 мегабайт памяти. Теперь нам нужно опубликовать образ на Docker Hub. На момент написания этого пособия, ecs-cli не поддерживает команду build. Но Docker Compose поддерживает ее без проблем.

$ docker push prakhar1989/foodtrucks-web

Красота! Давайте запустим финальную команду, которая произведет деплой на ECS!

$ ecs-cli compose --file aws-compose.yml up
INFO[0000] Using ECS task definition                     TaskDefinition=ecscompose-foodtrucks:2
INFO[0000] Starting container...                         container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es
INFO[0000] Starting container...                         container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web
INFO[0000] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0000] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0036] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0048] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0048] Describe ECS container status                 container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=PENDING taskDefinition=ecscompose-foodtrucks:2
INFO[0060] Started container...                          container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/web desiredStatus=RUNNING lastStatus=RUNNING taskDefinition=ecscompose-foodtrucks:2
INFO[0060] Started container...                          container=845e2368-170d-44a7-bf9f-84c7fcd9ae29/es desiredStatus=RUNNING lastStatus=RUNNING taskDefinition=ecscompose-foodtrucks:2

То, что вывод похож на вывод Docker Compose — не совпадение. Аргумент --file используется для переопределения файла по умолчанию (docker-compose.yml). Если все прошло хорошо, то вы увидите строку desiredStatus=RUNNING lastStatus=RUNNING в самом конце.

Круто! Теперь приложение запущено. Как к нему обратиться?

ecs-cli ps
Name                                      State    Ports                     TaskDefinition
845e2368-170d-44a7-bf9f-84c7fcd9ae29/web  RUNNING  54.86.14.14:80->5000/tcp  ecscompose-foodtrucks:2
845e2368-170d-44a7-bf9f-84c7fcd9ae29/es   RUNNING                            ecscompose-foodtrucks:2

Откройте http://54.86.14.14 в браузере, и увидите Food Trucks во всей своей желто-черной красе! Заодно, давайте взглянем на консоль AWS ECS.

ECS cluster

ECS cluster

Видно, что был создан ECS-кластер ‘foodtrucks’, и в нем выполняется одна задача с двумя инстансами. Советую поковыряться в этой консоли и изучить разные ее части и опции.

Вот и все. Всего несколько команд — и приложение работает на AWS!

4.0 Заключение

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

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

4.1 Следующие шаги

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

Ниже — список дополнительных полезных ресурсов. Советую использовать Докер в вашем следующем проекте. И не забывайте — практика приводит к совершенству.

Дополнительные ресурсы

  • Awesome Docker
  • Hello Docker Workshop
  • Building a microservice with Node.js and Docker
  • Why Docker
  • Docker Weekly and archives
  • Codeship Blog

Удачи, юный падаван!

4.2 Фидбек автору

Теперь моя очередь задавать вопросы. Вам понравилось пособие? Оно показалось вам запутанным, или вам удалось научиться чему-то?

Напишите мне (автору оригинального пособия, — прим. пер.) напрямую на prakhar@prakhar.me или просто создайте issue. Я есть в Твиттере, так что если хотите, то можете писать туда.

(Автор оригинального пособия говорит по-английски, — прим. пер.).

Буду рад услышать ваши отзывы. Не стесняйтесь предлагать улучшения или указывать на мои ошибки. Я хочу, чтобы это пособие стало одним из лучших стартовых руководств в интернете. У меня не получится это без вашей помощи.

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

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

Когда я наконец-то понял все тонкости работы с Docker (на полное изучение которого ушло несколько месяцев), и начал правильно применять его при разработке (а он как раз и нужен для разработки, в большей степени), то почувствовал, как будто обрёл какую-то сверхспособоность. Смотря на свой опыт изучения Докера, я понял, что мне есть что рассказать, и чем поделиться. В этой статье я постарался создать максимально понятную для новичков инструкцию, благодаря которой вы сможете полностью изучить Docker за 30 минут. Я долго думал о том, чтобы написать туториал, и наконец-то осилил эту задачу, и, как мне кажется, получилось неплохо :)

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

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

В этом туториале я показываю всё на примере ОС Windows 10, делая все команды из консоли винды, и демонстрируя процесс установки Docker на Windows 10. Но, все команды будут работать аналогично и на Linux и Mac. Эта статья — это продолжение ряда статей, посвященных настройке рабочего окружения. В прошлой статье мы рассматривали работу с Vagrant, что не менее интересно, чем Docker. И Docker и Vagrant преследуют цель — упростить жизнь разработчикам, но с Докером открывается больше возможностей.

Так же, прошу заметить, если вы используете Vagrant, и решите установить Docker, то Vagrant перестанет работать. Такая жизнь, но с этим можно смириться, тем более, субъективно, Docker круче ^^.

Что вы узнаете из этой статьи

  • Как работает Docker?
  • Как установить Docker на Windows?
  • Что такое Docker Image (образ)?
  • Что такое Docker контейнер?
  • Что такое Docker Volumes?
  • Что такое Dockerfile?
  • Как пробрасывать локальную папку в контейнер Докера (монтирование папки)?
  • Как работают и пробрасываются Docker порты?
  • Слои Docker образа, особенности создания образов.
  • Что такое Docker-compose?
  • Что такое микросервисы и микросервисная архитектура?

Что такое Docker

О том, как появился Docker:

Docker — это программное обеспечение, которое начинало с того, что зародилось в одной компании, как внутренний проект platform-as-a-service в компании dotCloud.

В процессе развития Докера, он вырос из масштабов внутреннего проекта, стал доступен для широких масс, и затмил своей популярностью своего родителя dotCloud, из-за чего было принято решение создать новую отдельную компанию под названием Docker Incorporated. Направление новосозданной компании было только в разработке Докера, и развитию его экосистемы.

На сайте Докера можно найти статью, в которой подробно рассказывается, что такое Docker. Из их слов — это стандартизированное ПО для разработки и развёртывания проектов.

Но, что это на самом деле значит?

Давайте на секунду забудем про Докер, и вспомним про такую ностальгическую штуку, как GameBoy Color:game-boy-coloe

Если вы помните, игры для этой приставки поставлялись в виде картриджей:cartrige

И я уверен в том, что производители видео игр пользуются успехом из-за своей простоты:

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

Docker следует похожему принципу — позволяет запускать своё ПО настолько просто, что это соизмеримо с вставкой картриджа и нажатием кнопки ON на приставке.

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

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

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

Какое программное обеспечение можно запустить с помощью докера? В техническом плане, Docker чем-то похож на виртуальную машину:

Докер — это движок, который запускает виртуальную операционную систему, имеющую чрезвычайно маленький вес (в отличие от Vagrant-а, который создаёт полноценную виртуальную ОС, Докер, имеет особые образы ПО, запускающиеся в виртуальной среде, не создавая полную копию ОС).

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

Зачем использовать Docker?

Кошмар при установке ПО, с которым приходится сталкиваться. У вас когда-нибудь было такое, что вы пытаетесь установить ПО на ваш компьютер, а оно отказывается работать? Вы получаете несколько непонятных вам ошибок, из-за которых ничего не запускается. И после нескольких часов гугления, на десятой странице гугла…и на каком-то форуме, до этого неизвестного вам, вы наконец-то находите случайный комментарий, который помогает исправить вашу проблему.

Аналогично, что делает написание PC игр более сложным, чем написание Game Boy игр — это то, что приходится проектировать систему с учётом большого множества существующих PC девайсов и спецификаций. Так как разные компьютеры имеют различные операционные системы, драйвера, процессоры, графические карты, и т.д.
И потому задача разработчика — написать приложение совместимое со всеми популярными системами, является достаточно затруднительной и трудоёмкой.

Docker спасёт нас. Docker, как и Game Boy приставка, берёт стандартизированные части программного обеспечения и запускает их так, как Game Boy запускал бы игру.

В этом случае вы не должны беспокоиться об операционной системе, на которой пользователь будет запускать ваше приложение. Теперь, когда пользователи будут запускать приложение через Docker — конфигурация будет собрана автоматически, и код будет выполняться ВСЕГДА.

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

Для пользователя все действия сводятся к принципу подключи и играй.

Установка Docker

Docker доступен для любой из операционных систем: Windows, Linux, Max. Для скачивания установочного файла — перейдите по ссылке и выберите подходящую вам версию. Я же, как и писал ранее, выбираю версию docker для Windows 10.

Docker предоставляет 2 сборки:

  • Community Edition (полностью бесплатная версия)
  • Enterprise Edition (платно). Enterprise Edition содержит в себе дополнительные свистелки-перделки функции, которые, на данном этапе, точно не нужны. Функциональность, которую мы будем использовать совершенно не отличается в этих двух сборках.

После установки потребуется перезагрузка системы, и уже можно начинать полноценно работать с Докером.

Для того, чтобы проверить, запущен ли Docker, откроем командную строку (на Windows 10 — Нажмите кнопку windows, и начните писать командная строка)command-call
Где, напишем команду docker, и в случае успешно работающего докера, получим ответ command-docker
Дальше, нужно удостовериться, что вместе с докером, доступен так же, docker-compose, для этого, выполним команду:

docker-compose (вывод обеих команд будет примерно одинакового содержания).

Если вы используете Linux, то, docker-compose нужно будет устанавливать отдельно по инструкции.

Что такое Docker Image?

Docker образ (он же Docker Image), похож на Game Boy картридж — это просто программное обеспечение. Это стандартизированное программное обеспечение, которое запускается на любой приставке Game Boy. Вы можете дать игру вашему другу, и он сможет просто вставить картридж в приставку, и играть.

Как в случае с картриджами, бывают различные игры, так и Docker имеет различные образы ПО: ubuntu, php (который наследуется от оригинального образа Ubuntu), nodejs, и т.д.

Рассмотрим пример скачивания нашего первого образа.

Для этого, существует команда:
docker pull <IMAGE_NAME>, где <IMAGE_NAME> — имя скачиваемого образа

Зная эту команду, скачаем образ Ubuntu 18.10:

docker pull ubuntu:18.10

docker-pull-ubuntu-18

Эта команда сообщает Докеру о том, что нужно скачать образ Ubuntu 18.10 с Dockerhub.com — основной репозиторий Docker-образов, на котором вы и можете посмотреть весь их список и подобрать нужный образ для вашей программы.

Это как поездка за новым картриджем в магазин, только намного быстрее :).

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

docker images

docke-images

У вас, как и на скрине, должен появиться только что скачанный образ Ubuntu 18.10.
Как и обсуждалось выше, по поводу маленького размера образов, чистый образ Ubuntu при установке из Docker-образа, весит всего 74 МБ. Не чудо ли?

Проводя аналогии, команда docker images выглядит как коллекция картриджей от приставки, которые у вас есть сейчас:Depositphotos_194136098_xl-2015

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

Теперь представьте, что мы обновили нашу приставку с Game Boy на GameCube. Игры хранятся на диске, который предназначен только для чтения самого образа игры. А прочие файлы (сохранения, кеш и т.д.) сохраняются внутри самой приставки, локально.

Так же, как и игра на диске, исходный Docker Image (образ) — неизменяемый.

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

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

Depositphotos_194135642_xl-2015

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

Для запуска контейнера существует команда:

docker run <image> <опциональная команды, которая выполнится внутри контейнера>

Давайте запустим наш первый контейнер Ubuntu:

docker run ubuntu:18.10 echo 'hello from ubuntu'

docker-run

Команда echo 'hello from ubuntu' была выполнена внутри среды Ubuntu. Другими словами, эта команда была выполнена в контейнере ubuntu:18.10.

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

docker ps

docker-ps

Здесь пустота… это потому что docker ps показывает только список контейнеров, которые запущены в данный момент (наш же контейнер выполнил одну команду echo 'hello from ubuntu' и завершил свою работу).

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

docker ps -a

docker-ps-a

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

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

Выполнение неограниченное количество команда внутри контейнера

Давайте добавим немного интерактивности в наше обучение. Мы можем подключиться к консоли виртуальной ОС (Ubuntu 18.10), и выполнять любое количество команд без завершения работы контейнера, для этого, запустим команду:

docker run -it ubuntu:18.10 /bin/bash

Опция -it вместе с /bin/bash даёт доступ к выполнению команд в терминале внутри контейнера Ubuntu.

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

Узнаём ID контейнера

Иногда является очень полезным узнать ID контейнера, с которым мы работаем. И как раз-таки, при выполнении команды docker run -it <IMAGE> /bin/bash, мы окажемся в терминале, где все команды будут выполняться от имени пользователя root@<containerid>.

Теперь, все команды буду выполняться внутри операционной системы Ubuntu. Попробуем, например, выполнить команду ls, и посмотрим, список директорий, внутри этого образа Ubuntu.docker-ubuntu-ls

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

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

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

Теперь откройте новое окно терминала (не закрывая и не отключаясь от текущего), и выполните команду docker ps
docker-ps-new-window

Только на этот раз вы можете увидеть, что контейнер с Ubuntu 18.10 в текущий момент запущен.

Теперь вернёмся назад к первому окну терминала (который находится внутри контейнера), и выполним:

mkdir /truedir  #создаст папку truedir
exit #выйдет из контейнера, и вернётся в основную ОС

Выполнив команду exit, контейнер будет остановлен (чтобы убедиться, можете проверить командой docker ps). Теперь, вы так же знаете, как выйти из Docker контейнера.

Теперь, попробуем ещё раз просмотреть список всех контейнеров, и убедимся, что новый контейнер был создан docker ps -adocker-ps--a2

Так же, для того, чтобы запустить ранее созданный контейнер, можно выполнить команду docker start <CONTAINER_ID>,

где CONTAINER_ID — id контейнера, который можно посмотреть, выполнив команду docker ps -a (и увидеть в столбце CONTAINER_ID)

В моём случае, CONTAINER_ID последнего контейнера = 7579c85c8b7e (у вас же, он будет отличаться)

Запустим контейнер командой:

docker start 7579c85c8b7e    #ваш CONTAINER_ID
docker ps
docker exec -it 7579c85c8b7e /bin/bash #ваш CONTAINER_ID

И теперь, если внутри контейнера выполнить команду ls, то можно увидеть, что ранее созданная папка truedir существует в этом контейнереdocker-exex-truedir

Команда exec позволяет выполнить команду внутри запущенного контейнера. В нашем случае, мы выполнили /bin/bash, что позволило нам подключиться к терминалу внутри контейнера.

Для выхода, как обычно, выполним exit.

Теперь остановим и удалим Docker контейнеры командами:
docker stop <CONTAINER_ID>
docker rm <CONTAINER_ID>

docker ps a   # просмотрим список активных контейнеров 
docker stop aa1463167766   # остановим активный контейнер
docker rm aa1463167766     # удалим контейнер
docker rm bb597feb7fbe     # удалим второй контейнер

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

docker run -it --rm ubuntu:18.10 /bin/bash

Что такое DockerFile?

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

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

К примеру, если вы разрабатывали приложение на php7.2, и использовали ElasticSearch 9 версии, и сохранили это в Dockerfile-е, то другие пользователи, которые запустят образ используя ваш Dockerfile, получат ту же среду с php7.2 и ElasticSearch 9.

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

Представьте, что вы играете в покемонов

Вы пытаетесь пройти первый уровень, но безрезультатно. И я, как ваш друг, хочу с этим помочь. У меня есть 2 таблетки варианта:

  1. Я дам вам файл сохранений, в котором игра ничинается со второго уровня. Всё что вам нужно — это загрузить файл.
  2. Я могу написать инструкцию, в которой опишу шаг за шагом процесс прохождения уровня. Это как рецепт, которому нужно будет следовать в точности, как описано. Эта инструкция могла бы выглядеть как-то так:

Инструкция прохождения первого уровня

  • Выбрать покемона Squirtle
  • Направляйтесь к лесу, к северу от города
  • Тренируйтесь, пока покемон не достигнет 10 уровня
  • Направляйтесь в Оловянный город
  • Подойдите к боссу, и победите его заклинанием Watergun

Что является более полезным? Я склоняюсь, что это второй вариант. Потому что он демонстрирует, как добиться желаемого состояния. Это не просто чёрный ящик, который переносит игру на второй уровень.

С докером вы так же имеете два варианта при создании образа:

  1. Вы можете запаковать ваш контейнер, создать из него образ (аналогично тому, что вы запишите на диск новую игру с собственными модификациями). Это похоже на способ, когда вы делитесь сохранениями напрямую.
  2. Или же, можно описать Dockerfile — подробную инструкцию, которая приведёт среду к нужному состоянию.

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

Пришло время попрактиковаться на реальном примере. Для начала, создадим файл cli.php в корне проекта с содержимым:

<?php
$n = $i = 5;

while ($i--) {
    echo str_repeat(' ', $i).str_repeat('* ', $n - $i)."n";
}

И файл под названием Dockerfile, с содержимым:

FROM php:7.2-cli
COPY cli.php /cli.php
RUN chmod +x /cli.php
CMD php /cli.php

Имена команд в Dockerfile (выделенные красным) — это синтаксис разметки Dockerfile. Эти команды означают:

  • FROM — это как буд-то вы выбираете движок для вашей игры (Unity, Unreal, CryEngine). Хоть вы и могли бы начать писать движок с нуля, но больше смысла было бы в использовании готового. Можно было бы использовать, к примеру, ubuntu:18.10, в нашем коде используется образ php:7.2-cli, потому весь код будет запускаться внутри образа с предустановленным php 7.2-cli.
  • COPY — Копирует файл с основной системы в контейнер (копируем файл cli.php внутрь контейнера, с одноимённым названием)
  • RUN — Выполнение shell-команды из терминала контейнера (в текущем случае, присвоим права на выполнение скрипта /cli.php
  • CMD — Выполняет эту команду каждый раз, при новом запуске контейнера

Для просмотра полного списка команд можете перейти по ссылке

При написании Dockerfile, начинать следует с наиболее актуального существующего образа, дополняя его в соответствии с потребностями вашего приложения.
К примеру, мы могли не использовать образ php:7.2-cli, а могли взять ubuntu:18.10, последовательно выполняя команды в RUN одна за одной, устанавливая нужное ПО. Однако, в этом мало смысла, когда уже есть готовые сборки.

Для создания образа из Dockerfile нужно выполнить:
docker build <DOCKERFILE_PATH> --tag <IMAGE_NAME>
<DOCKERFILE_PATH> — путь к файлу Dockerfile (. — текущая директория),
<IMAGE_NAME> — имя, под которым образ будет создан

Выполним:

docker build . --tag pyramid

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

build-image-1

После того, как команда выполнилась, мы можем обращаться к образу по его имени, которое было указано в <IMAGE_NAME>, проверим список образов: docker images
images-piramyd

Теперь, запустим контейнер из нашего образа командой docker run pyramid
run-pyramid

Круто! Shell скрипт был успешно скопирован, и выполнен благодаря указанному в Dockerfile параметру CMD.

Сначала мы скопировали файл cli.php в Docker образ, который создался с помощью Dockerfile. Для того, чтобы удостовериться в том, что файл действительно был проброшен внутрь контейнера, можно выполнить команду docker run pyramid ls, которая в списке файлов покажет и cli.php.

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

Для этого, отредактируем файл cli.php, и изменим, чтобы количество аргументов принималось из командной строки. Отредактируем вторую строку на:

$n = $i = $argv[1] ?? 5; //а было $n = $i = 5
// это значит, что мы принимаем аргумент из консоли, а если он не получен, то используем по-умолчанию 5

После чего, пересоберём образ: docker build . --tag pyramid
И запустим контейнер: docker run pyramid php /cli.php 9, получив вывод ёлки пирамиды в 9 строк
cli_arg

Почему это работает?
Когда контейнер запускается, вы можете переопределить команду записанную в Dockerfile в поле CMD.

Наша оригинальная CMD команда, записанная в Dockerfile php /cli.php — будет переопределена новой php /cli.php 9.
Но, было бы неплохо передавать этот аргумент самому контейнеру, вместо переписывания всей команды. Перепишем так, чтобы вместо команды php /cli.php 7 можно было передавать просто аргумент-число.

Для этого, дополним Dockerfile:

FROM php:7.2-cli
COPY cli.php /cli.php
RUN chmod +x /cli.php
ENTRYPOINT ["php", "/cli.php"]
## аргумент, который передаётся в командную строку
CMD  ["9"]

Мы немного поменяли формат записи. В таком случае, CMD будет добавлена к тому, что выполнится в ENTRYPOINT.

["php", "/cli.php"] на самом деле запускается, как php /cli.php. И, учитывая то, что CMD будет добавлена после выполнения текущей, то итоговая команда будет выглядеть как: php /cli.php 9 — и пользователь сможет переопределить этот аргумент, передавая его в командную строку, во время запуска контейнера.

Теперь, заново пересоберём образ

docker build . --tag pyramid

И запустим контейнер с желаемым аргументом

docker run pyramid 3

result-3

Монтирование локальной директории в Docker-контейнер

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

Когда игра читает файлы сохранений, файловая система Game Cube внедряет их в текущий сеанс игры (представим это, даже если это не так). Игра может изменять файл сохранений, и это изменение отразится на файловой системе Game Cube, т.е. возникает двусторонняя связь.

Монтирование директории в контейнер позволяет ему читать и писать данные в эту директорию, изменяя её состояние.

Для того, чтобы смонтировать папку из основной системы в контейнер, можно воспользоваться командой
docker run -v <DIRECTORY>:<CONTAINER_DIRECTORY> ...,
где DIRECTORY — это путь к папке, которую нужно смонтировать,
CONTAINER_DIRECTORY — путь внутри контейнера.

Только путь к монтируемой папке должен быть прописан полностью: C:projectsdocker-example, или на *nix-системах можно воспользоваться конструкцией $(pwd)

Выполним команду:

docker run -it -v C:projectsdocker-examplecli:/mounted  ubuntu:18.10 /bin/bash
ls
ls mounted
touch mounted/testfile

При выполнении этой команды, указанная папка смонтируется в папку /mounted, внутри файловой системы контейнера, а команда touch mounted/testfile создаст новый файл под названием testfile, который вы можете увидеть из основной ОС.

Теперь вы можете увидеть, что после выполнения этой команды в текущей директории появился новый файл testfile. И это говорит о том, что двусторонняя связь работает — при изменении директории на основной ОС всё отразится на смонтированную папку внутри контейнера, а при изменениях изнутри контейнера всё отразится на основную ОС.

Монтирование папки позволяет вам изменять файлы вашей основной системы прямо во время работы внутри Docker контейнера.

Это удобная особенность, которая позволяет нам редактировать код в редакторе на основной ОС, а изменения будут сразу же применяться внутри контейнера.

Что такое Docker Volumes?

Docker Volumes — что-то похоже на карты памяти для Game Cube. Эта карта памяти содержит данные для игры. Эти карты съемные, и могу работать, когда Gamecube приставка выключается. Вы так же можете подключить различные карты памяти, содержащие разные данные, а так же, подключать к разным приставкам.

Вы можете вставить вашу карту внутрь приставки, точно так же, как и Docker Volume может быть прикреплён к любому из контейнеров.

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

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

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

Порты контейнеров

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

Для этого, на по-быстрому, запустим Docker-образ php-apache, который работает на 80 порту.

Для начала, создадим новую папку apache (перейдём в неё cd apache), в которой создадим файл index.php, на основе которого мы и поймём, что всё работает.

<?php
echo 'Hello from apache. We have PHP version = ' . phpversion() . PHP_EOL;

А так же, в этой папке создадим файл Dockerfile:

FROM php:7.2-apache
# Указываем рабочую папку
WORKDIR /var/www/html
# Копируем все файлы проекта в контейнер
COPY . /var/www/html
EXPOSE 80

Пробежимся по командам:
FROM: это вам уже знакомо, это образ с уже установленным php и apache
WORKDIR: создаст папку если она не создана, и перейдёт в неё. Аналогично выполнению команд mkdir /var/www/html && cd /var/www/html
EXPOSE: Apache по-умолчанию запускается на 80 порту, попробуем «прокинуть» его в нашу основную ОС (посмотрим как это работает через несколько секунд)

Для работы с сетью в Docker, нужно проделать 2 шага:

  • Прокинуть системный порт (Expose).
  • Привязать порт основной ОС к порту контейнера (выполнить соответствие).

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

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

EXPOSE в Докерфайле разрешает подключение к 80 порту контейнера — как разрешение HDMI подключения к PS4.

Выполним первый шаг прокидывания порт. Сбилдим контейнер:

docker build . --tag own_php_apache

И после этого, запустим контейнер:

docker run own_php_apache

apache_call

После чего, попробуем перейти по адресу localhost:80

Но, это не сработало, потому что мы ещё не выполнили 2 шаг по маппингу портов.

Выйдите из контейнера, нажав CTRL+C.

Если у вас проблемы с остановкой контейнера, в новом окне откройте терминал, выполните docker ps, найдите ID контейнера, который сейчас запущен, и выполните docker stop {CONTAINER_ID} (указав ваш ID контейнера)

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

docker run -p <HOST_PORT>:<CONTAINER_PORT>

И мы можем указать любое соответствие портов, но сейчас просто укажем, что порт системы 80 будет слушать 80 порт контейнера:

docker run -p 80:80 own_php_apache

Здесь, вы уже наверное заметили, что добавился новый параметр -p 80:80, который говорит Docker-у: я хочу, чтобы порт 80 из apache был привязан к моему локальному порту 80.

И теперь, если перейти по адресу localhost:80, то должны увидеть успешный ответ: apache_result

Оказывается, это даже легче, чем подключение HDMI-кабеля. Сейчас, можем попробовать выполнить запуск на разных портах:

docker run -p 8080:80 own_php_apache

apache_result_8080

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

docker ps
docker stop <CONTAINER_ID> ...
docker rm <CONTAINER_ID> ...

Для *nix пользователей есть небольшой хак, который позволит остановить и удалить все контейнеры Docker:

docker stop $(docker ps -a -q)   # Остановит все контейнеры
docker rm $(docker ps -a -q)     # Удалит все остановленные контейнеры

Запомните, что любой, кто будет запускать этот код на своём компьютере, не должен иметь установленный PHP, всё что ему нужно — один только Docker.

Docker образ: прослойка данных и кеширование

Docker умнее, чем вы могли бы подумать :).

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

Каждая команда в Dockerfile сохраняется как отельный слой образа.

Рассмотрим это на примере нашего прошлого Dockerfile-а:

FROM php:7.2-apache
# Копирует код ядра
COPY . /var/www/html
WORKDIR /var/www/html
EXPOSE 80

Когда вы пишите свой Dockerfile, вы добавляете слои поверх существующего основного образа (указанного в FROM), и создаёте свой собственный образ (Image).

FROM: говорит Докеру взять за основу этот существующий образ. А все новые команды будут добавлены слоями поверх этого основного образа.
COPY: копирует файлы с основной ОС в образ
WORKDIR: устанавливает текущую папку образа в /var/www/html

Слой Образа Докера это как точка сохранения в игре Super Mario. Если вы хотите изменить какие-то вещи, произошедшие до этой точки сохранения, то вам придётся перезапустить этот уровень полностью. Если вы хотите продолжить прогресс прохождения, вы можете начать с того места, где остановились.

Docker начинает кешировать с «того места, где остановился» во время билдинга Dockerfile. Если в Докерфайле не было никаких изменений с момента последнего билдинга, то образ будет взят полностью из кеша. Если же вы измените какую-то строку в Dockerfile — кеш будет взят только тех слоёв команд, которые находятся выше изменённой команды.

Для иллюстрации этого, добавим новые строки в Dockerfile:

FROM php:7.2-apache
WORKDIR /var/www/html
# Copy the app code
COPY . /var/www/html
RUN apt-get update && apt-get upgrade -y && apt-get install -y curl php7.2-mbstring php7.2-zip php7.2-intl php7.2-xml php7.2-json php7.2-curl
RUN echo "Hello, Docker Tutorial"
EXPOSE 80

После чего, пересоберём образ:

docker build . --tag own_php_apache

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

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

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

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

Какие выводы из этого можно сделать:

  1. Команды, которые вероятнее всего не будут меняться в будущем, нужно помещать как можно выше в Dockerfile.
  2. Команды копирования данных нужно помещать ниже, потому что файлы при разработке изменяются довольно часто.
  3. Команды, которые требуют много времени на билдинг, нужно помещать выше.

В заключение, так же хочу сказать, как можно уменьшить размер слоёв Docker образов.
В Dockerfile вы можете иметь несколько команд (RUN) на выполнение:

RUN apt-get update
RUN apt-get install -y wget
RUN apt-get install -y curl

В результате выполнения этой команды, будет создано 3 разных слоя в образе. Вместо этого, все команды стараются объединить в одну строку:

RUN apt-get update && apt-get install -y wget curl

Если команда становится длинной, и нечитаемой, то для переноса на следующую строку делаем так:

RUN apt-get update && apt-get install -y wget curl && 
    && apt-get clean -y 
    && docker-php-ext-install soap mcrypt pdo_mysql zip bcmath

Если же команда становится слишком большой, и неудобной для чтения, то можно создать новый shell скрипт, в который поместить длинную команду, и запускать этот скрипт одной простой командой RUN.

Технически, только команды ADD, COPY, и RUN создают новый слой в Docker образе, остальные команды кешируются по-другому

Что такое Docker-Compose?

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

Docker Compose управляет контейнерами, запускает их вместе, в нужной последовательности, необходимой для вашего приложения.
Его можно назвать дирижёром в мире Docker-а.

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

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

Docker-compose написан в формате YAML который по своей сути похож на JSON или XML. Но YAML имеет более удобный формат для его чтения, чем вышеперечисленные. В формате YAML имеют значения пробелы и табуляции, именно пробелами отделяются названия параметров от их значений.

Создадим новый файл docker-compose.yml, для рассмотрения синтаксиса Docker Compose:

version: '3'

services:
  app:
    build:
      context: .
    ports:
      - 8080:80

Теперь, построчно разберёмся с заданными параметрами, и что они значат:
version: какая версия docker-compose используется (3 версия — самая последняя на даный момент).
services: контейнеры которые мы хотим запустить.
app: имя сервиса, может быть любым, но желательно, чтобы оно описывало суть этого контейнера.
build: шаги, описывающие процесс билдинга.
context: где находится Dockerfile, из которого будем билдить образ для контейнера.
ports: маппинг портов основной ОС к контейнеру.

Мы можем использовать этот файл для билдинга нашего предыдущего образа apache:

docker-compose build

После выполнения этой команды, Docker спарсит файл docker-compose и создаст описанные сервисы на основе инструкций во вкладке build.

А context говорит о том, из какой директории мы берём Dockerfile для создания образа сервиса (в текущем случае — это означает текущую директорию ., но могло быть и /php-cli, /nginx, и т.д.).

И теперь, запустим эти сервисы, которые создали:

docker-compose up

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

Теперь, отключитесь от консоли, нажав CTRL+C.

Когда контейнер под названием app запускается, docker-compose автоматически связывает указанные порты во вкладке ports. Вместо того, как мы делали ранее, выполняя -v 8080:80, docker-compose делает это за нас, получив информацию параметра ports. Docker-compose избавляет нас боли, связанной с указанием параметров в командной строке напрямую.

С docker-compose.yml мы переносим все параметры, ранее записываемые в командной строке при запуске контейнера в конфигурационный YAML файл.

В этом примере мы записали BUILD и RUN шаги для нашего сервиса в docker-compose.yml. И преимущество такого подхода ещё в том, что теперь для билдинга и для запуска этих сервисов нам нужно запомнить только 2 команды: docker-compose build и docker-compose up. При таком подходе не нужно помнить, какие аргументы нужно указывать, какие опции нужно задавать при запуске контейнера.

Теперь давайте сделаем нашу разработку немного легче. Сделаем, чтобы вместо постоянного перестроения образа при каждом изменении файлов, мы можем примонтировать нашу рабочую папку в контейнер.
Для этого, удалите строку COPY . /var/www/html с Dockerfile — теперь все файлы будут прокинуты из основной ОС. Новый Dockerfile будет иметь вид:

FROM php:7.2-apache
WORKDIR /var/www/html
RUN apt-get update && apt-get install -y wget
EXPOSE 80

Ранее мы рассматривали, как примонтировать папку в контейнер, для этого мы запускали контейнер с аргументом -v <HOST_DIRECTORY>:<CONTAINER_DIRECTORY>.
С Docker-compose мы можем указать напрямую в docker-compose.yml:

version: '3'
services:
  app:
    build:
      context: .
    ports:
      - 8080:80
    volumes:
      - .:/var/www/html

Добавленная строка примонтирует текущую директорию основой операционной системы к директории /var/www/html контейнера.

В отличие от указания путь в консоли, здесь можно указывать относительный путь (.), не обязательно указывать полный путь (C:projectsdocker-exampleapache), как было ранее при ручном запуске контейнера.

Теперь, выполните по очереди команды:

docker-compose stop
docker-compose rm

При удалении, вас спросят, действительно ли удалять, напишите y и нажмите кнопку enter. Эти команды остановят и удалят все контейнеры, описанные в файле docker-compose.yml (то же самое, как мы ранее запускали docker stop <CONTAINER_ID> и docker rm <CONTAINER_ID>)

Теперь перебилдим сервисы, потому что мы изменили Dockerfile:

docker-compose build

И заново запустим:

docker-compose up

И опять, по адресу localhost:8080 поднимется наш сервер.

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

Чтобы в этом убедиться, изменим файл index.php, добавим в него скрипт нами любимой пирамиды:

<?php
$n = $i = $_GET['count'] ?? 4;
echo '<pre>';
while ($i--) {
    echo str_repeat(' ', $i).str_repeat('* ', $n - $i)."n";
}
echo '</pre>';

И теперь, если перейти по адресу localhost:8080?count=10, то увидим, что пирамида выводится: apache_index_count

Монтирование вашей локальной папки как Docker Volume это основной метод как разрабатывать приложения в контейнере.

Так же, как мы ранее выполняли команды внутри контейнера, указывая аргументы -it ... /bin/bash. Docker-compose так же предоставляет интерфейс по удобному выполнению команд внутри конкретного контейнера. Для этого нужно выполнить команду:

docker-compose exec {CONTAINER_NAME} {COMMAND}

где, вместо {CONTAINER_NAME} нужно записать имя контейнера, под которым он записан в сервисах;
а вместо {COMMAND} — желаемую команду.

К примеру, эта команда может выглядеть так:

docker-compose exec php-cli php -v

Но, сделаем это на основе текущего Dockerfile:

docker-compose down # остановим контейнеры
docker-compose up -d # здесь используется опция -d которая сообщает, что контейнер должен висеть в режиме демона
docker-compose exec app apache2 -v

И в результате должны получить exec

Пока что, в docker-compose.yml описан только один сервис, потому разворачиваем мы только один контейнер. Но реальный файл docker-compose выглядит больше. К примеру, для Laravel он такой:

version: '3'
services:
  nginx:
    build:
      context: ./
      dockerfile: docker/nginx.docker
    volumes:
      - ./:/var/www
    ports:
      - "8080:80"
    depends_on:
      - php-fpm
  php-fpm:
    build:
      context: ./
      dockerfile: docker/php-fpm.docker
    volumes:
      - ./:/var/www
    depends_on:
      - mysql
      - redis
    environment:
      - "DB_PORT=3306"
      - "DB_HOST=mysql"
      - "REDIS_PORT=6379"
      - "REDIS_HOST=redis"
  php-cli:
    build:
      context: ./
      dockerfile: docker/php-cli.docker
    volumes:
      - ./:/var/www
    depends_on:
      - mysql
      - redis
    environment:
      - "DB_PORT=3306"
      - "DB_HOST=mysql"
      - "REDIS_PORT=6379"
      - "REDIS_HOST=redis"
    tty: true
  mysql:
    image: mysql:5.7
    volumes:
      - ./storage/docker/mysql:/var/lib/mysql
    environment:
      - "MYSQL_ROOT_PASSWORD=secret"
      - "MYSQL_USER=app"
      - "MYSQL_PASSWORD=secret"
      - "MYSQL_DATABASE=app"
    ports:
      - "33061:3306"
  redis:
    image: redis:3.0
    ports:
      - "6379:6379"

И при выполнении одной только команды docker-compose up, поднимутся 5 сервисов. В сравнении с тем, что мы бы вручную выполняли 5 раз команду docker run .... Так что, использование docker-compose в этом случае — очевидно. Так что, теперь, ещё один шаг позадчи, теперь вы знаете, что такое docker и docker compose, и для чего нужен каждый из них.

Как писать Микро Сервисы с Docker? Что такое микросервисы?

Docker найболее часто используемый инструмент для написания Микросервисов. Микросервисы — это архитектурный шаблон проектирования который следует философии «разделения ответственности».

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

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

Идея разделения по ответственности предоставляет преимущество в том, что команда разработчиков может работать параллельно, фокусируясь над разными компонентами.

В основном, микросервисы имеют канал коммуникации мужду собой, в виде REST API, который возвращает данные в JSON, или что-то типа того.

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

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

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

Резюме

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

Docker является open source инструментом, который автоматизирует разворачивание приложения внутри программного контейнера. Мы перевели для вас руководство по работе с Docker для новичков.

Простейший способ понять идею Docker — это сравнить его с обычным контейнером для транспортировки. Когда-то давно компании, занимающиеся транспортировкой, столкнулись со следующими проблемами:

  • Как перевозить разные (несовместимые) друг с другом типы грузов, например, еду и химикаты или стекло и кирпичи?
  • Как работать с упаковками различных размеров, используя один и тот же транспорт?

С тех пор, как появились контейнеры, стало возможным помещать кирпичи поверх стекла, а химикаты — хранить рядом с едой. Груз различных размеров может быть помещен в стандартный контейнер, который загружен/разгружен одним и тем же транспортом.

Но вернемся к контейнерам в программной разработке.

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

В чем отличие от виртуализации?

Традиционно виртуальные машины используются, чтобы избежать неожиданного поведения. Главная проблема с ними заключается в том, что «дополнительная ОС» поверх основной ОС добавляет к проекту гигабайты места. Большую часть времени ваш сервер будет держать несколько виртуальных машин, что займет ещё больше пространства. Другой значительный недостаток — медленная загрузка.

Docker устраняет эти проблемы разделением ядра между всеми контейнерами, которые работают как отдельные процессы основной ОС.

Надо понимать, что Docker — не первая и не единственная платформа, основанная на контейнерах. Однако на данный момент он является самым большим и самым мощным инструментом на рынке.

Docker

Список преимуществ таков:

  • Быстрый процесс разработки. Нет нужды в установке сторонних программ вроде PostgreSQL, Redis, Elasticsearch. Они могут быть запущены в контейнерах.
  • Удобная инкапсуляция приложений. Вы можете предоставить свое приложение как единое целое.
  • Одинаковое поведение на локальном компьютере и тестовом/производственном сервере.
  • Простой и понятный мониторинг.
  • Легко масштабируется. Если вы сделали свое приложение правильно, то оно будет готово к масштабированию не только в Docker.

Поддерживаемые платформы

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

Более того, есть много дополнительных приложений, таких как Kitematic или Docker Machine, которые помогут установить Docker и управлять им на отличных от Linux платформах.

Установка

Вы можете посмотреть инструкцию по установке на официальном сайте. Если вы запустили Docker на Linux, вам следует выполнить все последующие команды из-под root или добавить своего пользователя в группу docker и перелогиниться:

sudo usermod -aG docker $(whoami)

Терминология

  • Контейнер — текущий экземпляр, который инкапсулирует необходимое ПО. Контейнеры всегда создаются из образов. Могут открывать порты и тома для взаимодействия с другими контейнерами и/или внешним миром, а также легко удаляются и пересоздаются заново за короткий промежуток времени.
  • Образ — основной элемент для каждого контейнера. После его создания каждый шаг кэшируется и может быть использован повторно (модель копирования при записи). В зависимости от образа может понадобиться некоторое время для его построения. Из них могут быть сразу запущены контейнеры.
  • Порт — TCP/UDP-порт в своем обычном представлении. Порты могут быть открыты для внешнего мира (доступы через основную ОС) или подсоединены к другим контейнерам — доступны только из тех контейнеров и невидимы для внешнего мира.
  • Том — может быть описан как общая папка. Тома инициализируются при создании контейнера. Они спроектированы для хранения данных независимо от жизненного цикла контейнера.
  • Реестр — сервер, который хранит образы Docker. Функционирует по аналогии с Github — можно вытянуть образ, чтобы развернуть его локально, а затем закинуть обратно в реестр.
  • Docker Hub — реестр с веб-интерфейсом, созданный Docker Inc. Он хранит большое количество Docker-образов с разным ПО. Docker Hub является источником «официальных» Docker-образов, созданных командой Docker. Официальные образы содержат списки своих потенциальных уязвимостей. Эта информация доступна для каждого авторизированного пользователя. Доступны два типа аккаунтов: бесплатные и платные. На бесплатном аккаунте может быть один приватный образ и бесконечное количество общедоступных образов.

Docker

Пример 1: Hello world

Настало время запустить ваш первый контейнер:

docker run ubuntu /bin/echo 'Hello world'

Вывод консоли:

Unable to find image 'ubuntu:latest' locally  
latest: Pulling from library/ubuntu  
d54efb8db41d: Pull complete  
f8b845f45a87: Pull complete  
e8db7bf7c39f: Pull complete  
9654c40e9079: Pull complete  
6d9ef359eaaa: Pull complete  
Digest: sha256:dd7808d8792c9841d0b460122f1acf0a2dd1f56404f8d1e56298048885e45535  
Status: Downloaded newer image for ubuntu:latest  
Hello world
  • docker run — команда, запускающая контейнер.
  • ubuntu — образ, который вы запускаете, например, образ ОС Ubuntu. Когда вы определяете его, Docker начинает поиск на Docker-хосте. Если образ не существует локально, тогда он будет вытянут из общественного реестра — Docker Hub.
  • /bin/echo 'Hello world' — команда, которая будет запущена внутри нового контейнера. Данный контейнер просто выведет «Hello world» и остановит выполнение.

Давайте попробуем создать интерактивную оболочку внутри Docker-контейнера:

docker run -i -t –rm ubuntu /bin/bash
  • -t — флаг, добавляющий псевдотерминал внутри нового контейнера.
  • -i — флаг, который позволяет установить интерактивное соединение, взяв стандартный ввод (STDIN) контейнера.
  • --rm — флаг, автоматически удаляющий контейнер после завершения процесса. По умолчанию контейнеры не удаляются. Этот контейнер существует, пока установлена текущая сессия оболочки, и уничтожается, когда мы покидаем сессию (например, SSH-сессию с удаленным сервером).

Если вы хотите оставить контейнер работающим после завершения сессии, превратите его в демон-процесс:

docker run –name daemon ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
  • --name daemon — устанавливает имя демона для нового контейнера. Если вы не определите имя, то Docker сгенерирует и присвоит его автоматически.
  • -d — флаг запускает контейнер в фоновом режиме (демонизирует его).

Давайте взглянем, какие контейнеры у нас есть на текущий момент:

docker ps -a

Вывод консоли:

CONTAINER ID  IMAGE   COMMAND                 CREATED             STATUS                         PORTS  NAMES  
1fc8cee64ec2  ubuntu  "/bin/sh -c 'while..."  32 seconds ago      Up 30 seconds                         daemon  
c006f1a02edf  ubuntu  "/bin/echo 'Hello ..."  About a minute ago  Exited (0) About a minute ago         gifted_nobel
  • docker ps — команда для вывода списка контейнеров.
  • -a — флаг, который показывает все контейнеры (без этого флага ps выведет только работающие контейнеры).

Команда ps показала, что у нас всего два контейнера:

  • gifted_nobel (имя для этого контейнера сгенерировано автоматически, оно будет другим на вашем компьютере) — это первый контейнер, который мы создали, выводит один раз «Hello world».
  • daemon — это третий контейнер, который мы создали, работает как фоновый процесс.

Замечание: здесь нет второго контейнера (с интерактивной оболочкой), потому что мы установили флаг --rm. Как следствие, этот контейнер автоматически удалился сразу после выполнения.

Давайте проверим логи и посмотрим, что фоновый контейнер делает прямо сейчас:

docker logs -f daemon

Вывод консоли:

. . .
hello world
hello world
hello world
  • docker logs — объединяет логи контейнера.
  • -f — флаг для слежения за выводом логов (работает так же, как tail -f).

Теперь остановим фоновый контейнер:

docker stop daemon

Удостоверимся, что контейнер остановился:

docker ps -a

Вывод консоли:

CONTAINER ID  IMAGE   COMMAND                 CREATED        STATUS                      PORTS  NAMES  
1fc8cee64ec2  ubuntu  "/bin/sh -c 'while..."  5 minutes ago  Exited (137) 5 seconds ago         daemon  
c006f1a02edf  ubuntu  "/bin/echo 'Hello ..."  6 minutes ago  Exited (0) 6 minutes ago           gifted_nobel

Контейнер остановился. Мы можем запустить его заново:

docker start daemon

Удостоверимся, что он работает:

docker ps -a

Вывод консоли:

ONTAINER ID  IMAGE   COMMAND                 CREATED        STATUS                    PORTS  NAMES  
1fc8cee64ec2  ubuntu  "/bin/sh -c 'while..."  5 minutes ago  Up 3 seconds                     daemon  
c006f1a02edf  ubuntu  "/bin/echo 'Hello ..."  6 minutes ago  Exited (0) 7 minutes ago         gifted_nobel

Теперь остановим его опять и удалим все контейнеры вручную:

docker stop daemon
docker rm <your first container name>
docker rm daemon

Чтобы удалить все контейнеры, мы можем использовать следующую команду:

docker rm -f $(docker ps -aq)
  • docker rm — команда для удаления контейнера.
  • -f — флаг для rm, который останавливает контейнер, если он работает (принудительное удаление).
  • -q — флаг для ps, который выводит только ID контейнеров.

Пример 2: Nginx

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

Настало время создать и запустить более полезный контейнер типа Nginx.

Сменим директорию на examples/nginx:

docker run -d --name test-nginx -p 80:80 -v $(pwd):/usr/share/nginx/html:ro nginx:latest

Вывод консоли:

Unable to find image 'nginx:latest' locally  
latest: Pulling from library/nginx  
693502eb7dfb: Pull complete  
6decb850d2bc: Pull complete  
c3e19f087ed6: Pull complete  
Digest: sha256:52a189e49c0c797cfc5cbfe578c68c225d160fb13a42954144b29af3fe4fe335  
Status: Downloaded newer image for nginx:latest  
436a602273b0ca687c61cc843ab28163c720a1810b09005a36ea06f005b0c971
  • -p — преобразование портов HOST PORT:CONTAINER PORT.
  • -v — монтирование тома HOST DIRECTORY:CONTAINER DIRECTORY.

Важно: команда run принимает только абсолютные пути. В нашем примере мы использовали $(pwd), чтобы установить абсолютный путь текущей директории.

Теперь вы можете открыть localhost в вашем браузере.

Или можно изменить /example/nginx/index.html (который смонтирован как том в /usr/share/nginx/html внутри контейнера) и обновить страницу.

Получим информацию о контейнере test-nginx:

docker inspect test-nginx

Эта команда показывает широкую системную информацию об установке Docker. Эта информация включает в себя версию ядра, количество контейнеров и образов, открытые порты, смонтированные тома и так далее.

Пример 3: написание Dockerfile

Чтобы создать Docker-образ, вам необходимо создать Dockerfile. Это просто текстовый файл с инструкциями и аргументами. Вот описание инструкций, которые мы будем использовать в нашем следующем примере:

  • FROM — устанавливает базовый образ.
  • RUN — выполняет команду в контейнере.
  • ENV — устанавливает переменную окружения.
  • WORKDIR — устанавливает рабочую директорию.
  • VOLUME — создает точку монтирования для тома.
  • CMD — устанавливает исполняемый файл для контейнера.

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

Теперь создадим образ, который будет получать содержимое сайта с помощью curl и записывать его в текстовый файл. Нам надо передать URL сайта через переменную окружения SITE_URL. Результирующий файл будет помещен в директорию, смонтированную в качестве тома:

FROM ubuntu:latest  
RUN apt-get update  
RUN apt-get install --no-install-recommends --no-install-suggests -y curl  
ENV SITE_URL https://google.com/  
WORKDIR /data  
VOLUME /data  
CMD sh -c "curl -L $SITE_URL > /data/results"

Dockerfile готов. Теперь настало время создать сам образ.

Перейдем в examples/curl и выполним следующую команду для создания образа:

docker build . -t test-curl

Вывод консоли:

Sending build context to Docker daemon 3.584 kB  
Step 1/7 : FROM ubuntu:latest  
 ---> 0ef2e08ed3fa
Step 2/7 : RUN apt-get update  
 ---> Running in 4aa839bb46ec
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]  
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [102 kB]  
...
Fetched 24.9 MB in 4s (5208 kB/s)  
Reading package lists...  
 ---> 35ac5017c794
Removing intermediate container 4aa839bb46ec  
Step 3/7 : RUN apt-get install --no-install-recommends --no-install-suggests -y curl  
 ---> Running in 3ca9384ecf8d
Reading package lists...  
Building dependency tree...  
Reading state information...  
The following additional packages will be installed...  
 ---> f3c6d26b95e6
Removing intermediate container 3ca9384ecf8d  
Step 4/7 : ENV SITE_URL https://google.com/  
 ---> Running in 21b0022b260f
 ---> 9a733ee39a46
Removing intermediate container 21b0022b260f  
Step 5/7 : WORKDIR /data  
 ---> c024301ddfb8
Removing intermediate container 3bc973e5584c  
Step 6/7 : VOLUME /data  
 ---> Running in a9594a8958fe
 ---> 6802707a7114
Removing intermediate container a9594a8958fe  
Step 7/7 : CMD sh -c "curl -L $SITE_URL > /data/results"  
 ---> Running in 37503bc4e386
 ---> 5ebb2a65d771
Removing intermediate container 37503bc4e386  
Successfully built 5ebb2a65d771
  • docker bulid — создает новый локальный образ.
  • -t — устанавливает именную метку для образа.

Теперь у нас есть новый образ, и мы можем посмотреть список существующих:

docker images

Вывод консоли:

REPOSITORY  TAG     IMAGE ID      CREATED         SIZE  
test-curl   latest  5ebb2a65d771  37 minutes ago  180 MB  
nginx       latest  6b914bbcb89e  7 days ago      182 MB  
ubuntu      latest  0ef2e08ed3fa  8 days ago      130 MB

Мы можем создать и запустить контейнер из образа. Попробуем сделать это со стандартными параметрами:

docker run --rm -v $(pwd)/vol:/data/:rw test-curl

Чтобы увидеть результаты, сохраненные в файле, выполним команду:

cat ./vol/results

Попробуем с facebook.com:

docker run --rm -e SITE_URL=https://facebook.com/ -v $(pwd)/vol:/data/:rw test-curl

И снова посмотрим результаты:

cat ./vol/results

Пример 4: связь контейнеров Python + Redis

Docker compose — единственный правильный способ соединять контейнеры друг с другом. В этом примере мы соединим контейнеры Python и Redis:

version: '2'  
services:  
  app:
    build:
      context: ./app
    depends_on:
      - redis
    environment:
      - REDIS_HOST=redis
    ports:
      - "5000:5000"
  redis:
    image: redis:3.2-alpine
    volumes:
      - redis_data:/data
volumes:  
  redis_data:
Go to example

Перейдем в examples/compose и выполним следующую команду:

docker-compose --project-name app-test -f docker-compose.yml up

Вывод консоли:

Creating network "apptest_default" with the default driver  
Creating volume "apptest_redis_data" with default driver  
Pulling redis (redis:3.2-alpine)...  
3.2-alpine: Pulling from library/redis  
627beaf3eaaf: Pull complete  
a503a4771a4a: Pull complete  
72c5d910c683: Pull complete  
6aadd3a49c30: Pull complete  
adf925aa1ad1: Pull complete  
0565da0f872e: Pull complete  
Digest: sha256:9cd405cd1ec1410eaab064a1383d0d8854d1eef74a54e1e4a92fb4ec7bdc3ee7  
Status: Downloaded newer image for redis:3.2-alpine  
Building app  
Step 1/9 : FROM python:3.5.2-alpine  
3.5.2-alpine: Pulling from library/python  
b7f33cc0b48e: Pull complete  
8eda8bb6fee4: Pull complete  
4613e2ad30ef: Pull complete  
f344c00ca799: Pull complete  
Digest: sha256:8efcb12747ff958de32b32424813708f949c472ae48ca28691078475b3373e7c  
Status: Downloaded newer image for python:3.5.2-alpine  
 ---> e70a322afafb
Step 2/9 : ENV BIND_PORT 5000  
 ---> Running in 8518936700b3
 ---> 0f652cdd2cee
Removing intermediate container 8518936700b3  
Step 3/9 : ENV REDIS_HOST localhost  
 ---> Running in 027286e90699
 ---> 6da3674f79fa
Removing intermediate container 027286e90699  
Step 4/9 : ENV REDIS_PORT 6379  
 ---> Running in 0ef17cb512ed
 ---> c4c514aa3008
Removing intermediate container 0ef17cb512ed  
Step 5/9 : COPY ./requirements.txt /requirements.txt  
 ---> fd523d64faae
Removing intermediate container 8c94c82e0aa8  
Step 6/9 : COPY ./app.py /app.py  
 ---> be61f59b3cd5
Removing intermediate container 93e38cd0b487  
Step 7/9 : RUN pip install -r /requirements.txt  
 ---> Running in 49aabce07bbd
Collecting flask==0.12 (from -r /requirements.txt (line 1))  
  Downloading Flask-0.12-py2.py3-none-any.whl (82kB)
Collecting redis==2.10.5 (from -r /requirements.txt (line 2))  
  Downloading redis-2.10.5-py2.py3-none-any.whl (60kB)
Collecting itsdangerous>=0.21 (from flask==0.12->-r /requirements.txt (line 1))  
  Downloading itsdangerous-0.24.tar.gz (46kB)
Collecting Werkzeug>=0.7 (from flask==0.12->-r /requirements.txt (line 1))  
  Downloading Werkzeug-0.11.15-py2.py3-none-any.whl (307kB)
Collecting Jinja2>=2.4 (from flask==0.12->-r /requirements.txt (line 1))  
  Downloading Jinja2-2.9.5-py2.py3-none-any.whl (340kB)
Collecting click>=2.0 (from flask==0.12->-r /requirements.txt (line 1))  
  Downloading click-6.7-py2.py3-none-any.whl (71kB)
Collecting MarkupSafe>=0.23 (from Jinja2>=2.4->flask==0.12->-r /requirements.txt (line 1))  
  Downloading MarkupSafe-1.0.tar.gz
Installing collected packages: itsdangerous, Werkzeug, MarkupSafe, Jinja2, click, flask, redis  
  Running setup.py install for itsdangerous: started
    Running setup.py install for itsdangerous: finished with status 'done'
  Running setup.py install for MarkupSafe: started
    Running setup.py install for MarkupSafe: finished with status 'done'
Successfully installed Jinja2-2.9.5 MarkupSafe-1.0 Werkzeug-0.11.15 click-6.7 flask-0.12 itsdangerous-0.24 redis-2.10.5  
 ---> 18c5d1bc8804
Removing intermediate container 49aabce07bbd  
Step 8/9 : EXPOSE $BIND_PORT  
 ---> Running in f277fa7dfcd5
 ---> 9f9bec2abf2e
Removing intermediate container f277fa7dfcd5  
Step 9/9 : CMD python /app.py  
 ---> Running in a2babc256093
 ---> 2dcc3b299859
Removing intermediate container a2babc256093  
Successfully built 2dcc3b299859  
WARNING: Image for service app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.  
Creating apptest_redis_1  
Creating apptest_app_1  
Attaching to apptest_redis_1, apptest_app_1  
redis_1  | 1:C 08 Mar 09:56:55.765 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf  
redis_1  |                 _._  
redis_1  |            _.-``__ ''-._  
redis_1  |       _.-``    `.  `_.  ''-._           Redis 3.2.8 (00000000/0) 64 bit  
redis_1  |   .-`` .-```.  ```/    _.,_ ''-._  
redis_1  |  (    '      ,       .-`  | `,    )     Running in standalone mode  
redis_1  |  |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379  
redis_1  |  |    `-._   `._    /     _.-'    |     PID: 1  
redis_1  |   `-._    `-._  `-./  _.-'    _.-'  
redis_1  |  |`-._`-._    `-.__.-'    _.-'_.-'|  
redis_1  |  |    `-._`-._        _.-'_.-'    |           http://redis.io  
redis_1  |   `-._    `-._`-.__.-'_.-'    _.-'  
redis_1  |  |`-._`-._    `-.__.-'    _.-'_.-'|  
redis_1  |  |    `-._`-._        _.-'_.-'    |  
redis_1  |   `-._    `-._`-.__.-'_.-'    _.-'  
redis_1  |       `-._    `-.__.-'    _.-'  
redis_1  |           `-._        _.-'  
redis_1  |               `-.__.-'  
redis_1  |  
redis_1  | 1:M 08 Mar 09:56:55.767 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.  
redis_1  | 1:M 08 Mar 09:56:55.767 # Server started, Redis version 3.2.8  
redis_1  | 1:M 08 Mar 09:56:55.767 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.  
redis_1  | 1:M 08 Mar 09:56:55.767 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.  
redis_1  | 1:M 08 Mar 09:56:55.767 * The server is now ready to accept connections on port 6379  
app_1    |  * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit)  
app_1    |  * Restarting with stat  
app_1    |  * Debugger is active!  
app_1    |  * Debugger pin code: 299-635-701

Данный пример увеличит счетчик просмотров в Redis. Откройте localhost в браузере и проверьте.

Теперь вы можете поиграться с различными образами из Docker Hub или, если хотите, создать свои собственные образы, соблюдая лучшие практики, описанные ниже. Единственное, что можно добавить насчет использования docker-compose: всегда давайте точные имена вашим томам в docker-compose.yml (если в образе есть тома). Это простое правило спасет вас от проблем при проверке ваших томов.

version: '2'  
services:  
  ...
  redis:
    image: redis:3.2-alpine
    volumes:
      - redis_data:/data
volumes:  
  redis_data:version: '2'  
services:  
  ...
  redis:
    image: redis:3.2-alpine
    volumes:
      - redis_data:/data
volumes:  
  redis_data:

В этому случае redis_data будет именем внутри docker-compose.yml, для настоящего имени тома будет добавлен префикс с именем проекта.

Чтобы просмотреть список томов:

docker volume ls

Вывод консоли:

DRIVER              VOLUME NAME  
local               apptest_redis_data

Без точного имени тома там будет находиться UUID. Вот пример с локального компьютера:

DRIVER              VOLUME NAME  
local               ec1a5ac0a2106963c2129151b27cb032ea5bb7c4bd6fe94d9dd22d3e72b2a41b  
local               f3a664ce353ba24dd43d8f104871594de6024ed847054422bbdd362c5033fc4c  
local               f81a397776458e62022610f38a1bfe50dd388628e2badc3d3a2553bb08a5467f  
local               f84228acbf9c5c06da7be2197db37f2e3da34b7e8277942b10900f77f78c9e64  
local               f9958475a011982b4dc8d8d8209899474ea4ec2c27f68d1a430c94bcc1eb0227  
local               ff14e0e20d70aa57e62db0b813db08577703ff1405b2a90ec88f48eb4cdc7c19  
local               polls_pg_data  
local               polls_public_files  
local               polls_redis_data  
local               projectdev_pg_data  
local               projectdev_redis_data

Стайлгайды Docker

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

  • 1 приложение = 1 контейнер.
  • Запускайте процесс на переднем плане (не используйте systemd, upstart или другие похожие инструменты).
  • Для хранения данных вне контейнера используйте тома.
  • Не иcпользуйте SSH (если вам надо залезть внутрь контейнера, используйте docker exec).
  • Избегайте ручных настроек или действий внутри контейнера.
  • Включайте только необходимый контекст — используйте .dockerignore файл (как .gitignore в git).
  • Избегайте установки ненужных пакетов — это займет лишнее дисковое пространство.
  • Используйте кэш. Добавьте контекст, который часто меняется, например, исходный код вашего проекта, в конец Dockerfile — кэш Docker будет использоваться более эффективно.
  • Будьте осторожны с томами. Вы должны помнить, какие данные находятся в томах. Поскольку тома постоянны и не исчезают вместе с контейнерами, следующий контейнер будет использовать данные, которые были созданы предыдущим контейнером.
  • Используйте переменные окружения: RUN, EXPOSE, VOLUME. Это сделает ваш Dockerfile более гибким.

Заключение

В заключение стоит отметить, что Docker стал одним из необходимых инструментов разработчика, наравне с различными IDE и Git.

Перевод статьи «Here’s How You Start Using Docker»

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

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

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

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

При этом контейнеры легковесны. Они не требуют дополнительной загрузки гипервизора. Гипервизор — это гостевая операционная система вроде VMWare или VirtualBox. Так вот, для запуска контейнеров гостевая ОС не требуется, они запускаются прямо в ядре хоста.

От редакции Techrocks. Также рекомендуем статью «Контейнеры Docker: простое объяснение на примере аренды офиса».

Когда использовать Docker?

Изучение новых технологий

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

Многие проекты имеют готовые образы Docker, где продвигаемое приложение уже установлено и настроено.

Базовое использование

Если речь идет о каком-то базовом или достаточно стандартном приложении, способном работать с дефолтным образом Docker, загрузка образов из Docker Hub — тоже хорошее решение.

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

Изоляция приложений

Если вы хотите запускать несколько приложений на одном сервере, будет полезным содержать их компоненты в разных контейнерах. Это избавит вас от проблем с управлением зависимостями.

Работа в команде

«На моей машине это работает!» Мы, разработчики, знаем, что одна из самых заковыристых проблем в разработке — необходимость учитывать разные машины и платформы.

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

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

О каких бы инструментах и языках ни шла речь — вы всегда можете контейнеризовать вашу локальную среду.

Система с ограниченными ресурсами

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

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

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

Виртуальные машины vs Docker

И контейнеры Docker, и виртуальные машины — это средства для развертывания приложений в окружении, изолированном от железа хоста. Ключевая разница между ними в уровне изоляции.

Если речь идет о виртуальной машине (VM), то все, что находится внутри нее, не зависит от операционной системы хоста. VM-платформа запускает процесс (virtual machine monitor или VMM) для управления процессом виртаулизации отдельной виртуальной машины. Система хоста выделяет некоторое количество ресурсов машины для этой VM.

VM кардинально отличается тем, что при ее запуске загружается новое выделенное ядро для ее среды и запускается набор процессов операционной системы (часто довольно большой). Это делает размер виртуальной машины намного больше размера типичного контейнера, содержащего только приложение.

А вот в контейнерной среде запуска вроде Docker ваше приложение помещается в песочницу с изолированными функциями, предоставляемыми контейнером. При этом оно делит ресурсы ядра с другими приложениями в контейнерах на этом же хосте.

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

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

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

От редакции Techrocks. Также рекомендуем статью «В чём разница между Docker и Kubernetes?» и «Почему Docker на macOS намного хуже, чем на Linux?»

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

Контейнер — это запускаемый экземпляр образа. При помощи Docker API или командной строки (CLI) контейнеры можно создавать, запускать, останавливать, перемещать или удалять. Вы можете подключить контейнер к одной или нескольким сетям, добавить к нему хранилище и даже создать новый образ на основе его текущего состояния.

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

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

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

Образ — это доступный только для чтения шаблон с инструкциями по созданию контейнера Docker. Часто в основе образов лежат другие образы с дополнительными настройками.

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

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

Каждая инструкция в Dockerfile создает слой образа. Если вы измените Dockerfile и перестроите образ, изменятся только те слои, которых это будет касаться.

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

Что такое Docker Hub?

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

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

Пишем простой Dockerfile

Предположим, у нас есть простое приложение на node JS. В нем есть файл server.js, который прослушивает порт 3040 и выводит ‘Hello World!’ при переходе по адресу ‘localhost:3040/’.

Файловая структура приложения следующая:

simpleNodeApplication

  • Dockerfile
  • package-lock.json
  • package.json
  • server.js
//Initializes a new build stage and sets the Base Image for subsequent instructions
FROM node:14-alpine

//Defining working directory for our application and 
it will be the default directory for our further steps
WORKDIR /app

//Copy package.json file to our created workdir
COPY package.json .

//Does npm install
RUN npm install

//Copy entire code from local to our workdir
COPY . .

//Exposing application on port 3040
EXPOSE 3040

//Command to run our application
CMD ["node", "server.js"]

Чтобы поиграться с этим dockerfile:

  • Собрать образ => docker build -t myApp:V1 .
  • Создать контейнер и нацелить его на порт 3000 => docker run -p 3000:3040 --name myContainer myApp:V1
  • Вывести запущенные контейнеры => docker ps
  • Остановить контейнер => docker stop myContainer
  • Запустить контейнер заново => docker start myContainer

Примечание. Вероятно, вам интересно, в чем разница между RUN и CMD. Команда RUN может быть выполнена при сборке контейнера. Инструкция CMD должна использоваться для запуска программ, содержащихся в вашем образе.

Команды Docker

В этом разделе мы рассмотрим несколько консольных команд для работы с Docker. Этими командами разработчики пользуются постоянно.

Допустим, у нас есть образ с ImageId myApp:V1 и ContainerName myContainer.

Образ

docker build [OPTIONS] PATH | URL | -
docker build -t myApp:V1 .

При помощи этих команд, используя dockerfile, можно создать образ приложения. Имя образу можно задать самостоятельно, для этого нужно использовать опцию -t: docker build -t name:tag .

docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]
docker tag myApp:V1 myNewApp:V1

С помощью этой команды можно переименовать или пометить тегом уже созданный образ. Сам образ при этом не перестраивается.

docker images

Вывод всех образов, доступных локально.

docker image inspect Name|ImageId
docker image inspect myApp:V1

Вывод подробной информации об образе, по умолчанию — в формате JSON.

docker rmi [OPTIONS] IMAGE [IMAGE...]
docker rmi myApp:V1

С помощью этой команды можно удалить один или несколько образов.

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

docker system prune [OPTIONS]

Удаление всех неиспользуемых контейнеров, сетей, образов и, опционально, разделов.

Контейнеры

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
docker run -p 3000:3040 -it -rm --name myContainer myApp:V1

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

По умолчанию команда ищет образ локально, а если не находит, обращается к репозиториям.

Чтобы дать контейнеру имя, можно использовать опцию --name: docker run --name string Name|ImageId.

Для запуска приложения в интерактивном режиме (чтение входящих данных из консоли и т. п.) используйте опцию -it.

Опция -rm говорит docker удалить контейнер, как только он будет остановлен.

docker stop [OPTIONS] CONTAINER [CONTAINER...]
docker stop myContainer

С помощью этих команд можно остановить один или несколько контейнеров.

docker start [OPTIONS] CONTAINER [CONTAINER...]
docker start myContainer

Эти команды запускают остановленные контейнеры. По умолчанию это происходит в фоновом режиме.

docker restart [OPTIONS] CONTAINER [CONTAINER...]
docker restart myContainer

Перезапуск одного или нескольких контейнеров.

docker rename CONTAINER NEW_NAME
docker rename myContainer myNewContainer

Смена имени уже созданного контейнера.

docker ps [OPTIONS]

Вывод списка всех запущенных контейнеров (по умолчанию). Для вывода вообще всех контейнеров используйте опцию -a.

docker rm [OPTIONS] CONTAINER [CONTAINER...]
docker rm myContainer

С помощью этой команды можно удалить один или несколько контейнеров.

docker logs [OPTIONS] CONTAINER
docker logs myContainer

Получение логов контейнера.

docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-

Копирование файлов/папок между контейнером и локальной файловой системой. Можно использовать, когда хотите вытащить файл лога из контейнера в локальную систему для дебага.

docker login [OPTIONS] [SERVER]
docker login localhost:8080

Вход в реестр Docker.

docker logout [SERVER]
docker logout localhost:8080

Выход из реестра Docker.

docker push [OPTIONS] NAME[:TAG]
docker image push myApp:V1

Используйте эту команду, когда хотите поделиться вашими образами в реестре Docker Hub.

docker pull [OPTIONS] NAME[:TAG|@DIGEST]
docker pull ubuntu:20.04

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

Перевод статьи «Beginner’s guide to Docker(Part 1)- Dockerfile and Docker CLI commands».

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

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

  • Установка
  • Запуск
  • Зачем все это?
  • Приложение в контейнере
  • Работа с образами
  • Управление контейнерами
  • Взаимодействие с другими частями системы
  • Подготовка собственного образа
  • Docker Compose
  • В бою
  • Докер под капотом
  • Дополнительные ссылки

Установка

Чтобы начать пользоваться Докером, необходимо установить движок — Docker Engine. На странице https://docs.docker.com/engine/install/ доступны ссылки для скачивания под все популярные платформы. Выберите вашу и установите Докер.

В работе Докера есть одна деталь, которую важно знать при установке на Mac и Linux. По умолчанию Докер работает через unix сокет. В целях безопасности сокет закрыт для пользователей, не входящих в группу docker. И хотя установщик добавляет текущего пользователя в эту группу автоматически, Докер сразу не заработает. Дело в том, что если пользователь меняет группу сам себе, то ничего не изменится до тех пор, пока пользователь не перелогинится. Такова особенность работы ядра. Для проверки того, в какие группы входит ваш пользователь, можно набрать команду id.

Проверить успешность установки можно командой docker info:

$ docker info
Containers: 22
 Running: 2
 Paused: 0
 Stopped: 20
Images: 72
Server Version: 17.12.0-ce
Storage Driver: overlay2
 Backing Filesystem: extfs
 Supports d_type: true
 Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
...

Она выдаёт довольно много информации о конфигурации самого Докера и статистику работы.

Запуск

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

Начнем с самого простого варианта:

$ docker run -it nginx bash
root@a6c26812d23b:/#

При первом вызове данная команда начнет скачивать образ (image) nginx, поэтому придется немного подождать. После того, как образ скачается, запустится bash, и вы окажетесь внутри контейнера (container).

Побродите по файловой системе, посмотрите директорию /etc/nginx. Как видите, её содержимое не совпадает с тем, что находится у вас на компьютере. Эта файловая система появилась из образа nginx. Всё, что вы сделаете здесь внутри, никак не затронет вашу основную файловую систему. Вернуться в родные скрепы можно командой exit.

Теперь посмотрим вариант вызова команды cat, выполненной уже в другом контейнере, но тоже запущенном из образа nginx:

$ docker run nginx cat /etc/nginx/nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;
...
$

Команда выполняется практически мгновенно, так как образ уже загружен. В отличие от предыдущего старта, где запускается баш и начинается интерактивная сессия внутри контейнера, запуск команды cat /etc/nginx/nginx.conf для образа nginx выведет на экран содержимое указанного файла (взяв его из файловой системы запущенного контейнера) и вернет управление в то место, где вы были. Вы не окажетесь внутри контейнера.

Последний вариант запуска будет таким:

# Обратите внимание на то, что после имени образа не указана никакая команда.
# Такой подход работает в случае если команда на запуск прописана в самом образе
$ docker run -p 8080:80 nginx

Данная команда не возвращает управление, потому что стартует nginx. Откройте браузер и наберите localhost:8080. Вы увидите как загрузилась страница Welcome to nginx!. Если в этот момент снова посмотреть в консоль, где был запущен контейнер, то можно увидеть, что туда выводится лог запросов к localhost:8080. Остановить nginx можно командой Ctrl + C.

Несмотря на то, что все запуски выполнялись по-разному и приводили к разным результатам, общая схема их работы — одна. Докер при необходимости автоматически скачивает образ (первый аргумент после docker run) и на основе него стартует контейнер с указанной командой.

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

Контейнер — запущенный процесс операционной системы в изолированном окружении с подключенной файловой системой из образа.

Повторюсь, что контейнер — всего лишь обычный процесс вашей операционной системы. Разница лишь в том, что благодаря возможностям ядра (о них в конце) Докер стартует процесс в изолированном окружении. Контейнер видит свой собственный список процессов, свою собственную сеть, свою собственную файловую систему и так далее. Пока ему не укажут явно, он не может взаимодействовать с вашей основной операционной системой и всем, что в ней хранится или запущено.

Попробуйте выполнить команду docker run -it ubuntu bash и наберите ps auxf внутри запущенного контейнера. Вывод будет таким:

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.1  18240  3300 pts/0    Ss   15:39   0:00 /bin/bash
root        12  0.0  0.1  34424  2808 pts/0    R+   15:40   0:00 ps aux

Как видно, процесса всего два, и у Bash PID равен 1. Заодно можно посмотреть в директорию /home командой ls /home и убедиться, что она пустая. Также обратите внимание, что внутри контейнера по умолчанию используется пользователь root.

Зачем все это?

Докер — универсальный способ доставки приложений на машины (локальный компьютер или удаленные сервера) и их запуска в изолированном окружении.

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

  • Установить все необходимые зависимости под вашу операционную систему (их список еще надо найти).
  • Скачать архив, распаковать.
  • Запустить конфигурирование make configure.
  • Запустить компиляцию make compile.
  • Установить make install.

Как видите, процесс нетривиальный и далеко не всегда быстрый. Иногда даже невыполнимый из-за непонятных ошибок. И это не говоря про загрязнение операционной системы.

Докер позволяет упростить эту процедуру до запуска одной команды причем с почти 100% гарантией успеха. Посмотрите на вымышленный пример, в котором происходит установка программы Tunnel на локальный компьютер в директорию /usr/local/bin используя образ tunnel:

docker run -v /usr/local/bin:/out tunnel

Запуск этой команды приводит к тому, что в основной системе в директории /usr/local/bin оказывается исполняемый файл программы, находящейся внутри образа tunnel. Команда docker run запускает контейнер из образа tunnel, внутри происходит компиляция программы и, в конечном итоге, она оказывается в директории /usr/local/bin основной файловой системы. Теперь можно стартовать программу, просто набрав tunnel в терминале.

А что если программа, которую мы устанавливаем таким способом, имеет зависимости? Весь фокус в том, что образ, из которого был запущен контейнер, полностью укомплектован. Внутри него установлены все необходимые зависимости, и его запуск практически гарантирует 100% работоспособность независимо от состояния основной ОС.

Часто даже не обязательно копировать программу из контейнера на вашу основную систему. Достаточно запускать сам контейнер, когда в этом возникнет необходимость. Предположим, что мы решили разработать статический сайт на основе Jekyll. Jekyll — популярный генератор статических сайтов, написанный на Ruby. Например, гайд который вы читаете прямо сейчас, находится на статическом сайте, сгенерированном с его помощью. И при его генерации использовался Докер (об этом можно прочитать в гайде: как делать блог на Jekyll).

Старый способ использования Jekyll требовал установки на вашу основную систему как минимум Ruby и самого Jekyll в виде гема (gem — название пакетов в Ruby). Причем, как и всегда в подобных вещах, Jekyll работает только с определенными версиями Ruby, что вносит свои проблемы при настройке.

С Докером запуск Jekyll сводится к одной команде, выполняемой в директории с блогом (подробнее можно посмотреть в репозитории наших гайдов):

docker run --rm --volume="$PWD:/srv/jekyll" -it jekyll/jekyll jekyll server

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

docker logo

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

loading cargo onto a ship

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

Но в какой-то момент все изменилось. Картинка стоит тысячи слов:

shipping container terminal

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

То же самое произошло и в разработке ПО. Docker стал универсальным средством доставки софта независимо от его структуры, зависимостей и способа установки. Всё, что нужно программам, распространяемым через Докер, находится внутри образа и не пересекается с основной системой и другими контейнерами. Важность этого факта невозможно переоценить. Теперь обновление версий программ никак не задействует ни саму систему, ни другие программы. Сломаться больше ничего не может. Всё, что нужно сделать, это скачать новый образ той программы, которую требуется обновить. Другими словами, Докер убрал проблему dependency hell и сделал инфраструктуру immutable (неизменяемой).

Больше всего Docker повлиял именно на серверную инфраструктуру. До эры Докера управление серверами было очень болезненным мероприятием даже несмотря на наличие программ по управлению конфигурацией (chef, puppet, ansible). Основная причина всех проблем — изменяемое состояние. Программы ставятся, обновляются, удаляются. Происходит это в разное время на разных серверах и немного по-разному. Например, обновить версию таких языков, как PHP, Ruby или Python могло стать целым приключением с потерей работоспособности. Проще поставить рядом новый сервер и переключиться на него. Идейно Докер позволяет сделать именно такое переключение. Забыть про старое и поставить новое, ведь каждый запущенный контейнер живет в своем окружении. Причем, откат в такой системе тривиален: всё что нужно — остановить новый контейнер и поднять старый, на базе предыдущего образа.

Приложение в контейнере

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

  1. Всё приложение — один контейнер, внутри которого поднимается дерево процессов: приложение, веб сервер, база данных и всё в этом духе.
  2. Каждый запущенный контейнер — атомарный сервис. Другими словами каждый контейнер представляет из себя ровно одну программу, будь то веб-сервер или приложение.

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

Первый подход крайне редко, но бывает нужен. Например, Хекслет работает в двух режимах. Сам сайт с его сервисами использует вторую модель, когда каждый сервис отдельно, но вот практика, выполняемая в браузере, стартует по принципу «один пользователь — один контейнер». Внутри контейнера может оказаться всё что угодно в зависимости от практики. Как минимум, там всегда стартует сама среда Хекслет IDE, а она в свою очередь порождает терминалы (процессы). В курсе по базам данных в этом же контейнере стартует и база данных, в курсе, связанном с вебом, стартует веб-сервер. Такой подход позволяет создать иллюзию работы на настоящей машине и резко снижает сложность в поддержке упражнений. Повторюсь, что такой вариант использования очень специфичен и вам вряд ли понадобится.

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

Работа с образами

Docker — больше, чем просто программа. Это целая экосистема со множеством проектов и сервисов. Главный сервис, с которым вам придется иметь дело — Registry. Хранилище образов.

Концептуально оно работает так же, как и репозиторий пакетов любого пакетного менеджера. Посмотреть его содержимое можно на сайте https://hub.docker.com/, кликнув по ссылке Explore.

Когда мы выполняем команду run docker run <image name>, то Docker проверяет наличие указанного образа на локальной машине и скачивает его по необходимости. Список образов, уже скачанных на компьютер, можно посмотреть командой docker images:

$ docker images
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
workshopdevops_web                   latest              cfd7771b4b3a        2 days ago          817MB
hexletbasics_app                     latest              8e34a5f631ea        2 days ago          1.3GB
mokevnin/rails                       latest              96487c602a9b        2 days ago          743MB
ubuntu                               latest              2a4cca5ac898        3 days ago          111MB
Ruby                                 2.4                 713da53688a6        3 weeks ago         687MB
Ruby                                 2.5                 4c7885e3f2bb        3 weeks ago         881MB
nginx                                latest              3f8a4339aadd        3 weeks ago         108MB
elixir                               latest              93617745963c        4 weeks ago         889MB
postgres                             latest              ec61d13c8566        5 weeks ago         287MB

Разберемся с тем, как формируется имя образа, и что оно в себя включает.

Вторая колонка в выводе выше называется TAG. Когда мы выполняли команду docker run nginx, то на самом деле выполнялась команда docker run nginx:latest. То есть мы не просто скачиваем образ nginx, а скачиваем его конкретную версию. Latest — тег по умолчанию. Несложно догадаться, что он означает последнюю версию образа.

Важно понимать, что это всего лишь соглашение, а не правило. Конкретный образ вообще может не иметь тега latest, либо иметь, но он не будет содержать последние изменения, просто потому, что никто их не публикует. Впрочем, популярные образы следуют соглашению. Как понятно из контекста, теги в Докере изменяемы, другими словами, вам никто не гарантирует, что скачав образ с одним и тем же тегом на разных компьютерах в разное время вы получите одно и то же. Такой подход может показаться странным и ненадежным, ведь нет гарантий, но на практике есть определенные соглашения, которым следуют все популярные образы. Тег latest действительно всегда содержит последнюю версию и постоянно обновляется, но кроме этого тега активно используется семантическое версионирование. Рассмотрим https://hub.docker.com/_/nginx

1.13.8, mainline, 1, 1.13, latest
1.13.8-perl, mainline-perl, 1-perl, 1.13-perl, perl
1.13.8-alpine, mainline-alpine, 1-alpine, 1.13-alpine, alpine
1.13.8-alpine-perl, mainline-alpine-perl, 1-alpine-perl, 1.13-alpine-perl, alpine-perl
1.12.2, stable, 1.12
1.12.2-perl, stable-perl, 1.12-perl
1.12.2-alpine, stable-alpine, 1.12-alpine
1.12.2-alpine-perl, stable-alpine-perl, 1.12-alpine-perl

Теги, в которых присутствует полная семантическая версия (x.x.x) всегда неизменяемы, даже если в них встречается что-то еще, например, 1.12.2-alphine. Такую версию смело нужно брать для продакшен-окружения. Теги, подобные такому 1.12, обновляются при изменении path версии. То есть внутри образа может оказаться и версия 1.12.2, и в будущем 1.12.8. Точно такая же схема и с версиями, в которых указана только мажорная версия, например, 1. Только в данном случае обновление идет не только по патчу, но и по минорной версии.

Как вы помните, команда docker run скачивает образ, если его нет локально, но эта проверка не связана с обновлением содержимого. Другими словами, если nginx:latest обновился, то docker run его не будет скачивать, он использует тот latest, который прямо сейчас уже загружен. Для гарантированного обновления образа существует другая команда: docker pull. Вот она всегда проверяет, обновился ли образ для определенного тега.

Кроме тегов имя образа может содержать префикс: например, etsy/chef. Этот префикс является именем аккаунта на сайте, через который создаются образы, попадающие в Registry. Большинство образов как раз такие, с префиксом. И есть небольшой набор, буквально сотня образов, которые не имеют префикса. Их особенность в том, что эти образы поддерживает сам Docker. Поэтому если вы видите, что в имени образа нет префикса, значит это официальный образ. Список таких образов можно увидеть здесь: https://github.com/docker-library/official-images/tree/master/library

Удаляются образы командой docker rmi <imagename>.

$ docker rmi Ruby:2.4
Untagged: Ruby:2.4
Untagged: Ruby@sha256:d973c59b89f3c5c9bb330e3350ef8c529753ba9004dcd1bfbcaa4e9c0acb0c82

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

Управление контейнерами

Docker Container LifeCycle

Картинка описывает жизненный цикл (конечный автомат) контейнера. Кружками на нём изображены состояния, жирным выделены консольные команды, а квадратиками показывается то, что в реальности выполняется.

Проследите путь команды docker run. Несмотря на то, что команда одна, с точки зрения работы Докера выполняется два действия: создание контейнера и запуск. Существуют и более сложные варианты исполнения, но в этом разделе мы рассмотрим только базовые команды.

Запустим nginx так, чтобы он работал в фоне. Для этого после слова run добавляется флаг -d:

$ docker run -d -p 8080:80 nginx
431a3b3fc24bf8440efe2bca5bbb837944d5ae5c3b23b9b33a5575cb3566444e

После выполнения команды Докер выводит идентификатор контейнера и возвращает управление. Убедитесь в том, что nginx работает, открыв в браузере ссылку localhost:8080. В отличие от предыдущего запуска, наш nginx работает в фоне, а значит не видно его вывода (логов). Посмотреть его можно командой docker logs, которой нужно передать идентификатор контейнера:

$ docker logs 431a3b3fc24bf8440efe2bca5bbb837944d5ae5c3b23b9b33a5575cb3566444e

172.17.0.1 - - [19/Jan/2018:07:38:55 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" "-"

Вы также можете подсоединиться к выводу лога в стиле tail -f. Для этого запустите docker logs -f 431a3b3fc24bf8440efe2bca5bbb837944d5ae5c3b23b9b33a5575cb3566444e. Теперь лог будет обновляться каждый раз, когда вы обновляете страницу в браузере. Выйти из этого режима можно набрав Ctrl + C, при этом сам контейнер остановлен не будет.

Теперь выведем информацию о запущенных контейнерах командой docker ps:

CONTAINER ID        IMAGE                            COMMAND                  CREATED             STATUS              PORTS                                          NAMES
431a3b3fc24b        nginx                            "nginx -g 'daemon of…"   2 minutes ago       Up 2 minutes        80/tcp                                         wizardly_rosalind

Расшифровка столбиков:

  • CONTAINER_ID — идентификатор контейнера. Так же, как и в git, используется сокращенная запись хеша.
  • IMAGE — имя образа, из которого был поднят контейнер. Если не указан тег, то подразумевается latest.
  • COMMAND — команда, которая выполнилась на самом деле при старте контейнера.
  • CREATED — время создания контейнера
  • STATUS — текущее состояние.
  • PORTS — проброс портов.
  • NAMES — алиас. Докер позволяет кроме идентификатора иметь имя. Так гораздо проще обращаться с контейнером. Если при создании контейнера имя не указано, то Докер самостоятельно его придумывает. В выводе выше как раз такое имя у nginx.

(Команда docker stats выводит информацию о том, сколько ресурсов потребляют запущенные контейнеры).

Теперь попробуем остановить контейнер. Выполним команду:

# Вместо CONTAINER_ID можно указывать имя
$ docker kill 431a3b3fc24b # docker kill wizardly_rosalind
431a3b3fc24b

Если попробовать набрать docker ps, то там этого контейнера больше нет. Он удален.

Команда docker ps выводит только запущенные контейнеры. Но кроме них могут быть и остановленные. Причем, остановка может происходить как и по успешному завершению, так и в случае ошибок. Попробуйте набрать docker run ubuntu ls, а затем docker run ubuntu bash -c "unknown". Эти команды не запускают долгоживущий процесс, они завершаются сразу после выполнения, причем вторая с ошибкой, так как такой команды не существует.

Теперь выведем все контейнеры командой docker ps -a. Первыми тремя строчками вывода окажутся:

CONTAINER ID        IMAGE                            COMMAND                  CREATED                  STATUS                       PORTS                                          NAMES
85fb81250406        ubuntu                           "bash -c unkown"         Less than a second ago   Exited (127) 3 seconds ago                                                  loving_bose
c379040bce42        ubuntu                           "ls"                     Less than a second ago   Exited (0) 9 seconds ago                                                    determined_tereshkova

Здесь как раз два последних наших запуска. Если посмотреть на колонку STATUS, то видно, что оба контейнера находятся в состоянии Exited. То есть запущенная команда внутри них выполнилась, и они остановились. Разница лишь в том, что один завершился успешно (0), а второй с ошибкой (127). После остановки контейнер можно даже перезапустить:

docker start determined_tereshkova # В вашем случае будет другое имя

Только в этот раз вы не увидите вывод. Чтобы его посмотреть, воспользуйтесь командой docker logs determined_tereshkova.

Взаимодействие с другими частями системы

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

Interactive mode

Самый простой вариант использования Докера, как мы уже убедились — поднять контейнер и выполнить внутри него какую-либо команду:

$ docker run ubuntu ls /usr
bin
games
include
lib
local
sbin
share
src
$

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

$ docker run ubuntu bash
$

Дело в том, что bash запускает интерактивную сессию внутри контейнера. Для взаимодействия с ней нужно оставить открытым поток STDIN и запустить TTY (псевдо-терминал). Поэтому для запуска интерактивных сессий нужно не забыть добавить опции -i и -t. Как правило их добавляют сразу вместе как -it. Поэтому правильный способ запуска баша выглядит так: docker run -it ubuntu bash.

Ports

Если запустить nginx такой командой docker run nginx, то nginx не сможет принять ни один запрос, несмотря на то, что внутри контейнера он слушает 80 порт (напомню, что каждый контейнер по умолчанию живет в своей собственной сети). Но если запустить его так docker run -p 8080:80 nginx, то nginx начнет отвечать на порту 8080.

Флаг -p позволяет описывать как и какой порт выставить наружу. Формат записи 8080:80 расшифровывается так: пробросить порт 8080 снаружи контейнера в контейнер на порт 80. Причем, по умолчанию, порт 8080 слушается на 0.0.0.0, то есть на всех доступных интерфейсах. Поэтому запущенный таким образом контейнер доступен не только через localhost:8080, но и снаружи машины (если доступ не запрещен как-нибудь еще). Если нужно выполнить проброс только на loopback, то команда меняется на такую: docker run -p 127.0.0.1:8080:80 nginx.

Docker позволяет пробрасывать столько портов, сколько нужно. Например, в случае nginx часто требуется использовать и 80 порт, и 443 для HTTPS. Сделать это можно так: docker run -p 80:80 -p 443:443 nginx Про остальные способы пробрасывать порты можно прочитать в официальной документации.

Volumes

Другая частая задача связана с доступом к основной файловой системе. Например, при старте nginx-контейнера ему можно указать конфигурацию, лежащую на основной фс. Докер прокинет её во внутреннюю фс, и nginx сможет её читать и использовать.

Проброс осуществляется с помощью опции -v. Вот как можно запустить баш сессию из образа Ubuntu, подключив туда историю команд с основной файловой системы: docker run -it -v ~/.bash_history:/root/.bash_history ubuntu bash. Если в открытом баше понажимать стрелку вверх, то отобразится история. Пробрасывать можно как файлы, так и директории. Любые изменения производимые внутри volume меняются как внутри контейнера, так и снаружи, причем по умолчанию доступны любые операции. Как и в случае портов, количество пробрасываемых файлов и директорий может быть любым.

При работе с Volumes есть несколько важных правил, которые надо знать:

  • Путь до файла во внешней системе должен быть абсолютным.
  • Если внутренний путь (то, что идет после :) не существует, то Докер создаст все необходимые директории и файлы. Если существует, то заменит старое тем, что было проброшено.

Кроме пробрасывания части фс снаружи, Докер предоставляет еще несколько вариантов создания и использования Volumes. Подробнее — в официальной документации.

Переменные окружения

Конфигурирование приложения внутри контейнера, как правило, осуществляется с помощью переменных окружения в соответствии с 12factors. Существует два способа их установки:

  • Флаг -e. Используется он так: docker run -it -e "HOME=/tmp" ubuntu bash
  • Специальный файл, содержащий определения переменных окружения, который пробрасывается внутрь контейнера опцией --env-file.

Подготовка собственного образа

Создание и публикация собственного образа не сложнее его использования. Весь процесс делится на три шага:

  • Создается файл Dockerfile в корне проекта. Внутри описывается процесс создания образа.
  • Выполняется сборка образа командой docker build
  • Выполняется публикация образа в Registry командой docker push

Рассмотрим процесс создания образа на примере упаковки линтера eslint (не забудьте повторить его самостоятельно). В результате сборки, мы получим образ, который можно использовать так:

$ docker run -it -v /path/to/js/files:/app my_account_name/eslint

/app/index.js
  3:6  error  Parsing error: Unexpected token

  1 | import path from 'path';
  2 |
> 3 | path(;)
    |      ^
  4 |1 problem (1 error, 0 warnings)

То есть достаточно запустить контейнер из этого образа, подключив каталог с файлами js для проверки как Volume во внутреннюю директорию /app.

1. Конечная структура директории, на основе файлов которой соберется образ, выглядит так:

eslint-docker/
    Dockerfile
    eslintrc.yml

Файл eslintrc.yml содержит конфигурацию линтера. Он автоматически прочитывается, если лежит в домашней директории под именем .eslintrc.yml. То есть этот файл должен попасть под таким именем в директорию /root внутрь образа.

2. Создание Dockerfile

# Dockerfile
FROM node:9.3

WORKDIR /usr/src

RUN npm install -g eslint babel-eslint
RUN npm install -g eslint-config-airbnb-base eslint-plugin-import

COPY eslintrc.yml /root/.eslintrc.yml

CMD ["eslint", "/app"]

Dockerfile имеет довольно простой формат. На каждой строчке указывается инструкция (директива) и её описание.

FROM

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

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

RUN

Основная инструкция в Dockerfile. Фактически здесь указывается sh команда, которая будет выполнена в рамках окружения, указанного во FROM при сборке образа. Так как по умолчанию всё выполняется от пользователя root, то использовать sudo не нужно (и скорее всего его нет в базовом образе). К тому же учтите, что сборка образа — процесс не интерактивный. В тех ситуациях, когда вы используете команду, которая может запросить что-то от пользователя, необходимо подавлять этот вывод. Например, в случае пакетных менеджеров делают так: apt-get install -y curl. Флаг -y как раз говорит о том что нужно производиться установку без дополнительных вопросов.

Технически образ Докера — это не один файл, а набор так называемых слоев. Каждый вызов RUN формирует новый слой, который можно представить как набор файлов, созданных и измененных (в том числе удаленных) командой, указанной в RUN. Такой подход позволяет значительно улучшить производительность системы, задействовав кеширование слоев, которые не поменялись. С другой стороны, Докер переиспользует слои в разных образах если они идентичны, что сокращает и скорость загрузки и занимаемое пространство на диске. Тема кеширования слоев довольно важная при активном использовании Докера. Для её эффективной работы нужно понимать как она устроена и как правильно описывать инструкции RUN для максимальной утилизации.

COPY

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

WORKDIR

Инструкция, устанавливающая рабочую директорию. Все последующие инструкции будут считать, что они выполняются именно внутри неё. По инструкция WORKDIR действует, как команда cd. Кроме того, когда мы запускаем контейнер, то он также стартует из рабочей директории. Например, запустив bash, вы окажетесь внутри неё.

CMD

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

3. Сборка

Для сборки образа используется команда docker build. С помощью флага -t передается имя образа, включая имя аккаунта и тег. Как обычно, если не указывать тег, то подставляется latest.

$ docker build -t my_account_name/eslint .

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

4. Публикация

$ docker push my_account_name/eslint

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

  • Зарегистрироваться на Docker Cloud и создать там репозиторий для образа.
  • Залогиниться в cli интерфейсе используя команду docker login.

Docker Compose

Docker Compose — продукт, позволяющий разрабатывать проект локально, используя Докер. По решаемым задачам его можно сравнивать с Vagrant.

Docker Compose позволяет управлять набором контейнеров, каждый из которых представляет из себя один сервис проекта. Управление включает в себя сборку, запуск с учетом зависимостей и конфигурацию. Конфигурация Docker Compose описывается в файле docker-compose.yml, лежащем в корне проекта, и выглядит примерно так:

# https://github.com/hexlet-basics/hexlet_basics

version: '3.3'

services:
  db:
    image: postgres
  app:
    build:
      context: services/app
      dockerfile: Dockerfile
    command: mix phx.server
    ports:
      - "${PORT}:${PORT}"
    env_file: '.env'
    volumes:
      - "./services/app:/app:cached"
      - "~/.bash_history:/root/.bash_history:cached"
      - ".bashrc:/root/.bashrc:cached"
      - "/var/tmp:/var/tmp:cached"
      - "/tmp:/tmp:cached"
    depends_on:
      - db

В бою

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

  1. Скачать новый образ.
  2. Остановить старый контейнер.
  3. Поднять новый контейнер.

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

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

Докер под капотом

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

Дополнительные ссылки

  • Вебинар: Введение в докер
  • Docker: Основы

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

Это ПО с открытым кодом, принцип работы которого проще всего сравнить с транспортными контейнерами. Только подумайте, ведь когда-то транспортные компании сталкивались с похожими проблемами:

  1. Как перевозить разные (несовместимые) типы товаров вместе (например, продукты питания с химикатами или стекло с кирпичом)?
  2. Как обрабатывать пакеты разных размеров одним и тем же транспортным средством?

Docker

С введением контейнеров стало возможным перевозить вместе кирпичи и стекло, химикаты и еду, а также многое другое. Груз разного размера может быть распределен по стандартизированным контейнерам, которые загружаются/выгружаются одним и тем же транспортным средством.

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

Эта проблема решается через создание независимости ПО от системы.

В чем отличие от виртуализации?

Изначально виртуализация была призвана избавить от подобных проблем, но в ней есть существенные недостатки:

  • медленная загрузка;
  • возможная плата за предоставление дополнительного пространства;
  • не все виртуальные машины (VM) поддерживают совместимое использование;
  • поддерживающие VM часто требуют сложной настройки;
  • образ может быть слишком большим, так как «дополнительная ОС» добавляет гигабайт пространства в проект поверх операционной системы, а в большинстве случаев на сервер ставится несколько VM, которые занимают еще больше места.

Docker

Докер же просто разделяет ядро ОС на все контейнеры (Docker container), работающие как отдельные процессы. Это не единственная подобная платформа, но, бесспорно, одна из самых популярных и востребованных.

Какие очевидные плюсы?

Docker

К его преимуществам относятся:

  1. Ускоренный процесс разработки. Нет необходимости устанавливать вспомогательные инструменты вроде PostgreSQL, Redis, Elasticsearch: их можно запускать в контейнерах.
  2. Удобная инкапсуляция приложений.
  3. Понятный мониторинг.
  4. Простое масштабирование.

Поддерживаемые платформы

Докер работает не только на его родной ОС, Linux, но также поддерживается Windows и macOS. Единственное отличие от взаимодействия с Linux в том, что на macOS и Windows платформа инкапсулируется в крошечную виртуальную машину. На данный момент Докер для macOS и Windows достиг значительного уровня удобства в использовании.

Linux

Кроме того, существует множество дополнительных приложений, таких как Kitematic или Docker Machine, которые помогают устанавливать и использовать Докер на платформах, отличных от Linux.

Установка

Здесь можно посмотреть подробную инструкцию по установке. Если вы работаете с Докером на ОС Linux, вам нужно выполнить несколько несложных действий и повторно войти в систему:

sudo usermod -aG docker $(whoami)

Терминология

1. Контейнер – это исполняемый экземпляр, который инкапсулирует требуемое программное обеспечение. Он состоит из образов. Его можно легко удалить и снова создать за короткий промежуток времени.

2. Образ – базовый элемент каждого контейнера. В зависимости от образа, может потребоваться некоторое время для его создания.

3. Порт – это порт TCP/UDP в своем первоначальном значении. Чтобы все было просто, предположим, что порты могут быть открыты во внешнем мире или подключены к контейнерам (доступны только из этих контейнеров и невидимы для внешнего мира).

Docker

4. Том – описывается как общая папка. Тома инициализируются при создании контейнера и предназначены для сохранения данных, независимо от жизненного цикла контейнера.

5. Реестр – это сервер, на котором хранятся образы. Сравним его с GitHub: вы можете вытащить образ из реестра, чтобы развернуть его локально, и так же локально можете вносить в реестр созданные образы.

6. Docker Hub – публичный репозиторий с интерфейсом, предоставляемый Докер Inc. Он хранит множество образов. Ресурс является источником «официальных» образов, сделанных командой Докер или созданных в сотрудничестве с разработчиком ПО. Для официальных образов перечислены их потенциальные уязвимости. Эта информация открыта для любого зарегистрированного пользователя. Доступны как бесплатные, так и платные аккаунты.

Docker Hub

Пример 1: Hello World

Пришло время запустить наш первый контейнер:

docker run ubuntu /bin/echo 'Hello world'

Консольный вывод:

Unable to find image 'ubuntu:latest' locally  
latest: Pulling from library/ubuntu  
d54efb8db41d: Pull complete  
f8b845f45a87: Pull complete  
e8db7bf7c39f: Pull complete  
9654c40e9079: Pull complete  
6d9ef359eaaa: Pull complete  
Digest: sha256:dd7808d8792c9841d0b460122f1acf0a2dd1f56404f8d1e56298048885e45535  
Status: Downloaded newer image for ubuntu:latest  
Hello world
  • docker run – это команда запуска контейнера.
  • ubuntu – образ, который вы запускаете (например, образ операционной системы Ubuntu). Когда вы его указываете, Докер сначала анализирует элемент в разрезе хоста.
  • /bin/echo ‘Hello world’ – команда, которая будет запускаться внутри нового контейнера. Данный контейнер просто выводит «Hello world» и останавливает выполнение.

Теперь попробуем создать интерактивную оболочку внутри контейнера:

docker run -i -t --rm ubuntu /bin/bash
  • -t присваивает псевдо-tty или терминал внутри нового контейнера.
  • -i позволяет создавать интерактивное соединение, захватывая стандартный вход (STDIN) контейнера.
  • —rm требуется для автоматического удаления контейнера при выходе из процесса. По умолчанию контейнеры не удаляются.

Если вы хотите, чтобы контейнер работал после окончания сеанса, вам необходимо его «демонизировать»:

docker run --name daemon -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"
  • —name daemon назначает имя новому контейнеру. Если вы его не укажете, имя сгенерируется и назначится автоматически.
  • -d запускает контейнер в фоновом режиме («демонизирует» его).

Давайте посмотрим, какие контейнеры у нас есть на данный момент:

docker ps -a

Консольный вывод:

CONTAINER ID  IMAGE   COMMAND                 CREATED             STATUS                         PORTS  NAMES  
1fc8cee64ec2  ubuntu  "/bin/sh -c 'while..."  32 seconds ago      Up 30 seconds                         daemon  
c006f1a02edf  ubuntu  "/bin/echo 'Hello ..."  About a minute ago  Exited (0) About a minute ago         gifted_nobel
  • docker ps – команда для перечисления контейнеров.
  • -a показывает все контейнеры (без -a ps покажет только запущенные контейнеры).

ps показывает нам, что у нас есть два контейнера:

  • gifted_nobel (имя для этого контейнера генерировалось автоматически) – первый контейнер, который мы создали с набранным «Hello world».
  • daemon – третий контейнер, который мы создали и «демонизировали».

Примечание: второй контейнер (с интерактивной оболочкой) отсутствует, потому что мы устанавливаем параметр -rm, в результате чего этот контейнер автоматически удаляется после выполнения.

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

docker logs -f daemon

Консольный вывод:

...
hello world  
hello world  
hello world
  • docker logs получают журналы контейнера.
  • -f следит за выходом журнала.

Теперь давайте остановим контейнер-демон:

docker stop daemon

Проверяем его остановку:

docker ps -a

Консольный вывод:

CONTAINER ID  IMAGE   COMMAND                 CREATED        STATUS                      PORTS  NAMES  
1fc8cee64ec2  ubuntu  "/bin/sh -c 'while..."  5 minutes ago  Exited (137) 5 seconds ago         daemon  
c006f1a02edf  ubuntu  "/bin/echo 'Hello ..."  6 minutes ago  Exited (0) 6 minutes ago           gifted_nobel

Контейнер остановлен. Давайте запустим его снова:

docker start daemon

Убедимся, что он запущен:

docker ps -a  

Консольный вывод:

CONTAINER ID  IMAGE   COMMAND                 CREATED        STATUS                    PORTS  NAMES  
1fc8cee64ec2  ubuntu  "/bin/sh -c 'while..."  5 minutes ago  Up 3 seconds                     daemon  
c006f1a02edf  ubuntu  "/bin/echo 'Hello ..."  6 minutes ago  Exited (0) 7 minutes ago         gifted_nobel

Теперь остановим его и удалим все контейнеры вручную:

docker stop daemon  
docker rm <your first container name>  
docker rm daemon

Чтобы удалить все контейнеры, мы можем использовать следующую команду:

docker rm -f $(docker ps -aq)
  • docker rm – команда удаления контейнера.
  • -f (для rm) должен остановить контейнер, если он работает (принудительное удаление).
  • -q (для ps) – это вывод только идентификаторов контейнера.

Пример 2: Nginx

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

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

Измените каталог на examples/nginx:

docker run -d --name test-nginx -p 80:80 -v $(pwd):/usr/share/nginx/html:ro nginx:latest

Консольный вывод:

Unable to find image 'nginx:latest' locally  
latest: Pulling from library/nginx  
693502eb7dfb: Pull complete  
6decb850d2bc: Pull complete  
c3e19f087ed6: Pull complete  
Digest: sha256:52a189e49c0c797cfc5cbfe578c68c225d160fb13a42954144b29af3fe4fe335  
Status: Downloaded newer image for nginx:latest  
436a602273b0ca687c61cc843ab28163c720a1810b09005a36ea06f005b0c971
  • -p – отображение портов HOST PORT: CONTAINER PORT.
  • -v отвечает за HOST DIRECTORY:CONTAINER DIRECTORY.

Теперь проверьте этот URL-адрес в своем веб-браузере.

Еще мы можем попробовать изменить /example/nginx/index.html (который добавляется в каталог /usr/share/nginx/html внутри контейнера) и обновить страницу.

Получим информацию о контейнере test-nginx:

docker inspect test-nginx

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

Пример 3: запись Dockerfile

Чтобы создать образ, сперва вам нужно создать Dockerfile: это текстовый файл с инструкциями и аргументами. Краткое описание инструкций, которые мы собираемся использовать в примере:

  • FROM – задать базовый образ
  • RUN – выполнить команду в контейнере
  • ENV – задать переменную среды
  • WORKDIR – установить рабочий каталог
  • VOLUME – создать точку монтирования для тома
  • CMD – установить исполняемый файл для контейнера

Более подробная информация здесь.

Давайте создадим образ, который получит содержимое сайта и сохранит его в текстовом файле. Нам нужно передать URL-адрес через переменную SITE_URL. Результирующий файл будет помещен в каталог, установленный как том:

FROM ubuntu:latest  
RUN apt-get update  
RUN apt-get install --no-install-recommends --no-install-suggests -y curl  
ENV SITE_URL https://google.com/  
WORKDIR /data  
VOLUME /data  
CMD sh -c "curl -L $SITE_URL > /data/results"

Dockerfile готов, пришло время создать образ.

Создание образа

Перейдите к examples/curl и выполните следующую команду:

docker build . -t test-curl  

Консольный вывод:

Sending build context to Docker daemon 3.584 kB  
Step 1/7 : FROM ubuntu:latest  
 ---> 0ef2e08ed3fa
Step 2/7 : RUN apt-get update  
 ---> Running in 4aa839bb46ec
Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]  
Get:2 http://archive.ubuntu.com/ubuntu xenial-updates InRelease [102 kB]  
...
Fetched 24.9 MB in 4s (5208 kB/s)  
Reading package lists...  
 ---> 35ac5017c794
Removing intermediate container 4aa839bb46ec  
Step 3/7 : RUN apt-get install --no-install-recommends --no-install-suggests -y curl  
 ---> Running in 3ca9384ecf8d
Reading package lists...  
Building dependency tree...  
Reading state information...  
The following additional packages will be installed...  
 ---> f3c6d26b95e6
Removing intermediate container 3ca9384ecf8d  
Step 4/7 : ENV SITE_URL https://google.com/  
 ---> Running in 21b0022b260f
 ---> 9a733ee39a46
Removing intermediate container 21b0022b260f  
Step 5/7 : WORKDIR /data  
 ---> c024301ddfb8
Removing intermediate container 3bc973e5584c  
Step 6/7 : VOLUME /data  
 ---> Running in a9594a8958fe
 ---> 6802707a7114
Removing intermediate container a9594a8958fe  
Step 7/7 : CMD sh -c "curl -L $SITE_URL > /data/results"  
 ---> Running in 37503bc4e386
 ---> 5ebb2a65d771
Removing intermediate container 37503bc4e386  
Successfully built 5ebb2a65d771
  • docker build создает новый образ локально.
  • -t устанавливает в образе метку имени.

Теперь у нас есть новый образ, и мы можем его увидеть в списке существующих:

docker images  

Консольный вывод:

REPOSITORY  TAG     IMAGE ID      CREATED         SIZE  
test-curl   latest  5ebb2a65d771  37 minutes ago  180 MB  
nginx       latest  6b914bbcb89e  7 days ago      182 MB  
ubuntu      latest  0ef2e08ed3fa  8 days ago      130 MB

Мы можем создавать и запускать контейнер из образа. Давайте попробуем сделать это с параметрами по умолчанию:

docker run --rm -v $(pwd)/vol:/data/:rw test-curl

Чтобы просмотреть результаты, сохраненные в файле:

cat ./vol/results  

Попробуем с facebook.com:

docker run --rm -e SITE_URL=https://facebook.com/ -v $(pwd)/vol:/data/:rw test-curl

Чтобы просмотреть результаты, сохраненные в файле:

cat ./vol/results

Рекомендации по созданию образов

  • Избегайте установки ненужных пакетов – они будут потреблять дополнительное дисковое пространство.
  • Используйте кэш.
  • Будьте осторожны с объемами. Вы должны помнить, какие данные в томах. Поскольку тома являются постоянными и не «умирают» с контейнерами – следующий контейнер будет использовать данные из тома, которые были созданы предыдущим контейнером.
  • Используйте переменные среды (в RUN, EXPOSE, VOLUME). Это сделает ваш Dockerfile более гибким.

Соединение между контейнерами

Docker compose — это единственный правильный способ подключения контейнеров друг к другу.

Пример 4: Python + Redis

В этом примере мы подключим контейнеры Python и Redis.

version: '2'  
services:  
  app:
    build:
      context: ./app
    depends_on:
      - redis
    environment:
      - REDIS_HOST=redis
    ports:
      - "5000:5000"
  redis:
    image: redis:3.2-alpine
    volumes:
      - redis_data:/data
volumes:  
  redis_data:

Перейдем к examples/compose и выполним команду:

docker-compose --project-name app-test -f docker-compose.yml up

Консольный вывод

Текущий пример увеличит счетчик просмотров в Redis. Откройте ссылку и убедитесь в этом.

Использование docker-compose – это тема для целого учебника. Чтобы начать работу, вы можете поиграться с некоторыми образами из Docker Hub, а если хотите создать свои собственные – следуйте рекомендациям, перечисленным выше. Единственное, что можно добавить с точки зрения использования docker-compose – всегда давайте явные имена вашим томам. Это простое правило избавит вас от проблемы в будущем.

version: '2'  
services:  
  ...
  redis:
    image: redis:3.2-alpine
    volumes:
      - redis_data:/data
volumes:  
  redis_data:

В этом случае redis_data будет именем внутри файла docker-compose.yml.

Смотрим выполнение тома:

docker volume ls  

Консольный вывод:

DRIVER              VOLUME NAME  
local               apptest_redis_data

Без явного имени тома будет UUID. И вот пример:

DRIVER              VOLUME NAME  
local               ec1a5ac0a2106963c2129151b27cb032ea5bb7c4bd6fe94d9dd22d3e72b2a41b  
local               f3a664ce353ba24dd43d8f104871594de6024ed847054422bbdd362c5033fc4c  
local               f81a397776458e62022610f38a1bfe50dd388628e2badc3d3a2553bb08a5467f  
local               f84228acbf9c5c06da7be2197db37f2e3da34b7e8277942b10900f77f78c9e64  
local               f9958475a011982b4dc8d8d8209899474ea4ec2c27f68d1a430c94bcc1eb0227  
local               ff14e0e20d70aa57e62db0b813db08577703ff1405b2a90ec88f48eb4cdc7c19  
local               polls_pg_data  
local               polls_public_files  
local               polls_redis_data  
local               projectdev_pg_data  
local               projectdev_redis_data

В заключение

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

Также рекомендуем Вам посмотреть:

Более 100 полезных сервисов для разработки на все случаи жизни
164 крутых опенсорс проекта для новичков
Путь Python Junior-а в 2017
10 ресурсов для изучения Linux

Опорные статьи:

https://habr.com/ru/post/253877/

https://tproger.ru/translations/docker-terms/

https://tproger.ru/translations/docker-instuction/

https://xakep.ru/2015/06/01/docker-usage/

https://xakep.ru/2015/06/04/docker-faq/

Официальная документация:

https://docs.docker.com/

https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

Для обычного бытового использования Docker интересен тем, что позволяет собирать программы без смешивания их пакетов с системными и запускать изолированно в контейнере. Это особенно важно при использовании дистрибутивов с длительным сроком поддержки (LTS).

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

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

Основы использования Docker.

Установка:

sudo apt install docker.io

Базовая проверка работоспособности docker:

sudo docker run hello-world

В выводе должно оказаться нечто подобное:

Если демон Docker по какой-то причине не запустился, будет показано предупреждение:

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

Запуск можно сделать вручную:

sudo service docker start

Образы.

Образы загружаются из специального хранилища — docker-реестра: https://hub.docker.com/ В нём хранятся публичные и приватные образы. Можно сформировать собственное хранилище образов или экспортировать образ в файл, что позволит поделиться им любым удобным образом.

Если упрощённо, то образ — это шаблон, содержащий в себе программные компоненты, из копий которого создаются контейнеры.

Образы делятся на базовые (Base images) и дочерние (Child images).

Базовые образы — как правило, содержат в себе операционную систему. (Ubuntu, Debian и подобные).

Дочерние образы — зависят от базовых.

Существуют официальные и пользовательские образы. Названия пользовательских начинаются с имени пользователя, загрузившего образ.

Загрузить образ из https://hub.docker.com/:

sudo docker pull <название_образа>

Можно выбрать конкретную версию образа:

sudo docker pull ubuntu:20.04

В данном случае это Ubuntu 20.04.

Поиск образов в реестре. Найти все образы Ubuntu 20.xx:
sudo docker search ubuntu-20

Вывести список всех загруженных образов:

sudo docker images

Удалить образ:

sudo docker rmi <IMAGE ID>

Пример:

sudo docker rmi 56def654ec22

Принудительно удалить образ:

sudo docker rmi -f <IMAGE ID>

f — отфильтровать образы или контейнеры по предоставленному условию. В данном случае по ID образа.

Удалить образы, которые не используются ни одним контейнером:

sudo docker image prune -a -f

Контейнеры.

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

Вывести список всех контейнеров:

sudo docker ps -a

Вывести список только запущенных контейнеров:

sudo docker ps --no-trunc

—no-trunc — не обрезать вывод. К примеру, будут полностью отображены ID, вместо их сокращения.

Место хранения контейнеров: /var/lib/docker/

Создание и запуск контейнера осуществляется с помощью команды run.

docker run --help

Создание и запуск контейнера на примере busybox (комплект консольных утилит):

sudo docker run busybox echo "hello world"

Из реестра (хранилища) hub.docker.com будет загружен образ busybox (если его нет на локальной машине), из образа будет создан контейнер, внутри которого будет запущена утилита echo, которая отправит в вывод строку «hello world».

Запустить ранее созданный контейнер:

sudo docker start <ID>

Остановить работу контейнера:

sudo docker stop <ID>

Подключение к запущенному контейнеру:

sudo docker exec -it <ID> sh

Контейнеры можно удалить по имени и по ID. Команда:

sudo docker rm <ID>

Пример:

sudo docker rm 791834eb255d

Принудительное удаление запущенного контейнера:

sudo docker rm -f <ID>

Удалить все остановленные контейнеры:

sudo docker container prune -f

Создание и запуск контейнера с желаемым именем (в примере my-busy) с последующим запуском echo, которая выведет строку «hello world»:

sudo docker run --name my-busy busybox echo "hello world"

Создать и запустить контейнер, а затем удалить его по завершению работы (опция —rm).

sudo docker run --rm busybox echo "hello world"

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

Вывести потребляемые контейнером ресурсы:

sudo docker stats <ID>

В контейнеры можно монтировать каталоги основной системы.

Dockerfile и создание собственных образов.

Официальная документация: https://docs.docker.com/engine/reference/builder/

Это текстовый файл, в котором содержатся инструкции для клиента Docker. Используется для создания собственных образов.

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

Собственные образы и контейнеры очень удобно применять для сборки программ. Это особенно актуально при использовании дистрибутивов с длительным сроком поддержки (LTS). Ключевым является то, что не придётся смешивать различные зависимости и самосборные пакеты с системными пакетами. Все они будут изолированы в контейнере, что позитивно скажется на безопасности и стабильности системы.

Наполнение Dockerfile.

Пример содержимого Dockerfile:

# базовый образ
FROM ubuntu:20.04

# установка желаемых пакетов и зависимостей
RUN apt-get update
RUN apt-get install -y wget && echo "We installed wget!"

# создание каталога /mytest
WORKDIR /mytest

# создание файла testfile
RUN touch testfile

# ls и echo будут выполняться при запуске контейнера
CMD ls && echo "Show me mytest"

Каждая строка с командами является инструкцией. Инструкции выполняются одна за другой.

Примечание: Docker рекомендует использовать именно apt-get.

Основные инструкции.

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

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

Команды выполняются последовательно, то есть результат первой порции команд, перечисленных в инструкции RUN, не влияет на команды, перечисленные в следующей RUN. Пример: RUN cd /tmp не будет действовать для RUN ls, то есть ls выведет каталоги корня, а не /tmp.

При выполнении инструкции RUN Docker будет сохранять изменения в файловой системе, накладывая новые изменения поверх старых по слоям. Каждая инструкция RUN создаёт слой. Это используется для ускорения создания образов. Те слои, что не изменились, будут использоваться без повторной обработки.

WORKDIR — создать и/или выбрать рабочий каталог внутри образа, в котором будут выполнены последующие команды, перечисленные в инструкциях RUN и CMD. Особенно полезно при сборке программ.

COPY — копировать указанные файлы и каталоги из каталога контекста сборки в создаваемый образ по пути, указанному в инструкции WORKDIR или напрямую.

ENV — объявить переменную окружения, которая будет использоваться в контейнере.

ENTRYPOINT — запускает указанную программу с желаемыми параметрами при старте контейнера. Команды можно записывать в синтаксисе JSON. Обычно указывается в конце Dockerfile. Пример: ENTRYPOINT [«/bin/sh», «-c»]

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

Прочие инструкции можно посмотреть в официальной документации: https://docs.docker.com/engine/reference/builder/

Пример создания образа.

Команда на создание образа:

sudo docker build -f </путь/до/Dockerfile> -t <имя_создаваемого_образа> </путь/до/контекста>

-f — используется, чтобы указать путь до конкретного Dockerfile. Без этой опции демон Docker будет искать Dockerfile в каталоге выполнения.

-t — имя (тэг) создаваемого образа. В обычном случае создаётся локальный образ с указанным именем, но можно загрузить создаваемый образ в репозиторий, если указать имя пользователя в реестре hub.docker.com, а затем аутентификационные данные. Пример: docker build -t myuser/myimage .

</путь/до/контекста> — это аргумент, указывающий демону контекст сборки, то есть какие файлы и каталоги использовать для сборки образа. Если не планируется добавлять какие-то конкретные файлы и каталоги, то достаточно указать любой пустой каталог. Sending build context to Docker daemon — процесс упаковки и отправки файлов демону для сборки образа. Образ будет собираться в специальном временном каталоге. Указанные файлы будут скопированы, упакованы (tar-архив) и помещены в специальный каталог для последующей сборки. После сборки скопированные файлы будут удалены, а оригиналы останутся без изменений.

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

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

Альтернативный способ создания образа.

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

sudo docker commit <ID_контейнера> <имя_нового_образа>

Пример использования можно найти далее в статье.

Поделиться Docker-образом без использования hub.docker.com.

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

sudo docker save --output <имя_файла_образа> <ID_исходного_образа>

Пример сохранения образа ubuntu в файл-образ с именем my-exp-ubuntu.tar в каталог ~/Documents/.

sudo docker save --output ~/Documents/my-exp-ubuntu.tar ubuntu

Чтобы использовать сохранённый образ, его необходимо импортировать:

sudo docker load --input <имя_файла_образа>

sudo docker load --input ~/Downloads/my-exp-ubuntu.tar

Примеры использования Docker.

Монтирование каталогов в контейнер Docker.

Официальная документация:

https://docs.docker.com/storage/volumes/

Docker позволяет монтировать каталоги основной системы в контейнеры, что делает доступным их содержимое для утилит внутри контейнера. К примеру, можно смонтировать в контейнер каталог ~/Video/, чтобы обработать его содержимое с помощью ffmpeg, который находится в контейнере.

Использование на примере busybox.

Создать контейнер из образа busybox, примонтировать каталог ~/Documents/ к контейнеру, запустить утилиту ls внутри контейнера для смонтированного в него каталога, вывести результат и удалить контейнер:

sudo docker run -v ~/Documents/:/mnt/ --rm busybox ls /mnt/

-v — указывает хранилище, которое будет смонтировано в контейнер. Хранилище представляет собой обычный ранее созданный каталог, в котором могут содержаться другие каталоги и файлы. При монтировании контейнер получит доступ к этому каталогу и всему его содержимому. В примере это каталог ~/Documents/.

/mnt — обозначает точку монтирования. В примере это ~/Documents/. Пример пути: /mnt/каталог/файл, что является аналогичным ~/Documents/каталог/файл.

—rm — удалить контейнер после завершения его работы.

Использование интерпретатора sh из контейнера.

sudo docker run --rm -it busybox sh

Образ busybox будет загружен из реестра hub.docker.com (если его нет на локальной машине), из образа busybox будет создан контейнер, внутри которого будет запущена утилита sh, после чего станет доступным ввод команд, которые будут выполняться утилитами busybox внутри контейнера. После завершения работы контейнера он будет удалён (опция —rm).

-i — интерактивный режим.

-t — проброс терминала в контейнер.

Благодаря опции -it будет выделен псевдо-tty, работающий в интерактивном режиме, а не только на вывод, что позволит выполнять команды внутри контейнера через терминал. Без опции -it утилита sh будет запущена единожды, отобразится вывод и работа контейнера завершится.

Команда ls покажет набор каталогов от корня внутри контейнера:

Пример перехода в каталог /bin и выполнение команды ls:

На иллюстрации показано всё многообразие утилит из комплекта busybox.

Для завершения работы этого контейнера необходимо ввести exit.

Использование браузера elinks из контейнера.

Ниже рассмотрен пример установки в контейнер текстового браузера elinks и сохранения изменений в образ.

На базе образа ubuntu будет создан контейнер с именем —name my-base-ubuntu:

sudo docker run -it --name my-base-ubuntu ubuntu sh

При этом с помощью опции -it пробрасывается терминал в интерактивной сессии и запускается утилита sh.

Теперь можно выполнять команды непосредственно внутри контейнера.

Установка браузера elinks:

apt-get update

apt-get install elinks

После завершения установки пакетов можно завершить сессию:

exit

Проверяем наличие контейнера в списке контейнеров:

sudo docker ps -a

Всё в порядке.

Теперь предстоит сохранение изменений, которые были сделаны в контейнере (установлены пакеты для браузера elinks), в образ:

sudo docker commit my-base-ubuntu my-ubuntu-elinks

Сначала указывается имя контейнера (можно указать ID), а потом имя для образа, который будет создан. В данном случае будет создан образ с именем my-ubuntu-elinks.

Проверим наличие образа. Вывести список всех доступных образов:

sudo docker images

Теперь можно удалить контейнер, из которого был создан образ:

sudo docker rm my-base-ubuntu

Создание контейнера из свежесозданного образа и запуск браузера в интерактивной сессии проброшенного терминала:

sudo docker run -it --rm my-ubuntu-elinks elinks

Результат:

Благодаря опции —rm, контейнер будет удалён после завершения работы.

Использование на примере Cmake-Converter.

Официальная документация: https://cmakeconverter.readthedocs.io/en/latest/intro.html

Программа предназначена для автоматического преобразования солюшена (.sln) Visual Studio в CmakeLists.txt. Программа написана на Python и доступна из репозитория посредством пакетного менеджера pip.

Установка и использование очень просты, что очень удобно для освоения Docker.

Настройка Dockerfile.

FROM python

RUN pip install cmake_converter

# Программа располагается здесь: /usr/local/bin/cmake-converter
WORKDIR /usr/local/bin

Для примера Dockerfile будет сохранён по следующему пути: ~/Documents/docker/cmake-converter/

Создание образа.

Перейти в каталог с Dockerfile. Пример:

cd ~/Documents/docker/cmake-converter/

Создать образ с именем my-cmake-converter:

sudo docker build -t my-cmake-converter .

Точка в конце указывает на то, что контекст сборки располагается в том же каталоге, где выполняется команда на сборку. В данном случае это место хранения Dockerfile: ~/Documents/docker/cmake-converter/

Запуск контейнера и использование cmake-converter.

Примечание: В данном примере sln-файл находится в ~/Documents/.

Запустить контейнер my-cmake-converter, смонтировать каталог ~/Documents/ в каталог /mnt/ внутри контейнера, выполнить программу cmake-converter и указать ей путь до sln-файла, который нужно преобразовать в CMakeLists.txt:

sudo docker run -v ~/Documents/:/mnt/ --rm my-cmake-converter cmake-converter -s /mnt/my-project.sln

Готово.

Использование на примере утилиты untrunc.

Официальный репозиторий: https://github.com/ponchio/untrunc

Утилита предназначена для исправления следующей проблемы: moov atom not found audio.mp4: Invalid data found when processing input.

То есть позволяет исправить битое видео в формате mov. К примеру, видео может побиться, если некорректно завершить процесс записи. Утилита позволяет подсунуть moov атом из нормального видео в битое, тем самым решая проблему.

Docker-файл: https://github.com/ponchio/untrunc/blob/master/Dockerfile

Открыть в терминале желаемый каталог, куда планируется загрузить Dockerfile. Пример:

cd ~/Documents/docker/untrunc/

Загрузить файл:

wget https://github.com/ponchio/untrunc/blob/master/Dockerfile

В этой же сессии терминала выполнить команду на создание образа:

sudo docker build -t untrunc .

Создание контейнера и запуск утилиты:

sudo docker run -v ~/Video/:/mnt/ --rm untrunc /mnt/video_good.mp4 /mnt/video_bad.mp4

-v — указать каталог, который следует смонтировать в контейнер. Контейнер получит доступ ко всему содержимому каталога. В данном примере монтируется каталог ~/Video/ в каталог /mnt/ внутри контейнера.

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

untrunc — непосредственно сама утилита, находящаяся в контейнере. Сначала указывается видео-донор, в котором всё в порядке с moov атомом, а следом указывается видео с повреждённым.

Если всё правильно, то пойдёт процесс обработки, который зависит от объёма видео. Обработка видео 1 Гб занимает до 10 минут. Если moov атом не подходит, то будет указана ошибка.

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

Использование утилиты alien.

Утилита применяется для конвертации rpm-пакетов в deb-пакеты. Она требует довольно много зависимостей, которые не хочется тащить в систему. Поэтому сделаем собственный Docker-образ на базе Ubuntu.

Настройка Dockerfile.

FROM ubuntu

# настройка часовых поясов для tzdata
ENV TZ=Europe/Moscow
RUN ln -fs /usr/share/zoneinfo/$TZ /etc/localtime

RUN apt-get update

# установка с автоматическим подтверждением в диалогах
RUN apt-get install -y alien

# выполнить /usr/bin/alien при старте контейнера
ENTRYPOINT ["/usr/bin/alien"]

В данном примере Dockerfile будет сохранён в ~/Documents/docker/alien/.

Создание образа.

Перейти в каталог с Dockerfile:

cd ~/Documents/docker/alien/

Создать образ с именем alien:

sudo docker build -t alien .

Создание контейнера и запуск утилиты:

sudo docker run -v ~/Downloads/:/mnt/ --rm alien /mnt/пакет.rpm

В данном примере rpm-пакет находится в каталоге ~/Downloads/, который был смонтирован в каталог /mnt/ внутри контейнера.

deb-пакет будет создан с тем же именем, что и исходный rpm-пакет.

Contents

  • 1 Docker и Docker-Compose — Tutorial и подборка видео по темам
    • 1.1 Что такое Docker и зачем он нужен?
      • 1.1.1 Видео — Что такое Docker за 200 секунд
      • 1.1.2 Сущности Docker: docker daemon, container, image, Dockerfile, Docker Registry
    • 1.2 Что такое docker image (образ)
    • 1.3 Что такое docker container (контейнер)
    • 1.4 Управление контейнерами. Схема Lifecycle of Docker Container
    • 1.5 Что такое Docker Hub?
    • 1.6 Как создать свой образ? Что такое Dockerfile?
      • 1.6.1 Пример dockerfile
      • 1.6.2 Команды Dockerfile
      • 1.6.3 Пример dockerfile для приложения flask app python
      • 1.6.4 Видео Tutorials — Dockerfile
    • 1.7 Что такое Docker Volume?
      • 1.7.1 Команды Docker Volume
      • 1.7.2 Подборка видео по Docker Volume
    • 1.8 Как взаимодействовать с контейнером?
    • 1.9 Docker Networking
    • 1.10 Что такое Docker Compose?
    • 1.11 docker-compose.xml
    • 1.12 Дополнительные подборки видео по Docker
  • 2 Примеры создания приложений с помощью Docker или Docker-Compose
      • 2.0.1 Docker + ReactJS tutorial: Development to Production workflow + multi-stage builds + docker compose
  • 3 Краткий экскурс в Linux
    • 3.1 Базовый список команд Linux
    • 3.2 Linux File System/Structure Explained
    • 3.3 Основы Ubuntu Linux: apt-get, bash, командная строка
    • 3.4 Linux command line for beginners
    • 3.5 Права Доступа и владения файлами и директориями
  • 4 Установка Докера на Linux. Install Docker on Ubuntu 20.04
  • 5 Portainer — что это такое?
    • 5.1 Установка Portainer внутри Docker
  • 6 GitHub Actions — CI/CD Pipeline with Docker
  • 7 Использованные источники для подготовки статьи и другие полезные статьи

Что такое Docker и зачем он нужен?

Официальный сайт docker.com.

Docker — это платформа для разработки, развертывания и запуска приложений внутри контейнеров.

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

И еще одно определение докера:

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

Видео — Что такое Docker за 200 секунд

Сущности Docker: docker daemon, container, image, Dockerfile, Docker Registry

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

Рассмотрим компоненты рабочего процесса и инструменты для управления контейнерами и их развертывания, составляющие экосистему Docker:

  • Daemon Docker (Сервер): Выполняется в хост-системе и управляет всеми запущенными контейнерами. Docker Daemon — это сервер Docker, который прослушивает запросы Docker API. Docker Daemon управляет образами, контейнерами, сетями и томами.
  • Docker Container (Контейнер): Автономная виртуальная система, содержащая выполняющийся процесс, все файлы, зависимости, адресное пространство процесса и сетевые порты, необходимые приложению. Так как каждый контейнер имеет свое пространство портов, следует организовать их отображение в фактические порты на уровне Docker;
  • Docker Client (Клиент): Пользовательский интерфейс, или интерфейс командной строки, для взаимодействий с демоном Docker. Клиент Docker — это основной способ взаимодействия с Docker. Когда вы используете интерфейс командной строки (CLI) Docker, вы вводите в терминал команду, которая начинается сdocker. Затем клиент Docker использует API Docker для отправки команды демону Docker.
  • Docker Image (Образ): это шаблон только для чтения, который содержит набор инструкций по созданию контейнера, который может работать на платформе Docker. Он предоставляет удобный способ упаковать приложения и предварительно настроенные серверные среды, которые вы можете использовать для личного использования или публично публиковать с другими пользователями Docker. Также можно воспользоваться командой docker diff, чтобы увидеть различия между двумя образами. Каждый образ состоит из нескольких уровней, или слоев, которые могут совместно использоваться несколькими образами.
  • Реестр Docker: Репозиторий для хранения и распространения образов контейнеров Docker. Пример известного реестра — Docker Hub, куда можно помещать и откуда можно извлекать образы.
  • Dockerfile: Это очень простой текстовый файл, содержащий команды, которые выполняют сборку образов Docker. Посредством этих команд можно устанавливать дополнительные программные компоненты, настраивать переменные окружения, рабочие каталоги и точку входа ENTRYPOINT, а также добавлять новый код;
  • Docker Swarm: По сути своей, это готовый к использованию механизм кластеризации, позволяющий объединить несколько узлов Docker в один большой хост Docker.
  • Docker Compose: Приложения часто состоят из множества компонентов, и соответственно они будут выполняться в нескольких контейнерах. В состав Docker входит инструмент Compose, с помощью которого можно легко запустить приложение в нескольких контейнерах. Вы можете определить окружение для приложения в общем файле Dockerfile и определить перечень служб в файле docker-compose.yml, после чего Docker автоматически будет создавать и запускать необходимые контейнеры, как определено в этих файлах.

Что такое docker image (образ)

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

Образ Docker состоит из набора файлов, которые объединяют воедино все необходимое, например installations, application code, и dependencies, необходимые для настройки полностью работоспособной среды контейнера. Вы можете создать образ Docker одним из двух способов:

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

  • Dockerfile: путем создания текстового файла, известного как Dockerfile, который предоставляет спецификации для создания образа Docker.

Dockerfile — это файл с инструкциями о том, как Docker должны строить свой image.

С физической точки зрения docker image состоит из набора слоев, доступных только для чтения (read-only layers). Слои image работают следующим образом:

  • Каждый слой image является результатом одной команды в файле Dockerfile. Образ докера представляет собой сжатый (tar) файл, содержащий серию слоев.
  • Каждый дополнительный слой image включает только набор отличий от предыдущего слоя (попробуйте запустить для docker image команду docker history, которая выведет все его слои и те команды, которые их создало).

Пример Dockerfile:

FROM node:13.12.0alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install

COPY . .

Что такое docker container (контейнер)

Docker Container — это исполняемый экземпляр образа. Образ Docker плюс команда docker run image_name создает и запускает контейнер из образа.

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

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

Управление контейнерами. Схема Lifecycle of Docker Container

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

Пример схемы docker container lifecycle 1:

Пример схемы docker container lifecycle 2:

Create container

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

docker create name <containername> <imagename>

Run docker container

Запустите контейнер докера с требуемым образом и указанной командой / процессом. Флаг -d используется для запуска контейнера в фоновом режиме.

docker run it d name <containername> <imagename> bash 

Pause container

Используется для приостановки процессов, запущенных внутри контейнера.

docker pause <containerid/name> 

Unpause container

Используется для возобновления процессов внутри контейнера.

docker unpause <containerid/name> 

Start container

Запустите контейнер, если он находится в остановленном состоянии.

docker start <containerid/name> 

Stop container

Остановить контейнер и процессы, запущенные внутри контейнера:

docker stop <containerid/name> 

Чтобы остановить все запущенные контейнеры докеров

docker stop $(docker ps a q)

Restart container

Используется для перезапуска контейнера, а также процессов, работающих внутри контейнера.

docker restart <containerid/name> 

Kill container

Мы можем убить работающий контейнер.

docker kill <containerid/name> 

Destroy container

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

docker rm <containerid/name> 

Чтобы удалить все остановленные контейнеры докеров

docker rm $ (docker ps q f status = exited)

Что такое Docker Hub?

Docker Hub — это облачная служба реестра, которая позволяет загружать образы Docker, созданные другими сообществами. Вы также можете загрузить свои собственные образы, созданные Docker, в Docker Hub.

Как создать свой образ? Что такое Dockerfile?

Что такое Dockerfile?

  • Dockerfile — это текстовый файл конфигурации, написанный с использованием специального синтаксиса.
  • В нем описываются пошаговые инструкции по всем командам, которые необходимо выполнить для сборки образа Docker.
  • Команда docker build обрабатывает этот файл, создавая образ Docker в вашем локальном кэше образов, который затем можно запустить с помощью docker run команды или отправить в постоянный репозиторий образов (push).

Как создать Dockerfile?

Dockerfile создается в любом текстовом редакторе. Далее пишутся инструкции.

Пример dockerfile

Пример dockerfile для NGINX:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

# Каждая инструкция в этом файле генерирует новый слой, который помещается в ваш локальный кеш docker image

# Строки, которым предшествует #, считаются комментариями и игнорируются

# В строке ниже указано, что мы будем основывать наш новый образ на последней официальной версии Ubuntu

FROM ubuntu: latest

# Указываем, кто поддерживает docker image (автор)

LABEL Maintainer = «myname@somecompany.com»

# Обновляем образ до последних пакетов

RUN aptget update && aptget upgrade y

# Устанавливаем NGINX

RUN aptget install nginx y

# Выставить порт 80

EXPOSE 80

# Последний пункт — это команда для запуска NGINX в нашем контейнере

CMD [«nginx», «-g», «daemon off;»]

Команды Dockerfile

ADD — определяет файлы для копирования из файловой системы хоста в контейнер

ADD ./local/config.file /etc/service/config.file

CMD — это команда, которая будет запускаться при запуске контейнера

CMD [nginx, g, daemon off;]

ENTRYPOINT — устанавливает приложение по умолчанию, используемое каждый раз, когда контейнер создается из образа. Если используется вместе с CMD, вы можете удалить приложение и просто определить там аргументы

CMD Hello World!

ENTRYPOINT echo

ENV — установка / изменение переменных среды в контейнерах, созданных из образа

EXPOSE — Определите, какие порты контейнера открывать

FROM — Выберите базовый образ, чтобы построить новый образ поверх

LABEL Maintainer — необязательное поле, чтобы вы могли идентифицировать себя как maintainer этого образа. Это просто ярлык

LABEL maintainer=someone@xyz.xyz

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

RUN aptget update && aptget upgrade y && aptget install y nginx && rm rf/var/lib/apt/lists/*

USER — Определите пользователя по умолчанию, все команды будут запускаться, как в любом контейнере, созданном из вашего образа. Это может быть либо UID, либо имя пользователя.

VOLUME — создает точку монтирования в контейнере, связывая ее с файловыми системами, доступными хосту Docker. Новые тома заполняются уже существующим содержимым указанного места на image. Особенно уместно упомянуть, что определение томов в Dockerfile может привести к проблемам. Томами следует управлять с помощью команд docker-compose или docker run. Volumes не являются обязательными. Если у вашего приложения нет состояния (и большинство веб-приложений работают так), вам не нужно использовать тома. Volume требуются для баз данных, например, или для сохранения логов приложения.

WORKDIR — Определите рабочий каталог по умолчанию для команды, определенной в инструкциях «ENTRYPOINT» или «CMD».

Пример dockerfile для приложения flask app python

FROM python:3.6.4alpine3.6

ENV FLASK_APP=minitwit

COPY . /app

WORKDIR /app

RUN pip install editable .

RUN flask initdb

EXPOSE 5000

CMD [ «flask», «run», «—host=0.0.0.0» ]

Видео Tutorials — Dockerfile

Video: Dockerfile Tutorial — Docker in Practice

Видео: Docker создаем собственный образ

Видео: Dockerfile. Формат и создание образа контейнера

Что такое Docker Volume?

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

Когда мы запускаем новый контейнер, Docker добавляет слой чтения-записи поверх слоев image, позволяя контейнеру работать как в стандартной файловой системе Linux .

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

При монтировании привязки (bind mount) используется файловая система хоста, но Docker Volumes встроены в Docker. Данные хранятся где-то в хранилище, подключенном к хосту — часто в локальной файловой системе:

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

В docker-compose.ymlvolumes может появляться в двух разных местах:

version: «3.7»

services:

  database:

    # …

    volumes: # Вложенный ключ. Настраивает тома для определенной службы.

volumes: # Ключ верхнего уровня. Объявляет тома, на которые можно ссылаться из нескольких сервисов.

  # …

Команды Docker Volume

Command Description
docker volume create Create a volume
docker volume inspect Display detailed information on one or more volumes
docker volume ls List volumes
docker volume prune Remove all unused local volumes
docker volume rm Remove one or more volumes

Подборка видео по Docker Volume

Docker Volumes explained in 6 minutes

Docker Volumes (Тома) урок 8

Attaching volumes to containers

Как взаимодействовать с контейнером?

Docker не открывает порты по умолчанию, вы должны настроить каждый открытый порт самостоятельно!

Чтобы сопоставить порт на хосте с контейнером, нам нужно использовать флаг -p команды docker run:

docker run p <port_number_on_host>:<port_number_on_container> <image>

или

docker run v <порт_на_хосте>:<порт_в_контейнере> <образ>

Следующая команда запустит контейнер для mlflow и сопоставит порт 7000 этого контейнера с портом 7000 хоста докера:

docker run p 7000:7000 mlflow

Аналогичным образом будет работать контейнер для POSTGRESQL и порта 5432 этого контейнера в порт 5432 от DOCKER хоста:

docker run p 5432:5432 postgresql

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

docker run p 1234:5432 postgresql

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

Чтобы открыть только один порт, выполните следующую строку:

docker container run p 8080:80 d nginx

Порт 80 контейнера Nginx доступен для внешнего мира через порт хоста 8080.

По умолчанию Docker предоставляет порты контейнера IP-адресу 0.0.0.0 (это соответствует любому IP-адресу в системе). Вы также можете указать Docker, какой IP-адрес нужно привязать. Это может быть 127.0.0.1 или другой IP-адрес.

Чтобы привязать порт 80 контейнера Docker к порту 8000 хост-системы и IP-адресу 127.0.0.1 (он же localhost), просто выполните следующую команду:

docker run d p 127.0.0.1:8000:80 nginx

Docker Networking

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

Docker Networking

Docker Networking Tutorial

Что такое Docker Compose?

Docker Compose — это инструмент, который упрощает запуск приложений, состоящих из нескольких контейнеров.

Docker Compose позволяет записывать команды в docker-compose.yml файл для повторного использования. Интерфейс командной строки Docker Compose (cli) упрощает взаимодействие с вашим многоконтейнерным приложением. Docker Compose поставляется бесплатно с установленным вами Docker.

Схема работы Docker-Compose:

Docker-Compose in 12 Minutes

docker-compose.xml

docker-compose.yml — это файл Docker-Compose, который содежит инструкции, используемые для запуска и настройки сервисов (отдельных контейнеров нашего многоконтейнерного приложения).

Структура файла docker-compose.xml (источник — Руководство по Docker Compose для начинающих):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

# Файл docker-compose должен начинаться с тега версии.

# Мы используем «3» так как это — самая свежая версия на момент написания этого кода.

version: «3»

# Следует учитывать, что docker-composes работает с сервисами.

# 1 сервис = 1 контейнер.

# Сервисом может быть клиент, сервер, сервер баз данных…

# Раздел, в котором будут описаны сервисы, начинается с ‘services’.

services:

  # Как уже было сказано, мы собираемся создать клиентское и серверное приложения.

  # Это означает, что нам нужно два сервиса.

  # Первый сервис (контейнер): сервер.

  # Назвать его можно так, как нужно разработчику.

  # Понятное название сервиса помогает определить его роль.

  # Здесь мы, для именования соответствующего сервиса, используем ключевое слово ‘server’.

  server:

    # Ключевое слово «build» позволяет задать

    # путь к файлу Dockerfile, который нужно использовать для создания образа,

    # который позволит запустить сервис.

    # Здесь ‘server/’ соответствует пути к папке сервера,

    # которая содержит соответствующий Dockerfile.

    build: server/

    # Команда, которую нужно запустить после создания образа.

    # Следующая команда означает запуск «python ./server.py».

    command: python ./server.py

    # Вспомните о том, что в качестве порта в ‘server/server.py’ указан порт 1234.

    # Если мы хотим обратиться к серверу с нашего компьютера (находясь за пределами контейнера),

    # мы должны организовать перенаправление этого порта на порт компьютера.

    # Сделать это нам поможет ключевое слово ‘ports’.

    # При его использовании применяется следующая конструкция: [порт компьютера]:[порт контейнера]

    # В нашем случае нужно использовать порт компьютера 1234 и организовать его связь с портом

    # 1234 контейнера (так как именно на этот порт сервер

    # ожидает поступления запросов).

    ports:

       1234:1234

  # Второй сервис (контейнер): клиент.

  # Этот сервис назван ‘client’.

  client:

    # Здесь ‘client/ соответствует пути к папке, которая содержит

    # файл Dockerfile для клиентской части системы.

    build: client/

    # Команда, которую нужно запустить после создания образа.

    # Следующая команда означает запуск «python ./client.py».

    command: python ./client.py

    # Ключевое слово ‘network_mode’ используется для описания типа сети.

    # Тут мы указываем то, что контейнер может обращаться к ‘localhost’ компьютера.

    network_mode: host

    # Ключевое слово ‘depends_on’ позволяет указывать, должен ли сервис,

    # прежде чем запуститься, ждать, когда будут готовы к работе другие сервисы.

    # Нам нужно, чтобы сервис ‘client’ дождался бы готовности к работе сервиса ‘server’.

    depends_on:

       server

Пример сложного файла docker-compose.xml:

Если хотите реально разобраться в docker — очень рекомендую курс Docker и Docker Compose — Деплой проекта с нуля

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

version: ‘3’

services:

  frontend:

    build:

      dockerfile: Dockerfile.prod

      context: ./frontend

    container_name: dockerfrontend

    restart: unlessstopped

    command: serve s build l 3000

    networks:

       dockernetwork

  api:

    build: ./api

    container_name: dockerapi

    command: npm run start

    restart: unlessstopped

    environment:

       PORT=3001

       MONGO_URL=mongodb://api_db:27017/api

       AUTH_API_URL=http://auth:3002/api

    depends_on:

       api_db

    networks:

       dockernetwork

  auth:

    build: ./auth

    container_name: dockerauth

    command: npm run start

    restart: unlessstopped

    environment:

       PORT=3002

       MONGO_URL=mongodb://auth_db:27017/auth

       API_URL=http://api:3001/api

    depends_on:

       auth_db

    networks:

       dockernetwork

  api_db:

    image: mongo:latest

    container_name: dockerapidb

    volumes:

       mongodb_api:/data/db

    networks:

       dockernetwork

  auth_db:

    image: mongo:latest

    container_name: dockerauthdb

    volumes:

       mongodb_auth:/data/db

    networks:

       dockernetwork

  nginx:

    image: nginx:stablealpine

    container_name: dockernginx

    ports:

       «80:80»

    volumes:

       ./nginx/nginx.conf.prod:/etc/nginx/conf.d/nginx.conf

    depends_on:

       frontend

       api

       auth

    networks:

       dockernetwork

volumes:

  mongodb_api:

  mongodb_auth:

networks:

  dockernetwork:

    driver: bridge

Дополнительные подборки видео по Docker

Что такое Docker? Вечерняя школа Слёрма по Kubernetes.

Основы Docker. Большой практический выпуск

Docker Tutorial for Beginners [FULL COURSE in 3 Hours]

Docker-compose что это?

Примеры создания приложений с помощью Docker или Docker-Compose

Docker + ReactJS tutorial: Development to Production workflow + multi-stage builds + docker compose

Краткий экскурс в Linux

Базовый список команд Linux

1. Команда help
Запомнить все возможные параметры, которые можно использовать для команды, невозможно. Если вы не использовали команду ранее или в течение длительного времени, у вас есть возможность изучить возможные варианты, которые можно передать. Большинство команд поддерживают передачу справки в качестве опции и отображают небольшое сообщение о том, как использовать команду.

<command_name> help

Чтобы получить всю возможную помощь, есть команда man. Страница руководства (сокращенно man) — это документ, который иллюстрирует, что делает команда, возможные параметры, примеры использования и т. Д. Это вся помощь, которую вы можете получить для команды

man <command_name>

2. pwd: pwd печатает имя текущего рабочего каталога. Когда вы открываете терминал, вы попадаете в домашний каталог пользователя, под которым вы вошли в систему. pwdпечатает абсолютный путь. Он начинается с /корневого каталога файловой системы Linux.

3. ls: ls распечатывает все файлы, которые присутствуют в каталоге, в котором вы находитесь. Вы можете получить дополнительную информацию о файлах, а также увидеть скрытые файлы с помощью этой -al опции.

4. cd: cd означает смену каталога. Если вы хотите перейти в другой каталог, используйте эту команду. При использовании без аргументов cd приведет вас в ваш домашний каталог. Вы можете передать абсолютный или относительный путь команде cd. Также обратите внимание, что это . представляет текущий каталог и .. родительский каталог.

# takes you to your home directory (comment)

cd

# To navigate to the parent directory

cd ../

# To change to previous working directory

cd ~

5. mv: mv (расширяется при перемещении) используется для перемещения файлов / каталогов из location1 в location2. Его также можно использовать для переименования файла.

# Renaming file a.txt to b.txt

mv a.txt b.txt

# move file from directory test in your current directory to tmp

mv test/test.txt /tmp/test.txt

# move multiple files in your current directory to /tmp

mv a.txt b.txt c.txt /tmp

# move file from a dir A to dir B by specifying the absolute paths

mv /var/log/test.log /tmp/test.log

6. cp: скопируйте файлы из location1 в location2. Каталоги можно копировать с помощью -R опции.

# copy file from your home to directory to another location

cp test.txt /tmp/bckup

# copy contents of directories recursively

cp R test_dir /tmp/bckup

7. rm: rm (расширяется как remove) используется для удаления файла или каталога. При удалении файла нет отмены. Так что будьте осторожны, когда хотите что-то удалить

# rm a file in a location

rm /home/test_user/test.txt

# rm a directory that is empty

rm r <location_of_dir>

# rm a directory with contents in it

rm rf <location_of_dir>

8. mkdir: mkdir (расширенный как каталог make) используется для создания нового каталога в определенном месте.

# create a directory

mkdir test_dir

# create intermediate directory (test_directory) when creating a directory (test_directory_child)

mkdir p /home/test_user/test_directory/test_directory_child

9. rmdir: удалить каталог. Это альтернативная команда дляrm -rf

# delete a directory test_directory_child

rmdir /home/test_user/test_directory/test_directory_child

# Delete the intermediate directories in the path. It only works if # the intermediate directories don’t contain any other child

# directories other than the one specified

rmdir p test_dir/test_dir_child/test_dir_child2

10. cat: распечатывает содержимое файлов на терминале и возвращает обратно в командную строку. Существуют различные редакторы, которые вы можете использовать для просмотра содержимого файла (например, vim, nano и т. Д.), Но эта команда выводит содержимое в STDOUT (стандартный вывод).

# Print contents of a file.

cat test.sh

11. touch: Изменить file timestamps. Обновляет отметки времени в существующих файлах или создает файлы с текущей отметкой времени, если она не существует.

# Assuming file1.txt doesn’t exist, it creates a new file

touch file1.txt

12. sudo: Сокращение от SuperUser Do , эта команда помогает вам выполнять задачи, требующие административных разрешений. Однако не рекомендуется использовать эту команду случайным образом без причины, потому что любая ошибка, сделанная пользователем root, необратима.

# Read the contents of syslog

sudo cat /var/log/syslog

13. find: эта команда ищет в иерархии папок файл или каталог, соответствующий указанному имени или шаблону. Он выполняет рекурсивный поиск во всех каталогах вниз по дереву.

# find a file with name test.txt in a folder

find /home/test_user name test.txt

# find file of specific pattern recursively

find . type f name «*.sh» R

# find and remove multiple files following a same pattern

find . type f name «*.py» exec rm f {}

14.grep: эта команда печатает все строки в файле, соответствующие определенному шаблону.

# recursively grep for a pattern in a directory tree

grep r search_field /etc/

# search two different words

egrep w ‘x|y’ /home/test_user/*

15.df: Эта команда сообщает об использовании дискового пространства файловой системы.

# lists all the file system, use -a option in human readable format i.e., power of 1024

df ah

16. du: оценка использования файлового пространства. Он имеет различные параметры для отображения вывода в желаемом формате.

# Display disk usage of all files and directories in the current directory

du ah

# sum of sizes of files and directories in the directory specified

du sh /home/test_user

17. uname: Распечатать системную информацию. Есть несколько вариантов, которые можно передать, если вам нужна конкретная информация о системе, такая как версия ядра, тип процессора, аппаратная платформа и т. Д.

# Prints all the information about the system

uname a

18. lsblk: lsblk (Expanded as list block devices) используется для отображения всех блочных устройств в виде дерева. Он также предоставляет информацию о разделах, имеющихся на блочном устройстве.

NAME    MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINT

sda       8:0    0  55.9G  0 disk

└─sda1    8:2    0  55.9G  0 part  /

sdb       8:16   0 111.8G  0 disk

└─sdb1    8:17   0 111.8G  0 part

19. hostname: распечатать / установить имя хоста машины. Только суперпользователь может обновить имя хоста

# print hostname of system

hostname

# set hostname

hostname set name <host_name>

20. tail: отображает последнюю часть файлов. По умолчанию, если файл передан, он печатает последние 10 строк

# output appended data as the file grows;

tail f /var/log/syslog

Linux File System/Structure Explained

Основы Ubuntu Linux: apt-get, bash, командная строка

Linux command line for beginners

Права Доступа и владения файлами и директориями

Установка Докера на Linux. Install Docker on Ubuntu 20.04

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

# Обновляем существующий список пакето

sudo apt update

# Далее устанавливаем пакеты, которые позволят apt использовать пакеты через HTTPS:

sudo apt install apttransporthttps cacertificates curl softwarepropertiescommon

# Далее добавим ключ GPG для официального репозитория Docker

curl fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add —

# Добавляем ремозиторий докер в источники apt

sudo addaptrepository «deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable»

# Обновляем базу данных пакетов и добавим в нее пакеты Docker из недавно добавленного репозитория

sudo apt update

# Далее проверим, что установка будет выполняться из репозитория Docker, а не из репозитория Ubuntu по умолчанию

aptcache policy dockerce

# Мы должны получить следующий ответ (номер версии Docker может отличаться):

# root@apache1superset:~# apt-cache policy docker-ce

# docker-ce:

#   Installed: (none)

#   Candidate: 5:20.10.6~3-0~ubuntu-focal

#   Version table:

#      5:20.10.6~3-0~ubuntu-focal 500

#         500 https://download.docker.com/linux/ubuntu focal/stable amd64 Packages

#      5:20.10.5~3-0~ubuntu-focal 500

#         500 https://download.docker.com/linux/ubuntu focal/stable amd64 Packages

#      5:20.10.4~3-0~ubuntu-focal 500

#         500 https://download.docker.com/linux/ubuntu focal/stable amd64 Packages

#      5:20.10.3~3-0~ubuntu-focal 500

# …

# Далее устанавливаем докер командой (на доп.вопрос отвечаем «yes»)

sudo apt install dockerce

# Docker будет автоматически установлен, также запустится демон-процесс и будет активирован запуск при загрузке.

# Проверить статус докера можно командой (что он running/active):

sudo systemctl status docker

# Загружаем текущую стабильную версию Docker Compose

sudo curl L «https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m)» o /usr/local/bin/dockercompose

# Применяем разрешения для исполняемого файла к двоичному файлу

sudo chmod +x /usr/local/bin/dockercompose

# Чтобы протестировать docker-compose (установилась версия или нет), запустим команду

dockercompose version

Portainer — что это такое?

Portainer — это легкий пользовательский интерфейс управления, который позволяет легко управлять различными средами Docker и docker-containers. С помощью Portainer вы сможете в несколько кликов запустить на вашем сервере готовые контейнеры с популярным ПО и связать их между собой.

Описание с Github: Making Docker and Kubernetes management easy. На момент написания статьи 18.7k stars

Этот мощный набор инструментов с открытым исходным кодом, насчитывающий более полумиллиона постоянных пользователей, позволяет легко создавать и управлять контейнерами в Docker, Swarm, Kubernetes и Azure ACI.

По умолчанию мы управляем Docker через командную строку. Portainer предоставляет отличный и очень информативный пользовательский интерфейс для управления всеми аспектами Docker. Это диаграммы и информационные панели, которые предоставляют диаграммы использования ресурсов контейнера в реальном времени. Запуск и управление контейнерами и стеками можно выполнить несколькими щелчками мыши.

После установки portainer и захода внутрь вы видете первое окно настройки. У меня докер развернут локально, поэтому я подключаюсь через пункт local, нажимаю connect и попадаю в админку.

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

Щелкните экземпляр Local Docker, чтобы увидеть панель мониторинга (Dashboard), на которой отображается общий обзор того, что в настоящее время выполняется в Docker.

Здесь нам открывается вся суть инструмента:

  • Можно создать в несколько кликов из шаблонов приложения в докере

  • Можно управлять сетью, контейнерами.
  • Также можно создавать и настраивать Volumes:

Portainer — отличный инструмент, который можно использовать для управления контейнерами Docker, службами Swarm и другими ресурсами с помощью простого и интуитивно понятного пользовательского веб-интерфейса. Portainer предоставляет несколько бесплатных функций и платных плагинов, которые могут улучшить взаимодействие с пользователем при управлении кластерами Docker.

Установка Portainer внутри Docker

#portainer install

sudo docker volume create portainer_data

docker run d p 8000:8000 p 9443:9443 name portainer

    restart=always

    v /var/run/docker.sock:/var/run/docker.sock

    v portainer_data:/data

    portainer/portainerce:2.9.3

Далее нужно перейти по ссылке https://68.133.234.42:9443/ (замените 68.133.234.42 на ваш адрес).

GitHub Actions — CI/CD Pipeline with Docker

Использованные источники для подготовки статьи и другие полезные статьи

  • https://medium.com/@BeNitinAgarwal/lifecycle-of-docker-container-d2da9f85959
  • Микросервисы и контейнеры Docker. Парминдер Сингх Кочер. Addison-Wesley. ДМК
  • Learn Enough Docker to be Useful
  • Building Docker Images with Dockerfiles
  • [Docker] Основы Docker: Dockerfile и docker-compose.yml
  • 20 Basic Linux Commands for Beginners!

Понравилась статья? Поделить с друзьями:
  • Mometix burun spreyi инструкция на русском
  • Закрутка для банок инструкция по применению
  • Дексаметазон реневал капли глазные инструкция по применению взрослым отзывы
  • Ветбицин 3 инструкция по применению в ветеринарии для собак дозировка
  • Docsvision руководство пользователя