Kubernetes Custom Resource là gì?
Dựa theo documents của hãng https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/
Thì chúng ta có thể hiểu 1 cách đơn giản : Custom resource là 1 phần mở rộng của Kubernetes API.
(Cần nắm rõ về K8S API, vì đây là component rất quan trọng của K8S)
Custom resource cho phép administrator định nghĩa và quản lý các loại resource mới không có sẵn trong mặc định Kubernetes.
Custom Resource giúp Kubernetes có thể mở rộng và tùy chỉnh theo nhu cầu cụ thể của ứng dụng hoặc hệ thống.
Mình có 1 bài toán nhỏ như này để giúp mọi người có 1 cái nhìn khách quan hơn vể Custom Resource.
Ví dụ : Chúng ta có khoảng 10 deployment, và mỗi 1 deployment này sẽ có 1 configmap tương ứng. Khi chúng ta thay đổi giá trị trong configmap, để deployment ăn được new config thì cần phải restart lại các pods trong dlp đó.
Số lượng dlp ít thì không sao, làm tay cũng được. Nhưng trong trường hợp vài chục dlp đi kèm cài chục configmap thì hơi oải.
Ok, vậy mình sẽ viết custom resource để nó sẽ thay mình check các thay đổi trong configmap và restart pods tương ứng.
- Tạo 1 configmap đơn giản :
apiVersion: v1
kind: ConfigMap
metadata: name: my-configmap namespace: default
data: app-config: "Hello, this is version 1"
- Tạo deployment :
apiVersion: apps/v1
kind: Deployment
metadata: name: app-deployment namespace: default
spec: replicas: 20 selector: matchLabels: app: app-configmap template: metadata: labels: app: app-configmap spec: containers: - name: app-container image: nginx:latest volumeMounts: - name: config-volume mountPath: /usr/share/nginx/html/index.html subPath: app-config volumes: - name: config-volume configMap: name: my-configmap
Mục đích là sẽ gán giá trị trong configmap my-configmap tạo ở trên vào /usr/share/nginx/html/index.html bên trong pod. Sau khi deploy thì ta check content trong index.html
- Bây giờ ta sẽ tạo CustomResourceDefinition (CRD)
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata: name: configmaprestarters.example.com
spec: group: example.com versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: configMapName: type: string namespace: type: string scope: Namespaced names: plural: configmaprestarters singular: configmaprestarter kind: ConfigMapRestarter shortNames: - cmr
Define 1 chút về CRD trên nhé : Trong spec sẽ có những trường sau :
- group: example.com -> Group sẽ được tạo trong API resource
- name: v1 -> version của API
- served: true storage: true -> API có được sử dụng không, có được lưu trữ không.
- schema -> cấu trúc của CR với type là Object, và properties là configmapName và namespace.
- scope: Namespaced -> CRD sẽ áp dụng trong namespace
Sau đó apply CRD này :
Check crd:
Có thể describe crd này để xem kỹ hơn :
Và chúng ta có thể check trong API resource :
- Tạo custom resource:
apiVersion: example.co/v1
kind: ConfigMapRestarter
metadata: name: restart-pod-on-config-change namespace: default
spec: configMapName: my-configmap namespace: default
Và apply nó thôi.
Như vậy là CR đã được map đến CRD ở trên.
- Viết 1 script check thay đổi configmap, ở đây mình dùng python:
from kubernetes import client, config, watch config.load_kube_config() v1 = client.CoreV1Api()
custom_api = client.CustomObjectsApi()
namespace = "default"
configmap_name = "my-configmap" def restart_pods(namespace, configmap_name): print(f"Restarting pods that use ConfigMap: {configmap_name}") pods = v1.list_namespaced_pod(namespace) for pod in pods.items: for volume in pod.spec.volumes: if volume.config_map and volume.config_map.name == configmap_name: print(f"Deleting pod: {pod.metadata.name}") v1.delete_namespaced_pod(pod.metadata.name, namespace) #Watch for changes in ConfigMap
def watch_configmap(namespace, configmap_name): w = watch.Watch() for event in w.stream(v1.list_namespaced_config_map, namespace=namespace): cm = event['object'] if cm.metadata.name == configmap_name: print(f"ConfigMap {configmap_name} has changed. Event: {event['type']}") restart_pods(namespace, configmap_name) if __name__ == "__main__": print(f"Watching ConfigMap: {configmap_name} in namespace: {namespace}") watch_configmap(namespace, configmap_name)
Cài đặt kubernetes library cho python :
pip install kubernetes
Run code :
Ok, bây giờ ta mở thêm 1 tab mới, và thực hiện thay đổi giá trị của configmap từ 1 -> 2
Và check file index.html trong các pod
OK, vậy là CR, CRD, scripts chạy ngon rồi.
Tiếp theo, ta sẽ đóng gói script này lại, và tạo 1 deployment để nó sẽ chạy giúp chúng ta.
- Build image và push lên dockerhub
FROM python:3.10-slim WORKDIR /app COPY check_cm_controller.py /app/check_cm_controller.py RUN pip install kubernetes CMD ["python", "check_cm_controller.py"]
- Tạo 1 deployment để chạy images này nào :
apiVersion: apps/v1
kind: Deployment
metadata: name: restart-controller namespace: default
spec: replicas: 1 selector: matchLabels: app: restart-controller template: metadata: labels: app: restart-controller spec: containers: - name: restart-controller image: kiettran164/crds:cm-controller imagePullPolicy: Always
và apply nó thôi. Rồi, lỗi rồi:
Và logs báo như sau : Traceback (most recent call last):
│ File "/app/check_cm_controller.py", line 4, in <module> │ config.load_kube_config() │ File "/usr/local/lib/python3.10/site-packages/kubernetes/config/kube_config.py", line 819, in load_kube_config │ loader = _get_kube_config_loader( │ File "/usr/local/lib/python3.10/site-packages/kubernetes/config/kube_config.py", line 776, in _get_kube_config_loader │ raise ConfigException( │ kubernetes.config.config_exception.ConfigException: Invalid kube-config file. No configuration found.
Nguyên nhân : Lỗi này xảy ra do controller đang chạy trong Pod, và kubeconfig (thường dùng cho kết nối local) không khả dụng bên trong container.
Chúng ta sẽ cần phải sửa lại code trong python.
Sửa thành : config.load_incluster_config()
Mục đích : cho phép script Python kết nối với Kubernetes API Server từ bên trong.
Xong build lại và push image lên Docker Hub again.
Từ đoạn này mình phải chuyển sang 1 cụm khác, vì proxy của cụm hiện tại đang lỗi.
- Cấp cho các con pod bên trong deployment check cm có quyền để đọc và xóa Pod, cũng như theo dõi ConfigMap. Để làm được điều này, ta cần tạo serviceaccount, role và role binding serviceaccount.yml
apiVersion: v1
kind: ServiceAccount
metadata: name: restart-controller-sa namespace: default
rbac-role.yml
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: restart-controller-role namespace: default rules: - apiGroups: [""] resources: ["pods", "configmaps"] verbs: ["get", "list", "watch", "delete"] ---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: name: restart-controller-rolebinding namespace: default
subjects: - kind: ServiceAccount name: restart-controller-sa namespace: default
roleRef: kind: Role name: restart-controller-role apiGroup: rbac.authorization.k8s.io
Apply thôi.
Rồi, h tạo deployment cho thằng check configmap thôi :
apiVersion: apps/v1 kind: Deployment metadata: name: restart-controller namespace: default spec: replicas: 1 selector: matchLabels: app: restart-controller template: metadata: labels: app: restart-controller spec: serviceAccountName: restart-controller-sa containers: - name: restart-controller image: kiettran164/crds:cm-controller-v2 imagePullPolicy: IfNotPresent
Apply nó, và cùng test nào.
Hiện tại mình có 20 con pod, và 1 pod sẽ làm nhiệm vụ check configmap. Đây là configmap hiện tại.
H mình thực hiện thay đổi configmap này.
Và chờ xem thằng check config có chạy k nhé :
Ten ten, đang xóa đi và tạo lại 1 loạt pods.
Giờ check content trong index.html xem sao :
wasadm@masternode:~/crds/cm-controller$ kubectl exec -it app-deployment-79f7cfc658-26658 -- cat /usr/share/nginx/html/index.html
Hello, this is version 20
Vậy là thành công. Phần sau mình sẽ viết 1 Operator để kết hợp với thằng này. Chúc mọi người lab thành công.