tkuchikiの日記

新ブログ https://blog.tkuchiki.net

Mackerel のホスト名を ssh の補完候補リストに出力する

この記事は、Mackerel Advent Calendar 2015 8日目の記事です。

Mackerel に登録しているホストに ssh するとき、
補完できたら楽かもしれないと思い、その実現方法を模索してみました。
補完機能を一から書くのは大変なので、
bash-completion を使います。
動作環境は Linux です。

※紹介する方法は、ローカルマシンからの ssh で補完する方法ではなく、
リモートマシンからリモートマシンへ ssh する際に補完する方法です。

bash-completion

bash-completion は、bash の補完機能を拡張するものです。
細かく説明されている記事がいくつもあると思いますので説明は省きます。
以下のようにインストールできます。

yum

yum install -y epel-release
yum install -y --enablerepo=epel bash-completion

apt-get

apt-get install -y bash-completion

補完

consul membersの結果でbash補完する - Qiita を参考に、
_known_hosts_real() を上書きして補完できるようにします。
consul の場合は DNS の機能があるので、
dnsmasq などと組み合わせることで、ホスト名で名前解決できます。
そのため、consul members でホスト名一覧を取ることができれば、
ssh に使うことができます。
しかし、今回のケースでは、ホストの一覧を取るだけでは、
ssh することができません。
bash-completion の補完は、
/etc/hosts, ~/.ssh/config, ~/.ssh/known_hosts を使うようですので、
/etc/hosts, ~/.ssh/config に自動で設定を追加することで、
補完できるようにする方法を考えます。
Mackerel の /api/v0/hosts.json を parse するのに jq を使いますので、
yum, apt-get もしくは source build してインストールしてください。

/etc/hosts を使う

_known_hosts_real() を使うと書いておきながら、
/etc/hosts に設定を追加する場合、
root 以外は書き込めないので使えなさそうです。
仕方がないので cron で定期的に更新します。
以下のようなスクリプトになりました。

ssh で入れるホストには mackerel-agent が入っていると思いますので、
mackerel-agent.conf から apikey を取得して使っています。
curlhttps に対して Request を送ると dentry_cache が肥大化してしまうので、
export NSS_SDB_USE_CACHE=yes することをおすすめします。
cron の場合、普通に使えば毎分より短い間隔では実行できないので、
Mackerel のサーバへの負荷もそれほど高くないと思いますが、
台数に応じて実行間隔をばらつかせる、実行間隔を伸ばすなどしましょう。

実行例

Mackerel に、

  • server01 192.168.1.100
  • server02 192.168.1.110
  • server03 192.168.1.120
  • server04 192.168.1.130

というホストが登録されていると仮定します。

$ ssh s[Tab]
$ ssh server0[Tab]
server01  server02    server03  server04

といった具合に補完されます。

/etc/hosts には以下のように設定が追記されます。

192.168.1.100 server01 # mackerel
192.168.1.110 server02 # mackerel
192.168.1.120 server03 # mackerel
192.168.1.130 server04 # mackerel

# mackerel を検索して追記に利用していますので、
事前に /etc/hosts に色々な設定が書いてあっても、
影響することはありません。
また、追記する前に古い設定を削除していますので、
同じような設定が無限に追記されていくことはありません。

~/.ssh/config を使う

/etc/hosts の場合、root 以外は書き込めないので
_known_hosts_real() を使えませんでしたが、
~/.ssh/config であれば、自分の権限で書き込むことができます。
/etc/bash_completion_known_hosts_real() を、
以下のコードで上書きすると ~/.ssh/config に動的に設定を追加することができます。

ファイルの更新間隔をチェックして、
interval で設定した秒数経過するまで api を叩かないようにしています。
こちらも同時に使用するユーザ数に応じてばらつかせたり、
間隔を伸ばしたりしましょう。

実行例

補完のされ方は /etc/hosts のときと同じです。

~/.ssh/config には以下のように追記されます。

Host server01 # mackerel
Hostname 192.168.1.100
Host server02 # mackerel
Hostname 192.168.1.110
Host server03 # mackerel
Hostname 192.168.1.120
Host server04 # mackerel
Hostname 192.168.1.130

こちらも # mackerel を検索して追記に利用していますので、
~/.ssh/config に色々設定していた場合でも影響はありませんし、
設定が無限に追記されていくこともありません。

まとめ

ssh する際に Mackerel のホストを補完する方法を紹介しました。
かなり無理やりな方法ですので、もっと良い方法をご存知でしたら教えて下さい...
また、ローカルホストから ssh するときにも使えれば良いと思ったのですが、
複数オーガニゼーションで運用している場合、
apikey の切り替えが難しそうでしたので諦めてしまいました。

9日目の担当は、@norisu0313 さんの、
http://qiita.com/norisu0313/items/8e66f6b6adae60279d5f です!

mackerel-agent を root 以外で動かす

執筆時の mackerel-agent の version は、
mackerel-agent version 0.25.0 (rev 0ce0115) [linux 386 go1.4.2] です。

RHEL 系の Linuxmackerel-agentroot 以外で動かす方法です。
mackerel-agent を動かすユーザは mackerel とします。

設定

以下のコマンドを実行してユーザとディレクトリを作成します。

useradd -r mackerel
mkdir /var/log/mackerel-agent /var/run/mackerel-agent
chown mackerel: /var/log/mackerel-agent /var/run/mackerel-agent

/etc/init.d/mackerel-agent を以下のように変更します。

24,25c24,25
< LOGFILE=${LOGILE:="/var/log/$prog.log"}
< PIDFILE=${PIDFILE:="/var/run/$prog.pid"}
---
> LOGFILE=${LOGILE:="/var/log/mackerel-agent/$prog.log"}
> PIDFILE=${PIDFILE:="/var/run/mackerel-agent/$prog.pid"}
26a27
> USER="mackerel"
41c42
<     $BIN ${APIBASE:+--apibase=$APIBASE} ${APIKEY:+--apikey=$APIKEY} --pidfile=$PIDFILE --root=$ROOT $OTHER_OPTS >>$LOGFILE 2>&1 &
---
>     runuser -c "$BIN ${APIBASE:+--apibase=$APIBASE} ${APIKEY:+--apikey=$APIKEY} --pidfile=$PIDFILE --root=$ROOT $OTHER_OPTS >>$LOGFILE 2>&1" $USER &

変更したら、 service mackerel-agent restartmackerel ユーザで動作させることができます。

解説

軽く何をしているか書くと、

  • mackerel ユーザを作成
  • mackerel ユーザで pid と log を書き込めるようにし
  • runusermackerel ユーザで mackerel-agent を実行

しています。
runuser の代わりに su でも動作すると思います。
runuser は入っていない環境もあるようですので、適宜使い分けると良いです。

まとめ

mackerel-agentroot 以外で動かす方法を紹介しました。
CPU や Memory Usage のメトリクス収集は問題なく動作していましたが、
正常に動作しないことも考えられますのでご注意ください。
RHEL 以外のディストリビューションについても、
pid と log を書き込めるようにして、 runuser (su) 経由で実行すれば動作すると思います
(useraddadduser などの細かい違いもあるかもしれません)。
他には、systemdsupervisord で起動するという方法もありそうです。

Deploy ツール Stretcher で Rollback する

Stretcher を使うと、Consul と連携して、所謂 Pull 型の Deploy ができるようになります。

Consul と連携させる場合は、

$ consul event -name deploy s3://xxx-stretcher-files/deploy-20151112-193139.yml

のように、consul event で Manifest の path を指定してイベントを送ると、
YAML に書いてある path(s3, http, file) から tar.gz を取得して展開してくれます。
Stretcher は Rollback するのも簡単で、
Rollback したいバージョンの Manifest の path を指定して consul event を実行するだけです。
S3 に置いている場合、都度 aws s3 ls などして Manifest の path を調べて consul event を実行するのは面倒です。

そこで、以下の様なスクリプトを用意しておくと Rollback が楽になります。

aws s3 ls の結果から最新の Manifest を除いて、
bash の select で番号で選択できるようにしています。
実行すると以下のようになります。

$ ./rollback_stretcher.sh
 1) deploy-20151112-191452.yml  14) deploy-20151029-215027.yml
 2) deploy-20151112-191236.yml  15) deploy-20151028-182546.yml
 3) deploy-20151112-190934.yml  16) deploy-20151028-175655.yml
 4) deploy-20151112-190820.yml  17) deploy-20151028-175201.yml
 5) deploy-20151112-190421.yml  18) deploy-20151028-174909.yml
 6) deploy-20151112-190254.yml  19) deploy-20151028-174155.yml
 7) deploy-20151112-190127.yml  20) deploy-20151028-171838.yml
 8) deploy-20151112-190018.yml  21) deploy-20151022-191616.yml
 9) deploy-20151112-185832.yml  22) deploy-20151022-143736.yml
10) deploy-20151112-185441.yml  23) deploy-20151022-143011.yml
11) deploy-20151112-185341.yml  24) deploy-20151022-142645.yml
12) deploy-20151112-185242.yml  25) deploy-20151022-141728.yml
13) deploy-20151110-162450.yml  26) deploy-20151022-140614.yml
Select manifest (quit q or Ctrl-C) : 1
Event ID: 7a5e80c5-27aa-973e-3328-b7093a91439f

Rollback するスクリプトを用意しておくと、
障害発生時にも落ち着いて作業ができるので良いですね。

※前提条件として、Manifest のファイル名に timestamp を含めて sort できるようにしておく必要があります。

AWS Lambda のスケジュールイベントで定期的に RDS の Snapshot を作成する

AWS Lambda でスケジュールイベントの設定ができるようになりました。

これにより cron で実行していたスクリプトを Lambda に移行できます。
cron の冗長化は面倒ですし、cron のためだけに EC2 を起動しなくて良いので便利ですね。

そこで(?)、Lambda から AWSAPI を叩いて定期的に RDS の Snapshot を作成してみます。

Create Lambda Function

  • 「Get started now」か 「Create Lambda Function」を押下

Select blueprint

Lambda のテンプレートを選択します。

f:id:tkuchiki:20151106111308p:plain

  • Python 2.7 を選択
  • 「lambda-canary」か「hello-world-python」を選択
    • (一通り設定した後にスケジュールイベントの設定をしたかったので「hello-world-python」を選択)

Configure function

Lambda の設定をします。

f:id:tkuchiki:20151106112043p:plain

  • Name, Description を入力

Lambda function code

Lambda のコードを作成します。

f:id:tkuchiki:20151106112138p:plain

  • 以下のコードを入力する
    • lambda_handler の以下の変数を適宜修正
      • delete_days : 指定日経過したら削除する
        • 削除する必要がなければ delete_snapshots ごと削除
      • snapshot_prefix : Snapshot 名の prefix
      • instances : RDS の Instance 名を配列で指定
    • Snapshot 名の時刻は、JST に変換する必要がなければ tz=JST() を削除

追記 2017-01-11 14:09
Aurora の場合は以下のコードで snapshot を取ることができます。

Lambda function handler and role

Lambda function handler はソースコード中の関数を指定します。
handler を変更すれば lambda_handler 以外の関数を実行することも可能です。
また、Lambda 実行に必要な IAM role も設定します。
こちらもテンプレートがありますが、
RDS の Snapshot を操作するものは用意されていないので、
IAM policy を修正します。

f:id:tkuchiki:20151106112555p:plain

  • 「Basic execution role」を押下
    • IAM role 設定画面に遷移する
      • ポップアップブロックしていると設定できないようです

f:id:tkuchiki:20151106112839p:plain

  • 「編集」を押下

f:id:tkuchiki:20151106113006p:plain

  • (ドキュメントを読んで)「OK」を押下
  • 以下の json を入力する

Advanced settings

メモリやタイムアウトの時間を設定します。
数台分の Snapshot を作るだけの想定ですので、
タイムアウトは 10 秒で設定します。

f:id:tkuchiki:20151106113555p:plain

Review

  • 確認して「Create function」を押下

スケジュールイベントの設定

スケジュールイベントの設定を行います。
cron の書式が若干違うので、
詳しくは以下のドキュメントを参照してください。

http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/getting-started-scheduled-events.html

(執筆時は、)設定する時間は UTC ですので、
日本時間で設定しないように注意してください。

f:id:tkuchiki:20151106114226p:plain

  • 「Event sources」タブの「Add event source」を押下

f:id:tkuchiki:20151106113755p:plain

  • 「Scheduled Event」を押下

f:id:tkuchiki:20151106114508p:plain

  • Name, Description を入力
  • Schedule expression を入力
    • (cron(0 4 * ? * *))
      • (例は日本時間で毎13:00 に実行)

f:id:tkuchiki:20151106115118p:plain

これで、スケジュールイベントの設定完了です。
あとは、時間になれば対象 Instance の Snapshot が作成されます。

CloudWatch

CloudWatch に実行ログが出力されます。

f:id:tkuchiki:20151106115250p:plain

  • ログを選択

f:id:tkuchiki:20151106115328p:plain

print などで出力した結果やエラーログが出力されます。

まとめ

AWS Lambda で RDS の Snapshot を作成する方法を紹介しました。
EC2を使わないようにできるので、
可能な範囲で Lambda で実行させるようにすると運用が楽になりそうです。
また、紹介したコードでは省略しましたが、
実際に使う場合は Snapshot 作成に成功・失敗したかを、
チャットなどに通知するようにしたほうが良いですね。

Nginx で query string を見て動的にファイルを配信する

server {
    listen 80;
    server_name localhost;

    rewrite_log on;
    error_log /var/log/nginx/rewrite.log notice;

    location ~ ^/weather+\.json {
        rewrite ^ /weather/$arg_date.json;
    }

    location  ~ ^/weather/.*\.json {
        default_type application/json;
        root /var/www/html;
        try_files $uri =404;
        error_page 404 = @info;
    }

    location @info {
        more_set_headers -s 404 "Cache-Control: no-cache, no-store";
        return 404 " ";
    }
}

という設定ファイルで、

$ cat /var/www/html/weather/20141001.json
{"weather":"sunny"}

を置いている状態で、http request を送ると、

$ curl -s "http://localhost/weather.json?date=20141001"
{"weather":"sunny"}

$ curl -I "http://localhost/weather.json?date=20141001"
HTTP/1.1 200 OK
Server: ngx_openresty/1.4.3.6
Date: Fri, 10 Oct 2014 07:25:05 GMT
Content-Type: application/json
Content-Length: 20
Last-Modified: Fri, 10 Oct 2014 07:02:02 GMT
Connection: keep-alive
ETag: "5437846a-14"
Accept-Ranges: bytes

のようになります。
以下は、rewrite log です。

2014/10/10 16:08:03 [notice] 21659#0: *1 "^" matches "/weather.json", client: 127.0.0.1, server: localhost, request: "GET /weather.json?date=20141001 HTTP/1.1", host: "localhost"
2014/10/10 16:08:03 [notice] 21659#0: *1 rewritten data: "/weather/20141001.json", args: "date=20141001", client: 127.0.0.1, server: localhost, request: "GET /weather.json?date=20141001 HTTP/1.1", host: "localhost"

ファイルが存在しない場合は以下のように 404 を返すことが確認できます。

$ curl -I "htp://localhost/weather.json?date=20141002"
HTTP/1.1 404 Not Found
Server: ngx_openresty/1.4.3.6
Date: Fri, 10 Oct 2014 07:25:32 GMT
Content-Type: application/octet-stream
Content-Length: 1
Connection: keep-alive
Cache-Control: no-cache, no-store

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.

実行結果の詳細は以下のページにアクセスすると確認できます。

https://travis-ci.org/tkuchiki/bats-travis-ci

まとめ

Travis CI で bash のテストを行う方法を紹介しました。
yaml を少し書くだけで bash の CI を回せるようになりますので、
是非お試しください。