orenoblog

エンジニアになりたいExcel方眼紙erの物語

Galera Cluster に Asynchronous replicateion ノードを追加する

こんばんは。

Galera Clusterを構成しているCluster nodeにはMySQLレプリケーションノードが追加できます。

以下はパラメータの設定例ですが log_slave_updates は必ず有効にしておかないと、 SlaveノードでCHANGE MASTER TOを実行したノードからのトランザクションしかレプリケーションされません。

server_id=<unique>
binlog_format=ROW
log_bin=binlog
log_slave_updates=1
relay_log=relay-bin
expire_logs_days=8

さっきかなり痛い目にあった...

んでスレーブを追加する利点ですが(AWS的に)

  • 別AZへのバックアップが可能(GaleraのWANモードでもいいよねというのもあるけど...)
  • スレーブでクッソ重いクエリを思い思いに実行可能

MySQL/MariaDB 5.6/10.0系以上ならGTID使えるから破損してもレプリ元のノードを気にしなくてよくなるよ。という資料を見たのですが忘れてしまった...

Infrastructure as 脳筋のためのchef recipe tips. HAProxyの例

chef-zeroのC/S wrapper knife-zero使ってますか?

とても便利です。重宝しています。

higanworks/knife-zero · GitHub

個人的にchefの真髄であるsearchが利用できるため、chef-soloと違って関連nodeの情報をattributeに記述すること無くrecipeを記述でき、

管理の幅を広げてくれるツールなので是非使いましょうと思っている次第です(がまだ横展開しきれず)

今回はHAproxyを用いてGalera Cluster MariaDBへの接続を行うための定義を簡単に書くためのtipsを残したいと思います。

EnvironmentとRoleを使い、HAProxyのロードバランス対象となるノードをnode情報から自動判別してHAProxyの設定を生成します。

chef repositoryで利用するEnvironmentとRole

  • Environment
chef_environment 意味
production 本番環境
  • Role
role 意味
galera_master Galera Cluster初期ノード
galera_member Galera Clusterメンバーノード

DBサーバとなるノードにはrole[galera_master]またはrole{galera_member]を割り当ててknife zeroを実行しておきます。(実行しておかないとnode.jsonに対象ノードの情報が集約されない)

recipe

  • haproxy/recipes/default.rb

Galera Clusterを構成するノードの情報をsearchで取得してtemplate variablesに渡します

if node.chef_environment.include?("production")
  cookbook_file "/root/haproxy-1.5.9-2.amzn1.x86_64.rpm" do
    source "haproxy-1.5.9-2.amzn1.x86_64.rpm"
    owner "root"
    group "root"
    mode 0644
  end
  
  package "haproxy" do
    action :install
    provider Chef::Provider::Package::Rpm
    source "/root/haproxy-1.5.9-2.amzn1.x86_64.rpm"
  end
  
  template "/etc/haproxy/haproxy.cfg" do
    source "haproxy.cfg.erb"
    owner "root"
    group "root"
    mode 0644
    notifies :restart, "service[haproxy]"
    variables(
      :galerahosts => search(:node,"roles:galera* AND chef_environment:#{node.chef_environment}")
    )
  end

  service "haproxy" do
    action :nothing
  end
end 
  • haproxy/template/haproxy.cfg.erb(一部抜粋)

template variablesから渡された情報をeachでぶん回します。

listen  haproxy-galera
        bind *:33306
        mode tcp
        timeout client  28800s
        timeout server  28800s
        balance roundrobin
        option httpchk
        option allbackups
        <% @galerahosts.each do |host|  %>
        server <%= host.ipaddress %> <%= host.ipaddress %>:3306 check
        <% end %> 

apply後

このようにHAProxyを導入するサーバにrecipeを適用します(chef-repo毎にbundler使ってるのでbundle execかいてます)

bundle exec knife zero chef_client search "role:app AND chef_environment production" -x ec2-user --sudo --attribute name

実行後は対象ノードに以下のようなhaproxy.cfgが作成されます。

..省略..
listen  haproxy-galera
        bind *:33306
        mode tcp
        timeout client  28800s
        timeout server  28800s
        balance roundrobin
        option httpchk
        option allbackups
        server 192.168.1.100 192.168.1.100:3306 check
        server 192.168.1.101 192.168.1.101:3306 check
        server 192.168.1.102 192.168.1.102:3306 check

attributeを使用せずroleとchef_environmentの検索でロードバランス対象ノードのIPアドレスを設定することが出来ました。

search最高。

ちなみに自分はknife zeroのコマンドラインを実行するのが面倒なので 簡単なRakefileを作成して、search条件だけ渡してknife zeroを実行しています。

適用するroleやenvironment毎にtaskを作成したらもっと簡単にかつsearchのミス無く適用することができると思います。

require 'rake'

desc "run, bundle exec knife <type> list"
task :list, [:type] do |t,args|
  command = %(bundle exec knife #{args[:type]} #{t} )
  puts command
  Bundler.clean_system command
end

desc "run, bundle exec knife search <hint>"
task :search, [:hint] do |t, args|
  command = %(bundle exec knife #{t} "#{args[:hint]}")
  puts command
  Bundler.clean_system command
end

knife_args = "-x ec2-user --sudo --attribute name"

desc "run, bundle exec knife zero chef_client --why-run"
task "dry-run", [:hint] do |t,args|
  command = %(bundle exec berks vendor cookbooks;bundle exec knife zero chef_client "#{args[:hint]}" #{knife_args} --why-run)
  puts command
  Bundler.clean_system command
end

desc "run, bundle exec knife zero chef_client."
task :apply, [:hint] do |t,args|
  command = %(bundle exec berks vendor cookbooks;bundle exec knife zero chef_client "#{args[:hint]}" #{knife_args})
  puts command
  Bundler.clean_system command
end
  • Rake実行例

why-run

bundle exec rake "dry-run[(roles:galera* AND chef_environment:production)]"

apply

bundle exec rake "apply[(roles:galera* AND chef_environment:production)]"

Rake saikou.

Infrastructure as 脳筋のためのterraform tips

terraform始めました。

使っていてこれはどーすりゃよいのかしらと思っていたことが実装できたのでメモを残します。

(1)AWS Security Groupを用いて内部通信ツーツーの設定を作りたい

Security GroupにAll trafficを許可するInboundルールを書くことがあると思います。

Management Consoleから作成したAll Trafficを選択してセキュリティグループのIDを登録するだけで良いです。

ではterraformで実装する時はaws_security_groupリソースのingressにselfを指定すると自身のセキュリティグループのIDを登録できますが、

All trafficはどう指定したらよいのかとドキュメント読んでも見当たらなかったのでソースを眺めた所

protocolがstring型で定義されていたぐらいなので、コードの中で指定可能な値を定義しているところはありませんでした。

ということはprotocolで指定した値をそのままAWSAPIサーバへPOSTしているだけであれば

CloudformationドキュメントIpProtocolに指定可能なパラメータは指定できると思い、

下記の様なresourceを記述してterraform applyを実行すると上手く出来ました。

resource "aws_security_group" "common" {
  name = "common-sg"
  description = "common security group"
  vpc_id = "${aws_vpc.hogehoge.id}"
  ingress {
    from_port = 0
    to_port = 65535
    protocol = "-1"
    self = true
  }
}

(2) 起動するinstance数を指定したい

同一のロールをもつインスタンス複数台あるとき(たとえばwwwとか) resource "aws_instance"を並べるのは苦行以外の何物でもありません

ドキュメントには記載されていませんがcountが指定できます。

exampleはここ

resource "aws_instance" "hogehoge" {
    ami = "ami-1234"
    instance_type = "t2.micro"
    count = 10
    tags {
        Name = "hogehoge"
    }
}

(3) terraform apply実行時にThrottlingエラー

ひたすらAPIサーバへPOSTしている模様なのでresourceが多いと頻発する。 terraform applyをリトライしたらよい

Error applying plan:

1 error(s) occurred:

* Error retrieving ELB: Throttling: Rate exceeded

Terraform does not automatically rollback in the face of errors.
Instead, your Terraform state file has been partially updated with
any resources that successfully completed. Please address the error
above and apply again to incrementally change your infrastructure.

HAProxy経由でGalera Cluster MariaDBに接続する

oreですこんばんは。

アプリケーションサーバからGalera Clusterへの接続をどうやって行うか考えており、HAProxyで実装してみました。

以下動作確認結果です-

環境

  • OS

Amazon Linux 2014.09

  • ミドル
ミドル バージョン
ロードバランサ HAProxy 1.5.9
データベース MariaDB-Galera-server-5.5.40-1.el6.x86_64
レプリケーションライブラリ galera-25.3.5-1.rhel6.x86_64
  • サーバ一覧
サーバ IP
HAProxy 10.0.1.50
Galera Cluster#1 10.0.0.187
Galera Cluster#2 10.0.0.97
Galera Cluster#3 10.0.0.246

HAProxy 1.5.9のビルド

Amazon Linuxが標準で提供しているHAProxyは1.4.22だったのでHAProxy 1.5.9をrpmbuildしました。

mockを利用してhaproxyのrpmを作成するスクリプトを公開している方がいたので、こちらを参考にrpmbuildでrpmを作成しました。

boogieshafer/haproxy-rpm · GitHub

※このスクリプトをそのまま使えたらよかったのですがmockが上手く動かず断念.

  • build-haproxy失敗ログ
[ec2-user@ip-10-0-1-50 haproxy-rpm]$ bash build-haproxy.1.5.x.sh
Downloading sources...
Building initial source rpm...
書き込み完了: /usr/src/rpm/SRPMS/haproxy-1.5.9-2.src.rpm
Using mock to build rpm...
INFO: mock.py version 1.1.41 starting...
Start: init plugins
INFO: selinux disabled
Finish: init plugins
Start: run
INFO: Start(/usr/src/rpm/SRPMS/haproxy-1.5.9-2.src.rpm)  Config(amzn-x86_64)
Start: lock buildroot
Start: clean chroot
INFO: chroot (/var/lib/mock/amzn-x86_64) unlocked and deleted
Finish: clean chroot
Finish: lock buildroot
Start: chroot init
Start: lock buildroot
Mock Version: 1.1.41
INFO: Mock Version: 1.1.41
INFO: calling preinit hooks
INFO: enabled root cache
INFO: enabled yum cache
Start: cleaning yum metadata
Finish: cleaning yum metadata
INFO: enabled ccache
Start: device setup
Finish: device setup
Start: yum update
ERROR: Exception(/usr/src/rpm/SRPMS/haproxy-1.5.9-2.src.rpm) Config(amzn-x86_64) 0 minutes 0 seconds
INFO: Results and/or logs in: /var/lib/mock/amzn-x86_64/result
ERROR: Command failed:
 # ['/usr/bin/yum', '--installroot', '/var/lib/mock/amzn-x86_64/root/', 'install', '@buildsys-build']
Configuration file /var/lib/mock/amzn-x86_64/root/etc/yum/pluginconf.d/update-motd.conf not found
Unable to find configuration file for plugin update-motd
Configuration file /var/lib/mock/amzn-x86_64/root/etc/yum/pluginconf.d/upgrade-helper.conf not found
Unable to find configuration file for plugin upgrade-helper
Group buildsys-build does not exist.
Error: Nothing to do

Cleaning up...

HAProxyのインストール

$ cd /usr/src/rpm/RPMS/x86_64
$ sudo yum localinstall haproxy-1.5.9-2.amzn1.x86_64.rpm

HAProxyの設定

とりあえずほぼデフォルトで。

  • /etc/haproxy.cfg
global
        log 127.0.0.1   local0
        log 127.0.0.1   local1 notice
        #log loghost    local0 info
        maxconn 1024
        chroot /var/lib/haproxy
        user haproxy
        group haproxy
        daemon

defaults
        log     global
        mode    http
        option  httplog
        option  dontlognull
        retries 3
        redispatch
        maxconn 2000
        contimeout      5000
        clitimeout      50000
        srvtimeout      50000

listen galera_clister 0.0.0.0:3306
        mode tcp
        balance roundrobin
        option tcpka
        option mysql-check user haproxy
        server galera01 10.0.0.187:3306 check weight 1
        server galera02 10.0.0.97:3306 check weight 1
        server galera03 10.0.0.246:3306 check weight 1

DBにcheck用ユーザの作成

ヘルスチェック用のユーザをデータベース上に作成します。適当なGalera Clusterノードでmysql-checkを実行するユーザを作成します。

GRANT USAGE ON *.* TO haproxy@'%'
FLUSH PRIVILEGES;

接続テスト

HAProxyサーバから行ないます。ラウンドロビンで接続できています。

[root@ip-10-0-1-50 haproxy]# mysql -uroot -proot -h127.0.0.1 -e "show variables like 'wsrep_node_name' ;"
+-----------------+------------+
| Variable_name   | Value      |
+-----------------+------------+
| wsrep_node_name | 10.0.0.187 |
+-----------------+------------+
[root@ip-10-0-1-50 haproxy]# mysql -uroot -proot -h127.0.0.1 -e "show variables like 'wsrep_node_name' ;"
+-----------------+-----------+
| Variable_name   | Value     |
+-----------------+-----------+
| wsrep_node_name | 10.0.0.97 |
+-----------------+-----------+
[root@ip-10-0-1-50 haproxy]# mysql -uroot -proot -h127.0.0.1 -e "show variables like 'wsrep_node_name' ;"
+-----------------+------------+
| Variable_name   | Value      |
+-----------------+------------+
| wsrep_node_name | 10.0.0.246 |
+-----------------+------------+

とりあえず負荷分散するだけ!なら簡単でした。

次はノード障害テストやらないと。

その他

Galera Load Balancerというのもあるみたいです。気が向いたら試そうかな...

codership/glb · GitHub

Galera Cluster MariaDBエラーtips

oreが遭遇したエラーtipsまとめ

(1) Percona Xtrabackupインストールしてないのにwsrep_sst_method=xtrabackup奴wwww

Galera Clusterの検証環境をつくろうと思って、既にある環境からmy.cnfを丸コピしてきたものを使って、

初期ノード起動後、2台目以降のGalera Clusterを起動しようとした時に発生したエラー

  • エラー内容

ログファイルより抜粋

141230 21:41:42 [Note] WSREP: Running: 'wsrep_sst_xtrabackup --role 'joiner' --address '10.0.0.97' --auth 'root' --datadir '/var/lib/mysql/' --defaults-file '/etc/my.cnf' --parent '4906''
which: no innobackupex in (/usr/sbin:/sbin:/usr//bin:/sbin:/usr/sbin:/bin:/usr/bin:/usr/bin)
WSREP_SST: [ERROR] innobackupex not in path: /usr/sbin:/sbin:/usr//bin:/sbin:/usr/sbin:/bin:/usr/bin:/usr/bin (20141230 21:41:42.953)
141230 21:41:42 [ERROR] WSREP: Failed to read 'ready <addr>' from: wsrep_sst_xtrabackup --role 'joiner' --address '10.0.0.97' --auth 'root' --datadir '/var/lib/mysql/' --defaults-file '/etc/my.cnf' --parent '4906'
        Read: '(null)'
141230 21:41:42 [ERROR] WSREP: Process completed with error: wsrep_sst_xtrabackup --role 'joiner' --address '10.0.0.97' --auth 'root' --datadir '/var/lib/mysql/' --defaults-file '/etc/my.cnf' --parent '4906': 2 (No such file or directory)
141230 21:41:42 [ERROR] WSREP: Failed to prepare for 'xtrabackup' SST. Unrecoverable.
141230 21:41:42 [ERROR] Aborting
  • 原因

wsrep_sst_method=xtrabackup だったため

wsrep_sst_methodはGalera Cluster起動時にマスタデータベース・サーバから同期する手段を指定するパラメータで、デフォルトがrsyncなのですが既にあった環境にはxtrabackupを指定していました。

つくりたてほやほやぁのサーバにはPercona XtrabackupをインストールしていなかったためGalera Cluster起動時に同期ができず失敗した模様です。

  • Percona XtraBackupのインストール
$ yum install http://www.percona.com/downloads/percona-release/redhat/0.1-3/percona-release-0.1-3.noarch.rpm
$ yum install xtrabackup
  • Galera Clusterの起動

Galera Cluster

$ service mysql start

また wsrep_sst_method=xtrabackup としたときは wsrep_sst_auth=<認証ユーザ>:<password> も設定しておくことを忘れずに。

(2) Clusterジョイン後に、参加したノードのシーケンス番号がClusterよりも高いため同期に失敗して起動しない

  • ログ抜粋
150108  0:45:22 [ERROR] WSREP: Local state seqno (12) is greater than group seqno (10): states diverged. Aborting to avoid potential data loss. Remove '/storage/data/mysql//grastate.dat' file and restart if you wish to continue. (FATAL)
         at galera/src/replicator_str.cpp:state_transfer_required():33
  • 原因

まだ調べてない..

  • 対応方法

起動にfailしたgrastate.datを削除する.

  • ログ詳細
150108  0:45:22 [Note] WSREP: STATE EXCHANGE: sent state msg: a097fc2f-96cf-11e4-9c92-e32d0822faad
150108  0:45:22 [Note] WSREP: STATE EXCHANGE: got state msg: a097fc2f-96cf-11e4-9c92-e32d0822faad from 0 (10.0.0.53)
150108  0:45:22 [Note] WSREP: STATE EXCHANGE: got state msg: a097fc2f-96cf-11e4-9c92-e32d0822faad from 2 (10.0.0.31)
150108  0:45:22 [Note] WSREP: STATE EXCHANGE: got state msg: a097fc2f-96cf-11e4-9c92-e32d0822faad from 1 (10.0.0.120)
150108  0:45:22 [Note] WSREP: Quorum results:
        version    = 3,
        component  = PRIMARY,
        conf_id    = 14,
        members    = 2/3 (joined/total),
        act_id     = 10,
        last_appl. = -1,
        protocols  = 0/5/3 (gcs/repl/appl),
        group UUID = 2a245897-968f-11e4-bc67-13c84f4a5ebb
150108  0:45:22 [Note] WSREP: Flow-control interval: [28, 28]
150108  0:45:22 [Note] WSREP: Shifting OPEN -> PRIMARY (TO: 10)
150108  0:45:22 [Note] WSREP: Closing send monitor...
150108  0:45:22 [Note] WSREP: Closed send monitor.
150108  0:45:22 [Note] WSREP: gcomm: terminating thread
150108  0:45:22 [Note] WSREP: gcomm: joining thread
150108  0:45:22 [Note] WSREP: gcomm: closing backend
150108  0:45:22 [Note] WSREP: view(view_id(NON_PRIM,213ef9d9-96ce-11e4-b33f-0756a3a1e93e,15) memb {
        a05cd71b-96cf-11e4-98d8-561067f120d0,0
} joined {
} left {
} partitioned {
        213ef9d9-96ce-11e4-b33f-0756a3a1e93e,0
        f6e4b3c1-96cd-11e4-ab41-5f43f8aae952,0
})
150108  0:45:22 [Note] WSREP: view((empty))
150108  0:45:22 [Note] WSREP: gcomm: closed
150108  0:45:22 [Note] WSREP: New COMPONENT: primary = no, bootstrap = no, my_idx = 0, memb_num = 1
150108  0:45:22 [Note] WSREP: Flow-control interval: [16, 16]
150108  0:45:22 [Note] WSREP: Received NON-PRIMARY.
150108  0:45:22 [Note] WSREP: Shifting PRIMARY -> OPEN (TO: 10)
150108  0:45:22 [Note] WSREP: Received self-leave message.
150108  0:45:22 [Note] WSREP: Flow-control interval: [0, 0]
150108  0:45:22 [Note] WSREP: Received SELF-LEAVE. Closing connection.
150108  0:45:22 [Note] WSREP: Shifting OPEN -> CLOSED (TO: 10)
150108  0:45:22 [Note] WSREP: RECV thread exiting 0: Success
150108  0:45:22 [Note] WSREP: recv_thread() joined.
150108  0:45:22 [Note] WSREP: Closing replication queue.
150108  0:45:22 [Note] WSREP: Closing slave action queue.
☆☆150108  0:45:22 [ERROR] WSREP: Local state seqno (12) is greater than group seqno (10): states diverged. Aborting to avoid potential data loss. Remove '/storage/data/mysql//grastate.dat' file and restart if you wish to continue. (FATAL)
         at galera/src/replicator_str.cpp:state_transfer_required():33
150108  0:45:22 [Note] WSREP: applier thread exiting (code:8)
150108  0:45:22 [ERROR] Aborting

150108  0:45:24 [Note] WSREP: Closing send monitor...
150108  0:45:24 [Note] WSREP: Closed send monitor.
150108  0:45:24 [Note] WSREP: Service disconnected.
150108  0:45:24 [Note] WSREP: rollbacker thread exiting
150108  0:45:25 [Note] WSREP: Some threads may fail to exit.
150108  0:45:25 [Note] /usr/sbin/mysqld: Shutdown complete

Error in my_thread_global_end(): 1 threads didn't exit
150108 00:45:30 mysqld_safe mysqld from pid file /storage/data/mysql//ip-10-0-0-120.pid ended
150108 00:46:50 mysqld_safe Starting mysqld daemon with databases from /storage/data/mysql/
150108 00:46:50 mysqld_safe WSREP: Running position recovery with --log_error='/storage/data/mysql//wsrep_recovery.uw41rR' --pid-file='/storage/data/mysql//ip-10-0-0-120-recover.pid'
150108 00:46:53 mysqld_safe WSREP: Recovered position 2a245897-968f-11e4-bc67-13c84f4a5ebb:12

(3) wsrep_sst_method=xtrabackup指定していて2台目以降のGalera Clusterを起動すると[[Warning] WSREP: Gap in state sequence. Need state transfer.]がログに出力されてClusterが起動しない

現状出くわしたケースとしてgalera cluster -> replication slaveを構成しているDBで replication slaveで実行したinnobackupexのbackup fileをリストアすると2台目以降のmysql起動時に発生する。

力技だけど2台目以降のmy.cnfに wsrep_sst_method=rsync を指定して起動する。

起動完了後はwsrep_sst_method=xtrabackupに戻してmysqlを再起動したらよい。

なーんでこれがでるかなあ。わかってない件

EBSスナップショットでMySQL用データディスクをバックアップ

突貫版を直したもの。

EBSスナップショットでMySQLデータディスクをバックアップします。

スレーブDBで実行することを想定してます。

jq必要です(入ってないならインストールするを追加しておこう..あとで間違いなく忘れる)

service mysqld stop してるけどFLUSH TABLES WITH READ LOCKでもよいかも。

毎日cronやjenkinsおじさんで動くものと想定して、取得後に前日分のSnapshot/Volumeを削除してます。 世代残すなら2 days agoのほうがいいね。

シェルスクリプト変数スコープ無しって脳筋にとって最高。

某コピペみたいに小さな音がすべてに響くみたいな。

だめだけど。いいんです。