application で認証した後に nginx で static file を配信する
application で認証している場合のみファイルをダウンロードさせたい場合があると思いますが、
そんなときの設定例です。
以下に簡単に試す方法を記載しています。
tkuchiki/nginx-direct-data-transfer-sample · GitHub
例では Basic 認証ですが、実際はちゃんとした認証がいいでしょう。
application 側で http headr に、
X-Accel-Redirect としてファイルのパスを追加すると、
nginx から static file を配信することができます。
X-Accel-Redirect を使用しなくても、
nginx の proxy cache がききますが、
大容量ファイルの配信を行う場合、
proxy cache の作成に Disk I/O、CPU リソースを大量に使ってしまう可能性があります。
sinatra の場合、以下のようになります。
content_type "image/png" headers "X-Accel-Redirect" => "/img/black.png" status 200
これで負荷を低減することができます。
Bats を使って Travis CI で bash(シェルスクリプト) のCI を回す
Bats で bash のテストを書く - tkuchikiの日記
の続きです。
bash のテストができるようになると CI を回したくなりますよね。
ということで、Travis CI でテストを回すまでの方法を説明します。
https://github.com/tkuchiki/bats-travis-ci
にすべてのコードを置いています。
.travis.yml
before_install で bats をインストールし、script で実行します。
language: bash before_install: - sudo add-apt-repository ppa:duggan/bats --yes - sudo apt-get update -qq - sudo apt-get install -qq bats script: - bats /path/to/test.bats
これで Travis CI で bats が使えるようになります。
あとは、テストを書いて push すれば bats を実行できます。
以下は、実行結果の一部です。
$ git clone --depth=50 --branch=master https://github.com/tkuchiki/bats-travis-ci.git tkuchiki/bats-travis-ci $ cd tkuchiki/bats-travis-ci $ git checkout -qf 56e66e566c89c7e891862bb2f3128cb8c9014dcf $ sudo add-apt-repository ppa:duggan/bats --yes $ sudo apt-get update -qq $ sudo apt-get install -qq bats $ sudo apt-get install -qq bc $ bats ./test.bats 1..7 ok 1 addition using bc ok 2 status code ok 3 output ok 4 lines ok 5 run command ok 6 load ok 7 # skip (skipped) skip The command "bats ./test.bats" exited with 0. Done. Your build exited with 0.
実行結果の詳細は以下のページにアクセスすると確認できます。
Bats で bash(シェルスクリプト) のテストを書く
※記事を書いて公開するまで1年くらい経っているので情報が古い可能性があります。
Bats(Bash Automated Testing System)は、bash のテスティングフレームワークです。sstephenson/bats · GitHub
Test Kitchen(Bussser) の Bats plugin で使うことができるのでご存知の方も多いと思います。
簡単に使い方を説明すると、
@test "テストの説明" { command ... }
という記法でテストを記述し、コマンドの返り値が 0 ならテストをパスします。
以下のようにスクリプトを実行してテストをすることができます。
$ bats test.bats
インストール
インストール方法は、以下のとおりです。
sudo add-apt-repository ppa:duggan/bats --yes sudo apt-get update -qq sudo apt-get install -qq bats
brew install bats
- その他
git clone https://github.com/sstephenson/bats.git cd bats ./install.sh /usr/local
テストの書き方
テストコードと実行結果は、以下のようになります。
- テストコード
- 実行結果
テストコードを元に上から順に説明していきます。
setup, teardown
- setup
- 各テストの実行前に実行される処理を関数として定義
- teardown
- 各テストの実行後に実行される処理を関数として定義
となります。
コード内で、BATS_ から始まる変数が使われていますが、
これらは Bats の特殊変数です。
詳細は後述します。
run, $status, $output, $lines
run FILEPATH で外部スクリプトを実行すると、
$status, $output, $lines に値がセットされます。
各変数は以下のとおりです。
- $status
- $output
- 標準出力
- $lines
- 標準出力を行ごとに配列に格納
以下のスクリプトだと、
status=1 output=foobar lines[0]=foobar
になります。
lines については配列なので、出力が 2 行の場合は lines[1] に 2 行目の出力結果が格納されます。
skip
skip は、文字通りテストをスキップします。
load
load は、外部スクリプトを読み込みます。
load の引数となるファイルパスを書く場合は 2 つの注意点があります。
特殊変数
以下の実行結果から、それぞれの変数の値を見ていくと、
- BATS_TEST_NAME
- テストの説明部分を test_ prefix と、半角スペースを _ に変換したもの
- BATS_TEST_FILENAME
- 実行している Bats ファイルのフルパス
- BATS_TEST_DIRNAME
- Bats ファイルを配置しているディレクトリ
- BATS_TEST_NAMES
- BATS_TEST_NAME を 0 から始まる配列に格納
- BATS_TEST_DESCRIPTION
- テストの説明部分
- BATS_TEST_NUMBER
- テストの番号
となっていることがわかります。
また、実行結果を見ると、
setup -> テスト -> teardown と実行されていることが確認できます。
テスト外のコードについて
@test, setup, teardown 関数の外で標準出力をするコードを書いた場合、
>&2 で標準エラー出力にリダイレクトするようにしないと、
TAP の標準出力を汚染してテストが失敗する可能性があるようです。
linux で TZ の offset を出力する
date +%z
を使います。
$ TZ=UTC date +%z +0000 $ TZ=Asia/Tokyo date +%z +0900 $ TZ=EST date +%z -0500
全ての TZ と offset を出力する場合は、以下のシェルスクリプトを実行します。
実行結果
$ ./tz.sh EST5EDT -0500 MST7MDT -0700 Portugal +0000 Jamaica -0500 Poland +0100 Zulu +0000 GMT-0 +0000 ROK +0900 Chile/EasterIsland -0500 Chile/Continental -0300 ...
HAPrxoy 1.6.0 で導入された DNS の動的名前解決の検証結果
追記(2016-07-13 16:40)
Changelog に書いてあるとおり(BUG/MEDIUM: dns: unbreak DNS resolver after header fix
)、
動的名前解決ができないバグが修正されました。
HAProxy 1.6.6 で動的名前解決できないバグが修正されていました(Changelog にも書いていますが一応検証しました)
— tkuchiki (@tkuchiki) 2016年7月13日
詳細は省きますが、本記事と同様の検証を行い、動的名前解決ができることを確認しました。
追記(2016-06-24 12:34)
HAProxy 1.6.5 は、DNS の動的名前解決が動作しないようです。
ご注意ください。
HAProxy 1.6.5 で動的名前解決できない問題については 1.6.4 で困っていなければそれを使って、1.6.5 がよければ patch を当てると良いそうです https://t.co/LPpQac1NeC
— tkuchiki (@tkuchiki) June 4, 2016
検証環境は以下のとおりです。
http://www.haproxy.org の Quick News に、
dynamic DNS-based server address resolution
と書いてあるとおり、
HAProxy 1.6.0 から DNS の動的名前解決に対応しました。
HAProxy 1.6.0 より前のバージョンではどうだったかというと、
起動時に DNS を参照して、それ以降 DNS を参照することはありません。
この挙動については、ソースコードを見るか、
port 53 の通信をキャプチャすると確認することができます。
追記(2015-10-30 14:57)
http://b.hatena.ne.jp/mapk0y/20151030#bookmark-270133853
ブックマークにコメントを頂いたとおり、
A レコードを動的に変えるものとして RDS を使いましたが、
例ですのでこの構成に意味はありません。
RDS の前段に HAProxy を置く場合は、
Slave(Read Replica) を複数台立てて参照を分散する、
といった使い方になると思います。
強引にメリットを上げるとすれば、
最初は1台でスタートして、
あとから複数台の DB を立てて参照分散したいとしたときに、
アプリの修正が必要ないことでしょうか。
コメントありがとうございました!
動的名前解決しないとどうなるか
動的名前解決機能を説明する前に、
HAProxy 1.5.2 を使い、
動的名前解決しない場合の挙動について説明します。
RDS の準備
サーバがダウンしたらいい感じに A レコードを更新してくれるものとして、
Amazon RDS があります。
Amazon RDS には reboot with failover という、
強制的に failover させる機能がありますので、これを検証に使います。
failover させるために、Multi-AZ を有効にしてインスタンスを作成してください。
MySQL の準備
HAProxy には mysql の health check を行う機能があります。
監視用のユーザが必要ですので作成します。
haproxy ユーザを作るために、以下の SQL を実行します。
grant usage on *.* to 'haproxy'@'%';
ngrep の準備
port 53 への通信をキャプチャするために、
ngrep
をインストールします。
キャプチャできれば良いので、tcpdump
でも良いです。
yum install -y epel-release yum install -y ngrep
HAProxy の準備
amzn-main に HAProxy 1.5.2 があったので、
yum install -y haproxy
してインストールします。
設定ファイルは以下のとおりです。
# /etc/haproxy/haproxy.cfg listen mysql-slave bind 127.0.0.1:3307 mode tcp option mysql-check user haproxy balance roundrobin server master test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check
検証
準備が整ったので、HAProxy 1.5.2 では動的名前解決しないことを確認します。
RDS が failover する前の IP アドレスを調べます。
$ host test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com test-db01.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com has address 10.1.0.100
ngrep
を実行して、port 53 への通信がないかを確認します。
$ ngrep -d any -W byline port 53 -q
上記のコマンドを実行した状態で、
service haproxy restart
を実行して再起動すると、
$ ngrep -d any -W byline port 53 -q interface: any filter: ( port 53 ) and (ip or ip6) U 10.1.0.2:53 -> 10.1.0.10:42761 .............test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com................. .." U 10.1.0.10:49225 -> 10.1.0.2:53 .............test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com..... U 10.1.0.2:53 -> 10.1.0.10:49225 .............test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com................. .." U 10.1.0.10:34814 -> 10.1.0.2:53 OU...........test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com..... U 10.1.0.2:53 -> 10.1.0.10:34814 OU...........test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com................. .."
のように、DNS を参照していることがわかります。
前述のとおり、HAProxy 1.5.2 は動的名前解決をしないので、
これ以降は ngrep
の様子を気にする必要はありません。
それでは、RDS を failover させます。
AWS CLI では、以下のようにコマンドを実行し、
$ aws rds reboot-db-instance --db-instance-identifier test-db --force-failover
Management Console から操作する場合は、
チェックボックスにチェックを入れて再起動します。
Multi-AZ instance failover completed
というログが出たら failover しています。
$ host test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com test-db01.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com has address 10.2.0.200
IP アドレスが変わっていることを確認できます。
この状態で、HAProxy を通して RDS に接続します。
$ mysqladmin ping -u haproxy -h 127.0.0.1 -P 3307 mysqladmin: connect to server at '127.0.0.1' failed error: 'Lost connection to MySQL server at 'reading initial communication packet', system error: 0'
接続できないことが確認できます。
netstat でも確認してみると、
$ netstat -anp | grep haproxy | grep :3306 tcp 0 1 10.1.0.10:56199 10.1.0.100:3306 SYN_SENT 1553/haproxy
failover 前の IP アドレスに接続しようとしています。
以上のことから、動的名前解決しないと、
A レコードなどを変更しても接続先を変えることができないことがわかります。
動的名前解決を試す
動的名前解決できないとどうなるかがわかったところで、
HAProxy 1.6.1 を使い、
どのように挙動が変わったか確認します。
amzn-main には HAProxy 1.6.1(1.6.0 も)ないので、
適宜ビルドしてください。
HAProxy のログを出力するために、
rsyslog
を設定します。
# /etc/rsyslog.conf + $ModLoad imudp + $UDPServerRun 514
# /etc/rsyslog.d/haproxy.conf + $ModLoad imudp + $UDPServerRun 514 + $template Haproxy,"%msg%\n" + local1.* -/var/log/haproxy.log;Haproxy
service rsyslog restart
で再起動します。
動的名前解決用の設定をします。
# /etc/haproxy/haproxy.cfg resolvers mydns nameserver dns1 10.2.0.2:53 nameserver dns2 8.8.8.8:53 resolve_retries 3 timeout retry 1s hold valid 60s listen mysql-slave bind 127.0.0.1:3307 mode tcp log 127.0.0.1 local1 debug option mysql-check user haproxy balance roundrobin server master test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com:3306 check resolvers mydns
resolve_retries 3
, timeout retry 1s
は見たままですが、
リトライする回数とタイムアウトの秒数です。
hold valid 60s
は、名前解決が成功したら、指定秒数名前解決しない設定です。
このオプションは、health check が fail したときは使われません。
nameserver
は DNS サーバを複数設定できます。
http://cbonte.github.io/haproxy-dconv/configuration-1.6.html#5.3.2-nameserver を見ると、
port を省略しても良いという記述はないので、
省略することはできません。
私は、port を省略していたため動的名前解決ができなくて、
2時間くらい無駄にしました...。
設定ができたら、先述の ngrep
を実行しておきます。
service haproxy restart
して準備完了です。
この時点で、port 53 への通信が定期的にあるので、
動的名前解決ができそうな予感がします。
RDS の IP アドレスを確認して、RDS を failover させます。
$ host test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com test-db01.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com has address 10.2.0.200
ngrep
の様子を見ているとhold
の設定とは関係なく、
health check の間隔くらいで名前解決している様子を確認できます。
RDS の A レコードが書き換わり、
HAProxy の health check が成功すると、
mysql-slave/master changed its IP from 10.2.0.200 to 10.1.0.100 by mydns/dns1. Server mysql-slave/master is UP, reason: Layer7 check passed, code: 0, info: "5.6.23", check duration: 1ms. 1 active and 0 backup servers online. 0 sessions requeued, 0 total in queue.
と、接続先が変わったことがログに出力されます。
IP アドレスを確認して、mysqladmin ping
、netstat
でも確認すると、
$ host test-db.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com test-db01.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com has address 10.1.0.100 $ mysqladmin ping -u haproxy -h 127.0.0.1 -P 3307 mysqld is alive $ netstat -anp | grep 3306 tcp 0 0 10.1.0.10:57427 10.1.0.100:3306 TIME_WAIT -
failover 後のインスタンスに接続していることがわかります。
まとめ
HAProxy 1.6.0 から数行設定を追加するだけで動的名前解決できることを示しました。
メンテナンスで DNS 切り替えたら繋がらなくなった...、
みたいなことがなくなるので便利そうですね。
port 問題で消耗したのでソースコードはあまり読めていないので、
詳細な挙動が知りたい方は、ご一読いただくと良いと思います。
※ 検証するときは、以下のことに注意してください。
- ドキュメントをよく読む
- port を省略してはいけない、port を省略するのは甘え
zabbix の LLD で gearmand の job を動的に監視する
検証した環境の zabbix のバージョンは、2.0.15 です。
gearmand の監視を zabbix で行う際、
job の数がアプリケーションによって違うと思いますので、
テンプレートを作っても汎用的に使うことができません。
こういうケースで使えるのが LLD(ローレベルディスカバリ) です。
アイテムを動的に追加したりするあれです(vfs.fs.discovery など)。
LLD は自作もできるので、自作してみました。
Template
をインポートします。
ポートは、テンプレートのマクロで、
{$GEARMAND_PORTS} = 4730
を設定しています。
ホストごとにポートを変更したい場合は、
ホストのマクロに {$GEARMAND_PORTS} = 14730
などと設定して上書きしてください。
また、複数ポート指定できるようにしてあるので、
必要であれば空白区切りでポートを列挙してください。
UserParameter
LLD も UserParameter で集計するので設定ファイルとシェルスクリプトを置く必要があります。
gearmand.conf
UserParameter の設定を記述します。
zabbix_agentd.conf にベタ書きする場合は、
zabbix_agentd.conf で Include せず、直接記述してください。
zabbix_agentd.conf
UserParameter の定義を書いたファイルを読み込むために、
Include を追記します。
Include=/etc/zabbix/zabbix_agentd.d/gearmand.conf
user.discovery.gearmand.sh
アイテムを自動収集するためのスクリプトです。
LLD では、決まった形式の json を返すだけで、zabbix がアイテムを登録してくれます。
となっているので、{$GEARMAND_PORTS} で設定したポートを引数にスクリプトを実行します。
user.discovery.gearmand.sh は第一引数に port を受けるようになっているので、
zabbix-agent が user.discovery.gearmand.sh 4730
のように叩き、
以下のような json を返します。
{ "data": [ { "{#GEARMAND_FUNC_NAME}": "Notification", "{#GEARMAND_PORT}": 4730 }, { "{#GEARMAND_FUNC_NAME}": "GroupNews", "{#GEARMAND_PORT}": 4730 } ] }
アイテムのプロトタイプに、gearmand[{#GEARMAND_PORT}, {#GEARMAND_FUNC_NAME}, capable_workers]
と設定しているので、
gearmand[4730, Notification, capable_workers]
gearmand[4730, GroupNews, capable_workers]
というアイテム(キー) が自動で登録されます。
json とアイテムのプロトタイプを見るとなんとなくお分かりいただけると思いますが、
json の key をアイテムのプロトタイプを作るときに利用でき、
key と対応する value が展開される、という動作をします。
capable_workers は、もう一つのシェルスクリプト(gearmand.sh)で使っています。
gearmand.sh
以下は、gearadmin
の実行結果です。
$ gearadmin --status Notification 1 0 1 GroupNews 2 1 1 .
左から順に、job名、キュー数、実行中のジョブ数、実行可能なworker数です。
job名以外は忘れやすそうでしたので、名前で参照できるようにしています。
名前で指定できるようにして、 STAT=$(eval echo '$'${3})
の部分で変数を展開して数値に戻しています。
設定完了
Template_gearmand をホストに設定して zabbix-agent を restart すれば、
LLD の設定完了です。
zbx_template_gearmand.xml では、port を listen しているか、capable_workers が 0 でないかを監視するトリガを設定しています。
必要であれば適宜トリガを追加してください。
まとめ
LLD で gearmand の job を監視する方法を紹介しました。
gearmand を使っていない方も、LLD の設定の参考にしていただければ幸いです。
参考資料
/etc/shadow の hash を生成するコマンドを作りました
perl, php, python の標準モジュールで /etc/shadow の hash を生成する方法 - tkuchikiの日記
で、/etc/shadow の hash を生成する方法を書きましたが、
どの環境でも同じ方法で出来たら楽だと思いましたので、
https://github.com/tkuchiki/encrypt-pw を作りました。
Installation
https://github.com/tkuchiki/encrypt-pw/releases からお使いの環境用のバイナリを取得して、
解凍してください(Windows でも動作します)。
Usage
実行例は、以下のとおりです。
$ ./encrypt-pw --help usage: encrypt-pw [<flags>] Encrypts password (starts from $1$, $5$, $6$ hash) Flags: --help Show context-sensitive help (also try --help-long and --help-man). -h, --hash="sha512" Hash algorithm (sha512, sha256, md5) -r, --rounds=5000 Number of hashing rounds (min: 1000, max: 999999999) -c, --confirm Confirm password -p, --password=PASSWORD Password --version Show application version. $ ./encrypt-pw Enter password: $6$eVuwo0XKesWWkbv3$/L.Sw8RJuk69aamprVSN.id2tCWJ0OiSmRJ12JNyahAdPopx7aHOWDvZ/PYustFFQ6Eu7vp22FYLqvXTUIo9I0 $ ./encrypt-pw -c Enter password: Enter same password again: $6$59LB.ugo8iWh4dJf$4YjghX24znjgRC5MPF/h/f.FP8K37EiWBjmaybGWIMFTEQRPaQUDibCUcg72qRx54qW0As3GsvkCEYShEifMA. $ ./encrypt-pw -p your-password $6$N2UrLLeq3i3560Vv$cOC/xvVRHo1vGAJcsp788UrkUIg1Bc66.pwYRhcQFfiI4lor8SDQQKgW8zT7qdc4bflbEnkTGEyulU7v9DCcT $ ./encrypt-pw -h sha256 -p your-password $5$m0Q22Redjm5D7.zH$rvRH/obRUtCu9osnxxOYcg0JOTLoDpPnSLagpU9gn6B $ ./encrypt-pw -h md5 -p your-password $1$CaybI5OM$p3F4OZCYEmOLOCtZmmbAw1 $ ./encrypt-pw -r 100000 -p your-password $6$rounds=100000$g9EPi5gIxSX2cpMR$UVIstanJwu4uCtNpW1HIdQmu.Y4yPm9HzVL1mcwoz0E87Gn0FI7AunYy5wOQ8FBArwlIQc6N6YZITsDW6aZZh/
各オプションは、
-c|--confirm
: 2回入力して一致したら hash を生成-h|--hash (md5|sha256|sha512)
: hash アルゴリズムを選択-p|--password PASSWORD
: 対話入力せずに hash を生成-r|--rounds
: hashing rounds を指定 (md5 では無効)
となっています。