Consul Cluster を Service Mesh にする Connect という機能を備えた Consul 1.2.0 がリリースされました。ただし、Public Beta の位置づけで、正式版は 2018 年末とか。
記事によれば、Service Mesh は以下の 3 つ を提供すべきとされています(色々な意見があります)が、このうちの Segmentation が Connect で解決されるということになります。
- Discovery: サービスは互いに見付けあうことができる
- Configuration: ランタイム設定を Central Source から抽出できる
- Segmentation: サービスの通信は許可・暗号化されたものである
今日は、とりあえず Getting Started を読んだので、ちょっと実際に動かしてみようと思います。 作成した環境については、一番最後に。
Consul Connect とは
Connect を簡単に言うと、主として以下の 2 つの機能を備えた、Consul の新規機能です。
- mTLS (mutual TLS) で暗号化、および相互認証されたサービス間通信
intension
と呼ばれる、論理的なサービス名を用いた通信制御 (accept/deny)
これらを実現するため、Connect は Consul 組み込みの Sidecar Proxy を持ちます。この Proxy が mTLS 回りや通信制御の面倒を見てくれるので、既存のアプリケーションは変更不要という形になります。 この Proxy もかなり性能面には気を配られて設計・実装されているようですが、さらに性能を上げたい場合は、Consul Connect の API をアプリに組込む (Native Integration) ことも可能です。
上記のとおり、結構な部分を mTLS で行うため、証明書運用が絡んできます。
この証明書に関しても、かなり考慮されているようでして、Connect ではビルトインの CA を持っていますし、root 証明書のローテーションを(cross-sign を行うことで) 無停止で実現することもできたり、 Vault と連携することも可能です。
また、ここでの証明書は SPIFFE に準拠しているっぽくて、他の SPIFFE 準拠のシステムとも相互運用可能…なように読めます。
Connect を使ってみる
それでは、公式のConsul Dockerイメージ を使って、実際の Connect を使ってみようと思います。
Connect の有効化
Connect の機能は、-dev
のコマンドラインオプションを付与しない限りデフォルトで無効化されています。
以下のような設定を Consul Server 側で行うことで、Connect を利用可能になります。
{ "connect": { "enabled": true } }
公式の consul イメージは、環境変数 CONSUL_LOCAL_CONFIG
に設定を記述できるようになっています。このため、以下のような設定をしておけば、docker run
時に Connect を有効化できます。
export CONSUL_LOCAL_CONFIG='{"connect": { "enabled": true }}'
サーバ側で Sidecar Proxy を立ち上げる
まず最初に、以下のような consul server x 1、client x 2 のクラスタを構築しました。
# consul members Node Address Status Type Build Protocol DC Segment server1 172.18.0.3:8301 alive server 1.2.0 2 dc1 <all> client1 172.18.0.4:8301 alive client 1.2.0 2 dc1 <default> client2 172.18.0.2:8301 alive client 1.2.0 2 dc1 <default>
ここで、サーバ から service 設定を投入し、設定をリロードします。
[server]# cat <<EOF > /consul/config/socat.json { "service": { "name": "socat", "port": 8181, "connect": { "proxy": {} } } } EOF [server]# consul reload
ここの connect
の設定が、Connect に関する設定です。ここでは、socat サービス用に Manged Proxy を立ち上げるように指示しています。
このタイミングで ps を確認すると、consul connect proxy が起動していることが分かります。これが sidecar proxy (Managed Proxy)ですね。
[server]# ps | grep [p]roxy 46 consul 0:00 /bin/consul connect proxy
Sidecar Proxy を使ってみる
それでは早速、Sidecar Proxy を使ってみます。
まずは Proxy 先のサービスとして、server 上で Echo サーバを立ち上げます。 これで、server 上では Echo サーバと Sidecar Proxy の 2 つが立ち上がった状態です。
[server]$ socat -v tcp-l:8181,fork exec:"/bin/cat"
それでは、この Echo サーバに対して、client1 から接続を行ってみましょう。
クライアント側で、コマンドラインで Sidecar Proxy を作る
接続のためには、client1 上においても Sidecar Proxy を立ち上げる必要があります。これを簡易的に行うのが、consul connect proxy
コマンドです。さっき、自動的に立ち上がっていたヤツですね。
[client1]$ consul connect proxy -service web -upstream socat:9191 &
上記の設定により、client1 にも web
Service の元で、 Proxy 先として socat サービスが指定された Sidecar Proxy が 9191 ポートで立ち上がります。
それでは、この 9191 番に対して接続してみます。
[client1]$ nc localhost 9191
hello world
hello world
^D
上記のように、client1 が localhost:9191 向けに接続すると、それが socat サービスに接続され、応答が返却されています。
ここでのポイントは、client1 は、server
というホストの情報を指定していないということ。socat
という論理的なサービス名のみを指定すると、自動的に Service Discovery が行われ、server
上で動作している
socat サービスに接続できていることが分かります。client1・server の Sidebar Proxy 間の通信は TLS で暗号化されているはずです。
クライアント側で、設定ファイルで Sidecar Proxy を作る
上記では、コマンドラインで Sidecar Proxy を投入しました。 次に、設定ファイルで同様の設定を行いたいと思います。
これまでの consul と同様のサービス設定ですが、connect
プロパティ以下が Connect 用の新規設定項目になります。
upstreams
がプロキシ先ですね。今度は 10,000 ポートで Listen させてみます。
[client1]# cat <<EOF > /consul/config/web.json { "service": { "name": "web", "port": 8080, "connect": { "proxy": { "config": { "upstreams": [{ "destination_name": "socat", "local_bind_port": 10000 }] } } } } } EOF
あとは reload すれば、server 上の socat サービスに接続できることがわかります。
[client1]# consul reload [client1]# nc localhost 10000 hello consul connect! hello consul connect!
intention での通信制御
consul connect での通信制御は intention
と呼ばれるもので行われます。
intention による通信制御は、Sidecar Proxy から見たときの inbound で行われ、そのタイミングは、mTLS での認証後です。このタイミングで、authorize API
が呼ばれ、コネクションの確立可否が判定されます。
デフォルトでは、任意のサービスから任意のサービスに通信が可能になっています。
しかし、セキュリティにおいてはデフォルトは deny にせよってことになっているのでそこはご注意くださいませ…
今日は、デフォルトであろう allow の状態から、deny のルールを追加する形で進めます。
まず最初に現状を確認してみます。client1 では、web サービスが動作しています。
そして、cluster 全体で intention は設定されていません。このため、すべての通信は許可 (allow) されます。
[client1]# curl http://localhost:8500/v1/connect/intentions
[]
このように、intention については、このように REST API でも操作できますし、CLI や UI でも操作可能です。
この時点で、client1 からは socat サービスに接続ができています。
[client1]# nc localhost 10000
hello
hello
ここで、web -> socat への通信を禁止してみます。
[client1]# consul intention create -deny web socat Created: web => socat (deny)
UI を見ても、intention が追加されていることが分かります。
さて、実際の通信はどうでしょうか。
[client1]# nc localhost 10000
接続できないまま終了し、intention が効いていることが分かります。
では、web
サービスを立ち上げていない、client2 はどうでしょうか。
[client2]$ consul connect proxy -service kiririmode -upstream socat:8000 & [client2]$ nc localhost 8000 hello world hello world
ここでは、kiririmode
というサービスをでっちあげましたが、そうすると正しく socat サービスに接続できていることが分かります。
すごくおもしろいです。。。
まぁ、consul でセキュリティを語る上では、intention の前に ACL 設定があるので、そこから攻めた方が良さそうという話はあるんですが。
まとめ
というわけで、とりあえずは Sidecar Proxy と Intention を使ってみました。
ドキュメントを見る限りにおいては、証明書運用まわりや、性能面でもかなり面白そうなことになってるみたいです。
動作確認環境を作る
ここでは、以下のような Dockerfile を作成の上、それを利用した consul cluster を docker-compose で作成しました。
FROM consul:1.2.0 RUN set -ex \ && apk update \ && apk add --no-cache --virtual .deps \ socat \ bind-tools \ tcpdump
docker-compose.yaml
は以下なかんじ。
version: '3.2' services: server: &agent build: context: . dockerfile: Dockerfile image: kiririmode/consul:1.2.0 command: "agent -server -ui -bootstrap-expect 1 -retry-join consul_server_1 -client 0.0.0.0" environment: - 'CONSUL_LOCAL_CONFIG={"connect": { "enabled": true }}' hostname: server1 ports: - "8500:8500" volumes: - type: bind source: ./work target: /work client1: &client <<: *agent command: "agent -ui -client 0.0.0.0 -retry-join consul_server_1" hostname: client1 ports: - "8501:8500" client2: <<: *client hostname: client2 ports: - "8502:8500"