加入收藏 | 设为首页 |

宠儿-无需结构和SDK!运用Python来写一个Kubernetes Operator

海外新闻 时间: 浏览:270 次

现在,Go在人们创立Kubernetes Operator时选用的编程言语中成为了事实上的垄断者。他们的偏好源于以下客观原因:

Python资源共享群:626017123

  1. Operator SDK 这个强壮的结构可用于运用Go来开发Operator
  2. 许多根据Go的应用程序,例如Docker和Kubernetes,已成为改动游戏规矩的人物。运用Go来编写Operator答应你运用同种言语与这些生态对话。
  3. 根据Go的应用程序的高性能以及开箱即用的简略机制。

可是假如你短少时间或仅是积极性阻止了你学习Go呢?在此文中,咱们将向你展现怎么运用简直一切DevOps工程师了解的、最盛行的编程言语之一即Python来创立一个牢靠的Operator。

欢迎Copyrator!

为了简略有用,咱们将创立一个简略的Operator,用于当新的命名空间呈现或当ConfigMap或Secret两者之一更改其状况时仿制ConfigMap。从有用视点来看,咱们新的Operator可用于批量更新应用程序装备(经过更新ConfigMap)或许重设secr普通话等级ets。例如用于Docker Registry的密钥(当Secret增加到命名空间时)。

那么一个优异的Kubernetes Operator需具有什么功用呢?让咱们罗列一下:

  1. 与Operator的交互是经过 Custom Resource Definitions (以下简称CRD)
  2. 该Operator是可装备的。咱们能运用指令行参数或许是环境变量来装备它。
  3. Docker镜像和Helm图表在创立时考虑了易用性,所以用户能够毫不费力地装置它(根本上只需一个指令)到他们的Kubernetes集群。

CRD

为了让Operator知道哪些资源以及从哪里查找,咱们需求装备一些规矩。每个规矩将被表明为指定的CRD目标。那这个CRD目标中需求有哪些字段呢?

  1. 咱们所感兴趣的 资源的类型 (ConfigMap或许是Secret)
  2. 存储资源的 命名空间列表
  3. Selector 用于协助咱们在特定的命名空间中查找资源。

让咱们来界说咱们的CRD:

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: copyrator.flant.com
spec:
g宠儿-无需结构和SDK!运用Python来写一个Kubernetes Operatorroup: 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

并当即增加一个简略的规矩来挑选匹配在default命名空间中带有 copyrator: "true" 标签的ConfigMap。

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

现在咱们有必要以某种办法获取有关咱们规矩的信息。咱们将不运用手动办法制造集群API恳求。所以咱们将运用名为 kubernetes-client 的Python库:

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']}

十分好!现在咱们现已有一个针对Operator的规矩。更重要的是,咱们现已能够运用所谓的Kubernetes的办法来做到这一点。

环境变量仍是标志呢?我全都要!

现在是时分进行根本的Operator设置了。装备应用程序有两种首要的办法:

  • 经过指令行参数
  • 经过环境变量

你能够经过具有更多灵活性以及支撑数据类型验证的宠儿-无需结构和SDK!运用Python来写一个Kubernetes Operator指令行参数检索装备。咱们将运用 *argparser* 规范Python库中的模块。 Python文档中 供给了其运用的详细信息和示例。

以下是适配咱们需求的用于装备指令行标志检索的示例:

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.name宠儿-无需结构和SDK!运用Python来写一个Kubernetes Operatorspace

Operator的操作逻辑

让咱们运用指定的字典来区分运用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',
}

然后你需求从APIserver获取事情。咱们将以下面的办法来完结该功用:

def handle(specs):
kubernetes.config.load_incluster_config()
v1 = kubernetes.client.CoreV1Api()
# Get the method for tracking objects
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)

收到事情后,咱们持续处理它的根本逻辑:

Types of events to which we will respond

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', {})

# Look for the matches using selector

for key, value in specs['selector'].items():

if labels.get(key) != value:

return

# Get active namespaces

namespaces = map(

lambda x: x.metadata.name,

filter(

lambda x: x.status.phase == 'Active',

v1.list_namespace().items

)

)

for namespace in namespaces:

# Clear the metadata, set the namespace

object_['metadata'] = {

'labels': object_['metadata']['labels'],

'namespace': namespace,

'name': object_['metadata']['name'],

}

# Call the method for creating/updating an object

methodcaller(

CREATE_TYPES_MAP[specs['ruleType']],

namespace,

object_

)(v1)

完结根本逻辑之后,现在咱们需求将它打包到单个Python包中。咱们将创立 setup.py 并增加有关项目的元数据:

from sys import version_info
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),
]
}
)

留意:Kubernetes的Python客户端库有自己的版别控制系统。此 矩阵 中概述了客户端和Kubernetes版别的兼容性。

现在,咱们的项目具有以下结构:

copyrator
├── copyrator
│ ├── cli.py # 指令行操作逻辑
│ ├── constant.py # 上面界说的常量
│ ├── load_crd.py # CRD加载逻辑
│ └── operator.py # operator的集成逻辑
└── setup.py # 包描绘

Docker和Helm

生成的Dockerfile十分简略:咱们将选用根本的 python-alpine 根底镜像并装置咱们的软件包(咱们先疏忽掉优化相关部分):

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

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

最终,咱们有必要为operator创立一个具有必要权限的相关人物:

apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Chart.Name }}-acc
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
name: {{ .Chart.Name }}
rules:
- apiGroups: [""]
resources: ["name宠儿-无需结构和SDK!运用Python来写一个Kubernetes Operatorspaces"]
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 }}

定论

在本文中,咱们展现了怎么为Kubernetes创立自己的根据Python的Operator。当然它还有增加的空间,例如你能够经过处理多个规矩的才能来丰厚它,经过本身来监控CRD的改变,从并发才能中获益等等。

一切代码都能够在咱们的 公共存储库 中找到,以便你了解它。假如你对根据Python的Operator的其他示例感兴趣,咱们主张你重视两个用来布置mongodb的Operator, ( 12 )。

PS. 假如你不想处理Kubernetes事情,或许你更喜爱运用Bash,那么你或许也会喜爱咱们易于运用的称为 shell-operator 的解决计划(咱们已在4月份宠儿-无需结构和SDK!运用Python来写一个Kubernetes Operator 宣告 )。

再此PS,有一种运用Python编写K8s的代替计划-经过称为 kopf (Kubernetes Operator Pythonic Framework)的特定结构。假如你想最小化你的Python代码,它会很有用。点击这儿检查kopf 文档