溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

docker container DNS如何配置

發(fā)布時(shí)間:2021-12-14 09:48:07 來源:億速云 閱讀:239 作者:小新 欄目:云計(jì)算

小編給大家分享一下docker container DNS如何配置,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

Configure container DNS

DNS in default bridge network

OptionsDescription
-h HOSTNAME or --hostname=HOSTNAME在該容器啟動(dòng)時(shí),將HOSTNAME設(shè)置到容器內(nèi)的/etc/hosts, /etc/hostname, /bin/bash提示中。
--link=CONTAINER_NAME or ID:ALIAS在該容器啟動(dòng)時(shí),將ALIAS和CONTAINER_NAME/ID對(duì)應(yīng)的容器IP添加到/etc/hosts. 如果 CONTAINER_NAME/ID有多個(gè)IP地址 ?
--dns=IP_ADDRESS...在該容器啟動(dòng)時(shí),將nameserver IP_ADDRESS添加到容器內(nèi)的/etc/resolv.conf中。可以配置多個(gè)。
--dns-search=DOMAIN...在該容器啟動(dòng)時(shí),將DOMAIN添加到容器內(nèi)/etc/resolv.conf的dns search列表中??梢耘渲枚鄠€(gè)。
--dns-opt=OPTION...在該容器啟動(dòng)時(shí),將OPTION添加到容器內(nèi)/etc/resolv.conf中的options選項(xiàng)中,可以配置多個(gè)。

說明:

  • 如果docker run時(shí)不含--dns=IP_ADDRESS..., --dns-search=DOMAIN..., or --dns-opt=OPTION... 參數(shù),docker daemon會(huì)將copy本主機(jī)的/etc/resolv.conf,然后對(duì)該copy進(jìn)行處理(將那些/etc/resolv.conf中ping不通的nameserver項(xiàng)給拋棄),處理完成后留下的部分就作為該容器內(nèi)部的/etc/resolv.conf。因此,如果你想利用宿主機(jī)中的/etc/resolv.conf配置的nameserver進(jìn)行域名解析,那么你需要宿主機(jī)中該dns service配置一個(gè)宿主機(jī)內(nèi)容器能ping通的IP。

  • 如果宿主機(jī)的/etc/resolv.conf內(nèi)容發(fā)生改變,docker daemon有一個(gè)對(duì)應(yīng)的file change notifier會(huì)watch到這一變化,然后根據(jù)容器狀態(tài)采取對(duì)應(yīng)的措施:

    • 如果容器啟動(dòng)時(shí),用了--dns, --dns-search, or --dns-opt選項(xiàng),其啟動(dòng)時(shí)已經(jīng)修改了宿主機(jī)的/etc/resolv.conf過濾后的內(nèi)容,因此docker daemon永遠(yuǎn)不會(huì)更新這種容器的/etc/resolv.conf。

    • 如果容器狀態(tài)為stopped,則立刻根據(jù)宿主機(jī)的/etc/resolv.conf內(nèi)容更新容器內(nèi)的/etc/resolv.conf.

    • 如果容器狀態(tài)為running,則容器內(nèi)的/etc/resolv.conf將不會(huì)改變,直到該容器狀態(tài)變?yōu)閟topped.

    • 如果容器啟動(dòng)后修改過容器內(nèi)的/etc/resolv.conf,則不會(huì)對(duì)該容器進(jìn)行處理,否則可能會(huì)丟失已經(jīng)完成的修改,無論該容器為什么狀態(tài)。

    • 注意: docker daemon監(jiān)控宿主機(jī)/etc/resolv.conf的這個(gè)file change notifier的實(shí)現(xiàn)是依賴linux內(nèi)核的inotify特性,而inotfy特性不兼容overlay fs,因此使用overlay fs driver的docker deamon將無法使用該/etc/resolv.conf自動(dòng)更新的功能。、

Embedded DNS in user-defined networks

在docker 1.10版本中,docker daemon實(shí)現(xiàn)了一個(gè)叫做embedded DNS server的東西,用來當(dāng)你創(chuàng)建的容器滿足以下條件時(shí):

  • 使用自定義網(wǎng)絡(luò);

  • 容器創(chuàng)建時(shí)候通過--name,--network-alias or --link提供了一個(gè)name;

docker daemon就會(huì)利用embedded DNS server對(duì)整個(gè)自定義網(wǎng)絡(luò)中所有容器進(jìn)行名字解析(你可以理解為一個(gè)網(wǎng)絡(luò)中的一種服務(wù)發(fā)現(xiàn))。

因此當(dāng)你啟動(dòng)容器時(shí)候滿足以上條件時(shí),該容器的域名解析就不應(yīng)該去考慮容器內(nèi)的/etc/hosts, /etc/resolv.conf,應(yīng)該保持其不變,甚至為空,將需要解析的域名都配置到對(duì)應(yīng)embedded DNS server中。具體配置參數(shù)及說明如下:

OptionsDescription
--name=CONTAINER-NAME在該容器啟動(dòng)時(shí),會(huì)將CONTAINER-NAME和該容器的IP配置到該容器連接到的自定義網(wǎng)絡(luò)中的embedded DNS server中,由它提供該自定義網(wǎng)絡(luò)范圍內(nèi)的域名解析
--network-alias=ALIAS將容器的name-ip map配置到容器連接到的其他網(wǎng)絡(luò)的embedded DNS server中。PS:一個(gè)容器可能連接到多個(gè)網(wǎng)絡(luò)中。
--link=CONTAINER_NAME:ALIAS在該容器啟動(dòng)時(shí),將ALIAS和CONTAINER_NAME/ID對(duì)應(yīng)的容器IP配置到該容器連接到的自定義網(wǎng)絡(luò)中的embedded DNS server中,但僅限于配置了該link的容器能解析這條rule。
--dns=[IP_ADDRESS...]當(dāng)embedded DNS server無法解析該容器的某個(gè)dns query時(shí),會(huì)將請(qǐng)求foward到這些--dns配置的IP_ADDRESS DNS Server,由它們進(jìn)一步進(jìn)行域名解析。注意,這些--dns配置到nameserver IP_ADDRESS全部由對(duì)應(yīng)的embedded DNS server管理,并不會(huì)更新到容器內(nèi)的/etc/resolv.conf.
--dns-search=DOMAIN...在該容器啟動(dòng)時(shí),會(huì)將--dns-search配置的DOMAIN們配置到the embedded DNS server,并不會(huì)更新到容器內(nèi)的/etc/resolv.conf。
--dns-opt=OPTION...在該容器啟動(dòng)時(shí),會(huì)將--dns-opt配置的OPTION們配置到the embedded DNS server,并不會(huì)更新到容器內(nèi)的/etc/resolv.conf。

說明:

  • 如果docker run時(shí)不含--dns=IP_ADDRESS..., --dns-search=DOMAIN..., or --dns-opt=OPTION... 參數(shù),docker daemon會(huì)將copy本主機(jī)的/etc/resolv.conf,然后對(duì)該copy進(jìn)行處理(將那些/etc/resolv.conf中ping不通的nameserver項(xiàng)給拋棄),處理完成后留下的部分就作為該容器內(nèi)部的/etc/resolv.conf。因此,如果你想利用宿主機(jī)中的/etc/resolv.conf配置的nameserver進(jìn)行域名解析,那么你需要宿主機(jī)中該dns service配置一個(gè)宿主機(jī)內(nèi)容器能ping通的IP。

  • 注意容器內(nèi)/etc/resolv.conf中配置的DNS server,只有當(dāng)the embedded DNS server無法解析某個(gè)name時(shí),才會(huì)用到。

embedded DNS server源碼分析

所有embedded DNS server相關(guān)的代碼都在libcontainer項(xiàng)目中,幾個(gè)最主要的文件分別是/libnetwork/resolver.go,/libnetwork/resolver_unix.go,sandbox_dns_unix.go

OK, 先來看看embedded DNS server對(duì)象在docker中的定義:

libnetwork/resolver.go

// resolver implements the Resolver interface
type resolver struct {
	sb         *sandbox
	extDNSList [maxExtDNS]extDNSEntry
	server     *dns.Server
	conn       *net.UDPConn
	tcpServer  *dns.Server
	tcpListen  *net.TCPListener
	err        error
	count      int32
	tStamp     time.Time
	queryLock  sync.Mutex
}

// Resolver represents the embedded DNS server in Docker. It operates
// by listening on container's loopback interface for DNS queries.
type Resolver interface {
	// Start starts the name server for the container
	Start() error
	// Stop stops the name server for the container. Stopped resolver
	// can be reused after running the SetupFunc again.
	Stop()
	// SetupFunc() provides the setup function that should be run
	// in the container's network namespace.
	SetupFunc() func()
	// NameServer() returns the IP of the DNS resolver for the
	// containers.
	NameServer() string
	// SetExtServers configures the external nameservers the resolver
	// should use to forward queries
	SetExtServers([]string)
	// ResolverOptions returns resolv.conf options that should be set
	ResolverOptions() []string
}

可見,resolver就是embedded DNS server,每個(gè)resolver都bind一個(gè)sandbox,并定義了一個(gè)對(duì)應(yīng)的dns.Server,還定義了外部DNS對(duì)象列表,但embedded DNS server無法解析某個(gè)name時(shí),就會(huì)forward到那些外部DNS。

Resolver Interface定義了embedded DNS server必須實(shí)現(xiàn)的接口,這里會(huì)重點(diǎn)關(guān)注SetupFunc()和Start(),見下文分析。

dns.Server的實(shí)現(xiàn),全部交給github.com/miekg/dns,限于篇幅,這里我將不會(huì)跟進(jìn)去分析。

從整個(gè)container create的流程上來看,docker daemon對(duì)embedded DNS server的處理是從endpoint Join a sandbox開始的:

libnetwork/endpoint.go


func (ep *endpoint) Join(sbox Sandbox, options ...EndpointOption) error {
	...

	return ep.sbJoin(sb, options...)
}


func (ep *endpoint) sbJoin(sb *sandbox, options ...EndpointOption) error {
	...

	if err = sb.populateNetworkResources(ep); err != nil {
		return err
	}

	...
}

sandbox join a sandbox的流程中,會(huì)調(diào)用sandbox. populateNetworkResources做網(wǎng)絡(luò)資源的設(shè)置,這其中就包括了embedded DNS server的啟動(dòng)。

libnetwork/sandbox.go
func (sb *sandbox) populateNetworkResources(ep *endpoint) error {
	...
	if ep.needResolver() {
		sb.startResolver(false)
	}
	...
}


libnetwork/sandbox_dns_unix.go
func (sb *sandbox) startResolver(restore bool) {
	sb.resolverOnce.Do(func() {
		var err error
		sb.resolver = NewResolver(sb)
		defer func() {
			if err != nil {
				sb.resolver = nil
			}
		}()

		// In the case of live restore container is already running with
		// right resolv.conf contents created before. Just update the
		// external DNS servers from the restored sandbox for embedded
		// server to use.
		if !restore {
			err = sb.rebuildDNS()
			if err != nil {
				log.Errorf("Updating resolv.conf failed for container %s, %q", sb.ContainerID(), err)
				return
			}
		}
		sb.resolver.SetExtServers(sb.extDNS)

		sb.osSbox.InvokeFunc(sb.resolver.SetupFunc())
		if err = sb.resolver.Start(); err != nil {
			log.Errorf("Resolver Setup/Start failed for container %s, %q", sb.ContainerID(), err)
		}
	})
}

sandbox.startResolver是流程關(guān)鍵:

  • 通過sanbdox.rebuildDNS生成了container內(nèi)的/etc/resolv.conf

  • 通過resolver.SetExtServers(sb.extDNS)設(shè)置embedded DNS server的forward DNS list

  • 通過resolver.SetupFunc()啟動(dòng)兩個(gè)隨機(jī)可用端口作為embedded DNS server(127.0.0.11)的TCP和UDP Linstener

  • 通過resolver.Start()對(duì)容器內(nèi)的iptable進(jìn)行設(shè)置(見下),并通過miekg/dns啟動(dòng)一個(gè)nameserver在53端口提供服務(wù)。

下面我將逐一介紹上面的各個(gè)步驟。

sanbdox.rebuildDNS

sanbdox.rebuildDNS負(fù)責(zé)構(gòu)建容器內(nèi)的resolv.conf,構(gòu)建規(guī)則就是第一節(jié)江參數(shù)配置時(shí)候提到的:

  • Save the external name servers in resolv.conf in the sandbox

  • Add only the embedded server's IP to container's resolv.conf

  • If the embedded server needs any resolv.conf options add it to the current list

libnetwork/sandbox_dns_unix.go

func (sb *sandbox) rebuildDNS() error {
	currRC, err := resolvconf.GetSpecific(sb.config.resolvConfPath)
	if err != nil {
		return err
	}

	// localhost entries have already been filtered out from the list
	// retain only the v4 servers in sb for forwarding the DNS queries
	sb.extDNS = resolvconf.GetNameservers(currRC.Content, types.IPv4)

	var (
		dnsList        = []string{sb.resolver.NameServer()}
		dnsOptionsList = resolvconf.GetOptions(currRC.Content)
		dnsSearchList  = resolvconf.GetSearchDomains(currRC.Content)
	)

	dnsList = append(dnsList, resolvconf.GetNameservers(currRC.Content, types.IPv6)...)

	resOptions := sb.resolver.ResolverOptions()

dnsOpt:
	...
	dnsOptionsList = append(dnsOptionsList, resOptions...)

	_, err = resolvconf.Build(sb.config.resolvConfPath, dnsList, dnsSearchList, dnsOptionsList)
	return err
}

resolver.SetExtServers

設(shè)置embedded DNS server的forward DNS list, 當(dāng)embedded DNS server不能解析某name時(shí),就會(huì)將請(qǐng)求forward到ExtServers。代碼很簡(jiǎn)單,不多廢話。

libnetwork/resolver.go
func (r *resolver) SetExtServers(dns []string) {
	l := len(dns)
	if l > maxExtDNS {
		l = maxExtDNS
	}
	for i := 0; i < l; i++ {
		r.extDNSList[i].ipStr = dns[i]
	}
}

resolver.SetupFunc

啟動(dòng)兩個(gè)隨機(jī)可用端口作為embedded DNS server(127.0.0.11)的TCP和UDP Linstener。

libnetwork/resolver.go

func (r *resolver) SetupFunc() func() {
	return (func() {
		var err error

		// DNS operates primarily on UDP
		addr := &net.UDPAddr{
			IP: net.ParseIP(resolverIP),
		}

		r.conn, err = net.ListenUDP("udp", addr)
		...

		// Listen on a TCP as well
		tcpaddr := &net.TCPAddr{
			IP: net.ParseIP(resolverIP),
		}

		r.tcpListen, err = net.ListenTCP("tcp", tcpaddr)
		...
	})
}

resolver.Start

resolver.Start中兩個(gè)重要步驟,分別是:

  • setupIPTable設(shè)置容器內(nèi)的iptables

  • 啟動(dòng)dns nameserver在53端口開始提供域名解析服務(wù)

func (r *resolver) Start() error {
	...
	if err := r.setupIPTable(); err != nil {
		return fmt.Errorf("setting up IP table rules failed: %v", err)
	}
	...
	tcpServer := &dns.Server{Handler: r, Listener: r.tcpListen}
	r.tcpServer = tcpServer
	go func() {
		tcpServer.ActivateAndServe()
	}()
	return nil
}

先來看看怎么設(shè)置容器內(nèi)的iptables的:

func (r *resolver) setupIPTable() error {
	...
	// 獲取setupFunc()時(shí)的兩個(gè)本地隨機(jī)監(jiān)聽端口
	laddr := r.conn.LocalAddr().String()
	ltcpaddr := r.tcpListen.Addr().String()

	cmd := &exec.Cmd{
		Path:   reexec.Self(),
		// 將這兩個(gè)端口傳給setup-resolver命令并啟動(dòng)執(zhí)行
		Args:   append([]string{"setup-resolver"}, r.sb.Key(), laddr, ltcpaddr),
		Stdout: os.Stdout,
		Stderr: os.Stderr,
	}
	if err := cmd.Run(); err != nil {
		return fmt.Errorf("reexec failed: %v", err)
	}
	return nil
}

// init時(shí)就注冊(cè)setup-resolver對(duì)應(yīng)的handler
func init() {
	reexec.Register("setup-resolver", reexecSetupResolver)
}

// setup-resolver對(duì)應(yīng)的handler定義
func reexecSetupResolver() {
	...
	// 封裝iptables數(shù)據(jù)
	_, ipPort, _ := net.SplitHostPort(os.Args[2])
	_, tcpPort, _ := net.SplitHostPort(os.Args[3])
	rules := [][]string{
		{"-t", "nat", "-I", outputChain, "-d", resolverIP, "-p", "udp", "--dport", dnsPort, "-j", "DNAT", "--to-destination", os.Args[2]},
		{"-t", "nat", "-I", postroutingchain, "-s", resolverIP, "-p", "udp", "--sport", ipPort, "-j", "SNAT", "--to-source", ":" + dnsPort},
		{"-t", "nat", "-I", outputChain, "-d", resolverIP, "-p", "tcp", "--dport", dnsPort, "-j", "DNAT", "--to-destination", os.Args[3]},
		{"-t", "nat", "-I", postroutingchain, "-s", resolverIP, "-p", "tcp", "--sport", tcpPort, "-j", "SNAT", "--to-source", ":" + dnsPort},
	}
	...

	// insert outputChain and postroutingchain
	...
}

在reexecSetupResolver()中清楚的定義了iptables添加outputChain 和postroutingchain,將到容器內(nèi)的dns query請(qǐng)求重定向到embedded DNS server(127.0.0.11)上的udp/tcp兩個(gè)隨機(jī)可用端口,embedded DNS server(127.0.0.11)的返回?cái)?shù)據(jù)則重定向到容器內(nèi)的53端口,這樣完成了整個(gè)dns query請(qǐng)求。

模型圖如下: docker container DNS如何配置

貼一張實(shí)例圖: docker container DNS如何配置

docker container DNS如何配置

到這里,關(guān)于embedded DNS server的源碼分析就結(jié)束了。當(dāng)然,其中還有很多細(xì)節(jié),就留給讀者自己走讀代碼了。

福利

另外,借用同事wuke之前畫的一個(gè)時(shí)序圖,看看embedded DNS server的操作在整個(gè)容器create流程中的位置,我就不重復(fù)造輪子了。 docker container DNS如何配置

以上是“docker container DNS如何配置”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI