【翻译】akka-cluster 部署于k8s(二)形成 Akka 集群

形成 Akka 集群

总文章:
Deploying Akka Cluster to Kubernetes • Akka Management

使用 Akka Cluster 的服务对无状态应用程序有额外的要求。要形成集群,每个 Pod 需要知道哪些其他 Pod 已部署为该服务的一部分,以便它们可以相互连接。Akka 提供了一个 Cluster Bootstrap 库,允许 Kubernetes 中的 Akka 应用程序使用 Kubernetes API 自动发现这一点。具体流程如下:

  1. 当应用程序启动时,应用程序会轮询 Kubernetes API 以查找部署了哪些 Pod,直到发现配置的最小 Pod 数量。
  2. 然后,它尝试使用 Akka 的 HTTP 管理接口连接到这些 Pod,并查询这些 Pod 中是否有任何一个已经形成集群。
  3. 如果已形成集群,则应用程序将加入集群。
  4. 如果尚未在任何 Pod 上形成集群,则使用确定性函数来决定哪个 Pod 将启动集群
  5. 此函数确保当前正在经历此过程的所有 Pod 将决定使用同一个 Pod。
  6. 决定启动集群的 Pod 与自身形成一个集群。
  7. 剩余的 Pod 轮询该 Pod,直到它报告它已形成一个集群,然后它们加入该集群。

【翻译】akka-cluster 部署于k8s(二)形成 Akka 集群_第1张图片

Bootstrap 和管理依赖项

将以下依赖项添加到应用程序:

  • Akka 管理集群 HTTP:提供 HTTP 管理端点以及集群运行状况检查
  • Akka Discovery Kubernetes:这提供了一种查询 Kubernetes API 的发现机制
  • Akka Bootstrap:从通过 Kubernetes API 发现的节点引导集群 Akka 依赖项可从 Akka 的库存储库获得。

要在那里访问它们,您需要配置此存储库的 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 Cluster、Akka Management HTTP 和 Akka Cluster Bootstrap。

akka cluster

  • 将执行组件提供程序设置为群集。
  • 如果集群形成不起作用,请关闭。这将导致 Kubernetes 重新创建 Pod。
  • 在 ActorSystem 终止时退出 JVM,以允许 Kubernetes 重新创建它。
akka {
    actor {
        provider = cluster
    }

    cluster {
        shutdown-after-unsuccessful-join-seed-nodes = 60s
    }
    coordinated-shutdown.exit-jvm = on
}

Akka Management HTTP

Akka 管理 HTTP 的默认配置适合在 Kubernetes 中使用,它将绑定到 Pod 外部 IP 地址上的默认端口 8558。


Akka Cluster Bootstrap

【翻译】akka-cluster 部署于k8s(二)形成 Akka 集群_第2张图片
要配置 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个节点可用,以确保高可用性和容错性。

Starting 启动 cluster

要确保 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执行上述操作的权限。

关于使用 RBAC 的密钥的说明

使用基于角色的访问控制时需要注意的一点是,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

没翻译完:


Role based access control

Rolling Updates • Akka Management

你可能感兴趣的:(akka,kubernetes,云原生,akka)