kubeadm init cluster và join node
Mục tiêu bài học
Sau bài này, bạn phải làm được 4 việc sau, theo đúng thứ tự mình vẫn làm trong phòng thi:
- Chuẩn bị node đủ điều kiện để
kubeadm initchạy qua preflight check mà không phải vá víu. - Khởi tạo control plane bằng
kubeadm init, cài CNI và xác nhận node ở trạng tháiReady. - Join worker node bằng join command, biết cách tạo lại join command khi token đã hết hạn.
- Đọc đúng thông báo lỗi khi node không join được và xử lý trong vòng 2 phút.
[!NOTE] Ôn nhanh bài trước: control plane gồm kube-apiserver, etcd, kube-scheduler, kube-controller-manager, tất cả chạy dưới dạng static pod trong
/etc/kubernetes/manifests. Kubelet trên mỗi node là thứ duy nhất không phải pod, và nó là "tay sai" trực tiếp của control plane. Hiểu điều này thì bạn sẽ hiểu vì sao lỗi join gần như luôn nằm ở kubelet hoặc ở kết nối tới cổng 6443.
Ví dụ mẫu từng bước
Mình dựng lab 3 máy: cp1 (192.168.10.10), worker1 (192.168.10.11), worker2 (192.168.10.12). Toàn bộ lệnh dưới đây chạy bằng sudo.
Bước 1: Chuẩn bị trên cả 3 node
# Tắt swap ngay và tắt vĩnh viễn
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
# Kernel module cho CNI
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay && sudo modprobe br_netfilter
# Sysctl bắt buộc
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
Sau đó cài containerd và đặt cgroup driver là systemd (mặc định của kubelet từ 1.22), rồi cài kubeadm, kubelet, kubectl cùng version và apt-mark hold chúng lại để tránh upgrade ngoài ý muốn.
[!IMPORTANT] Ba thứ hay bị bỏ sót nhất: swap chưa tắt,
net.ipv4.ip_forwardchưa bật, vàSystemdCgroup = truechưa set trong/etc/containerd/config.toml. Thiếu một trong ba,kubeadm inithoặc kubelet sẽ lỗi ngay.
Bước 2: Khởi tạo control plane
Cách nhanh nhất, dùng cho lab và cho đề thi nếu đề không bắt dùng file config:
sudo kubeadm init \
--apiserver-advertise-address=192.168.10.10 \
--pod-network-cidr=10.244.0.0/16
Output cuối cùng sẽ có đúng 2 thứ bạn cần giữ lại:
Your Kubernetes control-plane has initialized successfully!
kubeadm join 192.168.10.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:3f8c...e21
Bước 3: Cấu hình kubectl cho user hiện tại
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl get nodes
Lúc này cp1 sẽ ở trạng thái NotReady. Đó là bình thường, vì chưa có CNI.
Bước 4: Cài CNI
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
kubectl get pods -n kube-system -w
Đợi kube-flannel-*, coredns-*, kube-proxy-* đều Running, rồi kiểm tra kubectl get nodes. Giờ cp1 mới là Ready.
Bước 5: Join worker node
Chạy đúng lệnh join ở trên, trên worker1:
sudo kubeadm join 192.168.10.10:6443 --token abcdef.0123456789abcdef \
--discovery-token-ca-cert-hash sha256:3f8c...e21
Rồi quay lại cp1 kiểm tra:
kubectl get nodes -o wide
Sơ đồ luồng đầy đủ:
flowchart TD
Prep["🧹<br/>Chuẩn bị node<br/>swap off, sysctl, containerd"] --> Init["🚀<br/>kubeadm init<br/>control plane"]
Init --> Kubeconfig["🔑<br/>Copy admin.conf<br/>về ~/.kube/config"]
Init --> Token["🎫<br/>Join command<br/>token + CA hash"]
Kubeconfig --> CNI["🌐<br/>Cài CNI plugin"]
CNI --> Ready1["✅<br/>cp1 Ready"]
Token --> Join["📥<br/>kubeadm join<br/>trên worker"]
Join --> Ready2["✅<br/>worker Ready"]
Luyện tập có hướng dẫn
Phần này là xương sống của bài. Bạn làm lần lượt, mỗi bài đều có lệnh kiểm tra để biết mình đã đúng.
Bài 1: Chuẩn bị node và tự kiểm tra
Chạy phần chuẩn bị ở Bước 1 trên cả 3 node, sau đó xác nhận:
swapon --show # phải không in ra gì
sysctl net.ipv4.ip_forward # phải là = 1
systemctl is-active containerd # phải là active
kubeadm version -o short
[!TIP] Trong đề thi CKA, các node thường đã được chuẩn bị sẵn. Bạn chỉ cần chạy
kubeadm inithoặckubeadm join. Nhưng khi luyện ở nhà, hãy tự tay làm phần chuẩn bị ít nhất một lần, vì đề mock capstone ở Module 8 sẽ bắt bạn làm lại từ đầu.
Bài 2: Init bằng file config
Đề thi đôi khi đưa sẵn file config và yêu cầu dùng nó. Sinh file mẫu trước, đừng viết tay từ đầu:
kubeadm config print init-defaults > kubeadm-config.yaml
Sửa lại các trường quan trọng:
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.168.10.10
bindPort: 6443
nodeRegistration:
criSocket: unix:///run/containerd/containerd.sock
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.31.0
controlPlaneEndpoint: "192.168.10.10:6443"
networking:
podSubnet: 10.244.0.0/16
Rồi chạy:
sudo kubeadm init --config kubeadm-config.yaml
[!WARNING]
apiVersionphụ thuộc version kubeadm. Bản 1.31 trở lên dùngkubeadm.k8s.io/v1beta4, các bản cũ hơn dùngv1beta3. Đừng học thuộc, hãy luôn chạykubeadm config print init-defaultsđể lấy đúng apiVersion của máy đang dùng.
Bài 3: Cài CNI và xác nhận Ready
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
kubectl get nodes
kubectl get pods -n kube-system
Điều kiện đạt: cp1 là Ready, và các pod coredns, kube-proxy, kube-flannel đều Running.
Bài 4: Join worker1 bằng join command
Copy nguyên lệnh kubeadm join ... từ output của Bước 2, chạy trên worker1. Nếu bạn đã mất output đó, tạo lại bất cứ lúc nào:
kubeadm token create --print-join-command
Lệnh này in ra join command đầy đủ, đúng token và đúng CA hash, dùng được ngay. Đây là lệnh mình dùng nhiều nhất trong phòng thi, hãy nhớ nó.
Kiểm tra:
kubectl get nodes -o wide
kubectl describe node worker1 | grep -A5 Conditions
Bài 5: Mô phỏng token hết hạn
Đây là tình huống gần như chắc chắn xuất hiện trong mock capstone. Giả lập:
kubeadm token list # xem TTL, mặc định 24h
kubeadm token delete <token-id>
Giờ thử join worker2 bằng token cũ, bạn sẽ nhận lỗi:
error execution phase preflight: couldn't validate the identity of the API Server:
could not find a JWS signature in the cluster-info ConfigMap for token ID "..."
Cách xử lý: tạo join command mới rồi join lại, không cần đụng gì tới control plane.
Bài 6 (tùy chọn, nâng cao): Join control plane node thứ hai
Nếu lab cho phép, thêm một control plane node thứ hai. Yêu cầu bắt buộc là phải có --control-plane và một certificate key còn hạn:
sudo kubeadm init phase upload-certs --upload-certs
# in ra certificate key, mặc định có hiệu lực 2 giờ
sudo kubeadm join 192.168.10.10:6443 --control-plane \
--certificate-key <key> \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
Sau khi join, chạy kubectl get nodes và thấy node mới với role control-plane,master.
Tự kiểm tra nhanh
Flag dùng để khai báo dải IP cho Pod khi chạy kubeadm init là {{c1::--pod-network-cidr}}.
Bootstrap token mặc định hết hạn sau {{c2::24 giờ}}.
Join một node với vai trò control plane cần thêm flag {{c3::--control-plane}} và {{c4::--certificate-key}}.
Trước khi join lại một node đã từng nằm trong cluster, cần chạy {{c5::kubeadm reset}}.
Cổng mặc định của kube-apiserver là {{c6::6443}}.
Lỗi thường gặp và cách xử lý
Bảng dưới là những lỗi mình gặp nhiều nhất, cả khi luyện và trong mock exam.
| Triệu chứng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
Join báo could not find a JWS signature hoặc token ... not found |
Token hết hạn hoặc gõ sai token | kubeadm token create --print-join-command rồi join lại |
Join báo x509: certificate signed by unknown authority |
CA cert hash sai hoặc thiếu | Lấy hash từ join command mới, không gõ tay |
| Join treo rồi timeout | Firewall chặn 6443 hoặc 10250 | Kiểm tra curl -k https://192.168.10.10:6443 từ worker |
Join thành công nhưng node NotReady |
Chưa có CNI plugin | Cài CNI trên control plane, đợi vài phút |
kubeadm init dừng ở preflight Swap |
Swap chưa tắt | swapoff -a và sửa /etc/fstab |
kubeadm init dừng ở FileContent--proc-sys-net-bridge-bridge-nf-call-iptables |
Thiếu br_netfilter hoặc sysctl |
Bật module, chạy lại sysctl --system |
kubelet CrashLoopBackOff/restart liên tục |
Cgroup driver lệch (systemd vs cgroupfs) |
Set SystemdCgroup = true trong containerd config, restart containerd |
| Hostname trùng nhau giữa các node | Image VM clone không đổi hostname | hostnamectl set-hostname cho từng node |
| Node join được nhưng lệch giờ gây lỗi cert | NTP không chạy | Bật systemd-timesyncd hoặc chrony |
Quy trình đọc lỗi mình luôn làm khi node không join được:
systemctl status kubelet
journalctl -u kubelet -n 50 --no-pager
sudo kubeadm join ... --v=5 # tăng verbosity để thấy bước nào fail
[!CAUTION] Đừng bao giờ dùng
--ignore-preflight-errorsđể "cho qua" lỗi swap hay lỗi sysctl trong lúc luyện tập. Nó che mất nguyên nhân gốc, và trong đề thi bạn sẽ không có thời gian gỡ lại.
Nếu phải join lại một node từ đầu, thứ tự sạch sẽ là:
sudo kubeadm reset -f
sudo rm -rf /etc/cni/net.d $HOME/.kube
sudo iptables -F && sudo iptables -t nat -F && sudo iptables -t mangle -F
sudo systemctl restart containerd kubelet
Sau đó join lại bằng join command mới.
Góc chuyên gia
Vài quy tắc mình rút ra sau khi thi, viết ngắn để bạn nhớ nhanh:
- Join command luôn lấy từ
kubeadm token create --print-join-command, không copy từ đề hay từ note cũ. Token 24h, certificate key 2h, đề thi có thể đưa token đã cũ để thử bạn. kubectl get nodeslà câu trả lời cuối cùng, không phải output củakubeadm join. Join xong vẫn phải thấy nodeReadymới tính là xong.- Node
NotReadysau join thì nghĩ tới CNI trước tiên, sau đó mới nghĩ tới kubelet. Thứ tự này tiết kiệm rất nhiều thời gian. - Không sửa file trong
/etc/kubernetes/manifestsbằng cách restart lung tung. Với static pod, sửa file là kubelet tự nạp lại. Nhưng đó là bài sau, ở đây chỉ cần biết. - ==Nếu đề cho sẵn join command, hãy chạy đúng nguyên văn, đừng "cải tiến".== Nhiều bạn tự thêm
--cri-sockethoặc đổi--apiserver-advertise-addressvà làm hỏng bước join đang chạy tốt. - Với lab một node, sau khi init bạn có thể bỏ taint để pod thường chạy trên control plane:
kubectl taint nodes cp1 node-role.kubernetes.io/control-plane:NoSchedule-. Chỉ làm trong lab, không làm trong đề thi nếu đề không yêu cầu.
Bảng cổng cần nhớ khi debug kết nối:
| Thành phần | Cổng |
|---|---|
| kube-apiserver | 6443/TCP |
| etcd | 2379-2380/TCP |
| kubelet API | 10250/TCP |
| kube-scheduler | 10259/TCP |
| kube-controller-manager | 10257/TCP |
| NodePort service | 30000-32767/TCP |
Tóm tắt và bước tiếp theo
Tóm gọn trong 5 dòng:
- Chuẩn bị node gồm: tắt swap, bật
overlayvàbr_netfilter, bậtip_forward, containerd với cgroup driversystemd. kubeadm inittạo control plane, sinh ra join command chứa token và CA cert hash.- Copy
/etc/kubernetes/admin.confvề$HOME/.kube/configđểkubectldùng được. - Cài CNI thì node mới
Ready, thiếu CNI thì node mãiNotReadydù đã join. - Token sống 24h, certificate key sống 2h, hết hạn thì tạo lại bằng
kubeadm token create --print-join-command.
Bộ thẻ ôn tập cho bài này, dùng để tự kiểm tra trước khi sang bài sau:
[
{ "front": "Thao tác bắt buộc với swap trước khi chạy kubeadm init?", "back": "Tắt swap bằng swapoff -a và comment dòng swap trong /etc/fstab." },
{ "front": "Flag nào của kubeadm init khai báo dải IP cho Pod?", "back": "--pod-network-cidr, ví dụ 10.244.0.0/16." },
{ "front": "File kubeconfig của admin nằm ở đâu sau khi init?", "back": "/etc/kubernetes/admin.conf, copy về $HOME/.kube/config." },
{ "front": "Vì sao node đã join nhưng vẫn NotReady?", "back": "Chưa cài CNI plugin nên network plugin chưa sẵn sàng." },
{ "front": "Bootstrap token mặc định có TTL bao lâu?", "back": "24 giờ." },
{ "front": "Lệnh tạo lại join command đầy đủ khi token hết hạn?", "back": "kubeadm token create --print-join-command." },
{ "front": "Flag bắt buộc khi join một node vai trò control plane?", "back": "--control-plane kèm --certificate-key." },
{ "front": "Certificate key dùng khi join control plane có hiệu lực bao lâu?", "back": "2 giờ." },
{ "front": "Lỗi 'could not find a JWS signature' khi join nghĩa là gì?", "back": "Token sai hoặc đã hết hạn, phải tạo token mới." },
{ "front": "Cổng mặc định của kube-apiserver?", "back": "6443/TCP." },
{ "front": "Lệnh dọn node trước khi join lại?", "back": "kubeadm reset -f, xóa /etc/cni/net.d và $HOME/.kube, flush iptables." },
{ "front": "Taint mặc định trên control plane node?", "back": "node-role.kubernetes.io/control-plane:NoSchedule." },
{ "front": "Hai lệnh xem log khi kubelet không chạy đúng?", "back": "systemctl status kubelet và journalctl -u kubelet -n 50 --no-pager." }
]
Bài tiếp theo: etcd backup và restore. Bạn vừa có một cluster chạy được, giờ là lúc học cách chụp snapshot etcd và phục hồi khi nó hỏng. Mình sẽ chỉ đúng 3 lệnh cần nhớ, vị trí file CA mà đề thi luôn bắt bạn khai báo, và mẹo đọc etcdctl mà không mất 10 phút mò biến môi trường.