作成したアプリをPipelineでApp Serviceに自動デプロイする方法

# やりたいこと
FastAPIで作成したアプリをApp Serviceにデプロイします。
Azure Portalからリポジトリを参照して手動デプロイすることも可能ですが、デプロイするたびに作業するのが面倒なのでmainブランチにマージされたら自動デプロイしてくれるようにしたいです。

DevOpsでPipelineを作成して、Dockerのコンテナイメージの管理とApp Serviceへのデプロイを自動化させます。

# フローの流れ
![スクリーンショット 2023-11-02 18.34.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/80ba82e2-2373-6262-2785-835941d68daa.png)

① 開発者がローカルで書いたソースコードをReposにPushします。
② ReposのmasterブランチにPushされたことを検知して、Pipelineを自動で実行させます。
③ PipelineからContainer Registryに新しいコンテナイメージをアップします。
④ 新しいコンテナイメージをApp Service側に配置して、デプロイさせます。

# 使うサービス
#### ◾️ FastAPI
Pythonの軽量フレームワークで、APIの開発に特化。
アプリはこのFastAPIで作成してます。

https://fastapi.tiangolo.com/ja/

#### ◾️ Docker
Dockerについての詳しい内容は記載しませんが、使用。

https://www.docker.com/

#### ◾️ Azure Container Registry
Dockerをクラウド側で管理するために使用。

https://azure.microsoft.com/ja-jp/products/container-registry

#### ◾️ App Service
作成したアプリをデプロイするために使用。

https://azure.microsoft.com/ja-jp/products/app-service

#### ◾️ Azure Pipelines
デプロイを自動化するために使用

https://azure.microsoft.com/ja-jp/products/devops/pipelines

# 事前準備
#### ◾️ Azureの環境を用意
Subscriptionを作成し、リソースグループを一つ作成

#### ◾️ App Service Planを作成
App Serviceを作成するために必要です。

#### ◾️ Azure Container Registryを作成
Dockerのコンテナイメージを管理するために必要です。

#### ◾️ Azure Devopsの用意
Reposでのソースコードの管理、Pipelinesの作成を行うために必要です。

# 手順
### 1. App Serviceを作成
![スクリーンショット 2023-12-10 20.27.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/363bd0ae-fff6-ad68-6e1e-1463145afaaa.png)
リソースの作成から「**Webアプリ**」を選択
<br>

![スクリーンショット 2023-12-10 20.01.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/75ea8b54-eb90-8dd6-af58-15aa540beafd.png)
公開方法は「**Dockerコンテナー**」にします。
OSとRegionはApp Services Planと同じにしてください。
自分はPlan作成時にJapan Eastにしたので、App Serviceも同様に「**Japan East**」にします。

![スクリーンショット 2023-12-10 20.01.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/2dab59f2-92da-410c-47cf-b1647ce64820.png)
OSとRegionをApp Service Planと同じに合わせると「価格プラン」に作成したApp Service Planが出てくると思うので、選択。

<br>

![スクリーンショット 2023-12-10 20.01.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/3d799133-4546-c953-eeec-399cb5b86496.png)
「データベース」タブはスキップして、「Docker」タブに移動。
イメージソースは「**クイックスタート**」を選択。

<br>

![スクリーンショット 2023-12-10 20.08.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/916cb353-ecfc-0134-6a9a-869851659993.png)
既にContainer Registryにイメージをアップしている場合は、イメージソースを「Azure Container Registry」にしてレジストリ、イメージ、タグを選択することでApp Serviceリソース作成と同時にDockerの情報をもとにデプロイしてくれます。

<br>

![スクリーンショット 2023-12-10 20.01.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/5bd0f1f3-f23a-3dd1-ea6e-221b78149abf.png)
「ネットワーク」タブは既定のままでOK。

<br>

![スクリーンショット 2023-12-10 20.01.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/94e866f4-db34-9c5c-7819-b17ae2049521.png)
「監視」タブも既定のまま。
<br>

「**確認および作成**」を選択して、リソース作成完了。

<hr>

### 2. ローカルで簡単なアプリを作成
今回はFastAPIでアプリを作成してます。

##### 1. プロジェクト作成
```
mkdir <プロジェクト名>
```

##### 2. プロジェクト直下に移動
```
cd <プロジェクト名>
```

##### 3. pythonファイルを一つ作成
```
touch main.py
```

##### 4. 仮想環境を作成
```
python -m venv .venv
```

##### 5. 仮想環境に入る
```
source .venv/bin/activate
```

##### 6. 必要なパッケージをインストール
```
pip install fastapi uvicorn gunicorn
```

##### 7. main.pyに記載
```python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


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

```
デプロイできたことを確認できればいいので、必要最低限のみ記載。

<hr>

### 3. Dockerの用意
##### 1. Dockerファイルを作成
```
touch Dockerfile
```

##### 2. Dockerfileに記載
```Dockerfile
FROM python:3.9

WORKDIR /code

COPY requirements.txt .

RUN apt-get update && apt-get -y upgrade

RUN pip install --no-cache-dir --upgrade -r requirements.txt

COPY . .

EXPOSE 3100

CMD ["gunicorn", "main:app"]
```

##### 3. .dockerignoreファイルを作成
```
touch .dockerignore
```

##### 4. .dockerignoreファイルに記載
```.dockerignore
.git*
**/*.pyc
.venv/
```
<hr>

### 4. デプロイ設定
##### 1. 依存関係を出力するファイルを作成
```
touch requirements.txt
```

##### 2. パッケージをrequirements.txtファイルに出力。
```
pip freeze > requirements.txt
```

##### 3. gunicornの構成ファイルを作成
```
touch gunicorn.conf.py
```

##### 4. gunicorn.conf.pyに記載
```gunicorn.conf.py
# Gunicorn configuration file
import multiprocessing

max_requests = 1000
max_requests_jitter = 50

log_file = "-"

bind = "0.0.0.0:3100"

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

```
<hr>

### 5. service connectionの作成
![スクリーンショット 2023-12-10 20.44.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/a3661738-5366-6d04-4b7e-699dd63f7e31.png)
Devopsの左下にある設定ボタンを選択して、「**Service connections**」を選択。
右上の「**New service connection**」から新しいservice connectionを作成。
<br>

![スクリーンショット 2023-12-10 20.41.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/c2ffd301-51a0-337d-505f-1a55e581291a.png)
「**Azure Resource Manager**」を選択。

<br>

![スクリーンショット 2023-12-10 20.48.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/8f1009dc-4811-070c-c99a-773a53b4d407.png)
「**Service principal(automatic)**」を選択。

<br>

![スクリーンショット 2023-12-10 20.43.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/65fefb25-4514-23c7-5cff-02cc38ac7669.png)
対象のSubscriptionとResource Groupを選択して、適当な名前のService connection nameを入力して、「Save」をクリック。

これでservice connectionは作成完了。

<hr>

### 6. Pipelineを作成
![スクリーンショット 2023-12-10 20.58.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/5dc0d5ea-8105-cece-c516-e76f285257dd.png)
サイドメニューから「Pipelines」　を選択して、「**New pipeline**」から新しいパイプラインを作成。
<br>

![スクリーンショット 2023-12-10 20.52.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/5e53bcae-4654-30e2-e67b-da1e03a16316.png)
「**Azure Repos Git**」を選択。
<br>

![スクリーンショット 2023-12-10 20.52.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/84ed55e8-054b-5ef6-a6ed-cf1cefcdad0b.png)
対象のリポジトリを選択。
二つある「**Docker**」のうち、「**Build and push an image to Azure Container Registry**」の方を選択。
<br>

![スクリーンショット 2023-12-10 20.56.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/1a33d880-40e8-0669-7250-247ac9b1b902.png)
対象のSubscriptionを選択。
<br>

![スクリーンショット 2023-12-10 21.05.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/8663856a-05f4-2c07-d9d2-24a71ce85bf9.png)
認証が通った後、Container Registryを選択して、適当なImage Nameをつける。
「Validate and configure」をクリック。

<br>

![スクリーンショット 2023-12-10 20.56.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/7bad63f0-4d47-5c3d-e591-dbc8aa0c0593.png)
以下の画面になったらOK。

<hr>

### 7. ymlファイルを記載
```azure-pipelines.yml
# Docker
# Build and push an image to Azure Container Registry
# https://docs.microsoft.com/azure/devops/pipelines/languages/docker

trigger:
- master

resources:
- repo: self

variables:
  # 以下追記
  azureSubscription: '<5.で作成したservice connection>'
  appName: '<App Serviceリソース名>'
  
  # Container registry service connection established during pipeline creation
  dockerRegistryServiceConnection: '<規定のまま>'
  imageRepository: '<規定のまま>'
  containerRegistry: '<規定のまま>'
  dockerfilePath: '**/Dockerfile-deploy'
  tag: '$(Build.BuildId)'

  # Agent VM image name
  vmImageName: 'ubuntu-latest'

stages:
- stage: Build
  displayName: Build and push stage
  jobs:
  - job: Build
    displayName: Build
    pool:
      vmImage: $(vmImageName)
    steps:
    - task: Docker@2
      displayName: Build and push an image to container registry
      inputs:
        command: buildAndPush
        repository: $(imageRepository)
        dockerfile: $(dockerfilePath)
        containerRegistry: $(dockerRegistryServiceConnection)
        tags: |
          $(tag)

#　追記
- stage: Deploy
  dependsOn: Build
  condition: succeeded()
  displayName: Deploy to App Services
  jobs:
    - job: Deploy
      displayName: Deploy
      pool:
        vmImage: $(vmImageName)
      steps:
        - task: AzureWebAppContainer@1
          displayName: Azure Web App on Container Deploy
          inputs:
            azureSubscription: $(azureSubscription)
            appName: $(appName)
            imageName: $(containerRegistry)/$(imageRepository):$(tag)
```
▪️ triggerは「master」に設定しているため、masterにpushされるたびに実行されます。もし自動で実行されたくない場合は「none」とする。
▪️ stageの「Build」ではContainer Registryにイメージをpushします。
▪️ stageの「Deploy」でApp Serviceにデプロイします。

<hr>

### 8. 実行
![スクリーンショット 2023-12-10 21.13.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/9b42aa1d-67f0-c963-fd17-69622d738a1b.png)
「Save and run」を実行。

...
<br>

![スクリーンショット 2023-12-10 21.11.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/e1169a17-3d61-4b26-834b-b8578aa0cab7.png)

![スクリーンショット 2023-12-10 21.12.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/7c8c388a-78d8-5a15-06f9-9378e2a714d4.png)


![スクリーンショット 2023-12-10 21.17.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1812049/6e44714d-7761-5617-ffa6-7992d9a864bd.png)
無事デプロイできました。