AWS ECS Fargate で keycloak をクラスタ構成で動かしてみる 

Keycloak が ver17 より WildFly から Quarkus へ移行しているが、
Quarkus ベースのバージョンでクラスタ構成を検証している情報が少ないため、
今回 Keycloak を AWS のコンテナ環境でクラスタ構成で動作させてみる。
※本記事は、Keycloakのバージョンアップに伴い、「[Keycloakを冗長構成で動かしてみる](https://qiita.com/yoonis/items/4f4a9df0f6f8e858bd4a)」と「[インフラ管理不要なコンテナ環境のAWS FargateでKeycloakを動かしてみる](https://qiita.com/wadahiro/items/0837729e7c57becbfd06)」の記事を最新化したものです。

# 本記事でやること

1. Keycloak の AWS 向けカスタム Docker イメージを ECR にプッシュする。

   1. AWS 向けに S3_PING を用いたクラスタ構成設定を追加
   2. コンテナビルド
   3. ECR でプライベートリポジトリを作成
   4. コンテナを ECR リポジトリへプッシュ

2. Fatgate を用いた Keycloak 環境を AWS に構築する。

   1. VPC、ELBなどの作成
   2. Aurora(MySQL)上に作成および Keycloak 用データベースの作成
   3. S3_PING 用の S3 を作成
   4. ECS(クラスター、タスク定義、サービス)を作成

3. 動作検証

# 前提条件

- Keycloak バージョン：21.1.1
- Dockerfile は[公式ガイドに記載](https://www.Keycloak.org/server/containers)の内容をベース必要な部分のみ追加＆削除する形で作成
- JGroups によるノード間通信は「S3_PING」を利用

# Keycloak の AWS 向けカスタム Docker イメージを ECR にプッシュする
公式ガイドのイメージでは、以下のような設定を想定したDockerイメージとなっている。

 - DBにpostgresを利用する設定
 - ノード間通信にUDPマルチキャストを利用
 - ヘルスチェックエンドポイントを有効にする環境変数を設定

しかし、AWSではUDPマルチキャスト使えない仕様であること、
コンテナの環境変数はできる限りECSタスク定義で設定するようにしたいということなど踏まえ、
以下のように変更したDockerfileを用いてAWS環境用にイメージをビルドする。

```.tsx:Dcokerfile
FROM quay.io/keycloak/keycloak:latest as builder

WORKDIR /opt/keycloak

# for demonstration purposes only, please make sure to use proper certificates in production instead
RUN keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize 2048 -dname "CN=server" -alias server -ext "SAN:c=DNS:localhost,IP:127.0.0.1" -keystore conf/server.keystore
RUN /opt/keycloak/bin/kc.sh build

FROM quay.io/keycloak/keycloak:latest
COPY --from=builder /opt/keycloak/ /opt/keycloak/

#add jgroup-aws.jar and depencies
ADD --chown=keycloak:keycloak https://repo1.maven.org/maven2/org/jgroups/aws/jgroups-aws/2.0.1.Final/jgroups-aws-2.0.1.Final.jar /opt/keycloak/providers/jgroups-aws-2.0.1.Final.jar
ADD --chown=keycloak:keycloak https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-core/1.12.410/aws-java-sdk-core-1.12.410.jar  /opt/keycloak/providers/aws-java-sdk-core-1.12.410.jar
ADD --chown=keycloak:keycloak https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-s3/1.12.410/aws-java-sdk-s3-1.12.410.jar   /opt/keycloak/providers/aws-java-sdk-s3-1.12.410.jar
ADD --chown=keycloak:keycloak https://repo1.maven.org/maven2/joda-time/joda-time/2.12.2/joda-time-2.12.2.jar  /opt/keycloak/providers/joda-time-2.12.2.jar

ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
```

# AWS 向けに S3_PING を用いたクラスタ構成設定を追加
Keycloakでは、JGroupsを使ってクラスタ構成する各ノードのディスカバリを行う仕組みとなっている。
デフォルト設定ではUDPを使う設定となっており、AWSでも利用可能な方式に変更する。
本記事ではS3_PINGという方法を設定してみる。
  
S3_PINGを利用するにあたり、[JGroups AWS](https://github.com/jgroups-extras/jgroups-aws)というライブラリをKeycloakに適用する必要がある。
下記のDockerfileへの追加内容は、Keycloakのprovidersフォルダへ必要なjarファイルをあらかじめ追加しておく設定である。これによりコンテナ起動時にprovidersに追加されているjarを取り込み、Keycloakを起動することができるようになっている。

```.tsx:Dcokerfile抜粋
#add jgroup-aws.jar and depencies
ADD --chown=keycloak:keycloak https://repo1.maven.org/maven2/org/jgroups/aws/jgroups-aws/2.0.1.Final/jgroups-aws-2.0.1.Final.jar /opt/keycloak/providers/jgroups-aws-2.0.1.Final.jar
ADD --chown=keycloak:keycloak https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-core/1.12.410/aws-java-sdk-core-1.12.410.jar  /opt/keycloak/providers/aws-java-sdk-core-1.12.410.jar
ADD --chown=keycloak:keycloak https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-s3/1.12.410/aws-java-sdk-s3-1.12.410.jar   /opt/keycloak/providers/aws-java-sdk-s3-1.12.410.jar
ADD --chown=keycloak:keycloak https://repo1.maven.org/maven2/joda-time/joda-time/2.12.2/joda-time-2.12.2.jar  /opt/keycloak/providers/joda-time-2.12.2.jar
```

:::note info
本記事ではS3_PINGを利用しているがコンテナ環境だとDNS_PINGを使うケースが多い。
ECSの場合、ECS Service Discoveryを使うことでRoute 53に最大8ノードまで登録してくれるため、8ノードクラスタ内であればDNS_PINGが利用できる。
また、EKSの場合、k8sのDNSを利用することができます。
:::

# コンテナビルド
上記で作成したDockerfileを`docker build`でビルドを実行する。
正常にビルドされたか確認したい場合は`docker images`でビルドしたイメージが表示されることを確認すると良い。


# ECR でプライベートリポジトリを作成
AWS管理コンソールからECRのリポジトリを事前に作成する。
特に難しい要素はなく、可視性設定(今回はプライベート)とリポジトリ名を指定する程度でOK。


# コンテナを ECR リポジトリへプッシュ
ビルド済コンテナイメージを作成したECRのリポジトリへ`docker push`する。これでコンテナの準備は完了である。あとはコンテナ実行環境をAWSで構築していく。

:::note info
ECRリポジトリ向けへのビルド～プッシュの方法については、作成したリポジトリのページにある「プッシュコマンドの表示」ボタンから参照可能
:::

# Fargate を用いた Keycloak 環境を AWS に構築する。
AWS環境についてはFargateを利用することも初めてだったこともあり、コンソールを利用してGUIベースで以下の構成を作成した。後々はCloudFormationでテンプレート化しておき、検証内容に応じてカスタマイズして爆速でKeycloakを構築できるようにしていきたい。
![VPC_image.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3333320/55061636-f3a5-1a3f-7e5a-c41ade908b54.png)

# VPC、ELB などの作成

### VPC関連
最近はVPCを作成する際に作成するリソースに「VPCなど」を選択すると、VPCサブネット、各種GWを一気に作成できるようになっている。今回はこちらを利用して上記の構成のVPC、サブネット、各種GWをサクッと作成。

### ELB
各コンテナにリクエストを分散するため、ALBを設置して外向けのエンドポイントとした。
ECS FargateへALBからリクエストを転送する際の注意点として、ターゲットグループのタイプをIP addressで作成する必要がある。これはECS Fargate利用時のタスクネットワークモードが`awsvpc`固定となる。awsvpcモードの場合、各コンテナがENIが付与されて管理されるようになるため、ターゲットグループもIPでの指定が必要となる。

### セキュリティグループ
セキュリティグループに関しては概ね以下辺りを開通できるように適宜設定が必要
 - コンテナ　⇔　RDS（3306）
 - コンテナ　⇔　ALB（8080）
 - コンテナ　⇔　コンテナ（7800）※S3_PING用
 - Any　　　 ⇔　ALB（80,443）

### IAMロール
ロールは以下のロールが最低限必要となる。
 - コンテナ(タスク)用のロール：S3への操作を可能とするポリシーを適用したロール
 - クラスタ用ロール：AmazonECSTaskExecutionRolePolicyを適用したロール


# Aurora(MySQL)上に作成および Keycloak 用データベースの作成
次にKeycloakが利用するデータベースとしてAuroraを作成する。こちらもRDSサービスのコンソール上からポチポチして作成する。検証用のため、開発用かつマルチAZはなしで問題ない。
Auroraが立ち上がったらMysqlのクライアントソフトなどから接続し、以下のコマンドを実行してKeycloak用のDBを作成する。


```.tsx:msqlコマンド
# DBを作成（keycloakという名称ので作成した例）
create database keycloak character set utf8 collate utf8_unicode_ci;

# DB用のユーザーを作成（keycloak_user：keycloak-passで作成した例）
create user 'keycloak_user' identified by 'keycloak-pass'

# 作成したDBへ権限付与
grant all privileges on keycloak.* to 'keycloak_user';
```

:::note info
MySQLデータベースへの接続はEC2のSSMやCloud9などでMysqlクライアントをインストールすると、AWS環境内で完結することができる。
:::

# S3_PING 用の S3 を作成
今回S3は各コンテナから一つのファイルへのRead/Write処理が発生するだけであるため、単純なプライベートバケットを一つ作成すればよい。

# ECS(クラスター、タスク定義、サービス)を作成
最後にECS Fargateの各種設定する。

### クラスター
以下の内容でクラスターを作成する。

|パラメータ名|設定内容|補足|
|---|---|---|
|クラスタ名|任意でOK||
|VPC|作成済VPC||
|サブネット|プライベートサブネット2つ指定|クラスタを配置したいサブネットを指定|
|名前空間|デフォルト|デフォルトでクラスタ名が設定される|
|インフラストラクチャ|Fargare|ここをEC2に変更するクラスタようにEC2インスタンスが起動される|


### タスク定義
以下の内容でタスク定義を作成する。
|パラメータ名　　　　　　　　　　　　　　　　　|設定内容|補足　　　　|
|---|---|---|
|タスク定義ファミリー|任意でOK||
|コンテナ|-||
|　　名前|任意でOK||
|　　イメージURI|ECRのリポジトリURI||
|　　ポートマッピング-1|-||
|　　　　コンテナポート|8080|Keycloakサービス用ポート|
|　　　　プロトコル|TCP||
|　　　　ポート名|適当でOK||
|　　　　アプリケーションプロトコル|HTTP||
|　　ポートマッピング-2|-||
|　　　　コンテナポート|7800|S3_PING用ポート|
|　　　　プロトコル|TCP||
|　　　　ポート名|適当でOK||
|　　　　アプリケーションプロトコル|None||
|　　環境変数|-||
|　　　　KEYCLOAK_ADMIN|admin|管理コンソールAdminアカウント|
|　　　　KEYCLOAK_ADMIN_PASSWORD|admin|管理コンソールAdminパスワード|
|　　　　KC_DB|mysql|DBの種類|
|　　　　KC_DB_URL|jdbc:mysql://db_domain:3306/msqlデータベース名|jdbc形式でDBの接続先を指定|
|　　　　KC_DB_USERNAME|keycloak_user|msqlデータベースユーザー名|
|　　　　KC_DB_PASSWORD|keycloak-pass|msqlデータベースユーザーパスワード|
|　　　　KC_HTTP_ENABLED|true|HTTPリスナを有効化|
|　　　　KC_HOSTNAME_STRICT|false|ホスト名検証を無効化|
|　　　　KC_HOSTNAME|ホスト名を設定|今回だとABLのドメイン|
|　　　　KC_ADMIN_HOSTNAME|ホスト名を設定|今回だとABLのドメイン|
|　　　　KC_CACHE|ispn|クラスタ構成を有効化|
|　　　　KC_CACHE_STACK|ec2|S3_PINGを利用|
|　　　　KC_HEALTH_ENABLED|true|ヘルスチェックURLの有効化|
|　　　　JAVA_OPTS_APPEND|-Djgroups.s3.region_name=S3リージョン -Djgroups.s3.bucket_name=S3バケット名|jgroupで利用するJAVAオプション|
|　　Dockers設定-オプション|||
|　　　　コマンド|start|kc.shの実行オプション|
|環境|-||
|　　タスクサイズ|-||
|　　　　CPU||本検証では1 vCPU|
|　　　　メモリ||本検証では2GB|
|　　タスクロール|作成済ロールを指定||
|　　タスク実行ロール|作成済ロールを指定||

:::note warn
タスクサイズのCPUやメモリは任意の値でOKだが、小さくしすぎるとKeycloakが正常に起動できないため、正常に起動しない場合は十分な値を設定すること。
:::

### サービス
以下の内容でサービスを作成する。
サービスの作成が完了すると定義に沿ってコンテナが自動的に起動される。
|パラメータ名　　　　　　| 設定内容|補足|
|---|---|---|
|環境|-||
|　　既存クラスター|作成済クラスターを指定||
|　　コンピューティングオプション|キャパシティプロバイダー戦略||
|　　キャパシティプロバイダ戦略|カスタムを使用(アドバンス)||
|　　キャパシティプロバイダ|FARGATE||
|　　ベース|1||
|　　ウェイト|1||
|デプロイ設定|-||
|　　アプリケーションタイプ|サービス|タスクにするとバッチジョブ的な起動となる|
|　　タスク定義|-||
|　　　　ファミリー|作成済タスク定義||
|　　　　リビジョン|利用するリビジョン|基本はLatestでOK|
|　　サービス名|任意||
|　　サービスタイプ|レプリカ||
|　　必要なタスク|2|タスクを削除するときはここを0にする|
|ネットワーキング|-||
|　　VPC|既存のVPCを指定||
|　　サブネット|プライベートサブネット2つを指定||
|　　セキュリティグループ|作成済セキュリティグループ||
|　　パブリックIP|OFF|今回はALB経由でのアクセスのため|
|ロードバランシング|-||
|　　ロードバランサーの種類|Application Load Balancer||
|　　Application Load Balancer|既存のロードバランサーを使用||
|　　ロードバランサー|作成済ロードバランサーを選択||
|　　ロードバランス用のコンテナの選択|8080ポートの方を指定||
|　　リスナー|既存のリスナーを使用||
|　　リスナー|作成済リスナーを指定||
|　　ターゲットグループ|既存のターゲットグループを選択||
|　　ターゲットグループ名|作成済ターゲットグループを選択|
|　　ヘルスチェックパス|/health||
|　　ヘルスチェック猶予期間|120|コンテナ起動時の初回ヘルスチェックまでの猶予時間|

:::note warn
ヘルスチェック猶予期間はコンテナの起動にかかる時間を確認して適宜変更すること。
:::

# 動作検証
まず、コンテナ起動時に2つのコンテナが正しくクラスタを実現できているか見ていく。
以下起動時のコンテナログの抜粋であるが、メンバーとして2つのコンテナのIPが出力されている。

```.txt:コンテナログ抜粋
2023-04-18 07:41:13,519 INFO [org.infinispan.LIFECYCLE] (jgroups-6,ip-192-168-141-217-53361) [Context=org.infinispan.COUNTER] ISPN100002: Starting rebalance with members [ip-192-168-146-54-55433, ip-192-168-141-217-53361], phase READ_OLD_WRITE_ALL, topology id 2
2023-04-18 07:41:13,526 INFO [org.infinispan.LIFECYCLE] (jgroups-6,ip-192-168-141-217-53361) [Context=org.infinispan.COUNTER] ISPN100010: Finished rebalance with members [ip-192-168-146-54-55433, ip-192-168-141-217-53361], topology id 2
```
また、S3に作成されたファイルを確認してみる。
こちらにも同様のIPで2つの情報が登録されており、正常にクラスタとして認識してS3_PINGにて通信ができていることが確認取れた。

```.txt:.listファイル
ip-192-168-146-54-55433 	07f2e6ee-582f-454a-924d-13e3bc9b4d69 	192.168.146.54:7800 	T
ip-192-168-141-217-53361 	1c165785-b671-4c0a-9f3a-56643e9d69c1 	192.168.141.217:7800 	F
```


次に、Keycloakのアプリの動作確認を行う。
アプリの動作確認には、Keycloakに付属するログインユーザの個人情報画面を利用する。

　1. ALBのDNS名+`/realms/master/account/#/personal-info`にアクセスし、ログイン画面を表示
　2. admin でログイン(パスワードも同じ)
　3. 個人情報画面が表示される
　4. ECSの実行中タスクを1つ強制ストップさせる
　5. 再ログインが発生しないで個人情報画面内を遷移できることを確認
　6. ECSにより自動的に再度コンテナが起動しきるのを待つ(CloudWatch logsでチェック)
　7. 最初からあったもう1つの実行中タスクを強制ストップさせる
　8. 再ログイン発生しないでプロフィール画面内を遷移できることを確認


上記のような操作をし、コンテナを1台ずつ落としても再ログイン操作が発生せずに操作可能なことが確認できたため、クラスタ構成が正常に動作していることが確認できた。

# 最後に
今回QuarkusベースのKeycloakでAWS上にクラスタ構成を構築してみたが、Keycloak自体の設定方法では特に難しい点はなく案外あっさり動いたという感想であった（その分AWSの設定でハマったけど。。。）

次の機会があれば、KubernetesやOpenShift利用してKeycloakを構築してECS比較しながら検証してみたい。

# 参考資料
　https://www.keycloak.org/server/containers
　https://www.keycloak.org/server/all-config
　https://github.com/jgroups-extras/jgroups-aws
　https://infinispan.org/docs/stable/titles/embedding/embedding.html#jgroups-extras-properties_cluster-transport
　https://qiita.com/wadahiro/items/0837729e7c57becbfd06
　https://qiita.com/yoonis/items/4f4a9df0f6f8e858bd4a