Как с помощью Docker упаковать приложение ASP.NET Core

Упаковка приложения ASP.NET Core с помощью Docker

Кажется, Microsoft все больше и больше любит Linux! Приложения ASP.NET Core теперь по-настоящему кроссплатформенны и могут запускаться в «никсах», а соответственно, и в Docker. Давай посмотрим, как их можно упаковать, чтобы развертывать на Linux и использовать в связке с Nginx.

Пару слов о Docker

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

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

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

Как с помощью Docker упаковать приложения ASP.NET Core
Docker
Упаковка приложения ASP.NET Core с помощью Docker
Виртуальная машина

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

Образ/Image «Докера» — это read-only-объект, который, по сути, хранит в себе шаблон для построения контейнера. Контейнер — это среда, в которой выполняется код. Образы хранятся в репозиториях. Например, официальный репозиторий Docker Hub позволяет хранить приватно только один образ. Впрочем, это бесплатно, так что даже за это нужно их поблагодарить.

«Докер» не единственный предоставляет контейнеризацию: существуют и другие технологии. Например, rkt (произносится как «рокет») от CoreOS, LXD (произносится как «лексди») от Ubuntu, Windows Containers ни за что не угадаете от кого.

Разбирать установку «Докера» особого смысла нет, ведь его можно установить на множество операционных систем. Укажу только, что скачать его под свою платформу можно из Docker Store. Если ты устанавливаешь Docker под Windows, то необходимо, чтобы в BIOS и в ОС была включена виртуализация. О том, как включить ее в «десятке», можно прочитать в статье «Установка Hyper-V в Windows 10».

Теперь, когда мы ознакомились с теорией, давай перейдем к практике.

Создание проекта с поддержкой Docker

«Докер» — это, конечно, линуксовый продукт, но при необходимости можно его использовать при разработке под Mac или под Windows. При создании проекта в Visual Studio для добавления поддержки «Докера» достаточно поставить флажок Enable Docker Support.

Поддержку «Докера» можно включить и в существующий проект. Добавляется она таким же образом, как и различные новые компоненты: контекстное меню Add –> Docker Support.

Если на твоей машине установлен и запущен «Докер», будет автоматически открыта консоль и выполнена команда

которая запускает скачивание образа. Этот образ фактически заготовка, на основе которой будет создан твой образ. ASP.NET Core 2.1 использует уже другой образ — microsoft/dotnet:sdk.

В директории с решением для тебя будут автоматически созданы следующие файлы: .dockerignore (исключение файлов и директорий из образа «Докера»), docker-compose.yml (с помощью этого файла можно сконфигурировать выполнение нескольких сервисов), docker-compose.override.yml (вспомогательная конфигурация docker-compose), docker-compose.dcproj (файл проекта для Visual Studio).

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

Начальная конфигурация для .NET Core 2.0 не позволит сразу построить образ с помощью команды docker build. Она настроена на то, что будет запущен файл docker-compose из директории уровнем выше. Чтобы построение пошло успешно, Dockerfile можно привести к подобному виду:

Все, что я сделал, — убрал лишнюю директорию DockerServiceDemo.

Если ты используешь Visual Studio Code, то файлики придется генерировать вручную, хотя в VS Code и имеется вспомогательная функциональность в виде расширения Docker. Добавлю ссылку на мануал, как работать с «Докером» из VS Code: Working with Docker. Да, статья на английском, но она ведь с картинками.

Три аккорда «Докера»

Для ежедневной работы с «Докером» достаточно помнить всего лишь несколько команд.

Самая главная команда — это, конечно, построение образа. С помощью bash/CMD/PowerShell заходим в директорию, где лежит Dockerfile, и выполняем команду

Здесь после параметра -t задается имя твоего образа. Внимание: в конце команды после пробела точка. Эта точка означает, что используется текущая директория. Образ можно пометить каким-нибудь тегом (номером или названием). Для этого после имени поставить двоеточие и указать тег. Если этого не сделать, то по умолчанию тег будет задан с наименованием latest. Чтобы отправить образ в репозиторий, в имя образа нужно включить имя репозитория. Примерно так:

Здесь your_docker_account_name — это имя твоего аккаунта в docker hub.

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

Чтобы отправить изменения в хаб, теперь нужно выполнить

Перед этим необходимо зайти в твой аккаунт «Докера». На Windows это делается из UI приложения, а вот на *nix — командой

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

Параметр -it создаст псевдо-TTY, и твой контейнер будет отвечать на запросы. После запуска команды сервис станет доступным по адресу http://localhost:5000/. Параметр -p 5000:80 связывает 5000-й порт контейнера с 80-м портом хоста.

Кроме того, есть такие команды:

  • docker ps -a — покажет список контейнеров. Так как добавлен ключ -a, то будут отображены все контейнеры, а не только те, которые запущены на данный момент;
  • docker rm container_name — эта команда удалит контейнер с именем container_name (rm — сокращение от remove);
  • docker logs container_name — отобразит логи контейнера;
  • docker rmi image_name — удалит образ с именем image_name.

Запуск контейнера через обратный прокси-сервер

Дело в том, что сами приложения .NET Core используют свой веб-сервер Kestrel. Этот сервер не рекомендуется для работы на production. Почему? Есть несколько объяснений.

Если несколько приложений делят между собой IP и порт, то Kestrel не сможет распределять трафик. Кроме того, обратный прокси-сервер предоставляет дополнительный слой безопасности, упрощает балансировку нагрузки и настройку SSL, а также лучше интегрируется в существующую инфраструктуру. Для большинства разработчиков главная причина необходимости реверс-прокси именно в дополнительной безопасности.

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

Файл docker-compose.override.yml добавляет в конфигурацию несколько настроек:
version: ‘3.4’

Построить созданное решение мы можем с помощью docker-compose build, а вызвав команду docker-compose up, мы запустим наш контейнер. Все работает? Тогда переходим к следующему шагу. Создаем файл nginx.info. Конфигурация будет примерно следующая:

Здесь мы указываем, что nginx будет прослушивать 80-й порт (listen 80;). А полученные запросы будет переадресовывать на 80-й порт хоста в контейнер dockerservicedemo. Кроме того, мы указываем nginx, какие заголовки необходимо передавать дальше.

Мы можем использовать HTTP в nginx, а доступ к веб-сайту настроить через HTTPS. Когда HTTPS-запрос проходит через HTTP-прокси, то много информации из HTTPS не передается в HTTP. Кроме того, при использовании прокси теряется внешний IP-адрес. Чтобы эта информация передавалась в заголовках, необходимо изменить код нашего ASP.NET-проекта и добавить в начало метода Configure файла Startup.cs следующий код:

Большинство прокси-серверов используют заголовки X-Forwarded-For и X-Forwarded-Proto. Именно эти заголовки и указаны сейчас в конфигурации nginx.

Теперь включим образ nginx и файл nginx.conf в конфигурацию docker-compose. Осторожно, в YAML пробелы имеют значение:

Здесь мы добавляем к нашей конфигурации прокси в виде образа nginx. К этому образу «цепляем» внешний файл настроек. Его мы как бы монтируем в файловую систему контейнера с помощью механизма под названием volume. Если добавить в конец :ro, то объект будет смонтирован только для чтения.

Прокси слушает внешний 80-й порт машины, на которой запущен контейнер, и передает запрос на внутренний 80-й порт контейнера.

Выполнив команду docker-compose up, мы запулим, то есть извлечем из репозитория, образ nginx и стартанем наш контейнер вместе с контейнером прокси. Теперь по адресу http://localhost:80/ он будет доступен через nginx. На 5000-м порте приложение крутится еще и под Kestrel.

Проверить, что запрос к веб-приложению проходит через реверс-прокси, мы можем так: открыть в браузере Chrome developer tools, зайти на закладку Network, здесь кликнуть на localhost и выбрать закладку Headers.

ASP.NET Core Docker
Заголовки ответа в Chrome

Запуск контейнера через прокси и HTTPS

Версия ASP.NET Core 2.1 принесла с собой улучшения поддержки HTTPS. Скажем, такой middleware позволяет совершать редирект с незащищенного соединения на защищенное:

А следующий позволяет использовать HTTP Strict Transport Security Protocol — HSTS.

HSTS — это фича из протокола HTTP/2, спецификация которого была выпущена в 2015 году. Поддерживается современными браузерами и информирует о том, что веб-сайт использует только HTTPS. Так обеспечивается защита от downgrade-атаки, во время которой атакующий может, перейдя на незащищенный протокол HTTP, например, понизить версию TLS или даже подменить сертификат. Как правило, данный вид атак используется совместно с атаками man in the middle.

Следует помнить, что HSTS не поможет, если пользователь заходит на сайт по протоколу HTTP и потом редиректится на HTTPS. Существует так называемый Chrome preload list, который содержит ссылки на сайты, поддерживающие HTTPS. Другие браузеры (Firefox, Opera, Safari, Edge) также поддерживают списки HTTPS-сайтов, созданные на базе списка Chrome. Но во всех этих списках содержатся далеко не все сайты.

При первом запуске какого-либо Core-приложения на Windows ты получишь сообщение о том, что был создан и установлен сертификат разработчика. Кликнув кнопку и установив сертификат, ты таким образом сделаешь его доверенным. Из командной строки на macOS добавить доверие сертификату можно с помощью

Если утилита dev-certs не установлена, то установить ее можно командой

Как добавить сертификат в trusted на Linux, зависит от дистрибутива. В тестовых целях используем именно сертификат разработчика. Действия с сертификатом, подписанным CA, аналогичны. При желании можно воспользоваться и бесплатными сертификатами Let’s Encrypt.

Экспортировать сертификат разработчика в файл можно с помощью команды

Файл следует скопировать в директорию %APPDATA%/ASP.NET/Https/ под Windows или же в /root/.aspnet/https/ под macOS/Linux. Для того чтобы контейнер подцепил путь к сертификату и его пароль, необходимо создать User secrets со следующим содержимым:

Этот файл хранит незашифрованные данные и потому используется только при разработке. Создается файл в Visual Studio через вызов контекстного меню на иконке проекта или с помощью утилиты user-secrets на Linux.

На Windows файл будет сохранен в директории %APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json, а на macOS и Linux он сохранится в ~/.microsoft/usersecrets/<user_secrets_id>/secrets.json.

Для сохранения настроек на продакшене некоторые дистрибутивы Linux могут использовать systemd. Настройки сохраняются под атрибутом Service. Например, так:

[/crayon]
Далее приведу и разберу сразу рабочий вариант конфигурации «Докера» для прокси и контейнера через HTTPS.

Файл docker-compose:

Теперь опишу непонятные моменты. ASPNETCORE_URLS позволяет нам не указывать в коде приложения с помощью app.UseUrl прослушиваемый приложением порт. ASPNETCORE_HTTPS_PORT делает редирект, аналогичный тому, какой бы сделал следующий код:

То есть трафик с HTTP-запросов будет перенаправлен на определенный порт HTTPS-запросов.

С помощью ports указывается, что запрос с внешнего 59404-го порта будет перенаправлен на 80-й контейнера, а с 44392-го внешнего порта на 44392-й. Теоретически, раз у нас будет настроен обратный прокси-сервер, ports с этими перенаправлениями мы можем и убрать. С помощью volumes монтируется директория с pfx-сертификатом и UserSecrets приложения с указанием пароля и ссылки на сертификат. В разделе proxy указывается, что запросы с 5001-го внешнего порта будут перенаправляться на 44392-й порт nginx. Кроме того, монтируется файл с конфигурацией nginx, а также сертификат и ключ к сертификату.

Чтобы из одного сертификата формата pfx (который у нас уже есть) создать файлы crt и rsa, можно воспользоваться OpenSSL. Сначала необходимо извлечь сертификат:

А затем приватный ключ:

Конфигурация nginx следующая:

Прокси-сервер прослушивает 44392-й порт. На этот порт приходят запросы с 5001-го порта хоста. Далее прокси перенаправляет запросы на 44392-й порт контейнера dockerservicedemo.

РЕКОМЕНДУЕМ:
Безопасность Docker

Разобравшись с данными примерами, ты получишь хороший бэкграунд для работы с «Докером», микросервисами и nginx.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий