.NET 7のPreviewを使ってDockerfileを使わずにコンテナをビルドしてみる。

# はじめに

.NET 7 からは、Dockerfileが無くてもDockerコンテナがビルドできるということで、どんな動きになるのかを確認していきます。
ただ、.NET 7はまだPreview状態なので、コンテナ内から親のWSLのDockerホストに接続してコンテナをビルドしてみます。

# .NET 7の起動

とりあえず.NET 7.0のコンテナを起動しておきます。

```sh:WSL
$ docker run -it mcr.microsoft.com/dotnet/sdk:7.0
root@5d0192433df9:/#
```

# コンテナ内からWSLのDockerホストに接続する

今回はDockerで起動した.NETのコンテナからWSLで起動しているDockerHostに接続するので、前準備としてコンテナ内からWSLにSSHできるように設定を行います。
まずは、コンテナ内の.NETのコンテナに必要なものをインストールしてSSHするための秘密鍵と公開鍵を作ります。

```sh:コンテナ内
$ apt update
$ apt install -y ssh vim
$ curl -fsSL https://get.docker.com/ | sh
$ ssh-keygen -f docker1
$ ls -la ~/.ssh
total 16
drwx------ 2 root root 4096 Sep 12 04:16 .
drwx------ 1 root root 4096 Sep 12 04:16 ..
-rw------- 1 root root 2602 Sep 12 04:15 docker1
-rw-r--r-- 1 root root  571 Sep 12 04:15 docker1.pub
```

Docker内で作ったカギをWSL側で取り出し、WSL側のauthorized_keysに追記します。

```sh:WSL
$ docker ps
CONTAINER ID   IMAGE                              COMMAND        CREATED          STATUS          PORTS  NAMES
5d0192433df9   mcr.microsoft.com/dotnet/sdk:7.0   "bash"         10 minutes ago   Up 10 minutes          lucid_mclean

$ docker cp 5d0192433df9:/root/.ssh/docker1.pub ~/.ssh/
$ cat ~/.ssh/docker1.pub >> ~/.ssh/authorized_keys
```

Docker側から見た、WSL側のIPアドレスを確認します。
WSL側のIPアドレスは172.17.0.1になっているようです。

```sh:WSL
$ ip a show docker0
8: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
    link/ether 02:42:78:57:e9:8f brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:78ff:fe57:e98f/64 scope link
```

コンテナの中から、WSLにSSHできるかを確認します。

```sh:コンテナ内
$ ssh -i ~/.ssh/docker1 y-sugiyama@172.17.0.1
```

コンテナ内にSSH用の設定ファイルを作ります。

```config:~/.ssh/config
host wsl
    hostname        172.17.0.1
    user            y-sugiyama
    port            22
    identityfile    ~/.ssh/docker1
    identitiesonly  yes
```

同様にconfigを使ってSSH接続できることを確認します。

```sh:コンテナ内
$ ssh wsl
```

無事接続できましたね。
続いてコンテナ内のDockerコマンドの接続先を親のWSLにします。

```sh:コンテナ内
$ docker context create wsl --docker "host=ssh://wsl"
$ docker context use wsl
$ docker ps
CONTAINER ID   IMAGE                              COMMAND        CREATED          STATUS          PORTS                                                                                            NAMES
5d0192433df9   mcr.microsoft.com/dotnet/sdk:7.0   "bash"         49 minutes ago   Up 49 minutes                                                                                                    lucid_mclean
6be44e5722c5   portainer/portainer-ce:2.11.0      "/portainer"   4 months ago     Up 3 hours      0.0.0.0:8000->8000/tcp, :::8000->8000/tcp, 0.0.0.0:9443->9443/tcp, :::9443->9443/tcp, 9000/tcp   portainer
```

これでDockerコンテナ内のDockerコマンドでは、親のWSLで起動するDockerホストを利用するようになりました。

# .NET 7 のプロジェクトをコンテナとしてビルドして実行する

MVCのサンプルアプリを作り、`Microsoft.NET.Build.Containers`パッケージを追加してコンテナをビルドします。

```sh:コンテナ内
$ dotnet new mvc -o SampleMvc
$ cd SampleMvc
$ dotnet add package Microsoft.NET.Build.Containers
$ dotnet publish --os linux --arch x64 -p:PublishProfile=DefaultContainer
MSBuild version 17.4.0-preview-22368-02+c8492483a for .NET
  Determining projects to restore...
  All projects are up-to-date for restore.
/usr/share/dotnet/sdk/7.0.100-preview.7.22377.5/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.RuntimeIdentifierInference.targets(219,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [/root/SampleMvc/SampleMvc.csproj]
  SampleMvc -> /root/SampleMvc/bin/Debug/net7.0/linux-x64/SampleMvc.dll
  SampleMvc -> /root/SampleMvc/bin/Debug/net7.0/linux-x64/publish/
/root/.nuget/packages/microsoft.net.build.containers/0.1.8/build/Microsoft.NET.Build.Containers.targets(45,9): warning CONTAINER001: ContainerImageName was not a valid container image name, it was normalized to samplemvc [/root/SampleMvc/SampleMvc.csproj]
  Pushed container 'samplemvc:1.0.0' to registry 'docker://'

$ docker images | grep sample
samplemvc                              1.0.0                5f38dac6fb96   2 minutes ago   219MB
```

Dockerfileを記述せずに新しい`samlemvc:1.0.0`というコンテナがローカルのリポジトリに登録されました。

:::note info
本来コンテナの名前は小文字の英数、ハイフン、アンダースコアしか含められません。この機能でコンテナをビルドした場合、デフォルトでは.NETのアセンブリ名を小文字に変換したものが利用されます。
:::

コンテナの名前を変更したい場合、MSBuildプロパティーの`ContainerImageName`で変更できます。

```sh:コンテナ内
$ dotnet publish --os linux --arch x64 \
    -p:PublishProfile=DefaultContainer \
    -p:ContainerImageName=sample-mvc
MSBuild version 17.4.0-preview-22368-02+c8492483a for .NET
  Determining projects to restore...
  All projects are up-to-date for restore.
/usr/share/dotnet/sdk/7.0.100-preview.7.22377.5/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.RuntimeIdentifierInference.targets(219,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [/root/SampleMvc/SampleMvc.csproj]
  SampleMvc -> /root/SampleMvc/bin/Debug/net7.0/linux-x64/SampleMvc.dll
  SampleMvc -> /root/SampleMvc/bin/Debug/net7.0/linux-x64/publish/
  Pushed container 'sample-mvc:1.0.0' to registry 'docker://'

# docker images | grep sample
sample-mvc                             1.0.0                a0c7b28acd7c   5 seconds ago    219MB
samplemvc                              1.0.0                5f38dac6fb96   11 minutes ago   219MB
```

sample-mvcという名前でコンテナが作成されたことが確認できます。
実際に動かしてみましょう。今回作ったコンテナはWSLのDockerホストに作成されているのでWindows側からもアクセスできるはずです。

```sh:WSL、コンテナ内どちらでも
$ docker run --rm -p 5000:80 sample-mvc:1.0.0
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
      Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
      No XML encryptor configured. Key {e35ac4a2-42b3-4a77-8736-8fe4130f7afd} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /app
```

無事表示されました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36535/e3254303-72c7-e86e-4d50-94c81f6c8fe6.png)

# いろいろいじってみる

とりあえずビルドされたコンテナの中に入って、現在の内容を確認してみます。
デフォルトのイメージとしては`mcr.microsoft.com/dotnet/aspnet`が利用されるようなので、Debian 11のイメージが動いていることがわかります。

```sh:WSL、コンテナどちらでも
$ docker run --rm -it --entrypoint=sh -p 5000:80 sample-mvc:1.0.0

$ cat /etc/os-release
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

$ dotnet --list-sdks

$ dotnet --list-runtimes
Microsoft.AspNetCore.App 7.0.0-preview.7.22376.6 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 7.0.0-preview.7.22375.6 [/usr/share/dotnet/shared/Microsoft.NETCore.App]

$ set
ASPNETCORE_URLS='http://+:80'
ASPNET_VERSION='7.0.0-preview.7.22376.6'
DOTNET_RUNNING_IN_CONTAINER='true'
DOTNET_VERSION='7.0.0-preview.7.22375.6'
... 略 ...
```

ディストリビューションを変更したい場合は、ContainerBaseImageプロパティーを変更すればよさそうです。
以前のDebianのコンテナとは別に作りたいので、ContainerImageTagプロパティーでタグも一緒に付与します。

```sh:コンテナ内
$ dotnet publish --os linux --arch x64 \
    -p:PublishProfile=DefaultContainer \
    -p:ContainerImageName=sample-mvc \
    -p:ContainerImageTag=1.0.0-jammy \
    -p:ContainerBaseImage=mcr.microsoft.com/dotnet/runtime:7.0-jammy
MSBuild version 17.4.0-preview-22368-02+c8492483a for .NET
  Determining projects to restore...
  All projects are up-to-date for restore.
/usr/share/dotnet/sdk/7.0.100-preview.7.22377.5/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.RuntimeIdentifierInference.targets(219,5): message NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy [/root/SampleMvc/SampleMvc.csproj]
  SampleMvc -> /root/SampleMvc/bin/Debug/net7.0/linux-x64/SampleMvc.dll
  SampleMvc -> /root/SampleMvc/bin/Debug/net7.0/linux-x64/publish/
  Pushed container 'sample-mvc:1.0.0-jammy' to registry 'docker://'

$ docker run --rm -it --entrypoint=sh -p 5000:80 sample-mvc:1.0.0-jammy

$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.1 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.1 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
```

Ubuntu 22.0.4.1 LTS で動いているのが確認できますね。

# 制限

下記のドキュメントに色々書いてありますが、基本dockerコマンドで実行できるものは取り込んでいく方針のようです。

https://github.com/dotnet/sdk-container-builds/blob/main/docs/ContainerCustomization.md

現時点では次のサポートが予定されています。
- エントリポイント
- エントリポイント引数
- ポート
- 環境変数
- ラベル

また、RUNコマンドで実行するようなものはサポートされないようなので、コンテナに追加でインストールような命令がある場合個別にDockerfileを作成するか、必要なモジュールをインストールしたベースイメージをあらかじめ作成しておき、ContainerBaseImageでそのイメージを指定するような使い方になりそうです。

（aptやapkなどでのインストールは↑の方法で良いけれど、NPMやEF Coreのマイグレーション、Libmanなどはプロジェクトの成果物(package.jsonやcsprojなど）を利用するので、今のところDockerfile書くしかないってことかな？？）

# おわりに

他のエコシステムの動きを追っていなかったのですが、Javaの[Jib](https://github.com/GoogleContainerTools/jib)や、Goの[konet](https://github.com/ko-build/ko)なんかでも同様の流れがあるんですね。細かい動作を定義しようとしたり、ネイティブモジュールをインストールしたりするとDockrefileのお世話になりそうですが、お手軽にコンテナをビルドするには面白い選択肢が増えたのはうれしいですね。

今回はWSLと、コンテナ内の.NETを行ったり来たりするし、コンテナ内からWSLのDockerを触ったりと、だいぶ混乱しました