ข้ามไปยังเนื้อหา

Volume และ Volume Type

Volume คือ directory ที่ container ใน Pod เข้าถึงได้ และ lifecycle ของตัวเองผูกกับ Pod ไม่ใช่ผูกกับ container ตัวใดตัวหนึ่ง

container แต่ละตัวมี writable layer เป็นของตัวเอง แต่ layer นั้นถูกทิ้งทันทีที่ container restart ไม่ว่าจะเพราะ crash, image update หรือ liveness probe fail ก็ตาม ซึ่งไม่มีปัญหาสำหรับ application code แบบ stateless แต่พังทันทีถ้าอะไรบางอย่างต้องอยู่รอดข้าม restart หรือต้อง share กันระหว่าง container ใน Pod เดียวกัน Volume แก้ทั้งสองปัญหานี้ เพราะถูก declare ครั้งเดียวที่ระดับ Pod และคงอยู่ตราบใดที่ Pod นั้นยังอยู่ ไม่ว่า container ตัวใดใน Pod จะ restart กี่รอบก็ตาม

Terminal window
# The container restarts, but the Pod (and its volumes) does not disappear
kubectl get pod log-processor -o jsonpath='{.status.containerStatuses[*].restartCount}'
  • emptyDir — directory เปล่าที่ถูกสร้างตอน Pod เริ่มทำงาน เป็น scratch space เหมาะกับ cache หรือ share file ระหว่าง container หลักกับ sidecar emptyDir ถูกลบถาวรตอน Pod ถูกลบ ไม่ใช่แค่ตอน container ตัวใดตัวหนึ่ง restart
  • hostPath — mount path จาก filesystem ของ node จริงเข้ามาใน Pod ตรง ๆ ทรงพลังแต่เสี่ยง เพราะผูก Pod เข้ากับข้อมูลที่อยู่บน node เฉพาะตัวนั้น และอาจเปิดให้ container เข้าถึงส่วนที่ sensitive ของ filesystem ของ node ได้ ใช้เท่าที่จำเป็นจริง ๆ เท่านั้น (เช่น log collector หรือ agent ระดับ node)
  • configMap / secret volume — project ข้อมูล key-value จาก ConfigMap หรือ Secret เข้าไปใน Pod เป็นไฟล์ หนึ่ง key ต่อหนึ่งไฟล์ รายละเอียดอยู่ในบทถัดไปเรื่อง ConfigMap และ Secret
  • projected volume — รวมหลาย source (ConfigMap, Secret, downwardAPI, serviceAccountToken) เข้าเป็น directory เดียว container เลยต้องใช้ mount point เดียวก็เห็นข้อมูลที่มาจากหลาย object ได้

นี่คือ Pod ที่ container สองตัว share emptyDir volume เดียวกัน ที่เป็น pattern ที่พบบ่อยเวลามี container หลักคู่กับ sidecar ที่คอย ship log

apiVersion: v1
kind: Pod
metadata:
name: log-processor
spec:
volumes:
- name: shared-logs
emptyDir: {}
containers:
- name: app
image: my-app:1.0
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
- name: log-shipper
image: fluent-bit:2.2
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
readOnly: true

และตัวอย่าง hostPath ที่ mount directory /proc ของ node แบบ read-only เข้า monitoring agent

apiVersion: v1
kind: Pod
metadata:
name: node-exporter
spec:
volumes:
- name: proc
hostPath:
path: /proc
type: Directory
containers:
- name: node-exporter
image: prom/node-exporter:v1.7.0
volumeMounts:
- name: proc
mountPath: /host/proc
readOnly: true

แค่ตั้งชื่อ volume ยังใช้งานไม่ได้ volume ระดับ Pod ทุกตัวต้องมีสองส่วนที่ตรงกัน

  1. spec.volumes[] — declare volume และบอกว่าข้อมูลมาจากไหน (emptyDir, hostPath, configMap, persistentVolumeClaim ฯลฯ) ส่วนนี้อยู่ในระดับ Pod
  2. spec.containers[].volumeMounts[] ของแต่ละ container — เอา volume ที่ declare ไว้แล้วมา mount เข้า filesystem ของ container ตัวนั้น ที่ mountPath ที่กำหนด

volume ที่ declare ไว้ใน spec.volumes แต่ไม่มี container ไหน reference ผ่าน volumeMounts เลย จะไม่มีผลอะไรกับ container นั้น คือแค่ไม่ถูก mount เข้าไป นี่คือวิธีที่ container สองตัวใน Pod เดียวกัน share ข้อมูลกันได้ด้วย ทั้งสองตัวใส่ name ของ volume เดียวกันใน volumeMounts ของตัวเอง ชี้ไปที่ directory เดียวกันที่ declare ไว้ครั้งเดียวใน spec.volumes

Terminal window
# Confirm both containers see the same file through the shared emptyDir
kubectl exec log-processor -c log-shipper -- ls /var/log/app
flowchart LR
  subgraph pod["Pod: log-processor"]
    a["Container: app"] -->|writes| vol[("emptyDir volume: shared-logs")]
    vol -->|reads| b["Container: log-shipper"]
  end
container สองตัวใน Pod เดียวกัน share emptyDir volume เดียว
เกิดอะไรขึ้นกับข้อมูลใน emptyDir volume เมื่อ Pod ที่เป็นเจ้าของถูกลบ
ทำไม hostPath ถึงถูกมองว่าเสี่ยง
ทำไมการ mount volume เข้า container ถึงต้องใช้ทั้ง spec.volumes และ volumeMounts
volume type ไหนถูกออกแบบมาให้รวมหลาย source เช่น ConfigMap กับ Secret เข้าเป็น mount directory เดียว