Đăng nhập cluster bằng SSO
Kubernetes không có cơ sở dữ liệu người dùng — không có object User, không có lệnh tạo user. API server chỉ biết cách xác minh danh tính do Identity Provider (IdP) của bạn cung cấp.
Luồng xác thực: bạn (kubectl + kubelogin) đăng nhập → IdP (Keycloak/Azure AD/Okta) trả về ID token → bạn gửi token đó thẳng tới API server của cluster → API server verify chữ ký qua JWKS của IdP → Kubernetes RBAC quyết định bạn được làm gì.
Token đi thẳng từ bạn tới API server — IdP không nằm trong đường đi của request. Nếu IdP tạm ngừng hoạt động, token đang cache vẫn dùng được tới khi hết hạn, cluster vẫn chạy bình thường.
Đăng nhập thành công không đồng nghĩa với việc có quyền làm bất cứ điều gì. Người dùng đã xác thực nhưng chưa có RoleBinding sẽ nhận 403 Forbidden khi chạy lệnh như kubectl get pods. Đây là hành vi đúng theo thiết kế, không phải lỗi cấu hình.
Ba tầng phải phân biệt
Để chạy được workload GPU cho team, có 3 tầng riêng biệt — nhầm lẫn giữa các tầng này là nguồn gốc phổ biến nhất gây bối rối:
| Tầng | Trả lời câu hỏi | Cấu hình qua |
|---|---|---|
| Xác thực (Authentication) | Bạn là ai? | OIDC (Issuer URL, Client ID) — Cluster Admin cấu hình 1 lần |
| Phân quyền (Authorization) | Bạn được làm gì? | Kubernetes RBAC (RoleBinding / ClusterRoleBinding) — trang này |
| Hạn ngạch (Quota) | Bạn dùng được bao nhiêu? | ResourceQuota / LimitRange (CPU, RAM, và requests.nvidia.com/gpu) — trang này |
Một user có thể xác thực thành công nhưng vẫn bị chặn bởi 1 trong 2 tầng còn lại: có quyền trên namespace nhưng hết quota sẽ gặp lỗi scheduling, không phải lỗi phân quyền.
Trước khi bắt đầu
| Mục | Yêu cầu |
|---|---|
| Cluster | Admin đã bật xác thực OIDC — xem Configure OIDC authentication for the cluster. |
| Công cụ | Đã cài kubectl và kubelogin trên máy bạn. |
| Mạng | Máy bạn truy cập được cả API server của cluster và IdP. |
| HTTPS | IdP có chứng chỉ hợp lệ — nếu dùng CA nội bộ, cần nạp CA đó vào máy bạn. |
| Tài khoản | Username/password trên IdP, đã được thêm vào nhóm phù hợp. |
Bạn cũng cần 4 thông tin từ quản trị viên cluster — không có đủ 4 thứ này thì không tạo được kubeconfig:
| Thông tin | Dùng để |
|---|---|
| API server URL | Trường server trong kubeconfig. |
| CA certificate của cluster | Trường certificate-authority-data. |
| Issuer URL (IdP) | Chính là giá trị Issuer URL admin đã điền lúc cấu hình OIDC trên Portal. |
| Client ID | Chính là giá trị Client ID admin đã điền lúc cấu hình OIDC trên Portal. |
Cài đặt kubelogin
Cài krew (trình quản lý plugin của kubectl)
Xem hướng dẫn cài krew chính thức tại krew.sigs.k8s.io.
Thêm vào PATH
echo 'export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Using zsh: replace ~/.bashrc with ~/.zshrc
# Confirm
kubectl krew version
PATH khai báo trong ~/.bashrc chỉ áp dụng cho shell tương tác. Lệnh chạy qua ssh host 'kubectl ...' hoặc trong script không nạp file này. Những trường hợp đó phải tự thêm: export PATH="$HOME/.krew/bin:$PATH"; kubectl get nodes.
Cài plugin oidc-login
kubectl krew install oidc-login
# Upgrade later
kubectl krew upgrade oidc-login
# See installed version
kubectl krew list
Kiểm tra cài đặt
# 1. Does the binary run?
kubectl oidc-login --version
# kubelogin version v1.36.4
# 2. Does kubectl recognize the plugin?
kubectl plugin list
# /root/.krew/bin/kubectl-oidc_login
# 3. Can you reach the IdP? (replace with your real issuer URL)
curl -sI https://sso.example.com/realms/kubernetes/.well-known/openid-configuration | head -1
# HTTP/2 200
| Lỗi khi cài | Nguyên nhân | Cách sửa |
|---|---|---|
unknown command "krew" | Chưa chạy Bước 1, hoặc bỏ qua Bước 2 | Thêm ~/.krew/bin vào PATH rồi source ~/.bashrc |
unknown command "oidc-login" | Plugin chưa cài, hoặc PATH chưa có ~/.krew/bin | kubectl krew list để kiểm tra, rồi xem lại Bước 2 |
| Chạy tay được, qua SSH thì không | ~/.bashrc không nạp trong shell phi tương tác | Thêm export PATH="$HOME/.krew/bin:$PATH" vào đầu lệnh |
| Phiên bản kubelogin quá cũ | Cài từ lâu chưa cập nhật | kubectl krew upgrade oidc-login |
Tạo kubeconfig cho người dùng
kubeconfig OIDC không chứa mật khẩu hay token. Nó chỉ mô tả cách gọi kubelogin để lấy token. Nhờ vậy một file duy nhất phát được cho cả team — mỗi người đăng nhập bằng tài khoản riêng và nhận đúng quyền của mình.
File mẫu đầy đủ
apiVersion: v1
kind: Config
current-context: my-gpu-cluster-sso
clusters:
- name: my-gpu-cluster
cluster:
server: https://api.my-gpu-cluster.example.com
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t...
contexts:
- name: my-gpu-cluster-sso
context:
cluster: my-gpu-cluster
user: sso-user
namespace: ml-training
users:
- name: sso-user
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
command: kubectl
args:
- oidc-login
- get-token
- --oidc-issuer-url=https://sso.example.com/realms/kubernetes
- --oidc-client-id=kubernetes
- --oidc-extra-scope=email
- --oidc-extra-scope=profile
# Machine with no browser: add the 2 lines below
# - --grant-type=device-code
# - --skip-open-browser
interactiveMode: IfAvailable
installHint: |
kubelogin isn't installed. Install it with:
kubectl krew install oidc-login
--oidc-issuer-url và --oidc-client-id chính là 2 giá trị Issuer URL và Client ID mà quản trị viên cluster đã cấu hình trên Portal (xem Configure OIDC authentication for the cluster) — xin đúng 2 giá trị này từ quản trị viên cluster của bạn.
Cấu trúc file — ba khối và cách chúng nối với nhau
clusters ──┐ which cluster (address + CA)
├── contexts ── links a cluster to a user, plus a default namespace
users ──┘ how to log in
current-context chỉ ra context nào đang được dùng. Một file có thể chứa nhiều cụm và nhiều context — chuyển qua lại bằng kubectl config use-context.
Khối clusters:
| Trường | Ý nghĩa |
|---|---|
server | Địa chỉ HTTPS của API server. Xin từ quản trị viên. |
certificate-authority-data | CA của cụm, dạng base64. Dùng để xác minh chứng chỉ API server — không phải bí mật. |
certificate-authority | Thay thế cho trường trên, trỏ tới đường dẫn file .crt. Chỉ dùng một trong hai. |
insecure-skip-tls-verify | Tránh dùng. Bỏ qua kiểm tra chứng chỉ — chỉ dùng trong lab dùng một lần. |
Khối contexts:
| Trường | Ý nghĩa |
|---|---|
cluster | Tên cụm, phải khớp một mục trong clusters. |
user | Tên người dùng, phải khớp một mục trong users. |
namespace | Namespace mặc định khi không truyền -n. |
Giải thích từng tham số trong khối exec
Đây là phần quyết định cách đăng nhập. kubectl chạy lệnh trong command kèm args, rồi đọc token từ đầu ra của nó.
Các trường của exec
| Trường | Giá trị | Ý nghĩa |
|---|---|---|
apiVersion | client.authentication.k8s.io/v1beta1 | Phiên bản giao thức exec credential |
command | kubectl | Lệnh được gọi (vì kubelogin là plugin) |
args | danh sách | Tham số truyền cho lệnh |
interactiveMode | IfAvailable | Khi nào được phép chiếm terminal |
installHint | văn bản | Thông báo hiện ra khi máy chưa cài kubelogin |
Hai tham số bắt buộc
| Tham số | Ý nghĩa |
|---|---|
oidc-login get-token | Tên plugin + lệnh con, phải đứng đầu và đúng thứ tự |
--oidc-issuer-url | Địa chỉ IdP — phải khớp chính xác từng ký tự với cấu hình trên cụm |
--oidc-client-id | Định danh ứng dụng đã đăng ký trên IdP, cũng là aud mà API server kiểm tra |
Tham số tùy chọn hay dùng
| Tham số | Khi nào cần |
|---|---|
--oidc-client-secret | Chỉ khi client là confidential |
--oidc-extra-scope | Xin thêm scope, thường email và profile |
--grant-type | auto (mặc định), authcode, device-code, password |
--skip-open-browser | Máy không có trình duyệt |
--certificate-authority | CA của IdP (khác CA của cụm) |
--force-refresh | Bỏ qua cache, lấy token mới — hữu ích sau khi đổi nhóm |
-v=1 … -v=5 | Mức log, -v=5 để chẩn đoán lỗi |
Đừng thêm --oidc-extra-scope=groups. Thông tin nhóm đến từ protocol mapper mà quản trị viên IdP cấu hình, không phải từ scope. Xin scope groups khi IdP không định nghĩa sẽ lỗi invalid_scope.
Cơ chế cache token
Lần đầu chạy, kubelogin lưu token vào ~/.kube/cache/oidc-login. Các lần sau đọc cache, tự refresh, chỉ bắt đăng nhập lại khi refresh token hết hạn.
# Clear the cache when you need to force a re-login (required after a group change)
rm -rf ~/.kube/cache/oidc-login # Linux/macOS
Remove-Item -Recurse -Force "$env:USERPROFILE\.kube\cache\oidc-login" # Windows
Xác nhận đăng nhập thành công
kubectl auth whoami
ATTRIBUTE VALUE
Username oidc:nguyenvana
Groups [oidc:ml-team system:authenticated]
Thấy đúng tên và đúng nhóm nghĩa là tầng xác thực đã xong. Nếu tiếp theo gặp 403, đó là chuyện của phân quyền, xem Gán quyền Kubernetes.
Máy không có trình duyệt
Jump host, server, container — nơi không mở được trình duyệt. Dùng kubeconfig mặc định ở đây sẽ treo rồi báo context deadline exceeded.
-
Device code (khuyến nghị): thêm 2 dòng vào
args:- --grant-type=device-code- --skip-open-browserTerminal in ra link kèm mã sẵn, mở trên điện thoại/laptop khác để đăng nhập, quay lại terminal chờ vài giây là xong. Mã sống 10 phút.
-
SSH port forwarding: giữ nguyên kubeconfig, chạy
ssh -L 8000:localhost:8000 -L 18000:localhost:18000 user@jumphost, rồi chạy kubectl trên jump host bình thường. -
Authcode keyboard:
--grant-type=authcode-keyboard— mở URL, đăng nhập, dán mã trả về vào terminal. -
Password grant:
--grant-type=password --username=...— không dùng được nếu tài khoản bật MFA, đã deprecated, chỉ nên dùng test nhanh.
CI/CD đừng dùng đăng nhập OIDC — dùng ServiceAccount token (xem use case 7 bên dưới).
Gán quyền Kubernetes
Quy tắc ánh xạ
Quản trị viên cụm cấu hình một prefix (chính là Username Prefix / Groups Prefix đã điền ở mục Advanced khi cấu hình OIDC trên Portal) được cộng vào mọi danh tính đến từ IdP:
| Trên IdP | Claim trong token | Tên dùng trong RBAC |
|---|---|---|
User nguyenvana | preferred_username | oidc:nguyenvana |
Group ml-team | groups[] | oidc:ml-team |
Không chắc prefix là gì? Chạy kubectl auth whoami — giá trị hiện ra chính là tên phải dùng trong RBAC.
Luôn phân quyền theo Group, không theo User. Gán cho kind: User nghĩa là mỗi lần có người vào/rời team phải sửa RBAC trên từng cụm. Gán cho kind: Group thì chỉ cần thêm/bớt thành viên trên IdP. Ngoại lệ duy nhất là tài khoản break-glass.
Bốn ClusterRole dựng sẵn
| ClusterRole | Quyền | Dùng cho |
|---|---|---|
cluster-admin | Toàn quyền, mọi namespace | Quản trị hạ tầng |
admin | Toàn quyền trong 1 namespace, gồm cả tạo RoleBinding | Trưởng nhóm MLOps |
edit | Sửa tài nguyên, không sửa được RBAC | Data scientist, ML engineer chạy training/inference |
view | Chỉ đọc, không đọc được Secret | Người review, giám sát |
ClusterRoleBinding cấp quyền toàn cụm; RoleBinding giới hạn 1 namespace (có thể tham chiếu tới ClusterRole).
8 use case phổ biến
Quản trị viên toàn cụm
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: sso-cluster-admins
subjects:
- kind: Group
name: "oidc:gpu-admins"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
Chỉ đọc toàn cụm
Giống use case 1 nhưng roleRef.name: view. ClusterRole view cố tình không cho đọc Secret.
Team sở hữu namespace riêng
Trưởng nhóm ML quyền admin, data scientist/ML engineer quyền edit:
# ML team lead
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ml-training-leads
namespace: ml-training
subjects:
- kind: Group
name: "oidc:ml-team-lead"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: admin
apiGroup: rbac.authorization.k8s.io
---
# Data scientists / ML engineers
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ml-training-users
namespace: ml-training
subjects:
- kind: Group
name: "oidc:ml-team"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: edit
apiGroup: rbac.authorization.k8s.io
Cho phép xem danh sách namespace
Namespace là tài nguyên cấp cụm, RoleBinding không đủ:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-lister
rules:
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: all-users-list-namespaces
subjects:
- kind: Group
name: "system:authenticated"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: namespace-lister
apiGroup: rbac.authorization.k8s.io
Điều này để lộ tên mọi namespace cho mọi người đã đăng nhập.
Nhiều team dùng chung cụm, có hạn ngạch GPU
RBAC không giới hạn dùng bao nhiêu — cần thêm ResourceQuota cho GPU:
apiVersion: v1
kind: ResourceQuota
metadata:
name: ml-team-quota
namespace: ml-training
spec:
hard:
requests.cpu: "40"
requests.memory: 160Gi
requests.nvidia.com/gpu: "8"
persistentvolumeclaims: "20"
count/deployments.apps: "50"
Quota GPU là bắt buộc khi nhiều team dùng chung cluster — không đặt thì 1 team có thể chiếm hết GPU bằng 1 Deployment.
Quyền hẹp trên một loại tài nguyên
Ví dụ: đội trực ca xem log/restart pod training, không sửa Deployment/đọc Secret:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: oncall-responder
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "events"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["delete"] # restart = delete the pod, the ReplicaSet recreates it
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list"]
CI/CD dùng ServiceAccount, không dùng SSO
Cho pipeline train/deploy model chạy theo lịch:
kubectl -n ml-training create serviceaccount ci-deployer
kubectl -n ml-training create rolebinding ci-deployer \
--clusterrole=edit --serviceaccount=ml-training:ci-deployer
kubectl -n ml-training create token ci-deployer --duration=24h
Từ Kubernetes 1.24, ServiceAccount không tự sinh Secret chứa token vĩnh viễn nữa — token sinh bằng create token có thời hạn, an toàn hơn.
Thu hồi quyền khi nhân sự thay đổi
| Tình huống | Thao tác | Hiệu lực |
|---|---|---|
| Chuyển team | Đổi group trên IdP | Lần làm mới token kế tiếp |
| Nghỉ việc | Vô hiệu hóa tài khoản trên IdP | Tối đa bằng token lifetime |
| Cần chặn ngay lập tức | Vô hiệu hóa tài khoản và xóa session | Tức thì với phiên mới |
Token đã phát ra không thu hồi được — giới hạn cố hữu của bearer token. Cửa sổ rủi ro đúng bằng thời gian sống của token. Với hệ thống nhạy cảm, đề nghị đặt 5–10 phút.
Chẩn đoán lỗi
Phân biệt 401 và 403 — bước đầu tiên luôn là bước này
| Mã | Nghĩa | Tầng hỏng | Ai xử lý |
|---|---|---|---|
| 401 | Unauthorized | Xác thực — token không được chấp nhận | Quản trị viên cụm / IdP |
| 403 | Forbidden | Phân quyền — token hợp lệ, thiếu quyền | Quản trị viên cụm (RBAC) |
Đừng sửa RBAC khi đang gặp 401. 401 nghĩa là API server chưa hề công nhận danh tính của bạn — thêm bao nhiêu RoleBinding cũng vô ích.
Bảng tra lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách sửa |
|---|---|---|
401 dù đăng nhập thành công | Issuer URL trong kubeconfig lệch với cấu hình cụm; hoặc cụm chưa bật OIDC | Đối chiếu từng ký tự issuer URL; liên hệ quản trị viên cụm |
403, whoami đúng tên | Chưa có RoleBinding, hoặc tên group trong RBAC không khớp | Gửi output kubectl auth whoami cho quản trị viên |
| Groups rỗng trong whoami | IdP chưa cấu hình mapper đẩy nhóm vào token | Liên hệ quản trị viên IdP |
context deadline exceeded | kubelogin chờ trình duyệt nhưng chạy phi tương tác | Dùng --grant-type=device-code |
invalid_scope | Xin scope mà IdP không định nghĩa | Bỏ --oidc-extra-scope=groups |
| Whoami ra danh tính lạ | Biến KUBECONFIG chưa đổi | Dùng $env:KUBECONFIG, hoặc truyền --kubeconfig |
| Đổi group mà quyền chưa đổi | Token cũ còn trong cache | Xóa ~/.kube/cache/oidc-login |
invalid_grant | Sai mật khẩu, hoặc tài khoản bị vô hiệu hóa | Kiểm tra tài khoản trên IdP |
x509: certificate signed by unknown authority | IdP dùng CA nội bộ chưa được tin cậy | Thêm --certificate-authority=/đường/dẫn/ca.crt |
address already in use | Cổng 8000 đang bị chiếm | Thêm --listen-address=127.0.0.1:8100, đăng ký redirect URI mới |
Missing parameter: code_challenge_method | IdP bắt buộc PKCE trên device endpoint | Đề nghị quản trị viên gỡ ràng buộc PKCE, hoặc dùng SSH port forwarding |
could not open the browser: exit status 3 | Không phải lỗi — máy không có trình duyệt | Tìm dòng "Please visit..." bên dưới, mở link đó |
Ba lệnh chẩn đoán (theo thứ tự)
# 1. Who does the API server think I am?
kubectl auth whoami
# 2. Can I do this specific thing?
kubectl auth can-i create deployments -n ml-training
# 3. See everything I can do in one namespace
kubectl auth can-i --list -n ml-training
Xem chi tiết quá trình đăng nhập
kubectl oidc-login get-token -v=5 \
--oidc-issuer-url=https://sso.example.com/realms/kubernetes \
--oidc-client-id=kubernetes
Lấy được token nghĩa là phía IdP đã ổn, vấn đề nằm ở cụm. Không lấy được thì lỗi nằm ở IdP hoặc đường mạng tới IdP.
Checklist bảo mật
- Không chia sẻ kubeconfig kèm token. File kubeconfig OIDC an toàn để phát cho cả team vì không chứa bí mật — nhưng file có sẵn
token:hoặcclient-key-data:thì tuyệt đối không. - Không dùng
insecure-skip-tls-verifyngoài môi trường lab dùng một lần. - Bật MFA cho tài khoản có quyền quản trị cụm.
- Xóa cache token khi rời máy dùng chung:
rm -rf ~/.kube/cache/oidc-login. - Đặt namespace mặc định trong context để tránh thao tác nhầm namespace.
- Bắt đầu bằng quyền
view. Xin nâng quyền khi có nhu cầu cụ thể, đừng xincluster-admincho tiện. - Phân quyền theo group, không theo user. Ngoại lệ duy nhất là tài khoản break-glass.
- Luôn đặt
ResourceQuotachorequests.nvidia.com/gpukhi nhiều team dùng chung cluster — không đặt thì RBAC không đủ để ngăn 1 team chiếm hết GPU. - Giữ kubeconfig break-glass. IdP ngừng hoạt động thì vẫn phải vào được cụm để xử lý. Cất nơi an toàn.
- Rà soát định kỳ. Mỗi quý đối chiếu thành viên nhóm trên IdP với danh sách nhân sự thực tế.
Tài liệu này áp dụng cho cluster đã được bật xác thực OIDC qua Portal (xem Configure OIDC authentication for the cluster), với kubelogin cài qua krew trên Linux. Việc cấu hình Identity Provider và bật OIDC trên cluster thuộc trách nhiệm quản trị viên cluster — liên hệ họ để lấy 4 thông tin nêu ở mục Trước khi bắt đầu.