CoreOS クラスタ上で SkyDNS2 を使ってサービスディスカバリする


## 概要
CoreOS 上でのサービスディスカバリに SkyDNS2 を利用するという話。SkyDNS2 は Docker コンテナとして動かす。

### SkyDNS2 とは
DNS サーバの一つ。データの保存先に etcd を使うことで分散 DNS サーバを実現してる。例えば、 5 台の CoreOS マシンからなるクラスタがあるとき、各マシンで SkyDNS2 を起動させておく。etcd にデータを置いているので、どのマシンの SkyDNS2 を利用しても同じ結果になる。

そして、後述する SRV レコードを返すことで単なる DNS サーバというよりはサービスディスカバリーを実現するためのサービスという位置づけになっている。

## サービスディスカバリーに SRV レコードを使う
SRV レコードのポイントは

* ポート情報まで持ってること
* 負荷分散できること

### ポート情報まで持っていること
DNS は通常名前 <---> IP の解決をするだけなので、あるサービス使いたい場合、「どの IP のどこのポートに繋げば良い？」みたいな場合に利用出来ない。が `SRV` レコード を使うと、IP+ポート情報まで持たせられるのでサービスディスカバリーに利用出来る。

```
# 例
$ dig SRV 1.rails.skydns.local

;; ANSWER SECTION:
1.rails.skydns.local. 3600	IN	SRV	10 100 8080 1.rails.skydns.local.

# ANSWER SECTION は以下の内容が書かれている
# service name, environment, TTL, priority, weight, service port, host name
```

さらに、 SRV レコードは負荷分散にも利用できる。

### 負荷分散できること
SRV レコードは、

* Priority
* Weight

という情報も持たせられる。 Priority の値の小さい方から優先的に通信される。 Weight は同じ優先順位のホスト郡での振り分け具合を決められる。

## SkyDNS2 を使ったサービスディスカバリ
### SkyDNS2 の Docker イメージ作成

```bash
# on CoreOS machine
core@core-01 ~ $ git clone https://github.com/skynetservices/skydns.git
core@core-01 ~ $ cd skydns
```

同梱されている `Dockerfile` は language stack を使っていない。なるだけ language stack ベースのイメージを使いたいので以下のように編集してビルドした。

```
# Dockerfile
FROM golang:1.3.3
MAINTAINER Miek Gieben <miek@miek.nl> (@miekg)

ADD . /go/src/github.com/skynetservices/skydns
RUN go get github.com/skynetservices/skydns

EXPOSE 53
ENTRYPOINT ["skydns"]
```

```bash
core@core-01 ~ $ docker build -t skydns2 .
```

### SkyDNS のデフォルト設定を etcd にセット

```bash
core@core-01 ~ $ curl -L -XPUT http://172.17.8.101:4001/v2/keys/skydns/config -d value='{"nameservers": ["8.8.8.8:53","8.8.4.4:53"]}'
```

### SkyDNS2 コンテナ起動

```bash
# SkyDNS2 の UDP 53 ポートをホストの Private IPの 53 ポートに bind 
core@core-01 ~/skydns $ docker run \
  -d \
  --name skydns2 \
  -p 172.17.8.101:53:53/udp \
  -e ETCD_MACHINES='172.17.8.101:4001,172.17.8.102:4001,172.17.8.103:4001' \
  skydns2:latest \
  -addr=0.0.0.0:53

core@core-01 ~ $ docker logs skydns2
[skydns] Oct 14 03:26:33.702 INFO      | ready for queries on skydns.local. for tcp://0.0.0.0:53 [rcache 0]
[skydns] Oct 14 03:26:33.702 INFO      | ready for queries on skydns.local. for udp://0.0.0.0:53 [rcache 0]
[skydns] Oct 14 03:27:51.679 INFO      | no nameservers defined or name too short, can not forward
[skydns] Oct 14 03:27:51.679 INFO      | no nameservers defined or name too short, can not forward
```

これで Core-01 の Private IP (172.17.8.101) を DNS nameserver として利用出来るになる

### etcd にサービス情報をセット
Core-01 上に起動している logspout を `logspout.1` のように、3 台登録することにする

`logspout.1.json`

```json
{
  "host":"172.17.8.101",
  "port":8080
}
```

`logspout.2.json`

```json
{
  "host":"172.17.8.102",
  "port":8080
}
```

`logspout.3.json`

```json
{
  "host":"172.17.8.103",
  "port":8080
}
```

etcd にセット

```bash
core@core-01 ~ $ curl -L -XPUT http://172.17.8.101:4001/v2/keys/skydns/local/skydns/logspout/1 --data-urlencode value@logspout.1.json
{"action":"set","node":{"key":"/skydns/local/skydns/logspout/1","value":"{\n  \"host\":\"172.17.8.101\",\n  \"port\":8000\n}\n","modifiedIndex":9000,"createdIndex":9000},"prevNode":{"key":"/skydns/local/skydns/logspout/1","value":"","modifiedIndex":8995,"createdIndex":8995}}

core@core-01 ~ $ curl -L -XPUT http://172.17.8.101:4001/v2/keys/skydns/local/skydns/logspout/2 --data-urlencode value@logspout.2.json
{"action":"set","node":{"key":"/skydns/local/skydns/logspout/2","value":"{\n  \"host\":\"172.17.8.102\",\n  \"port\":8000\n}\n","modifiedIndex":8887,"createdIndex":8887}}

core@core-01 ~ $ curl -L -XPUT http://172.17.8.101:4001/v2/keys/skydns/local/skydns/logspout/3 --data-urlencode value@logspout.3.json
{"action":"set","node":{"key":"/skydns/local/skydns/logspout/3","value":"{\n  \"host\":\"172.17.8.103\",\n  \"port\":8000\n}\n","modifiedIndex":9022,"createdIndex":9022}}
```

### CoreOS マシン上のプライマリ DNS サーバを SkyDNS2 に変更する
SkyDNS2 での設定のとおり、ドメインの権威が無い問い合わせが届いた時は 8.8.8.8:53 と 8.8.4.4:53 に転送するので、SkyDNS2 プライマリ DNS サーバとして扱える。

```
core@core-01 ~ $ cat /etc/resolv.conf
# This file is managed by systemd-resolved(8). Do not edit.
#
# Third party programs must not access this file directly, but
# only through the symlink at /etc/resolv.conf. To manage
# resolv.conf(5) in a different way, replace the symlink by a
# static file or a different symlink.

#nameserver 10.0.2.3
nameserver 172.17.8.101 # -> 書き換え
```

（直に書き換えるのは良くはない）

### CoreOS マシン（コンテナホスト）から ping してみる
ping できるか確認してみる
（CoreOS には、dig が入ってない。）

```bash
core@core-01 ~ $ ping 1.logspout.skydns.local
PING 1.logspout.skydns.local (172.17.8.101) 56(84) bytes of data.
64 bytes from 172.17.8.101: icmp_seq=1 ttl=64 time=0.113 ms
64 bytes from 172.17.8.101: icmp_seq=2 ttl=64 time=0.031 ms
64 bytes from 172.17.8.101: icmp_seq=3 ttl=64 time=0.039 ms
64 bytes from 172.17.8.101: icmp_seq=4 ttl=64 time=0.032 ms
64 bytes from 172.17.8.101: icmp_seq=5 ttl=64 time=0.048 ms
```

```bash
# Core-02 の logspout に振り分けされた
core@core-01 ~ $ ping logspout.skydns.local
PING logspout.skydns.local (172.17.8.102) 56(84) bytes of data.
64 bytes from 172.17.8.102: icmp_seq=1 ttl=64 time=0.369 ms
64 bytes from 172.17.8.102: icmp_seq=2 ttl=64 time=0.437 ms
64 bytes from 172.17.8.102: icmp_seq=3 ttl=64 time=0.409 ms
64 bytes from 172.17.8.102: icmp_seq=4 ttl=64 time=0.429 ms

# 今後は Core-03 に振り分けされた。
# 接続するごとに接続先ホストが変わる = 負荷分散されてることがわかる
core@core-01 ~ $ ping logspout.skydns.local
PING logspout.skydns.local (172.17.8.103) 56(84) bytes of data.
64 bytes from 172.17.8.103: icmp_seq=1 ttl=64 time=0.290 ms
64 bytes from 172.17.8.103: icmp_seq=2 ttl=64 time=0.417 ms
64 bytes from 172.17.8.103: icmp_seq=3 ttl=64 time=0.477 ms
```

### ruby コンテナから SRV レコードを取得してみる

```ruby
#!/usr/local/bin/ruby
# get_srv.rb

require 'resolv'
require 'pp'

resolver = Resolv::DNS.new(:nameserver => ['172.17.8.101'], :search => ['skydns.local'], :ndots => 1)
hosts = resolver.getresources('logspout.skydns.local', Resolv::DNS::Resource::IN::SRV)
pp hosts
puts ""
pp hosts.map{|h| (h.target.to_s + ":" + h.port.to_s) }
```

```bash
$ ruby get_srv.rb
```

```ruby
[#<Resolv::DNS::Resource::IN::SRV:0x007f47882f6620
  @port=8000,
  @priority=10,
  @target=#<Resolv::DNS::Name: 1.logspout.skydns.local.>,
  @ttl=3600,
  @weight=33>,
 #<Resolv::DNS::Resource::IN::SRV:0x007f47882f5518
  @port=8080,
  @priority=10,
  @target=#<Resolv::DNS::Name: 2.logspout.skydns.local.>,
  @ttl=3600,
  @weight=33>,
 #<Resolv::DNS::Resource::IN::SRV:0x007f47882f45c8
  @port=8080,
  @priority=10,
  @target=#<Resolv::DNS::Name: 3.logspout.skydns.local.>,
  @ttl=3600,
  @weight=33>]

["1.logspout.skydns.local:8000",
 "2.logspout.skydns.local:8080",
 "3.logspout.skydns.local:8080"]
```

### ubuntu:14.04 コンテナから ping してみる

```bash
# on CoreOS
core@core-01 ~ $ docker run -it --rm --dns=172.17.8.101 ubuntu:14.04 /bin/bash
```

```bash
# コンテナ内から ping してみる
root@91fcca071b27:/$ ping 1.logspout.skydns.local
PING 1.logspout.skydns.local (172.17.8.101) 56(84) bytes of data.
64 bytes from 172.17.8.101: icmp_seq=1 ttl=64 time=0.029 ms
64 bytes from 172.17.8.101: icmp_seq=2 ttl=64 time=0.038 ms
64 bytes from 172.17.8.101: icmp_seq=3 ttl=64 time=0.063 ms
64 bytes from 172.17.8.101: icmp_seq=4 ttl=64 time=0.053 ms
```

### ubuntu:14.04 コンテナから dig してみる

image ubuntu:14.04 では dig が入ってないので入れる

```bash
# in ubuntu:14.04, install dig
root@952708774c41:/$ sudo apt-get update && sudp apt-get install dnsutils
```

`skydns.local` のドメイン情報取得

```bash
root@41ea3b47bc69:/$ dig skydns.local

; <<>> DiG 9.9.5-3-Ubuntu <<>> skydns.local
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29922
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;skydns.local.			IN	A

;; ANSWER SECTION:
skydns.local.		3600	IN	A	172.17.8.101

;; Query time: 17 msec
;; SERVER: 172.17.8.101#53(172.17.8.101)
;; WHEN: Tue Oct 14 09:44:11 UTC 2014
;; MSG SIZE  rcvd: 46
```

`1.logspout.skydns.local` の SRV 情報を取得。先ほど etcd にセットした `/skydns/local/logspout/1` の情報が返ってくる。


```bash
root@41ea3b47bc69:/# dig SRV 1.logspout.skydns.local

; <<>> DiG 9.9.5-3-Ubuntu <<>> SRV 1.logspout.skydns.local
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51547
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; QUESTION SECTION:
;1.logspout.skydns.local.	IN	SRV

;; ANSWER SECTION:
1.logspout.skydns.local. 3600	IN	SRV	10 100 8000 1.logspout.skydns.local.

;; ADDITIONAL SECTION:
1.logspout.skydns.local. 3600	IN	A	172.17.8.101

;; Query time: 13 msec
;; SERVER: 172.17.8.101#53(172.17.8.101)
;; WHEN: Tue Oct 14 10:33:28 UTC 2014
;; MSG SIZE  rcvd: 100
```


wildcard も利用できる。
`*.logspout.skydns.local` の SRV 情報を取得。先ほど etcd にセットした `/skydns/local/logspout/*` の情報が返ってくる。

```bash
root@41ea3b47bc69:/$ dig SRV *.logspout.skydns.local

; <<>> DiG 9.9.5-3-Ubuntu <<>> SRV *.logspout.skydns.local
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2579
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 3

;; QUESTION SECTION:
;*.logspout.skydns.local.	IN	SRV

;; ANSWER SECTION:
*.logspout.skydns.local. 3600	IN	SRV	10 33 8000 1.logspout.skydns.local.
*.logspout.skydns.local. 3600	IN	SRV	10 33 8080 2.logspout.skydns.local.
*.logspout.skydns.local. 3600	IN	SRV	10 33 8080 3.logspout.skydns.local.

;; ADDITIONAL SECTION:
1.logspout.skydns.local. 3600	IN	A	172.17.8.101
2.logspout.skydns.local. 3600	IN	A	172.17.8.102
3.logspout.skydns.local. 3600	IN	A	172.17.8.103

;; Query time: 12 msec
;; SERVER: 172.17.8.101#53(172.17.8.101)
;; WHEN: Tue Oct 14 10:28:25 UTC 2014
;; MSG SIZE  rcvd: 224
```

```bash
root@41ea3b47bc69:/$ dig SRV logspout.skydns.local

; <<>> DiG 9.9.5-3-Ubuntu <<>> SRV logspout.skydns.local
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44941
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 3

;; QUESTION SECTION:
;logspout.skydns.local.		IN	SRV

;; ANSWER SECTION:
logspout.skydns.local.	3600	IN	SRV	10 33 8000 1.logspout.skydns.local.
logspout.skydns.local.	3600	IN	SRV	10 33 8080 2.logspout.skydns.local.
logspout.skydns.local.	3600	IN	SRV	10 33 8080 3.logspout.skydns.local.

;; ADDITIONAL SECTION:
1.logspout.skydns.local. 3600	IN	A	172.17.8.101
2.logspout.skydns.local. 3600	IN	A	172.17.8.102
3.logspout.skydns.local. 3600	IN	A	172.17.8.103

;; Query time: 13 msec
;; SERVER: 172.17.8.101#53(172.17.8.101)
;; WHEN: Tue Oct 14 10:28:59 UTC 2014
;; MSG SIZE  rcvd: 222
```

## サービス情報の自動登録
[CoreOS 上の Docker コンテナのサービス情報を registrator を使って SkyDNS2 に自動登録する](http://qiita.com/spesnova/items/1d384f80a76d8fa5c23f) を参照


## メモ
### クライアント側で SRV レコードを使うようにしないといけない
SkyDNS2 で「あるサービスが、このホスト郡のこのポートで起動してるよ」というのは取得できるようになったが、クライアント側で、 SRV レコードを利用して接続するようにしないといけない。

### 負荷分散できるの便利
最初は、`1.rails.skydns.local` でサービス情報がとれても、実際には、rails コンテナはその時その時で何台あるかわからない状態。番号まで指定して情報を取得するのは大変だなと思っていたが、`*.rails.skydns.local` という感じでサービス情報郡を取得でき、さらに負荷分散までできる。クライアント側で SRV レコードを使うようにしさえすれば非常に便利だと思った。

## REF

* [skynetservices/skydns](https://github.com/skynetservices/skydns)
* [SKYDNS VERSION 2](http://miek.nl/posts/2014/Jun/08/announcing%20SkyDNS%20version%202/)
* [Docker container won't connect to etcd in another container on same host #42](https://github.com/skynetservices/skydns/issues/42)
* [SkyDNS2のセットアップ方法](http://qiita.com/zembutsu/items/626c55cc702d54f7fa65)
* [etcd API](https://coreos.com/docs/distributed-configuration/etcd-api/)
 * Setting a key from a file
* [Some application clients use SRV lookups, a few (to their embarrassment) do not.](http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/dns-srv-record-use-by-clients.html)
* [RFC2782の日本語訳（和訳）](http://www5d.biglobe.ne.jp/~stssk/rfc/rfc2782j.html)
* [SRV record lookups returning more than 4 results are not queryable by Ruby DNS resolver](https://github.com/skynetservices/skydns/issues/69)