The Cloud Camp Week#08 (K8S Part3: Store Data)

สัปดาห์นี้ก็ Store Data ล้วนๆ

สำหรับ part ก่อนหน้า

Volumes

ก่อนที่จะมาเรื่องใหม่ต้องมา Recap ก่อนจาก Part 1 ตัว Pod มันเก็บข้อมูลได้นะ แต่จะอยู่ชั่วคราว (ephemeral) เท่านั้น ดังนั้นเราจะพบ non-trivial problems ของ App ได้ ดังนี้

  • ถ้า Pod ตาย Data ที่อยู่ในนั้นจะหายไปด้วย
  • ถ้า Pod นั้นมี Container 2 ตัว จะแชร์ Data ระหว่าง Container อย่างไร ?

default pod ไม่ได้แชร์ไฟล์กันระหว่าง container นะ

ดังนั้น Volume จะมาช่วยแก้ปัญหาพวกนี้ แต่ควรมาเข้าใจตัว YAML Pattern กันก่อน

YAML Pattern

  • Pod Level เชื่อม Volume
 volumes:
  - name: cache-volume  #ชื่อ Volume 
    hostPath:           #ชนิดของ Volume ในที่นี้จะเป็น hostPath       
      #ส่วนข้างล่างนี้จะเป็น Config ของแต่ละ Type ที่ทำได้ตาม Spec เช่น hostPath มี path/type
      #หรือ emptyDir จะมี เช่น sizeLimit / medium
      #หรือ persistentVolumeClaim เช่น claimName
      path: /tmp
      type: Directory
  • Container Level เอา Volume จาก Pod มาใช้งาน
   volumeMounts:
    - mountPath: /tmp/cache    #Path ใน Container 
      name: cache-volume       #ชื่อ Volume ที่ประกาศไว้ใน Pod Level

Volume Type ที่เด่นๆ ดังนี้

- hostPath

แบบนี้จะคล้ายกับตัว docker volume โดย Volume แบบ hostPath จะเอา path ของ node ที่ pod Deploy อยู่มา map กับ pod

apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: mcss
  name: mcss
spec:
  containers:
  - image: nginx
    name: mcss
    resources: {}
    volumeMounts:
    - mountPath: /tmp/cache
      name: cache-volume
  - image: pingkunga/service-test
    name: c2
    volumeMounts:
    - mountPath: /tmp/cache
      name: cache-volume
  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
  - name: cache-volume
    hostPath:
      # directory location on host
      path: /tmp
      # this field is optional
      type: Directory
status: {}
  • Key Config
    - Path: ที่อยู่จริงๆบน Node ที่เราจะเชื่อมเข้ามา
    - Type: มันจะมีหลายแบบต้องระวังการเรียกใช้
    >> FileOrCreate: มันจะไม่สร้าง Directory มาให้นะ
    >> File: ต้องมีไฟล์นั้นจริงๆ
    >> DirectoryOrCreate / Directory ล้อกันมา และะมีแบบอื่นๆตาม Link
    Ref: Volumes-HostPath | Kubernetes
  • Sample Use-Case
    - ถ้าใช้ docker อาจจะต้องเข้าไป Config Path /var/lib/docker
    - เปิดช่องให้ใน Tools monitor เข้ามาดูข้อมูล อย่างตัว google/cadvisor: Analyzes resource usage and performance characteristics of running containers. (github.com)
    - สร้าง Path หลอก ในกรณีที่ Container มันต้องการใช้ตอน Start
  • Limitation
    - ถ้า pod ตาย แล้ว kube-scheduler ไปสร้าง pod ใหม่ที่ node data จะหาย อารมณ์ประมาณวิญญาณเข้าผิดร่าง
    แก้โดยใช้ตัว network path มา map แทน nfs
    - Security Issue: ถ้าจำเป้นต้องใช้ hostPath ให้ Scope ที่เล็ก file / directory และ readOnly access ด้วย //มันแก้จาก node มาใส่ได้
- emptyDir

สร้าง Volume เปล่าๆ ผูกกับ pod (เกิด และตายไปด้วยกัน) ไม่จำเป็นต้องใช้ Path ของ Node แบบ hostPath อาจจะใช้ memory มา config ก็ได้ โดยจะมาแก้ปัญหาการแชร์ Data ระหว่าง Container ภายใน Pod
Note: emptyDir จัดการโดย kubelet

emptyDir can read and write the same files / mounted at the same or different paths in each container

  • YAML
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: mcss
  name: mcss
spec:
  containers:
  - image: nginx
    name: mcss
    resources: {}
    volumeMounts:
    - mountPath: /tmp/cache
      name: cache-volume
  - image: pingkunga/service-test
    name: c2
    volumeMounts:
    - mountPath: /tmp/cache
      name: cache-volume
  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
  - name: cache-volume
    emptyDir:
      sizeLimit: 500Mi
  • Sample Use-Case
    - scratch space เป็นพื้นที่ทด เช่น
    >> merge sort Json เยอะๆ
    >> พักงานที่ consume จาก queue เข้ามาก่อนประมวลผลต่อ
    - checkpointing กรณีที่เรามีงานขนาดใหญ่ และ compute intensive (ใช้ CPU) เยอะ ถ้าพังระหว่างทางเสียเวลา Process ใหม่อีก เลยเอาตัว emptyDir มาเป็นกระดาษทด ทำไปแล้วเท่าไหร่ ในกรณีที่ pod มันค้าง แล้วตายไป อีก pod ที่จับอยู่ จะได้เอางานมาทำต่อจากจุดล่าสุด ไม่ต้อง process ใหม่ทั้งหมด แต่ตัว app ต้องปรับด้วยนะ
    - local content cache - cache แบบ ถ้าในพวก WordPress ทำแบบ WP Super Cache ทำ เอามาเป็นพวก Static HTML แต่ที่จะไป Render PHP ใหม่ทุกรอบ

พอเห็น Sample Use-Case เข้าใจเลยว่าทำไมใช้ memory

 volumes:
  - name: cache-volume
    emptyDir:
      sizeLimit: 500Mi
      medium: Memory
- downwardAPI

เอาข้อมูล Pod Information มาใช้งาน เช่น เอามาลง Log หรือ เขียนเป็น File

  • Key Config
metadata:
  creationTimestamp: null
  #0. Initial Metadata พวก labels / annotations / namespace  
  labels:                        
    run: nginx
    release: stable
    environment: dev
    tier: frontend
...
   downwardAPI:
      items:
      - path: "namespace.txt"                  #1-2 อ่านมาแล้ว Save เป็นไฟล์ชื่อ namespace.txt
        fieldRef:
         fieldPath: metadata.labels            #1-1 อ่านจากอะไร อันนี้จะมีใน doc จะอ่านทั้งหมด
      - path: "appinfo/release"                #2-2 อ่านมาแล้ว Save ไฟล์ชื่อ release อยู่ appinfo   
        fieldRef:
         fieldPath: metadata.labels['release'] #2-1.อ่านเฉพาะ Label ชื่อ release    
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx
    release: stable
    environment: prod
    tier: backend
    app.kubernetes.io/name: "inv-app"
    app.kubernetes.io/version: "8.8.12.0"
  annotations:
    apf.kubernetes.io/autoupdate-spec: "true"
    applyset.kubernetes.io/additional-namespaces: "inv-app,reg-app"
    applyset.kubernetes.io/tooling: "kubectl/v1.28"
  name: nginxping
  namespace: appbackend
spec:
  containers:
  - image: nginx
    name: nginx
    resources: {}
    volumeMounts:
    - name: podinfo
      mountPath: /opt/app/labels
  dnsPolicy: ClusterFirst
  restartPolicy: Always
  volumes:
  - name: podinfo
    downwardAPI:
      items:
      - path: "release"
        fieldRef:
         fieldPath: metadata.labels['release']
      - path: "environment"
        fieldRef:
         fieldPath: metadata.labels['environment']
      - path: "tier"
        fieldRef:
         fieldPath: metadata.labels['tier']
      - path: "app.kubernetes.io/name"
        fieldRef:
         fieldPath: metadata.labels['app.kubernetes.io/name']
      - path: "app.kubernetes.io/version"
        fieldRef:
         fieldPath: metadata.labels['app.kubernetes.io/version']
      - path: "apf.kubernetes.io/autoupdate-spec"
        fieldRef:
         fieldPath: metadata.annotations['apf.kubernetes.io/autoupdate-spec']
      - path: "applyset.kubernetes.io/additional-namespaces"
        fieldRef:
         fieldPath: metadata.annotations['applyset.kubernetes.io/additional-namespaces']
      - path: "namespace"
        fieldRef:
         fieldPath: metadata.namespace
status: {}

Ref: Downward API | Kubernetes

Ephemeral Volumes

Volume ที่ใช้เก็บข้อมูลชั่วคราว แก้เรื่อง Data หาย หรือ ทำ Cache (จริงๆมองอีก View ของการแบ่ง Volume)

  • emptyDir: empty at Pod startup,
  • configMap, downwardAPI, secret: inject different kinds of Kubernetes data into a Pod
  • CSI ephemeral volumes: เป็น Volume ค่ายๆอื่นๆ ของที่พัฒนาตามมาตรฐาน CSI เช่น พวก Volume ของ Cloud ค่ายต่างๆ
  • generic ephemeral volumes พวก PV/ PVC

Persistent Volume Family

- Storage Class (Class)

เอาไว้ทำ disk profile ก่อนนำมาใช้งานใน PV / PVC อาทิ เช่น SPEED (MEM / SSD / HDD / NETWORK) หรือ พวก Backup Policy เป็นต้น

  • Key Property
    - provisioner บอกว่าจะใช้ของค่ายไหน
    Internal ของ K8S มีมาให้เองจะขึ้นต้นด้วย kubernetes.io เช่น Local (no-provisioner)
    External ของ Cloud หรือ On-Premise ค่ายๆ อื่นๆ เช่น rancher.io/local-path
    Note: หลัง K8S 1.28 Storage ของ Cloud จากเดิมที่ Internal จะแยกเป็น External คุยผ่าน CSI Driver
    - parameters ล้อตาม provisioner ที่เลือก
    - reclaimPolicy retain(เก็บไว้) vs delete
    - allowVolumeExpansion - dynamically provisioned มีผลกับพวก pv / pvc ที่ on top อีกที
    - mountOptions
    - volumeBindingMode unset, Immediate, WaitForFirstConsumer (ที่เห็นใน Lab อันนี้)
  • Sample YAML
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
  • List Storage Class ใช้กรณีทีเราไม่ได้ลง K8S เอง จะได้มา List Storage Class ที่มี Profile ไว้ สำหรับตอนสร้าง PV / PVC ต่อไป
kubectl get StorageClass

#Sample Result 
NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  16d

Ref: Storage Classes | Kubernetes

- Persistent Volume (PV)

Persistent Volume เป็นการจองพื้นที่ตาม Profile ที่กำหนดไว้ใน Storage Class

  • Key Config
    - capacity > storage บอกขนาดของ Disk ที่จองหน่วย Gi
    - volumeMode
    >> FileSystem - พวก Folder ที่เราคุ้นเคย
    >> Blob - เห็นเนื่อที่เท่าๆกัน ถ้าดูจะได้ Binary
    - storageClassName - บอก Profile ของ Disk
    NOTE: สังเกตุ YAML อันถัดไปมันจะมี HostPath ด้วย ใน doc k8s for test
    Ref: Configure a Pod to Use a PersistentVolume for Storage | Kubernetes
    - accessModes
    - persistentVolumeReclaimPolicy
    >> Delete - Pod ตายลบเลย
    >> Retain - เก็บไว้ แต่ต้องใช้สร้างใหม่ต้องเอา Volume ใหม่มาจับ
    >> Recycle - เอาออกในอนาคต แทนด้วย Dynamic Provisioner
    accessModes
    >> ReadWriteOnce - อ่าน + เขียน จาก Node เดียว
    >> ReadOnlyMany - อ่านจากหลายๆ Node ได้
    >> ReadWriteMany - อ่าน + เขียน จากหลายๆ Node ได้
    >> ReadWriteOncePod - ensuring that only one pod can write to the volume at a time
    Ref: Kubernetes 1.27: Single Pod Access Mode for PersistentVolumes Graduates to Beta | Kubernetes
  • Sample YAML
apiVersion: v1
kind: PersistentVolume
metadata:
  name: ds-pv
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  #hostPath for test
  hostPath:
    path: /tmp
  storageClassName: local-path
  persistentVolumeReclaimPolicy: Recycle
- Persistent Volume Claim (PVC)

Persistent Volume Claim (PVC) - เป็นยึดพื้นที่ (Claim) จาก PV เพื่อใช้งาน (a request for storage by a user)

  • Key Config
    - resources - บอกพื้นที่ที่จะใช้ resources.requests.storage
    - selector - เลือก Claims จากการระบุตรงๆ หรือ expression (ตาม Selector)
    > volumeName = มาจาก PV / Volume ตามชื่อตรงๆ
    > selector จับตามเงื่อนไขจาก matchLabels / matchExpressions
    Note: selector ตรงนี้อาจจะเขียนจับตาม Region / Zone จะได้ใน Data ใน Cloud เดียวกัน ใน DC เดียวกัน
  • Sample YAML ตอนสร้างเสร็จ ต้องได้สถานะ Bind
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ds-pvc
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 512Mi
  storageClassName: local-path
  volumeName: ds-pv
  #selector:
  #  matchLabels:
  #    release: "customer"
  #  matchExpressions:
  #  - {key: environment, operator: In, values: [dev]}
  • ตอนเอาไปใช้งานเอา PVC ที่เตรียมไว้ใน มาอ้างอิง Volume ของ Pod (persistentVolumeClaim.claimName)
apiVersion: v1
kind: Pod
metadata:
  name: task-pv-pod
spec:
  containers:
    - name: task-pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: ds-pvc-storage
  volumes:
    - name: ds-pvc-storage
      persistentVolumeClaim:
        claimName: ds-pvc

พวกลำดับของ SC > PV > PVC เหมือนจะข้ามได้จาก SC > PVC เลย
Ref: kubernetes - Can we get Persistent Volume with only PVC (without PV) in k8s? - Stack Overflow

Lifecycle of a volume and claim

เด่น Provisioning / Binding / Using / Reclaiming

Ref: Persistent Volumes | Kubernetes

Inject Data Into App

Command and Arguments - การกำหนดคำสั่งจากตัว declarative file เพื่อมา override Command ของ container image ที่มีให้ ใน dockerfile ก็คือ ตัว EntryPoint

apiVersion: v1
kind: Pod
metadata:
  name: command-demo
  labels:
    purpose: demonstrate-command
spec:
  containers:
  - name: command-demo-container
    image: debian
    command: ["printenv"]                   #Overide container image  ด้วยคำสั่ง printenv
    args: ["HOSTNAME", "KUBERNETES_PORT"]   #ส่ง Param ที่ printenv ต้องการ
  restartPolicy: OnFailure

Environment Variables - Inject ตัวแปร Environment Variables เข้าไปใน Container โดยมี 2 รูปแบบ

  • env ประกาศใน Declarative File
apiVersion: v1
kind: Pod
metadata:
  name: envar-demo
  labels:
    purpose: demonstrate-envars
spec:
  containers:
  - name: envar-demo-container
    image: gcr.io/google-samples/node-hello:1.0
    env:
    - name: DEMO_GREETING
      value: "Hello from the environment"
    - name: DEMO_FAREWELL
      value: "Such a sweet sorrow"
#ตอนตรวจก็
  • envFrom ดึงจาก ConfigMap จำได้แม่น 55 คนอื่น Mount เข้ามาเราโยนมาใส่ แล้วเขียนไฟล์ใหม่ 555

use-case

apiVersion: v1
kind: Pod
metadata:
  name: dependent-envars-demo
spec:
  containers:
    - name: dependent-envars-demo
      args:
        - while true; do echo -en '\n'; printf UNCHANGED_REFERENCE=$UNCHANGED_REFERENCE'\n'; printf SERVICE_ADDRESS=$SERVICE_ADDRESS'\n';printf ESCAPED_REFERENCE=$ESCAPED_REFERENCE'\n'; sleep 30; done;
      command:
        - sh
        - -c
      image: busybox:1.28
      env:
        - name: SERVICE_PORT
          value: "80"
        - name: SERVICE_IP
          value: "172.17.0.1"
        - name: UNCHANGED_REFERENCE
          value: "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
        - name: PROTOCOL
          value: "https"
        - name: SERVICE_ADDRESS
          value: "$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
        - name: ESCAPED_REFERENCE
          value: "$$(PROTOCOL)://$(SERVICE_IP):$(SERVICE_PORT)"
UNCHANGED_REFERENCE=$(PROTOCOL)://172.17.0.1:80
SERVICE_ADDRESS=https://172.17.0.1:80
ESCAPED_REFERENCE=$(PROTOCOL)://172.17.0.1:80
apiVersion: v1
kind: Pod
metadata:
  name: dapi-envars-fieldref
spec:
  containers:
    - name: test-container
      image: registry.k8s.io/busybox
      command: [ "sh", "-c"]
      args:
      - while true; do
          echo -en '\n';
          printenv MY_NODE_NAME MY_POD_NAME;
          printenv MY_POD_IP;
          sleep 10;
        done;
      env:
        - name: MY_NODE_NAME
          valueFrom:
            fieldRef:
              fieldPath: spec.nodeName
        - name: MY_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: MY_POD_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
  restartPolicy: Never

ConfigMap

เก็บ Config ที่ใช้งานบ่อยๆ หรือใช้กันหลายที่ จะได้จัดการจากจุดเดียว แต่ขนาดต้องไม่เกิน 1MB อยู่ในรูปแบบ key & value

  • Command
kubectl create configmap config --from-literal=db_user=admin --from-literal=db_password=test

#Get the config map as yaml file and try to understand
kubectl get configmap config -o yaml

#from the file
kubectl create configmap configenv --from-env-file=config.env

kubectl create configmap configfile --from-file=config.txt

—from-file คือใส่ได้ทั้ง file ที่นามสกุลอื่นๆ ได้เลย แต่ถ้าเป็น -env ใส่ .env เข้าไป มันจะ convert เป้น env ใน configmap ให้เลย
  • Declarative File
apiVersion: v1
kind: ConfigMap
metadata:
  name: invconfigmap
  namespace: default
data:
  config_dev.json: |
    {
        "current_env" : "dev",
        "Directory_Type" : "folder",
        "report_location": "reports"
    }
  config_prod.json: |
    {
      "current_env" : "prod",
      "Directory_Type" : "folder",
      "report_location": "reports"
    }

Sample Use-Case

  • ENV - Override Env ใช้กรณีที่ Key เปลี่ยน กรณีนี้ Image แก้จาก ASPNETCORE_ENVIRONMENT >> ASPNETCORE_ENV ถ้าแก้จาก ConfigMap Container ที่ Run Image Version ก่อน อาจจะตุยได้
...
  env:
  - name: ASPNETCORE_ENV
    valueFrom:
      configMapKeyRef:
        name: options                  #ชิ้่อ Config Map
        key: ASPNETCORE_ENVIRONMENT    #key ใน Config Map
  • ENV:KeyRef - Initial Env จาก Config Map ทับทั้งชุด
kubectl create configmap config --from-literal=db_user=admin --from-literal=db_password=test

#Get the config map as yaml file and try to understand
kubectl get configmap config -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    run: nginx2
  name: nginx2
spec:
  containers:
  - image: nginx
    name: nginx2
    resources: {}
    envFrom: 
    - configMapRef: 
        name: config 
  dnsPolicy: ClusterFirst
  restartPolicy: Always
status: {}
  • Volume - All Key as File > Mount เข้าไป
apiVersion: apps/v1
kind: Deployment
metadata:
  name: invapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: invapp
  template:
    metadata:
      labels:
        app: invapp
    spec:
      containers:
        - name: invapp
          image: invapp:latest
          volumeMounts:
          - mountPath: /etc/config
            name: json-configmap-volume
          resources:
            requests:
              cpu: 100m
              memory: 1Gi
      #Mount configmap เข้าไป
      volumes:
        - name: json-configmap-volume
          configMap:
            name: invconfigmap      #<-- ต้องประกาศไว้ก่อน ถ้าใน Blog ตัวอย่างอยู่ใน Declarative File

Note ถ้ามัน pending นานให้ describe ดูได้ อาจจะติดเช่น หา ConfigMap ไม่เจอ

Secret

มัน คือ ConfigMap ที่ใช้เก็บข้อมูลสำคัญ โดยจะ Encrypt อยู่ในรูปแบบ Base64 แต่บน K8S มี Guideline ใช้ Secret ดังนี้

  • Encrypt At Rest (ปล ข้อมูลใน ETCD ไม่ Encrypt)
  • RBAC + least privilege
  • Restrict Container Access เช่น การป้องกัน Shell / Distroless Image
  • ใช้ External Service Provide เช่น Azure KeyVault / HashiCorp Vault

ตัว Secret พอเข้า env ของ app ไปเดี๋ยวมัน decrypt ออกให้

  • Command
kubectl create secret generic pingsecret --from-literal=db_user=admin --from-literal=db_password=test -n group-1

#Get the secret as yaml file and try to understand
kubectl get secrets pingsecret -o yaml -n group-1
  • Declarative File
apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm

Sample Use-Case - เปลี่ยนจาก configMapRef > secretKeyRef

  • ENV - Override Env
apiVersion: v1
kind: Pod
metadata:
  name: env-single-secret
spec:
  containers:
  - name: envars-test-container
    image: nginx
    env:
    - name: SECRET_USERNAME       #APP ใช้
      valueFrom:
        secretKeyRef:
          name: backend-user      #ชื่อ Config
          key: backend-username   #ชื่อ key
  • ENV:KeyRef - Initial Env จาก Secret Map ทับทั้งชุด
apiVersion: apps/v1
kind: Deployment
metadata:
  name: invapp
  labels:
    app: invapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: invapp
  template:
    metadata:
      labels:
        app: invapp
    spec:
      containers:
        - name: app
          image: invapp:lastest
          imagePullPolicy: Always
          ports:
            - containerPort: 80
          envFrom:
          - configMapRef:
              name: env-configmap
          - secretRef:
              name: my-secret
  • Volume - All Key as File > Mount เข้าไป
apiVersion: v1
kind: Pod
metadata:
 name: secret-test-pod
spec:
 containers:
 - name: test-container
   image: nginx
   volumeMounts:
    - name: secret-volume
      mountPath: /etc/secret-volume
      readOnly: true
 volumes:
 - name: secret-volume
   secret:
   secretName: my-secret

Note: ใน yaml - คือ List มีลำดับ

Q: ถ้า configmap / secret เปลี่ยน ให้ restart เพื่อ load ค่าใหม่
A: witness controller ปรับ App Live reload configuration

สรุปก่อน

การเก็บข้อมูลใน K8S จะแบ่งกลุ่มได้ ดังนี้

Storage Data ที่เรียนมา
- Volume
-> hostPath - แปะลง node path ระวังเรื่อง pod ตายวิญญาณอาจจะเข้าผิดร่าง
-> emptyDir - ตายไปของหลาย ทำงานชั่วคราว
-> downwardAPI - ดึงข้อมูลของ Pod
-> Persistent Volume - เก็บถาวร  PVC / PV / Storage Class
-> อื่่นๆ
- Inject Data to App: Env + Command & Args
- ConfigMap
- Secret 

วันเสาร์มีมาทำ LAB ตอนเช้าพอทำได้ ตอนบ่ายทำได้แหละ แต่ท่าไม่เหมือนชาวบ้าน คนอื่น mount secret / configmap เป็น volume ผม inject เข้า app แล้ว print ออกมา 555

Reference


Discover more from naiwaen@DebuggingSoft

Subscribe to get the latest posts to your email.