本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
容器映像自訂
載入測試器映像
此解決方案使用 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目錄中,位於程式碼庫中。
您可以建置映像並將其推送至主機目的地。
-
對於私有 Amazon ECR 儲存庫和映像,請參閱《Amazon ECR 使用者指南》中的 Amazon ECR 私有儲存庫和私有映像。
-
如需公有 Amazon ECR 儲存庫和映像,請參閱《Amazon ECR 公有使用者指南》中的 Amazon ECR 公有儲存庫和公有映像。
建立自己的映像後,您可以在建置自訂解決方案之前宣告下列環境變數。
#!/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"]
除了容器檔案之外,目錄還包含下列 bash 指令碼,可在執行 Taurus/Blazemeter 程式之前從 Amazon S3 下載測試組態。
#!/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
除了 Dockerfileecslistener.py指令碼,而領導任務則會執行ecscontroller.py指令碼。ecslistener.py 指令碼會在連接埠 50000 上建立通訊端,並等待訊息。ecscontroller.py 指令碼會連線至通訊端,並將啟動測試訊息傳送至工作者任務,讓它們可以同時啟動。
Web 主控台映像 (僅限 AHB + ECS Fargate 範本)
ALB + ECS Fargate 範本會以 ECS Fargate 上的容器的形式執行 Web 主控台。根據預設,解決方案會從 AWS 管理的公有 Amazon ECR 儲存庫提取 Web 主控台映像。
在限制存取公有容器登錄檔的環境中 (例如,沒有網際網路存取權VPCs 或具有 ECR 公有存取政策的帳戶),您必須將公有映像鏡像至私有 Amazon ECR 儲存庫,並在 Web 主控台映像 URI CloudFormation 參數中提供私有映像 URI。 CloudFormation 您也可以使用此方法來自訂 Web 主控台映像,以符合您的需求。
將公有映像鏡像至私有 ECR 儲存庫
若要將 Web 主控台映像鏡像至私有 ECR 儲存庫:
-
驗證為公有 ECR 登錄檔:
$ aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws -
提取公有 Web 主控台映像:
$ docker pull public.ecr.aws/aws-solutions/distributed-load-testing-on-aws-web-console:<version> -
在帳戶中建立私有 ECR 儲存庫 (如果尚未存在):
$ aws ecr create-repository --repository-name <your-repo-name> --region <region> 2>/dev/null || true -
驗證您的私有 ECR 登錄檔:
$ aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account-id>.dkr.ecr.<region>.amazonaws.com -
標記並推送映像到您的私有儲存庫:
$ 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> -
啟動 ALB + ECS Fargate 堆疊時,請在 Web 主控台映像 URI 參數中輸入私有映像 URI:
<account-id>.dkr.ecr.<region>.amazonaws.com/<your-repo-name>:<version>
自訂 Web 主控台映像
您也可以自訂 Web 主控台映像。映像資源位於 deployment/ecr/distributed-load-testing-on-aws-web-console目錄中,位於 GitHub 儲存庫
Web 主控台容器使用 Nginx 來提供靜態 Web 應用程式。啟動時,進入點指令碼會從 Amazon S3 下載 Web 主控台資產,並將其擷取至 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 下載 Web 主控台資產,並啟動 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 組態為單頁應用程式 (SPA) 提供 ALB、gzip 壓縮、靜態資產快取和安全性標頭的運作狀態檢查支援。
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 使用者指南》中的將 Docker 映像推送至 Amazon ECR 私有儲存庫。