Shell-operator:用于简化Kubernetes operator的创建

Posted 分布式实验室

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shell-operator:用于简化Kubernetes operator的创建相关的知识,希望对你有一定的参考价值。

我们很高兴在此介绍我们新的开源方案,它能将Kubernetes中Operator的开发提升到一个全新的更简单的水平。它可以让你在15分钟内将你的小脚本变成完全成熟的Operator而吸引你。欢迎shell-operator[1]!

Shell-operator:用于简化Kubernetes operator的创建

目标

Shell-operator:用于简化Kubernetes operator的创建


shell-operator的思路很简单,它订阅来自Kubernetes对象的事件,并在事件发生后执行外部程序,为其提供有关事件的信息:

Shell-operator:用于简化Kubernetes operator的创建

在我们运行Kubernetes集群期间,很多小任务开始显现。在Flant[2],我们迫切想要以正确的方式来自动化它们,因此我们觉得需要更智能的解决方案。通常你可以使用基本的bash脚本来解决所有这些任务,但是如你所知,更推荐的方式是使用Golang来编写Operators。很显然,为每个小任务分别开发成熟的Operator将会很低效。

15分钟内创建一个Operator

Shell-operator:用于简化Kubernetes operator的创建


我们将举一个在Kubernetes集群中可以被自动化的例子,以及shell-operator可以如何帮助我们。我们将尝试复制用于访问Docker仓库的凭证。

使用私有仓库镜像的Pod应在其清单中包含指定的用于访问仓库的secret。这个secret必须在创建Pod之前先创建在每个命名空间中。你可以手动执行此操作,但是,如果我们将配置动态的多个环境,我们将为单个应用程序创建许多命名空间。在多个应用程序(甚至两个或三个)的情况下,secret的数量会变得巨大。关于secret还有一个需求:我们希望能够偶尔更改(注册表)仓库的访问密钥。因此,手动解决方案变得非常低效,你必须自动创建和更新secret。

简单的自动化

我们来写一个脚本,每N秒运行一次,并检查命名空间中secret是否存在。如果secret不存在,那么它将会被创建。这个解决方案的优势是它看起来就像是cron中的一个shell脚本,一种经典且易于理解的方法。缺点是在此脚本的两次启动之间的间隔期间可能会出现一些新的命名空间,因此在一段时间内它将不会持有这个secret。这种情况会导致启动Pod的过程中出错。

使用shell-operator进行自动化

为了使我们的脚本准确运行,经典的cron执行应该被当有新增命名空间事件发生时的执行所取代。在这种情况下,你可以在使用之前创建一个secret。让我们看看如何使用shell-operator来实现这个功能。

首先,我们先分析一下脚本,就shell-operator而言,脚本都被称之为“钩子“。每个钩子在使用 --config标志执行时都会通知shell-operator将其绑定(即需要执行哪些事件)。在我们的例子中,我们将使用 onKubernetesEvent:

 
   
   
 
  1. #!/bin/bash

  2. if [[ $1 == "--config" ]] ; then

  3. cat <<EOF

  4. {

  5. "onKubernetesEvent": [

  6. {

  7. "kind": "namespace",

  8. "event": [ "add" ]

  9. }

  10. ]

  11. }

  12. EOF

  13. fi


在这里,我们定义我们关注的 namespace类型的添加( add)对象事件。
现在我们需要添加当事件发生时需要执行的代码:

 
   
   
 
  1. #!/bin/bash

  2. if [[ $1 == "--config" ]] ; then

  3. # configuration

  4. cat <<EOF

  5. {

  6. "onKubernetesEvent": [

  7. {

  8. "kind": "namespace",

  9. "event": [ "add" ]

  10. }

  11. ]

  12. }

  13. EOF

  14. else

  15. # response:

  16. # find out what namespace has emerged

  17. createdNamespace=$(jq -r '.[0].resourceName' $BINDING_CONTEXT_PATH)

  18. # create the appropriate secret in it

  19. kubectl create -n ${createdNamespace} -f - <<EOF

  20. apiVersion: v1

  21. kind: Secret

  22. metadata:

  23. ...

  24. data:

  25. ...

  26. EOF

  27. fi


真棒!我们现在已有一个简洁且漂亮的脚本,想让它能真正发挥作用,我们需要准备一个镜像并将其跑在集群中。

使用钩子制作我们的镜像

你可以很轻易观察到我们在脚本里面使用了 kubectl和 jq命令。这意味着镜像中需要包含钩子,shell-operator二进制文件(它将监视事件并执行这个钩子),以及钩子需要用到的命令( kubectl和 jq)。hub.docker.com上已提供了包含shell-operator,kubectl和jq的即用型镜像。现在是时候使用 Dockerfile来添加一个钩子:

 
   
   
 
  1. $ cat Dockerfile

  2. FROM flant/shell-operator:v1.0.0-beta.1-alpine3.9

  3. ADD namespace-hook.sh /hooks

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

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


在集群中运行

我们再来看看这个钩子,这次我们将关注具体的操作以及它在集群中执行的对象:

  1. 它订阅了 namespace的创建事件;

  2. 它在不与它所运行的命名空间相同的空间创建一个secret。


这里我们会发现运行这个镜像的Pod需要有执行这些操作的权限。你可以授权给一个 ServiceAccount。由于我们是关注整个集群中的对象,那么权限需要使用 ClusterRole和 ClusterRoleBinding形式来配置。

YAML最终配置描述如下:

 
   
   
 
  1. ---

  2. apiVersion: v1

  3. kind: ServiceAccount

  4. metadata:

  5. name: monitor-namespaces-acc

  6. ---

  7. apiVersion: rbac.authorization.k8s.io/v1beta1

  8. kind: ClusterRole

  9. metadata:

  10. name: monitor-namespaces

  11. rules:

  12. - apiGroups: [""]

  13. resources: ["namespaces"]

  14. verbs: ["get", "watch", "list"]

  15. - apiGroups: [""]

  16. resources: ["secrets"]

  17. verbs: ["get", "list", "create", "patch"]

  18. ---

  19. apiVersion: rbac.authorization.k8s.io/v1beta1

  20. kind: ClusterRoleBinding

  21. metadata:

  22. name: monitor-namespaces

  23. roleRef:

  24. apiGroup: rbac.authorization.k8s.io

  25. kind: ClusterRole

  26. name: monitor-namespaces

  27. subjects:

  28. - kind: ServiceAccount

  29. name: monitor-namespaces-acc

  30. namespace: example-monitor-namespaces


你可以将创建的镜像部署为一个简单的Deployment:

 
   
   
 
  1. apiVersion: extensions/v1beta1

  2. kind: Deployment

  3. metadata:

  4. name: my-operator

  5. spec:

  6. template:

  7. spec:

  8. containers:

  9. - name: my-operator

  10. image: registry.example.com/my-operator:v1

  11. serviceAccountName: monitor-namespaces-acc


为方便起见,我们将创建一个单独的命名空间,用于运行shell-operator并应用创建的部署清单:

 
   
   
 
  1. $ kubectl create ns example-monitor-namespaces

  2. $ kubectl -n example-monitor-namespaces apply -f rbac.yaml

  3. $ kubectl -n example-monitor-namespaces apply -f deployment.yaml


好了,shell-operator启动,它将订阅命名空间创建事件并在需要时执行钩子。

Shell-operator:用于简化Kubernetes operator的创建

这样一个 简单的shell脚本就变成了Kubernetes中一个真正的Operator,并成为集群的一部分。这样做的好处是我们避免了使用Golang来开发Operator的复杂过程:

Shell-operator:用于简化Kubernetes operator的创建

过滤

Shell-operator:用于简化Kubernetes operator的创建


关于对象的观察很棒,但我们通常需要响应对象中某些属性的更改,例如,增加/减少部署中的副本数量或对象对标签中的任何更新。

当一个事件发生时,shell-operator接收该对象的JSON清单。在此JSON中,你可以选择要监视的属性,并 仅在更改时启动钩子。jqFilter字段可以帮助你完成这点:你应该输入将应用于JSON清单的jq表达式。

举个例子,要响应Deployment对象标签中的修改,你必须从 metadata字段中提取 labels字段。这个例子中你将需要如下的配置:

 
   
   
 
  1. cat <<EOF

  2. {

  3. "onKubernetesEvent": [

  4. {

  5. "kind": "deployment",

  6. "event":["update"],

  7. "jqFilter": ".metadata.labels"

  8. }

  9. ]

  10. }

  11. EOF


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

Shell-operator:用于简化Kubernetes operator的创建

shell-operator将只会在这个简短的JSON发生变化时执行钩子。其它属性的变更将会被忽略。

钩子的执行上下文

Shell-operator:用于简化Kubernetes operator的创建


钩子的配置允许你指定几种事件。例如你可以定义两个Kubernetes事件和两个计划调度:

 
   
   
 
  1. {

  2. "onKubernetesEvent": [

  3. {

  4. "name": "OnCreatePod",

  5. "kind": "pod",

  6. "event": [

  7. "add"

  8. ]

  9. },

  10. {

  11. "name": "OnModifiedNamespace",

  12. "kind": "namespace",

  13. "event": [

  14. "update"

  15. ],

  16. "jqFilter": ".metadata.labels"

  17. }

  18. ],

  19. "schedule": [

  20. {

  21. "name": "every 10 min",

  22. "crontab": "0 */10 * * * *"

  23. },

  24. {

  25. "name": "on Mondays at 12:10",

  26. "crontab": "0 10 12 * * 1"

  27. }

  28. ]

  29. }


注意:shell-operator支持以 crontab样式运行脚本!你可以在文档[3]中找到额外的信息。

为了区分钩子执行的原因,shell-operator会创建一个临时文件并将其路径保存到 BINDING_CONTEXT_TYPE变量中。此文件包含了执行钩子的原因的JSON描述。例如,每隔10分钟将会使用以下内容启动钩子:

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


在周一的话它将以以下内容启动:

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


同时将有 onKubernetesEvent调用的更详细的JSON,因为它包含了对象的描述:

 
   
   
 
  1. [

  2. {

  3. "binding": "onCreatePod",

  4. "resourceEvent": "add",

  5. "resourceKind": "pod",

  6. "resourceName": "foo",

  7. "resourceNamespace": "bar"

  8. }

  9. ]


你能通过名称来全面了解字段的内容(更多详细信息可在文档[4]中找到)。使用jq从 resourceName获取资源名称的示例已经在复制secret的钩子中展示:

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


你可以通过类似的方式去获取到其它字段。

下一步是什么呢?


在该项目仓库中的/examples directory[5]目录里包含了一些可以直接在集群中使用的示例。你可以将它们用作你开发自己的钩子的基础。

Shell-operator同样支持使用Prometheus来收集指标。METRICS[6]章节已描述了这些可用的指标。

你能轻易想到,shell-operator是使用Go编写的,并根据开源许可证(Apache 2.0)的条款进行分发。我们非常感谢任何关于开发在GitHub上的这个项目[7]的帮助。你可以通过给我们点Star,反馈问题或者是PR来支持我们!

相关链接:

  1. https://github.com/flant/shell-operator

  2. https://flant.com/

  3. https://github.com/flant/shell-operator/blob/master/HOOKS.md

  4. https://github.com/flant/shell-operator/blob/master/HOOKS.md#binding-context

  5. https://github.com/flant/shell-operator/tree/master/examples

  6. https://github.com/flant/shell-operator/blob/master/METRICS.md

  7. https://github.com/flant/shell-operator


原文链接:https://medium.com/flant-com/kubernetes-shell-operator-76c596b42f23

以上是关于Shell-operator:用于简化Kubernetes operator的创建的主要内容,如果未能解决你的问题,请参考以下文章

用于简化排序的 Angular 指令

为啥简化的 CommonJS Wrapper 语法不适用于我的 Dojo AMD 模块?

用于简化递归的惯用 Haskell 代码

K-NN聚类算法的良好泛化,可用于数据简化

SVD简化数据

使用 Thanos+Prometheus+Grafana 打造监控系统