技術分享 | Kubernetes 學習筆記之基礎知識篇

愛可生雲資料庫發表於2021-10-19

作者:張強

愛可生研發中心成員,後端研發工程師,目前負責DMP產品 Redis 相關業務開發。

本文來源:原創投稿

*愛可生開源社群出品,原創內容未經授權不得隨意使用,轉載請聯絡小編並註明來源。


一. 什麼是 Kubernetes ?

Kubernetes,又稱為 k8s(首字母為 k、首字母與尾字母之間有 8 個字元、尾字母為 s,所以簡稱 k8s )或者簡稱為「kube」,是一個可移植的、可擴充套件的開源平臺,用於管理容器化的工作負載和服務。相比與傳統部署以及虛擬化部署方式而言,具有如下特點:

  • 敏捷應用程式的建立和部署:與使用 VM 映象相比,提高了容器映象建立的簡便性和效率。
  • 持續開發、整合和部署:通過快速簡單的回滾(由於映象不可變性),支援可靠且頻繁的 容器映象構建和部署。
  • 關注開發與運維的分離:在構建/釋出時而不是在部署時建立應用程式容器映象, 從而將應用程式與基礎架構分離。
  • 可觀察性:不僅可以顯示作業系統級別的資訊和指標,還可以顯示應用程式的執行狀況和其他指標訊號。
  • 跨開發、測試和生產的環境一致性:在行動式計算機上與在雲中相同地執行。
  • 跨雲和作業系統發行版本的可移植性:可在 Ubuntu、RHEL、CoreOS、本地、 Google Kubernetes Engine 和其他任何地方執行。
  • 以應用程式為中心的管理:提高抽象級別,從在虛擬硬體上執行 OS 到使用邏輯資源在 OS 上執行應用程式。
  • 鬆散耦合、分散式、彈性、解放的微服務:應用程式被分解成較小的獨立部分, 並且可以動態部署和管理 - 而不是在一臺大型單機上整體執行。
  • 資源隔離:可預測的應用程式效能。
  • 資源利用:高效率和高密度。

1. Docker 的興起

要說 Kubernetes,還要從 docker 容器化技術談起。

2013年,Docker 公司(彼時還稱之為 dotCloud Inc.)在 PyCon 大會上首次公開介紹了 Docker 這一產品。當時最熱門的 PaaS 專案是 Cloud Foundary,然而 Docker 專案在 docker 映象上的優秀設計,解決了當時其他 PaaS 技術未能解決的應用打包和應用釋出的繁瑣步驟問題。大多數 docker 映象是直接由一個完整作業系統的所有檔案和目錄構成的,所以這個壓縮包裡的內容跟你本地開發和測試環境用的作業系統是完全一樣的 —— 正是這一優秀的特性使得 docker 專案在眾多 Pass 技術迅速崛起。

2. Docker 編排的進化

Swarm 的最大特點,是完全使用 Docker 專案原本的容器管理 API 來完成叢集管理。在部署了 Swarm 的多機環境下,使用者只需要使用原先的 Docker 指令建立一個容器,這個請求就會被 Swarm 攔截下來處理,然後通過具體的排程演算法找到一個合適的 Docker Daemon 執行起來。

2014年,docker 併購 Fig 專案,將其發展為現在的 Compose,使得容器的部署更加的便利 —— 可以使用配置檔案就可以完成較為複雜的容器的編排引數的配置。同年6月,Google 公司釋出了 Kubernetes。

2015 年 6 月 22 日,由 Docker 公司牽頭,CoreOS、Google、RedHat 等公司共同宣佈,Docker 公司將 Libcontainer 捐出,並改名為 RunC 專案,交由一個完全中立的基金會管理,然後以 RunC 為依據,大家共同制定一套容器和映象的標準和規範。這套標準和規範,就是 OCI( Open Container Initiative )。OCI 的提出,意在將容器執行時和映象的實現從 Docker 專案中完全剝離出來。從 API 到容器執行時的每一層,Kubernetes 專案都為開發者暴露出了可以擴充套件的外掛機制,鼓勵使用者通過程式碼的方式介入到 Kubernetes 專案的每一個階段。Kubernetes 專案的這個變革的效果立竿見影,很快在整個容器社群中催生出了大量的、基於 Kubernetes API 和擴充套件介面的二次創新工作,比如:

  • 微服務治理專案 Istio;
  • 有狀態應用部署框架 Operator;
  • 還有像 Rook 這樣的開源創業專案,它通過 Kubernetes 的可擴充套件介面,把 Ceph 這樣的重量級產品封裝成了簡單易用的容器儲存外掛。

二. Docker 容器的技術基礎

1. 程式與 Linux Namespaces

為了達到在容器中排除其他程式,docker 利用了 Linux 的 Namespace 機制。在 Linux 系統中建立執行緒的系統呼叫是 clone(),比如:

int pid = clone(main_function, stack_size, SIGCHLD, NULL);

此時,這個系統呼叫就會為我們建立一個新的程式,並且返回它的程式號 pid。而如果在用 clone() 系統呼叫建立一個新程式時,加入 CLONE_NEWPID 引數,比如:

int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL);

此時這個程式就會在一個全新的程式空間裡,在這個程式空間裡,它的 pid 是1。 除去 PID Namespace,Linux 系統還提供了 Mount、UTS、IPC、Newwork 和 User 等 Namespace。這就是為什麼容器中只能「看到」自身容器內的程式的原因了。

2. 資源限制與Linux Cgroups

雖然在容器中 PID 為1的程式只能看到容器裡的情況。但是從宿主機角度來看,它作為一個普通程式與其他程式依然是平等關係,也就說它能夠使用的資源(CPU、記憶體等),仍舊可以被宿主機上的其他程式佔用。這種情況導致了容器內的程式「隔離了但沒有完全隔離」的尷尬,是不符合容器作為一個「沙盒」的特性的。

Linux Cgroups 的全稱是 Linux Control Groups。它的最主要作用,就是限制一個程式組能夠使用的資源上限,包括 CPU、記憶體、磁碟、網路頻寬等。比如:

  • blkio,為塊裝置設定 I/O 限制,一般用於磁碟等裝置;
  • cpuset,為程式分配單獨的 CPU 核和對應的記憶體節點;
  • memory,為程式設定記憶體使用的限制。 等等

憑藉 Cgroups 的機制,容器化技術可以完成對容器內程式的資源限制。

3. 容器映象和檔案系統

Linux 作業系統中有一個名為chroot的命令,作用就是改變程式的根目錄到指定的位置。比如執行chroot $HOME/test /bin/bash之後,如果執行ls /,就會看到命令返回的內容都是 $HOME/test 目錄下的內容。Linux 作業系統的第一個 Namespace 是 Mount Namespace,正是基於 chroot 的不斷改良而來的。

為了讓容器中的目錄看起來更「真實」,一般會在容器內的根目錄下掛載一個完整作業系統的檔案系統。比如 Ubuntu 16.04 的 ISO。這樣在啟動容器後,在容器內執行ls /檢視根目錄下的內容,就是 Ubuntu 16.04 的所有目錄和檔案。這個掛載在容器根目錄上、用來為容器程式提供隔離後執行環境的檔案系統,就是所謂的「容器映象」,也稱之為 rootfs (根檔案系統)。

所以,對於 Docker 來說,核心流程即:

  1. 啟用 Linux Namespace 配置;
  2. 設定指定的 Cgroups 引數;
  3. 切換程式的根目錄(pivot_root 或 chroot)。

同時需要注意:rootfs 只是一個作業系統所包含的檔案、配置和目錄,並不包含 Linux 作業系統核心。所有的容器,都共享宿主機作業系統的核心。

綜上,一個「容器」,實際上是一個由 Linux Namespace、Linux Cgroups 和 rootfs 三種技術構建出來的程式的隔離環境。可以分為兩部分:

  • 容器映象,即聯合掛載在 /var/lib/docker/aufs/mnt 上的 rootfs。
  • 容器執行時,即一個由 Namespace + Cgroups 構成的隔離環境。

三. Kubernetes 架構簡介

從一個開發者的角度來說,真正需要關注的是容器映象。從容器叢集管理角度來說,而能夠定義容器組織和管理規範的「容器編排」技術是最重要的。其中最具代表性的容器編排工具,當屬 Docker 公司的 Compose+Swarm 組合,以及 Google 與 RedHat 公司共同主導的 Kubernetes。

Kubernetes 脫胎與 Google 內部的 Borg 專案。而 Borg,是承載 Google 公司整個基礎設施的核心依賴。在 Google 公司已經公開發表的基礎設施體系論文中,Borg 專案當仁不讓地位居整個基礎設施技術棧的最底層。同時在開源社群的努力下,Kubernetes 又修復了原來 Borg 體系中的缺陷和問題。藉著 Borg 專案的理論優勢,逐步確定了一個如下圖所示的全域性架構:

Kubernetes 專案的架構,都由 Master 和 Node 兩種節點組成,分別對應著控制節點和計算節點。其中:

  • Master 控制節點

    • 負責 API 服務的 kube-apiserver;
    • 負責排程的 kube-scheduler;
    • 負責內容編排的 kube-controller-manager;
    • 負責叢集持久化資料的 etcd 。
  • Node 計算節點

    • 負責與容器執行時互動的 kubelet 元件。另外,還為容器配置網路和持久化儲存;
    • 負責維護節點上的網路規則 kube-proxy 。
    • 容器執行時,Kubernetes 支援 Docker、containerd、CRI-O 等多個容器執行環境。

以上設計的考量是 Kubernetes 沒有把 Docker 作為架構的核心,而僅僅把 Docker 作為一個底層的容器執行時實現。Kubernetes 最核心的問題是:執行在大規模叢集中的各種任務之間,實際上存在著各種各樣的關係。這些關係的處理,才是作業編排和管理系統最困難的地方。

在一個稍複雜的叢集系統中,會有各種不同的服務關係存在,比如:一個 Web 應用與資料庫之間的訪問關係,一個計算服務和監控套件之間的訪問關係等。在容器技術普及之前,處於部署上的便利,這些應用可能會被部署在同一臺虛擬機器中。在容器技術出現後,原本的各個應用、元件、守護程式,都可以被分別做成映象並且執行在每個專屬的容器中。它們之間互不干涉,擁有各自的資源配額,可以被排程在整個叢集的任何一臺機器上。

為了更好地處理容器間應用的訪問關係,連線緊密的容器會被劃分為一個「Pod」,Pod 裡的容器共享同一個 Network Namespace、同一組資料卷等。同時 Kubernetes 會給 Pod 繫結一個 Service 服務(kube-proxy),主要作用是作為 Pod 的代理入口,從而代替 Pod 對外暴露一個固定的網路地址。

Kubernetes 還定義了基於 Pod 改進後的物件。比如用 Job 來描述一次性執行的 Pod;用 DaemonSet 來描述有且只有一個副本的守護程式;又比如 CronJob,用於描述定時任務。

Kubernetes 使用宣告式 API 方式來編排應用。所謂宣告式,就是提交一個定義好的 API 物件來「宣告」,表示所期望的最終狀態即可。如果提交的是一個個命令,去一步一步達到期望狀態,這就是「指令式」。比如,使用 kubernetes 來啟動一個 Nginx 容器映象,具體步驟為:

  1. 編寫一個名為 nginx-deployment.yaml 檔案,定義一個 Depoloyment 物件。主體是一個使用 Nginx 映象的Pod,可以定義副本數為 2 (replicas=2);
  2. 執行命令kubectl create -f nginx-deployment.yaml來啟動容器。

從設計目標來看, Kubernetes 提供了一套基於容器的、能夠便利地構建分散式系統的基礎依賴,能夠完成容器應用的部署以及應用的彈性管理。它所擅長的,就是按照使用者的定義和系統規則,來自動處理容器(應用/服務)之間的各種關係。而這一切,都是基於容器化技術來實現的。至於更進一步的學習 Kubernetes 架構相關知識,以及 Kubernetes 環境下的環境開發,請見後續相關文章更新。

四. 參考

[1] 維基百科:Kubernetes 詞條 (https://zh.wikipedia.org/wiki...)

[2] Kubernetes 官方文件 (https://kubernetes.io/)

[3] 深入剖析Kubernetes (https://time.geekbang.org/col...)

[4] 維基百科:Linux namespaces 詞條 (https://en.wikipedia.org/wiki...)

[5] 維基百科:Cgroups 詞條 (https://zh.wikipedia.org/wiki...)

相關文章