Volume emptyDir, hostPath và volume từ ConfigMap, Secret
Chào bạn, mình đã đỗ CKA sau một lần thi và bài này là bài mình thấy "ăn điểm" nhất trong domain Storage. Lý do rất đơn giản: các câu hỏi về volume trong đề CKA thường ngắn, rõ ràng, và bạn có thể làm xong trong 3 đến 5 phút nếu nhớ đúng 4 loại volume cơ bản. Ngược lại, mình đã thấy rất nhiều bạn mất 15 phút cho một câu chỉ vì quên rằng ConfigMap mount dạng volume thì không cập nhật khi dùng subPath. Bài này sẽ giúp bạn tránh đúng cái bẫy đó.
Mục tiêu bài học
Sau bài này, bạn sẽ:
- Phân biệt được vòng đời và mục đích dùng của
emptyDir,hostPath, volumeconfigMapvà volumesecret. - Viết được YAML mount cả 4 loại volume trên vào pod, kể cả
items,defaultModevàsubPath. - Biết chính xác khi nào dữ liệu trong volume tự cập nhật và khi nào không.
- Tạo nhanh ConfigMap và Secret bằng imperative command để tiết kiệm thời gian thi.
- Nhận ra các lỗi mount phổ biến (che mất file gốc,
subPathkhông refresh, thiếu key) trước khi chúng làm hỏng bài thi.
Ôn lại nhanh (5 phút)
Trước bài này bạn đã làm việc nhiều với pod spec, resource requests/limits và scheduling. Ba thứ cần nhớ lại ngay:
- Pod spec có hai phần liên quan đến volume:
spec.volumes(khai báo volume ở cấp pod) vàspec.containers[].volumeMounts(gắn volume vào đường dẫn trong container). Tên volume ở hai chỗ phải trùng nhau. - Mọi container trong cùng một pod chia sẻ network namespace, và nếu cùng mount một volume thì chia sẻ luôn dữ liệu. Đây là nền tảng của pattern sidecar.
kubectl explainlà người bạn tốt nhất khi quên field:kubectl explain pod.spec.volumes.emptyDir.
[!NOTE] Volume không phải PVC. Volume là thứ gắn vào pod, PVC là thứ yêu cầu storage. Bài này chỉ nói về volume "nhẹ", bài sau mới sang PV/PVC.
Lý thuyết
1. Bức tranh tổng thể: chọn loại volume nào?
flowchart TD
Start["Cần đưa dữ liệu vào Pod"] --> Q1{"Dữ liệu chỉ cần sống cùng Pod?"}
Q1 -->|"Có"| EmptyDir["📦<br/>emptyDir"]
Q1 -->|"Không"| Q2{"Là config hoặc thông tin nhạy cảm?"}
Q2 -->|"Có"| CM["🔐<br/>ConfigMap / Secret"]
Q2 -->|"Không"| Q3{"Phải chạm filesystem của node?"}
Q3 -->|"Có"| HostPath["🖥️<br/>hostPath"]
Q3 -->|"Không"| PVC["💾<br/>PV / PVC (bài sau)"]
2. emptyDir
emptyDir là volume được tạo ra khi pod được gán vào node, và bị xóa vĩnh viễn khi pod bị xóa (hoặc khi pod bị chuyển sang node khác, ví dụ node bị drain).
Đặc điểm cần nhớ:
- Khởi tạo rỗng. Nếu container restart trong cùng pod, dữ liệu vẫn còn (vì pod chưa bị xóa).
- Dùng tốt nhất cho: scratch space, cache tạm, và trao đổi file giữa các container trong cùng pod (pattern sidecar).
medium: Memorybiến nó thành tmpfs nằm trong RAM, và dung lượng này tính vào memory limit của container. Đây là chi tiết nhiều bạn quên khi debug OOMKilled.
volumes:
- name: scratch
emptyDir:
medium: Memory
sizeLimit: 64Mi
3. hostPath
hostPath mount một đường dẫn từ filesystem của node vào container.
type |
Ý nghĩa |
|---|---|
DirectoryOrCreate |
Tạo thư mục trên node nếu chưa có, quyền 0755 |
Directory |
Thư mục phải tồn tại sẵn |
FileOrCreate |
Tạo file nếu chưa có, quyền 0644 |
File |
File phải tồn tại sẵn |
Socket, CharDevice, BlockDevice |
Dùng cho các thiết bị đặc biệt |
Rủi ro: pod gắn chặt với một node cụ thể. Pod bị schedule sang node khác là mất dữ liệu, và nội dung trên mỗi node lại khác nhau. Trong đề thi, hostPath hay xuất hiện ở các câu về DaemonSet hoặc node-level agent (thu thập log từ /var/log). Nếu đề cho phép, hãy dùng DirectoryOrCreate để tránh lỗi "path not found" khi node chưa có thư mục đó.
[!WARNING]
hostPathmount/hoặc/var/lib/kubeletlà lỗ hổng bảo mật nghiêm trọng và có thể bị Pod Security Admission chặn ở namespace có labelrestrictedhoặcbaseline. Đừng tự thêm vào nếu đề không yêu cầu.
4. Volume từ ConfigMap
Khi mount ConfigMap dưới dạng volume, mỗi key trở thành một file: tên file là key, nội dung file là value.
volumes:
- name: cfg
configMap:
name: app-config
defaultMode: 0640
items:
- key: app.properties
path: app.properties
- key: feature.flag
path: flags/feature.flag
itemscho phép chọn tập con các key và đổi đường dẫn tương đối (path) bên trongmountPath.defaultModemặc định là0644. Secret volume cũng mặc định0644.- Volume ConfigMap và Secret luôn là read-only. Container không ghi được vào đó.
Cơ chế cập nhật: kubelet đồng bộ định kỳ, nên khi bạn sửa ConfigMap, file trong pod sẽ được cập nhật sau khoảng 1 đến 2 phút. Kubelet làm việc này bằng cách tạo thư mục ..data mới và đổi symlink, nên bạn sẽ thấy các file là symlink trỏ tới ..data/. Đây là lý do việc cập nhật mang tính atomic.
Ngoại lệ quan trọng nhất: nếu bạn mount bằng subPath, file không bao giờ tự cập nhật. Phải xóa và tạo lại pod.
[!IMPORTANT] Env var lấy từ ConfigMap (
valueFrom.configMapKeyRef) cũng không cập nhật khi ConfigMap đổi. Muốn thấy giá trị mới thì phải restart pod. Đây là câu hỏi lý thuyết rất hay gặp.
5. Volume từ Secret
Cách hoạt động giống hệt ConfigMap, chỉ khác nguồn dữ liệu. Điểm cần nhớ:
- Giá trị Secret lưu trong
dataở dạng base64. Khi mount thành file, nội dung đã được giải mã, tức là plaintext bên trong container. - Secret có thể khai báo bằng
stringDatađể viết thẳng plaintext trong YAML, Kubernetes sẽ tự encode. - Nên đặt
defaultMode: 0400cho file chứa mật khẩu. - Secret dạng volume cũng không nên dùng làm nơi lưu credential cho ứng dụng đọc một lần lúc khởi động, vì lý do cập nhật ở trên.
6. Bảng so sánh nhanh
| Tiêu chí | emptyDir | hostPath | configMap | secret |
|---|---|---|---|---|
| Sống theo pod | Có | Không (theo node) | Không (theo object) | Không (theo object) |
| Ghi được | Có | Có | Không | Không |
| Chia sẻ giữa các pod | Không | Có, nếu cùng node | Có | Có |
| Tự cập nhật khi nguồn đổi | Không áp dụng | Không áp dụng | Có, trừ subPath |
Có, trừ subPath |
| Dùng trong đề thi cho | sidecar, cache | log agent, DaemonSet | config file | credential, TLS cert |
Ví dụ có hướng dẫn
Ví dụ 1: emptyDir chia sẻ dữ liệu giữa hai container
Mục tiêu: container writer ghi log, container reader đọc cùng file.
apiVersion: v1
kind: Pod
metadata:
name: shared-scratch
spec:
containers:
- name: writer
image: busybox
command: ["sh", "-c", "while true; do date >> /scratch/log.txt; sleep 5; done"]
volumeMounts:
- name: scratch
mountPath: /scratch
- name: reader
image: busybox
command: ["sh", "-c", "tail -f /scratch/log.txt"]
volumeMounts:
- name: scratch
mountPath: /scratch
volumes:
- name: scratch
emptyDir: {}
Cách kiểm tra:
kubectl apply -f shared-scratch.yaml
kubectl logs shared-scratch -c reader
kubectl exec shared-scratch -c reader -- df -h /scratch
Đọc kết quả: hai container thấy cùng một file vì cùng mount scratch. Xóa pod là dữ liệu mất, đúng như bản chất emptyDir.
Ví dụ 2: mount ConfigMap thành nhiều file cấu hình
Bước 1, tạo ConfigMap bằng imperative command (nhanh hơn viết YAML):
kubectl create configmap app-config \
--from-literal=app.properties=$'mode=production\ntimeout=30' \
--from-literal=feature.flag=on
Bước 2, tạo pod mount toàn bộ ConfigMap:
apiVersion: v1
kind: Pod
metadata:
name: cfg-demo
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: cfg
mountPath: /etc/app
volumes:
- name: cfg
configMap:
name: app-config
defaultMode: 0640
Bước 3, kiểm tra:
kubectl exec cfg-demo -- ls -l /etc/app
kubectl exec cfg-demo -- cat /etc/app/app.properties
kubectl exec cfg-demo -- cat /etc/app/feature.flag
Bạn sẽ thấy app.properties và feature.flag đều là symlink trỏ vào ..data/. Đó là dấu hiệu volume đang hoạt động đúng cơ chế cập nhật atomic.
Ví dụ 3: Secret với subPath để ghi đè đúng một file
Tình huống thực tế: bạn muốn mount mỗi file password vào một đường dẫn cụ thể, không muốn mount cả thư mục.
kubectl create secret generic db-secret --from-literal=password=S3cr3tPass
apiVersion: v1
kind: Pod
metadata:
name: secret-demo
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: db-secret
mountPath: /etc/app/db-password
subPath: db-password
readOnly: true
volumes:
- name: db-secret
secret:
secretName: db-secret
defaultMode: 0400
items:
- key: password
path: db-password
Kiểm tra:
kubectl exec secret-demo -- cat /etc/app/db-password
kubectl exec secret-demo -- ls -l /etc/app/db-password
Lưu ý: với subPath, mountPath phải là đường dẫn file, không phải thư mục. Và nhớ rằng file này sẽ không tự cập nhật.
Thực hành có hướng dẫn
Phần này là trọng tâm. Hãy làm từng bài, gõ lệnh bằng tay, đừng copy. Mục tiêu là phản xạ với cú pháp volumes và volumeMounts trong khoảng 2 phút mỗi bài.
Bài 1: ConfigMap từ file, mount vào thư mục
Yêu cầu:
- Tạo file
index.htmlvới nội dunghello CKA. - Tạo ConfigMap tên
web-htmltừ file đó (--from-file). - Tạo pod
webdùng imagenginx, mount ConfigMapweb-htmlvào/usr/share/nginx/html, nhưng chỉ lấy keyindex.html. - Kiểm tra bằng cách exec vào pod và đọc file.
Xem gợi ý và đáp án
echo "hello CKA" > index.html
kubectl create configmap web-html --from-file=index.html
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
configMap:
name: web-html
items:
- key: index.html
path: index.html
kubectl apply -f web.yaml
kubectl exec web -- cat /usr/share/nginx/html/index.html
Bẫy ở đây: nếu bạn mount vào /usr/share/nginx/html mà ConfigMap có nhiều key rác, thư mục sẽ chứa lẫn các file đó. Dùng items để kiểm soát.
Bài 2: Sửa ConfigMap và quan sát cơ chế cập nhật
Yêu cầu:
- Tạo pod
watcher(imagebusybox,sleep 3600) mount ConfigMapapp-configtừ Ví dụ 2 vào/etc/app. - Sửa ConfigMap bằng
kubectl edit configmap app-config, đổifeature.flagthànhoff. - Theo dõi file trong pod, ghi lại thời gian tới khi giá trị thay đổi.
- Bây giờ tạo thêm pod
watcher-subcũng mount keyfeature.flagnhưng bằngsubPath, và lặp lại bước 2 và 3.
Xem gợi ý và đáp án
Theo dõi bằng vòng lặp:
while true; do
echo "$(date +%H:%M:%S) $(kubectl exec watcher -- cat /etc/app/feature.flag 2>/dev/null)"
sleep 10
done
Kết quả mong đợi: watcher thấy giá trị mới sau khoảng 30 giây đến 2 phút. watcher-sub không bao giờ thấy giá trị mới, dù chờ 10 phút. Đó chính là lý do phải xóa và tạo lại pod.
Bài 3: emptyDir với medium Memory
Yêu cầu: tạo pod memcache, mount một emptyDir có medium: Memory và sizeLimit: 32Mi vào /cache, sau đó kiểm tra filesystem type và dung lượng.
Xem gợi ý và đáp án
apiVersion: v1
kind: Pod
metadata:
name: memcache
spec:
containers:
- name: app
image: busybox
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: cache
mountPath: /cache
volumes:
- name: cache
emptyDir:
medium: Memory
sizeLimit: 32Mi
kubectl apply -f memcache.yaml
kubectl exec memcache -- df -h /cache
kubectl exec memcache -- mount | grep /cache
df -h sẽ hiển thị khoảng 32M và mount cho thấy tmpfs. Nhớ rằng dung lượng này ăn vào memory limit của container, nên nếu ứng dụng cần nhiều RAM, đừng đặt sizeLimit quá lớn.
Bài 4: Tìm và sửa lỗi mount che mất file gốc
Yêu cầu: áp dụng pod dưới đây, quan sát nginx không khởi động được và giải thích nguyên nhân, sau đó sửa.
apiVersion: v1
kind: Pod
metadata:
name: broken-nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: cfg
mountPath: /etc/nginx
volumes:
- name: cfg
configMap:
name: app-config
Xem gợi ý và đáp án
kubectl apply -f broken-nginx.yaml
kubectl get pod broken-nginx
kubectl describe pod broken-nginx | tail -20
kubectl logs broken-nginx
Nguyên nhân: mount vào /etc/nginx đã che toàn bộ nội dung gốc của thư mục đó, bao gồm nginx.conf. Container không tìm thấy config và thoát.
Cách sửa đúng: mount vào thư mục con mà nginx tự đọc, ví dụ /etc/nginx/conf.d, hoặc dùng subPath để ghi đè đúng một file cụ thể chứ không che cả thư mục.
volumeMounts:
- name: cfg
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
Bài 5 (nâng cao, làm nếu còn thời gian): hostPath với DirectoryOrCreate
Yêu cầu: tạo pod hostwriter dùng hostPath type DirectoryOrCreate trỏ tới /tmp/cka-data, sau đó xóa pod và kiểm tra dữ liệu còn hay mất.
Xem gợi ý và đáp án
volumes:
- name: data
hostPath:
path: /tmp/cka-data
type: DirectoryOrCreate
kubectl exec hostwriter -- sh -c "echo persist > /data/proof.txt"
kubectl delete pod hostwriter
Sau khi xóa pod, nếu tạo lại một pod khác cùng mount /tmp/cka-data, file proof.txt vẫn còn, vì dữ liệu nằm trên node chứ không nằm trong pod. Đây chính là điểm khác biệt cốt lõi so với emptyDir.
Trên cluster nhiều node, muốn kiểm chứng bạn cần biết pod chạy ở node nào (kubectl get pod -o wide) và sau đó dùng kubectl debug node/<node> để xem file.
Góc chuyên gia
Những lỗi mình từng mắc, hoặc thấy người khác mắc trong phòng thi:
- Mount ConfigMap vào một thư mục đã có nội dung quan trọng. Kubernetes sẽ che toàn bộ thư mục đó. Nguyên tắc: mount vào thư mục chuyên dụng, hoặc dùng
subPathcho đúng một file. - Quên rằng
volumeMounts.namephải khớp chính xác vớivolumes.name. Đây là lỗi gõ tay phổ biến nhất khi làm bài nhanh. Lệnhkubectl apply --dry-run=serversẽ không bắt được lỗi này, nhưngkubectl describe podthì có. - Tưởng rằng sửa ConfigMap là pod tự nhận config mới ngay lập tức. Không có chuyện đó với env var và với
subPath. - Dùng
hostPathcho dữ liệu cần bền vững rồi ngạc nhiên khi Deployment scale hoặc reschedule làm mất dữ liệu. - Quên
defaultMode: 0400cho Secret chứa mật khẩu. Không sai về chức năng nhưng mất điểm "chi tiết" nếu đề yêu cầu rõ.
Mẹo tốc độ cho CKA:
- Tạo ConfigMap/Secret luôn bằng imperative, không viết YAML tay:
kubectl create configmap app-config --from-literal=key1=v1 --from-literal=key2=v2
kubectl create secret generic db-secret --from-literal=password=S3cr3t
kubectl create configmap from-file-cm --from-file=./config.yaml
- Dùng
kubectl explainkhi quên field, và--dry-run=client -o yamlđể lấy khung YAML:
kubectl run tmp --image=busybox --dry-run=client -o yaml -- sleep 3600 > pod.yaml
- Khi cần mount vào một Deployment đang chạy, dùng
kubectl edit deploythay vì apply lại toàn bộ file. Nhanh hơn và ít rủi ro sai sót. - Luôn kiểm tra bằng
kubectl exec. Đừng nộp bài chỉ vìkubectl get podbáo Running.
Quy tắc ngón tay:
- Dữ liệu tạm, chia sẻ trong pod:
emptyDir. - Config hoặc credential:
configMaphoặcsecret, ưu tiên volume nếu cần cập nhật động, ưu tiên env nếu chỉ đọc một lần. - Cần chạm node:
hostPath, và chỉ khi đề nói rõ. - Còn lại: chờ bài sau về PV/PVC.
[!TIP] Trong đề CKA, nếu câu hỏi nói "update the pod so it reads the new ConfigMap" thì 90% đáp án là xóa và tạo lại pod, hoặc kiểm tra xem có
subPathkhông. Đọc kỹ chữ "automatically" trước khi trả lời.
Kiểm tra nhanh bằng cloze
emptyDir chỉ sống theo {{c1::Pod}}: pod bị xóa thì dữ liệu cũng mất.
Đặt {{c2::medium: Memory}} cho emptyDir để dữ liệu nằm trong RAM dưới dạng tmpfs.
Khi mount ConfigMap dạng volume, mỗi {{c3::key}} trở thành một file riêng.
Mount bằng {{c4::subPath}} thì file sẽ KHÔNG tự cập nhật khi ConfigMap thay đổi.
Quyền file mặc định khi mount ConfigMap hoặc Secret là {{c5::0644}}.
hostPath type {{c6::DirectoryOrCreate}} sẽ tạo thư mục trên node nếu chưa tồn tại.
Thẻ ôn tập
[
{ "front": "emptyDir tồn tại trong bao lâu?", "back": "Trọn vòng đời của Pod; bị xóa khi Pod bị xóa hoặc bị chuyển sang node khác." },
{ "front": "emptyDir với medium: Memory dùng gì và tính vào đâu?", "back": "tmpfs trong RAM, dung lượng tính vào memory limit của container." },
{ "front": "hostPath type nào tạo thư mục trên node nếu chưa có?", "back": "DirectoryOrCreate" },
{ "front": "Rủi ro chính của hostPath là gì?", "back": "Dữ liệu gắn với một node cụ thể; pod reschedule sang node khác là mất dữ liệu, và đây là lỗ hổng bảo mật." },
{ "front": "Mỗi key trong ConfigMap khi mount dạng volume trở thành gì?", "back": "Một file riêng, tên file là key, nội dung là value." },
{ "front": "Field nào chọn key và đổi đường dẫn file khi mount ConfigMap?", "back": "items với cặp key và path, kết hợp defaultMode để đặt quyền." },
{ "front": "ConfigMap thay đổi thì file trong volume có tự cập nhật không?", "back": "Có, sau khoảng 1 đến 2 phút, do kubelet đồng bộ định kỳ." },
{ "front": "Điều kiện nào khiến volume ConfigMap KHÔNG tự cập nhật?", "back": "Khi mount bằng subPath; phải xóa và tạo lại Pod." },
{ "front": "Env var lấy từ ConfigMap có tự cập nhật khi ConfigMap đổi không?", "back": "Không, phải restart Pod để nhận giá trị mới." },
{ "front": "defaultMode mặc định của volume ConfigMap và Secret là bao nhiêu?", "back": "0644" },
{ "front": "Nội dung Secret khi mount thành file ở dạng nào?", "back": "Đã giải mã base64, tức là plaintext bên trong container." },
{ "front": "Volume ConfigMap và Secret cho phép ghi hay chỉ đọc?", "back": "Chỉ đọc (read-only)." },
{ "front": "Lệnh tạo nhanh ConfigMap từ hai literal?", "back": "kubectl create configmap app-config --from-literal=k1=v1 --from-literal=k2=v2" },
{ "front": "Lỗi gì xảy ra khi mount ConfigMap vào /etc/nginx?", "back": "Toàn bộ nội dung gốc của thư mục bị che, gồm nginx.conf, nên nginx không khởi động được." }
]
Tóm tắt và bước tiếp theo
Chốt lại 5 dòng để bạn ghi ra giấy nháp:
emptyDirsống cùng pod, xóa pod là mất, dùng cho cache và chia sẻ giữa các container.hostPathbám vào filesystem của node, tiện cho agent nhưng nguy hiểm và không bền vững.- ConfigMap và Secret mount dạng volume thì mỗi key thành một file, mặc định
0644, read-only. items,defaultMode,subPathlà ba field quyết định hình dạng cuối cùng của file trong container.subPathvà env var thì không tự cập nhật. Đây là bẫy thi phổ biến nhất của domain Storage.
Bài sau chúng ta bước sang phần nặng ký hơn: PV, PVC với binding, access modes và reclaim policy. Những gì bạn vừa học về volumes và volumeMounts vẫn dùng nguyên, chỉ khác là nguồn dữ liệu lúc đó sẽ là một PVC thay vì ConfigMap hay emptyDir. Bạn sẽ thấy kubectl get pvc báo Pending rất nhiều lần, và bài 5.4 sẽ dạy cách gỡ đúng những lỗi đó.