Azure Container Apps (ACA) でブルーグリーンデプロイする

# Azure Container Apps (ACA) でブルーグリーンデプロイする

こんにちは、@studio_meowtoon です。今回は、Azure Container Apps 環境でコンテナアプリをブルーグリーンデプロイする方法を紹介します。
![spring-boot_on_azure-container-apps.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/bfc76221-aac9-4666-60c7-dda1502a828c.png)

## 目的

Windows 11 の Linux でクラウド開発します。

こちらから記事の一覧がご覧いただけます。

https://qiita.com/studio_meowtoon/items/6119ab05d8b13527efd8 

## 実現すること

[Microsoft Azure Container Apps (ACA)](https://learn.microsoft.com/ja-jp/azure/container-apps/overview) にて、コンテナアプリのブルーグリーンデプロイを実践してみます。

:::note alert
この記事では、**Docker Hub** に公開する**パブリックアクセス可能なカスタムコンテナイメージ**を使用しています。実際のシステム開発では、どのレジストリからコンテナイメージを取得するかは異なる場合がありますので、適宜ご確認ください。
:::

## 技術トピック

### ブルーグリーンデプロイとは？  

<details>
<summary>こちらを展開してご覧いただけます。</summary>

### [ブルーグリーンデプロイメント](https://martinfowler.com/bliki/BlueGreenDeployment.html)

:::note info
ブルーグリーンデプロイは、アプリケーションの新しいバージョンを本番環境にデプロイする際に、従来の運用方法とは異なり、新しいバージョンを本番環境に構築し、そこでアプリケーションをテストした後、従来の本番環境と切り替える方法です。

|内容|
|:--|
通常、ブルーの本番環境とグリーンの新しい本番環境を用意します。最初はブルーがアクティブな本番環境として稼働し、グリーンはアクティブではありません。|
|新しいバージョンのアプリケーションをグリーン環境にデプロイしてテストし、問題がなければトラフィックをグリーン環境に切り替えます。|
|これにより、ダウンタイムを最小限に抑え、システムの信頼性を高めることができます。|

:::

</details>

## 開発環境

+ Windows 11 Home 22H2 を使用しています。  

:::note info
[WSL](https://learn.microsoft.com/ja-jp/windows/wsl/about) の Ubuntu を操作していきますので macOS の方も参考にして頂けます。  
:::

<details>

WSL (Microsoft Store アプリ版) [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
> wsl --version
WSL バージョン: 1.0.3.0
カーネル バージョン: 5.15.79.1
WSLg バージョン: 1.0.47
```

Ubuntu [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/2aaa6579cb8e4fa1d874)

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.1 LTS
Release:        22.04
```

Java JDK [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/4d11e94a2389758759cd)

```sh
$ java -version
openjdk version "11.0.18" 2023-01-17
OpenJDK Runtime Environment (build 11.0.18+10-post-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.18+10-post-Ubuntu-0ubuntu122.04, mixed mode, sharing)
```

Maven [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/f4cdc4039158153365fb)

```sh
$ mvn -version
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 11.0.18, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
```

Docker [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/3bceee25ac67cc73ebbe)

```sh
$ docker --version
Docker version 23.0.1, build a5ee5b1
```

Azure CLI [※ こちらの関連記事からインストール方法をご確認いただけます](https://qiita.com/studio_meowtoon/items/9721041f9e6e3e987cf0)

```sh
$ az --version
azure-cli                         2.45.0
core                              2.45.0
telemetry                          1.0.8
```

</details>

:::note warn
この記事では基本的に **Ubuntu のターミナルで操作**を行います。**Vim を使用してコピペする方法**を初めて学ぶ人のために、以下の記事で手順を紹介しています。ぜひ挑戦してみてください。
:::

https://qiita.com/studio_meowtoon/items/7771ad48676926e41463

## ブルーグリーンデプロイする仕様
※ この記事での仕様となります。

|識別するタグ|内容|エンドポイント|JSON レスポンス|
|:--|:--|:--|:--|
|v1|現用のサービス|/api/data|{"message":"Hello **Blue**!"}|
|v2|更新するサービス|/api/data|{"message":"Hello **Green**!"}|


**カスタムコンテナイメージ形式のアプリ**

|No|コンテナイメージ|内容|
|:--|:--|:--|
|1|app-hello-spring-boot:v1|現用のアプリケーション|
|2|app-hello-spring-boot:v2|更新するアプリケーション|

## ブルーグリーンデプロイする手順

### v1 アプリのコンテナイメージの作成

<details>
<summary>こちらを展開して手順をご覧いただけます。</summary>

#### Java アプリ作成

こちらの関連記事で手順がご確認いただけます。 

https://qiita.com/studio_meowtoon/items/9c07e20b4124d8c5f972

プロジェクトフォルダに移動します。  
※ ~/tmp/hello-spring-boot をプロジェクトフォルダとします。    

```sh
$ cd ~/tmp/hello-spring-boot
```

v1 アプリのソースコードを作成します。

```sh
$ vim src/main/java/com/example/springboot/controller/HelloController.java
```

ファイルの内容

```java:HelloController.java
package com.example.springboot.controller;

import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class HelloController {
    @GetMapping("/data")
    public Map<String, String> getData() {
        Map<String, String> map = Map.of("message", "Hello Blue!");
        return map;
    }
}
```

v1 アプリをビルドします。

```sh
$ mvn clean package
```

#### コンテナイメージ作成

こちらの関連記事で手順がご確認いただけます。 

https://qiita.com/studio_meowtoon/items/9c07e20b4124d8c5f972

Docker デーモンを起動します。

```sh
$ sudo service docker start
 * Starting Docker: docker      [ OK ]
```

v1 アプリをコンテナイメージにビルドします。

```sh
$ docker build --no-cache -t app-hello-spring-boot:v1 .
```

v1 アプリのコンテナイメージを確認します。

```sh
$ docker images | grep app-hello-spring-boot
app-hello-spring-boot   v1        c61d32929a90   58 seconds ago   390MB
```

#### コンテナイメージを Docker Hub にプッシュ

こちらの関連記事で手順がご確認いただけます。 

https://qiita.com/studio_meowtoon/items/cbcfe1433885bae514db

Docker Hub にログインします。  

```sh
$ docker login
Login Succeeded
```

v1 アプリのコンテナイメージにタグを付けます。
※ $USER の部分はご自身のコンテナリポジトリに読み替えて下さい。  

```sh
$ docker tag app-hello-spring-boot:v1 $USER/app-hello-spring-boot:v1
```

Docker Hub に v1 アプリのコンテナイメージをプュシュします。

```sh
$ docker push $USER/app-hello-spring-boot:v1
```

ローカルでコンテナを起動させアプリの動作を確認します。
※ $USER の部分はご自身のコンテナリポジトリに読み替えて下さい。  

```sh
$ docker run --rm --name app-local -p 8080:8080 $USER/app-hello-spring-boot:v1
```

```sh
$ curl http://localhost:8080/api/data/
{"message":"Hello Blue!"}
```

:::note warn 
コンテナイメージをビルドし、Docker Hub にプッシュする際、キャッシュが不適切に使用され、ソースコードの変更が反映されないことがあります。この問題を解決するために、事前に Docker Hub にプッシュしたコンテナイメージをローカルで起動し、正しい動作を確認することをお勧めします。
:::

</details>

:::note info
ここまでの作業で **v1 アプリのカスタムコンテナイメージ**が **Docker Hub に公開**できました。
:::

### v1 アプリを Azure Container Apps にデプロイ

#### ログイン

Azure CLI で Azure にログインします。  

```sh
$ az login
```

こちらの関連記事で手順がご確認いただけます。 

https://qiita.com/studio_meowtoon/items/9721041f9e6e3e987cf0

#### リソースグループ

リソースグループを作成します。

```sh:Microsoft.Resources/resourceGroups
$ az group create \
    --name rg-hello \
    --location japaneast
```

#### コンテナ アプリ環境

こちらの関連記事で手順がご確認いただけます。

https://qiita.com/studio_meowtoon/items/cbcfe1433885bae514db

コンテナ アプリ環境を作成します。    
※ Kubernetes 基盤の環境がベースとなっているので少し時間がかかります。  

```sh:Microsoft.App/managedEnvironments
$ az containerapp env create \
    --resource-group rg-hello \
    --name cae-hello \
    --location japaneast
```

#### コンテナ アプリ

v1 コンテナ アプリの作成とデプロイを行います。    
※ $USER の部分はご自身のコンテナリポジトリに読み替えて下さい。  

```sh:Microsoft.App/containerApps
$ az containerapp create \
    --resource-group rg-hello \
    --environment cae-hello \
    --name ca-hello-spring-boot \
    --image $USER/app-hello-spring-boot:v1 \
    --target-port 8080 \
    --ingress 'external' \
    --min-replicas 1 \
    --revisions-mode multiple \
    --revision-suffix v1
```

<details>
<summary>説明を開きます。</summary>

:::note info
このコマンドは、Azure Container App インスタンスを作成するためのコマンドです。以下が各オプションが実行する内容です。

|コマンド|内容|
|:--|:--|
|az containerapp create|Container Apps を作成する為のコマンドです。|

|オプション|値|内容|
|:--|:--|:--|
|--resource-group|rg-hello|デプロイするリソースが属するリソースグループの名前を指定します。|
|--environment|cae-hello|Container Apps 環境の名前を指定します。|
|--name|ca-hello-spring-boot|Container Apps の名前を指定します。|
|--image|$USER/app-hello-spring-boot:v1|実行するコンテナイメージの名前を指定します。ここでは、前述の手順で Docker Hub にプッシュしたイメージ名を使用しています。|
|--target-port|8080|コンテナが公開するポートを指定します。ここでは、Spring Boot アプリケーションがポート 8080 で公開されているため、8080 を指定しています。|
|--ingress|'external'|コンテナを外部からアクセスできるようにするためのイングレス設定を指定します。ここでは、外部からのアクセスを許可する 'external' を指定しています。|
|--min-replicas|1|最小レプリカ数を指定します。ここでは、最小レプリカ数を 1 に設定しています。|
|--revisions-mode|multiple|リビジョンのモードを指定します。ここでは複数のリビジョンをサポートする multiple を指定しています。|
|--revision-suffix|v1|新しいリビジョンを作成するために、Container Apps の名前の末尾に付ける接尾辞を指定します。ここでは v1 を指定しています。|
:::

</details>

コンテナ アプリが作成されました。

```sh
https://ca-hello-spring-boot.jollyisland-XXXXXXX.japaneast.awesomecontainerapps.io/
```

別ターミナルから curl コマンドで**アプリケーション URL** を確認します。

```sh
$ curl https://ca-hello-spring-boot.jollyisland-XXXXXXX.japaneast.awesomecontainerapps.io/api/data
{"message":"Hello Blue!"}
```

:::note info
ここまでの作業で v1 アプリのカスタムコンテナイメージが Azure Container Apps で起動できました。
:::

#### Azure Portal の確認

[コンテナ アプリ] → [リビジョン管理] を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/794389f6-ba1b-bbbd-40f7-54cb84f1e99b.png)
以下のように、コンテナアプリにリビジョンが付加されています。

```sh
ca-hello-spring-boot--v1
```

また、v1 アプリの**リビジョン URL** も確認することができます。

```sh
https://ca-hello-spring-boot--v1.jollyisland-XXXXXXX.japaneast.awesomecontainerapps.io/
```

### v2 アプリのコンテナイメージの作成

<details>
<summary>こちらを展開して手順をご覧いただけます。</summary>

#### Java アプリ作成

v2 アプリのソースコードを作成します。

```sh
$ vim src/main/java/com/example/springboot/controller/HelloController.java
```

ファイルの内容

```java:HelloController.java
package com.example.springboot.controller;

import java.util.Map;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class HelloController {
    @GetMapping("/data")
    public Map<String, String> getData() {
        Map<String, String> map = Map.of("message", "Hello Green!");
        return map;
    }
}
```

v2 アプリをビルドします。

```sh
$ mvn clean package
```

#### コンテナイメージ作成

v2 アプリをコンテナイメージにビルドします。

```sh
$ docker build --no-cache -t app-hello-spring-boot:v2 .
```

v2 アプリのコンテナイメージを確認します。

```sh
$ docker images | grep app-hello-spring-boot
app-hello-spring-boot   v1        c61d32929a90   58 seconds ago   390MB
app-hello-spring-boot   v2        9d54d1056fb2    9 seconds ago   390MB
```

#### コンテナイメージを Docker Hub にプッシュ

v2 アプリのコンテナイメージにタグを付けます。
※ $USER の部分はご自身のコンテナリポジトリに読み替えて下さい。  

```sh
$ docker tag app-hello-spring-boot:v2 $USER/app-hello-spring-boot:v2
```

Docker Hub に v2 アプリのコンテナイメージをプュシュします。

```sh
$ docker push $USER/app-hello-spring-boot:v2
```

ローカルでコンテナを起動させアプリの動作を確認します。
※ $USER の部分はご自身のコンテナリポジトリに読み替えて下さい。  

```sh
$ docker run --rm --name app-local -p 8080:8080 $USER/app-hello-spring-boot:v2
```

```sh
$ curl http://localhost:8080/api/data/
{"message":"Hello Green!"}
```

</details>

:::note info
ここまでの作業で **v2 アプリのカスタムコンテナイメージ**が **Docker Hub に公開**できました。
:::

### v2 アプリを Azure Container Apps にデプロイ

#### コンテナ アプリ

コンテナ アプリのトラフィック分割を構成します。
※ 現状のリビジョンを 100% にします。

:::note alert
コンテナ アプリのトラフィック分割の構成を設定しない場合、**更新するリビジョンのコンテナ アプリのトラフィックが 100%** として設定されます。ご注意ください。
:::

```sh
$ az containerapp ingress traffic set \
    --resource-group rg-hello \
    --name ca-hello-spring-boot \
    --revision-weight ca-hello-spring-boot--v1=100
```

<details>
<summary>説明を開きます。</summary>

:::note info
このコマンドは、Azure Container Apps のリビジョンごとにトラフィックのウェイトを設定するために使用されます。

|コマンド|内容|
|:--|:--|
|az containerapp ingress traffic set|Container Apps のリビジョンごとにトラフィックのウェイトを設定する為のコマンドです。|

|オプション|値|内容|
|:--|:--|:--|
|--resource-group|rg-hello|デプロイするリソースが属するリソースグループの名前を指定します。|
|--name|ca-hello-spring-boot|Container Apps の名前を指定します。|
|--revision-weight|ca-hello-spring-boot--v1=100|ca-hello-spring-boot--v1 というリビジョンのトラフィックウェイトを 100 に設定しています。|
:::

</details>

※ 以下のようなレスポンスが返ります。
```json
[
  {
    "latestRevision": true,
    "weight": 0
  },
  {
    "revisionName": "ca-hello-spring-boot--v1",
    "weight": 100
  }
]
```

v2 コンテナ アプリのデプロイを行います。    
※ $USER の部分はご自身のコンテナリポジトリに読み替えて下さい。  

```sh:Microsoft.App/containerApps
$ az containerapp update \
    --resource-group rg-hello \
    --name ca-hello-spring-boot \
    --image $USER/app-hello-spring-boot:v2 \
    --revision-suffix v2
```

コンテナ アプリが更新されました。

別ターミナルから curl コマンドで**アプリケーション URL** を確認します。

```sh
$ curl https://ca-hello-spring-boot.jollyisland-XXXXXXX.japaneast.awesomecontainerapps.io/api/data
{"message":"Hello Blue!"}
```

:::note warn
コンテナ アプリの**アプリケーション URL**をリクエストすると、更新前の **v1 アプリの出力が表示**されます。
:::

#### Azure Portal の確認

[コンテナ アプリ] → [リビジョン管理] を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/d301a2d7-5f9d-1919-1881-46d88c4e0c96.png)
以下のように、コンテナ アプリに新しいリビジョンが追加されています。

```sh
ca-hello-spring-boot--v2
```

:::note warn
新しいリビジョンはトラフィックが 0% になっています。この状態ではコンテナ アプリの**アプリケーション URL**は最新のリビジョンである **v2 アプリに向いていません**。
:::

また、v2 アプリの**リビジョン URL** を確認することができます。

```sh
https://ca-hello-spring-boot--v2.jollyisland-XXXXXXX.japaneast.awesomecontainerapps.io/
```

別ターミナルから curl コマンドで**リビジョン URL** を確認します。
v2 アプリの出力が表示されます。

```sh
$ curl https://ca-hello-spring-boot--v2.jollyisland-XXXXXXX.japaneast.awesomecontainerapps.io/api/data
{"message":"Hello Green!"}
```

:::note info
アプリを更新するために、v2 アプリを本番環境にデプロイすることができました。また、実際にリリースする前に**リビジョン URL** を利用して**新しいリビジョンの動作確認**を行うことができます。これは、**ブルーグリーンデプロイの目的の1つ**です。
:::

### 新しいリビジョンへの切り替え

v1 アプリから、v2 アプリに切り替えます。
※ v1 リビジョンから、v2 リビジョンへリビジョンを切り替えます。  

```sh
$ az containerapp ingress traffic set \
    --resource-group rg-hello \
    --name ca-hello-spring-boot \
    --revision-weight ca-hello-spring-boot--v1=0 ca-hello-spring-boot--v2=100
```
※ 以下のようなレスポンスが返ります。
```json
[
  {
    "latestRevision": true,
    "weight": 0
  },
  {
    "revisionName": "ca-hello-spring-boot--v1",
    "weight": 0
  },
  {
    "revisionName": "ca-hello-spring-boot--v2",
    "weight": 100
  }
]
```

リビジョンが切り替わりました。

別ターミナルから curl コマンドで**アプリケーション URL** を確認します。

```sh
$ curl https://ca-hello-spring-boot.jollyisland-XXXXXXX.japaneast.awesomecontainerapps.io/api/data
{"message":"Hello Green!"}
```

:::note info
コンテナ アプリの**アプリケーション URL**をリクエストして、更新後の **v2 アプリの出力が表示**されました。**本番環境へのデプロイ作業が完了**しました。※ この記事での本番環境となります。
:::

#### Azure Portal の確認

[コンテナ アプリ] → [リビジョン管理] を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/9fa00584-b520-3b67-bef9-c25e66659b33.png)
v2 リビジョン側のトラフィックが 100% になっていることを確認できます。

### トラブル発生時の切り戻し

:::note alert
サービスを最新バージョンにデプロイした後に**トラブルが発生**した場合、以前に**正常に動作**していた**リビジョンに戻す**必要があります。これは、**ブルーグリーンデプロイの目的の1つ**です。
:::

v2 アプリから、v1 アプリに切り戻します。  
※ リビジョンを切り替えます。  

```sh
$ az containerapp ingress traffic set \
    --resource-group rg-hello \
    --name ca-hello-spring-boot \
    --revision-weight ca-hello-spring-boot--v1=100 ca-hello-spring-boot--v2=0
```

別ターミナルから curl コマンドで**アプリケーション URL** を確認します。

```sh
$ curl https://ca-hello-spring-boot.jollyisland-XXXXXXX.japaneast.awesomecontainerapps.io/api/data
{"message":"Hello Blue!"}
```

コンテナ アプリの**アプリケーション URL**をリクエストして、更新前の **v1 アプリの出力が表示**されました。

#### Azure Portal の確認

[コンテナ アプリ] → [リビジョン管理] を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/44a04b15-573a-7694-9f8f-0c45c62ba749.png)
再度 v1 リビジョン側のトラフィックが 100% に設定され、**切り戻せた**ことを確認できます。

### デプロイ後の注意点

:::note alert
コンテナ アプリのリビジョンが**アクティブ状態**だと、利用に応じて**課金**されます。新しいリビジョンが正常に動作し、安定した状態になった後に、元々の古いリビジョンのアクティブ状態を解除することができます。これにより、必要以上に課金されることを防ぐことができます。
:::

```sh
$ az containerapp revision deactivate \
    --resource-group rg-hello \
    --name ca-hello-spring-boot \
    --revision ca-hello-spring-boot--v1 
```

<details>
<summary>説明を開きます。</summary>

:::note info
このコマンドは、Azure Container Apps のリビジョンを非アクティブ化するために使用されます。

|コマンド|内容|
|:--|:--|
|az containerapp revision deactivate|Container Apps のリビジョンを非アクティブ化する為のコマンドです。|

|オプション|値|内容|
|:--|:--|:--|
|--resource-group|rg-hello|デプロイするリソースが属するリソースグループの名前を指定します。|
|--name|ca-hello-spring-boot|Container Apps の名前を指定します。|
|--revision|ca-hello-spring-boot--v1|ca-hello-spring-boot--v1 というリビジョンを非アクティブに設定しています。|
:::

</details>

### Azure Portal の確認

[コンテナ アプリ] → [リビジョン管理] を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206449/751a4dad-cffa-9307-a1e6-58fe050c35c8.png)
v1 リビジョンが非アクティブに設定されたことを確認できました。

## まとめ

Azure Container Apps 環境で、ブルーグリーンデプロイを実践することができました。

:::note warn
**Maven**、**Docker**、**Azure CLI** を使って、Spring Boot アプリの開発からコンテナイメージの作成、Azure 環境へのデプロイまで、**すべてをターミナルから行うことができます**。このように、クラウドでのシステム開発に必要なスキルや理解を深めることができます。初めての人でも簡単に手順を追うことができるので、ぜひ挑戦してみてください。
:::

どうでしたか？ 検証目的の Azure Container Apps 環境で、コンテナアプリのブルーグリーンデプロイを手軽に体験することができます。ぜひお試しください。今後も Azure の開発環境などを紹介していきますので、ぜひお楽しみにしてください。

## 推奨コンテンツ

https://qiita.com/studio_meowtoon/items/5c7f288078c314462568