App Service on Linux の組み込みコンテナイメージについて

# はじめに
[Microsoft Azure Tech Advent Calendar 2022](https://qiita.com/advent-calendar/2022/microsoft-azure-tech) の 7 日目の記事です。本稿では、App Service on Linux で利用される組み込みコンテナイメージについて深掘りしてみます。

# App Service on Linux でのランタイムの指定方法

App Service on Linux では、`LinuxFxVersion` というサイトプロパティを用いてランタイムのバージョンを指定することが可能です。

例:
[NET Core バージョンを設定する](https://learn.microsoft.com/ja-jp/azure/app-service/configure-language-dotnetcore?pivots=platform-linux#set-net-core-version)
[Node.js のバージョンを設定する](https://learn.microsoft.com/ja-jp/azure/app-service/configure-language-nodejs?pivots=platform-linux#set-nodejs-version)

利用可能な値は、[`az webapp list-runtimes`](https://learn.microsoft.com/ja-jp/cli/azure/webapp?view=azure-cli-latest#az-webapp-list-runtimes) によって確認することが可能となっています。
2022/12/06 時点では以下の値が利用可能となっています。
```bash
$ az webapp list-runtimes --os linux
[
  "DOTNETCORE:7.0",
  "DOTNETCORE:6.0",
  "DOTNETCORE:3.1",
  "NODE:18-lts",
  "NODE:16-lts",
  "NODE:14-lts",
  "PYTHON:3.10",
  "PYTHON:3.9",
  "PYTHON:3.8",
  "PYTHON:3.7",
  "PHP:8.1",
  "PHP:8.0",
  "RUBY:2.7",
  "JAVA:17-java17",
  "JAVA:11-java11",
  "JAVA:8-jre8",
  "JBOSSEAP:7-java11",
  "JBOSSEAP:7-java8",
  "TOMCAT:10.0-java17",
  "TOMCAT:10.0-java11",
  "TOMCAT:10.0-jre8",
  "TOMCAT:9.0-java17",
  "TOMCAT:9.0-java11",
  "TOMCAT:9.0-jre8",
  "TOMCAT:8.5-java11",
  "TOMCAT:8.5-jre8",
  "GO:1.19",
  "GO:1.18"
]
```

この値をもとに、App Service のプラットフォームはアプリケーションコードを実行するランタイムを含むコンテナイメージを取得することになります。

# 実際に利用されるコンテナイメージ(Blessed Image)

`LinuxFxVersion` に指定された値によって利用されるコンテナイメージを確認していきます。

コンテナイメージは App Service の [Web Worker インスタンス](https://learn.microsoft.com/en-us/archive/msdn-magazine/2017/february/azure-inside-the-azure-app-service-architecture#web-workers) 上に、pull されることになります。[^1]
その実行の様子は、ポータルから [ログストリーム] から、[診断ログ](https://learn.microsoft.com/ja-jp/azure/app-service/troubleshoot-diagnostic-logs#supported-log-types) を利用した場合は同等の内容が `AppServicePlatformLogs` に出力されます。また [App Service ログ] から [ファイルシステムへの保存] を有効にすることで `/home/LogFiles/<YYYY>_<MM>_<DD>_<COMPUTERNAME>_docker.log` から確認することも可能です。

[^1]: https://learn.microsoft.com/ja-jp/azure/app-service/configure-custom-container?pivots=container-linux#how-container-images-are-stored

例として　`LinuxFxVersion` に `NODE:18-lts` を指定した場合、2022/12/06 時点では `mcr.microsoft.com/appsvc/node:18-lts_20221007.3.tuxprod` というイメージが取得され実行されていることが下記のログから見て取れます。

```kql:kusto query
AppServicePlatformLogs
| order by TimeGenerated asc
| project TimeGenerated,OperationName,Message
```

```csv: AppServicePlatformLogs.csv
"2022/12/6 11:19:30.236",ContainerLogs,"Recycling container because of AppFrameworkChange and appFramework = NODE"
"2022/12/6 11:19:32.057",ContainerLogs,"18-lts_20221007.3.tuxprod Pulling from appsvc/node"
"2022/12/6 11:19:32.076",ContainerLogs,"e756f3fdd6a3 Pulling fs layer"
"2022/12/6 11:19:32.078",ContainerLogs,"bf168a674899 Pulling fs layer"
~~省略~~
"2022/12/6 11:19:32.194",ContainerLogs,"27744537ca2a Waiting"
"2022/12/6 11:19:32.195",ContainerLogs,"4366ce258e4d Waiting"
~~省略~~
"2022/12/6 11:19:40.924",ContainerLogs,"901db6977683 Downloading 137B / 137B"
"2022/12/6 11:19:40.931",ContainerLogs,"901db6977683 Verifying Checksum"
"2022/12/6 11:19:40.932",ContainerLogs,"901db6977683 Download complete"
~~省略~~
"2022/12/6 11:19:51.389",ContainerLogs,"e756f3fdd6a3 Extracting 4MB / 52MB"
"2022/12/6 11:19:52.379",ContainerLogs,"e756f3fdd6a3 Extracting 7MB / 52MB"
"2022/12/6 11:20:04.245",ContainerLogs,"e756f3fdd6a3 Pull complete"
~~省略~~
"2022/12/6 11:21:11.373",ContainerLogs," Digest: sha256:659a50f5d66207bad2ca5cedcebcff920e1180c714cb71b0d73334844332e77a"
"2022/12/6 11:21:11.416",ContainerLogs," Status: Downloaded newer image for mcr.microsoft.com/appsvc/node:18-lts_20221007.3.tuxprod"
"2022/12/6 11:21:11.427",ContainerLogs,"Pull Image successful, Time taken: 1 Minutes and 40 Seconds"
"2022/12/6 11:21:11.485",ContainerLogs,"EventName:coldstart-pullimage - Reason: - Message:Docker image pull for image: appsvc/node:18-lts_20221007.3.tuxprod succeeded, Time taken(ms): 101039 - ContainerIDs: - AdditionalInfo: "
"2022/12/6 11:21:12.462",ContainerLogs,"Starting container for site"
"2022/12/6 11:21:12.463",ContainerLogs,"docker run -d --expose=8080 --name qiita2022-wafl_1_8ab92906 -e WEBSITE_SITE_NAME=qiita2022-wafl -e WEBSITE_AUTH_ENABLED=False -e WEBSITE_ROLE_INSTANCE_ID=0 -e WEBSITE_HOSTNAME=qiita2022-wafl.azurewebsites.net -e WEBSITE_INSTANCE_ID=be39435765bdc1e2915106fc7752ae45ac0b8144f3003e97e7d7d3ea6d8b5e67 -e HTTP_LOGGING_ENABLED=1 -e WEBSITE_USE_DIAGNOSTIC_SERVER=True appsvc/node:18-lts_20221007.3.tuxprod  "
"2022/12/6 11:21:16.867",ContainerLogs,"Initiating warmup request to container qiita2022-wafl_1_8ab92906 for site qiita2022-wafl"
"2022/12/6 11:21:20.093",ContainerLogs,"Container qiita2022-wafl_1_8ab92906 for site qiita2022-wafl initialized successfully and is ready to serve requests."
```

ここで取得される `appsvc/node:18-lts_20221007.3.tuxprod` などは、"Blessed Image" と呼ばれ、Microsoft Artifact Registry (https://mcr.microsoft.com) でホストされています。Microsoft Artifact Registry のポータルから確認することはできませんが、https://mcr.microsoft.com/v2/appsvc/node/tags/list などからその存在を確認することができます。

2022/12/06 時点では、`NODE:18-lts` を指定した際には `appsvc/node:18-lts_20221007.3.tuxprod` が取得されますが、プラットフォーム側のアップデートに伴い `NODE:18-lts` に紐づくタグが変更され、新しいランタイムの利用が可能となります。
なお、イメージの pull は Web Worker インスタンス上で、アプリケーションが起動するタイミングで行われるため、Web Apps の再起動されたタイミングや、スケールアウトやスケールアップ・ダウンによって、Web Worker インスタンスが変更されたタイミングなどで発生することになります。

`LinuxFxVersion` に `NODE:16-lts` を指定した場合は、`mcr.microsoft.com/appsvc/node:16-lts_20220609.1.tuxprod`　に解決され、差分レイヤのみ取得されます。

```csv: AppServicePlatformLogs.csv
"2022/12/6 11:27:22.151",ContainerLogs,"Recycling container because of AppFrameworkVersionChange and appFrameworkVersion = 16-lts"
"2022/12/6 11:27:23.454",ContainerLogs,"16-lts_20220609.1.tuxprod Pulling from appsvc/node"
"2022/12/6 11:27:23.457",ContainerLogs,"b03a94565ecb Already exists"
~~省略~~
"2022/12/6 11:27:24.149",ContainerLogs,"0b757cf189e0 Pulling fs layer"
"2022/12/6 11:27:27.642",ContainerLogs,"caf5cd387270 Downloading 947B / 947B"
"2022/12/6 11:27:27.650",ContainerLogs,"caf5cd387270 Verifying Checksum"
~~省略~~
"2022/12/6 11:28:16.137",ContainerLogs,"0b757cf189e0 Pull complete"
"2022/12/6 11:28:16.201",ContainerLogs," Digest: sha256:4d79adca34b16a07b4515991391a6f0dbbb4b9485806b2e536af46233f168f92"
"2022/12/6 11:28:16.244",ContainerLogs," Status: Downloaded newer image for mcr.microsoft.com/appsvc/node:16-lts_20220609.1.tuxprod"
"2022/12/6 11:28:16.252",ContainerLogs,"Pull Image successful, Time taken: 0 Minutes and 53 Seconds"
"2022/12/6 11:28:16.340",ContainerLogs,"EventName:coldstart-pullimage - Reason: - Message:Docker image pull for image: appsvc/node:16-lts_20220609.1.tuxprod succeeded, Time taken(ms): 54025 - ContainerIDs: - AdditionalInfo: "
"2022/12/6 11:28:16.600",ContainerLogs,"Starting container for site"
"2022/12/6 11:28:16.601",ContainerLogs,"docker run -d --expose=8080 --name qiita2022-wafl_2_04bdb7ab -e WEBSITE_SITE_NAME=qiita2022-wafl -e WEBSITE_AUTH_ENABLED=False -e WEBSITE_ROLE_INSTANCE_ID=0 -e WEBSITE_HOSTNAME=qiita2022-wafl.azurewebsites.net -e WEBSITE_INSTANCE_ID=be39435765bdc1e2915106fc7752ae45ac0b8144f3003e97e7d7d3ea6d8b5e67 -e HTTP_LOGGING_ENABLED=1 -e WEBSITE_USE_DIAGNOSTIC_SERVER=True appsvc/node:16-lts_20220609.1.tuxprod  "
"2022/12/6 11:28:19.478",ContainerLogs,"Initiating warmup request to container qiita2022-wafl_2_04bdb7ab for site qiita2022-wafl"
"2022/12/6 11:28:23.594",ContainerLogs,"Container qiita2022-wafl_2_04bdb7ab for site qiita2022-wafl initialized successfully and is ready to serve requests."
```

再び `LinuxFxVersion` に `NODE:18-lts` に戻した場合は、この Web Worker インスタンス上には pull 済みのイメージが利用されることになります。

```csv: AppServicePlatformLogs.csv
"2022/12/6 11:29:57.522",ContainerLogs,"Recycling container because of AppFrameworkVersionChange and appFrameworkVersion = 18-lts"
"2022/12/6 11:29:59.656",ContainerLogs,"18-lts_20221007.3.tuxprod Pulling from appsvc/node"
"2022/12/6 11:29:59.657",ContainerLogs," Digest: sha256:659a50f5d66207bad2ca5cedcebcff920e1180c714cb71b0d73334844332e77a"
"2022/12/6 11:29:59.658",ContainerLogs," Status: Image is up to date for mcr.microsoft.com/appsvc/node:18-lts_20221007.3.tuxprod"
"2022/12/6 11:29:59.696",ContainerLogs,"Pull Image successful, Time taken: 0 Minutes and 1 Seconds"
"2022/12/6 11:29:59.745",ContainerLogs,"EventName:coldstart-pullimage - Reason: - Message:Docker image pull for image: appsvc/node:18-lts_20221007.3.tuxprod succeeded, Time taken(ms): 2055 - ContainerIDs: - AdditionalInfo: "
"2022/12/6 11:29:59.791",ContainerLogs,"Starting container for site"
"2022/12/6 11:29:59.793",ContainerLogs,"docker run -d --expose=8080 --name qiita2022-wafl_3_c8a8fa84 -e WEBSITE_SITE_NAME=qiita2022-wafl -e WEBSITE_AUTH_ENABLED=False -e WEBSITE_ROLE_INSTANCE_ID=0 -e WEBSITE_HOSTNAME=qiita2022-wafl.azurewebsites.net -e WEBSITE_INSTANCE_ID=be39435765bdc1e2915106fc7752ae45ac0b8144f3003e97e7d7d3ea6d8b5e67 -e HTTP_LOGGING_ENABLED=1 -e WEBSITE_USE_DIAGNOSTIC_SERVER=True appsvc/node:18-lts_20221007.3.tuxprod  "
```

## Blessed Image の 構成

以後は Node.js のイメージをもとに確認を進めます。言語によっては若干構成が異なる可能性があります。
実際に動作するコンテナの情報をSSH 接続して見てみます。


```
Last login: Tue Dec  6 11:47:14 2022 from 169.254.129.3
   _____                               
  /  _  \ __________ _________   ____  
 /  /_\  \\___   /  |  \_  __ \_/ __ \ 
/    |    \/    /|  |  /|  | \/\  ___/ 
\____|__  /_____ \____/ |__|    \___  >
        \/      \/                  \/ 
A P P   S E R V I C E   O N   L I N U X

Documentation: http://aka.ms/webapp-linux
NodeJS quickstart: https://aka.ms/node-qs
NodeJS Version : v18.2.0
Note: Any data outside '/home' is not persisted

root@36e4e181ff74:/home# which node
/usr/local/bin/node
root@36e4e181ff74:/home# node -v
v18.2.0
root@36e4e181ff74:/home# which npm
/usr/local/bin/npm
root@36e4e181ff74:/home# npm -v
6.14.15
root@36e4e181ff74:/home# which pm2
/usr/local/bin/pm2
root@36e4e181ff74:/home# pm2 -V
4.5.6
```

Node.JS 本家においては、2022-10-25 に、18 系の Active LTS が開始されており、その際のバージョニングは 18.12.0 で現在の最新バージョンは、18.12.1 となっています[^2]が、上記のように App Service 上では反映されておらず 18.2.0 がインストールされています。`pm2` もちょっと古いですね。いつ反映されるかといった明確なスケジュールの公開情報はありませんが以下のポリシーに沿って順次更新されるものと考えられます。

[Node.js on App Service | Node.js Update Policy](https://github.com/Azure/app-service-linux-docs/blob/master/Runtime_Support/node_support.md#nodejs-update-policy)
> App Service upgrades the underlying Node.js runtime and SDK of your application as part of the regular platform updates. As a result of this update process, your application will be automatically updated to the latest patch version available in the platform for the configured runtime of your app.

App Service on Linux におけるそのほかの言語のサポートタイムラインは [こちら](https://github.com/Azure/app-service-linux-docs/tree/master/Runtime_Support) から確認できます。
サポートが終了した場合、`NODE|12-lts` のように、`LinuxFxVersion` に設定できる選択肢から削除されます。

Node ランタイムのマイナーバージョン更新をプラットフォーム側に任せることができる一方で、自動更新の落とし穴としてマイナーバージョン更新時にアプリケーションで利用しているライブラリ等が動作しなくなるといった可能性もゼロとは言い切れません。
また、前述したようにイメージの pull は Web Worker インスタンス上で、アプリケーションが起動するタイミングで行われます。そのためタイミングによっては複数のインスタンスで実行しているイメージが異なるといった可能性もあり得ます。
「デプロイしてないし、設定変更も何もしてないのにアプリケーションが旧に動かなくなった」ってときはイメージが変更されていないかが 1 つの確認ポイントとなります。

任意のバージョンのランタイムや、モジュールを固定して利用する上では、[カスタム コンテナー](https://learn.microsoft.com/ja-jp/azure/app-service/configure-custom-container?pivots=container-linux)の利用が確実な方法となります。
また、[カスタムコマンド](https://learn.microsoft.com/ja-jp/azure/app-service/configure-language-nodejs?pivots=platform-linux#run-custom-command) を利用して、コンテナ起動後にインストールすることで回避可能なシナリオ[^3] もありますが、カスタムコマンドはコンテナ起動後に実行されるためアプリケーション起動時間の増加に繋がります。個人的には追加でインストールするようなものがあるのであれば最初からカスタム コンテナーを選択したほうが良いのではないかと考えます。
カスタムコマンドについては後述します。

[^2]: https://github.com/nodejs/release#release-schedule
[^3]: https://azureossd.github.io/2022/10/14/Nodejs-on-App-Service-Linux-and-why-to-avoiding-installing-packages-in-startup-scripts/index.html

## Blessed Image においてどのようにアプリケーションが実行されるか

次に、Blessed Image が起動時にアプリケーションコード実行までどのような処理をしているかを見ていきます。
ここでは `NODE:18-lts`を指定した際に利用される `mcr.microsoft.com/appsvc/node:18-lts_20221007.3.tuxprod` を扱います。
[公開リポジトリ](https://github.com/Azure-App-Service/ImageBuilder)もありますが、ローカル環境に pull して現物から確認していきます。

```
// pull
docker pull mcr.microsoft.com/appsvc/node:18-lts_20221007.3.tuxprod 
// 詳細確認
docker inspect mcr.microsoft.com/appsvc/node:18-lts_20221007.3.tuxprod
```

inspect の結果、下記のようなEntrypointが指定されていることがわかります。
```
"Entrypoint": [
    "/opt/startup/init_container.sh"
],
```

/opt/startup/init_container.sh を確認してみます。
SSH 接続時のアスキーアート出力や、sshd の開始、`WEBSITE_USE_DIAGNOSTIC_SERVER`指定時のcronの開始[^4]の後、[Oryx](https://github.com/microsoft/Oryx) を用いて `/opt/startup/startup.sh` を作成および実行していることがわかります。
```bash: /opt/startup/init_container.sh確認
$ docker run -it --rm --entrypoint "bash" mcr.microsoft.com/appsvc/node:18-lts_20221007.3.tuxprod
root@0f322f584ba0:/home# cat /opt/startup/init_container.sh
#!/usr/bin/env bash
cat >/etc/motd <<EOL
   _____
  /  _  \ __________ _________   ____
 /  /_\  \\\___   /  |  \_  __ \_/ __ \
/    |    \/    /|  |  /|  | \/\  ___/
\____|__  /_____ \____/ |__|    \___  >
        \/      \/                  \/
A P P   S E R V I C E   O N   L I N U X

Documentation: http://aka.ms/webapp-linux
NodeJS quickstart: https://aka.ms/node-qs
NodeJS Version : `node --version`
Note: Any data outside '/home' is not persisted

EOL
cat /etc/motd

mkdir "$PM2HOME"
chmod 777 "$PM2HOME"
ln -s /home/LogFiles "$PM2HOME"/logs

# Get environment variables to show up in SSH session
eval $(printenv | sed -n "s/^\([^=]\+\)=\(.*\)$/export \1=\2/p" | sed 's/"/\\\"/g' | sed '/=/s//="/' | sed 's/$/"/' >> /etc/profile)

# starting sshd process
source /opt/startup/startssh.sh

echo '' > /etc/cron.d/diag-cron
if [ "$WEBSITE_USE_DIAGNOSTIC_SERVER" != false ]; then
    /run-diag.sh > /dev/null
    echo '*/5 * * * * bash -l -c "/run-diag.sh > /dev/null"' >> /etc/cron.d/diag-cron
    chmod 0644 /etc/cron.d/diag-cron
    crontab /etc/cron.d/diag-cron
    /etc/init.d/cron start
fi

STARTUP_COMMAND_PATH="/opt/startup/startup.sh"
ORYX_ARGS="create-script -appPath /home/site/wwwroot -output $STARTUP_COMMAND_PATH -defaultApp=/opt/startup/default-static-site.js -userStartupCommand '$@'"

if [[ $APPSVC_REMOTE_DEBUGGING == "TRUE" ]]; then
    ORYX_ARGS="$ORYX_ARGS -remoteDebug -debugPort $APPSVC_TUNNEL_PORT"
elif [[ "$APPSVC_REMOTE_DEBUGGING_BREAK" == "TRUE" ]]; then
    ORYX_ARGS="$ORYX_ARGS -remoteDebugBrk -debugPort $APPSVC_TUNNEL_PORT"
fi

if [ -f "oryx-manifest.toml" ] && [[ "$APPSVC_RUN_ZIP" == "TRUE" ]]; then
    # NPM adds the current directory's node_modules/.bin folder to PATH before it runs, so commands in
    # "npm start" can files there. Since we move node_modules, we have to add it to the path ourselves.
    echo 'Fixing up path'
    export PATH=/node_modules/.bin:$PATH
    echo "$PATH"
fi

eval oryx $ORYX_ARGS

STARTUPCOMMAND=$(cat $STARTUP_COMMAND_PATH)
echo "Running $STARTUPCOMMAND"
exec $STARTUP_COMMAND_PATH
```

アプリケーションコード未デプロイの状態では、`/opt/startup/startup.sh`には下記のようなファイルが生成されています。
デフォルトアプリケーションとして `/opt/startup/default-static-site.js` を起動しています。

```/opt/startup/startup.sh(デフォルト)
root@a670a2466806:/home# cat /opt/startup/startup.sh 
#!/bin/sh

# Enter the source directory to make sure the script runs where the user expects
cd "/home/site/wwwroot"

export NODE_PATH=/usr/local/lib/node_modules:$NODE_PATH
if [ -z "$PORT" ]; then
                export PORT=8080
fi

node /opt/startup/default-static-site.js
```
このアプリケーションが下記の画面を表示しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4914/6d040215-8c42-ca12-8cf0-c36166ff12a9.png)

アプリケーションをデプロイしてみます。今回はとりあえず [zip デプロイ](https://learn.microsoft.com/ja-jp/azure/app-service/deploy-zip?tabs=cli#deploy-a-zip-package)で。

```bash
npx express-generator myExpressApp --view ejs
cd myExpressApp
npm install
zip -r release.zip .
az webapp deploy -n qiita2022-wafl -g toshida-qiita-2022 --src-path release.zip
```

改めてSSH 接続後に `/opt/startup/startup.sh` を見てみます。

```/opt/startup/startup.sh(アプリあり) 
root@84503cd72c80:/home# cat /opt/startup/startup.sh  
#!/bin/sh

# Enter the source directory to make sure the script runs where the user expects
cd "/home/site/wwwroot"

export NODE_PATH=/usr/local/lib/node_modules:$NODE_PATH
if [ -z "$PORT" ]; then
                export PORT=8080
fi

npm start
```

さきほど `node /opt/startup/default-static-site.js` だった箇所が `npm start` に変わっています。
これは、`oryx create-script`の実施によって /home/site/wwwroot/package.json 内の start コマンドが検出されたためとなります。
これによって、コンテナ起動時に、`/opt/startup/init_container.sh` -> `/opt/startup/startup.sh` -> `npm start` という順序で実行されアプリケーションが起動することになります。

```json: package.json抜粋
  "scripts": {
    "start": "node ./bin/www"
  },
```

[^4]:なにかの診断取得用と考えられるが、[リファレンス](https://learn.microsoft.com/ja-jp/azure/app-service/reference-app-settings?tabs=kudu%2Cdotnet)に未記載のため詳細不明


## Blessed Image の 起動とカスタムコマンド

次にカスタムコマンドを実行した場合の挙動を確認します。
以下のように App Settings と、スタートアップコマンドを設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4914/3a2b17aa-f967-0e91-1aa9-b5bcd4fea81c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4914/fefe05b5-06ac-f73e-f4f3-f36563bf1546.png)


コンテナ起動時のコマンドは以下のようになります。
スタートアップコマンドで指定した内容が docker run コマンドの引数に加えられています。
```
docker run -d --expose=8080 --name qiita2022-wafl_0_d2d9c27c -e WEBSITE_SITE_NAME=qiita2022-wafl -e WEBSITE_AUTH_ENABLED=False -e WEBSITE_ROLE_INSTANCE_ID=0 -e WEBSITE_HOSTNAME=qiita2022-wafl.azurewebsites.net -e WEBSITE_INSTANCE_ID=be39435765bdc1e2915106fc7752ae45ac0b8144f3003e97e7d7d3ea6d8b5e67 -e HTTP_LOGGING_ENABLED=1 -e WEBSITE_USE_DIAGNOSTIC_SERVER=True appsvc/node:18-lts_20221007.3.tuxprod echo $MY_SETTING_VAR && npm start
```

この値は ENTRYPOINT である、`/opt/startup/init_container.sh`の引数となり、 `ORYX_ARGS="create-script -appPath /home/site/wwwroot -output $STARTUP_COMMAND_PATH -defaultApp=/opt/startup/default-static-site.js -userStartupCommand '$@'"` の `$@`として利用されます。その結果生成される `/opt/startup/startup.sh` は以下のようになりました。

```/opt/startup/startup.sh(カスタムスタートアップあり)
root@cafb5d2c068d:/home# cat /opt/startup/startup.sh 
#!/bin/sh

# Enter the source directory to make sure the script runs where the user expects
cd "/home/site/wwwroot"

export NODE_PATH=/usr/local/lib/node_modules:$NODE_PATH
if [ -z "$PORT" ]; then
                export PORT=8080
fi

PATH="$PATH:/home/site/wwwroot" echo $MY_SETTING_VAR && npm start
```

実行された際の出力内容は以下の通り、アプリケーション設定値として設定した `MY_SETTING_VAR` が環境変数として存在し、 `HelloWorld` の出力となっていることがわかります。


```kql: query
AppServiceConsoleLogs
| order by TimeGenerated asc
| project TimeGenerated,ResultDescription
```

```csv: AppServiceConsoleLogs
"2022/12/6 13:42:27.282"," Writing output script to '/opt/startup/startup.sh'
"2022/12/6 13:42:27.424"," Running #!/bin/sh
"2022/12/6 13:42:27.424"," 
"2022/12/6 13:42:27.424"," # Enter the source directory to make sure the script runs where the user expects
"2022/12/6 13:42:27.424"," cd ""/home/site/wwwroot""
"2022/12/6 13:42:27.424"," 
"2022/12/6 13:42:27.424"," export NODE_PATH=/usr/local/lib/node_modules:$NODE_PATH
"2022/12/6 13:42:27.424"," if [ -z ""$PORT"" ]; then
"2022/12/6 13:42:27.424"," 		export PORT=8080
"2022/12/6 13:42:27.424"," fi
"2022/12/6 13:42:27.424"," 
"2022/12/6 13:42:27.430"," PATH=""$PATH:/home/site/wwwroot"" echo $MY_SETTING_VAR && npm start
"2022/12/6 13:42:27.442"," HelloWorld
"2022/12/6 13:42:28.929"," npm info it worked if it ends with ok
"2022/12/6 13:42:28.930"," npm info using npm@6.14.15
"2022/12/6 13:42:28.938"," npm info using node@v18.2.0
"2022/12/6 13:42:29.578"," npm info lifecycle myexpressapp@0.0.0~prestart: myexpressapp@0.0.0
"2022/12/6 13:42:29.607"," npm info lifecycle myexpressapp@0.0.0~start: myexpressapp@0.0.0
"2022/12/6 13:42:29.643"," 
"2022/12/6 13:42:29.643"," > myexpressapp@0.0.0 start /home/site/wwwroot
```
ところで、AppServicePlatformLogs で確認できる、`docker run` コマンドには `-e`オプションで`MY_SETTING_VAR`が指定されていないように見られますが、これは秘密情報が含まれる可能性のある アプリケーション設定値 をログに出力しないようにサニタイズされているものと考えられます。実際には実行結果から得られるようにコンテナ上ではアプリケーション設定値の設定内容は、環境変数としてアクセスできる状態になっています。
今回は、Node.js のイメージで確認を進めましたが、.NET や Python など他の言語のイメージにおいても、カスタムスタートアップコマンドを受け付ける仕組みが設けられています。
このように、カスタムスタートアップコマンドはコンテナ起動後に処理を行うことができますが、これはコンテナの起動時間の増加につながる可能性も含んでいます。デフォルトではコンテナ起動から [240 秒以内](https://learn.microsoft.com/ja-jp/troubleshoot/azure/app-service/faqs-app-service-linux?toc=%2Fazure%2Fapp-service%2Ftoc.json#----------------------------------)[^5]に[Webサーバーが応答できる状態](https://learn.microsoft.com/ja-jp/azure/app-service/configure-language-nodejs?pivots=platform-linux#robots933456-in-logs)になっている必要があります。

[^5]:最大 1800 まで増やすことができます。

例えば、カスタムスタートアップスクリプト内で `npm install` や `npx` などを行い依存モジュールを追加することは不必要に起動時間を遅くすることになるため、必要なモジュールはビルドの段階で含めるようにしたほうが良いと考えます。

カスタムスタートアップスクリプトが有効なシナリオとしては、例えばコンテナ内で
[cron を動作](https://azureossd.github.io/2020/01/23/custom-startup-for-nodejs-python/)させたり、[ミドルウェアの規定の設定を更新](https://techcommunity.microsoft.com/t5/apps-on-azure-blog/configure-nginx-for-php-8-linux-azure-app-service/ba-p/3069373)するといった用途に役立つと考えられます。

# Kuduliteについて

App Service on Linux では、[Kudu サービス](https://learn.microsoft.com/ja-jp/azure/app-service/resources-kudu)は、同一Workerインスタンス上で動作する [kudulite](https://github.com/Azure-App-Service/KuduLite) のコンテナによって提供されています。[^6]
Kuduアクセス時の Bash 接続は Kudu コンテナへの接続、WebSSH は アプリケーションコンテナへの接続となります。そのため例えば Bash 接続時に確認できる `node -v` と WebSSH 利用時に確認できる `node -v` は全く別物となります。
またコンテナのファイルシステムは、/home 以下以外は共有されていません。


[^6]: [Kudulite newui の紹介](https://jpazpaas.github.io/blog/2022/11/28/How-to-use-Kudu-site.html#tips-2-new-ui-kudulite)