Docker, часть 3

1: Загрузка экземпляра Go API

Вы должны сначала загрузить пример API, из которого вы будете создавать образы Docker. Использование простого Go API покажет все ключевые этапы сборки и запуска приложения внутри контейнера Docker. Этот мануал использует Go, потому что это скомпилированный язык, такой как C ++ или Java, но в отличие от них, он занимает очень мало места.

Клонируйте API Go:

Теперь на вашем сервере есть каталог mux-go-api. Перейдите в него:

Это домашний каталог для вашего проекта. Все образы Docker нужно создавать в этом каталоге. Внутри вы найдете исходный код API, написанный на Go, в файле api.go. Хотя этот API минимальный и имеет всего несколько конечных точек, он подойдет для имитации готового к работе API.

Теперь, когда вы загрузили образец Go API, можно создать базовый Docker-образ Ubuntu, с которым вы сможете сравнить оптимизированные образы, которые вы создадите позже.

Основы ENTRYPOINT && CMD

Перед началом нужно кратко вспомнить, что это такое и чем отличается. Эти две директивы в Dockerfile наверняка всем известны – обе запускают процессы в контейнере. Как правило, в entrypoint используется какой-либо скрипт или команда для запуска процесса внутри контейнера, а в cmd – параметры, которые передаются в entrypoint.

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

Если же указать команду для запуска без фигурных скобок, то внутри контейнера можно будет увидеть, что процесс запущен через форму shell и процесс внутри будет вида sh -c “ping ya.ru”.

Грубый пример Dockerfile для демонстрации работы exec формы:

После сборки образа с именем “ping” и дальнейшего запуска контейнера из него будет выполняться команда “/bin/ping it-lux.ru”. Домен можно переопределить, указав его при запуске контейнера, например:

Тем самым выполняется переопределение значения из параметра CMD в Dockerfile, что очень удобно – из одного образа можно запускать команды с различными аргументами. Такой способ я использовал для выполнения крон-задач в отдельном докер-контейнере, указывая на хосте в crontab через аргументы путь к скриптам при запуске docker run, которые принимал для выполнения php.

Создание файла Dockerfile

Файл Dockerfile используется командой для создания образа контейнера. Это текстовый файл с именем Dockerfile, не имеющий расширения.

Создайте файл с именем Dockerfile в каталоге, содержащем файл CSPROJ, и откройте его в текстовом редакторе. В этом учебнике будет использоваться образ среды выполнения ASP.NET Core (который содержит образ среды выполнения .NET Core). Он подходит для консольного приложения .NET Core.

Примечание

Образ среды выполнения ASP.NET Core используется намеренно, хотя может использоваться образ .

Ключевое слово требует полного имени образа контейнера Docker. Реестр контейнеров Microsoft (MCR, mcr.microsoft.com) представляет собой коллекцию Docker Hub, в которой размещены общедоступные контейнеры. Сегмент  — это репозиторий контейнеров, а сегмент является именем образа контейнера. Образ помечается для управления версиями. Таким образом,  — это среда выполнения .NET Core 5.0. Убедитесь, что вызываете версию среды выполнения, которая соответствует версии среды выполнения, с которой работает пакет SDK. Например, приложение, созданное в предыдущем разделе, использовало пакет SDK для .NET Core 5.0, а базовый образ, указанный в Dockerfile, имеет тег 5.0. .

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

В терминале выполните следующую команду:

Docker обработает все строки файла Dockerfile. Символ в команде используется, чтобы выполнить с помощью Docker поиск файла Dockerfile в текущей папке. Эта команда создает образ и локальный репозиторий с именем counter-image, который указывает на такой образ. После завершения работы этой команды выполните команду , чтобы просмотреть список установленных образов:

Обратите внимание, что два образа имеют одинаковое значение IMAGE ID. Это связано с тем, что единственная команда в файле Dockerfile создает новый образ на основе существующего

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

Команда предписывает Docker скопировать указанную папку на вашем компьютере в папку в контейнере. В этом примере папка publish копируется в папку с именем App в контейнере.

Команда изменяет текущий каталог в контейнере на App.

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

Совет

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

Примечание

.NET 6 стандартизует префикс вместо для переменных среды, которые настраивают поведение .NET во время выполнения. Но префикс будет и дальше работать. Если вы используете предыдущую версию среды выполнения .NET, следует и дальше использовать префикс для переменных среды.

В окне терминала выполните команду , а после ее выполнения — команду .

Каждая команда в файле Dockerfile создает уровень и экземпляр IMAGE ID. Последний экземпляр IMAGE ID (ваш идентификатор будет отличаться) имеет значение cd11c3df9b19. Теперь вы создадите контейнер на основе этого образа.

Подключение к базе из веб-сервера

По отдельности, наши серверы готовы к работе. Теперь настроим их таким образом, чтобы из веб-сервера можно было подключиться к СУБД.

Зайдем в контейнер с базой данных:

docker exec -it maria_db /bin/bash

Подключимся к mariadb:

:/# mysql -p

Создадим базу данных, если таковой еще нет:

> CREATE DATABASE docker_db DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci;

* в данном примере мы создаем базу docker_db.

Создаем пользователя для доступа к нашей базе данных:

> GRANT ALL PRIVILEGES ON docker_db.* TO ‘docker_db_user’@’%’ IDENTIFIED BY ‘docker_db_password’;

* и так, мы дали полные права на базу docker_db пользователю docker_db_user, который может подключаться от любого хоста (%). Пароль для данного пользователя — docker_db_password.

Отключаемся от СУБД:

> quit

Выходим из контейнера:

:/# exit

Теперь перезапустим наши контейнеры с новым параметром, который будет объединять наши контейнеры по внутренней сети.

Останавливаем работающие контейнеры и удаляем их:

docker stop maria_db web_server

docker rm maria_db web_server

Создаем docker-сеть:

docker network create net1

* мы создали сеть net1.

Создаем новые контейнеры из наших образов и добавляем опцию —net, которая указывает, какую сеть будет использовать контейнер:

docker run —name maria_db —net net1 -d -v mariadb:/var/lib/mysql mariadb

docker run —name web_server —net net1 -d -p 80:80 dmosk/webapp:v1

* указав опцию —net, наши контейнеры начинают видеть друг друга по своим именам, которые мы задаем опцией —name.

Готово. Для проверки соединения с базой данных в php мы можем использовать такой скрипт:

<?php
ini_set(«display_startup_errors», 1);
ini_set(«display_errors», 1);
ini_set(«html_errors», 1);
ini_set(«log_errors», 1);
error_reporting(E_ERROR | E_PARSE | E_WARNING);
$con = mysqli_connect(‘maria_db’, ‘docker_db_user’, ‘docker_db_password’, ‘docker_db’);
?>

* в данном примере мы подключаемся к базе docker_db на сервере maria_db с использованием учетной записи docker_db_user и паролем docker_db_password.

После его запуска, мы увидим либо пустой вывод (если подключение выполнено успешно), либо ошибку.

Используем Alpine

Ещё один способ сэкономить на размере образа — использовать маленький родительский образ. Родительский образ — это образ, на основе которого готовится наш образ. Нижний слой указывается командой  в Dockerfile. В нашем случае мы используем образ на основе Ubuntu, в котором уже стоит nodejs. И весит он …

… почти гигабайт. Изрядно сократить объем можно, используя образ на основе Alpine Linux. Alpine — это очень маленький линукс. Докер-образ для nodejs на основе alpine весит всего 88.5 Мбайт. Поэтому давайте заменим наш жиииирный вдомах образ:

Нам пришлось установить некоторые штуки, которые необходимы для сборки приложения. Да, Angular не собирается без питона ¯(°_o)/¯

Но зато размер образа сбросил 619 Мбайт:

Идём ещё дальше.

Список запущенных Docker контейнеров

Базовый формат Docker:

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

Как вы можете видеть, изображение выше указывает на отсутствие запущенных контейнеров.

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

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

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

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

Команда предоставляет несколько столбцов информации:

  • Container ID — Уникальный буквенно-цифровой номер для каждого контейнера
  • Image — Образ базовой операционной системы, на котором основан контейнер
  • Command — команда, запустившая контейнер
  • Created — Как давно был создан контейнер
  • Status — Время работы или простои
  • Ports — Указывает любые порты, перенаправляемые в контейнер для работы в сети.
  • Name — Памятное имя, присвоенное программным обеспечением Docker

Разворачивание контейнера с PGAdmin

Чтобы быстро развернуть PGAdmin, клонируем репозиторий https://github.com/khezen/compose-postgres.

Внесем изменения в файл docker-compose.yml.

  • Секция postgres нам здесь не нужна, мы ее отсюда удаляем.

  • Порты закомментируем,

  • Вставим настройки для Traefik.

  • Имя внешней сети у меня называется proxy.

Больше ничего менять не будем.

Теперь поднимаю контейнер по команде:

Логи от каждого контейнера можно посмотреть по команде:

Переходим по адресу pgadmin.demoncat.ru – открывается PGAdmin.

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

Этих сервисов очень много. Вы можете зайти на https://hub.docker.com/ и здесь есть огромное количество образов на все случаи жизни, которые вы можете посмотреть, скопировать и переделать под себя.

2: Сборка базового образа Ubuntu

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

Начнем с написания Dockerfile, который помогает Docker создать образ Ubuntu, установить Go и запустить API. Обязательно создайте Dockerfile в каталоге клонированного репозитория. Если вы клонировали его в домашний каталог, путь должен быть $HOME/mux-go-api.

Создайте новый файл Dockerfile.ubuntu. Откройте его в текстовом редакторе:

В этом файле нужно определить образ Ubuntu и установку Golang. После этого вы установите зависимости и соберете двоичный файл. Добавьте в Dockerfile.ubuntu следующие строки:

Команда FROM указывает, какую базовую операционную систему будет использовать образ. Затем команда RUN устанавливает язык Go при создании образа. ENV устанавливает конкретные переменные окружения, которые нужны компилятору Go для правильной работы. WORKDIR указывает каталог, в который нужно скопировать код, а COPY берет код из каталога, в котором находится Dockerfile.ubuntu, и копирует его в образ. Последняя команда RUN устанавливает зависимости Go, необходимые для компиляции исходного кода и запуска API.

Примечание: Операторы && для объединения команд RUN очень важны при оптимизации Dockerfiles, поскольку каждая команда RUN создает новый слой, а каждый новый слой увеличивает размер конечного образа.

Сохраните и закройте файл. Теперь вы можете запустить команду build для создания образа Docker из вашего Dockerfile:

Команда build создает образ из Dockerfile. Флаг -f указывает имя исходного Dockerfile, Dockerfile.ubuntu, а флаг -t добавляет заданный тег (в данном случае с его помощью файл будет помечен именем ubuntu). Последняя точка представляет текущий контекст, в котором находится Dockerfile.ubuntu.

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

Запустите следующую команду, чтобы получить список всех образов Docker и узнать размер полученного образа Ubuntu:

У вас теперь есть базовый образ Ubuntu со всеми необходимыми инструментами и зависимостями Go для запуска API, клонированного в разделе 1. В следующем разделе мы используем предварительно собранный образ Docker для упрощения Dockerfile и процесса сборки.

Вопросы

Я знаю, что Hyper-V на Windows не уживается со всеми остальными гипервизорами, типа VirtualBox. А как проявляет себя WSL2?

Действительно, Hyper-V и VirtualBox между собой конфликтуют. У Docker в Windows есть поддержка экспериментальных функций, которая позволяет не создавать виртуальную машину в гипервизоре, а использовать линуксовый движок ядра. Но как это работает, я сказать точно сказать не могу, потому что я очень редко использую Docker на Windows – он не работает на нем стабильно. Можно использовать Docker на Windows для тестов, можно поиграться, а на продуктив ставить пока что, к сожалению, не стоит.

UPD: Недавно узнал, что на последних версиях wsl2 уже не конфликтует с другими гипервизорами. Но не проверял.

Эталонная база очень большая – 250 Гб. Если я эту базу упакую в Docker, где будет сидеть еще и 1С и все остальное окружение, как такого монстра разворачивать?

База – это все-таки данные. Я знаю примеры, когда люди упаковывают базу именно в Docker и потом его поднимают. Но я не пробовал упаковывать такие огромные базы в сам контейнер. Мне кажется, лучше сделать так – данные базы будут храниться в каком-то именованном volume, а в контейнере понимается сервис – тот же postgres. И он подцепляет в качестве источника данных этот именованный volume вовнутрь. Таким вариантом можно сделать, но тут другой вопрос – Docker подразумевает, что нужно быстро создать новое окружение и что-то на нем потестить, а как быстро создать новую копию базы на 250 Гб я пока не знаю.

*************

Данная статья написана по итогам доклада (видео), прочитанного на онлайн-митапе «DevOps в 1С: Тестирование и контроль качества решений на 1С». Больше статей можно прочитать здесь.

Использование диска томами

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

Допустим, мы запускаем контейнер на основе MongoDB, а затем используем его для тестирования бэкапа, который мы сделали ранее (он доступен локально в файле bck.json):

# Запуск контейнера mongo$ docker run --name db -v $PWD:/tmp -p 27017:27017 -d mongo:4.0# Импорт существующего бэкапа (из огромного файла bck.json)$ docker exec -ti db mongoimport \  --db 'test' \  --collection 'demo' \  --file /tmp/bck.json \  --jsonArray

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

Извлечение Dockerfile, используемого для сборки образа контейнера MongoDB

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

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

$ docker volume rm $(docker volume ls -q)

Или использовать :

$ docker volume pruneWARNING! This will remove all local volumes not used by at least one container.Are you sure you want to continue? [y/N] yDeleted Volumes:d50b6402eb75d09ec17a5f57df4ed7b520c448429f70725fc5707334e5ded4d58f7a16e1cf117cdfddb6a38d1f4f02b18d21a485b49037e2670753fa34d115fc599c3dd48d529b2e105eec38537cd16dac1ae6f899a123e2a62ffac6168b2f5f...732e610e435c24f6acae827cd340a60ce4132387cfc512452994bc0728dd66df9a3f39cc8bd0f9ce54dea3421193f752bda4b8846841b6d36f8ee24358a85bae045a9b534259ec6c0318cb162b7b4fca75b553d4e86fc93faafd0e7c77c79799c6283fe9f8d2ca105d30ecaad31868410e809aba0909b3e60d68a26e92a094daTotal reclaimed space: 25.82GBluc@saturn:~$

Builder

  • buildkit: Fix nil dereference in cache logic moby/moby#41279
  • buildkit: Treat unix sockets as regular files during COPY/ADD moby/moby#41269
  • buildkit: Ignore system and security xattrs in calculation to ensure consistent COPY caching regardless of SELinux environment moby/moby#41222
  • buildkit: Make —cache-from behavior more reliable moby/moby#41222
  • buildkit: Fix infinite loop burning CPU when exporting cache moby/moby#41185

Client

  • Bump Golang 1.13.15 docker/cli#2674
  • Fix config file permission issues (~/.docker/config.json) docker/cli#2631
  • build: Fix panic on terminals with zero height docker/cli#2719
  • windows: Fix potential issue with newline character in console docker/cli#2623

Как использовать ресурсы контейнера ¶

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

Проброс портов

Для примера запустим контейнер с Debian 9 и пробросим локальный порт 3132 на 80 порт контейнера:

Пояснения к команде:

  • — создаёт и запускает новый контейнер.
  • — подключает виртуальную консоль. Это нужно, чтобы команда не завершала работу, иначе контейнер остановится.
  • — запускает выполнение контейнера в фоне. Без этого аргумента консоль будет ждать, когда контейнер остановится (для остановки придётся использовать другую консоль).
  • — использовать в качестве процесса системную утилиту . Просто потому, что она не завершится пока не закроется stdin, а значит и контейнер будет работать.
  • — уникальное имя, которое используется для управления контейнером. Если его не указывать, то докер сам придумает какое-нибудь имя.
  • — проброс портов. Сначала надо указать порт машины (можно указать вместе с IP), потом порт в контейнере.

Общий принцип запуска контейнеров довольно простой:

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

Теперь давайте рассмотрим, как выполнять команды внутри контейнера. Для примера установим и запустим консольный сервер php 7:

Пояснения к команде:

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

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

В этом примере я использовал две разные консоли. На одной я запускал сервер, а на другой curl. Ещё можно использовать браузер, если докер установлен у вас в системе.

Проброс папки

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

Прежде, чем начать что-то менять, надо удалить старый контейнер:

Теперь подготовим наш «проект»:

А теперь запускаем контейнер с пробросом папки проекта:

Если вы помните, я удалил контейнер, в котором был установлен php, а это значит, что мне заново придётся установить пакет php7.0-cli:

Теперь в контейнере есть и проект, и php, можно запускать сервер:

Теперь проверяем, как работает наш проект:

Для наглядности давайте создадим ещё один файл в «проекте», чтобы удостовериться, что всё работает как надо:

Должно вывести что-то вроде этого:

Если у вас получилось, смело пишите в резюме, что владеете докером!

Общее потребление

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

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

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

$ docker system df

Пример потребления памяти Docker в файловой системе хоста

Эта команда показывает использование диска Docker в нескольких категориях:

  • Образы (Images): размер образов, извлеченных из реестра и созданных локально.
  • Контейнеры (Containers): дисковое пространство, занимаемое слоями чтения-записи каждого из контейнеров, работающих в системе.
  • Локальные тома (Local Volumes): в случае, если хранение осуществляется на хосте, но вне файловой системы контейнера.
  • Кэш сборки (Build Cache): кэш, сгенерированный процессом сборки образа (касается BuildKit в Docker 18.09).

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

Резервное копирование и восстановление контейнера

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

Создание резерва

И так, для создания резервной копии контейнера, смотрим их список:

docker ps -a

… и для нужного выполняем команду:

docker save -o /backup/docker/container.tar <container image>

* в данном примере мы создаем архив контейнера <container image> в файл /backup/docker/container.tar.

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

gzip /backup/docker/container.tar

* в итоге, мы получим файл container.tar.gz.

Восстановление

Сначала распаковываем архив:

gunzip container.tar.gz

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

docker load -i container.tar

Смотрим, что нужный нам контейнер появился:

docker images

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: