ridgepole+pt-osc

Ridgepole--external-scriptというオプションを付けました。 更新SQLの実行を外部スクリプトで行うオプションです。

これで、一応pt-oscを使って、DDLを実行できるようにはなりました。あとは、MySQLのALTER文で末尾にLOCK=NONEを付けるとか。

ただ、MySQLのインデックス追加・削除に関してはCREATE INDEXDROP INDEXが渡されるため、ALTER文への書き換えが必要です。 また、ALTER文についても、pt-oscで実行する場合はテーブル名と以降のSQLへの分解が必要です。

Example

pt-osc.rb

#!/usr/bin/env ruby
require 'open3'
require 'shellwords'

def parse_sql(sql)
  case sql
  when /\AALTER +TABLE +`([^`]+)`(.+)\z/i
    [$1, $2]
  when /\ACREATE +INDEX +`([^`]+)`(.+)ON +`([^`]+)`(.+)\z/
    [$3, "ADD INDEX `#{$1}` #{$2} #{$4}"]
  when /\ADROP +INDEX +`([^`]+)` +ON +`([^`]+)`\z/i
    [$2, "DROP INDEX `#{$1}`"]
  else
    raise 'Unsuppored SQL'
  end
end

table, alter = parse_sql(ARGV[0])

dsn = "h=localhost,u=root,p=root,D=employees,t=#{table},A=utf8"

cmd = ['pt-online-schema-change']
cmd << '--alter' << alter
#cmd << '--dry-run'
cmd << '--execute'
cmd << dsn

cmd = Shellwords.join(cmd)
out, err, status = Open3.capture3(cmd)

puts out
$stderr.puts err
exit status.to_i

実行してみる

まずはdry-run。

$ ridgepole -c database.yml -a --external-script ./pt-osc.rb --dry-run
pply `Schemafile` (dry-run)
add_column("titles", "to_date2", :date, {:after=>"from_date"})
remove_column("titles", "to_date")
remove_index("titles", {:name=>"emp_no"})
add_index("titles", ["emp_no"], {:name=>"emp_no2", :using=>:btree})

# ALTER TABLE `titles` ADD `to_date2` date AFTER `from_date`
# ALTER TABLE `titles` DROP `to_date`
# DROP INDEX `emp_no` ON `titles`
# CREATE  INDEX `emp_no2` USING btree ON `titles` (
# `emp_no`)

問題なさそうなので適用します。

$ ridgepole -c database.yml -a --external-script ./pt-osc.rb
Apply `Schemafile`
-- add_column("titles", "to_date2", :date, {:after=>"from_date"})
Execute ./pt-osc.rb ALTER\ TABLE\ \`titles\`\ ADD\ \`to_date2\`\ date\ AFTER\ \`from_date\`
./pt-osc.rb: pt-online-schema-change --alter \ ADD\ \`to_date2\`\ date\ AFTER\ \`from_date\` --execute h\=localhost,u\=root,p\=root,D\=employees,t\=titles,A\=utf8
No slaves found.  See --recursion-method if host vagrant-ubuntu-trusty-64 has slaves.
Not checking slave lag because no slaves were found and --check-slave-lag was not specified.
Operation, tries, wait:
  copy_rows, 10, 0.25
  create_triggers, 10, 1
  drop_triggers, 10, 1
  swap_tables, 10, 1
  update_foreign_keys, 10, 1
Altering `employees`.`titles`...
Creating new table...
Created new table employees._titles_new OK.
Altering new table...
Altered `employees`.`_titles_new` OK.
2015-11-20T16:09:42 Creating triggers...
2015-11-20T16:09:42 Created triggers OK.
2015-11-20T16:09:42 Copying approximately 442453 rows...
2015-11-20T16:09:45 Copied rows OK.
2015-11-20T16:09:45 Swapping tables...
2015-11-20T16:09:45 Swapped original and new tables OK.
2015-11-20T16:09:45 Dropping old table...
2015-11-20T16:09:45 Dropped old table `employees`.`_titles_old` OK.
2015-11-20T16:09:45 Dropping triggers...
2015-11-20T16:09:45 Dropped triggers OK.
Successfully altered `employees`.`titles`.
   -> 3.6346s
-- remove_column("titles", "to_date")
Execute ./pt-osc.rb ALTER\ TABLE\ \`titles\`\ DROP\ \`to_date\`
./pt-osc.rb: pt-online-schema-change --alter \ DROP\ \`to_date\` --execute h\=localhost,u\=root,p\=root,D\=employees,t\=titles,A\=utf8
No slaves found.  See --recursion-method if host vagrant-ubuntu-trusty-64 has slaves.
Not checking slave lag because no slaves were found and --check-slave-lag was not specified.
Operation, tries, wait:
  copy_rows, 10, 0.25
  create_triggers, 10, 1
  drop_triggers, 10, 1
  swap_tables, 10, 1
  update_foreign_keys, 10, 1
Altering `employees`.`titles`...
Creating new table...
Created new table employees._titles_new OK.
Altering new table...
Altered `employees`.`_titles_new` OK.
2015-11-20T16:09:46 Creating triggers...
2015-11-20T16:09:46 Created triggers OK.
2015-11-20T16:09:46 Copying approximately 1 rows...
2015-11-20T16:09:49 Copied rows OK.
2015-11-20T16:09:49 Swapping tables...
2015-11-20T16:09:49 Swapped original and new tables OK.
2015-11-20T16:09:49 Dropping old table...
2015-11-20T16:09:49 Dropped old table `employees`.`_titles_old` OK.
2015-11-20T16:09:49 Dropping triggers...
2015-11-20T16:09:49 Dropped triggers OK.
Successfully altered `employees`.`titles`.
   -> 3.1043s
-- remove_index("titles", {:name=>"emp_no"})
Execute ./pt-osc.rb DROP\ INDEX\ \`emp_no\`\ ON\ \`titles\`
./pt-osc.rb: pt-online-schema-change --alter DROP\ INDEX\ \`emp_no\` --execute h\=localhost,u\=root,p\=root,D\=employees,t\=titles,A\=utf8
No slaves found.  See --recursion-method if host vagrant-ubuntu-trusty-64 has slaves.
Not checking slave lag because no slaves were found and --check-slave-lag was not specified.
Operation, tries, wait:
  copy_rows, 10, 0.25
  create_triggers, 10, 1
  drop_triggers, 10, 1
  swap_tables, 10, 1
  update_foreign_keys, 10, 1
Altering `employees`.`titles`...
Creating new table...
Created new table employees._titles_new OK.
Altering new table...
Altered `employees`.`_titles_new` OK.
2015-11-20T16:09:49 Creating triggers...
2015-11-20T16:09:49 Created triggers OK.
2015-11-20T16:09:49 Copying approximately 1 rows...
2015-11-20T16:09:51 Copied rows OK.
2015-11-20T16:09:51 Swapping tables...
2015-11-20T16:09:51 Swapped original and new tables OK.
2015-11-20T16:09:51 Dropping old table...
2015-11-20T16:09:51 Dropped old table `employees`.`_titles_old` OK.
2015-11-20T16:09:51 Dropping triggers...
2015-11-20T16:09:51 Dropped triggers OK.
Successfully altered `employees`.`titles`.
   -> 2.3925s
-- add_index("titles", ["emp_no"], {:name=>"emp_no2", :using=>:btree})
Execute ./pt-osc.rb CREATE\ \ INDEX\ \`emp_no2\`\ USING\ btree\ ON\ \`titles\`\ \(\`emp_no\`\)\
./pt-osc.rb: pt-online-schema-change --alter ADD\ INDEX\ \`emp_no2\`\ \ USING\ btree\ \ \ \(\`emp_no\`\)\  --execute h\=localhost,u\=root,p\=root,D\=employees,t\=titles,A\=utf8
No slaves found.  See --recursion-method if host vagrant-ubuntu-trusty-64 has slaves.
Not checking slave lag because no slaves were found and --check-slave-lag was not specified.
Operation, tries, wait:
  copy_rows, 10, 0.25
  create_triggers, 10, 1
  drop_triggers, 10, 1
  swap_tables, 10, 1
  update_foreign_keys, 10, 1
Altering `employees`.`titles`...
Creating new table...
Created new table employees._titles_new OK.
Altering new table...
Altered `employees`.`_titles_new` OK.
2015-11-20T16:09:51 Creating triggers...
2015-11-20T16:09:51 Created triggers OK.
2015-11-20T16:09:51 Copying approximately 1 rows...
2015-11-20T16:09:54 Copied rows OK.
2015-11-20T16:09:54 Swapping tables...
2015-11-20T16:09:54 Swapped original and new tables OK.
2015-11-20T16:09:54 Dropping old table...
2015-11-20T16:09:54 Dropped old table `employees`.`_titles_old` OK.
2015-11-20T16:09:54 Dropping triggers...
2015-11-20T16:09:54 Dropped triggers OK.
Successfully altered `employees`.`titles`.
   -> 3.1088s

正常にDBの更新が行われました。再度、適用しても変更はありません。

$ ridgepole -c database.yml -a --external-script ./pt-osc.rb
Apply `Schemafile`
No change

その他

pt-online-schema-changeがSQLの間違いなどで失敗しても、終了コードが0になって胃るっぽいんですが、どうしたもんでしょうね…

CloudWatch Logsを見るやつ作った

LogsFlowというCloudWtach LogsのビューアWebアプリ作りました。

github.com

検索とかは全然なくて、だた見れるだけ。

URLが/groups/my-group/streams/my-streamsのようにREST的なので、適当なツールからぱっとログを見るのにはいいかも。

あと、/groups/my-group/streams/my-streams.jsonとか/groups.jsonとか開くと、JSONがふってきます。

f:id:winebarrel:20151120135403p:plain

ログ表示画面はTerminalっぽいかっちょいい画面にしたかったけど、参考になるcssとかが見つからず。残念。

どうぞご利用ください。

Barkdog v0.1.3

DatadogのアラートDSL管理ツール Barkdog v0.1.3をリリースしました。

github.com

Libratoで監視まわりを全部まかなおうとしてたとき、アラート管理にはテンプレート必須だなー、と思ったのでつけました。

以下のように、いろんな箇所でテンプレートつかえます。

template "cpu template" do
  query "avg(last_5m):avg:#{context.target}.load_avg.1m{host:i-XXXXXXXX} > 1"
  message context.message
  options do
    notify_no_data true
    no_data_timeframe 2
    notify_audit true
    silenced({})
  end
end

monitor "Check load avg", :type=>"metric alert" do
  context.message = "@winebarrel@example.net"
  include_template "cpu template", :target=>"ddstat"
end

template "basic monitor" do
  monitor "#{target} cpu" do
    query "avg(last_5m):avg:#{context.target}.load_avg.1m{host:i-XXXXXXXX} > 1"
    ...
  end

  # any other monitor
  monitor ...
end

"myhost".tap do |host|
  include_template "basic monitor", :target=>host
  include_template "mysql monitor", :target=>host
  ...
end

これで、ベーシックな監視(CPUとかI/Oとか)をテンプレート化して、すべてのホストにひもづけて、MySQLをインストールしているホストにはMySQLのテンプレートをひもづけて。。。とかがやりやすくなったと思います。

ところで、Datadogで監視やってる人はそのへんどうしているんだろう?すでにテンプレートの仕組みとかあるんだろうか?

どうぞご利用ください。

Miam v0.2.2.beta3

Miam v0.2.2.beta3をリリースしました。

github.com

主な変更点は以下の通り。

  • Improve update (show diff)
  • Support Template
  • Add --ignore-login-profile option
  • Sort policy array

Improve update (show diff)

いままでdry-runではアップデート後の値しか表示していなかったんですが、diffを表示するようにしました。

f:id:winebarrel:20151010004721p:plain

Support Template

テンプレート機能をサポートしました。

emplate "common-policy" do
  policy "my-policy" do
    {"Version"=>context.version,
     "Statement"=>
      [{"Action"=>
         ["s3:Get*",
          "s3:List*"],
        "Effect"=>"Allow",
        "Resource"=>"*"}]}
  end
end

user "bob", :path => "/developer/" do
  login_profile :password_reset_required=>true

  groups(
    "Admin"
  )

  include_template "common-policy", version: "2012-10-17"
end

Add --ignore-login-profile option

login profileのpassword_reset_requiredの変更を無視できるようになりました。

f:id:winebarrel:20151010005029p:plain

Sort policy array

各ポリシー内のArrayの順序が差分として出てしまうことがあったので、無視するようにしました。(問題はないはず)

どうぞご利用ください。

Roadworker v0.5.6.beta2

Roadworker v0.5.6.beta2をリリースしました。

github.com

Calculated Health Checks/Latency Checksのサポート

Calculated Health ChecksとLatency Checksをサポートしました。

Calculated Health Checksはこんな感じ。

rrset "zzz.info.winebarrel.jp", "A" do
  set_identifier "Secondary"
  failover "SECONDARY"
  health_check :calculated => ["07c03a45-5b69-4044-9ec3-016cd8e5f74b", "bba4d1ea-27c2-4d0c-a249-c857a3e46d88"], :health_threshold => 1, :inverted => false
  ttl 456
  resource_records(
    "127.0.0.3",
    "127.0.0.4"
  )
end

Latency Checksはこんな感じです。

rrset "zzz.info.winebarrel.jp", "A" do
    set_identifier "Primary"
    failover "PRIMARY"
    health_check "http://example.com:80/path", :search_string => "ANY_RESPONSE_STRING", :request_interval => 30, :failure_threshold => 3, :measure_latency => true
    ttl 456
    resource_records(
      "127.0.0.1",
      "127.0.0.2"
    )
  end

また、APIの変更に伴って、ヘルスチェック周りで意図しない変更が検出されるのは諸々直しました。

それから、ふる〜いI/Fとの互換性を保つため、以下のような書き方を許可していたのですが、やめました。

health_check "http://192.168.0.1:80/path", "example.com"

Health Check GCをデフォルトで無効に

Health Check GCという使われていないHealth Checkを自動的に削除する機能があり、デフォルトで有効になっていたのですが、無効にしました。 --health-check-gcオプションを渡すと、有効になります。Calculated Health Checksで参照されているHealth Checkは削除対象にはなりません。*1

テンプレートのサポート

以下のように、テンプレートを使えるようにしました。

template "default_rrset" do
  rrset context.name + "." + context.hosted_zone_name, "A" do
    ttl context.ttl
    resource_records(
      "127.0.0.1"
    )
  end
end

hosted_zone "winebarrel.jp." do
  context.ttl = 100
  include_template "default_rrset", :name => "www"
  include_template "default_rrset", :name => "www2"
end

テンプレートに渡す変数はcontextという特殊変数で受け渡せます。 また、include_templateの第二引数にHashを渡すことで、テンプレート内のcontextにHashの値をセットすることができます。 context変数のクラスはHashie::Mashなので、普通のハッシュのように値をセットすることもできます。

  context[:ttl] = 100

あと、include_templateはどのブロックでも使えるので

template "default_hz" do
  hosted_zone context.hz_name do
    rrset "www." + context.hosted_zone_name, "A" do
      ttl context.ttl
      resource_records(
        "127.0.0.1"
      )
    end
  end
end

include_template "default_hz", hz_name: "winebarrel.jp."
include_template "default_hz", hz_name: "winebarrel2.jp."

とかもできます。

その他

rrsetからHealth Checkを名前/IDで参照する機能は、今のところ実装してないんですが、そういうユースケースはあるんですかね…

*1:が、再帰的参照までは調べてません。できるのかな…

fluent-plugin-munin-nodeを作った

先日に引き続いて、fluent-plugin-munin-nodeを作りました。

github.com

これは何?

munin-nodeからmetricsを取得するためのfluentdのプラグインです。

実はすでに同様のプラグインあったりするんですが、いくつか不満があって改修量が多そうだったので、車輪を再実装しました。

fluent-plugin-muninとの主な違いは以下の通りです。

  • 1フィールド、1レコード(bulkモードもあります)
  • munin-nodeとのコネクションは都度接続
  • ホスト名は取得しない

使い方

<source>
  type munin_node
  extra {"hostname", "my-host"}
</source>
2015-91-02 12:30:09 +0000 munin.cpu.user: {"service":"cpu","field":"user","value":"4192","hostname":"my-host"}
2015-91-02 12:30:09 +0000 munin.cpu.nice: {"service":"cpu","field":"nice","value":"0","hostname":"my-host"}
2015-91-02 12:30:09 +0000 munin.cpu.system: {"service":"cpu","field":"system","value":"1935","hostname":"my-host"}

四方山

本当は、どこかに投入しようと考えていたのですが、残念ながら別のメトリクス収集方法を使うことになりそうな感じです。 ただ、シンプルなプラグインでテストも書いているので、production readyのつもりではあります。

いまさらmunin…という感じですが、munin-nodeはfluentd用のメトリクス収集ツールとしては優れていると思うので、使う機会はそれなりにありそうです。

どうぞご利用ください。

それはさておき

設定ファイルのテストだけのプラグイン、結構多くないですかね…

fluent-plugin-zabbix-agentを作った

fluent-plugin-zabbix-agentというfluentdのプラグインを作りました。

github.com

zabbixのpassive checkを使って、zabbix-agentから、各種メトリクスをfluentdに流すInputプラグインです。

使い方

以下のような設定ファイルを書くと、指定したitemをzabbix-agentから取得します。

<source>
  type zabbix_agent
  tag zabbix.item
  interval 60
  extra {"hostname", "my-host"}
  items {
    "system.cpu.load[all,avg1]": "load_avg1",
    "system.cpu.load[all,avg5]": null
  }
</source>
2015-01-02 12:30:40 +0000 zabbix.item: {"key":"load_avg1","value":0.0,"hostname":"my-host"}
2015-01-02 12:30:40 +0000 zabbix.item: {"key":"system.cpu.load[all,avg5]","value":0.01,"hostname":"my-host"}

itamsの指定はhashで、キーがzabbixのitem名、値がfluentdのレコードに書くときのキーです(nullの場合、item名をそのまま使う)。 itemsを外部のJSONファイルから読み込むとか、複数itemを一つのレコードにまとめるとかもできます。

ユースケース

ひとつはzabbixのitemをzabbixサーバだけではなくて、DataDogとかGrowthForecastにも流したい場合などです。 それらを併用している環境がどのくらいあるか分からないんですが…。

もうひとつは、zabbix-agentは使いたいけどzabbix-serverは使いたくないような場合です。

とある環境でメトリクス収集をfluentdでやろうとしてまして、fluent-plugin-dstatを導入しようとしてみたのですが、メトリクスの「収集」部分に関してはかなり手間な感じでした。fluentdのcron的pluginとかcronそのものでがんばるとかも考えたのですが、zabbix-agentに収集させるのが結構楽そうだったので、今回作った次第です。

TIME_WAIT問題

今のところ、ひとつ問題がありまして、1メトリクスの収集ごとにTCPの接続をzabbix-agentに対して行っているので、あんまりintervalを短くするとTIME_WAITが一杯になります。

zabbixのドキュメントやソースコードで、1セッションで複数のメトリクスを取得する方法や、つなぎっぱなしのコネクションを使い回す方法を探したのですが、現状では対応していないように見えました(active check使えってことですかね)。 個人的には60sぐらいならそれほど問題にならないかと考えています。


というわけで、どうぞご利用ください。