0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CoreDNSプラグイン開発入門

Last updated at Posted at 2025-04-14

CoreDNSとは何か?

CoreDNSは、Go言語で実装された柔軟で拡張可能なDNSサーバーです (CoreDNS の仕組みとプラグイン - ストイックに生きたい) (Using CoreDNS for Service Discovery | Kubernetes)。もともとCaddyサーバーの仕組みを基に構築されており、CoreDNS自体のほとんどの機能は「プラグイン」 として提供されています (CoreDNS の仕組みとプラグイン - ストイックに生きたい)。たとえばゾーンファイルの提供やキャッシュ、メトリクス収集といった機能はすべてプラグインとして実装されており、必要なプラグインを組み合わせてDNSサーバーの振る舞いをカスタマイズできます ( Writing Plugins for CoreDNS )。CoreDNSはCNCF (Cloud Native Computing Foundation) にホストされており、KubernetesクラスターのデフォルトDNSサーバー(kube-dnsの後継)としても利用されています (CoreDNS の仕組みとプラグイン - ストイックに生きたい) (Using CoreDNS for Service Discovery | Kubernetes)。

CoreDNSにおけるプラグインの役割

CoreDNSにおけるプラグインは、DNSサーバーの機能をモジュール単位で拡張・変更する役割を担います (CoreDNS の仕組みとプラグイン - ストイックに生きたい)。プラグインを追加・有効化することで、DNSサーバーに新たな機能を簡単に組み込めます。例えば:

プラグインはCorefile(CoreDNSの設定ファイル)に記述することで有効化され、記述されたプラグインの組み合わせによってDNSサーバーの動作が決定します (CoreDNS の仕組みとプラグイン - ストイックに生きたい) (CoreDNS の仕組みとプラグイン - ストイックに生きたい)。必要な機能だけをプラグインとして組み込み、不要な機能は外して軽量化することも可能です。このプラグイン機構によって、CoreDNSは非常に高い拡張性と柔軟性を実現しています。

有名なプラグイン例: Kubernetesプラグイン

数ある公式プラグインの中でもkubernetesプラグインは特に有名で、Kubernetes環境におけるサービスディスカバリ(内部DNS)を実現する重要なプラグインです (Using CoreDNS for Service Discovery | Kubernetes)。Kubernetesプラグインを有効にしたCoreDNSは、クラスタ内部のServiceやPodに対応するDNSレコードを自動的に提供します。これにより、Kubernetes内のアプリケーションは<サービス名>.<ネームスペース>.svc.cluster.localといった名前でお互いを名前解決できます。

なぜKubernetesプラグインが必要か? 従来、Kubernetesではkube-dnsというコンポーネントが内部DNSを提供していましたが、CoreDNSはその後継として採用されました (Using CoreDNS for Service Discovery | Kubernetes)。CoreDNS本体はプラグインの組み合わせで動作を変えられるため、Kubernetesに特化したプラグインを追加するだけでクラスタDNSの要件を満たせます。kubernetesプラグインはKubernetes APIと連携し、サービスやエンドポイントの変更を監視してDNSレコードに反映します。CoreDNS起動時にはこのプラグインがKubernetes APIサーバーへの接続を確立し、リソース(ServiceやPod情報)のウォッチを開始します ( kubernetes )。初期同期が完了するまでは最大5秒間DNS応答を遅らせ、同期完了後にクエリ応答を開始します ( kubernetes )(5秒経っても同期できない場合は同期が続く間SERVFAILで応答)。このようにしてクラスタ内のリソース変化(新規サービス追加やPod消滅など)に追随し、常に最新のDNSレコードを提供します。また、エンドポイント情報取得には効率化のためKubernetesのEndpointSlices APIを利用しています ( kubernetes )。

kubernetesプラグインはバックエンド系プラグインの一例であり、外部システム(Kubernetes)をデータソースとしてDNSレコードを生成する役割を持ちます ( How Queries Are Processed in CoreDNS )。このプラグインのおかげで、Kubernetesクラスターでは各種サービスを名前でスムーズに発見でき、CoreDNSはクラウドネイティブ環境におけるサービスディスカバリの中核を担っています。

CoreDNSプラグインの仕組み (アーキテクチャ)

CoreDNSでは、プラグインは**チェイン(鎖状)**に連結されてDNSクエリを処理します。各プラグインは順番に呼び出され、必要に応じて次のプラグインに処理を渡しながら最終的な応答を生成します ( How Queries Are Processed in CoreDNS ) ( How Queries Are Processed in CoreDNS )。Corefileに記述された各サーバーブロック(ゾーンごとの設定)が、内部的にはひとつのプラグインチェイン(dnsserver.Server)となります。問い合わせが来ると、そのクエリにマッチするゾーンのチェイン上でプラグインが順次実行されます ( How Queries Are Processed in CoreDNS ) ( How Queries Are Processed in CoreDNS )。プラグインの実行順序はCoreDNSをビルドする際に決まっており、plugin.cfgファイルに列挙された順番がそのままチェインの順序になります ( How Queries Are Processed in CoreDNS )。したがって、Corefile上の記述順と実行順が必ずしも一致しない点には注意が必要です(例えばcacheプラグインはCorefile下部に書かれていても、ビルド時の順序で後段に配置されていれば最終処理ではありません ( How Queries Are Processed in CoreDNS ))。

( How Queries Are Processed in CoreDNS )CoreDNSにおけるプラグインチェインの概念図。上図のように、CoreDNSはCorefileの各サーバーブロックに対応してプラグインチェインを構成し、問い合わせは該当するチェイン上の各プラグインを順に通過して処理されます ( How Queries Are Processed in CoreDNS ) ( How Queries Are Processed in CoreDNS )。プラグインには種類があり、一部はリクエスト処理自体を行わず設定のために存在します(例: rootstartupなどは実行時の処理チェインに入らない) ( How Queries Are Processed in CoreDNS )。通常のプラグインはリクエストを何らか処理した後、基本的には次のプラグインへ処理を渡します ( How Queries Are Processed in CoreDNS )。たとえば前述のrewriteプラグインはクエリを書き換えた後で次のプラグインに渡し、チェイン後段で応答が戻ってきたら質問セクションを書き換える前の状態に戻してクライアントに返します ( How Queries Are Processed in CoreDNS )。一方、バックエンド系プラグイン(例: kubernetesやfileなどゾーンデータを返すもの)はそれ自身が権威データソースとなりうるため、基本的に自分で回答(もしくはNXDOMAIN)を返してチェインを終端させます ( How Queries Are Processed in CoreDNS )。ただし、バックエンド系でも fallthroughオプション が指定されると、該当ゾーンにレコードが無い場合にNXDOMAINの代わりに次のプラグインに処理を委譲できます ( How Queries Are Processed in CoreDNS )。

プラグインは実装上、Goのインターフェースであるplugin.Handlerを満たすことで定義されます。plugin.HandlerはHTTPサーバーのハンドラに似たインターフェースで、DNSクエリ用のServeDNSメソッド(引数にDNSクエリとコンテキスト、レスポンスライターを取る)とName()メソッド(プラグイン名を返す)から構成されています ( Writing Plugins for CoreDNS ) ( Writing Plugins for CoreDNS )。開発者はプラグインごとに構造体を定義し、このインターフェースを実装します。典型的には以下のようになります ( Writing Plugins for CoreDNS ):

// プラグインのハンドラ構造体(Nextは次のプラグインへの参照)
type MyPlugin struct {
    Next plugin.Handler
    // 他に必要な設定フィールドがあれば追加
}

// プラグイン名を返す
func (m MyPlugin) Name() string { return "myplugin" }

// DNSクエリを処理するメソッド
func (m MyPlugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, req *dns.Msg) (int, error) {
    // (ここで何らかの処理を行う)
    return m.Next.ServeDNS(ctx, w, req)  // 次のプラグインに処理を委ねる
}

上記のように、自作プラグインのServeDNS内で基本は次のプラグイン (m.Next) のServeDNSを呼び出して処理を渡します ( Writing Plugins for CoreDNS )。これにより、現在のプラグインがチェイン上のフィルタのように機能します。ただし、プラグインが最終的な応答を自分で返せる場合(例えば特定のドメインに対して固定のIPを返すカスタムプラグインなど)は、次のプラグインを呼ばずにここで応答を返してチェインを終了しても構いません (CoreDNS の仕組みとプラグイン - ストイックに生きたい)。応答を返す際には、github.com/miekg/dnsパッケージを用いてDNSメッセージを作成し(例: Aレコードのdns.Msgを構築)、dns.ResponseWriterWriteMsg()でクライアントに返送します (Developing Custom Plugins for CoreDNS - DEV Community) (Developing Custom Plugins for CoreDNS - DEV Community)。一方、次のプラグインに処理を渡す場合でも、plugin.NextOrFailure(pluginName, Next, ctx, w, req)といったヘルパーを使うことで、万一チェインの次にプラグインが存在しない場合でも適切に処理できます (example/example.go at master · coredns/example · GitHub) (CoreDNS の仕組みとプラグイン - ストイックに生きたい)。

各プラグインでは、必要に応じてログ出力やメトリクスカウントも行えます。CoreDNSにはplugin/pkg/logというログパッケージが用意されており、これを使ってプラグイン名付きのロガーを簡単に生成できます (example/example.go at master · coredns/example · GitHub) (CoreDNS の仕組みとプラグイン - ストイックに生きたい)。log := clog.NewWithPlugin("plugin名")のように設定すれば、log.Info(...)log.Debug(...)で情報やデバッグログを出力可能です (Developing Custom Plugins for CoreDNS - DEV Community) (Developing Custom Plugins for CoreDNS - DEV Community)。デバッグログはdebugプラグインをCorefileで有効化することで出力されるようになります (CoreDNS の仕組みとプラグイン - ストイックに生きたい) (CoreDNS の仕組みとプラグイン - ストイックに生きたい)。

CoreDNSプラグイン開発の手順 (ステップバイステップ)

それでは、CoreDNSのプラグインを自作する具体的な手順を順を追って説明します。ここではGo言語に習熟しているがCoreDNSプラグイン開発は初めてという読者を想定し、基本的な流れを示します (CoreDNS の仕組みとプラグイン - ストイックに生きたい)。

  1. プラグインの雛形作成: 開発を始める前に、プラグインのプロジェクトディレクトリ(Goモジュール)を用意します。公式リポジトリにはexampleという最小実装のサンプルプラグインが提供されており、参考になります (CoreDNS の仕組みとプラグイン - ストイックに生きたい) (CoreDNS の仕組みとプラグイン - ストイックに生きたい)。まずはこのような雛形をベースに、自分のプラグイン用にパッケージを作成しましょう。例としてプラグイン名をfooとします。

  2. プラグインの登録 (init関数): CoreDNSに新しいプラグインを認識させるには、プラグインのパッケージでplugin.Register()を呼び出して登録する必要があります。これは通常、プラグインパッケージのinit()関数内で行います ( Writing Plugins for CoreDNS )。例えば以下のように記述します:

    package foo
    
    import "github.com/coredns/coredns/plugin"
    
    func init() {
        plugin.Register("foo", setup)
    }
    

    このplugin.Register("foo", setup)により、CoreDNSはCorefile内でfooというディレクティブ(プラグイン名)を見つけた際に対応するセットアップ関数を呼び出すようになります ( Writing Plugins for CoreDNS )。プラグイン名はコアプラグインと重複しないユニークなものを付けましょう。

  3. セットアップ関数の実装: 次に、上で登録したsetup関数を実装します。シグネチャはfunc setup(c *caddy.Controller) errorで、Corefileの該当部分のパースとプラグインの初期設定を行います ( Writing Plugins for CoreDNS )。caddy.Controller(CoreDNSはCaddyサーバーv1由来のコンフィグパーサーを使用)は、トークン化された設定行を提供します。一般的な実装としては、次のようになります ( Writing Plugins for CoreDNS ) (example/setup.go at master · coredns/example · GitHub)。

    func setup(c *caddy.Controller) error {
        // プラグイン名トークンを読み飛ばし、次のトークンへ
        c.Next() 
        // 必要に応じて引数をパース
        if c.NextArg() {
            arg := c.Val()  // 例えば一つだけ引数を取る場合
            // ... arg を使った設定処理 ...
        }
        if c.NextArg() {
            // 想定外の追加引数があればエラーを返す
            return c.ArgErr()
        }
        // プラグインチェインに自分のプラグインを追加
        dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
            return &FooPlugin{Next: next /*, 他のフィールド設定 */}
        })
        return nil
    }
    

    上記では、c.Next()でディレクティブ名(foo)をスキップし、c.NextArg()で必要な引数を1つ読み取っています ( Writing Plugins for CoreDNS )。もし引数が期待数に満たない/多い場合はc.ArgErr()でエラーを返し、CoreDNSの起動プロセスにエラーを伝えます ( Writing Plugins for CoreDNS )。最後に、dnsserver.GetConfig(c).AddPlugin(...)を呼び出してこのプラグインをチェインに登録しています (CoreDNS の仕組みとプラグイン - ストイックに生きたい)。AddPluginには次のプラグインを受け取って自分のハンドラを返す関数を渡します。上記例では、無名関数内で自分のプラグイン構造体(FooPlugin)を生成し、フィールドNextに渡されたnextハンドラをセットして返しています (example/setup.go at master · coredns/example · GitHub)。これにより、CoreDNSのプラグインチェイン中にfooプラグインが組み込まれることになります。

  4. プラグインハンドラの実装: プラグインの主要部分であるplugin.Handlerインターフェースを実装します。まず構造体を定義します。例えば:

    type FooPlugin struct {
        Next plugin.Handler      // 次のプラグイン(チェインの後続)
        Option string            // 何らかのオプション(引数)例
    }
    

    ここでOptionは設定で受け取った引数など、プラグインが動作に必要なデータを保持するためのフィールドです(必要なものを自由に追加できます)。次に、この構造体にName()メソッドとServeDNS()メソッドを実装します。

    func (f *FooPlugin) Name() string { 
        return "foo" 
    }
    
    func (f *FooPlugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, req *dns.Msg) (int, error) {
        // 例: クエリ情報を取得してログ出力
        state := request.Request{W: w, Req: req}
        log.Infof("Query for %s from %s", state.Name(), state.IP())
        // 他の処理 ... (必要ならreqを改変したりできます)
        // 次のプラグインに処理を委譲
        return plugin.NextOrFailure(f.Name(), f.Next, ctx, w, req)
    }
    

    Name()はプラグイン名を返すだけの簡単な関数です ( Writing Plugins for CoreDNS )。ServeDNS()はDNSクエリごとに呼ばれる処理本体で、この中でプラグイン固有のロジックを実行します (Developing Custom Plugins for CoreDNS - DEV Community)。上記の例では、request.Requestというヘルパー型を使ってクエリ名や依頼元IPを取得し、事前に用意したロガー(後述)で情報を記録しています。その後、plugin.NextOrFailureを用いて次のプラグインに処理を渡し、その戻り値(DNS応答コードとエラー)をそのまま返しています (example/example.go at master · coredns/example · GitHub)。自プラグインでクエリを完全に処理し応答を返す場合は、ここでw.WriteMsg(responseMsg)を呼んでdns.RcodeSuccessなどをリターンし、Nextを呼ばずに終了します (Developing Custom Plugins for CoreDNS - DEV Community) (CoreDNS の仕組みとプラグイン - ストイックに生きたい)。

  5. ログ出力やユーティリティの利用: 開発時には適宜ログを出力すると便利です。上記コード中のlogは、プラグインごとにロガーを生成したものです。プラグインではclog "github.com/coredns/coredns/plugin/pkg/log"をインポートし、var log = clog.NewWithPlugin("foo")のようにしておくと、ログメッセージに自動で[INFO] plugin/foo:のようなプレフィックスが付与されます (Developing Custom Plugins for CoreDNS - DEV Community) (example/example.go at master · coredns/example · GitHub)。log.Info(...)log.Debug(...)を使うことで情報・デバッグログを出力できます(デバッグログはCorefileでdebugプラグインを有効にした場合に出力されます (CoreDNS の仕組みとプラグイン - ストイックに生きたい))。また、github.com/miekg/dnsパッケージを利用してDNSレコードを表す構造体(例えばdns.Adns.TXTなど)を生成し応答に入れることもできます (Developing Custom Plugins for CoreDNS - DEV Community)。さらに、CoreDNS組み込みのメトリクス収集機能(Prometheus統合)を利用してクエリ数などをカウントすることも可能です。必要に応じて公式ドキュメントを参照してください (Developing Custom Plugins for CoreDNS - DEV Community)。

  6. プラグインのビルドと組み込み: プラグインの実装ができたら、それを含めてCoreDNSをビルドします。CoreDNSはGoのビルド時にプラグインを静的に組み込む設計になっており、動的ロードはできません。そのため2通りの方法があります ( Compile Time Enabling or Disabling Plugins ) ( Compile Time Enabling or Disabling Plugins )。

以上が基本的な開発の流れです。次章では、実際に非常にシンプルなプラグインのコード例を示します。

プラグイン実装例: リクエストをログに記録するプラグイン

最後に、CoreDNSプラグインの具体的な実装例としてDNSリクエストをログに記録するだけの簡単なプラグインを示します。これは前述のステップに沿って作成したものです。

// パッケージと必要なインポート
package foo

import (
    "context"

    "github.com/coredns/caddy"
    "github.com/coredns/coredns/core/dnsserver"
    "github.com/coredns/coredns/plugin"
    "github.com/coredns/coredns/request"
    clog "github.com/coredns/coredns/plugin/pkg/log"
    "github.com/miekg/dns"
)

// ロガーをプラグイン名付きで作成
var log = clog.NewWithPlugin("foo")

// プラグインの登録
func init() {
    plugin.Register("foo", setup)
}

// Corefileパーサー兼セットアップ関数
func setup(c *caddy.Controller) error {
    c.Next() // "foo"トークンをスキップ
    // このプラグインは追加の設定項目を持たないため、余分な引数があればエラー
    if c.NextArg() {
        return c.ArgErr()  // 未知の設定があればエラー
    }
    // プラグインをチェインに登録
    dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
        return &FooPlugin{Next: next}
    })
    return nil
}

// プラグインハンドラ構造体
type FooPlugin struct {
    Next plugin.Handler
}

// Nameメソッド (プラグイン名を返す)
func (f *FooPlugin) Name() string { return "foo" }

// DNSリクエストを処理する
func (f *FooPlugin) ServeDNS(ctx context.Context, w dns.ResponseWriter, req *dns.Msg) (int, error) {
    // リクエスト情報を取得
    state := request.Request{W: w, Req: req}
    qName := state.Name()   // 質問のドメイン名
    qType := state.Type()   // 質問のタイプ(A, AAAAなど)
    clientIP := state.IP()  // クライアントのIPアドレス

    // ログ出力
    log.Infof("DNS query for %s (%s) from %s", qName, qType, clientIP)

    // チェインの次のプラグインに処理を渡す
    return plugin.NextOrFailure(f.Name(), f.Next, ctx, w, req)
}

上記fooプラグインは非常にシンプルで、追加のオプションも持たず、受け取ったDNSクエリの名前・種類・送信元IPをログに記録してから次のプラグインに処理を引き継ぐだけの動作をします。request.Request構造体を使うことでreq.Questionを手作業で解析することなくName()Type()を取得でき、便利です (Developing Custom Plugins for CoreDNS - DEV Community)。ログ出力にはplugin/pkg/log経由で作成したロガーを用い、Infofメソッドで情報レベルのログを書き出しています(実行時に-log stdoutオプションを付けて起動すればコンソール上で確認できます)。

このプラグインを利用するには、前述の手順6に従ってCoreDNSに組み込みます。たとえばCoreDNSソースに組み込む場合、plugin.cfgfoo:github.com/yourname/fooを追加してビルドし、Corefileに以下のように記述します。

. {
    foo        # 自作プラグインfooを有効化
    forward . 1.1.1.1
    log
}

上記の設定でCoreDNSを起動し、任意のDNSクエリを投げると、通常のlogプラグインの出力に加えてfooプラグインによるログが表示されるはずです。例えばクライアントからexample.com Aレコードの問い合わせが来た場合、fooプラグインからは次のようなログが出力されます(実行環境によって形式は多少異なります)。

[INFO] plugin/foo: DNS query for example.com. (A) from 192.168.0.10

このように、CoreDNSのプラグインを開発すれば、自分の用途に合わせたカスタムなDNS処理を組み込むことができます。Go言語で実装された柔軟なプラグインシステムを活用し、ぜひCoreDNSを拡張してみてください。

参考文献: CoreDNS公式サイトのプラグイン開発ドキュメント ( Writing Plugins for CoreDNS ) ( Writing Plugins for CoreDNS )や、CoreDNSのGitHub上のサンプルプラグインコード (example/example.go at master · coredns/example · GitHub) (example/example.go at master · coredns/example · GitHub)も併せて参照してください。本記事の内容は2025年時点の情報に基づいており、CoreDNSのバージョンアップにより一部変更となる可能性があります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?