OKE(Oracle Container Engine for Kubernetes)でjmeter実行環境 導入手順

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用リポジトリが利用できるよう[こちら](https://container-registry.oracle.com)にアクセスします。

Container Servicesをクリック。
<img width="514" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131627/2f357c5c-1bb2-3dcb-2b4f-bf698c54462b.png">

右上「Sign in」より自身のOracleアカウントでサインイン。
<img width="752" alt="キャプチャ2.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131627/07ed866c-71bf-fcbe-2860-6fbbc3959858.png">


## 前準備
※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)
<img width="364" alt="キャプチャ3.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131627/275b2051-ae1b-fc05-1ab0-d1a2d289ec83.png">

Create Clusterを選択 
<img width="410" alt="キャプチャ4.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131627/b54703a4-902f-01f0-929d-7e0082b44d31.png">

設定
<img width="465" alt="キャプチャ5.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131627/ae3ce495-4a0c-1197-9384-0a080b47a35e.png">
Cluster Creation画面で、対象項目を入力／選択

NAME：任意。今回はcluster1
KUBERNETES VERSION: 今回は最新Verのv1.12.7
QUICK CREATEを選択：必要なネットワーク周りを自動で構成

<img width="450" alt="キャプチャ6.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131627/497e1071-2e06-9e88-0690-2953e1b2709f.png">
SHAPE: 今回はVM.Standards1.1を選択

QUANTITY PER SUBNET:１サブネットあたりのノード数指定
PUBLIC SSH KEY: 自分の公開鍵を入力

上記入力し「create」ボタンをクリック。
Cluster StatusがActiveとなり、Node PoolsよりNodeもActiveとなれば完成です。
※デフォルトで3つのNodeを作成します。そしてデプロイに結構時間かかります。

## OKEクラスター接続
作成したクラスタの「Access Kubeconfig」を選択します。
<img width="362" alt="キャプチャ7.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131627/909b083d-7bb1-0afb-024b-ac7003cc2426.png">
ここで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
※３ノード表示されたら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
 ※５つの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
<img width="343" alt="キャプチャ8.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131627/9bdd8761-8c35-a1e4-568d-36a27234d0a3.png">

ブラウザでアクセスするとgrafanaの画面が表示されます。
http://localhost:888

今回こちらのDashboardを使います。
https://grafana.com/grafana/dashboards/4026
Grafanaホーム画面 > 左メニュー「+」マークより上記のダッシュボードをインポートします。

## シナリオのJMXファイルを用意
お試しで以下を使用します。


<details><summary>test.jmx</summary><div>

```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>
```
</div></details>

※内容について
LoopControler.loops: 100
⇒とりあえず100回にしています。
ThreadGroup.num_threads: 2
ThreadGroup.ramp_time: 
⇒１秒間に2ユーザアクセスするようにしています。
⇒「xxxxx」という箇所があるのでアクセスしたいドメイン名に変更してください。



## JMeterでテスト実行
あとはjmeter実行スクリプト(start_test.sh)の引数にシナリオのjmxファイルを指定して実行するだけです。

```
./start_test.sh test.jmx
```
Master POD→各Slave PODにjmxファイルの内容が送信され実行されます。
Grafana上でメトリクスが表示されます。

※Jmeter実行時のグラフイメージ
<img width="465" alt="キャプチャ9.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/131627/105a2842-2e72-13a0-e68c-38b5688cdb21.png">

あとはログも確認しましょう。

## 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の数を簡単に変更できるのは便利だと思います。