1. 程式人生 > >容器、Docker與Kubernetes——從基礎設施的容器化談起

容器、Docker與Kubernetes——從基礎設施的容器化談起

作為一名長期從事運維的工程師來說,我會時常審視維護伺服器工作的簡單性與可重現性;而我的一個最重要的工作原則便是“永遠不要手動操作伺服器”。所有伺服器都必須由工具進行啟動(provisioned)與配置以執行,同時通過工具來監控、維護伺服器的狀態——而我的選擇是Chef;當然你也可以有很多其他選擇如:AnsibleSaltPuppet

Collective Idea中,Chef確實執行得非常好,它管理著公司內部與外部客戶的很多伺服器。但是,漸漸的我也意識到了Chef的一個缺陷:對變化的處理不好。生產環境的應用與基礎設施都是有很多複雜的構件(creatures)組成,這些構件都由很多變化的元件(moving parts)組成,元件之間又有大量的顯式或者隱式的依賴——它們隨時都會由於不可預知的原因發生變化。其中,有些變化容易處理,如配置檔案更改或者對系統進行微調;但是其他一些就比較複雜,如,不停機升級一個應用依賴的Ruby執行時版本。而且,還有一些對伺服器的調整需要人工操作,如,升級作業系統核心後的重啟等。



簡而言之,以我多年使用Chef的經驗,它在搭建新的執行環境與配置系統上確實工作得非常好,而且也是我需要的;但是在處理一些升級,變更等需求的時候,就會變得非常費事而且容易出錯。有沒有工具能夠減輕伺服器升級變配帶來的痛苦呢?

不可變基礎設施

解決問題的一個方案是“不可變基礎設施”應用部署模式;具體表現為,相對於一個個機器的升級(針對上面所講的情況)基礎設施,這種方式會直接丟棄掉老版本的基礎設施,然後將應用程式整體遷移到一個已經升級完畢的新的基礎設施中執行。但是,本質上“不可變基礎設施”部署方式仍然需要一個類似於Chef的工具用於初始化、配置與啟動基礎設施,不同的是一旦一個基礎設施在運行了,它的狀態與配置就不允許發生變更;如果有配置變更需求則啟動新的基礎設施來代替老的。當然,這種部署方式有其自己的複雜性,它必須能夠隨時刪除或者下線一個老的基礎設施然後啟動一個新的——任何時候都有可能發生。那麼問題就來了,對於資料庫來說怎麼升級?升級新的web伺服器後怎麼重新註冊到老的負載均衡器上?升級新的負載均衡器又如何能保持其下諸多的Web伺服器不掉線的升級?


實際上,要完全實現不可變基礎設施的伺服器部署是有很多技術瓶頸的,導致其在現實環境中很難實現。有些工具如: Packer 能夠在一定程度上減輕建立虛擬機器映象的工作難度的,但是你仍然需要面對一整套的環境搭建工作,這通常需要花費很長的構建與生成時間,因為你一般需要下載數G的檔案來搭建一個單獨的系統。

有沒有方法能夠同時兼顧不可變基礎構架的方便直觀,而且映象檔案又能保持足夠小呢?有沒有工具使我們打包映象時剔除掉作業系統與底層抽象只保留應用程式本省與其依賴呢?這樣我們就能只部署一個很小的映象,遷移那些發生變化的資料,最後節約大量的部署時間。這就是 容器 技術為我們帶來的改變,如
Docker rkt (讀音同“rocket”)。在容器為基礎的基礎環境中,底層的伺服器與虛擬機器都被抽象成了資源如:CPU與記憶體。

因為我平時使用Docker所以在以下介紹中我用Docker為工具來講述我的觀點,當然還有其他類似的容器化工具如rkt,效果是一樣的。

Docker

Docker,簡單來說,它將應用程式的執行檔案、命令都打包在一起,稱為映象(image),然後在宿主機或者虛擬機器上部署執行這個映象;而執行起來的映象被稱為“容器”,反過來說,容器就是執行時的映象。容器在一個封閉、隔離的環境中執行,在這個環境中它認為自己是系統中唯一執行的程式,這種隔離性保證一個宿主機可以同時執行多個容器,但是互相併不知道對方的存在。

Docker映象檔案是一次寫入的(write-once,就像一張光碟),這使得Docker基礎環境看上去就是一個不可變基礎構架。映象與容器永遠不會更新,新的映象生成伴隨著老的容器的關閉,而這個過程相對於關閉與啟動一個伺服器或者虛擬機器來說只需要非常短暫的時間。

所以,你會問,那我們Collective Idea是不是把所有的服務都遷移到了Docker上了?我的回答是,沒有,現在還沒有。容器的使用有個單一職責的原則需要遵循:執行多個容器,各個容器只完成一個單一的工作。進一步說,將服務容器化的過程就是基礎構架SOA化的過程;這個過程一樣面臨很多問題與困難:
  • 容器與容器之間如何發現與通訊?
  • 如何決定在哪執行以及執行多少個容器?
  • 如何獲取容器執行的日誌與執行狀態資訊?
  • 如何部署新的映象?
  • 容器崩潰時都發生了什麼?
  • 如何只將特定的一部分容器暴露在公網或者內網環境下?

直至當下,相比現在服役的Chef工具來說,回答這些問題以及大規模在Collective Idea部署Docker應用都還不太成熟。但是,現在還是湧現出了很多解決我提出的問題的工具,而且還提供了可用於生產環境的容器化部署方案,如:Docker公司自己的Docker Compose;但是我們還是選擇了Google的方案——Kubernetes ,它號稱融合了Google在十幾年來在其超大規模資料中心運行了數十億個容器的經驗。



在本系列文章的第一篇中我闡述了從基礎設施的容器化角度闡述了什麼是容器,什麼是Docker以及它們是怎麼來重新定義運維工作以及對基礎設施產生的影響。但是,僅僅瞭解了容器與Docker還不足以將它們運用到我們實際的技術棧中去;所以在本篇文章中我會介紹Kubernetes——一個容器的編排(orchestration)工具——我選擇用它來為基礎設施容器化部署提供支援。


Google是容器的“重度玩家”,在Google內部的成百上千臺伺服器上夜以繼日的執行著數以十億計的容器;時間一長,便開發出了自己的容器管理工具集用於管理如此巨量的基礎設施—— Borg ;而就在幾年前,Borg團隊將多年積累的容器執行編排管理經驗聚集到了一個新的專案,取名叫做Kubernetes,並將其開源貢獻給了大眾。

Kubernetes——繼承自Borg,是一組能夠彼此配合與協作的工具與服務的集合,通過使用這組工具,就能解決我在 上一篇部落格 中所提出的容器服務化所面臨的問題。當然,Kubernetes一個非常複雜的系統,它由很多互相配合使用的元件構成;同時,它也是一個適用於生產環境使用,並經過嚴格與廣泛測試過的,並在除google公司以外很多公司檢驗過的成熟產品。Kubernetes的學習曲線也是很陡峭的,但是慶幸的是它的 官方文件 是非常全的;任何關於Kubernetes的細節都能在其官方文件中找到答案;同時還為每個知識點配置了很多例子與開發建議,當然還有可能出現的報錯說明;更加令人驚訝的是,Kubernetes的官方文件網站還提供了一個基於瀏覽器的互動式的 操作指導 ,我個人非常推薦大家去試試。

正像我之前說的,你會發現Kubernetes有很多東西需要掌握,是這樣的;但是你並不需要理解所有關於Kubernetes的技術細節才能夠正確使用它。在這篇文章中,我會把我瞭解到的關於Kubernetes的一些基礎知識、概念以及我現在使用的那些工具做個介紹。

資源(Resources)

從整體來看,Kubernetes叢集是由很多由JSON或者YAML定義的‘資源’組成,我個人比較推崇使用YAML寫配置,因為它讀寫都很容易,同時還支援註釋。

我不會闡述Kubernetes中所有的‘資源’型別,因為太多而且還在增加;我會介紹在Kubernetes中啟動一個應用所需要了解的幾個基本‘資源’型別,如:Pod、Deployment、Service與Namespace;我還會圍繞這幾種資源型別介紹其他一些概念與如何使用它們。

Node

首先是 Node ,它代表Kubernetes叢集執行的宿主物理機或者虛擬機器伺服器,它為容器提供必要的計算資源如:CPU與記憶體。

Pod

Kubernetes中最底層的抽象則是 Pod 。一個Pod中可以包含一個或者多個執行的容器,這些容器執行在同一個Node上,並且共享此Node的資源。在同一個Pod中的容器可以互相通過localhost的方式通訊,這樣就可以以叢集與可擴充套件方式執行一個應用提供了支援。

Pod就是Kubernetes中的‘不可變層(immutable layer )’;Pod不會升級,只會關閉、丟棄與被代替。它們可以被手動啟停,但是不建議這樣。在Kubernetes叢集中對Pod的配置與管理都是通過‘Deployment’來完成。

Deployment

Deployment 是Kubernetes叢集的管理引擎,它負責管理叢集中頻繁的 Pod 啟停工作,如:它負責配置一個叢集中一共需要跑多少個 Pod Pod 執行的內容,以及根據部署方案或者 Node 、叢集發生的問題來決定如何來啟停 Pod 。(技術上來說, Deployment 把上面說的一些工作交給了Replica Set來做了,但是現在沒必要了解這麼多)

這是一個Deployment,它配置了3個nginx Pods:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        role: web
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80  

Service

一個叢集可以有多個 Deployment ,每個 Deployment 管理多個 Pods 與容器,這很好;但是,如何將容器中執行的服務暴露給外部網路呢?這就需要 Service 來解決了。 Service 提供了一個從Deployment與Pod到外部網路以及外部網路到內部容器的一個雙工的通道。其中, NodePort Service 型別的Service提供了容器的內部訪問機制,但是也能夠將部分高段的埠(30000-35000)對映給叢集外部以訪問叢集內部的容器。如果你使用Amazon(AWS)或者google的容器引擎(GKE),你就可以使用 LoadBalancer 型別的Service,它可以通過配置一些規則在你的雲環境中實現一個負載均衡器。

這可能有些難理解,我下面舉一個例子來說明。比如,我們在一個 rails 應用的前端加一個nginx的負載均衡,同時使用redis作為此應用的資料後端,只需要內部訪問。假如,我們使用GKE或者AWS來部署,我們的負載均衡指向nginx,nginx將請求路由到 rails 應用程式,同時通過redis讀寫資料。下面是Service的配置檔案,注意其中的 selector 節點中 label (後面的label節會提及)值 role:web 讓Kubernetes知道此 LoadBalancer 型別的 Service 的具體 Deployment 在哪。(上節講Deployment時例子中的3個nginx)。
##
# nginx
# Listen to the world on port 80 and 443
##
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    role: web
spec:
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    targetPort: 80
  - name: https
    port: 443
    targetPort: 443
  selector:
    # Find all Resources that are tagged with the "role: web" label
    # In our case, it will find the nginx Deployment mentioned above
    role: web
---
##
# Rails
# Listen for traffic on 8080 so we don't have to run as root.
##
apiVersion: v1
kind: Service
metadata:
  name: rails
  labels:
    role: rails
spec:
  type: NodePort
  ports:
  - port: 8080
    targetPort: 8080
  selector:
    role: rails
---
##
# Redis
##
apiVersion: v1
kind: Service
metadata:
  name: redis
  labels:
    role: redis
spec:
  type: NodePort
  ports:
  - port: 6379
    targetPort: 6379
  selector:
    role: redis   

Namespace

在Service上面我們還可以定義它的 Namespace 屬性,它實際上只是個識別符號,用於封裝、劃分你的基礎設施。Kubernetes內部就是用名稱空間的方式來區分自己內部的服務(kubedns, kube-proxy等等)與使用者自己定義的服務,Kubernetes的名稱空間是: kube-system  。如果你不定義名稱空間,Kubernetes會將你的‘資源’放置到一個預設的名稱空間中,在大多數情況下,這就足夠了;但是,當你在多個團隊何總的情況下,使用名稱空間可以防止資源的衝突與混淆。
下面是一個名稱空間的例子:
apiVersion: v1
kind: Namespace
metadata:
  name: my-app

Labels

Kubernetes中大量使用 Label 在整個叢集中來標記與查詢資源。你可以通過上面提供的例子中找到線索,Deployment中有 labels 節點,它的值跟下面Service中 selectors 中的值是對應的。Kubernetes用 Label 將各種‘資源’中定義的配置關聯在了一起,引用的層級是:Service->Deployment->Pod->Container。

就像開始說的,Kubernetes是個很大的生態系統,並且隨著版本的迭代不斷的在變得更加複雜。但是,只要你瞭解這些基礎知識,基本的‘資源’如何使用,接下來學習更深的知識就會變得容易。下一步我會介紹以下內容:
  • 任何應用都有敏感資訊需要保持安全(資料庫密碼等),Kubernetes中的secrets資源就是用來在Pod與容器中保護你的敏感資訊的;
  • DaemonSet資源型別用於定義在部分或者所有Nodes中都冗餘的執行一個Pod。
  • Job資源用於建立一次性的任務,例如當你需要遷移一個數據庫時可以使用。