Module 08 Private Cloud 5 labs

On-prem Kubernetes: kubeadm, RKE2, K3s, OpenShift Overview

Triển khai Kubernetes trong private cloud từ đầu: cluster design với kubeadm và K3s, cấu hình CNI, persistent storage, ingress controller, private container registry — đủ nền tảng vận hành production on-prem.

Công cụ thực hành kubectl, kubeadm, k3s, Helm, Docker, crictl
Nền tảng Linux (Ubuntu 22.04 / Rocky 9), WSL2, VirtualBox / Hyper-V
Thời điểm phát hành 23/05/2026
Ngày biên soạn 23/05/2026
Người biên soạn Trần Văn Hòa — Microsoft Certified Trainer (MCT)

Mục tiêu học tập

1. Lý thuyết cốt lõi

1.1. Kiến trúc Kubernetes

Kubernetes tổ chức thành hai lớp: Control PlaneWorker Node. Control plane gồm kube-apiserver (cổng duy nhất mọi tương tác), etcd (key-value store lưu toàn bộ cluster state), kube-scheduler (gán Pod lên node), kube-controller-manager (vòng lặp reconciliation). Worker node chạy kubelet (agent nhận lệnh từ API server), kube-proxy (iptables/IPVS cho Service networking) và container runtime (containerd / CRI-O).

So sánh nhanh: kubeadm · K3s · RKE2

Distro Use-case Runtime mặc định CNI mặc định Ghi chú
kubeadmProduction on-premcontainerdTự chọnCNCF official bootstrap tool
K3sEdge, IoT, dev labcontainerdFlannelSingle binary <70MB; HA với etcd nhúng
RKE2Gov / FIPS / enterprisecontainerdCanal (Calico+Flannel)Hardened by Rancher; CIS Benchmark compliant

1.2. CNI — Container Network Interface

CNI là plugin layer giữa Kubernetes và network. Mỗi Pod nhận IP riêng (Flat networking). Hai trường phái chính: Overlay (VXLAN/IP-in-IP — đóng gói packet, hoạt động trên bất kỳ hạ tầng nào; ví dụ Flannel, Weave) và Underlay/BGP (quảng bá route thật tới switch/router, hiệu năng cao hơn; ví dụ Calico BGP, Cilium eBPF). Calico là lựa chọn phổ biến cho on-prem vì hỗ trợ cả overlay lẫn BGP peering và NetworkPolicy chi tiết.

1.3. Persistent Storage trên on-prem

Kubernetes tách biệt StorageClass (how) → PersistentVolumeClaim (want) → PersistentVolume (actual). Trên on-prem, các giải pháp phổ biến: local-path-provisioner (Rancher, đơn giản, không HA), NFS Subdir Provisioner (shared NFS server, ReadWriteMany), Longhorn (distributed block storage, HA, snapshot/backup), Ceph/Rook (enterprise-grade, phức tạp). Chọn local-path cho lab; NFS hoặc Longhorn cho production nhỏ.

1.4. Ingress Controller

Service NodePort/LoadBalancer expose TCP trực tiếp; Ingress là L7 routing theo host/path. ingress-nginx là controller phổ biến nhất (nginx reverse proxy + Kubernetes controller). Luồng: Client → LoadBalancer/NodePort → ingress-nginx Pod → ClusterIP Service → Pod. TLS termination tại ingress; cert-manager tự động cấp/renew Let's Encrypt hoặc private CA.

1.5. Private Container Registry

On-prem cần registry riêng để kiểm soát image và giảm phụ thuộc internet. Hai lựa chọn phổ biến: registry:2 (Docker official, nhẹ, không có UI) và Harbor (CNCF sandbox, có UI, RBAC, image scanning với Trivy, replication). Cluster cần cấu hình imagePullSecrets hoặc /etc/containerd/config.toml để authenticate với registry nội bộ.

1.6. OpenShift Overview

Red Hat OpenShift là Kubernetes distribution enterprise với nhiều lớp bổ sung: OCP (OpenShift Container Platform) dùng CRI-O runtime, tích hợp sẵn Operator Framework, Route (thay Ingress), ImageStream, S2I (Source-to-Image build), Web Console đầy đủ, RBAC chặt hơn (SCC thay PodSecurityPolicy). Khác biệt cốt lõi: mặc định không chạy container với UID 0 (security by default); dùng oc CLI thay kubectl (tương thích ngược). Phù hợp doanh nghiệp cần hỗ trợ chính thức, compliance (FIPS, DISA STIG).

1.7. Cluster Upgrade Strategy

Kubernetes hỗ trợ upgrade n-2 minor versions. Thứ tự bắt buộc: upgrade control plane trước, sau đó từng worker. Với kubeadm: kubeadm upgrade plankubeadm upgrade apply vX.Y.Zkubectl drain node → nâng kubelet/kubectl → kubectl uncordon node. K3s đơn giản hơn: chạy lại install script với phiên bản mới. Luôn backup etcd trước khi upgrade.

2. Thực hành (Labs)

LAB-036

Cài Kubernetes lab bằng kubeadm / K3s

CLI · kubectl · kubeadm / k3s

🎯 Mục tiêu: Bootstrap cluster Kubernetes 1 control plane + 2 worker node trên VM Linux; xác minh cluster healthy bằng kubectl.

🧰 Công cụ / nền tảng: Ubuntu 22.04 LTS (3 VM: 2 vCPU, 2 GB RAM mỗi node), kubeadm v1.29+ hoặc K3s v1.28+, kubectl, containerd.

📦 Chuẩn bị: 3 VM có IP tĩnh (ví dụ: 192.168.100.10 control, 192.168.100.11–12 worker), SSH key giữa các node, tắt swap, bật IP forwarding.

▶️ Phương án A — kubeadm (tất cả 3 node):

# === CHẠY TRÊN TẤT CẢ 3 NODE ===
# 1. Tắt swap vĩnh viễn
sudo swapoff -a
sudo sed -i '/swap/d' /etc/fstab

# 2. Bật modules kernel cần thiết
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter

# 3. Cấu hình sysctl
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF
sudo sysctl --system

# 4. Cài containerd
sudo apt-get update
sudo apt-get install -y containerd
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
# Đổi SystemdCgroup = true
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl enable containerd

# 5. Cài kubeadm, kubelet, kubectl
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | \
  sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | \
  sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
# === CHẠY TRÊN CONTROL PLANE NODE ===
# 6. Init cluster (thay --pod-network-cidr nếu dùng CNI khác)
sudo kubeadm init \
  --apiserver-advertise-address=192.168.100.10 \
  --pod-network-cidr=10.244.0.0/16 \
  --control-plane-endpoint=192.168.100.10

# 7. Cấu hình kubectl cho user hiện tại
mkdir -p $HOME/.kube
sudo cp -f /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 8. Lưu lệnh join (output từ kubeadm init)
# VÍ DỤ: kubeadm join 192.168.100.10:6443 --token abcdef.0123456789abcdef \
#         --discovery-token-ca-cert-hash sha256:<hash>
# === CHẠY TRÊN WORKER NODE 1 VÀ 2 ===
# 9. Join cluster (dán lệnh từ bước 8)
sudo kubeadm join 192.168.100.10:6443 \
  --token abcdef.0123456789abcdef \
  --discovery-token-ca-cert-hash sha256:<hash>

▶️ Phương án B — K3s (nhanh hơn, phù hợp lab nhỏ):

# === CONTROL PLANE (server) ===
curl -sfL https://get.k3s.io | sh -s - server \
  --cluster-init \
  --write-kubeconfig-mode 644

# Lấy token để join worker
sudo cat /var/lib/rancher/k3s/server/node-token
# Output ví dụ: K107a...::server:abc123

# === WORKER NODE 1 VÀ 2 ===
K3S_URL="https://192.168.100.10:6443"
K3S_TOKEN="<token-từ-trên>"
curl -sfL https://get.k3s.io | K3S_URL=$K3S_URL K3S_TOKEN=$K3S_TOKEN sh -

▶️ Xác minh cluster (control plane):

# Kiểm tra nodes
kubectl get nodes -o wide

# Kết quả mong đợi:
# NAME        STATUS   ROLES           AGE   VERSION
# control     Ready    control-plane   5m    v1.29.x
# worker-01   Ready    <none>          3m    v1.29.x
# worker-02   Ready    <none>          3m    v1.29.x

# Kiểm tra pods hệ thống
kubectl get pods -n kube-system

# Kiểm tra cluster info
kubectl cluster-info

✅ Kết quả mong đợi: 3 node ở trạng thái Ready; tất cả pod trong kube-system ở trạng thái Running hoặc Completed. (Nếu node vẫn NotReady sau bước này thì cần cài CNI — xem LAB-037.)

🧹 Cleanup: Để reset node: sudo kubeadm reset -f && sudo rm -rf /etc/cni/net.d. K3s: sudo k3s-uninstall.sh (server) hoặc sudo k3s-agent-uninstall.sh (worker).

LAB-037

Cấu hình CNI — Calico trên kubeadm cluster

CLI · kubectl · Helm

🎯 Mục tiêu: Cài Calico CNI lên cluster kubeadm, xác minh Pod networking hoạt động, kiểm tra NetworkPolicy cô lập traffic giữa namespace.

🧰 Công cụ / nền tảng: kubectl, cluster từ LAB-036 (kubeadm, pod-network-cidr=10.244.0.0/16), calicoctl (tùy chọn).

📦 Chuẩn bị: Cluster LAB-036 đã init (nodes NotReady vì chưa có CNI); internet access từ node hoặc image đã pull sẵn.

▶️ Các bước:

# 1. Cài Calico bằng Helm (cách khuyến nghị cho production)
helm repo add projectcalico https://docs.tigera.io/calico/charts
helm repo update

# 2. Tạo values file tùy chỉnh CIDR khớp với kubeadm init
cat <<EOF > calico-values.yaml
installation:
  calicoNetwork:
    ipPools:
    - cidr: 10.244.0.0/16
      encapsulation: VXLAN
EOF

# 3. Cài Calico Operator
kubectl create namespace tigera-operator
helm install calico projectcalico/tigera-operator \
  --namespace tigera-operator \
  --values calico-values.yaml

# 4. Chờ tất cả Calico pods running (~2 phút)
watch kubectl get pods -n calico-system

# 5. Xác minh nodes Ready
kubectl get nodes
# NAME        STATUS   ROLES           AGE   VERSION
# control     Ready    control-plane   10m   v1.29.x
# worker-01   Ready    <none>          8m    v1.29.x
# worker-02   Ready    <none>          8m    v1.29.x
# 6. Kiểm tra Pod-to-Pod networking
# Deploy 2 pods test trên 2 worker khác nhau
kubectl run pod-a --image=busybox --restart=Never -- sleep 3600
kubectl run pod-b --image=busybox --restart=Never -- sleep 3600

# Lấy IP của pod-a
kubectl get pod pod-a -o jsonpath='{.status.podIP}'
# Ví dụ: 10.244.1.5

# Ping từ pod-b đến pod-a
kubectl exec pod-b -- ping -c 3 10.244.1.5
# PING 10.244.1.5: 56 data bytes
# 64 bytes from 10.244.1.5: seq=0 ttl=63 time=0.8 ms ✓
# 7. Test NetworkPolicy — cô lập namespace
kubectl create namespace prod
kubectl create namespace dev

kubectl run web --image=nginx -n prod --expose --port=80
kubectl run client -n dev --image=busybox --restart=Never -- sleep 3600

# Lúc này client (dev) CÓ THỂ curl web (prod)
kubectl exec -n dev client -- wget -qO- http://web.prod.svc.cluster.local
# => Trả về HTML nginx

# Áp dụng NetworkPolicy chặn traffic vào prod từ namespace khác
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-from-other-ns
  namespace: prod
spec:
  podSelector: {}
  policyTypes: [Ingress]
  ingress:
  - from:
    - podSelector: {}
EOF

# Bây giờ client (dev) KHÔNG thể curl web (prod)
kubectl exec -n dev client -- wget --timeout=3 -qO- http://web.prod.svc.cluster.local
# => wget: download timed out ✓ (bị chặn)

✅ Kết quả mong đợi: Tất cả node Ready; ping Pod-to-Pod thành công; NetworkPolicy chặn cross-namespace traffic đúng như cấu hình.

🧹 Cleanup: kubectl delete pod pod-a pod-b; kubectl delete ns prod dev.

LAB-038

Tạo StorageClass lab — local-path & NFS

CLI · kubectl · Helm

🎯 Mục tiêu: Cài local-path-provisioner, tạo PVC, mount vào Pod và ghi/đọc dữ liệu persistent; tùy chọn cài NFS provisioner cho ReadWriteMany.

🧰 Công cụ / nền tảng: kubectl, Helm, cluster từ LAB-036/037; NFS server tùy chọn (hoặc dùng một node làm NFS).

📦 Chuẩn bị: Cluster healthy; worker nodes có ổ đĩa trống ≥5 GB tại /opt/local-path-provisioner.

▶️ Phần A — local-path-provisioner (Rancher):

# 1. Cài local-path-provisioner
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.26/deploy/local-path-storage.yaml

# 2. Kiểm tra StorageClass
kubectl get storageclass
# NAME                 PROVISIONER             RECLAIMPOLICY
# local-path (default) rancher.io/local-path   Delete

# 3. Đặt làm default (nếu chưa)
kubectl patch storageclass local-path \
  -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
# 4. Tạo PVC và Pod test
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: local-path
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
  name: pvc-test-pod
spec:
  containers:
  - name: app
    image: busybox
    command: ["sh", "-c", "echo 'PVC works!' > /data/test.txt && cat /data/test.txt && sleep 3600"]
    volumeMounts:
    - name: storage
      mountPath: /data
  volumes:
  - name: storage
    persistentVolumeClaim:
      claimName: test-pvc
EOF

# 5. Xác minh PVC Bound và Pod Running
kubectl get pvc test-pvc
# NAME       STATUS   VOLUME         CAPACITY   ACCESS MODES
# test-pvc   Bound    pvc-xxxx-xxxx  1Gi        RWO

kubectl get pod pvc-test-pod
# NAME           READY   STATUS    RESTARTS
# pvc-test-pod   1/1     Running   0

# 6. Đọc dữ liệu từ Pod
kubectl exec pvc-test-pod -- cat /data/test.txt
# PVC works!

▶️ Phần B — NFS Subdir Provisioner (ReadWriteMany):

# Trên NFS server (ví dụ worker-01 làm NFS server)
sudo apt-get install -y nfs-kernel-server
sudo mkdir -p /srv/nfs/k8s
sudo chown nobody:nogroup /srv/nfs/k8s
echo "/srv/nfs/k8s *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports
sudo exportfs -rav

# Cài NFS client trên TẤT CẢ node
sudo apt-get install -y nfs-common

# Cài provisioner bằng Helm
helm repo add nfs-subdir-external-provisioner \
  https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm repo update
helm install nfs-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
  --set nfs.server=192.168.100.11 \
  --set nfs.path=/srv/nfs/k8s \
  --set storageClass.name=nfs-client \
  --set storageClass.defaultClass=false

# Tạo PVC ReadWriteMany
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes: [ReadWriteMany]
  storageClassName: nfs-client
  resources:
    requests:
      storage: 500Mi
EOF

kubectl get pvc nfs-pvc
# NAME      STATUS   VOLUME           CAPACITY   ACCESS MODES
# nfs-pvc   Bound    pvc-yyyy-yyyy    500Mi      RWX

✅ Kết quả mong đợi: PVC test-pvc ở trạng thái Bound; Pod ghi và đọc lại dữ liệu thành công; NFS PVC hỗ trợ RWX (ReadWriteMany).

🧹 Cleanup: kubectl delete pod pvc-test-pod; kubectl delete pvc test-pvc nfs-pvc.

LAB-039

Cài ingress-nginx controller và định tuyến HTTP

CLI · kubectl · Helm

🎯 Mục tiêu: Cài ingress-nginx bằng Helm, tạo 2 Service (app-a, app-b), cấu hình Ingress rule định tuyến theo host/path, test từ máy local bằng cách thêm hosts entry.

🧰 Công cụ / nền tảng: kubectl, Helm 3, cluster từ LAB-037, curl.

📦 Chuẩn bị: Cluster healthy với CNI; Helm đã cài (curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash).

▶️ Các bước:

# 1. Thêm Helm repo và cài ingress-nginx
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

# Lab không có LoadBalancer thật → dùng NodePort
helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.service.type=NodePort \
  --set controller.service.nodePorts.http=30080 \
  --set controller.service.nodePorts.https=30443

# 2. Chờ ingress-nginx controller running
kubectl rollout status deployment ingress-nginx-controller -n ingress-nginx
# deployment "ingress-nginx-controller" successfully rolled out

# 3. Kiểm tra service
kubectl get svc -n ingress-nginx
# NAME                       TYPE       CLUSTER-IP    PORT(S)
# ingress-nginx-controller   NodePort   10.96.x.x     80:30080/TCP,443:30443/TCP
# 4. Deploy 2 app demo
kubectl create deployment app-a --image=nginxdemos/hello --replicas=1
kubectl expose deployment app-a --port=80 --name=svc-app-a

kubectl create deployment app-b --image=httpd --replicas=1
kubectl expose deployment app-b --port=80 --name=svc-app-b

# 5. Tạo Ingress rule (host-based routing)
cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: app-a.lab.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc-app-a
            port:
              number: 80
  - host: app-b.lab.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: svc-app-b
            port:
              number: 80
EOF

kubectl get ingress demo-ingress
# NAME           CLASS   HOSTS                           ADDRESS   PORTS
# demo-ingress   nginx   app-a.lab.local,app-b.lab.local           80
# 6. Test từ control plane (hoặc máy local nếu thêm hosts entry)
# Thêm vào /etc/hosts (thay 192.168.100.11 bằng IP của bất kỳ worker):
# 192.168.100.11  app-a.lab.local app-b.lab.local

curl -H "Host: app-a.lab.local" http://192.168.100.11:30080/
# Server address: ... (nginx demo page của app-a)

curl -H "Host: app-b.lab.local" http://192.168.100.11:30080/
# <html><body><h1>It works!</h1></body></html> (Apache httpd của app-b)

# 7. Kiểm tra logs ingress controller
kubectl logs -n ingress-nginx deployment/ingress-nginx-controller --tail=20

✅ Kết quả mong đợi: curl -H "Host: app-a.lab.local" trả về trang nginx demo; curl -H "Host: app-b.lab.local" trả về trang Apache "It works!". Hai request đến cùng NodePort:30080 nhưng được route đến đúng Service.

🧹 Cleanup: kubectl delete ingress demo-ingress; kubectl delete deployment app-a app-b; kubectl delete svc svc-app-a svc-app-b; helm uninstall ingress-nginx -n ingress-nginx.

LAB-040

Push image vào private registry và pull từ cluster

CLI · kubectl · Helm · Docker

🎯 Mục tiêu: Dựng registry:2 (Docker private registry), build và push image tùy chỉnh, cấu hình cluster pull image từ registry nội bộ sử dụng imagePullSecrets.

🧰 Công cụ / nền tảng: Docker CE, kubectl, cluster từ LAB-036; một node (hoặc VM riêng) làm registry host (ví dụ 192.168.100.10).

📦 Chuẩn bị: Docker cài sẵn trên control plane; openssl để tạo self-signed cert.

▶️ Phần A — Dựng registry:2 với self-signed TLS:

# 1. Tạo thư mục và self-signed cert
mkdir -p ~/registry/{certs,auth,data}
openssl req -newkey rsa:4096 -nodes -sha256 \
  -keyout ~/registry/certs/domain.key \
  -x509 -days 365 \
  -out ~/registry/certs/domain.crt \
  -subj "/CN=registry.lab.local" \
  -addext "subjectAltName=IP:192.168.100.10,DNS:registry.lab.local"

# 2. Tạo htpasswd (user: admin, pass: Admin1234)
docker run --rm --entrypoint htpasswd httpd:2 -Bbn admin Admin1234 \
  > ~/registry/auth/htpasswd

# 3. Chạy registry container
docker run -d \
  --name registry \
  --restart=always \
  -p 5000:5000 \
  -v ~/registry/certs:/certs \
  -v ~/registry/auth:/auth \
  -v ~/registry/data:/var/lib/registry \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \
  -e REGISTRY_AUTH=htpasswd \
  -e REGISTRY_AUTH_HTPASSWD_REALM="Registry" \
  -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
  registry:2

# Kiểm tra registry chạy
curl -k -u admin:Admin1234 https://192.168.100.10:5000/v2/_catalog
# {"repositories":[]}
# 4. Tin tưởng cert trên TẤT CẢ node Kubernetes
# (Chạy trên mỗi node)
sudo mkdir -p /etc/docker/certs.d/192.168.100.10:5000
sudo cp ~/registry/certs/domain.crt /etc/docker/certs.d/192.168.100.10:5000/ca.crt

# Với containerd (kubeadm cluster) — thêm vào /etc/containerd/config.toml
sudo mkdir -p /etc/containerd/certs.d/192.168.100.10:5000
cat <<EOF | sudo tee /etc/containerd/certs.d/192.168.100.10:5000/hosts.toml
server = "https://192.168.100.10:5000"
[host."https://192.168.100.10:5000"]
  capabilities = ["pull", "resolve", "push"]
  ca = "/etc/containerd/certs.d/192.168.100.10:5000/ca.crt"
EOF
sudo cp ~/registry/certs/domain.crt /etc/containerd/certs.d/192.168.100.10:5000/ca.crt
sudo systemctl restart containerd
# 5. Build và push image tùy chỉnh
mkdir ~/myapp && cat <<EOF > ~/myapp/Dockerfile
FROM nginx:alpine
RUN echo "<h1>Hello from Private Registry!</h1>" > /usr/share/nginx/html/index.html
EOF
docker build -t 192.168.100.10:5000/myapp:v1.0 ~/myapp/

docker login 192.168.100.10:5000 -u admin -p Admin1234
docker push 192.168.100.10:5000/myapp:v1.0

# Xác minh image trong registry
curl -k -u admin:Admin1234 https://192.168.100.10:5000/v2/_catalog
# {"repositories":["myapp"]}
curl -k -u admin:Admin1234 https://192.168.100.10:5000/v2/myapp/tags/list
# {"name":"myapp","tags":["v1.0"]}
# 6. Tạo imagePullSecret trong Kubernetes
kubectl create secret docker-registry regcred \
  --docker-server=192.168.100.10:5000 \
  --docker-username=admin \
  --docker-password=Admin1234 \
  [email protected]

# 7. Deploy Pod từ private registry
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: private-reg-pod
spec:
  containers:
  - name: app
    image: 192.168.100.10:5000/myapp:v1.0
    ports:
    - containerPort: 80
  imagePullSecrets:
  - name: regcred
EOF

# 8. Xác minh Pod running và kiểm tra nội dung
kubectl get pod private-reg-pod
# NAME               READY   STATUS    RESTARTS
# private-reg-pod    1/1     Running   0

kubectl exec private-reg-pod -- curl -s localhost
# <h1>Hello from Private Registry!</h1>

# Xem image được pull từ đâu
kubectl describe pod private-reg-pod | grep "Image:"
# Image: 192.168.100.10:5000/myapp:v1.0

✅ Kết quả mong đợi: Pod private-reg-pod ở trạng thái Running; image pull thành công từ registry nội bộ (không phải Docker Hub); curl localhost trong pod trả về "Hello from Private Registry!".

🧹 Cleanup: kubectl delete pod private-reg-pod; kubectl delete secret regcred; docker stop registry && docker rm registry.

3. Tình huống doanh nghiệp thực tế

Bối cảnh

Một công ty fintech Việt Nam cần triển khai nền tảng microservices on-prem vì yêu cầu dữ liệu không rời datacenter nội địa (tuân thủ Nghị định 13/2023/NĐ-CP). Họ có 10 bare-metal server VMware vSphere, đội DevOps 3 người, và cần hỗ trợ 20 microservices trong 3 tháng.

Kiến trúc đề xuất

  • Cluster topology: 3 control plane nodes (HA etcd) + 6 worker nodes; dùng RKE2 vì CIS Benchmark compliance và hỗ trợ FIPS 140-2.
  • CNI: Calico với BGP peering vào switches Cisco — không overlay, latency thấp cho giao dịch tài chính.
  • Storage: Longhorn trên 3 worker node riêng (dedicated storage nodes, 2 TB NVMe mỗi node) — distributed replication, snapshot tự động trước mỗi deploy.
  • Ingress: ingress-nginx với MetalLB (bare-metal LoadBalancer) để có IP thật thay vì NodePort; cert-manager tích hợp với PKI nội bộ.
  • Registry: Harbor với image scanning (Trivy) và replication sang DR site; policy chặn image có CVE Critical.
  • Upgrade strategy: Rolling upgrade từng node, drain trước khi upgrade, etcd snapshot backup tự động trước mỗi lần nâng version.

Kết quả

  • 20 microservices chạy trên cluster on-prem, zero downtime trong 6 tháng vận hành.
  • Deploy frequency tăng từ 1 lần/tuần lên 5 lần/ngày nhờ pipeline CI/CD tích hợp Harbor.
  • Dữ liệu 100% trong datacenter nội địa, pass audit kiểm toán data residency.

📚 Nguồn tham khảo

Module 07: Private Cloud Architecture Module 09: AWS Core for DevOps
Zalo