SSD Nodes Learn 🎉 VPS từ $5.50/tháng
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-21

Cách harden k3s một node trên VPS public

Bản cài k3s mặc định mở TCP 6443, kubelet 10250, NodePort và pod đặc quyền. Làm theo các bước để khóa API, kubeconfig và firewall đúng cách.

Những gì một k3s cluster một node để lộ ngay từ ngày đầu

Một k3s cluster một node trên VPS public để lộ 5 điểm cụ thể vào ngày hôm sau khi one-line installer hoàn tất: Kubernetes API server trên TCP 6443, kubelet trên TCP 10250, file kubeconfig nằm trên disk, dải NodePort mà firewall của bạn không nhìn thấy, và mọi pod được phép yêu cầu privileged hoặc hostPath. Mỗi điểm đều có cách khắc phục chỉ mất vài phút. Hướng dẫn này giả định k3s đã chạy. Nếu chưa, hãy bắt đầu với cài k3s một node trên VPS rồi quay lại sau.

Hãy kiểm tra những gì đang listening trước khi thay đổi bất kỳ thứ gì.

sudo ss -tulpn | grep -E '6443|10250|10256|8472'

Một bản cài mặc định hiển thị 6443 (API server), 10250 (kubelet), 10256 (health check của kube-proxy) và 8472/udp (overlay của flannel, sử dụng VXLAN, virtual extensible LAN). Theo mặc định, k3s bind vào 0.0.0.0, vì vậy tất cả các cổng đó đều nằm trên địa chỉ public của bạn, không chỉ trên loopback.

Vì sao port 6443 kiểm soát toàn bộ cluster

Bất kỳ ai có thể xác thực vào port 6443 với quyền admin đều có thể tạo pod, và pod có thể chạy với quyền root trên host. Port 6443 là cổng vào máy chủ.

Mở port 6443 không đồng nghĩa máy chủ sẽ bị xâm nhập ngay, vì Kubernetes không chấp nhận password. Nó yêu cầu client certificate hoặc bearer token. Tuy vậy, vẫn có 2 điều cần lưu ý.

Thứ nhất, API server phản hồi một số request mà không cần credential nào. Kubernetes RBAC (role-based access control) mặc định bind group system:unauthenticated vào role có tên system:public-info-viewer. Role này cho phép /version, /healthz, /livez/readyz. Từ một máy khác:

curl -sk https://YOUR_SERVER_IP:6443/version

Lệnh này trả về chính xác version Kubernetes của bạn. Đây là dữ liệu đầu vào cho việc tìm kiếm CVE (common vulnerabilities and exposures), đồng thời là lý do scanner đánh giá máy chủ của bạn đáng chú ý. Các path ngoài những path này đều bị từ chối, và phản hồi từ chối sẽ nêu rõ danh tính của bạn:

forbidden: User "system:anonymous" cannot get path "/api"

Thứ hai, mọi lỗi trong API server đều có thể bị truy cập từ xa khi port này đang mở. Khi đó, việc patch không còn là tùy chọn.

Cách sửa đơn giản là thêm rule vào firewall. Trong trường hợp này, ufw cần nhiều hơn một dòng vì k3s định tuyến traffic của cluster qua cùng kernel.

sudo ufw default deny incoming
sudo ufw allow OpenSSH
sudo ufw allow from 203.0.113.10 to any port 6443 proto tcp
sudo ufw allow from 10.42.0.0/16 to any
sudo ufw allow from 10.43.0.0/16 to any
sudo ufw enable

2 rule cuối lấy trực tiếp từ tài liệu k3s. 10.42.0.0/16 là pod network mặc định và 10.43.0.0/16 là service network mặc định. Nếu thiếu 2 rule này, ufw sẽ drop traffic nội bộ của cluster, khiến các pod mất kết nối đến API server và lẫn nhau. Ví dụ của k3s cho phép 6443 từ mọi nơi; thay giá trị đó bằng địa chỉ của bạn là thay đổi đáng thực hiện. Nếu bạn chưa quen với ufw, phần cơ bản về ufw firewall cho VPS giải thích các policy mặc định mà cấu hình này phụ thuộc vào.

Tài liệu k3s nói rất rõ về overlay port: "VXLAN port trên các node không được expose ra Internet vì điều đó cho phép bất kỳ ai truy cập vào cluster network của bạn." Policy mặc định deny đối với incoming traffic sẽ xử lý việc này mà không cần chỉ rõ port.

Cách sửa chắc chắn hơn là ngừng truy cập API qua public address và sử dụng địa chỉ VPN hoặc mesh thay thế. Server certificate phải liệt kê địa chỉ mà bạn kết nối đến, vì vậy hãy thêm địa chỉ đó dưới dạng SAN (subject alternative name) trong /etc/rancher/k3s/config.yaml:

tls-san:
  - 10.8.0.1
  - k3s.example.com
secrets-encryption: true
sudo systemctl restart k3s
sudo k3s secrets-encrypt status

secrets-encryption: true mã hóa các object Secret trong datastore. Tài liệu k3s lưu ý rằng "Không thể bật mã hóa Secret trên một server hiện có nếu không restart server", và các Secret được ghi trước khi thay đổi vẫn giữ dạng cũ cho đến khi bạn chạy sudo k3s secrets-encrypt reencrypt. Cần hiểu rõ lợi ích của việc này. Nó bảo vệ file datastore được copy từ một bản backup. Nó không ngăn được người có thể giao tiếp với API server, vì API server sẽ giải mã Secret cho bất kỳ ai được phép đọc chúng. Phân biệt này cũng áp dụng cho mọi secret store tự host, đó là lý do bài hardening Vaultwarden tập trung vào admin token và file backup thay vì bản thân cơ chế mã hóa.

Vì sao kubelet trên cổng 10250 đáng quan tâm

kubelet là agent khởi động các container. API của nó trên cổng 10250 liệt kê các pod và chạy lệnh bên trong chúng. kubelet chấp nhận request ẩn danh đồng nghĩa với việc mở một remote shell vào mọi workload trên máy.

Kiểm tra kubelet của bạn:

curl -sk https://127.0.0.1:10250/pods | head -c 60

k3s hiện tại trả về Unauthorized, vì kubelet yêu cầu API server xác thực và cấp quyền cho mọi caller. Nếu nhận được danh sách pod dạng JSON, quyền truy cập ẩn danh đang bật. Khi đó, bất kỳ ai truy cập được cổng 10250 đều có thể đọc dữ liệu và thực thi lệnh trong các container của bạn.

Dù kết quả thế nào, hãy chặn cổng này từ bên ngoài. Trên một node đơn, client duy nhất của kubelet là control plane trên cùng máy. Lưu lượng đó đi qua loopback interface, vốn được ufw cho phép mặc định. Chặn 10250 từ Internet không gây ảnh hưởng gì. Nếu bạn đến đây sau khi gặp kubectl top bị lỗi hoặc metrics-server không ổn định, nguyên nhân được tổng hợp trong lỗi kubelet trên cổng 10250.

kubeconfig của bạn là credential quản trị cluster

k3s tạo /etc/rancher/k3s/k3s.yaml với owner là root và mode 600. Tài liệu nêu rõ hậu quả khi thay đổi mode này: "File kubeconfig thuộc về root và được ghi với mode mặc định là 600. Đổi mode thành 644 sẽ cho phép những user không có quyền trên host đọc file này."

Hãy hiểu như sau: mode 644 khiến mọi account cục bộ trở thành cluster administrator. Nhiều hướng dẫn đề xuất đúng cách đó, thường dùng --write-kubeconfig-mode 644, để kubectl hoạt động mà không cần sudo. Cách này hoạt động vì nó cấp credential quản trị cho bất kỳ ai có shell.

Thay vào đó, hãy copy file cho một user cụ thể.

mkdir -p ~/.kube
sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config
kubectl get nodes

Sau đó xác nhận file gốc vẫn được bảo vệ chặt:

stat -c '%a %U:%G' /etc/rancher/k3s/k3s.yaml

600 root:root là kết quả bạn cần. File đó chứa client certificate của một thành viên trong system:masters, group mà API server mặc định cho phép không điều kiện, nên RBAC rules không bao giờ được áp dụng cho certificate này. Kubernetes không có certificate revocation list. Vì vậy, bản copy bị lộ vẫn hợp lệ cho đến khi bạn rotate certificate authority của cluster. Hãy bảo vệ file này như SSH private key và giữ số account có thể truy cập nó ở mức thấp, tương tự nguyên tắc user account có quyền tối thiểu trên VPS.

Firewall của bạn không thấy traffic NodePort

Một service type: NodePort mở một port trong khoảng từ 30000 đến 32767 trên mọi địa chỉ mà node đang có, bao gồm cả địa chỉ public. Một service type: LoadBalancer còn đi xa hơn trên k3s: ServiceLB, load balancer tích hợp sẵn, tạo một pod nhỏ cho mỗi service trong kube-system. Pod này chiếm trực tiếp service port trên host.

kubectl -n kube-system get pods | grep svclb
sudo ss -tulpn | grep -E ':3[0-2][0-9]{3}'

Đây là phần thường khiến mọi người bất ngờ. Bạn chặn port đó bằng ufw nhưng port vẫn tiếp tục trả lời.

sudo ufw deny 30080/tcp
curl http://YOUR_SERVER_IP:30080

Trang vẫn tải được vì packet đi qua các chain nào. kube-proxy ghi các rule DNAT (destination network address translation) vào chain PREROUTING của table nat. PREROUTING chạy trước khi kernel đưa ra quyết định filter. Destination được đổi thành địa chỉ pod. Địa chỉ này không phải host, nên kernel đưa packet qua chain FORWARD và không bao giờ đi qua INPUT. Các rule của ufw nằm trong INPUT. Packet không bao giờ gặp các rule đó. Thứ tự jump có thể xem như sau:

sudo iptables -S PREROUTING -t nat | head
sudo iptables -S FORWARD | head

KUBE-SERVICES nằm ở đầu chain PREROUTING. Các jump của Kubernetes trong FORWARD cũng nằm trước các chain riêng của ufw. Đây là cùng cơ chế cho phép Docker publish port vượt qua ufw, và cách xử lý cũng giống nhau.

  • Filter tại network firewall của provider. Firewall này chạy trước máy chủ và không phụ thuộc vào cách kernel định tuyến packet.
  • Không dùng NodePort và LoadBalancer. Giữ các service tại ClusterIP rồi truy cập chúng bằng kubectl port-forward qua SSH session hiện có.
  • Chỉ expose một ingress trên 80 và 443, không expose thêm gì khác.
  • Thu hẹp khoảng port bằng service-node-port-range trong kube-apiserver-arg, để một NodePort vô tình được tạo sẽ rơi vào khoảng bạn đang theo dõi.

ufw vẫn đáng dùng. Nó quản lý traffic gửi đến chính host, gồm SSH và API server. Nhưng ufw không kiểm soát traffic đến pod. Nếu kỳ vọng nó làm việc đó, cuối cùng database có thể bị expose ra ngoài.

Pod có hostPath hoặc privileged có quyền root trên VPS

Container chỉ là các process thông thường chạy trên kernel của bạn, với quyền xem kernel bị giới hạn. Một số field của pod có thể gỡ bỏ giới hạn đó.

  • securityContext.privileged: true cấp cho container mọi Linux capability và quyền truy cập vào device của host.
  • hostPath mount một thư mục của host vào pod. Pod mount / ở chế độ read-write có thể thêm một key vào /root/.ssh/authorized_keys.
  • hostPID: true đưa container vào process namespace của host, nơi nsenter trên PID 1 sẽ mở một shell trên host.
  • hostNetwork: true đưa container vào network stack của host. Khi đó, container có thể bind vào các port của host và truy cập các service đang bind vào loopback.

Vì vậy, câu hỏi “ai có thể tạo pod ở đây” cũng chính là câu hỏi “ai có quyền root trên VPS này”. Bất kỳ ServiceAccount nào có create trên pod trong bất kỳ namespace nào đều tương đương root, trừ khi có cơ chế từ chối pod trước đó.

Cơ chế đó là Pod Security admission, được tích hợp trong API server. Cách cấu hình nhanh là đặt một label cho từng namespace và không cần restart.

kubectl label namespace default \
  pod-security.kubernetes.io/enforce=baseline \
  pod-security.kubernetes.io/enforce-version=latest \
  pod-security.kubernetes.io/warn=restricted

baseline từ chối cả 4 field trên. restricted còn yêu cầu user không phải root, profile seccomp (secure computing mode), không được privilege escalation và phải drop capability xuống ALL. Điều này làm nhiều chart được công bố không hoạt động. Enforce baseline nhưng chỉ cảnh báo về restricted cho phép bạn xem trước những gì sẽ bị lỗi trước khi áp dụng chính thức.

Kiểm tra cơ chế này:

kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: pstest
spec:
  containers:
  - name: app
    image: busybox
    command: ["sleep", "60"]
    securityContext:
      privileged: true
EOF

API server sẽ từ chối pod và cho biết field nào bị từ chối:

Error from server (Forbidden): error when creating "STDIN": pods "pstest" is forbidden: violates PodSecurity "baseline:latest": privileged (container "app" must not set securityContext.privileged=true)

Nếu muốn đặt mặc định cho toàn cluster thay vì gắn label cho từng namespace, k3s có tài liệu về file cấu hình admission tại /var/lib/rancher/k3s/server/psa.yaml:

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
  configuration:
    apiVersion: pod-security.admission.config.k8s.io/v1beta1
    kind: PodSecurityConfiguration
    defaults:
      enforce: "baseline"
      enforce-version: "latest"
      warn: "restricted"
      warn-version: "latest"
    exemptions:
      namespaces: [kube-system]

Trỏ API server đến file này trong /etc/rancher/k3s/config.yaml, rồi restart k3s:

kube-apiserver-arg:
  - 'admission-control-config-file=/var/lib/rancher/k3s/server/psa.yaml'

Exemption kube-system là bắt buộc. Các pod ServiceLB của k3s chiếm các port của host, điều mà baseline cấm. Nếu bỏ kube-system khỏi danh sách, các pod đó sẽ bị từ chối vào lần tiếp theo có thành phần nào tạo lại chúng. Hãy giữ một phiên SSH thứ hai khi restart k3s sau khi thay đổi admission.

Một điểm đáng chú ý khác: k3s tích hợp network policy controller và bật sẵn theo mặc định. Vì vậy, các object NetworkPolicy có hiệu lực trên cluster này mà không cần cài thêm gì. Điều này không đúng với mọi Kubernetes distribution. Đây là công cụ để ngăn một pod đã bị breach truy cập đến phần còn lại của hệ thống.

Gỡ các component đi kèm mà bạn không sử dụng

Installer triển khai một nhóm add-on. Mỗi add-on mở thêm một listener và tạo thêm một thành phần cần cập nhật bản vá. --disable chấp nhận các giá trị sau: coredns, servicelb, traefik, local-storage, metrics-server, runtimes.

Giữ lại coredns. Không có nó, không thành phần nào trong cluster phân giải được tên. Các thành phần còn lại là tùy chọn. Trong /etc/rancher/k3s/config.yaml:

disable:
  - traefik
  - servicelb
disable-helm-controller: true
sudo systemctl restart k3s
kubectl get pods -A

k3s xóa các component bạn disable, vì vậy các pod traefik và pod svclb- sẽ tự biến mất. Hãy hiểu rõ hệ quả trước khi thực hiện. Khi servicelb bị gỡ, mọi service type: LoadBalancer sẽ giữ nguyên ở <pending> vĩnh viễn vì không có thành phần nào gán địa chỉ cho chúng. Khi traefik bị gỡ, không còn ingress controller, nên các object Ingress hoàn toàn không có tác dụng. Hãy disable chúng khi bạn chuyển traffic bằng cách khác, chẳng hạn dùng reverse proxy trên host. Nếu đang sử dụng chúng thì giữ nguyên. disable-helm-controller: true gỡ controller theo dõi các resource HelmChart. Đây là một component có quyền cao mà bạn không cần nếu tự chạy helm.

Dừng tự động mount token ServiceAccount mặc định

Mọi pod đều nhận token ServiceAccount tại /var/run/secrets/kubernetes.io/serviceaccount/token, trừ khi bạn chỉ định khác. ServiceAccount default không có quyền RBAC, nên bản thân token này không làm được nhiều việc. Nhưng với kẻ tấn công bên trong một container đã bị xâm nhập, nó cung cấp một credential hợp lệ và một API server có thể truy cập được. Đây là bước đầu tiên trong hầu hết các hướng dẫn leo thang quyền trong cluster.

Hướng dẫn hardening của k3s tắt tính năng này theo từng namespace:

kubectl patch serviceaccount --namespace default default --patch '{"automountServiceAccountToken": false}'
kubectl patch serviceaccount --namespace kube-node-lease default --patch '{"automountServiceAccountToken": false}'
kubectl patch serviceaccount --namespace kube-public default --patch '{"automountServiceAccountToken": false}'

Kiểm tra xem thay đổi đã có hiệu lực chưa. Trước tiên, chờ pod khởi động vài giây.

kubectl run t --image=busybox --restart=Never --command -- sleep 30
kubectl exec t -- ls /var/run/secrets/kubernetes.io/serviceaccount

Path này đã biến mất, nên ls in ra No such file or directory. Workload thực sự cần truy cập API sẽ đặt automountServiceAccountToken: true trong pod spec của chính nó, nên không có gì bị khóa vĩnh viễn. Đây là một cải thiện nhỏ nếu xét riêng. Cải thiện lớn hơn là không cấp cho workload một ServiceAccount có quyền thực tế. Bạn có thể kiểm tra token hiện tại có thể làm được gì:

kubectl auth can-i --list --as=system:serviceaccount:default:default

Giới hạn tài nguyên để một pod không thể làm sập cả cluster

Trên một node duy nhất, control plane và workload của bạn dùng chung một kernel và một pool bộ nhớ. Một pod bị rò rỉ bộ nhớ không phải lúc nào cũng chỉ tự nó bị dừng. OOM (out of memory) killer của kernel chọn tiến trình để dừng dựa trên score, trong đó các tiến trình lớn có trọng số cao hơn. k3s là một tiến trình lớn chạy lâu dài, nên thứ biến mất có thể là cả cluster thay vì pod. Khi đó không còn thành phần nào khởi động lại workload, vì chính thành phần có nhiệm vụ khởi động lại workload đã bị dừng.

LimitRange tự điền limits cho các pod không thiết lập limits:

apiVersion: v1
kind: LimitRange
metadata:
  name: defaults
  namespace: default
spec:
  limits:
  - type: Container
    default:
      cpu: 500m
      memory: 512Mi
    defaultRequest:
      cpu: 50m
      memory: 128Mi

ResourceQuota giới hạn tổng tài nguyên mà toàn bộ namespace có thể yêu cầu:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: cap
  namespace: default
spec:
  hard:
    limits.cpu: "3"
    limits.memory: 3Gi
    pods: "20"

Sau đó dành riêng tài nguyên cho chính k3s trong /etc/rancher/k3s/config.yaml:

kubelet-arg:
  - 'system-reserved=cpu=250m,memory=512Mi'

Sự khác biệt thể hiện ở 2 vị trí. Container bị kill vì vượt limit của chính nó sẽ báo Reason: OOMKilled trong Last State thuộc kubectl describe pod, còn các phần khác của node vẫn tiếp tục hoạt động. Node bị hết hoàn toàn bộ nhớ sẽ để lại một dòng Killed process trong dmesg và thường kéo theo các node lân cận. Trường hợp đầu là limit đang hoạt động đúng. Trường hợp thứ hai là lý do cần có các limit này.

Quét manifest trong CI và cluster k3s theo lịch

Cần quét ở 2 nơi, vì mỗi nơi phát hiện những vấn đề khác nhau. Trước tiên, hãy cài Trivy. Project phát hành Debian package trong mỗi release, và 0.74.0 là phiên bản hiện tại vào tháng 8 năm 2026, nên hãy kiểm tra trang releases để tìm phiên bản mới hơn trước khi pin phiên bản này vào automation.

sudo apt-get install -y wget
wget -q https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-64bit.deb
sudo dpkg -i trivy_0.74.0_Linux-64bit.deb
trivy --version

Nơi đầu tiên là các manifest, trước khi chúng được đưa vào cluster.

trivy fs --scanners misconfig --severity HIGH,CRITICAL --exit-code 1 ./deploy

--exit-code 1 làm CI (continuous integration) job fail khi phát hiện vấn đề. Mỗi kết quả nêu rõ field gây lỗi và mức độ nghiêm trọng, vì vậy một privileged: true mà bạn không định commit sẽ làm build fail thay vì được gửi đến API server. Finding mà bạn quyết định chấp nhận được ghi vào file .trivyignore. Cách này lưu quyết định đó trong git, ngay cạnh manifest gây ra finding.

Nơi thứ hai là cluster đang chạy, được quét theo lịch.

trivy k8s --compliance=k8s-cis-1.23 --report summary

Có một điểm cần nói rõ về command đó. trivy k8s deploy một node collector pod cần quyền truy cập host để kiểm tra các thiết lập cấp node. Nếu bạn vừa bắt đầu từ chối privileged pod trong cluster, đây là điều cần lưu ý thay vì tìm cách né tránh. trivy k8s --report summary --disable-node-collector bỏ qua collector và đồng thời mất các kiểm tra cấp node.

kube-bench kiểm tra phía host: quyền file và các process flag. Đây là nơi phần lớn nội dung của CIS (Center for Internet Security) benchmark thực sự nằm. kube-bench có sẵn profile cho k3s. Hãy lấy upstream Job manifest rồi điều chỉnh nó.

curl -sfL -o kube-bench-job.yaml https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml

Đổi container command thành ["kube-bench", "--benchmark", "k3s-cis-1.7"], và thay các mount /etc/kubernetes/var/lib/etcd bằng /etc/rancher/var/lib/rancher, vì đó là nơi k3s lưu các file của nó. Sau đó:

kubectl apply -f kube-bench-job.yaml
kubectl logs -l app=kube-bench --tail=-1

Hãy lưu ý Job đó thực chất là gì: hostPID: true cộng với các host directory mount. Đây chính xác là dạng pod mà bạn vừa bắt đầu từ chối ở section trước. Hãy chạy nó trong namespace kube-system đã được miễn kiểm tra, đọc output, rồi kubectl delete job kube-bench. Một scanner bắt buộc phải có quyền privileged không phải là lý do để ngừng cấm các workload privileged.

Nội dung image là một vấn đề riêng. trivy image ghcr.io/example/app:1.4 đọc package database bên trong image và liệt kê những package được biết là có lỗ hổng, giống với việc bạn kiểm tra server để tìm các CVE đã biết.

Bây giờ là phần cần nhìn nhận đúng về output của scanner. Một cluster hobby chỉ có 1 node sẽ fail một danh sách dài các CIS control, và phần lớn các failure đó vừa đúng vừa không liên quan đến bạn. Benchmark được viết cho cluster nhiều node, nhiều tenant: etcd chạy trên các host riêng, audit log được gửi ra ngoài máy, một certificate authority riêng cho kubelet, cùng các admission plugin phục vụ một quy định compliance mà bạn không chịu áp dụng. k3s cố ý chạy control plane dưới dạng một process duy nhất với 1 config file, nên các control kiểm tra quyền của file manifest kube-scheduler không thể pass, vì file đó không tồn tại.

Hãy đọc các failure theo thứ tự này và dừng khi giá trị không còn đáng kể: file mode và ownership bên dưới /etc/rancher/var/lib/rancher; mọi mục đề cập đến anonymous access hoặc unauthenticated access; mọi mục báo cáo một component đang bind vào 0.0.0.0; và mọi container chạy dưới UID 0 mà không có lý do chính đáng. Các mục còn lại có thể chờ đến khi bạn có node thứ 2 hoặc người thứ 2 có quyền truy cập. Một report dài 100 dòng mà bạn bỏ qua có giá trị thấp hơn report 5 dòng mà bạn xử lý.

Thứ tự hardening k3s cho một node đơn

  1. Đặt policy mặc định cho traffic vào của ufw là deny, cho phép SSH, cho phép cổng 6443 từ địa chỉ của bạn, và cho phép các pod network và service network.
  2. Copy kubeconfig cho user của bạn với mode 600, và không bao giờ đặt --write-kubeconfig-mode 644.
  3. Xác nhận kubelet trên cổng 10250 từ chối các request anonymous, đồng thời không expose cổng này ra Internet.
  4. Tắt các bundled component không sử dụng, sau đó restart k3s.
  5. Gắn label cho các namespace để dùng Pod Security admission với enforce=baselinewarn=restricted.
  6. Tắt cơ chế tự động mount token của ServiceAccount theo mặc định.
  7. Thêm LimitRange và ResourceQuota, đồng thời reserve CPU và memory cho k3s.
  8. Đưa trivy fs --scanners misconfig vào CI và chạy CIS scan mỗi tháng.

Host bên dưới vẫn cần được bảo trì như mọi server khác, nhưng k3s có một điểm cần lưu ý. k3s được cài bằng script thay vì apt, nên apt upgrade không bao giờ cập nhật nó. Tự patch operating system theo lịch riêng bằng unattended upgrades trên Ubuntu, và chủ động upgrade k3s bằng cách chạy lại installer với channel hoặc version mong muốn. Hai đường cập nhật trên cùng một máy rất dễ bị bỏ quên, vì vậy hãy ghi rõ component nào dùng đường cập nhật nào.

FAQ

Có an toàn khi expose k3s API server trên port 6443 ra Internet không?

Bản thân việc này không phải là mở toang hệ thống, vì API server yêu cầu client certificate hoặc token và từ chối mọi yêu cầu khác bằng forbidden: User "system:anonymous". Tuy nhiên vẫn còn 2 rủi ro. Client ẩn danh vẫn có thể đọc /version. Thông tin này cho scanner biết chính xác Kubernetes release nào cần kiểm tra. Ngoài ra, mọi lỗ hổng trong API server được phát hiện sau này đều có thể bị truy cập từ xa khi port này còn mở. Với cluster một node, không có máy nào bên ngoài cần truy cập 6443 ngoài kubectl của bạn. Vì vậy, chỉ cho phép địa chỉ của bạn bằng sudo ufw allow from YOUR_IP to any port 6443 proto tcp và để policy deny mặc định xử lý phần còn lại.

Vì sao rule ufw của tôi không chặn được service NodePort?

Vì packet không đi qua chain chứa rule của bạn. kube-proxy đặt các rule DNAT trong table nat, tại chain PREROUTING. Chain này chạy trước và rewrite destination thành địa chỉ pod. Sau đó packet được forward thay vì delivery cục bộ. Vì vậy, nó đi qua FORWARD và bỏ qua INPUT, nơi các rule của ufw được đặt. Xác nhận bằng sudo iptables -S PREROUTING -t nat | head. Hãy filter NodePort tại network firewall của nhà cung cấp, hoặc không dùng type: NodePort và truy cập service bằng kubectl port-forward.

Có nên chạy k3s với --write-kubeconfig-mode 644 không?

Không. Tài liệu k3s nêu rõ tác dụng của tùy chọn này: "Changing the mode to 644 will allow it to be read by other unprivileged users on the host." File đó chứa client certificate cho system:masters. Vì vậy, mode 644 biến mọi local account thành cluster administrator. Thay vào đó, hãy copy file cho một user bằng sudo install -o "$USER" -g "$USER" -m 600 /etc/rancher/k3s/k3s.yaml ~/.kube/config và giữ file gốc ở 600 root:root.

Nên dùng benchmark kube-bench nào cho k3s?

Dùng k3s-cis-1.7. Tài liệu kube-bench nêu: "kube-bench includes benchmarks for Rancher K3S platform. To run this you will need to specify --benchmark k3s-cis-1.7 when you run the kube-bench command." Hãy truyền tùy chọn này một cách rõ ràng, vì auto-detection giả định layout của kubeadm, trong khi k3s lưu file tại /etc/rancher/var/lib/rancher. Một số kiểm tra sẽ fail vì không áp dụng cho cluster một node. Trước tiên, hãy xử lý quyền file và anonymous access.

Pod Security admission có làm các component tích hợp sẵn của k3s bị lỗi không?

Có, nếu bạn enforce policy này trên kube-system. Các pod ServiceLB mà k3s tạo cho service type: LoadBalancer sẽ claim port trên host. baseline cấm host port, nên các pod đó sẽ bị reject khi được tạo lại. Hãy exempt kube-system trong file cấu hình admission, hoặc chỉ áp dụng Pod Security bằng label trên các namespace do bạn quản lý. Bắt đầu bằng enforce=baselinewarn=restricted để đọc xem restricted sẽ làm hỏng những gì trước khi bật policy.