kubernetes - 将 Keycloak HA 集群部署到 kubernetes | Pod 没有发现彼此

标签 kubernetes google-kubernetes-engine keycloak high-availability keycloak-services

我正在尝试在 Kubernetes (GKE) 上部署 HA Keycloak 集群(2 个节点)。到目前为止,根据我从日志中推断出的情况,集群节点(pods)在所有情况下都无法相互发现。 Pod 启动且服务已启动但无法看到其他节点的位置。

组件

  • 在默认端口上使用 clusterIP 服务部署 PostgreSQL 数据库。
  • Keycloak 部署 2 个具有所需端口容器端口 8080、8443、相关 clusterIP 和 LoadBalancer 类型服务的节点以将服务公开到互联网

日志片段:

INFO  [org.infinispan.remoting.transport.jgroups.JGroupsTransport] (MSC service thread 1-4) ISPN000078: Starting JGroups channel ejb
INFO  [org.infinispan.remoting.transport.jgroups.JGroupsTransport] (MSC service thread 1-4) ISPN000094: Received new cluster view for channel ejb: [keycloak-567575d6f8-c5s42|0] (1) [keycloak-567575d6f8-c5s42]
INFO  [org.infinispan.remoting.transport.jgroups.JGroupsTransport] (MSC service thread 1-1) ISPN000094: Received new cluster view for channel ejb: [keycloak-567575d6f8-c5s42|0] (1) [keycloak-567575d6f8-c5s42]
INFO  [org.infinispan.remoting.transport.jgroups.JGroupsTransport] (MSC service thread 1-3) ISPN000094: Received new cluster view for channel ejb: [keycloak-567575d6f8-c5s42|0] (1) [keycloak-567575d6f8-c5s42]
INFO  [org.infinispan.remoting.transport.jgroups.JGroupsTransport] (MSC service thread 1-4) ISPN000079: Channel ejb local address is keycloak-567575d6f8-c5s42, physical addresses are [127.0.0.1:55200]
.
.
.
INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 15.0.2 (WildFly Core 15.0.1.Final) started in 67547ms - Started 692 of 978 services (686 services are lazy, passive or on-demand)
INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
INFO  [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990

正如我们在上面的日志中看到的,节点将自己视为唯一的容器/pod ID

尝试 KUBE_PING 协议(protocol)

我尝试使用 kubernetes.KUBE_PING 协议(protocol)进行发现,但它没有工作,并且调用了 kubernetes 向下 API。日志中出现 403 授权错误(下面是其中的一部分):

Server returned HTTP response code: 403 for URL: https://[SERVER_IP]:443/api/v1/namespaces/default/pods

此时,我能够登录到门户并进行更改,但它还不是 HA 集群,因为更改没有被复制并且 session 没有被保留,换句话说,如果我删除了 pod我正在使用我被重定向到另一个新 session (好像它是一个单独的节点)

尝试 DNS_PING 协议(protocol)

当我尝试 DNS_PING 时,情况有所不同,我没有 Kubernetes 向下 API 问题,但我无法登录。

详细来说,我能够正常访问登录页面,但是当我输入我的凭据并尝试登录页面时尝试加载,但让我回到登录页面,在这方面 pod 中没有日志。

以下是我在过去几天使用的一些引用资料:

我的 Yaml list 文件

Postgresql 部署

apiVersion: apps/v1
kind: Deployment
metadata:
  name: postgres
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:13
          imagePullPolicy: IfNotPresent
          ports:
          - containerPort: 5432
          env:
            - name: POSTGRES_PASSWORD
              value: "postgres"
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
---
apiVersion: v1
kind: Service
metadata:
  name: postgres
spec:
  selector:
    app: postgres
  ports:
  - port: 5432
    targetPort: 5432

Keycloak HA集群部署

apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  replicas: 2 
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
  selector:
    matchLabels:
      app: keycloak
  template:
    metadata:
      labels:
        app: keycloak
    spec:
      containers:
      - name: keycloak
        image: jboss/keycloak
        env:
            - name: KEYCLOAK_USER 
              value: admin
            - name: KEYCLOAK_PASSWORD 
              value: admin123
            - name: DB_VENDOR
              value: POSTGRES
            - name: DB_ADDR
              value: "postgres" 
            - name: DB_PORT
              value: "5432"
            - name: DB_USER
              value: "postgres"
            - name: DB_PASSWORD
              value: "postgres"
            - name: DB_SCHEMA
              value: "public"
            - name: DB_DATABASE
              value: "keycloak"
#            - name: JGROUPS_DISCOVERY_PROTOCOL
#              value: kubernetes.KUBE_PING
#            - name: JGROUPS_DISCOVERY_PROPERTIES
#              value: dump_requests=true,port_range=0,namespace=default
#              value: port_range=0,dump_requests=true
            - name: JGROUPS_DISCOVERY_PROTOCOL
              value: dns.DNS_PING
            - name: JGROUPS_DISCOVERY_PROPERTIES
              value: "dns_query=keycloak"
            - name: CACHE_OWNERS_COUNT
              value: '2'
            - name: CACHE_OWNERS_AUTH_SESSIONS_COUNT
              value: '2'
            - name: PROXY_ADDRESS_FORWARDING
              value: "true"
        ports:
            - name: http
              containerPort: 8080
            - name: https
              containerPort: 8443

---
apiVersion: v1
kind: Service
metadata:
  name: keycloak
  labels:
    app: keycloak
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: https
      port: 443
      targetPort: 8443
  selector:
    app: keycloak
---
apiVersion: v1
kind: Service
metadata:
  name: keycloak-np
  labels:
    app: keycloak
spec:
  type: LoadBalancer 
  ports:
    - name: http
      port: 80
      targetPort: 8080
    - name: https
      port: 443
      targetPort: 8443
  selector:
    app: keycloak

重要提示

  • 无论是否设置数据库,我都尝试了这两种协议(protocol)。
  • 上面的 yaml 包含我一次尝试过的所有发现协议(protocol)组合(评论的那些)

最佳答案

更新 2022-08-01:此配置适用于旧版 keycloak(或 16 及以下版本)。从 17 开始,Keycloak 迁移到 Quarkus 发行版,配置有所不同。

默认情况下,这些版本使用 DNS_PING 作为 JGroups 的发现机制(底层集群机制),但您仍然需要激活它。

你需要:

  • 一个 headless service 指向你的 keycloak pod( headless 服务只是一个普通服务,但有 ClusterIP: none )
  • env KC_CACHE_STACK=kubernetes(激活 kubernetes jgroup 配置)和 JAVA_OPTS_APPEND=-Djgroups.dns.query=<name-of-headless-service>(告诉它如何找到其他 keycloak pod)。

这样,在启动时,jgroups 将发出一个 dns 查询(例如:keycloak-headless.my_namespace.svc.cluster.local),响应将是与 headless 服务关联的所有 pod 的 IP。

然后 JGroups 将联系通信端口中的每个 IP 并建立集群。


KUBE_PING 的工作方式类似于在一个 Keycloak pod 中运行 kubectl get pods 以查找其他 Keycloak pod 的 IP,然后尝试一个接一个地连接到它们。除了 Keycloak 通过直接查询 Kubernetes API 而不是运行 kubectl 来做到这一点。

为此,它需要凭据来查询 API,基本上是一个访问 token 。

您可以直接传递您的 token ,如果您有它,但它不是很安全也不是很方便 (you can check other options and behavior here)。

Kubernetes 有一种非常方便的方法来注入(inject) token ,供 pod(或在该 pod 内运行的软件)使用以查询 API。 Check the documentation for a deeper look

该机制是创建一个服务帐户,授予它使用 RoleBinding 调用 API 的权限,并在 pod 配置中设置该帐户。

它的工作原理是将 token 作为文件安装在已知位置,硬编码并为所有 Kubernetes 客户端所期望。当客户端想要调用 API 时,它会在该位置查找 token 。


虽然不是很方便,但您可能会遇到更不方便的情况,即缺少创建 RoleBinding 的权限(在更严格的环境中有些常见)。

然后您可以要求管理员为您创建服务帐户和 RoleBinding,或者只是(非常不安全地)通过 kubectl get pod 环境变量传递您自己的用户 token (如果您能够在 Keycloak 的命名空间上执行 SA_TOKEN_FILE,则您拥有权限) .

使用 secret 或 configmap 创建文件,将其挂载到 pod 并将 SA_TOKEN_FILE 设置为该文件位置。请注意,此方法特定于 Keycloak。


如果您确实有权在集群中创建服务帐户和角色绑定(bind):

一个例子(未测试):

export TARGET_NAMESPACE=default

# convenient method to create a service account 
kubectl create serviceaccount keycloak-kubeping-service-account -n $TARGET_NAMESPACE

# No convenient method to create Role and RoleBindings
# Needed to explicitly define them.
cat <<EOF | kubectl apply -f -
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: keycloak-kubeping-pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]

---

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: keycloak-kubeping-api-access
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: keycloak-kubeping-pod-reader
subjects:
- kind: ServiceAccount
  name: keycloak-kubeping-service-account
  namespace: $TARGET_NAMESPACE

EOF

在部署时,您设置 serviceAccount:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: keycloak
spec:
  template:
    spec:
      serviceAccount: keycloak-kubeping-service-account
      serviceAccountName: keycloak-kubeping-service-account
      containers:
      - name: keycloak
        image: jboss/keycloak
        env:
#          ...
            - name: JGROUPS_DISCOVERY_PROTOCOL
              value: kubernetes.KUBE_PING
            - name: JGROUPS_DISCOVERY_PROPERTIES
              value: dump_requests=true
            - name: KUBERNETES_NAMESPACE
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: metadata.namespace
#          ...

dump_requests=true 将帮助您调试 Kubernetes 请求。最好在生产环境中使用 false。您可以使用 namespace=<yournamespace 而不是 KUBERNETES_NAMESPACE ,但这是 Pod 必须自动检测其运行所在的命名空间的便捷方式。

请注意,KUBE_PING 将找到命名空间中的所有 pod,而不仅仅是 keycloak pod,并将尝试连接到所有这些 pod。当然,如果你的其他 pod 不关心这个,也没关系。

关于kubernetes - 将 Keycloak HA 集群部署到 kubernetes | Pod 没有发现彼此,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70286956/

相关文章:

kubernetes - 如何在分布式系统中维护套接字或通信 channel

spring-security - spring security oauth2 客户端提供程序颁发​​者-uri 上的自签名证书

kubernetes - 如何将文件路径读入 Kubernetes 集群中的队列?

kubernetes - 使用Kubernetes secret 作为配置映射中的环境变量

azure - Kubernetes NodeLost/NotReady/高 IO 磁盘

docker - 使用 Kubernetes 在不同机器上运行 Docker 容器

ssl - Google 托管 SSL 证书卡在 FAILED_NOT_VISIBLE

java - 如何以编程方式创建 keycloak 客户端角色并分配给用户

Keycloak 使用默认角色 "uma_authorization"和 "offline_access"创建新领域。我不希望创建这些角色

kubernetes - 为什么 kubernetes HA 需要奇数个 master