この記事は、ゆるWeb勉強会@札幌 Advent Calendar 2019の20日目(2019-12-20)の記事です。
##はじめに
この記事ではEKSクラスタ上にmaster-slave構成のJMeterを構築し、
ある程度大量のリクエストが可能な負荷試験環境を作成することをゴールとしています。
※少量の負荷であれば1台のJMeterもしくは、ApacheBenchやvegeta(READMEが好き)などで十分
当初、EKSのnodegroup上にJMeterクラスタを構築し高負荷を与える検証環境を作る予定でしたが、
Re:InventでFargate for EKSが発表されたのでそちらに切り替えて作成していきます。
Fargate for EKS
従来はECSのみで利用可能だったFargateがEKSでも使えるようになり、
EC2ワーカーノードの管理を必要とせずまたKubernetersの専門的な知識を必要とせず
クラスターを稼働させることが出来るとても素晴らしいアップデートと思います。
詳しくは、
https://aws.amazon.com/jp/blogs/news/amazon-eks-on-aws-fargate-now-generally-available/
https://dev.classmethod.jp/cloud/aws/try-amazon-fargate-for-amazon-eks/
などで紹介されているのでここでは特に言及はしません。
Fargate for EKS料金
気になる料金についてはpodを実行するために必要なvCPU,メモリリソースを支払うFargateの料金(Fargateの料金プラン)に加えEKSクラスタの利用料金が発生します。
負荷環境としては必要なときだけ立ち上げて利用すればいいのでコスト面はそれほど気にせず利用できます。
##ソースコード
今回検証で利用したソースコードはここに置いてあります
https://github.com/hirokawai/load-test
この記事では一部説明するために長ったらしく書いてますが、
実行コマンドはスクリプトにまとめてあり、実行手順はREADMEに書いてあるのでその通りに実行すれば稼働します。
##EKSクラスタの作成
実行環境のツールバージョンは以下です
% aws --version
aws-cli/1.16.290 Python/3.7.5 Darwin/18.2.0 botocore/1.13.26
% eksctl version
[ℹ] version.Info{BuiltAt:"", GitCommit:"", GitTag:"0.11.1"}
% kubectl version --client --short
Client Version: v1.16.3
環境作成と実行の流れ
- ECRイメージの作成
- EKSクラスタの作成
- fargateprofileの作成
- JMeterクラスタの作成
- 負荷テスト
- 集計データの取得
- 後片付け・削除
といった流れで実行していきます
##ECRイメージの作成
slaveとmasterのDockerfileを用意しbuildします。
基本的にはalpine上にJMeterをインストールしserverモードで起動できるようにセットアップしていくだけです。
コンテナのimageの取得先としてECRにimageを登録していきます。
docker imageの作成
・jmeter-slave:latest
・jmeter-master:latest
というタグがついたimageを作成します
% docker build --tag="jmeter-slave:latest" -f Dockerfile_slave .
% docker build --tag="jmeter-master:latest" -f Dockerfile_master .
Dockerfile_master
masterは必要なアプリケーションをインストールし、
後ほどdeploymentで定義しますが起動しっぱなしする想定で作成します。
FROM alpine:3.9
ARG JMETER_VERSION="5.1.1"
ARG PLUGIN_VERSION="1.4.0"
ENV JMETER_HOME /opt/apache-jmeter-${JMETER_VERSION}
ENV JMETER_BIN ${JMETER_HOME}/bin
ENV JMETER_DOWNLOAD_URL https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-${JMETER_VERSION}.tgz
ENV PLUGIN_DOWNLOAD_URL https://jmeter-plugins.org/downloads/file/JMeterPlugins-Standard-${PLUGIN_VERSION}.zip
# Install extra packages
ARG TZ="Asia/Tokyo"
RUN apk update \
&& apk upgrade \
&& apk add ca-certificates \
&& update-ca-certificates \
&& apk add --update openjdk8-jre tzdata curl unzip bash \
&& apk add --no-cache nss \
&& apk add python \
&& rm -rf /var/cache/apk/* \
&& mkdir -p /tmp/dependencies \
&& curl -L --silent ${JMETER_DOWNLOAD_URL} > /tmp/dependencies/apache-jmeter-${JMETER_VERSION}.tgz \
&& curl -L --silent ${PLUGIN_DOWNLOAD_URL} > /tmp/dependencies/JMeterPlugins-${PLUGIN_VERSION}.zip \
&& mkdir -p /opt \
&& tar -xzf /tmp/dependencies/apache-jmeter-${JMETER_VERSION}.tgz -C /opt \
&& unzip -oq "/tmp/dependencies/JMeterPlugins-${PLUGIN_VERSION}.zip" -d $JMETER_HOME \
&& rm -rf /tmp/dependencies
ENV PATH $PATH:$JMETER_BIN
WORKDIR ${JMETER_HOME}
#==============
# Expose Ports
#==============
EXPOSE 60000
Dockerfile_slave
slaveは必要なアプリケーションをインストールし、
container_start_slave.shでJMeterを起動します
FROM alpine:3.9
ARG JMETER_VERSION="5.1.1"
ARG PLUGIN_VERSION="1.4.0"
ENV JMETER_HOME /opt/apache-jmeter-${JMETER_VERSION}
ENV JMETER_BIN ${JMETER_HOME}/bin
ENV JMETER_DOWNLOAD_URL https://archive.apache.org/dist/jmeter/binaries/apache-jmeter-${JMETER_VERSION}.tgz
ENV PLUGIN_DOWNLOAD_URL https://jmeter-plugins.org/downloads/file/JMeterPlugins-Standard-${PLUGIN_VERSION}.zip
# Install extra packages
ARG TZ="Asia/Tokyo"
RUN apk update \
&& apk upgrade \
&& apk add ca-certificates \
&& update-ca-certificates \
&& apk add --update openjdk8-jre tzdata curl unzip bash \
&& apk add --no-cache nss \
&& apk add python \
&& rm -rf /var/cache/apk/* \
&& mkdir -p /tmp/dependencies \
&& curl -L --silent ${JMETER_DOWNLOAD_URL} > /tmp/dependencies/apache-jmeter-${JMETER_VERSION}.tgz \
&& curl -L --silent ${PLUGIN_DOWNLOAD_URL} > /tmp/dependencies/JMeterPlugins-${PLUGIN_VERSION}.zip \
&& mkdir -p /opt \
&& tar -xzf /tmp/dependencies/apache-jmeter-${JMETER_VERSION}.tgz -C /opt \
&& unzip -oq "/tmp/dependencies/JMeterPlugins-${PLUGIN_VERSION}.zip" -d $JMETER_HOME \
&& rm -rf /tmp/dependencies
ENV PATH $PATH:$JMETER_BIN
WORKDIR ${JMETER_HOME}
ADD container_start_slave.sh /usr/local/bin
RUN chmod +x /usr/local/bin/container_start_slave.sh
#==============
# Expose Ports
#==============
EXPOSE 1099 50000
ENTRYPOINT []
CMD ["container_start_slave.sh"]
ECRレポジトリを作成
% aws ecr create-repository --repository-name jmeter-slave
% aws ecr create-repository --repository-name jmeter-master
ECRにpush
ECRログイン
% $(aws ecr get-login --no-include-email --region ap-northeast-1)
master
% docker tag jmeter-master:latest xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/jmeter-master:latest
% docker push xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/jmeter-master:latest
slave
% docker tag jmeter-slave:latest xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/jmeter-slave:latest
% docker push xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/jmeter-slave:latest
EKSクラスタの作成
ポイントは「--fargate」を指定している部分です。
このオプションによりFargate profileが作成され、kube-system namespaceに作成されるPODがFargate上で実行可能になります。
--fargate Create a Fargate profile scheduling pods in the default and kube-system namespaces onto Fargate
% eksctl create cluster --name jmeter-load-test --region ap-northeast-1--version 1.14 --vpc-private-subnets=subnet-aaa,subnet-bbb --vpc-public-subnets=subnet-ccc,subnet-ddd --node-private-networking --fargate --without-nodegroup
※ 負荷を与える対象がVPCに存在しVPC内で実行したい背景があったので、subnet指定等しています
fargateprofileの作成
今回namespace「jmeter」を作成し実行したいのでfargateprofileを作成します。
この設定によりPod selectorの指定に合致したPodがFargate上で実行できます。
% eksctl create fargateprofile --name fp-jmeter-load-test --namespace jmeter --cluster jmeter-load-test
JMeterクラスタの作成
・namespaceとしてjmeter
・Deploymentとしてjmeter-master,jmeter-salves
・Serviceとしてjmeter-slaves-svc
・ConfigMap(masterの実行スクリプトを定義しているだけ) jmeter-load-test
を作成します。
詳細はmanifestファイルをGithubにあげているので割愛しますが
リモートクライアントとして1099ポートで待受を行い、
master-slave間はPODのIPを取得しRMI通信を50000ポートで行うようになっています。
% kubectl create namespace jmeter
% kubectl create -n jmeter -f jmeter_slaves_deploy.yaml
% kubectl create -n jmeter -f jmeter_slaves_svc.yaml
% kubectl create -n jmeter -f jmeter_master_configmap.yaml
% kubectl create -n jmeter -f jmeter_master_deploy.yaml
コンテナリソース設定が必要であればDeploymentに適当に設定します
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
クラスタの状態を確認するとこのような状態になっています
% kubectl get all -o wide -n jmeter
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/jmeter-master-1111 1/1 Running 0 175m 172.20.17.40 fargate-ip-172-20-17-40.ap-northeast-1.compute.internal <none> <none>
pod/jmeter-slaves-2222 1/1 Running 0 175m 172.20.21.151 fargate-ip-172-20-21-151.ap-northeast-1.compute.internal <none> <none>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
service/jmeter-slaves-svc ClusterIP None <none> 1099/TCP,50000/TCP 175m jmeter_mode=slave
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/jmeter-master 1/1 1 1 175m jmmaster xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/jmeter-master:latest jmeter_mode=master
deployment.apps/jmeter-slaves 1/1 1 1 175m jmslave xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/jmeter-slave:latest jmeter_mode=slave
NAME DESIRED CURRENT READY AGE CONTAINERS IMAGES SELECTOR
replicaset.apps/jmeter-master-665cc5d67f 1 1 1 175m jmmaster xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/jmeter-master:latest jmeter_mode=master,pod-template-hash=xxxx
replicaset.apps/jmeter-slaves-6ff95f6cbd 1 1 1 175m jmslave xxxx.dkr.ecr.ap-northeast-1.amazonaws.com/jmeter-slave:latest jmeter_mode=slave,pod-template-hash=xxxx
負荷テスト
slaveの台数はreplica数で増減可能です。負荷の規模に応じて調整します。
kubectl -n jmeter scale deployment/jmeter-slaves --replicas=5
-f で負荷シナリオファイル
-G でシナリオに展開するプロパティ&値を設定
シナリオファイルはGUIで作成しExportしました。
この例ではユーザ定義変数がある想定で外から渡せるようにしてあります。
./container_start_test.sh -f/load-test.jmx -GTARGET_HOST=xxxx -GTHREAD=1 -GRAMPUP=10 -GLOOP=10
実行するとログが出力されていきます
Creating summariser <summary>
Created the tree successfully using /load-test.jmx
Configuring remote engine: 172.20.16.202
Configuring remote engine: 172.20.16.229
Configuring remote engine: 172.20.16.68
Configuring remote engine: 172.20.17.60
Configuring remote engine: 172.20.21.151
Starting remote engines
Starting the test @ Tue Dec 10 08:49:52 GMT 2019 (1575967792047)
summary + 1 in 00:00:01 = 0.8/s Avg: 933 Min: 933 Max: 933 Err: 0 (0.00%) Active: 1 Started: 0 Finished: 0
Remote engines have been started
Waiting for possible Shutdown/StopTestNow/HeapDump/ThreadDump message on port 4445
Tidying up remote @ Tue Dec 10 08:50:05 GMT 2019 (1575967805280)
Tidying up remote @ Tue Dec 10 08:50:09 GMT 2019 (1575967809218)
Tidying up remote @ Tue Dec 10 08:50:09 GMT 2019 (1575967809975)
... end of run
集計データの取得
stdoutにログが流れていくのでそれで十分だったりもしますが、
jmeterの集計結果はmasterの/report/result.jtlに作成されるようにしてあります。
以下の様にファイルを取得可能です。
ベストプラクティス的にはログは外部に出し、
ステートを持たない方が良いのですが小軽く実行できるようにしています。
master_pod=`kubectl get po -n jmeter | grep jmeter-master | awk '{print $1}'`
kubectl cp -n jmeter "$master_pod:/report" ./report
後片付け・削除
負荷試験がしたくなったらまた構築すればいいので、
検証が終わったら作成したリソースを削除し料金が発生しないようにしておきます
kubernetesリソースを削除
% kubectl delete -n jmeter -f jmeter_slaves_deploy.yaml
% kubectl delete -n jmeter -f jmeter_slaves_svc.yaml
% kubectl delete -n jmeter -f jmeter_master_configmap.yaml
% kubectl delete -n jmeter -f jmeter_master_deploy.yaml
% kubectl delete namespace jmeter
eksクラスタを削除
% eksctl delete cluster --name jmeter-load-test
[ℹ] eksctl version 0.11.1
[ℹ] using region ap-northeast-1
[ℹ] deleting EKS cluster "tmp-load-test"
[ℹ] deleting Fargate profile "fp-default"
[ℹ] deleted Fargate profile "fp-jmeter-load-test"
[ℹ] deleted 3 Fargate profile(s)
[✔] kubeconfig has been updated
[ℹ] cleaning up LoadBalancer services
[ℹ] 1 task: { delete cluster control plane "jmeter-load-test" [async] }
[ℹ] will delete stack "eksctl-jmeter-load-test-cluster"
[✔] all cluster resources were deleted
ECRの削除
##まとめ
以上の手順でとあるサービスに秒間数千リクエストの負荷を与えられる環境が出来ました。
Fargate for EKSのおかげでワーカーノードを全く意識せずにJMeterのクラスタ環境を構築出来ています。
アプリケーションに集中できるマネージドサービスの恩恵をがっちり享受出来た気がします。
こういったツールが手軽に実行できるのもコンテナの魅力の一つですね。
今後は運用サービスでも利用し事例を作っていければと思います。