Сетевой дневник одного программиста

Персональный блог Константина Огородова

Контейнеризация приложения WaterMeterAutomation

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

  1. Свойства проекта -> вкладка Debug -> поле DockerFile run arguments. Сюда можно вписать аргументы для команды docker run такие как -e имя_переменной_окружения. Значение этого поля будет сохранено в файле Properties/launchSettings.json.
  2. Вручную в файле проекта изменить секцию <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, а WordPress5.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

Осталось только дождаться воскресного вечера и убедиться, что всё отработало как надо 🙂

Контейнеризация приложения WaterMeterAutomation

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Пролистать наверх