Skip to content

๐Ÿ’ก How To? Terraform Locust๋กœ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ์ง„ํ–‰ํ•˜๊ธฐ

Kim Minju edited this page Sep 1, 2024 · 13 revisions

Terraform์„ ๋” ์ž˜ ์“ฐ๋Š” ๋ฐฉ๋ฒ•๋ณด๋‹ค๋Š”, ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ ๊ตฌ์ถ•์˜ ๋ถˆํŽธํ•จ์„ ํ•ด๊ฒฐํ•˜๋ ค๋Š” ์ˆ˜๋‹จ์œผ๋กœ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค~

Terraform ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ธํŒ…

์„ค์น˜

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

์„ค์น˜ ํ™•์ธ

terraform version

autocomplete ์„ธํŒ…

terraform -install-autocomplete

๊ฐ„๋‹จ ์˜ˆ์ œ

main.tf

provider "aws" {
  region = "ap-northeast-2"  # ๋ฆฌ์ „์„ ap-northeast-2๋กœ ์„ค์ •
}

resource "aws_instance" "terrafor-example-miiiinju" {
  ami                    = "ami-056a29f2eddc40520"  # ์ง€์ •๋œ AMI ID
  instance_type          = "t3.micro"  # ์ง€์ •๋œ ์ธ์Šคํ„ด์Šค ํƒ€์ž…
  key_name               = "team5_guys_pub"  # ์ง€์ •๋œ ํ‚ค ํŽ˜์–ด ์ด๋ฆ„
  subnet_id              = "subnet-ac32f2f3"  # default subnet
  vpc_security_group_ids = ["sg-0946e533330930594"]  # ์‚ฌ์šฉํ•  ๋ณด์•ˆ ๊ทธ๋ฃน์˜ ID

  tags = {
    Name = "miiiinju-terraform"
  }

  root_block_device {
    delete_on_termination = true  # ์ธ์Šคํ„ด์Šค ์ข…๋ฃŒ ์‹œ EBS ๋ณผ๋ฅจ ์‚ญ์ œ
  }

  credit_specification {
    cpu_credits = "standard"
  }

  enable_monitoring = false
}

๋””๋ ‰ํ„ฐ๋ฆฌ ์ดˆ๊ธฐํ™”

terraform init

Terraform ์‹คํ–‰ ๊ณ„ํš ํ™•์ธ

terraform plan

์‹คํ–‰

terraform apply

Terraform + Locust๋กœ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ์ง„ํ–‰ํ•˜๊ธฐ

Locust๋ž€?

๋ถ„์‚ฐ ๋ชจ๋“œ๋ฅผ ์ง€์›ํ•˜๋Š” Python ๊ธฐ๋ฐ˜์˜ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ ์˜คํ”ˆ์†Œ์Šค

Python ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ํ†ตํ•ด ๊ฐ„๋‹จํ•˜๊ฒŒ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Œ

์‹œ๋‚˜๋ฆฌ์˜ค ์˜ˆ์‹œ

from locust import HttpUser, TaskSet, task, between

class UserBehavior(TaskSet):
    
    @task(1)
    def index(self):
        self.client.get("/")

    @task(2)
    def about(self):
        self.client.get("/about")

class WebsiteUser(HttpUser):
    tasks = [UserBehavior]
    wait_time = between(5, 15)

UserBehavior : ์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜ํ–‰ํ•  ์ž‘์—…์„ ์ •์˜

@task : ์ž‘์—…์˜ ์šฐ์„ ์ˆœ์œ„๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„์˜ ์˜ˆ์‹œ์—์„œ๋Š”, about ๋ฉ”์„œ๋“œ๊ฐ€ index ๋ฉ”์„œ๋“œ๋ณด๋‹ค ๋‘ ๋ฐฐ ๋” ์ž์ฃผ ์‹คํ–‰๋˜๋„๋ก ์„ค์ •๋จ

์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋ถ€ํ•˜ํ…Œ์ŠคํŠธ์—์„œ ์‚ฌ์šฉํ•  ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

import random
import string
from datetime import datetime, timedelta
from locust import HttpUser, task

class MyUser(HttpUser):

    @task
    def send_post_request(self):
        # HTTP ํ—ค๋” ์„ค์ •
        headers = {
            "App-Key": "f1607246-611e-4532-a08b-6023be328739",
            "Content-Type": "application/json"
        }

        # ๋กœ๊ทธ ๋ ˆ๋ฒจ ์„ค์ •
        log_levels = ["info", "error", "debug", "warn", "trace"]

        # ๋žœ๋ค ๋ฌธ์ž์—ด ์ƒ์„ฑ ํ•จ์ˆ˜
        def random_string(length=10):
            letters = string.ascii_letters + string.digits
            return ''.join(random.choice(letters) for _ in range(length))

        # ๋žœ๋ค ํƒ€์ž„์Šคํƒฌํ”„ ์ƒ์„ฑ ํ•จ์ˆ˜
        def random_timestamp():
            start_time = datetime(2023, 1, 1)  # ์ž„์˜์˜ ์‹œ์ž‘ ๋‚ ์งœ
            end_time = datetime.now()
            random_time = start_time + (end_time - start_time) * random.random()
            return random_time.isoformat()

        # ์ „์†กํ•  ๋ฐ์ดํ„ฐ ์„ค์ • (100๊ฐœ์˜ ๋กœ๊ทธ ํ•ญ๋ชฉ)
        payload = []
        for _ in range(100):
            log_entry = {
                "level": random.choice(log_levels),
                "data": random_string(100),  # 100์ž๋ฆฌ ๋žœ๋ค ๋ฌธ์ž์—ด
                "timestamp": random_timestamp()
            }
            payload.append(log_entry)

        # POST ์š”์ฒญ ๋ณด๋‚ด๊ธฐ
        with self.client.post("/logs", json=payload, headers=headers, catch_response=True) as response:
            if response.status_code == 201:
                response.success()
            else:
                response.failure(f"Failed with status code {response.status_code}")

๋กœ๊ทธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” API ํฌ๋งท์— ๋งž๊ฒŒ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” Python์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ์œผ๋ฉฐ, ๋กœ๊ทธ๋ ˆ๋ฒจ์€ ์ž„์˜๋กœ ๋žœ๋คํ•˜๊ฒŒ ๊ณ ๋ฅด๋„๋ก ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.


์ปค์Šคํ…€ AMI๋ฅผ ๋งŒ๋“ค์–ด์•ผํ•˜๋Š” ์ด์œ 

  • ํ•ญ์ƒ EC2๋ฅผ ์ผœ๋‘” ์ƒํƒœ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ, ํ•„์š”ํ•  ๋•Œ ์ž ๊น terraform์œผ๋กœ ์ธ์Šคํ„ด์Šค๋ฅผ ์ผœ์„œ ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ˆ˜ํ–‰ํ•œ ์ดํ›„, ์ข…๋ฃŒํ•˜๋Š” ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ EC2๋ฅผ ๋งค๋ฒˆ ์ผœ์„œ ํ™˜๊ฒฝ ์„ค์ •์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ์—๋Š” ์‹œ๊ฐ„ ๋ถ€๋‹ด์ด ํฌ๋ฏ€๋กœ, ๋ฏธ๋ฆฌ ์„ธํŒ…ํ•ด์„œ ์ปค์Šคํ…€ AMI๋ฅผ ๋งŒ๋“ค์–ด๋‘” ๋’ค ์ด๋ฅผ ํ†ตํ•ด EC2 ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

-> ๊ฐ‘์ž๊ธฐ ์‚ฌ์šฉํ•  ์ผ์ด ์ƒ๊ฒผ์„ ๋•Œ์—๋„ EC2 ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค๋”๋ผ๋„ ๊ธฐ์กด์˜ ์„ธํŒ… ์ž‘์—…์ด ๋ฏธ๋ฆฌ ๋‹ค ๋ผ์žˆ์œผ๋ฏ€๋กœ ์‹œ๊ฐ„์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์„ธํŒ… ์™„๋ฃŒํ•˜๊ธฐ

  • EC2๋ฅผ ๊ธฐ๋ณธ ubuntu ์ด๋ฏธ์ง€๋ฅผ ํ†ตํ•ด ์ง์ ‘ ๋งŒ๋“  ํ›„, Locust ๊ตฌ๋™์— ํ•„์š”ํ•œ python, locust ์„ค์น˜ ๋“ฑ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค.

aws-cli๋กœ ami๋งŒ๋“ค๊ธฐ

aws ec2 create-image \
    --instance-id {์ธ์Šคํ„ด์Šค-ID} \
    --name "locust-ami" \
    --no-reboot

AMI๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ EC2 ์ƒ์„ฑ

resource "aws_instance" "locust_master" {
  ami = "ami-046fdc13144b0da31"  # ์ง์ ‘ ๋งŒ๋“  AMI
  instance_type = "t3.small"

Locust Master ์ƒ์„ฑํ•˜๊ธฐ

Locust EC2 ์ธ์Šคํ„ด์Šค์—์„œ ์‚ฌ์šฉํ•  Security Group ์ƒ์„ฑ ์ฝ”๋“œ

# Security Group for Locust Master and Workers
resource "aws_security_group" "locust_sg" {
  name        = "locust_cluster_sg"
  description = "Security group for Locust master and workers"
  vpc_id      = "vpc-3106975a"  # VPC ID

  ingress {
    description = "Allow HTTP Web UI"
    from_port   = 8089
    to_port     = 8089
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "Allow Locust Worker to Master communication"
    from_port   = 5557
    to_port     = 5558
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "Allow SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "team5_locust_security_group"
  }
}

8089๋ฒˆ ํฌํŠธ: Locust Web UI์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด ์—ด๋ ค ์žˆ์–ด์•ผ ํ•จ. ์ด ํฌํŠธ๋ฅผ ํ†ตํ•ด ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์Œ.

5557-5558๋ฒˆ ํฌํŠธ: Locust Worker์™€ Master ๊ฐ„์˜ ํ†ต์‹ ์„ ์œ„ํ•ด ํ•„์š”. ์ด ํฌํŠธ๋ฅผ ํ†ตํ•ด ์›Œ์ปค๊ฐ€ ๋งˆ์Šคํ„ฐ์—๊ฒŒ ํ…Œ์ŠคํŠธ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•  ์ˆ˜ ์žˆ์Œ.

Security Group์„ ์ถ”๊ฐ€ํ•˜์—ฌ EC2 ์ƒ์„ฑํ•˜๊ธฐ

# Locust Master Instance
resource "aws_instance" "locust_master" {
  ami = "ami-046fdc13144b0da31"  # ์ง์ ‘ ๋งŒ๋“  AMI
  instance_type = "t3.small"
  key_name = "team5_guys_pub"
  subnet_id = "subnet-ac32f2f3"
  vpc_security_group_ids = [aws_security_group.locust_sg.id]


  tags = {
    Name = "team5_locust_master"
  }

์œ„์™€ ๊ฐ™์ด vpc_security_group_ids๋ฅผ ํ†ตํ•ด ์œ„์—์„œ ๋งŒ๋“  security group์„ ์ง์ ‘ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Œ

EC2 ์ƒ์„ฑ ํ›„, Locust ์‹คํ–‰ ์Šคํฌ๋ฆฝํŠธ ์ถ”๊ฐ€ํ•˜๊ธฐ


  user_data = <<-EOF
              #!/bin/bash
              exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1
              echo "Starting Locust Master setup"

              # Decode and create locustfile.py
              echo "${local.locustfile_content}" | base64 --decode > /home/ubuntu/locustfile.py

              # Ensure locustfile.py is owned by ubuntu user
              chown ubuntu:ubuntu /home/ubuntu/locustfile.py
              chmod 644 /home/ubuntu/locustfile.py

              # Create log file for Locust master and ensure ownership
              touch /home/ubuntu/locust_master.log
              chown ubuntu:ubuntu /home/ubuntu/locust_master.log

              # Install Locust if not already installed
              if ! command -v locust &> /dev/null; then
                  apt update
                  apt install -y python3-pip
                  pip3 install locust
              fi

              # Start Locust Master as ubuntu user
              su - ubuntu -c "nohup locust -f /home/ubuntu/locustfile.py --master --master-bind-port=5557 --web-port=8089 > /home/ubuntu/locust_master.log 2>&1 &"
              EOF
}

User Data ์Šคํฌ๋ฆฝํŠธ๋Š” ์ธ์Šคํ„ด์Šค๊ฐ€ ์‹œ์ž‘๋  ๋•Œ ์ž๋™์œผ๋กœ ์‹คํ–‰๋˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋Š” ์Šคํฌ๋ฆฝํŠธ์ž…๋‹ˆ๋‹ค.

exec > >(tee /var/log/user-data.log|logger -t user-data -s 2>/dev/console) 2>&1)

User Data ์Šคํฌ๋ฆฝํŠธ์˜ ์ถœ๋ ฅ์„ /var/log/user-data.log์— ๊ธฐ๋กํ•˜๊ฒŒ ํ•˜๋Š” ์Šคํฌ๋ฆฝํŠธ

echo "${local.locustfile_content}" | base64 --decode > /home/ubuntu/locustfile.py

์œ„๋Š” locustfile ๋‚ด์šฉ์„ locustfile.py๋กœ ์ง์ ‘ ์“ฐ๋Š” ์—ญํ• ์„ ํ•˜๋Š”๋ฐ, "๊ฐ€ ์‚ฌ๋ผ์ง€๋Š” ํ˜„์ƒ์ด ์žˆ์–ด base64๋กœ ์ธ์ฝ”๋”ฉ ์ดํ›„ decodingํ•˜์—ฌ ์ €์žฅํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

# Ensure locustfile.py is owned by ubuntu user
chown ubuntu:ubuntu /home/ubuntu/locustfile.py
chmod 644 /home/ubuntu/locustfile.py

# Create log file for Locust master and ensure ownership
touch /home/ubuntu/locust_master.log
chown ubuntu:ubuntu /home/ubuntu/locust_master.log

user_data๊ฐ€ root๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์‚ฌ์šฉ์ž ๊ถŒํ•œ์œผ๋กœ๋„ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ธฐ์œ„ํ•ด chown์„ ์ˆ˜ํ–‰ํ•ด์คฌ์Šต๋‹ˆ๋‹ค.

su - ubuntu -c "nohup locust -f /home/ubuntu/locustfile.py --master --master-bind-port=5557 --web-port=8089 > /home/ubuntu/locust_master.log 2>&1 &"

ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ubuntu ์‚ฌ์šฉ์ž๋กœ ์ „ํ™˜ํ•œ ํ›„, ๋”ฐ์˜ดํ‘œ ์•ˆ์— ์žˆ๋Š” ๋ช…๋ น์„ ์‹คํ–‰ํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ User Data ์Šคํฌ๋ฆฝํŠธ๋Š” root ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰๋˜์ง€๋งŒ, Locust๋Š” ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž ๊ถŒํ•œ์œผ๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๋ฏ€๋กœ su๋ฅผ ์‚ฌ์šฉํ•ด ubuntu ์‚ฌ์šฉ์ž๋กœ ์ „ํ™˜ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • nohup์„ ํ†ตํ•ด ํ•ด๋‹น ํ”„๋กœ์„ธ์Šค๊ฐ€ SIGHUP์„ ๋ฌด์‹œํ•˜๋„๋ก ํ•˜์—ฌ ์‰˜์ด ์ข…๋ฃŒ๋˜๋”๋ผ๋„ systemd์˜ ์ž์‹์œผ๋กœ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

Locust Worker ์ƒ์„ฑํ•˜๊ธฐ

๋‹ค๋ฅธ ๋ถ€๋ถ„์€ ๊ฑฐ์˜ ๋™์ผํ•˜๊ณ , Worker์—์„œ๋Š” Master๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

# Wait for Locust Master to be ready
MASTER_IP="${aws_instance.locust_master.private_ip}"
MAX_RETRIES=24
RETRY_COUNT=0
SLEEP_INTERVAL=5

while ! nc -z $MASTER_IP 5557; do
    RETRY_COUNT=$((RETRY_COUNT+1))
    if [ $RETRY_COUNT -ge $MAX_RETRIES ]; then
        echo "Failed to connect to Locust Master after $((RETRY_COUNT * SLEEP_INTERVAL)) seconds."
        exit 1
    fi
    echo "Waiting for Locust Master to be ready... ($RETRY_COUNT/$MAX_RETRIES)"
    sleep $SLEEP_INTERVAL
done

์‹คํ–‰ ํ›„ IP ์ถœ๋ ฅ

# Output the Public IP of the Locust Master
output "locust_master_ip" {
  value = aws_instance.locust_master.public_ip
}

# Output the Public IPs of the Locust Workers
output "locust_worker_ips" {
  value = [for instance in aws_instance.locust_worker : instance.public_ip]
}

์ดํ›„ terraform init, terraform apply๋ฅผ ์ง„ํ–‰ํ•˜์—ฌ ์ธ์Šคํ„ด์Šค๋ฅผ ์‹œ์ž‘ํ•œ ์ดํ›„

master์˜ IP:8089๋ฅผ ํ†ตํ•ด ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿงซ ํŒ€ ๋ฌธํ™”

โš™๏ธ ๊ตฌ์„ฑ

๐Ÿ”จ ๊ฐœ๋ฐœ ์œ„ํ‚ค

๐Ÿž Bug Report

๐Ÿงช Test Logs

Clone this wiki locally