带你快速了解 Docker 和 Kubernetes
作者:honghaohu,騰訊 PCG 后臺開發工程師
從單機容器化技術 Docker 到分布式容器化架構方案 Kubernetes,當今容器化技術發展盛行。本文面向小白讀者,旨在快速帶領讀者了解 Docker、Kubernetes 的架構、原理、組件及相關使用場景。
Docker
1.什么是 Docker
Docker 是一個開源的應用容器引擎,是一種資源虛擬化技術,讓開發者可以打包他們的應用以及依賴包到一個可移植的容器中,然后發布到任何流行的 Linux 機器上。虛擬化技術演歷路徑可分為三個時代:
物理機時代,多個應用程序可能跑在一臺物理機器上
虛擬機時代,一臺物理機器啟動多個虛擬機實例,一個虛擬機跑多個應用程序
容器化時代,一臺物理機上啟動多個容器實例,一個容器跑多個應用程序
在沒有 Docker 的時代,我們會使用硬件虛擬化(虛擬機)以提供隔離。這里,虛擬機通過在操作系統上建立了一個中間虛擬軟件層 Hypervisor ,并利用物理機器的資源虛擬出多個虛擬硬件環境來共享宿主機的資源,其中的應用運行在虛擬機內核上。但是,虛擬機對硬件的利用率存在瓶頸,因為虛擬機很難根據當前業務量動態調整其占用的硬件資源,加之容器化技術蓬勃發展使其得以流行。
Docker、虛擬機對比:
另外開發人員在實際的工作中,經常會遇到測試環境或生產環境與本地開發環境不一致的問題,輕則修復保持環境一致,重則可能需要返工。但 Docker 恰好解決了這一問題,它將軟件程序和運行的基礎環境分開。開發人員編碼完成后將程序整合環境通過 DockerFile 打包到一個容器鏡像中,從根本上解決了環境不一致的問題。
2.Docker 的構成
Docker 由鏡像、鏡像倉庫、容器三個部分組成
鏡像: 跨平臺、可移植的程序+環境包
鏡像倉庫: 鏡像的存儲位置,有云端倉庫和本地倉庫之分,官方鏡像倉庫地址(https://hub.docker.com/)
容器: 進行了資源隔離的鏡像運行時環境
3.Docker 的實現原理
到此讀者們肯定很好奇 Docker 是如何進行資源虛擬化的,并且如何實現資源隔離的,其核心技術原理主要有(內容部分參考自 Docker 核心技術與實現原理):
(1).Namespace
在日常使用 Linux 或者 macOS 時,我們并沒有運行多個完全分離的服務器的需要,但是如果我們在服務器上啟動了多個服務,這些服務其實會相互影響的,每一個服務都能看到其他服務的進程,也可以訪問宿主機器上的任意文件,這是很多時候我們都不愿意看到的,我們更希望運行在同一臺機器上的不同服務能做到完全隔離,就像運行在多臺不同的機器上一樣。
命名空間 (Namespaces) 是 Linux 為我們提供的用于分離進程樹、網絡接口、掛載點以及進程間通信等資源的方法。Linux 的命名空間機制提供了以下七種不同的命名空間,通過這七個選項我們能在創建新的進程時設置新進程應該在哪些資源上與宿主機器進行隔離。
CLONE_NEWCGROUP
CLONE_NEWIPC
CLONE_NEWNET
CLONE_NEWNS
CLONE_NEWPID
CLONE_NEWUSER
CLONE_NEWUTS
在 Linux 系統中,有兩個特殊的進程,一個是 pid 為 1 的 /sbin/init 進程,另一個是 pid 為 2 的 kthreadd 進程,這兩個進程都是被 Linux 中的上帝進程 idle 創建出來的,其中前者負責執行內核的一部分初始化工作和系統配置,也會創建一些類似 getty 的注冊進程,而后者負責管理和調度其他的內核進程。
當在宿主機運行 Docker,通過docker run或docker start創建新容器進程時,會傳入 CLONE_NEWPID 實現進程上的隔離。
接著,在方法createSpec的setNamespaces中,完成除進程命名空間之外與用戶、網絡、IPC 以及 UTS 相關的命名空間的設置。
func?(daemon?*Daemon)?createSpec(c?*container.Container)?(*specs.Spec,?error)?{s?:=?oci.DefaultSpec()//?...if?err?:=?setNamespaces(daemon,?&s,?c);?err?!=?nil?{return?nil,?fmt.Errorf("linux?spec?namespaces:?%v",?err)}return?&s,?nil } func?setNamespaces(daemon?*Daemon,?s?*specs.Spec,?c?*container.Container)?error?{//?user//?network//?ipc//?uts//?pidif?c.HostConfig.PidMode.IsContainer()?{ns?:=?specs.LinuxNamespace{Type:?"pid"}pc,?err?:=?daemon.getPidContainer(c)if?err?!=?nil?{return?err}ns.Path?=?fmt.Sprintf("/proc/%d/ns/pid",?pc.State.GetPID())setNamespace(s,?ns)}?else?if?c.HostConfig.PidMode.IsHost()?{oci.RemoveNamespace(s,?specs.LinuxNamespaceType("pid"))}?else?{ns?:=?specs.LinuxNamespace{Type:?"pid"}setNamespace(s,?ns)}return?nil }網絡
當 Docker 容器完成命名空間的設置,其網絡也變成了獨立的命名空間,與宿主機的網絡互聯便產生了限制,這就導致外部很難訪問到容器內的應用程序服務。Docker 提供了 4 種網絡模式,通過--net指定。
host
container
none
bridge
由于后續介紹 Kubernetes 利用了 Docker 的 bridge 網絡模式,所以僅介紹該模式。Linux 中為了方便各網絡命名空間的網絡互相訪問,設置了 Veth Pair 和網橋來實現,Docker 也是基于此方式實現了網絡通信。
下圖中 eth0 與 veth9953b75 是一個 Veth Pair,eth0 與 veth3e84d4f 為另一個 Veth Pair。Veth Pair 在容器內一側會被設置為 eth0 模擬網卡,另一側連接 Docker0 網橋,這樣就實現了不同容器間網絡的互通。加之 Docker0 為每個容器配置的 iptables 規則,又實現了與宿主機外部網絡的互通。
掛載點
解決了進程和網絡隔離的問題,但是 Docker 容器中的進程仍然能夠訪問或者修改宿主機器上的其他目錄,這是我們不希望看到的。
在新的進程中創建隔離的掛載點命名空間需要在 clone 函數中傳入 CLONE_NEWNS,這樣子進程就能得到父進程掛載點的拷貝,如果不傳入這個參數子進程對文件系統的讀寫都會同步回父進程以及整個主機的文件系統。當一個容器需要啟動時,它一定需要提供一個根文件系統(rootfs),容器需要使用這個文件系統來創建一個新的進程,所有二進制的執行都必須在這個根文件系統中,并建立一些符號鏈接來保證 IO 不會出現問題。
另外,通過 Linux 的chroot命令能夠改變當前的系統根目錄結構,通過改變當前系統的根目錄,我們能夠限制用戶的權利,在新的根目錄下并不能夠訪問舊系統根目錄的結構個文件,也就建立了一個與原系統完全隔離的目錄結構。
(2).Control Groups(CGroups)
Control Groups(CGroups) 提供了宿主機上物理資源的隔離,例如 CPU、內存、磁盤 I/O 和網絡帶寬。主要由這幾個組件構成:
控制組(CGroup) 一個 CGroup 包含一組進程,并可以在這個 CGroup 上增加 Linux Subsystem 的各種參數配置,將一組進程和一組 Subsystem 關聯起來。
Subsystem 子系統 是一組資源控制模塊,比如 CPU 子系統可以控制 CPU 時間分配,內存子系統可以限制 CGroup 內存使用量。可以通過lssubsys -a命令查看當前內核支持哪些 Subsystem。
Hierarchy 層級樹 主要功能是把 CGroup 串成一個樹型結構,使 CGruop 可以做到繼承,每個 Hierarchy 通過綁定對應的 Subsystem 進行資源調度。
Task 在 CGroups 中,task 就是系統的一個進程。一個任務可以加入某個 CGroup,也可以從某個 CGroup 遷移到另外一個 CGroup。
在 Linux 的 Docker 安裝目錄下有一個 docker 目錄,當啟動一個容器時,就會創建一個與容器標識符相同的 CGroup,舉例來說當前的主機就會有以下層級關系:
每一個 CGroup 下面都有一個 tasks 文件,其中存儲著屬于當前控制組的所有進程的 pid,作為負責 cpu 的子系統,cpu.cfs_quota_us 文件中的內容能夠對 CPU 的使用作出限制,如果當前文件的內容為 50000,那么當前控制組中的全部進程的 CPU 占用率不能超過 50%。
當我們使用 Docker 關閉掉正在運行的容器時,Docker 的子控制組對應的文件夾也會被 Docker 進程移除。
(3).UnionFS
聯合文件系統(Union File System),它可以把多個目錄內容聯合掛載到同一個目錄下,而目錄的物理位置是分開的。UnionFS 可以把只讀和可讀寫文件系統合并在一起,具有寫時復制功能,允許只讀文件系統的修改可以保存到可寫文件系統當中。Docker 之前使用的為 AUFS(Advanced UnionFS),現為 Overlay2。
Docker 中的每一個鏡像都是由一系列只讀的層組成的,Dockerfile 中的每一個命令都會在已有的只讀層上創建一個新的層:
FROM?ubuntu:15.04 COPY?.?/app RUN?make?/app CMD?python?/app/app.py容器中的每一層都只對當前容器進行了非常小的修改,上述的 Dockerfile 文件會構建一個擁有四層 layer 的鏡像:
當鏡像被 命令創建時就會在鏡像的最上層添加一個可寫的層,也就是容器層,所有對于運行時容器的修改其實都是對這個容器讀寫層的修改。容器和鏡像的區別就在于,所有的鏡像都是只讀的,而每一個容器其實等于鏡像加上一個可讀寫的層,也就是同一個鏡像可以對應多個容器。
Kubernetes
Kubernetes,簡稱 K8s,其中 8 代指中間的 8 個字符。Kubernetes 項目龐大復雜,文章不能面面俱到,因此這個部分將向讀者提供一種主線學習思路:
什么是 Kubernetes?
Kubernetes 提供的組件及適用場景
Kubernetes 的架構
Kubernetes 架構模塊實現原理
有更多未交代或淺嘗輒止的地方讀者可以查閱文章或書籍深入研究。
1.為什么要 Kubernetes
盡管 Docker 為容器化的應用程序提供了開放標準,但隨著容器越來越多出現了一系列新問題:
單機不足以支持更多的容器
分布式環境下容器如何通信?
如何協調和調度這些容器?
如何在升級應用程序時不會中斷服務?
如何監視應用程序的運行狀況?
如何批量重新啟動容器里的程序?
...
Kubernetes 應運而生。
2.什么是 Kubernetes
Kubernetes 是一個全新的基于容器技術的分布式架構方案,這個方案雖然還很新,但卻是 Google 十幾年來大規模應用容器技術的經驗積累和升華的重要成果,確切的說是 Google 一個久負盛名的內部使用的大規模集群管理系統——Borg 的開源版本,其目的是實現資源管理的自動化以及跨數據中心的資源利用率最大化。
Kubernetes 具有完備的集群管理能力,包括多層次的安全防護和準入機制、多租戶應用支撐能力、透明的服務注冊和服務發現機制、內建的智能負載均衡器、強大的故障發現和自我修復能力、服務滾動升級和在線擴容能力、可擴展的資源自動調度機制,以及多力度的資源配額管理能力。同時,Kubernetes 提供了完善的管理工具,這些工具涵蓋了包括開發、部署測試、運維監控在內的各個環節,不僅是一個全新的基于容器技術的分布式架構解決方案,還是一個一站式的完備分布式系統開發和支撐平臺。
3.Kubernetes 術語
(1).Pod
Pod 是 Kubernetes 最重要的基本概念,可由多個容器(一般而言一個容器一個進程,不建議一個容器多個進程)組成,它是系統中資源分配和調度的最小單位。下圖是 Pod 的組成示意圖,其中有一個特殊的 Pause 容器:
Pause 容器的狀態標識了一個 Pod 的狀態,也就是代表了 Pod 的生命周期。另外 Pod 中其余容器共享 Pause 容器的命名空間,使得 Pod 內的容器能夠共享 Pause 容器的 IP,以及實現文件共享。以下是一個 Pod 的定義:
apiVersion:?v1??#?分組和版本 kind:?Pod???????#?資源類型 metadata:name:?myWeb???#?Pod名labels:app:?myWeb?#?Pod的標簽 spec:containers:-?name:?myWeb?#?容器名image:?kubeguide/tomcat-app:v1??#?容器使用的鏡像ports:-?containerPort:?8080?#?容器監聽的端口env:??#?容器內環境變量-?name:?MYSQL_SERVICE_HOSTvalue:?'mysql'-?name:?MYSQL_SERVICE_PORTvalue:?'3306'resources:???#?容器資源配置requests:??#?資源下限,m表示cpu配額的最小單位,為1/1000核memory:?"64Mi"cpu:?"250m"limits:????#?資源上限memory:?"128Mi"cpu:?"500m"EndPoint : PodIP + containerPort,代表一個服務進程的對外通信地址。一個 Pod 也存在具有多個 Endpoint 的情 況,比如當我們把 Tomcat 定義為一個 Pod 時,可以對外暴露管理端口與服務端口這兩個 Endpoint。
(2).Label
Label 是 Kubernetes 系統中的一個核心概念,一個 Label 表示一個 key=value 的鍵值對,key、value 的值由用戶指定。Label 可以被附加到各種資源對象上,例如 Node、Pod、Service、RC 等,一個資源對 象可以定義任意數量的 Label,同一個 Label 也可以被添加到任意數量的資源對象上。Label 通常在資源對象定義時確定,也可以在對象創建后動態添加或者刪除。給一個資源對象定義了 Label 后,我們隨后可以通過 Label Selector 查詢和篩選擁有這個 Label 的資源對象,來實現多維度的資源分組管理功能,以便靈活、方便地進行資源分配、調 度、配置、部署等管理工作。
Label Selector 當前有兩種表達式,基于等式的和基于集合的:
name=redis-slave: 匹配所有具有標簽name=redis-slave的資源對象。
env!=production: 匹配所有不具有標簽env=production的資源對象。
name in(redis-master, redis-slave):name=redis-master或者name=redis-slave的資源對象。
name not in(php-frontend):匹配所有不具有標簽name=php-frontend的資源對象。
以 myWeb Pod 為例:
apiVersion:?v1??#?分組和版本 kind:?Pod???????#?資源類型 metadata:name:?myWeb???#?Pod名labels:app:?myWeb?#?Pod的標簽當一個 Service 的 selector 中指明了這個 Pod 時,該 Pod 就會與該 Service 綁定
apiVersion:?v1 kind:?Service metadata:name:?myWeb spec:selector:app:?myWebports:-?port:?8080(3).Replication Controller
Replication Controller,簡稱 RC,簡單來說,它其實定義了一個期望的場景,即聲明某種 Pod 的副本數量在任意時刻都符合某個預期值。
RC 的定義包括如下幾個部分:
Pod 期待的副本數量
用于篩選目標 Pod 的 Label Selector
當 Pod 的副本數小于預期數量時,用于創建新 Pod 的模版(template)
當提交這個 RC 在集群中后,Controller Manager 會定期巡檢,確保目標 Pod 實例的數量等于 RC 的預期值,過多的數量會被停掉,少了則會創建補充。通過kubectl scale可以動態指定 RC 的預期副本數量。
目前,RC 已升級為新概念——Replica Set(RS),兩者當前唯一區別是,RS 支持了基于集合的 Label Selector,而 RC 只支持基于等式的 Label Selector。RS 很少單獨使用,更多是被 Deployment 這個更高層的資源對象所使用,所以可以視作 RS+Deployment 將逐漸取代 RC 的作用。
(4).Deployment
Deployment 和 RC 相似度超過 90%,無論是作用、目的、Yaml 定義還是具體命令行操作,所以可以將其看作是 RC 的升級。而 Deployment 相對于 RC 的一個最大區別是我們可以隨時知道當前 Pod“部署”的進度。實際上由于一個 Pod 的創建、調度、綁定節點及在目 標 Node 上啟動對應的容器這一完整過程需要一定的時間,所以我們期待系統啟動 N 個 Pod 副本的目標狀態,實際上是一個連續變化的“部署過程”導致的最終狀態。
apiVersion:?v1 kind:?Deployment metadata:name:?frontend spec:replicas:?3selector:matchLabels:app:?frontendmatchExpressions:-?{key:?app,?operator:?In,?values?[frontend]}template:metadata:labels:app:?frontendspec:containers:-?name:?tomcat_dempimage:?tomcatports:-?containerPort:?8080(5).Horizontal Pod Autoscaler
除了手動執行kubectl scale完成 Pod 的擴縮容之外,還可以通過 Horizontal Pod Autoscaling(HPA)橫向自動擴容來進行自動擴縮容。其原理是追蹤分析目標 Pod 的負載變化情況,來確定是否需要針對性地調整目標 Pod 數量。當前,HPA 有一下兩種方式作為 Pod 負載的度量指標:
CPUUtilizationPercentage,目標 Pod 所有副本自身的 CPU 利用率的平均值。
應用程序自定義的度量指標,比如服務在每秒內的相應請求數(TPS 或 QPS)
根據上邊定義,當 Pod 副本的 CPUUtilizationPercentage 超過 90%時就會出發自動擴容行為,數量約束為 1 ~ 3 個。
(6).StatefulSet
在 Kubernetes 系統中,Pod 的管理對象 RC、Deployment、DaemonSet 和 Job 都面向無狀態的服務。但現實中有很多服務是有狀態的,特別是 一些復雜的中間件集群,例如 MySQL 集群、MongoDB 集群、Akka 集 群、ZooKeeper 集群等,這些應用集群有 4 個共同點。
每個節點都有固定的身份 ID,通過這個 ID,集群中的成員可 以相互發現并通信。
集群的規模是比較固定的,集群規模不能隨意變動。
集群中的每個節點都是有狀態的,通常會持久化數據到永久 存儲中。
如果磁盤損壞,則集群里的某個節點無法正常運行,集群功 能受損。
因此,StatefulSet 具有以下特點:
StatefulSet 里的每個 Pod 都有穩定、唯一的網絡標識,可以用來 發現集群內的其他成員。假設 StatefulSet 的名稱為 kafka,那么第 1 個 Pod 叫 kafka-0,第 2 個叫 kafka-1,以此類推。
StatefulSet 控制的 Pod 副本的啟停順序是受控的,操作第 n 個 Pod 時,前 n-1 個 Pod 已經是運行且準備好的狀態。
StatefulSet 里的 Pod 采用穩定的持久化存儲卷,通過 PV 或 PVC 來 實現,刪除 Pod 時默認不會刪除與 StatefulSet 相關的存儲卷(為了保證數 據的安全)。
StatefulSet 除了要與 PV 卷捆綁使用以存儲 Pod 的狀態數據,還要與 Headless Service 配合使用。
Headless Service : Headless Service 與普通 Service 的關鍵區別在于, 它沒有 Cluster IP,如果解析 Headless Service 的 DNS 域名,則返回的是該 Service 對應的全部 Pod 的 Endpoint 列表。
(7).Service
Service 在 Kubernetes 中定義了一個服務的訪問入口地址,前段的應用(Pod)通過這個入口地址訪問其背后的一組由 Pod 副本組成的集群實例,Service 與其后端 Pod 副本集群之間則是通過 Label Selector 來實現無縫對接的。
apiVersion:?v1 kind:?service metadata:name:?tomcat_service spec:ports:-?port:?8080name:?service_port-?port:?8005name:?shutdown_portselector:app:?backendService 的負載均衡
在 Kubernetes 集群中,每個 Node 上會運行著 kube-proxy 組件,這其實就是一個負載均衡器,負責把對 Service 的請求轉發到后端的某個 Pod 實例上,并在內部實現服務的負載均衡和繪畫保持機制。其主要的實現就是每個 Service 在集群中都被分配了一個全局唯一的 Cluster IP,因此我們對 Service 的網絡通信根據內部的負載均衡算法和會話機制,便能與 Pod 副本集群通信。
Service 的服務發現
因為 Cluster IP 在 Service 的整個聲明周期內是固定的,所以在 Kubernetes 中,只需將 Service 的 Name 和 其 Cluster IP 做一個 DNS 域名映射即可解決。
(8).Volume
Volume 是 Pod 中能夠被多個容器訪問的共享目錄,Kubernetes 中的 Volume 概念、用途、目的與 Docker 中的 Volumn 比較類似,但不等價。首先,其可被定義在 Pod 上,然后被 一個 Pod 里的多個容器掛載到具體的文件目錄下;其次,Kubernetes 中的 Volume 與 Pod 的生命周期相同,但與容器的生命周期不相關,當容器終止或者重啟時,Volume 中的數據也不會丟失。
template:metadata:labels:app:?frontendspec:volumes:??#?聲明可掛載的volume-?name:?dataVolemptyDir:?{}containers:-?name:?tomcat_demoimage:?tomcatports:-?containerPort:?8080volumeMounts:??#?將volume通過name掛載到容器內的/mydata-data目錄-?mountPath:?/mydata-dataname:?dataVolKubernetes 提供了非常豐富的 Volume 類型:
emptyDir,它的初始內容為空,并且無須指定宿主機上對應的目錄文件,因為這是 Kubernetes 自動分配的一個目錄,當 Pod 從 Node 上移除 emptyDir 中的數據也會被永久刪除,適用于臨時數據。
hostPath,hostPath 為在 Pod 上掛載宿主機上的文件或目錄,適用于持久化保存的數據,比如容器應用程序生成的日志文件。
NFS,可使用 NFS 網絡文件系統提供的共享目錄存儲數據。
其他云持久化盤等
(9).Persistent Volume
在使用虛擬機的情況下,我們通常會先定義一個網絡存儲,然后從中 劃出一個“網盤”并掛接到虛擬機上。Persistent Volume(PV) 和與之相關聯的 Persistent Volume Claim(PVC) 也起到了類似的作用。PV 可以被理解成 Kubernetes 集群中的某個網絡存儲對應的一塊存儲,它與 Volume 類似,但有以下區別:
PV 只能是網絡存儲,不屬于任何 Node,但可以在每個 Node 上訪問。
PV 并不是被定義在 Pod 上的,而是獨立于 Pod 之外定義的。
accessModes,有幾種類型,1.ReadWriteOnce:讀寫權限,并且只能被單個 Node 掛載。2. ReadOnlyMany:只讀權限,允許被多個 Node 掛載。3.ReadWriteMany:讀寫權限,允許被多個 Node 掛載。
如果 Pod 想申請某種類型的 PV,首先需要定義一個 PersistentVolumeClaim 對象,
apiVersion:?v1 kind:?PersistentVolumeClaim??#?聲明pvc metadata:name:?pvc001spec:resources:requests:storage:?5GiaccessMods:-?ReadWriteOnce然后在 Pod 的 Volume 中引用 PVC 即可。
volumes:-?name:?mypdpersistentVolumeClaim:claimName:?pvc001PV 有以下幾種狀態:
Available:空閑
Bound:已綁定到 PVC
Relead:對應 PVC 被刪除,但 PV 還沒被回收
Faild:PV 自動回收失敗
(10).Namespace
Namespace 在很多情況下用于實現多租戶的資源隔離。分組的不同項目、小組或用戶組,便于不同的分組在共享使用整個集群的資源的同時還能被分別管理。Kubernetes 集群在啟動后會創建一個名為 default 的 Namespace,通過 kubectl 可以查看:
(11).ConfigMap
我們知道,Docker 通過將程序、依賴庫、數據及 配置文件“打包固化”到一個不變的鏡像文件中的做法,解決了應用的部署的難題,但這同時帶來了棘手的問題,即配置文件中的參數在運行期如何修改的問題。我們不可能在啟動 Docker 容器后再修改容器里的配置 文件,然后用新的配置文件重啟容器里的用戶主進程。為了解決這個問題,Docker 提供了兩種方式:
在運行時通過容器的環境變量來傳遞參數;
通過 Docker Volume 將容器外的配置文件映射到容器內。
在大多數情況下,后一種方式更合 適我們的系統,因為大多數應用通常從一個或多個配置文件中讀取參數。但這種方式也有明顯的缺陷:我們必須在目標主機上先創建好對應 配置文件,然后才能映射到容器里。上述缺陷在分布式情況下變得更為嚴重,因為無論采用哪種方式, 寫入(修改)多臺服務器上的某個指定文件,并確保這些文件保持一致,都是一個很難完成的目標。針對上述問題, Kubernetes 給出了一個很巧妙的設計實現。
首先,把所有的配置項都當作 key-value 字符串,這些配置項可以 作為 Map 表中的一個項,整個 Map 的數據可以被持久化存儲在 Kubernetes 的 Etcd 數據庫中,然后提供 API 以方便 Kubernetes 相關組件或 客戶應用 CRUD 操作這些數據,上述專門用來保存配置參數的 Map 就是 Kubernetes ConfigMap 資源對象。Kubernetes 提供了一種內建機制,將存儲在 etcd 中的 ConfigMap 通過 Volume 映射的方式變成目標 Pod 內的配置文件,不管目標 Pod 被調度到哪臺服務器上,都會完成自動映射。進一步地,如果 ConfigMap 中的 key-value 數據被修改,則映射到 Pod 中的“配置文件”也會隨之自動更新。
4.Kubernetes 的架構
Kubernetes 由 Master 節點、 Node 節點以及外部的 ETCD 集群組成,集群的狀態、資源對象、網絡等信息存儲在 ETCD 中,Mater 節點管控整個集群,包括通信、調度等,Node 節點為工作真正執行的節點,并向主節點報告。Master 節點由以下組件構成:
(1).Master 組件:
API Server —— 提供 HTTP Rest 接口,是所有資源增刪改查和集群控制的唯一入口。(在集群中表現為名稱是 kubernetes 的 service)。可以通過 Dashboard 的 UI 或 kubectl 工具來與其交互。(1)集群管理的 API 入口;
(2)資源配額控制入口;
(3)提供完備的集群安全機制。
Controller Manager —— 資源對象的控制自動化中心。即監控 Node,當故障時轉移資源對象,自動修復集群到期望狀態。
Scheduler —— 負責 Pod 的調度,調度到最優的 Node。
(2).Node 組件:
kubelet —— 負責 Pod 內容器的創建、啟停,并與 Master 密切協作實現集群管理(注冊自己,匯報 Node 狀態)。
kube-proxy —— 實現 k8s Service 的通信與負載均衡。
Docker Engine —— Docker 引擎,負責本機容器的創建和管理。
5.Kubernetes 架構模塊實現原理
(1).API Server
Kubernetes API Server 通過一個名為 kube-apiserver 的進程提供服務,該進程運行在 Master 上。在默認情況下,kube-apiserver 進程在本機的 8080 端口(對應參數--insecure-port)提供 REST 服務。我們可以同時啟動 HTTPS 安全端口(--secure-port=6443)來啟動安全機制,加強 REST API 訪問的安全性。
由于 API Server 是 Kubernetes 集群數據的唯一訪問入口,因此安全性與高性能就成為 API Server 設計和實現的兩大核心目標。通過采用 HTTPS 安全傳輸通道與 CA 簽名數字證書強制雙向認證的方式,API Server 的安全性得以保障。此外,為了更細粒度地控制用戶或應用對 Kubernetes 資源對象的訪問權限,Kubernetes 啟用了 RBAC 訪問控制策略。Kubernetes 的設計者綜合運用以下方式來最大程度地保證 API Server 的性 能。
API Server 擁有大量高性能的底層代碼。在 API Server 源碼中 使用協程(Coroutine)+隊列(Queue)這種輕量級的高性能并發代碼, 使得單進程的 API Server 具備了超強的多核處理能力,從而以很快的速 度并發處理大量的請求。
普通 List 接口結合異步 Watch 接口,不但完美解決了 Kubernetes 中各種資源對象的高性能同步問題,也極大提升了 Kubernetes 集群實時響應各種事件的靈敏度。
采用了高性能的 etcd 數據庫而非傳統的關系數據庫,不僅解決 了數據的可靠性問題,也極大提升了 API Server 數據訪問層的性能。在 常見的公有云環境中,一個 3 節點的 etcd 集群在輕負載環境中處理一個請 求的時間可以低于 1ms,在重負載環境中可以每秒處理超過 30000 個請求。
(2).安全認證
RBAC
Role-Based Access Control(RBAC),基于角色的訪問控制。
4 種資源對象
Role
RoleBinding
ClusterRole
ClusterRoleBinding
Role 與 ClusterRole
一個角色就是一組權限的集合,都是以許可形式,不存在拒絕的規則。Role 作用于一個命名空間中,ClusterRole 作用于整個集群。
apiVersion:rbac.authorization.k8s.io/v1beta1 kind:Role metadata:namespace:?default?#ClusterRole可以省略,畢竟是作用于整個集群name:?pod-reader rules: -?apiGroups:?[""]resources:?["pod"]verbs:?["get","watch","list"]RoleBinding 和 ClusterRoleBinding 是把 Role 和 ClusterRole 的權限綁定到 ServiceAccount 上。
kind:?ClusterRoleBinding apiVersion:?rbac.authorization.k8s.io/v1 metadata:namespace:?defaultname:?app-admin subjects: -???kind:?ServiceAccountname:?appapiGroup:?""namespace:?default roleRef:kind:?ClusterRolename:?cluster-adminapiGroup:?rbac.authorization.k8s.ioServiceAccount
Service Account 也是一種賬號,但它并不是給 Kubernetes 集群的用戶 (系統管理員、運維人員、租戶用戶等)用的,而是給運行在 Pod 里的進程用的,它為 Pod 里的進程提供了必要的身份證明。在每個 Namespace 下都有一個名為 default 的默認 Service Account 對象,在這個 Service Account 里面有一個名為 Tokens 的可以當作 Volume 被掛載到 Pod 里的 Secret,當 Pod 啟動時,這個 Secret 會自動被掛載到 Pod 的指定目錄下,用來協助完成 Pod 中的進程訪問 API Server 時的身份鑒權。
(3).Controller Manager
下邊介紹幾種 Controller Manager 的實現組件
ResourceQuota Controller
kubernetes 的配額管理使用過 Admission Control 來控制的,提供了兩種約束,LimitRanger 和 ResourceQuota。LimitRanger 作用于 Pod 和 Container 之上(limit ,request),ResourceQuota 則作用于 Namespace。資源配額,分三個層次:
容器級別,對容器的 CPU、memory 做限制
Pod 級別,對一個 Pod 內所有容器的可用資源做限制
Namespace 級別,為 namespace 做限制,包括:
Namespace Controller
管理 Namesoace 創建刪除.
Endpoints Controller
Endpoints 表示一個 service 對應的所有 Pod 副本的訪問地址,而 Endpoints Controller 就是負責生成和維護所有 Endpoints 對象的控制器。
負責監聽 Service 和對應 Pod 副本的變化,若 Service 被創建、更新、刪除,則相應創建、更新、刪除與 Service 同名的 Endpoints 對象。
EndPoints 對象被 Node 上的 kube-proxy 使用。
(4).Scheduler
Kubernetes Scheduler 的作用是將待調度的 Pod(API 新創 建的 Pod、Controller Manager 為補足副本而創建的 Pod 等)按照特定的調 度算法和調度策略綁定(Binding)到集群中某個合適的 Node 上,并將綁定信息寫入 etcd 中。Kubernetes Scheduler 當前提供的默認調度流程分為以下兩步。
預選調度過程,即遍歷所有目標 Node,篩選出符合要求的候 選節點。為此,Kubernetes 內置了多種預選策略(xxx Predicates)供用戶選擇。
確定最優節點,在第 1 步的基礎上,采用優選策略(xxx Priority)計算出每個候選節點的積分,積分最高者勝出。
(5).網絡
Kubernetes 的網絡利用了 Docker 的網絡原理,并在此基礎上實現了跨 Node 容器間的網絡通信。
同一個 Node 下 Pod 間通信模型:
不同 Node 下 Pod 間的通信模型(CNI 模型實現):
CNI 提供了一種應用容器的插件化網絡解決方案,定義對容器網絡 進行操作和配置的規范,通過插件的形式對 CNI 接口進行實現,以 Flannel 舉例,完成了 Node 間容器的通信模型。
可以看到,Flannel 首先創建了一個名為 flannel0 的網橋,而且這個 網橋的一端連接 docker0 網橋,另一端連接一個叫作 flanneld 的服務進程。flanneld 進程并不簡單,它上連 etcd,利用 etcd 來管理可分配的 IP 地 址段資源,同時監控 etcd 中每個 Pod 的實際地址,并在內存中建立了一 個 Pod 節點路由表;它下連 docker0 和物理網絡,使用內存中的 Pod 節點 路由表,將 docker0 發給它的數據包包裝起來,利用物理網絡的連接將 數據包投遞到目標 flanneld 上,從而完成 Pod 到 Pod 之間的直接地址通信。
(6).服務發現
從 Kubernetes 1.11 版本開始,Kubernetes 集群的 DNS 服務由 CoreDNS 提供。CoreDNS 是 CNCF 基金會的一個項目,是用 Go 語言實現的高性能、插件式、易擴展的 DNS 服務端。
結語
文章包含的內容說多不多,說少不少,但對于 Docker、Kubernetes 知識原理的小白來說是足夠的,筆者按照自己的學習經驗,以介紹為出發點,讓大家更能了解相關技術原理,所以實操的部分較少。Kubernetes 技術組件還是十分豐富的,文章有選擇性地進行了介紹,感興趣的讀者可以再自行從官方或者書籍中學習了解。(附《Kubernetes 權威指南——從 Docker 到 Kubernetes 實踐全接觸》第四版)
相關鏈接
Docker 核心技術與實現原理
官方鏡像倉庫地址
Kubernetes 官網
Kubernetes 中文社區
最近其他好文:
淺談 Protobuf 編碼
gRPC 基礎概念詳解
不吹不擂,一文揭秘鴻蒙操作系統
騰訊程序員視頻號最新視頻
總結
以上是生活随笔為你收集整理的带你快速了解 Docker 和 Kubernetes的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: gRPC 基础概念详解
- 下一篇: 研效优化实践:Python单测——从入门