Coding Gun

การทำ Distributed load test บน Kubernetes cluster

ในตัวอย่างนี้เราจะ deploy locust ลงใน kubernetes cluster เพื่อทำ distributed load test ซึ่งประโยชน์ของการใช้ locust ใน Kubernetes cluster คือ

  1. ทำ distributed load test
  2. สามารถทดสอบ services ต่างๆที่อยู่ใน Kubernetes ได้
  3. สามารถทดสอบ services ทุกๆ services ใน Kubernetes จากภายใน cluster ไม่ต้องผ่าน ingress

Distributed load test

distributed load test คือการทำ load test ที่ส่ง request มาจากหลายๆที่ ซึ่งในกรณีเราจะทำการส่ง request ออกมาจาก containers ที่อยู่ในแต่ละ Pods ซึ่งถ้าเราใช้ locust ใน Kubernetes เราจะสามารถ generate transaction ได้มากกว่าใช้ผ่าน command line แบบปกติ

Distribute load test

Master จะเป็นคนออกคำสั่งให้เริ่มทำ load test ซึ่งหลังจากได้รับคำสั่งให้ start load test Worker แต่ละ node จะส่ง request ออกไปยัง target(application ที่เราต้องการทดสอบ) ซึ่งหลังจากได้ response กลับมา locust จะนำผลลัพธ์ที่ได้กลับไปรวมไว้ที่ Master

Step 1. Deploy Application(Target)

ก่อนอื่นเราต้องทำการ deploy application หรือ services ที่เราต้องการทดสอบลงไปใน Kubernetes ก่อนโดยเริ่มจากการ clone ตัวอย่างจาก

$ git clone https://github.com/eon01/kubernetes-locust-example.git

หลังจากนั้นก็ทำการ deploy application ที่ต้องการทดสอบลงไปใน Kubernetes cluster(เราต้องมี Kubernetes cluster ก่อน)

$ cd guestbook
$ kubectl apply -f .

หลังจากจบ step ที่ 1 เราจะได้โครงสร้างของ file เป็นแบบนี้

├── guestbook
│   ├── frontend-service.yml
│   ├── frontend-deployment.yml
│   ├── redis-master-deployment.yml
│   ├── redis-master-service.yml
│   ├── redis-slave-deployment.yml
│   ├── redis-slave-service.yml

Step 2. สร้าง locustfile ขึ้นมา

สร้าง locustfile ขึ้นมาแล้วทำการเขียน python script ในตัวอย่างนี้จะเก็บไว้ใน folder docker/locust-tasks ที่ใส่ไว้ใน folder docker เพราะเดี๋ยวเราจะต้องมี Dockerfile และ shell script เพื่อกำหนด parameters ของ locust cli ในขั้นตอนต่อไป เพื่อให้ง่ายต่อการค้นหาผมเลยรวมไฟล์ของ docker ทั้งหมดไว้ใน folder เดียวกัน

from locust import HttpUser, task, between

class MyUser(HttpUser):
   wait_time = between(5, 15)

    @task
    def index(self):
       self.client.get("/")
    @task
    def update(self):
       self.client.get("/guestbook.php?cmd=set&key=messages&value=,JohnDietish,")

หลังจาก Step ที่ 2 แล้วเราก็จะได้โครงสร้างของ file แบบนี้(เพิ่ม requirement.txt และ Locustfile)

├── docker
│   ├── locust-tasks
│   |   ├── Locustfile
│   |   ├── requirement.txt
├── guestbook
│   ├── ...

Step 3. สร้าง Locust docker image

สร้าง Dockerfile เพื่อนำ Locustfile และ run.sh เข้าไปใน container โดยที่เนื้อหาของ Dockerfile จะมีดังนี้

FROM locustio/locust:1.2.3

# นำ locustfile ที่อยู่ใน folder locust-tasks เข้าไปไว้ใน container
COPY locust-tasks /locust-tasks
COPY run.sh .

# ติดตั้ง package ที่ต้องใช้ในการ run locustfile
RUN pip install -r /locust-tasks/requirements.txt

# เปลี่ยนให้ run.sh สามารถ execute ได้(755)
User root
RUN chmod 755 run.sh
# หลังจากเปลี่ยน user เป็น root แล้วให้เปลี่ยนกลับมาเป็น user ที่มีสิทธิต่ำเสมอ
User locust

# สั่งให้ run.sh ทำงาน
ENTRYPOINT ["bash", "./run.sh"]

Dockerfile นี้จะเรียก run.sh เพื่อ start node ต่างๆ โดยจะใช้ตัวแปร LOCUST_OPTS เป็นตัวกำหนด target และใช้ LOCUST_MODE เป็นตัวกำหนดว่าเป็น master หรือ worker

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
LOCUST="locust"
LOCUS_OPTS="-f /locust-tasks/ --host=$TARGET_HOST"

# Default = standalone
LOCUST_MODE=${LOCUST_MODE:-standalone}

# Master node
if [[ "$LOCUST_MODE" = "master" ]]; then
    LOCUS_OPTS="$LOCUS_OPTS --master --master-port=5557"

# Worker/slave node
elif [[ "$LOCUST_MODE" = "worker" ]]; then
    LOCUS_OPTS="$LOCUS_OPTS --worker --master-port=5557 --master-host=$LOCUST_MASTER_URL"
fi

$LOCUST $LOCUS_OPTS

หลังจากที่สร้าง Dockerfile และ run.sh ขึ้นมาแล้วโครงสร้างของ file จะเป็นแบบนี้

├── docker
│   ├── locust-tasks
│   |   ├── ...
│   ├── Dockerfile
│   ├── run.sh
├── guestbook
│   ├── ...

หลังจากนั้นให้ลอง build docker image ขึ้นมาบน local ด้วยคำสั่ง

$ cd docker
$ docker build -t locust:guestbook

Step 4. Deploy Locust ลงไปใน Kubernetes

เราต้องทำการ deploy locust ลงไปใน Kubernetes โดยใช้ distributed mode ซึ่งใน Kubernetes resources จะประกอบไปด้วย

โดยที่เราจะเก็บไฟล์ทั้ง 3 ไฟล์นี้ไว้ใน folder k8s โดย path ของ file จะเป็นแบบนี้

├── docker
│   ├── locust-tasks
│   |   ├── ...
│   ├── Dockerfile
│   ├── run.sh
├── guestbook
│   ├── ...
├── k8s
│   ├── locust-mater-deployment.yml
│   ├── locust-master-service.yml
│   ├── locust-worker-deployment.yml

Locust Master Deployment

ใน Kubernetes Deployment ในบรรทัดที่ 21-22 เราจะกำหนด LOCUST_MODE เป็น master ซึ่งจะเข้าเงื่อนไขในบรรทัดที่ 8 ของ run.sh

 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
apiVersion: apps/v1
kind: Deployment
metadata:
 name: locust-master
 labels:
   name: locust-master
spec:
 replicas: 1
 selector:
   matchLabels:
     app: locust-master
 template:
   metadata:
     labels:
       app: locust-master
   spec:
     containers:
       - name: locust-master
         image: locust:guestbook
         env:
           - name: LOCUST_MODE
             value: master
           - name: TARGET_HOST
             value: http://frontend-internal
         ports:
           - name: loc-master-web
             containerPort: 8089
             protocol: TCP
           - name: loc-master-p1
             containerPort: 5557
             protocol: TCP
           - name: loc-master-p2
             containerPort: 5558
             protocol: TCP

กำหนด TARGET_HOST ยังไง?

เนื่องจากไฟล์ตัวอย่าง frontend-service.yml(อยู่ใน folder guestbook) ที่เรานำมาใช้จะระบุชื่อ service ของ guestbook application ไว้ในบรรทัดที่ 4 เวลาที่เราระบุ target เราถึงต้องใช้ TARGET_HOST เป็น http://frontend-internal

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
apiVersion: v1
kind: Service
metadata:
  name: frontend-internal
  labels:
    app: guestbook
    tier: frontend
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: guestbook
    tier: frontend

Locust Master Service

ใน Kubernetes Service ของ Master จะเป็น resource ที่ระบุ MASTER_URL เพื่อให้ Worker สามารถ connect เข้ามายัง Master ได้

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
kind: Service
apiVersion: v1
metadata:
 name: locust-master
 labels:
   app: locust-master
spec:
 ports:
   - port: 5557
     targetPort: loc-master-p1
     protocol: TCP
     name: loc-master-p1
   - port: 5558
     targetPort: loc-master-p2
     protocol: TCP
     name: loc-master-p2
   - port: 8089
     name: loc-master-web
 selector:
   app: locust-master

Locust Worker Deployment

ใน Kubernetes Deployment สำหรับ Worker นั้นจะกำหนด replicas ไว้เป็น 3 นั่นหมายความว่าจะมี container ที่ทำการส่ง request ไปยัง target อยู่ 3 ตัวด้วยกัน ซึ่งใน container แต่ละตัวจะกำหนด Environment Variables ไว้ดังนี้

  1. LOCUST_MODE กำหนดเป็น worker
  2. LOCUST_MASTER_URL สำหรับ URL ของ master เราจะระบุตาม Locust Master Service ที่ได้ระบุไว้ในหัวข้อก่อนหน้า
  3. TARGET_HOST เราจะระบุตามชื่อของ service ที่ได้ deploy ไว้ในไฟล์ frontend-service.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
apiVersion: apps/v1
kind: Deployment
metadata:
 name: locust-worker
 labels:
   name: locust-worker
spec:
 replicas: 3
 selector:
   matchLabels:
     app: locust-worker
 template:
   metadata:
     labels:
       app: locust-worker
   spec:
     containers:
       - name: locust-worker
         image: locust:guestbook
         env:
           - name: LOCUST_MODE
             value: worker
           - name: LOCUST_MASTER_URL
             value: locust-master
           - name: TARGET_HOST
             value: http://frontend-internal

เมื่อสร้าง Manifest file ครบแล้วก็ทำการ deploy ทุกๆไฟล์ลงไปใน kubernetes ด้วยคำสั่ง

$ cd k8s
$ kubectl apply -f .

Step 5. Running test

เมื่อทำครบทุกขั้นตอนแล้วเราก็จะทำการ run load test กันซักที ซึ่งเราต้อง forward port จากข้างนอกเข้าไปใน Locust Master ด้วยคำสั่ง

$ kubectl port-forward svc/locust-master-web 8089:8089

เมื่อเข้าหน้าแรกได้แล้วก็เริ่ม start new load test และกำหนด parameters ต่างๆ ดังนี้

หลังจากนั้นเราก้จะได้ผลลัพธ์ ดังรูป

Result from locust distributed load test

อ่านต่อเพิ่มเติมได้ที่

Phanupong Permpimol
Follow me