GKE に 冗長構成の Keycloak を構築して HTTPS で公開する
こんにちは!クラウドエースの kazz です。
この記事では 冗長構成の Keycloak を Google Kubernetes Engine(以下、GKE)上で構築し、HTTPS で公開する手順を解説します。
前提条件
- gcloud コマンドを実行できる環境がある
- DNS レコードの設定ができるドメインを取得している
システム構成
冗長構成にすることで、いずれかのサーバーが停止しても、他のサーバーで処理を引き継いで継続できるため、高い可用性を維持できます。
準備
GKE に Keycloak を構築する前に必要なリソースの準備をします。
証明書
GKE の Gateway で利用する SSL 証明書を作成します。
gcloud compute ssl-certificates create keycloak-cert \
--domains=YOUR_DOMAIN
SQL
Keycloak の情報を保持するための Database(以下、DB)を作成します。
今回は Cloud SQL の MySQL を利用します。
gcloud sql instances create keycloak-sql \
--region=asia-northeast1 \
--database-version=MYSQL_8_0 \
--edition=ENTERPRISE \
--tier=db-g1-small \
--root-password=password
SQL インスタンスを作成できたら、データベースを作成します。
gcloud sql databases create keycloak-db --instance=keycloak-sql
Keycloak から接続する際に使用するユーザーを作成します。
gcloud sql users create keycloak \
--instance=keycloak-sql \
--password=password
Workload Identity の設定
GKE から Cloud SQL への接続は、Cloud SQL Auth Proxy を実行して接続します。
Workload Identity で、Pod が使用する Service Account(以下、SA)が SQL 管理者の権限を利用できるよう準備をしておきます。
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
--role="roles/cloudsql.admin" \
--member="principal://iam.googleapis.com/projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/YOUR_PROJECT_ID.svc.id.goog/subject/ns/default/sa/keycloak-sa"
GKE クラスタ作成
Autopilot クラスタを作成します。
Standard クラスタを使用する場合は、Workload Identity がデフォルトで有効になっていないことに注意してください。
gcloud container clusters create-auto keycloak-cluster --region=asia-northeast1
作成できたら GKE クラスタに接続しておきましょう。
gcloud container clusters get-credentials keycloak-cluster --region asia-northeast1
マニフェスト作成
1 つのマニフェストファイルにしてしまうと非常に長くなるので、分割しながら解説していきます。
Gateway + HTTPRoute
Gateway と HTTPRoute を作成し、ロードバランサを通してインターネットから Keycloak へ HTTPS アクセスできるようにします。
HTTPRoute の hostnames
は、準備で作成した証明書と同じドメインを設定してください。
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: keycloak-gateway
spec:
gatewayClassName: gke-l7-global-external-managed
listeners:
- name: https
protocol: HTTPS
port: 443
tls:
mode: Terminate
options:
networking.gke.io/pre-shared-certs: keycloak-cert
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: keycloak
labels:
gateway: keycloak-gateway
spec:
parentRefs:
- name: keycloak-gateway
hostnames:
- "YOUR_DOMAIN"
rules:
- backendRefs:
- name: keycloak
port: 8080
HealthCheckPolicy
Keycloak はヘルスチェックを有効化すると、デフォルトでポート 9000 に専用のエンドポイントを公開します。
apiVersion: networking.gke.io/v1
kind: HealthCheckPolicy
metadata:
name: healthcheck
spec:
default:
checkIntervalSec: 15
timeoutSec: 15
healthyThreshold: 1
unhealthyThreshold: 2
config:
type: HTTP
httpHealthCheck:
port: 9000
requestPath: /health
targetRef:
group: ""
kind: Service
name: keycloak
Service
Keycloak のサービスに加えて、Headless Service を作成します。
Headless Service が必要な理由
Keycloak は Infinispan を使用することで、複数のインスタンス間でキャッシュを分散することが可能です。この Infinispan は JGroups を利用して複数の Keycloak インスタンス間で通信を行います。
Kubernetes における JGroups では、クラスタのメンバーを検出するために DNS_PING プロトコルを使用します。このプロトコルは、DNS Aレコードや SRV レコードを利用して、ポッドの状態やクラスタのメンバーを確認します。
Kubernetes には Headless Service という、ClusterIP を持たないサービスがあります。このサービスを使って名前解決を行うと、各ポッドのアドレスを得ることができます。
つまり、Headless Service を作成することによって、JGroups が DNS を使用してクラスタメンバーを探せるようになり、Infinispan で分散キャッシュができるようになります。
apiVersion: v1
kind: Service
metadata:
name: keycloak
labels:
app: keycloak
spec:
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app: keycloak
type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
name: keycloak-headless-service
spec:
clusterIP: None
ports:
- name: http
port: 8080
targetPort: 8080
selector:
app: keycloak
ServiceAccount
Workload Identity で使用する SA を作成します。
apiVersion: v1
kind: ServiceAccount
metadata:
name: keycloak-sa
Deployment
最後に Deployment を作成します。
KC_HOSTNAME
と cloud-sql-proxy コンテナの args にある YOUR_PROJECT_ID は各々の値に変更してください。
Kubernetes で冗長構成を実装する際のポイント
-
KC_CACHE
をispn
にすることで、キャッシュとして Infinispan を使用し、分散キャッシュ機能を有効にします。
デフォルトでispn
になっていますが、今回は明示的に設定しています。 -
KC_CACHE_STACK
をkubernetes
にすることで、Infinispan は JGroups の DNS_PING プロトコルを使用して、クラスタ内の他の Keycloak インスタンスを 探します。 -
JAVA_OPTS_APPEND
に Headless Service を設定することで、JGroups は指定した Headless Service を通じて、クラスタ内の Keycloak インスタンスを DNS で検出します。
-Djgroups.dns.query={headless service name}.{namespace}.svc.cluster.local
サイドカー パターン の Cloud SQL Auth Proxy
Cloud SQL に接続するために、サイドカー で Cloud SQL Auth Proxy を実行します。
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
labels:
app: keycloak
spec:
replicas: 3
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
serviceAccountName: keycloak-sa
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:26.0.0
command: ["/bin/sh"]
args:
- "-c"
- |
exec /opt/keycloak/bin/kc.sh start
env:
- name: KEYCLOAK_ADMIN
value: "admin"
- name: KEYCLOAK_ADMIN_PASSWORD
value: "admin"
- name: KC_PROXY
value: "edge"
- name: KC_HOSTNAME
value: "https://YOUR_DOMAIN"
- name: KC_HEALTH_ENABLED
value: "true"
- name: KC_HTTP_ENABLED
value: "true"
- name: KC_DB
value: "mysql"
- name: KC_DB_URL
value: "jdbc:mysql://127.0.0.1:3306/keycloak-db"
- name: KC_DB_USERNAME
value: "keycloak"
- name: KC_DB_PASSWORD
value: "password"
- name: KC_CACHE
value: "ispn"
- name: KC_CACHE_STACK
value: "kubernetes"
- name: JAVA_OPTS_APPEND
value: "-Djgroups.dns.query=keycloak-headless-service.default.svc.cluster.local"
ports:
- name: http
containerPort: 8080
startupProbe:
httpGet:
path : /health/started
port: 9000
periodSeconds: 120
failureThreshold: 30
livenessProbe:
httpGet:
path: /health/live
port: 9000
readinessProbe:
httpGet:
path: /health/ready
port: 9000
- name: cloud-sql-proxy
image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.14.1
args:
- "--port=3306"
- "YOUR_PROJECT_ID:asia-northeast1:keycloak-sql"
- "--structured-logs"
securityContext:
runAsNonRoot: true
デプロイ
それでは作成したマニフェストを apply します。
上記で分割したマニフェストを keycloak.yaml にまとめてから実行します。ファイルを分けたい方はそれぞれ apply してください。
kubectl apply -f keycloak.yaml
2、3 分待った後、正常にデプロイできたか確認します。
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/keycloak-567854899c-6n6vq 2/2 Running 0 2m26s
pod/keycloak-567854899c-sxfbd 2/2 Running 0 2m26s
pod/keycloak-567854899c-zgdsd 2/2 Running 0 2m27s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/keycloak ClusterIP 34.0.0.0 <none> 8080/TCP 2m27s
service/keycloak-headless-service ClusterIP None <none> 8080/TCP 2m27s
service/kubernetes ClusterIP 34.0.0.0 <none> 443/TCP 34m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/keycloak 3/3 3 3 2m27s
NAME DESIRED CURRENT READY AGE
replicaset.apps/keycloak-567854899c 3 3 3 2m27s
Keycloak が正常にデプロイできていることを確認したら、Gateway に付与された IP アドレスの値を調べます。
$ kubectl get gateway
NAME CLASS ADDRESS PROGRAMMED AGE
keycloak-gateway gke-l7-global-external-managed 34.0.0.0 True 5m19s
この IP アドレスをドメインの A レコードとして DNS に設定します。
レコードを設定してしばらく待つと、証明書の状態が ACTIVE になります。
ACTIVE にならないときは、一旦証明書をコンソールなどから削除して再作成してみてください。
$ gcloud compute ssl-certificates describe keycloak-certs --format="get(managed.status)"
ACTIVE
動作確認
証明書に設定したドメインにアクセスします。
正常にデプロイできていたら、以下のような画面が表示されます。
KEYCLOAK_ADMIN
と KEYCLOAK_ADMIN_PASSWORD
に設定した値を入力すると管理コンソールへ入れます。
冗長構成の確認
最後に、冗長構成がうまくできているかを確認します。
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
keycloak-567854899c-6n6vq 2/2 Running 0 14m
keycloak-567854899c-sxfbd 2/2 Running 0 14m
keycloak-567854899c-zgdsd 2/2 Running 0 14m
どれかの Pod のログを確認します。
kubectl logs keycloak-567854899c-6n6vq -c keycloak
大量のログが出ていますが、以下のようなログが出ていたらクラスタリングはうまくいっています。
2025-01-30 03:08:54,865 INFO [org.infinispan.CLUSTER] (jgroups-10,keycloak-567854899c-sxfbd-11327) ISPN000093: Received new, MERGED cluster view for channel ISPN: MergeView::[keycloak-567854899c-sxfbd-11327|1] (3) [keycloak-567854899c-sxfbd-11327, keycloak-567854899c-zgdsd-44294, keycloak-567854899c-6n6vq-8174], 3 subgroups: [keycloak-567854899c-zgdsd-44294|0] (1) [keycloak-567854899c-zgdsd-44294], [keycloak-567854899c-6n6vq-8174|0] (1) [keycloak-567854899c-6n6vq-8174], [keycloak-567854899c-sxfbd-11327|0] (1) [keycloak-567854899c-sxfbd-11327]
このログには、Infinispan で以下のクラスタがマージされたことが示されています。
- keycloak-567854899c-sxfbd-11327
- keycloak-567854899c-zgdsd-44294
- keycloak-567854899c-6n6vq-8174
これにより、 Keycloak インスタンスが 1 つのクラスタとして動作を開始したことが確認できます。
おわりに
本記事では GKE に 冗長構成の Keycloak を構築する手順を紹介しました。
Headless Service 周りが少し複雑ですが、冗長構成にすることで高い可用性を確保できます。
この手順が、Keycloak の構築に役立てば幸いです。
Discussion