การจัดการ configuration ของแอปพลิเคชันใน Kubernetes ด้วย ConfigMap

Nont Banditwong
5 min readJan 9, 2019

--

เพื่อความยืดหยุ่นในการนำไปใช้งาน แอปพลิเคชันส่วนใหญ่ จะมีช่องทางในการกำหนดค่า 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

Kubernetes ConfigMap

วิธีการนำค่าของ 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 จากไฟล์

สร้าง ConfigMap จาก ไฟล์ในไดเรกทอรี

kubectl create configmap my-config --from-file=/path/to/dir
สร้าง ConfigMap จากไดเรกทอรี

สร้าง 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 จาก Option หลายแบบผสมกัน

การอ้างถึง 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
การ Mount ไฟล์จาก volume

การกำหนด 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 เพราะตอนนี้เนื้อหายาวเกินไปแล้ว

--

--

Nont Banditwong

Cloud Engineering Specialist, Software Developer, System Engineer, Photographer and Learner