View a markdown version of this page

컨테이너 이미지 사용자 지정 - AWS의 분산 로드 테스트

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

컨테이너 이미지 사용자 지정

테스터 이미지 로드

이 솔루션은 AWS에서 관리하는 퍼블릭 Amazon Elastic Container Registry(Amazon ECR) 이미지 리포지토리를 사용하여 구성된 테스트를 실행하는 데 사용되는 이미지를 저장합니다. 컨테이너 이미지를 사용자 지정하려면 이미지를 다시 빌드하고 자체 AWS 계정의 ECR 이미지 리포지토리에 푸시할 수 있습니다.

이 솔루션을 사용자 지정하려면 기본 컨테이너 이미지를 사용하거나 필요에 맞게이 컨테이너를 편집할 수 있습니다. 솔루션을 사용자 지정하는 경우 사용자 지정 솔루션을 빌드하기 전에 다음 코드 샘플을 사용하여 환경 변수를 선언합니다.

#!/bin/bash export REGION=aws-region-code # the AWS region to launch the solution (e.g. us-east-1) export BUCKET_PREFIX=my-bucket-name # prefix of the bucket name without the region code export BUCKET_NAME=$BUCKET_PREFIX-$REGION # full bucket name where the code will reside export SOLUTION_NAME=my-solution-name export VERSION=my-version # version number for the customized code export PUBLIC_ECR_REGISTRY=public.ecr.aws/awssolutions/distributed-load-testing-on-aws-load-tester # replace with the container registry and image if you want to use a different container image export PUBLIC_ECR_TAG=v3.1.0 # replace with the container image tag if you want to use a different container image

컨테이너 이미지를 사용자 지정하도록 선택한 경우 프라이빗 이미지 리포지토리 또는 AWS 계정의 퍼블릭 이미지 리포지토리에서 호스팅할 수 있습니다. 이미지 리소스는 코드 베이스에 있는 deployment/ecr/distributed-load-testing-on-aws-load-tester 디렉터리에 있습니다.

이미지를 빌드하여 호스트 대상으로 푸시할 수 있습니다.

자체 이미지를 생성한 후에는 사용자 지정 솔루션을 빌드하기 전에 다음 환경 변수를 선언할 수 있습니다.

#!/bin/bash export PUBLIC_ECR_REGISTRY=YOUR_ECR_REGISTRY_URI # e.g. YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/YOUR_IMAGE_NAME export PUBLIC_ECR_TAG=YOUR_ECR_TAG # e.g. latest, v3.4.0

다음 예제에서는 컨테이너 파일을 보여줍니다.

FROM public.ecr.aws/amazonlinux/amazonlinux:2023-minimal RUN dnf update -y && \ dnf install -y python3.11 python3.11-pip java-21-amazon-corretto bc procps jq findutils unzip && \ dnf clean all ENV PIP_INSTALL="pip3.11 install --no-cache-dir" # install bzt RUN $PIP_INSTALL --upgrade bzt awscli setuptools==78.1.1 h11 urllib3==2.2.2 && \ $PIP_INSTALL --upgrade bzt COPY ./.bzt-rc /root/.bzt-rc RUN chmod 755 /root/.bzt-rc # install bzt tools RUN bzt -install-tools -o modules.install-checker.exclude=selenium,gatling,tsung,siege,ab,k6,external-results-loader,locust,junit,testng,rspec,mocha,nunit,xunit,wdio,robot,newman RUN rm -rf /root/.bzt/selenium-taurus RUN mkdir /bzt-configs /tmp/artifacts ADD ./load-test.sh /bzt-configs/ ADD ./*.jar /bzt-configs/ ADD ./*.py /bzt-configs/ RUN chmod 755 /bzt-configs/load-test.sh RUN chmod 755 /bzt-configs/ecslistener.py RUN chmod 755 /bzt-configs/ecscontroller.py RUN chmod 755 /bzt-configs/jar_updater.py RUN python3.11 /bzt-configs/jar_updater.py # Remove jar files from /tmp RUN rm -rf /tmp/jmeter-plugins-manager-1* && \ rm -rf /usr/local/lib/python3.11/site-packages/setuptools-65.5.0.dist-info && \ rm -rf /usr/local/lib/python3.11/site-packages/urllib3-1.26.17.dist-info # Add settings file to capture the output logs from bzt cli RUN mkdir -p /etc/bzt.d && echo '{"settings": {"artifacts-dir": "/tmp/artifacts"}}' > /etc/bzt.d/90-artifacts-dir.json WORKDIR /bzt-configs ENTRYPOINT ["./load-test.sh"]

이 디렉터리에는 컨테이너 파일 외에도 Taurus/Blazemeter 프로그램을 실행하기 전에 Amazon S3에서 테스트 구성을 다운로드하는 다음 bash 스크립트가 포함되어 있습니다.

#!/bin/bash # set a uuid for the results xml file name in S3 UUID=$(cat /proc/sys/kernel/random/uuid) pypid=0 echo "S3_BUCKET:: ${S3_BUCKET}" echo "TEST_ID:: ${TEST_ID}" echo "TEST_TYPE:: ${TEST_TYPE}" echo "FILE_TYPE:: ${FILE_TYPE}" echo "PREFIX:: ${PREFIX}" echo "UUID:: ${UUID}" echo "LIVE_DATA_ENABLED:: ${LIVE_DATA_ENABLED}" echo "MAIN_STACK_REGION:: ${MAIN_STACK_REGION}" cat /proc/self/cgroup TASK_ID=$(grep -oE '[a-f0-9]{32}' /proc/self/cgroup | head -n 1) echo $TASK_ID sigterm_handler() { if [ $pypid -ne 0 ]; then echo "container received SIGTERM." kill -15 $pypid wait $pypid exit 143 #128 + 15 fi } trap 'sigterm_handler' SIGTERM echo "Download test scenario" aws s3 cp s3://$S3_BUCKET/test-scenarios/$TEST_ID-$AWS_REGION.json test.json --region $MAIN_STACK_REGION # Set the default log file values to jmeter LOG_FILE="jmeter.log" OUT_FILE="jmeter.out" ERR_FILE="jmeter.err" KPI_EXT="jtl" # download JMeter jmx file if [ "$TEST_TYPE" != "simple" ]; then # setting the log file values to the test type LOG_FILE="${TEST_TYPE}.log" OUT_FILE="${TEST_TYPE}.out" ERR_FILE="${TEST_TYPE}.err" # set variables based on TEST_TYPE if [ "$TEST_TYPE" == "jmeter" ]; then EXT="jmx" TYPE_NAME="JMeter" # Copy *.jar to JMeter library path. See the Taurus JMeter path: https://gettaurus.org/docs/JMeter/ JMETER_LIB_PATH=`find ~/.bzt/jmeter-taurus -type d -name "lib"` echo "cp $PWD/*.jar $JMETER_LIB_PATH" cp $PWD/*.jar $JMETER_LIB_PATH elif [ "$TEST_TYPE" == "k6" ]; then curl --output /tmp/artifacts/k6.rpm https://dl.k6.io/rpm/x86_64/k6-v0.58.0-amd64.rpm rpm -ivh /tmp/artifacts/k6.rpm dnf install -y k6 rm -rf /tmp/artifacts/k6.rpm EXT="js" KPI_EXT="csv" TYPE_NAME="K6" elif [ "$TEST_TYPE" == "locust" ]; then EXT="py" TYPE_NAME="Locust" fi if [ "$FILE_TYPE" != "zip" ]; then aws s3 cp s3://$S3_BUCKET/public/test-scenarios/$TEST_TYPE/$TEST_ID.$EXT ./ --region $MAIN_STACK_REGION else aws s3 cp s3://$S3_BUCKET/public/test-scenarios/$TEST_TYPE/$TEST_ID.zip ./ --region $MAIN_STACK_REGION unzip $TEST_ID.zip echo "UNZIPPED" ls -l # If zip and locust, make sure to pick locustfile if [ "$TEST_TYPE" != "locust" ]; then TEST_SCRIPT=$(find . -name "*.${EXT}" | head -n 1) else TEST_SCRIPT=$(find . -name "locustfile.py" | head -n 1) fi # only looks for the first test script file. TEST_SCRIPT=`find . -name "*.${EXT}" | head -n 1` echo $TEST_SCRIPT if [ -z "$TEST_SCRIPT" ]; then echo "There is no test script (.${EXT}) in the zip file." exit 1 fi sed -i -e "s|$TEST_ID.$EXT|$TEST_SCRIPT|g" test.json # copy bundled plugin jars to jmeter extension folder to make them available to jmeter BUNDLED_PLUGIN_DIR=`find $PWD -type d -name "plugins" | head -n 1` # attempt to copy only if a /plugins folder is present in upload if [ -z "$BUNDLED_PLUGIN_DIR" ]; then echo "skipping plugin installation (no /plugins folder in upload)" else # ensure the jmeter extensions folder exists JMETER_EXT_PATH=`find ~/.bzt/jmeter-taurus -type d -name "ext"` if [ -z "$JMETER_EXT_PATH" ]; then # fail fast - if plugins bundled they will be needed for the tests echo "jmeter extension path (~/.bzt/jmeter-taurus/**/ext) not found - cannot install bundled plugins" exit 1 fi cp -v $BUNDLED_PLUGIN_DIR/*.jar $JMETER_EXT_PATH fi fi fi #Download python script if [ -z "$IPNETWORK" ]; then python3.11 -u $SCRIPT $TIMEOUT & pypid=$! wait $pypid pypid=0 else aws s3 cp s3://$S3_BUCKET/Container_IPs/${TEST_ID}_IPHOSTS_${AWS_REGION}.txt ./ --region $MAIN_STACK_REGION export IPHOSTS=$(cat ${TEST_ID}_IPHOSTS_${AWS_REGION}.txt) python3.11 -u $SCRIPT $IPNETWORK $IPHOSTS fi echo "Running test" stdbuf -i0 -o0 -e0 bzt test.json -o modules.console.disable=true | stdbuf -i0 -o0 -e0 tee -a result.tmp | sed -u -e "s|^|$TEST_ID $LIVE_DATA_ENABLED |" CALCULATED_DURATION=`cat result.tmp | grep -m1 "Test duration" | awk -F ' ' '{ print $5 }' | awk -F ':' '{ print ($1 * 3600) + ($2 * 60) + $3 }'` # upload custom results to S3 if any # every file goes under $TEST_ID/$PREFIX/$UUID to distinguish the result correctly if [ "$TEST_TYPE" != "simple" ]; then if [ "$FILE_TYPE" != "zip" ]; then cat $TEST_ID.$EXT | grep filename > results.txt else cat $TEST_SCRIPT | grep filename > results.txt fi if [ -f results.txt ]; then sed -i -e 's/<stringProp name="filename">//g' results.txt sed -i -e 's/<\/stringProp>//g' results.txt sed -i -e 's/ //g' results.txt echo "Files to upload as results" cat results.txt files=(`cat results.txt`) extensions=() for f in "${files[@]}"; do ext="${f##*.}" if [[ ! " ${extensions[@]} " =~ " ${ext} " ]]; then extensions+=("$ext") fi done # Find all files in the current folder with the same extensions all_files=() for ext in "${extensions[@]}"; do for f in *."$ext"; do all_files+=("$f") done done for f in "${all_files[@]}"; do p="s3://$S3_BUCKET/results/$TEST_ID/${TYPE_NAME}_Result/$PREFIX/$UUID/$f" if [[ $f = /* ]]; then p="s3://$S3_BUCKET/results/$TEST_ID/${TYPE_NAME}_Result/$PREFIX/$UUID$f" fi echo "Uploading $p" aws s3 cp $f $p --region $MAIN_STACK_REGION done fi fi if [ -f /tmp/artifacts/results.xml ]; then # Insert the Task ID at the same level as <FinalStatus> curl -s $ECS_CONTAINER_METADATA_URI_V4/task Task_CPU=$(curl -s $ECS_CONTAINER_METADATA_URI_V4/task | jq '.Limits.CPU') Task_Memory=$(curl -s $ECS_CONTAINER_METADATA_URI_V4/task | jq '.Limits.Memory') START_TIME=$(curl -s "$ECS_CONTAINER_METADATA_URI_V4/task" | jq -r '.Containers[0].StartedAt') # Convert start time to seconds since epoch START_TIME_EPOCH=$(date -d "$START_TIME" +%s) # Calculate elapsed time in seconds CURRENT_TIME_EPOCH=$(date +%s) ECS_DURATION=$((CURRENT_TIME_EPOCH - START_TIME_EPOCH)) sed -i.bak 's/<\/FinalStatus>/<TaskId>'"$TASK_ID"'<\/TaskId><\/FinalStatus>/' /tmp/artifacts/results.xml sed -i 's/<\/FinalStatus>/<TaskCPU>'"$Task_CPU"'<\/TaskCPU><\/FinalStatus>/' /tmp/artifacts/results.xml sed -i 's/<\/FinalStatus>/<TaskMemory>'"$Task_Memory"'<\/TaskMemory><\/FinalStatus>/' /tmp/artifacts/results.xml sed -i 's/<\/FinalStatus>/<ECSDuration>'"$ECS_DURATION"'<\/ECSDuration><\/FinalStatus>/' /tmp/artifacts/results.xml echo "Validating Test Duration" TEST_DURATION=$(grep -E '<TestDuration>[0-9]+.[0-9]+</TestDuration>' /tmp/artifacts/results.xml | sed -e 's/<TestDuration>//' | sed -e 's/<\/TestDuration>//') if (( $(echo "$TEST_DURATION > $CALCULATED_DURATION" | bc -l) )); then echo "Updating test duration: $CALCULATED_DURATION s" sed -i.bak.td 's/<TestDuration>[0-9]*\.[0-9]*<\/TestDuration>/<TestDuration>'"$CALCULATED_DURATION"'<\/TestDuration>/' /tmp/artifacts/results.xml fi if [ "$TEST_TYPE" == "simple" ]; then TEST_TYPE="jmeter" fi echo "Uploading results, bzt log, and JMeter log, out, and err files" aws s3 cp /tmp/artifacts/results.xml s3://$S3_BUCKET/results/${TEST_ID}/${PREFIX}-${UUID}-${AWS_REGION}.xml --region $MAIN_STACK_REGION aws s3 cp /tmp/artifacts/bzt.log s3://$S3_BUCKET/results/${TEST_ID}/bzt-${PREFIX}-${UUID}-${AWS_REGION}.log --region $MAIN_STACK_REGION aws s3 cp /tmp/artifacts/$LOG_FILE s3://$S3_BUCKET/results/${TEST_ID}/${TEST_TYPE}-${PREFIX}-${UUID}-${AWS_REGION}.log --region $MAIN_STACK_REGION aws s3 cp /tmp/artifacts/$OUT_FILE s3://$S3_BUCKET/results/${TEST_ID}/${TEST_TYPE}-${PREFIX}-${UUID}-${AWS_REGION}.out --region $MAIN_STACK_REGION aws s3 cp /tmp/artifacts/$ERR_FILE s3://$S3_BUCKET/results/${TEST_ID}/${TEST_TYPE}-${PREFIX}-${UUID}-${AWS_REGION}.err --region $MAIN_STACK_REGION aws s3 cp /tmp/artifacts/kpi.${KPI_EXT} s3://$S3_BUCKET/results/${TEST_ID}/kpi-${PREFIX}-${UUID}-${AWS_REGION}.${KPI_EXT} --region $MAIN_STACK_REGION else echo "An error occurred while the test was running." fi

Dockerfile 및 bash 스크립트 외에도 디렉터리에 두 개의 Python 스크립트가 포함됩니다. 각 작업은 bash 스크립트 내에서 Python 스크립트를 실행합니다. 작업자 작업은 ecslistener.py 스크립트를 실행하는 반면 리더 작업은 ecscontroller.py 스크립트를 실행합니다. ecslistener.py 스크립트는 포트 50000에 소켓을 생성하고 메시지를 기다립니다. ecscontroller.py 스크립트는 소켓에 연결하여 테스트 시작 메시지를 작업자 작업에 전송하므로 동시에 시작할 수 있습니다.

웹 콘솔 이미지(ALB + ECS Fargate 템플릿만 해당)

ALB + ECS Fargate 템플릿은 ECS Fargate에서 웹 콘솔을 컨테이너로 실행합니다. 기본적으로 솔루션은 AWS에서 관리하는 퍼블릭 Amazon ECR 리포지토리에서 웹 콘솔 이미지를 가져옵니다.

퍼블릭 컨테이너 레지스트리에 대한 액세스가 제한된 환경(예: 인터넷 액세스가 없는 VPCs 또는 ECR 퍼블릭 액세스 정책이 있는 계정)에서는 퍼블릭 이미지를 프라이빗 Amazon ECR 리포지토리에 미러링하고 웹 콘솔 이미지 URI CloudFormation 파라미터에 프라이빗 이미지 URI를 제공해야 합니다. CloudFormation 이 접근 방식을 사용하여 필요에 맞게 웹 콘솔 이미지를 사용자 지정할 수도 있습니다.

퍼블릭 이미지를 프라이빗 ECR 리포지토리에 미러링

웹 콘솔 이미지를 프라이빗 ECR 리포지토리에 미러링하려면:

  1. 퍼블릭 ECR 레지스트리에 인증:

    $ aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws
  2. 퍼블릭 웹 콘솔 이미지를 가져옵니다.

    $ docker pull public.ecr.aws/aws-solutions/distributed-load-testing-on-aws-web-console:<version>
  3. 계정에 프라이빗 ECR 리포지토리를 생성합니다(아직 존재하지 않는 경우).

    $ aws ecr create-repository --repository-name <your-repo-name> --region <region> 2>/dev/null || true
  4. 프라이빗 ECR 레지스트리에 인증:

    $ aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com
  5. 이미지에 태그를 지정하고 프라이빗 리포지토리로 푸시합니다.

    $ docker tag public.ecr.aws/aws-solutions/distributed-load-testing-on-aws-web-console:<version> \ <account-id>.dkr.ecr.<region>.amazonaws.com/<your-repo-name>:<version> $ docker push <account-id>.dkr.ecr.<region>.amazonaws.com/<your-repo-name>:<version>
  6. ALB + ECS Fargate 스택을 시작할 때 웹 콘솔 이미지 URI 파라미터에 프라이빗 이미지 URI를 입력합니다.

    <account-id>.dkr.ecr.<region>.amazonaws.com/<your-repo-name>:<version>

웹 콘솔 이미지 사용자 지정

웹 콘솔 이미지를 사용자 지정할 수도 있습니다. 이미지 리소스는 GitHub 리포지토리에 있는 deployment/ecr/distributed-load-testing-on-aws-web-console 디렉터리에 있습니다. 필요에 맞게 컨테이너 파일을 편집하고, 로컬에서 이미지를 빌드하고, 프라이빗 ECR 리포지토리로 푸시하고, 웹 콘솔 이미지 URI 파라미터에 URI를 제공합니다.

웹 콘솔 컨테이너는 Nginx를 사용하여 정적 웹 애플리케이션을 제공합니다. 시작 시 진입점 스크립트는 Amazon S3에서 웹 콘솔 자산을 다운로드하고 Nginx 문서 루트로 추출합니다.

다음 예제에서는 컨테이너 파일을 보여줍니다.

FROM public.ecr.aws/nginx/nginx:alpine # Install AWS CLI for S3 operations RUN apk upgrade --no-cache zlib libpng && \ apk add --no-cache aws-cli # Copy nginx configuration COPY nginx.conf /etc/nginx/nginx.conf # Copy entrypoint script COPY entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh EXPOSE 80 ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

진입점 스크립트는 S3에서 웹 콘솔 자산을 다운로드하고 Nginx를 시작합니다.

#!/bin/sh set -e S3_URI="s3://${S3_BUCKET}/${S3_KEY}" echo "[$(date -Iseconds)] Downloading from $S3_URI" # AWS CLI has built-in retry with exponential backoff (standard mode) AWS_MAX_ATTEMPTS=5 aws s3 cp "$S3_URI" /tmp/web-app.zip echo "[$(date -Iseconds)] Download successful" # Extract and cleanup unzip -o /tmp/web-app.zip -d /usr/share/nginx/html/ rm -f /tmp/web-app.zip # Start Nginx exec nginx -g 'daemon off;'

Nginx 구성은 ALB, gzip 압축, 정적 자산 캐싱 및 보안 헤더에 대한 상태 확인 지원을 통해 단일 페이지 애플리케이션(SPA)을 제공합니다.

events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml; server { listen 80; root /usr/share/nginx/html; index index.html; # Health check endpoint for ALB location /healthz { return 200 'OK'; } # SPA routing location / { try_files $uri $uri/ /index.html; } # Cache static assets for 1 year location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } # No cache for index.html (SPA entry point) location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; } # No cache for runtime config location = /aws-exports.json { add_header Cache-Control "no-store, no-cache, must-revalidate"; } # Security headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; } }

자세한 내용은 Amazon ECR 사용 설명서의 Amazon ECR 프라이빗 리포지토리로 도커 이미지 푸시를 참조하세요.