OCIのサービスであるOKE(Oracle Container Engine for Kubernetes)を使い、
jmeterクラスター環境を構築する手順です。
-
前提
Kubernetes実行環境はOracleLinux7で構築。
Selinux,firewalldは無効化。 -
今回導入する構成
Master POD (Jmeter実行のMaster)
Slave POD その1 (Jmeter実行のslave)
Slave POD その2 (Jmeter実行のslave)
influx DB (コンテナの監視取得)
Grafana (監視をグラフにして可視化)
上記を3つのVMで構成します。
Oracle Container Servicesにサインイン
OracleのContainer Service用リポジトリが利用できるようこちらにアクセスします。
右上「Sign in」より自身のOracleアカウントでサインイン。
前準備
※rootユーザからの作業です。
dockerインストール・設定
リポジトリ設定
# vi /etc/yum.repos.d/docker.repo
以下記載
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/oraclelinux/7
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
インストール
# yum install docker-engine
確認
# dockerd --version
起動、自動起動
# systemctl start docker.service
# systemctl enable docker.service
Oracle Container Registryログイン
# docker login container-registry.oracle.com/kubernetes
→Oracleアカウントのユーザ名、パスワード入力
iptables設定
# iptables -P FORWARD ACCEPT
# iptables-save > /etc/sysconfig/iptables
Kubernetes環境準備
kubectlコマンド以外も使いたかったのでkubeadmをセットアップします。
インストール
# yum install kubeadm
kubeadm-setup.sh確認
# which kubeadm-setup.sh
rebootしておく
# shutdown -r now
kubeadm-setup.sh実行
# kubeadm-setup.sh up
Kubernetes操作用ユーザ作成
# useradd kubeuser
sudo設定
# echo 'kubeuser ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/kubeuser
su
切り替え
# su - kubeuser
Kubernetes の設定を読み込むように設定
$ sudo cp /etc/kubernetes/admin.conf $HOME/
$ sudo chown $(id -u):$(id -g) $HOME/admin.conf
$ ls -l $HOME/admin.conf
$ export KUBECONFIG=$HOME/admin.conf
$ echo 'export KUBECONFIG=$HOME/admin.conf' >> $HOME/.bashrc
実行確認(node確認コマンド)
$ kubectl get nodes
OCI CLI導入
インストール
$ bash -c "$(curl ?L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)"
セットアップ
$ oci setup config
ユーザにAPI Keyを追加
$ cat ~/.oci/oci_api_key_public.pem
この公開鍵をコピーしてOCIコンソール上にて、
Identity > Users > 該当ユーザ選択 > Add Public Key > コピーした公開鍵を貼り付けてAdd
OKEクラスター構築
OCIコンソール上、左メニューよりDeveloper Services > Container Clusters(OKE)
設定
Cluster Creation画面で、対象項目を入力/選択
NAME:任意。今回はcluster1
KUBERNETES VERSION: 今回は最新Verのv1.12.7
QUICK CREATEを選択:必要なネットワーク周りを自動で構成
QUANTITY PER SUBNET:1サブネットあたりのノード数指定
PUBLIC SSH KEY: 自分の公開鍵を入力
上記入力し「create」ボタンをクリック。
Cluster StatusがActiveとなり、Node PoolsよりNodeもActiveとなれば完成です。
※デフォルトで3つのNodeを作成します。そしてデプロイに結構時間かかります。
OKEクラスター接続
作成したクラスタの「Access Kubeconfig」を選択します。
ここで2.oci~から始まるコマンドをメモに控えます。
※Kubernetes開発環境にてkubeuserで作業
既存configバックアップ
$ cp ~/admin.conf ~/.kube/config_bak1
jmeter-clusterを「.kube/config」に設定(先ほど控えたコマンドを実行します。)
$ oci ce cluster create-kubeconfig --cluster-id ocid1.cluster.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --file $HOME/.kube/config --region xx-xxxxxx-1
jmeter-clusterのconfigをバックアップ
$ cp ~/.kube/config ~/.kube/config_bak2
2つのconfigマージ
$ KUBECONFIG=~/.kube/config_bak1:~/.kube/config_bak2 kubectl config view --flatten > ~/.kube/config
環境変数修正
$ export KUBECONFIG=$HOME/.kube/config
$ vi ~/.bashrc
※以下に変更
export KUBECONFIG=$HOME/.kube/config
コンテキストのクラスタ一覧確認
$ kubectl config get-clusters
※以下のように表示される
NAME
cluster-xxxxxxxxxxx
kubernete
現在のクラスター確認 (kubernetes-admin@kubernetesが表示される)
$ kubectl config current-context
接続クラスター変更
$ kubectl config use-context "context-xxxxxxxxxxx"
現在のクラスター確認 (変更されたことを確認)
$ kubectl config current-context
クラスターにアクセスできるか確認
$ kubectl get nodes
※3ノード表示されたらOKです。
JMeterクラスター構築
今回こちらのソースを使用します。
https://github.com/kubernauts/jmeter-operator
クローン
$ git clone https://github.com/kubernauts/jmeter-kubernetes.git
$ cd jmeter-kubernetes
スクリプトに権限付与
$ chmod +x jmeter_cluster_create.sh dashboard.sh start_test.sh
Jmeter実行環境構築スクリプト実行
$ ./jmeter_cluster_create.sh
Namespaceの入力を求められるので、任意の名前入力
Enter the name of the new tenant unique name, this will be used to create the namespace
例)test
pod確認
$ kubectl -n test get pod -o wide
※5つのpodが表示されること。
NAME READY STATUS RESTARTS AGE IP NODE
influxdb-jmeter-xxxxxxxxx-xxxxx 1/1 Running 0 5h x.x.x.x x.x.x.x
jmeter-grafana-xxxxxxxxx-xxxxx 1/1 Running 0 5h x.x.x.x x.x.x.x
jmeter-master-xxxxxxxxx-xxxxx 1/1 Running 0 5h x.x.x.x x.x.x.x
jmeter-slaves-xxxxxxxxx-xxxxx 1/1 Running 0 5h x.x.x.x x.x.x.x
jmeter-slaves-xxxxxxxxx-xxxxx 1/1 Running 0 5h x.x.x.x x.x.x.x
influxDB,Grafana設定スクリプト実行
$ ./dashboard.sh
grafana画面表示
プライベートネットワーク上で構築しているため、直接IPを指定しても接続できません。
今回ポートフォワードを使い接続します。
3001番でgrafana起動
kubectl -n test port-forward jmeter-grafana-xxxxxxxxxxx-xxxxx 3001:3000
terratermを使った例:
設定 > SSH転送より以下ポート設定
ローカルのポート: 888
リモート側のポート: 3001
ブラウザでアクセスするとgrafanaの画面が表示されます。
http://localhost:888
今回こちらのDashboardを使います。
https://grafana.com/grafana/dashboards/4026
Grafanaホーム画面 > 左メニュー「+」マークより上記のダッシュボードをインポートします。
シナリオのJMXファイルを用意
お試しで以下を使用します。
test.jmx
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="3.2" jmeter="3.3 r1808647">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="cloussky.jmx" enabled="true">
<stringProp name="TestPlan.comments">This test plan was created by the BlazeMeter converter v.1.1.307. Please contact support@blazemeter.com for further support.</stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
<elementProp name="TestPlan.user_defined_variables" elementType="Arguments">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<HeaderManager guiclass="HeaderPanel" testclass="HeaderManager" testname="HTTP Header manager" enabled="true">
<collectionProp name="HeaderManager.headers">
<elementProp name="Accept" elementType="Header">
<stringProp name="Header.name">Accept</stringProp>
<stringProp name="Header.value">text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8</stringProp>
</elementProp>
<elementProp name="Upgrade-Insecure-Requests" elementType="Header">
<stringProp name="Header.name">Upgrade-Insecure-Requests</stringProp>
<stringProp name="Header.value">1</stringProp>
</elementProp>
<elementProp name="User-Agent" elementType="Header">
<stringProp name="Header.name">User-Agent</stringProp>
<stringProp name="Header.value">Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36</stringProp>
</elementProp>
<elementProp name="Accept-Encoding" elementType="Header">
<stringProp name="Header.name">Accept-Encoding</stringProp>
<stringProp name="Header.value">gzip, deflate, br</stringProp>
</elementProp>
</collectionProp>
</HeaderManager>
<hashTree/>
<Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="BASE_URL_1" elementType="Argument">
<stringProp name="Argument.name">BASE_URL_1</stringProp>
<stringProp name="Argument.value">xxxxx.jp</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</Arguments>
<hashTree/>
<ConfigTestElement guiclass="HttpDefaultsGui" testclass="ConfigTestElement" testname="HTTP Request Defaults" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain"></stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol"></stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<boolProp name="HTTPSampler.image_parser">true</boolProp>
<boolProp name="HTTPSampler.concurrentDwn">true</boolProp>
<stringProp name="HTTPSampler.concurrentPool">6</stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</ConfigTestElement>
<hashTree/>
<DNSCacheManager guiclass="DNSCachePanel" testclass="DNSCacheManager" testname="DNS Cache Manager" enabled="true">
<collectionProp name="DNSCacheManager.servers"/>
<boolProp name="DNSCacheManager.clearEachIteration">true</boolProp>
<boolProp name="DNSCacheManager.isCustomResolver">false</boolProp>
</DNSCacheManager>
<hashTree/>
<AuthManager guiclass="AuthPanel" testclass="AuthManager" testname="HTTP Authorization Manager" enabled="true">
<collectionProp name="AuthManager.auth_list"/>
</AuthManager>
<hashTree/>
<CookieManager guiclass="CookiePanel" testclass="CookieManager" testname="HTTP Cookie Manager" enabled="true">
<collectionProp name="CookieManager.cookies"/>
<boolProp name="CookieManager.clearEachIteration">true</boolProp>
</CookieManager>
<hashTree/>
<CacheManager guiclass="CacheManagerGui" testclass="CacheManager" testname="HTTP Cache Manager" enabled="true">
<boolProp name="clearEachIteration">true</boolProp>
<boolProp name="useExpires">false</boolProp>
</CacheManager>
<hashTree/>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group" enabled="true">
<stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
<elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" enabled="true">
<boolProp name="LoopController.continue_forever">false</boolProp>
<stringProp name="LoopController.loops">100</stringProp>
</elementProp>
<stringProp name="ThreadGroup.num_threads">2</stringProp>
<stringProp name="ThreadGroup.ramp_time">2</stringProp>
<longProp name="ThreadGroup.start_time">1363247040000</longProp>
<longProp name="ThreadGroup.end_time">1363247040000</longProp>
<boolProp name="ThreadGroup.scheduler">false</boolProp>
<stringProp name="ThreadGroup.duration">0</stringProp>
<stringProp name="ThreadGroup.delay">0</stringProp>
</ThreadGroup>
<hashTree>
<HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="toppage" enabled="true">
<elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments"/>
</elementProp>
<stringProp name="HTTPSampler.domain">${BASE_URL_1}</stringProp>
<stringProp name="HTTPSampler.port"></stringProp>
<stringProp name="HTTPSampler.protocol">https</stringProp>
<stringProp name="HTTPSampler.contentEncoding"></stringProp>
<stringProp name="HTTPSampler.path"></stringProp>
<stringProp name="HTTPSampler.method">GET</stringProp>
<boolProp name="HTTPSampler.follow_redirects">true</boolProp>
<boolProp name="HTTPSampler.auto_redirects">false</boolProp>
<boolProp name="HTTPSampler.use_keepalive">true</boolProp>
<boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
<stringProp name="HTTPSampler.embedded_url_re"></stringProp>
<stringProp name="HTTPSampler.connect_timeout"></stringProp>
<stringProp name="HTTPSampler.response_timeout"></stringProp>
</HTTPSamplerProxy>
<hashTree>
<ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
<stringProp name="ConstantTimer.delay">0</stringProp>
</ConstantTimer>
<hashTree/>
</hashTree>
<BackendListener guiclass="BackendListenerGui" testclass="BackendListener" testname="Backend Listener" enabled="true">
<elementProp name="arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" enabled="true">
<collectionProp name="Arguments.arguments">
<elementProp name="influxdbMetricsSender" elementType="Argument">
<stringProp name="Argument.name">influxdbMetricsSender</stringProp>
<stringProp name="Argument.value">org.apache.jmeter.visualizers.backend.influxdb.HttpMetricsSender</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="influxdbUrl" elementType="Argument">
<stringProp name="Argument.name">influxdbUrl</stringProp>
<stringProp name="Argument.value">http://jmeter-influxdb:8086/write?db=jmeter</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="application" elementType="Argument">
<stringProp name="Argument.name">application</stringProp>
<stringProp name="Argument.value">xxxxx</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="measurement" elementType="Argument">
<stringProp name="Argument.name">measurement</stringProp>
<stringProp name="Argument.value">jmeter</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="summaryOnly" elementType="Argument">
<stringProp name="Argument.name">summaryOnly</stringProp>
<stringProp name="Argument.value">false</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="samplersRegex" elementType="Argument">
<stringProp name="Argument.name">samplersRegex</stringProp>
<stringProp name="Argument.value">.*</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="percentiles" elementType="Argument">
<stringProp name="Argument.name">percentiles</stringProp>
<stringProp name="Argument.value">90;95;99</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="testTitle" elementType="Argument">
<stringProp name="Argument.name">testTitle</stringProp>
<stringProp name="Argument.value">Test name</stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
<elementProp name="eventTags" elementType="Argument">
<stringProp name="Argument.name">eventTags</stringProp>
<stringProp name="Argument.value"></stringProp>
<stringProp name="Argument.metadata">=</stringProp>
</elementProp>
</collectionProp>
</elementProp>
<stringProp name="classname">org.apache.jmeter.visualizers.backend.influxdb.InfluxdbBackendListenerClient</stringProp>
</BackendListener>
<hashTree/>
</hashTree>
</hashTree>
<WorkBench guiclass="WorkBenchGui" testclass="WorkBench" testname="WorkBench" enabled="true">
<boolProp name="WorkBench.save">true</boolProp>
</WorkBench>
<hashTree/>
</hashTree>
</jmeterTestPlan>
※内容について
LoopControler.loops: 100
⇒とりあえず100回にしています。
ThreadGroup.num_threads: 2
ThreadGroup.ramp_time:
⇒1秒間に2ユーザアクセスするようにしています。
⇒「xxxxx」という箇所があるのでアクセスしたいドメイン名に変更してください。
JMeterでテスト実行
あとはjmeter実行スクリプト(start_test.sh)の引数にシナリオのjmxファイルを指定して実行するだけです。
./start_test.sh test.jmx
Master POD→各Slave PODにjmxファイルの内容が送信され実行されます。
Grafana上でメトリクスが表示されます。
あとはログも確認しましょう。
Slave PODの数を増やしたい場合
クローンしたフォルダ内にあるjmeter_slaves_deploy.yamlを編集します。
設定ファイル内、"replicas"の数を変更すれば簡単にSlave PODの数が増減できます。
現在Slave PODは2つのため3つに増やしてみます。
編集
$ vi jmeter_slaves_deploy.yaml
※replicasを3に変更
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: jmeter-slaves
labels:
jmeter_mode: slave
spec:
replicas: 3
再デプロイ
$ kubectl -n test apply -f jmeter_slaves_deploy.yaml
確認
$ kubectl -n test get pod -o wide
※jmeter-slavesで始まるPODが3つ存在し、StatusがrunnningになればOKです。
長い記事になったので最後のほうどんどん適当になってしまいました…
PODの数を簡単に変更できるのは便利だと思います。