トップ ブログ EKSを使ってクラスタ構成のKeycloakを構築してみた

EKSを使ってクラスタ構成のKeycloakを構築してみた

2025/03/05

本記事は、NRIエンジニアによって2023年12月16日にQiitaに投稿された記事です。

前回こちらの記事でECSを使ってKeycloakのクラスタ構成を検証してみたが、今回は同じAWSのコンテナ関連サービスであるEKSをつかってKeycloakのクラスタ構成を構築し、動作確認してみる。

本記事でやること

  1. EKS環境設定
  2. マニュフェスト作成
  3. デプロイ
  4. 動作検証

前提条件

  • Keycloak バージョン:21.1.1
  • コンテナイメージは公式のものをそのまま利用
  • JGroups によるノード間通信は「DNS_PING」を利用
  • Kubernetesのマニュフェストファイルは公式で案内されているものをベースにカスタマイズ

EKS環境設定

Cloud Shellの設定

今回はkubenetes操作用の端末としてCloud Shellを利用する。
こちらの記事を参考にさせていただき、以下のコマンドを実行してkubectlをインストールする。

CloudShell

# ディレクトリ作成
mkdir -p $HOME/.local/bin
cd $HOME/.local/bin

# kubectl のインストール
curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.18.13/bin/linux/amd64/kubectl
chmod +x kubectl

# ホームディレクトリに戻る
cd $HOME

これでKubernetesの操作に必要なツールをCloudShell上に準備することができた。

クラスターの作成

AWSマネージメントコンソール上からクラスタを作成していく。
EKSの画面でクラスターを選択するとクラスター一覧が表示され、その画面上にあるクラスターを作成ボタンを押下して作成を開始する。
各設定にて変更した部分を抜粋して以下に記載する。

クラスターを設定

  • 名前:任意のクラスタ名
  • kubernetesバージョン:1.27(デフォルトを選択)
  • クラスターサービスロール:
    AmazonEKSClusterPolicyAmazonEKSServicePolicyをアタッチしたロールを作成して選択

ネットワーキングを指定

  • VPC:事前に作成したVPCを指定
  • サブネット:事前に作成したサブネットを2つ指定
  • クラスターエンドポイントアクセス:パブリック

ログ記録の設定

今回はすべてOFFとした。

アドオンを選択

デフォルトでインストールとなっている以下のみ選択

  • kube-puroxy
  • CoreDNS
  • Amazon VPC CNI

選択したアドオン設定を構成する

特に変更せずデフォルトのバージョンを選択

各設定を入力・選択後に確認画面で作成ボタンを押下するとクラスターの作成が開始される。
クラスター作成には数分かかるため、しばらく完了するまで待つ。
正常にクラスターが作成されると以下のようにステータスがアクティブでクラスター一覧に表示される。

eksctlを利用してコマンドベースでクラスター作成することも可能。
eksctlを利用する場合はツールを操作環境へインストールする必要がある。

ノードグループの作成

ノードグループはノードのプロビジョニングとライフサイクル管理を自動化してくれるもので、EKSでノードを使うためにこちらを作成する。
先ほど作成したクラスターを選択して、クラスターの詳細画面でコンピューティングタブを選択し、ノードグループを追加ボタンを押下しノードグループの設定画面を開く。

ノードグループの各設定にて変更した部分を抜粋して以下に記載する。

ノードグループを設定

  • 名前:任意の名前を設定
  • ノードIAMロール:
    AmazonEKSWorkerNodePolicyAmazonEC2ContainerRegistryReadOnlyAWSAppSyncPushToCloudWatchLogsAmazonEKS_CNI_Policyをアタッチしたロールを作成して選択

ノードグループのコンピューティング設定

  • AMIタイプ:Amazon Linux (AL2_x86_64)
  • キャパシティタイプ:On-Demand
  • インスタンスタイプ:t3.medium
  • ディスクサイズ:20 GiB
  • ノードグループのスケーリング設定
    • 希望のサイズ:1 ノード
    • 最小サイズ:1 ノード
    • 最大サイズ:1 ノード
  • ノードグループの更新設定:数値 1 ノード
  • ノードグループのネットワーク設定:クラスター設定指定したものと同じサブネットを指定

各設定を入力・選択後に確認画面で作成ボタンを押下するとノードグループおよび指定したサイズのノードの作成が開始される。
こちらに関しても作成には数分かかるため、しばらく完了するまで待つ。

正常に作成されると上記のように1つのノードと1つのノードグループが表示される。

クラスターへ接続

Cloud Shellから作成したクラスターの操作ができるように設定する。こちらを参考にして、Cloud Shell上で以下のコマンドを実行する。

CloudShell

aws eks --region example_region update-kubeconfig --name cluster_name

正常にコマンドが実行できるとAdded new context ~のようなメッセージが表示され、クラスターの kubeconfig ファイルが作成または更新される。
また、以下のコマンドを実行してノードの情報がみれるか確認する。

CloudShell

kubectl get node

ここまでの設定がすべて正しくできていると以下のようにノードの情報を確認できる。

CloudShell

NAME                                              STATUS   ROLES    AGE   VERSION
ip-192-168-4-94.ap-northeast-1.compute.internal   Ready    <none>   57m   v1.27.1-eks-2f008fe

マニュフェスト作成

最初にマニュフェストファイルの全容は以下の通りで、こちらをkubectl applyすればALBからDBまですべて起動するようになっている。

keycloak.yaml

apiVersion: v1
kind: Service
metadata:
  name: mysql-server
spec:
  type: ClusterIP
  ports:
    - name: mysql
      port: 3306
      targetPort: 3306
      protocol: TCP
  selector:
    app: mysql-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-server
spec:
  selector:
    matchLabels:
      app: mysql-server
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql-server
    spec:
      containers:
        - image: mysql:5.7
          name: mysql
          resources:
          env:
            - name: MYSQL_USER
              value: keycloak_user
            - name: MYSQL_PASSWORD
              value: keycloak_password
            - name: MYSQL_ROOT_PASSWORD
              value: password
            - name: LANG
              value: C.UTF-8
          ports:
            - containerPort: 3306
              name: mysql
          volumeMounts:
            - name: mysql-server-initdb
              mountPath: /docker-entrypoint-initdb.d
            - name: mysql-server-conf
              mountPath: /etc/mysql/conf.d
      volumes:
        - name: mysql-server-initdb
          configMap:
            name: mysql-server-initdb-config
        - name: mysql-server-conf
          configMap:
            name: mysql-server-conf-config
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-server-initdb-config
data:
  createdb.sql: |
    CREATE DATABASE keycloak_db;
    GRANT ALL ON keycloak_db.* TO keycloak_user;
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-server-conf-config
data:
  custom.cnf: |
    [mysqld]
    character-set-server=utf8
---
apiVersion: v1
kind: Service
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  selector:
    app: keycloak
  type: NodePort
---
apiVersion: v1
kind: Service
metadata:
  name: keycloak-discovery
spec:
  clusterIP: None
  ports:
    - name: "http"
      port: 8080
      protocol: "TCP"
      targetPort: 8080
  selector:
    app: keycloak
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  replicas: 2
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
        - name: keycloak
          image: quay.io/keycloak/keycloak:21.1.1
          args: ["start"]
          env:
            - name: KEYCLOAK_ADMIN
              value: "admin"
            - name: KEYCLOAK_ADMIN_PASSWORD
              value: "admin"
            - name: KC_PROXY
              value: "edge"
            - name: KC_HTTP_ENABLED
              value: "true"
            - name: KC_HOSTNAME_STRICT
              value: "false"
            - name: KC_DB
              value: "mysql"
            - name: KC_DB_URL
              value: "jdbc:mysql://mysql-server:3306/keycloak_db"
            - name: KC_DB_USERNAME
              value: "keycloak_user"
            - name: KC_DB_PASSWORD
              value: "keycloak_password"
            - name: KC_CACHE
              value: "ispn"
            - name: KC_CACHE_STACK
              value: "kubernetes"
            - name: KC_HEALTH_ENABLED
              value: "true"
            - name: JAVA_OPTS_APPEND
              value: "-Djgroups.dns.query=keycloak-discovery.default.svc.cluster.local"
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /realms/master
              port: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: keycloak-ingress
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/subnets: subnet-xxx, subnet-xxx #your subnets
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS" :443}]'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:xxxxxx:certificate/xxxx #your cert arn
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: keycloak
                port:
                  number: 8080

非常に長いので個別に分けて説明していく。

MySQL

MySQLのコンテナとサービスを作成に関する記載は以下の通り。
MySQLはクラスター内でのみサービスを提供できれば良いため、サービスのタイプをClusterIPとしている。

kind: Service
metadata:
  name: mysql-server
spec:
  type: ClusterIP
  ports:
    - name: mysql
      port: 3306
      targetPort: 3306
      protocol: TCP
  selector:
    app: mysql-server
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-server
spec:
  selector:
    matchLabels:
      app: mysql-server
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: mysql-server
    spec:
      containers:
        - image: mysql:5.7
          name: mysql
          resources:
          env:
            - name: MYSQL_USER
              value: keycloak_user
            - name: MYSQL_PASSWORD
              value: keycloak_password
            - name: MYSQL_ROOT_PASSWORD
              value: password
            - name: LANG
              value: C.UTF-8
          ports:
            - containerPort: 3306
              name: mysql
          volumeMounts:
            - name: mysql-server-initdb
              mountPath: /docker-entrypoint-initdb.d
            - name: mysql-server-conf
              mountPath: /etc/mysql/conf.d
      volumes:
        - name: mysql-server-initdb
          configMap:
            name: mysql-server-initdb-config
        - name: mysql-server-conf
          configMap:
            name: mysql-server-conf-config

また、以下のようにConfigMap上にDBの初期設定とクエリを登録して、MySQL起動時に初期DBと初期ユーザーを作成するように設定。

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-server-initdb-config
data:
  createdb.sql: |
    CREATE DATABASE keycloak_db;
    GRANT ALL ON keycloak_db.* TO keycloak_user;
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql-server-conf-config
data:
  custom.cnf: |
    [mysqld]
    character-set-server=utf8

Keycloak

Keycloakのコンテナとサービスを作成に関する記載は以下の通り。
クラスター構成を確認するため、replicasの設定を2にしてKeycloakの2台起動するように設定。
また、Keycloakノード間通信は「DNS_PING」を利用するため、KC_CACHE_STACKkubernetesJAVA_OPTS_APPENDjgroups.dns.queryを設定。こちらに設定する内容は次に説明するHeadless servicesを利用する。

apiVersion: v1
kind: Service
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  ports:
    - name: http
      port: 8080
      targetPort: 8080
  selector:
    app: keycloak
  type: NodePort
---yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  replicas: 2
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
        - name: keycloak
          image: quay.io/keycloak/keycloak:21.1.1
          args: ["start"]
          env:
            - name: KEYCLOAK_ADMIN
              value: "admin"
            - name: KEYCLOAK_ADMIN_PASSWORD
              value: "admin"
            - name: KC_PROXY
              value: "edge"
            - name: KC_HTTP_ENABLED
              value: "true"
            - name: KC_HOSTNAME_STRICT
              value: "false"
            - name: KC_DB
              value: "mysql"
            - name: KC_DB_URL
              value: "jdbc:mysql://mysql-server:3306/keycloak_db"
            - name: KC_DB_USERNAME
              value: "keycloak_user"
            - name: KC_DB_PASSWORD
              value: "keycloak_password"
            - name: KC_CACHE
              value: "ispn"
            - name: KC_CACHE_STACK
              value: "kubernetes"
            - name: KC_HEALTH_ENABLED
              value: "true"
            - name: JAVA_OPTS_APPEND
              value: "-Djgroups.dns.query=keycloak-discovery.default.svc.cluster.local"
          ports:
            - name: http
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /realms/master
              port: 8080

Headless services

Headless servicesでは、各Pod の IP アドレスを DNS ラウンドロビンで返却できるため、こちらをKeycloakノード間通信は「DNS_PING」に利用する。
Headless servicesはclusterIP: Noneを設定することで利用可能となる。

apiVersion: v1
kind: Service
metadata:
  name: keycloak-discovery
spec:
  clusterIP: None
  ports:
    - name: "http"
      port: 8080
      protocol: "TCP"
      targetPort: 8080
  selector:
    app: keycloak

Ingress

IngressはAWSのALBを利用するため、ingressClassName: albを設定。
また、ALBをHTTPSで利用可能とするため、事前に発行した証明書をACMへインポートしておきマニュフェストで指定している。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: keycloak-ingress
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/subnets: subnet-xxx, subnet-xxx #your subnets
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS" :443}]'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:ap-northeast-1:xxxxxx:certificate/xxxx #your cert arn
spec:
  ingressClassName: alb
  rules:
    - http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: keycloak
                port:
                  number: 8080

デプロイ

AWS Load Balancer Controller アドオンのインストール

ALBを使ってKubernetesのIngressを作成する場合にこちらのアドオンをインストールしておく必要があるため、AWS公式の手順に沿ってAWS Load Balancer Controller アドオンをインストールする。

デプロイ

作成したマニュフェストファイルをCloud Shell上にアップロード(もしくはShell上で作成)してデプロイする。
以下のコマンドを実行することでマニュフェストに記載した内容を順次デプロイされる。

CloudShell

kubectl apply -f keycloak.yaml

マニュフェストの内容が正しい形式であれば、以下のようにそれぞれが作成される旨の出力が返ってくる。

CloudShell

service/mysql-server created
deployment.apps/mysql-server created
configmap/mysql-server-initdb-config created
configmap/mysql-server-conf-config created
service/keycloak created
service/keycloak-discovery created
deployment.apps/keycloak created
ingress.networking.k8s.io/keycloak-ingress created

正常にデプロイされているか以下のコマンドを実行して確認する。

CloudShell

kubectl get all

以下のようにすべてのPODのSTATUSがRunningになっていればIngress(ALB)以外のデプロイはOK。

CloudShell

NAME                                READY   STATUS    RESTARTS   AGE
pod/keycloak-7d8f8949df-67894       1/1     Running   0          2m6s
pod/keycloak-7d8f8949df-zcx2g       1/1     Running   0          2m6s
pod/mysql-server-6c8d866586-s42rb   1/1     Running   0          2m6s

NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/keycloak             NodePort    10.100.3.101    <none>        8080:32549/TCP   2m6s
service/keycloak-discovery   ClusterIP   None            <none>        8080/TCP         2m6s
service/kubernetes           ClusterIP   10.100.0.1      <none>        443/TCP          6h28m
service/mysql-server         ClusterIP   10.100.106.59   <none>        3306/TCP         2m6s

NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/keycloak       2/2     2            2           2m6s
deployment.apps/mysql-server   1/1     1            1           2m6s

NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/keycloak-7d8f8949df       2         2         2       2m6s
replicaset.apps/mysql-server-6c8d866586   1         1         1       2m6s

次にIngressの状況を以下のコマンドで確認する

CloudShell

kubectl get ingress

以下のように表示されればIngressの作成も成功。ただし、ALBのサービスがActiveになるまでには少し時間がかかるため、適宜コンソール上でALBがActiveになっているかは確認が必要。

CloudShell

NAME               CLASS   HOSTS   ADDRESS                                                                       PORTS   AGE
keycloak-ingress   alb     *       k8s-default-keycloak-xxxxxxxxxx-xxxxxxxxxx.ap-northeast-1.elb.amazonaws.com   80      5m29s

動作検証

前回のECSの時と同様に確認していく。
まず、コンテナ起動時に2つのコンテナが正しくクラスタを実現できているか確認。
以下のコマンドを実行してコンテナの起動時のログを確認していく。

CloudShell

kubectl log コンテナ名

以下が出力されたコンテナログの抜粋であり、メンバーとして2つのコンテナ名が出力されている。

CloudShell

2023-06-02 08:14:38,055 INFO  [org.infinispan.LIFECYCLE] (jgroups-16,keycloak-7d8f8949df-67894-17810) [Context=offlineSessions] ISPN100002: Starting rebalance with members [keycloak-7d8f8949df-67894-17810, keycloak-7d8f8949df-zcx2g-58694], phase READ_OLD_WRITE_ALL, topology id 4
2023-06-02 08:14:38,057 INFO  [org.infinispan.LIFECYCLE] (jgroups-16,keycloak-7d8f8949df-67894-17810) [Context=offlineSessions] ISPN100010: Finished rebalance with members [keycloak-7d8f8949df-67894-17810, keycloak-7d8f8949df-zcx2g-58694], topology id 4

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

 1. ALBのDNS名+/realms/master/account/#/personal-infoにアクセスし、ログイン画面を表示
 2. admin でログイン(パスワードも同じ)
 3. 個人情報画面が表示される
 4. マニュフェストファイルのKeyclaokのreplicasの値を1に変更してkubectl applyを実行する。
 5. 再ログインが発生しないで個人情報画面内を遷移できることを確認
 6. マニュフェストファイルのKeyclaokのreplicasの値を2に戻してkubectl applyを実行する。
 7. 再ログインが発生しないで個人情報画面内を遷移できることを確認
 8. マニュフェストファイルのKeyclaokのreplicasの値を1に変更してkubectl applyを実行する。
 9. 再ログイン発生しないでプロフィール画面内を遷移できることを確認

上記のような操作をし、Podのreplicasを変更しても再ログイン操作が発生せずに操作可能であり、クラスタ構成が正常に動作していることが確認できた

お気軽にお問い合わせください
オープンソースに関するさまざまな課題、OpenStandiaがまるごと解決します。
下記コンテンツも
あわせてご確認ください。