컨테이너 환경에서 wrk로 벤치마크 테스트하기

개발

컨테이너 환경에서 wrk로 벤치마크 테스트하기
최종 수정일:

wrk란?

wrk는 C로 작성된 HTTP 벤치마킹 도구로, 다양한 옵션을 통해 HTTP 서버의 성능을 측정할 수 있습니다.
간단하게 사용할 수 있을 뿐 아니라, Lua 스크립트를 통해 더 복잡한 테스트를 수행할 수도 있습니다.

개인적으로 부하 테스트를 수행할 때 가장 많이 쓰는 툴인데, 컨테이너 등 제공되는 것이 없고 소스를 빌드해서 써야하는 소소한 번거로움이 있습니다.
한 번 빌드만 해두면 불편을 느낄 일이 없어 지금까지 잘 살다가, 이번에 네트워크가 격리된 환경에서도 테스트를 수행할 일이 많아서 이미지를 제작해보았습니다.

❗ 참고

자세한 코드는 docker-wrk에서 확인하실 수 있습니다.

이미지 생성

FROM alpine:3.20.3 AS builder
 
RUN apk add --no-cache \
    alpine-sdk \
    perl \
    git \
    openssl-dev \
    zlib-dev \
    linux-headers
 
RUN git clone https://github.com/wg/wrk.git \
    && cd wrk \
    && make
 
FROM alpine:3.20.3
 
RUN apk add --no-cache \
    libgcc \
    openssl \
    zlib
 
COPY --from=builder /wrk/wrk /usr/local/bin/
 
ENTRYPOINT ["wrk"]

빌드에 필요한 의존성을 설치하고, wrk 소스를 클론한 뒤 빌드하고, 최종적인 이미지에는 wrk 바이너리와 필수적인 라이브러리만 설치되도록 했습니다.
wrk2 버전도 거의 동일합니다.

컨테이너 실행

위 단계를 따라 Dockerfile을 생성하면, 이제 아래와 같이 wrk를 빌드하고 실행할 수 있게 되었습니다.

docker build -t wrk .
docker run wrk -t 1 -c 10 -d 5s https://www.example.com/

Kubernetes 클러스터 내부에서 실행

이제 이 이미지를 통해 Kubernetes 안에서 클러스터 내부에서만 통신하며 벤치마킹을 수행할 수 있습니다.

단순한 GET 요청 처리 테스트

apiVersion: batch/v1
kind: Job
metadata:
    name: wrk-load-test
    namespace: wrk
spec:
    template:
        spec:
            containers:
                - name: wrk
                  image: ghcr.io/marshallku/alpine-wrk:latest
                  args:
                      - "-t1"
                      - "-c4"
                      - "-d180s"
                      - "--timeout"
                      - "30s"
                      - "http://service.namespace.svc.cluster.local/"
                  resources:
                      requests:
                          cpu: "2"
                          memory: "1Gi"
                      limits:
                          cpu: "2"
                          memory: "1Gi"
            restartPolicy: Never
    backoffLimit: 4

간단하게 위와 같이 Job을 생성하면, 설정한 시간동안 벤치마킹을 수행합니다.

k9s에서 확인한 실행 결과

Lua 스크립트를 이용한 POST 요청 처리 테스트

ConfigMap을 통해 Lua 스크립트를 작성하고, 이를 Job 내부에서 사용할 수 있도록도 설정할 수 있습니다.

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
    name: wrk-scripts
    namespace: wrk
data:
    script.lua: |
        -- Read request body from file
        local file = io.open("/data/request.json", "r")
        wrk.body = file:read("*all")
        file:close()
 
        wrk.method = "POST"
        wrk.headers["Content-Type"] = "application/json"
        wrk.headers["Accept"] = "*/*"

위와 같이 ConfigMapdata:$SCRIPT_NAME.lua에 Lua 스크립트를 작성해 줍니다.
저는 요청에 사용할 JSON 파일이 수 MB가 되다보니 파일로 저장하게 해뒀는데, 어지간한 스크립트는 굳이 이렇게 따로 빼지 않아도 ConfigMap 내부에 전부 작성할 수 있습니다.

# job.yaml
apiVersion: batch/v1
kind: Job
metadata:
    name: wrk-load-test
    namespace: wrk
spec:
    template:
        spec:
            initContainers:
                - name: prepare-data
                  image: curlimages/curl:latest
                  command:
                      - /bin/sh
                      - -c
                      - |
                          set -e  # Exit on any error
                          echo "Downloading JSON file..."
                          curl -f -S -s -o /data/request.json "https://www.example.com/request.json"
                          echo "Download complete. Checking file..."
                          if [ ! -s /data/request.json ]; then
                            echo "Error: Downloaded file is empty"
                            exit 1
                          fi
                          echo "File check passed"
                  volumeMounts:
                      - name: data-volume
                        mountPath: /data
            containers:
                - name: wrk
                  image: ghcr.io/marshallku/alpine-wrk:latest
                  args:
                      - "-t1"
                      - "-c4"
                      - "-s"
                      - "/scripts/script.lua"
                      - "-d180s"
                      - "--timeout"
                      - "30s"
                      - "http://service.namespace.svc.cluster.local/"
                  resources:
                      requests:
                          cpu: "2"
                          memory: "1Gi"
                      limits:
                          cpu: "2"
                          memory: "1Gi"
                  volumeMounts:
                      - name: scripts-volume
                        mountPath: /scripts
                      - name: data-volume
                        mountPath: /data
            volumes:
                - name: scripts-volume
                  configMap:
                      name: wrk-scripts
                - name: data-volume
                  emptyDir: {}
            restartPolicy: Never
    backoffLimit: 4

다음으로 job.yaml을 위와 같이 수정했습니다.

scripts-volumewrk-scripts에서 마운트하도록 설정하면, 위 ConfigMap에 작성한 Lua 스크립트를 사용할 수 있습니다.
initContainersdata-volume은 request body에 사용할 JSON 파일을 다운로드하는 목적으로 사용했습니다.

마무리

이렇게 간단하게 컨테이너 환경에서 wrk로 부하를 줘보았습니다!

개인적으로는 이렇게 적당히 트래픽을 부어 보면서, HPA도 테스트하고, metric 수치도 조금씩 조절하는 등의 작업을 진행하고 있습니다.
서비스에 맞는 다양한 시나리오를 테스트하면서, 더욱 안정적인 서비스를 제공하시는 데 도움이 되었으면 좋겠습니다.

Report an issue