深淵の闇から逃れる為のMariaDB Galera Cluster 5.5 入門(2 node編)

昔つかってたHatena Blog より転載

MariaDB Galera Cluster 5.5を試す

oujiにMySQLの深淵の闇(マスタ昇格とかマスタ昇格とか)から逃れたいと相談したところ MariaDB Galera Clusterが良いと教えてもらったのでやっとこさ試した記録を残します。

RDS最高なのですが、選定できないと思われるケースもあるのでこっちが利用できたらいいな。

MariaDB Galera Clusterって何

デュアルマスタ構成が可能なデータベースクラスタらしいです。最高。

今回はMySQL 5.5からMariaDBへの移行を考え MariaDB Galera Clusterは5.5で試しています。

MariaDB 5.5はMySQL 5.5と互換性があるので、アプリケーションやデータベースを変更せずmysqldumpで移行できると思います。

詳しい資料は

こことか

日本語で詳しいのはさくらインターネット研究所さんの記事だと思います。

資料を熟読してないのですが今回はインストールとMaster破損時の挙動確認をしてみたいとおもいます。

Galera Cluster 5.5 ServerとClientのインストール

今回はEC2(t2.micro)*2をVagrantで準備し、それぞれの内部IPを以下にして検証しています。OSはAmazon Linuxです。

役割 IPアドレス
Master 10.0.0.245
Secondary 10.0.0.67

参考資料

  • リポジトリの準備

Amazon Linux用のリポジトリは提供されていませんが、動RHEL向けのリポジトリを使いました

$ sudo cat /etc/yum.repos.d/mariadb.repo << EOF
# MariaDB 5.5 RedHat repository list - created 2014-09-20 02:09 UTC
# http://mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/5.5/rhel6-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1
EOF
  • Galera Clusterのインストール
$ sudo yum install --enablerepo=mariadb MariaDB-Galera-server galera MariaDB-Client

Galera Clusterの事前準備

  • Master Nodeの設定
$ sudo vi /etc/my.cnf.d/server.cnf
[galera]
# Mandatory settings
wsrep_provider=/usr/lib64/galera/libgalera_smm.so
wsrep_cluster_address=gcomm://
wsrep_node_address=10.0.0.245
wsrep_slave_threads=1
binlog_format=row
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
query_cache_size=0
  • Secondary Nodeの設定
$ sudo vi /etc/my.cnf.d/server.cnf
[galera]
# Mandatory settings
wsrep_provider=/usr/lib64/galera/libgalera_smm.so
wsrep_cluster_address=gcomm://10.0.0.245
wsrep_node_address=10.0.0.67
wsrep_slave_threads=1
binlog_format=row
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
query_cache_size=0

mysqldの起動

Master Node

  • Master Nodeのmysqld起動
$ sudo service mysql start
  • サンプルデータベースの作成
$ mysql -u root -e "create database orenomaria"
$ mysql -u root -e "show databases"
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| orenomaria         |
| performance_schema |
| test               |
+--------------------+
$ mysql -u root -e "create table orenomaria.test( col varchar(10))"
$ mysql -u root -e "insert into orenomaria.test values('galera')"
$ mysql -u root -e "select * from orenomaria.test"
+--------+
| col    |
+--------+
| galera |
+--------+

Secondary Node

  • Secondary Nodeのmysqld起動

Master Nodeが起動済みでないと同期できず起動しない

$ sudo service mysql start
Starting MySQL.....SST in progress, setting sleep higher. SUCCESS!``
  • 同期確認
$ sudo mysql -u root -e "show databases"
$ mysql -u root -e "select * from orenomaria.test"
+--------+
| col    |
+--------+
| galera |
+--------+

Secondary Node 起動後の確認

Master Node

  • wsrep系パラメータの確認
$ mysql -u root -e "show status like  'wsrep_%'"
+------------------------------+--------------------------------------+
| Variable_name                | Value                                |
+------------------------------+--------------------------------------+
| wsrep_local_state_uuid       | 3f056461-4076-11e4-a2b8-6216b1337d22 |
| wsrep_protocol_version       | 5                                    |
| wsrep_last_committed         | 0                                    |
| wsrep_replicated             | 0                                    |
| wsrep_replicated_bytes       | 0                                    |
| wsrep_repl_keys              | 0                                    |
| wsrep_repl_keys_bytes        | 0                                    |
| wsrep_repl_data_bytes        | 0                                    |
| wsrep_repl_other_bytes       | 0                                    |
| wsrep_received               | 23                                   |
| wsrep_received_bytes         | 1821                                 |
| wsrep_local_commits          | 0                                    |
| wsrep_local_cert_failures    | 0                                    |
| wsrep_local_replays          | 0                                    |
| wsrep_local_send_queue       | 0                                    |
| wsrep_local_send_queue_avg   | 0.000000                             |
| wsrep_local_recv_queue       | 0                                    |
| wsrep_local_recv_queue_avg   | 0.043478                             |
| wsrep_local_cached_downto    | 18446744073709551615                 |
| wsrep_flow_control_paused_ns | 0                                    |
| wsrep_flow_control_paused    | 0.000000                             |
| wsrep_flow_control_sent      | 0                                    |
| wsrep_flow_control_recv      | 0                                    |
| wsrep_cert_deps_distance     | 0.000000                             |
| wsrep_apply_oooe             | 0.000000                             |
| wsrep_apply_oool             | 0.000000                             |
| wsrep_apply_window           | 0.000000                             |
| wsrep_commit_oooe            | 0.000000                             |
| wsrep_commit_oool            | 0.000000                             |
| wsrep_commit_window          | 0.000000                             |
| wsrep_local_state            | 4                                    |
| wsrep_local_state_comment    | Synced                               |
| wsrep_cert_index_size        | 0                                    |
| wsrep_causal_reads           | 0                                    |
| wsrep_cert_interval          | 0.000000                             |
| wsrep_incoming_addresses     | 10.0.0.245:3306,10.0.0.67:3306       |
| wsrep_cluster_conf_id        | 10                                   |
| wsrep_cluster_size           | 2                                    |
| wsrep_cluster_state_uuid     | 3f056461-4076-11e4-a2b8-6216b1337d22 |
| wsrep_cluster_status         | Primary                              |
| wsrep_connected              | ON                                   |
| wsrep_local_bf_aborts        | 0                                    |
| wsrep_local_index            | 0                                    |
| wsrep_provider_name          | Galera                               |
| wsrep_provider_vendor        | Codership Oy <info@codership.com>    |
| wsrep_provider_version       | 25.3.5(rXXXX)                        |
| wsrep_ready                  | ON                                   |
| wsrep_thread_count           | 2                                    |
+------------------------------+--------------------------------------+
  • Master Node に Anther Node が接続される時のログ

Cluster UUID [3f056461-4076-11e4-a2b8-6216b1337d22]に新しいメンバーがJoinしてwsrep_sst_rsyncが走っている

$ cat /dev/null > `hostname`.err
140920 12:47:18 [Note] WSREP: declaring d193920e-4078-11e4-9422-86a0381f90eb stable
140920 12:47:18 [Note] WSREP: Node 3f04f808-4076-11e4-a830-1bcc73dcf244 state prim
140920 12:47:18 [Note] WSREP: view(view_id(PRIM,3f04f808-4076-11e4-a830-1bcc73dcf244,10) memb {
        3f04f808-4076-11e4-a830-1bcc73dcf244,0
        d193920e-4078-11e4-9422-86a0381f90eb,0
} joined {
} left {
} partitioned {
})
140920 12:47:18 [Note] WSREP: New COMPONENT: primary = yes, bootstrap = no, my_idx = 0, memb_num = 2
140920 12:47:18 [Note] WSREP: STATE_EXCHANGE: sent state UUID: d1e0b305-4078-11e4-a8ed-16954c252dca
140920 12:47:18 [Note] WSREP: STATE EXCHANGE: sent state msg: d1e0b305-4078-11e4-a8ed-16954c252dca
140920 12:47:18 [Note] WSREP: STATE EXCHANGE: got state msg: d1e0b305-4078-11e4-a8ed-16954c252dca from 0 ()
140920 12:47:19 [Note] WSREP: STATE EXCHANGE: got state msg: d1e0b305-4078-11e4-a8ed-16954c252dca from 1 ()
140920 12:47:19 [Note] WSREP: Quorum results:
        version    = 3,
        component  = PRIMARY,
        conf_id    = 9,
        members    = 1/2 (joined/total),
        act_id     = 0,
        last_appl. = 0,
        protocols  = 0/5/3 (gcs/repl/appl),
        group UUID = 3f056461-4076-11e4-a2b8-6216b1337d22
140920 12:47:19 [Note] WSREP: Flow-control interval: [23, 23]
140920 12:47:19 [Note] WSREP: New cluster view: global state: 3f056461-4076-11e4-a2b8-6216b1337d22:0, view# 10: Primary, number of nodes: 2, my index: 0, protocol version 3
140920 12:47:19 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
140920 12:47:19 [Note] WSREP: REPL Protocols: 5 (3, 1)
140920 12:47:19 [Note] WSREP: Service thread queue flushed.
140920 12:47:19 [Note] WSREP: Assign initial position for certification: 0, protocol version: 3
140920 12:47:19 [Note] WSREP: Service thread queue flushed.
140920 12:47:21 [Note] WSREP: Member 1.0 () requested state transfer from '*any*'. Selected 0.0 ()(SYNCED) as donor.
140920 12:47:21 [Note] WSREP: Shifting SYNCED -> DONOR/DESYNCED (TO: 0)
140920 12:47:21 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
140920 12:47:21 [Note] WSREP: Running: 'wsrep_sst_rsync --role 'donor' --address '10.0.0.67:4444/rsync_sst' --auth '(null)' --socket '/var/lib/mysql/mysql.sock' --datadir '/var/lib/mysql/' --defaults-file '/etc/my.cnf' --gtid '3f056461-4076-11e4-a2b8-6216b1337d22:0''
140920 12:47:21 [Note] WSREP: sst_donor_thread signaled with 0
140920 12:47:21 [Note] WSREP: Flushing tables for SST...
140920 12:47:21 [Note] WSREP: Provider paused at 3f056461-4076-11e4-a2b8-6216b1337d22:0 (25)
140920 12:47:21 [Note] WSREP: Tables flushed.
140920 12:47:22 [Note] WSREP: resuming provider at 25
140920 12:47:22 [Note] WSREP: Provider resumed.
140920 12:47:22 [Note] WSREP: 0.0 (): State transfer to 1.0 () complete.
140920 12:47:22 [Note] WSREP: Shifting DONOR/DESYNCED -> JOINED (TO: 0)
140920 12:47:22 [Note] WSREP: Member 0.0 () synced with group.
140920 12:47:22 [Note] WSREP: Shifting JOINED -> SYNCED (TO: 0)
140920 12:47:22 [Note] WSREP: Synchronized with group, ready for connections
140920 12:47:22 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
140920 12:47:25 [Note] WSREP: 1.0 (): State transfer from 0.0 () complete.
140920 12:47:25 [Note] WSREP: Member 1.0 () synced with group.

Secondary Node

  • Secondary Node起動後のwsrep系パラメータ
+------------------------------+--------------------------------------+
| Variable_name                | Value                                |
+------------------------------+--------------------------------------+
| wsrep_local_state_uuid       | 3f056461-4076-11e4-a2b8-6216b1337d22 |
| wsrep_protocol_version       | 5                                    |
| wsrep_last_committed         | 0                                    |
| wsrep_replicated             | 0                                    |
| wsrep_replicated_bytes       | 0                                    |
| wsrep_repl_keys              | 0                                    |
| wsrep_repl_keys_bytes        | 0                                    |
| wsrep_repl_data_bytes        | 0                                    |
| wsrep_repl_other_bytes       | 0                                    |
| wsrep_received               | 3                                    |
| wsrep_received_bytes         | 203                                  |
| wsrep_local_commits          | 0                                    |
| wsrep_local_cert_failures    | 0                                    |
| wsrep_local_replays          | 0                                    |
| wsrep_local_send_queue       | 0                                    |
| wsrep_local_send_queue_avg   | 0.000000                             |
| wsrep_local_recv_queue       | 0                                    |
| wsrep_local_recv_queue_avg   | 0.000000                             |
| wsrep_local_cached_downto    | 18446744073709551615                 |
| wsrep_flow_control_paused_ns | 0                                    |
| wsrep_flow_control_paused    | 0.000000                             |
| wsrep_flow_control_sent      | 0                                    |
| wsrep_flow_control_recv      | 0                                    |
| wsrep_cert_deps_distance     | 0.000000                             |
| wsrep_apply_oooe             | 0.000000                             |
| wsrep_apply_oool             | 0.000000                             |
| wsrep_apply_window           | 0.000000                             |
| wsrep_commit_oooe            | 0.000000                             |
| wsrep_commit_oool            | 0.000000                             |
| wsrep_commit_window          | 0.000000                             |
| wsrep_local_state            | 4                                    |
| wsrep_local_state_comment    | Synced                               |
| wsrep_cert_index_size        | 0                                    |
| wsrep_causal_reads           | 0                                    |
| wsrep_cert_interval          | 0.000000                             |
| wsrep_incoming_addresses     | 10.0.0.245:3306,10.0.0.67:3306       |
| wsrep_cluster_conf_id        | 10                                   |
| wsrep_cluster_size           | 2                                    |
| wsrep_cluster_state_uuid     | 3f056461-4076-11e4-a2b8-6216b1337d22 |
| wsrep_cluster_status         | Primary                              |
| wsrep_connected              | ON                                   |
| wsrep_local_bf_aborts        | 0                                    |
| wsrep_local_index            | 1                                    |
| wsrep_provider_name          | Galera                               |
| wsrep_provider_vendor        | Codership Oy <info@codership.com>    |
| wsrep_provider_version       | 25.3.5(rXXXX)                        |
| wsrep_ready                  | ON                                   |
| wsrep_thread_count           | 2                                    |
+------------------------------+--------------------------------------+
  • Secondary Node起動後のログ

wsrepによりMaster Nodeからデータを同期した後(今回の同期方式はデフォルトのwsrep_sst_rsync)

mysqldが起動している

$ cat /var/lib/mysql/`hostname`.err
140920 12:47:15 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql
140920 12:47:15 mysqld_safe WSREP: Running position recovery with --log_error='/var/lib/mysql/wsrep_recovery.pkbACS' --pid-file='/var/lib/mysql/ip-10-0-0-67-recover.pid'
140920 12:47:18 mysqld_safe WSREP: Recovered position 00000000-0000-0000-0000-000000000000:-1
140920 12:47:18 [Note] WSREP: wsrep_start_position var submitted: '00000000-0000-0000-0000-000000000000:-1'
140920 12:47:18 [Note] WSREP: Read nil XID from storage engines, skipping position init
140920 12:47:18 [Note] WSREP: wsrep_load(): loading provider library '/usr/lib64/galera/libgalera_smm.so'
140920 12:47:18 [Note] WSREP: wsrep_load(): Galera 25.3.5(rXXXX) by Codership Oy <info@codership.com> loaded successfully.
140920 12:47:18 [Note] WSREP: CRC-32C: using hardware acceleration.
140920 12:47:18 [Warning] WSREP: Could not open saved state file for reading: /var/lib/mysql//grastate.dat
140920 12:47:18 [Note] WSREP: Found saved state: 00000000-0000-0000-0000-000000000000:-1
140920 12:47:18 [Note] WSREP: Passing config to GCS: base_host = 10.0.0.67; base_port = 4567; cert.log_conflicts = no; debug = no; evs.inactive_check_period = PT0.5S; evs.inactive_timeout = PT15S; evs.join_retrans_period = PT1S; evs.max_install_timeouts = 1; evs.send_window = 4; evs.stats_report_period = PT1M; evs.suspect_timeout = PT5S; evs.user_send_window = 2; evs.view_forget_timeout = PT24H; gcache.dir = /var/lib/mysql/; gcache.keep_pages_size = 0; gcache.mem_size = 0; gcache.name = /var/lib/mysql//galera.cache; gcache.page_size = 128M; gcache.size = 128M; gcs.fc_debug = 0; gcs.fc_factor = 1.0; gcs.fc_limit = 16; gcs.fc_master_slave = no; gcs.max_packet_size = 64500; gcs.max_throttle = 0.25; gcs.recv_q_hard_limit = 9223372036854775807; gcs.recv_q_soft_limit = 0.25; gcs.sync_donor = no; gmcast.segment = 0; gmcast.version = 0; pc.announce_timeout = PT3S; pc.checksum = false; pc.ignore_quorum = false; pc.ignore_sb = false; pc.npvo = false; pc.version = 0; pc.wait_prim = true; pc.wait_prim_timeout = P30S; pc.weight = 1; protonet.b
140920 12:47:18 [Note] WSREP: Service thread queue flushed.
140920 12:47:18 [Note] WSREP: Assign initial position for certification: -1, protocol version: -1
140920 12:47:18 [Note] WSREP: wsrep_sst_grab()
140920 12:47:18 [Note] WSREP: Start replication
140920 12:47:18 [Note] WSREP: Setting initial position to 00000000-0000-0000-0000-000000000000:-1
140920 12:47:18 [Note] WSREP: protonet asio version 0
140920 12:47:18 [Note] WSREP: Using CRC-32C (optimized) for message checksums.
140920 12:47:18 [Note] WSREP: backend: asio
140920 12:47:18 [Note] WSREP: GMCast version 0
140920 12:47:18 [Note] WSREP: (d193920e-4078-11e4-9422-86a0381f90eb, 'tcp://0.0.0.0:4567') listening at tcp://0.0.0.0:4567
140920 12:47:18 [Note] WSREP: (d193920e-4078-11e4-9422-86a0381f90eb, 'tcp://0.0.0.0:4567') multicast: , ttl: 1
140920 12:47:18 [Note] WSREP: EVS version 0
140920 12:47:18 [Note] WSREP: PC version 0
140920 12:47:18 [Note] WSREP: gcomm: connecting to group 'my_wsrep_cluster', peer '10.0.0.245:'
140920 12:47:18 [Note] WSREP: declaring 3f04f808-4076-11e4-a830-1bcc73dcf244 stable
140920 12:47:18 [Note] WSREP: Node 3f04f808-4076-11e4-a830-1bcc73dcf244 state prim
140920 12:47:18 [Note] WSREP: view(view_id(PRIM,3f04f808-4076-11e4-a830-1bcc73dcf244,10) memb {
        3f04f808-4076-11e4-a830-1bcc73dcf244,0
        d193920e-4078-11e4-9422-86a0381f90eb,0
} joined {
} left {
} partitioned {
})
140920 12:47:19 [Note] WSREP: gcomm: connected
140920 12:47:19 [Note] WSREP: Changing maximum packet size to 64500, resulting msg size: 32636
140920 12:47:19 [Note] WSREP: Shifting CLOSED -> OPEN (TO: 0)
140920 12:47:19 [Note] WSREP: Opened channel 'my_wsrep_cluster'
140920 12:47:19 [Note] WSREP: Waiting for SST to complete.
140920 12:47:19 [Note] WSREP: New COMPONENT: primary = yes, bootstrap = no, my_idx = 1, memb_num = 2
140920 12:47:19 [Note] WSREP: STATE EXCHANGE: Waiting for state UUID.
140920 12:47:19 [Note] WSREP: STATE EXCHANGE: sent state msg: d1e0b305-4078-11e4-a8ed-16954c252dca
140920 12:47:19 [Note] WSREP: STATE EXCHANGE: got state msg: d1e0b305-4078-11e4-a8ed-16954c252dca from 0 ()
140920 12:47:19 [Note] WSREP: STATE EXCHANGE: got state msg: d1e0b305-4078-11e4-a8ed-16954c252dca from 1 ()
140920 12:47:19 [Note] WSREP: Quorum results:
        version    = 3,
        component  = PRIMARY,
        conf_id    = 9,
        members    = 1/2 (joined/total),
        act_id     = 0,
        last_appl. = -1,
        protocols  = 0/5/3 (gcs/repl/appl),
        group UUID = 3f056461-4076-11e4-a2b8-6216b1337d22
140920 12:47:19 [Note] WSREP: Flow-control interval: [23, 23]
140920 12:47:19 [Note] WSREP: Shifting OPEN -> PRIMARY (TO: 0)
140920 12:47:19 [Note] WSREP: State transfer required:
        Group state: 3f056461-4076-11e4-a2b8-6216b1337d22:0
        Local state: 00000000-0000-0000-0000-000000000000:-1
140920 12:47:19 [Note] WSREP: New cluster view: global state: 3f056461-4076-11e4-a2b8-6216b1337d22:0, view# 10: Primary, number of nodes: 2, my index: 1, protocol version 3
140920 12:47:19 [Warning] WSREP: Gap in state sequence. Need state transfer.
140920 12:47:21 [Note] WSREP: Running: 'wsrep_sst_rsync --role 'joiner' --address '10.0.0.67' --auth '' --datadir '/var/lib/mysql/' --defaults-file '/etc/my.cnf' --parent '21327''
140920 12:47:21 [Note] WSREP: Prepared SST request: rsync|10.0.0.67:4444/rsync_sst
140920 12:47:21 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
140920 12:47:21 [Note] WSREP: REPL Protocols: 5 (3, 1)
140920 12:47:21 [Note] WSREP: Service thread queue flushed.
140920 12:47:21 [Note] WSREP: Assign initial position for certification: 0, protocol version: 3
140920 12:47:21 [Note] WSREP: Service thread queue flushed.
140920 12:47:21 [Warning] WSREP: Failed to prepare for incremental state transfer: Local state UUID (00000000-0000-0000-0000-000000000000) does not match group state UUID (3f056461-4076-11e4-a2b8-6216b1337d22): 1 (Operation not permitted)
         at galera/src/replicator_str.cpp:prepare_for_IST():447. IST will be unavailable.
140920 12:47:21 [Note] WSREP: Member 1.0 () requested state transfer from '*any*'. Selected 0.0 ()(SYNCED) as donor.
140920 12:47:21 [Note] WSREP: Shifting PRIMARY -> JOINER (TO: 0)
140920 12:47:21 [Note] WSREP: Requesting state transfer: success, donor: 0
140920 12:47:22 [Note] WSREP: 0.0 (): State transfer to 1.0 () complete.
140920 12:47:22 [Note] WSREP: Member 0.0 () synced with group.
WSREP_SST: [INFO] Joiner cleanup. (20140920 12:47:23.479)
WSREP_SST: [INFO] Joiner cleanup done. (20140920 12:47:23.987)
140920 12:47:23 [Note] WSREP: SST complete, seqno: 0
140920 12:47:23 InnoDB: The InnoDB memory heap is disabled
140920 12:47:23 InnoDB: Mutexes and rw_locks use GCC atomic builtins
140920 12:47:23 InnoDB: Compressed tables use zlib 1.2.3
140920 12:47:23 InnoDB: Using Linux native AIO
140920 12:47:23 InnoDB: Initializing buffer pool, size = 128.0M
140920 12:47:24 InnoDB: Completed initialization of buffer pool
140920 12:47:24 InnoDB: highest supported file format is Barracuda.
InnoDB: Log scan progressed past the checkpoint lsn 1598129
140920 12:47:24  InnoDB: Database was not shut down normally!
InnoDB: Starting crash recovery.
InnoDB: Reading tablespace information from the .ibd files...
InnoDB: Restoring possible half-written data pages from the doublewrite
InnoDB: buffer...
InnoDB: Doing recovery: scanned up to log sequence number 1598283
140920 12:47:24  InnoDB: Waiting for the background threads to start
140920 12:47:25 Percona XtraDB (http://www.percona.com) 5.5.38-MariaDB-35.2 started; log sequence number 1598283
140920 12:47:25 [Note] Plugin 'FEEDBACK' is disabled.
140920 12:47:25 [Note] Server socket created on IP: '0.0.0.0'.
140920 12:47:25 [Note] Event Scheduler: Loaded 0 events
140920 12:47:25 [Note] WSREP: Signalling provider to continue.
140920 12:47:25 [Note] WSREP: SST received: 3f056461-4076-11e4-a2b8-6216b1337d22:0
140920 12:47:25 [Note] /usr/sbin/mysqld: ready for connections.
Version: '5.5.39-MariaDB-wsrep'  socket: '/var/lib/mysql/mysql.sock'  port: 3306  MariaDB Server, wsrep_25.10.r4014
140920 12:47:25 [Note] WSREP: 1.0 (): State transfer from 0.0 () complete.
140920 12:47:25 [Note] WSREP: Shifting JOINER -> JOINED (TO: 0)
140920 12:47:25 [Note] WSREP: Member 1.0 () synced with group.
140920 12:47:25 [Note] WSREP: Shifting JOINED -> SYNCED (TO: 0)
140920 12:47:25 [Note] WSREP: Synchronized with group, ready for connections
140920 12:47:25 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.

Multi Masterの確認

Secondary Nodeに同期されたサンプルデータベースのテーブルにデータを挿入します

Secondary Node

$ mysql -u root -e "insert into orenomaria.test values('galera')"
$ mysql -u root -e "select * from orenomaria.test"
+--------+
| col    |
+--------+
| galera |
| galera |
+--------+

Master Node

確認

$ mysql -u root -e "select * from orenomaria.test"
+--------+
| col    |
+--------+
| galera |
| galera |←最高
+--------+

非常に簡単にMulti Masterを構成することが出来ました。

Master Nodeを壊す

Master Node

Master Nodeのmysqldatadirを削除してプロセスをkillします

$ ps -ef|grep mysql |grep -v grep
root     23107     1  0 14:57 pts/1    00:00:00 /bin/sh /usr/bin/mysqld_safe --datadir=/var/lib/mysql --pid-file=/var/lib/mysql/ip-10-0-0-245.pid
mysql    23338 23107  0 14:57 pts/1    00:00:00 /usr/sbin/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --wsrep-provider=/usr/lib64/galera/libgalera_smm.so --log-error=/var/lib/mysql/ip-10-0-0-245.err --pid-file=/var/lib/mysql/ip-10-0-0-245.pid --wsrep_start_position=3f056461-4076-11e4-a2b8-6216b1337d22:14
$ sudo rm -rf /var/lib/mysql
$ ll mysql
ls: mysql にアクセスできません: そのようなファイルやディレクトリはありません
$ sudo kill -9 23107 23338
$ ps -ef|grep mysql
root     23552 22176  0 15:06 pts/0    00:00:00 grep mysql

死にました

Secondary Node

  • Master破損時のログ
$ cat /var/lib/mysql/`hostname`.err
140920 15:06:50 [Note] WSREP: (b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, 'tcp://0.0.0.0:4567') turning message relay requesting on, nonlive peers: tcp://10.0.0.245:4567
140920 15:06:51 [Note] WSREP: (b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, 'tcp://0.0.0.0:4567') reconnecting to 11c5fdd2-408b-11e4-8712-53af37cbe2b9 (tcp://10.0.0.245:4567), attempt 0
140920 15:06:54 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, OPERATIONAL, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:06:55 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:06:55 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:06:56 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:06:56 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:06:57 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:06:57 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:06:58 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:06:58 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:06:59 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:06:59 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:07:00 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:07:00 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:07:01 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:07:01 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:07:02 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:07:02 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:07:03 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:07:03 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:07:04 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) suspecting node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:07:04 [Note] WSREP: evs::proto(b9f4ed9f-408b-11e4-b85d-53a5bbcd3048, GATHER, view_id(REG,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4)) detected inactive node: 11c5fdd2-408b-11e4-8712-53af37cbe2b9
140920 15:07:05 [Note] WSREP: view(view_id(NON_PRIM,11c5fdd2-408b-11e4-8712-53af37cbe2b9,4) memb {
        b9f4ed9f-408b-11e4-b85d-53a5bbcd3048,0
} joined {
} left {
} partitioned {
        11c5fdd2-408b-11e4-8712-53af37cbe2b9,0
})
140920 15:07:05 [Note] WSREP: New COMPONENT: primary = no, bootstrap = no, my_idx = 0, memb_num = 1
140920 15:07:05 [Note] WSREP: Flow-control interval: [16, 16]
140920 15:07:05 [Note] WSREP: Received NON-PRIMARY.
140920 15:07:05 [Note] WSREP: Shifting SYNCED -> OPEN (TO: 19)
140920 15:07:05 [Note] WSREP: New cluster view: global state: 3f056461-4076-11e4-a2b8-6216b1337d22:19, view# -1: non-Primary, number of nodes: 1, my index: 0, protocol version 3
140920 15:07:05 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
140920 15:07:05 [Note] WSREP: view(view_id(NON_PRIM,b9f4ed9f-408b-11e4-b85d-53a5bbcd3048,5) memb {
        b9f4ed9f-408b-11e4-b85d-53a5bbcd3048,0
} joined {
} left {
} partitioned {
        11c5fdd2-408b-11e4-8712-53af37cbe2b9,0
})
140920 15:07:05 [Note] WSREP: New COMPONENT: primary = no, bootstrap = no, my_idx = 0, memb_num = 1
140920 15:07:05 [Note] WSREP: Flow-control interval: [16, 16]
140920 15:07:05 [Note] WSREP: Received NON-PRIMARY.
140920 15:07:05 [Note] WSREP: New cluster view: global state: 3f056461-4076-11e4-a2b8-6216b1337d22:19, view# -1: non-Primary, number of nodes: 1, my index: 0, protocol version 3
140920 15:07:05 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
  • データ確認

Master Nodeが破損したためかクエリを受け付けなくなっています

$ mysql -u root -e "select count(*) from orenomaria.test"
ERROR 1047 (08S01) at line 1: WSREP has not yet prepared node for application use

復旧

Secondary Node

  • Secondary NodeのMaster化

Secondary Nodeのwsrep_cluster_addressをgcomm://に変更します

mysql -u root -e "set global wsrep_cluster_address='gcomm://'"

検索できるようになりました。データの挿入もOKっぽいです

$ mysql -u root -e "select count(*) from orenomaria.test"
+----------+
| count(*) |
+----------+
|       15 |
+----------+
$ mysql -u root -e "insert into orenomaria.test values('galera')"
$ mysql -u root -e "select count(*) from orenomaria.test"
+----------+
| count(*) |
+----------+
|       16 |
+----------+
  • ログの確認

Masterが停止しwsrep_cluster_addressを変更するまでのログを確認してみます。
WSREP: Quorum: No node with complete state: などとかでてますね。
AIX触っていた頃のVG Quorumを思い出します。

やつも確かQuorum50%以下になったらVGがinactiveになるので3本以上のDiskでVG構成すると良いよとかあったような(うるおぼえ)

Quorum型ってことは3node以上でGalera Clusterを作っておけば1台死んでもトランザクションは止まらないのかしら?

140920 15:10:00 [Note] WSREP: Stop replication
140920 15:10:00 [Note] WSREP: Closing send monitor...
140920 15:10:00 [Note] WSREP: Closed send monitor.
140920 15:10:00 [Note] WSREP: gcomm: terminating thread
140920 15:10:00 [Note] WSREP: gcomm: joining thread
140920 15:10:00 [Note] WSREP: gcomm: closing backend
140920 15:10:00 [Note] WSREP: view((empty))
140920 15:10:00 [Note] WSREP: Received self-leave message.
140920 15:10:00 [Note] WSREP: gcomm: closed
140920 15:10:00 [Note] WSREP: Flow-control interval: [0, 0]
140920 15:10:00 [Note] WSREP: Received SELF-LEAVE. Closing connection.
140920 15:10:00 [Note] WSREP: Shifting OPEN -> CLOSED (TO: 19)
140920 15:10:00 [Note] WSREP: RECV thread exiting 0: Success
140920 15:10:00 [Note] WSREP: New cluster view: global state: 3f056461-4076-11e4-a2b8-6216b1337d22:19, view# -1: non-Primary, number of nodes: 0, my index: -1, protocol version 3
140920 15:10:00 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
140920 15:10:00 [Note] WSREP: applier thread exiting (code:0)
140920 15:10:00 [Note] WSREP: recv_thread() joined.
140920 15:10:00 [Note] WSREP: Closing replication queue.
140920 15:10:00 [Note] WSREP: Closing slave action queue.
140920 15:10:02 [Note] WSREP: rollbacker thread exiting
140920 15:10:02 [Note] WSREP: Start replication
140920 15:10:02 [Note] WSREP: Setting initial position to 3f056461-4076-11e4-a2b8-6216b1337d22:19
140920 15:10:02 [Note] WSREP: protonet asio version 0
140920 15:10:02 [Note] WSREP: Using CRC-32C (optimized) for message checksums.
140920 15:10:02 [Note] WSREP: backend: asio
140920 15:10:02 [Note] WSREP: GMCast version 0
140920 15:10:02 [Note] WSREP: (c2657b9f-408c-11e4-9081-e700f8703e0a, 'tcp://0.0.0.0:4567') listening at tcp://0.0.0.0:4567
140920 15:10:02 [Note] WSREP: (c2657b9f-408c-11e4-9081-e700f8703e0a, 'tcp://0.0.0.0:4567') multicast: , ttl: 1
140920 15:10:02 [Note] WSREP: EVS version 0
140920 15:10:02 [Note] WSREP: PC version 0
140920 15:10:02 [Note] WSREP: gcomm: connecting to group 'my_wsrep_cluster', peer ''
140920 15:10:02 [Note] WSREP: Node c2657b9f-408c-11e4-9081-e700f8703e0a state prim
140920 15:10:02 [Note] WSREP: view(view_id(PRIM,c2657b9f-408c-11e4-9081-e700f8703e0a,1) memb {
        c2657b9f-408c-11e4-9081-e700f8703e0a,0
} joined {
} left {
} partitioned {
})
140920 15:10:02 [Note] WSREP: gcomm: connected
140920 15:10:02 [Note] WSREP: Changing maximum packet size to 64500, resulting msg size: 32636
140920 15:10:02 [Note] WSREP: Shifting CLOSED -> OPEN (TO: 19)
140920 15:10:02 [Note] WSREP: Opened channel 'my_wsrep_cluster'
140920 15:10:02 [Note] WSREP: New COMPONENT: primary = yes, bootstrap = no, my_idx = 0, memb_num = 1
140920 15:10:02 [Note] WSREP: STATE_EXCHANGE: sent state UUID: c265d73a-408c-11e4-a52c-2a7ddfe1c27a
140920 15:10:02 [Note] WSREP: STATE EXCHANGE: sent state msg: c265d73a-408c-11e4-a52c-2a7ddfe1c27a
140920 15:10:02 [Note] WSREP: STATE EXCHANGE: got state msg: c265d73a-408c-11e4-a52c-2a7ddfe1c27a from 0 ()
140920 15:10:02 [Warning] WSREP: Quorum: No node with complete state:


        Version      : 3
        Flags        : 0x1
        Protocols    : 0 / 5 / 3
        State        : NON-PRIMARY
        Prim state   : SYNCED
        Prim UUID    : ba41f234-408b-11e4-b1fa-d37de606186c
        Prim  seqno  : 4
        First seqno  : -1
        Last  seqno  : 19
        Prim JOINED  : 2
        State UUID   : c265d73a-408c-11e4-a52c-2a7ddfe1c27a
        Group UUID   : 3f056461-4076-11e4-a2b8-6216b1337d22
        Name         : ''
        Incoming addr: '10.0.0.67:3306'

140920 15:10:02 [Note] WSREP: Partial re-merge of primary ba41f234-408b-11e4-b1fa-d37de606186c found: 1 of 2.
140920 15:10:02 [Note] WSREP: Quorum results:
        version    = 3,
        component  = PRIMARY,
        conf_id    = 4,
        members    = 1/1 (joined/total),
        act_id     = 19,
        last_appl. = 0,
        protocols  = 0/5/3 (gcs/repl/appl),
        group UUID = 3f056461-4076-11e4-a2b8-6216b1337d22
140920 15:10:02 [Note] WSREP: Flow-control interval: [16, 16]
140920 15:10:02 [Note] WSREP: Restored state OPEN -> SYNCED (19)
140920 15:10:02 [Note] WSREP: New cluster view: global state: 3f056461-4076-11e4-a2b8-6216b1337d22:19, view# 5: Primary, number of nodes: 1, my index: 0, protocol version 3
140920 15:10:02 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
140920 15:10:02 [Note] WSREP: REPL Protocols: 5 (3, 1)
140920 15:10:02 [Note] WSREP: Service thread queue flushed.
140920 15:10:02 [Note] WSREP: Assign initial position for certification: 19, protocol version: 3
140920 15:10:02 [Note] WSREP: Service thread queue flushed.
140920 15:10:02 [Note] WSREP: Synchronized with group, ready for connections
140920 15:10:02 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.

破損したMaster NodeをSecondaryとして起動

  • Master NodeのSecondary化

wsrep_cluster_addressを新Master(10.0.0.67)に変更します

$ vi /etc/my.cnf.d/server.conf
# Mandatory settings
wsrep_provider=/usr/lib64/galera/libgalera_smm.so
wsrep_cluster_address=gcomm://10.0.0.67
wsrep_node_address=10.0.0.245
wsrep_slave_threads=1
binlog_format=row
default_storage_engine=InnoDB
innodb_autoinc_lock_mode=2
query_cache_size=0
  • 起動と確認

SSTにより新Master(10.0.0.67)からデータが転送されてSuccessが返ります。

データも同期され、新しいデータの挿入もOKです

$ sudo service mysql start
Starting MySQL.......SST in progress, setting sleep higher. SUCCESS!
mysql -u root -e "select count(*) from orenomaria.test"
+----------+
| count(*) |
+----------+
|       16 |
+----------+
$ mysql -u root -e "insert into orenomaria.test values('galera')"
$ mysql -u root -e "select count(*) from orenomaria.test"
+----------+
| count(*) |
+----------+
|       17 |
+----------+

新Master Nodeでの確認

  • データの確認

新Secondaryで挿入したデータは反映されてます。最高です

$ mysql -u root -e "select count(*) from orenomaria.test"
+----------+
| count(*) |
+----------+
|       17 |
+----------+
  • ログの確認

新しいメンバー通知が走りwsrep_sst_rsyncが行われています。

完了後にmemberとしてJOINしています。

140920 15:31:37 [Note] WSREP: REPL Protocols: 5 (3, 1)
140920 15:31:37 [Note] WSREP: Service thread queue flushed.
140920 15:31:37 [Note] WSREP: Assign initial position for certification: 20, protocol version: 3
140920 15:31:37 [Note] WSREP: Service thread queue flushed.
140920 15:31:37 [Warning] WSREP: Releasing seqno 20 before 21 was assigned.
140920 15:31:39 [Note] WSREP: Member 1.0 () requested state transfer from '*any*'. Selected 0.0 ()(SYNCED) as donor.
140920 15:31:39 [Note] WSREP: Shifting SYNCED -> DONOR/DESYNCED (TO: 20)
140920 15:31:39 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
140920 15:31:39 [Note] WSREP: Running: 'wsrep_sst_rsync --role 'donor' --address '10.0.0.245:4444/rsync_sst' --auth '(null)' --socket '/var/lib/mysql/mysql.sock' --datadir '/var/lib/mysql/' --defaults-file '/etc/my.cnf' --gtid '3f056461-4076-11e4-a2b8-6216b1337d22:20''
140920 15:31:39 [Note] WSREP: sst_donor_thread signaled with 0
140920 15:31:39 [Note] WSREP: Flushing tables for SST...
140920 15:31:39 [Note] WSREP: Provider paused at 3f056461-4076-11e4-a2b8-6216b1337d22:20 (16)
140920 15:31:39 [Note] WSREP: Tables flushed.
140920 15:31:41 [Note] WSREP: resuming provider at 16
140920 15:31:41 [Note] WSREP: Provider resumed.
140920 15:31:41 [Note] WSREP: 0.0 (): State transfer to 1.0 () complete.
140920 15:31:41 [Note] WSREP: Shifting DONOR/DESYNCED -> JOINED (TO: 20)
140920 15:31:41 [Note] WSREP: Member 0.0 () synced with group.
140920 15:31:41 [Note] WSREP: Shifting JOINED -> SYNCED (TO: 20)
140920 15:31:41 [Note] WSREP: Synchronized with group, ready for connections
140920 15:31:41 [Note] WSREP: wsrep_notify_cmd is not defined, skipping notification.
140920 15:31:43 [Note] WSREP: 1.0 (): State transfer from 0.0 () complete.
140920 15:31:43 [Note] WSREP: Member 1.0 () synced with group.

以上でMariaDB Galera Clusterのデュアルマスタ機能を試すことが出来ました。

引き続き検証を続けたいと思います。

最高。

続きを読む

AWS Lambda から Incoming Webhooks を使って Slack に投稿する

概要

slack では webhook 用のURL を通し、メッセージを投稿することができます。
ここでは、AWS Lambda を invoke -> 内部でシェルスクリプトを実行 -> Incoming Webhooks に POST という手順を試してみたので、その方法を書いておきます。

Incoming Webhooks

Slack Apps の Incoming Webhooks のページから、web hook 用の URL を取得します。

https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX
↑こんな感じのURL

この URL にメッセージをPOSTすることで、設定したチャンネルにそのメッセージが投稿されます。
メッセージだけでなく、投稿者の名前、アイコンなども設定できます。

例えば、

curl -X POST https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX -d '{"text": "TEST!!!!", "username": "dango", "icon_emoji": ":dango:"}'

こんな感じのPOSTをすると、

20170222-183306.png

こんな感じの投稿になります。

引数にテキスト、ユーザー名、アイコンを受け取ってポストできるような適当スクリプトを作っておきます。

post.sh
#!/bin/sh

TEXT=$1
USERNAME=$2
ICON=$3

BODY="{'text': '${TEXT}', 'username': '${USERNAME}', 'icon_emoji': '${ICON}'}"

curl -X POST https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXX -d "${BODY}" 

Lambda

次は上記を Lambda を通してやってみます。
Lambda 関数の作成時に、ランタイムは node.js を選択、設計図には node-exec を選択します。

このLambda 関数から先ほど作ったシェルスクリプトを実行し、Slack への投稿を行います。

Lambda 関数は実行時に json 形式のペイロードを受け取り、その内容を関数に渡すことができます。

event.json
{
    "text": "Post from lambda!!",
    "username": "test",
    "icon": ":golfer:"
}

↑こんなペイロードを作っておきます。
これを渡してスクリプトを実行できるような関数を作成します。

index.js
var exec = require('child_process').exec;

exports.handler = function(event, context) {
  command = '/bin/bash ./post.sh ' + event['text'] + ' ' + event['username'] + ' ' + event['icon'];
  child = exec(command, function(error) {
    // Resolve with result of process
    context.done(error, 'Process complete!');
  });

  // Log process stdout and stderr
  child.stdout.on('data', console.log);
  child.stderr.on('data', console.error);
};

この index.js、post.sh を zip 形式にまとめてlambda へとアップロードし、Lambda 関数を作成します。
後の設定は適当に。。。

作成した関数に、アクション>テストイベントの設定から、先ほど作った json を設定し、テストを実行します。

20170218-124510.png

20170222-183909.png

成功のログとともに、Slack へと投稿されていることが確認できました!

感想

Lambda を使うのはこれが初めてなので、細かい設定はよくわからず作ってますが、なんとか成功しました。
(基本的に参考に記したリンク先のやり方を真似させていただきました。)
Lambda を使ってみたかっただけなので Lambda である必要はないと思いますが、割と簡単に設定できました。
Incoming Webhooks ではいろんな形式の投稿ができるっぽいので時間があれば試してみたい。。。

参考

続きを読む

Terraform で AWS環境を実運用する上で困ったことと、その対処

忘備録的なもの。
2017年2月時点、Terraformは0.8.6.

操作用AWSアカウントの認証情報の扱い

困ったこと

ネットの参考情報だと、awsの認証情報(credentials)を直接書くサンプルが非常に多かった。
しかし、tfファイルを書き換える運用だと、いつか間違えてcommit & pushしてインシデントになりそう。

Terraformを最初に動かすためのユーザーの認証情報は、その性質上大きな権限を持っていることが多いと思うので、慎重になりたい。

解決策

AWS-CLIのNamed Profile使おう。

$ aws configure --profile my-profile-name
AWS Access Key ID [None]: xxxxxxxxxx
AWS Secret Access Key [None]: xxxxxxxxxx
Default region name [None]: ap-northeast-1
Default output format [None]: 

事前にこうして設定しておけば、認証情報は$home/.aws/credentialsに名前付きで入る。

スクリプトからはこう参照する。これならばcommitしてしまっても問題ない。

example1.tf
provider "aws" {
  profile = "my-profile-name"
}

あとは、上記の設定手順をREADME.md なんかに書いて、KEYIDとSECRET自体はいつも通り正しく管理してあげればいい。

.tfstateファイルの扱い

困ったこと

.tfstateファイルを紛失してしまうと、作成したインスタンスを、Terraformから管理できなくなってしまうので、最重要ファイルだ。
かといって、gitにcommitするルールだと、commit忘れが怖いし、その際pullし忘れるとつらい。
一方、手動であちこち引き回しても同じことで、別の開発者が古いstateに基づいて、重複したインスタンスを立ててしまうかもしれない…

解決策

Backendの、remote state機能使おう。

ちゃんと公式ドキュメントある。 https://www.terraform.io/docs/state/remote/s3.html

こんな感じ。profileも指定できるので、そちらにregionを書いておけば省略できる。

$ terraform remote config 
    -backend=s3 
    -backend-config="bucket=my-tfstate-store-name-at-s3" 
    -backend-config="key=hogehoge/terraform.tfstate" 
    -backend-config="profile=my-profile-name"

これを実行した時点で、存在していたterraform.tfstateは、./.terraform/terraform.tfstate に移動されるようだ。

あとは自動でtfstateファイルをアップロード、ダウンロードしてくれる。
指定するバケットはacl=privateにしておくこと!!
あと、上記リンクでは、S3のversioningとかもつけておくことを勧めている。(私はやらなかった)

S3以外にも、いろいろ手段は用意されているようだ。

環境ごとのvariableの扱い

困ったこと

ステージングと本番、みたいに分けようと思って、環境ごとにvariableで設定値を与えられるようにしたけど、
-var オプションで引数に全部指定するの辛い

解決策

共通の設定ならば、terraform.tfvars という名前のファイルに書いておけば、指定しないでも勝手に読み込んでくれるとのこと。
https://www.terraform.io/intro/getting-started/variables.html#from-a-file

環境ごとに違う変数は、-var-fileオプションを使ってスイッチするのがよさそうだ。

$ terraform plan -var-file staging.tfvars
$ terraform plan -var-file production.tfvars

的な。

varは後ろに指定したものが有効(上書きされる)とのことなので、上手に使えば強いはず。

Packerで作ったAMIでEC2インスタンスを生成したい

困ったこと

auto-scalingを考えたときに、元となるAMIをちゃんと運用したい。
(注意、私はAWSのAutoScaling をよくわかっていないかも。)

そこで、Packerでイミュータブルなイメージ作ったらすごーいはず。
イミュータブルということは、イメージにDB接続情報がないといけない気がする。
よって、Terraformで先にRDS立てないといけないけど、そのTerraform側でAMIを使いたいからこういう話になっているわけで…
循環参照してしまう。

そもそも、AutoScaling配下のインスタンスを入れ替える際に全インスタンスを落とさないようにする、というのがなかなか厳しいようだ。
AutoScalingとの相性は、改善まち、という感じか。

参考: http://qiita.com/minamijoyo/items/e32eaeebc906b7e77ef8

準解決策1 null_resource

最終的にはうまくいかなかったが、最初に試した方法

null_resourceというものがある。
https://www.terraform.io/docs/provisioners/null_resource.html

何もしない代わりに、自身の状態(=変更が起きるトリガー)を定義するtriggers属性と、provisionerを設定できるリソースだ。
これを使って、

example2.tf
variable "ami_name_prefix" {}

resource "null_resource" "packer" {
  triggers {
    db_host = "${aws_db_instance.hogehoge.address}"
  }

  provisioner "local-exec" {
    command = <<EOS
packer build 
  -var 'DB_HOST=${aws_db_instance.hogehoge.address}' 
  -var 'AMI_NAME_PREFIX=${ami_name_prefix}' 
  packer.json"
EOS
  }
}

data "aws_ami" "packer_ami" {
  most_recent = true
  name_regex = "^${var.ami_name_prefix}.*"
  owners = ["self"]
  depends_on = ["null_resource.packer"]
}

resource "aws_instance" "hoge" {
  ami_id = "${data.aws_ami.packer_ami.id}"
  ...
}

とこんな感じ。

しかし、Terraform 0.8.6だと、triggersの中にinterpolationを混ぜると、問答無用でcomputed扱いになってしまうようで、
この記述では毎回AMIが作成されてしまって、差分のみ実行というTerraformの観点からは、使えなかった。

準解決策2 イミュータブルを諦める

オートスケーリング自体はこのパターンでは不可能か。AMI化を再度行う必要があると思う。

ほぼ完成品のAMIを組み立てておいて、aws_instanceのprovisionerで最後の仕上げをする。
Dockerなんかは、環境変数で外部情報を読み込ませる、なんてことをするらしいので、この手法に踏み切った。

// .envが欠けた状態でAMIをつくる
$ packer packer.json
example3.tf
data "aws_ami" "packer_ami" {
  most_recent = true
  name_regex = "^packer-ami-.*$"
  owners = ["self"]
  depends_on = ["null_resource.packer"]
}

data "template_file" "envfile" {
  # template = "${file(./env.tpl)}" などとした方が望ましいが例示のため。
  template = <<EOS
export DB_HOST="${db_host}"
export DB_PORT="${db_port}"
....
EOS

  vars {
    db_host = "${aws_db_instance.main.address}"
    db_port = "${aws_db_instance.main.port}"
  }
}

resource "aws_instance" "ec2" {
  ami_id = "${data.aws_ami.packer_ami.id}"
  ...

  # envファイルをuploadする。envファイルはdirenvとかdotenvとかで読み込まれるようにしておく。
  provisioner "file" {
    content = "${data.template_file.envfile.rendered}"
    destination = "/home/ec2-user/.env"
  }

  # 上で入れた環境変数を使ってサービスを立ち上げる。
  provisioner "remote-exec" {
    inline = ["sudo service unicorn start"]
  }

  # provisionerは、実行した環境からssh(のgolang実装。sshコマンドではない)で接続することになる。
  # 当然security_groupとvpc_internet_gatewayが適切に設定されている必要がある。
  connection {
    type = "ssh"
    ...
  }
}

tfファイルを構造化したい

困ったこと

コピペや反復は悪だし、再利用性も下がる。

COUNT

COUNT = nで配列のように同じタイプのリソースを複数作れる。
それぞれ微妙に違う値を書きたいなら、

COUNT = 2
ATTR = "${element(list("a", "b", "c", "d"), count.index)}"

などとできる。
listは変数化するとなおよい。

module

複数のリソースにまたがっての反復パターンなら、module化してしまうとよい。
module自体の書き方はここでは説明しないが、

./modules/my_module/variables.tf
./modules/my_module/main.tf
./modules/my_module/output.tf

を作り、

example4.tf
module "use_of_my_module" {
  source = "./my_module"
  var1 = ...
  var2 = ...
}

と書くことで使用。

$ terraform get

で、モジュールをterraformに読み込ませる準備をさせる。(./.terraform/modules/ にsym-linkが作成されるようだ)

様々に公開されているmoduleもあるようなので、むしろ自分でresourceを書かないほうが良いのかもしれない。

その他

また何かあれば書く。

続きを読む

サーバーレスシステムでユーザー情報をアクセストークンを使って取得する

やりたいこと

サーバレス認証.png

サーバーレスシステムでの個人情報保護について考えてみます。

システムで管理するべき個人情報は、できるだけクラウド内にとどめ、クライアントとやりとりしないということにできれば、通信路上での漏えいを防ぐことができます。
また、個人情報の管理を、限定することができれば、システムの大部分は、個人情報の漏洩について意識することなくシステム開発を行うことができます。

このような要求のシステムの場合、AWSでは、次のようになります。

  1. Cognito Identity Poolでユーザー管理をする。
  2. クライアント⇔AWS間の通信路上はアクセストークンと処理結果のみの受け渡しする。
  3. AWS内ではLambdaがアクセストークンを受け取りCognitoのユーザー情報をもとに処理を行う。

アクセストークンを受け取ったLambdaにアクセストークンに対応するユーザー情報へのアクセス権限が委譲され、LambdaはCognitoのユーザー情報を取得し処理できるようになります。よって、Lambdaは、アクセストークンを受け取れるようにする必要はありますが、ユーザー情報にアクセスするためのロールを付与する必要がなくなります。

参考までに、LambdaからCognitoにHTTPS通信する際に、どのような情報をやりとりするとアクセストークンと引き換えにユーザー情報を取得できるのか、cURL, Postman, AWS CLIを使って調べたのでまとめておきます。

結論

必要な情報は以下でした。
– Cognito Identity Service Providerが発行したアクセストークン
– Cognito Identity Poolのリージョン
– 各種おきまりのヘッダー(Content-Type、X-Amz-Target)

※ 各種おきまりのヘッダーをつけないと以下のエラーが返ってきます。

{"code":"BadRequest","message":"The server did not understand the operation that was requested.","type":"client"}

cURL

$ curl 'https://cognito-idp.<リージョン>.amazonaws.com/' -H 'Content-Type: application/x-amz-json-1.1' -H 'X-Amz-Target: AWSCognitoIdentityProviderService.GetUser' --data-binary '{"AccessToken":"<アクセストークン>"}'

Postman

AWS CLI

$ aws cognito-idp get-user --access-token <アクセストークン>

(備考)

  • <リージョン>は、東京だったら「ap-northeast-1」です。
  • このシステムでは、HTTPS通信が必須です。
  • AWSのサービス間のセキュリティ(ex. Lambda ⇔ Cognito)の信頼性は、別途、考慮が必要です。

続きを読む

AWS X-RayでSolrの検索処理をトレースする

AWS X-Ray(プレビュー版)を使ってSolrの検索処理をトレースしてみる。
Solrへリクエストを投げるところからトレースを開始する。

システム構成

サーバ

使用するインスタンスは2台。
Amazon Linuxを使う。

  • Webサーバ(express/node.js – t2.micro)
  • 検索エンジン(Solr/Jetty – t2.medium)

サービスマップ

こんな感じになる。
localhostと書いてあるけどSolrへのリクエスト。

servicemap.png

サーバの構築

共通

セキュリティグループ

ローカルマシン→Webサーバの80番ポートは開けておく。
Webサーバ→検索エンジンの80番ポートも開けておく。
ローカルマシン→Solrにアクセスするならそちらの8983番ポートも開けておく。

IAMロール/インスタンスプロファイル

後述のX-RayデーモンがAWS X-Rayサービスにデータを送るので書き込み権限を設定してあげる。
インスタンスに関連付けるIAMロールを適当に作り、AWSマネージドポリシーからAWSXrayWriteOnlyAccessポリシーをアタッチする。
ポリシーの内容はこんな感じ。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "xray:PutTraceSegments",
                "xray:PutTelemetryRecords"

            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

X-Rayデーモン

X-Rayデーモンのインストール。
もう起動してる。気が早い。

$ curl -sL https://s3.amazonaws.com/aws-xray-assets.us-east-1/xray-daemon/aws-xray-daemon-1.x.rpm -o /home/ec2-user/xray.rpm
$ sudo yum install -y xray.rpm
$ sudo initctl status xray
xray start/running, process 2607

インストール内容はこんな感じ。
xrayデーモンはGolang製の模様。
systemdの設定もあるようだがデフォルトだとUpstartで起動している。

$ rpm -ql xray
/etc/amazon/xray/cfg.yaml
/etc/init/xray.conf
/etc/systemd/system/xray.service
/usr/bin/xray

設定はデフォルトのままでいじらず。

Webサーバ

検索エンジンのIPアドレスをxray-solrとしてを/etc/hostsに登録した。

node.js

epelからインストールすると0.10系になるので最新版になる方法でインストールする。
レポジトリを登録。

$ curl -sL https://rpm.nodesource.com/setup_7.x | sudo bash -

## Installing the NodeSource Node.js 7.x repo...


## Inspecting system...

+ rpm -q --whatprovides redhat-release || rpm -q --whatprovides centos-release || rpm -q --whatprovides cloudlinux-release || rpm -q --whatprovides sl-release
+ uname -m

## Confirming "el7-x86_64" is supported...

+ curl -sLf -o /dev/null 'https://rpm.nodesource.com/pub_7.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm'

## Downloading release setup RPM...

+ mktemp
+ curl -sL -o '/tmp/tmp.OHBteTsWWV' 'https://rpm.nodesource.com/pub_7.x/el/7/x86_64/nodesource-release-el7-1.noarch.rpm'

## Installing release setup RPM...

+ rpm -i --nosignature --force '/tmp/tmp.OHBteTsWWV'

## Cleaning up...

+ rm -f '/tmp/tmp.OHBteTsWWV'

## Checking for existing installations...

+ rpm -qa 'node|npm' | grep -v nodesource

## Run `yum install -y nodejs` (as root) to install Node.js 7.x and npm.
## You may also need development tools to build native addons:
##   `yum install -y gcc-c++ make`

v7.6.0をインストール。

$ sudo yum install -y nodejs
$ node -v
v7.6.0

yarn

npmpackage.jsonを管理する気があるのかないのか分からないのでyarnをインストールする。

$ sudo curl -sL https://dl.yarnpkg.com/rpm/yarn.repo -o /etc/yum.repos.d/yarn.repo
$ sudo yum install -y yarn

express

/home/ec2-user/web以下にプロジェクトを作った。

$ yarn init -y
$ yarn add express aws-sdk aws-xray-sdk

ここまででpackage.jsonはこんな感じ。

{
  "name": "web",
  "version": "1.0.0",
  "main": "index.js",
  "repository": {},
  "license": "MIT",
  "dependencies": {
    "aws-sdk": "^2.12.0",
    "aws-xray-sdk": "^1.0.4-beta",
    "express": "^4.14.1"
  }
}

リクエストが来たら検索エンジンで検索するWebアプリを書く。

/home/ec2-user/web/index.js
const express = require('express');
const app = express();

const AWSXRay = require('aws-xray-sdk');
AWSXRay.config([AWSXRay.plugins.EC2]);

const http = require('http');
AWSXRay.captureHTTPs(http);


app.use(AWSXRay.express.openSegment('xray-web'));

app.get('/', (req, res) => {
  const options = {
    hostname: 'xray-solr',
    port: 8983,
    path: '/solr/xray/select?indent=on&q=*:*&wt=json'
  };
  http.get(options, (response) => {
    let data = '';
    response.on('data', (chunk) => data += chunk);
    response.on('end', () => {
      res.send(data);
    });
  });
});

app.use(AWSXRay.express.closeSegment());


app.listen(80);

EC2プラグインを指定するとAWS X-Rayに渡される情報にインスタンス情報が付加される。
今のところインスタンスIDとAZのみ。

AWSXRay.config([AWSXRay.plugins.EC2]);

サンプリングルールの設定も出来る模様。

AWSXRay.setSamplingRules('sampling-rules.json');

公式ドキュメントに記載されている形式だとエラーで動かないので下記のような形式にすると動いた。
ただDefaultの設定しか反映されないので、どこか間違っていそう。
毎秒fixed_target数のリクエストをトレース、その後はrateの割合でトレース。

sampling-rules.json
{
  "rules": {
    "1": {
      "service_name": "*",
      "http_method": "*",
      "url_path": "*",
      "fixed_target": 10,
      "rate": 0.1
    },
    "Default": {
      "fixed_target": 10,
      "rate": 0.1
    }
  }
}

express用のミドルウェアが用意されているので使わせて頂く。
これでWebサーバに来たリクエストをトレース出来る。

app.use(AWSXRay.express.openSegment('xray-web'));
  :
app.use(AWSXRay.express.closeSegment());

httphttpsを使った通信をトレースすることが出来る。
内部的にhttp.requestを置き換えている。
これで検索エンジンとの通信をトレース出来る。

AWSXRay.captureHTTPs(http);

http.get('http://.../')のようにURLを直接渡すとhttp://localhost/へのリクエストが無限ループになって死ぬ。
接続先が未知の場合はlocalhostに設定されるのはnode.jsの仕様らしい。誰が喜ぶんだこれ。
今のところoptionsはハッシュで渡されることが期待されている。

const options = {
  hostname: 'xray-solr',
  port: 8983,
  path: '/solr/xray/select?indent=on&q=*:*&wt=json'
};
http.get(options, (response) => {...});

検索エンジン

自分のIPアドレスをxray-solrとしてを/etc/hostsに登録した。

node.js

SolrへのプロキシとしてWebサーバと同様に node.js + express をインストールした。
コードはWebサーバとほぼ同じだが、検索エンジンのクライアントは色々ある前提でDynamic Naming Modeにしてみた。
defaultNameを与えないとエラーになった。

AWSXRay.middleware.enableDynamicNaming();
app.use(AWSXRay.express.openSegment('defaultName'));

java

1.8以上が必要らしい。

$ sudo yum install java-1.8.0-openjdk
$ sudo alternatives --config java

2 プログラムがあり 'java' を提供します。

  選択       コマンド
-----------------------------------------------
*+ 1           /usr/lib/jvm/jre-1.7.0-openjdk.x86_64/bin/java
   2           /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Enter を押して現在の選択 [+] を保持するか、選択番号を入力します:2

Solr

/home/ec2-user/solr/以下にSolrをインストールした。

$ curl -sL 'http://ftp.jaist.ac.jp/pub/apache/lucene/solr/6.4.1/solr-6.4.1.tgz' -o /home/ec2-user/solr.tgz
$ tar zxf solr.tgz
$ mv solr-6.4.1 solr

デフォルトの8983番ポートで起動。

$ cd solr
$ ./bin/solr start

コアを新規作成する。
solrコマンドのヘルプは./bin/solr create_core -helpなどと打てば見られる。

$ ./bin/solr create_core -c xray -d sample_techproducts_configs

Servlet Filter

GradleやMavenでのビルド方法がよく分からないので、とりあえず必要そうなJARを持ってきてぶち込んだ。

$ curl -sL 'http://central.maven.org/maven2/com/amazonaws/aws-xray-recorder-sdk-core/1.0.4-beta/aws-xray-recorder-sdk-core-1.0.4-beta.jar' -o aws-xray-recorder-sdk-core-1.0.4-beta.jar
$ curl -sL 'http://central.maven.org/maven2/com/amazonaws/aws-java-sdk-core/1.11.91/aws-java-sdk-core-1.11.91.jar' -o aws-java-sdk-core-1.11.91.jar
$ curl -sL 'http://central.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.8.6/jackson-databind-2.8.6.jar' -o jackson-databind-2.8.6.jar
$ curl -sL 'http://central.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.8.6/jackson-dataformat-cbor-2.8.6.jar' -o jackson-dataformat-cbor-2.8.6.jar
$ curl -sL 'http://central.maven.org/maven2/commons-logging/commons-logging/1.2/commons-logging-1.2.jar' -o commons-logging-1.2.jar
$ curl -sL 'http://central.maven.org/maven2/joda-time/joda-time/2.9.7/joda-time-2.9.7.jar' -o joda-time-2.9.7.jar
$ curl -sL 'http://central.maven.org/maven2/software/amazon/ion/ion-java/1.0.2/ion-java-1.0.2.jar' -o ion-java-1.0.2.jar
$ curl -sL 'http://central.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.8.6/jackson-annotations-2.8.6.jar' -o jackson-annotations-2.8.6.jar
$ curl -sL 'http://central.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.8.6/jackson-core-2.8.6.jar' -o jackson-core-2.8.6.jar
$ mv *.jar /home/ec2-user/solr/server/solr-webapp/webapp/WEB-INF/lib/

/home/ec2-user/solr/server/solr-webapp/webapp/WEB-INF/web.xmlAWSXRayServletFilterの設定を追加。
SolrRequestFilterの手前に入れた。

web.xml
<filter>
  <filter-name>AWSXRayServletFilter</filter-name>
  <filter-class>com.amazonaws.xray.javax.servlet.AWSXRayServletFilter</filter-class>
  <init-param>
    <param-name>fixedName</param-name>
    <param-value>xray-solr</param-value>
  </init-param>
</filter>

<filter-mapping>
  <filter-name>AWSXRayServletFilter</filter-name>
  <url-pattern>/solr/*</url-pattern>
</filter-mapping>

リクエストとトレースの確認

node.jsとSolrを起動してxray-webにリクエストを投げてみる。
トレース結果は下記のようになった。

trace.png

時間がずれるとこううまく表示されないのが気持ち悪い。
なにが悪いのか・・・

trace2.png

続きを読む

[AWS] EC2 で OpenVPN サーバを起動する

OpenVPN サーバ作る必要があったので、作業メモ

OpenVPN サーバのローンチ

MarketPlace に AMI があるので、それから作るのが簡単です。
Launch on EC2: OpenVPN Access Server
上のリンクからローンチすればセキュリティグループも作ってくれるけど、自分でセキュリティグループ作りたい場合は、インバウンドで最低限以下を許可するように設定。

Connection Method Protocol Port Range Source (IP or Group)
SSH tcp 22 Anywhere or IP Range
HTTPS tcp 443 Anywhere
tcp 943 Anywhere
udp 1194 Anywhere

起動したら EIP 割り当てておけば、後々便利。

しばらくすると EC2 起動してくるので、ssh 接続してイニシャルセッティングする。

$ ssh -i ~/.ssh/openvpn.pem openvpnas@xxx.xxx.xxx.xxx
Welcome to OpenVPN Access Server Appliance 2.1.4b

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

user-data not available: HTTP Error 404: Not Found: util/ec2:12,util/ec2:7,python2.7/urllib2:154,python2.7/urllib2:437,python2.7/urllib2:550,python2.7/urllib2:475,python2.7/urllib2:409,python2.7/urllib2:558 (urllib2.HTTPError)

          OpenVPN Access Server
          Initial Configuration Tool
------------------------------------------------------
OpenVPN Access Server End User License Agreement (OpenVPN-AS EULA)
 ()

Please enter 'yes' to indicate your agreement [no]: yes

Once you provide a few initial configuration settings,
OpenVPN Access Server can be configured by accessing
its Admin Web UI using your Web browser.

Will this be the primary Access Server node?
(enter 'no' to configure as a backup or standby node)
> Press ENTER for default [yes]: yes

Please specify the network interface and IP address to be
used by the Admin Web UI:
(1) all interfaces: 0.0.0.0
(2) eth0: 192.168.0.1
Please enter the option number from the list above (1-2).
> Press Enter for default [2]: 2

Please specify the port number for the Admin Web UI.
> Press ENTER for default [943]: 943

Please specify the TCP port number for the OpenVPN Daemon
> Press ENTER for default [443]: 443

Should client traffic be routed by default through the VPN?
> Press ENTER for default [no]: no

Should client DNS traffic be routed by default through the VPN?
> Press ENTER for default [no]: yes

Use local authentication via internal DB?
> Press ENTER for default [yes]: yes

Private subnets detected: ['19.168.0.0/16']

Should private subnets be accessible to clients by default?
> Press ENTER for EC2 default [yes]: yes

To initially login to the Admin Web UI, you must use a
username and password that successfully authenticates you
with the host UNIX system (you can later modify the settings
so that RADIUS or LDAP is used for authentication instead).

You can login to the Admin Web UI as "openvpn" or specify
a different user account to use for this purpose.

Do you wish to login to the Admin UI as "openvpn"?
> Press ENTER for default [yes]: yes 
(admin ユーザ名変えたい時は、ここで no 答えて、次の質問で変えたい名前を入力)

Type the password for the 'openvpn' account:
Confirm the password for the 'openvpn' account:

> Please specify your OpenVPN-AS license key (or leave blank to specify later): 


Initializing OpenVPN...
Adding new user login...
useradd -s /sbin/nologin "openvpn"
Writing as configuration file...
Perform sa init...
Wiping any previous userdb...
Creating default profile...
Modifying default profile...
Adding new user to userdb...
Modifying new user as superuser in userdb...
Getting hostname...
Hostname: xxx.xxx.xxx.xxx
Preparing web certificates...
Getting web user account...
Adding web group account...
Adding web group...
Adjusting license directory ownership...
Initializing confdb...
Generating init scripts...
Generating PAM config...
Generating init scripts auto command...
Starting openvpnas...

NOTE: Your system clock must be correct for OpenVPN Access Server
to perform correctly.  Please ensure that your time and date
are correct on this system.

Initial Configuration Complete!

You can now continue configuring OpenVPN Access Server by
directing your Web browser to this URL:

https://xxx.xxx.xxx.xxx:943/admin
Login as "openvpn" with the same password used to authenticate
to this UNIX host.

During normal operation, OpenVPN AS can be accessed via these URLs:
Admin  UI: https://xxx.xxx.xxx.xxx:943/admin
Client UI: https://xxx.xxx.xxx.xxx:943/

See the Release Notes for this release at:
   http://www.openvpn.net/access-server/rn/openvpn_as_2_1_4b.html

openvpnas@openvpnas2:~$ sudo apt-get update && sudo apt-get upgrade

これで https://{public IP Address}:943/admin にアクセスすることで、ローンチした OpenVPN サーバの管理パネルに接続できます。
(自己署名証明書による HTTPS 接続なので、セキュリティ例外追加しないと接続できないので注意)

OpenVPN サーバで接続用のユーザ作成

https://{public IP Address}:943/admin から管理パネルにログインして [User Management] の [User Permitions] でユーザを追加。
[Allow Auto-login] にチェックしておくと、ユーザの設定が簡略化されるのでオススメ。

他の設定とか諸々はドキュメント参照。

OpenVPN クライアントのインストールと設定

Tunnelblick あたりをインストールしておけばいいです。
上の設定で VPN 接続用のユーザを作成してあるのであれば https://{public IP Address}:943/ にログイン。
Screen Shot 2017-02-22 at 12.54.03.png

すると、以下のような画面が表示されるので [Yourself (autologin profile)] ってリンクから ovpn ファイルをダウンロード。
Screen Shot 2017-02-22 at 12.54.13.png

ダウンロードした ovpn ファイルをダブルクリックすると Tunnelblick が起動して、よしなに設定してくれます。

iPhone とかでも VPN 接続したい場合は iPhoneやiPad (iOS) でOpenVPNを使ってみよう! を参考にしてみましょう。

MarketPlace にある AMI 使うと簡単にできて便利すなぁ。

参考 URL

続きを読む

IAMロールを持たないEC2に対して、一括でIAMロールを付与する

はじめに

IAMロール割り当て方針

  • Nameタグをもとに、IAMロールの名前を決めた

    • Name「hoge01」 => IAMロール「ec2_hoge」
    • profile も同じ
  • IAMロールに割り当てるポリシーは空
    • これは別途やっていくので、ここでは触れない

環境

  • AWS CLI

    • aws-cli/1.11.46 Python/2.7.10 Darwin/15.6.0 botocore/1.5.9
  • jq
    • 1.5

実行

アクセスキーは、環境変数又はawsコマンドのプロファイルに適宜設定しておく

$ ./attach_iam_role_to_all_ec2.sh

実行後にもう一度実行して何も出なければ、全て完了している

やっていること

  • インスタンスプロファイルが未設定のインスタンスを抽出
  • IAMロールが無ければ作成
  • インスタンスプロファイルが無ければ作成
  • インスタンスプロファイルにIAMロールが設定されていなければ登録
  • インスタンスプロファイルが出来上がるのを待つ (wait 直後にassociate-iam-instance-profileを行うと失敗する事があるので追加でsleep)
  • EC2にインスタンスプロファイルをアタッチする

https://gist.github.com/tkimura/8c4f709df633d09052bcc89aabf01c17

attach_iam_role_to_all_ec2.sh
#!/bin/bash

AWS_DEFAULT_REGION=ap-northeast-1
export AWS_DEFAULT_REGION

list_roles=`mktemp`
list_profiles=`mktemp`

aws iam list-roles| jq -r '.Roles[]' > $list_roles
aws iam list-instance-profiles | jq -r '.InstanceProfiles[]' > $list_profiles

cat <<_JSON > ec2_role_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
_JSON


aws ec2 describe-instances |\
jq -r '.Reservations[] | .Instances[] | select(.IamInstanceProfile==null) | [.InstanceId, (.Tags[] | select(.Key=="Name")).Value] | @tsv' |\
while read instanceid name;do
  sleep=0
  role_name=ec2_${name%%[0-9]*}

  exist_role=`cat $list_roles | jq -r "select(.RoleName==\"$role_name\")"`
  if [ -z "$exist_role" ]; then
    # create role
    role_arn=`aws iam create-role --role-name $role_name --assume-role-policy-document file://ec2_role_policy.json | jq -r '.Role | .Arn'`
    sleep=10
  else
    role_arn=`echo $exist_role | jq -r '.Arn'`
  fi

  exist_profile=`cat $list_profiles | jq -r "select(.InstanceProfileName==\"$role_name\")"`
  if [ -z "$exist_profile" ]; then
    # create profile
    profile_arn=`aws iam create-instance-profile --instance-profile-name $role_name --instance-profile-name $role_name | jq -r '.Role | .Arn'`
    sleep=10
  else
    profile_arn=`echo $exist_profile | jq -r '.Arn'`
  fi

  exist_role_in_profile=`aws iam list-instance-profiles-for-role --role-name $role_name | jq -r '.InstanceProfiles[]'`
  if [ -z "$exist_role_in_profile" ]; then
    # add role to profile
    aws iam add-role-to-instance-profile --role-name $role_name --instance-profile-name $role_name
    sleep=10
  fi

  # waiting until create profile
  aws iam wait instance-profile-exists --instance-profile-name $role_name
  sleep $sleep

  # attach role to EC2
  echo "$name : $instanceid , $role_name , $role_arn , $profile_arn"
  if ! aws ec2 associate-iam-instance-profile --instance-id $instanceid --iam-instance-profile Name=$role_name; then
    exit 1
  fi

done

rm -f $list_roles $list_profiles

AWS CLIでの操作メモ

インスタンスプロファイルが未設定のインスタンスのID一覧を得る

aws --region ap-northeast-1 ec2 describe-instances |\
jq -r '.Reservations[] | .Instances[] | select(.IamInstanceProfile==null) | .InstanceId'

インスタンスプロファイルが未設定のインスタンスのIDとNameタグ一覧を得る (JSON)

aws --region ap-northeast-1 ec2 describe-instances | jq -r '.Reservations[] | .Instances[] | select(.IamInstanceProfile==null) | {"InstanceId": .InstanceId, "Name": (.Tags[] | select(.Key=="Name")).Value}'

インスタンスプロファイルが未設定のインスタンスのIDとNameタグ一覧を得る (TSV)

aws --region ap-northeast-1 ec2 describe-instances | jq -r '.Reservations[] | .Instances[] | select(.IamInstanceProfile==null) | [.InstanceId, (.Tags[] | select(.Key=="Name")).Value] | @tsv'

IAMロール一覧を得る

aws iam list-roles

インスタンスプロファイル一覧を得る

aws iam list-instance-profiles

名前でロールの有無を確認

aws iam list-roles| jq -r '.Roles[] | select(.RoleName=="ロール名")'

IAMロールを作る

aws iam create-role --role-name $role_name --assume-role-policy-document file://ec2_role_policy.json

インスタンスプロファイルを作る

aws iam create-instance-profile --instance-profile-name $role_name --instance-profile-name $role_name

インスタンスプロファイルにIAMロールを登録する

aws iam add-role-to-instance-profile --role-name $role_name --instance-profile-name $role_name

EC2にインスタンスプロファイルをアタッチする

aws ec2 associate-iam-instance-profile --instance-id $instanceid --iam-instance-profile Name=$role_name

続きを読む

Chrome拡張でAWSコンソールでのオペミスを防止する

対象

以下のようにAWSのリージョンでプロダクション環境/開発環境と使い分けて運用している人

東京(ap-northeast-1) => プロダクション環境
バージニア(us-east-1) => 開発(ステージング)環境

概要

AWSのリージョンによってプロダクション環境/開発環境と使い分けている場合、「開発環境だと思って操作したら実は本番だった」というオペミスが起こり得ます。
もちろんコンソール上部のリージョン名で区別がつきますが、これだと心もとないのでChrome拡張で本番環境の場合に警告を表示するようにしました。

kore-honban-desuyo.png

※本来はIAMで適切に権限を絞るべきだと思います。

Chrome拡張の作り方

公式ドキュメントがありますが、時間がなかったのでこちらの記事を参考にさせていただきました。

スクリプトを任意のページで実行できる「Content script」というのが今回の用途にあっていたので、コンソールのヘッダー部分に特定のリージョンを表す文字列が入っていたら警告を出す、というスクリプトを実行するようにしました。

manifest.json
{
  "manifest_version": 2,
  "name": "これ本番ですよ",
  "description": "AWSコンソールがプロダクション環境(東京リージョン)の場合に警告を表示します",
  "version": "1.0",

  "content_scripts": [
    {
      "matches": ["https://*.console.aws.amazon.com/*"],
      "js": ["jquery.min.js", "script.js"]
    }
  ]
}
script.js
/**
 * Created by Takuhiro Yoshida
 */
$(function() {
    console.log($(".nav-elt-label").text());
    var tokyo = /東京/;
    var global = /グローバル/;
    if($(".nav-elt-label").text().match(tokyo)) {
        $("#h").after('<div style="background-color:#FF0000;text-align:center;"><h3>これ本番ですよ!これ本番ですよ!これ本番ですよ!これ本番ですよ!これ本番ですよ!これ本番ですよ!</h3></div>');
    } else if($(".nav-elt-label").text().match(global)) {
        $("#h").after('<div style="background-color:#FFFF00;text-align:center;"><h3>これグローバルですよ!これグローバルですよ!これグローバルですよ!これグローバルですよ!これグローバルですよ!これグローバルですよ!</h3></div>');
    }
});

以下のようにディレクトリ、ファイルを作成し、Chromeの設定->その他ツール->拡張機能で「デベロッパーモード」をオンにして、「パッケージ化されていない拡張機能を読み込む」でこのディレクトリを指定すると使えるようになります。
jqueryはダウンロードしてきてください。

$ ls kore-honban-desuyo
jquery.min.js manifest.json script.js

最後に

今回Chrome拡張を初めて作りましたが、簡単なので他にもちょっとしたブラウザ操作の効率化に使えそうです。

参考

続きを読む

CloudWatch専用の通知bot、marbotを使ってみた

CloudWatchのslack連携というとLambdaでpostが鉄板ですが、より良くしようとするとLambdaをメンテしていく必要があります。

メッセージを視覚的に良くするならslackのattachmentsなどで工夫が必要だし、用途別に通知を分けるとかなると、Lambdaに分岐書いたりとかLambdaをコピペしたりなど。。。

たまたま、marbotというCloudWatchの専用botを見つけ、試しに導入してみたら結構イイ感じだったのでその紹介です。

marbotについて

公式サイト – marbot

CloudWatch専用の通知botで、アラートのslack通知や簡易的なエスカレーションがあります。
今のところは有料プランはなく、費用ゼロで使用できます。

AWSのChatbotコンテストで入賞してたみたいです。
https://aws.amazon.com/jp/blogs/news/congratulations-to-the-winners-of-the-serverless-chatbot-competition/

marbotの機能

marbotのFeaturesに説明がありますが、ザックリまとめると以下になります。

  • Slackへのアラート通知
  • アラートのエスカレーション
  • アラートの管理(チェック、パス、クローズ)
  • Dailyのアラートサマリ

導入方法

ここではCloudWatchの設定(SNSのtopicも登録済み)は済んでいるという前提で進めます。

  1. marbot# Add to Slackがあるので、認可してSlackに追加
  2. slackのchannelにmarbotを追加
    • 追加するとSNSで設定するEndpointが表示されます
    • 指定したchannelに参加しているメンバーがmarbot通知の対象になり、これについては後述
  3. CloudWatchのnotificationで指定しているSNSにsubscriptionを追加
    • HTTPS形式を選び、2.のEndpointを設定
  4. slack上でYou completed the SNS topic subscriptionとなっていれば、SNS連携は完了

マスクしてますが、ここにEndpointが表示されます。

invite-image

あとはCloudWatchのイベントが発火すると、marbotから通知がslackへ送信されます。

marbotのアラート通知

marbotからのアラート通知ですが、基本はchannelメンバーへのダイレクトメッセージです。
ここではアラート発生の流れと、アラートへのアクション、エスカレーションについて説明します。

アラートへのアクション

marbotからのアラート通知には、Acknowledge,Pass,Closeいずれかのアクションが出来るようになっています。
実際のインシデント想定をした場合、こんな感じでしょう。

  • Acknowledge : アラートの認知、障害内容のチェック開始など
  • Pass : 誰かにパス
  • Close : 終了

アラート発生のフロー

実際にやってみたところ、以下の流れになりました。

  1. アラート発生 🔔
  2. channelのオンラインメンバーにmarbotからアラート通知のダイレクトメッセージ
    • オンラインメンバーが複数の場合、誰か一人に通知するっぽい
  3. アラートへのアクション
    • Acknowledge => marbotがAcknowledgedと認知
    • Pass => marbotが次のオンラインメンバーへ通知
    • Close => marbotがClosedと認知

marbotのアラート通知はこんな感じになります。

alert-image

エスカレーション

marbotは特定の条件で、エスカレーションとしてchannelへ全体通知します。

  • オンラインメンバーが誰もいなかった
  • 通知を受けたメンバーがアクションを起こさなかった(5分固定っぽい)
  • 全員がPassした

つまり、誰も気づかなかった、誰も行動を起こさなかったらchannelへの全体通知されることになります。

escalate-image

Dailyのアラートサマリ

marbotは一日のアラートを集計し、日々channelへこんな感じのまとめを投稿してくれます。
(これはテストで適当に発生させたアラート)

summary-image

marbotの運用を考えてみる

marbotはslackのchannelをグループ管理としてみなしているようです。
なので、アラートを受けるべきメンバーを集めたchannelにmarbotを入れれば、アラートのコントロールもしやすくなるかと思います。

例えばこんな感じにわけてみるとか。

  • myservice : サービス関係者全員
  • myservice-dev : サービスのエンジニアAll
  • myservice-incident : 通知に必ず対応すべき関係者
    • marbot参加

ちなみにインテグレーション数ですが、channel個別にmarbotを招待できるので登録は1つで済みます。

Architectureについて

この記事に、アーキテクチャの概要が説明されていました。
https://cloudonaut.io/marbot-aws-serverless-chatbot-competition/

  • AWS Lambda and Amazon API Gateway
  • Amazon DynamoDB
  • Amazon Kinesis Streams and Amazon Kinesis Analytics
  • Amazon SQS
  • Amazon SNS and AWS CloudWatch

LambdaとAPI Gateway、DynamoDBはサーバレスの鉄板なので、すぐに想像できましたが、目を引いたのはSQSとKinesisです。

記事を読んで理解できましたが、アラート通知でn分スルーされたらchannelへ通知などを実装するために、SQSをタイマーとして使っているようです。なるほど。

KinesisはDailyサマリーの算出用途ですね。

まとめ

CloudWatchの通知をSlackに連携していない、またはslackに連携しているけどアラートを放置しがちなケースでは、marbotのエスカレーションが効果的になるかも。

続きを読む

公開slackチーム作りました

クラウドを活用していく上で、いろいろサービスを使ってみた過程で生じた困ったことや、共有したいこと、こんな便利なサービスがあるよ〜とか、社内外関係なくみんなで話せる場欲しいよね〜って流れで作ってみました!

参加方法

下記URLにメアド入力するとinviteが飛ぶので、joinすれば完了です。
https://clouddevelopers-slack.herokuapp.com/

image.png

slackの使い方については、よさそうな記事があったのでこちらを参照してください。
http://qiita.com/shizuma/items/6fc86e4718ff912d76e8

皆さんで、ベストプラクティスを目指しましょう!

続きを読む