Custom Resource Definition (CRD) Python
[Kubernetes Custom Resource 관련글 목록]
Kubernetes Version 1.18 에 해당함.
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 |