ExternalDNSで未対応のプロバイダを開発する方法
概要
ExternalDNSはKubernetesにおいて、IngressリソースやServiceリソース作成/更新時にDNSレコードを動的に設定してくれるものですが、プロバイダが未対応な環境ではExternalDNSを利用することが出来ません。(*v0.12.2時点での対応プロバイダ一覧)
しかし、ExternalDNSには新しいプロバイダを開発できる仕組みが用意されているため、任意の環境におけるプロバイダを開発することでExternalDNSを利用することができるようになります。
今回はExternalDNS未対応の環境としてさくらのクラウドを例に、プロバイダを開発する方法を解説します。
方針
公式ドキュメントに記載があるように、プロバイダを開発するにはProvider
インターフェースに定義されたRecords
メソッドとApplyChanges
メソッドを実装し、Provider
インターフェースを満たす任意のProvider
を作成することが基本的な方針となります。
実装
主に実装すべき箇所や修正すべきところを解説していきます。
パッケージを作成
provider/
配下に開発するプロバイダのパッケージを作成します。
今回、さくらのクラウドのプロバイダを作成する際にはprovider/sakuracloud
としました。
プロバイダの構造体を定義
開発するプロバイダの構造体を定義します。
一般的にフィールドにはprovider.BaseProvider
とendpoint.DomainFilter
、DryRun
、Client
を定義します。
provider.BaseProvider
フィールドはProvider
インターフェースを実装するためのRecords
メソッドとApplyChanges
メソッド以外のメソッドが実装されているProvider
です。
endpoint.DomainFilter
フィールドはExternalDNS起動時にフラグ--domain-filter
で指定されたDNSレコードの操作を行いたいゾーンに関する情報を保持するためのものです。
DryRun
フィールドは ExternalDNS起動時にフラグ--dry-run
が指定されたかどうかの情報を保持するためのものです。
dry-runモードで起動している場合はDNSレコードが作成されないようにしなければなりません。
Client
フィールドはExternalDNSに対応させたい環境のDNSレコードの操作が行えるAPIクライアントを保持するためのものです。
ExternalDNSを任意のクラウドプロバイダーのサービスに対応させたい場合はクラウドプロバイダーによって用意されたAPIクライアントを利用することができます。
これはクラウドプロバイダーのサービスに限らず、APIを用いてDNSレコードの操作が行える環境の場合は対応するAPIクライアントを利用することができます。
しかし、ExternalDNSに対応させたい環境にDNSレコードを操作するAPIがない場合は、何らかしらの方法でExternalDNSからDNSレコードを操作できる手段が必要となります。
今回はExternalDNSに対応させたい環境のAPIクライアントが利用できるものとしてプロバイダを開発していきます。
(今回、さくらのクラウドに対応させる際はsacloud/iaas-api-goを利用しました。)
type SakuraCloudProvider struct {
provider.BaseProvider
Client *iaas.Client
domainFilter endpoint.DomainFilter
DryRun bool
}
Records
メソッドを実装
上記で定義した構造体がProvider
インターフェースを満たすためにRecords
メソッドを実装します。
このメソッドは[]*endpoint.Endpoint
を返す必要があります。
func (p *SakuraCloudProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) {
...
}
endpoint.Endpoint
はExternalDNS起動時にフラグ--domain-filter
で指定されたゾーンのDNSレコードの情報をExternalDNSのコアコンポーネントが利用するためのものです。
APIクライアント(Client
フィールド)を利用してDNSレコードの情報を取得し、それをもとにendpoint.Endpoint
を作成する必要があります。
endpoint.Endpoint
を作成する際はSupportedRecordType
関数がTrue
を返すレコードタイプのDNSレコードの情報のみ利用した方がよさそうです。
(他のプロバイダの実装コードを参照してみると上記を満たしている。)
ApplyChanges
メソッドを実装
次に、ApplyChanges
メソッドを実装します。
func (p *SakuraCloudProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
...
}
引数changes
にはExternalDNSのコアコンポーネントが作成した反映すべきDNSレコードの情報が保持されているので、APIクライアント(Client
フィールド)を利用してDNSレコードの作成/更新/削除の処理を行う必要があります。
フラグ--dry-run
が指定されている場合(DryRun
フィールドがTrue
)、DNSレコードが作成/更新/削除を行わないようにします。
NewHogeHogeProvider
関数を作成
開発するプロバイダの構造体を初期化する関数を作成します。
主な処理はAPIクライアントの認証情報の取得処理やAPIクライアントの生成になると思います。
開発するプロバイダの構造体の定義内容に応じて作成します。
さくらのクラウドに対応したプロバイダ開発の場合、下記のようになりました。
func NewSakuraCloudProvider(domainFilter endpoint.DomainFilter, dryRun bool) (*SakuraCloudProvider, error) {
token, ok := os.LookupEnv("SAKURACLOUD_ACCESS_TOKEN")
if !ok {
return nil, fmt.Errorf("No token found")
}
secret, ok := os.LookupEnv("SAKURACLOUD_ACCESS_TOKEN_SECRET")
if !ok {
return nil, fmt.Errorf("No secret found")
}
client := iaas.NewClient(token,secret)
provider := &SakuraCloudProvider{
Client: client,
domainFilter: domainFilter,
DryRun : dryRun,
}
return provider, nil
pkg/apis/externaldns/types.go
開発したプロバイダを利用してExternalDNSを起動できるように、provider
フラグに任意のプロバイダ名を追加します。
@@ -413,7 +413,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("exclude-target-net", "Exclude target nets (optional)").StringsVar(&cfg.ExcludeTargetNets)
// Flags related to providers
- app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, ibmcloud, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi, safedns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "ibmcloud", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi", "safedns")
+ app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, godaddy, google, azure, azure-dns, azure-private-dns, bluecat, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, ibmcloud, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns, scaleway, vultr, ultradns, gandi, safedns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "ibmcloud", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns", "scaleway", "vultr", "ultradns", "godaddy", "bluecat", "gandi", "safedns", "sakuracloud")
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains)
app.Flag("regex-domain-filter", "Limit possible domains and target zones by a Regex filter; Overrides domain-filter (optional)").Default(defaultConfig.RegexDomainFilter.String()).RegexpVar(&cfg.RegexDomainFilter)
main.go
プロバイダの開発で作成したパッケージをインポートし、開発したプロバイダを利用するためにprovider
フラグに任意のプロバイダ名が指定された際、対応するプロバイダが作成されるようなコードを追加します。
@@ -68,6 +68,7 @@ import (
"sigs.k8s.io/external-dns/provider/ultradns"
"sigs.k8s.io/external-dns/provider/vinyldns"
"sigs.k8s.io/external-dns/provider/vultr"
+ "sigs.k8s.io/external-dns/provider/sakuracloud"
"sigs.k8s.io/external-dns/registry"
"sigs.k8s.io/external-dns/source"
)
@@ -333,6 +334,8 @@ func main() {
p, err = ibmcloud.NewIBMCloudProvider(cfg.IBMCloudConfigFile, domainFilter, zoneIDFilter, endpointsSource, cfg.IBMCloudProxied, cfg.DryRun)
case "safedns":
p, err = safedns.NewSafeDNSProvider(domainFilter, cfg.DryRun)
+ case "sakuracloud":
+ p, err = sakuracloud.NewSakuraCloudProvider(domainFilter, cfg.DryRun)
default:
log.Fatalf("unknown dns provider: %s", cfg.Provider)
}
まとめ
ExternalDNSで未対応のプロバイダを開発する方法を解説しました。
ExternalDNSには新しいプロバイダを開発できる仕組みが用意されているため、任意の環境におけるプロバイダを開発することでExternalDNSを利用することができるようになります。
Provider
インターフェースに定義されたRecords
メソッドとApplyChanges
メソッドを実装し、Provider
インターフェースを満たす任意のProvider
を作成することが基本的な開発方針となります。
主に実装すべき箇所や修正すべきところを解説しました。
最後に
主にRecords
メソッドとApplyChanges
メソッドの実装解説では、メソッド内でどういうデータを使って、どういうデータが、どういう処理が必要があるのか、という基本的な実装方針を解説しました。
必要に応じて、クライアントAPIを利用して取得したDNSレコードの情報をもとに[]*endpoint.Endpoint
を作成する処理(または関数)やその逆のことを行う処理(または関数)などを作成し、Records
メソッドとApplyChanges
メソッドを実装してみてください。
本記事を作成するにあたって、ExternalDNS未対応の環境であるさくらのクラウドのプロバイダを開発しました。参考になれば幸いです。
参考
sacloud-archives/external-dns
をたいへん参考にさせていただきました。
また、AWSやCloudflare、GoogleなどExternalDNS対応プロバイダのコードがたくさんありますので開発する際の参考になりました。
Discussion