Docker Compose CLI を使って Keycloak を AWS ECS にデプロイする

Docker Compose CLI を使い、docker-compose.yml の設定から Keycloak を AWS ECS にデプロイします。

Docker Compose CLI

https://github.com/docker/compose-cli

従来の docker-compose とは別に、クラウドデプロイ対応の Docker Compose CLI が Docker の CLI プラグインとして提供されています。

インストール

WindowsmacOS では Docker Desktop に同梱されています。

Linux では、リポジトリにあるインストールスクリプトを実行します。

docker-ce (コミュニティ版 Docker 環境) はインストール済みとします。

$ curl -L https://raw.githubusercontent.com/docker/compose-cli/main/scripts/install/install_linux.sh | sh

手動でインストールする場合は以下のように。

# docker コマンドをインストールするので、docker-ce で提供されている docker コマンドとは別の場所に配置することになる。
# /usr/local/bin にパスを通した上で /usr/local/bin にインストール

$ curl -L -o /usr/local/bin/docker https://github.com/docker/compose-cli/releases/download/v1.0.17/docker-linux-amd64
$ chmod +x /usr/local/bin/docker

# docker-ce の docker コマンドに別名でリンクを張る

$ ln -s /usr/bin/docker /usr/local/bin/com.docker.cli

# compose プラグインを配置する

$ mkdir -p ~/.docker/cli-plugins
$ curl -L -o ~/.docker/cli-plugins/docker-compose https://github.com/docker/compose-cli/releases/download/v2.0.0-beta.3/docker-compose-linux-amd64
$ chmod +x ~/.docker/cli-plugins/docker-compose 

設定

デプロイ用の AWS ユーザを用意しておきます。最低限、以下のロールが必要なようです。

  • application-autoscaling:*
  • cloudformation:*
  • ec2:AuthorizeSecurityGroupIngress
  • ec2:CreateSecurityGroup
  • ec2:CreateTags
  • ec2:DeleteSecurityGroup
  • ec2:DescribeRouteTables
  • ec2:DescribeSecurityGroups
  • ec2:DescribeSubnets
  • ec2:DescribeVpcs
  • ec2:RevokeSecurityGroupIngress
  • ecs:CreateCluster
  • ecs:CreateService
  • ecs:DeleteCluster
  • ecs:DeleteService
  • ecs:DeregisterTaskDefinition
  • ecs:DescribeClusters
  • ecs:DescribeServices
  • ecs:DescribeTasks
  • ecs:ListAccountSettings
  • ecs:ListTasks
  • ecs:RegisterTaskDefinition
  • ecs:UpdateService
  • elasticloadbalancing:*
  • iam:AttachRolePolicy
  • iam:CreateRole
  • iam:DeleteRole
  • iam:DetachRolePolicy
  • iam:PassRole
  • logs:CreateLogGroup
  • logs:DeleteLogGroup
  • logs:DescribeLogGroups
  • logs:FilterLogEvents
  • route53:CreateHostedZone
  • route53:DeleteHostedZone
  • route53:GetHealthCheck
  • route53:GetHostedZone
  • route53:ListHostedZonesByName
  • servicediscovery:*

docker context create で ECS デプロイ用のコンテキストを作成します。

$ docker context create ecs ecs-test
? Create a Docker context using:  [Use arrows to move, type to filter]
> An existing AWS profile
  AWS secret and token credentials
  AWS environment variables

docker context ls で確認。

$  docker context ls
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT   ORCHESTRATOR
default             moby                Current DOCKER_HOST based configuration   unix:///var/run/docker.sock                         swarm
ecs-test *          ecs

docker context use でコンテキストを切り替えることで、デプロイ先が ECS になります。 環境変数 DOCKER_CONTEXT で指定することもできるので、運用によっては direnv で設定すると便利そうです。

$ docker context use ecs-test
ecs-test

# または
$ export DOCKER_CONTEXT=ecs-test

# デフォルト (ローカルへのデプロイ) に戻すには
$ docker context use default
$ unset DOCKER_CONTEXT

デプロイ

docker-compose.yml を用意して、 docker compose up でデプロイします。 後述の docker-compose.yml (初版) で実施すると以下のようになりました。

$ docker compose up
[+] Running 14/14
 ⠿ keycloak                       CreateComplete                    265.0s
 ⠿ KeycloakTaskExecutionRole      CreateComplete                     21.0s
 ⠿ KeycloakTCP8080TargetGroup     CreateComplete                      1.0s
 ⠿ Cluster                        CreateComplete                      6.0s
 ⠿ CloudMap                       CreateComplete                     47.0s
 ⠿ DefaultNetwork                 CreateComplete                      6.0s
 ⠿ LogGroup                       CreateComplete                      3.1s
 ⠿ LoadBalancer                   CreateComplete                    153.0s
 ⠿ Default8080Ingress             CreateComplete                      1.0s
 ⠿ DefaultNetworkIngress          CreateComplete                      0.0s
 ⠿ KeycloakTaskDefinition         CreateComplete                      3.0s
 ⠿ KeycloakServiceDiscoveryEntry  CreateComplete                      2.0s
 ⠿ KeycloakTCP8080Listener        CreateComplete                      3.0s
 ⠿ KeycloakService                CreateComplete                    100.0s

docker-compose.yml の内容が CloudFormation テンプレートに翻訳され、CloudFormation スタックが作成されます。 翻訳後の内容は docker compose convert で確認できます。

$ docker compose convert

AWSTemplateFormatVersion: 2010-09-09
Resources:
  CloudMap:
    Properties:
      Description: Service Map for Docker Compose project keycloak
      Name: keycloak.local
      Vpc: vpc-46193423
    Type: AWS::ServiceDiscovery::PrivateDnsNamespace
...(略)...

スタックを削除する場合は docker compose down です。

$ docker compose down
[+] Running 14/14
 ⠿ keycloak                       DeleteComplete                    455.0s
 ⠿ Default8080Ingress             DeleteComplete                      1.1s
 ⠿ DefaultNetworkIngress          DeleteComplete                      1.1s
 ⠿ KeycloakService                DeleteComplete                    402.1s
 ⠿ KeycloakTCP8080Listener        DeleteComplete                      2.0s
 ⠿ DefaultNetwork                 DeleteComplete                      1.1s
 ⠿ KeycloakTaskDefinition         DeleteComplete                      2.0s
 ⠿ KeycloakServiceDiscoveryEntry  DeleteComplete                      2.0s
 ⠿ Cluster                        DeleteComplete                      1.9s
 ⠿ CloudMap                       DeleteComplete                     47.0s
 ⠿ LogGroup                       DeleteComplete                      1.0s
 ⠿ KeycloakTaskExecutionRole      DeleteComplete                      2.0s
 ⠿ KeycloakTCP8080TargetGroup     DeleteComplete                      0.0s
 ⠿ LoadBalancer                   DeleteComplete                      1.0s

docker-compose.yml

docker-compose.yml の内容について見ていきます。 今回は動作確認のため、データベースはデフォルトの h2 を利用しています。

初版

ローカルデプロイ用に書いた docker-compose.yml をベースに、最低限起動できるようにリソース設定を追加しました。

version: '3.9'
services:
  keycloak:
    image: quay.io/keycloak/keycloak
    environment:
      - KEYCLOAK_USER=admin
      - KEYCLOAK_PASSWORD=complicated-password
      - TZ=Asia/Tokyo
    ports:
      - "8080:8080"
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: 2Gb

docker context convert の結果は以下のようになりました。 (VPC は作成済みの VPC ID が使われます)

AWSTemplateFormatVersion: 2010-09-09
Resources:
  CloudMap:
    Properties:
      Description: Service Map for Docker Compose project keycloak
      Name: keycloak.local
      Vpc: vpc-XXXXXXXX
    Type: AWS::ServiceDiscovery::PrivateDnsNamespace
  Cluster:
    Properties:
      ClusterName: keycloak
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
    Type: AWS::ECS::Cluster
  Default8080Ingress:
    Properties:
      CidrIp: 0.0.0.0/0
      Description: keycloak:8080/tcp on default network
      FromPort: 8080
      GroupId:
        Ref: DefaultNetwork
      IpProtocol: TCP
      ToPort: 8080
    Type: AWS::EC2::SecurityGroupIngress
  DefaultNetwork:
    Properties:
      GroupDescription: keycloak Security Group for default network
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
      - Key: com.docker.compose.network
        Value: keycloak_default
      VpcId: vpc-XXXXXXXX
    Type: AWS::EC2::SecurityGroup
  DefaultNetworkIngress:
    Properties:
      Description: Allow communication within network default
      GroupId:
        Ref: DefaultNetwork
      IpProtocol: "-1"
      SourceSecurityGroupId:
        Ref: DefaultNetwork
    Type: AWS::EC2::SecurityGroupIngress
  KeycloakService:
    DependsOn:
    - KeycloakTCP8080Listener
    Properties:
      Cluster:
        Fn::GetAtt:
        - Cluster
        - Arn
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DeploymentController:
        Type: ECS
      DesiredCount: 1
      LaunchType: FARGATE
      LoadBalancers:
      - ContainerName: keycloak
        ContainerPort: 8080
        TargetGroupArn:
          Ref: KeycloakTCP8080TargetGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
          - Ref: DefaultNetwork
          Subnets:
          - subnet-2ff5d876
          - subnet-42c3896a
          - subnet-22eafe55
      PlatformVersion: 1.4.0
      PropagateTags: SERVICE
      SchedulingStrategy: REPLICA
      ServiceRegistries:
      - RegistryArn:
          Fn::GetAtt:
          - KeycloakServiceDiscoveryEntry
          - Arn
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
      - Key: com.docker.compose.service
        Value: keycloak
      TaskDefinition:
        Ref: KeycloakTaskDefinition
    Type: AWS::ECS::Service
  KeycloakServiceDiscoveryEntry:
    Properties:
      Description: '"keycloak" service discovery entry in Cloud Map'
      DnsConfig:
        DnsRecords:
        - TTL: 60
          Type: A
        RoutingPolicy: MULTIVALUE
      HealthCheckCustomConfig:
        FailureThreshold: 1
      Name: keycloak
      NamespaceId:
        Ref: CloudMap
    Type: AWS::ServiceDiscovery::Service
  KeycloakTCP8080Listener:
    Properties:
      DefaultActions:
      - ForwardConfig:
          TargetGroups:
          - TargetGroupArn:
              Ref: KeycloakTCP8080TargetGroup
        Type: forward
      LoadBalancerArn:
        Ref: LoadBalancer
      Port: 8080
      Protocol: TCP
    Type: AWS::ElasticLoadBalancingV2::Listener
  KeycloakTCP8080TargetGroup:
    Properties:
      Port: 8080
      Protocol: TCP
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
      TargetType: ip
      VpcId: vpc-XXXXXXXX
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
  KeycloakTaskDefinition:
    Properties:
      ContainerDefinitions:
      - Command:
        - ap-northeast-1.compute.internal
        - keycloak.local
        Essential: false
        Image: docker/ecs-searchdomain-sidecar:1.0
        LogConfiguration:
          LogDriver: awslogs
          Options:
            awslogs-group:
              Ref: LogGroup
            awslogs-region:
              Ref: AWS::Region
            awslogs-stream-prefix: keycloak
        Name: Keycloak_ResolvConf_InitContainer
      - DependsOn:
        - Condition: SUCCESS
          ContainerName: Keycloak_ResolvConf_InitContainer
        Environment:
        - Name: KEYCLOAK_PASSWORD
          Value: Yke97M9rVbdq9xizVNVF
        - Name: KEYCLOAK_USER
          Value: admin
        - Name: TZ
          Value: Asia/Tokyo
        Essential: true
        Image: quay.io/keycloak/keycloak:latest@sha256:45c02bc75a4d440292761a1c1cfc245cfa1160cc2b665ae3299b336ce2078379
        LinuxParameters: {}
        LogConfiguration:
          LogDriver: awslogs
          Options:
            awslogs-group:
              Ref: LogGroup
            awslogs-region:
              Ref: AWS::Region
            awslogs-stream-prefix: keycloak
        Name: keycloak
        PortMappings:
        - ContainerPort: 8080
          HostPort: 8080
          Protocol: tcp
      Cpu: "1024"
      ExecutionRoleArn:
        Ref: KeycloakTaskExecutionRole
      Family: keycloak-keycloak
      Memory: "2048"
      NetworkMode: awsvpc
      RequiresCompatibilities:
      - FARGATE
    Type: AWS::ECS::TaskDefinition
  KeycloakTaskExecutionRole:
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Action:
          - sts:AssumeRole
          Condition: {}
          Effect: Allow
          Principal:
            Service: ecs-tasks.amazonaws.com
        Version: 2012-10-17
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
      - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
      - Key: com.docker.compose.service
        Value: keycloak
    Type: AWS::IAM::Role
  LoadBalancer:
    Properties:
      LoadBalancerAttributes:
      - Key: load_balancing.cross_zone.enabled
        Value: "true"
      Scheme: internet-facing
      Subnets:
      - subnet-2ff5d876
      - subnet-42c3896a
      - subnet-22eafe55
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
      Type: network
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
  LogGroup:
    Properties:
      LogGroupName: /docker-compose/keycloak
    Type: AWS::Logs::LogGroup

Keycloak が ECS 上にデプロイされ、ネットワークロードバランサー経由でアクセスするという形になります。

ここでの注意点としては、

  • ports の設定について、公開ポートは ECS の内部ポート (アプリケーションが待ち受けるポート) と一致させる必要がある。(異なると docker compose up 時にエラーになります)
  • デフォルトのリソース設定 (最小値?) では Keycloak の要求環境を満たせないようで、動作中にクラッシュする。

2版

初版の問題点

初版の設定では、Keycloak (のロードバランサー) へのアクセスは HTTP になっています。認証に使うからには HTTPS にすべきです。

また、Keycloak 自身もグローバル IP 下では HTTP による管理者コンソールへのログインを拒否するため、このままでは Web 上での設定ができません。

現状の Docker Compose CLI では、アプリケーションポートによってロードバランサーの種類 (アプリケーション or ネットワーク) やリスナーの接続プロトコル (TCP or TLS or HTTP or HTTPS) が決定され、素の docker-compose.yml の仕様だけではうまく構成できないようです。

x-aws-cloudformation

Docker Compose CLI では docker-compose.yml の仕様を拡張し、AWS に関する設定ができるようになっています。

x-aws-cloudformation キーを使い、直接 CloudFormation テンプレートを追加していきます。既存項目については上書きする形になります。

version: '3.9'
services:
  keycloak:
    image: quay.io/keycloak/keycloak
    environment:
      - KEYCLOAK_USER=admin
      - KEYCLOAK_PASSWORD=complicated-password
      - PROXY_ADDRESS_FORWARDING=true
      - TZ=Asia/Tokyo
    ports:
      - "8080:8080"
    deploy:
      resources:
        limits:
          cpus: "1"
          memory: 2Gb

x-aws-cloudformation:
  Resources:
    KeycloakTCP8080Listener:
      Properties:
        Certificates:
        - CertificateArn: "arn:aws:acm:ap-northeast-1:XXXXXXXXXXXX:certificate/11111111-2222-3333-4444-555555555555"
        Port: 443
        Protocol: HTTPS
    KeycloakTCP8080TargetGroup:
      Properties:
        Protocol: HTTP
    LoadBalancer:
      Properties:
        LoadBalancerAttributes: []
        SecurityGroups:
        - Ref: LoadBalancerNetwork
        Type: application
    LoadBalancerNetwork:
      Properties:
        GroupDescription: keycloak Security Group for load balancer
        Tags:
        - Key: com.docker.compose.project
          Value: keycloak
        - Key: com.docker.compose.network
          Value: keycloak_loadbalancer
        SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          FromPort: 443
          ToPort: 443
          IpProtocol: TCP
        VpcId:
          Fn::GetAtt: [DefaultNetwork, VpcId]
      Type: AWS::EC2::SecurityGroup

docker context convert の結果は以下のようになりました。

AWSTemplateFormatVersion: 2010-09-09
Resources:
  CloudMap:
    Properties:
      Description: Service Map for Docker Compose project keycloak
      Name: keycloak.local
      Vpc: vpc-XXXXXXXX
    Type: AWS::ServiceDiscovery::PrivateDnsNamespace
  Cluster:
    Properties:
      ClusterName: keycloak
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
    Type: AWS::ECS::Cluster
  Default8080Ingress:
    Properties:
      CidrIp: 0.0.0.0/0
      Description: keycloak:8080/tcp on default network
      FromPort: 8080
      GroupId:
        Ref: DefaultNetwork
      IpProtocol: TCP
      ToPort: 8080
    Type: AWS::EC2::SecurityGroupIngress
  DefaultNetwork:
    Properties:
      GroupDescription: keycloak Security Group for default network
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
      - Key: com.docker.compose.network
        Value: keycloak_default
      VpcId: vpc-XXXXXXXX
    Type: AWS::EC2::SecurityGroup
  DefaultNetworkIngress:
    Properties:
      Description: Allow communication within network default
      GroupId:
        Ref: DefaultNetwork
      IpProtocol: "-1"
      SourceSecurityGroupId:
        Ref: DefaultNetwork
    Type: AWS::EC2::SecurityGroupIngress
  KeycloakService:
    DependsOn:
    - KeycloakTCP8080Listener
    Properties:
      Cluster:
        Fn::GetAtt:
        - Cluster
        - Arn
      DeploymentConfiguration:
        MaximumPercent: 200
        MinimumHealthyPercent: 100
      DeploymentController:
        Type: ECS
      DesiredCount: 1
      LaunchType: FARGATE
      LoadBalancers:
      - ContainerName: keycloak
        ContainerPort: 8080
        TargetGroupArn:
          Ref: KeycloakTCP8080TargetGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: ENABLED
          SecurityGroups:
          - Ref: DefaultNetwork
          Subnets:
          - subnet-2ff5d876
          - subnet-42c3896a
          - subnet-22eafe55
      PlatformVersion: 1.4.0
      PropagateTags: SERVICE
      SchedulingStrategy: REPLICA
      ServiceRegistries:
      - RegistryArn:
          Fn::GetAtt:
          - KeycloakServiceDiscoveryEntry
          - Arn
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
      - Key: com.docker.compose.service
        Value: keycloak
      TaskDefinition:
        Ref: KeycloakTaskDefinition
    Type: AWS::ECS::Service
  KeycloakServiceDiscoveryEntry:
    Properties:
      Description: '"keycloak" service discovery entry in Cloud Map'
      DnsConfig:
        DnsRecords:
        - TTL: 60
          Type: A
        RoutingPolicy: MULTIVALUE
      HealthCheckCustomConfig:
        FailureThreshold: 1
      Name: keycloak
      NamespaceId:
        Ref: CloudMap
    Type: AWS::ServiceDiscovery::Service
  KeycloakTCP8080Listener:
    Properties:
      DefaultActions:
      - ForwardConfig:
          TargetGroups:
          - TargetGroupArn:
              Ref: KeycloakTCP8080TargetGroup
        Type: forward
      LoadBalancerArn:
        Ref: LoadBalancer
      Port: 443
      Protocol: HTTPS
      Certificates:
      - CertificateArn: arn:aws:acm:ap-northeast-1:XXXXXXXXXXXX:certificate/11111111-2222-3333-4444-555555555555
    Type: AWS::ElasticLoadBalancingV2::Listener
  KeycloakTCP8080TargetGroup:
    Properties:
      Port: 8080
      Protocol: HTTP
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
      TargetType: ip
      VpcId: vpc-XXXXXXXX
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
  KeycloakTaskDefinition:
    Properties:
      ContainerDefinitions:
      - Command:
        - ap-northeast-1.compute.internal
        - keycloak.local
        Essential: false
        Image: docker/ecs-searchdomain-sidecar:1.0
        LogConfiguration:
          LogDriver: awslogs
          Options:
            awslogs-group:
              Ref: LogGroup
            awslogs-region:
              Ref: AWS::Region
            awslogs-stream-prefix: keycloak
        Name: Keycloak_ResolvConf_InitContainer
      - DependsOn:
        - Condition: SUCCESS
          ContainerName: Keycloak_ResolvConf_InitContainer
        Environment:
        - Name: KEYCLOAK_PASSWORD
          Value: complicated-password
        - Name: KEYCLOAK_USER
          Value: admin
        - Name: PROXY_ADDRESS_FORWARDING
          Value: "true"
        - Name: TZ
          Value: Asia/Tokyo
        Essential: true
        Image: quay.io/keycloak/keycloak:latest@sha256:45c02bc75a4d440292761a1c1cfc245cfa1160cc2b665ae3299b336ce2078379
        LinuxParameters: {}
        LogConfiguration:
          LogDriver: awslogs
          Options:
            awslogs-group:
              Ref: LogGroup
            awslogs-region:
              Ref: AWS::Region
            awslogs-stream-prefix: keycloak
        Name: keycloak
        PortMappings:
        - ContainerPort: 8080
          HostPort: 8080
          Protocol: tcp
      Cpu: "1024"
      ExecutionRoleArn:
        Ref: KeycloakTaskExecutionRole
      Family: keycloak-keycloak
      Memory: "2048"
      NetworkMode: awsvpc
      RequiresCompatibilities:
      - FARGATE
    Type: AWS::ECS::TaskDefinition
  KeycloakTaskExecutionRole:
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Action:
          - sts:AssumeRole
          Condition: {}
          Effect: Allow
          Principal:
            Service: ecs-tasks.amazonaws.com
        Version: 2012-10-17
      ManagedPolicyArns:
      - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
      - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
      - Key: com.docker.compose.service
        Value: keycloak
    Type: AWS::IAM::Role
  LoadBalancer:
    Properties:
      LoadBalancerAttributes: []
      Scheme: internet-facing
      Subnets:
      - subnet-2ff5d876
      - subnet-42c3896a
      - subnet-22eafe55
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
      Type: application
      SecurityGroups:
      - Ref: LoadBalancerNetwork
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
  LogGroup:
    Properties:
      LogGroupName: /docker-compose/keycloak
    Type: AWS::Logs::LogGroup
  LoadBalancerNetwork:
    Properties:
      GroupDescription: keycloak Security Group for load balancer
      SecurityGroupIngress:
      - CidrIp: 0.0.0.0/0
        FromPort: 443
        IpProtocol: TCP
        ToPort: 443
      Tags:
      - Key: com.docker.compose.project
        Value: keycloak
      - Key: com.docker.compose.network
        Value: keycloak_loadbalancer
      VpcId:
        Fn::GetAtt:
        - DefaultNetwork
        - VpcId
    Type: AWS::EC2::SecurityGroup

動作確認のため、DefaultNetwork については変更せず、ECS 側に直接アクセスできる状態になっています。

注意点

環境変数PROXY_ADDRESS_FORWARDING=true を指定する

リバースプロキシやロードバランサー等によって Keycloak の前段で TLS を終端させる場合、Keycloak が URL リダイレクトを正しく行えるようにするため、環境変数PROXY_ADDRESS_FORWARDING=true を設定して X-Forwarded-Proto ヘッダを受ける必要があります。

ロードバランサーは Application LoadBalancer (ALB) を使用する

アプリケーションのポートが 80 や 443 以外の場合、ロードバランサーとしてネットワークロードバランサーが設定されます。 この場合、ロードバランサーTLS を終端するだけなので X-Forwarded-Proto が送信されず、Keycloak が正しく動作しません。

明示的にアプリケーションロードバランサーを指定する必要があります。

リスナーのポートとTLS 証明書を設定する

リスナー (KeycloakTCP8080Listener) の設定で外部公開ポートと TLS 証明書を指定できます。

Protocol 項目は HTTPS にする必要がありますが、これはロードバランサーが ALB でないと設定できません。 (ネットワークロードバランサーでは TCPTLS のみ)

CloudFormation の関数タグは使えない (?)

現状、Docker Compose CLI!GetAtt!Ref 等のタグ (関数の短縮形構文) を解釈できないようです。そのため、上記の LoadBalancerNetwork.VpcId では完全名構文で関数 Fn::GetAtt を指定しています。

まとめ

Docker Compose CLI で Keycloak の CloudFormation スタックを構成し ECS にデプロイしました。

現状では制限もありますが、比較的少ない記述で、使い慣れた docker コマンド一発でデプロイできるというのは便利かと思います。

参考文献

https://docs.docker.com/cloud/ecs-integration/