Kubernetes 實戰 —— 02. 開始使用 Kubernetes 和 Docker

創建、運行及共享容器鏡像 P23

運行容器 P24

運行 P24

可以運行 Docker 客戶端可執行文件來執行各種 Docker 命令。例如:可以試著從 Docker Hub 的公共鏡像倉庫拉取、運行鏡像。 Docker Hub 中有許多隨時可用的常見鏡像,其中就包括 busybox ,可以用來運行簡單的命令,例如: echo "Hello world"P24

docker run busybox echo "Hello world"

原理 P25

圖 2.1 在一個基於 busybox 鏡像的容器中運行 echo Hello world

執行 docker run 命令後: P25

  1. Docker 檢查 busybox:lastest 鏡像是否存在於本機。如果不存在,則會自動從 Docker 鏡像中心拉取鏡像
  2. Docker 基於 busybox:lastest 鏡像創建一個容器並在容器中運行命令 echo "Hello world"

運行鏡像 P25

docker run <image>
docker run <image>:<tag>

Docker 支援同一鏡像的多個版本,每個版本必須有唯一的 tag 名,當引用鏡像沒有顯式地指定 tag 時, Docker 會默認指定 tag 為 latestP25

創建應用 P26
const http = require('http');
const os = require('os');

console.log("Kubia server starting...");

var handler = function(request, response) {
  console.log("Received request from " + request.connection.remoteAddress);
  response.writeHead(200);
  response.end("You've hit " + os.hostname() + "\n");
};

var www = http.createServer(handler);
www.listen(8080);

這個應用會接收 HTTP 請求並響應應用運行的伺服器真實主機名(非宿主機名),當其部署在 Kubernetes 上並進行伸縮時(水平伸縮,複製應用到多個節點),可以發現 HTTP 請求切換到了應用的不同實例上。 P26

創建 Dockerfile P27
FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]
  • FROM 行定義了鏡像的起始內容(構建所基於的基礎鏡像) P27
  • ADD 行把 app.js 文件從本地添加到鏡像的根目錄,並保持文件名為 app.js P27
  • ENTRYPOINT 行定義了當鏡像被運行時需要執行的命令 P27
構建容器鏡像 P27
docker build -t kubia .

上述命令會基於當前目錄下的 Dockerfile 文件中的指令創建一個名為 kubia 的鏡像。 P27

圖 2.2 基於 Dockerfile 構建一個新的容器鏡像

鏡像是如何構建的 P28

構建過程不是由 Docker 客戶端運行的,而是將整個目錄的文件上傳到 Docker 守護進程並在那裡進行的。如果在一台非 Linux 作業系統中使用 Docker ,客戶端運行在宿主機作業系統上,而守護進程運行在一個虛擬機內。 P28

提示: 不要在構建目錄中包含任何不需要的文件,這樣會減慢構建的速度,尤其是 Docker 守護進程運行在一個遠端機器的時候。 P28

鏡像分層 P28

鏡像不是一個大的二進位塊,而是由許多層組成的,不同鏡像可能會共享分層,這樣會讓存儲和傳輸變得更加高效。 P28

構建鏡像時, Dockerfile 中每一條單獨的指令都會創建一個新層,最後一層會被標記為指定的鏡像名。 P29

圖 2.3 容器鏡像是由多層組成的,每一層可以被不同的鏡像復用

可以通過 docker images 查看本地存儲的鏡像。

運行容器鏡像 P30
docker run --name kubia-container -p 8080:8080 -d kubia

上述命令會基於 kubia 鏡像創建一個叫 kubia-container 的新容器。 -d 表示這個容器與命令行分離,意味著在後台運行。 -p 8080:8080 表示本機上的 8080 埠會映射到容器內的 8080 埠,所以可以通過 localhost:8080 訪問這個應用。 P30

列出所有運行中的容器 P30

docker ps 會列印出每一個容器的 ID 和名稱、容器運行所使用的鏡像、容器中執行程式碼命令、創建時間、以及當前狀態。 P30

  • -a: 查看所有容器,包括運行中的和已停止的

獲取更多的容器資訊 P30

docker inspect kubia-container 可以查看包含容器底層資訊的長 JSON 。 P31

探索運行容器的內部 P31

在已有的容器內部運行 shell P31

docker exec -ti kubia-container bash

上述命令會在已有的 kubia-container 容器內部運行 bash 。 bash 進程會和主容器進程擁有相同的命名空間,可以用於查看 Node.js 和應用是如何在容器里運行的。 P31

  • -t: 分配一個偽終端 (TTY) ,可以顯示命令提示符
  • -i: 確保標準輸入流保持開放,可以在 shell 中輸入命令

進入容器對於調試容器內運行的應用來說非常有用。出錯時,需要做的第一件事是查看應用運行的系統的真實狀態。應用不僅擁有獨立的文件系統,還有進程、用戶、主機名和網路介面。 P32

停止和刪除容器 P32
  • docker stop kubia-container: 停止容器,容器停止後仍然存在 P32
  • docker rm kubia-container: 刪除容器,所有內容都會被刪除並且無法再次啟動 P32
向鏡像倉庫推送鏡像 P33

使用附加標籤標註鏡像 P33

docker tag kubia idealism/kubia

上述命令不會重命名標籤,而是給同一個鏡像創建一個額外的標籤。 kubiaidealism/kubia 指向同一個鏡像 ID ,實際上是同一個鏡像的兩個標籤。 P33

推送鏡像 P33

docker push idealism/kubia 可以將本地的鏡像推送到 idealism 名下的 kubia

在不同機器上運行鏡像 P33

推送完成之後,就可以通過 docker run --name kubia-container -p 8080:8080 -d kubia 在任何機器上運行同一個鏡像了。

配置 Kubernetes 集群 P34

用 Minikube 運行一個本地三節點 Kubernetes 集群

Minikube 是一個構建單節點集群的工具,對於測試 Kubernetes 和本地開發應用都非常有用。 P35

  • minikube start --nodes 4: 啟動一個四節點的 Kubernetes 集群,包含一個主節點和三個工作節點。
  • kuibectl cluster-info: 展示集群資訊
  • minikube ssh: 登錄到 Minikube VM 從內部訪問,可以查看在節點上運行的進程
  • kubectl get nodes: 列出集群的節點
  • kubectl describe node minikube: 查看指定節點的更多資訊

使用 kubectl get nodes 查看當前集群的節點資訊如下:

NAME           STATUS   ROLES    AGE   VERSION
minikube       Ready    master   56m   v1.18.2
minikube-m02   Ready    <none>   13m   v1.18.2
minikube-m03   Ready    <none>   11m   v1.18.2
minikube-m04   Ready    <none>   10m   v1.18.2

可以發現有三個節點的角色是 <none> ,需要手動將它們的 ROLES 設置為 worker ,運行如下命令即可:

kubectl label node minikube-m02 node-role.kubernetes.io/worker=worker
kubectl label node minikube-m03 node-role.kubernetes.io/worker=worker
kubectl label node minikube-m04 node-role.kubernetes.io/worker=worker

圖 2.4 如何與三節點 Kubernetes 集群進行交互

在 Kubernetes 上運行第一個應用 P40

部署 Node.js 應用 P40
kubectl create deployment kubia --image=idealism/kubia

上述命令無須配置文件即可創建一個名為 kubia 的 development (可以使用 kubectl get developments 查看),同時自動創建一個名稱前綴為 kubia 的 pod (可以使用 kubectl get pods 查看)。 P40

介紹 pod P41

Kubernetes 不直接處理單個容器,它使用多個共存容器的理念,這組容器叫作 pod 。 P41

一個 pod 是一組緊密相關的容器,它們總是一起運行在同一個工作節點上,以及同一個 Linux 命名空間中。每個 pod 就像一個獨立的邏輯機器,擁有自己的 IP 、主機名、進程等,運行一個獨立的應用程式。應用程式可以是單個進程,運行在單個容器中,也可以是一個主應用進程和其他支援進程,每個進程都在自己的容器中運行。一個 pod 的所有容器都運行在同一個邏輯機器上,而其他 pod 中的容器,即使運行在同一個工作節點上,也是運行在不同的邏輯機器上。 P41

圖 2.5 容器、 pod 及物理工作節點之間的關係

列出 pod P41

  • kubectl get pods: 查看 pod 的重要資訊: pod 名稱、 READY 、狀態、重啟次數和 AGE
  • kubectl describe pod <pod-name>: 查看指定 pod 的詳細資訊

幕後發生的事情 P42

圖 2.6 在 Kubernetes 中運行 kubia 容器鏡像

上圖顯示了在 Kubernetes 中運行容器鏡像所必須的兩個步驟: P42

  1. 構建鏡像並將其推送到 Docker Hub
  2. 運行 kubectl 命令
    1. 命令向 Kubernetes API 伺服器發送一個 REST HTTP 請求
    2. 創建一個新的 pod ,調度器將其調度到一個工作節點上
    3. Kubelet 看到 pod 被調度到節點上,就告知 Docker 從鏡像中心中拉取指定的鏡像,因為本地沒有該鏡像
    4. 下載鏡像後, Docker 創建並運行容器

調度 (scheduling): 將 pod 分配給一個節點。 pod 會立即執行,而不是將要執行。 P43

訪問 Web 應用 P43

通過 kubectl describe pod <pod-name> 可以看到 pod 自己的 IP ,不過這個地址只能從集群內部訪問。通過創建一個 LoadBalancer 類型的服務,可以創建一個外部的負載均衡,通過負載均衡的公共 IP 就可以訪問 pod 。 P43

創建一個服務對象 P43

kubectl expose deployment kubia --type=LoadBalancer --name kubia-http --port=8080: 對外暴露之前創建的 Pod P43

大多數資源都有縮寫: P43

  • po: pod 的縮寫
  • svc: service 的縮寫
  • rc: replicationcontroller 的縮寫 (目前已 不推薦使用該控制器
  • deploy: deployment 的縮寫
  • rs: replicaset 的縮寫

由於我們使用的是 minikube ,所以沒有集成 LoadBalancer ,運行完上述命令後,使用 kubectl get services 會得到如下結果:

NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP      10.96.0.1      <none>        443/TCP          37m
kubia-http   LoadBalancer   10.100.74.254  <pending>     8080:32342/TCP   5m52s

可以發現 LoadBalancerEXTERNAL-IP 一直處於 <pending>,我們可以使用 minikube 自帶的 minikube tunnel 命令可以完成暴露,運行完後,可得如下結果:

NAME         TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)          AGE
kubernetes   ClusterIP      10.96.0.1      <none>         443/TCP          39m
kubia-http   LoadBalancer   10.100.74.254  10.100.74.254  8080:32342/TCP   7m56s

列出服務 P44

expose 命令的輸出是一個名為 kubia-http 的服務。服務是類似於 pod 和 node 的對象,因此可以通過 kubectl get services 命令查看新創建的服務對象。

使用外部 IP 訪問服務 P44

curl 10.100.74.254:8080
# 輸出如下:
# You've hit kubia-5b6b94f7f8-h6dbr

可以發現應用將 pod 名稱作為它的主機名。 P45

使用 minikube 為集群添加節點可能會導致新增的節點無法訪問(在這裡耗時幾小時終於搞明白了,還是要多讀文檔 ),可以按照文檔中進行設置;當然也有可能是等待的時間不夠,設置等操作還沒有結束(按照前面的配置了後,還是無法訪問,以為哪裡操作有問題,各種操作順序都試了還是無法訪問,最後等待了近 10 min 就自動好了。。。)

系統的邏輯部分 P45

Deployment 、 pod 和服務是符合組合在一起的 P45

Kubernetes 的基本構建是 pod 。但是,我們前面並沒有真的直接創建出任何 pod 。通過運行 kubectl create deployment 命令,創建了一個 Deployment 控制器,它用於創建 pod 實例。為了使該 pod 能夠從集群外部訪問,需要讓 Kubernetes 將該 Deployment 管理的所有 pod 由一個服務對外暴露。 P45

圖 2.7 由 Deployment 、 pod 和服務組成的系統

pod 和它的容器 P45

系統中最重要的組件是 pod ,一個 pod 中可以包含任意數量的容器。 pod 有自己獨立的私有 IP 地址和主機名。 P46

Deployment 的角色 P46

Deployment 控制器確保始終存在一個運行中的 pod 實例。通常, Deployment 用於複製 pod (即創建 pod 的多個副本)並讓它們保持運行。如果 pod 因為任何原因消失了,那麼 Deployment 將創建一個新的 pod 來替換消失的 pod 。 P46

為什麼需要服務 P46

系統的第三個組件是 kubia-http 服務。 pod 的存在是短暫的,一個 pod 可能會在任何時候消失(1. 所在節點發生故障; 2. 有人刪除了 pod ; 3. pod 被從一個健康的節點剔除了),此時消失的 pod 將被 Deployment 替換為新的 pod 。新舊 pod 具有不同的 IP 地址。所以需要一個服務來解決不斷變化的 pod IP 地址的問題,以及在一個固定的 IP 和埠對上對外暴露多個 pod 。 P46

服務表示一組或多組提供相同服務的 pod 的靜態地址。到達服務 IP 和埠的請求將被轉發到屬於該服務的一個容器的 IP 和埠。 P46

水平伸縮 P46
kubectl get deployments
# 輸出如下:
# NAME    READY   UP-TO-DATE   AVAILABLE   AGE
# kubia   1/1     1            1           49m

使用 kubectl get 可以查看所有類型的資源,上述命令查看所有的 Deployment ,目前僅有一個名為 kubia 的控制器,總共需要 1 個 pod ,目前已有 1 個 pod 已準備好, 1 個 pod 已是最新版本, 1 個 pod 可用。

增加期望的副本數 P47

kubectl scale deployment kubia --replicas=3
# 輸出如下:
# deployment.apps/kubia scaled

上述命令告訴 Kubernetes 需要確保 pod 始終有三個實例在運行。這是 Kubernetes 最基本的原則之一。不告訴 Kubernetes 應該執行什麼操作,而是聲明性地改變系統的期望狀態,並讓 Kubernetes 檢查當前的狀態是否與期望的狀態一致。 P47

打到服務上到請求會打到所有到三個 pod 上 P47

curl 10.100.74.254:8080
# 由於存在三個 pod ,所以有以下三種輸出
# You've hit kubia-5b6b94f7f8-h6dbr
# You've hit kubia-5b6b94f7f8-wwbdh
# You've hit kubia-5b6b94f7f8-zzxks

當 pod 有多個實例時 Kubernetes 服務作為負載均衡擋在多個 pod 前面,請求隨機地打到不同的 pod 。 P48

圖 2.8 由同一 Deployment 管理並通過服務 IP 和埠暴露的 pod 的三個實例

查看應用運行在哪個節點上 P49
kubectl get pods -o wide
# 輸出如下:
# NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE           ...
# kubia-5b6b94f7f8-h6dbr   1/1     Running   0          12m   10.244.3.2   minikube-m04   ...
# kubia-5b6b94f7f8-wwbdh   1/1     Running   0          12m   10.244.2.2   minikube-m03   ...
# kubia-5b6b94f7f8-zzxks   1/1     Running   0          55m   10.244.1.2   minikube-m02   ...

-o wide: 顯示 pod 的 IP 和所運行的節點,以及其他部分資訊。 P49

介紹 Kubernetes dashboard P50

訪問 Minikube 的 dashboard P51

minikube dashboard

以上命令會啟動 dashboard ,並輸出可訪問的網址供瀏覽器中打開。 P51

實際操作時,發現這個命令會一直卡在 Verifying proxy health ... 這一行,隨後會返回 503 錯誤,目前 github 上已有類似的 issue 。我按照討論中的方法發現, kubernetes-dashboard namespace 下的兩個 pod 都成功啟動了,並且可以通過 kubectl port-forward -n kubernetes-dashboard pod/kubernetes-dashboard-696dbcc666-gbgwl 9090:9090 將埠映射到本地並能成功訪問。

本文首發於公眾號:滿賦諸機(點擊查看原文) 開源在 GitHub :reading-notes/kubernetes-in-action