kube-apiserver: Cổng Vào Cluster và Dây Chuyền Xử Lý Request
etcd đã chạy, nhưng chưa ai được phép chạm vào nó ngoài một thành phần: kube-apiserver. Bài này dựng nó — thành phần trung tâm mà mọi request, từ kubectl của bạn tới kubelet trên worker, đều phải đi qua. Trước khi cấu hình, ta cần hiểu rõ hơn vì sao nó là cổng vào duy nhất và một request đi qua nó gặp những gì.
api-server làm gì trong một request
Ở Bài 1 ta đã thấy sơ đồ authn → authz → admission. Giờ nhìn kỹ từng chặng, vì các cờ ta sắp viết chính là để bật từng chặng đó:
request ──► [1] authn ──► [2] authz ──► [3] admission ──► [4] validate ──► etcd
"là ai?" "được phép?" "hợp lệ/sửa?" "đúng schema?"
- Authentication — api-server xác định bạn là ai. Trong cluster của ta, cách chính là client certificate: api-server đọc CN/O từ cert (cờ
--client-ca-filecho biết tin CA nào). Còn token của ServiceAccount thì xác minh bằng--service-account-key-file. - Authorization — bạn được làm gì. Ta bật hai chế độ:
Node(Node authorizer, giới hạn mỗi kubelet trong phạm vi node của nó) vàRBAC. Cờ--authorization-mode=Node,RBAC. - Admission control — các plugin can thiệp trước khi ghi: từ chối, hoặc chỉnh sửa object. Ta bật
NodeRestrictionđể một kubelet không sửa được object của node khác. - Validate và ghi — kiểm tra object đúng schema rồi mã hóa (nếu có) và ghi xuống etcd.
api-server còn một vai khác: nó là client của hai cuộc hội thoại — gọi xuống kubelet (logs, exec, metrics) bằng cert apiserver-kubelet-client, và đọc/ghi etcd bằng cert apiserver-etcd-client. Đây là lý do nó cần nhiều cert đến vậy. Và vì api-server không giữ trạng thái riêng (mọi thứ ở etcd), ta chạy được ba bản song song — đó chính là phần HA của bức tranh.
Bước 1 — Tải binary control plane
Trên mỗi controller, tải ba binary control plane v1.36.1 (bài này dùng kube-apiserver; kube-controller-manager và kube-scheduler để dành Bài 8, nhưng tải luôn một thể cho gọn):
# chạy trên TỪNG controller
VER=v1.36.1
BASE=https://dl.k8s.io/release/${VER}/bin/linux/amd64
for b in kube-apiserver kube-controller-manager kube-scheduler; do
sudo curl -fSL -o /usr/local/bin/$b ${BASE}/$b
sudo chmod +x /usr/local/bin/$b
done
sudo mkdir -p /var/lib/kubernetes
Kiểm tra binary sau khi tải. File binary khá lớn (mỗi cái ~90MB), và một lần
curllỗi mạng giữa chừng có thể để lại file thiếu mà không báo gì rõ ràng. Luôn xác nhận trước khi đi tiếp — dùng-fSLđể curl báo lỗi khi HTTP thất bại, và kiểm version:
kube-apiserver --version
Kubernetes v1.36.1
Bước 2 — Đưa cert và encryption-config lên controller
api-server cần khá nhiều file từ thư mục pki. Copy chúng vào /var/lib/kubernetes trên mỗi controller:
# từ workstation, trong ~/k8s-scratch/pki
FILES="ca.pem ca-key.pem kube-apiserver.pem kube-apiserver-key.pem \
apiserver-kubelet-client.pem apiserver-kubelet-client-key.pem \
apiserver-etcd-client.pem apiserver-etcd-client-key.pem etcd-ca.pem \
service-account.pem service-account-key.pem \
front-proxy-ca.pem front-proxy-client.pem front-proxy-client-key.pem \
encryption-config.yaml"
for h in controller-0 controller-1 controller-2; do
scp $FILES ${h}:/tmp/
ssh $h 'sudo mkdir -p /var/lib/kubernetes && sudo mv /tmp/*.pem /tmp/encryption-config.yaml /var/lib/kubernetes/'
done
Mỗi controller giờ có đủ giấy tờ: cert serving của chính nó (kube-apiserver.pem), CA để xác thực client (ca.pem), cert để gọi kubelet và etcd, khóa service-account, cặp front-proxy cho aggregation, và file mã hóa.
Bước 3 — systemd unit cho kube-apiserver
Đây là unit nhiều cờ nhất trong cả series. Để dễ thở, nhóm các cờ theo chức năng. Unit cho controller-0 (IP 10.0.1.11); hai máy kia chỉ đổi --advertise-address:
[Unit]
Description=Kubernetes API Server
After=network.target
[Service]
ExecStart=/usr/local/bin/kube-apiserver \
--advertise-address=10.0.1.11 \
--allow-privileged=true \
--apiserver-count=3 \
--authorization-mode=Node,RBAC \
--bind-address=0.0.0.0 \
--client-ca-file=/var/lib/kubernetes/ca.pem \
--enable-admission-plugins=NodeRestriction \
--encryption-provider-config=/var/lib/kubernetes/encryption-config.yaml \
--etcd-cafile=/var/lib/kubernetes/etcd-ca.pem \
--etcd-certfile=/var/lib/kubernetes/apiserver-etcd-client.pem \
--etcd-keyfile=/var/lib/kubernetes/apiserver-etcd-client-key.pem \
--etcd-servers=https://10.0.1.11:2379,https://10.0.1.12:2379,https://10.0.1.13:2379 \
--kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \
--kubelet-client-certificate=/var/lib/kubernetes/apiserver-kubelet-client.pem \
--kubelet-client-key=/var/lib/kubernetes/apiserver-kubelet-client-key.pem \
--runtime-config=api/all=true \
--service-account-key-file=/var/lib/kubernetes/service-account.pem \
--service-account-signing-key-file=/var/lib/kubernetes/service-account-key.pem \
--service-account-issuer=https://10.0.1.10:6443 \
--service-cluster-ip-range=10.32.0.0/24 \
--service-node-port-range=30000-32767 \
--tls-cert-file=/var/lib/kubernetes/kube-apiserver.pem \
--tls-private-key-file=/var/lib/kubernetes/kube-apiserver-key.pem \
--requestheader-client-ca-file=/var/lib/kubernetes/front-proxy-ca.pem \
--requestheader-allowed-names=front-proxy-client \
--requestheader-extra-headers-prefix=X-Remote-Extra- \
--requestheader-group-headers=X-Remote-Group \
--requestheader-username-headers=X-Remote-User \
--proxy-client-cert-file=/var/lib/kubernetes/front-proxy-client.pem \
--proxy-client-key-file=/var/lib/kubernetes/front-proxy-client-key.pem \
--v=2
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Đọc theo nhóm cho dễ nhớ:
- Kết nối etcd (
--etcd-servers,--etcd-cafile/certfile/keyfile): trỏ tới cả ba etcd, dùng certapiserver-etcd-client. Mỗi api-server gọi được bất kỳ etcd nào. - Serving TLS (
--tls-cert-file,--tls-private-key-file,--bind-address=0.0.0.0): cert mà client thấy khi gọi vào:6443. Đây là cert có SAN đầy đủ ở Bài 4. - Xác thực client (
--client-ca-file): tin các cert do Kubernetes CA ký. - Gọi xuống kubelet (
--kubelet-client-certificate/key,--kubelet-certificate-authority). - Phân quyền (
--authorization-mode=Node,RBAC,--enable-admission-plugins=NodeRestriction). - ServiceAccount (
--service-account-*): khóa ký/kiểm token, và--service-account-issuer(URL phát hành token, ta dùng địa chỉ load balancer). - Mã hóa (
--encryption-provider-config): trỏ tới file đã tạo ở Bài 5. - Dải mạng Service (
--service-cluster-ip-range=10.32.0.0/24): dải ClusterIP ảo; nhớ10.32.0.1đã nằm trong SAN. - front-proxy / requestheader: phục vụ aggregation layer.
Ghi file vào /etc/systemd/system/kube-apiserver.service trên mỗi controller (đổi --advertise-address), rồi:
sudo systemctl daemon-reload
sudo systemctl enable kube-apiserver
Bước 4 — Khởi động và kiểm chứng
Khác với etcd, api-server không cần chờ nhau, nên start bình thường trên cả ba:
for h in controller-0 controller-1 controller-2; do
ssh $h 'sudo systemctl start kube-apiserver'
done
Sau vài giây, kiểm tra endpoint /healthz trên từng máy — gọi 127.0.0.1:6443 với CA để TLS hợp lệ:
for h in controller-0 controller-1 controller-2; do
printf "%-14s " "$h"
ssh $h 'echo "healthz=$(curl -s --cacert /var/lib/kubernetes/ca.pem https://127.0.0.1:6443/healthz); active=$(systemctl is-active kube-apiserver)"'
done
controller-0 healthz=ok; active=active
controller-1 healthz=ok; active=active
controller-2 healthz=ok; active=active
/healthz không cần xác thực (đó là chủ đích, để load balancer thăm dò được). Thử một lời gọi có xác thực bằng cert admin để chắc cả authn lẫn authz đều thông — liệt kê namespace và xem version:
# copy tạm cert admin lên controller-0 để test
curl -s --cacert ca.pem --cert admin.pem --key admin-key.pem \
https://127.0.0.1:6443/api/v1/namespaces -o /dev/null -w "HTTP %{http_code}\n"
curl -s --cacert ca.pem --cert admin.pem --key admin-key.pem \
https://127.0.0.1:6443/version
HTTP 200
{
"major": "1",
"minor": "36",
"gitVersion": "v1.36.1",
"gitCommit": "756939600b9a7180fc2df6550a4585b638875e67",
"goVersion": "go1.26.2",
"compiler": "gc",
"platform": "linux/amd64"
}
HTTP 200 nghĩa là cert admin (với O=system:masters) qua được authn và authz. api-server đã sống và nói chuyện với etcd.
Bước 5 — Kiểm chứng mã hóa Secret thật sự xảy ra
Ở Bài 5 ta hứa sẽ chứng minh Secret được mã hóa trong etcd, chứ không chỉ tin vào cấu hình. Giờ làm thật: tạo một Secret qua api-server, rồi đọc thẳng bytes trong etcd (không qua api-server) xem nó ra gì.
# tạo Secret "test-enc" với một trường pw
PW=$(echo -n 'supersecret' | base64)
curl -s --cacert ca.pem --cert admin.pem --key admin-key.pem \
-XPOST -H 'Content-Type: application/json' \
https://127.0.0.1:6443/api/v1/namespaces/default/secrets \
-d '{"apiVersion":"v1","kind":"Secret","metadata":{"name":"test-enc"},"data":{"pw":"'$PW'"}}' \
-o /dev/null -w "POST HTTP %{http_code}\n"
POST HTTP 201
Đọc key tương ứng thẳng từ etcd và xem hexdump:
E="--cacert=/etc/etcd/etcd-ca.pem --cert=/etc/etcd/etcd.pem --key=/etc/etcd/etcd-key.pem"
sudo etcdctl get /registry/secrets/default/test-enc $E | hexdump -C | head -5
00000000 2f 72 65 67 69 73 74 72 79 2f 73 65 63 72 65 74 |/registry/secret|
00000010 73 2f 64 65 66 61 75 6c 74 2f 74 65 73 74 2d 65 |s/default/test-e|
00000020 6e 63 0a 6b 38 73 3a 65 6e 63 3a 61 65 73 63 62 |nc.k8s:enc:aescb|
00000030 63 3a 76 31 3a 6b 65 79 31 3a a9 0c 67 35 32 90 |c:v1:key1:..g52.|
00000040 7e cc 22 cb 1d 69 9c 5b e3 bd b6 35 e4 dd 7d d4 |~."..i.[...5..}.|
Nhìn cột bên phải: sau tên key là k8s:enc:aescbc:v1:key1: rồi tới một mớ bytes ngẫu nhiên. Chuỗi supersecret không xuất hiện ở đâu cả — nó đã bị mã hóa bằng AES-CBC với key1, đúng như khai trong encryption-config.yaml. Nếu ta không bật mã hóa, chỗ này sẽ đọc được supersecret bằng mắt thường. Xóa Secret thử nghiệm cho sạch:
curl -s --cacert ca.pem --cert admin.pem --key admin-key.pem \
-XDELETE https://127.0.0.1:6443/api/v1/namespaces/default/secrets/test-enc \
-o /dev/null -w "DELETE HTTP %{http_code}\n"
DELETE HTTP 200
🧹 Dọn dẹp
api-server giờ là thành phần thường trú, không tắt. Lưu ý ta vẫn đang gọi qua 127.0.0.1 trên từng controller — chưa có load balancer, nên kubectl từ laptop chưa dùng được. Việc dựng HAProxy và cấu hình kubectl từ xa nằm ở Bài 9. Nhớ xóa các file cert tạm (admin.pem...) nếu bạn đã copy lên controller để test.
Tổng kết
Cổng vào của cluster đã mở: ba api-server chạy song song, nối vào etcd bằng TLS, phân quyền bằng Node và RBAC, và mã hóa Secret trước khi ghi — điều ta vừa kiểm chứng tận bytes. Đây là thành phần ta sẽ tương tác nhiều nhất về sau, nên hiểu nó nhận request rồi xử lý qua bốn chặng thế nào là vốn quý.
Nhưng api-server chỉ lưu trạng thái mong muốn; nó không tự biến mong muốn thành hiện thực. Việc đó thuộc về hai thành phần ở Bài 8: kube-controller-manager chạy các control loop, và kube-scheduler chọn node cho pod. Cả hai đều là client của api-server (dùng kubeconfig đã tạo ở Bài 5), và cả hai đều phải bầu leader vì ta chạy ba bản — ta sẽ thấy leader election hoạt động thật.