引入 shell-operator:为 Kubernetes 创建操作符变得更加容易

我们的博客上已经有文章讨论过 Kubernetes 中的算子能力 如何 自己写一个简单的运算符。 这次我们想向您介绍我们的开源解决方案,它将运算符的创建提升到超级简单的水平 - 请查看 外壳操作符!

Зачем?

shell-operator 的想法非常简单:订阅来自 Kubernetes 对象的事件,当收到这些事件时,启动外部程序,为其提供有关事件的信息:

引入 shell-operator:为 Kubernetes 创建操作符变得更加容易

当在集群运行期间,开始出现我们真正希望以正确的方式自动化的小任务时,就出现了对它的需求。 所有这些小任务都是使用简单的 bash 脚本解决的,尽管如您所知,最好在 Golang 中编写运算符。 显然,为每个这样的小任务投资运营商的全面开发是无效的。

15分钟内操作员

让我们看一个示例,了解 Kubernetes 集群中可以实现哪些自动化以及 shell 操作员如何提供帮助。 示例如下:复制密钥以访问 docker 注册表。

使用私有注册表中的映像的 Pod 必须在其清单中包含指向包含用于访问注册表的数据的密钥的链接。 创建 pod 之前,必须在每个命名空间中创建此密钥。 这可以手动完成,但是如果我们设置动态环境,那么一个应用程序的命名空间就会变得很多。 如果也没有 2-3 个应用程序……秘密的数量就会变得非常大。 关于秘密的另一件事是:我想不时更改访问注册表的密钥。 最终, 手动操作 作为解决方案 完全无效 ——我们需要自动化秘密的创建和更新。

简单的自动化

让我们编写一个每 N 秒运行一次的 shell 脚本,检查名称空间是否存在秘密,如果不存在秘密,则创建它。 这个解决方案的优点是它看起来像 cron 中的 shell 脚本——这是一种经典且每个人都可以理解的方法。 缺点是,在其启动之间的时间间隔内,可以创建一个新的命名空间,并且在一段时间内它将保持没有秘密,这将导致启动 Pod 时出错。

使用 shell 操作符实现自动化

为了让我们的脚本正常工作,经典的 cron 启动需要替换为添加命名空间时的启动:在这种情况下,您可以在使用它之前创建一个秘密。 让我们看看如何使用 shell-operator 来实现这一点。

首先,我们看一下脚本。 shell 操作符术语中的脚本称为钩子。 带有标志运行时的每个钩子 --config 通知 shell 操作符它的绑定,即应该启动哪些事件。 在我们的例子中,我们将使用 onKubernetesEvent:

#!/bin/bash
if [[ $1 == "--config" ]] ; then
cat <<EOF
{
"onKubernetesEvent": [
  { "kind": "namespace",
    "event":["add"]
  }
]}
EOF
fi

这里描述了我们有兴趣添加事件(add) 类型的对象 namespace.

现在您需要添加事件发生时将执行的代码:

#!/bin/bash
if [[ $1 == "--config" ]] ; then
  # конфигурация
cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "namespace",
  "event":["add"]
}
]}
EOF
else
  # реакция:
  # узнать, какой namespace появился
  createdNamespace=$(jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH)
  # создать в нём нужный секрет
  kubectl create -n ${createdNamespace} -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  ...
data:
  ...
EOF
fi

伟大的! 结果是一个小而漂亮的脚本。 要“恢复”它,还需要两个步骤:准备映像并在集群中启动它。

准备带有钩子的图像

如果你看一下脚本,你可以看到使用了这些命令 kubectl и jq。 这意味着镜像必须具有以下内容:我们的钩子、将监视事件并运行钩子的 shell 操作符以及钩子使用的命令(kubectl 和 jq)。 Hub.docker.com已经有一个现成的镜像,其中封装了shell-operator、kubectl和jq。 剩下的就是添加一个简单的钩子 Dockerfile:

$ cat Dockerfile
FROM flant/shell-operator:v1.0.0-beta.1-alpine3.9
ADD namespace-hook.sh /hooks

$ docker build -t registry.example.com/my-operator:v1 . 
$ docker push registry.example.com/my-operator:v1

在集群中运行

让我们再次查看该钩子,这次记下它在集群中执行的操作以及对象:

  1. 订阅命名空间创建事件;
  2. 在启动它的命名空间以外的命名空间中创建一个秘密。

事实证明,将启动我们的映像的 pod 必须具有执行这些操作的权限。 这可以通过创建您自己的 ServiceAccount 来完成。 权限必须以ClusterRole和ClusterRoleBinding的形式来做,因为我们对整个集群中的对象感兴趣。

YAML 中的最终描述将如下所示:

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: monitor-namespaces-acc

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: monitor-namespaces
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["get", "watch", "list"]
- apiGroups: [""]
  resources: ["secrets"]
  verbs: ["get", "list", "create", "patch"]

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: monitor-namespaces
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: monitor-namespaces
subjects:
  - kind: ServiceAccount
    name: monitor-namespaces-acc
    namespace: example-monitor-namespaces

您可以将组装的映像作为简单的部署启动:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: my-operator
spec:
  template:
    spec:
      containers:
      - name: my-operator
        image: registry.example.com/my-operator:v1
      serviceAccountName: monitor-namespaces-acc

为方便起见,将创建一个单独的命名空间,在其中启动 shell 操作符并应用创建的清单:

$ kubectl create ns example-monitor-namespaces
$ kubectl -n example-monitor-namespaces apply -f rbac.yaml
$ kubectl -n example-monitor-namespaces apply -f deployment.yaml

这就是全部:shell 操作符将启动,订阅名称空间创建事件并在需要时运行挂钩。

引入 shell-operator:为 Kubernetes 创建操作符变得更加容易

因此, 一个简单的 shell 脚本变成了 Kubernetes 的真正操作符 并作为集群的一部分工作。 而这一切都不需要在 Golang 中开发操作符的复杂过程:

引入 shell-operator:为 Kubernetes 创建操作符变得更加容易

关于这个问题还有另一个例子......引入 shell-operator:为 Kubernetes 创建操作符变得更加容易

我们将在以下出版物之一中更详细地揭示其含义。

过滤

跟踪物体固然很好,但通常需要对物体做出反应 改变一些对象属性,例如,更改 Deployment 中的副本数量或更改对象标签。

当事件到达时,shell 操作符会收到对象的 JSON 清单。 我们可以在此 JSON 中选择我们感兴趣的属性并运行挂钩 当他们改变时。 有一个字段可以用于此目的 jqFilter,您需要在其中指定将应用于 JSON 清单的 jq 表达式。

例如,要响应 Deployment 对象标签的更改,您需要过滤该字段 labels 场外 metadata。 配置将是这样的:

cat <<EOF
{
"onKubernetesEvent": [
{ "kind": "deployment",
  "event":["update"],
  "jqFilter": ".metadata.labels"
}
]}
EOF

此 jqFilter 表达式将 Deployment 的长 JSON 清单转换为带有标签的短 JSON:

引入 shell-operator:为 Kubernetes 创建操作符变得更加容易

shell-operator 仅当这个简短的 JSON 更改时才会运行钩子,并且其他属性的更改将被忽略。

钩子启动上下文

挂钩配置允许您为事件指定多个选项 - 例如,来自 Kubernetes 的事件的 2 个选项和 2 个计划:

{"onKubernetesEvent":[
  {"name":"OnCreatePod",
  "kind": "pod",
  "event":["add"]
  },
  {"name":"OnModifiedNamespace",
  "kind": "namespace",
  "event":["update"],
  "jqFilter": ".metadata.labels"
  }
],
"schedule": [
{ "name":"every 10 min",
  "crontab":"* */10 * * * *"
}, {"name":"on Mondays at 12:10",
"crontab": "* 10 12 * * 1"
]}

一个小题外话:是的,shell-operator 支持 运行 crontab 风格的脚本。 更多详细信息可以参见 文件资料.

为了区分启动钩子的原因,shell 操作员创建一个临时文件并将其路径通过变量传递给钩子 BINDING_CONTEXT_TYPE。 该文件包含运行挂钩原因的 JSON 描述。 例如,每 10 分钟该钩子就会运行一次并显示以下内容:

[{ "binding": "every 10 min"}]

...周一将以此开始:

[{ "binding": "every 10 min"}, { "binding": "on Mondays at 12:10"}]

onKubernetesEvent 将会有更多的 JSON 触发器,因为它包含对象的描述:

[
 {
 "binding": "onCreatePod",
 "resourceEvent": "add",
 "resourceKind": "pod",
 "resourceName": "foo",
 "resourceNamespace": "bar"
 }
]

字段的内容可以从它们的名称中了解到,更详细的内容可以阅读 文件资料。 从字段获取资源名称的示例 resourceName 使用 jq 已经在复制秘密的钩子中显示:

jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH

其他字段也可以用类似的方法获取。

接下来是什么?

在项目存储库中, /示例目录,有一些可以在集群上运行的挂钩示例。 当你编写自己的钩子时,你可以使用它们作为基础。

支持使用 Prometheus 收集指标 - 可用指标在 参考资料 部分中有描述 指标.

正如您可能猜到的,shell 运算符是用 Go 编写的,并根据开源许可证 (Apache 2.0) 分发。 我们将感谢任何发展援助 GitHub 上的项目:还有星星、问题和拉取请求。

揭开秘密的面纱,我们还将通知您 shell-operator 是 我们系统的一部分,可以使 Kubernetes 集群中安装的附加组件保持最新状态并执行各种自动操作。 了解有关该系统的更多信息 告诉 确切地说,将于周一在圣彼得堡举行的 HighLoad++ 2019 大会上 - 我们很快将发布本报告的视频和文字记录。

我们计划开放该系统的其余部分:插件操作符以及我们的钩子和模块集合。 顺便说一下,addon-operator 已经是 在 github 上可用,但它的文档仍在路上。 模块集合计划于夏季发布。

敬请关注!

PS

另请阅读我们的博客:

来源: habr.com

添加评论