没有框架和SDK的Python中的Kubernetes Operator



当前,Go是人们选择为Kubernetes编写语句的编程语言中的垄断者。 出于以下客观原因:

  1. 有一个强大的框架可用于在Go- Operator SDK上开发运算
  2. Go编写了颠倒的应用程序,例如Docker和Kubernetes。 要在Go中编写自己的运算符,请使用与生态系统相同的语言。
  3. Go上的高性能应用程序和用于并发使用的简单工具。

注意 :顺便说一句,我们已经描述了外国作者在我们的翻译版本如何在Go上编写自己的运算符。

但是,如果由于缺乏时间或琐碎的动机而阻止学习Go,该怎么办? 本文提供了一个示例,说明了如何使用几乎每位DevOps工程师都知道的最流行的语言之一编写可靠的运算符-Python。

见面:文案-复制操作员!


例如,考虑开发一个简单的运算符,该运算符旨在在出现新的名称空间或两个实体之一(ConfigMap和Secret)发生更改时复制ConfigMap。 从实际应用程序的角度来看,操作员对于大规模更新应用程序配置(通过更新ConfigMap)或更新秘密数据(例如,用于Docker注册表的密钥(在将Secret添加到名称空间时))非常有用。

因此,好的操作员应该具备以下条件

  1. 使用自定义资源定义 (以下称为CRD)与操作员进行交互。
  2. 可以定制操作员。 为此,我们将使用命令行标志和环境变量。
  3. 正在设计Docker容器和Helm图表的组合,以便用户可以轻松地(使用一个命令)将操作员安装在其Kubernetes集群中。

CRD


为了使操作员知道要寻找什么资源以及在哪里寻找他,我们需要为他制定规则。 每个规则将表示为单个CRD对象。 该CRD应该拥有哪些领域?

  1. 我们正在寻找的资源类型 (ConfigMap或Secret)。
  2. 资源应位于的名称空间列表
  3. Selector ,通过它我们可以在命名空间中查找资源。

我们描述CRD:

apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: copyrator.flant.com spec: group: flant.com versions: - name: v1 served: true storage: true scope: Namespaced names: plural: copyrators singular: copyrator kind: CopyratorRule shortNames: - copyr validation: openAPIV3Schema: type: object properties: ruleType: type: string namespaces: type: array items: type: string selector: type: string 

并立即创建一个简单规则 -在名称空间中搜索所有ConfigMap的default名称,并使用诸如copyrator: "true"类的标签:

 apiVersion: flant.com/v1 kind: CopyratorRule metadata: name: main-rule labels: module: copyrator ruleType: configmap selector: copyrator: "true" namespace: default 

做完了! 现在,您需要以某种方式获取有关我们规则的信息。 我必须立即预约,我们将不会向集群服务器API写入请求。 为此,我们将使用现成的Python库kubernetes-client

 import kubernetes from contextlib import suppress CRD_GROUP = 'flant.com' CRD_VERSION = 'v1' CRD_PLURAL = 'copyrators' def load_crd(namespace, name): client = kubernetes.client.ApiClient() custom_api = kubernetes.client.CustomObjectsApi(client) with suppress(kubernetes.client.api_client.ApiException): crd = custom_api.get_namespaced_custom_object( CRD_GROUP, CRD_VERSION, namespace, CRD_PLURAL, name, ) return {x: crd[x] for x in ('ruleType', 'selector', 'namespace')} 

作为此代码的结果,我们得到以下信息:

 {'ruleType': 'configmap', 'selector': {'copyrator': 'true'}, 'namespace': ['default']} 

优秀:我们设法为运营商制定了规则。 最重要的是,我们采用了所谓的Kubernetes方式。

环境变量或标志? 我们承担一切!


我们转到操作员的主要配置。 有两种配置应用程序的基本方法:

  1. 使用命令行选项
  2. 使用环境变量。

命令行选项使您可以更灵活地读取设置,并支持和验证数据类型。 Python标准库有一个argparser模块,我们将使用它。 官方文档中提供了其功能的详细信息和示例。

这是在我们的案例中设置命令行标志读取的示例:

  parser = ArgumentParser( description='Copyrator - copy operator.', prog='copyrator' ) parser.add_argument( '--namespace', type=str, default=getenv('NAMESPACE', 'default'), help='Operator Namespace' ) parser.add_argument( '--rule-name', type=str, default=getenv('RULE_NAME', 'main-rule'), help='CRD Name' ) args = parser.parse_args() 

另一方面,使用Kubernetes中的环境变量,您可以轻松地将有关Pod的服务信息传输到容器。 例如,我们可以通过以下结构获取有关运行pod的名称空间的信息:

 env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace 

运算符逻辑


为了了解如何区分使用ConfigMap和Secret的方法,我们将使用特殊的卡。 然后,我们可以了解跟踪和创建对象所需的方法:

 LIST_TYPES_MAP = { 'configmap': 'list_namespaced_config_map', 'secret': 'list_namespaced_secret', } CREATE_TYPES_MAP = { 'configmap': 'create_namespaced_config_map', 'secret': 'create_namespaced_secret', } 

接下来,您需要从API服务器接收事件。 我们将其实现如下:

 def handle(specs): kubernetes.config.load_incluster_config() v1 = kubernetes.client.CoreV1Api() #       method = getattr(v1, LIST_TYPES_MAP[specs['ruleType']]) func = partial(method, specs['namespace']) w = kubernetes.watch.Watch() for event in w.stream(func, _request_timeout=60): handle_event(v1, specs, event) 

收到事件后,我们继续其处理的主要逻辑:

 #  ,     ALLOWED_EVENT_TYPES = {'ADDED', 'UPDATED'} def handle_event(v1, specs, event): if event['type'] not in ALLOWED_EVENT_TYPES: return object_ = event['object'] labels = object_['metadata'].get('labels', {}) #    selector' for key, value in specs['selector'].items(): if labels.get(key) != value: return #   namespace' namespaces = map( lambda x: x.metadata.name, filter( lambda x: x.status.phase == 'Active', v1.list_namespace().items ) ) for namespace in namespaces: #  ,  namespace object_['metadata'] = { 'labels': object_['metadata']['labels'], 'namespace': namespace, 'name': object_['metadata']['name'], } #   /  methodcaller( CREATE_TYPES_MAP[specs['ruleType']], namespace, object_ )(v1) 

基本逻辑已准备就绪! 现在,您需要将所有这些打包到一个Python包中。 我们执行setup.py ,在其中写入有关项目的元信息:

 from sys import version_info from setuptools import find_packages, setup if version_info[:2] < (3, 5): raise RuntimeError( 'Unsupported python version %s.' % '.'.join(version_info) ) _NAME = 'copyrator' setup( name=_NAME, version='0.0.1', packages=find_packages(), classifiers=[ 'Development Status :: 3 - Alpha', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', ], author='Flant', author_email='maksim.nabokikh@flant.com', include_package_data=True, install_requires=[ 'kubernetes==9.0.0', ], entry_points={ 'console_scripts': [ '{0} = {0}.cli:main'.format(_NAME), ] } ) 

注意 :适用于Python的kubernetes客户端具有自己的版本控制。 您可以从兼容性列表中了解有关客户端版本和Kubernetes版本之间的兼容性的更多信息。

现在,我们的项目如下所示:

 copyrator ├── copyrator │ ├── cli.py #      │ ├── constant.py # ,     │ ├── load_crd.py #   CRD │ └── operator.py #     └── setup.py #   

Docker和Helm


Dockerfile将非常简单:获取基本的python-alpine映像并安装我们的软件包。 我们会将其优化推迟到更好的时候:

 FROM python:3.7.3-alpine3.9 ADD . /app RUN pip3 install /app ENTRYPOINT ["copyrator"] 

操作员的部署也非常简单:

 apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Chart.Name }} spec: selector: matchLabels: name: {{ .Chart.Name }} template: metadata: labels: name: {{ .Chart.Name }} spec: containers: - name: {{ .Chart.Name }} image: privaterepo.yourcompany.com/copyrator:latest imagePullPolicy: Always args: ["--rule-type", "main-rule"] env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace serviceAccountName: {{ .Chart.Name }}-acc 

最后,您必须为具有必要权限的操作员创建适当的角色:

 apiVersion: v1 kind: ServiceAccount metadata: name: {{ .Chart.Name }}-acc --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: {{ .Chart.Name }} rules: - apiGroups: [""] resources: ["namespaces"] verbs: ["get", "watch", "list"] - apiGroups: [""] resources: ["secrets", "configmaps"] verbs: ["*"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: {{ .Chart.Name }} roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: {{ .Chart.Name }} subjects: - kind: ServiceAccount name: {{ .Chart.Name }}-acc 

总结


因此,无需担心,责备和学习Go,我们就可以使用Python编写自己的Kubernetes运算符。 当然,他仍然有成长的空间:将来,他将能够处理多个规则,在多个线程中工作,独立监视其CRD中的更改...

为了更仔细地查看代码,我们将其放在公共存储库中 。 如果要使用Python实现更严格的运算符的示例,可以将注意力转向两个用于部署mongodb的运算符(第一个第二个 )。

PS并且,如果您懒于处理Kubernetes事件,或者您只是更习惯使用Bash,我们的同事将准备一个现成的解决方案,以shell运算符的形式(我们在4月宣布 )。

PPS


另请参阅我们的博客:

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


All Articles