LFS258 [8/15]: Kubernetes Volumes and Data
Tags: kubernetes, devops, nginx
Published: 2020-03-23 10:34:49

# TL;DR

- [Overview](#Overview): Volume มองง่ายๆ มันคือ hard disk ของ container มีทั้งแบบที่ถ้า Pods ตาย แล้ว data หาย (Ephemeral) และ data ไม่หาย (Persistent) โดย Kubernetes นั้น support storage หลายชนิด ที่เป็นแบบนั้นเพราะ มี FlexVolume และ CSI ซึ่งทำให้ vendor ต่างๆ สามารถพัฒนา library ที่ support product ของตนเองแล้วนำมาต่อกับ Kubernetes ได้เลย
- [Introducing Volumes](#Introducing-Volumes): Kubernetes mount Volume ในระดับ Pods แสดงว่า container ต่างๆ ที่อยู่ใน Pods จะเห็น Volume เดียวกัน ขึ้นอยู่กับ container จะเลือกเอาอะไรไปใช้ การ access volume ขึ้นอยู่กับ Permission ที่เรากำหนดไว้ โดย Permission จะเป็นระดับ Node เช่น `ReadWriteOnce`, `ReadOnlyMany` และ `ReadWriteMany`
- [Volume Types](#Volume-Types): Kubernetes support backend storage ได้หลากหลายชนิด เรียกชนิดเหล่านั้นว่า `StorageClass` โดย `StorageClass` นี้เองจะเป็นคนไปคุยกับ backend storage เพื่อทำงานต่างๆ ให้
- [Volume Spec](#Volume-Spec): แสดงการใช้งาน `emptyDir` ซึ่งเป็น `StorageClass` ที่เป็น Ephemeral Storage
- [Share Volumes](#Share-Volumes): แสดงการใช้ 2 containers ที่อยู่ใน Pod เดียวกัน mount Volume เดียวกัน โดยจะเห็นว่าทั้ง 2 containers เห็น data เดียวกัน
- [Persistence Volumes and Volume Claims](#Persistence-Volumes-and-Volume-Claims): ในการใช้งาน Persistent Volume ต้องสร้าง PV ก่อน จากนั้นสร้าง PVC เพื่อจองพื้นที่จาก PV มาใช้ จากนั้นจึงจะสามารถ attach ไปให้ Pods ใช้งานได้
- [Secrets](#Secrets): เป็นวิธีช่วยให้ Pods การเข้าถึง data ได้ โดย Secret จะถูก encode แบบ base64 โดย Pods สามารถใช้งาน Secret ได้ทั้งแบบ Volume และ Environment Variable
- [ConfigMap](#ConfigMap): เป็นวิธีช่วยให้ Pods การเข้าถึง data ได้ โดย ConfigMap เป็น plain textโดย Pods สามารถใช้งาน ConfigMap ได้ทั้งแบบ Volume และ Environment Variable

<a name="Overview"></a>

---

# Overview

Volume ก็คือ storage นั่นเอง มองง่ายๆ มันคือ hard disk ของ container โดยทั่วไป ถ้า container ตายหรือ restart data ในนั้นก็จะหายไป ใน Kubernetes volume จะผูกอยู่กับ Pods ไม่ใช้ container ภายใน โดยถ้า Pods มี หลาย container อยู่ภายใน container นะเห็น volume และ data เดียวกัน

ถ้าไม่อยากให้ data หายสามารถใช้ **Persistent Volumes** ได้ ซึ่ง Pods จะต้องทำการ claim volume ผ่านทาง **Persistent Volumes Claim**

การเขียนหรืออ่านอะไรใน volume จะต้องทำผ่าน backend storage ซึ่งใน v1.17 รองรับถึง [28 ชนิด](https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes) ไม่ว่าจะเป็น Ceph, NFS หรือ storage ของ cloud providers เจ้าต่างฟๆ ซึ่งอาจมีการ setting ต่างกันตามชนิดของ backend

Backend storage ของ kubernetes มี 2 แบบ คือ 
- **in-tree**: ส่วนใหญ่เป็นมากับ kubernetes มาอย่างยาวนาน โดย code จะถูก built, linked, compiled และ shipped มากับ core ของ Kubernetes เลย Backend ประเภทนี้ค่อยๆ ถูก migrate ไปเป็น **out-of-tree**
- **out-of-tree**: หลังจากการมาของ [FlexVolume](https://kubernetes.io/docs/concepts/storage/volumes/#flexVolume) ใน v1.2 และ [Container Storage Interface (CSI)](https://kubernetes.io/docs/concepts/storage/volumes/#csi) ใน v1.9 ทำให้ code ของ backend storage ไม่ต้องรวมอยู่ใน core ของ Kubernetes อีกต่อไป โดย เจ้าของ product สามารถ develop เองแล้วค่อยมาต่อกับ kubernetes ด้วยวิธีนี้ทำให้ core ของ kubernetes เบาขึ้น และ เจ้าของ product สามารถ fix bug หรือ เพิ่ม feature ได้เองโดยไม่ต้องรอมาก merge code กับ kubernets การทำงานก็จะเร็วขึ้น 

นอกจากการใช้ volume ที่กล่าวข้างต้น ยังมีอีก 2 ทางในการนำ data เข้าไปยัง Pods นั่นคือ 
- **Secret**: เป็นการส่งข้อมูลที่ถูก encode แล้ว เข้าไปใน pods เช่น SSH Key หรือ HTTPS certification
- **ConfigMap**: เป็นการส่งข้อมูลที่ไม่ถูก encode เข้าไปใน pods เช่น configuration file ต่างๆ

<a name="Introducing-Volumes"></a>

---

# Introducing Volumes

![Pods-Volumes](https://dev-to-uploads.s3.amazonaws.com/i/fab39mncp3y6vgi6kdbf.png)

ใน 1 Pods สามารถ mount ได้หลาย volume และสามารถคละ backend type ได้ และเนื่องจาก volume ถูก present ในระดับ Pod ดังนั้น ต่าง container กันสามารถเห็น volume เดียวกันได้ อาจใช้ท่านี้เพื่อใช้ในการทำ container-to-container communition 

นอกจากนี้ Volume ยังสามารถถูกเข้าถึงได้จากหลายๆ pods พร้อมๆ กัน และสามารถให้สิทธิ์ที่แตกต่างกันได้ แต่เนื่องจาก volume ไม่มี concurrent checking ดังนั้น อาจเกิดปัญหา file corrupt หรือ locking ได้

ในการกำหนด access mode ต้องทำ 2 ทางคือ ที่ volume เอง และ ตอนที่ Pods request claim เข้ามา โดย request จะต้องไม่มากกว่าสิทธิ์ที่ volume กำหนดไว้ โดย access mode มี ดังนี้
- **ReadWriteOnce**: อนุญาติให้อ่านและเขียนได้จาก node เดียวเท่านั้น (2 pods ที่อยู่ node เดียวกัน สามารถ read/write ได้พร้อมกัน แต่ pods ที่อยู่ต่าง node read/write ไม่ได้ จะได้ error `FailedAttachVolume`)
- **ReadOnlyMany**: อนุญาติให้อ่านอย่างเดียว จากหลายๆ node พร้อมๆ กัน
- **ReadWriteMany**: อนุญาติให้อ่านและเขียนได้จากหลายๆ node พร้อมๆ กัน

kubernetes จะจัดกลุ่มของ volume ที่มี permission เหมือนกันไว้ และ sort volume size จาก น้อยไปหามาก เมื่อมี request เข้ามาก็จะเลือก volume เหมาะสมที่สุดให้ไป 

เมื่อมีการ request volume 
- API server ของ kubernetes จะ request ขอ storage ไปยัง `StorageClass` plugin และ `StorageClass` plugin จะเป็นคนไปคุยกับ backend storage เอง
- kubelet จะ map raw devices กับ mount point ใน container แลัวทำเป็น symbolic link บน host node filesystem เพื่อให้ container ใช้งาน storage ได้

ถ้าเราไม่ระบุ `StorageClass` Kubernetes จะเลือก Storage อะไรก็ได้ที่เหมาะสมกับ size และ access mode ที่เราส่งไปกลับมาให้เรา

<a name="Volume-Types"></a>

---

# Volume Types

Kubernetes รองรับ Backend Storage ได้หลากหลาย บางอย่างใช้แค่ใน local บางอย่างต้องใช้งานผ่าน network ซึ่งแต่ละชนิดก็มีข้อดี ข้อเสีย แต่ต่างกันออกไป เรามาดูตัวอย่างกันซักหน่อย

- **GCEpersistentDisk**: เป็นการ mount disk GCE บน Google Cloud Platform เข้าไปใน Pods
- **awselasticblockstore**: เป็นการ mount disk EBS บน AWS เข้าไปใน Pods
- **emptyDir**: เป็นการ mount empty directory ให้กับ Pods โดย volume จะเกิดและตายไปพร้อมกับ Pod
- **hostPath**: เป็นการ mount resource จาก host เช่น file หรือ directoty ให้กับ Pods ซึ่งต้องมี resource ดังกล่าวอยู่ที่ host ก่อน ยกเว้น ถ้าเราใช้ option `DirectoryOrCreate` หรือ `FileOrCreate` ที่จะสร้าง resource ให้ Pods หากไม่มี
- **nfs**: เป็นการ mount NFS (Network File System) ให้กับ Pods (เหมาะกับ multiple readers)
- **iscsi**: เป็นการ mount iSCSI (SCSI over IP) ให้กับ Pods (เหมาะกับ multiple readers)
- **rbd**: เป็นการ mount Rados Block Device ให้กับ Pods (เหมาะกับ multiple writers)
- **cephfs**: เป็นการ mount CephFS volume ให้กับ Pods (เหมาะกับ multiple writers)
- **glusterfs**: เป็นการ mount  Glusterfs (an open source networked filesystem) ให้กับ Pods (เหมาะกับ multiple writers)

สามารถดู Backend Storage เพิ่มเติมได้จาก [Official Doc](https://kubernetes.io/docs/concepts/storage/volumes/)

<a name="Volume-Spec"></a>

---

# Volume Spec

ตัวอย่างการ mount volume ง่ายๆ คือ **emptyDir** โดย `emptyDir` คือ การสร้าง directory ภายใน container นั่นเอง ไม่ได้ไป mount ที่ไหน การเขียน data จะอยู่ใน shared container space ทำให้ไม่ persistence และ หายไปพร้อมกับการตายของ Pod

``` yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox
    name: busy
    command:
      - sleep
      - "3600"
    volumeMounts:
    - mountPath: /scratch
      name: scratch-volume
  volumes:
  - name: scratch-volume
    emptyDir: {}
```

YAML file ข้างต้น เป็นการสร้าง Pod ที่มี 1 container และมี volume ชื่อ scratch-volume mount ไว้ที /scratch ภายใน container 

สามารถทดสอบได้ดังนี้ 

``` bash
$ cat > busybox-emptyDir.yml << EOF
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox
    name: busy
    command:
      - sleep
      - "3600"
    volumeMounts:
    - mountPath: /scratch
      name: scratch-volume
  volumes:
  - name: scratch-volume
    emptyDir: {}
EOF
$ kubectl apply -f busybox-emptyDir.yml 
pod/busybox created
$ kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
busybox   1/1     Running   0          9s
$ kubectl exec -it busybox -- sh
/ # df -h /scratch
Filesystem                Size      Used Available Use% Mounted on
/dev/mapper/rhel-var     60.0G      2.5G     57.5G   4% /scratch
/ # exit
$ kubectl delete -f busybox-emptyDir.yml 
pod "busybox" deleted
```

<a name="Share-Volumes"></a>

---

# Share Volumes

จากที่เรารู้แล้วว่า container ใน pod เดียวกัน จะ share volume กัน หัวข้อนี้จะมาลงมือทำกันดู ก่อนอื่นมาดู YAML กันก่อน

``` yaml
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox
    name: busy
    command:
      - sleep
      - "3600"
    volumeMounts:
    - mountPath: /busy
      name: test
  - image: busybox
    name: box
    command:
      - sleep
      - "3600"
    volumeMounts:
    - mountPath: /box
      name: test
  volumes:
  - name: test
    emptyDir: {}
```

จาก YAML file ข้างต้น ใน pod มี 2 containers ชื่อ busy และ box โดย แต่ละ container mount volume ชื่อ test ไว้ที่ path /busy และ /box ตามลำดับ เดี๋ยวเราจะลองเขียนอ่าน file ดูกันว่ามันเห็นเหมือนกันไหม

``` bash
$ cat > busybox-sharedVolume.yml << EOF
apiVersion: v1
kind: Pod
metadata:
  name: busybox
  namespace: default
spec:
  containers:
  - image: busybox
    name: busy
    command:
      - sleep
      - "3600"
    volumeMounts:
    - mountPath: /busy
      name: test
  - image: busybox
    name: box
    command:
      - sleep
      - "3600"
    volumeMounts:
    - mountPath: /box
      name: test
  volumes:
  - name: test
    emptyDir: {}
EOF
$ kubectl apply -f busybox-sharedVolume.yml
pod/busybox created 
$ kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
busybox   2/2     Running   0          16s
$ kubectl exec -it busybox -c box -- touch /box/foobar
$ kubectl exec -it busybox -c busy -- ls /busy/
foobar
$ kubectl delete -f busybox-sharedVolume.yml
pod "busybox" deleted
```

<a name="Persistence-Volumes-and-Volume-Claims"></a>

---

# Persistence Volumes and Volume Claims

Persistence Volumes (pv) คือ storage ที่ ถ้า pods ตาย data ก็ยังอยู่ การ assign volume ประเภทนี้ ให้กับ Pods จะต้อง define `PersistentVolume` ก่อน จากนี้ สร้าง `persistenctVolumeClaim` (pvc) เพื่อขอเฉือน volume มาให้ pod ใช้ จากนั้นจึงค่อย attach persistent volume ก้อนที่ claim มาให้กับ Pod  

Lifecycle ของ volume และ claim มีดังนี้

- **Provisioning**: เป็นการสร้าง PV โดยทำได้ทั้ง Static และ Dynamic
  - **Statics**: admin create PV ทิ้งไว้ รอให้ users มา claim ไปใช้
  - **Dynamic**: ถ้าไม่มี PV ไหน match กับ PVC เลย kubernetes จะ create PV ให้อันโนมัติ โดย cluster ต้องระบุ `DefaultStorageClass` ด้วย
- **Binding**: อาจเป็นช่วงที่กำลัง match PVC กับ PV หรือ PV รอให้ `StorageClass` provision PV ขึ้นมา
- **Using**: เป็นช่วงที่ Pods mount volume ไปใช้แล้ว
- **Releasing**: เป็นช่วงที่ Pods ส่งคำสั่งไปเลิกใช้ PVC และทำการ delete PVC เมื่อ PVC ถูกลบไปแล้ว data อาจยังอยู่หรือถูกลบขึ้นอยู่กับ `persistentVolumeReclaimPolicy`
- **Reclaiming**: เป็นช่วงหลังจากที่ PVC ถูก delete เรียบร้อย โดย มี 3 options คือ
  - **Retain**: ยังเก็บ PV ไว้อยู่ ให้ admin เอาไปจัดการเอง
  - **Delete**: ลบ PV และ Backend Storage ที่ allocate ให้ไปด้วยเลย
  - **Recycle**: (deprecated แล้ว) ลบข้อมูลใน PVC ด้วย (`rm -rf /thevolume/*`) จากนั้นก็พร้อมสำหรับการถูก claim ใหม่ 

PV นั้นไม่ได้อยู่ใน namespace ใด namespace หนึ่ง แต่ PVC จะอยู่ได้แค่ namespace เดียวเท่านั้น 

<u>ตัวอย่างการ create PV</u> ชื่อ task-pv-volume ซึ่งแบบ hostPath ขนาด 10 GB ใน mode ReadWriteOnce 

``` bash
$ cat > pv-volume.yaml << EOF
apiVersion: v1
kind: PersistentVolume
metadata:
  name: task-pv-volume
  labels:
    type: local
spec:
  storageClassName: manual
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"
EOF
$ kubectl apply -f pv-volume.yaml 
persistentvolume/task-pv-volume created
$ kubectl get pv task-pv-volume
NAME             CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
task-pv-volume   10Gi       RWO            Retain           Available           manual                  14s
```

<u>ตัวอย่างการ create PVC</u> ชื่อ task-pv-claim ซึ่ง เฉือนมาใช้ 3 GB ใน mode ReadWriteOnce

``` bash
$ cat > pv-claim.yaml << EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: task-pv-claim
spec:
  storageClassName: manual
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
EOF
$ kubectl apply -f pv-claim.yaml
persistentvolumeclaim/task-pv-claim created
$ kubectl get pv task-pv-volume
NAME             CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                   STORAGECLASS   REASON   AGE
task-pv-volume   10Gi       RWO            Retain           Bound    default/task-pv-claim   manual                  6m23s
$ kubectl get pvc task-pv-claim
NAME            STATUS   VOLUME           CAPACITY   ACCESS MODES   STORAGECLASS   AGE
task-pv-claim   Bound    task-pv-volume   10Gi       RWO            manual         75s
```

> จะเห็นว่า PVC เปลี่ยนจาก status "Available" เป็น "Bound"

<u>ตัวอย่างการ attch PVC ไปยัง Pods</u>

``` bash
# ที่ Master Node
$ cat > pv-pod.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
  name: task-pv-pod
spec:
  volumes:
    - name: task-pv-storage
      persistentVolumeClaim:
        claimName: task-pv-claim
  containers:
    - name: task-pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: task-pv-storage
EOF
$ kubectl apply -f pv-pod.yaml
pod/task-pv-pod created
$ kubectl get pod task-pv-pod -o wide
NAME          READY   STATUS    RESTARTS   AGE   IP              NODE                  NOMINATED NODE   READINESS GATES
task-pv-pod   1/1     Running   0          71s   192.168.32.51   kube-0003.novalocal   <none>           <none>

# login เข้าไปยังเครื่องที่ pods run อยู่ ในที่นี้คือ kube-0003.novalocal 
$ sudo sh -c "echo 'Hello from Kubernetes storage' > /mnt/data/index.html"
$ cat /mnt/data/index.html
Hello from Kubernetes storage

# กลับไปยัง Master Node
kubectl exec -it task-pv-pod -- /bin/bash
root@task-pv-pod:/# apt update
root@task-pv-pod:/# apt install curl
root@task-pv-pod:/# curl http://localhost/
Hello from Kubernetes storage
root@task-pv-pod:/# exit

# Clean up
$ kubectl delete pod task-pv-pod
pod "task-pv-pod" deleted
$ kubectl delete pvc task-pv-claim
persistentvolumeclaim "task-pv-claim" deleted
$ kubectl delete pv task-pv-volume
persistentvolume "task-pv-volume" deleted

# Login ไปยังเครื่องที่ สร้าง file ไว้ (kube-0003.novalocal)
$ sudo rm /mnt/data/index.html
$ sudo rmdir /mnt/data
```

<a name="Secrets"></a>

---

# Secrets

ถึงแม้ Pods จะสามารถเข้าถึง data ต่างๆ ด้วย volume แต่ก็มีบาง data ที่เราไม่อยากให้เห็นได้ด้วยตาเปล่า เช่น password หรือ certification จึงมี **Secret** เกิดขึ้นมา โดย Secret จะถูกเก็บในรูปแบบ base64-encoded (default) 

แต่เราสามารถ configure ให้มัน encrypt ได้โดยการสร้าง `EncryptionConfiguration` ด้วย key และ identity ที่เหมาะสม จากนั้นทำการเพิ่ม flag `--encryption-provider-config` ที่ใช่ระบุวิธีการ encrypt เช่น "aescdc" หรือ "ksm" ให้กับ`kube-apiserver` แล้วทำการ recreate Secret ใหม่ทั้งหมด

ในการเปลี่ยน key ต้องสร้าง key ใหม่ก่อน แล้วจึง restart `kube-apiserver` ทุกตัว จากนั้นจึง recreate Secret ใหม่ทั้งหมด

kubernetes ไม่ได้จำกับจำนวน Secret แต่ Secret ไม่ควรมีขนาดเกิน 1 MB โดย Secret จะถูกเก็บใน `tmpfs` ซึ่งเป็น memory ดังนั้นถ้ามีเยอะกินไปก็จะเปลือง memory ของ host

## เราสามารถ create Secret ได้ดังนี้

1. Create ด้วย `kubectl create secret` command จาก file

    ``` bash
    $ echo -n 'admin' > username.txt
    $ echo -n '1f2d1e2e67df' > password.txt
    $ kubectl create secret generic db-user-pass --from-file=./username.txt --from-file=./password.txt
    secret/db-user-pass created
    $ kubectl get secrets
    NAME                  TYPE                                  DATA   AGE
    db-user-pass          Opaque                                2      10s
    $ kubectl describe secret/db-user-pass
    Name:         db-user-pass
    Namespace:    default
    Labels:       <none>
    Annotations:  <none>

    Type:  Opaque

    Data
    ====
    password.txt:  12 bytes
    username.txt:  5 bytes
    ```

2. Create ด้วย `kubectl create secret` command จากการ pass argument

    ``` bash
    $ kubectl create secret generic dev-db-secret --from-literal=username=devuser --from-literal=password='S!B\*d$zDsb'
    secret/dev-db-secret created
    $ kubectl get secrets
    NAME                  TYPE                                  DATA   AGE
    dev-db-secret         Opaque                                2      4s
    $ kubectl describe secret/dev-db-secret
    Name:         dev-db-secret
    Namespace:    default
    Labels:       <none>
    Annotations:  <none>

    Type:  Opaque

    Data
    ====
    password:  11 bytes
    username:  7 bytes
    ```

3. Create ด้วย YAML file แบบ manual

    ``` bash
    $ echo -n 'admin' | base64
    YWRtaW4=
    $ echo -n '1f2d1e2e67df' | base64
    MWYyZDFlMmU2N2Rm
    $ cat > secret.yaml << EOF
    apiVersion: v1
    kind: Secret
    metadata:
    name: mysecret
    type: Opaque
    data:
    username: YWRtaW4=
    password: MWYyZDFlMmU2N2Rm
    EOF
    $ kubectl apply -f secret.yaml
    secret/mysecret created
    $ kubectl get secrets
    NAME                  TYPE                                  DATA   AGE
    mysecret              Opaque                                2      15s
    $ kubectl describe secret/mysecret
    Name:         mysecret
    Namespace:    default
    Labels:       <none>
    Annotations:  
    Type:         Opaque

    Data
    ====
    password:  12 bytes
    username:  5 bytes
    ```

4. Create ด้วย YAML file โดยใช้ `stringData`

    ``` bash
    $ cat > secret_config.yaml << EOF
    apiVersion: v1
    kind: Secret
    metadata:
    name: config.yaml
    type: Opaque
    stringData:
    config.yaml: |-
        apiUrl: "https://my.api.com/api/v1"
        username: "user"
        password: "password"
    EOF
    $ kubectl apply -f secret_config.yaml
    secret/config.yaml created
    $ kubectl get secrets
    NAME                  TYPE                                  DATA   AGE
    config.yaml           Opaque                                1      8s
    $ kubectl describe secret/config.yaml
    Name:         config.yaml
    Namespace:    default
    Labels:       <none>
    Annotations:  
    Type:         Opaque

    Data
    ====
    config.yaml:  73 bytes
    ```

## เราสามารถใช้งาน Secret ได้ดังนี้ 

1. ใช้ในรูปแบบของ file ใน Pods

    ``` bash
    $ kubectl describe secret/mysecret
    Name:         mysecret
    Namespace:    default
    Labels:       <none>
    Annotations:  
    Type:         Opaque

    Data
    ====
    password:  12 bytes
    username:  5 bytes
    $ cat > pod-with-mysecret-01.yaml << EOF
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: mypod
        image: redis
        volumeMounts:
        - name: foo
        mountPath: "/etc/foo"
        readOnly: true
    volumes:
    - name: foo
        secret:
        secretName: mysecret
    EOF
    $ kubectl apply -f pod-with-mysecret-01.yaml
    pod/mypod created
    $ kubectl exec -it mypod -- bash
    root@mypod:/data# ls -l /etc/foo
    total 0
    lrwxrwxrwx. 1 root root 15 Mar 20 15:30 password -> ..data/password
    lrwxrwxrwx. 1 root root 15 Mar 20 15:30 username -> ..data/username
    root@mypod:/data# cat /etc/foo/username
    admin
    root@mypod:/data# cat /etc/foo/password
    1f2d1e2e67df
    root@mypod:/data# exit
    $ kubectl delete -f pod-with-mysecret-01.yaml
    pod "mypod" deleted
    ```

    เราสามารถเลือก mount บาง file แล้วเปลี่ยนที่วาง และ ชื่อ file ด้วยได้ดังนี้ 

    ``` bash
    $ cat > pod-with-mysecret-02.yaml << EOF
    apiVersion: v1
    kind: Pod
    metadata:
    name: mypod
    spec:
    containers:
    - name: mypod
        image: redis
        volumeMounts:
        - name: foo
        mountPath: "/etc/foo"
        readOnly: true
    volumes:
    - name: foo
        secret:
        secretName: mysecret
        items:
        - key: username
          path: my-group/my-username
    EOF
    $ kubectl apply -f pod-with-mysecret-02.yaml
    pod/mypod created
    $ kubectl exec -it mypod -- bash
    root@mypod:/data# ls -l /etc/foo/my-group/my-username 
    -rw-r--r--. 1 root root 5 Mar 20 15:35 /etc/foo/my-group/my-username
    root@mypod:/data# cat /etc/foo/my-group/my-username
    admin
    root@mypod:/data# exit
    $ kubectl delete -f pod-with-mysecret-02.yaml
    pod "mypod" deleted
    ```

2. ใช้ในรูปแบบของ Environment Variable ใน Pods

``` bash
$ cat > pod-with-mysecret-03.yaml << EOF
apiVersion: v1
kind: Pod
metadata:
  name: secret-env-pod
spec:
  containers:
  - name: mycontainer
    image: redis
    env:
      - name: SECRET_USERNAME
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: username
      - name: SECRET_PASSWORD
        valueFrom:
          secretKeyRef:
            name: mysecret
            key: password
  restartPolicy: Never
EOF
$ kubectl apply -f pod-with-mysecret-03.yaml
pod/secret-env-pod created
$ kubectl exec -it secret-env-pod -- bash
root@secret-env-pod:/data# echo $SECRET_USERNAME
admin
root@secret-env-pod:/data# echo $SECRET_PASSWORD
1f2d1e2e67df
root@secret-env-pod:/data# exit
exit
$ kubectl delete -f pod-with-mysecret-03.yaml
pod "secret-env-pod" deleted
```

<a name="ConfigMap"></a>

---

# ConfigMap

ConfigMap เหมือน Secret เลย ยกเว้นแค่ไม่ได้ encode เราใช้ ConfigMap เพื่อแยก configuration file ออกมาจาก image ของ container เพื่อเวลาที่ต้องการแก้ไข configuration file จะได้ไม่ต้อง build image ใหม่ ซึ่งจะทำให้ version ของ image เพิ่มขึ้น โดยไม่ได้มีการแก้ไข code ด้วย

ConfigMap สามารถเก็บข้อมูลได้ทั้งในรูปแบบ key-value pair หรือ plain configuration file

## เราสามารถสร้าง ConfigMap ได้ดังนี้

1. สร้างจาก Directory

    ``` bash
    $ mkdir -p configure-pod-container/configmap/
    $ wget https://kubernetes.io/examples/configmap/game.properties -O configure-pod-container/configmap/game.properties
    $ wget https://kubernetes.io/examples/configmap/ui.properties -O configure-pod-container/configmap/ui.properties
    $ ls -l configure-pod-container/configmap/
    total 8
    -rw-rw-r--. 1 admin admin 157 Mar 20 22:57 game.properties
    -rw-rw-r--. 1 admin admin  83 Mar 20 22:57 ui.properties
    $ kubectl create configmap game-config --from-file=configure-pod-container/configmap/
    configmap/game-config created
    $ kubectl describe configmaps game-config
    Name:         game-config
    Namespace:    default
    Labels:       <none>
    Annotations:  <none>

    Data
    ====
    game.properties:
    ----
    enemies=aliens
    lives=3
    enemies.cheat=true
    enemies.cheat.level=noGoodRotten
    secret.code.passphrase=UUDDLRLRBABAS
    secret.code.allowed=true
    secret.code.lives=30
    ui.properties:
    ----
    color.good=purple
    color.bad=yellow
    allow.textmode=true
    how.nice.to.look=fairlyNice

    Events:  <none>
    $ kubectl get configmaps game-config -o yaml
    apiVersion: v1
    data:
    game.properties: |-
        enemies=aliens
        lives=3
        enemies.cheat=true
        enemies.cheat.level=noGoodRotten
        secret.code.passphrase=UUDDLRLRBABAS
        secret.code.allowed=true
        secret.code.lives=30
    ui.properties: |
        color.good=purple
        color.bad=yellow
        allow.textmode=true
        how.nice.to.look=fairlyNice
    kind: ConfigMap
    metadata:
    creationTimestamp: "2020-03-20T15:58:20Z"
    name: game-config
    namespace: default
    resourceVersion: "13587110"
    selfLink: /api/v1/namespaces/default/configmaps/game-config
    uid: efe9fc42-4c19-495f-a0f1-91b59dc77431
    ```

2. สร้างจาก File

    ``` bash
    $ kubectl create configmap game-config-3 --from-file=game-special-key=configure-pod-container/configmap/game.properties
    configmap/game-config-3 created
    $ kubectl describe configmaps game-config-3 
    Name:         game-config-3
    Namespace:    default
    Labels:       <none>
    Annotations:  <none>

    Data
    ====
    game-special-key:
    ----
    enemies=aliens
    lives=3
    enemies.cheat=true
    enemies.cheat.level=noGoodRotten
    secret.code.passphrase=UUDDLRLRBABAS
    secret.code.allowed=true
    secret.code.lives=30
    Events:  <none>
    $ kubectl get configmaps game-config-3 -o yaml
    apiVersion: v1
    data:
    game-special-key: |-
        enemies=aliens
        lives=3
        enemies.cheat=true
        enemies.cheat.level=noGoodRotten
        secret.code.passphrase=UUDDLRLRBABAS
        secret.code.allowed=true
        secret.code.lives=30
    kind: ConfigMap
    metadata:
    creationTimestamp: "2020-03-20T16:00:46Z"
    name: game-config-3
    namespace: default
    resourceVersion: "13587478"
    selfLink: /api/v1/namespaces/default/configmaps/game-config-3
    uid: 03e19330-0498-43f3-baf6-fce9501398df
    ```

3. สร้างจาก literal values

    ``` bash
    $ kubectl create configmap special-config --from-literal=special.how=very --from-literal=special.type=charm
    configmap/special-config created
    $ kubectl describe configmaps special-config
    Name:         special-config
    Namespace:    default
    Labels:       <none>
    Annotations:  <none>

    Data
    ====
    special.how:
    ----
    very
    special.type:
    ----
    charm
    Events:  <none>
    $ kubectl get configmaps special-config -o yaml
    apiVersion: v1
    data:
    special.how: very
    special.type: charm
    kind: ConfigMap
    metadata:
    creationTimestamp: "2020-03-20T16:03:16Z"
    name: special-config
    namespace: default
    resourceVersion: "13587836"
    selfLink: /api/v1/namespaces/default/configmaps/special-config
    uid: 7d5636bb-a677-46dd-8f49-a60f35999357
    ```

## เราสามารถใช้งาน ConfigMap ได้ดังนี้

1. Assign ConfigMap ไปยัง Environment Variable

    ``` bash
    $ kubectl create configmap special-config --from-literal=special.how=very
    configmap/special-config created
    $ cat > pod-single-configmap-env-variable.yaml << EOF
    apiVersion: v1
    kind: Pod
    metadata:
    name: dapi-test-pod
    spec:
    containers:
        - name: test-container
        image: redis
        env:
            - name: SPECIAL_LEVEL_KEY
            valueFrom:
                configMapKeyRef:
                name: special-config
                key: special.how
    restartPolicy: Never
    EOF
    $ kubectl create -f pod-single-configmap-env-variable.yaml
    pod/dapi-test-pod created
    $ kubectl exec -it dapi-test-pod -- bash
    root@dapi-test-pod:/data# echo $SPECIAL_LEVEL_KEY
    very
    root@dapi-test-pod:/data# exit
    exit
    $ kubectl delete -f pod-single-configmap-env-variable.yaml
    pod "dapi-test-pod" deleted
    ```

2. add ConfigMap เป็น data ใน Volume

    ``` bash
    $ cat > configmap-multikeys.yaml << EOF
    apiVersion: v1
    kind: ConfigMap
    metadata:
    name: special-config
    namespace: default
    data:
    SPECIAL_LEVEL: very
    SPECIAL_TYPE: charm
    EOF
    $ kubectl create -f configmap-multikeys.yaml
    configmap/special-config created
    $ cat > pod-configmap-volume.yaml << EOF
    apiVersion: v1
    kind: Pod
    metadata:
    name: dapi-test-pod
    spec:
    containers:
        - name: test-container
        image: redis
        volumeMounts:
        - name: config-volume
            mountPath: /etc/config
    volumes:
        - name: config-volume
        configMap:
            name: special-config
    restartPolicy: Never
    EOF
    $ kubectl create -f pod-configmap-volume.yaml
    pod/dapi-test-pod created
    $ kubectl exec -it dapi-test-pod -- bash
    root@dapi-test-pod:/data# ls -l /etc/config/
    total 0
    lrwxrwxrwx. 1 root root 20 Mar 20 16:25 SPECIAL_LEVEL -> ..data/SPECIAL_LEVEL
    lrwxrwxrwx. 1 root root 19 Mar 20 16:25 SPECIAL_TYPE -> ..data/SPECIAL_TYPE
    root@dapi-test-pod:/data# cat /etc/config/SPECIAL_LEVEL
    very
    root@dapi-test-pod:/data# cat /etc/config/SPECIAL_TYPE 
    charm
    root@dapi-test-pod:/data# exit
    $ kubectl delete -f pod-configmap-volume.yaml
    pod "dapi-test-pod" deleted
    $ kubectl delete -f configmap-multikeys.yaml
    configmap "special-config" deleted
    ```