読者です 読者をやめる 読者になる 読者になる

tail my trail

作るのも使うのも、結局は、人なのだ

HLS動画の配信テスト用に、JMeterで同時視聴シナリオを書いてみた

RTMP サーバ から AkamaiやCloudFrontのようなCDN 経由で HLSによるライブ配信を行っているとする。 構成は、以下のようなものを想像してもらえれば良い。

f:id:uorat:20160610000244p:plain

同時視聴者数の増加に従ってOriginにどのような負荷がかかるのか確認したかったので、シナリオを組んでみた。

前提: HLSのおさらい

HLS (HTTP Live Streaming) はAppleが開発した仕様。動画を10秒など適度な長さでセグメント化し、セグメントファイルの場所や再生時間、再生順序などが記録されたプレイリストに従って再生するもの。bitrate毎のプレイリストを用意することでAdaptive Bitrateにも対応できる。

詳しくはこのあたりにまとまっている。

背景: ライブ配信は同一ファイルへのリクエストが集中する

VODのHLS配信であれば既に動画の長さは決まっているので、セグメントファイル (*.ts) とプレイリスト (*.m3u8) がCDNのキャッシュに乗るようにCache-Controlを調整してあげれば良い。CDNのキャッシュに乗りさえすれば、あとはClientとCDN の Edgeの世界に閉じるので、Cache Hit率さえ気をつけておけば、Originの負荷はさほど気にする必要はない。

ただし、ライブ配信の場合はセグメントファイルは常に生成され、プレイリストも常に更新される。CDNに古いプレイリストがCacheされてしまうと動画視聴に支障が出るので、TTLはsegment-timeにあわせて短くする必要がある。 また、ロングテールなVODコンテンツと異なりライブ配信の場合、多数のユーザが同時に同じセグメントファイルとプレイリストを参照することになる。Cacheに乗っていなければ、当然Originにリクエストが流れる。

そのため、同時視聴者数の増加に従ってOriginにどのような負荷がかかるのか確認したかったので、シナリオを組んでみた。

HLS視聴のシナリオ作りの勘所

前置きが長くなったが本題。

プレイリストの定期取得とプレイリストに従ったセグメントファイルの取得を模倣する必要がある。ざっと流れは以下。

  1. playlist.m3u8
  2. (loop start)
    1. chunklist_${bitrate}.m3u8
    2. media-${chunk}.ts
    3. wait for segment-time
  3. (loop end)

今回はJMeterクラスタがちょうど手頃に使える環境にあったので、この流れに沿ってHLS視聴のシナリオを組んでみた。 XMLは記事末のGist参照。

全体像は以下のような感じ。

f:id:uorat:20160610153055p:plain

一つずつ説明する。

0. 初期値を設定

Test Planのユーザ定義変数を使ってドメインやポート、Pathやセグメントファイル長など環境や配信毎に変わりうる値を変数化しておく。 f:id:uorat:20160610153059p:plain

また、HTTPリクエスト初期値を定義し、後続のサンプラで冗長な設定をせずに済むようにしておく。 f:id:uorat:20160610153102p:plain

あとは、HTTP Header Manager や HTTP Cache Manager を環境やシナリオにあわせて定義する。 自分は、主要ブラウザにあわせてGzip圧縮前提で試験したかったので、Request Header に "Accept-Encoding: gzip" を入れるようにした。

1. Master Index fileを取得

初期値で定義したURLに対してHTTP Requestを送信するだけで、ここはシンプル。 f:id:uorat:20160610153105p:plain

Bandwidthとbitrate毎のプレイリストが手に入るので、正規表現を HTTP Request Sampler の後に追加して必要なプレイリストを抽出し、変数に格納しておく。 f:id:uorat:20160610153109p:plain

2. (以下繰り返し) Playlistを取得

ライブ配信の場合、プレイリストやセグメントファイルが定期的に更新/追加されるため、以降繰り返し処理となる。 1. で取得したプレイリストのファイル名をもとに、プレイリストを取得する。 f:id:uorat:20160610153112p:plain

セグメントファイルの一覧が手に入るので、1. と同じ要領で正規表現でセグメントファイル名を抽出し、変数に格納しておく。 f:id:uorat:20160610153115p:plain

3. セグメントファイルを取得する

  1. で取得したセグメントファイルのPathをもとに、セグメントファイルを取得する。ここもシンプル。 f:id:uorat:20160610153117p:plain

4. wait

以降、2と3の繰り返しになるが、マニフェストファイルはセグメントファイルの長さ程度の頻度でfetchするため、2の処理に入る前にWaitさせるようにする。 f:id:uorat:20160610153120p:plain

以上がシナリオ。 動かした時のアニメーションGIFを載せておく。

f:id:uorat:20160610175755g:plain

あとはThread数やThread Groupを増やしたり、複数のJMeterにリモート実行するなりして、多重度増やして試験すれば良い。 JMeterのリモート実行方法は、以下を参考にすればすんなり出来ると思う。

最後に

参考までに、私の環境での結果の概要を載せておく。 使っている製品や環境、セグメントファイル長、画質など様々な要因で性能や傾向はかなり変わると思うので、あくまで参考までに。 試験内容や結果に突っ込みあればコメントください。

環境

  • CDN: CloudFront
  • Origin: Wowza Streaming Engine 4 on EC2 (g2.2xlarge)
  • 配信設定: H.264, bitrate 2500 (CBR), キーフレーム2秒
  • セグメントファイル長: 2秒
  • Cache-Control
    • プレイリストファイル max-age = 1
    • セグメントファイル max-age = 3600

傾向

  • セグメント長が2秒と短く、Cache-Control も max-age = 1 のような短いものとしても、多重度を上げるとちゃんとCache Hitしてくれた
  • 多重度を10,000程度まであげて試験してみたが、Originサーバには大した負荷はかからなかった。
    • 同時視聴者数が数千レベルまで増えると、CDN - Origin server間のTCP Connection数が微増した程度。
    • CPUやメモリ、Network IOも大したレベルではなかったので、CDNの恩恵にあずかれた。
  • プレイリストファイルへのリクエストをOriginに全てPassする設定を加えたところ、CDN - Origin server間のTCP Connection数がリニアに増え、Originの負荷も増となった。
    • まぁ当然だよね。Bad knowhow。

1 RTMPサーバ + CloudFrontを挟む程度で、数千人程度のライブ配信はで捌けてしまいそう。 Originの可用性など考えると、RTMPサーバをさらにOriginとEdgeで分離する以下のような構成にしたほうが良いとは思う。 f:id:uorat:20160610000252p:plain その分サーバ稼働費やライセンスなどかかるので、このあたりは費用対効果考えて、判断すれば良いと思う。

Gist

HAProxy v1.6を使って複数のRDS Read Replilcaに分散させる

RDS ReadReplicaを立てて、参照クエリを逃がすことを考える。 可用性や拡張性を考えてReadReplicaは複数台構成とした場合、RDSの仕様を考慮して設計しておく必要がある。ポイントは以下。

  • Read Replicaは個々にEndpoint (DNS名) を持つ。
  • 複数Read Replicaに対してバランシングする仕組みは提供していない。
  • ELBは RDS (Read Replica含め) には使えない。ELBにぶら下げられるのはEC2のみ。
  • Read Replica各ノードの死活監視、障害時の切り離し/切り戻しを考慮する必要がある。

ということで、Read Replicaのバランシングを行うなら、自分で仕組みを用意する必要がある。 実現方法はいくつか選択肢があるが、今回はL7のバランサーとして定評のあるHAProxyを使ってみる。

Architecture with HAProxy

ざっくりと、こんな感じ。

  • 各WebサーバでHAProxyを稼働させる
    • localhost で Listen する
    • HAProxyのBackend serverはRead Replicaに向ける
    • backupにMaster RDSを指定し、Read Replica全滅時はMasterに向ける
  • 使い方
    • 参照: localhost:3306 に接続。HAProxy 経由で Read Replica に接続する。
    • 更新: RDS Master の Endpoint

f:id:uorat:20160529171236p:plain

HAProxyが複数Slaveへの振り分けと監視、Read Replica全滅時はMasterに向けるなど耐障害対策を担ってくれるので、アプリケーション側からは参照と更新のEndpointそれぞれ1つずつ記憶しておけば良い。

インストール方法

なお、HAProxy v1.5まではプロセス起動時にEndpointのA recordをキャッシュし、プロセス再起動するまで更新してくれないため、EndpointがDNS名であるAWS系のサービスとは相性が良くない。 (後述)

例えば、RDSのノード障害 ( →Failover) やインスタンスタイプ変更などによりEndpointのIPが変わった場合もHAProxyは古いIPを参照し続け、Backend serverのステータスがいつまで経ってもDOWNのまま変わらない。

2015年10月にリリースされたHAProxy v1.6から resolvers オプションが追加され、A record の変更を追従してくれるようになっているので、必ずv1.6以上を採用しよう。

What’s new in HAProxy 1.6 | HAProxy Technologies – Aloha Load Balancer

Server IP resolution using DNS at runtime In 1.5 and before, HAProxy performed DNS resolution when parsing configuration, in a synchronous mode and using the glibc (hence /etc/resolv.conf file). Now, HAProxy can perform DNS resolution at runtime, in an asynchronous way and update server IP on the fly. This is very convenient in environment like Docker or Amazon Web Service where server IPs can be changed at any time. Configuration example applied to docker. A dnsmasq is used as an interface between /etc/hosts file (where docker stores server IPs) and HAProxy:

なお、現時点でRPMは見当たらなかったので、要ソースコンパイル。 上述のとおり、各Web/APサーバにインストールするので、RPM化しておく。

RPMBuild HAProxy 1.6

検証実施したタイミングでは 1.6.4 が最新版だったので、これを利用した。 http://www.haproxy.org/download/1.6/src/haproxy-1.6.4.tar.gz

SPECファイルの詳細は割愛するが、CentOS 6 標準のYumで入るHAProxyの構成になるべく似せるようにした。 initスクリプトやlogrotateのサンプルファイル、haproxy.confのサンプル設置など。詳しくは文末のGist参照。

SPECさえ用意してしまえば、あとは rpmbuild するだけ。

$ su - rpmbuilder
$ wget http://www.haproxy.org/download/1.6/src/haproxy-1.6.4.tar.gz
$ tar xzvf haproxy-1.6.4.tar.gz

$ vim ~/rpmbuild/SPECS/haproxy.spec

(spec作成 & 必要なファイルの配置)

$ tar zcf ~/rpmbuild/SOURCES/haproxy-1.6.4.tar.gz /haproxy-1.6.4

$ mv haproxy-1.6.4.tar.gz ~/rpmbuild/SOURCES/

$ cd ~/rpmbuild
$ rpmbuild -ba SPECS/haproxy.spec

$ ls -l ~/rpmbuild/RPMS/x86_64/

-rw-rw-r-- 1 rpmbuilder rpmbuilder  746256  4月 26 13:19 2016 haproxy-1.6.4-1.x86_64.rpm
-rw-rw-r-- 1 rpmbuilder rpmbuilder    6916  4月 26 13:19 2016 haproxy-debuginfo-1.6.4-1.x86_64.rpm

出来たRPMをSelf Repositoryに登録してもよいし、直接 yum install haproxy-1.6.4-1.x86_64.rpm しても良い。

HAProxy の設定

HAProxyの設定ファイルはこんな感じ。ポイントは以下。

  • v1.6の resolvers オプションを使ってVPCのnameserverを参照するようにし、A Recordの変更に追従させる。
  • option mysql-check を指定して、 MySQL 層での Healthcheckを行う。
  • backup に RDS Masterのendpointを指定し、Read Replica全滅時はMasterに向けるようにする

rsyslogの設定

HAProxyはsyslogでログを飛ばす。上の設定ファイルでは local2 に飛ばしているので、syslog / rsyslog の設定もあわせて変更する必要がある。 local2 を他の用途で使っている場合は、よしなに変更して下さい。以下、rsyslogの設定例。

$ sudo cp -p /etc/rsyslog.conf{,.bk}
$ sudo vim /etc/rsyslog.conf

$ diff -u /etc/rsyslog.conf{.bk,}
--- /etc/rsyslog.conf.bk 2014-12-10 19:05:22.000000000 +0900
+++ /etc/rsyslog.conf    2016-04-28 13:30:55.150168043 +0900
@@ -10,8 +10,8 @@
 #$ModLoad immark  # provides --MARK-- message capability

 # Provides UDP syslog reception
-#$ModLoad imudp
-#$UDPServerRun 514
+$ModLoad imudp
+$UDPServerRun 514

 # Provides TCP syslog reception
 #$ModLoad imtcp
@@ -59,6 +59,9 @@

 # Save boot messages also to boot.log
 local7.*                                                /var/log/boot.log
+local2.*                                                -/var/log/haproxy.log
+#local2.warn                                             -/var/log/haproxy.log


 # ### begin forwarding rule ###

$ sudo /etc/init.d/rsyslog restart

監視用ユーザの追加

HAProxyの設定で、option mysql-check を指定しているが、これは MySQL 層での Healthcheckを行うためのもの。 このhealthcheckで使用するユーザをMySQLに用意してあげる。接続さえ出来れば良いので、最低限の権限で良い。

$ mysql -u rdsuser -h master.XXXXXXXXXXXX.ap-northeast-1.rds.amazonaws.com -p
...

mysql> grant usage on *.* to 'haproxy'@'%';
mysql> flush privileges;

ここまで来たら、HAProxyを起動する。

$ sudo /etc/init.d/haproxy start

socat インストール

あと、HAProxyのステータス確認用のsocatをインストールしておく。 設定ファイルで指定したUNIX fileに対して標準入力でコマンド投げることでステータスの確認が可能。

$ yum install socat --enablrepo=epel

show stat コマンドでステータス確認できる。

$ echo "show stat" | sudo socat stdio /var/lib/haproxy/stats
# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,
mysql,FRONTEND,,,0,0,512,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,0,,,,,,,,,,,0,0,0,,,0,0,0,0,,,,,,,,
mysql,read01,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,0,0,4512,0,,1,2,1,,0,,2,0,,0,L7OK,0,4,,,,,,,0,,,,0,0,,,,,-1,5.6.19,,0,0,0,0,
mysql,read02,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,0,0,4512,0,,1,2,2,,0,,2,0,,0,L7OK,0,1,,,,,,,0,,,,0,0,,,,,-1,5.6.19,,0,0,0,0,
mysql,master,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,0,1,0,0,4512,0,,1,2,3,,0,,2,0,,0,L7OK,0,1,,,,,,,0,,,,0,0,,,,,-1,5.6.19-log,,0,0,0,0,
mysql,BACKEND,0,0,0,0,52,0,0,0,0,0,,0,0,0,0,UP,2,2,1,,0,4512,0,,1,2,0,,0,,1,0,,0,,,,,,,,,,,,,,0,0,0,0,0,0,-1,,,0,0,0,0,

HAProxy経由でRead Replicaに接続してみる

今回の例ではRead Replicaは2台用意しており、round-robinされます。

$ mysql -u 127.0.0.1 -P 3306 -u recx -e "show variables like '%hostname%';"
+---------------+---------------+
| Variable_name | Value         |
+---------------+---------------+
| hostname      | ip-10-7-0-205 |
+---------------+---------------+

$ mysql -u 127.0.0.1 -P 3306 -u recx -e "show variables like '%hostname%';"
+---------------+--------------+
| Variable_name | Value        |
+---------------+--------------+
| hostname      | ip-10-7-1-66 |
+---------------+--------------

$ mysql -u 127.0.0.1 -P 3306 -u recx -e "show variables like '%hostname%';"
+---------------+---------------+
| Variable_name | Value         |
+---------------+---------------+
| hostname      | ip-10-7-0-205 |
+---------------+---------------+

Read Replicaなので当然read-onlyが有効化されており、更新操作は出来ません。

$ mysql -u 127.0.0.1 -P 3306 -u recx -e "create database cannot_write;"
ERROR 1290 (HY000) at line 1: The MySQL server is running with the --read-only option so it cannot execute this statement

localhost:3306 に繋げば自動的にReadReplicaに刺さることになるので、負荷分散以外にも日頃の運用で重宝する。

v1.6 のresolvers オプションの検証

期待通り A Record の変更に追従してくれるのか確認しておく。

まずは、HAproxyのバージョンが1.6であることを確認。

$ haproxy -v
HA-Proxy version 1.6.4 2016/03/13
Copyright 2000-2016 Willy Tarreau <willy@haproxy.org>

続いてRDS の内部IP を変更する。 RDSのNodeのIP変更が発生する操作を実施すれば良い。今回はRead Replica (read01) の InstanceType の変更を実施してみる。 InstanceType変更操作を実施すると、以下のようにHAProxy上でDOWNステータスに変わったことを確認。

$ echo "show stat" | sudo socat stdio /var/lib/haproxy/stats

# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,
mysql,FRONTEND,,,0,1,512,105,95066,538297,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,2,,,,,,,,,,,0,0,0,,,0,0,0,0,,,,,,,,
mysql,read01,0,0,0,1,,16,3228,4932,,0,,1,0,3,0,DOWN,1,1,0,5,1,398,398,,1,2,1,,13,,2,0,,1,L4TOUT,,2001,,,,,,,0,,,,0,0,,,,,398,,,0,0,0,1,
mysql,read02,0,0,0,1,,92,91838,533365,,0,,0,0,0,0,UP,1,1,0,0,0,501,0,,1,2,2,,92,,2,0,,1,L7OK,0,4,,,,,,,0,,,,0,0,,,,,6,5.6.19-log,,0,1,0,5,
mysql,master,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,0,1,0,0,501,0,,1,2,3,,0,,2,0,,0,L7OK,0,0,,,,,,,0,,,,0,0,,,,,-1,5.6.19-log,,0,0,0,0,
mysql,BACKEND,0,0,0,1,52,105,95066,538297,0,0,,1,0,3,0,UP,1,1,1,,0,501,0,,1,2,0,,105,,1,0,,2,,,,,,,,,,,,,,0,0,0,0,0,0,6,,,0,1,0,6,

しばらくするとRead ReplicaのInstanceType変更が終わり、それに伴いHAProxy上のread01のステータスもOKに戻ったことが確認できた。

$ echo "show stat" |sudo socat stdio /var/lib/haproxy/stats
# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail,chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration,hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,comp_in,comp_out,comp_byp,comp_rsp,lastsess,last_chk,last_agt,qtime,ctime,rtime,ttime,
mysql,FRONTEND,,,0,0,512,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,2,0,,,,0,0,0,0,,,,,,,,,,,0,0,0,,,0,0,0,0,,,,,,,,
mysql,read01,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,8,1,447,191,,1,2,1,,0,,2,0,,0,L7OK,0,4,,,,,,,0,,,,0,0,,,,,-1,5.6.19-log,,0,0,0,0,
mysql,read02,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,1,0,0,0,876,0,,1,2,2,,0,,2,0,,0,L7OK,0,0,,,,,,,0,,,,0,0,,,,,-1,5.6.19-log,,0,0,0,0,
mysql,master,0,0,0,0,,0,0,0,,0,,0,0,0,0,UP,1,0,1,0,0,876,0,,1,2,3,,0,,2,0,,0,L7OK,0,4,,,,,,,0,,,,0,0,,,,,-1,5.6.19-log,,0,0,0,0,
mysql,BACKEND,0,0,0,0,52,0,0,0,0,0,,0,0,0,0,UP,2,2,1,,0,876,0,,1,2,0,,0,,1,0,,0,,,,,,,,,,,,,,0,0,0,0,0,0,-1,,,0,0,0,0,

HAProxyのログには、IP変更の検知と、監視成功による切り戻しのログが出力される。

Apr 26 13:51:21 localhost haproxy[22217]: Server mysql/read01 is DOWN, reason: Layer4 connection problem, info: "Connection refused", check duration: 2ms. 1 active and 1 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
Apr 26 13:53:42 localhost haproxy[22217]: mysql/read01 changed its IP from 172.31.38.198 to 172.31.35.59 by awsvpc/vpc.
Apr 26 13:54:32 localhost haproxy[22217]: Server mysql/read01 is UP, reason: Layer7 check passed, code: 0, info: "5.6.19", check duration: 4ms. 2 active and 1 backup servers online. 0 sessions requeued, 0 total in queue.

終わりに

MariaDBJDBC Driverを使っても同要件を実現可能だが、実装コストが少なく言語問わず実現できるのでオススメ。 HAProxy自体は汎用的なL7 Balancerなので、何か課題に直面した時の選択肢として抑えておくと良いと思う。

Read Replicaのバランシングに関しては、Read Replica自体や、ELBがEC2以外のサービスに対応してくれる、みたいなリリースが出てくれるといいんだけどなぁ。

Amazon Web Services クラウドデザインパターン設計ガイド 改訂版

Amazon Web Services クラウドデザインパターン設計ガイド 改訂版

Amazon Web Services実践入門 (WEB+DB PRESS plus)

Amazon Web Services実践入門 (WEB+DB PRESS plus)

HAProxyのSPEC

Tarball は公式の HAProxy-1.6.4 から取得して下さい。 SPECにはTarballに含まれていないファイルのインストールも含まれているので、よしなにTarball固め直すなりSPEC編集して使って下さい。

参考


glibc 脆弱性対応に関する備忘録 (CVE-2015-7547)

※WebやSNSの情報漁った個人まとめなので、CVEやGoogleRedhatなど各社が出している公式情報を参照してください。

CVE-2015-7547の内容

悲報。glibc 2.9以降の脆弱性が本日 2/17 (日本時間) 発見された。getaddinfo() 関数の脆弱性で悪用されるとリモートコードの実行が可能なリスクを孕んでいるとのこと。

「glibc」ライブラリに脆弱性、Linuxの大部分に深刻な影響 - ITmedia エンタープライズ Google Online Security Blog: CVE-2015-7547: glibc getaddrinfo stack-based buffer overflow

The glibc DNS client side resolver is vulnerable to a stack-based buffer overflow when the getaddrinfo() library function is used. Software using this function may be exploited with attacker-controlled domain names, attacker-controlled DNS servers, or through a man-in-the-middle attack.

要するに、

  • glibc (2.9 later) に脆弱性が見付かった
  • glibcDNS resolver をbuffer overflowさせることで攻撃者は任意のコードを実行可能
  • 例えば、中間者攻撃介して不正ドメインに誘導され、攻撃者のドメイン名をcallされるような処理くらうと発火
  • さっさとglibc updateしてOS再起動(もしくはglibc参照しているプロセス再起動)しましょう

DNSサーバ側での対応

getaddrinfo() 関数のバッファオーバーフローが発動する2,148バイト以上の場合に成立するらしい。 そのため、DNSサーバ側で対処するなら、単純にDNS response sizeをTCP/UDP 共に2,048 byteに制限すれば良い。 上記の情報が正しければ、Google DNSAWS DNSも対処済みとのことなので、厳しければ再起動せずこの方法でも回避できるとのこと。

これに関して早速良記事。 何か起きてからでは遅いのでさっさとglibc上げるに越したことはなさそうだけど。

glibc使って外部通信行ってるLinuxサーバの対応

各Distributionのパッチが公開されてから、RedhatさんGoogleさん公式発表してくれた。なので、公開されているパッチ当てれば基本はOK。

GHOST対応時に、sshログインできなくなる事象があったらしいので、念のため開発/検証用のサーバで挙動確認してみたけど、特に異常なし。

GHOST脆弱性にyum update glibc、その後リブートする前に - Qiita

CentOS6.6の個人検証機は無事updateできたので、本番にも適宜展開していく。 (CentOS古くてお恥ずかしい...)

# cat /etc/redhat-release
CentOS release 6.6 (Final)
# rpm -qa glibc
glibc-2.12-1.166.el6_7.7.x86_64

OSのPython古くて困ってる場合はpyenv入れたら便利だよ

成り行き

運用管理サーバに使ってるCentOS 6で、色々オペレーションをやってるが、ここでaws-cliを使いたい。 aws-cliPython 2.6のサポートを打ち切っていたので、これを機にPython2.7にあげたい。

$ pip install awscli

DEPRECATION: Python 2.6 is no longer supported by the Python core team, please upgrade your Python. A future version of pip will drop support for Python 2.6
The directory '/home/uorat/.cache/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
The directory '/home/uorat/.cache/pip' or its parent directory is not owned by the current user and caching wheels has been disabled. check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
Requirement already satisfied (use --upgrade to upgrade): awscli in /usr/lib/python2.6/site-packages
Requirement already satisfied (use --upgrade to upgrade): botocore==1.0.0b1 in /usr/lib/python2.6/site-packages (from awscli)
Requirement already satisfied (use --upgrade to upgrade): bcdoc<0.16.0,>=0.15.0 in /usr/lib/python2.6/site-packages (from awscli)
Requirement already satisfied (use --upgrade to upgrade): colorama<=0.3.3,>=0.2.5 in /usr/lib/python2.6/site-packages (from awscli)
Requirement already satisfied (use --upgrade to upgrade): docutils>=0.10 in /usr/lib/python2.6/site-packages (from awscli)
Requirement already satisfied (use --upgrade to upgrade): rsa<=3.1.4,>=3.1.2 in /usr/lib/python2.6/site-packages (from awscli)
Requirement already satisfied (use --upgrade to upgrade): argparse>=1.1 in /usr/lib/python2.6/site-packages (from awscli)
Requirement already satisfied (use --upgrade to upgrade): jmespath==0.7.1 in /usr/lib/python2.6/site-packages (from botocore==1.0.0b1->awscli)
Requirement already satisfied (use --upgrade to upgrade): python-dateutil<3.0.0,>=2.1 in /usr/lib/python2.6/site-packages (from botocore==1.0.0b1->awscli)
Requirement already satisfied (use --upgrade to upgrade): ordereddict==1.1 in /usr/lib/python2.6/site-packages (from botocore==1.0.0b1->awscli)
Requirement already satisfied (use --upgrade to upgrade): simplejson==3.3.0 in /usr/lib64/python2.6/site-packages (from botocore==1.0.0b1->awscli)
Requirement already satisfied (use --upgrade to upgrade): six<2.0.0,>=1.8.0 in /usr/lib/python2.6/site-packages (from bcdoc<0.16.0,>=0.15.0->awscli)
Requirement already satisfied (use --upgrade to upgrade): pyasn1>=0.1.3 in /usr/lib/python2.6/site-packages (from rsa<=3.1.4,>=3.1.2->awscli)
/usr/lib/python2.6/site-packages/pip/_vendor/requests/packages/urllib3/util/ssl_.py:315: SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject Name Indication) extension to TLS is not available on this platform. This may cause the server to present an in
correct TLS certificate, which can cause validation failures. For more information, see https://urllib3.readthedocs.org/en/latest/security.html#snimissingwarning.
  SNIMissingWarning
/usr/lib/python2.6/site-packages/pip/_vendor/requests/packages/urllib3/util/ssl_.py:120: InsecurePlatformWarning: A true SSLContext object is not available. This prevents urllib3 from configuring SSL appropriately and may cause certain SSL connections to fail. For more in
formation, see https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning.
  InsecurePlatformWarning

$ python --version
Python 2.6.6

ただ、Pythonは依存関係が多く、単純にバージョン上げるとシステム全体に波及しかねないので、怖い。 Rubyだとrbenvやrvm, Node.jsだとnvm, 最近はこういうパッケージ管理/バージョン管理ツールが充実しており、Pytyonにも pyenv なるものがあることを知ったので、aws-cli使うときはこいつを利用しようという背景。

yyuu/pyenv https://github.com/yyuu/pyenv

pyenvインストール

とってもお手軽。 git cloneして、基本的にはpath通すだけ。

### git cloneしてくる

$ git clone https://github.com/yyuu/pyenv.git ~/.pyenv
Initialized empty Git repository in /home/uorat/.pyenv/.git/
remote: Counting objects: 11999, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 11999 (delta 0), reused 0 (delta 0), pack-reused 11994
Receiving objects: 100% (11999/11999), 2.12 MiB | 941 KiB/s, done.
Resolving deltas: 100% (8334/8334), done.


### .bashrc/.bash_profile や .zshrc などに必要な変数や処理を追加

# echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
# echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
# echo 'eval "$(pyenv init -)"' >> ~/.bash_profile

### シェル再起動 (source ~/.bash_profile でも可)

exec $SHELL -l

### 起動確認

$ pyenv help
Usage: pyenv <command> [<args>]

Some useful pyenv commands are:
   commands    List all available pyenv commands
   local       Set or show the local application-specific Python version
   global      Set or show the global Python version
   shell       Set or show the shell-specific Python version
   install     Install a Python version using python-build
   uninstall   Uninstall a specific Python version
   rehash      Rehash pyenv shims (run this after installing executables)
   version     Show the current Python version and its origin
   versions    List all Python versions available to pyenv
   which       Display the full path to an executable
   whence      List all Python versions that contain the given executable

See `pyenv help <command>' for information on a specific command.
For full documentation, see: https://github.com/yyuu/pyenv#readme

使い方

思想は rbenv と一緒。

python入れるときは pyenv install -l でインストール可能なバージョンを探して、installする。 pypyやjythonも入れられる。

$ pyenv install -l
Available versions:
  2.1.3
  2.2.3
  ...
  2.7.9
  2.7.10
  2.7.11
  ...
  3.5-dev
  3.5.1
  3.6-dev
  ...
  anaconda3-2.4.0
  anaconda3-2.4.1
  ...
  jython-2.7.0
  jython-2.7.1b1
  jython-2.7.1b2
  ...
  pypy3-2.3.1-src
  pypy3-2.3.1
  pypy3-2.4.0-src
  pypy3-2.4.0
  ...
  stackless-3.3.5
  stackless-3.4.1
  ...

python 2.7.11をpyenvで入れてみる。コンパイル走り負荷上がるので少しだけ注意。

$ pyenv install 2.7.11
Downloading Python-2.7.11.tgz...
-> https://www.python.org/ftp/python/2.7.11/Python-2.7.11.tgz
Installing Python-2.7.11...
WARNING: The Python readline extension was not compiled. Missing the GNU readline lib?
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
WARNING: The Python sqlite3 extension was not compiled. Missing the SQLite3 lib?
Installed Python-2.7.11 to /home/uorat/.pyenv/versions/2.7.11

### 入ったことを確認。

$ pyenv versions
* system (set by /home/uorat/.pyenv/version)
  2.7.11

pyenvで使用するPythonを切り替える。この辺りの使い勝手もrbenvと一緒。

### 現在のバージョン確認。最初はsystemのPython 2.6を見ている。

$ pyenv version
system (set by /home/uorat/.pyenv/version)

### 切り替える

$ pyenv global 2.7.11

### 確認

$ pyenv version
2.7.11 (set by /home/uorat/.pyenv/version)
$ pyenv versions
  system
* 2.7.11 (set by /home/uorat/.pyenv/version)
$ python --version
Python 2.7.11
$ which python
/home/uorat/.pyenv/shims/python

pyenv下でaws-cli入れてみる

無事Python 2.7が入ったのでaws-cli 入るか試してみる。

$ pip install awscli
The directory '/home/uorat/.cache/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
The directory '/home/uorat/.cache/pip' or its parent directory is not owned by the current user and caching wheels has been disabled. check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
Collecting awscli
  Downloading awscli-1.10.1-py2.py3-none-any.whl (880kB)
    100% |████████████████████████████████| 884kB 595kB/s
Collecting botocore==1.3.23 (from awscli)
  Downloading botocore-1.3.23-py2.py3-none-any.whl (2.2MB)
    100% |████████████████████████████████| 2.2MB 237kB/s
Collecting rsa<=3.3.0,>=3.1.2 (from awscli)
  Downloading rsa-3.3-py2.py3-none-any.whl (44kB)
    100% |████████████████████████████████| 45kB 2.7MB/s
Collecting colorama<=0.3.3,>=0.2.5 (from awscli)
  Downloading colorama-0.3.3.tar.gz
Collecting docutils>=0.10 (from awscli)
  Downloading docutils-0.12.tar.gz (1.6MB)
    100% |████████████████████████████████| 1.6MB 320kB/s
Collecting jmespath<1.0.0,>=0.7.1 (from botocore==1.3.23->awscli)
  Downloading jmespath-0.9.0-py2.py3-none-any.whl
Collecting python-dateutil<3.0.0,>=2.1 (from botocore==1.3.23->awscli)
  Downloading python_dateutil-2.4.2-py2.py3-none-any.whl (188kB)
    100% |████████████████████████████████| 192kB 2.4MB/s
Collecting pyasn1>=0.1.3 (from rsa<=3.3.0,>=3.1.2->awscli)
  Downloading pyasn1-0.1.9-py2.py3-none-any.whl
Collecting six>=1.5 (from python-dateutil<3.0.0,>=2.1->botocore==1.3.23->awscli)
  Downloading six-1.10.0-py2.py3-none-any.whl
Installing collected packages: jmespath, six, python-dateutil, docutils, botocore, pyasn1, rsa, colorama, awscli
  Running setup.py install for docutils ... done
  Running setup.py install for colorama ... done
Successfully installed awscli-1.10.1 botocore-1.3.23 colorama-0.3.3 docutils-0.12 jmespath-0.9.0 pyasn1-0.1.9 python-dateutil-2.4.2 rsa-3.3 six-1.10.0

成功! あとは、いつもどおりaws configureして、使えば良い。

$ aws configure --profile=hogehoge
AWS Access Key ID [None]: AKIA*******************
AWS Secret Access Key [None]: **************************************
Default region name [None]: ap-northeast-1
Default output format [None]:

$ aws s3 ls --profile=hogehoge
2016-02-02 13:07:03 hogehoge-bucket

aws-cliやAnsibleなどPython系のツールを使いたいけどPythonのバージョンがfdksajfjdsak; 的な悩みを抱えてる方はpyenv使いましょう。

MHAを使ってZabbixをカジュアルに冗長化してみる

Onpremiseな環境でHAなDBクラスタを組んだり監視サーバを構築したりなんて作業は、ちょっと前であればごくごく当たり前のものだったけど、最近はクラウドSaas/Paasをどれだけ活用して、よりビジネスの本流に集中しようというご時世。 でも、やっぱりOnpremise、どうしても必要な時はある。今のレールに頼り切るのもいいけど、やっぱり勘所はおさえておきたいので、カジュアルにZabbixをHAした時のことを備忘録しておきます。

やりたいこと

Zabbix serverを冗長構成にしたい。 Zabbix serverの稼働に必要なものはざっくり以下。

  • Zabbix server (zabbix_server: 監視サーバプロセス)
  • MySQL (mysqld: データベース)
  • Apache + PHP (httpd: フロントエンド)

Zabbix serverをActive-Activeで稼働させてしまうと、データベースに二重で書き込みが行われてしまうので、Active-Standbyにしておく必要がある。障害時に手動切替なんて面倒、かつ二次障害起こしかねないので避けたい。MySQLもReplication組むだけでは弱いので自動フェイルオーバーくらいさせておきたい。そもそも監視サーバなので手間隙かけたくないし、むしろちょっとしたノード障害くらい放置しておきたい。

MySQLには5.6以降、GTID + failoverが標準搭載されているが、5.6出たばかりの時にGTIDのバグを幾つか踏んで痛い目にあったのと、上述の通りZabbix serverもまとめてHA化したいが幾つものクラスタウェア使って運用するのは腰が重い。カジュアルに一括りにしてよろしくやってほしい。

ということで、MHA。MySQLのReplicationのラグをよしなに埋めてくれる素敵なものにもかかわらず、挙動や構成が大変シンプル、拡張ポイントも多く、カジュアルにZabbixまで含めたクラスタウェアとして使えそうだったので採択決めました。

MHAいろは

MHAのアーキテクチャや仕組みは、詳しくは 公式ドキュメント を参照。要点をまとめるとMHA自体の責務は大きく以下。

  • MySQLの監視
    • Master/Slave ノードの監視
    • Replicationステータス/各ノードのPOSチェック/遅延状況
    • Binlog出力有無
    • Read Only有無
    • など
  • Failover
    • 故MasterからのBinlog救出
    • 昇格候補の選出
    • 新Masterへの差分データ適用
    • 新MasterへのRead only無効化
    • 他Slaveへの差分データ適用
    • 他SlaveのMaster host切替 (CHANGE MASTER)

Architecture of MHA

実システムでDBのSlaveを昇格させるには、これ以外に業務や環境に依存する様々な処理が必要になるが、MHAでは様々なフックポイントが用意されているので、適切な場所に拡張スクリプトを仕込めば、実システムにあわせたFailover作業を全自動で行うことができる。 拡張スクリプトPerlスクリプトで実装する。MHAがPerlで実装されており、これが内部でcallされるため。詳しくはソースコード参照。

構成は、以下の図のとおり、MySQL監視プロセスとしてのMasterHA-Manager (masterha_managerプロセス) と、MySQL各サーバ上で動作するMasterHA-Nodeが必要となる。Managerは監視プロセスとなるため常駐させる必要がある。Nodeは、Failover時にManagerから適宜SSH経由で発行される幾つかのコマンドを処理するだけとなるので、MasterHA-Nodeの動作に必要なものをインストールしておくだけで良い。 MHA Components いずれも軽量なプロセスなので、好きなように構成組めば良い。例えばこんなところか。

松: 昇格用slaveと参照用DBを分ける

MHA manager [192.168.10.1]

Master [192.168.10.11, VIP: 192.168.10.10]
├── Slave (cadidate_master) [192.168.10.12]
├── Slave (no_master, Read only) [192.168.10.13]
└── Slave (no_master, Read only) [192.168.10.14]

竹: Slave 1機, Manager用のノードは別筐体

MHA manager [192.168.10.1]

Master [192.168.10.11, VIP: 192.168.10.10]
└── Slave [192.168.10.12]

梅: 最小構成

Master [192.168.10.11, VIP: 192.168.10.10]
└── Slave (w/ MHA manager) [192.168.10.12]

MHA自体は単体で動作し、あくまでクラスタの監視と、Failover時のリカバリとフックポイントを提供するのみなので、既に稼働中の環境でもねじ込める点も良い。非常に軽量な作りなので、新設/既設いずれも親和性が高いだろう。

MHAはRPMが公開されておりインストールは瞬殺で終わるので割愛。 Managerの他、MySQLが稼働している全サーバにMHA Nodeを入れる必要がある点に注意。 詳細は、Installation を参照。

MHAの拡張ポイント

MHAの設定ファイルで、以下のParameterを定義できる。Perlスクリプトのpathを記載することで、これらが呼び出されるようになる。 https://code.google.com/p/mysql-master-ha/wiki/Architecture#Custom_Extensions

代表的なものを紹介すると、

master_ip_failover_script

※詳しくは 公式ドキュメント を参照

Failover時の挙動を指定できる。代表的なものは、仮想IPの付け替え、クラスタウェアの再起動、アプリケーションの参照先DBの切替など。

以下の3地点で、これらの拡張Perlスクリプトがcallされる。 Failoverの瞬間だけ実行されるわけではない点に注意して実装すること。

MHA Manager calls master_ip_failover_script three times. First time is before entering master monitor (for script validity checking), second time is just before calling shutdown_script, and third time is after applying all relay logs to the new master. MHA Manager passes below arguments (you don't need to set these arguments in the config file).

要するに以下のタイミングで発火されるので、"command" パラメータで条件分岐するようなPerlスクリプトを仕込めば良い。その他、スクリプトに渡される引数もあわせて、以下に記載しておく。

  1. Checking phase : MHA-Manager起動時(--command=status)
    • --command=status
    • --ssh_user=(current master's ssh username)
    • --orig_master_host=(current master's hostname)
    • --orig_master_ip=(current master's ip address)
    • --orig_master_port=(current master's port number)
  2. Current master shutdown phase : Masterサーバシャットダウン時(--command=stop or stopssh)
    • --command=stop or stopssh
    • --ssh_user=(dead master's ssh username, if reachable via ssh)
    • --orig_master_host=(current(dead) master's hostname)
    • --orig_master_ip=(current(dead) master's ip address)
    • --orig_master_port=(current(dead) master's port number)
  3. New master activation phase : フェイルオーバー完了時(--command=start)
    • --command=start
    • --ssh_user=(new master's ssh username)
    • --orig_master_host=(dead master's hostname)
    • --orig_master_ip=(dead master's ip address)
    • --orig_master_port=(dead master's port number)
    • --new_master_host=(new master's hostname)
    • --new_master_ip=(new master's ip address)
    • --new_master_port(new master's port number)
    • --new_master_user=(new master's user)
    • --new_master_password(new master's password)

サンプルコードは以下 https://github.com/yoshinorim/mha4mysql-manager/blob/master/samples/scripts/master_ip_failover

shutdown_script

※詳しくは 公式ドキュメント を参照

オプションで指定するPerlスクリプトで、Failover後のshutdown処理を指定できる。Split brain防止のために使用する。例えば、IPMIやiDRAC, ilo経由, ESXi cli, その他Hypervisor経由の強制シャットダウン、電源OFFなど。 発火タイミングは、 master_ip_failover_script --command=stopssh|stop の直後。SSH reachableな場合とそうでない場合で挙動を変えられる。

  1. SSHで接続可能な場合
    • --command=stopssh
    • --ssh_user=(ssh username so that you can connect to the master)
    • --host=(master's hostname)
    • --ip=(master's ip address)
    • --port=(master's port number)
    • --pid_file=(master's pid file)
  2. SSHで接続できない場合
    • --command=stop
    • --host=(master's hostname)
    • --ip=(master's ip address)

サンプルコードは以下 https://github.com/yoshinorim/mha4mysql-manager/blob/master/samples/scripts/power_manager

例えば、ESXiの場合、vim-cmd で以下のようなコマンドを実行するイメージ。statusチェック, 電源OFF, 電源ONできるので、これを含むPerlスクリプトを仕込めば実現できる。

# VMID=`vim-cmd vmsvc/getallvms|grep zabbix-test01|awk '{print $1}'` && if [ -n "$VMID" ]; then vim-cmd vmsvc/power.getstate $VMID; fi
Retrieved runtime info
Powered on

# VMID=`vim-cmd vmsvc/getallvms|grep zabbix-test01|awk '{print $1}'` && if [ -n "$VMID" ]; then vim-cmd vmsvc/power.off $VMID; fi
Powering off VM:

# VMID=`vim-cmd vmsvc/getallvms|grep zabbix-test01|awk '{print $1}'` && if [ -n "$VMID" ]; then vim-cmd vmsvc/power.on $VMID; fi
Powering on VM:

MHA managerプロセスのバックグラウンド起動

普通に起動するとフォアグラウンド。公式ドキュメントDaemonTools使っているが、UpstartLinuxのサービス管理ツールでもシンプルに実現できるので、この辺りでデーモン化してあげると手軽で良いだろう。 MHA MnanagerプロセスはFailoverすると終了するライフサイクルになっているので、自動起動やrespawn処理はあまり入れないほうが良さそうな気がする。以下サンプル。

description     "MasterHA manager services"

chdir /var/log/masterha
exec /usr/bin/masterha_manager --conf=/etc/mha.cnf >> /var/log/masterha/masterha_manager.log 2>&1
pre-start exec /usr/bin/masterha_check_repl --conf=/etc/mha.cnf
post-stop exec /usr/bin/masterha_stop --conf=/etc/mha.cnf

起動/終了/ステータスチェックはこんな感じ。

  • start masterha-manager
  • status masterha-manager
  • stop masterha-manager

MySQL インストール

詳細は割愛。 MySQL 5.6でも5.7でも動くぽいので、好きなモノを選べば良いと思う。 注意点は以下。

  • MySQL
    • MySQL 5.7の罠があなたを狙っている - Slideshare
    • Semi Replicationは有効化
      • Master - Slave間の欠損が限りなく0に近くなるので、Failoverが早くなる。
      • ただし、レイテンシとのトレードオフで判断すること
    • relay_log_info_repository=TABLE はやめておけ
      • MHA利用時、これを入れていると以下のようなエラーが出た。relay_log情報はMHAがよろしく見てくれるので、my.cnfではあえて有効化しておく必要はなし。
      • Getting relay log directory or current relay logfile from replication table failed on

    • relay_log_purge = 0
      • MHAでrelay_logを参照することと、purge_relay_log スクリプトが提供されており、こちらに委ねたほうが望ましいため無効にしておく。推奨
      • むしろ無効にしていないと、エラーログにwarningが出る。
      • Wed Nov 4 18:00:09 2015 - [warning] relay_log_purge=0 is not set on slave zabbix-test02(192.168.10.12:3306).

    • read_only は set文で。my.cnfには書くな。
      • 元のSlaveがMasterに昇格した時に、my.cnfに read_only = 1 がセットされていると再起動のタイミングで戻り障害を引き起こす可能性があるため危険。

Zabbix インストール

ようやくZabbix。インストールの詳細は例によって割愛。

ポイントは、冒頭で触れたとおりMHAでZabbixまで含めてクラスタ化するため、MySQLのMasterと同一サーバ上でZabbix serverを稼働させるようにすること。 Zabbix serverプロセスをPacemaker等、別の機構でクラスタ化しても良いのだが、実績上Zabbix serverプロセスが落ちる可能性よりもホストがポシャることの方が多かったので。監視サーバなので、という割り切りです。なので、設定は以下のような感じ。

  • zabbix_server.conf
    • DBHostはlocalhost (default) でOK
    • DBSocketも Socket (mysql.sock)
    • SourceIPは指定なし
      • VIPを指定してもよいが、監視を行うときに逆に足枷になることが多いので、できれば指定しないほうが運用上楽。

Zabbix web (Apache + PHP) は、複数Activeで動作可能ので上述のクラスタとはわけて考えて良い。 何人ものエンジニアが大量のグラフが貼られたスクリーンを定期リロードすることを考えると、むしろ別サーバにApache + PHP専用機並べてバランシングさせておきたいくらい。 なので、PHPからDBの接続はVIP経由でアクセスさせる。以下、Webの初期設定のポイントを列挙しておく。それぞれのWebサーバに対して設定行うこと。

  1. http://$HOSTNAME/zabbix/ にアクセス
  2. インストール画面が出てくるので進めていく
  3. DB接続情報は以下で入力
    • Database Host: VIP
    • Database User: zabbix
    • Database Password: 設定したパスワード
    • Test Connectionして接続確認
  4. Zabbix server Detailは以下で入力
    • Host: VIP
    • Name: 任意の名前 (環境が分かると良い)
  5. 確認ダイアログが出るので、間違いないか確認。
  6. これを全てのApache/PHPに対して行う (↑で生成された設定ファイル撒くでも構わない)

master_ip_failoverスクリプト for zabbix

最後の仕上げ。MHAで障害検知時は、MySQLの他、VIPとZabbix serverもセットでFailoverさせるようなスクリプトを用意しておく。あとは、/etc/mha.cnf の master_ip_failover に指定しておけば良い。 VIPの付け替えを行うのでARPキャッシュの更新を忘れないように。さもないと、フェイルオーバーしたのにつながらないなんてことのないように。 以下、サンプルです。

#!/usr/bin/env perl

#
#  If you wanna know about the paramaster, "master_ip_failover_script",
#  read the below document.
#
#  https://code.google.com/p/mysql-master-ha/wiki/Parameters#master_ip_failover_script
#
#  [Usage]
#  master_ip_failover_zabbix \
#    --virtual_ip=192.168.10.10/16 \
#    --orig_master_vip_eth=eth0 \
#    --new_master_vip_eth=eth0
#
#  [Description]
#  --virtual_ip            => Virtual IP / mask
#  --orig_master_vip_eth   => Device name that is attached virtual ip on origin master host.
#  --new_master_vip_eth    => Device name that is attached virtual ip on new master host.
#

use strict;
use warnings FATAL => 'all';

use Getopt::Long;
use MHA::DBHelper;

my (
  $command,        $ssh_user,         $orig_master_host,
  $orig_master_ip, $orig_master_port, $new_master_host,
  $new_master_ip,  $new_master_port,  $new_master_user,
  $new_master_password,
  $virtual_ip,  $orig_master_vip_eth, $new_master_vip_eth
);
GetOptions(
  'command=s'             => \$command,
  'ssh_user=s'            => \$ssh_user,
  'orig_master_host=s'    => \$orig_master_host,
  'orig_master_ip=s'      => \$orig_master_ip,
  'orig_master_port=i'    => \$orig_master_port,
  'new_master_host=s'     => \$new_master_host,
  'new_master_ip=s'       => \$new_master_ip,
  'new_master_port=i'     => \$new_master_port,
  'new_master_user=s'     => \$new_master_user,
  'new_master_password=s' => \$new_master_password,
  'virtual_ip=s'          => \$virtual_ip,
  'orig_master_vip_eth=s' => \$orig_master_vip_eth,
  'new_master_vip_eth=s'  => \$new_master_vip_eth,
);

exit &main();

sub main {
  # for debug
  print("---------- start master_ip_failover script ----------");
  if ( defined $command )             { print("  command => $command\n") };
  if ( defined $ssh_user )            { print("  ssh_user=s => $ssh_user\n") };
  if ( defined $orig_master_host )    { print("  orig_master_host => $orig_master_host\n") };
  if ( defined $orig_master_ip )      { print("  orig_master_ip => $orig_master_ip\n") };
  if ( defined $orig_master_port )    { print("  orig_master_port => $orig_master_port\n") };
  if ( defined $new_master_host )     { print("  new_master_host => $new_master_host\n") };
  if ( defined $new_master_ip )       { print("  new_master_ip => $new_master_ip\n") };
  if ( defined $new_master_port )     { print("  new_master_port => $new_master_port\n") };
  if ( defined $virtual_ip )          { print("  virtual_ip => $virtual_ip\n") };
  if ( defined $orig_master_vip_eth ) { print("  orig_master_vip_eth => $orig_master_vip_eth\n") };
  if ( defined $new_master_vip_eth )  { print("  new_master_vip_eth => $new_master_vip_eth\n") };

  # For current mastre shutdown phase
  # execute the below flow.
  #   1. unbind virtual ip from the origin master host.
  #   2. stop zabbix server process.
  if ( $command eq "stop" || $command eq "stopssh" ) {
    my $exit_code = 1;
    eval {
      `ssh $orig_master_host -o 'ConnectTimeout=5' '/etc/init.d/zabbix-server stop; /sbin/ip addr del $virtual_ip dev $orig_master_vip_eth'`;
      $exit_code = 0;
    };
    if ($@) {
      warn "Got Error while shutdown phase: $@\n";
      exit $exit_code;
    }
    exit $exit_code;
  }
  # For new master activation phase
  # execute the below flow.
  #   1. start zabbix server process.
  #   2. bind virtual ip on the new master host.
  #
  # Notice:
  #   * You don't need to unable the paramster "read_only = 1" and enable binary log,
  #     because they are already defined MHA common module.
  #
  elsif ( $command eq "start" ) {
    my $exit_code = 10;
    eval {
      my $ping_interface = join(".", $virtual_ip =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/ );
      `ssh $new_master_host -o 'ConnectTimeout=15' '/sbin/ip addr add $virtual_ip dev $new_master_vip_eth && /sbin/arping -U $ping_interface -c 3 && /etc/init.d/zabbix-server start'`;
      $exit_code = 0;
    };
    if ($@) {
      warn "Got Error while activation phase: $@\n";
      # If you want to continue failover, exit 10.
      exit $exit_code;
    }
    exit $exit_code;
  }
  elsif ( $command eq "status" ) {
    # do nothing
    exit 0;
  }
  else {
    &usage();
    exit 1;
  }
}

sub usage {
  print
"Usage: master_ip_failover --command=start|stop|stopssh|status --orig_master_host=host --orig_master_ip=ip --orig_master_port=port --new_master_host=host --new_master_ip=ip --new_master_port=port --virtual_ip=ip --orig_master_vip_eth=eth --new_master_vip_eth=eth\n";
}

以上で、カジュアルにZabbix serverが冗長化出来ます。 Zabbix serverプロセスが落ちたりVIP剥がれてもFailoverしないので、そのあたりは割りきって参考にして下さい。

まとめ

MHAはソースもPerlでそこまでボリューム多くないので気軽に読めちゃいます。 日本語の情報も多い(なにせ作者が日本人: 元DeNA, 現Facebookの松信さん)ので、他メンバーへの技術トランスファーも楽ちん、運用容易性が非常に高いのでおすすめです。

結局、MHAの宣伝みたいになってしまいました。。

iPhone 6Sが出たのでiPhone6 + MVNO に乗り換えてみた

iPhone 6S / 6S plus が2015年9月25日に発売されました。 2年前にSoftbankで購入したiPhone 5Sの2年分割払 + 月月割 が10月で切れるので、次をどうするか悩んだのですが、タイトルのとおり、このタイミングでiPhone 6 + MVNO への切替を試してみました。 試算上、端末代含めた今後2年間の出費を半額程度に減らすことができました。

  • 以下、10月時点の情報です。
  • 当ブログの情報は個人的に調べて試算したものなので間違いがあるかもしれません。これによる一斉の責任は負いかねます。
  • もし数値や認識に間違いなど見つかったらご指摘頂けますと幸いです。

iPhone 6 + MVNOにした背景

今までSoftbankの機種変のレールにそのまま乗っかり、2年分割払いで購入して月々割引 & 旧端末下取のパターンでiPhone 4S, 5Sと使ってきました。 Facetime オーディオやLINE電話などを使うことが多いので通話料はあまり取られないのですが、機種の分割払いも含めて月々7,000円前後かかっていました。年間 84,000円。 こうしてみると、結構かかってる。 今年は子どもも生まれ、色々と支出も増える年になったので、出来るだけ余計な出費は抑えたい。 仮にこの固定費を半額程度に抑えられたら、ルンバでも買って時間捻出できるなと思ったのがきっかけでした。

iPhone 6Sのアップデート内容を見ると、1200万/500万画素のカメラ、4Kビデオ、3D touchなどそこそこアップデートはありましたが、会社で検証端末は手に入るので、私物として確保したいと強く思えるような、ビビッと来るものがなかったので、現状キープか型落ちのiPhone 6を調達するで良いかと思い始めました。

また、ここ1〜2年で急激に盛り上がりを魅せているMVNOも試してみたい思いがあったので、まずはフラットに検討してみようと思ったのが背景です。

料金比較

ということで、支出がどの程度になるか、幾つかのパターンでシミュレーションしてみました。

前提

自分の生活と用途を考えて、以下の制約をおきました。

月々の通信量3GB以内

自宅や職場にいる時はWifiにつないでいるので、月々の通信量は2GB程度に収まっています。 なので、3GB程度のプランで十分そうでした。

音声通話

Facetime オーディオやLINE電話などを使うことが多いですが、緊急時などいざというときに音声電話はほしいです。また、電話番号変わると各調整が非常に手間なので、キャリア変えるにしてもMNPして電話番号はキープしておきたいです。

iPhoneを購入する場合ストレージは16GB

今までは "大は小を兼ねる" という言葉に従い、面倒な容量切り詰め作にかかる時間を買う目的で64GBを使ってきました。ただ、この4年間の運用で不要なアプリやデータが貯まり散らかる一方だし、写真などのデータは端末に入れておく理由は全くなく、共有フォトストリームやGoogleフォトなどクラウドサービスに放り込んでおいたほうが利便性は高いので、仮に機種を返るなら不要物を根こそぎ消す前提で16GBを候補にしました。 あと、自分は手が大きい方ではなく画面が大きいと辛いので、Plusではなく小さい方を候補に選びました。

MVNO使う場合、購入するiPhone6docomo

SIMフリー版でもいいんですが、値段が大きく変わってくるので。10月時点で値段に3〜4万程度の開きがありました。 MVNOの多くはdocomo SIMなので、選択肢の多いほうが望ましいと考えました。

Apple iPhone 6 16GB シルバー 【docomo 白ロム】MG482J

Apple iPhone 6 16GB シルバー 【docomo 白ロム】MG482J

候補

ということで、上がった候補が以下。

利用中のキャリアが、"実質0円で機種変できるキャンペーン" をやっていたので、比較のため候補にあげてみました。 docomo乗換に関しては、代理店がキャッシュバックキャンペーンをやっていたりするので、もう少し調べれば大手キャリアに分のある選択肢も出てくるのかもしれません。10月時点では、iPhone6も台数限定で一括0円キャンペーン + キャッシュバックなどやっていたようです。 ただ、複雑怪奇な契約体型から脱したい思いもあったし、キャッシュバックやっている店舗を調べるのも足を運ぶのも時間がかかり過ぎる印象があり、"台数限定" に焦るのもバカバカしかったので、そういったものは対象外としました。

MVNO業者は、どこもプランが横並びなので、メジャーなIIJmio (みおふぉん) をサンプルにおきました。

  1. 現状維持: iPhone 5S (64GB) + Softbank (ホワイトプラン + パケットし放題フラット for 4G LTE)
  2. 機種/プラン変更: iPhone 6S (16GB) + Softbank (スマ放題ライト + データ定額パック5GB)
  3. docomoMNP: iPhone 6S (16GB) + docomo (カケホーダイライトプラン + データMパック5GB)
  4. MVNOMNP: iPhone 6S (16GB) + MVNO (3GB程度通信 + 音声通話)
  5. MVNOMNP: iPhone 6 (16GB) + MVNO (3GB程度通信 + 音声通話)

見積もってみると

あくまで試算ですが、こうしてみると結構な開きが出ました。

型落ちのiPhone6で、MVNOで運用するとさすがに安いです。新しい機種を買うにも関わらず、現状stayと比べると先2年の総出費が倍以上変わります。

面白いのが、Softbankで機種変をしてもしなくてもTotalの出費はほとんど変わらないという点です。

1.現状維持 2.機種変更 3. docomoMNP 4. MVNOMNP 5. MVNOMNP
音声通話基本料 ¥1,008 ¥1,836 ¥1,836 ¥756 ¥756
データ通信基本料 ¥6,156 ¥5,400 ¥5,400 ¥972 ¥972
インターネット接続サービス ¥324 ¥324 ¥324 ¥0 ¥0
端末代 24ヶ月分割 ¥0 ¥3,900 ¥3,888 (※1) ¥3,906 (※1) ¥2,354
機種変サポート(24ヶ月間) ¥0 -¥2,835 -¥3,456 ¥0 ¥0
MNP特典 (※2) -¥450 ¥0 ¥0
特典 (12ヶ月間) ¥0 ¥0 -¥1,350 ¥0 ¥0
下取り ¥0 -¥1,065 (※3) -¥625 (※3) -¥625 (※3) -¥625 (※3) -¥625
1年目 月々支払額 ¥7,488 ¥7,560 ¥5,567 ¥5,009 ¥3,329
2年目 月々支払額 ¥7,488 ¥7,560 ¥6,917 ¥5,009 ¥3,329
2年間 総支払額 ¥179,712 ¥181,440 ¥149,808 ¥120,216 ¥79,900

ということで、10月中旬〜11月中旬の2年契約更新期間 (違約金が発生しない1ヶ月) のタイミングで、プラン5の 型落ちiPhone6 + MVNO に切り替えることにしました。

iPhone調達とMVNO切替

iPhoneAmazonで購入しました。 今はさらに値下がりしているようですが、私が購入した時は上記見積もりの¥56,500で購入することが出来ました。 赤シム引いてしまうと後で悲しい思いをするので白ロム保証を明記しているところを選ぶと良いと思います。 iPhoneは注文して2日後に届きました。

MVNOはいくつか比較検討した結果、IIJmioのみおふぉんを選びました。理由は通信実績です。 MVNOは競争が激しく、特に新興はキャンペーンを打ち出して料金も割引されることがあります。 ユーザが少ないうちは通信も快適でメリットが多いのですが、ユーザが増えた途端速度が落ちたなんてこともあるようです。 あくまで10月までの実績ベースですが、IIJmioは比較的通信が安定しており、ユーザ増加にあわせて増強対応も進められているようでした。

参考にしたのはこちらのサイトです。

格安SIM(MVNO)の速度比較 - androidlover.net

ちなみに、解約違反金は音声通話を入れている場合は¥0ではないですが、月々その額は減っていき、1年経つとそれ以降は違約金はかからなくなるそうです。

IIJmioAmazonで購入しました。こちらは注文して翌日にMNP用の書類が届きました。

IIJ IIJmio SIM 音声通話 パック みおふぉん IM-B043

IIJ IIJmio SIM 音声通話 パック みおふぉん IM-B043

MNPの場合は書類のみ届きます。キャンペーンコードが入ってるので、IIJmioのWebサイトで申請を進めると、数日後にSIMカードが届きます。

なお、手続き前にキャリアからMNP予約番号を取得しておくこと。softbankMNP予約番号はMySoftbankから申請できず、店頭か電話での申し込みになります。

SIMカードが届いたら開通手続きを行えば、1時間程度で新しいSIMが使えるようになります。 全て自宅で行えるのでありがたいですね。

揃ったものがこちら。 SIMは当然docomo SIMでした。 f:id:uorat:20151115172528j:plain f:id:uorat:20151115172520j:plain

64GB→16GBへの移行とiPhone5S売却

データの整理は思った以上に大変でした。

4SでiPhoneを使い出して以来、ずっと64GBで運用してきたので不要なアプリや写真、聞かない音楽が貯まり散らかりまくっていたので。 16GBといえども、iOSメタデータが乗ってくるので、実際に使える領域は10GB程度と予め割りきっておくと良いです。 私は音楽の取捨選択に最も苦労しました。

移行が無事完了したら、あとは旧端末iPhone5Sの売却です。 私は周囲の買取評判が比較的良かったゲオのSmarketを利用しました。

スマートフォンの買取ならゲオのSmarket | iPhone・Androidなど高価買取

申し込みをすると、数日後段ボール箱が届くので、手順通り初期化とアクティベートを行ってから箱に収納し送り返します。 数日後査定結果がメールで送られてくるので、同意すればその額が銀行口座に振り込まれます。 この時のために2年前iPhoneの箱は捨てずにとっておいたので、傷はありながらも条件は少しは良くなるのではと期待。結果、私は¥17,500程度で買い取ってもらえました。 iPhone6の購入価格が ¥56,500 だったので、実質手に入れたので、実質 ¥39,000 程度で手に入れたことになります。

移行してみて

もうすぐ一ヶ月程度経ちますが、不自由なく快適に過ごしています。

通信状況はちゃんと計測していないですが、私の生活範囲では体感むしろ向上した気がします。 ランニングコストも大幅に削減できたし、複雑怪奇な料金プラン/契約から脱することもできました。

節約バンザイ。自由バンザイ。

AWS CLI のprofileを簡単に切り替える

意外と知らない人がちらほらいたので、書き留めておく。

AWSAPIとIAMについて

基本的にAWSの全てのサービス / リソースの操作はAPIによって行われます。 (Management Consoleの操作も内部的には全て同じAPIアクセス) S3への画像のuploadから、EC2 Instanceの増設/設定変更から、CloudWatchの参照、Support Caseの起票まで、あらゆる操作をAPIで行うことができるので、 単純なWeb Applicationだけでなく、オペレーション自動化など多種多様な用途で活用することになり、IAM User/Access Keyもあわせて複数用意することが多いと思います。

環境やサービスによってAWSのアカウント自体を分ける運用も多いと思いますが、そうなると扱うKeyの数は益々増えていきます。

AWS CLI

何かAWSのリソースを使ってものづくりをする際に、デバッグなどで AWS CLI を使うことは多いと思います。 なんたってワンライナーで手軽にAPI叩けますからね。 蛇足ですが、S3 のオブジェクト一覧見る時なんか、Management Console見るよりも、AWS CLI叩くことのほうが私は多いです。そのほうが速い。

本題

だいぶ引き伸ばしましたが、本題です。 幾つものUser, Keyを併用して使っている時に、ユーザを切り替えて権限確認、動作確認したい場合の方法です。 至ってシンプル。 --profile オプションを使いましょう。 以下、Helpより引用。

$ aws help
      
      ( ...omitted ...)

       --profile (string)

       Use a specific profile from your credential file.

       ...


$ aws configure help

      ( ...omitted ...)

SYNOPSIS
          aws configure [--profile profile-name]

      ...

aws configure コマンドで --profile *profile-name* を付与して設定を進めると、指定した名前で別の設定を加えることができます。 default (無名) は消えず別の内容で登録され、他サブコマンド実行時に同様に --profile *profile-name* オプションを付与するだけでアカウントを切り替えできます。

以下設定例。

$ aws configure --profile bob
AWS Access Key ID [AKIA************hoge]:
AWS Secret Access Key [****************hOgE]:
Default region name [ap-northeast-1]:
Default output format [None]:

設定内容は $HOME/.aws/config, credentials ファイルに INI形式で保存されます。 中身を見ると以下のように、default (無名) はそのままに、上のprofile-name で指定した情報が追記されています。

$ cat .aws/config
[profile default]
region = ap-northeast-1

[profile bob]
region = ap-northeast-1

$ cat .aws/credentials
[default]
aws_access_key_id = AKIA************DMKA
aws_secret_access_key = ************************************hoge

[bob]
aws_access_key_id = AKIA************hoge
aws_secret_access_key = ************************************hOgE

切替を試してみましょう。 defaultはrootアカウントのKey, S3の任意のbucketのみ権限があるようなIAM Userをbobとしてprofile登録しています。

# root権限でbucket一覧確認
$ aws s3 ls
2015-10-15 19:28:40 fuga
2015-10-15 19:29:17 hogehoge

# bobユーザでfuga bucketの中身をls
$ aws s3 ls s3://fuga/ --profile bob
2015-10-15 19:44:10      14152 test.png

# hogehoge bucketにアクセス (権限なし)
$ aws s3 ls s3://hogehoge --profile bob

A client error (AccessDenied) occurred when calling the ListObjects operation: Access Denied

終わりに

jq コマンド、補完入力設定と同じくらい必須のTIPsだと思います。 環境変数にcredential書く手もありますが、その場合profileオプションで切り替えられず少々不便になるので、こちらのほうがオススメです。

AWS CLIは鬼のようにできることが多いのと、たまにManagement Consoleではできない操作なんかもあったりするので、 移動中などでちまちま暇つぶしがてらリファレンス呼んでみると面白いです。 aws — AWS CLI 1.8.12 documentation