วิธีเก็บ Sensitive Data บน Kubernetes ด้วย Secrets

จากบล็อกที่แล้วได้ลองทำ Kubernetes บน Amazon EKS แบบง่ายๆ ด้วย eksctl แต่ก็ยังมีจุดที่กังวลอยู่ คือเรื่องการระบุ Sensitive Data เช่น Password, Token ต่างๆ ลงใน Environment Variable ที่ได้เขียนไว้ในไฟล์ Deployment YML

ซึ่งทาง Kubernetes เอง ได้ทำฟีเจอร์มารองรับการแก้ปัญหานี้แล้ว เป็น Kind ประเภท Secret นั่นเองครับ เลยขอบันทึกวิธีทำไว้สักหน่อย

ทำความเข้าใจก่อน ทำไมต้องแยก Sensitive Data

ก่อนจะไปถึงวิธีการ มาทำความเข้าใจก่อนว่าทำไมเราต้องแยก Sentitive Data ออกไป (ขอนอกเรื่อง Kubernetes สักหลายย่อหน้า เดี๋ยวโพสต์จะสั้นไป!!)

หากใครทำงานคนเดียว เก็บ Source Code ไว้เป็น Private ในเครื่องตัวเองเท่านั้น (หรือ Git Repository แบบ Private) และไม่ได้ใช้ Clound หรือทำพวก Scaling ด้วยแล้ว อาจไม่จำเป็นต้องใช้ก็ได้

แต่ถ้าเราทำงานเป็นทีมเมื่อไร เรื่องการกำหนดขอบเขตการเข้าถึงข้อมูลเป็นเรื่องสำคัญมาก เราคงไม่ต้องการให้ทีมทุกคนรู้ Password ในการเข้าสู่ระบบ หรือไม่ต้องการให้รู้ Token ในการควบคุม API ของบริการสำคัญๆ ดังนั้นแล้ว เราจะมีวิธีการจัดการพวก Sensitive Data กันมาบ้างอยู่แล้ว ที่นิยมทำกัน เช่น

  1. ง่ายสุดๆ คือ ฝัง Sensitive Data ลงในโค้ดเลย แล้ว if-else จากเงื่อนไขอะไรบางอย่าง เช่น IP Server, URL Domain, Environment Variable
  2. ซับซ้อนขึ้นมาหน่อย คือ ทำไฟล์ Config แยกจากโค้ด และ ignore ไม่ขึ้นไปอยู่บน Repository ดังนั้นเมื่อ Deploy เสร็จ จะมีทีมหลีดหรือผู้มีสิทธิ์เข้าถึง Sensitive Data และ Server เท่านั้น ที่จะไปวางไฟล์ Config (หรือไฟล์ Config อาจจะไปอยู่บน Server แต่ละ Environment รอไว้อยู่แล้ว)
  3. ซับซ้อนขึ้นไปอีก คือ ผู้ดูแลระบบสร้าง Environment Variable ไว้ในแต่ละ Server Environment จากนั้น เมื่อ Deploy Code ไปที่ Environment ไหน โค้ดที่เขียนก็จะเรียกใช้ Environment Variable ตามชื่อ Key ที่ตกลงกันไว้
  4. Advance ไปเลย คือ ทำ Config File Server แยกไปอีกเครื่อง และ sync มาที่ server ของระบบที่จะใช้งาน จากนั้นโค้ดจะเรียกใช้ Config ตามที่อยู่ของไฟล์ที่ตกลงกันไว้

และวิธีอื่นๆ อีกหลายวิธี ซึ่งแต่ละวิธีมันก็มีความเหมาะสมที่จะใช้งานในแต่ละระบบและกระบวนการทำงานของแต่ละทีม

แต่ถ้าทีมทำงานมีเงื่อนไขอยากทำ Scaling, Automate หรือ DevOps ล่ะ เช่น

  1. Source Code ต้องมาจากแหล่งเดียว และเป็นชุดเดียวกันเสมอในทุก Environment (Single Source of Truth)
  2. Sensitive Data จะต้องรู้ได้บางคนเท่านั้นที่มีสิทธิ์
  3. โค้ดจะต้องทำ Continuous Integration และ Continuous Deployment ได้
  4. โค้ดจะต้อรองรับการทำงานบน Server ที่ Scaling ได้
  5. มีการควบคุม Version ใน Deployment (Version Control)

เอาแค่ 5 เงื่อนไขนี้ วิธีการข้างต้น 4 ข้อ ก็ดูจะไม่ครอบคลุมทุกเงื่อนไข

ดังนั้น วิธีใช้ Secret ใน Kubernetes ที่จะเขียนในโพสต์นี้ จะเป็นวิธีการมาตรฐานที่ Kubernetes ได้ทำฟีเจอร์ออกมาให้ใช้ มีความคล้ายกับการทำ Config File Server เพียงแต่เป็นเหมือน Container ที่ใช้คุณสมบัติของ Kubernetes นั่นเอง

โครงสร้างของ Secret ใน Kubernetes

ตามภาพเลยครับ จะแยก Secret ออกมาจาก Pod (แต่ยังอยู่ใน Node และ Cluster เดียวกัน) ดังนั้น เวลาจะใช้งานใน Pod ไหนก็ต้อง จะต้องระบุ Secret และ Key ที่จะใช้ เพื่ออ้างอิงถึง Sensitive Data ที่เราต้องการ

รูปจาก https://livebook.manning.com/book/gitops-and-kubernetes/chapter-7/v-6/57

ตัวอย่างที่จะใช้ในโพสต์ต่อไปนี้ จะอ้างอิงจากโพสต์เดิมที่
ลองทำ Kubernetes บน Amazon EKS แบบง่ายๆ ด้วย eksctl

เริ่มต้นสร้าง Kubernetes Secret

สร้างไฟล์ YML ขึ้นมา เพื่อสร้างชุด secret โดยในที่นี้ผมใช้ไฟล์ชื่อ eks-myweb-secret.yml

apiVersion: v1
kind: Secret
metadata:
name: myweb-secret
namespace: myweb
labels:
app: myweb-secret
data:
secret_username: aWZldw==
secret_password: cGFzczEyMzQ=

จากนั้นทำการ apply ด้วยคำสั่งดังนี้

kubectl apply -f eks-myweb-secret.yml

และลองทำการเช็คดูว่าสร้างสำเร็จไหม ด้วยคำสั่งดังนี้

kubectl get secrets -n myweb

โดยในที่นี้ผมใช้ namespace ชื่อ myweb นะครับ

เราจะเห็นผลลัพธ์ตามภาพด้านล่าง

สังเกตว่า Type เป็น Opaque หมายถึง เป็นการกำหนดการเข้าถึงเองโดยผู้ใช้งาน ซึ่ง Type นี้จะเป็น default ให้อัตโนมัติ หากอยากใช้ Type อื่นๆ ก็มีให้เลือก 8 ตัว อยู่ที่รูปแบบที่เราอยากจะทำ สามารถไปหาอ่านได้ที่ Kubernetes Secret

มาดูรายละเอียดภายใน Secret กัน

หากต้องการดูรายละเอียดว่า Secret นี้มีอะไร ให้ใช้คำสั่งนี้

kubectl describe secrets/myweb-secret -n myweb

โดยชื่อ myweb-secret คือชื่อ Secret ของผมนะ (ตามที่ระบุใน YML File )

จะแสดงรายละเอียดของ Secret ขึ้นมา

คราวนี้เราเห็นแล้วว่า Secret มี Key อะไรบ้าง แต่อยากเห็นเนื้อหาข้างใน ก็สามารถดูได้ด้วยนะครับ ด้วยคำสั่งนี้

kubectl get secret myweb-secret -n myweb -o jsonpath='{.data}'

เราก็จะเห็นข้อมูลใน Secret ดังนี้

และหากใครใช้ Mac หรือ Linux ก็ decode ข้อมูล base64 ออกมาดูได้เลยว่าคืออะไร ตามภาพครับ

ทำให้ Pod เรียกใช้งานตัวแปรใน Secret

เมื่อเราสร้าง Secret รอไว้แล้ว คราวนี้ถึงเวลาเรียกใช้ โดยผมได้แก้ไข Source Code ให้มีการอ่าน Environment จาก Secret ไว้ สองตัว คือ SECRET_USERNAME กับ SECRET_PASSWORD

จากนั้นทำการ build image, tag และ push image ไปไว้ใน Docker Repo เป็น version 3 (docker.io/ifew/docker-test-php:v3)

ต่อมา เราจะแก้ไขไฟล์ Deployment YML ที่เอาไว้สร้าง Pod, โดยแต่เดิม มีการเรียกใช้ Environment Variable ตรงๆ ในไฟล์เลย ดังนี้

apiVersion: apps/v1
kind: Deployment
metadata:
name: myweb-deployment
namespace: myweb
labels:
app: myweb
spec:
replicas: 2
selector:
matchLabels:
app: myweb
template:
metadata:
labels:
app: myweb
spec:
containers:
- name: phpfpm
image: docker.io/ifew/docker-test-php:v3
ports:
- containerPort: 80
env:
- name: MYSQL_DB_HOST
value: mydb.host.com
- name: MYSQL_DATABASE
value: test
- name: MYSQL_USER
value: ifew
- name: MYSQL_PASSWORD
value: password1234
- name: MYSQL_ROOT_PASSWORD
value: password1234
- name: TEST
value: test

โดยวิธีการเรียกใช้ Secret จะมีรูปแบบโค้ดดังนี้

- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: myweb-secret
key: secret_username

อธิบายทีละค่า

  • name: SECRET_USERNAME คือ กำหนด ชื่อ Key ของ Environment Variable ที่จะถูกนำไปใช้ในโค้ด
  • valueFrom.secretKeyRef.name: myweb-secret คือ ชื่อ Secret ที่เราสร้าง
  • valueFrom.secretKeyRef.key: secret_username คือ ชื่อ Key ที่ใช้อ้างไปหา Value ใน Secret

ซึ่งผมจะเพิ่ม env สองตัว ที่เรียกใช้จาก Secret, ดังนั้นไฟล์ที่แก้ไขของ Deployment YML ผมจะได้รูปแบบนี้

apiVersion: apps/v1
kind: Deployment
metadata:
name: myweb-deployment
namespace: myweb
labels:
app: myweb
spec:
replicas: 2
selector:
matchLabels:
app: myweb
template:
metadata:
labels:
app: myweb
spec:
containers:
- name: phpfpm
image: docker.io/ifew/docker-test-php:v3
ports:
- containerPort: 80
env:
- name: MYSQL_DB_HOST
value: mydb.host.com
- name: MYSQL_DATABASE
value: test
- name: MYSQL_USER
value: ifew
- name: MYSQL_PASSWORD
value: password1234
- name: MYSQL_ROOT_PASSWORD
value: password1234
- name: TEST
value: test
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: myweb-secret
key: secret_username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: myweb-secret
key: secret_password

หลังจากแก้ไข Deployment YML เรียบร้อย ก็ทำการ Apply อีกครั้ง

kubectl apply -f eks-myweb-deploy.yml

และลองทดสอบดูครับ ว่ามีการเรียกใช้งานถูกต้องไหม

หากต้องการอัพเดทค่าใน Secret ล่ะ ทำอย่างไร?

ให้แก้ไข Secret YML ไฟล์ และทำการ Apply ใหม่ จากนั้นขั้นตอนสำคัญคือ จะต้อง Restart Pod ด้วย เพื่อให้อ่าน Environment Variable ใหม่ ด้วยคำสั่ง

kubectl -n myweb rollout restart deployment myweb-deployment

โดยที่ myweb คือชื่อ namespace และ myweb-deployment คือชื่อของ Deployment ที่ผมตั้งไว้

จากนั้นลองทดสอบใหม่ ก็จะเห็นการเปลี่ยนแปลงทันที

สรุป

Kubernetes ได้ทำฟีเจอร์ Secret ออกมาเพื่อให้เราใช้ เราก็ใช้เถอะ ซึ่งที่ผมเขียนมา เป็นหนึ่งในรูปแบบที่ Kubernetes แนะนำ แต่จริงๆ สามารถทำอีกวิธีหนึ่งได้คือ ทำเป็น Config File จาก Pod โดยการไปสร้าง Volumes และทำการ Mount Path จากนั้นก็เรียกข้อมูลจาก File Path นั้นได้เลย (เหมือนวิธี Config File Server ที่ทำๆ กันมา) ใครสนใจลองไปหาอ่านต่อได้ที่ Using Secrets as Files from a Pod

Reference

Owner of myifew.com — ถ้าปัญหาเป็นเรื่องที่แก้ได้ เราก็ไม่ควรกังวลกับมัน และถ้าปัญหาที่แก้ไม่ได้ กังวลไปก็ไม่ช่วยอะไรอยู่ดี

Owner of myifew.com — ถ้าปัญหาเป็นเรื่องที่แก้ได้ เราก็ไม่ควรกังวลกับมัน และถ้าปัญหาที่แก้ไม่ได้ กังวลไปก็ไม่ช่วยอะไรอยู่ดี