App Service on Linux Node runtime における デプロイおよびリモートビルド(Kudu Oryx Build) について

# はじめに

App Service に おいてリモートビルドが有効な場合には、SCM サイトである [kudu](https://github.com/projectkudu/kudu) (App Service On Linux の場合は [Kudulite](https://github.com/Azure-App-Service/KuduLite)コンテナ)上で、[Oryx](https://github.com/microsoft/Oryx) を用いたリモートビルドが行われます。

この記事では、App Service On Linux Node アプリケーションを題材に、デプロイおよび、リモートビルドがどのように実行されているのかを確認します。
デプロイ、ビルド時間を早める工夫について別途記事にて記載しています。

https://qiita.com/georgeOsdDev@github/items/7884fabcabc1cb30eb88

https://qiita.com/georgeOsdDev@github/items/03e5fc8bb9e35d0de792

https://qiita.com/georgeOsdDev@github/items/f6757afc46b6fc5566bb

なお、この記事は実行時のログや、検証から見えてきたことをもとに作成しており、必ずしも公式に公開されている情報ではありません。個人の見解です。

## アプリケーションはどこでビルドされるのか

App Service 以外でビルドされるか（ローカルビルド）、App Service 上でビルドされるか（リモートビルド）に大別されます。

- ローカルビルド
    - ローカル作業マシン
    - GitHub Actions や Azure DevOps 、Circle CI、Jenkins などの CICD パイプライン
- リモートビルド

GitHub Actions 等の CICD パイプライン上でビルドされることを `ローカルビルド` と呼ぶことに少し違和感がありますが、App Service 上でビルドするかどうかといった観点で考えます。

ローカル環境から zip デプロイを利用する場合や、GitHub Actions などからデプロイする場合には、通常は展開後に実行可能な状態のファイル群が含まれた（ローカルビルドされた） zip ファイルがデプロイされるため、App Service 上でのビルド処理（リモートビルド）は実行されません。

リモートビルドは実行される条件がいくつかあります

- [ローカル Git デプロイ](https://learn.microsoft.com/ja-jp/azure/app-service/deploy-local-git?tabs=cli)を用いて App Service 上の git リポジトリへ push した場合
- アプリケーション設定値 [SCM_DO_BUILD_DURING_DEPLOYMENT](https://learn.microsoft.com/ja-jp/azure/app-service/reference-app-settings?tabs=kudu%2Cdotnet#build-automation)に true が指定されている場合
- [`.deployment`](https://github.com/projectkudu/kudu/wiki/Configurable-settings#enabledisable-build-actions) ファイルで `SCM_DO_BUILD_DURING_DEPLOYMENT=true` が指定されている場合

ローカル Git デプロイを用いる場合は、App Service に push されたソースコードを利用可能な状態にするため、App Service 上でビルド処理をする必要がでてくることからわかりやすい利用例の1つです。
ローカル Git 以外のデプロイ方法において、設定値によってリモートビルドを有効にするメリットとしては、アプリケーションの実行環境と同等の環境[^1]でビルドすることで依存関係の問題が発生しなくなることが挙げられます。　
また、デプロイ時に依存モジュールを含める必要がなくなるため、デプロイモジュールの肥大化を回避することができます。

[^1]: 厳密にはアプリケーションが動作するコンテナとは異なる環境ではあるが、Azure App Service プラットフォーム側が動作を担保しているビルド環境と捉えることができます。

## VS Code を利用する場合の注意事項

VS Code App Service 拡張機能を利用してデプロイする場合には注意が必要です。
VS Code App Service 拡張機能 は、デプロイ時の zip 作成及びアップロード速度のために `node_modules` を zip ファイルに含めないで、リモートビルドを用いる機能があります。

https://github.com/microsoft/vscode-azureappservice/wiki/Configuring-Zip-Deployment

以下のダイアログで `Yes` を選択した場合、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4914/3f923481-3f30-6be2-f42b-ab266cf2d918.png)

VS Code App Service 拡張機能　がよしなに、`node_modules` をデプロイする `zip` ファイルから除外することになり、
次のような `.deployment` ファイルが自動的にプロジェクトルートに作成され、リモートビルドが実行されることになります。詳細は後述します。

```.deployment
[config]
SCM_DO_BUILD_DURING_DEPLOYMENT=true
```


# ローカルビルド実行の概要

大まかな処理の流れは以下の通りです。

- 作業環境がビルド処理を行い、すべての依存関係を含む zip ファイルを作成し、POST リクエストで Kudu サーバーに送信する
- HTTP リクエストとして、zip ファイルを Kudu が受け取る
- Kudu が zip ファイルを /tmp/zipdeploy/extracted に展開する
- `/home/site/deployments/tools/deploy.sh` が実行される
    - KuduSync を利用して、デプロイ差分のみ`/home/site/wwwroot` にコピーされる
- Kudu がアプリケーションのリサイクルを行う
- アプリケーションコンテナが再起動され、アプリケーションを実行する（詳細は [こちらの記事](https://qiita.com/georgeOsdDev@github/items/abdadd35248b98c4ef88#blessed-image-%E3%81%AB%E3%81%8A%E3%81%84%E3%81%A6%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%8C%E5%AE%9F%E8%A1%8C%E3%81%95%E3%82%8C%E3%82%8B%E3%81%8B) 参照）


# リモートビルド(Kudu Oryx Build) の概要

大まかな処理の流れは以下の通りです。

- 作業環境が依存関係を記述したファイルは含むが、依存関係そのものを除外した zip ファイルを作成し、POST リクエストで Kudu サーバーに送信する
- HTTP リクエストとして、zip ファイルを Kudu が受け取る
- Kudu が zip ファイルを /tmp/zipdeploy/extracted に展開する
- Kudu が Oryx のプロセスを開始してビルドする
    - Oryx が プロジェクトの言語を判定し、言語毎の処理を行う
    - Oryx が /tmp/ランダム値 な作業ディレクトリで[Node.js の場合](https://github.com/microsoft/Oryx/blob/main/doc/runtimes/nodejs.md#build)は、`npm install` 、[Python の場合](https://github.com/microsoft/Oryx/blob/main/doc/runtimes/python.md)は `pip install` など　[各言語毎の処理](https://github.com/microsoft/Oryx/tree/main/doc/runtimes)を行う
    - Oryx が 作業ディレクトリの成果物を `/home/site/wwwroot` にコピーする
- Kudu がアプリケーションのリサイクルを行う
- アプリケーションコンテナが再起動され、アプリケーションを実行する

ソースはたぶんこのあたり

https://github.com/projectkudu/kudu/blob/master/Kudu.Services/Deployment/PushDeploymentController.cs

https://github.com/microsoft/Oryx/blob/main/src/BuildScriptGenerator/Node/NodeBashBuildSnippet.sh.tpl

# 計測

実際に計測してみます。

## 環境

Azure リソースは以下の通りです。App Service Plan 上には、今回のデプロイ対象の Web App 以外は動作していません。

-　LinuxFxVersion: `NODE|18-lts`
-　App Service Plan SKU: `S1`

App Service リソースを作成した結果以下のバージョンのリソースが作成されました。

-　PLATFORM_VERSION: `99.0.10.795`
-　KuduLite version: `1.0.0.7`( [e59ed50ca2](https://github.com/Azure-App-Service/KuduLite/commit/e59ed50ca265f3b68642a72e14a47fbd6b85cac0))


検証時点の VS Code App Service 機能拡張のバージョンは `v0.24.7` となります。

## 題材

今回は、[Next.js](https://nextjs.org/docs/getting-started#automatic-setup) のサンプルアプリで試してみます。

```bash
$ npx create-next-app@latest --typescript next-app-sample-ts
Need to install the following packages:
  create-next-app@13.2.4
Ok to proceed? (y) y
✔ Would you like to use ESLint with this project? … No / Yes
✔ Would you like to use `src/` directory with this project? … No / Yes
✔ Would you like to use experimental `app/` directory with this project? … No / Yes
✔ What import alias would you like configured? … @/*
Creating a new Next.js app in /home/toshida/Projects/AzureWork/qiita/nodeDeploySpeedTest/next-app-sample-ts.

Using npm.

Initializing project with template: default


Installing dependencies:
- react
- react-dom
- next
- typescript
- @types/react
- @types/node
- @types/react-dom
- eslint
- eslint-config-next


added 270 packages, and audited 271 packages in 23s

100 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
Initialized a git repository.

Success! Created next-app-sample-ts at /home/toshida/Projects/AzureWork/qiita/nodeDeploySpeedTest/next-app-sample-ts


toshida at jp-toshida in ~/Projects/AzureWork/qiita/nodeDeploySpeedTest
$ cd next-app-sample-ts

toshida at jp-toshida in ~/Projects/AzureWork/qiita/nodeDeploySpeedTest/next-app-sample-ts (main)
$ du -sh node_modules
386M    node_modules
```

node_modules は 386M ほどです。 

## ローカルビルドの場合

### 事前準備

まずは、リモートビルドを使わない状態でデプロイしてみます。
なお、Zip デプロイによる [symlink 問題](https://azureossd.github.io/2022/10/24/NPM-Executables-not-being-found-at-startup-on-App-Service-Linux/index.html
)を回避するため、npm start コマンドは事前に変更しておきます。

```package.json
-    "start": "next start",
+    "start": "node_modules/next/dist/bin/next start",
```

また、デプロイ時にビルド済みモジュールが含まれるようにをするために、`preDeployTask` を構成します。
```json:.vscode/settings.json
{
  "appService.showBuildDuringDeployPrompt": false,
  "appService.preDeployTask": "npm: build"
}
```

```json:.vscode/tasks.json
{
	"version": "2.0.0",
	"tasks": [
		{
			"type": "npm",
			"script": "install",
			"group": "build",
			"problemMatcher": [],
			"label": "npm: install",
			"detail": "install dependencies from package"
		},
		{
			"type": "npm",
			"script": "build",
			"group": "build",
			"problemMatcher": [],
			"label": "npm: build",
			"detail": "next build",
			"dependsOn": [
				"npm: install"
			]
		}
	]
}

```

### ローカルビルド + zip デプロイの実行 

VS Code App Service 拡張機能から [Deploy to Web App...] を実行すると、preタスクとしてまず、`npm install` と `npm build` が実行されます。ここでかかる時間は主に作業マシンのスペックや、プロジェクト構成によるものとなります。

次に、実際の VS Code App Service 拡張機能による、zip デプロイ処理が開始されます。なお、VS Code 上の Output パネルには、 VS Code App Service 拡張機能からの出力と、Kudu からの出力が混ざって表示されてしまいますためタイムスタンプが少しわかりにくい状態となっていますがログからわかることとしては、

- `node_modules` や `.next` ディレクトリを含んだ zip ファイルのサイズはおおよそ 100 MB
- zip ファイルは展開されて`Kudu sync from: '/tmp/zipdeploy/extracted' to: '/home/site/wwwroot'`が行われている
- 12000超のファイルのコピーが発生している
- VS Code上での作業開始「2:04:37 Starting deployment...」からアプリコンテナの再起動開始「2:10:40 Triggering recycle」まで 6 分 3 秒かかっている

```log:output
2:04:37 AM node-deploy-speed-test: Starting deployment...
2:04:40 AM node-deploy-speed-test: Creating zip package...
2:05:18 AM node-deploy-speed-test: Zip package size: 103 MB
2:05:13 AM node-deploy-speed-test: Fetching changes.
2:05:14 AM node-deploy-speed-test: Cleaning up temp folders from previous zip deployments and extracting pushed zip file /tmp/zipdeploy/45b46238-980b-4839-807e-d78b0a08c356.zip (99.64 MB) to /tmp/zipdeploy/extracted
2:05:56 AM node-deploy-speed-test: Updating submodules.
2:05:57 AM node-deploy-speed-test: Preparing deployment for commit id 'fceec0ce-5'.
2:05:57 AM node-deploy-speed-test: PreDeployment: context.CleanOutputPath False
2:05:57 AM node-deploy-speed-test: PreDeployment: context.OutputPath /home/site/wwwroot
2:05:57 AM node-deploy-speed-test: Generating deployment script.
2:05:58 AM node-deploy-speed-test: Using cached version of deployment script (command: 'azure -y --no-dot-deployment -r "/tmp/zipdeploy/extracted" -o "/home/site/deployments/tools" --basic --sitePath "/tmp/zipdeploy/extracted"').
2:05:58 AM node-deploy-speed-test: Running deployment command...
2:05:58 AM node-deploy-speed-test: Command: "/home/site/deployments/tools/deploy.sh"
2:05:58 AM node-deploy-speed-test: Handling Basic Web Site deployment.
2:05:58 AM node-deploy-speed-test: Kudu sync from: '/tmp/zipdeploy/extracted' to: '/home/site/wwwroot'
2:05:59 AM node-deploy-speed-test: Copying file: '.eslintrc.json'
2:05:59 AM node-deploy-speed-test: Copying file: '.gitignore'
2:05:59 AM node-deploy-speed-test: Copying file: 'README.md'
2:05:59 AM node-deploy-speed-test: Copying file: 'next-env.d.ts'
2:05:59 AM node-deploy-speed-test: Copying file: 'next.config.js'
2:05:59 AM node-deploy-speed-test: Copying file: 'package-lock.json'
2:05:59 AM node-deploy-speed-test: Copying file: 'package.json'
2:05:59 AM node-deploy-speed-test: Copying file: 'tsconfig.json'
2:05:59 AM node-deploy-speed-test: Ignoring: .git
2:05:59 AM node-deploy-speed-test: Copying file: '.next/BUILD_ID'

=== 省略 ===

2:06:02 AM node-deploy-speed-test: Copying file: '.next/server/pages/_document.js'
2:06:02 AM node-deploy-speed-test: Omitting next output lines...
2:06:20 AM node-deploy-speed-test: Processed 654 files...
2:06:41 AM node-deploy-speed-test: Processed 1624 files...

=== 省略 ===

2:10:22 AM node-deploy-speed-test: Processed 12130 files...
2:10:39 AM node-deploy-speed-test: Finished successfully.
2:10:39 AM node-deploy-speed-test: Running post deployment command(s)...
2:10:40 AM node-deploy-speed-test: Triggering recycle (preview mode disabled).
2:10:40 AM node-deploy-speed-test: Deployment successful. deployer = ms-azuretools-vscode deploymentPath = ZipDeploy. Extract zip.
2:11:00 AM: Deployment to "node-deploy-speed-test" completed.
```

デプロイセンターでは Kudu 側のログのみが以下のように確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4914/54ecd2da-e75b-9432-b576-437ba3e76f78.png)

#### 2回目移行のデプロイ

アプリコードに修正を加えて再デプロイした場合は、変更箇所のみのコピーが行われることになります。そのため主に `.next` 配下のファイルコピーだけで済むことになり、処理時間は短くなります。
そのため新しく作成した Web Apps への初回リリース時に、ローカルビルドを用いる場合は一定の時間がかかることを見越して予めスケールアップしておくなども有効かと考えられます。

```
2:27:54 AM node-deploy-speed-test: Copying file: '.next/server/pages/api/hello.js'
2:27:54 AM node-deploy-speed-test: Omitting next output lines...
2:28:13 AM node-deploy-speed-test: Processed 74 files...
2:28:13 AM node-deploy-speed-test: Finished successfully.
```

[参考]
[KuduSync](https://github.com/projectkudu/KuduSync)

## リモートビルドを利用した場合

### 準備

続いてリモートビルドを試していきます。
VS Code でリモートビルドを有効にするために以下のように settings.jsonを変更します。

```json:./vscode/settings.json
{
  "appService.showBuildDuringDeployPrompt": true,
}
```

また、以前のローカルビルドデプロイのファイルとの混在をさけるため
Azure リソースは一旦停止し、Kudu から `/home/site/wwwroot` 配下のファイルをすべて削除して、再開させておきます。
また、`~/.npm/_cacache` も削除しておきます。(`npm cache clean --force`)

ローカル環境からは、ローカルビルド実行時に作成された `.next`、`out` ディレクトリを削除しておきます。

### zip デプロイ + リモートビルドの実行 

VS Code App Service 拡張機能から [Deploy to Web App...] を実行すると、「Would you like to update your workspace configuration ....」のダイアログが表示され、[Yes] をクリックすると、`.vscode/settings.json` が書き換えられ、以下のような、zip ファイルに含めない項目が設定されます。

```json:.vscode/settings.json
"appService.zipIgnorePattern": [
    "node_modules{,/**}",
    ".vscode{,/**}"
  ],
```

また、前述のように `.deployment` ファイルが作成され、これも zip に含まれます。

```.deployment
[config]
SCM_DO_BUILD_DURING_DEPLOYMENT=true
```

- アップロード用に作成される zip ファイルのサイズは 123KB となりました。
- zip ファイルは `/tmp/zipdeploy/extracted`に展開される。
- `/tmp/zipdeploy/extracted`は、さらに
- Oryx のプロセスが起動されます。
    ```
    oryx build /tmp/zipdeploy/extracted -o /home/site/wwwroot --platform nodejs --platform-version 18 -p virtualenv_name= --log-file /tmp/build-debug.log  -i /tmp/8db23a67f2bbcc7 -p compress_node_modules=tar-gz | tee /tmp/oryx-build.log` コマンドによって 
    ```
    - Oryx は、Node ビルド環境を`/tmp/oryx/platforms/`配下に用意します 13秒
    - `Using intermediate directory '/tmp/8db23a67f2bbcc7'` として一時的な作業ディレクトリで処理を行う
    - `npm install --unsafe-perm` を実行。install には 1m(1分)かかっています。ログのタイムスタンプの差分は 1分31秒
    - `npm build` を実行。10:47:32 ~ 10:49:26 のため 1分54秒 かかっています。
    - `Zipping existing node_modules folder...`に 101秒
    - `Copying files to destination directory '/home/site/wwwroot'...` に 5秒
    - `Done in 298 sec(s)` となり、全部で 330 秒かかりました
- - VS Code上での作業開始「10:45:20 Starting deployment...」からアプリコンテナの再起動開始「10:51:16 Triggering recycle」までおおよそ 4分56秒 かかりました

```
10:45:20 AM node-deploy-speed-test: Starting deployment...
10:45:23 AM node-deploy-speed-test: Creating zip package...
10:45:23 AM node-deploy-speed-test: Ignoring files from "appService.zipIgnorePattern"
"node_modules{,/**}"
".vscode{,/**}"
10:45:24 AM node-deploy-speed-test: Zip package size: 123 kB
10:45:26 AM node-deploy-speed-test: Fetching changes.
10:45:28 AM node-deploy-speed-test: Cleaning up temp folders from previous zip deployments and extracting pushed zip file /tmp/zipdeploy/1c5fedb0-771d-43ac-b26c-9301ac1b6a92.zip (0.12 MB) to /tmp/zipdeploy/extracted
10:45:32 AM node-deploy-speed-test: Updating submodules.
10:45:33 AM node-deploy-speed-test: Preparing deployment for commit id '5366c153-e'.
10:45:33 AM node-deploy-speed-test: PreDeployment: context.CleanOutputPath False
10:45:33 AM node-deploy-speed-test: PreDeployment: context.OutputPath /home/site/wwwroot
10:45:33 AM node-deploy-speed-test: Repository path is /tmp/zipdeploy/extracted
10:45:34 AM node-deploy-speed-test: Running oryx build...
10:45:34 AM node-deploy-speed-test: Command: oryx build /tmp/zipdeploy/extracted -o /home/site/wwwroot --platform nodejs --platform-version 18 -p virtualenv_name= --log-file /tmp/build-debug.log  -i /tmp/8db24f6f77139f8 -p compress_node_modules=tar-gz | tee /tmp/oryx-build.log
10:45:36 AM node-deploy-speed-test: Operation performed by Microsoft Oryx, https://github.com/Microsoft/Oryx
10:45:36 AM node-deploy-speed-test: You can report issues at https://github.com/Microsoft/Oryx/issues
10:45:36 AM node-deploy-speed-test: Oryx Version: 0.2.20220825.1, Commit: 24032445dbf7bf6ef068688f1b123a7144453b7f, ReleaseTagName: 20220825.1
10:45:36 AM node-deploy-speed-test: Build Operation ID: |PVrq71RIdc0=.16e5db31_
10:45:37 AM node-deploy-speed-test: Repository Commit : 5366c153-e170-4ef9-b21c-85dda323732c
10:45:37 AM node-deploy-speed-test: Detecting platforms...
10:45:41 AM node-deploy-speed-test: Detected following platforms:
10:45:42 AM node-deploy-speed-test:   nodejs: 18.14.0
10:45:42 AM node-deploy-speed-test: Version '18.14.0' of platform 'nodejs' is not installed. Generating script to install it...
10:45:42 AM node-deploy-speed-test: Detected the following frameworks: Next.js
10:45:43 AM node-deploy-speed-test: Using intermediate directory '/tmp/8db24f6f77139f8'.
10:45:43 AM node-deploy-speed-test: Copying files to the intermediate directory...
10:45:43 AM node-deploy-speed-test: Done in 0 sec(s).
10:45:44 AM node-deploy-speed-test: Source directory     : /tmp/8db24f6f77139f8
10:45:44 AM node-deploy-speed-test: Destination directory: /home/site/wwwroot
10:45:44 AM node-deploy-speed-test: Downloading and extracting 'nodejs' version '18.14.0' to '/tmp/oryx/platforms/nodejs/18.14.0'...
10:45:44 AM node-deploy-speed-test: Detected image debian flavor: bullseye.
10:45:48 AM node-deploy-speed-test: Downloaded in 5 sec(s).
10:45:48 AM node-deploy-speed-test: Verifying checksum...
10:45:48 AM node-deploy-speed-test: Extracting contents...
10:45:56 AM node-deploy-speed-test: performing sha512 checksum for: nodejs...
10:45:56 AM node-deploy-speed-test: Done in 13 sec(s).
10:45:57 AM node-deploy-speed-test: Removing existing manifest file
10:45:57 AM node-deploy-speed-test: Creating directory for command manifest file if it does not exist
10:45:57 AM node-deploy-speed-test: Creating a manifest file...
10:45:57 AM node-deploy-speed-test: Node Build Command Manifest file created.
10:45:57 AM node-deploy-speed-test: Using Node version:
10:45:57 AM node-deploy-speed-test: v18.14.0
10:45:58 AM node-deploy-speed-test: Using Npm version:
10:46:00 AM node-deploy-speed-test: 9.3.1
10:46:00 AM node-deploy-speed-test: Running 'npm install --unsafe-perm'...
10:47:31 AM node-deploy-speed-test: added 270 packages, and audited 271 packages in 1m
10:47:32 AM node-deploy-speed-test: 100 packages are looking for funding
10:47:32 AM node-deploy-speed-test:   run `npm fund` for details
10:47:32 AM node-deploy-speed-test: found 0 vulnerabilities
10:47:32 AM node-deploy-speed-test: Running 'npm run build'...
10:47:35 AM node-deploy-speed-test: > next-app-sample-ts@0.1.0 build
10:47:35 AM node-deploy-speed-test: > next build
10:47:38 AM node-deploy-speed-test: Attention: Next.js now collects completely anonymous telemetry regarding usage.
10:47:38 AM node-deploy-speed-test: This information is used to shape Next.js' roadmap and prioritize features.
10:47:39 AM node-deploy-speed-test: You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
10:47:39 AM node-deploy-speed-test: https://nextjs.org/telemetry
10:47:39 AM node-deploy-speed-test: info  - Linting and checking validity of types...
10:48:13 AM node-deploy-speed-test: info  - Creating an optimized production build...
10:48:41 AM node-deploy-speed-test: info  - Compiled successfully
10:48:42 AM node-deploy-speed-test: info  - Collecting page data...
10:49:25 AM node-deploy-speed-test: info  - Generating static pages (0/3)
10:49:26 AM node-deploy-speed-test: info  - Generating static pages (3/3)
10:49:26 AM node-deploy-speed-test: info  - Finalizing page optimization...
10:49:26 AM node-deploy-speed-test: Route (pages)                              Size     First Load JS
10:49:26 AM node-deploy-speed-test: ┌ ○ / (425 ms)                             4.6 kB         77.9 kB
10:49:26 AM node-deploy-speed-test: ├   └ css/50901216b76e581e.css             1.87 kB
10:49:26 AM node-deploy-speed-test: ├   /_app                                  0 B            73.3 kB
10:49:26 AM node-deploy-speed-test: ├ ○ /404                                   182 B          73.4 kB
10:49:26 AM node-deploy-speed-test: └ λ /api/hello                             0 B            73.3 kB
10:49:26 AM node-deploy-speed-test: + First Load JS shared by all              74 kB
10:49:26 AM node-deploy-speed-test:   ├ chunks/framework-2c79e2a64abdb08b.js   45.2 kB
10:49:26 AM node-deploy-speed-test:   ├ chunks/main-0ecb9ccfcb6c9b24.js        27 kB
10:49:26 AM node-deploy-speed-test:   ├ chunks/pages/_app-ae907860a06fe57a.js  296 B
10:49:26 AM node-deploy-speed-test:   ├ chunks/webpack-8fa1640cc84ba8fe.js     750 B
10:49:26 AM node-deploy-speed-test:   └ css/876d048b5dab7c28.css               706 B
10:49:26 AM node-deploy-speed-test: λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
10:49:26 AM node-deploy-speed-test: ○  (Static)  automatically rendered as static HTML (uses no initial props)
10:49:27 AM node-deploy-speed-test: Zipping existing node_modules folder...
10:51:08 AM node-deploy-speed-test: Done in 101 sec(s).
10:51:08 AM node-deploy-speed-test: Preparing output...
10:51:08 AM node-deploy-speed-test: Copying files to destination directory '/home/site/wwwroot'...
10:51:13 AM node-deploy-speed-test: Done in 5 sec(s).
10:51:13 AM node-deploy-speed-test: Removing existing manifest file
10:51:13 AM node-deploy-speed-test: Creating a manifest file...
10:51:13 AM node-deploy-speed-test: Manifest file created.
10:51:13 AM node-deploy-speed-test: Copying .ostype to manifest output directory.
10:51:13 AM node-deploy-speed-test: Done in 330 sec(s).
10:51:15 AM node-deploy-speed-test: Running post deployment command(s)...
10:51:15 AM node-deploy-speed-test: Generating summary of Oryx build
10:51:15 AM node-deploy-speed-test: Parsing the build logs
10:51:15 AM node-deploy-speed-test: Found 0 issue(s)
10:51:16 AM node-deploy-speed-test: Build Summary :
10:51:16 AM node-deploy-speed-test: ===============
10:51:16 AM node-deploy-speed-test: Errors (0)
10:51:16 AM node-deploy-speed-test: Warnings (0)
10:51:16 AM node-deploy-speed-test: Triggering recycle (preview mode disabled).
10:51:17 AM node-deploy-speed-test: Deployment successful. deployer = ms-azuretools-vscode deploymentPath = ZipDeploy. Extract zip.
10:51:36 AM: Deployment to "node-deploy-speed-test" completed.
```

#### 2回目移行のデプロイ 

なお、アプリコードに修正を加えて再デプロイした場合は、Kudu 上に Node ビルド実行環境としてダウンロード済みのもの(`/tmp/oryx/platforms/nodejs/18.14.0`)が残っている場合は、以下の部分が省略されます。

```
10:45:44 AM node-deploy-speed-test: Downloading and extracting 'nodejs' version '18.14.0' to '/tmp/oryx/platforms/nodejs/18.14.0'...
10:45:44 AM node-deploy-speed-test: Detected image debian flavor: bullseye.
10:45:48 AM node-deploy-speed-test: Downloaded in 5 sec(s).
10:45:48 AM node-deploy-speed-test: Verifying checksum...
10:45:48 AM node-deploy-speed-test: Extracting contents...
10:45:56 AM node-deploy-speed-test: performing sha512 checksum for: nodejs...
10:45:56 AM node-deploy-speed-test: Done in 13 sec(s).
```

`Using intermediate directory '/tmp/8db24f6f77139f8'.` などの作業ディレクトリはデプロイ毎に異なるものが用意され、その中で行われる `npm install` は１回目より若干早くなりますが、1分ほどかかりました。

```
10:56:43 AM node-deploy-speed-test: Running 'npm install --unsafe-perm'...
10:57:46 AM node-deploy-speed-test: added 270 packages, and audited 271 packages in 1m
10:57:46 AM node-deploy-speed-test: 100 packages are looking for funding
10:57:46 AM node-deploy-speed-test:   run `npm fund` for details
10:57:46 AM node-deploy-speed-test: found 0 vulnerabilities
```

最終的に `11:01:31 AM node-deploy-speed-test: Done in 291 sec(s).` と １回目からは Node.js ビルド環境ダウンロード部分以外は若干早くなった程度でした。


### リモートビルド利用時の /home/site/wwwroot 配下はどうなっているか


ローカルビルドを利用した際、主に時間がかかる箇所は、ファイルのアップロード完了までと、`/tmp/zipdeploy/extracted` から `/home/site/wwwroot` へのファイルコピーでした。

一方で、リモートビルドを利用した場合は、ファイルのアップロード完了は短時間ですが、`npm install` と `npm build`にかかる時間が追加されます。また、`npm build` 完了後には、作業ディレクトリ(`/tmp/8db23a9b21b741b` など)から`/home/site/wwwroot` に成果物をコピーしています。

このとき、以下のログにあるように、`node_modules` は zip 化してから `/home/site/wwwroot` に移動されています。

```
7:04:09 PM node-deploy-speed-test: Zipping existing node_modules folder...
7:05:55 PM node-deploy-speed-test: Done in 106 sec(s).
7:05:55 PM node-deploy-speed-test: Preparing output...
7:05:55 PM node-deploy-speed-test: Copying files to destination directory '/home/site/wwwroot'...
7:05:59 PM node-deploy-speed-test: Done in 4 sec(s).
```

`/home/site/wwwroot` はどうなっているか Kudu から 確認してみると、`node_modules` は空っぽの `/node_modules` へのシンボリックリンクとなり、そこそこのサイズの `node_modules.tar.gz` が存在しています。

```bash
kudu_ssh_user@92c555ab9ef0:/$ ls -la /home/site/wwwroot/
total 95501
drwxrwxrwx 2 nobody nogroup        0 Mar 13 10:06 .
drwxrwxrwx 2 nobody nogroup     4096 Mar 12 11:56 ..
-rwxrwxrwx 1 nobody nogroup       44 Mar 13 09:43 .deployment
-rwxrwxrwx 1 nobody nogroup       40 Mar 13 09:43 .eslintrc.json
-rwxrwxrwx 1 nobody nogroup      385 Mar 13 09:43 .gitignore
drwxrwxrwx 2 nobody nogroup        0 Mar 13 10:05 .next
-rwxrwxrwx 1 nobody nogroup       16 Mar 13 10:05 .ostype
-rwxrwxrwx 1 nobody nogroup     1751 Mar 13 09:43 README.md
-rwxrwxrwx 1 nobody nogroup      201 Mar 13 09:43 next-env.d.ts
-rwxrwxrwx 1 nobody nogroup      118 Mar 13 09:43 next.config.js
lrwxrwxrwx 1 nobody nogroup       13 Mar 13 10:06 node_modules -> /node_modules
-rwxrwxrwx 1 nobody nogroup 97646428 Mar 13 10:05 node_modules.tar.gz
-rwxrwxrwx 1 nobody nogroup      332 Mar 13 10:05 oryx-manifest.toml
-rwxrwxrwx 1 nobody nogroup   123041 Mar 13 09:43 package-lock.json
-rwxrwxrwx 1 nobody nogroup      503 Mar 13 09:43 package.json
drwxrwxrwx 2 nobody nogroup        0 Mar 13 10:05 pages
drwxrwxrwx 2 nobody nogroup        0 Mar 13 09:43 public
drwxrwxrwx 2 nobody nogroup        0 Mar 13 09:43 styles
-rwxrwxrwx 1 nobody nogroup      552 Mar 13 09:43 tsconfig.json
kudu_ssh_user@92c555ab9ef0:/$ ls /node_modules/
kudu_ssh_user@92c555ab9ef0:/$ ls -la /node_modules/
total 8
drwxrwxrwx   2 root root 4096 Aug 26  2022 .
drwxr-xr-x 105 root root 4096 Mar 13 09:36 ..
kudu_ssh_user@92c555ab9ef0:/$ 
```

### アプリケーションコンテナはどうやって動作しているのか

アプリケーション側の処理を確認してみます。

アプリケーションコンテナ上で起動スクリプトが作られる仕組みの詳細については、下記記事を参照してください。

https://qiita.com/georgeOsdDev@github/items/abdadd35248b98c4ef88#blessed-image-%E3%81%AB%E3%81%8A%E3%81%84%E3%81%A6%E3%81%A9%E3%81%AE%E3%82%88%E3%81%86%E3%81%AB%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%8C%E5%AE%9F%E8%A1%8C%E3%81%95%E3%82%8C%E3%82%8B%E3%81%8B

アプリケーションコンテナ上でも Oryx が存在し、`/opt/startup/init_container.sh` 内で実行される `oryx create-script` コマンドによって生成された `/opt/startup/startup.sh` は以下のようになっていました。

```bash:/opt/startup/startup.sh 
root@328e013a92bd:/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

echo Found tar.gz based node_modules.
extractionCommand="tar -xzf node_modules.tar.gz -C /node_modules"
echo "Removing existing modules directory from root..."
rm -fr /node_modules
mkdir -p /node_modules
echo Extracting modules...
$extractionCommand
export NODE_PATH="/node_modules":$NODE_PATH
export PATH=/node_modules/.bin:$PATH
if [ -d node_modules ]; then
    mv -f node_modules _del_node_modules || true
fi

if [ -d /node_modules ]; then
    ln -sfn /node_modules ./node_modules 
fi

echo "Done."
npm start
```

デフォルトの `startup.sh` には見られなかった、`node_modules.tar.gz` の展開処理が加えられています。
ここでは、`/home/site/wwwroot/node_modules.tar.gz` を、アプリケーションコンテナの `/node_modules` に展開し、パスに追加しています。
つまり、共有ディレクトリである、`/home` 配下の `node_modules` ではなく、コンテナ毎の `/node_modules` を利用してアプリケーションが動作しているという状況になります。

この動作については以下のドキュメントに記述があります。

https://github.com/microsoft/Oryx/blob/main/doc/hosts/appservice.md#compressing-nodejs-modules

なお、展開にかかった時間は `$HOME/LogFiles/YYYY_MM_DD_<MACHINENAME>_default_docker.log` から確認できます。今回の例であれば、19秒ほどかかっています。つまりアプリケーション起動まで19秒のオーバーヘッドとなっています。

```log
2023-03-15T02:01:58.479551966Z Extracting modules...
2023-03-15T02:02:17.836808394Z Done.
```

# まとめ

「ローカルビルド + zipデプロイ」と「zipデプロイ + リモートビルド」を実行した際の挙動について見てきました。
どちらが適切かは、アプリケーションのファイル数や、サイズ、利用する node_modules のサイズ、リリース頻度や差分の大きさになどプロジェクト構成に大きく依存するものと言えます。
どちらを利用するかは、スロットを用意して検証すると良いでしょう。

今回の記事で大体の仕組みがわかってきたので、次回の記事ではそれぞれ高速化する工夫について書いていきます。