Azure DevOpsのパイプラインでAzure FunctionsにTypescriptのNodeランタイムのAPIをデプロイしたメモ

## 概要
[前回](https://qiita.com/hibohiboo/items/6a49ed68e6560594278b)はbicepでTypescript の Functionsのリソースを構築するところまで行った。今回はAzure DevOps のパイプラインを通してデプロイしてみたいと思う。
手順は[Azure Pipelines を使用した継続的デリバリー](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-how-to-azure-devops?tabs=javascript&pivots=v2)を参考にする。

<details><summary>デプロイしたファイル</summary><div>

```
- functions-build-pipeline.yml
- batches
  - packages.json
  - tsconfig.json
  - host.json
  - .gitignore
  - .funcignore
  - src
    - functions
      - httpTrigger1.ts
```

```batches/package.json
{
  "name": "api",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "clean": "rimraf dist",
    "prestart": "npm run clean && npm run build",
    "start": "func start",
    "predev": "npm run clean && npm run build",
    "dev": "func start --port 7071",
    "test": "echo \"No tests yet...\"",
    "ncu": "ncu"
  },
  "dependencies": {
    "@azure/functions": "^4.4.0"
  },
  "devDependencies": {
    "@types/node": "^20.x",
    "typescript": "^5.4.5"
  },
  "main": "dist/src/functions/*.js"
}
```

```batches/tsconfig.json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es6",
    "outDir": "dist",
    "rootDir": ".",
    "sourceMap": true,
    "strict": false
  }
}
```

```batches/host.json
{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}
```


```batches/src/functions/httpTrigger.ts
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';

export async function httpTrigger1(request: HttpRequest,context: InvocationContext,): Promise<HttpResponseInit> {
  context.log(`Http function processed request for url "${request.url}"`);
  const name = request.query.get('name') || (await request.text()) || 'world!!!';
  return { jsonBody: name };
}

app.http('httpTrigger1', {
  methods: ['GET', 'POST'],
  authLevel: 'anonymous',
  handler: httpTrigger1,
});

```

```batches/.funcignore
*.js.map
*.ts
.git*
.vscode
__azurite_db*__.json
__blobstorage__
__queuestorage__
local.settings.json
test
tsconfig.json
```

```batches/.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.test

# parcel-bundler cache (https://parceljs.org/)
.cache

# next.js build output
.next

# nuxt.js build output
.nuxt

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TypeScript output
dist
out

# Azure Functions artifacts
bin
obj
appsettings.json
local.settings.json

# Azurite artifacts
__blobstorage__
__queuestorage__
__azurite_db*__.json
```


</div></details>

## ビルドパイプライン
[ArchiveFiles@2](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/tasks/reference/archive-files-v2?view=azure-pipelines) [PublishBuildArtifacts@1](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/tasks/reference/publish-build-artifacts-v1?view=azure-pipelines)

`batches`フォルダを切ってfunctionsのファイルを入れていたので、`workingDirectory`を追加した。

```functions-build-pipeline.yml
pool:
  vmImage: ubuntu-latest
steps:
- bash: |
    if [ -f extensions.csproj ]
    then
        dotnet build extensions.csproj --output ./bin
    fi
    npm install 
    npm run build --if-present
    npm prune --production
  workingDirectory: $(System.DefaultWorkingDirectory)/batches
- task: ArchiveFiles@2
  displayName: "Archive files"
  inputs:
    rootFolderOrFile: "$(System.DefaultWorkingDirectory)/batches"
    includeRootFolder: false
    archiveFile: "$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip"
- task: PublishBuildArtifacts@1
  inputs:
    PathtoPublish: '$(System.DefaultWorkingDirectory)/build$(Build.BuildId).zip'
    artifactName: 'drop'
```

ビルドパイプラインを一度実行する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/1c0338f4-e307-694b-ec64-3ddca6247d42.png)

## リリースパイプライン
[AzureFunctionApp@2](https://learn.microsoft.com/ja-jp/azure/devops/pipelines/tasks/reference/azure-function-app-v2?view=azure-pipelines)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/ef9efed1-2d93-f45b-0cd4-76689f103d17.png)

ビルドパイプラインで作成したArtifactを選択

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/6b941bf8-611e-5a18-5a93-79de9b4e4f73.png)

タスクを追加していく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/92c630d1-bfb1-7c51-3ee6-014b59161acc.png)

サブスクリプションは認証を求められるので許可しておく。
許可出来たら、AzureのFunctionsのAppServiceを選択。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/54a39640-1f20-2eb7-d20a-3b65b029639e.png)

実行環境はlinux(ubuntu-latest)にしておく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/5beab89e-e508-737a-1c34-7d1839184244.png)


デプロイタスクのバージョンは2にしておく。
`Package or folder`を `*.zip`まで指定。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/78bfe8fc-9949-0c7e-7e0e-77f06aaaea3a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/1731a950-5d75-17d8-c320-9a3cba0ca6a2.png)

## 動作確認

`https://hoge.azurewebsites.net/api/httptrigger1` にアクセスして、値が取得できることを確認した。

## 設定に失敗したときのログ

// ここまでは問題なし

デプロイタスクのバージョンは2にしておく。※ここで設定項目をミスった
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/159f4f98-166a-ee60-b4d5-5f7b105bfbe7.png)

Package or folder で指定するzipがちゃんとできているか一応確認。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/1d714cf2-2fe6-1945-930f-705953d14b04.png)

リリースしてみる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/321b1c87-a71a-b1b5-7557-141f1fb8ad06.png)

### うごかない

`WEBSITE_RUN_FROM_PACKAGE`の値がパイプラインによって書き換えられている。
VSCodeからデプロイしたときと値が異なるので、少し追ってみる。

デプロイ方法|`WEBSITE_RUN_FROM_PACKAGE`の値
--|--
VSCode|`https://hoge.blob.core.windows.net/function-releases/yyyyMMddHHmmss-略.zip?sv=2022...省略`
pipeline|`https://hoge.blob.core.windows.net/azure-pipelines-deploy/package_略.zip?st=2024...省略`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/ba87c3e3-4d55-70b9-d042-b2d0021886d2.png)

VSCodeからのデプロイは、`functions-release`コンテナに。パイプラインからのデプロイは`azure-pipeline-deploy`コンテナに入っている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/968c19ab-66bb-e90c-87c3-928dfbb74567.png)

成功しているとき(VSCode)のzipの中身。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/1f66a230-c41f-f298-a90d-e87bc21417f2.png)

失敗したとき(Pipeline)のzipの中身
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/1c13c32e-56ca-c2a8-8cd8-d3eae4281427.png)

どうやら、artifactの中身をさらにzipで包んでお出ししてしまっていることが原因のもよう。
jobの指定の仕方を修正。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99876/4ebcfdc1-aa57-08ec-f1ed-9dd10c308350.png)

無事動いたし、[手順の例](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-how-to-azure-devops?tabs=javascript&pivots=v2#deploy-your-app-1)にもちゃんと*.zipって書いてあった。

## 参考
[Azure Pipelines を使用した継続的デリバリー](https://learn.microsoft.com/ja-jp/azure/azure-functions/functions-how-to-azure-devops?tabs=javascript&pivots=v2)
[DevOpsでAzure FunctionsにAPIをデプロイしてみた（TypeScript編）](https://note.com/tandago/n/n0cbe557c2340)