去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

今年,主要的欧洲 Kubernetes 会议 - KubeCon + CloudNativeCon Europe 2020 - 是虚拟的。 不过,这样的格式变化并没有妨碍我们交付蓄谋已久的报告《走? 猛击! 认识一下 Shell 操作员”致力于我们的开源项目 外壳操作符.

本文受到演讲的启发,提出了一种简化为 Kubernetes 创建运算符的过程的方法,并展示了如何使用 shell 运算符以最少的努力创建自己的运算符。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

介绍 报告视频 (大约 23 分钟的英文,明显比文章内容丰富)以及文本形式的主要摘录。 去!

在 Flant,我们不断优化和自动化一切。 今天我们将讨论另一个令人兴奋的概念。 见面: 云原生 shell 脚本!

然而,让我们从这一切发生的背景开始:Kubernetes。

Kubernetes API 和控制器

Kubernetes 中的 API 可以表示为一种文件服务器,其中包含每种类型对象的目录。 该服务器上的对象(资源)由 YAML 文件表示。 此外,服务器还有一个基本的 API,允许您执行三件事:

  • 收到 资源的种类和名称;
  • 改变 资源(在这种情况下,服务器仅存储“正确”的对象 - 所有格式不正确或用于其他目录的对象都将被丢弃);
  • 遵循 对于资源(在这种情况下,用户立即收到其当前/更新版本)。

因此,Kubernetes 充当一种文件服务器(用于 YAML 清单),具有三种基本方法(是的,实际上还有其他方法,但我们现在将忽略它们)。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

问题是服务器只能存储信息。 为了让它发挥作用,你需要 调节器 - Kubernetes 世界中第二重要和基本的概念。

有两种主要类型的控制器。 第一个从 Kubernetes 获取信息,根据嵌套逻辑对其进行处理,然后将其返回给 K8s。 第二种从 Kubernetes 获取信息,但与第一种不同的是,它会更改某些外部资源的状态。

让我们仔细看看 Kubernetes 中创建 Deployment 的过程:

  • 部署控制器(包含在 kube-controller-manager)接收有关 Deployment 的信息并创建 ReplicaSet。
  • ReplicaSet 根据此信息创建两个副本(两个 Pod),但这些 Pod 尚未调度。
  • 调度程序调度 Pod 并将节点信息添加到其 YAML 中。
  • Kubelet 对外部资源(例如 Docker)进行更改。

然后以相反的顺序重复整个序列:kubelet 检查容器,计算 pod 的状态并将其发回。 ReplicaSet 控制器接收状态并更新副本集的状态。 部署控制器也会发生同样的事情,用户最终获得更新的(当前)状态。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

Shell 操作符

事实证明,Kubernetes 是基于各种控制器的联合工作(Kubernetes 操作员也是控制器)。 问题来了,如何以最小的努力创建自己的操作符? 我们开发的这款产品可以拯救您 外壳操作符。 它允许系统管理员使用熟悉的方法创建自己的报表。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

简单的例子:复制秘密

让我们看一个简单的例子。

假设我们有一个 Kubernetes 集群。 它有一个命名空间 default 带着一些秘密 mysecret。 此外,集群中还有其他命名空间。 其中一些贴有特定的标签。 我们的目标是将 Secret 复制到带有标签的命名空间中。

由于集群中可能出现新的命名空间,并且其中一些命名空间可能具有此标签,因此任务变得复杂。 另一方面,当标签被删除时,Secret也应该被删除。 除此之外,Secret 本身也可以更改:在这种情况下,必须将新的 Secret 复制到所有带有标签的命名空间。 如果任何命名空间中的 Secret 被意外删除,我们的操作员应该立即恢复它。

现在任务已经制定完毕,是时候开始使用 shell 操作符来实现它了。 但首先值得谈谈 shell 操作符本身。

shell 操作符如何工作

与 Kubernetes 中的其他工作负载一样,shell-operator 在自己的 pod 中运行。 在这个pod目录中 /hooks 存储可执行文件。 这些可以是 Bash、Python、Ruby 等中的脚本。 我们称这样的可执行文件为钩子(挂钩).

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

Shell-operator 订阅 Kubernetes 事件并运行这些钩子来响应我们需要的事件。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

shell 操作员如何知道运行哪个钩子以及何时运行? 要点是每个钩子都有两个阶段。 在启动期间,shell 操作符运行带有参数的所有钩子 --config 这是配置阶段。 之后,挂钩以正常方式启动 - 响应它们所附加的事件。 在后一种情况下,钩子接收绑定上下文(绑定上下文) - JSON 格式的数据,我们将在下面更详细地讨论。

在 Bash 中创建一个运算符

现在我们已准备好实施。 为此,我们需要编写两个函数(顺便说一下,我们建议 图书馆 外壳库,这大大简化了在 Bash 中编写钩子):

  • 第一个是配置阶段所需要的 - 它显示绑定上下文;
  • 第二个包含钩子的主要逻辑。

#!/bin/bash

source /shell_lib.sh

function __config__() {
  cat << EOF
    configVersion: v1
    # BINDING CONFIGURATION
EOF
}

function __main__() {
  # THE LOGIC
}

hook::run "$@"

下一步是决定我们需要什么对象。 在我们的例子中,我们需要跟踪:

  • 更改的源秘密;
  • 集群中的所有命名空间,以便您知道哪些命名空间附加了标签;
  • 目标机密以确保它们全部与源机密同步。

订阅秘密来源

它的绑定配置非常简单。 我们表明我们对名称为 Secret 感兴趣 mysecret 在命名空间中 default:

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

function __config__() {
  cat << EOF
    configVersion: v1
    kubernetes:
    - name: src_secret
      apiVersion: v1
      kind: Secret
      nameSelector:
        matchNames:
        - mysecret
      namespace:
        nameSelector:
          matchNames: ["default"]
      group: main
EOF

因此,当源秘密发生变化时,钩子将被触发(src_secret)并接收以下绑定上下文:

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

如您所见,它包含名称和整个对象。

跟踪命名空间

现在您需要订阅名称空间。 为此,我们指定以下绑定配置:

- name: namespaces
  group: main
  apiVersion: v1
  kind: Namespace
  jqFilter: |
    {
      namespace: .metadata.name,
      hasLabel: (
       .metadata.labels // {} |  
         contains({"secret": "yes"})
      )
    }
  group: main
  keepFullObjectsInMemory: false

如您所见,配置中出现了一个新字段,其名称为 jqFilter. 顾名思义, jqFilter 过滤掉所有不必要的信息,并使用我们感兴趣的字段创建一个新的 JSON 对象。 具有类似配置的钩子将接收以下绑定上下文:

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

它包含一个数组 filterResults 对于集群中的每个命名空间。 布尔变量 hasLabel 指示标签是否附加到给定的命名空间。 选择器 keepFullObjectsInMemory: false 表示不需要在内存中保留完整的对象。

追踪目标秘密

我们订阅所有指定了注释的 Secret managed-secret: "yes" (这些是我们的目标 dst_secrets):

- name: dst_secrets
  apiVersion: v1
  kind: Secret
  labelSelector:
    matchLabels:
      managed-secret: "yes"
  jqFilter: |
    {
      "namespace":
        .metadata.namespace,
      "resourceVersion":
        .metadata.annotations.resourceVersion
    }
  group: main
  keepFullObjectsInMemory: false

在这种情况下, jqFilter 过滤掉除命名空间和参数之外的所有信息 resourceVersion。 最后一个参数在创建机密时传递给注释:它允许您比较机密的版本并使其保持最新。

以这种方式配置的钩子在执行时将接收上述三个绑定上下文。 它们可以被认为是一种快照(快照) 簇。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

基于所有这些信息,可以开发基本算法。 它迭代所有名称空间并且:

  • 如果 hasLabel 事项 true 对于当前命名空间:
    • 将全局秘密与本地秘密进行比较:
      • 如果它们相同,则不执行任何操作;
      • 如果它们不同 - 执行 kubectl replace или create;
  • 如果 hasLabel 事项 false 对于当前命名空间:
    • 确保 Secret 不在给定的命名空间中:
      • 如果本地 Secret 存在,请使用删除它 kubectl delete;
      • 如果未检测到本地 Secret,则不执行任何操作。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

该算法在 Bash 中的实现 您可以在我们的下载 带有示例的存储库.

这就是我们如何使用 35 行 YAML 配置和大约相同数量的 Bash 代码创建一个简单的 Kubernetes 控制器! shell 操作符的工作是将它们连接在一起。

然而,复制机密并不是该实用程序的唯一应用领域。 这里还有几个例子来展示他的能力。

示例 1:对 ConfigMap 进行更改

让我们看一下由三个 Pod 组成的 Deployment。 Pod 使用 ConfigMap 来存储一些配置。 当 Pod 启动时,ConfigMap 处于某种状态(我们称之为 v.1)。 因此,所有 Pod 都使用此特定版本的 ConfigMap。

现在我们假设 ConfigMap 已更改 (v.2)。 但是,Pod 将使用以前版本的 ConfigMap (v.1):

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

我怎样才能让他们切换到新的 ConfigMap (v.2)? 答案很简单:使用模板。 让我们在该部分添加一个校验和注释 template 部署配置:

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

结果,这个校验和将被注册到所有的 pod 中,并且它将与 Deployment 的校验和相同。 现在您只需要在 ConfigMap 更改时更新注释即可。 在这种情况下,shell 运算符就派上用场了。 您所需要做的就是编程 一个将订阅 ConfigMap 并更新校验和的钩子.

如果用户对 ConfigMap 进行更改,shell 操作员将注意到它们并重新计算校验和。 之后 Kubernetes 的魔力将发挥作用:编排器将杀死 pod,创建一个新的 pod,等待它成为 Ready,然后继续下一个。 这样一来,Deployment就会同步并切换到新版本的ConfigMap。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

示例 2:使用自定义资源定义

如您所知,Kubernetes 允许您创建自定义类型的对象。 例如,您可以创建种类 MysqlDatabase。 假设该类型有两个元数据参数: name и namespace.

apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
  name: foo
  namespace: bar

我们有一个具有不同命名空间的 Kubernetes 集群,可以在其中创建 MySQL 数据库。 在这种情况下,可以使用 shell-operator 来跟踪资源 MysqlDatabase,将它们连接到 MySQL 服务器并同步集群的所需状态和观察到的状态。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

示例3:集群网络监控

如您所知,使用 ping 是监控网络的最简单方法。 在这个例子中,我们将展示如何使用 shell-operator 来实现这样的监控。

首先,您需要订阅节点。 shell 操作员需要每个节点的名称和 IP 地址。 在他们的帮助下,他将 ping 这些节点。

configVersion: v1
kubernetes:
- name: nodes
  apiVersion: v1
  kind: Node
  jqFilter: |
    {
      name: .metadata.name,
      ip: (
       .status.addresses[] |  
        select(.type == "InternalIP") |
        .address
      )
    }
  group: main
  keepFullObjectsInMemory: false
  executeHookOnEvent: []
schedule:
- name: every_minute
  group: main
  crontab: "* * * * *"

参数 executeHookOnEvent: [] 防止钩子运行以响应任何事件(即响应更改、添加、删除节点)。 然而,他 会跑 (并更新节点列表) 按计划 - 每分钟,按照现场规定 schedule.

现在问题来了,我们到底如何知道丢包等问题呢? 我们看一下代码:

function __main__() {
  for i in $(seq 0 "$(context::jq -r '(.snapshots.nodes | length) - 1')"); do
    node_name="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.name')"
    node_ip="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.ip')"
    packets_lost=0
    if ! ping -c 1 "$node_ip" -t 1 ; then
      packets_lost=1
    fi
    cat >> "$METRICS_PATH" <<END
      {
        "name": "node_packets_lost",
        "add": $packets_lost,
        "labels": {
          "node": "$node_name"
        }
      }
END
  done
}

我们遍历节点列表,获取它们的名称和 IP 地址,对它们进行 ping 操作并将结果发送到 Prometheus。 Shell-operator 可以将指标导出到 Prometheus,将它们保存到根据环境变量中指定的路径定位的文件中 $METRICS_PATH.

这样 您可以在集群中创建一个操作员来进行简单的网络监控。

排队机制

如果不描述 shell 操作符中内置的另一个重要机制,本文将是不完整的。 想象一下,它执行某种钩子来响应集群中的事件。

  • 如果集群中同时发生某些情况,会发生什么情况? 还有一件事 事件?
  • shell-operator 会运行钩子的另一个实例吗?
  • 比如说,如果集群中同时发生五个事件怎么办?
  • shell 操作符会并行处理它们吗?
  • 消耗的资源(例如内存和CPU)怎么样?

幸运的是,shell-operator 有一个内置的排队机制。 所有事件都按顺序排队和处理。

让我们用例子来说明这一点。 假设我们有两个钩子。 第一个事件进入第一个钩子。 一旦处理完成,队列就会向前移动。 接下来的三个事件被重定向到第二个钩子 - 它们被从队列中删除并以“捆绑”的形式进入队列。 那是 钩子接收事件数组 ——或者更准确地说,是一组绑定上下文。

还有这些 事件可以合并为一个大事件。 该参数负责此操作 group 在绑定配置中。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

您可以创建任意数量的队列/挂钩及其各种组合。 例如,一个队列可以使用两个钩子,反之亦然。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

您需要做的就是相应地配置该字段 queue 在绑定配置中。 如果未指定队列名称,则挂钩在默认队列(default)。 这种排队机制可以让你彻底解决使用钩子时的所有资源管理问题。

结论

我们解释了什么是 shell-operator,展示了如何使用它来快速、轻松地创建 Kubernetes 运算符,并给出了几个使用示例。

有关 shell 操作符的详细信息以及如何使用它的快速教程可在相应的 GitHub 上的存储库。 如有疑问,请随时与我们联系:您可以在特殊的方式中讨论这些问题 电报群 (俄语)或 这个论坛 (英文)。

如果您喜欢它,我们总是很高兴在 GitHub 上看到新问题/PR/stars,顺便说一句,您可以在其中找到其他内容 有趣的项目。 其中值得强调的是 插件操作符,它是 shell-operator 的老大哥。 该实用程序使用 Helm 图表来安装附加组件,可以提供更新并监视各种图表参数/值,控制图表的安装过程,还可以修改它们以响应集群中的事件。

去? 猛击! 认识 shell 操作员(KubeCon EU'2020 的评论和视频报告)

视频和幻灯片

表演视频(约 23 分钟):


报告介绍:

PS

另请阅读我们的博客:

来源: habr.com

添加评论