Несмотря на то, что число разговоров о Docker стало несколько меньше нежели пару лет назад, я считаю, что в блоге любого уважающего себя программиста-блоггера должен быть пост об этой технологии. Что важно, тезис об уменьшении обсуждения Docker возник не из-за того, что эта идея не прижилась или мало кого интересует. Просто все, кто хотел, уже погрузились в нее и используют в своих проектах. Сегодня мы будем использовать Docker для автоматизации процесса сборки и развертывания кода, написанного на еще одной модной технологии .NET Core.
Я не буду углубляться в то, как именно работает докер, по крайней мере сегодня. Однако, я постараюсь объяснить для чего он нам нужен. Для тех, кто знаком с этой технологией, ничего нового здесь я не расскажу.
В основе Docker лежит понятие контейнеризации. Этим словом описывается процесс создания и использования контейнеров - изолированных исполняемых экземпляров со своим программным обеспечением. Контейнеры - легковесная альтернатива виртуальным машинам. Благодаря им вы можете:
У Docker есть и много других плюсов, но мы пока остановимся на этом. Разберемся, из чего он состоит и как им пользоваться.
Как уже было упомянуто, одно из ключевых понятий в докере - это контейнеры. В контейнере содержится все, что нужно для работы приложения. Контейнеры создаются из образов. Образ - основа для контейнеров, шаблон с необходимым программным обеспечением. Образы можно хранить в реестре. Существуют публичные реестры, например, DockerHub. Также вы можете хранить ваши образы, в своем частном реестре. Напишем пару первых команд в Docker.
Скачивание образа. Команда pull находит и скачивает запрашиваемый образ с публичного реестра (если не указан другой)
Запуск контейнера. С помощью команды run запустим новый контейнер с этим образом.
Для контроля и повторного воспроизведения процесса построения образов можно писать докерфайлы. В том числе с помощью такого файла мы опишем процесс сборки нашего приложения. Рассмотрим несколько директив, определяемых в Dockerfile:
Dockerfile также поддерживает много других инструкций и предоставляет достаточно тонкую настройку сборки образа.
Я уже упоминал об этой технологии в посте о телеграм-боте. Напомню, что это ветвь .NET Framework, направленная на создание кросс-платформенных приложений. Мы создадим простейшую программу, которую запустим в Linux-контейнере.
Наше решение будет состоять из библиотечного и консольного проектов. Создадим библиотеку DockerTest.Library с одним классом PhraseGenerator. В этом классе используется код, сторонней зависимости Lorem.NET. Мы воспользуемся методом этой библиотеки, возвращающим “произвольную” строку латинского содержания. Класс PhraseGenerator содержит один метод Generate, который конкатенирует латинский текст с именем, переданным в параметре.
Также создадим консольное приложение DockerTest.App, которое в бесконечном цикле запрашивает имя у пользователя и отвечает ему, используя метод только что созданной нами библиотеки.
Именно для этого примитивного примера мы будем создавать билд-процесс. А сейчас подробнее о том, что это такое.
Под этим понятием я имею ввиду последовательность операций, выполняющихся автоматически и создающих из исходного кода некоторый продукт. В роли продукта, например, может быть инсталятор, опубликованная в Nuget библиотека или, как в нашем случае, новый образ с приложением.
Билд-процессы - неотъемлемая часть понятий Continious Integration и Continious Delivery. Как правило, они реализуются с помощью билд-систем, таких как MsBuild для .Net или Maven для Java (на самом деле, их огромное число для самых разных языков). Рассмотрим типичные шаги для большинства билд-процессов:
Мы будем производить сборку в докере, создавая новый образ, поэтому первый шаг нас не интересует. Кроме того, мы будем использовать многоэтапную сборку. Еt смысл в разделении билд-процесса на два образа. Процесс сборки проходит в тяжелом образе, содержащем весь необходимый инструментарий для этого, в то время как для результирующего образа, за основу берется легкий, который может только запускать наше приложение.
Итак, у нас есть консольное .NET Core приложение, использующее нашу библиотеку. Цель: описать билд-процесс, который создает образ с этим приложением и публикует в Nuget библиотеку, предварительно установив для них версию, переданную в параметре.
Начнем описание процесса сборки с создания докерфайла:
В роли базового образа используем dotnet-sdk версии 2.0.0, который содержит все необходимое для сборки .NET Core приложения. Ключевое слово ARG определяет имя для внешнего параметра. Скопировав весь исходный код приложения в папку src в образе, мы запустим скрипт docker_build.sh с тем же параметром, который был передан в докерфайл.
Файл с расширением sh является bash скриптом. Для незнакомых с Linux людей поясню: Bash - командная оболочка UNIX-систем. Он реагирует на команды пользователя в интерактивном режиме, либо построчно обрабатывает команды скрипта. В файле build.sh, в первую очередь, нам необходимо сменить версии файлов проектов на те, которые были переданы в скрипт через параметр:
Утилита sed заменяет все вхождения подстроки, подходящей под шаблон, на другое строковое значение. Символом $1 обозначается первый параметр, переданный в скрипт, то есть, в нашем случае, версия. Ключ i обеспечивает проведение замены непосредственно в файле.
Следующим шагом нужно скачать все зависимости (у наc это Lorem.NET) и собрать проекты. Для этого воспользуемся утилитой dotnet, которая входит в SDK, и, следовательно, содержится в этом образе. Пропишем в скрипт следующие строки:
Теперь нам необходимо заняться созданием и публикацией продукта. Сначала проделаем это с нашей библиотекой. Чтобы собрать nuget-пакет, нужно также воспользоваться утилитой dotnet и вызвать команду pack (все операции выполняем в конфигурации Release). В результате в папке bin/Release окажется nupkg файл, который мы и опубликуем в Nuget командой nuget push. Теперь библиотека DockerTest.Library в публичном доступе, и вы можете использовать ее в своем коде!
Самое время заняться образом с нашим приложением и вспомнить о многоэтапной сборке. Чтобы создать новый образ, в котором не будет ничего лишнего кроме нашей программы, нужно доработать Dockerfile. За основу второго образа возьмем легкий dotnet-runtime и поместим в него содержимое папки publish из предыдущего образа. Также обозначим точку входа в контейнер, который будет создан из этого образа.
У нас почти все готово. Осталось создать еще один скрипт, который будет вызываться на билд-машине, ведь мы максимально автоматизируем процесс. Первым делом в этом скрипте мы соберем наш docker-образ с тегом docker-test. После этого выполним последний пункт плана, то есть опубликуем образ на DockerHub. Код скрипта:
После выполнения этого скрипта образ с приложением содержится в публичном доступе. Теперь вы можете насладиться работой программы с любого компьютера, на котором установлен Docker, не устанавливая при этом никакие .NET зависимости.
Сегодня мы очень поверхностно познакомились с Docker и сборкой образов с помощью Dockerfile. Тем не менее, этих знаний нам хватило, чтобы создать автоматизированный билд-процесс для .NET Core приложения.
Written on November 5th, 2017 by Alexey Kalina