Khi đội dev cần tự xem log, vào pod hoặc sửa ConfigMap, cách nhanh nhất là đưa cho họ kubeconfig quản trị. Đó cũng là cách nguy hiểm nhất: file đó mở được mọi namespace và xoá được mọi thứ. Bài này hướng dẫn tạo kubeconfig cho dev giới hạn trong một namespace bằng ServiceAccount và Role, kiểm chứng quyền bằng kubectl auth can-i, gộp nhiều namespace vào một file, và quan trọng nhất là chỉ ra những rủi ro mà quyền "chỉ đọc" vẫn để lại.

Nếu bạn chưa có cluster, xem bài Dựng cluster LKE đầu tiên, nơi có kubeconfig quản trị mà bài này tránh phát cho dev. Khái niệm Secret và ConfigMap nằm ở bài ConfigMap và Secret cho Laravel.

Bài toán: kubeconfig cho dev theo namespace

Nhu cầu thực tế thường gồm bốn việc: xem danh sách pod, đọc log, exec vào pod như vào container Docker, và xem ConfigMap để đối chiếu biến môi trường. Một kubeconfig cho dev tốt chỉ cho đúng những việc đó, và chỉ trong namespace của site cần xử lý. Trước khi cấu hình, kubectl trên máy dev không có file nào nên báo không kết nối được tới localhost:8080. Đó là hành vi mặc định khi không có kubeconfig, chưa phải lỗi.

Bốn đối tượng cần tạo cho kubeconfig của dev

  1. ServiceAccount: danh tính của dev trong namespace.
  2. Role: danh sách quyền (resource và verb) trong namespace đó.
  3. RoleBinding: gắn Role với ServiceAccount.
  4. Secret loại kubernetes.io/service-account-token: chứa token để đưa vào kubeconfig.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: dev-sa
  namespace: site-a
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dev-access
  namespace: site-a
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log", "configmaps"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["pods/exec"]
    verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-access-binding
  namespace: site-a
subjects:
  - kind: ServiceAccount
    name: dev-sa
    namespace: site-a
roleRef:
  kind: Role
  name: dev-access
  apiGroup: rbac.authorization.k8s.io

Với kubeconfig cho dev, Role ở trên không có secrets; lý do nằm ở phần rủi ro bên dưới. Role này dành cho namespace thử hoặc dev; ở production hãy bỏ rule pods/exec. Một lỗi hay gặp khi viết hàng loạt là RoleBinding trỏ tới Role chưa tồn tại: kubectl apply vẫn thành công, nhưng dev không có quyền gì. Lệnh kubectl get role,rolebinding -n site-a chỉ liệt kê và không bắt được roleRef sai tên. Hãy dùng kubectl describe rolebinding dev-access-binding -n site-a và đặc biệt là kubectl auth can-i --list (bên dưới) để thấy quyền hiệu lực thật.

Dựng kubeconfig cho dev: server, CA và token

Từ Kubernetes 1.24, ServiceAccount không còn tự sinh Secret chứa token, nên cột SECRETS của ServiceAccount mới tạo là 0. Có hai cách lấy token cho dev. Với kubeconfig cho dev, cách thứ nhất là kubectl create token dev-sa -n site-a --duration=8h, token ngắn hạn, hết hạn tự động, phù hợp nhất cho truy cập tạm thời. Cách thứ hai là tạo Secret thủ công để có token không hết hạn:

apiVersion: v1
kind: Secret
metadata:
  name: dev-sa-token
  namespace: site-a
  annotations:
    kubernetes.io/service-account.name: dev-sa
type: kubernetes.io/service-account-token

Control plane sẽ tự điền token và ca.crt vào Secret này. Nếu lấy token ngay sau khi apply, trường có thể còn rỗng, nên cần chờ vài giây hoặc lặp lại lệnh đọc. Sau đó ghép kubeconfig cho dev bằng chính kubectl config, không cần tự viết YAML:

NS=site-a
SERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
kubectl get secret dev-sa-token -n $NS -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt
TOKEN=$(kubectl get secret dev-sa-token -n $NS -o jsonpath='{.data.token}' | base64 -d)

kubectl config set-cluster app --server="$SERVER" --certificate-authority=ca.crt --embed-certs=true --kubeconfig=dev.yaml
kubectl config set-credentials dev-sa --token="$TOKEN" --kubeconfig=dev.yaml
kubectl config set-context dev --cluster=app --user=dev-sa --namespace=$NS --kubeconfig=dev.yaml
kubectl config use-context dev --kubeconfig=dev.yaml

API server của cluster do CA riêng ký, nên kubeconfig phải mang CA, nếu không kubectl báo lỗi x509 certificate signed by unknown authority. kubectl config set-cluster không có cờ --certificate-authority-data; cách đúng là đưa file CA bằng --certificate-authority kèm --embed-certs=true để kubectl tự base64 vào certificate-authority-data. Xoá ca.crt sau khi ghép xong nếu không cần nữa. Đừng echo token ra màn hình hay log của pipeline: từ lúc đó token đã bị lộ.

Kiểm chứng quyền của file kubeconfig

Có hai bước kiểm chứng. Bước một, dùng chính kubeconfig cho dev vừa tạo: get pods và get configmaps trong namespace phải chạy được, còn get pods -n khac phải trả Forbidden. Bước hai, kiểm trước khi phát, bằng quyền admin và cờ --as, nên không cần dùng token của dev.

kubectl auth can-i --list --as=system:serviceaccount:site-a:dev-sa -n site-a
kubectl auth can-i get secrets --as=system:serviceaccount:site-a:dev-sa -n site-a

Lệnh --list cho thấy toàn bộ quyền hiệu lực, hữu ích để bắt trường hợp Role cấp nhiều hơn dự định. Với exec: kubectl dùng WebSocket cho exec từ khoảng phiên bản 1.30, nên có thể cần cả get và create trên pods/exec. Hãy thử kubectl exec bằng kubeconfig của dev; nếu bị Forbidden, thêm get vào rule pods/exec. Điều này chưa được kiểm chứng trên mọi phiên bản.

Một kubeconfig cho dev dùng nhiều namespace

Khi có nhiều namespace, phát mỗi namespace một file riêng rất bất tiện. Giải pháp là một file với nhiều context: mỗi context gồm cluster dùng chung, user riêng (token của ServiceAccount trong namespace đó) và namespace mặc định. Ví dụ thêm namespace site-b vào file đã có:

kubectl config set-credentials dev-sa-site-b --token="$TOKEN_B" --kubeconfig=dev.yaml
kubectl config set-context dev-site-b --cluster=app --user=dev-sa-site-b --namespace=site-b --kubeconfig=dev.yaml

Dev đổi namespace bằng kubectl config use-context và liệt kê bằng kubectl config get-contexts. Đổi lại, kubeconfig cho dev dạng này chứa token của mọi namespace, nên nếu lộ thì thiệt hại lớn hơn nhiều so với một file một namespace. Hãy cân nhắc cấp theo nhóm site hoặc dùng token ngắn hạn.

Role theo môi trường: vì sao quyền "chỉ đọc" vẫn rộng

Một cách quản lý gọn là gắn nhãn environment lên namespace, rồi áp Role rộng hơn cho development, Role hẹp hơn cho production. Đây là cách tổ chức đề xuất, chưa có kết quả kiểm chứng cuối.

kubectl label ns site-a environment=development
kubectl get ns -l environment=production

Role dev có thể rộng hơn (get, list, watch trên pod, log, configmap, kèm pods/exec); Role production chỉ get, list, watch trên pod, pods/log, không có secrets và không có pods/exec. Nếu có script tự tạo Role theo nhãn, cần gõ đúng giá trị nhãn: giá trị viết sai chính tả khiến namespace không khớp nhánh nào và không có Role nào được tạo. Lệnh get ns -l ở trên giúp bắt trường hợp này.

Nhưng Role production "chỉ get, list, watch" vẫn chưa an toàn nếu còn những mục sau:

  • secrets: đọc được mật khẩu database, key ứng dụng, thông tin registry và token ServiceAccount.
  • pods/exec: vào trong container, đọc biến môi trường và file, nên vẫn thấy hết bí mật của ứng dụng.
  • Secret của Helm release (sh.helm.release.v1.*): chứa toàn bộ values đã dùng để cài, có thể gồm cả mật khẩu.

Vì vậy kubeconfig cho dev "chỉ đọc" với secrets và pods/exec thực chất là quyền xem mọi bí mật trong namespace. Nên bỏ secrets khỏi Role của dev, và chỉ cho exec ở môi trường thử. Nếu dùng ClusterRole dựng sẵn như view, hãy nhớ gắn bằng RoleBinding để quyền chỉ có hiệu lực trong một namespace. Chi tiết nằm trong tài liệu RBAC của Kubernetes.

Rủi ro của kubeconfig cho dev và cách rotate token

  • Token trong kubeconfig cho dev lấy từ Secret không có thời hạn: nó chỉ mất hiệu lực khi Secret hoặc ServiceAccount bị xoá.
  • Một file chứa nhiều token: lộ file là lộ mọi namespace.
  • Token bị in ra log: các script cấp hàng loạt hay echo token để debug.

Để thu hồi, xoá Secret dev-sa-token rồi tạo lại, và phát file mới. Theo cơ chế token gắn với Secret, token cũ sẽ không còn dùng được; chúng tôi chưa thử quy trình rotate này đầu cuối, bạn hãy kiểm chứng trên cluster thử. Với token tạm, kubectl create token --duration tránh được cả vấn đề thu hồi.

Việc nên làm tiếp sau kubeconfig cho dev

Sau khi có kubeconfig cho dev, các hạng mục sau chỉ là hướng đi, bài này không khẳng định đã triển khai chúng: NetworkPolicy để giới hạn lưu lượng giữa pod, Pod Security Admission để hạn chế quyền của pod (PodSecurityPolicy đã bị gỡ từ Kubernetes 1.25), SecurityContext cho container, và chỉ cho phép truy cập API server qua VPN. Chúng tôi chưa có kết quả thực tế cho các mục này.

Một việc nhỏ khác là image riêng: gắn imagePullSecrets vào ServiceAccount default để mọi pod trong namespace dùng chung, không phải khai báo trong từng Deployment:

kubectl patch serviceaccount default -n site-a -p '{"imagePullSecrets":[{"name":"regcred"}]}'

Checklist cấp kubeconfig cho dev

  • Mỗi kubeconfig cho dev ứng với một ServiceAccount, một Role, một RoleBinding cho mỗi namespace; kiểm tra roleRef đúng Role.
  • Role không có secrets; pods/exec chỉ ở môi trường thử, và có thể cần thêm get với kubectl mới.
  • Với kubeconfig cho dev, ưu tiên kubectl create token --duration; nếu dùng Secret thì có kế hoạch rotate.
  • Kiểm chứng bằng auth can-i --list và thử một namespace khác để thấy Forbidden.
  • Kubeconfig cho dev có CA nhúng sẵn (--embed-certs); không in token ra log, không commit lên git.

Cách tiếp cận này khớp với tư duy tách quyền ở bài Pod, Deployment, Service và Namespace: namespace là ranh giới, RBAC là cái khoá.

Bài tiếp theo

MySQL đầy volume trên Kubernetes: binlog, dựng lại PVC và bẫy khi nới PVC. Khi dev vào pod MySQL và thấy Connection refused, nguyên nhân có thể là một ổ đĩa đã đầy.