CoreDNS は、Go で実装された DNS サーバーであり、Kubernetes の DNS サーバーとしても利用されている。
特徴的な仕組みとして、プラグインという仕組みがあり、殆どの機能はプラグインで実装されており、プラグインを自分で実装することも可能である。
CoreDNS のローカルでのビルドおよび実行は、簡単に行うことができる。
具体的には、下記のように make コマンドを実行することで、ビルドを行える。
$ git clone https://github.com/coredns/coredns.git
$ cd coredns
$ make
ビルドが完了すると、coredns
というバイナリが生成されるので、実行することで CoreDNS が起動する。
$ ./coredns
maxprocs: Leaving GOMAXPROCS=12: CPU quota undefined
.:53
CoreDNS-1.12.0
darwin/amd64, go1.23.3, 177253340
CoreDNS では、Corefile という設定ファイルを読み込み、その設定に従って DNS サーバーを起動する。
$ cat Corefile
.:10053 {
forward . 1.1.1.1
log
errors
}
$ ./coredns
maxprocs: Leaving GOMAXPROCS=12: CPU quota undefined
.:10053
CoreDNS-1.12.0
darwin/amd64, go1.23.3, 177253340
[INFO] 127.0.0.1:63479 - 32124 "A IN example.com. udp 40 false 4096" NOERROR qr,rd,ra,ad 67 0.005239353s
上記のような Corefile を用意することで、下記のような動作を行うようになる。
errors プラグインが有効でない場合、デフォルトではエラーログは出力されないので、エラーを確認したいなら有効にしましょう。
もし、Corefile が存在しない場合は、デフォルトの設定で起動するようになっており、whoami と log プラグインが有効になっている。
caddy.RegisterServerType(serverType, caddy.ServerType{
Directives: func() []string { return Directives },
DefaultInput: func() caddy.Input {
return caddy.CaddyfileInput{
Filepath: "Corefile",
Contents: []byte(".:" + Port + " {\nwhoami\nlog\n}\n"),
ServerTypeName: serverType,
}
},
NewContext: newContext,
})
Corefile は、Caddyfile と同じようにパースされるものとなっており、環境変数等を使用することが可能である。
なお、内部的に使用している coredns/caddy は caddyserver/caddy の v1 のフォークであるため、v2 の機能を利用することはできない。
forward . {$FORWARDS}
$ FORWARDS=1.1.1.1 ./coredns
また、複数のゾーンを指定することができ、それぞれのゾーンに対して別の設定を行うこともできる。
たとえば、example.net では hosts プラグインを有効にし、それ以外では forward プラグインを有効にするような設定を行うことができる。
.:10053 {
forward . 1.1.1.1
log
errors
}
example.net:10053 {
hosts {
127.0.0.1 example.net
}
log
errors
}
Corefile でデフォルトで使うことができるプラグインは、Plugins から確認することができる。
これらのプラグインは、plugin に実装があり、plugin.cfg に定義されているプラグインがビルド時に追加されるようになっている。
また、External Plugins に記載されているプラグインは、plugin.cfg に定義されていない外部のプラグインとなるため、自分で追記した上でビルドする必要がある。
では、具体的にどのように plugin.cfg に定義されているプラグインがビルド時に追加されるかというと、go generate
コマンドを実行することで、plugin.go で定義されている directives_generate.go が plugin.cfg を読み込んでコードを生成するようになっている。
その結果、 core/dnsserver/zdirectives.go および core/plugin/zplugin.go が生成されて、プラグインを使用することができるようになる。
Configuration に記載があるとおり、各プラグインが実行される順番は Corefile の順番ではなく、plugin.cfg に定義されている順番で実行される。
たとえば、cache プラグインが使用している場合、キャッシュが存在すれば、それ以降のプラグインは実行されない。
このため、Corefile に記載するプラグインの順番を気にする必要はない。
サンプルプラグインとして、example が提供されているので、これを使ってプラグインの追加を試すことができる。
plugin.cfg に下記のように追加し、make コマンドを実行することで、example プラグインが追加された状態でビルドされる。
また、前述したとおり、plugin.cfg に定義されているプラグインの順番は重要なため、今回の場合、example プラグインは forward プラグインより前に定義する必要がある。
example:github.com/coredns/example
forward:forward
$ git diff core/dnsserver/zdirectives.go core/plugin/zplugin.go
diff --git a/core/dnsserver/zdirectives.go b/core/dnsserver/zdirectives.go
index 56174955c..91ef82a7d 100644
--- a/core/dnsserver/zdirectives.go
+++ b/core/dnsserver/zdirectives.go
@@ -56,6 +56,7 @@ var Directives = []string{
"secondary",
"etcd",
"loop",
+ "example",
"forward",
"grpc",
"erratic",
diff --git a/core/plugin/zplugin.go b/core/plugin/zplugin.go
index 12bb4ce15..59bb64a51 100644
--- a/core/plugin/zplugin.go
+++ b/core/plugin/zplugin.go
@@ -57,4 +57,5 @@ import (
_ "github.com/coredns/coredns/plugin/tsig"
_ "github.com/coredns/coredns/plugin/view"
_ "github.com/coredns/coredns/plugin/whoami"
+ _ "github.com/coredns/example"
)
もし、プラグインが追加されていない場合は、CoreDNS の起動時に Corefile のパースに失敗する。
Corefile:6 - Error during parsing: Unknown directive 'example'
example プラグインでは、log.Info および log.Debug を使用しており、log.Debug の出力を確認するためには debug プラグインも有効にする必要がある。
.:10053 {
forward . 1.1.1.1
log
errors
debug
example
}
example プラグインを有効にした状態で、上記の Corefile を使用することで、クエリの実行時に example プラグインが実行されログが出力されるようになる。
[DEBUG] plugin/example: Received response
[INFO] plugin/example: example
[INFO] 127.0.0.1:65349 - 13760 "A IN example.com. udp 40 false 4096" NOERROR qr,rd,ra,ad 67 0.013614538s
もし、debug プラグインが有効でない場合、log.Debug は出力されない。
[INFO] plugin/example: example
[INFO] 127.0.0.1:49451 - 57280 "A IN example.com. udp 40 false 4096" NOERROR qr,rd,ra,ad 67 0.008662355s
プラグインを実装する方法については、Writing Plugins および How to Add Plugins to CoreDNS に記載がある。
より具体的には、plugin.Handler interface を実装し、AddPluggin でプラグインを登録することで実装できる。
example プラグインがわかりやすいので参考にすると良い。
はじめに、setup を通して、plugin.Handler interface を実装している Example struct をプラグインとして追加している。
// Add the Plugin to CoreDNS, so Servers can use it in their plugin chain.
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
return Example{Next: next}
})
ServeDNS では、プラグインが実行された際の処理を実装している。
また、plugin.NextOrFailure を使用することで、次のプラグインに処理を委譲することができる。
このため、plugin.NextOrFailure を意図的に呼びださないことで、プラグインチェーンの途中で処理を止めることができる。
func (e Example) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
また、各プラグインでは、ログを出力するために、github.com/coredns/coredns/plugin/pkg/log を使用することができる。
このパッケージは、Go の log パッケージをラップしたもので、ログレベルのプレフィックスをつけたり、debug プラグインによって有効化されるデバッグログの管理も行なっている。
例として、いくつかのプラグインの実装を見ていく。
cache プラグインは、TTL をもとにクエリの結果をキャッシュすることができる。
どのようにキャッシュを行なっているかというと、plugin/cache/handler.go で dns.ResponseWriter
のラッパーを作成し処理を移譲、その後のプラグインで WriteMsg()
が呼ばれたらラッパーの WriteMsg()
が呼ばれて、キャッシュするという仕組みである。
forward と cache プラグインを有効化した際の具体的な処理の流れとしては、下記のようになっている。
dns.ResponseWriter
のラッパーを作成するplugin.NextOrFailure
を呼び出し、次のプラグインに処理を委譲するWriteMsg()
を呼び出すWriteMsg()
でキャッシュを行い、dns.ResponseWriter
の WriteMsg()
を呼び出すログを確認するとわかるとおり、キャッシュ後はレスポンスが早くなっている。
[INFO] 127.0.0.1:64375 - 49069 "A IN example.com. udp 40 false 4096" NOERROR qr,rd,ra,ad 56 0.019756003s
[INFO] 127.0.0.1:50379 - 49563 "A IN example.com. udp 40 false 4096" NOERROR qr,aa,rd,ra,ad 56 0.000075345s
ready プラグインは、/ready
エンドポイントを有効にし、すべてのプラグインが使用可能なら OK、そうでなければ使用可能ではないプラグインを出力する。
どういう時に使うかというと、Kuberentes において CoreDNS がリクエストを受ける準備が完了したか確認するための readinessProbe を設定するために使用する。
$ curl -i localhost:8181/ready
HTTP/1.1 200 OK
Date: Wed, 01 Jan 2025 16:24:44 GMT
Content-Length: 2
Content-Type: text/plain; charset=utf-8
OK
$ curl -i localhost:8181/ready
HTTP/1.1 503 Service Unavailable
Date: Wed, 01 Jan 2025 16:24:16 GMT
Content-Length: 10
Content-Type: text/plain; charset=utf-8
errors,log
より具体的には、各プラグインで Readiness interface を実装することで、ready プラグインはそれを使用して、プラグインが使用可能かどうかを確認する。
なお、errors や log プラグインは、Readiness interface を実装していないので、上記の結果になることはない。
// The Readiness interface needs to be implemented by each plugin willing to provide a readiness check.
type Readiness interface {
// Ready is called by ready to see whether the plugin is ready.
Ready() bool
}
prometheus プラグインは、prometheus および各プラグインが集計したメトリクスを prometheus 形式で出力するプラグインである。
メトリクスは、localhost:9153/metrics
で公開されるようになっている。
$ curl -s localhost:9153/metrics | grep coredns_cache_hits_total
# HELP coredns_cache_hits_total The count of cache hits.
# TYPE coredns_cache_hits_total counter
coredns_cache_hits_total{server="dns://:10053",type="success",view="",zones="."} 9
実装としてはシンプルで、promhttp パッケージを使用して /metrics に対する HTTP ハンドラを登録、coredns_dns_requests_total などのメトリクスを集計している。
また、各プラグインでは、promauto パッケージを使用してメトリクスを定義しているので、このプラグインを有効化するだけでメトリクスを収集することができる。
たとえば、cache プラグインでは、plugin/cache/metrics.go にメトリクスを定義している。
// cacheHits is counter of cache hits by cache type.
cacheHits = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: "cache",
Name: "hits_total",
Help: "The count of cache hits.",
}, []string{"server", "type", "zones", "view"})