Всегда мечтал поиграться с контейнеризацией приложений и, к слову сказать, приложение передачи показаний водосчётчиков для этого подходит как нельзя лучше, потому что запускаться оно должно с некоторой периодичностью в автоматическом режиме. Более того: в приложении не используется какого-то ОС-специфического 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
Осталось только дождаться воскресного вечера и убедиться, что всё отработало как надо 🙂