[Kubernetes] ทำความเข้าใจเรื่อง Volume กันซักหน่อย
ก่อนอื่นเลยเราเราต้องทราบก่อนว่า Kubernetes volume เข้ามาเพื่อแก้ปัญหาอะไร
จำกันได้ใช่ไหมว่าใน Node มี Pod และใน Pod มี Container
ในส่วนของ Pod นั่นมันสามารถเกิดขึ้นและตายไปตาม life cycle ของมัน (ephemeral)
จะเกิดอะไรขึ้นถ้าใน Container มี data ของ application ที่เราต้องใช้ ถ้าหาก Pod มันตายไป ข้อมูลก็จะหายไปเช่นกัน
ไอเดียก็คือเราต้องการย้าย data ใน Container ออกมาจาก Pod ซึ่งวิธีนี้จะทำให้ data ไม่ขึ้นอยู่กับ Pod ใดๆ ใน Cluster ซึ่งเราจะเรียกอันนี้ว่า Persistent Volume หรือ PV
ประโยชน์ของมันก็คือสามารถที่จะทำเป็น State ซึ่งเราสามารถที่จะ persist state นี้ไปยังหลายๆ Pod ได้
Kubernetes มี Volume ให้เลือกหลายประเภท ขึ้นอยู่กับวัตถุประสงค์ที่แตกต่างกัน แบ่งเป็น 5 ประเภทหลักๆ ดังต่อไปนี้
emptyDir
emptyDir เป็น volume ที่สร้างขึ้นมาโดยผูกอยู่กับ Node ที่รัน Pod นั้นๆ และจะคงอยู่ต่อไปตราบเท่าที่ Pod นั้นยังทำงานอยู่บน Node
emptyDir จะทำการสร้าง volume ขึ้นมาโดยยังไม่มีข้อมูลใดๆ ซึ่ง Container ใน Pod สามารถที่จะ อ่าน/เขียน ข้อมูลได้ และใน container ไม่จำเป็นต้อง mount ไว้ที่ path เดียวกันกับเครื่อง host (สามารถ mount RAM แบบ tmpfs เข้าไปได้ด้วยนะ ไม่ใช่เพียงแค่ storage)
ข้อควรจะวัง
- เมื่อ Pod ถูกลบจาก Node ข้อมูลใน emptyDir จะถูกลบออกไปอย่างถาวร
ตัวอย่าง code
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
หากต้องการ mount RAM ทำได้โดยใส่ ฟิลด์ Memory ใน emptyDir.medium
hostPath
ในส่วนของ hostPath นั้นคล้ายกับ emptyDir แต่อาจมีข้อมูลบนเครื่อง Node อยู่ก่อนแล้ว และไม่สามารถทำ tmpfs ได้
ข้อควรระวัง
- ต้องมั่นใจว่า Pod ที่เราต้องการจะรัน ทำงานอยู่บน Node ที่เราเตรียมข้อมูลไว้แล้ว มิฉะนั่น Pod จะหา Volume ไม่เจอ
ตัวอย่าง code
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
# directory location on host
path: /data
# this field is optional
type: Directory
Cloud Volumes
Cloud volume เช่น awsElasticBlockStore, gcePersistentDisk (ขึ้นอยู่กับ Cloud Provider ที่เราใช้บริการ) หรือตัวอื่นๆ ที่คล้ายกัน เช่น
apiVersion: v1
kind: Pod
metadata:
name: test-ebs
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /test-ebs
name: test-volume
volumes:
- name: test-volume
# This AWS EBS volume must already exist.
awsElasticBlockStore:
volumeID: <volume-id>
fsType: ext4
จะเห็นว่าเราต้องกรอก volume-id และ fsType ลงไปใน yaml ไฟล์ด้วย
ข้อควรระวัง
- คนสร้างจำเป็นต้องรู้ข้อมูลทั้งหมดในการเชื่อมต่อกับ backend (เช่น access token เป็นต้น)
หากมีการแบ่ง role ระหว่าง user กับ admin แน่นอนว่า user ไม่ควรที่จะรู้ข้อมูลเหล่านี้ ซึ่งการแก้ปัญหาจะกล่าวในส่วนของ Persistent Volume Claim หรือเรียกสั้นๆ ว่า PVC
nfs
ก่อนจะไปพูดถึง PVC เราจะมาพูดถึง NFS (Network File System) กันก่อน
nfs volume สามารถที่จะแชร์ไฟล์ต่างๆ ผ่านทาง Network และทำการ mount ไปยัง Pod เพื่อให้ Pod ใช้งานได้ และถ้าหาก Pod ตาย ข้อมูลใน nfs server ก็ยังอยู่ซึ่งต่างจาก emptyDir ที่มันจะถูกลบเมื่อ Pod ได้ตายไป และหาก Pod ที่ใช้งาน volume ประเภทนี้อยู่ตายไป volume ประเภทนี้จะแสดงสถานะแค่ unmounted เท่านั้น ซึ่งหมายความว่า NFS volume สามารถที่จะ mount เข้าไปใหม่ สามารถแชร์ข้อมูลกันหลายๆ Pod และความเทพของมันอีกอย่างคือสามารถเขียนข้อมูลจากหลายๆ Pod พร้อมๆ กันได้ (ระวังเรื่อง race condition ด้วยนะ)
ตัวอย่าง code
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
capacity:
storage: 1Mi
accessModes:
- ReadWriteMany
nfs:
# FIXME: use the right IP
server: 10.244.1.4
path: "/"
หากจะใช้ volume ประเภทนี้ อย่าลืมติดตั้ง nfs-common บนเครื่อง node ด้วยนะ ไม่งั้น ใน pod จะ error
วิธีติดตั้ง NFS Server สามารถหาอ่านได้จาก
Persistent Volume Claim
PVC คือ Volume ที่สร้างจาก PV (PersistentVolume) หรือ StorageClass อีกทีหนึ่ง โดยไม่จำเป็นต้องรู้ข้อมูลของ Backend ในส่วนของการเชื่อมต่อกับ physical หรือ cloud volume ซึ่ง admin จะเป็นคน provide ให้ว่าใครมีสิทธิ์ที่จะ claim volume ดังกล่าว (เป็นแค่ user ทำในส่วนของ PVC ก็พอ) ซึ่ง PV อาจจะเป็น NFS หรือ GCE หรือ iSCSI volume ก็ได้ (ขึ้นอยู่กับ admin)
เมื่อสร้าง PVC เสร็จแล้ว มันจะพยายามหา PV เพื่อทำการ binding เข้าด้วยกัน หากไม่ตรงตามเงื่อนไขที่เคลมไว้ มันจะมีสถานะเป็น pending ตลอดไป และหากใครเคยใช้ fabric8 เราสามารถใช้คำสั่ง gofabric8 volumes
เพื่อสร้าง PV ขึ้นมาแบบ hostPath ให้ทุกตัวที่มีสถานะเป็น pending
กรณีที่มี PV หลายอัน เราสามารถใช้ selector สำหรับ select ได้ว่าจะใช้ PV อันไหน
ตัวอย่าง code
สร้าง PV
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
name: docker-registry-pv
spec:
accessModes:
- ReadWriteMany
capacity:
storage: 25Gi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: docker-registry-storage
namespace: default
nfs:
path: /docker_registry
# FIXME: use the right IP or domain
server: 10.244.1.4
persistentVolumeReclaimPolicy: Delete
storageClassName: standard
สร้าง PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
volume.beta.kubernetes.io/storage-class: standard
name: docker-registry-storage
namespace: default
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 25Gi
volumeName: docker-registry-pv
ข้อควรระวัง
- ไม่สามารถ bind 2 PVC ต่อ 1 PV ได้ (PVC เป็นแบบ exclusive) แต่สามารถใช้ PVC เดียวกันกับ Pod ที่แตกต่างกันได้
ใน culture ที่ผมทำงานอยู่ ไม่ได้มีการแบ่ง admin กับ user ที่ชัดเจน คนหนึ่งคนสามารถทำได้แทบทุกอย่าง เรียกได้ว่าเป็น full stack และต้องทำหน้าที่เป็น DevOps กันแทบทุกคน จึงไม่ค่อยซีเรียสกับเรื่องเหล่านี้เพราะสร้างเองเคลมเองนักเลงพอ (ฮ่าๆ)
แต่ถ้าเป็นองค์กรขนาดใหญ่ เรื่องเหล่านี้เป็นเรื่องสำคัญในการบริหารจัดการเรื่องความปลอดภัย
ขอจบเพียงเท่านี้ รูปที่วาดอาจดูแปลกๆ นิดนึง ต้องขออภัย และขอบคุณที่อ่านจนจบ หวังว่าคงได้ประโยชน์บ้างไม่มากก็น้อย หากมีข้อผิดพลาดตรงไหนรบกวนช่วย comment บอกด้านล่างจะขอบพระคุณมากครับ