FastAPIをAzure App Serviceで実行する

# ガッツリ、はまりました。
FastAPIでつくったアプリをAzure App Serviceで実行しようとして嵌りましたので、解決までのメモを残します。
嵌った内容・状況は以下です。

- コンテナは使いたくない（軽くGitからデプロイしたい)
- デプロイセンターからの継続的ビルドは設定したくない (フォルダ構成が特殊)
    - (デプロイセンターで継続的ビルドを設定した場合はなぜか動く)
- **uvicorn not found となってuvicorn/gunicornが起動できない**

# 必要だったもの（順番も）
Azure App Servcie のデプロイセンターから継続的ビルドを設定しない場合です。順番も結構重要です。特に2.と3.はデプロイするまでに設定しておく必要があります。

### 1. FastAPIのアプリを作成する
main.pyなりFastAPIのアプリをつくります。例えばこんなのですね。
```python:main.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}
```

最低限 fastapi, uvicorn, gunicorn はインストールする必要があります。あとは、完成した pip freeze > requirements.txt で出力しておきます。

起動用のスタートアップスクリプトを準備します。
```sh:startup.sh
gunicorn --config gunicorn.py main:app
```

Gunicorn用の設定ファイルも外に出しておきます。例えばこんな感じです。
```python:gunicorn.py
import multiprocessing
import os

name = "Gunicorn config for FastAPI"

bind = "0.0.0.0:8000"

worker_class = "uvicorn.workers.UvicornWorker"
workers = multiprocessing.cpu_count () * 2 + 1
```

上記だとファイル構成はこんな感じです。
```
$ ls
gunicorn.py  main.py  requirements.txt  startup.sh
```

### 2. Azure App Service を作成する
Pythonのバージョンを指定して普通に作ります。特に何かを設定する必要はありません。
もしデプロイセンターから継続的ビルドを指定した場合は以下のステップのうち 3.スタートアップコマンドのみでOKです。

### 3. スタートアップコマンドを入れる
App Service の「構成」パネルからスタートアップコマンドを指定します。
ここまでの流れの場合は、startup.shを指定します。（コマンド直でも大丈夫です）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/92ebc51f-4f66-f811-68d5-e31d6d0cf69a.png)

### 3. 環境変数として SCM_DO_BUILD_DURING_DEPLOYMENT を 1 にする
<font color="red">※ここが嵌りポイントでした</font>
デプロイセンターから継続的ビルドを設定するとこれが設定されます。これを指定しないとrequirement.txtの内容をインストールできないため、これを指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/977156/be6f12ae-1a96-525a-8e81-a330ce8c17d7.png)

### 4. GitHub/Azure DevOps からデプロイする

3.までが完了してからデプロイをします。デプロイセンターから継続的ビルドをつくった場合のymlだとPython仮想環境のフォルダもデプロイされてしまうため、そこを省いた形で以下のようなものを作ります。(GitHub Actionsの場合）
```
run: zip release.zip ./* -r -x \*venv\*
```
-xでvenvフォルダを排除しています。

```yaml:deploy_to_azure.yml
name: Build and deploy Python app to Azure Web App - shyamagu-fastapi

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python version
        uses: actions/setup-python@v1
        with:
          python-version: '3.11'

      - name: Create and start virtual environment
        run: |
          python -m venv venv
          source venv/bin/activate

      - name: Install dependencies
        run: pip install -r requirements.txt

      # Optional: Add step to run tests here (PyTest, Django test suites, etc.)yyy
      - name: Zip artifact for deployment
        run: zip release.zip ./* -r -x \*venv\*

      - name: Upload artifact for deployment jobs
        uses: actions/upload-artifact@v3
        with:
          name: python-app
          path: |
            release.zip
            !venv/

  deploy:
    runs-on: ubuntu-latest
    needs: build
    environment:
      name: 'Production'
      url: ${{ steps.deploy-to-webapp.outputs.webapp-url }}

    steps:
      - name: Download artifact from build job
        uses: actions/download-artifact@v3
        with:
          name: python-app

      - name: Unzip artifact for deployment
        run: unzip release.zip

      - name: 'Deploy to Azure Web App'
        uses: azure/webapps-deploy@v2
        id: deploy-to-webapp
        with:
          app-name: {Azure App Service の名前}
          slot-name: 'Production'
          publish-profile: ${{ secrets.発行プロファイル }}
```
 
# 終わってみればまぁそうなんですが
Azure App Service で FastAPI というドキュメント自体があまり多くなく、嵌りましたのでメモとして残しました。
ポイントは、
- uvicorn はAzure App Service に標準で入っていないため、SCM_DO_BUILD_DURING_DEPLOYMENTでrequierments.txtを読み込むこと
- 設定全部が終わってからデプロイすること
- デプロイセンター標準のymlだとvenvフォルダもデプロイして無駄に容量使うので、省くこと

でしょうか。終わってみればまぁそうなんですが、タイミング問題もあってちょっとというかかなり嵌りましたということで。