总文章:
Deploying Akka Cluster to Kubernetes • Akka Management
使用 Akka Cluster 的服务对无状态应用程序有额外的要求。要形成集群,每个 Pod 需要知道哪些其他 Pod 已部署为该服务的一部分,以便它们可以相互连接。Akka 提供了一个 Cluster Bootstrap 库,允许 Kubernetes 中的 Akka 应用程序使用 Kubernetes API 自动发现这一点。具体流程如下:
将以下依赖项添加到应用程序:
要在那里访问它们,您需要配置此存储库的 URL。
<properties>
<akka.management.version>1.5.0akka.management.version>
<scala.binary.version>2.13scala.binary.version>
properties>
<dependencies>
<dependency>
<groupId>com.lightbend.akka.managementgroupId>
<artifactId>akka-management-cluster-http_${scala.binary.version}artifactId>
<version>${akka.management.version}version>
dependency>
<dependency>
<groupId>com.lightbend.akka.managementgroupId>
<artifactId>akka-management-cluster-bootstrap_${scala.binary.version}artifactId>
<version>${akka.management.version}version>
dependency>
<dependency>
<groupId>com.lightbend.akka.discoverygroupId>
<artifactId>akka-discovery-kubernetes-api_${scala.binary.version}artifactId>
<version>${akka.management.version}version>
dependency>
dependencies>
需要配置三个组件:Akka Cluster、Akka Management HTTP 和 Akka Cluster Bootstrap。
akka {
actor {
provider = cluster
}
cluster {
shutdown-after-unsuccessful-join-seed-nodes = 60s
}
coordinated-shutdown.exit-jvm = on
}
Akka 管理 HTTP 的默认配置适合在 Kubernetes 中使用,它将绑定到 Pod 外部 IP 地址上的默认端口 8558。
要配置 Cluster Bootstrap,我们需要告诉它将使用哪种发现方法来发现集群中的其他节点。这使用 Akka Discovery 来查找节点,但是,Cluster Bootstrap 中使用的发现方法和配置通常与用于查找其他服务的方法不同。这样做的原因是,在 Cluster Bootstrap 期间,我们有兴趣发现节点,即使它们还没有准备好处理请求,例如,因为它们也在尝试形成集群。如果我们使用 DNS 等方法来查找其他服务,默认情况下,Kubernetes DNS 服务器将仅返回已准备好为请求提供服务的服务,这些服务由其就绪性检查通过指示。因此,在组建新集群时,存在先有鸡还是先有蛋的问题,Kubernetes 不会告诉我们哪些节点正在运行,我们可以与之组成集群,直到这些节点准备就绪,而这些节点在形成集群之前不会通过就绪检查。
因此,我们需要对 Cluster Bootstrap 使用不同的发现方法,而对于 Kubernetes,最简单的方法是使用 Kubernetes API,无论其就绪状态如何,它都会返回所有节点。
akka.management {
cluster.bootstrap {
contact-point-discovery {
discovery-method = kubernetes-api
}
}
}
您可以选择指定服务名称,否则将使用 AkkaSystem 的名称,该名称与您在部署规范中的标签相匹配。
akka {
loglevel = "DEBUG"
actor.provider = cluster
coordinated-shutdown.exit-jvm = on
cluster {
shutdown-after-unsuccessful-join-seed-nodes = 60s
}
}
#management-config
akka.management {
cluster.bootstrap {
contact-point-discovery {
# 这是你service akka system名称
service-name = "akkademo"
discovery-method = kubernetes-api
required-contact-point-nr = 2
}
}
}
# 使用Kubernetes API作为发现方法来找到集群中的节点。
# "service-name"参数指定了Akka系统的名称,
# "discovery-method"参数指定了使用Kubernetes API进行发现。
# "required-contact-point-nr"参数指定了需要至少2个联系点(即节点)才能成功引导集群。
# 这个配置将帮助确保Akka集群中至少有2个节点可用,以确保高可用性和容错性。
要确保 Cluster Bootstrap 已启动,必须同时启动 Cluster Bootstrap 和 Akka Management 扩展。这可以通过在应用程序启动时同时调用 ClusterBoostrap 和 AkkaManagement 扩展上的 start 方法来完成。
// Akka Management hosts the HTTP routes used by bootstrap
AkkaManagement.get(system).start();
// Starting the bootstrap process needs to be done explicitly
ClusterBootstrap.get(system).start();
默认情况下,Pod 无法使用 Kubernetes API,因为它们没有经过身份验证。为了允许应用程序 Pod 使用 Kubernetes API 形成 Akka 集群,我们需要定义一些基于角色的访问控制 (RBAC) 角色和绑定。
RBAC 允许使用两个关键概念(角色和角色绑定)配置访问控制。角色是一组访问 Kubernetes API 中某些内容的权限。例如,pod-reader 角色可能有权对特定命名空间中的 pods 资源执行 list、get 和 watch 操作,默认情况下,该命名空间与配置该角色的命名空间相同。事实上,这正是我们要配置的,因为这是我们的 Pod 需要的权限。以下是要在 kubernetes/akka-cluster.yaml 中添加的 pod-reader 角色的规范:
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
配置角色后,您可以将该角色绑定到 subject 。subject 通常是用户或组,用户可以是人类用户,也可以是service account 。service account 是 Kubernetes 为 Kubernetes 资源(例如在 Pod 中运行的应用程序)创建的用于访问 Kubernetes API 的账号。每个命名空间都有一个默认服务帐户,默认情况下,未显式声明服务帐户的 Pod 使用该帐户,否则,您可以定义自己的服务帐户。Kubernetes 会自动将 Pod 服务帐户的凭据注入到该 Pod 文件系统中,允许 Pod 使用它们在 Kubernetes API 上发出经过身份验证的请求。
由于我们只是使用默认服务帐户,因此我们需要将我们的角色绑定到默认服务帐户,以便我们的 Pod 能够作为 Pod-reader 访问 Kubernetes API。在 kubernetes/akka-cluster.yaml 中:
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: read-pods
subjects:
- kind: User
name: system:serviceaccount:appka-1:default
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
请注意,服务帐户名称 system:serviceaccount:appka-1:default 包含 appka-1 命名空间。您需要相应地更新它。
这段文本是Kubernetes(一种开源的容器编排平台)中的Role和RoleBinding的配置示例。在Kubernetes中,角色(Roles)和角色绑定(RoleBindings)是用来定义哪些用户或服务账号有权执行哪些操作,以及它们能在哪些资源上执行这些操作。
下面是对文本的逐行解释:
Role
kind: Role
: 指定了这是一个Role资源。apiVersion: rbac.authorization.k8s.io/v1
: 指定了API版本的版本号。metadata
: Role的元数据,包括:
name: pod-reader
: 定义了角色的名称。namespace: appka-1
: 指定了这个角色所在的命名空间。rules
: 定义了角色的规则,包括:
apiGroups: [""]
: 这里使用空字符串表示核心API组。resources: ["pods"]
: 指定了这个角色可以操作的资源类型,这里是“pods”。verbs: ["get", "watch", "list"]
: 指定了可以对上述资源执行的操作,这里是“get”、“watch”和“list”。
RoleBinding
kind: RoleBinding
: 指定了这是一个RoleBinding资源。apiVersion: rbac.authorization.k8s.io/v1
: 指定了API版本的版本号。metadata
: RoleBinding的元数据,包括:
name: read-pods
: 定义了RoleBinding的名称。namespace: appka-1
: 指定了这个RoleBinding所在的命名空间。subjects
: 包含了要绑定到Role的用户或服务账号信息,这里是:
kind: User
: 表示这是一个用户。name: system:serviceaccount:appka-1:default
: 指定了要绑定的服务账号名称,这里表示绑定到命名空间appka-1
中的默认服务账号。roleRef
: 指定了要绑定的Role,包括:
kind: Role
: 表示这是一个Role。name: pod-reader
: 指定了Role的名称,与上面的Role名称相匹配。apiGroup: rbac.authorization.k8s.io
: 指定了Role的API组。
简单来说,这段配置定义了一个名为pod-reader
的角色,该角色有权限对命名空间appka-1
中的Pods执行“get”、“watch”和“list”操作。然后,这个角色绑定到了命名空间appka-1
中的默认服务账号,意味着这个服务账号拥有对Pods执行上述操作的权限。
使用基于角色的访问控制时需要注意的一点是,pod-reader 角色将授予读取 appka-1 命名空间中所有 Pod 的访问权限,而不仅仅是应用程序的 Pod。这包括部署规范,其中包括在部署规范中硬编码的环境变量。如果您通过这些环境变量传递密钥,而不是使用 Kubernetes 密钥 API,则您的应用程序以及使用默认服务帐户的所有其他应用将能够看到这些密钥。这是一个很好的理由:为什么你不应该直接在部署规范中传递机密,而应该通过 Kubernetes 机密 API 传递它们。
如果这是一个问题,一种解决方案可能是为要部署的每个应用程序创建一个单独的命名空间。不过,您可能会发现这样做的配置开销非常高,这不是 Kubernetes 命名空间的用途。
运行状况检查
Akka 管理 HTTP 包含运行状况检查路由,这些路由将分别在 /alive 和 /ready 上公开活动和就绪运行状况检查。
在 Kubernetes 中,如果一个应用程序处于活动状态,则意味着它正在运行 - 它没有崩溃。但它可能不一定准备好为请求提供服务,例如,它可能还没有设法连接到数据库,或者,在我们的例子中,它可能还没有形成集群。
通过分离活动和就绪,Kubernetes 可以区分致命错误(如崩溃)和暂时性错误(如无法联系应用程序所依赖的其他资源),从而使 Kubernetes 能够更智能地决定是否需要重新启动应用程序,或者是否只需要给它时间进行自我整理。
这些路由公开了多次内部检查的结果。例如,通过依赖 akka-management-cluster-http,运行状况检查将考虑集群成员身份状态,并将作为确保集群已形成的检查。
最后,我们需要配置运行状况检查。如前所述,Akka Management HTTP 为我们提供了运行状况检查端点,包括就绪性和活动性。Kubernetes 只需要被告知这一点。我们要做的第一件事是为管理端口配置一个名称,虽然不是绝对必要的,但这允许我们在探测器中按名称引用它,而不是每次都重复端口号。我们将在这里配置一些数字,我们将告诉 Kubernetes 在尝试探测任何东西之前等待 20 秒,这让我们的集群有机会在 Kubernetes 开始尝试询问我们它是否准备好之前启动,并且因为在某些情况下,特别是如果你没有为 pod 分配大量 CPU, 群集可能需要很长时间才能启动,因此我们将为其设置较高的故障阈值 10。
可以在 kubernetes/akka-cluster.yaml 中调整健康检查探测:
ports:
- name: management
containerPort: 8558
readinessProbe:
httpGet:
path: "/ready"
port: management
periodSeconds: 10
failureThreshold: 10
initialDelaySeconds: 20
livenessProbe:
httpGet:
path: "/alive"
port: management
periodSeconds: 10
failureThreshold: 10
initialDelaySeconds: 20
从 Kubernetes v1.22 开始,ReplicaSet 不会先使用最年轻的节点进行缩减,这可能会导致 Akka 集群出现问题。为了解决这个问题,我们开发了一个新的 Akka 扩展,您可以在 Kubernetes 滚动更新部分找到文档。
滚动更新允许您通过逐步用新节点替换旧节点来更新应用程序。这可确保应用程序在整个更新过程中保持可用,并对客户端造成的干扰最小化。
如前所述,Akka 集群中最早的节点具有特殊角色,因为它托管单例。如果集群中最旧的节点频繁更改,则单例也需要四处移动,这可能会产生不良后果。 该模块提供了 Pod Deletion Cost 扩展,该扩展会自动注释较旧的 Pod,以便在删除节点时最后选择它们,从而为集群操作提供更好的整体稳定性。
<properties>
<akka.management.version>1.5.0</akka.management.version>
<scala.binary.version>2.13</scala.binary.version>
</properties>
<dependencies>
<dependency>
<groupId>com.lightbend.akka.management</groupId>
<artifactId>akka-rolling-update-kubernetes_${scala.binary.version}</artifactId>
<version>${akka.management.version}</version>
</dependency>
</dependencies>
必须启动 Akka Pod 删除成本扩展,这可以通过配置或以编程方式完成。
在 application.conf 中自动加载的 akka.extensions 中列出 PodDeletionCost 扩展也会导致它自动启动:
akka.extensions = ["akka.rollingupdate.kubernetes.PodDeletionCost"]
如果management or bootstrap 配置不正确,则自动启动将记录错误并终止执行组件系统。
// Starting the pod deletion cost annotator
PodDeletionCost.get(system).start();
以下配置是必需的,有关每个配置的更多详细信息,以及其他配置可以在 reference.conf 中找到:
akka.rollingupdate.kubernetes.pod-name:这可以通过在 Kubernetes 容器规范上将环境变量设置为 metadata.name 来提供KUBERNETES_POD_NAME。
env:
- name: KUBERNETES_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
此外,pod 注释者需要知道 pod 属于哪个命名空间。默认情况下,这将通过从 /var/run/secrets/kubernetes.io/serviceaccount/namespace 中的服务帐户密钥中读取命名空间来检测,但可以通过设置 akka.rollingupdate.kubernetes.namespace 或提供环境变量来覆盖KUBERNETES_NAMESPACE。
env:
- name: KUBERNETES_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
没翻译完:
Rolling Updates • Akka Management