您好,登錄后才能下訂單哦!
小編給大家分享一下docker container DNS如何配置,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
Options | Description |
---|---|
-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)更新的功能。、
在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ù)及說明如下:
Options | Description |
---|---|
--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相關(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負(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 }
設(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] } }
啟動(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中兩個(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)求。
模型圖如下:
貼一張實(shí)例圖:
到這里,關(guān)于embedded DNS server的源碼分析就結(jié)束了。當(dāng)然,其中還有很多細(xì)節(jié),就留給讀者自己走讀代碼了。
另外,借用同事wuke之前畫的一個(gè)時(shí)序圖,看看embedded DNS server的操作在整個(gè)容器create流程中的位置,我就不重復(fù)造輪子了。
以上是“docker container DNS如何配置”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!
免責(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)容。