引入shell运算符:使Kubernetes的运算符更加容易

我们的博客中已经有关于Kubernetes中运算符功能以及如何自己编写简单运算符的文章 。 这次,我们希望引起您的注意,我们的开源解决方案将操作员的创建带到了一个非常容易的程度-熟悉shell操作符

怎么了


shell运算符的想法非常简单:从Kubernetes对象订阅事件,并在接收到这些事件时启动一个外部程序,并为其提供有关事件的信息:



在集群运行期间,当我们真正希望以正确的方式实现自动化的小任务开始出现时,对我们的需求就随之而来。 所有这些次要任务都是通过简单的bash脚本解决的,尽管您知道,操作员最好用Golang编写。 显然,为每个如此小的任务投资全面的运营商开发将是低效的。

15分钟内即可操作


让我们看一个示例,该示例可以在Kubernetes集群中实现自动化,以及shell运算符将如何提供帮助。 示例如下:复制密钥以访问Docker注册表。

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

易于自动化


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

使用Shell-operator进行自动化


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

首先,让我们分析脚本。 用shell-operator命名的脚本称为钩子。 启动时每个带有--config标志的钩子都会告诉Shell操作员有关其绑定的信息,即 通过什么事件需要启动它。 在我们的例子中,我们将使用onKubernetesEvent

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

这里描述的是,我们对添加( addnamespace类型的对象的事件感兴趣。

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

 #!/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 

太好了! 结果是一个小的,漂亮的脚本。 要“恢复”,剩下两个步骤:准备映像并在集群中运行。

用挂钩准备图像


如果看一下脚本,可以看到使用了kubectljq 。 这意味着该映像必须具有以下内容:我们的钩子,一个将监视事件并启动该钩子的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. 在运行位置以外的名称空间中创建一个机密。

事实证明,将在其中启动映像的容器必须具有执行这些操作的权限。 这可以通过创建自己的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 

您可以以简单的Deployment形式运行组装的映像:

 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脚本变成了Kubernetes的实际运算符,并成为集群的组成部分。 所有这些-无需在Golang上开发运算符的复杂过程:



关于这个问题还有另一个例证...


我们将在以下出版物之一中更详细地揭示其含义。 更新 (2019年5月1日):请参阅“ 扩展和扩展Kubernetes(审阅和视频报告) ”。

筛选


跟踪对象是很好的方法,但是通常需要对对象某些属性的更改做出响应,例如,对Deployment中副本数的更改或对象标签的更改做出响应。

当事件到达时,shell运算符将接收对象的JSON清单。 您可以在此JSON中选择我们感兴趣的属性,并在它们更改时运行该挂钩。 为此,提供了jqFilter字段,您需要在其中指定将应用于JSON清单的jq表达式。

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

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

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



仅当此简短的JSON更改和对其他属性的更改被忽略时,shell运算符才会触发钩子。

挂钩启动上下文


钩子配置允许您为事件指定几个选项-例如,来自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":"0 */10 * * * *" }, {"name":"on Mondays at 12:10", "crontab": "0 10 12 * * 1" ]} 

有点题外话:是的,shell-operator支持运行crontab样式的脚本 您可以在文档中阅读更多内容

为了区分为什么启动钩子,shell操作员创建了一个临时文件,并将其路径传递给BINDING_CONTEXT_TYPE变量中的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" } ] 

字段的内容可以从它们的名称中了解,并且可以更详细地阅读文档中的内容 。 在复制机密的挂钩中,已经显示了使用jq从resourceName字段获取资源名称的示例:

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

同样,您可以获取其余字段。

接下来是什么?


在项目存储库中的/ examples目录中 ,有可以在群集中运行的挂钩示例。 编写钩子时,可以将其作为基础。

支持使用Prometheus收集指标-可用的指标写在METRICS部分中。

您可能会猜到,shell运算符是用Go编写的,并在开放源代码许可(Apache 2.0)下分发。 对于在GitHub上开发项目的任何帮助,我们将不胜感激:星号,问题和请求请求。

揭开保密的面纱,我们还告知shell操作系统仅是我们系统的一部分,它可以使Kubernetes集群中安装的附加组件保持最新状态,并执行各种自动操作。 我们在周一于圣彼得堡的HighLoad ++ 2019上更详细地讨论了该系统-该报告的视频和成绩单将很快发布。

我们计划开放该系统的其余部分:addon-operator以及我们的钩子和模块集合。 顺便说一句,addon-operator已经在GitHub上可用 ,但是有关它的文档仍在开发中。 计划在夏季发布模块集合。

敬请期待!

聚苯乙烯


另请参阅我们的博客:

Source: https://habr.com/ru/post/zh-CN447442/


All Articles