DaemonSet chạy đúng một bản trên mỗi node. Nghe đơn giản, nhưng "mỗi node" có một điều kiện ẩn mà phép đo đầu tiên đã cho thấy.
Bốn node, ba bản
agent-qpbmg sc-worker
agent-9jjrn sc-worker2
agent-hq278 sc-worker3
DESIRED=3 CURRENT=3 READY=3
Cụm có bốn node. DaemonSet tạo ba bản.
Node control plane bị bỏ qua vì nó có vết (taint), và DaemonSet mặc định không khai toleration nào — cùng cơ chế đã đo ở phần 47.
Thêm một dòng:
tolerations:
- {operator: Exists}
DESIRED=4 READY=4
operator: Exists không kèm key nghĩa là chịu được mọi vết. Đó chính là cách kube-proxy, CNI plugin và các CSI driver phủ được cả control plane — chúng phải có mặt ở mọi node để cụm hoạt động.
Với DaemonSet của bạn, operator: Exists không kèm điều kiện là quá rộng: nó cũng khiến pod chạy trên node đang bị drain để bảo trì. Nên khai cụ thể:
tolerations:
- {key: node-role.kubernetes.io/control-plane, operator: Exists, effect: NoSchedule}
Phản ứng trong 0,1 giây
Thêm nodeSelector rồi gán và gỡ nhãn:
chưa node nào có nhãn: DESIRED=0 READY=0
gán nhãn cho sc-worker -> có pod sau 0,1 giây
gán nhãn cho sc-worker2 -> có pod sau 0,1 giây
gỡ nhãn khỏi sc-worker2 -> pod bị xoá sau 2,2 giây
Một phần mười giây.
Bộ điều khiển DaemonSet theo dõi node qua watch (phần 46), nên nó thấy thay đổi ngay khi apiserver ghi nhận.
Chú ý sự tương phản với phần 42: node chết thì mất gần sáu phút mới có gì xảy ra. Node đổi nhãn thì 0,1 giây. Khác biệt không nằm ở bộ điều khiển mà ở chỗ Kubernetes phải mất bao lâu để kết luận rằng node đã chết.
Hệ quả thực dụng: khi cụm tự co giãn thêm node, DaemonSet có mặt trên node mới gần như ngay lập tức — trước cả khi pod ứng dụng được xếp lên đó. Đó là điều bắt buộc với agent thu log và plugin mạng.
Ba việc DaemonSet dùng để làm
Thu log — mỗi node một agent đọc /var/log/containers qua hostPath.
Đo đạc — node-exporter, cAdvisor, agent của hệ thống giám sát.
Hạ tầng mạng và lưu trữ — kube-proxy, CNI plugin (phần 60 đo kindnet và Calico, cả hai đều là DaemonSet), CSI node driver.
Điểm chung: việc gắn với node, không gắn với lượng người dùng. Thêm node là phải có thêm một bản; thêm tải thì không.
Đó cũng là lý do DaemonSet không có replicas. Số bản bằng số node khớp bộ chọn, và Kubernetes tự cộng trừ. Nó không co giãn được, và cũng không cần.
Cập nhật cuốn chiếu
updateStrategy:
type: RollingUpdate
rollingUpdate: {maxUnavailable: 1}
maxUnavailable ở đây có ý nghĩa khác Deployment: nó là số node được phép thiếu bản trong một lúc, không phải tỉ lệ pod.
Và không có maxSurge theo mặc định — vì hai bản trên cùng một node thường xung đột (cùng gắn hostPath, cùng cổng hostPort). Từ 1.22 có maxSurge cho DaemonSet nhưng phải bật rõ ràng.
Với cụm 100 node và maxUnavailable: 1, cập nhật một DaemonSet mất 100 lượt. Đặt maxUnavailable: 10% là cách rẻ để rút ngắn.
OnDelete là chiến lược thứ hai: chỉ cập nhật khi bạn tự xoá pod. Hữu ích với agent mà việc khởi động lại có rủi ro — bạn kiểm soát từng node một.
Ba thứ thường đi kèm
hostPath để đọc thư mục của node — log, /proc, socket của runtime.
hostNetwork: true để dùng thẳng không gian mạng của node. kube-proxy cần điều này vì nó sửa iptables của node.
priorityClassName: system-node-critical để DaemonSet hạ tầng không bị đuổi khi node cạn tài nguyên (phần 55) và được ưu tiên khi xếp chỗ.
Ba thứ này cộng lại khiến DaemonSet có quyền lớn hơn pod thường rất nhiều — nên chúng cũng là mục tiêu đáng chú ý về bảo mật. Một DaemonSet bị chiếm quyền có hostPath: / là quyền root trên mọi node.
Thử ba mươi giây
Xem DaemonSet nào của bạn không phủ hết node:
kubectl get ds -A -o custom-columns=\
NS:.metadata.namespace,TEN:.metadata.name,\
MONG_MUON:.status.desiredNumberScheduled,\
SAN_SANG:.status.numberReady
kubectl get nodes --no-headers | wc -l
MONG_MUON nhỏ hơn số node là bình thường — thường vì vết hoặc nodeSelector. SAN_SANG nhỏ hơn MONG_MUON thì không: có node đang thiếu agent, và với agent thu log nghĩa là log của node đó đang mất.
Phần sau đo Job và CronJob: chạy một lần rồi thôi, và ba cách nó hỏng.