🗝️

Keycloak on Kubernetes

2022/09/16に公開

はじめに

こんにちは。
フォルシア株式会社エンジニアの籏野です。

この度新規アプリを構築するにあたって、認証を通してからアプリにアクセスできるようにする必要が出てきました。
認証アプリにはKeycloakを利用し、Kubernetes(EKS)上にアプリをデプロイしています。
Kubernetes上にKeycloakアプリをデプロイするにあたり対応した内容を紹介したいと思います。

前準備

データベースの用意

Keycloakを利用するには、ユーザー情報等を保持しておくためのDBを用意する必要があります。
今回はAWSのマネージドサービスを利用できるため、CloudFormationを使ってAmazonRDSのインスタンスをサクッと用意しました。
※本番アプリで利用する際には暗号化設定等適宜行ってください。

関連する部分を抜粋すると以下のようになります。

# RDSインスタンス作成のための設定ファイル
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  BaseName:
    Type: String
    Default: keycloak
  DBMasterUserName:
    Type: String
    Default: postgres
    NoEcho: true
  DBPassword:
    Default: "dbpassword"
    NoEcho: true
    Type: String
    MinLength: 8
    MaxLength: 41
    AllowedPattern: "[a-zA-Z0-9]*"
    ConstraintDescription: "must contain only alphanumeric characters."
Resources:
  DBInstance:
    Type: "AWS::RDS::DBInstance"
    DeletionPolicy: "Delete"
    Properties:
      DBInstanceIdentifier: !Sub ${BaseName}-DB
      Engine: postgres
      EngineVersion: 13.4
      DBInstanceClass: db.t3.micro
      AllocatedStorage: 20
      StorageType: gp2
      MasterUsername: !Ref DBMasterUserName
      MasterUserPassword: !Ref DBPassword
      DBSubnetGroupName: !Ref DBSubnetGroup
      PubliclyAccessible: false
      MultiAZ: true
      AutoMinorVersionUpgrade: false
      DBParameterGroupName: !Ref DBParameterGroup
      VPCSecurityGroups:
        - !Ref DBSecurityGroup
      BackupRetentionPeriod: 7
      Tags: 
        - Key: "Name"
          Value: !Sub ${BaseName}-DB

  DBSubnetGroup:
    Type: "AWS::RDS::DBSubnetGroup"
    Properties: 
      DBSubnetGroupName: !Sub "${BaseName}-DB-SUBNET"
      DBSubnetGroupDescription: "-"
      # EKS利用のために作成したサブネットIDを指定
      SubnetIds: 
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2
        - !Ref PrivateSubnet3

  DBParameterGroup:
    Type: "AWS::RDS::DBParameterGroup"
    Properties:
      Family: !Sub "Postgres13"
      Description: !Sub "${BaseName}-DB-PARAM"

  DBSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      GroupDescription: !Sub ${BaseName}-DB-SG
      # EKSが利用するVPCのIDを指定
      VpcId: !Ref EksWorkVPC
      Tags:
        - Key: 'Name'
          Value: !Sub ${BaseName}-DB-SG
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: "5432"
          ToPort: "5432"
          # EKSから接続できるようにIp範囲を指定
          CidrIp: 172.25.0.0/16
    

管理者情報の生成

Keycloakをコンテナで立ち上げる際には、環境変数で管理者ID/パスワードを設定できます。
環境変数はKubernetesのマニフェストファイルに書くことになりますが、マニフェストファイルに直接ログイン情報を記載してリポジトリに登録してしまうのはあまりよくありません。
今回はKubernetesのsecretを用いてKubernetes上にログイン情報を保持しておく形にしました。

※Secretに登録している情報はbase64でエンコードされているだけの値になります。
今回作成するアプリではKubernetesリソースにアクセスできるメンバーを制限しているので十分と判断しましたが、構築するアプリの状況に応じて適切に管理しましょう。

以下のようなコマンドを叩けばsecretが作成できます。

# KEYCLOAK_USERNAME/KEYCLOAK_PASSWORDは任意の値
kubectl create secret generic keycloak-secret \
      --from-literal=username=${KEYCLOAK_USERNAME} \
      --from-literal=password=${KEYCLOAK_PASSWORD}

Keycloakの設定

Keycloakを本番運用する際には、システム全体の可用性を確保するため冗長構成を取ることになります。
この時ユーザーのログイン状態を保持するセッション情報は、KeycloakのAPサーバーであるWildFlyに搭載されたJGroupsを用いて各ホスト間で共有されるようです。
JGroupsを用いてセッション情報を共有するには、Keycloakが動作する各サーバーのIPを読み込ませクラスタを構成する必要があります。
EC2などでオンデマンド環境に構築する場合には立ち上げたEC2のIPを調べればよいので特に問題はありません。
ただ、Kubernetes上にKeycloakを立ち上げる場合には新しいポッドが次々に立ち上がっていくため、IPが可変となってしまいます。

色々調べてみると、KubernetesのHeadless Serviceと呼ばれるものを利用する例が紹介されていました。
Headless Searviceは立ち上がったポッドの個々のIPを返してくれるServiceのようです。
これを利用することにより、Keycloakが動作する各ポッドのIPが取得できるのですね。
というわけでマニフェストファイルは以下のようになりました。

# Keycloakアプリのマニフェストファイル
---
# Headless Service
apiVersion: v1
kind: Service
metadata:
  name: keycloak-headless-svc
spec:
  type: ClusterIP
  selector:
    app: keycloak
  ports:
  - port: 8080
    targetPort: 8080
  clusterIP: None
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  selector:
    matchLabels:
      app: keycloak
  replicas: 2
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
      - name: keycloak
        image: jboss/keycloak:16.1.1
        imagePullPolicy: Always
        ports:
          - name: http
            containerPort: 8080
          - name: jgroups
            containerPort: 7600
        readinessProbe:
          httpGet:
            path: /auth/
            scheme: HTTP
            port: 8080
        env:
          - name: TZ
            value: Asia/Tokyo
          - name: DB_VENDOR
            value: postgres
          - name: DB_DATABASE
            value: keycloak
          - name: DB_USER
            value: keycloak
          - name: DB_PASSWORD
            value: keycloak
          - name: DB_ADDR
            # 前準備で作成したRDSのホスト名を指定
            value: keycloak.ap-northeast-1.rds.amazonaws.com
          - name: DB_PORT
            value: "5432"
          - name: KEYCLOAK_USER
            valueFrom:
              secretKeyRef:
                name: keycloak-secret
                key: username
          - name: KEYCLOAK_PASSWORD
            valueFrom:
              secretKeyRef:
                name: keycloak-secret
                key: password
          - name: PROXY_ADDRESS_FORWARDING
            value: "true"
          - name: JGROUPS_DISCOVERY_PROTOCOL
            value: dns.DNS_PING
          - name: JGROUPS_DISCOVERY_PROPERTIES
            value: "keycloak-headless-svc"

動作チェック

上記マニフェストファイルを読み込ませて起動ログを見てみます。
先に立ち上がったポッドのログには以下のように表示されていました。

...
13:36:14,373 INFO  [org.infinispan.CLUSTER] (ServerService Thread Pool -- 56) ISPN000094: Received new cluster view for channel ejb: [keycloak-5c4598646b-zm84w|0] (1) [keycloak-5c4598646b-zm84w]
...

この時点では1つしかポッドが立ち上がっていないのでクラスタに参加しているポッドは1つのようです。

では、2つ目のpodも見てみましょう。

...
13:39:02,840 INFO  [org.infinispan.CLUSTER] (ServerService Thread Pool -- 60) ISPN000094: Received new cluster view for channel ejb: [keycloak-5c4598646b-zm84w|1] (2) [keycloak-5c4598646b-zm84w, keycloak-5c4598646b-kzjf9]
...

無事複数のポッドがクラスタに参加していそうです!
1つ目のポッドの方にも新たなポッドがクラスタに参加したログが出ていました。

...
13:39:01,569 INFO  [org.infinispan.CLUSTER] (thread-7,ejb,keycloak-5c4598646b-zm84w) ISPN100000: Node keycloak-5c4598646b-kzjf9 joined the cluster
...

最後に

今回はKubernetes(EKS)上にKeycloakを構築する際の一例を紹介しました。

実際のアプリではさらにApacheのmod_auth_openidcを用いてKeycloakと連携したのですが、その時にも今回のような冗長構成の場合の注意点がありました。
この時の経験談もまた改めて記事にしたいと思います。

FORCIA Tech Blog

Discussion