砂漠の旅人(たびと)

UNIX / MS-DOS 時代から電脳砂漠を旅しています

【docker】.NET 6 アプリを Dockerfile でビルドし、 cron で実行させる

こんにちは、たびとです。

Dockerfile に .NET 6 アプリのビルドから実行までを定義し、 一定間隔でアプリを実行させたいと思ったことはありませんか?

以前、AI システムを作ったとき、Azure にビデオファイルをアップロード、 AI 解析の完了確認、AI 解析の情報を DB に格納する等、 一定間隔で実行させるにはどうすれば良いのかを悩みました。

当時は、docker 上で .NET Core アプリをビルドする方法について、 情報が少なくて、かなり試行錯誤して作った記録があります。

今回は、WSL2 Linux (Ubuntu 20.04) 環境で、.NET 6 コンソールアプリを使い、 一定間隔でバッチ処理を実行する方法を説明します。

この記事の対象者

  • docker 環境で .NET 6 アプリをビルドして実行させたい。
  • docker 環境で cron により、一定間隔で処理を実行したい。

事前準備

docker 環境をまだ作っていなければ、以前の記事を参考にしてください。

sabakunotabito.hatenablog.com

.NET 6 のインストールは Microsoft のサイトを参考にしてください。 Ubuntu の版数に注意してください。

docs.microsoft.com

docker 構築

最終的なファイル構成を先に紹介します。 Visual Studio Code (VS Code) を使うと Windows 上からファイルの編集が可能です。 さらに、拡張機能を使うと何かと便利なので、 エディタに迷ったら VS Code を選択するのがお勧めです。

ファイル構成

最初に Remote - WSL (Microsoft) を事前にインストールしてください。

docker-compose.yml 作成

docker 資材を格納するため、適当な名前のフォルダを作成します。 ここでは、batsv とします。

$ mkdir batsv
$ cd batsv 

docker-compose.yml を作成し、VS Code で編集します。

$ touch docker-compose.yml
$ code .

code コマンドを実行すると、Windows 上で VS Code が起動します。 Remote - WSL 拡張機能をインストールしておくと、 WSL2 Linux への接続アイコンが左下に表示されるようになります。

docker-compose.yml にバッチ用サーバを作成するために記述します。

version: '3.8'
services:
  batch:
    container_name: 'batch'
    build:
      context: ./source
      dockerfile: Dockerfile
    environment:
      TZ: Asia/Tokyo
    tty: true
    restart: always
    stdin_open: true

サンプルコード作成

次にサンプルコードを作成していきます。 docker-compose.yml に記述した source ディレクトリを作成し、移動します。

$ mkdir source
$ cd source

先に crontab と Dockerfile を作成しておきます。

$ touch crontab
$ touch Dockerfile

コンソールアプリを BackApp という名前で作成します。

$ dotnet new console -o BackApp

Welcome to .NET 6.0!
---------------------
SDK Version: 6.0.301

Telemetry
---------
The .NET tools collect usage data in order to help us improve your experience. It is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell.

Read more about .NET CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry

----------------
Installed an ASP.NET Core HTTPS development certificate.
To trust the certificate run 'dotnet dev-certs https --trust' (Windows and macOS only).
Learn about HTTPS: https://aka.ms/dotnet-https
----------------
Write your first app: https://aka.ms/dotnet-hello-world
Find out what's new: https://aka.ms/dotnet-whats-new
Explore documentation: https://aka.ms/dotnet-docs
Report issues and find source on GitHub: https://github.com/dotnet/core
Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli
--------------------------------------------------------------------------------------
The template "Console App" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on /home/tabito/batsv/source/BackApp/BackApp.csproj...
  Determining projects to restore...
  Restored /home/tabito/batsv/source/BackApp/BackApp.csproj (in 58 ms).
Restore succeeded.

VS Code の Program.cs を編集します。 確認に便利なので、Hello, World! はそのまま残します。

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

using var sw = new StreamWriter("/tmp/testfile.txt");
sw.WriteLine(DateTime.Now);

cron が正しく実行されていることを確認するために、 日時をファイルに出力するだけの簡単な内容を追加します。

crontab 編集

1 分間隔でアプリを実行するように記述します。

*/1 * * * * /app/BackApp

Dockerfile 編集

先ほど作成した BackApp をビルドし、cron 実行させる内容を記述します。 Dockerfile 作成のポイントを簡単に説明します。

  • docker イメージは Ubuntu (focal) を使う。
  • ビルド環境(.NET SDK)と実行環境(.NET Runtime)は別イメージを使う。
  • cron 用として、busybox を追加する。
  • busybox crond で起動する。
# ビルド環境
FROM mcr.microsoft.com/dotnet/sdk:6.0-focal AS build
WORKDIR /source
COPY ["./BackApp/BackApp.csproj", "BackApp/"]
RUN dotnet restore "./BackApp/BackApp.csproj"
COPY . .
RUN dotnet build "./BackApp/BackApp.csproj" -c Release -o /app/build

FROM build AS publish
WORKDIR /source
RUN dotnet publish "./BackApp/BackApp.csproj" -c Releasse -o /app/publish

# 実行環境
FROM mcr.microsoft.com/dotnet/runtime:6.0.6-focal AS runtime
COPY ["crontab", "/var/spool/cron/crontabs/root"]
WORKDIR /app
COPY --from=publish /app/publish .
WORKDIR /

# cron用の資材を追加する
RUN apt-get update && apt-get -y install --no-install-recommends \
      busybox-static \
    && apt-get -y clean \
    && rm -rf /var/lib/apt/lists/*

ENTRYPOINT ["/usr/bin/busybox", "crond", "-f", "-l", "2", "-L", "/dev/stderr"]

mcr.microsoft.com/dotnet/sdk:6.0-focal (runtime:6.0.6-focal) は Microsoft の公式イメージです。 最新版、別の OS を使いたい場合、dockerhub で検索してください。

hub.docker.com

ちなみに、Alpine の方が軽いので使いたいところですが、 apk コマンドがハングする(回避方法あり)ため、 この問題が解決するまでは使わない方がいいと思います。

docker 実行

早速、ビルドして実行しましょう。 docker-compose.yml がある箇所まで移動し、docker-compose コマンドを実行します。

$ cd ~/batsv
$ docker-compose build
[+] Building 79.0s (18/19)
 => [internal] load .dockerignore                                                                                  0.1s
 => => transferring context: 2B                                                                                    0.0s
 => [internal] load build definition from Dockerfile                                                               0.1s
 => => transferring dockerfile: 902B                                                                               0.0s
 => [internal] load metadata for mcr.microsoft.com/dotnet/runtime:6.0.6-focal                                      1.9s
 => [internal] load metadata for mcr.microsoft.com/dotnet/sdk:6.0-focal                                            1.5s
 => [build 1/6] FROM mcr.microsoft.com/dotnet/sdk:6.0-focal@sha256:de284118af917e1f6ac2092c8c1e4a10d6c5171bd96bf  77.0s
 => => resolve mcr.microsoft.com/dotnet/sdk:6.0-focal@sha256:de284118af917e1f6ac2092c8c1e4a10d6c5171bd96bf4d91ed5  0.0s
 => => sha256:de284118af917e1f6ac2092c8c1e4a10d6c5171bd96bf4d91ed551d630285f2f 1.11kB / 1.11kB                     0.0s
 => => sha256:0437b56a84fe8389280cdeb0ebb9fba0b6f4d4a9b1572f48604a9a31e9889a43 18.27MB / 18.27MB                  11.0s
 => => sha256:acfd6470210c2c0dd47b82309390908214c8644069b261c333425820df6b2f32 31.62MB / 31.62MB                  13.7s
 => => sha256:141a07091f84b3b248fc277c0cdf5cdc71517eaa819fa5062adee5ace7fb935d 2.01kB / 2.01kB                     0.0s
 => => sha256:64c8486251a81eb825f83192fde9a175074fdce4b5a3f334a0f2b3bb1d90dcb8 7.21kB / 7.21kB                     0.0s
 => => sha256:d7bfe07ed8476565a440c2113cc64d7c0409dba8ef761fb3ec019d7e6b5952df 28.57MB / 28.57MB                   7.8s
 => => sha256:2b031d49096b5d56cbd8e3196a2258b455e65cf70715863ec59e46b9c869a11b 155B / 155B                         8.0s
 => => sha256:5b91841c9ffee9ec359867ab0f7f91744f2435dbb5346eee0761833f9f7286e4 9.45MB / 9.45MB                    11.6s
 => => extracting sha256:d7bfe07ed8476565a440c2113cc64d7c0409dba8ef761fb3ec019d7e6b5952df                         68.4s
 => => sha256:393bb4f8e0c924ebf05e11fcd75bc182b44e35ebb05acdb73edcb1e84934286b 31.31MB / 31.31MB                  25.4s
 => => extracting sha256:0437b56a84fe8389280cdeb0ebb9fba0b6f4d4a9b1572f48604a9a31e9889a43                         65.9s
 => => sha256:21a1ffcb599c97db4cd71aa48a995c4efd233f007cf4db6a5d08fa23a29788e3 145.60MB / 145.60MB                47.5s
 => => sha256:bb210f227e9575213426ff1633c45aaebc2ff465b1aa39c980f1bc1bb8f2576e 12.89MB / 12.89MB                  22.7s
 => => extracting sha256:acfd6470210c2c0dd47b82309390908214c8644069b261c333425820df6b2f32                         63.2s
 => => extracting sha256:2b031d49096b5d56cbd8e3196a2258b455e65cf70715863ec59e46b9c869a11b                          0.0s
 => => extracting sha256:5b91841c9ffee9ec359867ab0f7f91744f2435dbb5346eee0761833f9f7286e4                          0.4s
 => => extracting sha256:393bb4f8e0c924ebf05e11fcd75bc182b44e35ebb05acdb73edcb1e84934286b                          2.5s
 => => extracting sha256:21a1ffcb599c97db4cd71aa48a995c4efd233f007cf4db6a5d08fa23a29788e3                          7.7s
 => => extracting sha256:bb210f227e9575213426ff1633c45aaebc2ff465b1aa39c980f1bc1bb8f2576e                          0.7s
 => [runtime 1/6] FROM mcr.microsoft.com/dotnet/runtime:6.0.6-focal@sha256:082086b7cc716efada1744c0a608ca4f63ff2  15.6s
 => => resolve mcr.microsoft.com/dotnet/runtime:6.0.6-focal@sha256:082086b7cc716efada1744c0a608ca4f63ff261c794976  0.0s
 => => sha256:082086b7cc716efada1744c0a608ca4f63ff261c794976e17644a956ee4320b1 1.11kB / 1.11kB                     0.0s
 => => sha256:e645d3458a1d9531a5ba089fc8b0aafdd4f627a5e761184f9f2526d9738a2bc9 1.16kB / 1.16kB                     0.0s
 => => sha256:99ebd73a9cca6c0af2e41ddc970d249e70d4c7dc3e3abce8720f7999083aeb71 2.83kB / 2.83kB                     0.0s
 => => sha256:d7bfe07ed8476565a440c2113cc64d7c0409dba8ef761fb3ec019d7e6b5952df 28.57MB / 28.57MB                   7.8s
 => => sha256:0437b56a84fe8389280cdeb0ebb9fba0b6f4d4a9b1572f48604a9a31e9889a43 18.27MB / 18.27MB                  11.0s
 => => sha256:acfd6470210c2c0dd47b82309390908214c8644069b261c333425820df6b2f32 31.62MB / 31.62MB                  13.7s
 => => sha256:2b031d49096b5d56cbd8e3196a2258b455e65cf70715863ec59e46b9c869a11b 155B / 155B                         8.0s
 => => extracting sha256:d7bfe07ed8476565a440c2113cc64d7c0409dba8ef761fb3ec019d7e6b5952df                          2.0s
 => => extracting sha256:0437b56a84fe8389280cdeb0ebb9fba0b6f4d4a9b1572f48604a9a31e9889a43                          1.3s
 => => extracting sha256:acfd6470210c2c0dd47b82309390908214c8644069b261c333425820df6b2f32                          1.4s
 => => extracting sha256:2b031d49096b5d56cbd8e3196a2258b455e65cf70715863ec59e46b9c869a11b                         61.8s
 => [internal] load build context                                                                                  0.1s
 => => transferring context: 6.70kB                                                                                0.0s
 => [runtime 2/6] COPY [crontab, /var/spool/cron/crontabs/root]                                                    0.1s
 => [runtime 3/6] WORKDIR /app                                                                                     0.1s
 => [build 2/6] WORKDIR /source                                                                                    0.1s
 => [build 3/6] COPY [./BackApp/BackApp.csproj, BackApp/]                                                          0.1s
 => [build 4/6] RUN dotnet restore "./BackApp/BackApp.csproj"                                                      2.4s
 => [build 5/6] COPY . .                                                                                           0.1s
 => [build 6/6] RUN dotnet build "./BackApp/BackApp.csproj" -c Release -o /app/build                               3.1s
 => [publish 1/2] WORKDIR /source                                                                                  0.1s
 => [publish 2/2] RUN dotnet publish "./BackApp/BackApp.csproj" -c Releasse -o /app/publish                        2.8s
 => [runtime 4/6] COPY --from=publish /app/publish .                                                               0.1s
 => [runtime 5/6] RUN apt-get update && apt-get -y install --no-install-recommends       busybox-static     && a  11.7s
 => exporting to image                                                                                             0.1s
 => => exporting layers                                                                                            0.1s
 => => writing image sha256:1a4be80a911470403cd56936f39eceb904c1aeb745440f3287fc44b010993c94                       0.0s
 => => naming to docker.io/library/batsv_batch                                                                     0.0s

Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them

初回は時間が掛かるので、しばらく待ちます。 出来上がったら、実行します。

$ docker-compose up -d
[+] Running 2/2
 ⠿ Network batsv_default  Created                                                                                  0.1s
 ⠿ Container batch        Started                                                                                  0.8s

コンテナに接続します。

$ docker-compose exec batch /bin/bash
root@9b764b3b2692:/#

cron 実行が正常に動作していることを確認します。 1分経過後に実行し、時刻が変化していることを確認します。

# cat /tmp/testfile.txt
07/10/2022 22:59:00

不要になったらコンテナを削除します。

$ docker-compose down
[+] Running 2/2
 ⠿ Container batch        Removed                                                                                 10.3s
 ⠿ Network batsv_default  Removed                                                                                  0.2s

まとめ

docker で cron を実行するには、BusyBox を使うのが常套手段なので、情報集めには困らないと思います。 .NET アプリを Dockerfile でビルドから実行までを定義する方法は、情報が少なく 当時は Microsoft のページを参考に作成した記憶があります。

dokcer の理解に苦しんでいる方は、まずは書籍で体系的に学んだ方が良いかもしれません。 私が実際に読んで参考になった書籍を紹介しておきます。

アプリ開発が得意な方は、この本が参考になると思います。 VS Code + Python で、2020年6月時点の情報に基づいています。 VS Code の使い方も学べるため、アプリ開発者には良いと思います。

インフラ構築がメインの方は、この本が参考になると思います。 CentOS ベースで、2018 年 12月時点の情報なので少し古さがあるかもしれませんが、 かなり詳細に説明されていて良かったです。

では、皆さん、よい旅を。