Basic Kubernetes Part 5 - ConfigMap และ Secret

Basic Kubernetes Part 5 - ConfigMap และ Secret

ตอนนี้เรามาทำความทำความรู้จัก ConfigMap และ Secret กันว่าคืออะไร

รูปแบบการ Config ที่เป็นที่นิยม

สิ่งหนึ่งที่ขาดไม่ได้ของ Application คือการ Configuration เช่น nginx ต้องทำการ Config เปลี่ยน Endpoint ของ Reverse proxy หรือตัว Application เองต้องการกำหนดตัว Connection Pool ของ Database ให้มากขึ้นหรือลดลง หรือ แก้ไข Maximum file size ที่จะ Upload เข้ามาใน Application การ Config

Application นั้นนิยมทำการ Config ผ่านไฟล์ แต่ในปัจจุบันที่ใช้ Container กันนั้นเปลี่ยนมา Config ผ่านตัว Environment แทน เนื่องจากเราไม่อยากเข้าไปยุ่งกับตัว Volume เพราะเมื่อไหร่ที่ยุ่งกับ Volume จะมีข้อจำกัดต่างๆ เช่น Volume นี้ต้อง Mount จากไหน อ่านได้จากกี่ Node แต่ถ้าเป็น Environment นั้น ตัวสร้างสามารถกำหนดค่า Environment ผูกไปกับ Container ตอนสร้างได้เลย ซึ่งจะไป Run ที่ Node ก็ได้ ดังนั้นการ Config ผ่าน Environment จึงเป็นที่นิยม

เพื่อให้ทำการ Config ทั้ง 2 แบบได้ ทาง k8s ก็ได้ทำการสร้างสิ่งอำนวยความสะดวกในการทำการ Config ขึ้นมาซึ่งนั่นก็คือ ConfigMap โดย ConfigMap นั้นสามารถ Mount volume ไปเป็น File ได้และความพิเศษของมันก็คือมันสามารถถูกเข้าถึงได้จากทุก Node ใน Cluster ของ k8s ดังนั้น ConfigMap จึงแก้ปัญหาการ Config แบบ File ได้

ConfigMap คืออะไร

ConfigMap คือ Object แบบ KeyValue ที่ตัว Pods สามารถเข้าถึงและเอาไปเป็น Environment เอาไป Mount Volume เป็นไฟล์ (นิยามเต็มไปอ่านที่ : Link )

ตัวอย่างไฟล์ Configuration ทั้งหมดในตอนนี้อยู่ที่ Link

สร้าง ConfigMap กัน

สร้างไฟล์ basic-configmap.yml

1
2
3
4
5
6
7
8
apiVersion: v1
kind: ConfigMap
metadata:
name: basic-configmap
data:
config1: "data config 1"
config2: "data config 2"
CONFIG_DATABASE: "EXAMPLE CONFIG"

จากนั้นสั่ง apply และลอง get และ describe ตัว configMap ดู

จะเห็นว่ามี ConfigMap ที่เราสร้างโผล่ขึ้นมาและเมื่อ Describe ดูค่าแล้วเราจะเห็นค่าที่เรากำหนดไว้กับ key ต่างๆเลย เช่น key : config1 มี value : data config 1

อธิบายไฟล์ basic-configmap.yml

ส่วนของ apiVersion , kind , metadata ไม่ขออธิบายนะครับเพราะเหมือนกับ Pod , Deployment , Service

data

ตรงนี้จะเป็นการประกาศ Key , Value ของ ConfigMap โดยตัวอย่างจะเป็น

1
2
3
config1: "data config 1"
config2: "data config 2"
CONFIG_DATABASE: "EXAMPLE CONFIG"

ซึ่งหมายความว่า key : “config1” มี value : “data config 1” key : config2 มี value : “data config 2” โดยเราสามารถประกาศตัว key value ได้ตามใจเพียงค่าอย่าให้ key ซ้ำกันก็พอ

เอา ConfigMap ไปเป็น Environment

จากตัวอย่างที่เราทำมาเราจะทำการ set environment ของ Pod ผ่านตรง env เช่นตัวอย่างด้านล่าง

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: config1
value: "data config 1"
- name: config2
value: "data config 2"
- name: CONFIG_DATABASE
value: "EXAMPLE CONFIG"

ซึ่งถ้า Config env มันเยอะตรงนี้มันก็จะดูมากมายเต็มไปหมด เวลา Config อะไรก็ต้องมาแก้การ Define Pod ตลอด ดังนั้นถ้าเราแยกส่วน Config ออกมาเวลาแก้ไขก็แก้ที่ Config เลยน่าจะดีกว่า (ทีม Config จะได้ยุ่งแค่ Config ไฟล์เดียวไม่ต้องมายุ่งกับการประกาศ Pod ) ซี่งเราสามารถทำได้โดยประกาศแบบนี้

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# whoami-pod-002.yml
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: basic-configmap

เพื่อให้ง่ายต่อการเช็ค Environment ผมขอใช้ตัว image : whoami เพราะมันมี api ให้เรายิงไปดู Environment ที่ถูก set ไว้ให้กับ container ได้ (สามารถดูรายละเอียดได้ที่ : Link ) ดังนั้นผมขอสร้าง Service เพื่อเรียกผ่าน Browser นะครับ จะได้ไม่ต้องมานั่ง Describe ตัว Pod หา IP แล้วยิง

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# whoami-service.yml
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32020
type: NodePort

ให้เราเปิด Browser แล้วเปิด URL : http://<ip ของเครื่องที่ run k8s>:32020/api?env=true (ของผมเป็น http://192.168.156.101:32020/api?env=true)

จากภาพจะเห็นว่าเมื่อยิงไปจะได้ json response กลับมาจะมีส่วนที่เป็น environment กลับมาดังภาพ ซึ่งนั่นแปลว่าตัว Pod whoami ถูก Set environment จาก ConfigMap จริงๆ

อธิบายไฟล์ whoami-pod-002.yml

ในไฟล์นี้จะมีส่วนที่เพิ่มเติมมาคือ envFrom

envFrom

ตรงส่วนนี้จะเป็นการบอกว่าจะเอา Environment มาจากไหน

1
2
3
envFrom:
- configMapRef:
name: basic-configmap

โดยจากตัวอย่างเนี่ยเราบอกว่าให้เอามาจาก configMap โดยบอกจาก keyword : configMapRef โดยเอามาจาก ConfigMap ชื่อ basic-configmap ซึ่งการประกาศแบบนี้คือเอาทุกค่าใน ConfigMap มาใส่ในนี้เลย

อยาก Set แค่บาง Environment ล่ะ

ในส่วนที่แล้วเราลอง Set environment ผ่าน ConfigMap ซึ่งเป็นการเอามาใส่ทั้ง ConfigMap แต่บางกรณีเราไม่อยากเอาค่าทุกค่าจาก ConfigMap ไปเป็น environment เราอยากได้แค่บางค่าเราจะทำยังไง แน่นอนว่านี่คือปัญหาทั่วไปที่ทุกคนอยากได้ซึ่ง k8s ก็ทำให้เราสามารถทำแบบนั้นได้ครับ โดยทำแบบนี้

ทำการสร้างไฟล์ : whoami-pod-003.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# whoami-pod-003.yml
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: "WASINEE"
- name: "CONFIG_DATABASE_FOR_CONNECTION"
valueFrom:
configMapKeyRef:
name: "basic-configmap"
key: "CONFIG_DATABASE"

จากนั้นสั่งคำสั่งด้านล่างและเปิด Browser และใส่ url เดิมลงไป

1
2
3
# สั่งเพื่อลบ pod ตัวเก่าทิ้งไป
kubectl delete whoami-pod
kubectl apply -f whoami-pod-003.yml

จากภาพจะเห็นว่า Environment ถูก Set แค่ค่าที่ต้องการจริงๆคือ OWNER (อันนี้ประกาศแบบธรรมดา) กับ CONFIG_DATABASE_FOR_CONNECTION (อันนี้ประกาศแบบใหม่)

อธิบายไฟล์ whoami-pod-003.yml

จากไฟล์เราจะเห็นตัวอย่างมีการประกาศ evironment แบบใหม่คือ

1
2
3
4
5
- name: "CONFIG_DATABASE_FOR_CONNECTION"
valueFrom:
configMapKeyRef:
name: "basic-configmap"
key: "CONFIG_DATABASE"
valueFrom

ตรงนี้เป็นการบอกว่าค่า Value นั้นให้ไปเอามาจากไหนซึ่งตรงนี้ประกาศว่า configMapKeyRef ซึ่งแปลว่าเอามาจาก configMap ส่วนด้านในส่วน name , key นั้นเป็นการบอกว่า ConfigMap ชื่ออะไร และ key ที่จะให้เอาค่าคืออะไร จากตัวอย่างก็คือไปเอาค่าจาก ConfigMap ชื่อ basic-configmap และ key ชื่อ CONFIG_DATABASE

เอา ConfigMap ไป Mount volume เป็นไฟล์

ในตอนต้นเรารู้แล้วว่า Applicaiton นิยม Config ด้วยไฟล์ แต่ปัญหาเมื่อเอามาใช้กับ k8s คือมันต้องทำการ Mount volume ซึ่งก็จะมีปัญหาเรื่องอ่านเขียนได้จากกี่ Node เวลาแก้ไข Config ต้องไปแก้ที่ Volume ตรงไหน เพื่อแก้ปัญหานั้น k8s จึงทำให้เราสามารถ Mount ตัว ConfigMap ไปเป็น Volume ได้ โดยตัวอย่างนี้ผมจะสร้าง ConfigMap ที่มีค่าเป็นไฟล์ HTML และ Mount volume ไปใน nginx ซึ่งเมื่อเราเรียกผ่าน Browser จะต้องแสดงผลแบบไฟล์ HTML ของเรา

สร้างไฟล์ nginx-configmap.yml จากนั้นสั่ง apply

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-configmap
data:
indexFile: |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HELLO WORLD</title>
</head>
<body>
<h1>HELLO WORLD</h1>
</body>
</html>
wasineeFile: |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HELLO WASINEE</title>
</head>
<body>
<h1>HELLO WASINEE</h1>
</body>
</html>

จากไฟล์ที่เราประกาศจะเห็นว่าเราใช้เครื่อง “|” ซึ่งเครื่องหมายนี้เป็นการบอกว่าจะค่าที่จะ config นั้นมีหลายบรรทัด โดยในไฟล์นี้เราสร้าง configMap ที่มี 2 key คือ indexFile , wasineeFile

สร้างไฟล์ nginx-pod.yml จากนั้น apply ไฟล์

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: nginx-volume
volumes:
- name: nginx-volume
configMap:
name: nginx-configmap
items:
- key: "indexFile"
path: "index.html"
- key: "wasineeFile"
path: "wasinee.html"
---
# ตรงนี้แค่ประกาศ Service เพื่อไว้ใช้เปิดเข้ามาดูผ่าน Browser ได้
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32030
type: NodePort

จากนั้นเราเปิด Browser แล้วเข้า URL :

ซึ่งจะเห็นว่า nginx นั้นแสดงผลไฟล์ index.html เหมือนกับค่าใน configMap key : indexFile และ wasinee.html นั้นแสดงผลตรงกับค่าใน configMap key : wasineeFile

อธิบายไฟล์ nginx-pod.yml

ไฟล์นี้จะคล้ายๆไฟล์ nginx-use-pvc.yml แต่ต่างกันตรงการประกาศ volume

1
2
3
4
5
6
7
8
9
volumes:
- name: nginx-volume
configMap:
name: nginx-configmap
items:
- key: "indexFile"
path: "index.html"
- key: "wasineeFile"
path: "wasinee.html"

โดยอันนี้เราจะเปลี่ยนจาก persistentVolumeClaim มาเป็น configMap แทน

name

ตรงนี้เป็นการบอกว่าเราจะใช้ configMap ชื่ออะไรในการ Mount volume

items

ตรงนี้เป็นการบอกว่าจะเอา key ไหนไปเป็นไฟล์อะไรบ้างและเอาไปเป็นไฟล์วางไว้ในที่ไหน โดยจากตัวอย่าง เราจะเอา key: indexFile ไปเป็นไฟล์ index.html และ key : wasineeFile ไปเป็นไฟล์ wasinee.html

Secret คืออะไร

ถ้ามองแบบง่ายๆมันก็คือ ConfigMap นั่นแหละครับ แต่เป็น ConfigMap ที่มีการปกป้องไม่ให้สามารถอ่านออกได้แบบตรงๆ (ระดับง่ายสุดคือ base6
4) Secret เหมาะที่จะใช้กับอะไรที่ต้องการเป็นความลับ เช่น password , token , private key เป็นต้น (สามารถอ่านนิยามที่ถูกต้องได้ที่ : Link)

ลองสร้าง Secret

ทำการสร้างไฟล์ basic-secret.yml

1
2
3
4
5
6
7
8
apiVersion: v1
kind: Secret
metadata:
name: basic-secret
type: Opaque
data:
username: d2FzaW5lZQ== # Base64-encoded username raw is : wasinee
password: UEBzc3cwcmQ= # Base64-encoded passwor raw is : P@ssw0rd

จากนั้นสั่ง apply และทำการ get และ describe ดู secret

จากภาพจะเห็นว่ามันแสดงผลคล้ายๆกับ configMap เลยคือมี key ให้รู้ว่า secret นี้มี key อะไรบ้าง เพียงแต่เราไม่สามารถเห็นค่า value : secret ได้เหมือนกับ configMap ก็เท่านั้น

อธิบายไฟล์ basic-secret.yml

ในส่วนของ apiVersion , kind , metadata ผมขอไม่พูดนะครับเพราะเหมือนกับ pod , deployment , configMap จะพูดตรง type ลงไป

type

ตรงนี้เป็นการบอกว่า Secret นี้เป็น type อะไร โดย Opaque เป็น type basic ของ secret ตัว type มีอีกมากมายสามารถดูได้ที่ https://kubernetes.io/docs/concepts/configuration/secret/#secret-types

data

ส่วนนี้เป็นการประกาศ key กับ value โดย value จะต้องเป็นค่าที่ถูก Base64 ไว้ครับ

เอา Secret ไปเป็น Environment

ในเมื่อ configMap สามารถเอาไปผูกเป็น Environment ได้ secret ก็น่าจะต้องเอาไปใช้เป็น Environment ได้

** ทำการลบ whoami-pod และ whoami-service ทิ้งก่อนนะครับ

สร้างไฟล์ whoami-pod-004.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
envFrom:
- secretRef:
name: basic-secret
---
# ส่วนนี้ประกาศ Service เฉยๆ
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32020
type: NodePort

จากนั้นทำการ apply file และเปิด Browser แล้วเปิด URL : http://<ip ของเครื่องที่ run k8s>:32020/api?env=true (ของผมเป็น http://192.168.156.101:32020/api?env=true)

จากภาพจะเห็นว่าค่า username , password นั้นเอามาจาก secret: basic-secret

อธิบายไฟล์ : whoami-pod-004.yml

จากไฟล์จะเห็นว่าส่วนที่แตกต่างไปจากเดิมคือ envFrom

envFrom

ตรงส่วนนี้คือบอกว่าจะเอา env มาจากไหน โดย secretRef คือบอกว่าให้เอาจาก secretRef ซึ่งในตัวอย่างคือ secret ชื่อ basic-secret

1
2
3
envFrom:
- secretRef:
name: basic-secret

อยาก Set ค่าแค่บาง Environment จาก Secret ล่ะ

หัวข้อนี้เหมือนกับ configMap เลยคืออยาก set : environment แค่บางค่าจาก Secret ล่ะทำยังไง

** ทำการลบ whoami-pod และ whoami-service ทิ้งก่อนนะครับ

สร้างไฟล์ : whoami-pod-005.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
apiVersion: v1
kind: Pod
metadata:
name: whoami-pod
labels:
app: whoami
spec:
containers:
- name: whoami-container
image: traefik/whoami
ports:
- containerPort: 80
env:
- name: "OWNER"
value: "WASINEE"
- name: "CONFIG_DATABASE_PASSWORD"
valueFrom:
secretKeyRef:
name: "basic-secret"
key: "password"
---
# ส่วนนี้ประกาศ Service เฉยๆ
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32020
type: NodePort

จากนั้นทำการ apply file และเปิด Browser แล้วเปิด URL : http://<ip ของเครื่องที่ run k8s>:32020/api?env=true (ของผมเป็น http://192.168.156.101:32020/api?env=true)

จากภาพจะเห็นว่าค่า Environment : CONFIG_DATABASE_PASSWORD มาจาก basic-secret ที่ key : password

อธิบายไฟล์ : whoami-pod-005.yml

จากตัวอย่างจะเห็นว่าที่แตกต่างจาก configMap คือ valueFrom ที่กำหนดเป็น secretKeyRef

secretKeyRef
1
2
3
4
5
- name: "CONFIG_DATABASE_PASSWORD"
valueFrom:
secretKeyRef:
name: "basic-secret"
key: "password"

จากตัวอย่างจะเห็นว่าเราบอกว่าเราจะเอาค่าจาก secret ชื่อ basic-secret ที่ key : password ไปเป็น environment ชื่อ CONFIG_DATABASE_PASSWORD

เอา Secret ไป Mount volume เป็นไฟล์

Secret นั้นแทบจะเหมือนกับ ConfigMap ซึ่งตัว ConfigMap สามารถ Mount volume ได้ ดังนั้น Secret ก็น่าจะต้อง Mount volume ได้เช่นกัน

** ทำการลบ nginx-pod และ nginx-service ทิ้งก่อนนะครับ

สร้างไฟล์ : nginx-use-secret.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.25.1
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: nginx-volume
volumes:
- name: nginx-volume
secret:
secretName: basic-secret
items:
- key: "username"
path: "index.html"
---
# ส่วนนี้ประกาศ Service เฉยๆ
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 32030
type: NodePort

สั่ง apply ไฟล์ จากนั้นเราเปิด Browser แล้วเข้า URL : http://<ip ของเครื่อง run k8s>:32030/index.html (ของผมเป็น http://192.168.156.101:32030/index.html)

ซึ่งจากภาพจะเห็นว่าจะได้คำว่า wasinee โผล่ขึ้นมาที่หน้าจอ ซึ่งตรงกับค่า basic-secret ที่ key : username

อธิบายไฟล์ : nginx-use-secret.yml

จากไฟล์จะเห็นส่วนที่แตกต่างจาก configMap คือตรงประกาศ volume นั้นประกาศด้วย secret

secret
1
2
3
4
5
6
7
volumes:
- name: nginx-volume
secret:
secretName: basic-secret
items:
- key: "username"
path: "index.html"

จากตัวอย่างจะเห็นว่าเราประกาศบอกว่า volume นี้เอามาจาก secret โดยบอกว่าเอามาจาก secretName ชื่อ basic-secret และเอา key username ไปเป็นไฟล์ index.html ดังนั้นค่า wasinee ที่อยู่ที่ key : username เลยไปกลายเป็นไฟล์ index.html อย่างที่เราทดลอง

สรุป

สำหรับตอนนี้เราได้รู้ว่า ConfigMap กับ Secret คืออะไร ได้รู้วิธีการประกาศ การนำไปใช้เป็น environment และนำไป mount volume เป็นไฟล์ สำหรับตอนต่อไปเราจะไปดูเกี่ยวกับ ingress กันครับ