Kubernetes python package

Posted by chunyang on May 7, 2022

在项目中使用比较多的 kubernetes。主要使用相应的 python kubernetes 库包来进行各种部署的管理。 国内很多讲解 kubernetes 原理,但是对于这个库各种使用看起来比较少。

Initialization

网上搜索得到比较多的是使用默认的 kubeconfig 来初始化。

import os
from kubernetes import client, config

config.load_kube_config(os.path.expanduser("~/.kube/config"))

core_v1_api = client.CoreV1Api()
apps_v1 = client.AppsV1Api()
batch_v1_api = client.BatchV1Api()
networking_v1_api = client.NetworkingV1Api()

如果我们要管理多个集群,这种方式就不行。因为是使用了默认的 api_client。如何按需创建需要的 api_client 呢?

import os
from kubernetes import client, config
from kubernetes.client import ApiClient, Configuration

config_file = os.path.expanduser("~/.kube/config")

client_configuration = Configuration()
config.load_kube_config(
    config_file=config_file,
    client_configuration=client_configuration,
)
api_client = ApiClient(configuration=client_configuration)
core_v1_api = client.CoreV1Api(api_client)

我们就可以用这段代码去初始化不同的 api_client,然后用 client 来获取对应的 API 操作对象。

Deployments

为了简化代码,后续所有的示例都采用默认的配置,而不是单独创建 api_client 的形式。

创建一个 deployment,使用的是 AppsV1Api.create_namespaced_deployment

apps_v1_api = client.AppsV1Api()
namespace = "test"
response = apps_v1_api.create_namespaced_deployment(body=yaml_content, namespace=namespace)

这里的yaml_content 是创建具体的 deployment 的内容。

{{image}} 请替换成真实的镜像名字

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: '1'
  labels:
    app: my-awesome-deployment
spec:
  progressDeadlineSeconds: 600
  backoffLimit: 5
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: my-awesome-deployment
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: my-awesome-deployment
    spec:
      containers:
        - image: {{image}}
          imagePullPolicy: Always
          name: my-awesome-deployment
          ports:
            - containerPort: 8080
              protocol: TCP
          resources:
            requests:
              cpu: 1
              memory: 1Gi
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30

Deployment 的其它接口

# 不需要修改 replica
apps_v1_api.patch_namespaced_deployment(
     name, body=body, namespace=namespace
)

# 需要修改 replica
apps_v1_api.patch_namespaced_deployment_scale(
    name, body=body, namespace=namespace
)

apps_v1_api.read_namespaced_deployment(name, namespace=namespace)


apps_v1_api.delete_namespaced_deployment(name, namespace)

重启 deployment (patch_xxx)

重启实际上是需要增加一个新的 annotations

import datetime
import pytz

# deployment 就是加载进来的 yaml_content

deployment["spec"]["template"]["metadata"]["annotations"] = {
    "kubectl.kubernetes.io/restartedAt": datetime.datetime.utcnow()
    .replace(tzinfo=pytz.UTC)
    .isoformat()
}

Service and ingress

Service

Service 使用的是 CoreV1Api

core_v1_api = client.CoreV1Api()
core_v1_api.create_namespaced_service(body=yaml_content, namespace=namespace)
core_v1_api.delete_namespaced_service(name, namespace)

yaml_content 内容如下:

apiVersion: v1
kind: Service
metadata:
  name: {{service_name}}
spec:
  ports:
    - port: {{service_port}}
      targetPort: {{target_port}}
      protocol: TCP
  selector:
    app: {{target_name}}
  sessionAffinity: None
  type: {{service_type}}

注意填充上上述对应 {{}} 中的内容。其中要注意的是 selector 是和 deployment 中对应配置是对上的。spec.template.metadata 中的 labels

Ingress

Ingress 使用的 NetworkingV1Api

networking_v1_api = client.NetworkingV1Api()
core_v1_api.create_namespaced_ingress(body=yaml_content, namespace=namespace)
networking_v1_api.delete_namespaced_ingress(name, namespace)

yaml_content 如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress-name
spec:
  rules:
  - host: my-host
    http:
      paths:
      - backend:
          service:
            name: my-service-name
            port:
              number: my-service-port
        path: my-path
        pathType: Prefix  # Exact

上述所有 my- 开头都希望用户自己填充实际的。其中 my-service-name 和创建的 service 名称对应上。

Job

Job 使用的是 BatchV1Api

batch_v1_api = client.BatchV1Api()
batch_v1_api.create_namespaced_job(body=yaml_content, namespace=namespace)
batch_v1_api.read_namespaced_job(name, namespace)
batch_v1_api.delete_namespaced_job(name, namespace)

yaml_content 内容如下:

apiVersion: batch/v1
kind: Job
metadata:
  labels:
    app: {{ name }}
  name: {{ name }}
spec:
  ttlSecondsAfterFinished: 3600
  template:
    metadata:
      labels:
        app: {{ name }}
    spec:
      containers:
      - name: {{ name }}
        image: {{ image }}
        resources:
          requests:
            cpu: 1
            memory: 2Gi
        command: {{ command|safe }}
      restartPolicy: Never
  backoffLimit: 0

请自行填充 {{ }} 中的内容。

Logs and Events

查看 POD 的日志和事件时,一般需要先知道有哪些 POD,然后遍历这些 POD 来获取对应的内容。

core_v1_api = client.CoreV1Api()
# 使用具体的 label_selector 来选择相应的 POD
all_pods = core_v1_api.list_namespaced_pod(namespace=namespace, label_selector="app=my-awesome-deployment")

# all_pods 有个接口 `to_dict()`,可以把它打印出来,然后看怎么找到 POD name
dict_response = all_pods.to_dict()
pod_names = [item["metadata"]["name"] for item in dict_response["items"]]

Log

core_v1_api = client.CoreV1Api()
namespace = "test"
response = core_v1_api.read_namespaced_pod_log(
    name=name, namespace=namespace, tail_lines=100
)

event

core_v1_api = client.CoreV1Api()
namespace = "test"
pod_name = "zhangsan"
core_v1_api.list_namespaced_event(
    namespace=namespace,
    pretty=True,
    field_selector=f"involvedObject.name={pod_name}"
)

其它

  • Api 对应的函数有个 async_req=True 入参,可以发起异步调用,response.get() 拿到结果
  • 结果一般会有一个 to_dict 函数,可以通过打印这个结果来判断怎么去提取对应的信息
  • 建议 yaml + jinja2 来进行模板配置和模板的渲染,这样可以方便生成想要的 yaml_content

Reference