이쿠의 슬기로운 개발생활

함께 성장하기 위한 보안 개발자 EverNote 내용 공유

Kubernetes/Kubernetes 이론

Custom Resource Definition (CRD) Python

이쿠우우 2020. 11. 5. 20:03
반응형

 

 

 

 

 

Custom Resource Definition (CRD) Python

 


[Kubernetes Custom Resource 관련글 목록]

Kubernetes Version 1.18 에 해당함.

Kubernetes Custom Resource 개념

Custion Resource Definition (CRD) Python

Custom Resource Definition (CRD) GoLang


 

사용 개발 도구 : Python Operator SDK

 

[목표]

iksoon.test.com CRD를 생성 시 

choice 옵션에 mysql 또는 tomcat를 선택하면

해당 image Pod가 생성되고 

Service도 NodePort Type으로 자동생성되도록 함.

choice 옵션에 mysql, tomcat 이외에 값이 올 경우 error 처리함.

 

[환경]

 

Master Node server

 OS = CentOS 7

 리눅스 커널 버전 : Linux 3.10.0-1062.el7.x86_64

 docker version : 1.13.1

 api verison : 1.26

 

Worker Node server

 OS = CentOS 7

 리눅스 커널 버전 : Linux 3.10.0-1062.el7.x86_64

 docker version : 1.13.1

 api verison : 1.26

 

Kubernetes version

 1.18.3

 


 

1. Custom Resource Definition (CRD) 생성

 

1.1. 예제 crd.yaml 

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  # name : 아래의 spec 필드 설정과 동일해야함. spec의 plural와 group를 조합한 값이와야 함. <plural>.<group>
  name: crdplural.iksoon.test.com
spec:
  # group name to use for REST API: /apis/<group>/<version>
  group: iksoon.test.com
  # either Namespaced or Cluster
  scope: Namespaced
  names:
    # plural name to be used in the URL: /apis/<group>/<version>/<plural>
    plural: crdplural
    # singular name to be used as an alias on the CLI and for display
    singular: crdsingular
    # kind is normally the CamelCased singular type. Your resource manifests use this.
    kind: CrdKind
    # shortNames allow shorter string to match your resource on the CLI
    shortNames:
    - crdshort
    - iksoontest
  # list of versions supported by this CustomResourceDefinition
  versions:
    - name: v1
      # Each version can be enabled/disabled by Served flag.
      served: true
      # One and only one version must be marked as the storage version.
      storage: true
      schema:
      # openAPIV3Schema is the schema for validating custom objects.
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                choice:
                  type: string
                  pattern: (mysql|tomcat)
                replicas:
                  type: integer
                  minimum: 1
                  maximum: 10
      additionalPrinterColumns:
        - name: DBorWAS
          type: string
          priority: 0
          jsonPath: .spec.choice
          description: Choose Mysql or Tomcat
        - name: replicas
          type: string
          priority: 1
          jsonPath: .spec.replicas
          description: The type of the database
        - name: Age
          type: date
          jsonPath: .metadata.creationTimestamp

참고 :  https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/

 

1.2. CRD 옵션 설명

 

[version.schema.openAPIV3Schema]

kubernetes 1.13 버전부터 제공되는 옵션으로

spec에 어떤 내용을 설정할 수 있는지 검증할 수 있는 validation기능을 사용할 수 있음.

 

상위 예제에서는 

yaml의 spec부분에 choice와 replicas를 설정하면

replicas는 최소 1에서 최대 10 사이로 설정되어있는지 확인하고

choice의 경우 string 형식으로 값을 설정하고

pattern 옵션을 사용했는데 

pattern은 정규식을 사용하여 올 수 있는 문자열을 지정하 수 있음

예제에서는 pattern 옵션을 사용하여 mysql 또는 tomcat 문자열만 사용할 수 있도록 설정함

mysql, tomcat이외의 문자열이 올 경우 오류가 발생하도록 함.

 

 

(사용할 수 있는 옵션은 아래와 같음)

더보기

 

description

example

exclusiveMaximum

exclusiveMinimum

externalDocs

format

items

maximum

maxItems

maxLength

minimum

minItems

minLength

multipleOf

pattern : 정규식 사용가능

properties

required

title

type

uniqueItems

 

 

[version.additionalPrinterColumns]

kubernetes 1.11 버전부터 제공되는 옵션으로

kubectl get 명령을 했을 때 보이는 정보를 결정하는 설정.

 

(name)

kubectl get 명령을 했을때 가장 위에 나오는

data 이름.

 

(type)

칼럼의 데이터형을 설정함.

integer  : 정수형 숫자

number : 실수형 숫자

string     : 문자열

boolean : true 또는 false

date       : 생성된 시간

 

(priority)

kubectl get 명령을 했을 때 설정값이 보일지 안보일지 설정하는 것.

0        : kubectl get 명령을 했을 때 보임

1이상 : kubectl get 명령을 했을 때 안보이고 -o wide 옵션을 사용하면 하면 보임

 

(jsonPath)

표시될 정보의 대상을 입력해줌.

예) 

.spec.replicas 로 설정하면 

상위 설정의 openAPIV3Schema의 spec 하위에 있는 replicas를 대상으로 삼음.

.meatadata.creationTimestamp 로 설정하면

해당 crd가 생성된 시간이 나옴.

 

(description)

해당 설정에 대한 설명을 기록해놓음.

 

 

1.3. CRD 생성

 

[명령어]

kubectl apply -f crd.yaml

 

[생성 확인 명령어]

kubectl get crd

 

 

 

 

2. CRD에 해당는 CrdKind 생성해보기

 

2.1. 예제 test.yaml 

apiVersion: "iksoon.test.com/v1"
kind: CrdKind
metadata:
  name: iksoon-crd-test
spec:
  choice: mysql
  replicas: 1

spec 하위에 설정되는 옵션은 생략해도 create는 성공하지만

없는 옵션이 추가될 수는 없음.

 

 

2.2. 생성

[명령어]

kubectl apply -f test.yaml

 

 

 

2.3. 생성 확인

상위의 plural, singular short 에 설정된 값으로 조회 가능.

-o wide 옵션을 사용하면

상위에서 additionalPrinterColumns 설정의 priority 값이 1로 설정된

REPLICAS 설정이 보임

 

2.4. 결과

 

상위 설정이 반영되서

choice 설정이 mysql 또는 tomcat 으로 설정된 경우만 정상적으로 생성되고

이외의 값이 올 경우에는 오류가 발생하면서 생성이 안됨.

 

[참고 mysql, tomcat이외의 값으로 설정된 경우]

 

 

하지만 이 단계까지 완료해서 crd 생성까지 하고 해당 내용도 조회가 되지만

실제적으로 pod가 생성되거나 service가 생성되진 않음.

그저 kube-ectd 에 해당 data를 저장할 뿐임.

다른 모듈이 etcd 에 생성된 crd data를 확인하고 

pod 또는 service를 생성하는 등 정해진 임무를 수행해야하는데

아직 custom controller가 없어서 etcd에 data가 생성되어도 특별한 동작을 하지 않음.

 

이제 해당 crd 에 해당하는 custom controller 를 생성해서 

목표로 하는 pod 와 service가 생성되도록 해야함.

 

 

 

3. Custom Controller 생성

 

사용 개발 도구 : Python Operator SDK

kopf https://github.com/zalando-incubator/kopf

 

다양한 custom controller 개발도구가 있지만

python Operator SDK와 kopf를 사용해서 구현해보겠음

 

 

3.1. Python kopf 예제 코드

import kopf
from kubernetes import client, config
import yaml
import logging
 
 
# kopf.on.create : k8s에서 crd create 명령을 인식함
# 'iksoon.test.com' : crd에서 group에 해당하는 값
# 'v1' : crd에서 version name 에 해당하는 값
# 'crdplural' : crd에서 plural에 해당하는 값
 
# body for the whole body of the handled objects.
# spec as an alias for body['spec'].
# meta as an alias for body['metadata'].
# status as an alias for body['status'].
@kopf.on.create('iksoon.test.com', 'v1', 'crdplural')
def create_fn(spec, meta, status, body, **kwargs):
    
    
    # crd 로 생성한 object의 정보를 가져옴
    name = body['metadata']['name']
    namespace = meta['namespace']
    choice= spec['choice']
    replicas= spec['replicas']
    logging.info("\n")
    logging.info('--create information---')
    logging.info("name = %s", name)
    logging.info("namespace = %s", namespace)
    logging.info("choice = %s", choice)
    logging.info("replicas = %s", replicas)
    logging.info("\n")       




    # choice 옵션이 정상적으로 있는지 확인
    if not choice:
        raise kopf.HandlerFatalError(f"Type must be set. Got {choice}.")


    # replicas 옵션이 정상적으로 있는지 확인
    if not replicas:
        raise kopf.HandlerFatalError(f"Type must be set. Got {replicas}.")


    # tomcat or mysql deployment, service 설정
    image = ''
    port = ''
    if choice == 'tomcat':
      image = 'peksoon/iksoon_tomcat:1.0.6'
      port = 8080
      logging.info("tomcat setting start")
    if choice == 'mysql':
      image = 'peksoon/iksoon_mysql:1.0.2'
      port = 3306
      logging.info("mysql setting start")




    # Service template
    svc = {
       'apiVersion': 'v1',
       'metadata': {
          'name' : name
       },
       'spec': {
           'selector': {
              'crdlabel': 'test'
           },
           'type': 'NodePort'
       }
    }


    svc['spec']['ports'] = [{ 'port': port, 'targetPort': port}]
    


    # Deployment template
    #deployment = create_deployment_object(name, port, image, replicas)
    deployment = yaml.safe_load(f"""
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: {name}
      labels:
        crd: deployment-test
    spec:
      replicas: {replicas}
      selector:
        matchLabels:
          crdlabel: test
      template:
        metadata:
          labels:
            crdlabel: test
        spec:
          containers:
          - name: {name}-container
            image: {image}
            ports:
            - containerPort: {port}
    """)




    logging.info("---deployment---")
    logging.info(deployment)
    logging.info("\n")       
    logging.info("---service---")
    logging.info(svc)
    logging.info("\n")       


 
    # Make the Deployment and Service the children of the Database object
    # adopt를 해줘야 kubectl delete -f 로 yaml를 삭제햇을 시 생성됐던 object가 모두 delete 됨.
    logging.info("---Make the Deployment, Service the children of the Database object---")
    kopf.adopt(deployment, owner=body)
    kopf.adopt(svc, owner=body)


    # Object used to communicate with the API Server
    logging.info("---Object used to communicate with the API Server---")
    core = client.CoreV1Api()
    app  = client.AppsV1Api()


    # Create Deployment
    logging.info("---Create Deployment---")
    app.create_namespaced_deployment(
        body=deployment,
        namespace=deployment['metadata']['namespace'])


    # Create Service
    logging.info("---Create Service---")
    obj = core.create_namespaced_service(namespace, svc)




    # Update status
    logging.info("---Update status---")
    msg = f"Deployment and Service created by Database {name}"
    return {'message': msg}




@kopf.on.delete('iksoon.test.com', 'v1', 'crdplural')
def delete(body, **kwargs):
    logging.info("---CRD delete---")
    msg = f"Database {body['metadata']['name']} and its Deployment / Service children deleted"
    return {'message': msg}

 

 

 

 

 

3.2. 상위 kopf가 실행되는 Container image 생성

 

DockerFile을 사용해서 생성

 

[Dockerfile 생성]

원활한 디버깅을 위해 필요한 tool을 전부 설치한 이미지를 생성

FROM centos:7


RUN yum install -y net-tools
RUN yum install -y gcc openssl-devel bzip2-devel libffi-devel wget
RUN yum install -y make
RUN yum update
RUN wget https://www.python.org/ftp/python/3.8.5/Python-3.8.5.tgz
RUN tar xvf Python-3.8.5.tgz
WORKDIR Python-3.8.5/
RUN ./configure --enable-optimizations
RUN make altinstall
RUN unlink /bin/python
RUN ln -s /usr/local/bin/python3.8 /bin/python


RUN pip3.8 install kopf
RUN pip3.8 install kubernetes
RUN pip3.8 install pyyaml


RUN mkdir /crd_test
WORKDIR /crd_test
ADD ./custom_controller.py .


CMD kopf run /crd_test/custom_controller.py

 

[Dockerfile 빌드 명령어]

docker build --tag customcontroller:1.0.0 .

 

[Docker hub에 push 해놓음]

docker tag [생성된 image id] peksoon/custom_controller:1.0.0

docker login

docker push peksoon/custom_controller:1.0.0

 

 

 

3.3. RBAC Rules 생성

 

kubernetes Cluster에 Custom Controller가 작업하는

Deployment, Serivce를 생성 권한이 필요하므로

ServiceAccount와 clusterRoleBinding을 생성해줘야함

 

[clusterRoleBinding_crd.yaml]

test용도이므로 모든 권한이 있는 cluster-admin을 사용

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: crd-operator
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: crd-operator
    namespace: default

kubectl apply -f 명령으로 생성

 

 

[sa_crd.yaml]

apiVersion: v1
kind: ServiceAccount
metadata:
  name: crd-operator

kubectl apply -f 명령으로 생성

 

 

 

3.4. Custom Controller Deployment 생성

 

[deployment_customController.yaml]

apiVersion: apps/v1
kind: Deployment
metadata:
  name: custom-controller
spec:
  selector:
    matchLabels:
      custom-controller: pod
  template:
    metadata:
      labels:
        custom-controller: pod
    spec:
      serviceAccountName: crd-operator
      containers:
      - image: peksoon/custom_controller:1.0.0
        name: custom-controller

 

 

 

[생성 확인]

 

 

 

 

4. CRD와 Custom Controller Test

 

4.1. 예제 test.yaml 

apiVersion: "iksoon.test.com/v1"
kind: CrdKind
metadata:
  name: iksoon-crd-test
spec:
  choice: tomcat
  replicas: 1

 

 

상위에서 해당 yaml을 생성햇을때는

deployment가 생성되지 않았지만

Custom Controller를 배포한 뒤에는 

Python 코드와 같이 deployment와 service가 생성되는지 확인

 

[결과]

custom controller 코드와 같이 deployment와 service가 생성됨

 

yaml 를 delete 하면 생성됐던 자원이 삭제됨

 

 

 

 

 

 

 

 

 


 

 

 

Pod, Service 가 생성되는 custom controller 예제

 

import kopf

import kubernetes

import yaml

import logging

 

 

@kopf.on.create('iksoon.test.com', 'v1', 'crdplural')

def create_fn(spec, meta, status, body, **kwargs):

 

    name = body['metadata']['name']

    namespace = meta['namespace']

    choice= spec['choice']

    replicas= spec['replicas']

    logging.info("\n")

    logging.info('--create information---')

    logging.info("name = %s", name)

    logging.info("namespace = %s", namespace)

    logging.info("choice = %s", choice)

    logging.info("replicas = %s", replicas)

    logging.info("\n")       

 

    if not choice:

        raise kopf.HandlerFatalError(f"Type must be set. Got {choice}.")

 

    if not replicas:

        raise kopf.HandlerFatalError(f"Type must be set. Got {replicas}.")

 

 

    # Pod template

    pod = {'apiVersion': 'v1', 'metadata': {'name' : name, 'labels': {'crdlabel': 'test'}}}

 

 

    # Service template

    svc = {

       'apiVersion': 'v1',

       'metadata': {

          'name' : name

       },

       'spec': {

           'selector': {

              'crdlabel': 'test'

           },

           'type': 'NodePort'

       }

    }

 

 

 

 

    # tomcat or mysql pod, service 설정

    image = ''

    port = ''

    if choice == 'tomcat':

      image = 'peksoon/iksoon_tomcat:1.0.6'

      port = 8080

      logging.info("tomcat setting start")

    if choice == 'mysql':

      image = 'peksoon/iksoon_mysql:1.0.2'

      port = 3306

      logging.info("mysql setting start")

 

 

    pod['spec'] = { 'containers': [ { 'image': image, 'name': choice } ]}

    svc['spec']['ports'] = [{ 'port': port, 'targetPort': port}]

 

 

    logging.info("---pod---")

    logging.info(pod)

    logging.info("\n")       

    logging.info("---service---")

    logging.info(svc)

    logging.info("\n")       

 

 

    # Make the pod and Service the children of the Database object

    logging.info("---Make the pod and Service the children of the Database object---")

    kopf.adopt(pod, owner=body)

    kopf.adopt(svc, owner=body)

 

 

 

 

    # Object used to communicate with the API Server

    logging.info("---Object used to communicate with the API Server---")

    api = kubernetes.client.CoreV1Api()

 

 

 

 

    # Create pod

    logging.info("---Create pod---")

    obj = api.create_namespaced_pod(namespace, pod)

 

 

    # Create Service

    logging.info("---Create Service---")

    obj = api.create_namespaced_service(namespace, svc)

 

 

    # Update status

    logging.info("---Update status---")

    msg = f"pod and Service created by Database {name}"

    return {'message': msg}

 

 

 

 

@kopf.on.delete('iksoon.test.com', 'v1', 'crdplural')

def delete(body, **kwargs):

    logging.info("---CRD delete---")

    msg = f"Database {body['metadata']['name']} and its pod / Service children deleted"

    return {'message': msg}

 

 

 

 

 

 

 


제 글을 복사할 시 출처를 명시해주세요.
글에 오타, 오류가 있다면 댓글로 알려주세요! 바로 수정하겠습니다!


 

 

 

 

 

참고

[additionalPrinterColumns]

https://kok202.tistory.com/303

 

[custom resource definition]

https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/

 

[apiserver aggregation]

https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/

 

 

[custom controller]

https://ssup2.github.io/programming/Kubernetes_Kubebuilder/

https://blog.baeke.info/2020/01/26/writing-a-kubernetes-operator-with-kopf/

 

 

반응형

'Kubernetes > Kubernetes 이론' 카테고리의 다른 글

Kubernetes hyperkube container  (0) 2020.11.05
Custom Resource Definition (CRD) Golang  (0) 2020.11.05
Kubernetes Custom Resource 개념  (0) 2020.11.05
Validating Admission Controller  (0) 2020.11.05
Mutating Admission Controller  (0) 2020.11.05