兩年容器云工作經(jīng)驗(yàn),牛刀小試了幾家公司,將面試問到的問題記錄下來,鞭策自己不斷學(xué)習(xí)。
1、docker 后端存儲(chǔ)驅(qū)動(dòng) devicemapper、overlay 幾種的區(qū)別?
這道題是考察 docker 后端存儲(chǔ)知識。
剛開始拿到這道題我有點(diǎn)蒙,因?yàn)槲抑恢滥壳拔覀冇玫氖莢g-pool devicemapper 來存儲(chǔ)鏡像和容器,后來面試官問我鏡像分層的技術(shù)知道嗎?我說知道,就是聯(lián)合文件系統(tǒng),多層文件系統(tǒng)聯(lián)合組成一個(gè)統(tǒng)一的文件系統(tǒng)視角,當(dāng)需要修改文件時(shí)采用寫時(shí)復(fù)制(CopyW)的技術(shù)從上往下查找,找到之后復(fù)制到可寫的容器層,進(jìn)行修改并保存至容器層,說完之后面試官再問我,那每次修改文件都需要從上往下查找,層數(shù)又那么多,性能是否比較差,現(xiàn)在才反應(yīng)回來,原先面試官想考察我aufs、overlay 或者是 devicemapper 等幾種存儲(chǔ)驅(qū)動(dòng)的區(qū)別。
AUFS
AUFS (Another UnionFS)是一種 Union FS,是文件級的存儲(chǔ)驅(qū)動(dòng),AUFS 簡單理解就是將多層的文件系統(tǒng)聯(lián)合掛載成統(tǒng)一的文件系統(tǒng),這種文件系統(tǒng)可以一層一層地疊加修改文件,只有最上層是可寫層,底下所有層都是只讀層,對應(yīng)到 Docker,最上層就是 container 層,底層就是 image 層,結(jié)構(gòu)如下圖所示:

Overlay
Overlay 也是一種 Union FS,和 AUFS 多層相比,Overlay 只有兩層:一個(gè) upper 文件系統(tǒng)和一個(gè) lower 文件系統(tǒng),分別代表 Docker 的容器層(upper)和鏡像層(lower)。當(dāng)需要修改一個(gè)文件時(shí),使用 CopyW 將文件從只讀的 lower 層復(fù)制到可寫層 upper,結(jié)果也保存在 upper 層,結(jié)構(gòu)如下圖所示:

Devicemapper
Device mapper,提供的是一種從邏輯設(shè)備到物理設(shè)備的映射框架機(jī)制,前面講的 AUFS 和 OverlayFS 都是文件級存儲(chǔ),而 Device mapper 是塊級存儲(chǔ),所有的操作都是直接對塊進(jìn)行操作,而不是文件。Device mapper 驅(qū)動(dòng)會(huì)先在塊設(shè)備上創(chuàng)建一個(gè)資源池,然后在資源池上創(chuàng)建一個(gè)帶有文件系統(tǒng)的基本設(shè)備,所有鏡像都是這個(gè)基本設(shè)備的快照,而容器則是鏡像的快照。所以在容器里看到文件系統(tǒng)是資源池上基本設(shè)備的文件系統(tǒng)的快照。當(dāng)要寫入一個(gè)新文件時(shí),在容器的鏡像內(nèi)為其分配新的塊并寫入數(shù)據(jù),這個(gè)叫用時(shí)分配。當(dāng)要修改已有文件時(shí),再使用CoW為容器快照分配塊空間,將要修改的數(shù)據(jù)復(fù)制到在容器快照中新的塊里再進(jìn)行修改。Devicemapper 驅(qū)動(dòng)默認(rèn)會(huì)創(chuàng)建一個(gè)100G 的文件包含鏡像和容器。每一個(gè)容器被限制在 10G 大小的卷內(nèi),可以自己配置調(diào)整。結(jié)構(gòu)如下圖所示:

這里只介紹三種常見的 docker 存儲(chǔ)驅(qū)動(dòng),更多詳細(xì)的內(nèi)容可以參考這兩博客:?
深入了解 Docker 存儲(chǔ)驅(qū)動(dòng)
2、k8s 創(chuàng)建一個(gè)pod的詳細(xì)流程,涉及的組件怎么通信的?
這道題考察的是 k8s 內(nèi)部組件通信。
k8s 創(chuàng)建一個(gè) Pod 的詳細(xì)流程如下:?
(1) 客戶端提交創(chuàng)建請求,可以通過 api-server 提供的 restful 接口,或者是通過 kubectl 命令行工具,支持的數(shù)據(jù)類型包括 JSON 和 YAML。
(2) api-server 處理用戶請求,將 pod 信息存儲(chǔ)至 etcd 中。
(3) kube-scheduler 通過 api-server 提供的接口監(jiān)控到未綁定的 pod,嘗試為 pod 分配 node 節(jié)點(diǎn),主要分為兩個(gè)階段,預(yù)選階段和優(yōu)選階段,其中預(yù)選階段是遍歷所有的 node 節(jié)點(diǎn),根據(jù)策略篩選出候選節(jié)點(diǎn),而優(yōu)選階段是在第一步的基礎(chǔ)上,為每一個(gè)候選節(jié)點(diǎn)進(jìn)行打分,分?jǐn)?shù)最高者勝出。
(4) 選擇分?jǐn)?shù)最高的節(jié)點(diǎn),進(jìn)行 pod binding 操作,并將結(jié)果存儲(chǔ)至 etcd 中。
(5) 隨后目標(biāo)節(jié)點(diǎn)的 kubelet 進(jìn)程通過 api-server 提供的接口監(jiān)測到 kube-scheduler 產(chǎn)生的 pod 綁定事件,然后從 etcd 獲取 pod 清單,下載鏡像并啟動(dòng)容器。
整個(gè)事件流可以參考下圖:

3、k8s 架構(gòu)體系了解嗎?簡單描述下
這道題主要考察 k8s 體系,涉及的范圍其實(shí)太廣泛,可以從本身 k8s 組件、存儲(chǔ)、網(wǎng)絡(luò)、監(jiān)控等方面闡述,當(dāng)時(shí)我主要將 k8s 的每個(gè)組件功能都大概說了一下。
Master節(jié)點(diǎn)
Master節(jié)點(diǎn)主要有四個(gè)組件,分別是:api-server、controller-manager、kube-scheduler 和 etcd。
api-server
kube-apiserver 作為 k8s 集群的核心,負(fù)責(zé)整個(gè)集群功能模塊的交互和通信,集群內(nèi)的各個(gè)功能模塊如 kubelet、controller、scheduler 等都通過 api-server 提供的接口將信息存入到 etcd 中,當(dāng)需要這些信息時(shí),又通過 api-server 提供的 restful 接口,如get、watch 接口來獲取,從而實(shí)現(xiàn)整個(gè) k8s 集群功能模塊的數(shù)據(jù)交互。
controller-manager
controller-manager 作為 k8s 集群的管理控制中心,負(fù)責(zé)集群內(nèi) Node、Namespace、Service、Token、Replication 等資源對象的管理,使集群內(nèi)的資源對象維持在預(yù)期的工作狀態(tài)。
每一個(gè) controller 通過 api-server 提供的 restful 接口實(shí)時(shí)監(jiān)控集群內(nèi)每個(gè)資源對象的狀態(tài),當(dāng)發(fā)生故障,導(dǎo)致資源對象的工作狀態(tài)發(fā)生變化,就進(jìn)行干預(yù),嘗試將資源對象從當(dāng)前狀態(tài)恢復(fù)為預(yù)期的工作狀態(tài),常見的 controller 有 Namespace Controller、Node Controller、Service Controller、ServiceAccount Controller、Token Controller、ResourceQuote Controller、Replication Controller等。
kube-scheduler
kube-scheduler 簡單理解為通過特定的調(diào)度算法和策略為待調(diào)度的 Pod 列表中的每個(gè) Pod 選擇一個(gè)最合適的節(jié)點(diǎn)進(jìn)行調(diào)度,調(diào)度主要分為兩個(gè)階段,預(yù)選階段和優(yōu)選階段,其中預(yù)選階段是遍歷所有的 node 節(jié)點(diǎn),根據(jù)策略和限制篩選出候選節(jié)點(diǎn),優(yōu)選階段是在第一步的基礎(chǔ)上,通過相應(yīng)的策略為每一個(gè)候選節(jié)點(diǎn)進(jìn)行打分,分?jǐn)?shù)最高者勝出,隨后目標(biāo)節(jié)點(diǎn)的 kubelet 進(jìn)程通過 api-server 提供的接口監(jiān)控到 kube-scheduler 產(chǎn)生的 pod 綁定事件,從 etcd 中獲取 Pod 的清單,然后下載鏡像,啟動(dòng)容器。
預(yù)選階段的策略有:
(1) MatchNodeSelector:判斷節(jié)點(diǎn)的 label 是否滿足 Pod 的 nodeSelector 屬性值。
(2) PodFitResource:判斷節(jié)點(diǎn)的資源是否滿足 Pod 的需求,批判的標(biāo)準(zhǔn)是:當(dāng)前節(jié)點(diǎn)已運(yùn)行的所有 Pod 的 request值 + 待調(diào)度的 Pod 的 request 值是否超過節(jié)點(diǎn)的資源容量。
(3) PodFitHostName:判斷節(jié)點(diǎn)的主機(jī)名稱是否滿足 Pod 的 nodeName 屬性值。
(4) PodFitHostPort:判斷 Pod 的端口所映射的節(jié)點(diǎn)端口是否被節(jié)點(diǎn)其他 Pod 所占用。
(5) CheckNodeMemoryPressure:判斷 Pod 是否可以調(diào)度到內(nèi)存有壓力的節(jié)點(diǎn),這取決于 Pod 的 Qos 配置,如果是 BestEffort(盡量滿足,優(yōu)先級最低),則不允許調(diào)度。
(6) CheckNodeDiskPressure:如果當(dāng)前節(jié)點(diǎn)磁盤有壓力,則不允許調(diào)度。
優(yōu)選階段的策略有:
(1) SelectorSpreadPriority:盡量減少節(jié)點(diǎn)上同屬一個(gè) SVC/RC/RS 的 Pod 副本數(shù),為了更好的實(shí)現(xiàn)容災(zāi),對于同屬一個(gè) SVC/RC/RS 的 Pod 實(shí)例,應(yīng)盡量調(diào)度到不同的 node 節(jié)點(diǎn)。
(2) LeastRequestPriority:優(yōu)先調(diào)度到請求資源較少的節(jié)點(diǎn),節(jié)點(diǎn)的優(yōu)先級由節(jié)點(diǎn)的空閑資源與節(jié)點(diǎn)總?cè)萘康谋戎禌Q定的,即(節(jié)點(diǎn)總?cè)萘?- 已經(jīng)運(yùn)行的 Pod 所需資源)/ 節(jié)點(diǎn)總?cè)萘浚珻PU 和 Memory 具有相同的權(quán)重,最終的值由這兩部分組成。
(3) BalancedResourceAllocation:該策略不能單獨(dú)使用,必須和 LeaseRequestPriority 策略一起結(jié)合使用,盡量調(diào)度到 CPU 和 Memory 使用均衡的節(jié)點(diǎn)上。
ETCD
強(qiáng)一致性的鍵值對存儲(chǔ),k8s 集群中的所有資源對象都存儲(chǔ)在 etcd 中。
Node節(jié)點(diǎn)
node節(jié)點(diǎn)主要有三個(gè)組件:分別是 kubelet、kube-proxy 和 容器運(yùn)行時(shí) docker 或者 rkt。
kubelet
在 k8s 集群中,每個(gè) node 節(jié)點(diǎn)都會(huì)運(yùn)行一個(gè) kubelet 進(jìn)程,該進(jìn)程用來處理 Master 節(jié)點(diǎn)下達(dá)到該節(jié)點(diǎn)的任務(wù),同時(shí),通過 api-server 提供的接口定期向 Master 節(jié)點(diǎn)報(bào)告自身的資源使用情況,并通過 cadvisor 組件監(jiān)控節(jié)點(diǎn)和容器的使用情況。
kube-proxy
kube-proxy 就是一個(gè)智能的軟件負(fù)載均衡器,將 service 的請求轉(zhuǎn)發(fā)到后端具體的 Pod 實(shí)例上,并提供負(fù)載均衡和會(huì)話保持機(jī)制,目前有三種工作模式,分別是:用戶模式(userspace)、iptables 模式和 IPVS 模式。
容器運(yùn)行時(shí)——docker
負(fù)責(zé)管理 node 節(jié)點(diǎn)上的所有容器和容器 IP 的分配。
4、單元測試有考慮直接在 k8s 集群上做嗎?
這道題問的有點(diǎn)蒙,目前我們的工程是直接在 jenkins 編譯打包時(shí)強(qiáng)制要求跑單元測試,面試官的意思是說,能否單元測試不在 jenkins 公共環(huán)境上執(zhí)行,而直接在 k8s 集群上運(yùn)行,利用 k8s 有多租戶和 Namespace 隔離的機(jī)制,確保每個(gè)工程執(zhí)行單元測試能做到隔離和并行。
5、Google 號稱可以支撐5000個(gè)節(jié)點(diǎn),在調(diào)度這方面是有做了哪些優(yōu)化呢
這道題我回答的也不好,我只從調(diào)度分為兩個(gè)階段,每個(gè)階段有多種調(diào)度算法和策略適用于不同場景,另外也支撐自定義調(diào)度算法,我就大概說了這幾點(diǎn),面試官聽完之后也沒有繼續(xù)追問下去,面試官說算了,有點(diǎn)深。。。
6、flannel 和 ovs 網(wǎng)絡(luò)的區(qū)別
這道題主要考察 k8s 集群中跨節(jié)點(diǎn)容器間通信的 sdn 網(wǎng)絡(luò)組件:flannel 和 openvswitch,主要有以下兩方面的區(qū)別:
(1)配置是否自動(dòng)化
ovs 作為開源的交換機(jī)軟件,相對比較成熟和穩(wěn)定,支持各種網(wǎng)絡(luò)隧道和協(xié)議,經(jīng)歷了大型項(xiàng)目 OpenStack 的考驗(yàn),而 flannel 除了支持建立覆蓋網(wǎng)絡(luò)來實(shí)現(xiàn) Pod 到 Pod 之間的無縫通信之外,還跟 docker、k8s 的架構(gòu)體系緊密結(jié)合,flannel 能感知 k8s 中的 service 對象,然后動(dòng)態(tài)維護(hù)自己的路由表,并通過 etcd 來協(xié)助 docker 對整個(gè) k8s 集群的 docker0 網(wǎng)段進(jìn)行規(guī)范,而 ovs ,這些操作則需要手動(dòng)完成,假如集群中有 N 個(gè)節(jié)點(diǎn),則需要建立 N(N-1)/2 個(gè) Vxlan 或者 gre 連接,這取決于集群的規(guī)模,如果集群的規(guī)模很大,則必須通過自動(dòng)化腳本來初始化,避免出錯(cuò)。
(2)是否支持隔離
flannel 雖然很方便實(shí)現(xiàn) Pod 到 Pod 之間的通信,但不能實(shí)現(xiàn)多租戶隔離,也不能很好地限制 Pod 的網(wǎng)絡(luò)流量,而 ovs 網(wǎng)絡(luò)有兩種模式:單租戶模式和多租戶模式,單租戶模式直接使用 openvswitch + vxlan 將 k8s 的 pod 網(wǎng)絡(luò)組成一個(gè)大二層,所有的 pod 可以互相通信訪問,多租戶模式以 Namespace 為維度分配虛擬網(wǎng)絡(luò),從而形成一個(gè)網(wǎng)絡(luò)獨(dú)立用戶,一個(gè) Namespace 中的 pod 無法訪問其他 Namespace 中的 pod 和 svc 對象。
7、k8s 中服務(wù)級別,怎樣設(shè)置服務(wù)的級別才是最高的
這道題主要考察 k8s Qos 類別。
在 k8s 中,Qos 主要有三種類別,分別是 BestEffort、Burstable 和 Guaranteed,三種類別區(qū)別如下:
BestEffort
什么都不設(shè)置(CPU or Memory),佛系申請資源。
Burstable
Pod 中的容器至少一個(gè)設(shè)置了CPU 或者 Memory 的請求
Guaranteed
Pod 中的所有容器必須設(shè)置 CPU 和 Memory,并且 request 和 limit 值相等。
詳情可以參考這篇博客:K8s Qos
8、容器隔離不徹底,Memory 和 CPU 隔離不徹底,怎么處理解決這個(gè)問題?
由于 /proc 文件系統(tǒng)是以只讀的方式掛載到容器內(nèi)部,所以在容器內(nèi)看到的都是宿主機(jī)的信息,包括 CPU 和 Memory,docker 是以 cgroups 來進(jìn)行資源限制的,而 jdk1.9 以下版本目前無法自動(dòng)識別容器的資源配額,1.9以上版本會(huì)自動(dòng)識別和正常讀取 cgroups 中為容器限制的資源大小。
Memory 隔離不徹底
Docker 通過 cgroups 完成對內(nèi)存的限制,而 /proc 文件目錄是以只讀的形式掛載到容器中,由于默認(rèn)情況下,Java 壓根就看不到 cgroups 限制的內(nèi)容的大小,而默認(rèn)使用 /proc/meminfo 中的信息作為內(nèi)存信息進(jìn)行啟動(dòng),默認(rèn)情況下,JVM 初始堆大小為內(nèi)存總量的 1/4,這種情況會(huì)導(dǎo)致,如果容器分配的內(nèi)存小于 JVM 的內(nèi)存, JVM 進(jìn)程會(huì)被 linux killer 殺死。
那么目前有幾種解決方式:
(1)升級 JDK 版本到1.9以上,讓 JVM 能自動(dòng)識別 cgroups 對容器的資源限制,從而自動(dòng)調(diào)整 JVM 的參數(shù)并啟動(dòng) JVM 進(jìn)程。
(2)對于較低版本的JDK,一定要設(shè)置 JVM 初始堆大小,并且JVM 的最大堆內(nèi)存不能超過容器的最大內(nèi)存值,正常理論值應(yīng)該是:容器 limit-memory = JVM 最大堆內(nèi)存 + 750MB。
(3)使用 lxcfs ,這是一種用戶態(tài)文件系統(tǒng),用來支持LXC 容器,lxcfs 通過用戶態(tài)文件系統(tǒng),在容器中提供下列 procfs 的文件,啟動(dòng)時(shí),把宿主機(jī)對應(yīng)的目錄 /var/lib/lxcfu/proc/meminfo 文件掛載到 Docker 容器的 /proc/meminfo 位置后,容器中進(jìn)程(JVM)讀取相應(yīng)文件內(nèi)容時(shí),lxcfs 的 fuse 將會(huì)從容器對應(yīng)的 cgroups 中讀取正確的內(nèi)存限制,從而獲得正確的資源約束設(shè)定。
CPU 隔離不徹底
JVM GC (垃圾回收)對于 java 程序執(zhí)行性能有一定的影響,默認(rèn)的 JVM 使用如下公式: ParallelGCThreads = ( ncpu <= 8 ) ? ncpu:3 + (ncpu * 5)/ 8 來計(jì)算并行 GC 的線程數(shù),但是在容器里面,ncpu 獲取的就是所在宿主機(jī)的 cpu 個(gè)數(shù),這會(huì)導(dǎo)致 JVM 啟動(dòng)過多的 GC 線程,直接的結(jié)果就是 GC 的性能下降,java 服務(wù)的感受就是:延時(shí)增加, TPS 吞度量下降,針對這種問題,也有以下幾種解決方案:
(1)顯示傳遞 JVM 啟動(dòng)參數(shù):“-XX: ParallelGCThreads" 告訴 JVM 應(yīng)該啟動(dòng)多少個(gè)并行 GC 線程,缺點(diǎn)是需要業(yè)務(wù)感知,而且需要為不同配置的容器傳遞不同的 JVM 參數(shù)。
(2)在容器內(nèi)使用 Hack 過的 glibc ,使 JVM 通過 sysconf 系統(tǒng)調(diào)用能正確獲取容器內(nèi) CPU 資源核數(shù),優(yōu)點(diǎn)是業(yè)務(wù)無感知,并且能自動(dòng)適配不同配置的容器,缺點(diǎn)是有一定的維護(hù)成本。
9、kubelet 監(jiān)控 Node 節(jié)點(diǎn)資源使用是通過什么組件來實(shí)現(xiàn)的?
這道題主要考察 cAdvisor 組件
開源軟件 cAdvisor 是用于監(jiān)控容器運(yùn)行狀態(tài)的利器之一,在 Kubernetes 系統(tǒng)中,cAdvisor 已被默認(rèn)集成到 kubelet 組件內(nèi),當(dāng) kubelet 服務(wù)啟動(dòng)時(shí),它會(huì)自動(dòng)啟動(dòng) cAdvisor 服務(wù),然后 cAdvisor 會(huì)實(shí)時(shí)采集所在節(jié)點(diǎn)的性能指標(biāo)及在節(jié)點(diǎn)上運(yùn)行的容器的性能指標(biāo)。kubelet 的啟動(dòng)參數(shù) --cadvisor-port 可自定義 cAdvisor 對外提供服務(wù)的端口號,默認(rèn)是 4194。.
10、docker runc 漏洞是怎么修復(fù)的?
這道題是考察前陣子出現(xiàn)的 docker runc 漏洞獲得 Docker k8s 主機(jī)的 root 權(quán)限問題。
漏洞詳情
Docker、containerd或者其他基于runc的容器運(yùn)行時(shí)存在安全漏洞,攻擊者可以通過特定的容器鏡像或者exec操作可以獲取到所在宿主機(jī)的runc執(zhí)行時(shí)的文件句柄,并修改掉runc的二進(jìn)制文件,從而獲取到所在宿主機(jī)的root執(zhí)行權(quán)限。
關(guān)于這個(gè)漏洞如何發(fā)現(xiàn),并且如何修復(fù),可以參考如下以下兩個(gè)博客:
11、集群擴(kuò)廣遇到的挑戰(zhàn)是什么
這道題主要考察在擴(kuò)廣 k8s 集群實(shí)現(xiàn)微服務(wù)容器化部署實(shí)際落地過程中遇到的挑戰(zhàn)和踩過的坑有哪些,話題有點(diǎn)廣,可以說的點(diǎn)其實(shí)挺多的,我主要從以下幾個(gè)方面來闡述的。
部署的規(guī)范流程
雖然說容器和虛擬機(jī)部署本質(zhì)上沒有多大區(qū)別,但還是有些許不同的。容器的可執(zhí)行文件是一個(gè)鏡像,而虛擬機(jī)的可執(zhí)行文件往往是一個(gè)二進(jìn)制文件如 jar 包或者是 war包,另外,由于容器隔離的不是特別徹底,在上文也有所闡述,針對這種情況,如何更準(zhǔn)確獲取 cgroups 給容器限定的 Memory 和 CPU 值,這給平臺開發(fā)者帶來相應(yīng)的挑戰(zhàn)。此外,在容器化部署時(shí),作為用戶而言,需要遵循相應(yīng)的使用規(guī)范和流程,如每個(gè) Pod 都必須設(shè)置資源限額和健康檢測探針,在設(shè)置資源限額時(shí),又不能盲目設(shè)置,需要依賴監(jiān)控組件或者是開發(fā)者本身對自身應(yīng)用的認(rèn)知,進(jìn)行相關(guān)經(jīng)驗(yàn)值的設(shè)置。
多集群調(diào)度
對于如何管理多個(gè) k8s 集群,如何進(jìn)行跨集群調(diào)度、應(yīng)用部署和資源對象管理,這對于平臺本身,都是一個(gè)很大的挑戰(zhàn)。
調(diào)度均衡問題
隨著集群規(guī)模的擴(kuò)大以及微服務(wù)部署的數(shù)量增加,同個(gè)計(jì)算節(jié)點(diǎn),可以會(huì)運(yùn)行很多 Pod,這個(gè)時(shí)候就會(huì)出現(xiàn)資源爭用的問題,k8s 本身調(diào)度層面有兩個(gè)階段,分別是預(yù)選階段和優(yōu)選階段,每個(gè)階段都有對應(yīng)的調(diào)度策略和算法,關(guān)于如何均衡節(jié)點(diǎn)之后的調(diào)度,這需要在平臺層面上對調(diào)度算法有所研究,并進(jìn)行適當(dāng)?shù)恼{(diào)整。