+2

Kubernetes - Custom Resources

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. Vậy thì...

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.

Ok, lý thuyết ngắn gọn vậy thôi, h ta đi vào thực hành nhé.

1. 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"

image.png

2. 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

image.png

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

image.png

3. 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

image.png

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 :

image.png

Check crd:

image.png

Có thể describe crd này để xem kỹ hơn :

image.png

Và chúng ta có thể check trong API resource :

image.png

4. 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

image.png

Và apply nó thôi.

image.png

image.png

Như vậy là CR đã được map đến CRD ở trên.

5. 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)

image.png

Cài đặt kubernetes library cho python :

pip install kubernetes

image.png

Run code :

image.png

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

image.png

image.png

image.png

image.png

Và check file index.html trong các pod

image.png

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.

6. 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"]

image.png

image.png

image.png

7. Tạo 1 deployment cho việc check configmap :

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

image.png

và apply nó thôi. Rồi, lỗi rồi:

image.png

image.png

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.

image.png

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.

image.png

Sửa thành : config.load_incluster_config()

image.png

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.

image.png

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.

8. 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

image.png

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

image.png

Apply thôi.

image.png

image.png

image.png

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

image.png

Apply nó, và cùng test nào.

image.png

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.

image.png H mình thực hiện thay đổi configmap này.

image.png image.png Và chờ xem thằng check config có chạy k nhé :

image.png Ten ten, đang xóa đi và tạo lại 1 loạt pods.

image.png Giờ check content trong index.html xem sao :

image.png

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.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí