Kubernetes - 部署 WordPress 應用

# 前言

本文為部署 WordPress 應用到 Kubernetes cluster
內容非 Production 標準, 若要使用在 Production 環境尚有許多地方要改進



# 環境

Ray 是使用 KIND 當練習環境, 是個利用容器模擬出多 multi-node cluster 的應用

因為本文會用到 Metric Server, KIND 並不會安裝, 所以需要到 Metric server 官方頁面 來安裝

如果有遇到 x509: cannot validate certificate for ... because it doesn't contain any IP SANs 錯誤訊息, 請到 Metric server 官方 Github 下載 component yaml, 然後加入一個 arg --kubelet-insecure-tls



# 目標

  • 部署 WordPress 應用到 Kubernetes Cluster
  • 部署 MySQL 資料庫到 Kubernetes Cluster
  • WordPress 應用跟 MySQL 資料庫需可相通
  • 對 WordPress 應用做狀態檢查
  • 對 WordPress 應用建立 HPA 控管, 若負載高於定義, 需自動擴展


# 實作步驟



# 建立 Namespace

首先, 我們先建立一個 namespace, 本次練習中建立的物件都會部署到該 namespace

apiVersion: v1
kind: Namespace
metadata:
name: wordpress


# WordPress Deployment

接著, 要來撰寫 WordPress 的 Deployment yaml
Deployment 作用主要是 Pod 的管理器
為什麼不用 Pod 呢? 因為如果我們要具有水平擴展的能力的話, 那當流量大時, 1 個 Pod 可能並不足以應付, 所以我們使用 Deployment 來管理 Pod, 可視情況擴縮
直接來看 yaml

apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-deployment
namespace: wordpress
labels:
app: wordpress-deployment
spec:
minReadySeconds: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 40%
maxUnavailable: 40%
selector:
matchLabels:
app: wordpress-pod
template:
metadata:
labels:
app: wordpress-pod
spec:
initContainers:
- name: check-db-ready
image: imega/mysql-client
command:
- 'sh'
- '-c'
- 'until mysqladmin ping -h mysql-service &> /dev/null; do echo waiting; sleep 2; done'
containers:
- name: wordpress
image: wordpress
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: wp-pod-port
env:
- name: WORDPRESS_DB_HOST
value: mysql-service:3306
- name: WORDPRESS_DB_USER
value: wordpress
- name: WORDPRESS_DB_PASSWORD
value: wordpress
- name: WORDPRESS_DB_NAME
value: wordpress
resources:
limits:
cpu: 200m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
startupProbe:
tcpSocket:
port: wp-pod-port
initialDelaySeconds: 5
failureThreshold: 60
timeoutSeconds: 3
periodSeconds: 10
livenessProbe:
tcpSocket:
port: wp-pod-port
failureThreshold: 3
periodSeconds: 10
timeoutSeconds: 3
readinessProbe:
tcpSocket:
port: wp-pod-port
periodSeconds: 3
timeoutSeconds: 3

媽媽咪啊, 一大串 yaml 檔, 誒誒, 先別走啊! 你心裡是不是在想 是想搞死誰啊? 甭緊張, 我們一個一個來嘛…


# strategy

strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 40%
maxUnavailable: 40%

RollingUpdate, 如字面上的意思為滾動更新, 那為什麼要滾動呢? 如果今天我們觸發一個更新, 但 Deployment 把所有的 Pod 都給砍了, 一次性的啟動新的 Pod, 那服務當場就死翹翹了
所以凡事不要急, 要一個一個來, 心急吃不了熱豆腐!

至於 maxSurge 跟 maxUnavailable, 如果我現有的 Pod 是 100%, maxSurge 40% 表示最多可以啟動新的 Pod 到 140%, 而 maxUnavailable 也是同樣, 最多可減少到 60%
所以這兩個設定不可同時設為 0, 阿不然不能多也不能少, 是要滾動個鬼啊? 滾, 都給我滾…

更多細節部分可參考這篇, 或是官方文件


# selector

selector:
matchLabels:
app: wordpress-pod

selector 主要是定義 Deployment 所管理的 Pod 對象, 所以會跟 template 裡頭的 app: wordpress-pod 一樣


# initContainers

initContainers:
- name: check-db-ready
image: imega/mysql-client
command:
- 'sh'
- '-c'
- 'until mysqladmin ping -h mysql-service &> /dev/null; do echo waiting; sleep 2; done'

因為 wordpress 服務會用到 MySQL, 如果 MySQL Pod 啟動好之前, wordpress 服務就先好了, 那就使用者端可是會出錯的。
所以這邊啟動一個 mysql-client 的 container, 目的在於確定 MySQL 是否 ready, 話說這 image 我隨便弄弄找一個可以用的, 為了可用性, production 可是要把 image 自己準備好, 不然要是人家刪了 image 你就哭哭


# env

env:
- name: WORDPRESS_DB_HOST
value: mysql-service:3306
- name: WORDPRESS_DB_USER
value: wordpress
- name: WORDPRESS_DB_PASSWORD
value: wordpress
- name: WORDPRESS_DB_NAME
value: wordpress

env 我說了不算, 請參考 DockerHub WordPress


# resources

resources:
limits:
cpu: 200m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi

resources 簡單來說, requests 就是定義 Node 需要多少 resource 才可以放置這個 Pod, 而 limits 就是這個 Pod 最多可使用這個 Node 的多少資源
媽媽咪啊我怎麼有辦法說得這麼淺顯易懂, 我真是太帥了
細節可參考這篇文章


# Health Check

startupProbe:
tcpSocket:
port: wp-pod-port
initialDelaySeconds: 5
failureThreshold: 60
timeoutSeconds: 3
periodSeconds: 10
livenessProbe:
tcpSocket:
port: wp-pod-port
failureThreshold: 3
periodSeconds: 10
timeoutSeconds: 3
readinessProbe:
tcpSocket:
port: wp-pod-port
periodSeconds: 3
timeoutSeconds: 3

這麼多 probe, 簡單來說…
有些應用啟動時間比較久, 這時就可以用 startup probe
而 livenessProbe 如果是用來確定該 Pod 是否可用, 不可用可是會把 Pod 殺掉的!
readinessProbe 如果檢測沒過, 那 service 就不會把 traffic 往這個 Pod 送啦!

tcpSocket 只是檢測的一種方法之一, 也可使用 httpGet 或者 exec

細節部分可以參考這篇文章

好啦, 到這邊 wordpress-deployment 的部份我們都講完了, 下面繼續講, 不過如果有重複的我就不贅述囉!



# WordPress Service

service 的作用為將流量導向複數 Pods 的一個 endpoint, 可被其他的 kubernetes cluster 內的服務存取, 也可被外部存取 (需透過 mapping)
這個是不是短到有點沒存在感? 喂~ 我說的是 yaml 長度

apiVersion: v1
kind: Service
metadata:
name: wordpress-service
namespace: wordpress
spec:
type: NodePort
selector:
app: wordpress-pod
ports:
- port: 80
targetPort: wp-pod-port
name: wordpress-service-port
nodePort: 30000
protocol: TCP

首先, selector 會 select 出名稱為 wordpress-pod 的 Pods
接下來, 定義 service 本身的 port 為 80, 目標的 port 為 wp-pod-port, 還記得嗎? 這是定義在 wordpress-deployment 的 Pod port
再來, 定義此 service 的 port 為 wordpress-service-port
類型為 NodePort, 並定義確切的 Port
protocol 為 TCP, 預設值也是 TCP

NodePort 的作用在於將此 service map 到 Node 的 port, 所以我們就可以透過存取 Node 的 30000 port 來存取此 service, 這樣理解吧?



# MySQL Deployment

MySQL 是有狀態的服務, 基本上本文中的部署方式絕非 production 適用的

apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-deployment
namespace: wordpress
labels:
app: mysql-deployment
spec:
selector:
matchLabels:
app: mysql-pod
template:
metadata:
labels:
app: mysql-pod
spec:
containers:
- name: mysql
image: mysql:8.0
imagePullPolicy: IfNotPresent
args:
- --default_authentication_plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
ports:
- containerPort: 3306
name: mysql-pod-port
env:
- name: MYSQL_ROOT_PASSWORD
value: mysql_root_password
- name: MYSQL_DATABASE
value: wordpress
- name: MYSQL_PASSWORD
value: wordpress
- name: MYSQL_USER
value: wordpress
volumeMounts:
- mountPath: /var/lib/mysql
name: mysql-db
volumes:
- name: mysql-db
hostPath:
path: /var/lib/mysql

媽媽咪啊, 現在都流行一大串的 yaml 啊? 上面講過的我這邊就不會再贅述囉, 讓我們一個一個來!


# arg

args:
- --default_authentication_plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci

這個主要是定義資料庫的起始設定, 可參考官方 Dockerhub


# env

同上, env 也是定義這個資料庫的一些設定, 這邊要特別注意, wordpress env 會定義使用的 DB_HOST … 等等的各項資訊, 所以我們在建立資料庫時, 也要跟 wordpress env 定義的一樣, 這樣 wordpress 啟動之後才可以跟 MySQL 接上

env:
- name: MYSQL_ROOT_PASSWORD
value: mysql_root_password
- name: MYSQL_DATABASE
value: wordpress
- name: MYSQL_PASSWORD
value: wordpress
- name: MYSQL_USER
value: wordpress

# volume

volumeMounts:
- mountPath: /var/lib/mysql
name: mysql-db
volumes:
- name: mysql-db
hostPath:
path: /var/lib/mysql

跟 volume 有關的就一次解決, 注意這邊的 example 格式是不對的啊! 只是為了解說讓他們同排, 正確的請參考 yaml
這邊主要是做數據持久化, 將 Pod 中的 data 掛載到 Node 當中
mountPath 為 pod 中的 data 位置, 至於為什麼是這個位置? 你問我我問誰去… 不是啦哈哈, 可參考 官方 DockerHub, 或者也可以自己進到 container 去看看

volumes 為定義一個位置, 這個 volumes 可以讓 Pod 掛載到 Pod 內的任意位置, volumes 的種類有很多種, 這邊使用 hostPath 是定義在 Node 內的一個位置, 當然這不是一個好的做法, 因為當我 MySQL 重啟後, 是否還會被 schedule 到同一個 Node 是未知數, 所以這邊應該還要加個 Node selector 的設定, 不過暫且先這樣吧!

所以掛載後, Pods 內的 /var/lib/mysql 就會跟 Node 的 /var/lib/mysql 數據同步, 只要 Node disk 沒掛資料就可以持久化保存



# MySQL Service

apiVersion: v1
kind: Service
metadata:
name: mysql-service
namespace: wordpress
spec:
selector:
app: mysql-pod
ports:
- port: 3306
name: mysql-service-port
protocol: TCP
targetPort: mysql-pod-port

還是 service 比較可愛, 短小精悍!
基本上跟 wordpress service 相同, 這邊不贅述



# HPA

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: wordpress-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: wordpress-deployment
minReplicas: 1
maxReplicas: 15
targetCPUUtilizationPercentage: 50

假設今天流量突然暴增, 那我原本只有一個 wordpress pod, 怎麼辦? 使用者端畫面肯定會整個卡住, 因為光是一個 pod 無法處理這麼多 requests, 這時我們就可以使用 HPA

簡單來說, 他設定了最少一個 Pod, 最多 15 個 Pod, 如果目前每個 Pod 的 CPU 負載量超過 50% 的話, 那就再啟動一個 Pod, 直到負載量低於 50% 或者已達 15 個 Pod



# 部署

接下來就到了部署的時刻啦! 接下來只要將上面介紹的 yaml 檔案一個一個 apply 就可以完成部署啦, 為求簡單化, 我會將全部的 yaml 寫成一個 yaml, 並放在本文最下方
接下來只要部署此 yaml 檔就行啦!

kubectl apply -f all-in-one-yaml


# 功能測試

完成部署後, 我們訪問 yourNodeIp:30000 就可以看到 WordPress 的首頁啦!

然後就可以進去玩耍一番!



# HPA 測試

接著, 我們要對首頁來發出大量的 request, 看看 HPA 是否運作正常

while true; do wget -q -O- localhost:30000; done

然後耐心地看看 HPA 狀態

kubectl get hpa wordpress-hpa -n wordpress

好像沒在動? 給點耐心嘛!

哇靠! 有了有了!

接下來, 看看 Pod 是否有變多了

kubectl get pods -A

有了有了! 等到 autoscale 完成, 再去刷刷頁面, 會發現不會卡了呢!



# 結論

本文就到此告一段落啦! 我們下次見!



# Questions and Answers

以下的 Kubernetes example code 代表什麼意思?
  • Example:
    apiVersion: autoscaling/v1
    kind: HorizontalPodAutoscaler
    metadata:
    name: wordpress-hpa
    spec:
    scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: wordpress-deployment
    minReplicas: 1
    maxReplicas: 15
    targetCPUUtilizationPercentage: 50
  • Answer:
    建立一個 auto scaler, 最少一個 Pod, 最多 15 個 Pod, 當所有 Pod 的平均 CPU 使用量達到 50% 時觸發
以下的 Kubernetes example code 代表什麼意思?
  • Example:
    apiVersion: v1
    kind: Service
    metadata:
    name: wordpress-service
    namespace: wordpress
    spec:
    type: NodePort
    selector:
    app: wordpress-pod
    ports:
    - port: 80
    targetPort: wp-pod-port
    name: wordpress-service-port
    nodePort: 30000
    protocol: TCP
  • Answer:
    apiVersion: v1
    kind: Service
    metadata:
    name: wordpress-service
    namespace: wordpress
    spec:
    type: NodePort
    # 要導向的 Pod
    selector:
    app: wordpress-pod
    ports:
    # service 的 port
    - port: 80
    # 目標 Pod 的 port, 可直接使用 name mapping
    targetPort: wp-pod-port
    # 此 service port 的 name
    name: wordpress-service-port
    # map 到 Node 上的 port
    nodePort: 30000
    protocol: TCP
以下的 Kubernetes example code 代表什麼意思?
  • Example:
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: wordpress-deployment
    namespace: wordpress
    labels:
    app: wordpress-deployment
    spec:
    minReadySeconds: 5
    selector:
    matchLabels:
    app: wordpress-pod
    template:
    metadata:
    labels:
    app: wordpress-pod
    spec:
    containers:
    - name: wordpress
    image: wordpress
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 80
    name: wp-pod-port
    env:
    - name: WORDPRESS_DB_HOST
    value: mysql-service:3306
    - name: WORDPRESS_DB_USER
    value: wordpress
    - name: WORDPRESS_DB_PASSWORD
    value: wordpress
    - name: WORDPRESS_DB_NAME
    value: wordpress
  • Answer:
    定義 env, 可參考 WordPress DockerHub
以下的 Kubernetes example code 代表什麼意思?
  • Example:
    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: wordpress-deployment
    namespace: wordpress
    labels:
    app: wordpress-deployment
    spec:
    minReadySeconds: 5
    selector:
    matchLabels:
    app: wordpress-pod
    template:
    metadata:
    labels:
    app: wordpress-pod
    spec:
    initContainers:
    - name: check-db-ready
    image: imega/mysql-client
    command:
    - 'sh'
    - '-c'
    - 'until mysqladmin ping -h mysql-service &> /dev/null; do echo waiting; sleep 2; done'
    containers:
    - name: wordpress
    image: wordpress
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 80
    name: wp-pod-port
  • Answer:
    使用 mysqladmin 確定 MySQL 已可提供服務後, 再啟動 wordpress container
以下的 Kubernetes Deployment code, selector 的對象是?
  • Example:
    selector:
    matchLabels:
    app: wordpress
  • Answer:
    該 Deployment 所管理的 Pod


# all-in-one YAML

apiVersion: v1
kind: Namespace
metadata:
name: wordpress

---

apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-deployment
namespace: wordpress
labels:
app: mysql-deployment
spec:
selector:
matchLabels:
app: mysql-pod
template:
metadata:
labels:
app: mysql-pod
spec:
containers:
- name: mysql
image: mysql:8.0
imagePullPolicy: IfNotPresent
args:
- --default_authentication_plugin=mysql_native_password
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
ports:
- containerPort: 3306
name: mysql-pod-port
env:
- name: MYSQL_ROOT_PASSWORD
value: mysql_root_password
- name: MYSQL_DATABASE
value: wordpress
- name: MYSQL_PASSWORD
value: wordpress
- name: MYSQL_USER
value: wordpress
volumeMounts:
- mountPath: /var/lib/mysql
name: mysql-db
volumes:
- name: mysql-db
hostPath:
path: /var/lib/mysql

---

apiVersion: v1
kind: Service
metadata:
name: mysql-service
namespace: wordpress
spec:
selector:
app: mysql-pod
ports:
- port: 3306
name: mysql-service-port
protocol: TCP
targetPort: mysql-pod-port

---

apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-deployment
namespace: wordpress
labels:
app: wordpress-deployment
spec:
minReadySeconds: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2
maxUnavailable: 25%
selector:
matchLabels:
app: wordpress-pod
template:
metadata:
labels:
app: wordpress-pod
spec:
initContainers:
- name: check-db-ready
image: imega/mysql-client
command:
- 'sh'
- '-c'
- 'until mysqladmin ping -h mysql-service &> /dev/null; do echo waiting; sleep 2; done'
containers:
- name: wordpress
image: wordpress
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: wp-pod-port
env:
- name: WORDPRESS_DB_HOST
value: mysql-service:3306
- name: WORDPRESS_DB_USER
value: wordpress
- name: WORDPRESS_DB_PASSWORD
value: wordpress
- name: WORDPRESS_DB_NAME
value: wordpress
resources:
limits:
cpu: 200m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
startupProbe:
tcpSocket:
port: wp-pod-port
initialDelaySeconds: 5
failureThreshold: 60
timeoutSeconds: 3
periodSeconds: 10
livenessProbe:
tcpSocket:
port: wp-pod-port
failureThreshold: 3
periodSeconds: 10
timeoutSeconds: 3
readinessProbe:
tcpSocket:
port: wp-pod-port
periodSeconds: 3
timeoutSeconds: 3

---

apiVersion: v1
kind: Service
metadata:
name: wordpress-service
namespace: wordpress
spec:
type: NodePort
selector:
app: wordpress-pod
ports:
- port: 80
targetPort: wp-pod-port
name: wordpress-service-port
nodePort: 30000
protocol: TCP

---

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
name: wordpress-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: wordpress-deployment
minReplicas: 1
maxReplicas: 15
targetCPUUtilizationPercentage: 50
Kubernetes - DaemonSet Kubernetes - Dashboard - 使用自定義 Service Account 登入

留言

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×