Приложения ASP.NET Core по-настоящему кросс-платформенны и могут запускаться в «никсах», а соответственно, и в Docker. Посмотрим, как их можно упаковать, чтобы развертывать на Linux и использовать в связке с Nginx. Подробности под катом!  Примечание: мы продолжаем серию публикаций полных версий статей из журнала Хакер. Орфография и пунктуация автора сохранены. О микросервисной архитектуре слышали практически все. Сам концепт разбития приложения на части не сказать чтобы новый. Но, новое – это хорошо забытое и переработанное старое.  Если постараться рассказать об архитектуре в нескольких словах, то веб приложение разбивается на отдельные унитарные части — сервисы. Сервисы не взаимодействуют между собой напрямую и не имеют общих баз данных. Это делается для возможности изменять каждый сервис без последствий для других. Сервисы упаковываются в контейнеры. Среди контейнеров правит балом Docker. Для того, чтобы описать что такое Docker очень часто упрощенно используют термин «виртуальная машина». Сходство определенно есть, но говорить так неправильно. Проще всего это различие понять, посмотрев на следующие изображения с официальной документации докера:    Контейнеры используют ядро текущей операционной системы и делят его между собой. В то время как виртуальные машины с помощью hypervisor используют аппаратные ресурсы.
Образ/Image докера это read-only объект, который, по сути, хранит в себе шаблон для построения контейнера. Контейнер — это среда в которой выполняется код. Образы хранятся в репозиториях. Например, официальный репозиторий Docker Hub позволяет хранить только один образ приватно. Впрочем, это бесплатно, поэтому даже за это нужно их поблагодарить. Докер не является единственным представителем контейнеризации. Кроме его существуют и другие технологии. Например: rkt (произносится как 'рокет') от CoreOS LXD (произносится как ‘лексди’) от Ubuntu Windows Containers — ни за что не угадаете от кого. Теперь, когда мы ознакомились с теорией, давайте перейдем к практике. Установку докера особого смысла разбирать нет, ведь его можно установить на множество операционных систем. Укажу только, что скачать его под свою платформу можно из Docker Store. Если вы устанавливаете Docker под Windows, то необходимо чтобы в BIOS и в ОС была включена виртуализация. О том как включить ее в 10-ке можно прочитать в следующем артикуле: Установка Hyper-V вWindows10 Докер это, конечно Linux-овый продукт, но при необходимости можно его использовать при разработке под 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 хоста.  Кроме того, есть такие команды: Покажет вам список контейнеров. Так как добавлен ключ -a, то будут отображены все контейнеры, а не только те, которые запущены на данный момент. Эта команда удалит контейнер с именем container_name. rm – сокращение от remove Отобразит логи контейнера Удалит образ с именем image_name Дело в том, что сами приложения .NET Core используют свой веб-сервер Kestrel. Этот сервер не рекомендуется для работы на production. Почему? Есть несколько объяснений.
Если есть несколько приложений, которые делят между собой IP и порт, то Kestrel не сможет распределять трафик. Кроме того, реверс прокси-сервер предоставляет собой дополнительный слой безопасности, упрощает балансировку нагрузки и настройку SSL, а также лучше интегрируется в существующую инфраструктуру. Для большинства разработчиков самой главной причиной необходимости реверс-прокси, является дополнительная безопасность. Для начала восстановим начальную конфигурацию Dockerfile. А после разберёмся с файлом docker-compose.yml и попробуем запустить наш сервис в одиночку. Формат файла yml читается так «ямл» и является аббревиатурой то ли от «Yet Another Markup Language», то ли от «YAML Ain't Markup Language». То ли еще один язык разметки, то ли не язык разметки совсем. Как-то все не определенно. Мой файл 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 в конфигурацию doker-compose. Осторожно в YAML пробелы имеют значение: Здесь мы добавляем к нашей конфигурации прокси в виде образа nginx. К этому образу «цепляем» внешний файл настроек. Его мы как-бы монтируем в файловую систему контейнера с помощью механизма под названием volume. Если добавить в конец :ro то объект будет смонтирован только для чтения.  Прокси слушает внешний 80-ый порт машины на которой запущен контейнер и передает запрос на внутренний 80-ый порт контейнера. Выполнив команду doker-compose up мы запулим, то есть извлечем из репозитория образ nginx и стартанем наш контейнер вместе с контейнером прокси. Теперь по адресу http://localhost:80/ он будет доступен через nginx. На 5000-ом порту приложение «крутится» еще и под Kestrel. Проверить то, что запрос к веб приложению проходит через реверс-прокси мы можем так. Открыть в браузере Chrome developer tools и зайти на закладку Network. Здесь кликнуть на localhost и выбрать закладку Headers.   Версия 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 добавить доверие сертификату можно с помощью команды:
dotnet dev-certs https –trust Если утилита dev-certs не установлена, то установить ее можно командой: Как добавить сертификат в trusted на Linux зависит от дистрибутива.
В тестовых целях используем именно сертификат разработчика. Действия с сертификатом подписанным CA аналогичные. При желании можно использовать и бесплатные сертификаты LetsEncrypt Экспортировать сертификат разработчика в файл можно с помощью команды Файл необходимо скопировать в директорию %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. Например, так: Далее приведу и разберу сразу рабочий вариант конфигурации докера для прокси и контейнера через https.  Файл docker-compose:  Теперь опишу непонятные моменты. ASPNETCORE_URLS позволяет нам не указывать в коде приложения с помощью app.UseUrl прослушиваемый приложением порт.  ASPNETCORE_HTTPS_PORT делает редирект аналогичный тому, какой бы сделал следующий код:
services.AddHttpsRedirection(options => options.HttpsPort = 44392) То есть трафик с http запросов будет перенаправлен на определенный порт https запросов.
С помощью ports указывается, что запрос с внешнего 59404-ого порта будет перенаправлен на 80-ый контейнера, а с 44392-ого внешнего порта на 44392-ой. Теоретически, раз у нас будет настроен реверс прокси-сервер, то ports с этими перенаправлениями мы можем и убрать.
С помощью volumes монтируется директория с pfx сертификатом и UserSecrets приложения с указанием пароля и ссылки на сертификат. В разделе proxy указывается что запросы с 5001-ого внешнего порта будут перенаправляться на 44392-ой порт nginx. Кроме того, монтируется файл с конфигурацией nginx, а также сертификат и ключ к сертификату.  Для того, чтобы их одного сертификата формата pfx (который у нас уже есть) создать файлы crt и rsa можно воспользоваться OpenSSL. Сначала необходимо извлечь сертификат: А затем приватный ключ: Конфигурация nginx следующая: Прокси сервер прослушивает 44392-ой порт. На этот порт приходят запросы с 5001-ого порта хоста. Далее прокси перенаправляет запросы на 44392-ой порт контейнера dockerdemoservice. Разобравшись с данными примерами, вы получите хороший бэкграунд для работы с докером, микросервисами и nginx.  Напоминаем, что это полная версия статьи из журнала Хакер. Ее автор — Алексей Соммер. Developer Advocate