yapcasia2015

 yapcasia2015

我々はどのように冗長化を失敗したのか

32d848b3a4490001225e3e604c37645d?s=128

kenjiskywalker

August 21, 2015
Tweet

Transcript

  1. 我々は   どのように   冗長化を失敗したのか YAPC::Asia  2015   2015/08/22  (Day

     2) kenjiskywalker Photos : https://www.flickr.com/photos/gsfc 1
  2. ある思想を持って   システム構築を行い   失敗をした話をします よろしくお願いします 2

  3. 話すこと 挑戦したこと   冗長化について   Redis  +  Redis  Sentinel  

    Consul   MySQL  +  mysqlfailover   consul-template   hostsの順番   クラウドという名の混乱   対応   冗長化の終わり、そして 3
  4. 挑戦したこと 4

  5. 式年遷宮   インフラストラクチャ 5

  6. 式年遷宮とは 6

  7. ”神宮式年遷宮は、    神宮(伊勢神宮)において    行われる式年遷宮    (定期的に行われる遷宮)    である。”  

    Wikipedia調べ 7
  8. 何故   式年遷宮を行うのか 8

  9. 神道での清浄さの維持   技術の伝承という継続性 Wikipedia調べ ”式年遷宮が20年ごとに行われる理由についても、    同じく確たる記録はないため不明である。    推測される主な理由としては、  

     以下の5点が挙げられる。” からの一部抜粋 9
  10. 式年遷宮とは   長期的な品質の担保と   その概念自体の継続性の維持を   目的としたアーキテクチャ   ではなかったのだろうか(仮説) 10

  11. Webサービスの運用と   式年遷宮 11

  12. Kenji Naito a.k.a kenjiskywalker The world is not complete Photos

    : https://www.flickr.com/photos/gsfc YAPC::Asia 2014 2014/08/29 (day 1) 12
  13. 故障率曲線 MTBF(平均故障間隔)   MTTR(平均修理時間) 辛さの平均値 13

  14. 「動くものは必ず壊れる」 Webサービスの運用 相反するふたつの価値観を   結びつけ成長させる必要がある 14 「24時間365日      正常に動き続けなけければ

       ならない」
  15. 冗長化 15

  16. ”冗長化(じょうちょうか)とは、    システムの一部に    何らかの障害が発生した場合に備えて、    障害発生後でも    システム全体の機能を維持し  

     続けられるように予備装置を    平常時からバックアップとして    配置し運用しておくこと。    冗長化によって得られる安全性は    冗長性と呼ばれる。” Wikipedia調べ 16
  17. 異常が発生した際に   自動的にアクティブが   遷移する設定を   導入しているところは   結構あると思う 17

  18. しかし 18

  19. 障害発生時、   正常に   切り替わらないことが   ほとんどである (個人の感想であり効果・効能を示すものではありません) 19

  20. そこで 20

  21. 平常時であっても   常に冗長構成の   アクティブ/スタンバイを   切り替えることによって   異常時の正常動作を  

    担保できるのではないか Active/Standby Active/Standby 21
  22. それって   式年遷宮では? 22

  23. それって   式年遷宮では? 23

  24. 式年遷宮   インフラストラクチャ 思想 (正常時も常に稼動系を切り替える) 24

  25. 構成 25

  26. 構成 Public Web Server A 02 Web Server A 01

    Web Sever B 02 Web Server B 01 26
  27. 構成 MySQL A Slave MySQL A Master Private MySQL B

    Slave MySQL B Master 27
  28. 構成 Private Public VPN 28

  29. nginx + Redis(Slave) Application A 02 各ウェブサーバでは   nginxとRedis、  

    Web  Applicationが動いている nginx + Redis(Master) Application A 01 29
  30. この構成で   式年遷宮を   実装する場合の課題 30

  31. Master/Slave方式の   冗長化されたシステムを   どのように   式年遷宮に対応させるか 31

  32. Redisの場合 32

  33. Redis  Sentinel   Redis  Ver.  2.8.9 33

  34. Redis  Sentinel?   基本スプリットブレインを   予防するためには   最低でも   サーバが3台必要なのでは?

    34
  35. 監視用のサーバを置いた場合   監視用のサーバも遷移させる? nginx + Redis Application A 01 Sentinel

    x1 nginx + Redis Application A 02 Sentinel x1 nginx + Redis Application A 03-01 Sentinel x1 nginx + Redis Application A 03-01 Sentinel x1 ? 35
  36. 冗長化の為に   システム自体を   複雑化するのは避けたい 36

  37. 台数をそのままに   Redis  Sentinelを導入 37

  38. Master : Sentinel x1 Slave : Sentinel x2 Redis Master

    Sentinel x1 Redis Master Sentinel x2 38
  39. Masterが遷移した際に   Redis  Sentinelの数を   操作する(Master  x1/Slave  x2)に   する必要がある

    Redis (New) Slave Sentinel x1 ⇣ Sentinel x2 Redis (New) Master Sentinel x2 ⇣ Sentinel x1 39
  40. 詰めが甘いところはあるが   可能ではありそうだ 40

  41. サーバに障害が発生した際に   アプリケーションをどのように   新しいMasterへ向き先を変えるか Redis Master Host 01 Redis

    (New) Master Host 02 41
  42. Consul   Consul  Ver.  0.5.0   42

  43. Consul  とは 43

  44. 44

  45. Consulとは   いい感じに   DNSレコードを   返すもの 45

  46. このConsulを利用して   RedisのMasterが   遷移したことを   アプリケーションへ   知らせたい 46

  47. #!/usr/bin/env ruby redis_name = 'foo-redis' fqdn = 'service.dc1.consul' master_redis_name =

    "master.#{redis_name}.#{fqdn}" master_redis_hosts = `host -W 1 #{master_redis_name}| grep has`.split("\n") # マスターが複数台起動していたなら if master_redis_hosts.length > 1 exit 2 end redis_is_runnning = `netstat -l | grep -w 6379` # Redisが動いていなければfail if redis_is_runnning.nil? exit 2 end # Roleがmasterであればtrue my_redis_role = `/usr/local/bin/redis-cli info | grep role` my_redis_role.chomp! if "#{my_redis_role}" == "role:master" exit 0 else exit 2 end 47
  48. #!/usr/bin/env ruby redis_name = 'foo-redis' fqdn = 'service.dc1.consul' master_redis_name =

    "master.#{redis_name}.#{fqdn}" master_redis_hosts = `host -W 1 #{master_redis_name}| grep has`.split("\n") # マスターが複数台起動していたなら if master_redis_hosts.length > 1 exit 2 end redis_is_runnning = `netstat -l | grep -w 6379` # Redisが動いていなければfail if redis_is_runnning.nil? exit 2 end # Roleがmasterであればtrue my_redis_role = `/usr/local/bin/redis-cli info | grep role` my_redis_role.chomp! if "#{my_redis_role}" == "role:master" exit 0 else exit 2 end 1、RedisのMasterのDNSが     複数のIPアドレスを返していない   2、自分のサーバでRedisが動いている   3、その動いているRedisはMasterである 48
  49. #!/usr/bin/env ruby redis_name = 'foo-redis' fqdn = 'service.dc1.consul' master_redis_name =

    "master.#{redis_name}.#{fqdn}" master_redis_hosts = `host -W 1 #{master_redis_name}| grep has`.split("\n") # マスターが複数台起動していたなら if master_redis_hosts.length > 1 exit 2 end redis_is_runnning = `netstat -l | grep -w 6379` # Redisが動いていなければfail if redis_is_runnning.nil? exit 2 end # Roleがmasterであればtrue my_redis_role = `/usr/local/bin/redis-cli info | grep role` my_redis_role.chomp! if "#{my_redis_role}" == "role:master" exit 0 else exit 2 end この3つの条件を   満たした時のみ RedisのMasterのDNSレコードを   Consulで返すようにした 49
  50. { "service": { "id": "foo-redis master check in HOSTNAME", "tags":

    ["master"], "name": "foo-redis", "check": { "script": "/opt/consul/bin/redis-master-check.rb", "interval": "30s" } } } 50
  51. 「master.foo-redis.service.dc1.consul」 というRedisのMasterの   DNSレコードが利用できるようになる   51

  52. nothing Old Master fail master.foo-redis.service.dc1.consul 52 master.foo-redis.service.dc1.consul 52 192.0.2.11 check

    ok
  53. nothing Old Master fail master.foo-redis.service.dc1.consul 53 master.foo-redis.service.dc1.consul 53 192.0.2.11 check

    ok check ng nothing
  54. nothing Old Master fail Run Redis fail over nothing master.foo-redis.service.dc1.consul

    54 master.foo-redis.service.dc1.consul 54 192.0.2.11 check ok check ng failover nothing nothing
  55. master.foo-redis.service.dc1.consul 55 192.0.2.11 check ok check ng failover complete failover

    nothing 192.0.2.12(New) nothing
  56. 問題点 このやり方だとConsul上で   常にSlave側がFailした   状態になってしまうので   Consulを利用した   ステートメント管理の

      知見のある方、   教えてください& Master(OK) Slave(NG) 56
  57. Consul  ポート8600問題 57

  58. ConsulがDNSを返してくれるポートが   8600ポートなので、全サーバに   ConsulとDnsmasqを導入し、   「.consul」 のドメインへのリクエストは   全て8600ポートに

      問い合わせるようにした 58
  59. conf-dir=/etc/dnsmasq.d server=/consul./127.0.0.1#8600 /etc/dnsmasq.conf /etc/dnsmasq.d/10-consul 設定例 59

  60. Redisの冗長化完了 60

  61. MySQLの場合 61

  62. MySQL  5.6(GTID)   mysqlfailover   MySQL  Ver.  5.6.22-2   mysql-utilities

     Ver.  1.5.4-1 62
  63. mysqlfailoverとは 63

  64. mysqlfailoverとは 他にも一撃で   レプリケーション環境を構築できる mysqlreplicateなど   便利コマンドが内包されている MySQLが提供してくれている   MySQL

     Utilitiesという   便利ツールの中のひとつのコマンド 64
  65. mysqlfailoverを利用することで   障害時にMasterを   遷移することはできるが Redisと同様にどのように   MySQLを利用している   アプリケーションへ

      稼動系の切り替えを伝達するか 65
  66. Consul   Consul  Ver.0.5.0   66

  67. mysqlfailoverには 遷移実行前(before_exec)と   遷移実行後(after_exec)で 任意のコマンドが実行できる 67

  68. この機能を利用して   Consulで   DNSレコードを   操作してみましょう 68

  69. #!/bin/bash curl -X PUT -d '{"Node":"foo-db-master"}' localhost:8500/v1/catalog/deregister curl -X DELETE

    http://localhost:8500/v1/kv/foo-db-master 遷移実行前(before_exec) deregister-master-db.sh 遷移前にMasterのDNSレコードを削除し   接続できないようにする 69
  70. #!/bin/bash curl -X PUT -d '{"Node":"foo-db-slave"}' localhost:8500/v1/catalog/deregister curl -X PUT

    -d '{"Node":"foo-db-master}", "Address":"192.0.2.110"}' \ localhost:8500/v1/catalog/register curl -X PUT -d '192.0.2.110' http://localhost:8500/v1/kv/foo-db-master 遷移実行後(after_exec) register-master-db.sh Masterの遷移が正常に完了した後   Masterのレコードを新MasterのIPアドレスで返す ※  kvは念のための設定 70
  71. 192.0.2.111 start failover foo-db-master.node.dc1.consul 71

  72. 192.0.2.111 start failover run before_exec deregister foo-db-master.node.dc1.consul 72

  73. 192.0.2.111 start failover run before_exec failover deregister nothing foo-db-master.node.dc1.consul 73

  74. 192.0.2.111 start failover run before_exec run after_exec failover deregister 192.0.2.112(New)

    nothing foo-db-master.node.dc1.consul 74
  75. MySQLの冗長化完了 75

  76. しかし、ここで課題が 内部の名前解決をすべて   Consulに委ねてしまうと、   Consulの死  =  サービスの死に   つながってしまう

    76
  77. これではせっかく   サービスの継続性を狙って   自動遷移する冗長構成を   構築したものの   Consulの存在自体が  

    リスクになる 77
  78. あ、Consulが返してる   DNSレコードを   hostsに直接書いちゃお☺ 78

  79. hostsにDNSレコードを   書いておけば   万が一   Consulの応答がなくなっても   サービスへの影響はなくなる 79

  80. consul-template   consul-template  Ver.  v0.7.0 80

  81. consul-template  とは 81

  82. consul-template  とは Consulの管理しているDNSレコードを   ファイルに書き出す Consulの管理してるDNSレコードに   変更があった際に任意のコマンドを   実行できる便利ツール

    82
  83. 変化があった場合に   /etc/hostsを書き換えて   Dnsmasqを再起動させるようにした consul = "127.0.0.1:8500" template {

    source = "/etc/hosts.ctmpl" destination = "/etc/hosts" command = "service dnsmasq restart" } /etc/consul.d/consul-template.cfg 83
  84. # This file generated by consul-template 127.0.0.1 localhost localhost.localdomain localhost4

    localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 # node{{ range nodes }} {{.Address}} {{.Node}} {{.Address}} {{.Node}}.node.dc1.consul{{end}} {{range services}} # service {{.Name}}{{range service .Name}} {{.Address}} {{.Name}}.service.dc1.consul{{end}}{{end}} /etc/hosts.ctmpl 84
  85. 作成されるhostsファイル # This file generated by consul-template 127.0.0.1 localhost localhost.localdomain

    localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 # node 192.0.2.111 foo-db-master 192.0.2.111 foo-db-master.node.dc1.consul 192.0.2.112 foo-db-slave 192.0.2.112 foo-db-slvave.node.dc1.consul ... # service bar-app 192.0.2.11 bar-app.service.dc1.consul 192.0.2.12 bar-app.service.dc1.consul 85
  86. /usr/local/bin/consul-template \ -config=/etc/consul.d/consul-template.cfg 設定ファイルを指定し   DaemontoolsやSupervisorなどで   起動させておく 86

  87. # node 192.0.2.11 foo-app01 192.0.2.11 foo-app01.node.dc1.consul 192.0.2.12 foo-app02 192.0.2.12 foo-app02.node.dc1.consul

    192.0.2.13 foo-app03 192.0.2.13 foo-app03.node.dc1.consul # service foo-app 192.0.2.11 foo-app.service.dc1.consul 192.0.2.12 foo-app.service.dc1.consul 192.0.2.13 foo-app.service.dc1.consul 試しにfoo-app02を止めると 87
  88. こうなる。便利 こうなる # node 192.0.2.11 foo-app01 192.0.2.11 foo-app01.node.dc1.consul 192.0.2.13 foo-app03

    192.0.2.13 foo-app03.node.dc1.consul # service foo-app 192.0.2.11 foo-app.service.dc1.consul 192.0.2.13 foo-app.service.dc1.consul 88
  89. Redis Sentinel + Consul MySQL(GTID) + mysqlfailover + Consul Consul

    + consul-template 89
  90. この時点で   解決できていない課題 90

  91. MySQLおよびRedisの   遷移時のダウンタイム問題を   解決できていない   稼働系常時遷移を   実践している人いたら  

    知見を下さい。   よろしくお願いします& 91
  92. 今考えていることメモ Web  API側、ネイティブアプリ側   それぞれでデータストアに   アクセスできない時に   即エラーを返さず、  

    バッファリング機能を搭載し   エラーまでのタイムアウト時間を   伸ばすか?しかしその影響は   どの範囲まで許容できる? 92
  93. などなど考えていたが   いきなり100%を目指すのは難しい 細かい機能リリース時に   念のためメンテナンスを実施する   昭和的運用だったので   一旦は都度メンテナンスの

      タイミングで   正常系を遷移する予定だった 93
  94. そして 94

  95. この時構築中の   システムが乗る   アプリケーションは   リリースに追われ 結合テストや負荷テストなど   十二分に行われてはいないまま

      リリース日が刻々と迫っていた 95
  96. 本題 96

  97. 我々は   どのように   冗長化を失敗したのか 97

  98. 問題   hostsの場合 98

  99. 内部間の通信が01,  02とある内の   01のみにアクセスが集中していた  access no access 01 02 99

  100. 何が起こっていたのか 100

  101. hostsに設定が書いてある場合   上から順に見ていき、   最初にヒットしたレコードを返す hosts --- # service foo-app

    192.0.2.11 foo-app.service.dc1.consul 192.0.2.12 foo-app.service.dc1.consul --- 101 request response
  102. 01にしか処理が行かないのは   当然といえば当然だった   やる前に気付け問題 102

  103. 解決作 103

  104. Dnsmasq   nsswitch.conf   Dnsmasq  Ver.  2.48-14 104

  105. 全サーバにDnsmasqが   稼働していたので   nsswitch.confで   hostsより先にDnsmasqを   先に見るようにした 105

  106. nsswitch.conf   hostsの部分を書き換える hosts: files dns before: after: hosts: dns

    files 106
  107. DNSラウンドロビン   されるようになった 107

  108. 問題   mysqlfailoverの場合 108

  109. mysqlfailoverのログを見ると   ちょくちょくhealth  checkに   失敗していた 109

  110. mysqlfailoverの仕組み 110

  111. def is_alive(self): """Determine if connection to server is still alive.

    Returns bool - True = alive, False = error or cannot connect. """ res = True try: if self.db_conn is None: res = False else: # ping and is_connected only work partially, try exec_query # to make sure connection is really alive retval = self.db_conn.is_connected() if retval: self.exec_query("SHOW DATABASES") else: res = False except: res = False return res Health  Check時に   MySQLに接続し   SHOW  DATABASESを実行 111
  112. def _reconnect_master(self, pingtime=3): """Tries to reconnect to the master This

    method tries to reconnect to the mast after 3 attemps, returns False. """ if self.master and self.master.is_alive(): return True is_connected = False i = 0 while i < 3: try: self.master.connect() is_connected = True break except: pass time.sleep(pingtime) i += 1 return is_connected 失敗した場合は   ping(pingtime)  x3  試す x3はハードコードされている 112
  113. どうにか3回のリトライ中に   成功はしているようだが、   そもそも   ステージングで検証した時は   Failは起こらなかった... 113

  114. ping(pingtime)  x3の回数を   増やして様子を見る案もあったが   Failの原因が不明のまま   自動遷移の機構を   入れておくのは危険だった

    114
  115. 原因が不明なため、   Redisも含めた   自動遷移のシステムを停止した 115

  116. 冗長化は幻しとなった 116

  117. 原因について 117

  118. 原因その1   VPNがおかしい 118

  119. パブリック層から   プライベート層への接続が   日本中をめぐっていた 119

  120. 不要なルーティングを   改修した 120

  121. mysqladmin  ping 121

  122. 122

  123. 原因その2   謎のスロークエリ 123

  124. 時間によって   同じクエリでも   やたら遅いクエリが   存在していた 124

  125. クラウド上のDBサーバが   乗っかっているホストの   I/Oが忙しいのでは?と   仮説を立て、DBサーバを   別のホストに移動してもらった 125

  126. Slow  Queries 126

  127. 127

  128. その3   根本的原因   そもそも利用していた   クラウドサービスが   はじまったばかりで  

    全然枯れていなかった 128
  129. 129

  130. 不穏な動作の原因は   大体特定できたが   ネットワークの不調など   予期しないタイミングで   遷移されても困る 130

  131. 自動遷移の利便性   -  MTTR(平均修理時間)の大幅な削減   - 復旧作業時のヒューマンエラー防止   - 圧倒的な満足感

      - 人間がいない清らかな世界感 131
  132. 手動遷移の優位性   -  意図したタイミングで遷移が可能   - 冗長監視システム誤動作の心配がない I'm fail over

    button Don't push me !! 132
  133. 復旧時間を犠牲にして、   我々は自動遷移システムを止めた   その代わり   一撃で正確に遷移するような   ヒューマンエラーの起こらない  

    仕組みを入れることにした 133
  134. そして 134

  135. のっかるアプリケーションも   乗せるサーバ周りも   問題を抱えたまま   リリースを日を迎えた 135

  136. 昼寝ができるぐらい遅い   レスポンスタイム 繰り返されるN+1クエリ それが要件の結果の   動作だったのか   今となっては  

    誰もわからない各機能 136
  137. 迫り来る   圧倒的成長機会に   感謝をしながら   改修対応を   行う日々が始まった ※もちろん若干盛っています

    137
  138. 138

  139. まとめ、学び 139

  140. どれだけリリース直前まで   炎上していても   絶対に   本番環境と同等のシステム構成で   負荷テストをしよう 実証

    140
  141. 結合テストが難しい場合は   各機能毎に負荷テストが   実施できるようにしよう   外部との依存部分を   最小限のモックにするなど  

    結合を待ってから試験をすることは   極力避けるようにしよう 個別性能試験 141
  142. 使う理由が良くわからなくても   自分が担当するところは   最終的に自分が   責任を持つことになるので   疑問点をなくすために  

    完全に検証をやりきろう 道具への理解と責任 142
  143. 期待するな、   計測しよう 143

  144. 挑戦するなら   徹底的に検証しよう 144

  145. 手に馴染んだ   道具を使おう 145

  146. 当たり前のことを   当たり前に正しく行おう 146

  147. Production  System  is   not  your  sandbox. 147

  148. Webサービスが続く限り   顧客へ価値を   届け続けるために   我々の稼働率の戦いは   これからも続く 148

  149. 149

  150. 次回予告 150

  151. 式年遷宮   インフラストラクチャ   三部作最終章 151

  152. クラウドの中心で   式年遷宮を叫んだ愚か者   Take  care  of  system.   鋭意製作中

    152
  153. To  be  continued 153

  154. おわり ありがとう   ございました 154

  155. Redis   http://redis.io/   Redis  Sentinel   http://redis.io/topics/sentinel   MySQL

      https://www-jp.mysql.com/   MySQL  Utilities   https://dev.mysql.com/downloads/utilities/   Consul  by  HashiCorp   https://www.consul.io/   consul-template   https://github.com/hashicorp/consul-template   Dnsmasq  -  network  services  for  small  networks.  -  Simon  Kelley   http://www.thekelleys.org.uk/dnsmasq/doc.html   Consulと自作OSSを活用した100台規模のWebサービス運用  by  FUJIWARA  Shunichiro   https://speakerdeck.com/fujiwara3/consultozi-zuo-osswohuo-yong-sita100tai-gui-mo- falsewebsabisuyun-yong 参考URL 155
  156. 参考URL 式年遷宮Infrastracture  /  さよならインターネット   http://blog.kenjiskywalker.org/blog/2013/08/11/shikinen-sengoo-infrastracture/   神宮式年遷宮  /  Wikipedia

      https://ja.wikipedia.org/wiki/%E7%A5%9E%E5%AE%AE%E5%BC%8F %E5%B9%B4%E9%81%B7%E5%AE%AE   冗長化  /  Wikipedia   https://ja.wikipedia.org/wiki/%E5%86%97%E9%95%B7%E5%8C%96   完成されたシステムなどない。完成された人間もいない。あるのは成長し続ける未完成 なシステムと、それを支える未完成な人間だけだ  /  YAPC::Asia  Tokyo  2014   http://yapcasia.org/2014/talk/show/4c7651e8-ed53-11e3-9faf-6ba36aeab6a4   156
  157. 「迷ったら健全な方」   Being  healthy  dev  and  ops  in  Cookpad  by

     Issei  Naruta  /  Speaker  Deck   https://speakerdeck.com/mirakui/being-healthy-dev-and-ops-in-cookpad   「仕事道具に対しても責任を持ちたい」   監視ツールの話  by  @kazeburo  /  slideshare   http://www.slideshare.net/kazeburo/ss-13361002 参考URL 157 ImmutableServer  /  Martin  Fowler   http://martinfowler.com/bliki/ImmutableServer.html   BlueGreenDeployment  /  Martin  Fowler   http://martinfowler.com/bliki/BlueGreenDeployment.html   Trash  Your  Servers  and  Burn  Your  Code:  Immutable  Infrastructure  and  Disposable  Components  /  Chad  Fowler http://chadfowler.com/blog/2013/06/23/immutable-deployments/   インフラ系技術の流れ  /  Gosuke  Miyashita   http://mizzy.org/blog/2013/10/29/1/   Rebuild.fm  25:  Immutable  Infrastructure  (Naoya  Ito,  Gosuke  Miyashita).   http://rebuild.fm/25/