【負荷試験】ブラウザ上で JMeter を操作できるツールを作ってみた話【Flanker】

こんにちは。
突然ですが、皆様は負荷試験を実施したことはございますでしょうか?
私は仕事柄、時折、負荷試験案件に携わることがございまして。
あくまで私のケースですが、おおよそ下記のようなフェーズを辿ることが多いように感じています。

1. 試験設計 ( 方針設計 / 目標決め / シナリオ作成 etc )
2. JMeter 環境構築
3. 負荷試験環境調整 ( 申請 / モニタリング環境 etc )
4. JMeter 実行 ( シナリオ調整 / JMeter 調整 含む )
5. 調査 / 解析
6. 「4」と「5」のエンドレス…

「1」に関してはお客様と詳細なやり取りを行い、いわば要件定義に近い折衝を行う必要がございまして、シナリオ作成に関しては API 仕様書を元に、JMeter から発行するリクエスト内容を定義していかなくてはなりません。

「2」や「3」に関しては、 Ansible 等のツールで省力化に取り組んでいるのですが、その他のフェーズでは依然として雑多なタスクが重なり、大変ストレスがマッハでございました。

経緯

さて、つきましては、雑多なタスク減を目的に、この度「4」の JMeter 操作周りを WEB ベースで完結できるツールを作ってみようと思った次第でございます。
始めに必要な機能を洗い出し、実装する機能を下記 5 ポイントに絞りました。

・シナリオ ( .jmx ) のアップロード
・シナリオ内 Thread group コンポーネントの調整 ( 負荷強度の調整等 etc )
・クラスタリングした JMeter に紐づく Worker ノードの操作
・JMeter の実行、および非同期での進捗確認
・実行結果生成

前提として JMeter を扱うのですから、 最終的には JMeter の WEB クライアントを作る形となります。

既に JMeter 環境のデプロイ用途で Ansible を使っているのもあり、ツールの実行環境含めて Playbook に内包しました。

できたもの

・Flanker

https://github.com/snkk1210/flanker

ツール、および JMeter 環境のデプロイは、下記 Ansible の Playbook を流していただければ完了いたします。

※ 詳細は README を参照ください。

https://github.com/snkk1210/jmeter-MS

実際にツールを使って案件を進めてみたのですが、かなり仕事が楽になったように思います。
※ 作った人間ばいあす

これからも、これ系のツールを作って業務の効率化を進めていきたいと思います。
わぁい。

【PHP】Chatwork 通知用の Proxy API を作ってみる

こんにちは。
とある案件でお客様 Zabbix サーバから Chatwork にアラートを通知する必要がございました。
通常であれば、Chatwork の API を叩くスクリプトを用意すればよいのですが、お客様管理の Zabbix サーバですので、セキュリティ的な要因から Chatwork の APIトークンを使うことができません。
ついては、自社サーバに API トークンを配置した Proxy API を用意してみます。

コード

前回は cURL関数 を使ったので、今回は file_get_contents を使ってみます。

<?php
/*
$api_key = "";
$password = "";
*/
include('../../.env');

// リクエストの body を取得
$json = file_get_contents("php://input");
$contents = json_decode($json, true);

// POST されたデータが正しいか判定
if (isset($contents['cw_endpoint']) && is_numeric($contents['cw_endpoint']) && isset($contents['message']) && isset($contents['auth_id'])){
    $cw_endpoint = $contents['cw_endpoint'];
    $message = $contents['message'];
    $auth_id = $contents['auth_id'];
    } else {
        echo "Bad Request.\n";
        http_response_code(400);
        exit();
    }

// 認証情報が正しいか判定 
if ($password !== $auth_id){
    echo "Forbidden.\n";
    http_response_code(403);
    exit();
}

// HTTP ヘッダ
$header = array(
        "Content-Type: application/x-www-form-urlencoded",
        "X-ChatWorkToken: $api_key",
);

// HTTP リクエスト
$context = array(
        "http" => array(
                "method"  => "POST",
                "header"  => implode("\r\n", $header),
                "content" => http_build_query(array(
                    'body' => $message,
                )),
        )
);

// ChatWork API にリクエスト送信
$scc = stream_context_create($context);
$url = "https://api.chatwork.com/v1/rooms/$cw_endpoint/messages";
$res = file_get_contents($url, false, $scc);
echo $res . "\n";

APIトークン と 認証情報は外部より参照できないように、ドキュメントルートより上位に配置しておきます。

<?php
$api_key = "";
$password = "";
?>

使用例

POSTメソッド で HTTPリクエストを投げれば OK です。

・パラメータ
cw_endpoint:通知するルームID
message:通知内容
auth_id:xxxxxx

・curl の例

curl -X POST -d '{"cw_endpoint":"xxxxxxx", "message":"これはテスト", "auth_id":"xxxxxxxxxxxxxx"}' https://xxx.xxx.xxx.xxx/api/proxy.php

【PHP】Cookie を保存/送信して認証が必要なページをスクレイピングしてみる【curl】

こんにちは。
先日、CakePHP で作られた外部システムからデータを引っ張ってくる必要がありました。
ただ、データ取得用の API 等が用意されているわけでもなかったため、curl で叩いてデータを引っ張ってくることにします。

最初のログインページの認証後に Cookie を維持する必要があったので、その処理を PHP で実装してみます。

コード

PHPには cURL 関数が用意されているのでありがたく使わせて頂きます。

<?php

class PHPCurl {
    
    private $getCookieUrl;
    private $targetUrl;
    private $username;
    private $password;
    private $saveCookieFile;

    /**
     * コンストラクタ
     * 
     * @param string $getCookieUrl Cookie取得用のURL
     * @param string $targetUrl    CurlのターゲットURL
     * @param string $username     ログイン情報(ユーザ名)
     * @param string $password     ログイン情報(パスワード)
     */
    public function __construct($getCookieUrl, $targetUrl, $username, $password){
        $this->getCookieUrl = $getCookieUrl;
        $this->targetUrl = $targetUrl;
        $this->username = $username;
        $this->password = $password;
        $this->saveCookieFile = stream_get_meta_data($fp = tmpfile());
    }

    /**
     * Cookie取得用のメソッド
     */
    public function getCookie(){
        $data = array(
            'data[User][username]' => $this->username,
            'data[User][password]' => $this->password,
        );

        $curl = curl_init($this->getCookieUrl);
        curl_setopt($curl, CURLOPT_POST, TRUE);
        curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data));

        curl_setopt($curl, CURLOPT_COOKIEJAR, $this->saveCookieFile['uri']);
        curl_setopt($curl, CURLOPT_COOKIEFILE, $this->saveCookieFile['uri']);
        curl_exec($curl);
        curl_close($curl);
    }

    /**
     * コンテンツ取得用のメソッド
     * 
     * @return string HTMLコンテンツ
     */
    public function getContent(){
        $curl = curl_init($this->targetUrl);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_COOKIEFILE, $this->saveCookieFile['uri']);
        $result = curl_exec($curl);
        curl_close($curl);

        return $result;
    }
}

$curlInfo = new PHPCurl('https://xxxxxx.xxx/login', 'https://xxxxxx.xxx/target', 'finger', 'ease');

$curlInfo->getCookie();
$content = $curlInfo->getContent();

echo $content;

※ POST の bodyデータ は適宜置き換えてくださいね。
あとは必要なデータをパースするだけです。

おまけ

curl だったら下記 2 行だけなんですよね。

curl -c ./cookie.txt -d "data[User][username]=finger" -d "data[User][password]=ease" "https://xxxxxx.xxx/login"
curl -b ./cookie.txt "https://xxxxxx.xxx/target"

【EASE】Laravel で Chatwork 通知機能を実装してみる【Ansible】

こんにちは。
先日に Laravel で作った Ansible の管理ツールですが、プロビジョニング時に通知を行う機能が欲しいなー、と。
Slack でもよいのですが、会社で主に使っている Chatwork に通知してみます。

【Ansible】playbookをWEB上で管理/実行できるツールを作ってみた【Laravel】

事前準備

Chatwork の API トークンが必要です。
https://developer.chatwork.com/ja/

環境変数(.env)から API トークンと、通知先エンドポイントを読めるようにしておきます。

config/chatwork.php 
==========================================
<?php

# Chatworkのtokenを環境変数から取得
return [
    'cw-token' => env('CW_TOKEN'),
    'cw-endpoint' => env('CW_ENDPOINT'),
];

メソッド

Laravel では Guzzle HTTP クライアントで簡単に http リクエストを発行できるようです。
ヘッダに API トークン、ボディに通知内容のメッセージを指定するんですが、URL エンコードする必要があるので、リクエストを作成する前に asForm メソッドを呼び出します。
※プロビジョニング実行時、完了時で合計 2 つのメソッドを作成します。

use Illuminate\Support\Facades\Http;

    /**
     * 実行時 Chatwork 通知メソッド
     */
    public static function notify2ChatworkStart($user){
        $token = config('chatwork.cw-token');
        $endpoint = config('chatwork.cw-endpoint');
        $date = date("Y/m/d H:i:s");

        $message = "[info][title]EASE Ansible MG started provisioning at " . $date . "[/title]\n" . " by " . $user->name . "[/info]";
        $response = Http::asForm()->withHeaders([
            'X-ChatWorkToken' => $token,
        ])->post($endpoint, [
            'body' => $message
        ]);
        return $response;
    }

    /**
     * 終了時 Chatwork 通知メソッド
     */
    public static function notify2ChatworkEnd($ansible_output){
        $token = config('chatwork.cw-token');
        $endpoint = config('chatwork.cw-endpoint');
        $date = date("Y/m/d H:i:s");

        $message = "[info][title]EASE Ansible MG ended provisioning at " . $date . "[/title]\n" .  $ansible_output . "[/info]";
        $response = Http::asForm()->withHeaders([
            'X-ChatWorkToken' => $token,
        ])->post($endpoint, [
            'body' => $message
        ]);
        return $response;
    }

使い方

環境変数が設定されている時にメソッドを呼び出すだけです。
とてもお手軽。

# Chatworkへの通知(実行時)
if (config('chatwork.cw-token')){ Playbook::notify2ChatworkStart($user); };

# Chatworkへの通知(完了時)
if (config('chatwork.cw-token')){ Playbook::notify2ChatworkEnd($ansible_output[count($ansible_output)-2]); };  

CentOS7 を入れた Raspberry Pi に Laravel8 環境を構築する

こんにちは。
CentOS7 を入れたラズパイに Laravel8 環境を構築してみます。
デフォルトのリポジトリからだと、古い PHP と MariaDB が入っちゃうので、それぞれ工夫して導入します。

PHP

デフォルトのリポジトリだと PHP5 系が入っちゃいます。
armv7l 用の remi リポジトリがあるけども PHP7.2 なので、こちらもダメです。
ついては phpenv でソースから PHP7.4 をビルドします。

# EPEL リポジトリ導入
cat > /etc/yum.repos.d/epel.repo << EOF
[epel]
name=Epel rebuild for armhfp
baseurl=https://armv7.dev.centos.org/repodir/epel-pass-1/
enabled=1
gpgcheck=0
EOF

# 関連パッケージ 導入
yum groupinstall "Development tools"
yum install libicu libicu-devel oniguruma oniguruma-devel cmake cmake3
yum install gcc libxml2 libxml2-devel libcurl libcurl-devel libpng libpng-devel libmcrypt libmcrypt-devel libtidy libtidy-devel libxslt libxslt-devel openssl-devel bison libjpeg-turbo-devel readline-devel autoconf sqlite-devel bzip2-devel nginx

# phpenv 導入
cd /usr/local/src/
git clone https://github.com/CHH/phpenv.git
cd phpenv/bin/
./phpenv-install.sh
git clone https://github.com/CHH/php-build.git ~/.phpenv/plugins/php-build

vi ~/.bashrc
======================================
export PATH="/root/.phpenv/bin:$PATH"
eval "$(phpenv init -)"
======================================

source ~/.bashrc

# libzip 導入
cd /usr/local/src/
wget https://libzip.org/download/libzip-1.7.3.tar.gz
tar xzvf libzip-1.7.3.tar.gz 
cd libzip-1.7.3
cmake3 -DCMAKE_INSTALL_PREFIX=/usr/local/libzip
make
make install

export PKG_CONFIG_PATH="$PKG_CONFIG_PATH:/usr/local/libzip/lib/pkgconfig"

# PHP 導入
phpenv install --list
phpenv install 7.4.14
phpenv global 7.4.14

# PHP-FPM 設定
vi ~/.phpenv/versions/7.4.14/etc/php-fpm.d/www.conf
==========================================================
[www]
user = nginx
group = nginx
listen = /var/run/www.sock
listen.owner = nginx
listen.group = nginx
==========================================================

vi ~/.phpenv/versions/7.4.14/etc/php-fpm.conf
==========================================================
pid = /var/run/php-fpm.pid
==========================================================

ln -s /root/.phpenv/versions/7.4.14/sbin/php-fpm /usr/local/bin/php-fpm

# Unit ファイル作成
vi /usr/lib/systemd/system/php-fpm.service
==============================================================
[Unit]
Description=The PHP FastCGI Process Manager
After=syslog.target network.target

[Service]
Type=forking
PIDFile=/var/run/php-fpm.pid
ExecStartPre=/usr/bin/rm -f /run/php-fpm.pid
ExecStart=/usr/local/bin/php-fpm -D
ExecReload=/bin/kill -USR2 $MAINPID
PrivateTmp=true
RuntimeDirectory=php-fpm
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target
==============================================================

# PHP-FPM 起動
systemctl daemon-reload
mkdir -p /var/run/php
systemctl enable php-fpm
systemctl start php-fpm

MariaDB

こちらもデフォルトのリポジトリからだと 5.5 系が入っちゃうのでパスです。
一旦 yum で 5.5 系を導入して初期設定を行って、その後にソースから最新版をビルドし、Unit ファイルの書き換えで最新版にアップデートします。

# 5.5 系の MariaDB 導入
yum install mariadb-server
systemctl start mariadb
mysql_secure_installation

# 10.5.8 の MariaDB ソースをビルド
cd /usr/local/src/
wget https://downloads.mariadb.org/interstitial/mariadb-10.5.8/source/mariadb-10.5.8.tar.gz/from/https%3A//mirror.yongbok.net/mariadb/
mv index.html mariadb.tar.gz
tar -zxvf mariadb.tar.gz
cd mariadb-10.5.8/
cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mariadb
make
make install

# Unit ファイル書き換え
systemctl stop mariadb
vi /usr/lib/systemd/system/mariadb.service
================================================
#ExecStart=/usr/bin/mysqld_safe --basedir=/usr
ExecStart=/usr/local/mariadb/bin/mysqld_safe
================================================

# 10.5.8 にて MariaDB 起動
systemctl daemon-reload
systemctl start mariadb

Composer

Composer は公式に則って普通に導入です。

php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
php composer-setup.php
php -r "unlink('composer-setup.php');"

補足

EASE の Nginx 設定ファイル例も記載しておきます。
自分用の備忘録ですしお寿司。

upstream php-fpm-ease {
  ip_hash;
  server unix:/var/run/www.sock;
}

server {
     listen 80;
     server_name ease.local;
     root         /var/www/vhosts/ease/public;
     index        index.php index.html index.htm;
     access_log   /var/log/nginx/ease.local_access.log;
     error_log    /var/log/nginx/ease.local_error.log;

     fastcgi_read_timeout 999999;
     proxy_read_timeout 999999;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass  php-fpm-ease;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
        fastcgi_buffer_size  128k;
        fastcgi_buffers  256 16k;
        fastcgi_busy_buffers_size  256k;
        fastcgi_temp_file_write_size  256k;
        include  fastcgi_params;
    }

}

【Ansible】Playbook を WEB で管理できるツールを作ってみた【Laravel】

こんにちは。
Ansible の Playbook 管理ツールといえば、 Ansible Tower / AWX / Ansible Semaphore 等々がございます。
これらを業務フローに組み込めないかな、と試してみたのですが、いずれもあまりピンとこず。

ないなら自分で作ってしまえと思い、年末の勢いでツールを作ってみました。
https://github.com/snkk1210/ease

実現したかったこと

ツールを作成するに辺り、実現したかったポイントは下記 7 点です。

・WEB ブラウザ上で Playbook が管理できる。
・WEB ブラウザ上で Playbook に定義された処理を実行できる。
・処理実行 ( プロビジョニング ) 履歴を保持できる。
・既に存在する Playbook ( リポジトリ ) の資産を流用できる。
・ユーザ毎に細かな権限設定が可能であり、組織的に管理できる。
・プロビジョニング時に公開鍵認証/パスワード認証の両方が使用できる。
・外部ファイルをアップロードし、処理実行時に利用することができる。

社内でのユースケースとしては、下記のようなことを実現したく実装しました。

だいたいの使い方

1. プロビジョニング対象への接続認証を登録

サイドメニューから「Make Auth」を選択し、認証名、パスワード、秘密鍵を登録

2. Playbook を作成

サイドメニューから「Make Playbook」を選択し、下記項目をそれぞれ登録

・Playbook
→ Playbook の名前

・repository
→ Playbookに使うリポジトリ名

・認証
→ 作成した接続認証

・private_key
→ 認証に使う秘密鍵
※ここで設定した秘密鍵情報が優先される(空にすると接続認証の情報を使用)

・inventory
→ インベントリファイル

・vars
→ 変数ファイル

・main
→ レポジトリに存在する role を include する Playbook

3. Playbook を選択

サイドメニューから「Playbooks」を選択し、一覧の中から使用する Playbook の「Run」を選択

4. Playbookを実行

セレクトボックスから「鍵認証」「パスワード認証」を選択して、「ドライラン」または「実行」を選択

権限周り

実装する権限は下記 3 ロールに絞りました。
※ もっと細かく調整できるように改修したいですね。

admin → 全てのリソースの操作が可能/ユーザの作成が可能
read-only → 全てのリソースの「閲覧」のみ可能
他ユーザ → 自分で作成したリソースのみ操作が可能

終わりに

余力があれば、アカウント権限操作周りの機能と、WEB ベースでのリポジトリ追加機能を実装する予定です。
始めて Laravel 使いましたが、こんな感じに実装できるんですね。
色々と勉強になりました。