Всегда мечтал поиграться с контейнеризацией приложений и, к слову сказать, приложение передачи показаний водосчётчиков для этого подходит как нельзя лучше, потому что запускаться оно должно с некоторой периодичностью в автоматическом режиме. Более того: в приложении не используется какого-то ОС-специфического API, а это значит, что контейнер может быть построен на базе Linux’а, и использоваться на домашнем TrueNAS-сервере. Что ж — приступим: правый клик на проекте EntryPoint
-> Add
-> Docker Support
, Target OS
= Linux
. Данная команда немного изменит файл проекта, а также добавит 3 файла: .dockerignore в корень решения, EntryPoint/Properties/launchSettings.json и EntryPoint/Dockerfile. Последний наиболее интересен, т.к. содержит инструкции для сборки образа Docker’а:
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. FROM mcr.microsoft.com/dotnet/runtime:5.0 AS base WORKDIR /app FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build WORKDIR /src COPY ["EntryPoint/EntryPoint.csproj", "EntryPoint/"] COPY ["Core/Core.csproj", "Core/"] COPY ["Infrastructure/Infrastructure.csproj", "Infrastructure/"] COPY ["TomRcApi/TomRcApi.csproj", "TomRcApi/"] COPY ["SauresApi/SauresApi.csproj", "SauresApi/"] RUN dotnet restore "EntryPoint/EntryPoint.csproj" COPY . . WORKDIR "/src/EntryPoint" RUN dotnet build "EntryPoint.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "EntryPoint.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "EntryPoint.dll"]
В этом файле в первой строке есть комментарий содержащий ссылку на документацию с описанием процесса создания контейнезированного приложения в VS. За более детальной информацией по каждой команде можно обратиться к официальной документации по Dockerfile’у.
Вышеописанного действия Add -> Docker Support
достаточно, чтобы приложение запускалось в контейнере Docker’а в том числе и в режиме отладки. Правда работать оно не будет, т.к. зависит от переменных окружения, которые существуют в родительской операционной системе, но не в гостевой ОС. Исправить это можно двумя способами:
- Свойства проекта -> вкладка Debug -> поле DockerFile run arguments. Сюда можно вписать аргументы для команды
docker run
такие как -e имя_переменной_окружения. Значение этого поля будет сохранено в файле Properties/launchSettings.json. - Вручную в файле проекта изменить секцию
<PropertyGroup>
добавив новое свойство<DockerfileRunEnvironmentFiles>
в котором указать имя файла окружения применяемого при запуске контейнера. Кстати, вот полный список свойств применяемых для работы с контейнерами.
После вышеописанных манипуляций можно нажать F5 в VS и запустить контейнер с приложением. Однако конечная цель — это не запуск в VS, а получение из исходников образа с последующей публикацией в реестре и запуском контейнера из образа где угодно, в т.ч. и на локальном TrueNas Scale сервере. Поможет в этом утилита командной строки docker. Первое что необходимо сделать — собрать образ с помощью команды docker build. В документации указан полный синтаксис из которого видно, что собирать можно из локального каталога, URL или потокового ввода. В простейшем случае достаточно перейти в каталог с расположением файла Dockerfile и ввести команду docker build .
, где точка после пробела будет означать путь до контекста, т.е. текущий каталог. Если же говорить о сборке в VS, то текущим каталогом будет каталог решения, а не конкретного проекта. И команды в Dockerfile в качестве корневого каталога используют как раз каталог решения. Однако сам Dockerfile находится в каталоге проекта EntryPoint, а это значит, что команде docker build
нужно указать путь до Dockerfile’а. Делается это с помощью опции -f. Также хорошо было бы дать образу осмысленное имя и метки с дополнительной информацией о версии и т.п. Для этого используется опция -t. Формат имени следующий: адрес-реестра
:порт
/идентификатор-аккаунта
/имя-образа
:метка
. Адрес реестра и порт можно опустить, если используется hub.docker.com. Идентификатор аккаунта можно опустить, если образ будет использоваться только локально без отправки на удалённый реестр. Метка, если не указано — latest. Также существует договорённость, что для версионирования образа должно использоваться Семантическое версионирование. При этом образ может иметь одновременно множество различных меток. Наример официальный образ MySql имеет метки 8.0.27, 8.0, 8, latest
, а WordPress — 5.8.1-apache, 5.8-apache, 5-apache, apache, 5.8.1, 5.8, 5, latest, 5.8.1-php7.4-apache, 5.8-php7.4-apache, 5-php7.4-apache, php7.4-apache, 5.8.1-php7.4, 5.8-php7.4, 5-php7.4, php7.4
. Таким образом для сборки образа в VS с метками 1.0.0 и latest и отправки на hub.docker.com в репозиторий watermeterautomation под идентификатором ogorodov необходимо в терминале VS (Developer Power Shell) ввести команду:
docker build -f EntryPoint/Dockerfile -t ogorodov/watermeterautomation:1.0.0 -t ogorodov/watermeterautomation:latest .
Далее с помощью команды docker images можно убедиться, что образ был собран и помещён в локальный реестр. При необходимости можно пересобрать образ предварительно удалив существующий командой docker rmi.
Теперь что касается удалённого реестра: для начала предлагаю поиграться с официальным hub.docker.com. И первое, что необходимо сделать — зарегистрироваться, если это ещё не было сделано раньше. Далее в с помощью команды docker login необходимо авторизоваться. И наконец с помощью команды docker push отправить образ на удалённый реестр. Для образа созданного на предыдущем шаге команда примет следующий вид:
docker image push -a ogorodov/watermeterautomation
После непродолжительной загрузки образов имеет смысл перейти в web UI hub.docker.com и убедиться, что репозиторий создан и образы присутствуют. А раз образы присутствуют, значит можно их запустить на локальном сервере TrueNas Scale через UI. Только есть проблема: предполагается, что образы запускаемые через UI TrueNas Scale должны работать постоянно как сервисы. А если контейнер закончил работу — будет предпринята попытка запустить новый контейнер. В то же время контейнер WaterMeterAutomation работает совершенно иначе: он быстро выполняет свою работу и останавливается. Нестыковочка… С одной стороны я могу изменить приложение так, чтобы оно работало как сервис и периодически был запуск выполнения полезной работы. Но мне этот подход не нравится, потому что полезной работы тут на 1-2 секунды от одного до 4-х раз в месяц. Всё остальное время контейнер будет просто «висеть» без дела и «отжирать» системные ресурсы впустую. С другой стороны можно оставить приложение как есть и воспользоваться возможностью Kubernetes под названием CronJob — запуск работы по расписанию. Минусом данного решения будет то, что всё конфигурирование придётся делать из командной строки с помощью утилиты kubectl без какой-либо поддержки со стороны UI TrueNas Scale. Это решаемо с помощью Kubernetes Dashboard или сторонних UI и возможно в будущем я с этим заморочусь. Однако даже в текущем виде вариант с CronJob мне нравится гораздо больше, т.к. он лучше подходит под текущую задачу. Значит для начала где-нить в недрах массива жёстких дисков сервера с помощью WinSCP размещу следующий скрипт:
apiVersion: batch/v1 kind: CronJob metadata: name: water-meter-automation spec: schedule: "0 23 * * 0" concurrencyPolicy: Forbid jobTemplate: spec: template: spec: containers: - name: water-meter-automation image: ogorodov/watermeterautomation imagePullPolicy: IfNotPresent env: - name: wma_SauresApi__Login value: "secret" - name: wma_SauresApi__Password value: "secret" - name: wma_Smtp__Login value: "secret" - name: wma_Smtp__Password value: "secret" - name: wma_TomRcApi__Login value: "secret" - name: wma_TomRcApi__Password value: "secret" restartPolicy: Never backoffLimit: 0
Далее открыть терминал, в моём случае это был PuTTY и создать псевдоним для kubctl следующей командой:alias kubectl="k3s kubectl"
На всякий случай проверить версию Kubernetes:kubectl version
Должна быть не ниже 1.21 для запуска CronJob’ов и это как раз мой случай. Попробуем создать работу на основании файла yaml:
kubectl create -f /mnt/hdd/kubernetes-scripts/cron-jobs/water-meter-automation.yaml
И в случае успеха должен быть ответ cronjob.batch/water-meter-automation created
. Посмотрим на статус работы:kubectl get cronjob water-meter-automation
В ответ должно быть что-то такое:
NAME SCHEDULE SUSPEND ACTIVE LAST SCHEDULE AGE water-meter-automation 0 23 * * 0 False 0 <none> 4m1s
Осталось только дождаться воскресного вечера и убедиться, что всё отработало как надо 🙂