App Service on Linux Node runtime における リモートビルドのデプロイ時間を短縮する工夫

# はじめに

この記事は、以下の記事の続編です。

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

各デプロイ方法を用いた場合に、少しでも時間を短縮するTipsを記載していきます。


# リモートビルドを利用する場合のTips

リモートビルドを利用する場合、主に時間がかかる箇所は

- `Running 'npm install --unsafe-perm'...`
- `Running 'npm run build'...`
- `Zipping existing node_modules folder...`

の 3 箇所です。

`npm` をやめて、`yarn` や `pnpm` を使うことで、高速化が期待できるかもしれませんが、今回の記事では `npm` を使う前提で考えます。

まずは `npm install` の詳細を見ていきます。npm の実行ログは、Kudu から `$home/.npm/_logs/` ディレクトリで確認が可能です。
2回目は [npm のキャッシュ](https://docs.npmjs.com/cli/v9/commands/npm-cache)が効いていることわかります。がそれでも一定の時間がかかってしまいます。
残る改善の余地としては、audit や funds を無くすあたりでしょうか。

`npm run build` の高速化については、プロジェクトの構成に大きく依存したり、利用するフレームワーク(今回はnext)のノウハウによるものと考えられるので、今回は割愛します。

`Zipping existing node_modules folder...` については、変更がなければ省略するといったことができそうです。

## 作戦


次のような方針にしてみました。

通常 `npm build` 完了後に、Oryx が `node_modules` を `/home/site/wwwroot/node_modules.tar.gz` としてまとめます。これを再利用してみます。

このファイルを、`package-lock.json` のハッシュ値、Nodeバージョン、NPMバージョンをキーに`/home/site/deploy_cache/node_modules-${md5 hash_of_package_lock.json}-node${NODE_VERSION}-npm${NPM_VERSION}.tar.gz` としてキャッシュし、
ビルド時にキャッシュが存在する場合は、キャッシュを展開してnpm install を省略します。

```mermaid
graph TB
  Start([Start])
  Start-->CheckLockVersion{if package-lock.json changed}
  CheckLockVersion-->|True| DefaultInstall
  CheckLockVersion-->|False| CheckCachedModule
  CheckCachedModule{if cached module found}
  CheckCachedModule-->|True| CopyCache
  CheckCachedModule-->|False| DefaultInstall

  DefaultInstall[npm install]-->DefaultBuild
  DefaultBuild[npm build]
  DefaultBuild-->OryxCopyNodeModules[Oryx make node_modules.tar.gz]
  OryxCopyNodeModules-->CreateCache
  CreateCache[cp ./node_modules.tar.gz $cached_node_modules_archive]-->End

  CopyCache[tar -xzf $cached_node_modules_archive -C ./node_modules]-->CacheBuild
  CacheBuild[npm build]
  CacheBuild-->RemoveNodeModule[rm -rf ./node_modules]-->End

  End([End])
```

`PRE_BUILD_COMMAND`、`CUSTOM_BUILD_COMMAND`、`POST_BUILD_COMMAND` を利用して、上記のフローが実現できるようにしてみます。
詳細は以下の gist に書きました。

https://gist.github.com/georgeOsdDev/a1c9fa0925b43f70c28e384d5e7ac806

### 結果

`package-lock.json`、Nodeバージョン、NPMバージョンに変更がない場合 `npm install` と oryx による `Zipping existing node_modules folder...` が省略されました。

その代わりに追加された prebuildにおいて、tar.gz を展開する処理で30秒弱増えています。
```
3:34:51 PM node-deploy-speed-test: prebuild started
3:34:51 PM node-deploy-speed-test: current working dir: /tmp/8db251f5c423ad5
3:34:54 PM node-deploy-speed-test: package-lock.json hash: 2ada1c245c4a3cb78f887bfdcea8aa6f
3:34:54 PM node-deploy-speed-test: /home/site/deploy_cache/node_modules-2ada1c245c4a3cb78f887bfdcea8aa6f-nodev18.14.0-npm9.3.1.tar.gz found, extaracting to working dir
3:35:18 PM node-deploy-speed-test: prebuild end
3:35:18 PM node-deploy-speed-test: Finished executing pre-build command.
```

全体としては、`Done in 191 sec(s).` と前回の記事の 2 回目に比べて、100 秒早くなりました。


```
3:34:32 PM node-deploy-speed-test: Starting deployment...
3:34:33 PM node-deploy-speed-test: Creating zip package...
3:34:34 PM node-deploy-speed-test: Ignoring files from "appService.zipIgnorePattern"
"node_modules{,/**}"
".vscode{,/**}"
3:34:35 PM node-deploy-speed-test: Zip package size: 125 kB
3:34:36 PM node-deploy-speed-test: Fetching changes.
3:34:37 PM node-deploy-speed-test: Cleaning up temp folders from previous zip deployments and extracting pushed zip file /tmp/zipdeploy/4ab23023-a899-42a4-a105-8c996fdf0909.zip (0.12 MB) to /tmp/zipdeploy/extracted
3:34:41 PM node-deploy-speed-test: Updating submodules.
3:34:42 PM node-deploy-speed-test: Preparing deployment for commit id 'c01c7988-2'.
3:34:42 PM node-deploy-speed-test: PreDeployment: context.CleanOutputPath False
3:34:42 PM node-deploy-speed-test: PreDeployment: context.OutputPath /home/site/wwwroot
3:34:43 PM node-deploy-speed-test: Repository path is /tmp/zipdeploy/extracted
3:34:43 PM node-deploy-speed-test: Running oryx build...
3:34:43 PM 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/8db251f5c423ad5 -p compress_node_modules=tar-gz | tee /tmp/oryx-build.log
3:34:45 PM node-deploy-speed-test: Operation performed by Microsoft Oryx, https://github.com/Microsoft/Oryx
3:34:45 PM node-deploy-speed-test: You can report issues at https://github.com/Microsoft/Oryx/issues
3:34:46 PM node-deploy-speed-test: Oryx Version: 0.2.20220825.1, Commit: 24032445dbf7bf6ef068688f1b123a7144453b7f, ReleaseTagName: 20220825.1
3:34:46 PM node-deploy-speed-test: Build Operation ID: |QC7zwKCaWyg=.d8b8eb3f_
3:34:46 PM node-deploy-speed-test: Repository Commit : c01c7988-239e-431c-ae60-bd3a0c7d8f13
3:34:46 PM node-deploy-speed-test: Detecting platforms...
3:34:49 PM node-deploy-speed-test: Detected following platforms:
3:34:49 PM node-deploy-speed-test:   nodejs: 18.14.0
3:34:49 PM node-deploy-speed-test: Detected the following frameworks: Next.js
3:34:50 PM node-deploy-speed-test: Using intermediate directory '/tmp/8db251f5c423ad5'.
3:34:50 PM node-deploy-speed-test: Copying files to the intermediate directory...
3:34:50 PM node-deploy-speed-test: Done in 0 sec(s).
3:34:50 PM node-deploy-speed-test: Source directory     : /tmp/8db251f5c423ad5
3:34:50 PM node-deploy-speed-test: Destination directory: /home/site/wwwroot
3:34:51 PM node-deploy-speed-test: Executing pre-build command...
3:34:51 PM node-deploy-speed-test: prebuild started
3:34:51 PM node-deploy-speed-test: current working dir: /tmp/8db251f5c423ad5
3:34:54 PM node-deploy-speed-test: package-lock.json hash: 2ada1c245c4a3cb78f887bfdcea8aa6f
3:34:54 PM node-deploy-speed-test: /home/site/deploy_cache/node_modules-2ada1c245c4a3cb78f887bfdcea8aa6f-nodev18.14.0-npm9.3.1.tar.gz found, extaracting to working dir
3:35:18 PM node-deploy-speed-test: prebuild end
3:35:18 PM node-deploy-speed-test: Finished executing pre-build command.
3:35:18 PM node-deploy-speed-test: Removing existing manifest file
3:35:18 PM node-deploy-speed-test: Creating directory for command manifest file if it does not exist
3:35:18 PM node-deploy-speed-test: Creating a manifest file...
3:35:18 PM node-deploy-speed-test: Node Build Command Manifest file created.
3:35:18 PM node-deploy-speed-test: Using Node version:
3:35:19 PM node-deploy-speed-test: v18.14.0
3:35:19 PM node-deploy-speed-test: Using Npm version:
3:35:24 PM node-deploy-speed-test: 9.3.1
3:35:24 PM node-deploy-speed-test: Running 'sh azureappservice_custombuild.sh'...
3:35:24 PM node-deploy-speed-test: custombuild started
3:35:24 PM node-deploy-speed-test: current working dir: /tmp/8db251f5c423ad5
3:35:27 PM node-deploy-speed-test: Using cached node_modules, skipping npm install
3:35:30 PM node-deploy-speed-test: > next-app-sample-ts@0.1.0 build
3:35:30 PM node-deploy-speed-test: > next build
3:35:34 PM node-deploy-speed-test: Attention: Next.js now collects completely anonymous telemetry regarding usage.
3:35:35 PM node-deploy-speed-test: This information is used to shape Next.js' roadmap and prioritize features.
3:35:35 PM 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:
3:35:35 PM node-deploy-speed-test: https://nextjs.org/telemetry
3:35:35 PM node-deploy-speed-test: info  - Linting and checking validity of types...
3:36:33 PM node-deploy-speed-test: info  - Creating an optimized production build...
3:37:01 PM node-deploy-speed-test: info  - Compiled successfully
3:37:01 PM node-deploy-speed-test: info  - Collecting page data...
3:37:46 PM node-deploy-speed-test: info  - Generating static pages (0/3)
3:37:47 PM node-deploy-speed-test: info  - Generating static pages (3/3)
3:37:47 PM node-deploy-speed-test: info  - Finalizing page optimization...
3:37:47 PM node-deploy-speed-test: Route (pages)                              Size     First Load JS
3:37:47 PM node-deploy-speed-test: ┌ ○ / (391 ms)                             4.6 kB         77.9 kB
3:37:47 PM node-deploy-speed-test: ├   └ css/50901216b76e581e.css             1.87 kB
3:37:47 PM node-deploy-speed-test: ├   /_app                                  0 B            73.3 kB
3:37:47 PM node-deploy-speed-test: ├ ○ /404                                   182 B          73.4 kB
3:37:47 PM node-deploy-speed-test: └ λ /api/hello                             0 B            73.3 kB
3:37:47 PM node-deploy-speed-test: + First Load JS shared by all              74 kB
3:37:47 PM node-deploy-speed-test:   ├ chunks/framework-2c79e2a64abdb08b.js   45.2 kB
3:37:47 PM node-deploy-speed-test:   ├ chunks/main-0ecb9ccfcb6c9b24.js        27 kB
3:37:47 PM node-deploy-speed-test:   ├ chunks/pages/_app-ae907860a06fe57a.js  296 B
3:37:47 PM node-deploy-speed-test:   ├ chunks/webpack-8fa1640cc84ba8fe.js     750 B
3:37:47 PM node-deploy-speed-test:   └ css/876d048b5dab7c28.css               706 B
3:37:47 PM node-deploy-speed-test: λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
3:37:47 PM node-deploy-speed-test: ○  (Static)  automatically rendered as static HTML (uses no initial props)
3:37:48 PM node-deploy-speed-test: Remove node_modules
3:37:53 PM node-deploy-speed-test: package-lock.json hash: 2ada1c245c4a3cb78f887bfdcea8aa6f
3:37:56 PM node-deploy-speed-test: custombuild end
3:37:56 PM node-deploy-speed-test: Executing post-build command...
3:37:56 PM node-deploy-speed-test: postbuild started
3:37:56 PM node-deploy-speed-test: current working dir: /tmp/8db251f5c423ad5
3:37:58 PM node-deploy-speed-test: package-lock.json hash: 2ada1c245c4a3cb78f887bfdcea8aa6f
3:37:59 PM node-deploy-speed-test: /home/site/deploy_cache/node_modules-2ada1c245c4a3cb78f887bfdcea8aa6f-nodev18.14.0-npm9.3.1.tar.gz already exists
3:37:59 PM node-deploy-speed-test: postbuild end
3:37:59 PM node-deploy-speed-test: Finished executing post-build command.
3:38:00 PM node-deploy-speed-test: Preparing output...
3:38:00 PM node-deploy-speed-test: Copying files to destination directory '/home/site/wwwroot'...
3:38:01 PM node-deploy-speed-test: Done in 3 sec(s).
3:38:01 PM node-deploy-speed-test: Removing existing manifest file
3:38:01 PM node-deploy-speed-test: Creating a manifest file...
3:38:01 PM node-deploy-speed-test: Manifest file created.
3:38:01 PM node-deploy-speed-test: Copying .ostype to manifest output directory.
3:38:01 PM node-deploy-speed-test: Done in 191 sec(s).
3:38:03 PM node-deploy-speed-test: Running post deployment command(s)...
3:38:03 PM node-deploy-speed-test: Generating summary of Oryx build
3:38:04 PM node-deploy-speed-test: Parsing the build logs
3:38:04 PM node-deploy-speed-test: Found 0 issue(s)
3:38:04 PM node-deploy-speed-test: Build Summary :
3:38:04 PM node-deploy-speed-test: ===============
3:38:05 PM node-deploy-speed-test: Errors (0)
3:38:05 PM node-deploy-speed-test: Warnings (0)
3:38:05 PM node-deploy-speed-test: Triggering recycle (preview mode disabled).
3:38:06 PM node-deploy-speed-test: Deployment successful. deployer = ms-azuretools-vscode deploymentPath = ZipDeploy. Extract zip.
3:38:14 PM: Deployment to "node-deploy-speed-test" completed.
```


# ローカルビルドを利用する場合のTips

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

# GitHub Actions を利用する場合のTips

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

★別記事で★

# おまけ 1 RunFromPackageの利用

★別記事で★

# おまけ 2

餅は餅屋ということで。。。Vercel強い
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4914/ef58b92e-9d89-9c08-c632-c22f69127cf0.png)