Gói Cert Thành kubeconfig và Cấu Hình Mã Hóa Secret
Cuối Bài 4 ta có mười lăm cặp cert nằm trên workstation, nhưng chưa thành phần nào dùng được ngay. Lý do: một binary như kube-controller-manager khi khởi động cần biết ba thứ cùng lúc — gọi api-server ở địa chỉ nào, tin CA nào, và trình cert client nào. Gói ba thứ đó vào một file là việc của kubeconfig. Bài này sinh kubeconfig cho từng thành phần, rồi tạo nốt file cấu hình mã hóa Secret trước khi sang phần bootstrap.
kubeconfig gồm những gì
Một file kubeconfig có ba khối, và hiểu ba khối này thì về sau đọc bất kỳ kubeconfig nào cũng dễ:
┌─ clusters ──── api-server ở đâu + CA nào để tin nó
│ (server: https://...:6443, certificate-authority-data)
│
├─ users ─────── danh tính của tôi: client cert + client key
│ (client-certificate-data, client-key-data)
│
└─ contexts ──── ghép một cluster với một user (+ namespace) thành
một "ngữ cảnh" có tên, và chỉ ra context đang dùng
Mỗi thành phần (controller-manager, scheduler, kubelet...) sẽ có một kubeconfig riêng, trong đó khối users chứa đúng cert mang danh tính của nó. Khi nó gọi api-server, mTLS diễn ra: nó trình cert từ kubeconfig, api-server đọc CN/O ra danh tính, rồi RBAC phân quyền. Tất cả gói gọn trong một file.
Ta nhúng thẳng cert vào file (--embed-certs=true) thay vì để file trỏ tới đường dẫn cert bên ngoài. Như vậy mỗi kubeconfig là một file tự đủ, copy lên node là chạy, không phải kéo theo cert rời.
Hai địa chỉ api-server: 127.0.0.1 và load balancer
Trước khi sinh file, cần quyết một điều: mỗi thành phần trỏ tới api-server ở địa chỉ nào. Có hai nhóm, và lý do khác nhau:
controller-manager ┐
scheduler ├─► https://127.0.0.1:6443 (api-server NGAY TRÊN máy đó)
admin (trên node) ┘
kube-proxy ┐
kubelet ┴─────────► https://10.0.1.10:6443 (qua load balancer)
controller-manager và scheduler chạy cùng máy với một api-server (cả ba đều nằm trên controller). Cho chúng gọi 127.0.0.1 là gọn nhất và không phụ thuộc load balancer — mỗi controller tự lo phần của mình. Còn kubelet và kube-proxy nằm trên các worker, không có api-server tại chỗ, nên chúng đi qua load balancer ở 10.0.1.10 để được rải đều tới ba api-server và chịu được khi một api-server chết.
(Load balancer ấy ta mới chỉ định địa chỉ chứ chưa dựng — HAProxy sẽ lên ở Bài 9. Lúc đó worker mới thực sự cần nó; còn control plane dùng 127.0.0.1 nên bootstrap được trước khi có load balancer.)
Bước 1 — kubeconfig cho control plane
kubectl config là công cụ sinh kubeconfig. Mỗi file cần bốn lệnh: khai cluster, khai user, ghép thành context, chọn context. Để khỏi lặp, gói lại thành một hàm (chạy trong thư mục ~/k8s-scratch/pki nơi đang chứa cert):
cd ~/k8s-scratch/pki
LOCAL=https://127.0.0.1:6443
mk() { # $1=file $2=user $3=tên-cert $4=server
kubectl config set-cluster k8s-scratch \
--certificate-authority=ca.pem --embed-certs=true \
--server=$4 --kubeconfig=$1
kubectl config set-credentials $2 \
--client-certificate=$3.pem --client-key=$3-key.pem \
--embed-certs=true --kubeconfig=$1
kubectl config set-context default --cluster=k8s-scratch --user=$2 --kubeconfig=$1
kubectl config use-context default --kubeconfig=$1
}
mk admin.kubeconfig admin admin $LOCAL
mk kube-controller-manager.kubeconfig system:kube-controller-manager kube-controller-manager $LOCAL
mk kube-scheduler.kubeconfig system:kube-scheduler kube-scheduler $LOCAL
Để ý từng user khớp đúng CN của cert tương ứng đã tạo ở Bài 4: system:kube-controller-manager, system:kube-scheduler. admin.kubeconfig ở đây trỏ 127.0.0.1 để dùng trên controller lúc bootstrap RBAC; còn việc cấu hình kubectl từ laptop (trỏ qua Elastic IP của load balancer) ta làm ở Bài 9 khi cluster đã sẵn sàng nhận lệnh từ xa.
Bước 2 — kubeconfig cho worker
Hai thành phần trên worker — kube-proxy và kubelet — trỏ qua load balancer. kubelet thì mỗi node một file riêng vì mỗi node mang một danh tính system:node:<node> khác nhau:
LB=https://10.0.1.10:6443
mk kube-proxy.kubeconfig system:kube-proxy kube-proxy $LB
mk worker-0.kubeconfig system:node:worker-0 worker-0 $LB
mk worker-1.kubeconfig system:node:worker-1 worker-1 $LB
Sáu file kubeconfig giờ đã đủ. Nhìn vào cấu trúc một file (ẩn phần dữ liệu cert base64) để thấy đúng ba khối đã nói:
grep -E 'server:|name:|cluster:|user:|current-context:' admin.kubeconfig
- cluster:
server: https://127.0.0.1:6443
name: k8s-scratch
cluster: k8s-scratch
user: admin
name: default
current-context: default
- name: admin
clusters trỏ tới https://127.0.0.1:6443 với CA nhúng sẵn, contexts ghép cluster k8s-scratch với user admin, và current-context: default chỉ ra context đang dùng. Đúng mô hình ba khối.
Bước 3 — Cấu hình mã hóa Secret trong etcd
Phần còn lại của bài chuyển sang một chủ đề khác nhưng cùng thuộc khâu "chuẩn bị cấu hình": mã hóa dữ liệu khi lưu (encryption at rest).
Mặc định, mọi thứ api-server ghi vào etcd đều ở dạng plaintext — kể cả Secret. Nghĩa là nếu ai đó đọc được file dữ liệu của etcd, hoặc lấy được một bản backup etcd, họ đọc thẳng được mọi mật khẩu, token, khóa API mà bạn cất trong Secret. Để tránh, api-server hỗ trợ mã hóa trường dữ liệu của một số loại tài nguyên trước khi ghi xuống etcd, bằng một khóa do ta cấp.
Ta khai báo việc đó trong một file EncryptionConfiguration. Tạo một khóa ngẫu nhiên 32 byte rồi nhúng vào file:
ENC_KEY=$(head -c 32 /dev/urandom | base64)
cat > encryption-config.yaml <<EOF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: ${ENC_KEY}
- identity: {}
EOF
Hai chi tiết đáng hiểu trong file này:
resources: [secrets]— chỉ mã hóa Secret. Có thể thêm loại khác, nhưng Secret là thứ nhạy cảm nhất và là mục tiêu chính.- Thứ tự
providersrất quan trọng. api-server dùng provider đầu tiên để ghi (mã hóa), và thử lần lượt từ trên xuống để đọc (giải mã). Ở đâyaescbcđứng trước nên Secret mới sẽ được mã hóa bằng AES-CBC;identity(không mã hóa) đứng sau để api-server vẫn đọc được những Secret cũ còn ở dạng plaintext. Nếu sau này muốn xoay khóa hoặc tắt mã hóa, ta đổi thứ tự này.
GHI Secret: api-server ──aescbc(mã hóa)──► etcd (dùng provider đầu)
ĐỌC Secret: etcd ──► thử aescbc, rồi identity ──► api-server
File này sẽ được đặt trên các controller và truyền cho api-server qua cờ --encryption-provider-config ở Bài 7. Lúc đó ta sẽ kiểm chứng thật: tạo một Secret rồi đọc thẳng bytes trong etcd để thấy nó đã được mã hóa, không còn đọc được bằng mắt.
🧹 Lưu ý bảo mật
Lại không có tài nguyên cloud để dọn, nhưng thư mục pki giờ thêm các file nhạy cảm: sáu kubeconfig (có nhúng client key bên trong) và encryption-config.yaml (chứa khóa mã hóa). Mất encryption-config.yaml đồng nghĩa mất khả năng giải mã các Secret đã mã hóa trong etcd — nên ngoài việc giữ kín, ở môi trường thật bạn còn phải sao lưu khóa này cẩn thận. Vẫn đừng commit chúng lên Git.
Tổng kết
Khâu chuẩn bị đã xong trọn vẹn: hạ tầng (Bài 3), certificate (Bài 4), và giờ là kubeconfig cùng cấu hình mã hóa. Mọi thứ vẫn đang nằm trên workstation; chưa có tiến trình Kubernetes nào chạy. Nhưng từ đây mỗi bài sẽ bật một thành phần thật.
Bài 6 bắt đầu phần bootstrap với nền móng của control plane: etcd. Ta sẽ tìm hiểu vì sao etcd cần số node lẻ và quorum hoạt động ra sao, rồi dựng một cluster etcd ba node trên các controller, dùng đúng cert etcd và etcd-ca đã tạo. Khi etcd chạy, ta mới có chỗ để api-server ghi trạng thái vào.