【Azure】 Container Appsにて1つのコンテナアプリに複数のコンテナイメージをデプロイする

# はじめに
Azure Container Apps(ACA)はAzure上にてコンテナ化されたアプリケーションを稼働させるためのプラットフォームです。

Azureには他にもAzure Kubernetes Service(AKS)という別のコンテナプラットフォームがありますが、それと比べて自由度は低いものの運用・構築が容易という特徴があります。

1つのコンテナアプリに対して1つのコンテナイメージをデプロイするのが一般的ですが、今回1つのコンテナアプリに対して複数のコンテナイメージをデプロイしたいという要望があり方法を調べてみました。

# 作るもの
下図のように、コンテナーアプリ内で複数のイメージを稼働させ、それぞれ8080と8081番のポートにて外部からアクセスできるようにさせたいです。（Container Apps Environmentの記述は省略しています）
またContainer Apps EnvironmentはVNet統合を行い、自分たちで管理しているネットワーク上に配置します。
![container.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3689987/17d22a5f-d533-66cc-b67d-7ad9bec6ad17.png)

# 検証用のアプリ
検証用にJavaを用いたアプリケーションを作成します。
アプリケーションにはGETに対してapp1 responded!!（app2ではapp2 responded!!）と返却する簡単なAPIと、もう一方のコンテナをコールする2つのAPIを作成します。
また別途app2には8081番ポートを使用するよう設定しています。
```java
@RestController
public class SampleController {
  @GetMapping("/api/sample")
  public String sample() {
    return "app1 responded!!";
  }

  @GetMapping("/api/callAnother")
  public String callAnotherAPI() {
    RestTemplate rest = new RestTemplate();
    String url = "http://localhost:8081/api/sample";

    ResponseEntity<String> response = rest.getForEntity(url, String.class);
    return response.getBody();
  }
}
```

# 検証用のAzureリソース
VNet上にContainer Apps Environmentとその上で稼働するコンテナーアプリ、追加の公開ポート設定の3つのリソースを作成します。
（VNetやサブネット、ACRは作成している前提とします。）

## Container Apps Environment
既存VNet上へデプロイするため、infrastructure_subnet_idにてコンテナーアプリ専用のサブネットを指定しています。
サブネットのリソースでは別途、Container Apps Environmentへの委任設定を行っています。
```terraform
resource "azurerm_container_app_environment" "multiple_sample" {
  name = "multiple-sample-cae"
  location = var.location
  resource_group_name = var.resource_group_name
  infrastructure_subnet_id = azurerm_subnet.container.id

  workload_profile {
    name = "Consumption"
    workload_profile_type = "Consumption"
  }
  lifecycle {
    ignore_changes = [ 
      infrastructure_resource_group_name
     ]
  }
}
```

## コンテナーアプリ
先ほど作成したConainer Apps Environment上にコンテナーアプリを作成します。
ingress_portにはapp1にて使用している8080番ポートを指定しています。
```terraform
resource "azurerm_container_app" "multiple-sample" {
  name = "multiple-sample-app"
  resource_group_name = var.resource_group_name
  container_app_environment_id = azurerm_container_app_environment.multiple_sample.id
  revision_mode = "Single"
  workload_profile_name = "Consumption"
  

  template {
    min_replicas = 1
    container {
      name = "sample-app-1"
      image = var.app1_image
      cpu = 1
      memory = "2Gi"
    }
    container {
      name = "sample-app-2"
      image = var.app2_image
      cpu = 1
      memory = "2Gi"
    }
  }
  ingress {
    target_port = 8080
    exposed_port = 8080
    external_enabled = true
    transport = "tcp"
    traffic_weight {
      percentage = 100
      latest_revision = true
    }
  }

  secret {
    name = "admin-password"
    value = azurerm_container_registry.sample_apps.admin_password
  }

  registry {
    server = azurerm_container_registry.sample_apps.login_server
    username = azurerm_container_registry.sample_apps.admin_username
    password_secret_name = "admin-password"
  }
}
```

## 追加のポート設定
外部から両方のアプリへのアクセスを可能にするため、追加で8081番ポートも公開します。
azurermでは追加ポートの設定ができないため、この部分はazapiを用いて設定します。

コンテナーアプリに登録しているシークレットを読み込めずエラーが発生するため、azapi_uptdateではなくazapi_resource_actionを使用しています。
（この書き方ではリソースのアップデートごとに追加ポート設定が消えてしまうため、この部分は修正が必要ですね、、、）
```terraform
resource "azapi_resource_action" "additional_port" {
  type = "Microsoft.App/containerApps@2024-03-01"
  resource_id = azurerm_container_app.multiple-sample.id
  method = "PATCH"

  body = {
    properties = {
      configuration = {
        ingress = {
          additionalPortMappings = [
            {
              external = true
              targetPort = 8081
              exposedPort = 8081
            }
          ]
        }
      }
    }
  }
}
```

# 結果
## App1が稼働する8080番ポートへのアクセス
8080番ポートへのアクセスではapp1からの応答が返ってきていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3689987/a07111ce-aa8d-ab67-e3a5-b7ddddaa9c21.png)

## App2が稼働する8081番ポートへのアクセス
8081番ポートだとapp2からの応答が返ってきていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3689987/8911b5a8-bad8-43d0-fd47-5950a2100659.png)

## App1 -> App2へのアクセス
app1からapp2へのアクセスもできていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3689987/f1fa81f1-9318-e379-0a67-46a950076e1d.png)

## App2 -> App1へのアクセス
念のため確認しましたが、こちらも問題なくできていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3689987/cf99bdb6-e920-7198-0347-c05ec669b04c.png)

# まとめ
追加ポートの設定など改善が必要な部分もありますが、とりあえず期待通り動作することを確認でき良かったです！

本来なら異なるイメージであれば稼働するコンテナを分離すべきだとは思いますが、1つのコンテナで動作するアプリを機能ごとにチームを分けて開発したい時などには選択肢になるのではないでしょうか。