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"
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
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
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
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 :
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
Và apply nó thôi.
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)
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.
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"]
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
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.
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
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.
All rights reserved