Basic Kubernetes Part 7 - ทำโจทย์ - Deploy Wordpress บน k8s

Basic Kubernetes Part 7 - ทำโจทย์ - Deploy Wordpress บน k8s

WordPress เป็น Application หนึ่งที่ถูกเอามาใช้เพื่อสร้างเป็น Web ซึ่งได้รับความนิยมเป็นอย่างมากเพราะคนสามารถเอาไปดัดแปลงทำได้หลายอย่าง ตั้งแต่ Blog บอกเล่าเรื่องราวของคนทั่วไป ไปจนถึง ทำ Web ขายของออนไลน์ก็ได้ ใครอยากรู้ว่า WordPress คืออะไรแบบละเอียด กดที่ Link แล้วหาอ่านเพิ่มเติมได้เลย

วิเคราะห์โจทย์

คราวนี้ถ้าเราจะ Deploy ตัว WordPress ล่ะเราจะต้องทำอะไรบ้าง อย่างแรกคือเราต้องรู้ก่อนว่าตัว WordPress ต้องการอะไรบ้าง และมี Docker ให้ใช้ไหม

มี Docker ให้ใช้ไหมและต้องใช้อะไรบ้าง

เราสามารถ Search ที่ google ด้วยคำว่า WordPress + Docker ซึ่งเราก็เจอที่ Dockerhub โดยเรามี Docker ของ WordPress ให้ใช้ พร้อมบอกวิธีการ Set: ENVIRONMENT ด้วย

ซึ่งจากการดู ENVIRONMENT แล้ว เราจะพบว่าตัว WordPress เนี่ยต้องการใช้ MySQL ด้วย ซึ่งถ้าเรามี MySQL อื่นให้ใช้ก็ OK แต่เราไม่มีดังนั้นแปลว่าเราต้องสร้าง Database ด้วย

สรุปแล้วเราต้อง Deploy container 2 ตัวคือ

  1. WordPress

  2. MySQL

เริ่มสร้างไฟล์เพื่อ Deploy

ตัวอย่างไฟล์ทั้งหมดสามารถดูได้ที่ Link

สร้างส่วนที่เกี่ยวกับ MySQL

สิ่งที่ต้องสร้างสำหรับส่วน MySQL ที่ต้องมีคือ

  1. Deployment ที่มีไว้สร้าง Pod
  2. Service ที่ใช้เป็นตัวเปิดเส้นทางให้สามารถ Request ไปหา Pod ได้
  3. ConfigMap เนื่องจากเราอยากแยก Config ออกจาก Deployment
  4. Persistent Volume เนื่องจากตัว MySQL ต้องมีที่เก็บข้อมูล
  5. Persistent Volume Cliam ต้องสร้างเพื่อให้ Pod สามารถเข้าถึง Volume ที่ต้องการได้

สร้างส่วน PV , PVC

ทำการสร้างไฟล์ mysql-volume.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
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
spec:
storageClassName: mysql-volume
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
hostPath:
# ตรงนี้เปลี่ยนเป็น path ของเครื่องคุณ
path: /root/volume/mysql

---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: mysql-volume
resources:
requests:
storage: 2Gi

โดยไฟล์นี้จะทำการสร้าง pv กับ pvc เพื่อใช้เก็บข้อมูลของ MySQL โดยในการใช้งานจริงเราไม่ควรใช้ Storage ประเภท hostPath นะครับ เพราะมันจะทำให้เวลา Deploy เราจะต้องบังคับให้ Pod อยู่กับ Node ที่มี Volume อยู่เท่านั้น ไม่สามารถกระจายไป Node ต่างๆได้ ในงานจริงควรจะเป็นพวก nfs ครับ

สร้าง ConfigMap

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

1
2
3
4
5
6
7
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-configmap
data:
MYSQL_ROOT_PASSWORD: "root"
MYSQL_DATABASE: "wordpress_db"

เนื่องจากเราต้องการแยก config ออกจาก deployment เราจึงแยกออกมาเป็น ConfigMap โดยค่าที่อยู่ใน configMap คือ ENVIRONMENT ที่เราอยากจะเอาไป set ใน pod โดย Environment ที่เราจะ set คือ MYSQL_ROOT_PASSWORD (อันนี้คือตั้งค่า root password ) , MYSQL_DATABASE (อันนี้คือสร้าง database ชื่ออะไร)

สร้าง Deployment

สร้างไฟล์ mysql-deployment.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
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-pod
labels:
app: mysql-app
spec:
selector:
matchLabels:
app: mysql-app
template:
metadata:
labels:
app: mysql-app
spec:
containers:
- name: mysql-app
imagePullPolicy: Always
image: mysql:5.7
envFrom:
- configMapRef:
name: mysql-configmap
ports:
- containerPort: 3306
name: mysql-port
volumeMounts:
- mountPath: /var/lib/mysql
name: mysql-volume
volumes:
- name: mysql-volume
persistentVolumeClaim:
claimName: mysql-pvc

ไฟล์นี้ทำการสร้าง Deployment โดยใช้ environment จาก oonfigMap : mysql-configmap และทำการ mount volume ไปที่ /var/lib/mysql โดยใช้ volume จาก mysql-pvc

สร้าง Service

ทำการสร้างไฟล์ : mysql-service.yml จากนั้นสั่ง apply

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

โดยไฟล์นี้ทำการสร้าง Service type NodePort โดยให้เข้าที่ port : 32020 จากนั้นลองตรวจสอบว่าสามารถใช้งาน

จากภาพผมใช้ตัว dbeaver เข้าไปตรวจสอบว่าสามารถใช้งาน MySQL ได้ไหมและมี Database ชื่อ wordpress_db ที่สั่งสร้างไหม ซึ่งปรากฏว่าดังที่สร้าง

สร้างส่วนที่เกี่ยวกับ WordPress

ในส่วนของ WordPress นั้นมีส่วนที่เราต้องสร้างดังต่อไปนี้

  1. ส่วน Deployment เพื่อทำการสร้าง Pod
  2. Service ที่ใช้เป็นตัวเปิดเส้นทางให้สามารถ Request ไปหา Pod ได้
  3. ConfigMap เนื่องจากเราอยากแยก Config ออกจาก Deployment
  4. Ingress เนื่องจากเราต้องการใช้งาน WordPress ผ่าน Port 80

ในส่วนของ Volume นั้นเราไม่ได้ใช้เพราะเราจะทำการ Save ข้อมูลลง MySQL ซึ่งเราสร้างไปแล้ว

สร้าง ConfigMap

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

1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: ConfigMap
metadata:
name: wordpress-configmap
data:
WORDPRESS_DB_HOST: "mysql-service.default.svc.cluster.local"
WORDPRESS_DB_USER: "root"
WORDPRESS_DB_PASSWORD: "root"
WORDPRESS_DB_NAME: "wordpress_db"
WORDPRESS_TABLE_PREFIX: "wasinee_"

จากไฟล์เราสร้าง configMap โดยค่า configMap นี้จะเอาไปใช้เป็น Environment โดย Environment ที่เราตั้งค่าไว้จะเกี่ยวกับ Database เพราะ WordPress ต้องใช้ database หากสังเกตจะเห็นว่า WORDPRESS_DB_HOST นั้นเราชี้ไปหา service : mysql-service ที่เราสร้างโดยใช้ชื่อ service (ใครงงกลับไปอ่านตอน service นะครับ)

สร้าง Deployment

สร้างไฟล์ wordpress-deployment.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
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-deployment
labels:
app: wordpress-app
spec:
selector:
matchLabels:
app: wordpress-app
template:
metadata:
labels:
app: wordpress-app
spec:
containers:
- name: wordpress-app
imagePullPolicy: Always
image: wordpress:6.3.1-apache
envFrom:
- configMapRef:
name: wordpress-configmap
ports:
- containerPort: 80
name: wordpress-port

โดยจากไฟล์จะเห็นว่าเราสร้าง wordpress โดยเอา environment จาก configMap : wordpress-configmap และเปิด port 80

สร้าง Service

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

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

จากไฟล์เราจะทำการสร้าง service แบบ NodePort ที่ port 32030 แล้วชี้ไปที่ pod ที่มี label เป็น app = wordpress-app

ทำการทดลองเปิด Browser แล้วเข้า URL - http://<ip ของเครื่องที่ run>:32030 ของผมเป็น http://192.168.156.101:32030 ซึ่งเมื่อเปิดแล้วจะได้เป็นดังภาพ

จะเห็นว่าเราสามารถเปิด Wordpress ได้ (ไม่ต้องตกใจครับ มันคือหน้า install เนื่องจากเราเข้ามาครั้งแรก)

ส่วน Ingress

สร้างไฟล์ wordpress-ingress.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wordpress-ingress
spec:
rules:
- host: "wordpress-application.com"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wordpress-service
port:
number: 80

สุดท้ายเป็นการสร้าง ingress ให้สามารถเข้าผ่าน port 80 ได้ โดยเราตั้งกฏว่าถ้าเข้ามาด้วย domain name : wordpress-application.com จะ route ไปที่ service : wordpress-service

เรามาทดสอบว่า ingress ว่าใช้งานได้ไหมโดยเปิด Browser ไปที่ : http://wordpress-application.com (อย่าลืม set file hosts ก่อนนะครับ)

ซึ่งจากภาพจะเห็นว่าเราสามารถเปิด wordpress ได้

ลองใช้งาน Wordpress เพื่อตรวจสอบ

ลองมาใช้งาน wordpress เพื่อทดสอบว่ามีปัญหาอะไรไหม โดยเราลองทำการ install และใช้งาน

จากนั้นลอง login ด้วย username , password ที่เราใส่ไปตอน install

ซึ่งเมื่อ Login เข้าไปแล้วจะเข้าไปหน้า Manage ดังภาพ

ซึ่งจากการทดลองเล่นนั้นไม่พบว่าติดปัญหาอะไร ซึ่งนั่นก็พอจะการันตีได้แล้วว่าเรา Deploy ตัว WordPress สำเร็จ

แล้วถ้าอยาก Deploy wordpress แบบนี้อีกหลายตัวล่ะ

ถ้าสมมติมีความต้องการ Deploy : wordpress อีกหลายๆตัวล่ะ เช่น บริษัท A , บริษัท B , บริษัท C wordpress ของละบริษัทต้องมี mysql ของตัวเอง domain name ของตัวเอง แล้วเราทำอะไรได้บ้าง

1. Copy File

ง่ายๆเลยก็คือเรา copy ไฟล์ที่เราสร้างทั้งหมดแล้วไปแก้ไฟล์ตามแต่ละบริษัทต้องการ วิธีนี้ก็ง่ายครับเราสามารถทำได้เลย แต่ปัญหาถ้าบริษัทที่ต้องการไม่ใช่ 3 บริษัทแต่เป็น 100 บริษัทล่ะจะเป็นยังไง แค่นึกภาพว่าต้อง copy ไฟล์ทุกไฟล์ที่ว่ามาแล้วไปไล่แก้แต่ละไฟล์ก็เหนื่อยแล้ว

2. ใช้ envsubst

เราจะใช้ envsubst ร่วมกับการใช้ environment เพื่อทำการ Deploy หลายๆ config

โดยเราจะใช้ envsubst กับค่าที่เปลี่ยนแปลงไปในแต่ละบริษัทตัวอย่างเช่น ตัวอย่างเช่น domain name โดยเราจะทำประมาณนี้

ทำการสร้างไฟล์ : wordpress-ingress-envsubst.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: wordpress-ingress
spec:
rules:
- host: "$DOMAIN_NAME"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: wordpress-service
port:
number: 80

จากนั้นลองสั่ง

1
2
export DOMAIN_NAME=wasinee-application.com
envsubst < wordpress-ingress-envsubst.yaml

จากภาพจะเห็นว่าค่าของ DOMAIN_NAME ที่เรา set ไว้นั้นไปแทนที่ค่าที่ $DOMAIN_NAME ในไฟล์ wordpress-ingress-envsubst.yaml โดยหากเราจะใช้วิธีนี้เราต้องทำดังต่อไปนี้

  1. สร้าง template file
  2. สร้าง environment file
  3. สร้าง script เพื่อทำการ deploy
สร้าง Template file

ทำการสร้างไฟล์ whoami-template.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
38
39
40
41
42
43
44
45
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: "$OWNER"
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: whoami-ingress
spec:
rules:
- host: "$DOMAIN_NAME"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: whoami-service
port:
number: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami-service
spec:
selector:
app: whoami
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

จากไฟล์จะเห็นว่ามีการใช้ environment 2 ในไฟล์คือ

  • OWNER : อันนี้มีไว้ set ใน whoami เพื่อใช้ดูว่าค่าถูก set จริงไหม และไปถูก Pod ไหม
  • DOMAIN_NAME : อันนี้มีไว้ set domain name ไว้ใช้ตอน ingress
สร้าง environment file

สร้างไฟล์ company-a.sh และใส่ค่าตามด้านล่าง

1
2
export DOMAIN_NAME=company-a.com
export OWNER=company-a

สร้างไฟล์ company-b.sh และใส่ค่าตามด้านล่าง

1
2
export DOMAIN_NAME=company-b.com
export OWNER=company-b

ส่วนนี้คือการกำหนด environment ตามที่เราจะไปใช้กับ Template และ script โดยเราจะทำ 2 ไฟล์คือ company-a.sh กับ company-b.sh ซึ่งถ้าโดยไม่ต้องสน file extension มันคือไฟล์ config ดีๆนี่เอง

สร้าง script เพื่อทำการ deploy

สร้างไฟล์ deploy.sh

โดย script นี้ไม่มีอะไรมากคือทำการรับ parameter 1 ตัวเข้ามา ซึ่ง parameter ที่รับเข้ามาคือไฟล์ environment จากนั้นก็จะไปทำการ set environment ด้วยคำสั่ง source จากนั้นจะทำการเช็ค namespace ว่าเคยมีไหม ถ้าไม่มีจะทำการสร้าง (namespace ขอติดไว้ก่อน แต่มองว่า namespace เหมือนเป็นจักรวาลใน marvel ละกัน ถ้าอยู่คนละ namespace ถือว่าอยู่คนละจักรวาล ยุ่งเกี่ยวกันยากถ้าไม่ผ่านช่องทางที่กำหนด) จากนั้นทำการใช้คำสั่ง envsubst เพื่อแทนค่า environment ไปใน template และส่งผลลัพธ์นั้นไปให้คำสั่ง kubectl apply ทำงานต่อ

1
2
3
4
5
6
7
8
9
10
11
12
echo $1

# Set environment
source $1

# ทำการสร้าง namespace ในกรณีที่ไม่มี namespace
if ! kubectl get ns $OWNER > /dev/null 2>&1; then
kubectl create namespace $OWNER
fi

# ทำการเรียกใช้ envsubst เพื่อแทนค่า environment ลงไปในไฟล์ template จากนั้นส่งค่าไฟล์ที่ถูกแทนค่าแล้วไปให้คำสั่ง kubectl apply
envsubst < whoami-template.yml | kubectl apply --namespace=$OWNER -f -
ลอง Deploy

ตัวอย่างไฟล์ทั้งหมดเกี่ยวกับการทำ template deploy สามารถดูได้ที่ : Link

เดี๋ยวผมจะทำตัวอย่างการ Deploy โดยใช้คำสั่งด้านล่าง

1
2
3
4
5
# deploy โดยใช้ environment config ของ company-a
bash deploy.sh company-a.sh

# deploy โดยใช้ environment config ของ company-b
bash deploy.sh company-b.sh

จากนั้นลองสั่งด้านล่างเพื่อเช็คว่าสามารถสร้าง pod และ service สำเร็จหรือไม่

1
2
kubectl get all --namespace company-a
kubectl get all --namespace company-b

ซึ่งจะเห็นว่ามีการสร้าง pod และ service ขึ้นมาทั้ง 2 namespace(จักรวาล) และสังเกตุจะเห็นว่าชื่อ pod และ service เหมือนกัน ซึ่งในกรณีที่เราทำแบบไม่กำหนด namespace มันจะไปอยู่ที่ default ซึ่งถ้ามีชื่อซ้ำกันมันจะทำการ update ดังนั้นถ้าอยากให้ชื่อซ้ำกันได้ก็ต้องแยกไปอยู่คนละ namespace (ลองเทียบในหนัง จักรวาล 616 มี Spider man ได้คนเดียว แต่จักรวาลอื่นก็มี Spider man ได้เหมือนกัน )

คราวนี้เรามาลองเปิด Browser แล้วเข้า URL ดังต่อไปนี้เพื่อเช็คว่า Ingress ทำงานได้ไหมและ Route ไปถูกเครื่องรึเปล่า (อย่าลืม set file hosts)

1
2
http://company-a.com/?env=true
http://company-b.com/?env=true

จากภาพจะเห็นว่า Ingress route ได้ถูกดูได้จากค่า OWNER ที่วงแดงไว้ ซึ่งตรงกับการ config ใน environment ของเรา

วิธีนี้ดียังไง

วิธีนี้ดีกว่าวิธีแก้ไฟล์ยังไง อย่างแรกคือคุณไม่ต้องตามไปแก้ไฟล์ yml ทุกไฟล์ คุณต้องทำการแก้แค่ไฟล์ environment config เพียงไฟล์เดียว ถ้าในตัวอย่างที่เราทำหากต้องเพิ่ม company-c เราก็แค่สร้างไฟล์ company-c.sh จากนั้นกำหนดค่าให้ไฟล์ environment config เท่านั้น แถมเวลาอัพเดทเกี่ยวกับการ Pod service ingress ต่างๆนาๆ เราสามารถแก้ที่ชุด template ที่เดียวเลย ไม่ต้องไปไล่แก้ทุกอันที่เคย Deploy (ถ้า Deploy ไปแล้ว 100 เจ้า แล้วจะเพิ่มอะไรสักอย่างแล้วต้องไปแก้ 100 เจ้า สยองขวัญน่าดู)

มีวิธีที่ดีกว่านี้ไหม

คำตอบคือมีครับ หากเราลองคิดดีๆการที่เราทำมันคือการทำงานเกี่ยวกับ Template processor แบบพวก PHP ที่สามารถใส่ตัวแปรไปใน HTML แล้วสั่ง rendering หรือ JSP และ Template engine อีกมากมาย โดยพวก Template engine พวกนี้จะมีลูกเล่นอีกมากมายที่สามารถทำได้ เช่น if else กับ template เช่น ถ้าค่า type เป็น VIP ให้ทำการใช้ template ส่วนนี้ด้วย หากไม่ใช่ให้ใช้ template ที่ไม่มีส่วนนี้ ซึ่งในงานจริงคุณต้องเจอมันแน่ๆ เช่น บริษัท W เป็นลูกค้าระดับ VIP ดังนั้นเขาต้องการการทำงานที่เร็วกว่าปกติ เราจึงต้องเอา REDIS มาทำ CACHE ในการดึงข้อมูล ดังนั้นจะต้องมี Pod ของ REDIS เพิ่มเข้ามา แต่เพิ่มเข้ามาแค่เฉพาะลูกค้าที่เป็น VIP ถ้าที่เราทำอยู่ตอนนี้น่าจะไม่พอในการทำแบบนั้น

แต่โชคดีครับที่ชาวโลกมีคนเห็นปัญหานี้เหมือนกันและได้สร้างสิ่งที่มาช่วยในการทำแบบนี้ซึ่งนั่นก็คือ HELM ซึ่ง HELM เนี่ยทำได้มากกว่าเป็น Template engine มันสามารถทำ version ของการ Deploy ได้ด้วย ประมาณว่าถ้าคุณทำตัว template deploy ใหม่ไปอัพเดทแล้วเกิดปัญหา คุณสามารถสั่งให้ helm เนี่ยกลับไปใช้ตัว deploy ก่อนหน้าที่ไม่มีปัญหาได้เลย

สรุป

สำหรับตอนนี้เราได้ลองทำโจทย์ Deploy Wordpress กันไปแล้ว ซึ่งจะเห็นว่าไม่ได้ยากมากเลย แถมเรายังได้รู้ปัญหาเกี่ยวกับ Deploy และทำให้เรามารู้จักกับ Tool ที่ใช้ตัว Deploy ที่เราจะไปเรียนรู้กันในตอนต่อไปซึ่งนั่นก็คือ HELM