2017年02月13日

Consulのおもしろそうな仕組みについて調べてみました

はてなブックマークに登録

はじめに

KLabさんの協力会社として一緒にお仕事をさせて頂いておりますクラスターコンピューティングと申します。今回はSerfの同様にHashiCorpより提供されているConsulを試してみました。

コンテナによるクラスタなど随時サービスが追加削除されるような環境ではそのアドレスはDHCPなどにより動的に決定されます。コンテナ名などでそのサービスにアクセスできれば便利ですが、動的なアドレスとコンテナ名の関係をどのように解決するかという点が問題になります。クラスタ内でロードバランサやリバースプロキシ等を利用している場合、サービスの追加に応じてその設定ファイルも動的に更新しなくてはなりません。また、構成が随時変化するなかで、現在どのサービスがどのホストで実行されているかということを把握する必要もでてきます。

Consulを利用することによりこれらの問題を解決することができます。今回はそのためのConsulの機能について簡単にしらべてみました。

Consulについて

本家ホームページ

ConsulはSerfと比較してより高度な機能を持つクラスタの管理ツールです。 Serfはクラスタのメンバシップの管理に特化したものになっており、それ以外のオーケストレーションに関わる機能は自前で用意する必要がありましたが、Consulではいくつかの機能が初めから備わっています。

Consulの機能

Consulの機能は様々なものがありますが一例をあげますと

  • サービスの検索
  • 各Consulクライアントが登録したサービスをDNSやHTTPインターフェースを利用してクラスタ全体から検索できます。
  • 障害検知/ヘルスチェック
  • 各ノードの障害検知だけではなく、それ上で実行されているサービスのヘルスチェックも可能です。
  • Key Value Storage
  • KVSを利用してのクラスタ間でのデータの共有が可能です。
  • 複数データセンタへの対応
  • データセンタを跨ぐインターネットを介したクラスタの構成でも効率よく障害検知や情報の伝播がおこなるようになっています。

Consulの構成

consul_cluster

Consulのクラスタ構成でもSerfと同様にクラスタを構成する全てのノード上でConsul Agentが実行されている必要があります。Consulのベースの部分にはそのままSerfを利用しています。例えば、メンバシップの管理や、各ノードそのものの障害検知の仕組みにはSerfと同様です。 ただ、Serfでは中心となるサーバが存在しない全てのノードが等価な構成でしたが、Consulではデータ等を管理するServerとそれに付随するClientからなる構成になっています。 そのためデータを管理するServerが障害点になっており、それが失われるとクラスタが機能しなくなってしまいますが、Severを複数台で冗長化することにより対処しています。複数台のServerでデータを管理する場合その一貫性が問題になりますが。それにはいわゆる分散合意アルゴリズムを利用して対応しています。

Consul Server

Consul Serverは冗長化のため基本的に複数台存在しています。冗長性を保ちつつ、Server間のデータの一貫性を確保するために仕組みとして、分散合意アルゴリズムであるRaftプロトコルが利用されています。RaftではまずLeaderをいわゆる多数決で選出します。Server間で投票を行い過半数の支持を受けたノードがLeaderになる仕組みです。1台のLeaderが選出された残りのServerはFollowerとなります。一貫性確保のためデータのコミットはこのLeaderが行います。データは随時Server間でレプリケーションされています。そのためClientからのクエリにはServerであれば、LeaderでもFollowerでも応答することが可能になっており、負荷を分散しています。必要があればFollowerがあらためてLeaderに問い合わせをしてClientに正しい返答するようになっています。Leaderのサーバが障害などで不在になると新たなLeaderが自動で選出され業務を引き継ぎます。

Leaderに選出されるために元々のサーバの数の過半数の支持が必要です。そのため障害等で稼働しているServerの数が減り定足数に足りなくなるとLeaderが選出できなくなりクラスタが機能しなくなります。例えばConsul Serverを5台で冗長化している場合、Leaderになるためには少なくとも3台から支持される必要があり、稼働しているServerが2台以下になってしまうとまだServerのConsul Agentが存在していても、クラスタは機能しなくなります。

Raftによる仕組みはConsul Server間のみで構成されています。Raftは構成するノードの数が増加すると全体にかかる負荷が大きなものになってしまいますが、それをServer間のみに限ることによって負荷を抑えつつクラスタ全体でのデータの一貫性を保てる構成になっています。

Consul Client

Clientとして実行されているConsul AgentはServerのAgentと通信して必要なクラスタ情報を取得あるいは登録します。Clientのノード上でクラスタの情報を取得する場合、ユーザは基本的にはローカルホストのAgentに問い合わせます。その後、ローカルホストのはAgentがConsul Serverと通信して情報を取得し、その結果をユーサに返します。ClientはServerのノードと通信できなくなってしまうと(他のClientとは通信できたとしても)基本的にクラスタの一員として動作できなくなります。また、Clientとして実行されているAgentがServerが不在でクラスタが機能しなくなった際に自動でServerに昇格するような仕組みはありません。

Consul AgentへのアクセスはユーザからローカルホストへのAgentへのアクセス、あるいはAgentからAgentへのアクセスが基本ですが、外部からのAgentへのアクセスを許可することも可能です。そのようにするとConsul Agentが実行されていないホスト上からクラスタの情報を取得したりすることが可能です。

サービスの登録

SerfでもTagという形でサーバの役割などを登録することはできましたが、ConsulではServiceとしてもっと具体的に登録することが可能です。Service名、Tag、アドレス、ポート番号、そしてヘルスチェック方法などを設定できます。これら登録した情報はDNSやHTTPインターフェス経由のクエリを通して参照することができます。

ヘルスチェック

SerfでのヘルスチェックはAgentそのものの死活のみでした。Consulでも同様な仕組みで各Consul Agentの死活を監視しています。それに加えて、Consulではアプリケーションのヘルスチェックをおこなうことが可能です。ヘルスチェックはTCPチェックはHTTPチェックなどの汎用のものおよび任意のスクリプトによるチェックが利用できます。

ヘルスチェックをサービスと関連つけることによりサービスのヘルスチェックとして利用できます。 この場合はヘルスチェックに失敗するとサービスに障害が発生したと判断されます。特にサービスを指定しない場合ノード全体と関連つけられます。この場合はヘルスチェックに失敗するとノード全体に障害が発生したと判断されます。 ヘルスチェックはDNSやHTTP経由でのクエリと関連していて、障害が発生した判断されているサービスやノードはクエリの結果から除外されるようになります。

サービスの登録は下記のようにJSONで設定を記述してConsul agentに読み込ませます。


{
  "service": {
    "name": "web",
    "tags": ["web1"],
    "port": 80,
    "enableTagOverride": false,
    "checks": [
      {
        "id": "api",
        "name": "HTTP API on port 80",
        "http": "http://localhost:80/index.html",
        "interval": "10s",
        "timeout": "1s"
      }
    ]
  }
}

DNSクエリについて

Consulの特徴の1つとしてAgentがDNS機能を提供している点が挙げられます。DNSインターフェースを利用してConsulのノード名でのアドレスの取得や提供しているサービスを検索することが可能です。これによって一般のアプリケーションからもConsulのノード名でのノードへのアクセスが可能になります。ただ、ConsulがDNSを提供しているポートは通常とは異なる番号(デフォルトでは8600番)のため、一般のアプリケーションがそれを参照するためにはdnsmasqなどを利用してシステムがConsulのDNSにも問い合わせするように環境を整えて置く必要があります。

デフォルトではnode.consulというドメイン名でアクセスできます。例えばserver1というノード名のホストであれば、server1.node.consulというホスト名でローカルの8600番ポートにアクセスすると応答が得られます。


# dig @127.0.0.1 -p 8600 server1.node.consul

; <<>> DiG 9.9.5-9+deb8u7-Debian <<>> @127.0.0.1 -p 8600 server1.node.consul
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 53745
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;server1.node.consul.           IN      A

;; ANSWER SECTION:
server1.node.consul.    0       IN      A       10.20.0.11

;; Query time: 7 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Mon Oct 03 05:42:15 UTC 2016
;; MSG SIZE  rcvd: 53

また、アドレスのほかに登録したサービスの検索もできます。ただし、UDPのアクセスの場合、返答されるレコード数(サイズ)に制限があるので注意が必要です。

サービスは<サービス名>.service.consulで検索可能です。


# dig @127.0.0.1 -p 8600 web.service.consul

; <<>> DiG 9.9.5-9+deb8u7-Debian <<>> @127.0.0.1 -p 8600 web.service.consul
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42433
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;web.service.consul.            IN      A

;; ANSWER SECTION:
web.service.consul.     0       IN      A       10.20.0.51
web.service.consul.     0       IN      A       10.20.0.52

;; Query time: 9 msec
;; SERVER: 127.0.0.1#8600(127.0.0.1)
;; WHEN: Mon Oct 03 05:42:47 UTC 2016
;; MSG SIZE  rcvd: 68

ほかにもサービスの登録時に設定したポート番号などをSRVレコードとして検索することもできます。

暗号化

consul_ssl

ConsulではAgent間の通信をSSLで暗号化することができます。SSLで通信するためにはCAのルート証明書の秘密鍵で署名された自身のサーバ証明書とCAのルート証明書がそれぞれのノードに必要です。SSLでの認証は相手のサーバ証明書がCAで署名されいるかをルート証明書で検証しています。したがって自己署名の証明書を利用するなどしてサーバ証明書への署名を管理することにより、許可されたノードのみがConsulクラスタに接続できるようにすることができます。

暗号化の設定は通信する双方のAgentで一貫した設定がされている必要があります。またSSLによる暗号化はConsulとしての通信の部分に適用されます。Gossipプロトコルなどは別なので、設定にミスがあった場合、一見(Serfによる)クラスタには正しく参加しているのに、Consulの機能は利用できない状態になってしまうので注意が必要です。

暗号化および認証は基本出ていく通信(outgoing)において相手の証明書の署名の検証および暗号化をおこないます。入ってくる通信(incoming)においても相手の認証および暗号化の強制を行う、相手がConsul Serverの場合、証明書のCommon nameを確認するなどの設定も可能です。

verify_outgoing:true/false

  • AgentからAgentへの通信(outgoingの通信)にTLS通信を利用する。
  • 通信時に相手から提供されるサーバ証明書を自身の持つルート証明書で検証する 。
  • 通信先のConsul AgentがTLS通信に対応してない場合には通信できない状態になる

verify_server_hostname:true/false

  • 相手がConsul serverの通信では通常のoutgoingの通信の手順に加えて、証明書のホストネーム(common name)も確認する。
  • Consulサーバのサーバ証明書のホスト名(common name)がserver..<ドメイン名>になっていない場合通信を拒否する。"server”は実際のそのサーバのホスト名ではなく固定の名前。(例:server.dc1.consul)
  • consulのclientとしてしか許可されていないホストがserverにならないにするための設定

verify_incoming:true/false

  • 他のConsul Agentから(incomingの通信)のTLS通信において相手先を認証する。
  • 通常のTLS通信の手続きに加えて、受信側から送信側の証明書を要求してそれを自身の持つルート証明書で認証する。
  • verify_incomingがtrueに設定されている場合、他のAgentからのTLS以外の通信は許可しない
  • verify_incomingが設定されていない場合、TLS通信でも通常の通信でもどちらでも許可する

Key Value Storage

Consulの大きな特徴の1つはKVS(Key Value Storage)を備えていることです。KVSのデータにはクラスタ内どのノード上からでもアクセスが可能です。また、外部のサーバからも許可があればアクセス可能です。

データはConsul Serverとして起動されたAgentが管理しています。Consul Serverを複数実行することによってデータは冗長化され、サーバ間でのRaftによる分散合意アルゴリズムによって一貫性が保証されます。ノードの名前やアドレス、登録したサービスなどの情報もこのKVS上のデータとして共有されています。

データはまずLeaderのConsul ServerのWAL(Write Ahead Log)に書き込まれたあとコミットされた時点で永続化されます。具体的にはLeaderのConsul Agentの作業領域に指定されているディスク上に書き込まれます。Leaderで永続化されたあと、各FollowerのConsul Serverにレプリケーションされます。Consulの設定によって永続化を無効にすることも可能です。ディスクに書き込む手間がなくなる分早くなりますが、データはメモリ上のみの保存になるのであくまで検証用です。

デフォルトではKVS上のデータには誰でもフルアクセス(読み書き)が可能です。Consulをroot権限で実行していても一般ユーザからKVS上のデータにフルアクセスが可能になっています。KVSへのアクセスを制限するにはACL(Access Control List)という仕組みを利用します。これはトークン単位で可能なアクセスを設定するものでKVS上の値だけではなく、サービスの検索やイベントへのアクセス権限も設定することが可能です。

KVSの操作はHTTP APIまたはConsul Cli(consul kvコマンド)で可能です。HTTPでアクセスする場合、ローカルホストの8500番がデフォルトのアクセスポートになっています。KVSにはすべてのメンバ上でアクセス可能で、Clientノード上の場合ローカルホストのAgentが適当なServerにアクセスしてユーザに返答します。HTTPでの応答はJSON形式でキーの値はBase64でエンコードされてます。


# curl -X PUT -d 'Hello World' http://localhost:8500/v1/kv/test
true

# curl -X GET http://localhost:8500/v1/kv/test | jq "."
[
  {
    "LockIndex": 0,
    "Key": "test",
    "Flags": 0,
    "Value": "SGVsbG8gV29ybGQ=",
    "CreateIndex": 359871,
    "ModifyIndex": 359871
  }
]

# curl -X GET http://localhost:8500/v1/kv/test | jq ".[].Value" -r | base64 -d
Hello World

イベントハンドラ

Serfではイベントをトリガとしてノード上でスクリプト実行などの処理を自動で行わせることが可能でしたが、Consulではイベントだけではなく、KVS上のデータの変化やServiceの登録、ヘルスチェックの結果などもトリガとして利用できるようになっています。

ConsulではKVS上のデータの変化を監視にBlocking Queryと呼ばれる仕組みが利用できます。これはKVS上の値への問い合わせに対してその値に変化があるかタイムアウトするまで応答をBlockし、変化があれ(あるいはタイムアウトすれば)応答を返す仕組みです。これによって従来のPollingなどと比較してKVSなどの変化に対して即座に処理を実行することが可能になっています。

consul-template

KVSの値を元にテンプレートから各種設定ファイルを作成し、変化に応じてそれを更新して反映させるなどの処理を実行したい場合、consul-templateと呼ばれるツールを利用することができます。ConsulのKVSやServiceなどの状態を監視して変化があった場合にはテンプレートから設定ファイルを再生成したあと、デーモン等を再起動して設定を反映させることを自動で可能になります。

/etc/hostsファイルのテンプレートを作成します。以下の例ではConsulのServiceの登録情報からホストの一覧を作成します。consul-templateはテンプレートエンジンとしてgo templateを利用しておりループ構造なども利用できます。

host.ctmpl


{{range services}}# {{.Name}}{{range service .Name "any"}}
{{.Address}}    {{.Node}}{{end}}

{{end}}

consul-templateはBlocking Queryを利用してKVS上の値を監視し、変化(追加)があった場合即座にテンプレートを適用してファイルを更新します。必要に応じて適用後のデーモンの再起動なども可能です。


# consul-template -consul localhost:8500 -template "template/hosts.ctmpl:/etc/hosts" 
> /etc/hosts

# web
10.20.0.51    web1
10.20.0.52    web2
10.20.0.53    web3

まとめ

今回はコンテナによるクラスタ構成において利用できそうなConsulの機能について調べてみました。ConsulはSerfをベースにしたメンバーシップ管理機能に加えて、メンバのアドレスやサービスの検索、Key Value Storageを利用したメンバ間データの共有などの機能を提供します。また、イベントハンドラや外部ツールと組わせることによって構成の変化に応じて動的に設定ファイルを再生成しリロードさせるといったオーケストレーション的な動作も可能です。


tech_ccmp at 15:36│Comments(0)TrackBack(0)

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント
最新トラックバック
Archives