AWSクラウドにKeyCloakをデプロイするためにこれらのピットを踏みましたか?

バックグラウンド

当社は、フロントエンドアプリケーションとバックエンドマイクロサービスに統合された認証と認証サービスを提供する必要があり、Oauth2標準をサポートする必要があり、RBAC(ロールベースのアクセス制御)サポート、つまりロールベースのアクセス制御を提供する必要があります。マルチクラウドもサポートする必要があります調査と選択の結果、上記の要件を達成するためにKeyCloakを使用することが最終的に決定されました。

KeyCloakについて

Keycloakは、シングルサインオン(SSO)機能を提供し、OpenID Connect、OAuth 2.0、およびSAML2.0標準プロトコルをサポートするオープンソースのIDおよびアクセス制御ソフトウェアです。Keycloakは、ログイン、登録、アカウント管理などのためのカスタマイズ可能なユーザーインターフェイスを提供します。2018年3月の時点で、RedHatはこのJBossコミュニティプロジェクトをRH-SSO製品のアップストリームプロジェクトとして管理しています。概念的には、このツールの目的は、コーディングをほとんどまたはまったく行わずに、アプリケーションとサービスをより安全にすることです。

インフラストラクチャー

image.png

上の図は、AWSにKeyCloakをデプロイするときに使用したアーキテクチャ図です。AmazonFargateサービスを使用してAmazonECSクラスターを実行します。Amazon Fargateは、コンテナー向けのサーバーレスコンピューティングサービスです。サーバーを管理する必要はなく、アプリケーションを分離し、各アプリケーションに個別にリソースを割り当て、セキュリティを向上させることができます。高いシステム可用性を確保するために、異なるAZ(アベイラビリティーゾーン)にあるAmazon ECSサービスで2つのタスクが定義されているため、一方のタスクがサービスの提供に失敗しても、もう一方のタスクは引き続きサービスを提供します。外部リクエストは、ALB(Application Load Balancer)を介してECSにアクセスします。データベースタイプはAmazonRDSforMySQLです。

Amazon ECSは、DockerをサポートしてKeycloakのコンテナーイメージをデプロイおよび実行します。JBOSSコミュニティが提供するミラーを使用しています。バージョンは16.1.1です。

基本構成

データベース構成

KeyCloakはデフォルトでH2データベースを使用します。AmazonRDSforMySQLを使用するには、イメージの説明を参照して、環境変数に次の設定を追加します。

ジェネリック変数名は、任意のデータベースタイプを構成するために使用できます。デフォルトは、データベースによって異なる場合があります。

  • DB_ADDR:データベースのホスト名を指定します(オプション)。postgresの場合のみ、代替ホストをフェイルオーバーするために、コンマで区切られたホスト名のリストを提供できます。ホスト名は、ホストのみ、またはホストとポートのペアにすることができます(例:host1、host2またはhost1:5421、host2:5436またはhost1、host2:5000)。また、keycloakはDB_PORT(指定されている場合)をポートのないホストに追加します。それ以外の場合は、デフォルトのポート5432を、ポートのないアドレスにのみ追加します。
  • DB_PORT:データベースのポートを指定します(オプション、デフォルトはDBベンダーのデフォルトポートです)
  • DB_DATABASE: Specify name of the database to use (optional, default is keycloak).
  • DB_USER: Specify user to use to authenticate to the database (optional, default is ``).
  • DB_PASSWORD: Specify user's password to use to authenticate to the database (optional, default is ``).

开放统计信息

为了向ALB和ECS报告容器是否运行正常,需要打开查看统计信息的Endpoint,按照镜像的说明文档,我们设置了 KEYCLOAK_STATISTICS=all

Keycloak image can collect some statistics for various subsystem which will then be available in the management console and the /metrics endpoint. You can enable it with the KEYCLOAK_STATISTICS environment variables which take a list of statistics to enable.

集群配置

按照上面架构图的设计,我们会同时运行多个KeyCloak实例,而这些实例需要互相识别,以组成一个集群,共享内存数据。参照镜像说明文档,我们设置了一下环境变量,其中JGROUPS_DISCOVERY_PROTOCOL 设为 dns.DNS_PING

Clustering

Replacing the default discovery protocols (PING for the UDP stack and MPING for the TCP one) can be achieved by defining some additional environment variables:

  • JGROUPS_DISCOVERY_PROTOCOL - name of the discovery protocol, e.g. dns.DNS_PING
  • JGROUPS_DISCOVERY_PROPERTIES - an optional parameter with the discovery protocol properties in the following format: PROP1=FOO,PROP2=BAR
  • JGROUPS_TRANSPORT_STACK - an optional name of the transport stack to use udp or tcp are possible values. Default: tcp

导入初试设定

有些初始设置,我们需要在容器启动时自动导入,免去在页面登录,手动配置的烦恼。参照文档,我们设置了KEYCLOAK_IMPORT

Importing a realm

To create an admin account and import a previously exported realm run:

docker run -e KEYCLOAK_USER=\<USERNAME> -e KEYCLOAK_PASSWORD=\<PASSWORD> \\
   -e KEYCLOAK_IMPORT=/tmp/example-realm.json -v /tmp/example-realm.json:/tmp/example-realm.json jboss/keycloak
复制代码

开始踩坑

一切都准备就绪了,我们将镜像发布到AWS上,满心期待能看到KeyCloak的登录页面,然后看到的却是一堆错误。没想到是个坑,更让我们没想到的是,这还是个连环坑

一号坑:数据库连接失败

一开始 ECS 的任务无法启动成功,看了日志后发现是数据库连接失败。奇怪的是,同样的镜像配置,我们在本地Docker环境可以正常运行,一样 AWS 就挂了。仔细分析了以下日志后发现,应该和 SSL 的设定相关。

Caused by: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
Caused by: java.sql.SQLException: javax.resource.ResourceException: IJ000453: Unable to get managed connection for java:jboss/datasources/KeycloakDS
Caused by: javax.resource.ResourceException: IJ000453: Unable to get managed connection for java:jboss/datasources/KeycloakDS
复制代码

解决办法

因为 ECS 和 RDS 处于同一个 VPC ,所以他们直接的连接并不需要使用 SSL 加密。我们在环境变量中增加了如下配置后,问题解决了。

- JDBC_PARAMS: useSSL=false
复制代码

二号坑:无法导入初始设定

ECS 任务虽然启动成功,但是初始并没有被导入。调查之后发现,需要打开脚本上传开关 upload_scripts=enabled。另外我们发现,如果使用KEYCLOAK_IMPORT,只会在第一次启动时导入设定,如果数据库中已经存在该Realm,则不会执行导入。而我们希望的是,在每次设定文件修改之后,都能导入到 AWS 环境中。于是,在参考 KeyCloak官方文档 之后,我们删除了 KEYCLOAK_IMPORT 设定,而改用如下 migraion 设定。

解决办法

- JAVA_OPTS_APPEND=-Dkeycloak.profile.feature.upload_scripts=enabled \
-Dkeycloak.migration.action=import \
-Dkeycloak.migration.provider=singleFile \
-Dkeycloak.migration.realmName=my-realm \
-Dkeycloak.migration.usersExportStrategy=REALM_FILE \
-Dkeycloak.migration.file=/tmp/my-realm.json
复制代码

三号坑:Health Check失败

我们发现 ECS 任务在启动成功后不久,就会被 ALB 强制关闭,原因是 ALB 对 ECS 任务的 Health Check 失败了,ALB 认为 ECS 任务没有在正常运行,所以强制将其关闭,并重新启动新的 ECS 任务。

解决办法

虽然我们设置了 KEYCLOAK_STATISTICS=all ,但是还有一个隐藏选项需要打开。首先 ECS 的任务配置中,需要将 9990 端口打开。然后要在 JVM 系统参数中加入如下配置:

- JAVA_OPTS_APPEND=
... 
-Djboss.bind.address=0.0.0.0 
...
复制代码

四号坑:登录后无限重定向

现在终于可以看到 KeyCloak 后台登录界面了,不过不要大意,后面还有坑。在输入用户名和密码之后,浏览器的页面出现了多次重定向,然后就停留在一个雪白的页面上。

WTF ?!

image.png

调查了很久之后,终于在官方文档一个不起眼的角落发现了一丝蛛丝马迹。文档中很实诚的描述了这个问题会导致 an infinite number of redirects (无限重定向)。

If you try to authenticate with Keycloak to your application, but authentication fails with an infinite number of redirects in your browser and you see the errors like this in the Keycloak server log:

2017-11-27 14:50:31,587 WARN  [org.keycloak.events] (default task-17) type=LOGIN_ERROR, 
realmId=master, clientId=null, userId=null, ipAddress=aa.bb.cc.dd, error=expired_code, 
restart_after_timeout=true
复制代码

it probably means that your load balancer needs to be set to support sticky sessions.

解决办法

修改 AWS ALB Targe Group 的设置, 将 Stickness 开关打开。

image.png

五号坑:无法开启集群模式

好了,现在可以登录到 KeyCloak 管理页面了,看似一切配置正常了,于是打开前端页面,未登录的用户被拦截重定向到 KeyCloak 用户登录页面,登录成功后返回前端页面。此时前端页面拿到了 Access Token, 就当我以为大功告成的时候,意外还是发生了,当拿着这个 Token 去访问后端 API 的时候,有些访问成功了,有些访问失败了。失败的原因是 401 Unauthorized。更奇怪的是,同一个 API,同样的参数,这次访问失败了,下次访问又成功了!!!

image.png

这个问题在本地 Docker 环境同样无法复现,在谷歌找了一圈也没有发现任何头绪,最后只能靠自己推理了。由于这个问题是随机出现的,而且本地环境没有问题,初步推测是由于多节点部署造成的。比如KeyCloak有 AB 两个节点,其中 B 节点是问题节点,由于请求是 ALB 随机分配的,那么只有当请求被分发到 B 节点时才会出错。

为了验证这个推测,我关闭了一个 ECS 的任务(即 KeyCloak 实例),此时运行中的任务只有一个,这个时候的请求要么全部成功,要么全部失败,不应该出现随机失败的情况。于是从前端验证了一下,果然不出我所料!单一节点运行时所有请求都成功了!

等一等!我们的 ALB 不是打开了 Sticky Session 吗? 同一个客户端的请求应该会被分发到同一个实例来处理呀。为什么会被随机分配AB 节点呢?其实这是因为对于后端 API 的请求,经过 ALB 之后会被分配到 Gateway 实例上,Gateway 再请求 KeyCloak 对 Token 进行验证。如果我们只启动一个 Gateway 实例,问题也同样不会再出现。

解决办法

根本原因还是 KeyCloak 的多个节点之间没有组网成功,没有以集群模式作为一个整体运行。可是我们明明配置了JGROUPS_DISCOVERY_PROTOCOL=dns.DNS_PING,应该可以通过 DNS 发现其他节点才对啊。正当我疑惑不解时,这篇文章中的一句话提醒了我。

TCPPING use TCP protocol with 7600 port. This can be used when multicast is not available, e.g. deployments cross DC, containers cross host. image.png

我们采用的 DNS_PING 同样是基于 TCP 的,是不是也会需要使用 7600 端口呢?于是我们在 ECS 任务中打开了 7600 端口,同时在 AWS Security Group 中也允许了 7600 端口的 inbound TCP 请求。

于是我们在 A 节点看到如下日志:

05:16:24,965 INFO  [org.infinispan.CLUSTER] (thread-7,ejb,ip-10-11-193-82) ISPN000093: Received new, MERGED cluster view for channel ejb: MergeView::[ip-10-11-193-82|1] (2) [ip-10-11-193-82, ip-10-11-193-51], 2 subgroups: [ip-10-11-193-82|0] (1) [ip-10-11-193-82], [ip-10-11-193-51|0] (1) [ip-10-11-193-51]",
05:16:24,967 INFO  [org.infinispan.CLUSTER] (thread-7,ejb,ip-10-11-193-82) ISPN100000: Node ip-10-11-193-51 joined the cluster",
复制代码

B 节点的日志如下:

05:16:24,967 INFO  [org.infinispan.CLUSTER] (thread-5,null,ip-10-11-193-51) ISPN000093: Received new, MERGED cluster view for channel ejb: MergeView::[ip-10-11-193-82|1] (2) [ip-10-11-193-82, ip-10-11-193-51], 2 subgroups: [ip-10-11-193-82|0] (1) [ip-10-11-193-82], [ip-10-11-193-51|0] (1) [ip-10-11-193-51]",
05:16:24,972 INFO  [org.infinispan.CLUSTER] (thread-5,null,ip-10-11-193-51) ISPN100000: Node ip-10-11-193-82 joined the cluster",
复制代码

组网成功,问题解决!!!

六号坑:出现单点故障

系统终于可以正常运转了,你以为这就大结局了吗?那就 too naive 了。

在进行可用性相关的测试时,我们发现如果关掉 KeyCloak 的一个节点,部分请求会失败,在重新登录之后又可以成功请求了。难道又是多节点造成的问题?集群模式不是已经在成功运行了吗?

仔细翻阅了官方文档之后,发现 KeyCloak 默认不会备份缓存数据,如果一个节点挂了,节点中的缓存数据就永久丢失了

By default Keycloak does NOT replicate caches like sessions, authenticationSessions, offlineSessions, loginFailures and a few others (See Eviction and Expiration for more details), which are configured as distributed caches when using a clustered setup. Entries are not replicated to every single node, but instead one or more nodes is chosen as an owner of that data. If a node is not the owner of a specific cache entry it queries the cluster to obtain it. What this means for failover is that if all the nodes that own a piece of data go down, that data is lost forever. By default, Keycloak only specifies one owner for data. So if that one node goes down that data is lost.

解决办法

在环境变量中增加如下配置,将备份数设置为 2 或者更多:

- CACHE_OWNERS_COUNT=2
- CACHE_OWNERS_AUTH_SESSIONS_COUNT=2
复制代码

总结

本文归纳了作者在 AWS 上部署 KeyCloak 时踩过的一些坑,网上类似的资料不多,希望对你有帮助。 码子不易,如果喜欢请点赞关注加分享,感谢!

参考链接

おすすめ

転載: juejin.im/post/7083046684342616095
おすすめ