您好,登錄后才能下訂單哦!
小編給大家分享一下docker container DNS如何配置,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
Options | Description |
---|---|
-h HOSTNAME or --hostname=HOSTNAME | 在該容器啟動時,將HOSTNAME設置到容器內的/etc/hosts, /etc/hostname, /bin/bash提示中。 |
--link=CONTAINER_NAME or ID:ALIAS | 在該容器啟動時,將ALIAS和CONTAINER_NAME/ID對應的容器IP添加到/etc/hosts. 如果 CONTAINER_NAME/ID有多個IP地址 ? |
--dns=IP_ADDRESS... | 在該容器啟動時,將nameserver IP_ADDRESS 添加到容器內的/etc/resolv.conf中。可以配置多個。 |
--dns-search=DOMAIN... | 在該容器啟動時,將DOMAIN添加到容器內/etc/resolv.conf的dns search列表中。可以配置多個。 |
--dns-opt=OPTION... | 在該容器啟動時,將OPTION添加到容器內/etc/resolv.conf中的options選項中,可以配置多個。 |
說明:
如果docker run時不含--dns=IP_ADDRESS..., --dns-search=DOMAIN..., or --dns-opt=OPTION...
參數,docker daemon會將copy本主機的/etc/resolv.conf,然后對該copy進行處理(將那些/etc/resolv.conf中ping不通的nameserver項給拋棄),處理完成后留下的部分就作為該容器內部的/etc/resolv.conf。因此,如果你想利用宿主機中的/etc/resolv.conf配置的nameserver進行域名解析,那么你需要宿主機中該dns service配置一個宿主機內容器能ping通的IP。
如果宿主機的/etc/resolv.conf內容發生改變,docker daemon有一個對應的file change notifier會watch到這一變化,然后根據容器狀態采取對應的措施:
如果容器啟動時,用了--dns, --dns-search, or --dns-opt選項,其啟動時已經修改了宿主機的/etc/resolv.conf過濾后的內容,因此docker daemon永遠不會更新這種容器的/etc/resolv.conf。
如果容器狀態為stopped,則立刻根據宿主機的/etc/resolv.conf內容更新容器內的/etc/resolv.conf.
如果容器狀態為running,則容器內的/etc/resolv.conf將不會改變,直到該容器狀態變為stopped.
如果容器啟動后修改過容器內的/etc/resolv.conf,則不會對該容器進行處理,否則可能會丟失已經完成的修改,無論該容器為什么狀態。
注意: docker daemon監控宿主機/etc/resolv.conf的這個file change notifier的實現是依賴linux內核的inotify特性,而inotfy特性不兼容overlay fs,因此使用overlay fs driver的docker deamon將無法使用該/etc/resolv.conf自動更新的功能。、
在docker 1.10版本中,docker daemon實現了一個叫做embedded DNS server
的東西,用來當你創建的容器滿足以下條件時:
使用自定義網絡;
容器創建時候通過--name
,--network-alias
or --link
提供了一個name;
docker daemon就會利用embedded DNS server對整個自定義網絡中所有容器進行名字解析(你可以理解為一個網絡中的一種服務發現)。
因此當你啟動容器時候滿足以上條件時,該容器的域名解析就不應該去考慮容器內的/etc/hosts, /etc/resolv.conf,應該保持其不變,甚至為空,將需要解析的域名都配置到對應embedded DNS server中。具體配置參數及說明如下:
Options | Description |
---|---|
--name=CONTAINER-NAME | 在該容器啟動時,會將CONTAINER-NAME和該容器的IP配置到該容器連接到的自定義網絡中的embedded DNS server中,由它提供該自定義網絡范圍內的域名解析 |
--network-alias=ALIAS | 將容器的name-ip map配置到容器連接到的其他網絡的embedded DNS server中。PS:一個容器可能連接到多個網絡中。 |
--link=CONTAINER_NAME:ALIAS | 在該容器啟動時,將ALIAS和CONTAINER_NAME/ID對應的容器IP配置到該容器連接到的自定義網絡中的embedded DNS server中,但僅限于配置了該link的容器能解析這條rule。 |
--dns=[IP_ADDRESS...] | 當embedded DNS server無法解析該容器的某個dns query時,會將請求foward到這些--dns配置的IP_ADDRESS DNS Server,由它們進一步進行域名解析。注意,這些--dns配置到nameserver IP_ADDRESS 全部由對應的embedded DNS server管理,并不會更新到容器內的/etc/resolv.conf. |
--dns-search=DOMAIN... | 在該容器啟動時,會將--dns-search配置的DOMAIN們配置到the embedded DNS server,并不會更新到容器內的/etc/resolv.conf。 |
--dns-opt=OPTION... | 在該容器啟動時,會將--dns-opt配置的OPTION們配置到the embedded DNS server,并不會更新到容器內的/etc/resolv.conf。 |
說明:
如果docker run時不含--dns=IP_ADDRESS..., --dns-search=DOMAIN..., or --dns-opt=OPTION...
參數,docker daemon會將copy本主機的/etc/resolv.conf,然后對該copy進行處理(將那些/etc/resolv.conf中ping不通的nameserver項給拋棄),處理完成后留下的部分就作為該容器內部的/etc/resolv.conf。因此,如果你想利用宿主機中的/etc/resolv.conf配置的nameserver進行域名解析,那么你需要宿主機中該dns service配置一個宿主機內容器能ping通的IP。
注意容器內/etc/resolv.conf中配置的DNS server,只有當the embedded DNS server無法解析某個name時,才會用到。
所有embedded DNS server相關的代碼都在libcontainer項目中,幾個最主要的文件分別是/libnetwork/resolver.go
,/libnetwork/resolver_unix.go
,sandbox_dns_unix.go
。
OK, 先來看看embedded DNS server對象在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,每個resolver都bind一個sandbox,并定義了一個對應的dns.Server,還定義了外部DNS對象列表,但embedded DNS server無法解析某個name時,就會forward到那些外部DNS。
Resolver Interface定義了embedded DNS server必須實現的接口,這里會重點關注SetupFunc()和Start(),見下文分析。
dns.Server的實現,全部交給github.com/miekg/dns,限于篇幅,這里我將不會跟進去分析。
從整個container create的流程上來看,docker daemon對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的流程中,會調用sandbox. populateNetworkResources做網絡資源的設置,這其中就包括了embedded DNS server的啟動。
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是流程關鍵:
通過sanbdox.rebuildDNS生成了container內的/etc/resolv.conf
通過resolver.SetExtServers(sb.extDNS)設置embedded DNS server的forward DNS list
通過resolver.SetupFunc()啟動兩個隨機可用端口作為embedded DNS server(127.0.0.11)的TCP和UDP Linstener
通過resolver.Start()對容器內的iptable進行設置(見下),并通過miekg/dns啟動一個nameserver在53端口提供服務。
下面我將逐一介紹上面的各個步驟。
sanbdox.rebuildDNS負責構建容器內的resolv.conf,構建規則就是第一節江參數配置時候提到的:
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 }
設置embedded DNS server的forward DNS list, 當embedded DNS server不能解析某name時,就會將請求forward到ExtServers。代碼很簡單,不多廢話。
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] } }
啟動兩個隨機可用端口作為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中兩個重要步驟,分別是:
setupIPTable設置容器內的iptables
啟動dns nameserver在53端口開始提供域名解析服務
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 }
先來看看怎么設置容器內的iptables的:
func (r *resolver) setupIPTable() error { ... // 獲取setupFunc()時的兩個本地隨機監聽端口 laddr := r.conn.LocalAddr().String() ltcpaddr := r.tcpListen.Addr().String() cmd := &exec.Cmd{ Path: reexec.Self(), // 將這兩個端口傳給setup-resolver命令并啟動執行 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時就注冊setup-resolver對應的handler func init() { reexec.Register("setup-resolver", reexecSetupResolver) } // setup-resolver對應的handler定義 func reexecSetupResolver() { ... // 封裝iptables數據 _, 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,將到容器內的dns query請求重定向到embedded DNS server(127.0.0.11)上的udp/tcp兩個隨機可用端口,embedded DNS server(127.0.0.11)的返回數據則重定向到容器內的53端口,這樣完成了整個dns query請求。
模型圖如下:
貼一張實例圖:
到這里,關于embedded DNS server的源碼分析就結束了。當然,其中還有很多細節,就留給讀者自己走讀代碼了。
另外,借用同事wuke之前畫的一個時序圖,看看embedded DNS server的操作在整個容器create流程中的位置,我就不重復造輪子了。
以上是“docker container DNS如何配置”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。