我们的博客上已经有文章讨论过
Зачем?
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
在集群中运行
让我们再次查看该钩子,这次记下它在集群中执行的操作以及对象:
- 订阅命名空间创建事件;
- 在启动它的命名空间以外的命名空间中创建一个秘密。
事实证明,将启动我们的映像的 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 脚本变成了 Kubernetes 的真正操作符 并作为集群的一部分工作。 而这一切都不需要在 Golang 中开发操作符的复杂过程:
关于这个问题还有另一个例子......
我们将在以下出版物之一中更详细地揭示其含义。
过滤
跟踪物体固然很好,但通常需要对物体做出反应 改变一些对象属性,例如,更改 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 仅当这个简短的 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) 分发。 我们将感谢任何发展援助
揭开秘密的面纱,我们还将通知您 shell-operator 是 小 我们系统的一部分,可以使 Kubernetes 集群中安装的附加组件保持最新状态并执行各种自动操作。 了解有关该系统的更多信息
我们计划开放该系统的其余部分:插件操作符以及我们的钩子和模块集合。 顺便说一下,addon-operator 已经是
敬请关注!
PS
另请阅读我们的博客:
- «
Kubernetes 的 Operator:如何运行有状态应用程序 “; - «
用 Golang 为 Kubernetes 编写一个操作符 “; - «
引入 Grafana 新插件 - Statusmap 面板 “; - «
隆重推出 loghouse - 一个用于在 Kubernetes 中处理日志的开源系统 “; - «
我们正式推出 dapp - 用于 CI/CD 维护的 DevOps 实用程序 “。
来源: habr.com