Vì Sao Dựng Kubernetes Bằng Tay, và Ta Sẽ Dựng Cái Gì

K
Kai··9 min read

Nếu bạn đã đi qua series "Kubernetes Với Minikube", bạn biết cách dùng một cluster: viết YAML, kubectl apply, rồi Pod, Deployment, Service hiện ra và chạy. Ở series đó ta dừng lại ở mức biết lái xe, không mở nắp capo. Series này thì ngược lại: ta mở capo, tháo động cơ ra từng phần, rồi lắp lại bằng tay. Đi hết series, những thứ trước đây trông như tự động xảy ra sẽ trở nên rõ ràng: bạn biết mỗi thành phần là gì, vì sao nó có mặt, và nó nói chuyện với những thành phần khác bằng cách nào.

Cách làm là dựng một cluster Kubernetes hoàn chỉnh từ đầu: tự tạo từng certificate, tự khởi động từng binary với từng tham số, tự nối mạng cho pod, và không dùng kubeadm hay script tự động nào. Đây cũng là tinh thần của Kubernetes The Hard Way mà Kelsey Hightower viết, nhưng ta đi thêm một bước: dựng tới đâu thì dừng lại giải thích cơ chế bên trong tới đó.

Và series không dừng lại khi cluster đã chạy. Một khi đã có cluster tự tay dựng — nơi ta biết rõ từng thành phần làm gì — nó trở thành phòng thí nghiệm lý tưởng để đi sâu vào toàn bộ concept của Kubernetes: Pod và vòng đời của nó, scheduling, storage, mạng nâng cao, bảo mật, cách mở rộng API. Nửa sau của series làm đúng việc đó, lần lượt theo từng nhóm chủ đề, vẫn với cùng cách làm — chạy thật, đối chiếu tài liệu chính thức, giải thích cơ chế.

"From scratch" nghĩa là gì, và vì sao bỏ qua kubeadm

Trong thực tế gần như không ai dựng cluster production bằng tay. Người ta dùng kubeadm, hoặc managed service như EKS/GKE/AKS, hoặc các bộ cài như kops, k3s. Một lệnh, vài phút là có cluster. Vậy tại sao ở đây ta lại chọn đường dài?

Vì sự tiện lợi đó che mất phần đáng học. kubeadm init làm hàng chục việc trong một lệnh: sinh CA, ký cả loạt certificate, viết file kubeconfig, dựng etcd, khởi control plane dưới dạng static pod, cấu hình RBAC ban đầu. Khi nó chạy xong và mọi thứ hoạt động, bạn không biết được vì sao nó hoạt động. Đến lúc có sự cố — certificate hết hạn, etcd mất quorum, kubelet không join được — thì trước mặt bạn là một hộp đen.

   kubeadm init  ─────►  [ HỘP ĐEN ]  ─────►  cluster chạy
                          (phần đáng học nằm ở đây)

   from scratch  ─────►  bạn tự làm từng bước  ─────►  cluster chạy
                          ↑ mỗi bước là một thứ học được

Mục tiêu của ta không phải tìm một cách dựng cluster để đem ra production, mà là để hiểu. Khi bạn đã từng tự ký certificate cho kube-apiserver, tự viết cờ --etcd-servers, tự kẻ route cho mạng pod, thì lúc đọc log của một cluster do kubeadm dựng, từng dòng đều có nghĩa với bạn. Bạn debug được, vận hành được, và không còn ngại nó nữa.

Cluster thực ra chỉ là vài tiến trình nói chuyện với nhau

Trước khi tháo máy, hãy giữ một hình dung đơn giản. Một cluster Kubernetes, bóc hết các lớp bên ngoài, là một nhóm tiến trình Linux chạy trên vài cái máy, nói chuyện với nhau qua HTTP/gRPC, cộng với một cơ sở dữ liệu giữ trạng thái.

   etcd            ── cơ sở dữ liệu: lưu toàn bộ trạng thái cluster
   kube-apiserver  ── cổng vào: ai muốn đọc/ghi etcd đều qua đây
   controller-mgr  ── các vòng lặp giữ "thực tế" khớp "mong muốn"
   scheduler       ── chọn node cho pod chưa được gán
   kubelet         ── trên mỗi worker: nhận việc, bảo runtime chạy container
   kube-proxy      ── trên mỗi worker: dựng luật mạng cho Service
   containerd      ── runtime thật sự chạy container

Cả series, về bản chất, là cài bảy tám tiến trình đó lên đúng máy, cấp cho chúng certificate để tin nhau, chỉ cho chúng địa chỉ của nhau, rồi bật lên. Tư duy declarative mà ta gặp ở series minikube — bạn khai báo trạng thái mong muốn, các vòng lặp tự kéo hệ thống về đó — vẫn là cách Kubernetes vận hành; chỉ khác là giờ ta nhìn thấy từng vòng lặp đó chạy trong tiến trình cụ thể nào.

Ta sẽ dựng cái gì

Ta không dừng ở một control plane như KTHW gốc, mà dựng thẳng cấu hình HA: ba control plane và hai worker, etcd chạy ngay trên các control plane (kiểu stacked etcd), cộng một load balancer HAProxy đứng trước để gom ba api-server lại thành một địa chỉ.

                       ┌──────── LB HAProxy :6443 ────────┐
        kubectl ─────► │   gom 3 api-server thành 1 VIP   │
                       └───────────────┬──────────────────┘
            ┌──────────────────────────┼──────────────────────────┐
            ▼                          ▼                          ▼
     ┌─ controller-0 ─┐        ┌─ controller-1 ─┐        ┌─ controller-2 ─┐
     │ etcd  ◄────────┼────────┤ etcd  ◄────────┼────────┤ etcd           │  quorum 3
     │ apiserver      │        │ apiserver      │        │ apiserver      │
     │ controller-mgr │        │ controller-mgr │        │ controller-mgr │  (1 leader)
     │ scheduler      │        │ scheduler      │        │ scheduler      │  (1 leader)
     └────────────────┘        └────────────────┘        └────────────────┘
                       ┌────────────────┴────────────────┐
                       ▼                                  ▼
                ┌──── worker-0 ────┐               ┌──── worker-1 ────┐
                │ containerd       │               │ containerd       │
                │ kubelet          │               │ kubelet          │
                │ kube-proxy ──► (sau: Cilium eBPF) │ kube-proxy ──►...│
                └──────────────────┘               └──────────────────┘

Lý do chọn HA ngay từ đầu là vì cấu hình này làm lộ ra mấy chi tiết quan trọng mà một control plane đơn lẻ không có: vì sao etcd cần số node lẻ và khái niệm quorum, vì sao controller-manager và scheduler phải bầu leader thay vì cùng chạy ba bản, vì sao client trỏ vào load balancer chứ không vào thẳng một api-server. Dựng HA, ta buộc phải đối diện và hiểu những câu hỏi đó.

Về networking, ta đi hai chặng. Chặng đầu dựng theo lối cũ: tự cấu hình kube-proxy và một CNI cơ bản để định tuyến mạng pod. Đây là cách để hiểu một Service biến thành luật iptables như thế nào. Chặng sau, trong phần networking nâng cao, ta gỡ kube-proxy và thay bằng Cilium 1.19 (eBPF, kube-proxy-less) cùng Hubble — cách phần lớn cluster production năm 2026 đang chạy. Đi cả hai để bạn thấy được cả cái gốc lẫn cái hiện đại, và hiểu Cilium thay thế cái gì.

Pin version để không lỗi thời

Một nỗi lo chính đáng với mọi bài hướng dẫn Kubernetes là vài tháng sau đã lỗi thời. Ta xử lý chuyện đó theo hai hướng. Thứ nhất là ghim version cụ thể và nói rõ ngay từ đầu — toàn series chạy trên các phiên bản sau (mốc tháng 5/2026):

   Kubernetes     v1.36.1      (stable mới nhất khi viết)
   etcd           v3.6.x
   containerd     v2.x  + runc
   CNI plugins    v1.6+
   CoreDNS        1.12+
   Cilium         1.19.x  (+ Hubble)

Thứ hai là dành hẳn một bài riêng cho quy trình upgrade — nâng cấp control plane rồi tới node, và version skew policy (quy tắc chênh lệch phiên bản cho phép giữa các thành phần). Khi nắm được quy trình đó, bạn tự cập nhật cluster của mình lên bất kỳ phiên bản nào về sau, không phụ thuộc vào việc bài viết còn mới hay không. Trước mỗi bài, ta cũng đối chiếu tài liệu chính thức kubernetes.io (và docs của etcd, containerd, Cilium) ở đúng phiên bản đang dùng.

Môi trường thực hành và chi phí

Ta dựng trên AWS EC2: sáu máy ảo Ubuntu (1 LB, 3 controller, 2 worker), tạo ra để học rồi hủy đi khi xong. Mỗi bài hands-on có một mục 💰 Chi phí ước tính theo giờ và một mục 🧹 Dọn dẹp để bạn không bị phát sinh hóa đơn ngoài ý muốn. Mọi lệnh trong series đều chạy thật trên cụm EC2 này và output là thật, không dựng sẵn rồi chép lại.

Toàn bộ certificate, file cấu hình, manifest và script hỗ trợ được lưu ở repo riêng: github.com/nghiadaulau/kubernetes-from-scratch. Bạn có thể đối chiếu từng bước, nhưng tôi khuyên cứ gõ tay, vì đó là cả mục đích của series.

Lộ trình series

Series chia làm hai phần lớn. Phần một là phần dựng đã mô tả ở trên — từ certificate đầu tiên tới một cluster HA chạy thật, rồi lần theo một request đi xuyên qua nó. Phần hai dùng chính cluster đó làm phòng thí nghiệm để đi sâu vào từng concept của Kubernetes, nhóm theo đúng các chủ đề của tài liệu chính thức.

Phần một — Dựng cluster từ số không:

  • Nền tảng và lý thuyết: kiến trúc Kubernetes ở mức sâu, và PKI/TLS — vì sao một cluster cần nhiều certificate đến vậy.
  • PKI bằng tay: dựng 6 EC2 và chuẩn bị OS, tự tạo CA cùng toàn bộ certificate, sinh kubeconfig và cấu hình mã hóa Secret.
  • Control plane: etcd, kube-apiserver, controller-manager và scheduler, rồi load balancer HAProxy phía trước.
  • Worker: containerd/CRI, kubelet, kube-proxy.
  • Mạng và DNS: mô hình mạng Kubernetes, cấu hình mạng pod bằng tay, CoreDNS.
  • Kiểm chứng: smoke test toàn diện, rồi lần theo một request từ kubectl apply tới pod chạy.

Phần hai — Deep-dive concept trên cluster đã dựng:

  • Pods chuyên sâu: vòng đời, conditions, init/sidecar/ephemeral containers, probes, QoS, disruptions.
  • Workload controllers: Deployment, ReplicaSet, StatefulSet, DaemonSet, Job/CronJob.
  • Objects và API: labels/selectors, namespaces, annotations, finalizers, owners/dependents, garbage collection.
  • Cấu hình và chính sách: ConfigMap/Secret, resource requests/limits, LimitRange, ResourceQuota.
  • Scheduling, preemption, eviction: scheduler và scheduling framework, affinity, taints/tolerations, topology spread, priority.
  • Autoscaling: metrics-server, HPA, VPA.
  • Storage: Volume, PV/PVC, StorageClass, dynamic provisioning, CSI, snapshot.
  • Networking nâng cao: Cilium/eBPF (thay kube-proxy), NetworkPolicy, Ingress, Gateway API.
  • Bảo mật: authentication, RBAC, ServiceAccount, Pod Security Standards, seccomp/AppArmor, hardening.
  • Mở rộng Kubernetes: Custom Resource, admission webhook, operator pattern, API aggregation.
  • Vận hành và quan sát: backup/khôi phục etcd, nâng cấp cluster, logging, metrics, traces — rồi dọn dẹp và tổng kết.

Mỗi bài ở phần hai vẫn giữ đúng cách làm của phần một: chạy thật trên cluster, bám tài liệu kubernetes.io ở đúng phiên bản, và giải thích cơ chế thay vì liệt kê tham số. Đi hết series, bạn không chỉ dùng được Kubernetes mà hiểu được nó từ bên trong, đủ tự tin để vận hành, gỡ lỗi, và đọc bất kỳ cluster nào người khác dựng.

Bài 1 bắt đầu phần lý thuyết: nhìn lại kiến trúc một cluster, lần này đi sâu vào cách các thành phần phối hợp — control loop chạy ở đâu, vì sao mọi thứ đi qua api-server, và một lệnh kubectl apply đi qua những chặng nào trước khi container chạy.