Kubernetes 이야기

Kopf ( 예제 ) 본문

개발/python

Kopf ( 예제 )

kmaster 2023. 2. 5. 21:54
반응형

 

다음과 같은 CRD 가 존재한다고 가정한다. 해당 CRD는 Tomcat Service의 Replica와 Image 명을 활용하여 Deployment 객체와 Service 객체를 만드는 CRD이다.

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: appdeployments.kmaster8.com
spec:
  group: kmaster8.com
  names:
    kind: AppDeployment
    plural: appdeployments
    singular: appdeployment
    shortNames:
      - ads
      - ad
  scope: Namespaced
  versions:
  - name: v1beta1
    schema:
      openAPIV3Schema:
        properties:
          spec:
            properties:
              replica:
                type: integer
              image:
                type: string
              svcPort:
                type: integer
            required:
            - replica
            - image
            - svcPort
            type: object
          status:
            type: object
            x-kubernetes-preserve-unknown-fields: true
        type: object
    served: true
    storage: true
    additionalPrinterColumns:
    - name: replica
      type: integer
      jsonPath: .spec.replica
      description: Replica
    - name: image
      type: string
      jsonPath: .spec.image
      description: Container Image
    - name: svcPort
      type: integer
      jsonPath: .spec.svcPort
      description: Service Port

 

이제 ads 에 해당하는 다음과 같이 CR 을 생성해 보자.

apiVersion: kmaster8.com/v1beta1
kind: AppDeployment
metadata:
  name: app-sample
spec:
  replica: 1
  image: "nginx:1.22.1"
  svcPort: 80

 

생성 된 CR을 조회하면 다음과 같다.

# k get ads -A
NAMESPACE   NAME         REPLICA   IMAGE          SVCPORT
test        app-sample   1         nginx:1.22.1   80

 

이제 AppDeployment의 생성 및 수정에 대해 알아보자.

 

먼저 AppDeployment CR 을 생성되면 만들어지게 될 Deployment와 Service yaml 템플릿을 만들어보자.

 

[Deployment.yaml]

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {name}
spec:
  selector:
    matchLabels:
      app: {name}
  replicas: {replica}
  template:
    metadata:
      labels:
        app: container
    spec:
      containers:
      - name: container
        image: {image}
        ports:
        - containerPort: {svcPort}

 

[Service.yaml]

apiVersion: v1
kind: Service
metadata:
  name: {name}
  labels:
    app: {name}
spec:
  ports:
  - name: svc-port
    port: {svcPort}
    protocol: TCP
  selector:
    app: {name}

 

on.create

import kopf
import yaml
import kubernetes.client
import os

@kopf.on.create('kmaster8.com', 'v1beta1', 'appdeployments')
def create_fn(spec, name, namespace, logger, **kwargs):

    replica = spec.get('replica')
    image = spec.get('image')
    svcPort = spec.get('svcPort')

    if not replica:
        raise kopf.PermanentError(f"Replica must be set. Got {replica!r}.")

    if not image:
        raise kopf.PermanentError(f"Image must be set. Got {image!r}.")

    if not svcPort:
        raise kopf.PermanentError(f"svcPort must be set. Got {svcPort!r}.")


    # deployment
    path = os.path.join(os.path.dirname(__file__), 'deployment.yaml')
    tmpl = open(path, 'rt').read()
    text = tmpl.format(name=name, namnespace=namespace, replica=replica, image=image, svcPort=svcPort)
    data = yaml.safe_load(text)

    kopf.adopt(data)

    api = kubernetes.client.AppsV1Api()
    dep = api.create_namespaced_deployment(
        namespace=namespace,
        body=data,
    )

    logger.info(f"App Deployment child is created: {dep}")

    # service
    path = os.path.join(os.path.dirname(__file__), 'service.yaml')
    tmpl = open(path, 'rt').read()
    text = tmpl.format(name=name, namnespace=namespace, svcPort=svcPort)
    data = yaml.safe_load(text)

    kopf.adopt(data)

    api = kubernetes.client.CoreV1Api()
    svc = api.create_namespaced_service (
        namespace=namespace,
        body=data,
    )

    logger.info(f"App Deployment child is created: {svc}")

    return {
        'deployment': dep.metadata.name,
        'service': svc.metadata.name
    }

 

on.update

 

이제 생성된 deployment와 service 의 replica, image, port를 "2", "tomcat:9", "8080" 으로 변경하는 오퍼레이터를 만들어보자. ( 중요 코드만 살펴보자. )

@kopf.on.update('kmaster8.com', 'v1beta1', 'appdeployments')
def update_fn(spec, status, namespace, logger, **kwargs):

    # deployment
    deployment_name = status['create_fn']['deployment']
    body = [
             {"op": "replace", "path": "/spec/replicas", "value": replica},
             {"op": "replace", "path": "/spec/template/spec/containers/0/image", "value": image},
             {"op": "replace", "path": "/spec/template/spec/containers/0/ports/0/containerPort", "value": svcPort},
           ]

    api = kubernetes.client.AppsV1Api()
    dep = api.patch_namespaced_deployment(
        namespace=namespace,
        name=deployment_name,
        body=body,
    )

    # service
    service_name = status['create_fn']['service']
    body = [
             {"op": "replace", "path": "/spec/ports/0/port", "value": svcPort},
           ]

    api = kubernetes.client.CoreV1Api()
    svc = api.patch_namespaced_service (
        namespace=namespace,
        name=service_name,
        body=body,
    )

 

 

on.field

Old & New

 

특정 필드의 값을 업데이트 하고자 하는 경우에는 on.field를 사용한다.

@kopf.on.field('kmaster8.com', 'v1beta1', 'appdeployments', field='metadata.labels')
def relabel(old, new, status, namespace, logger, **kwargs):

    patch = {'metadata': {'labels': new}}

    # deployment
    deployment_name = status['create_fn']['deployment']
...

 

Diffs

 

개체의 상태를 추적하고 diff를 계산하는데 사용한다.

사용법은 다음과 같다.

((action, n-tuple of object or field path, old, new),)

예를들어,

(('add', ('metadata', 'labels', 'label1'), None, 'new-value'),
('change', ('metadata', 'labels', 'label2'), 'old-value', 'new-value'),
('remove', ('metadata', 'labels', 'label3'), 'old-value', None),
('change', ('spec', 'size'), '1G', '2G'))

다음의 코드를 보자.

@kopf.on.field('kmaster8.com', 'v1beta1', 'appdeployments', field='metadata.labels')
def diff_fn(diff, status, namespace, logger, **kwargs):
    logger.info(diff)

annotation에 label을 추가하면

 

[실행결과]

(('add', (), None, {'app': 'test'}),)

 

반응형

'개발 > python' 카테고리의 다른 글

playwright를 활용한 e2e 테스트  (0) 2023.02.15
Python에서 gRPC 구현  (0) 2023.02.13
Kopf (소개)  (0) 2023.02.03
Ubuntu에서 pipenv 실행 시 FileNotFoundError 오류  (0) 2023.01.28
Loguru  (0) 2023.01.24
Comments