Successfully reported this slideshow.

ECS+Locust로 부하 테스트 진행하기

10

Share

Upcoming SlideShare
나의 8년 (2012~2019)
나의 8년 (2012~2019)
Loading in …3
×
1 of 56
1 of 56

More Related Content

Related Books

Free with a 14 day trial from Scribd

See all

Related Audiobooks

Free with a 14 day trial from Scribd

See all

ECS+Locust로 부하 테스트 진행하기

  1. 1. ECS+Locust로 부하 테스트 진행하기 박윤곤 아이스크림에듀
  2. 2. 발표자 소개 • 아이스크림에듀 (2018.01~) • ‘아이스크림 홈런’ 내 ‘AI 생활기록부’ 서비스 인프라 구축/운영
  3. 3. 오늘 할 이야기들 • Locust로 부하 테스트 작성하기 • Docker 이미지 만들고 ECR에 올리기 • ECS에서 테스트 하기 • 하나의 컨테이너에서 테스트하기 • 여러 컨테이너에서 테스트하기 • Q&A
  4. 4. 무엇을 테스트하고 싶으세요? • 신규 서비스 구축 시, 어느 정도의 트래픽을 버틸 수 있을까? • Elastic Beanstalk 기반의 서비스들 (ALB, EC2, …)
  5. 5. 무엇을 테스트하고 싶으세요? • 데이터 수집 체계 개선 시, 어느 정도의 트래픽을 버틸 수 있을까? • API Gateway + Kinesis Data Firehose • 각 서비스에 대한 제약 사항은 이미 알고 있지만...
  6. 6. (우리 팀의) 부하 테스트 역사 • Apache JMeter를 여러 대의 컴퓨터에서 실행 • 테스트 시나리오를 만들어서 여러 대의 컴퓨터에서 실행 • Locust를 여러 대의 EC2에서 실행 • 동일한 시나리오를 복사해서 EC2에서 동시 실행
  7. 7. 불편했던 점들 • JMeter를 썼을 때 • GUI 환경이 아니어도 쓸 수 있지만, 결과를 모아서 보려면? • Locust + EC2 조합으로 테스트 했을 때 • 여러 인스턴스를 시작할 때 많은 시간이 소요됨 • 빠른 구축과 확장성을 위해 컨테이너 기반 테스트 환경을 구축하는 방 법을 고려
  8. 8. Locust로 부하 테스트 작성하기
  9. 9. Locust? • Python 기반의 부하 테스트 도구 • 설치 방법 • 상세한 설치 방법은 링크 참조 $ pip install locustio
  10. 10. 왜 Locust를 선택했나? • 개인적으로 생각했던 장점 • 다른 툴보다는 쉽게 설치할 수 있었음 • Python으로 테스트 시나리오 작성: 시나리오 작성이 쉬웠음 • 웹에서 결과를 확인할 수 있음 • 복잡한 시나리오가 필요하지 않았기 때문에 Locust로 충분했음
  11. 11. Locust 파일 만들기 출처: https://locust.io
  12. 12. HttpLocust 클래스 • Locust 클래스: 하나의 사용자를 나타냄 • task_set 속성: TaskSet 클래스  사용자가 실행할 것들을 나타냄 • wait_time 속성: Task 실행 사이의 간격 • weight 속성: 하나의 파일에 여러 Locust 클래스가 존재할 때 가중치 • HttpLocust 클래스: HTTP를 사용하는 사용자를 나타냄 • client 속성: HttpSession을 갖고 있음. 쿠키를 지원하며 세션 유지 가능 • requests 라이브러리 기반
  13. 13. HttpLocust 클래스 - 예제 between constant constant_pacing
  14. 14. TaskSet 클래스 • 하나의 사용자(Locust/HttpLocust 클래스)가 실행할 작업 • Locust 클래스가 생성되면, task_set 속성에 있는 task를 실행함 Task wait_time (Locust Class) Task
  15. 15. TaskSet 클래스 • 기본적으로 @task Decorator를 이용함 • @task(숫자) 와 같이 사용하면 실행 비율을 조정할 수 있음 • @task(1), @task(2)이 있으면 @task(2)로 설정된 Task가 2배 더 많이 실행됨
  16. 16. 이벤트 수행 순서 • setup / teardown method • Locust 클래스나 TaskSet 클래스에 존재 • 모든 작업을 실행하기 전 / 종료 전 한 번 실행 • on_start / on_stop method • TaskSet 클래스에 정의할 수 있음 • 사용자가 TaskSet 클래스를 실행할 때 / TaskSet이 중지될 때 호출됨
  17. 17. 이벤트 수행 순서 Locust Class의 teardown Locust Class의 setup TaskSet Class의 setup TaskSet Class의 on_stop TaskSet Class의 on_start TaskSet Class의 teardown TaskSet Class의 task 실행 한 번만 실행
  18. 18. 샘플 스크립트: 이벤트를 랜덤 전송하기
  19. 19. Docker 이미지 만들고 ECR에 올리기
  20. 20. Docker 이미지 만들기 • 참고자료 • Running Locust with Docker • 내가 만든 스크립트를 Docker 이미지에 추가하기 (Dockerfile) • Locust 공식 이미지는 /locustfile.py 파일을 찾아 실행함 • 다른 경로에 있다면 LOCUSTFILE_PATH 환경 변수에 지정
  21. 21. Docker 이미지 만들기 • 샘플 Dockerfile
  22. 22. Docker 이미지 만들기 • ECR에 Docker 이미지 올리기 (CLI 이용) • Windows의 경우 ECR에 로그인 할 때 (PowerShell) aws ecr create-repository --repository-name (저장소 이름) aws ecr get-login-password --region ap-northeast-2 | docker login --username AW S --password-stdin aws_account_id.dkr.ecr.ap-northeast-2.amazonaws.com docker build -t (저장소 이름) . docker tag (저장소 이름):latest (계정 ID).dkr.ecr.ap-northeast-2.amazonaws.com/(저장소 이 름):latest docker push (계정 ID).dkr.ecr.ap-northeast-2.amazonaws.com/(저장소 이름):latest (Get-ECRLoginCommand).Password | docker login --username AWS --password-stdin a ws_account_id.dkr.ecr.ap-northeast-2.amazonaws.com
  23. 23. Docker 이미지 테스트 (로컬에서) • 다음 명령을 입력합니다. • 그리고 localhost:8089로 접속해 보면? docker run -p 8089:8089 (Docker 이미지 Tag)
  24. 24. ECS에서 테스트 하기
  25. 25. 왜 ECS를 선택했나? • Fargate를 쓸 수 있어서: 서버를 따로 관리할 필요가 없음!
  26. 26. ECS 클러스터 생성하기 • CLI로 ECS 클러스터 생성하기 aws ecs create-cluster --cluster-name (클러스터 이름)
  27. 27. ECS Task 생성하기 • ECS가 Task를 실행할 수 있는 IAM Role 만들기 • 링크한 매뉴얼의 1단계만 진행
  28. 28. ECS Task 작업 정의 (상세 설명) { "requiresCompatibilities": [ "FARGATE" ], "containerDefinitions": [{ "name": "(Task 이름)", "image": "(계정 ID).dkr.ecr.ap-northeast-2.amazonaws.com/(저장소 이름)", "portMappings": [{ "containerPort": 8089, "protocol": "tcp" }] }], "networkMode": "awsvpc", "memory": "2048", → memory/cpu 조합은 링크 참조 "cpu": "1024", "executionRoleArn": "(앞에서 만든 IAM Role)", "family": "(Task 이름)" }
  29. 29. ECS Task 작업 정의 생성하기 • CLI로 생성하기 • 콘솔에서 생성하기 (이후 과정은 매뉴얼 참조) aws ecs register-task-definition --cli-input-json file://./task-definition.json
  30. 30. 하나의 컨테이너로 테스트하기 Amazon Elastic Container Service Task ECS Cluster
  31. 31. 하나의 컨테이너로 테스트하기 • 보안 그룹을 생성합니다. (8089 포트를 허용) aws ec2 create-security-group --description "Security Group for Locust Fargate Task" --group-name "Locust_Fargate_SG" { "GroupId": "sg-(보안 그룹 ID)" } aws ec2 authorize-security-group-ingress --group-id sg-(보안 그룹 ID) --protocol tcp --port 8089 --cidr 0.0.0.0/0
  32. 32. 하나의 컨테이너로 테스트하기 • Task에서 사용할 환경변수를 바꾸려면? • JSON 파일을 생성 후 Task 실행 시 override: 링크 참조 • TARGET_URL 환경 변수: 테스트 할 URL • ECS Task를 실행합니다. (Output에서 taskArn 속성의 내용 메모!) aws ecs run-task --launch-type FARGATE --cluster (클러스터 이름) --count 1 -- network-configuration 'awsvpcConfiguration={subnets=[" 서 브 넷 ID"," 다 른 서 브 넷 ID"],securityGroups=[" 보 안 그 룹 ID"],assignPublicIp="ENABLED"}' --overrides file://(앞에서 만든 JSON 파일) --task-definition (Task 이름:버전)
  33. 33. Task의 Public IP 확인하기 • Task의 Public IP를 확인한 뒤, (Public IP):8089로 접속 aws ecs describe-tasks --cluster (클러스터 이름) --tasks (Task ID-taskArn의 ‘task/’뒷부분) (결과 중) … { "name": "networkInterfaceId", "value": "eni-(ENI ID)" } aws ec2 describe-network-interfaces --network-interface-ids (ENI ID) (결과 중) … "PublicIp": "xxx.xx.xx.xxx", …
  34. 34. Task 종료 • CLI에서 다음과 같이 입력 • Task ID를 모르면 다음 명령으로 찾을 수 있습니다. aws ecs stop-task --cluster (클러스터 이름) --task (Task ID) aws ecs list-tasks --cluster (클러스터 이름) { "taskArns": [ "arn:aws:ecs:ap-northeast-2:(계정 ID):task/(Task ID)" ] }
  35. 35. 여러 컨테이너로 테스트하기 • 참고자료: Running Locust distributed • 여러 대의 서버를 사용해서 master – slave 관계로 테스트 가능 • 하나의 master / 여러 대의 slave로 테스트하는 것을 권장 • Slave 역할을 할 서버에는 각각 1개의 코어를 할당하는 것을 권장
  36. 36. 여러 컨테이너로 테스트하기 • master/slave 각각의 IP를 알면 서로 통신 가능하지 않을까?
  37. 37. 여러 컨테이너로 테스트하기 • master/slave 각각의 IP를 알면 서로 통신 가능하지 않을까? (master는 서비스로, slave는 작업(Task)로 설정하면 가능) • 컨테이너 간 통신을 위한 다른 방법들 • Service Discovery (Cloud Map) • App Mesh
  38. 38. 시스템 구성도 (여러 개의 컨테이너) Amazon Elastic Container Service Service (Master) Task (Slave) Task (Slave) ECS Cluster Master 서비스의 Private IP를 지정하면 통신 가능
  39. 39. Task 정의 업데이트 하기 • 앞에서 만든 Task는 5557, 5558 포트가 개방되어 있지 않음 • 그래서 다시 Task 정의를 만들어야 함 • 변경된 내용 (파일 참조) "portMappings": [ { "containerPort": 8089, "protocol": "tcp" }, { "containerPort": 5557, "protocol": "tcp" }, { "containerPort": 5558, "protocol": "tcp" } ], "environment": [ { "name": "TARGET_URL", "value": "(테스트할 URL)" }, { "name": "LOCUST_MODE", "value": "master" } ]
  40. 40. Task 정의 업데이트 하기 • CLI에서 Task 정의 생성 aws ecs register-task-definition --cli-input-json file://(Task 정의 파일 경로)
  41. 41. Master–Slave 간 통신을 위한 설정 • 보안 그룹 설정 (5557,5558 포트 개방) aws ec2 create-security-group --description "Security Group for Locust Fargate Task" --group-name "Locust_Fargate_Master_SG" { "GroupId": "sg-(보안 그룹 ID)" } aws ec2 authorize-security-group-ingress --group-id sg-(보안 그룹 ID) --protocol tcp --port 8089 --cidr 0.0.0.0/0 aws ec2 authorize-security-group-ingress --group-id sg-(보안 그룹 ID) --protocol tcp --port 5557 --cidr (IP 대역/서브넷 마스크) aws ec2 authorize-security-group-ingress --group-id sg-(보안 그룹 ID) --protocol tcp --port 5558 --cidr (IP 대역/서브넷 마스크)
  42. 42. Master 역할을 할 서비스 올리기 • 서비스 실행을 위한 설정 파일
  43. 43. Master 역할을 할 서비스 올리기 • CLI로 서비스 생성 • Master 서비스의 Private IP 주소 확인 aws ecs create-service --cli-input-json file://(서비스 설정 파일 경로) aws ecs describe-tasks --cluster (클러스터 이름) --tasks (Task ID-taskArn의 ‘task/’뒷부분) (결과 중) … { “privateIpv4Address": “xxx.xxx.xxx.xxx” }
  44. 44. Slave 역할을 할 Task 올리기 • Master/Slave 모두 동일한 Locust 파일을 갖고 있어야 하므로, Master의 작업 정의를 그대로 이용 • Task Override를 위한 환경 변수 (참고) • LOCUST_MODE: slave • LOCUST_MASTER_HOST: 앞에서 얻은 Master 서비스의 Private IP
  45. 45. Slave 역할을 할 Task 올리기 • Task 실행하기 • Master 서비스의 Public IP:8089로 접속하면? aws ecs run-task --launch-type FARGATE --cluster (클러스터 이름) --count 2 -- network-configuration 'awsvpcConfiguration={subnets=[" 서 브 넷 ID"," 다 른 서 브 넷 ID"],securityGroups=["앞에서 만든 보안 그룹 ID"],assignPublicIp="ENABLED"}’ -- overrides file://(앞에서 만든 JSON 파일) --task-definition (Task 이름:버전)
  46. 46. 테스트 결과 확인하기 • 실제 테스트 기준
  47. 47. 리소스 정리하기 • 전체 과정 • ECS Cluster에서 실행 중인 서비스 종료 후 삭제 • ECS Cluster 내 Slave Task 모두 종료 • ECS 클러스터 삭제 • ECR 저장소 삭제
  48. 48. 비용 • 테스트 환경 (비용에 영향을 주는 요소 – 볼드체) • 5 분 동안 수행 • 메모리 및 vCPU 구성: 2GB / 1 vCPU • Master: 1 개 / Slave: 4 개 • 사용자: 1,600 / Hatch Rate: 10
  49. 49. 비용 • 비용 계산 (서울 리전 기준) = $ 0.0237 • 총 비용 = vCPU 비용 + 메모리 비용 • vCPU 비용 = 작업 개수 x 1 vCPU/시간 당 요금 x 동작한 시간(초) 5 x 0.04656 x (300/3600) = $ 0.0194 • 메모리 비용 = 작업 개수x메모리(GB)x1GB/시간 당 요금x동작 시간(초) 5 x 2 x 0.00511 x (300/3600) = $ 0.0043
  50. 50. 개선해야 할 것들 • JavaScript를 실행하는 페이지 성능 측정이 어려움 • 테스트 내용이 달라질 때마다 Docker 이미지를 만들어야 함 • 지금까지 설명한 것들을 모아서 한 번에 만들 수 있는 방법?
  51. 51. DEMO Master – Slave 구성
  52. 52. 감사합니다! https://github.com/rubysoho07/locust-on-ecs hahafree12@gmail.com

Editor's Notes

  • 안녕하세요? 오늘 컨테이너 온라인 소모임에서 ‘ECS+Locust로 부하 테스트 진행하기’라는 주제로 발표할 박윤곤이라고 합니다.
  • 저는 2018년부터 아이스크림에듀에서 근무하고 있습니다. 저희 회사는 ‘아이스크림 홈런’이라는 서비스를 운영하고 있는데요. 저희 팀은 학생들의 학습 데이터를 수집하고 분석한 결과를 알려주는 ‘AI 생활기록부’ 서비스를 구축하고 운영하고 있구요. 저희 팀에서 사용하고 있는 인프라는 AWS에 대부분이 올라가 있습니다.
  • 오늘 이야기 할 내용은 화면에 나오는 내용과 같습니다. Locust로 부하 테스트를 작성하는 방법에 대해 이야기를 할 예정입니다. 다음으로는 로컬에서 테스트 해 볼 목적으로 Docker 이미지를 만들고 ECR에 올리는 과정을 설명해 드리겠습니다. 그리고 부하 테스트를 위해 왜 ECS와 Fargate를 선택했는지, 그리고 ECS에서 테스트 환경 구축을 어떻게 할 수 있는지에 대해 설명을 드릴 거예요. 특히 여러 컨테이너에서 테스트하는 부분은 간단한 데모를 통해 부족한 부분을 설명해 드릴 수 있을 것 같아요. 마지막으로는 Q&A 시간을 갖도록 하겠습니다.
  • 이 이야기를 하기 전에, 저희 팀이 어떤 부하 테스트를 진행 하는지에 대해 설명을 드리는 게 좋을 것 같아요. 먼저 저희가 갖고 있는 데이터를 바탕으로 신규 서비스를 구축할 때가 있는데요. 저희 팀은 보통 Elastic Beanstalk를 사용하고 있고, 이를 기반으로 하는 서비스들이 피크 타임에는 어느 정도의 트래픽을 감당할 수 있는지 테스트가 필요했습니다.  
  • 한 편으로, 저희팀의 데이터 수집 체계를 개선할 때, 어느 정도의 트래픽을 버틸 수 있는지에 대해 테스트가 필요했습니다. 저희 팀의 데이터 수집은 API Gateway와 Kinesis Firehose로 구성되어 있는데요. 이미 각 서비스에 대한 제약 사항은 알고 있지만, 여러 경우의 데이터가 무작위로 전송될 때 어느 정도의 트래픽을 버틸 수 있는지, Firehose에서 데이터를 변환하는 과정에 무리는 없는지 등을 테스트 할 필요가 있었습니다.
  • 저희 팀은 부하 테스트를 위해 여러 방법을 고민해 봤었는데요. 처음에는 여러 컴퓨터에 Apache JMeter를 설치해서 실행하는 방법을 채택했었구요. 그 다음에는 Locust를 여러 대의 EC2​에서 실행하는 방법을 사용 했었습니다.
  • 그렇지만 JMeter를 썼을 때나 Locust와 EC2를 조합해서 썼을 때 모두 나름의 불편한 점이 있었습니다. JMeter를 썼을 때는 그냥 Auto Scaling이 어떤 상황에서 동작하는 지 테스트를 하기 위해 사용했었구요. GUI 환경이 아니어도 쓸 수 있지만, 여러 컴퓨터에서 실행한 결과를 어떻게 모아서 ​볼 것인지 고민만 했었던 경험이 있었습니다. 그 다음으로는 Locust와 EC2 조합으로 테스트를 했을 때, 잠깐의 테스트 때문에 사용하는 것인데도 불구하고, 여러 인스턴스를 시작하고 종료하는 데 많은 시간이 소요된다는 점이 불편했습니다. 그래서 컨테이너 기반으로 테스트 환경을 구축하는 방법을 고려하게 되었습니다.
  • 이제부터는 Locust로 부하 테스트를 작성하는 방법에 대해 간단히 설명을 드리려고 합니다.
  • Locust는 Python 기반의 부하 테스트 도구입니다. pip로 간단하게 설치하실 수 있습니다. 
  • 저희 팀이 주로 쓰는 언어는 Java와 Python인데요. 개인적으로는 Python에 익숙했기 때문에 Locust를 선택하였는데요. 그렇지만 제가 써 보면서 느꼈던 점을 말씀드리면요. 다른 툴 보다는 쉽게 설치할 수 있었구요. Python으로 테스트 시나리오를 작성하는데, 구조가 단순한 편이라서 파이썬에 익숙하지 않은 분도 쉽게 시나리오를 작성할 수 있었습니다. 그리고 nGrinder와 같은 툴에서도 지원하는 기능이긴 하지만, 웹 브라우저에서 결과를 간단하게 확인할 수 있다는 점도 좋다고 생각했습니다. 전반적으로는 저희에게는 복잡한 시나리오가 필요하지 않았기 때문에, Locust로도 부하 테스트를 위한 어느정도의 목표는 달성할 수 있었습니다. 
  • Locust 홈페이지에 가면, 화면에 표시되는 것과 같이 Locust 파일의 샘플을 보실 수 있습니다. Locust 파일은 부하 테스트를 진행하기 위한 시나리오라고 보시면 됩니다. 샘플 파일을 보시면 WebSiteUser 클래스, WebsiteTasks 클래스가 있습니다. 이 클래스의 의미는 뒤에서 설명해 드릴 거예요. 파일을 저장한 뒤 터미널이나 쉘에서 locust –f locustfile.py 라고 입력하면, Locust를 통한 부하 테스트를 진행하실 수 있습니다. 
  • 먼저 Locust 클래스와 HttpLocust 클래스에 대해 설명을 드리겠습니다. Locust 클래스는 하나의 사용자를 나타냅니다. Task_set 속성은 뒤에서 설명할 TaskSet 클래스, 즉 이 사용자가 실행할 것들을 나타냅니다. 그리고 wait_time 속성은 Task 실행 사이에 얼마나 기다릴 것인지를 나타냅니다. Weight 속성은 여러 Locust 클래스가 있을 때 가중치를 의미합니다. 

    HttpLocust 클래스는 HTTP를 사용하는 사용자를 나타냅니다. Locust 클래스를 상속받았으니, Locust 클래스의 모든 속성을 쓸 수 있습니다. 여기에 client 속성이 추가되는데요. 이 client는 세션을 갖고 있구요. 쿠키를 지원하며, 세션을 유지할 수 있습니다. 그리고 이 클라이언트는 파이썬의 requests 라이브러리를 기반으로 하기 때문에, requests 라이브러리가 지원하는 것이라면 모두 쓸 수 있습니다.
  • 예제를 하나 보여드릴게요. 아래에 MyLocust라는 클래스를 보실 수 있는데요. Task_set 속성은 위의 MyTaskSet 클래스를 가르킵니다. 그리고 wait_time 속성은 화면에 있는 것처럼 between, constant, constant_pacing과 같이 쓸 수 있는데요. Between은 최소값과 최대값 사이에서 랜덤 값을 반환합니다. 단위는 초입니다. Constant는 고정된 값을 반환합니다. Constant_pacing은 task가 실행되는데 걸리는 시간에 관계 없이 task 실행 간격을 일정하게 유지하는 기능이라고 생각하시면 될 것 같습니다.
  • TaskSet 클래스는 하나의 사용자, Locust 클래스로 구성된 사용자가 실행할 작업입니다. Locust 클래스가 생성이 되면, task_set 속성에 있는 task를 실행합니다. 그리고 task를 실행한 후에는 Locust 클래스의 wait_time 속성에 따라 기다리구요. 그 뒤에 다음 task를 실행합니다.
  • 각각의 Task, 실행할 작업은 @task 데코레이터를 사용해서 적어줍니다. 뒤에 숫자를 붙여주면, 실행 비율을 조정할 수 있는데요. 값이 클 수록 더 많이 실행된다고 보시면 됩니다. 화면의 예에서는 read_home은 첫 페이지에 접속을 하구요. Read_thread라는 함수는 ‘/about’ 페이지에 접속을 합니다. Task 데코레이터에 2라고 적혀 있는 read_thread가 read_home보다 2배 더 많이 실행될 겁니다. 
  • 이벤트 수행 순서는 setup / teardown 메소드, 그리고 on_start / on_stop 메소드가 있습니다. Setup / teardown 메소드는 Locust 클래스나 TaskSet 클래스에 존재하는데요. 모든 작업을 실행하기 전, 종료 전 한 번만 실행됩니다. On_start / on_stop 메소드는 TaskSet 클래스에 정의할 수 있는데요. 사용자가 TaskSet 클래스를 실행할 때, TaskSet이 중지될 때 호출됩니다.
  • 이벤트 수행 순서를 정리해 보면, 다음과 같습니다.
  • 제가 테스트를 수행하기 위해 사용했던 샘플 스크립트를 보여드리려고 해요. 홈런 서비스는 보통 아침과 저녁 시간에 트래픽이 많이 들어오는데요. 특히 저녁 시간에 들어오는 트래픽의 양을 가정하고 테스트를 진행하기 위해 스크립트를 작성했습니다. 설명을 위해 실제로 썼던 스크립트에서 조금 생략한 부분이 있는 점 양해 부탁드리겠습니다. 

    UserBehavior라는 클래스는 locust 클래스가 갖고 있는 샘플 데이터 중에서 임의로 선택하여 API Gateway로 전송합니다. 아래 부분을 보시면 file_index를 임의로 선택하고, 하단의 내용과 같이 client.post 부분에서 데이터를 전송합니다. 경로를 적고, 어떤 데이터를 전송할 것인지, 그리고 어떤 헤더를 보낼 것인지를 결정해 줍니다.
  • 그 다음으로는 WebsiteUser라는 클래스가 있는데요. '/app/SampleEvents' 디렉토리에 JSON으로 샘플 데이터를 저장했구요. 이 클래스가 초기화 될 때 raw data 샘플을 가져와서 저장합니다. 그리고 위에서 살펴봤던 UserBehavior 클래스가 동작하도록 task_set 속성을 설정해 줍니다. 여기서는 생략했지만, 저는 이벤트가 유형별로 몇 건씩 전송되었는지 통계를 내서 저장을 했구요. 그 결과는 teardown 메소드에서 실행해서 로그를 남기도록 했습니다. ECS에서 작업 정의를 생성할 때 CloudWatch logs에서 확인할 수 있도록 설정하기도 했습니다. print문을 쓰면 CloudWatch logs에 저장이 되더라구요. 
  • 이제 Locust 파일이 준비되었다면, Docker 이미지를 만들어 볼 차례인데요.
  • 먼저 Dockerfile을 만들기 위해 내가 만든 스크립트를 Docker 이미지에 추가해 보겠습니다. Locustio의 Locust 공식 이미지가 있기 때문에 이를 이용하구요. 만든 Locustfile을 이 이미지에 추가해서 만들어 줍니다. Locust 공식 이미지는 기본적으로 /locustfile.py 파일을 찾아서 실행합니다. 만약 다른 경로에 스크립트를 추가해야 한다면, LOCUSTFILE_PATH 환경 변수에 지정해 주어야 합니다.
  • 앞에서 만들었던 스크립트와 샘플 이벤트를 포함하여 Dockerfile을 만들었는데요. 저는 제가 작성한 스크립트와 샘플 데이터를 /app 폴더에 복사하고, LOCUSTFILE_PATH 환경변수를 지정해서 제가 작성한 파일을 실행하도록 했습니다. 그리고 TARGET_URL 환경변수를 설정해서 API Gateway 주소를 설정해 주었습니다.
  • 저는 ECR에 Docker 이미지를 올려보았는데요. 먼저 repository를 만들고, ECR에 로그인 합니다. 그리고 Docker 이미지를 빌드하고, ECR에 push 해 줍니다. 윈도우의 경우, 두번째 줄의 ECR에 로그인하는 부분을 아래와 같이 대체하시면 됩니다.
  • 그러면, 이제 Docker 이미지를 테스트 해 볼 차례인데요. 8089 포트를 열어서 테스트 하시면 됩니다. 이제 localhost:8089로 접속해 보시면, 다음과 같은 화면을 보실 수 있습니다. 
  • 여기서 시뮬레이션 할 유저 수와 초당 사용자 증가 비율을 넣은 뒤, Start swarming 버튼을 누르면 여러분이 작성했던 시나리오 대로 테스트가 수행됩니다. 
  • 이제 ECS에서 테스트 하는 과정에 대해 소개해 드리겠습니다.
  • 먼저 왜 ECS를 선택했는지에 대해 이야기 해 보려고 합니다. 제 입장에서는 서버를 따로 관리할 필요가 없는 Fargate를 사용할 수 있다는 점이 가장 큰 이유였습니다. 그리고 Kubernetes, EKS 같은 선택지는 없었냐고 물어보신다면, 일단 제가 Kubernetes에 익숙하지 않은 상태였습니다. 그리고 작년 re:Invent 행사에서 Fargate on EKS 서비스가 소개되었는데요. 아직 서울 리전에서는 사용할 수 없다는 점 또한 Kubernetes 관련 서비스 대신 ECS를 선택하게 된 계기였습니다. 
  • 이후의 내용은 대부분 콘솔 보다는 CLI를 통한 설정 위주로 설명을 드리려고 해요. 먼저 CLI로 ECS 클러스터를 생성해 줍니다.
  • 이제 ECS Task, 작업을 만들어야 하는데요. 그 전에 ECS가 Task를 실행할 수 있는 IAM Role을 만들어야 합니다. 제가 링크한 매뉴얼의 1단계만 진행하시면 됩니다.
  • ECS Task는 Task definition, 작업 정의를 설정해야 하는데요. 어떤 이미지를 쓸 것인지, 어떤 포트를 열 것인지, CPU/메모리는 얼마만큼 할당할 것인지, 그리고 이 작업 실행에 대한 IAM Role이나 작업 자체가 갖고 있는 IAM Role, 로그는 어떻게 할 것인지 등을 설정해 주어야 합니다. 위에서부터 빨간색으로 표시한 부분을 보면, 일단 이 작업은 Fargate를 사용할 거구요. Task 이름과 제가 ECR에 올린 이미지를 사용할 것이고, 외부에는 8089 포트만 열어줄 것입니다.

    그리고 메모리와 CPU는 1024 – vCPU 1개, 그리고 메모리는 2GB를 사용하는 것으로 설정했습니다. 그리고 executionRoleArn은 작업 실행에 대한 권한으로 앞에서 만든 IAM Role의 ARN을 넣으면 됩니다.

    이렇게 정리가 되었으면 파일을 저장합니다.
  • 이제 작업 정의를 ECS에 등록합니다. 앞에서 만든 파일을 지정해 주면 작업 정의가 생성됩니다. 콘솔에서도 동일하게 생성하실 수 있습니다.
  • 먼저, 하나의 컨테이너로 테스트하는 환경을 구축해 보겠습니다. 구성은 다음과 같아요. ECS 클러스터에 Task를 하나 올립니다. 이 태스크에 할당된 Public IP에 접속해서 테스트를 진행할 겁니다.
  • 먼저, 8089 포트가 열려 있는 보안 그룹을 생성해 줍니다.
  • 그리고 이제 Task를 실행할 건데요. Task에서 사용할 환경변수를 바꾸기 위해 JSON 파일을 생성해 줍니다. 자세한 내용은 링크를 참고해 주시구요. 여기서 설정한 건, TARGET_URL 환경 변수입니다. 여기서 테스트 할 URL을 지정해 줍니다.

    그 다음으로는 ECS Task를 실행합니다. FARGATE로 실행하구요. 클러스터 이름을 지정해 주어야 하구요. 그 다음에 이건 1개만 실행해도 되니까 count는 1개, 어느 서브넷에 배치할 것인지, 보안 그룹은 어떤 걸 쓸 것인지, Public IP 할당은 켜 주어야 하겠죠. 앞에서 만든 JSON 파일의 경로를 설정해 줍니다. 마지막으로 어떤 작업 정의를 사용할 것인지 지정해 줍니다.
  • 근데, Task의 Public IP를 확인해야 하는데, 이걸 어떻게 확인하는 지 궁금하실 거예요. 이거는, 아까 taskArn 속성을 메모해 달라고 말씀드렸는데, ‘task/’ 뒷부분에 있는 내용이 Task ID입니다. 이걸로 연결된 ENI를 알아내고, ENI 정보에서 Public IP를 알아냅니다. 아니면 콘솔에서 확인하셔도 됩니다. 이제 Public IP:8089로 접속하시면 로컬에서 테스트했을 때와 같은 화면을 보실 수 있습니다.
  • Task 실행에는 당연히 비용이 들어가기 때문에, Task를 중지해 주셔야 합니다. CLI에서 aws ecs stop-task 클러스터 이름, Task ID를 입력하시면 됩니다. Task ID를 모르는 경우, list-tasks 명령으로 찾을 수 있습니다.
  • Locust는 여러 서버를 묶어서 테스트 할 수 있는 환경을 제공하는데요. 여러 대의 서버를 사용해서 master-slave 관계로 테스트 할 수 있습니다. 제가 위에 링크한 Locust 공식 문서를 보시면, 하나의 master와 여러 대의 slave로 테스트 하는 것을 권장하고 있구요. 그리고 slave 역할을 할 서버에는 각각 1개의 코어를 할당하는 것을 권장하고 있습니다. 
  • 그래서 저도 여러 컨테이너를 활용해서 master – slave 관계로 테스트하는 환경을 구축하려고 했는데요. 처음에는 모든 실행 중인 ECS Task는 적어도 private IP를 갖고 있으니까, 같은 서브넷에 master와 slave 역할을 할 task를 배치하면 서로 통신 가능하지 않을까? 라고 생각 했었어요. 
  • 근데, 실제로 해 보니 잘 안 되더라구요. 그래서 AWS 내에서 컨테이너 간 통신 방법으로 찾아 보니, Service Discovery나 App mesh와 같은 서비스가 있더라구요. Service Discovery의 경우, CLI에서는 servicediscovery 라는 이름인데 콘솔에서는 Cloud Map이라는 서비스로 등록되어 있는데요. 처음에 좀 혼동이 있긴 했는데, 실제로 사용하실 때 이 점 감안하시면 될 것 같습니다. App Mesh도 있는데, 저는 그냥 slave Task가 master에 붙을 수 있기만 하면 되서 Cloud Map을 사용하기로 했습니다.
  • 그래서, 이 시스템은 다음과 같이 구성합니다. ECS 클러스터를 만들고, Service Discovery에 네임스페이스와 서비스를 등록합니다. 그리고 ECS 클러스터에 서비스를 생성할 때는 Service Discovery에 등록한 서비스에 붙여 줍니다. 그러면 Route 53의 Hosted Zone에 서비스의 IP 주소가 등록이 되구요. Slave Task가 Master 서비스를 보도록 설정하면 slave task - Master 서비스 간에 통신이 이루어 집니다.
  • 근데, 제가 앞에서 만든 Task 정의에는 5557, 5558 포트가 개방되어 있지 않아요. 그렇다고 서비스를 시작할 때, 원래 Task 정의에 있던 설정 외의 포트를 추가로 개방할 수 없습니다. 그래서 Task 정의를 다시 만들어야 하는데요. 변경된 내용은 화면과 같습니다. 5557, 5558 포트를 개방하구요. 환경 변수에 TARGET_URL과 LOCUST_MODE를 설정합니다. 기본적으로 master 역할을 할 수 있도록, LOCUST_MODE 환경 변수를 master로 설정했어요. TARGET_URL은 나중에 slave task를 실행할 때 고칠 일이 없도록 그냥 고정해 줬습니다.
  • 그리고 다시 Task 정의를 등록합니다.
  • 그리고 master-slave 간에 통신하기 위해서 5557, 5558 포트를 열어야 하는데요. 기존에 만들어 둔 보안 그룹에서 인바운드 규칙을 추가해 주시거나, 아니면 새로운 보안그룹을 만들어서 쓰시면 됩니다. 저는 8089 포트는 앞에서 했던 것처럼 전체에 열어 두고, 5557, 5558 포트는 VPC의 Private IP 대역을 기준으로 생성했습니다.
  • 이제 master 역할을 할 서비스를 올려볼 차례인데요. 먼저 설정 파일을 JSON으로 작성해 보겠습니다. 클러스터 이름, 서비스 이름, Task 정의를 적어 줍니다. 그 다음으로 중요한 게, Service Discovery에 등록했던 서비스 있죠? 그 서비스의 인스턴스로 등록하기 위해 serviceRegistries 속성에 서비스 ID를 지정해 줘야 합니다. 그 다음으로는 네트워크 설정인데, 보안 그룹 ID, 서브넷 ID를 적고, Public IP 할당도 켜 줍니다. Master는 하나만 있으면 되니까 desiredCount는 1로 설정했습니다.
  • 앞에서 서비스 설정을 JSON 파일로 저장하고, 서비스를 생성합니다. 시간이 지나면 서비스 컨테이너가 올라옵니다. 그런데 slave task에 master 서비스의 주소를 지정해 줘야 하는데, 이는 DNS 레코드를 확인해서 얻을 수 있습니다. 보통 Service Discovery 설정을 했을 때 지정했던 ‘master 서비스 이름.네임스페이스 이름’과 같은 방식으로 등록되어 있을 겁니다.
  • 이제는 Slave 역할을 할 task를 올려 보겠습니다. Locust에서 master-slave 관계로 부하 테스트를 진행하려면, master, slave 모두 동일한 Locust 파일을 갖고 있어야 합니다. 그래서 저는 master의 작업 정의를 그대로 이용해 보겠습니다. Slave Task를 실행할 때, 환경 변수를 좀 바꿔야 하는데요. LOCUST_MODE는 slave로, LOCUST_MASTER_HOST는 앞에서 얻은 master 서비스의 DNS 정보를 등록해 주시면 됩니다. 이 설정은 JSON 파일로 만들어 줍니다.
  • 그리고 Slave 역할을 할 task를 올려줍니다. 이 예제에서는 2개의 task를 올립니다. 그리고 Master 서비스의 Public IP:8089 주소로 접속하면, 다음과 같은 화면을 보실 수 있습니다.
  • 우측 상단을 보면 Slaves라는 게 있고, 2라고 표시되어 있습니다. Master에 Slave 2개가 연결되어 있다는 의미입니다. 이제 2개의 slave로 테스트를 진행하실 수 있습니다.
  • 테스트를 수행하게 되면, 이 화면과 같이 통계를 확인하실 수 있습니다. 위의 화면은 제가 실제로 5분 동안 피크 타임에서 발생할 수 있는 상황을 가정하고 API Gateway로 이벤트를 전송했을 때의 스크린샷입니다. 실제로는 4 대의 Slave를 사용했구요. 1600명의 사용자와 사용자 증가는 초당 10명으로 가정했습니다. 대략 533.2 RPS를 찍은 것을 보실 수 있습니다. 그리고 어느 엔드포인트로 몇 번의 요청 또는 실패가 있었는지, 응답 속도는 얼마나 되었는지 등을 보실 수 있습니다.
  • 차트 메뉴를 가시면, 화면과 같이 얼마 정도의 RPS가 발생했고, 어느 정도의 response time이 소요되었는지에 대해 보실 수 있습니다. 있다가 데모에서 다른 메뉴들도 같이 확인해 보시죠.
  • 모든 테스트가 끝났다면, 리소스를 정리해야 하는데요. 일단 비용이 들 것 같은 인스턴스 위주로 정리해 봤습니다. 전체 과정은 링크한 내용을 참고해 주시기 바랍니다.
  • 비용과 같은 문제를 건너뛸 수는 없을 것 같은데요. 아까 전에도 설명드렸던 환경이었는데요. 
  • 비용 계산을 해 보면 이론상으로는 이정도의 비용이 발생하는데, 실제로는 여러 번 테스트를 했고, 한 번은 좀 오랜 시간동안 Fargate를 실행했던 적이 있어서 비용이 좀 더 추가되었습니다. 그럼에도 불구하고 Fargate 요금이 1$를 넘지는 않았어요.
  • 이제 마무리를 해 보겠습니다. 이 내용을 구현해 보면서, 개선해야 할 것들을 좀 정리해 봤는데요.

    먼저 JavaScript를 실행하는 페이지 성능 측정이 어렵습니다. Locust는 기본적으로 requests라는 라이브러리를 사용해서 요청을 수행하는데, JavaScript를 실행하지는 않아요. 그래서 JS를 쓰는 페이지에 대해서 성능 측정이 필요하다면 Selenium과 같은 라이브러리와 웹 브라우저가 필요한데, 이 부분은 좀 더 찾아봐야 할 것 같습니다.
    두 번째로는 테스트 내용이 달라질 때마다 Docker 이미지를 빌드하고 올려야 하는 점입니다. 개인적으로는 S3에 스크립트를 올리고 필요에 따라 환경 변수로 설정해서 테스트 할 수 있으면 어떨까 싶습니다.
    마지막으로는 지금까지 시스템을 구축하는 과정에서 많은 명령을 입력해야 했는데, 이걸 한 번에 만들 수 있는 방법이 필요하다고 생각했습니다.

  • 잠깐 Master-Slave 구성으로 만들어 둔 내용을 데모로 보여 드리겠습니다. 궁금한 사항이 있으시면 중간에 말씀해 주세요!

    (시나리오)
    - Service Discover​y
    - ECS Cluster (서비스와 작업)
    - 실제 테스트 진행
      테스트 시나리오 점검하기
      웹 페이지 3곳에 테스트 (한 개는 에러가 랜덤으로 발생하도록 구성)
    - 테스트 결과 확인하기
  • 늦은 시간까지 발표 들어 주셔서 감사합니다. 오늘 설명한 슬라이드와 샘플 코드는 AWSKRUG 슬랙의 container 채널에 올렸고, 링크한 GitHub 저장소에서도 확인하실 수 있습니다. 다시 한 번 감사합니다!
  • ×