การจัดการ configuration ของแอปพลิเคชันใน Kubernetes ด้วย ConfigMap
เพื่อความยืดหยุ่นในการนำไปใช้งาน แอปพลิเคชันส่วนใหญ่ จะมีช่องทางในการกำหนดค่า configuration ให้ ยกตัวอย่างเช่น โปรแกรม wordpress ก่อนจะใช้งานได้ก็ต้องมีการกำหนดค่าชนิดของฐานข้อมูล IP address ที่ใช้ในการเชื่อมต่อรวมถึง username และ password ของฐานข้อมูลเป็นต้น ดังนั้น wordpress จึงสามารถใช้งานฐานข้อมูลได้หลากหลาย
เมื่อมาถึงยุคของ container แอปพลิเคชันที่ถูกออกแบบมาเพื่อให้พร้อมใช้งานใน container มักจะกำหนดให้ใส่ค่า configuration เข้าไปใน environment variable เช่นในกรณีของ wordpress ที่อยู่บน dockerhub เวลาเราต้องการใช้งาน docker เราสามารถส่งค่า configuration ผ่านทาง environment variable ต่างๆได้ เช่น
- WORDPRESS_DB_HOST สำหรับ IP address หรือ hostname ของฐานข้อมูล
- WORDPRESS_DB_USER สำหรับ username ของฐานข้อมูล
- WORDPRESS_DB_PASSWORD สำหรับ password ของฐานข้อมูล และ
- WORDPRESS_DB_NAME เพื่อกำหนดชื่อของฐานข้อมูลเป็นต้น
แต่ถ้าเราไม่ใส่เข้าไปตัวแอปพลิเคชันส่วนใหญ่ก็จะกำหนดให้ใช้ค่าตั้งต้น (ค่า default)
ยกตัวอย่างเช่นเราสามารถสั่งให้ wordpress ที่ถูกสร้างมาสำหรับทำงานใน Docker ทำงานด้วยคำสั่งตามด้านล่าง
docker run --name some-wordpress \
-e WORDPRESS_DB_HOST=10.1.2.3:3306 \
-e WORDPRESS_DB_USER=dbuser \
-e WORDPRESS_DB_PASSWORD=njVGSwR9qSUK6MAw \
-d wordpress
เมื่อ wordpress พบว่ามีการกำหนดค่า environment variable เข้ามาก็จะนำค่าที่กำหนดให้ไปใช้แทนค่าตั้งต้น
สำหรับ Kubernetes แล้วมีวิธีจัดการ configuration เพื่อทำงานร่วมกับการกำหนดค่า configuration ของแอปพลิเคชันผ่าน environment variable โดยจัดการผ่าน object ที่เรียกว่า ConfigMap และ Secret
วิธีการนำค่าของ ConfigMap มาใช้ในแอปพลิเคชันก็ยังสามารถทำผ่าน environment varible แต่อ้างอิงค่าผ่าน ConfigMap ดังตัวอย่างด้านล่าง กำหนดค่าsของ environment variable ชื่อ MYKEY1 โดยใช้ค่าของ key1 จาก ConfigMap ชื่อ my-config
apiVersion: v1
kind: Pod
metadata:
name: myapp-env-from-configmap
spec:
containers:
- image: myorg/myapp:v1
env:
- name: MYKEY1
valueFrom:
configMapKeyRef:
name: my-config
key: key1
วิธีสร้าง ConfigMap
สามารถทำได้สองแบบคือ
1. สร้างจาก object spec ในรูปแบบของ YAML
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config
namespace: development
data:
key1: "val1"
key2: "val1"
key3: "val3"
การสร้าง object ใน kubernetes สามารถทำผ่านคำสั่ง kubectl เช่น
kubectl create -f my-config.yaml
2. ทำผ่านคำสั่ง kubectl create cofigmap เช่น
kubectl create configmap my-config --namespace development --from-literal=key1=val1
การสร้าง ConfigMap ในรูปแบบต่างๆ
วิธีการกำหนดข้อมูลของ ConfigMap ยังมีอีกหลายรูปแบบเช่น
สร้าง ConfigMap จาก configuration file
kubectl create configmap my-config --from-file=config-file.conf
เมื่อเราสร้าง ConfigMap จากไฟล์ key ของ configmap จะเป็นชื่อไฟล์ ส่วนค่าจะเป็น content ของไฟล์ทั้งหมด โดยไม่สนใจเนื้อหาด้านใน
สร้าง ConfigMap จาก ไฟล์ในไดเรกทอรี
kubectl create configmap my-config --from-file=/path/to/dir
สร้าง ConfigMap จาก Option หลายแบบผสมกัน
เราสามารถ
kubectl create configmap my-config \
--from-file=config.json \
--from-file=java=config.properties \
--from-file=conf.d/ \
--from-literal=hello=world
การอ้างถึง ConfigMap ที่ไม่มีอยู่
กรณีที่ Pod อ้างถึง ConfigMap ที่ไม่มีอยู่ จะทำให้ Pod ไม่สามารถ start ได้ แต่พอเราสร้าง ConfigMap ขึ้นมาตัว Pod ก็จะถูก start โดยที่ไม่จำเป็นต้องสร้าง Pod นั้นใหม่
เราสามารถตั้งค่าให้ ConfigMap ที่อ้างถึงนั้นเป็น optional ได้ด้วย configuration ด้านล่าง Pod จะถูก start ไม่ว่า ConfigMap ที่ถูกอ้างถึงจะมีหรือไม่มีอยู่ก็ตาม
configMapKeyRef.optional: true
การส่ง ConfigMap ทั้งหมดเข้าไปในแอปพลิเคชันในครั้งเดียว
ปกติแล้วแอปพลิเคชันจะมี configuration จำนวนมากที่สามารถปรับแต่งได้ ทำให้การเขียน object spec ของ Pod ในส่วนของ environment variable นั้นมีขนาดใหญ่ตามไปด้วย ซึ่งมีโอกาสพิมพ์ผิดได้ง่าย ดังนั้น Kubernetes จึงมีวิธีที่ทำให้ส่ง ConfigMap เข้าไปในแอปพลิเคชัน ได้ในครั้งเดียว โดยใช้ envFrom attribute
สมมติเรามี object spec ในส่วนของ environment variable ตาม code ด้างล่าง
env:
- name: http_proxy
valueFrom:
configMapKeyRef:
name: proxy-config
key: http_proxy
optional: true
- name: https_proxy
valueFrom:
configMapKeyRef:
name: proxy-config
key: https_proxy
optional: true
- name: ftp_proxy
valueFrom:
configMapKeyRef:
name: proxy-config
key: ftp_proxy
optional: true
- name: no_proxy
valueFrom:
configMapKeyRef:
name: proxy-config
key: no_proxy
optional: true
เราสามารถเขียน Pod spec ใหม่โดยใช้ envFrom attribute ได้ดังนี้
envFrom:
- prefix: config_
configMapRef:
name: proxy-config
optional: true
attribute prefix จะมีหรือไม่มีก็ได้ ถ้ามีจะช่วยให้หลีกเลี่ยงการที่ชื่อของ key ซ้ำกันจากการอ้างอิง ConfigMap จากหลายๆที่ได้ จากตัวอย่างชื่อของ key จะมี prefix เป็น config_ และ key จะเปลี่ยนเป็น config_<key name> เช่น config_http_proxy config_https_proxy และ config_ftp_proxy เป็นต้น
การสร้าง Volume จากข้อมูลที่เก็บอยู่ใน ConfigMap
Kubernetes อนุญาตให้เราสามารถ mount volume จาก ConfigMap ได้ โดย map key เป็นชื่อไฟล์ และ value ของ key เป็น content ในไฟล์ คล้ายกับเป็นขบวนการย้อนกลับของการสร้าง ConfigMap จากไดเรกทอรี
ยกตัวอย่าง ConfigMap ชื่อ special-config ด้านล่าง
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
special.level: very
special.type: charm
เมื่อเราสร้าง ConfigMap และสร้าง Pod ตาม Pod spec ด้านล่าง
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: k8s.gcr.io/busybox
command: [ "/bin/sh", "-c", "ls /etc/config/" ]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
# Provide the name of the ConfigMap containing the files
# you want to add to the container
name: special-config
restartPolicy: Never
เมื่อเราสั่ง kubectl logs dapi-test-pod คำสั่ง ls /etc/config/ จะ แสดงชื่อไฟล์ในไดเรกทอรีนี้ทั้งหมดออกมา
kubectl logs dapi-test-pod
special.level
special.type
จาก Pod spec ด้านบน volume ถูกสร้างจาก ConfigMap ชื่อ special-config ด้วย attribute
volumes:
- name: config-volume
configMap:
name: special-config
จากนั้น volume จะถูก mount ไปที่ filesystem ด้วย attribute
volumeMounts:
- name: config-volume
mountPath: /etc/config
การเพิ่มข้อมูลของ ConfigMap เข้าไปที่บาง Path ใน Volume
การเพิ่มข้อมูลของ ConfigMap โดยเลือกเฉพาะบางไฟล์ใน Volume
จากตัวอย่างก่อนหน้านี้ ถ้าเรา map volume จาก ConfigMap ข้อมูลเดิมที่อยู่ในไดเรกทอรีจะถูกซ่อนไว้ และโดนแทนที่ด้วยไฟล์ใน ConfigMap
ในบางกรณีเราต้องการเลือก map เฉพาะบางไฟล์ในไดเรกทอรี เช่น configuration ของ nginx ที่อยู่ใน Path /etc/nginx/nginx.d ถ้าเรา ต้องการเลือก map เฉพาะไฟล์ชื่อ default.conf สามารถทำได้โดยใส่ attribute subPath ตามด้วยชื่อไฟล์ (key) ที่ต้องการ
ถ้าไม่มี attribute subPath ไฟล์ทั้งหมดในไดเรกทอรี /etc/nginx/nginx.d จะถูกแทนที่ด้วยไฟล์ใน ConfigMap ทันที
spec:
containers:
- image: nginx
volumeMounts:
- name: myvolume
mountPath: /etc/nginx/nginx.d
subPath: default.conf
การกำหนด Permission ของไฟล์ใน ConfigMap volume
เราสามารถกำหนด Permission ของไฟล์ใน ConfigMap volume ได้โดยใช้ attribute defaultMode ซึ่งจากตัวอย่างเรากำหนดให้ defaultMode: “0600” ซึ่งหมายถึงกำหนดสิทธิ์ read-write สำหรับ owner ของไฟล์เท่านั้น
volumes:
- name: config-volume
configMap:
name: special-config
defaultMode: "0600"
การ update configuration ของแอปพลิเคชัน โดยที่ไม่ต้อง restart แอปพลิเคชัน
การ map ไฟล์ configuration จาก ConfigMap ที่อยู่ในรูปแบบของ Volume เปิดโอกาสให้ แอปพลิเคชันสามารถ reload ค่าของ configuration โดยที่ไม่ต้อง restart แต่เราต้อง implement ขบวนการ reload configuration นี้เอง เช่นในกรณีของภาษา Java เราอาจใช้ Watch Service เพื่อตรวจจับการเปลี่ยนแปลงของไฟล์ในไดเรกทอรี
ต่างจากในกรณีที่เราอ่านค่า configuration จาก environment variable จะไม่สามารถทำเช่นนี้ได้
ในบาง development framework จะมีวิธีที่สามารถจัดการกับการ reload configuration โดยที่ไม่ต้อง restart แอปพลิเคชันได้ เช่น Spring Cloud Config
แอปพลิเคชันบางตัวที่สามารถสั่ง reload configuration จาก command ine ได้เช่น Apache HTTP Server และ Nginx เราสามารถสั่งใช้ แอปพลิเคชัน reload configuration หลังจากถูกแก้ไขค่าใน ConfigMap ได้
ยกตัวอย่างในกรณีของ Nginx ถ้าหากเราแก้ไขค่าของ ConfigMap ดังนี้
kubectl edit configmap app-config
เมื่อเราแก้ไขเสร็จแล้ว เราสามารถ ดูว่าข้อมูลใน configuration เปลี่ยนหรือไม่ด้วยคำสั่ง cat /etc/nginx/conf.d/default.conf ซึ่งเราสามารถใช้คำสั่ง kubectl ได้ตามด้านล่าง
kubectl exec mynginxapp -c nginxweb -- cat /etc/nginx/conf.d/default.conf
สมมติ Pod ชื่อ mynginxapp และ container ใน Pod ชื่อ nginx เราสามารถสั่งให้ Nginx reload configuration โดยใช้คำสั่ง nginx -s reload ซึ่งเราสามารถใช้คำสั่ง kubectl ได้ตามด้านล่าง
kubectl exec mynginxapp -c nginxweb -- nginx -s reload
ในอนาคต kubernetes อาจจะมีวิธีที่จะส่ง signal บางอย่างไปที่ container เพื่อ reload configuration เมื่อเราเปลี่ยนค่าใน ConfigMap อัตโนมัติ คล้ายๆกับการส่ง signal SIGHUP เข้าไปที่ process เพื่อบอกให้ process ทำการ reload cpnfiguration ก็ได้
ความจริงว่าจะเขียนเรื่อง Secret เข้าไปด้วยเพราะมันทำงานเหมือน ConfigMap แต่ต้องขอแยกไว้อีก blog เพราะตอนนี้เนื้อหายาวเกินไปแล้ว