IT STUDY LOG

Sprint - aws EC2와 K6를 이용한 성능테스트 본문

devops bootcamp 4/pair/team log

Sprint - aws EC2와 K6를 이용한 성능테스트

roheerumi 2023. 6. 7. 16:27

#학습 목표

  • k6 도구 활용법을 학습
  • 성능 테스트 유형별 스크립트를 작성
  • aws ec2 인스턴스를 모니터링
  • aws에서 제공하는 버스트 크레딧을 이해

 

#과제 항목별 진행 상황

✏️  aws ec2 인스턴스 생성 및 성능 테스트를 위해 서버를 컨테이너로 배포

Step 1 : EC2 인스턴스 생성

- 버스트 기능이 있는 t2micro를 생성하고 운영체제는 ubuntu 20.04로 설정

Step 2 : EC2 인스턴스에 SSH 접속 후 도커 설치

1) SSH 접속

$ ssh -i "****.pem" ubuntu@ec2-**-***-**-**.ap-northeast-2.compute.amazonaws.com
The authenticity of host 'ec2-**-**-**-**.ap-northeast-2.compute.amazonaws.com (**.***.**.**)' can't be established.
ECDSA key fingerprint is SHA256:*
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes

2) 도커 설치 스크립트 실행

#!/bin/sh

#업데이트 및 HTTP 패키지 설치
sudo apt update -y
sudo apt-get install -y ca-certificates \
    curl \
    software-properties-common \
    apt-transport-https \
    gnupg \
    lsb-release
# GPG 키 및 저장소 추가
sudo mkdir -p /etc/apt/keyrings

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# 도커엔진 설치
sudo apt update
sudo apt install docker-ce docker-ce-cli containerd.io

Step 3 : 도커 컨테이너 배포

1) 도커 허브에서 가져온 이미지를 바탕으로 컨테이너 배포

$ sudo docker run --name <서버명> -d -p 8080:8080 sebcontents/cozserver:1.0

2) 성공적으로 배포되었는지 확인

$ curl http://localhost:8080

    <h1>CozServer에 오신 것을 환영합니다!</h1>
    <div>버전: v1.0</div>
    <div>파드 이름: 343ecf8c14d6</div>
    <div>::ffff:172.17.0.1에서 오셨군요!</div>

 

✏️  k6 설치

Step 1 : snapd를 이용해 k6 설치

$ sudo apt update
$ sudo apt install snapd

$ sudo snap install k6
Download snap "k6" (29) from channel "stable"                                                                                                                                         |
k6 v0.43.1 from Thomas Bille (tbmb) installed

Step 2 : 다양한 테스트 스크립트 실행

# 로컬에 있는 스크립트를 EC2 인스턴스로 업로드
# scp -i "키페어 경로 주소" [-r 옵션, 디렉터리] <파일/디렉터리명> <EC2 인스턴스 퍼블릭 주소>[-r 옵션 사용시, :/home/ubuntu 등 디렉터리 명시]

# ec2 인스턴스에서 로컬로 다운로드 받을 경우, 파일/디렉토리 위치를 반대로 입력하면 됨
$ scp -i "***.pem" -r ./k6_practice ubuntu@ec2-**-***-**-***.ap-northeast-2.compute.amazonaws.com:/home/ubuntu

 

✏️  k6를 이용해 EC2 인스턴스 성능 테스트

Step 0 : 부하(Load) 테스트 유형

출처 : k6.io/docs

유형 vus/처리량 지속 사용 사례
smoke 낮은 빠름(초 또는 분) 새로운 코드나 변경이 있을 때마다. 스크립트, 기준 시스템 메트릭 및 변경 사항의 편차를 확인합니다.
load 평균 생산량 중간 (15-60분) 종종 시스템이 평균 사용으로 성능을 유지하는지 확인하기 위해
stress 높음(평균 이상) 중간 (15-60분) 시스템이 관리 방법을 확인하기 위해 평균 이상의 부하를 받을 수 있는 경우
soak 평균 긺 (시간) 장기간 연속 사용 시 체크 시스템으로 변경 후
spike 매우 높음 빠름(초에서 분) 드물게 시스템 위험이 갑자기 돌진할 때
breakpoint 쉬는 시간까지 증가 필요한 만큼 시스템의 상한선을 찾기 위해 몇 번

Step 1 : 각각의 스크립트를 실행해 보고 ec2 인스턴스의 CPU 사용률 대시보드를 확인

1) basic test 실행

더보기
1-1) basic_test.js 코드
// basic_test.js

import http from 'k6/http';
import { sleep } from 'k6';
export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    vus: 10,
    duration: '30s'
};

export default () => {
    http.get('http://localhost:8080')
    sleep(1);
};

1-2) 스크립트 실행 결과

$ k6 run basic_test.js 

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: basic_test.js
     output: -

  scenarios: (100.00%) 1 scenario, 10 max VUs, 1m0s max duration (incl. graceful stop):
           * default: 10 looping VUs for 30s (gracefulStop: 30s)


     data_received..................: 134 kB 4.4 kB/s
     data_sent......................: 24 kB  792 B/s
     http_req_blocked...............: avg=1.15ms   min=3.78µs   med=5.31µs  max=46.08ms p(90)=8.05µs  p(95)=34.03µs 
     http_req_connecting............: avg=149.89µs min=0s       med=0s      max=9.56ms  p(90)=0s      p(95)=0s      
     http_req_duration..............: avg=4.81ms   min=886.72µs med=4.12ms  max=21.74ms p(90)=8.24ms  p(95)=9.48ms  
       { expected_response:true }...: avg=4.81ms   min=886.72µs med=4.12ms  max=21.74ms p(90)=8.24ms  p(95)=9.48ms  
     http_req_failed................: 0.00%  ✓ 0        ✗ 300 
     http_req_receiving.............: avg=63.35µs  min=19.53µs  med=36.28µs max=1.87ms  p(90)=85.21µs p(95)=104.87µs
     http_req_sending...............: avg=267.92µs min=9.7µs    med=14.28µs max=7.58ms  p(90)=56.42µs p(95)=892.14µs
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s      max=0s      p(90)=0s      p(95)=0s      
     http_req_waiting...............: avg=4.48ms   min=811.75µs med=3.86ms  max=21.64ms p(90)=7.87ms  p(95)=8.75ms  
     http_reqs......................: 300    9.903457/s
     iteration_duration.............: avg=1s       min=1s       med=1s      max=1.06s   p(90)=1.01s   p(95)=1.01s   
     iterations.....................: 300    9.903457/s
     vus............................: 10     min=10     max=10
     vus_max........................: 10     min=10     max=10


running (0m30.3s), 00/10 VUs, 300 complete and 0 interrupted iterations
default ✓ [======================================] 10 VUs  30s

2) load test 실행

- 평균 부하 테스트는 일반적인 부하에서 시스템이 어떻게 작동하는지 평가하며, 일반적인 부하는 프로덕션의 일상일 수도 있고 평균 순간일 수도 있음

더보기

2-1) load_test.js 코드

// load_test.js

import http from "k6/http";

import { sleep } from 'k6';

export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    stages: [
        { duration: '5m', target: 100 },
        { duration: '10m', target: 100 },
        { duration: '5m', target: 0 },   
    ],
    thresholds: {
        http_req_duration: ['p(99)<150'],
    }
};

export default () => {

    let respose = http.get("http://localhost:8080");

    
    sleep(1);
};

2-2) 스크립트 실행 결과

$ k6 run load_test.js 

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: load_test.js
     output: -

  scenarios: (100.00%) 1 scenario, 100 max VUs, 20m30s max duration (incl. graceful stop):

     data_received..................: 40 MB  33 kB/s
     data_sent......................: 7.2 MB 6.0 kB/s
     http_req_blocked...............: avg=8.63µs   min=3.23µs   med=5.35µs  max=15.74ms  p(90)=7.28µs   p(95)=11.25µs 
     http_req_connecting............: avg=508ns    min=0s       med=0s      max=2.89ms   p(90)=0s       p(95)=0s      
   ✓ http_req_duration..............: avg=5.8ms    min=543.08µs med=2.36ms  max=106.57ms p(90)=15.71ms  p(95)=19.98ms 
       { expected_response:true }...: avg=5.8ms    min=543.08µs med=2.36ms  max=106.57ms p(90)=15.71ms  p(95)=19.98ms 
     http_req_failed................: 0.00%  ✓ 0         ✗ 89571
     http_req_receiving.............: avg=133.07µs min=17.6µs   med=66.65µs max=32.74ms  p(90)=241.38µs p(95)=271.32µs
     http_req_sending...............: avg=275.35µs min=8.5µs    med=14.04µs max=56.45ms  p(90)=46.61µs  p(95)=289.23µs
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s      max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=5.39ms   min=483µs    med=2.19ms  max=86.73ms  p(90)=14.72ms  p(95)=18.67ms 
     http_reqs......................: 89571  74.603413/s
     iteration_duration.............: avg=1s       min=1s       med=1s      max=1.11s    p(90)=1.01s    p(95)=1.02s   
     iterations.....................: 89571  74.603413/s
     vus............................: 1      min=1       max=100
     vus_max........................: 100    min=100     max=100


running (20m00.6s), 000/100 VUs, 89571 complete and 0 interrupted iterations
default ✓ [======================================] 000/100 VUs  20m0s

3) stress test 실행

- 스트레스 테스트는 부하가 평소보다 무거울 때 시스템이 어떻게 작동하는지 평가함

- 스트레스 테스트의 부하 패턴은 평균 부하 테스트와 비슷하나,  주요 차이점은 더 높은 부하를 가한다는 점

더보기

3-1) stress_test.js 코드

// stress_test.js

import http from "k6/http";
import { sleep, check } from 'k6';

export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    stage: [
        { duration: '2m', target: 100 },
        { duration: '5m', target: 100 },
        { duration: '2m', target: 200 },
        { duration: '5m', target: 200 },
        { duration: '2m', target: 300 },
        { duration: '5m', target: 300 },
        { duration: '2m', target: 400 },
        { duration: '5m', target: 400 },
        { duration: '10m', target: 0 },

    ],
};


export default function () {
    const res = http.get('http://localhost:8080');
    check(res, { 'status was 200': (r) => r.status == 200 });
    sleep(1);
  };

3-2) 스크립트 실행 결과

$ k6 run stress_test.js 

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: stress_test.js
     output: -

  scenarios: (100.00%) 1 scenario, 400 max VUs, 38m30s max duration (incl. graceful stop):
           * default: Up to 400 looping VUs for 38m0s over 9 stages (gracefulRampDown: 30s, gracefulStop: 30s)


running (02m04.4s), 100/400 VUs, 6371 complete and 0 inter


     ✓ status was 200

     checks.........................: 100.00% ✓ 503151     ✗ 0     
     data_received..................: 224 MB  107 kB/s
     data_sent......................: 40 MB   19 kB/s
     http_req_blocked...............: avg=6.85µs   min=3.22µs   med=5.31µs   max=16.32ms  p(90)=6.16µs   p(95)=8.03µs  
     http_req_connecting............: avg=340ns    min=0s       med=0s       max=4.41ms   p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=2.13ms   min=555.81µs med=1.05ms   max=160.51ms p(90)=3.51ms   p(95)=6.84ms  
       { expected_response:true }...: avg=2.13ms   min=555.81µs med=1.05ms   max=160.51ms p(90)=3.51ms   p(95)=6.84ms  
     http_req_failed................: 0.00%   ✓ 0          ✗ 503151
     http_req_receiving.............: avg=124.85µs min=18.43µs  med=74.89µs  max=43.03ms  p(90)=208.17µs p(95)=249.22µs
     http_req_sending...............: avg=47.56µs  min=8.17µs   med=13.75µs  max=49.6ms   p(90)=37.65µs  p(95)=52.29µs 
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=1.96ms   min=444.39µs med=857.16µs max=140.54ms p(90)=3.32ms   p(95)=6.62ms  
     http_reqs......................: 503151  240.393445/s
     iteration_duration.............: avg=1s       min=1s       med=1s       max=1.16s    p(90)=1s       p(95)=1s      
     iterations.....................: 503025  240.333246/s
     vus............................: 126     min=1        max=400 
     vus_max........................: 400     min=400      max=400

4) soak test 실행

- 평균 부하 테스트의 또다른 변형으로 장기간동한 수행하며 아래 사항을 분석

  • 장기간에 걸친 시스템 성능 저하 및 리소스 낭비
  • 확장된 기간 동안 시스템의 가용성 및 안정성
더보기

4-1) soak_test.js 코드

// soak_test.js

import http from "k6/http";

import { sleep, check } from 'k6';

export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    stages: [
        { duration: '2m', target: 400 },
        { duration: '3h56m', target: 400 },
        { duration: '2m', target: 0 },   
    ],
    
};

export default function () {
    const res = http.get('http://localhost:8080');
    check(res, { 'status was 200': (r) => r.status == 200 });
    sleep(1);
  };

4-2) 스크립트 실행 결과

$ k6 run soak_test.js 

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: soak_test.js
     output: -

  scenarios: (100.00%) 1 scenario, 400 max VUs, 4h0m30s max duration (incl. graceful stop):
           * default: Up to 400 looping VUs for 4h0m0s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s)


     ✓ status was 200

     checks.........................: 100.00% ✓ 160029    ✗ 0     
     data_received..................: 71 MB   155 kB/s
     data_sent......................: 13 MB   28 kB/s
     http_req_blocked...............: avg=8.32µs   min=3.32µs   med=5.3µs   max=17.11ms p(90)=6.14µs   p(95)=9.01µs  
     http_req_connecting............: avg=1.74µs   min=0s       med=0s      max=17.03ms p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=1.84ms   min=561.48µs med=1.05ms  max=58.59ms p(90)=2.6ms    p(95)=5.45ms  
       { expected_response:true }...: avg=1.84ms   min=561.48µs med=1.05ms  max=58.59ms p(90)=2.6ms    p(95)=5.45ms  
     http_req_failed................: 0.00%   ✓ 0         ✗ 160029
     http_req_receiving.............: avg=125.26µs min=19.3µs   med=79.66µs max=24.85ms p(90)=208.25µs p(95)=228.62µs
     http_req_sending...............: avg=33.72µs  min=8.87µs   med=13.71µs max=24.74ms p(90)=37.84µs  p(95)=57.76µs 
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s      max=0s      p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=1.68ms   min=485.61µs med=857.2µs max=58.55ms p(90)=2.46ms   p(95)=5.29ms  
     http_reqs......................: 160029  347.45119/s
     iteration_duration.............: avg=1s       min=1s       med=1s      max=1.05s   p(90)=1s       p(95)=1s      
     iterations.....................: 159629  346.58272/s
     vus............................: 400     min=3       max=400 
     vus_max........................: 400     min=400     max=400 


running (0h07m40.6s), 000/400 VUs, 159629 complete and 400 interrupted iterations

5) spike test 실행

- 스파이크 테스트는 시스템이 갑자기 대용량의 사용량이 급증할 때 살아남고 성능을 발휘하는지 확인하는 테스트로 가장 흔하지 않은 테스트 중 하나

더보기

5-1) spike_test.js 코드

// spike_test.js

import http from "k6/http";

import { sleep, check } from 'k6';

export let options = {
    insecureSkipTLSVerify: true,
    noConnectionReuse: false,
    stages: [
        { duration: '10s', target: 100 },
        { duration: '1m', target: 100 },
        { duration: '10s', target: 1400 },
        { duration: '3m', target: 1400 },
        { duration: '10s', target: 100 },
        { duration: '3m', target: 100 },
        { duration: '10s', target: 0 },        
    ],
};

export default function () {
    const res = http.get('http://localhost:8080');
    check(res, { 'status was 200': (r) => r.status == 200 });
    sleep(1);
  };

5-2) 스크립트 실행 결과

$ k6 run spike_test.js 

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

  execution: local
     script: spike_test.js
     output: -

  scenarios: (100.00%) 1 scenario, 1400 max VUs, 8m10s max duration (incl. graceful stop):
           * default: Up to 1400 looping VUs for 7m40s over 7 stages (gracefulRampDown: 30s, gracefulStop: 30s)


running (1m18.8s), 1242/1400 VUs, 11404 complete and 0 interrupted iterat
default   [=====>--------------------------------] 1242/1400 VUs  1m18.8s/7m40.0s
WARN[0489] Request Failed                                error="Get \"http://localhost:8080\": request timeout"
WARN[0489] Request Failed                                error="Get \"http://localhost:8080\": request timeout"
WARN[0489] Request Failed                                error="Get \"http://localhost:8080\": request timeout"
WARN[0489] Request Failed                                error="Get \"http://localhost:8080\": request timeout"
WARN[0489] Request Failed                                error="Get \"http://localhost:8080\": request timeout"
WARN[0489] Request Failed                                error="Get \"http://localhost:8080\": request timeout"
WARN[0494] Request Failed                                error="Get \"http://localhost:8080\": request timeout"
WARN[0494] Request Failed                                error="Get \"http://localhost:8080\": request timeout"

Step 2 : EC2 대시보드에서 aws burst credit(CPU 크레딧 사용량(개수) )와 CPU 크레딧 밸런스(개수)를 관찰

(1) basic test

(2) load test

(3) stress test

(4) soak test

(5) spike test

 

# TROUBLE SHOOTING LOG

📝 문제 1 : k6 테스트 실행 시 stage 필드를 찾을 수 없음

1. 현상

WARN[0000] There were unknown fields in the options exported in the script  error="json: unknown field \"stage\""

2. 원인

- 옵션에 stage라는 필드가 존재하지 않음, k6 options document를 확인하면 필드명이 stage가 아닌 stages임을 알 수 있음

3. 해결 방안

- 스크립트의 stage를 stages로 변경

 

📝 문제 2 : k6 테스트 실행 시 request failed

1. 현상

WARN[0050] Request Failed                                error="Get \"https://localhost:8080\": http: server gave HTTP response to HTTPS client"

2. 원인

- 현재 로컬호스트 포트는 https가 아닌 http 통신을 수행하므로 발생하는 오류

3. 해결 방안

- k6 스크립트의 요청 주소를 http로 변경

 

📝 문제 3 : k6 테스트 실행 시 reference error

1. 현상

ERRO[0008] ReferenceError: check is not defined
running at file:///home/ubuntu/k6_practice/sprint_k6_test/stress_test.js:36:10(12)  executor=ramping-vus scenario=default source=stacktrace

2. 원인

- check이 정의되지 않았기 때문에

3. 해결 방안

- check 메서드를 사용하기 위해 check을 import 해주어야 함

 

 

 

# references

https://k6.io/docs/using-k6/k6-options/how-to/

 

How to use options

How to set options in different places (with examples), how to override options, and how to access the value of an option as the test runs.

k6.io

https://k6.io/docs/using-k6/checks/

 

Checks

Checks are like asserts but differ in that they do not halt the execution, instead, they just store the result of the check, pass or fail, and let the script execution continue.

k6.io

https://k6.io/docs/test-types/

 

k6 Documentation

The k6 Documentation helps you to use k6 to get your performance testing on the right track. Learn more about load and performance testing. Get started in minutes.

k6.io

 

Comments