1. 程式人生 > 其它 >雲原生入門 第三章:容器編排

雲原生入門 第三章:容器編排

1. 概述

在本章中,我們將瞭解容器編排的挑戰和機遇,以及它對網路和儲存有特殊要求的原因。

在開始討論容器的編排之前,讓我們先後退幾步,瞭解什麼是容器,以及是什麼使它在構建和操作應用程式時如此有用。

2. 學習目標

在本章結束時,你應該能夠:

  • 解釋什麼是容器,以及它與虛擬機器的區別。
  • 描述容器編排的基本原理。
  • 討論容器網路和儲存的挑戰。
  • 解釋服務網格的好處。

3. 使用容器

應用程式開發的歷史也是將這些應用程式打包為不同平臺和作業系統的歷史。
讓我們以一個用流行的程式語言Python編寫的簡單web應用程式為例。要在伺服器或本地機器上執行應用程式,系統通常需要滿足以下特定要求:

  • 安裝和配置基本作業系統
  • 安裝核心Python包來執行程式
  • 安裝程式使用的Python擴充套件
  • 為您的系統配置網路
  • 連線到第三方系統,如資料庫、快取或儲存。

雖然開發人員最瞭解自己的應用程式及其依賴關係,但通常是由系統管理員提供基礎設施、安裝所有依賴關係並配置應用程式執行的系統。這個過程非常容易出錯,而且很難維護,因此伺服器只用於單一目的配置,比如執行資料庫或應用伺服器,然後通過網路連線。

為了更有效地利用伺服器硬體,可以使用虛擬機器來模擬一個具有cpu、記憶體、儲存、網路、作業系統和軟體的完整伺服器。這允許在同一硬體上執行多個隔離的伺服器。

在廣泛採用容器之前,伺服器虛擬化是執行獨立且易於處理的應用程式的最有效方式,但由於必須執行包括核心在內的整個作業系統,如果需要執行大量伺服器,那麼它總是會帶來一些開銷。

容器可以用來解決這兩個問題,管理應用程式的依賴關係,並比旋轉大量虛擬機器更有效地執行。

4. 容器基礎

與普遍的看法相反,容器技術比人們預期的要古老得多。現代容器技術的最早祖先之一是chroot命令,它於1979年在Version 7 Unix中引入。chroot命令可用於將程序與根檔案系統隔離開來,並基本上將檔案從程序中“隱藏”起來,並模擬一個新的根目錄。隔離環境是所謂的chroot監獄,在這種環境中,程序無法訪問檔案,但檔案仍然存在於系統中。


可以在檔案系統的不同位置建立Chroot目錄

雖然chroot是一項相當古老的技術,但它仍然在一些流行的軟體專案中使用。我們今天擁有的容器技術仍然體現了這一概念,但是是一個現代化的版本,並且有很多特性。
為了比chroot更能隔離程序,當前的Linux核心提供了像名稱空間和cgroup這樣的特性。
名稱空間用於隔離各種資源,例如網路。可以使用網路名稱空間提供網路介面和路由表的完整抽象。這允許程序擁有自己的IP地址。Linux Kernel 5.6目前提供了8個名稱空間:

  • pid-程序ID提供了一個具有自己的程序ID集的程序。
  • net - network允許程序擁有自己的網路堆疊,包括IP地址。
  • MNT - mount抽象檔案系統檢視並管理掛載點。
  • Ipc -程序間通訊提供了命名共享記憶體段的分離。
  • user -為程序提供自己的一組使用者id和組id。
  • uts - Unix時間共享允許程序擁有自己的主機名和域名。
  • cgroup-一個新的名稱空間,允許程序擁有自己的一組Cgroup根目錄。
  • time-最新的名稱空間可以用來虛擬化系統的時鐘。

cgroup被用來組織層次結構組中的程序,併為它們分配資源,如記憶體和CPU。當你想限制你的應用程式容器的記憶體,比方說4GB, cgroup被用來確保這些限制。
Docker於2013年推出,成為building and running containers的代名詞。雖然Docker並沒有發明用於執行容器的技術,但他們以一種智慧的方式將現有的技術結合在一起,使容器對使用者更加友好和方便。
乍一看,容器似乎與虛擬機器非常相似,但是理解它們之間的差異是至關重要的。雖然虛擬機器模擬一個完整的機器,包括作業系統和核心,但容器共享主機的核心,正如所述,它們只是獨立的程序。
虛擬機器會帶來一些開銷,比如啟動時間、大小或執行作業系統的資源使用情況。另一方面,容器實際上是程序,就像您可以在機器上啟動的瀏覽器一樣,因此它們啟動得更快,佔用的空間也更小。


傳統部署vs虛擬化部署vs容器部署
在許多情況下,這不是使用容器或虛擬機器的問題,而是使用這兩種技術來從容器的效率中獲益,但仍然使用虛擬機器的更大隔離帶來的安全優勢。

5. 執行容器

要執行行業標準的容器,你不需要使用Docker;您可以只遵循OCI runtime-spec 規範標準。Open Container Initiative還維護一個名為runC的容器執行時引用實現。這種低階執行時被用於各種工具來啟動容器,包括Docker本身。
如果您是一名開發人員,並且瞭解面向物件程式設計,您可以想象容器映像和執行容器之間的關係,就像一個類,以及該類的例項化。
安裝Docker後,你可以像這樣啟動容器:

docker run nginx

runtime-specimage-spec密切相關,我們將在下一章中討論image-spec,因為它描述瞭如何解壓容器映像,然後管理整個容器生命週期,從建立容器環境,到啟動程序、停止和刪除它。
對於您的本地機器,有很多可供選擇的替代方案,其中一些僅用於構建映像,如buildahkaniko,而其他的則作為Docker的完全替代方案,如podman
Podman提供了與Docker相似的API,可以作為替代。此外,它還提供了一些額外的特性,比如執行沒有根許可權的容器,以及我們將在後面發現的Pod概念的使用。

6. 構建映象

為什麼容器首先被稱為容器?這裡使用的隱喻是針對按照ISO 668標準化的集裝箱的使用。海運集裝箱的標準格式使得它可以很容易地堆放在集裝箱船上,無論裡面裝的是什麼,都可以很容易地用起重機解除安裝或裝上卡車。您將看到容器和雲原生世界中的許多術語都遵循這個航海主題。
Docker重用了所有元件來隔離程序,比如namespacescgroup,但是幫助容器實現突破的關鍵是容器映像的引入。
容器映像使容器具有可移植性,並且易於在各種系統上重用。Docker對容器映象的描述如下:
Docker容器映象是一個輕量級的、獨立的、可執行的軟體包,它包含執行應用程式所需的一切:程式碼、執行時、系統工具、系統庫和設定。
2015年,Docker使其流行起來的影象格式被捐贈給了新成立的開放容器倡議組織,也被稱為 OCI image-spec,可以在GitHub上找到。映像由檔案系統包和元資料組成。


Container Images
可以通過從一個名為Dockerfile的構建檔案中讀取說明來構建映像。這些說明幾乎與在伺服器上安裝應用程式時使用的說明相同。下面是一個Dockerfile的例子,它包含了一個Python指令碼:

# Every container image starts with a base image.
# This could be your favorite linux distribution
FROM ubuntu:20.04 

# Run commands to add software and libraries to your image
# Here we install python3 and the pip package manager
RUN apt-get update && \
    apt-get -y install python3 python3-pip 

# The copy command can be used to copy your code to the image
# Here we copy a script called "my-app.py" to the containers filesystem
COPY my-app.py /app/ 

# Defines the workdir in which the application runs
# From this point on everything will be executed in /app
WORKDIR /app

# The process that should be started when the container runs
# In this case we start our python app "my-app.py"
CMD ["python3","my-app.py"]

如果你已經在你的機器上安裝了Docker,你可以用下面的命令來構建映象:

docker build -t my-python-image -f Dockerfile

使用-t my-python-image引數可以為映像指定一個名稱標記,使用-f Dockerfile可以指定可以找到Dockerfile的位置。這使開發人員能夠管理應用程式的所有依賴項,並將其打包以便執行,而不是將該任務留給另一個人或團隊。
要分發這些images,可以使用container registry。這只不過是一個你可以上傳和下載圖片的網路伺服器。Docker有內建的推和拉命令:

docker push my-registry.com/my-python-image
docker pull my-registry.com/my-python-image

7. 容器安全

理解容器與虛擬機器有不同的安全需求是很重要的。很多人依賴於容器的隔離特性,但這可能是非常危險的。
當容器在一臺機器上啟動時,它們總是共享同一個核心,如果允許容器呼叫核心函式(例如殺死其他程序或通過建立路由規則修改主機網路),這就會對整個系統構成風險。您可以在Docker文件中瞭解更多關於核心功能的資訊。
最大的安全風險之一(不僅是在容器領域)是執行具有太多特權的程序,特別是以root或管理員身份啟動程序。不幸的是,這個問題在過去被忽視了很多,有很多容器作為根使用者執行。
容器引入的一個相當新的攻擊面是公共影象的使用。兩個最流行的公共映像註冊中心是Docker HubQuay,雖然它們提供了公共訪問的映像,但您必須確保這些映像沒有被修改成包含惡意軟體。
Sysdig有一篇關於如何避免大量安全問題和構建安全容器映像的很棒的部落格文章。
一般來說,安全性並不是只能在容器層實現的。這是一個持續的過程,需要隨時調整。雲本地安全性的4C可以大致說明在使用容器時需要保護哪些層。確保覆蓋每一層,因為它有效地保護了內部的一層。Kubernetes文件是理解層的一個很好的起點。


The 4C's of Cloud Native Security, retrieved from the Kubernetes documentation

8. 容器編排

在本地機器或單個伺服器上執行幾個容器是相當容易的,但是容器的使用方式帶來了關於容器操作的新挑戰。這個概念的高效率導致應用程式和服務變得越來越小,您會發現現代應用程式可以由許多容器組成。
擁有大量鬆散耦合、隔離和獨立的小容器是所謂的微服務體系結構的基礎。這些小容器是自包含的業務邏輯的小部分,它們是更大的應用程式的一部分。
如果必須管理和部署大量容器,那麼很快就需要一個系統來幫助管理這些容器。需要解決的問題包括:

  • 提供可以執行容器的虛擬機器之類的計算資源
  • 以一種有效的方式將容器排程到伺服器
  • 為容器分配CPU和記憶體等資源
  • 管理容器的可用性,並在它們失敗時更換它們
  • 如果負載增加,縮放容器
  • 提供網路將容器連線在一起
  • 如果容器需要持久化資料,則提供儲存。

容器編排系統提供了一種方法來構建多個伺服器的叢集,並在其上託管容器。大多數容器編排系統由兩部分組成:負責管理容器的控制平面和實際託管容器的工作節點
多年來,已經有幾種系統可以用於編排,但大多數系統在今天已經不再重要,行業已經選擇Kubernetes作為編排容器的標準系統。

9. 容器網路

微服務架構在很大程度上依賴於網路通訊。與單片應用程式不同,微服務實現了一個介面,可以呼叫該介面來發出請求。例如,在電子商務應用程式中,您可以有一個響應產品列表的服務。
網路名稱空間允許每個容器擁有自己唯一的IP地址,因此多個應用程式可以開啟相同的網口;例如,您可以有多個容器化的web伺服器,它們都開放埠8080。
為了使應用程式可以從主機系統外部訪問,容器能夠將容器中的一個埠對映到主機系統中的一個埠。
為了允許容器跨主機進行通訊,我們可以使用overlay network,將它們放在跨越主機系統的虛擬網路中。
這使得容器之間的通訊非常容易,而系統管理員不需要在主機和容器之間配置複雜的網路和路由。
大多數覆蓋網路還需要處理IP地址管理,如果手動實現,這將是大量的工作。在這種情況下,覆蓋網路管理哪個容器獲得哪個IP地址,以及流量如何流動以訪問各個容器。


大多數現代的容器網路實現都基於容器網路介面(CNI)。CNI是一個可以用來編寫或配置網路外掛的標準,並且可以很容易地在各種容器編排平臺上交換不同的外掛。

10. 服務發現 & DNS

在很長一段時間裡,傳統資料中心的伺服器管理是可以管理的。許多系統管理員甚至記得他們必須使用的重要系統的所有IP地址。大量的伺服器列表、它們的主機名、IP地址和用途——都是手動維護的——都是日常事務。
在容器編排平臺中,事情要複雜得多:

  • 成百上千個帶有獨立IP地址的容器
  • 容器被部署在不同的主機、不同的資料中心甚至地理位置上
  • 容器或服務需要DNS進行通訊。使用IP地址幾乎是不可能的
  • 關於container的資訊在刪除時必須從系統中刪除。

解決這個問題的方法還是自動化。所有資訊都放在Service Registry中,而不是手工維護的伺服器列表(在本例中是容器)。在網路中查詢其他服務並請求關於它們的資訊稱為服務發現(Service Discovery)。

  • 擁有服務API的現代DNS伺服器可以用於在建立新服務時註冊它們。這種方法非常簡單,因為大多陣列織已經擁有具有適當功能的DNS伺服器。
  • 使用高度一致的資料儲存,特別是用於儲存關於服務的資訊。許多系統能夠通過強大的故障轉移機制進行高可用性操作。流行的選擇,特別是對於叢集來說,是etcdConsulApache Zookeeper

11. 服務網格(Service Mesh)

由於網路是微服務和容器如此重要的一部分,因此對於開發人員和管理員來說,網路可能變得非常複雜和不透明。除此之外,當容器彼此通訊時,還需要監視訪問控制網路流量加密等許多功能。
不必在應用程式中實現所有這些功能,只需啟動第二個實現了這些功能的容器。用來管理網路流量的軟體叫做代理。這是一個位於客戶機和伺服器之間的伺服器應用程式,可以在網路流量到達伺服器之前修改或過濾網路流量。受歡迎的代表是nginx, haproxyenvoy
更進一步,服務網格將代理伺服器新增到架構中的每個容器中。

Istio .io中檢索
現在可以使用代理來處理服務之間的網路通訊。
讓我們以加密為例。如果兩個或多個應用程式在彼此通訊時應該加密它們的通訊,那麼就需要新增庫、配置和管理數字證書,以證明所涉及的應用程式的身份。這可能是大量的工作,而且如果不特別小心的話,也可能容易出錯。
當使用服務網格時,應用程式不直接相互通訊,而是通過代理路由流量。目前最流行的服務網格是istiolinkerd。雖然它們在實現上有差異,但架構是相同的。
服務網格中的代理構成了資料平面。這是實現網路規則和塑造流量流的地方。
這些規則在服務網格的控制平面中集中管理。在這裡,您可以定義流量如何從服務A流向服務B,以及應該對代理應用哪些配置。
因此,無需編寫程式碼並安裝庫,只需編寫一個配置檔案,在其中告訴服務網格,服務a和服務B應該始終加密通訊。然後,配置被上傳到控制平面,並被分發到資料平面以執行新規則。
很長一段時間以來,術語“服務網格”只描述了容器平臺中如何使用代理處理流量的基本思想。服務網格介面(Service Mesh Interface, SMI)專案旨在定義一個關於如何實現來自不同提供者的服務網格的規範。他們非常關注Kubernetes,他們的目標是標準化服務網格的終端使用者體驗,以及為希望與Kubernetes整合的提供商提供一個標準。你可以在GitHub上找到當前的規範

12. 容器儲存

從儲存的角度來看,容器有一個明顯的缺陷:它們是短暫的。要理解其確切含義,我們需要了解當容器從container images開始時發生了什麼。
一般來說,container images是隻讀的,由不同的層組成,這些層包括您在構建階段新增的所有內容。這確保每次從映像啟動容器時,都能獲得相同的行為和功能。您可能可以想象,許多應用程式都需要編寫檔案。為了允許寫入檔案,當您從映像啟動容器時,會在容器映像的頂部放置一個讀寫層。

容器層,從Docker文件中檢索
這裡的問題是,當容器停止或刪除時,這個讀寫層就會丟失。就像你的電腦在你關閉它的時候就會被刪除一樣。要持久化資料,您需要將其寫入磁碟。
如果容器需要在主機上持久化資料,可以使用捲來實現這一點。其概念和技術非常簡單:不是隔離程序的整個檔案系統,而是將駐留在主機上的目錄傳遞到容器檔案系統中。如果你認為這會削弱容器的隔離性,你是對的。當使用容器卷時,可以有效地訪問主機檔案系統。

資料在同一主機上的兩個容器之間共享

當您編排許多容器時,在啟動容器的主機上持久化資料可能不是惟一的挑戰。通常,資料需要由在不同主機系統上啟動的多個容器訪問,或者當一個容器在不同主機上啟動時,它仍然可以訪問它的卷。
Kubernetes這樣的容器編排系統可以幫助緩解這些問題,但總是需要一個連線到主機伺服器的健壯儲存系統。

儲存是通過中央儲存系統提供的。伺服器A和伺服器B上的容器可以共享一個捲來讀寫資料

為了跟上各種儲存實現的不斷增長,同樣,解決方案是實現一個標準。容器儲存介面(CSI)提供了一個統一的介面,它允許附加不同的儲存系統,無論它是雲端儲存還是本地儲存。

13. 其它資源

13.1 容器的歷史

13.2 Chroot

13.3 容器效能

13.4 如何構建容器映像的最佳實踐

13.5 經典Dockerfile容器構建的替代方案

Buildpacks vs Jib vs Dockerfile:容器化方法的比較, by James Ward (2020)

13.6 服務發現

13.7 容器網路

13.8 容器儲存

13.9 容器與kubernetes安全

13.10 Docker Container Playground