Kubernetes 是一個用來自動化部署、擴展以及管理容器化應用程式的工具。Scale 這個字一直翻成「擴展」,但 scale 視需要是可以增大或縮小的,英文是用 scale up 或 scale down,但中文的擴展只有增大的意味,並不存在縮減的意味,所以 scale 也許翻成規模調整會好一點。Kubernetes 最早是由 Google 的工程師所開發的,受到 Google 內部稱為 Borg 的管理系統的啟發,在 2014 年首次發佈,現在專案移至 The Linux Foundation 管理。關於 Borg 可以參考 Google 的 Site Reliability Engineering
一書,對於 Google 內部如何使用這些工具有詳細的說明。根據前述文字,Kubernetes 有點像是 docker stack 在 swarm 模式中的運用。但有一點很重要的,就是 Kubernetes 不只可以用來管理 Docker,它也可以用來管理其他的容器。我們先從 Get Started 開始,Kubernetes 官網中類似 Get Started 的文件是 Tutorials,請參考 https://kubernetes.io/docs/tutorials/。
開始之前先來準備環境。Kubernetes 基本上要運行在叢集中,有點像是 docker swarm 的感覺,所以一開始要先建立一個叢集。一般叢集會有多個節點,但就像在 Docker 中,一開始只在單一主機運行 docker swarm,也就是單節點叢集。Kubernetes 提供了一個建立單節點叢集的工具稱為 Minikube,而 tutorial 也是從 Minikube 開始,為了快速上手,就先使用 Minikube 作為操作環境,之後會再使用其他工具,在本機上建立一個多節點的 Kubernetes 叢集。
首先先來安裝 Minikube,一般的狀況下需要 hypervisor,也就是建立一台虛擬器作為單節點的 Kubernetes 叢集,不過文件說也可以使用 Docker 直接在本機上架設,但好像需要額外的設定,所以還是使用 hypervisor 的方式,這裡使用 VirtualBox 就可以,應該大家的機器裡都有了,這一步就跳過。Kubernetes 主要的命令列工具為 kubectl,所以也要安裝。不知道 kubectl 和 Minikube 有沒有安裝上的相依性,但根據 https://kubernetes.io/docs/tasks/tools/install-minikube/ 是將 kubectl 的安裝放在 Minikube 之前,所以先來安裝 kubectl,請參考 https://kubernetes.io/docs/tasks/tools/install-kubectl/。
例如在 Ubuntu 上利用 apt 安裝的指令如下:
$ sudo apt-get update && sudo apt-get install -y apt-transport-https
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
$ echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee -a /etc/apt/sources.list.d/kubernetes.list
$ sudo apt-get update
$ sudo apt-get install -y kubectl
在 macOS 可以利用 Homebrew 安裝,指令為 brew install kubernetes-cli
。
接下來安裝 Minikube,請參考 https://github.com/kubernetes/minikube。我的環境是 macOS,所以使用 Homebrew 安裝,指令是 brew cask install minikube。安裝說明中有一段是在 Linux 中沒有 Hypervisor 的安裝方式,如果要在跑 Linux 的虛擬機中安裝 Minikube,可以參考看看,不過我自己沒試過便是。
安裝好之後以 minikube start
來啟動此虛擬機(叢集建立在該虛擬機中),如果是第一次啟動,會下載一個 iso (Minikube 虛擬機的映像檔)並安裝,要花一點時間,然後應該會看到下面的文字:
Starting local Kubernetes v1.10.0 cluster...
Starting VM...
Getting VM IP address...
Moving files into cluster...
Setting up certs...
Connecting to cluster...
Setting up kubeconfig...
Starting cluster components...
Kubectl is now configured to use the cluster.
Loading cached images from config file.
這樣就安裝好了,Minikube 有一個儀表板介面可以用來查看目前叢集的狀態,執行 minikube dashboard
後瀏覽器會開啟儀表板網頁,畫面如下。
第一個 tutorial 是在 Minikube 中建立並部署一個跑在 node.js 上的網頁伺服器,這個教案可參考 https://kubernetes.io/docs/tutorials/hello-minikube/。其實官網有提供一個線上的 tutorial,內容和 Hello Minikube 差異不大,但它會在網頁上提供一個終端機介面來進行操作,省去要自己建立 Minikube 的麻煩。它的解釋比 Hello Minikube 要詳細一點,可參考 https://kubernetes.io/docs/tutorials/kubernetes-basics/。以下會稍微整理一下兩邊的內容。首先建立一個目錄 hellonode 作為工作目錄,並在其中添加兩個檔案,第一個是 server.js
,用來監聽 http 連線,內容如下:
var http = require('http');
var handleRequest = function(request, response) {
console.log('Received request for URL: ' + request.url);
response.writeHead(200);
response.end('Hello World!');
};
var www = http.createServer(handleRequest);
www.listen(8080);
第二個檔案是 Dockerfile
,內容如下:
FROM node:6.14.2
EXPOSE 8080
COPY server.js .
CMD node server.js
經過前幾天的洗禮,應該對這個 Dockerfile
的內容沒任何問題,接下來是建立映像檔,不過這裡希望把這個建好的映像檔直接放在 Minikube 的 Docker registry,所以要先設定 shell,讓 docker 指令可以直接跟 Minikube 溝通,就像之前在 docker-machine
中所作的一樣。設定 shell 的指令是 eval $(minikube docker-env)
,要確認有沒有設置成功,可以執行 docker container ls
,會看到很多執行中的容器,它們的名稱都以 k8s 開頭。確認無誤就可以來建立映像檔了,指令是 docker build -t hello-node:v1 .
,映像檔命名為 hello-node
,標籤為 v1
。
建立完映像檔,先用 docker 來跑跑看,指令是 docker run -d -p 8080:8080 --rm hello-node:v1
,然後打開瀏覽器,連到 Minikube 的 8080 port,Minikube 的 IP 可以用剛才的 minikube docker-env
查看,在 DOCKER_HOST
這個變數,我的主機顯示的是 192.168.99.104
,所以用瀏覽器連到 http://192.168.99.104:8080
,就可以看到 server.js
吐出來的文字內容 Hello World!
。沒有問題的話就可以把個容器停掉了。
接下來要用 Kubernetes 來部署這個應用程式。首先要先建立一個部署 (Deployment)。Deployment 是 Kubernetes 中的術語,文件中說它是控制器 controller 的其中一種,就暫時先把它想成一種在 Kubernetes 內部擔任管理工作的物件或者是資源。之後如果提到的是 Kubernetes 中的術語,例如物件什麼的,就用原文來表示。
我覺得 Kubernetes 在管理容器的基本想法是產生各類型不同的物件資源作為管理控制容器的中間層,學習 Kubernetes 的第一個門檻,在於它的這些資源(中間層)的術語,感覺起來很「工程師」,不是那麼容易理解,所以學習時很重要的一點就是想辦法用自己能接受的方式去理解。在建立這些資源時,一般會利用 JSON 或 YAML 之類的檔案來描述其設置,然而在最一開始的 tutoial 中,並不需要自己去編寫這些設置,而是使用 kubectl 的命令列工具,自動去建立這些資源,所以我之前在比較片面式地學習 Kubernetes 時,會覺得為什麼有的時候要寫 YAML,有的時候又不用,其實只是因為 kubectl 的指令暗地裡幫忙做掉了。
回到 tutorial,文件這個地方在解釋 Deployment 前,其實是先提到 Pod 這個東西。之前有接觸過 Kubernetes 的話,應該有聽過 Pod,它是一或多個容器,因管理或網路連接需求而組成的集合,在同一個 Pod 的容器會共用一些資源,例如用來儲存的 volume、同一個 IP 的網路等。Kubernetes 中最小部署的單位是 Pod,而不是容器,或許可以這麼 理解,Kubernetes 不希望直接對容器進行操控,因此它所設計的工具,能管理的最小單位是 Pod。在這份 tutorial 中所用到的 Pod 只有一個容器。Deployment 可當作是一種 Pod 的管理物件,用來檢查 Pod 的健康狀態,如果 Pod 中的容器死掉了,負責將它重啟。要管理 Pod 的建立或 scaling,建議使用 Deployment 來操作。建立 Deployment 的指令是:
$ kubectl run hello-node --image=hello-node:v1 --port=8080 --image-pull-policy=Never
deployment.apps "hello-node" created
雖然說是建立 Deployment
,但從指令中看不太出來。hello-node
是名稱,--image
是要使用的映像檔,這裡用的是剛才建好的 hello-node:v1
,--port
則是要揭露的 port,--image-pull-policy=Never
表示要使用本機的映像檔,而不要去 registry 拉下來,因為並沒有上傳這個映像檔。
用 kubectl get deployments
可以查看剛才建立的 Deployment,加上 -o wide
參數會顯示多一點資訊,o 是 output 的意思。指令中的 deployments 也可以用單數 deployment,一樣會出現結果。
$ kubectl get deployments -o wide
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
hello-node 1 1 1 1 8m hello-node hello-node:v1 run=hello-node
如果想要詳細查看 Deployment 的情形,使用 kubectl describe deployment
指令,可加上 Deployment 的名稱,例如:
$ kubectl describe deployments hello-node
Name: hello-node
Namespace: default
CreationTimestamp: Sat, 03 Nov 2018 07:07:17 +0800
Labels: run=hello-node
Annotations: deployment.kubernetes.io/revision=1
Selector: run=hello-node
Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 1 max surge
Pod Template:
Labels: run=hello-node
Containers:
hello-node:
Image: hello-node:v1
Port: 8080/TCP
Host Port: 0/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: hello-node-57c6b66f9c (1/1 replicas created)
Events: <none>
然後看一下新建立的 Pod,指令是 kubectl get pods
。
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE
hello-node-57c6b66f9c-k65jb 1/1 Running 0 16m
到目前為止所做的事情,看起來是先建立一個 Deployment,然後 Deployment 會去建立 Pod。
現在要來建立服務 (Service)。文件說 Pod 預設只能在叢集內部透過內部 IP 存取,所以如果想要從叢集外部存取容器,必須先建立一個 Service 用來讓 Pod 對外揭露,指令如下:
$ kubectl expose deployment hello-node --type=LoadBalancer
service "hello-node" exposed
這個指令會建立一個服務,不過從指令中也是看不出來。這個服務會揭露 hello-node
,--type=LoadBalancer
表示希望這個 Service
能對外揭露,從外部存取,並使用 LoadBalancer 此類型。一樣可以 kubectl get services
來查看剛才建立的 service
,如果只要列出 hello-node
這個 Service,在指令最後加上其名稱。
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hello-node LoadBalancer 10.106.54.246 <pending> 8080:30754/TCP 4s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3h
會看到有兩個 Service,kubernetes 是 Kubernetes 本身的服務,而 hello-node
就是剛才建立的服務,CLUSTER-IP
指的是這個 Pod 的 IP,要在 Kubernetes 叢集內部才能存取。至此可以來存取我們的應用程式了,剛才 get services 從 PORT(S)
欄位可以看到,容器的 8080 port 對應到外面的 30754 port,所以要存取這個服務,要指定 Minikube IP 的 30754,以我這邊的例子是 http://192.168.99.104:30754
,應該會看到 Hello World!,跟剛才用 Docker 跑起來的結果一樣。另外實驗一下 CLUSTER-IP
,因為要在叢集內部才能存取,可以用 minikube ssh "curl 10.106.54.246:8080"
連進 Minikube 執行 curl
指令,存取叢集 IP 的 8080 port,結果一樣會回傳 Hello World! 字串。
現在要來調整應用程式的規模 (scaling),前面有提過,Kubenetes 部署的最小單位是 Pod,因此 scaling 時基本上是去調整 Pod 的數量,但是這件事是透過 Deployment 去作,前面也有提到 Pod 的 scaling 建議用 Deployment 完成。前面用 kubectl get pods
有看到目前只有一個 Pod,用以下指令來調整 Pod 的數量到四個。
$ kubectl scale deployments/hello-node --replicas=4
這裡 deployments 和 hello-node 之間的 /
也可以用空格取代,變成 kubectl scale deployments hello-node --replicas=4
。調整後先用 kubectl get deployments
查看,發現 DESIRED
欄位的值變成 4 了,然後再用 kubectl get pods
查看 Pod 數量,確實增加到了 4 個。
Tutorial 的下一個部分是更新應用程式,這裡會修改網頁上的文字,重新建立一個映像檔並更新已部署的應用程式。這裡的重點是 rolling updates,暫且翻成滾動式更新,它的目的是讓應用程式不會因為更新而無法提供服務。這要在 pod 有多個 replica 時才能作用,更新時 Kubernetes 一次只會作用在一個 Pod 上,將舊的容器銷毀再建立新的,而此時的服務流量會被導到可用的 Pod 身上。首先將 server.js
中 response.end()
中的文字置換成自己喜歡的內容,tutorial 中換成 Hello World Again!
,然後重新 build 映像檔,將 tag 改成 v2
,指令為 docker build -t hello-node:v2 .
。接下來用下面的指令來更新 Deployment 的映像檔,並重新以瀏覽器連接服務網頁,應該會看到更新後的文字。
$ kubectl set image deployment/hello-node hello-node=hello-node:v2
如果要查看更新的情況,可以用 kubectl rollout status deployment/hello-node
。
$ kubectl rollout status deployment/hello-node
deployment "hello-node" successfully rolled out
更新也可以回復,指令為 kubectl rollout undo deployment/hello-node
。接下來使用 minikube dashboard 看一下這個服務在儀表板中會如何呈現。
做到這裡,這個 tutorial 就差不多結束了,接下來要回復環境,先把剛才建立的資源 Service 刪除。
$ kubectl delete service hello-node
刪除 Service 基本上只是使 Pod 無法讓外界存取,Pod 本身還是活著的,可以用 kubectl get pods
查看 Pod 的狀態,目前仍為 Running,用 kubectl exec
連進去作一些事,首先要知道 Pod 的名稱,在剛才 kubectl get pods
指令的結果可以得知。kubectl exec
的範例如下:
$ kubectl exec -it hello-node-57c8dd9df8-pvtj8 curl localhost:8080
Hello World!
不過這裡到底是連進 Pod 還是容器其實不太清楚喔,感覺是連進容器,但如果 Pod 有多容器,那麼到底會連進那個容器,這裡好像沒有指定。就先知道有這個指令就可以了。
接下來將 Deployment 刪除,在刪除 Deployment 時 Pod 也會被連帶刪除。
$ kubectl delete deployment hello-node
把 shell 改回來,並停止 Minikube 虛擬機。
$ eval $(minikube docker-env -u)
$ minikube stop
若要再啟動 Minikube 虛擬機,或刪除它,指令如下:
$ minikube start
$ minikube delete
今天用 Minikube 建立了一個單個節點的 Kubernetes 叢集,並在其中部署了一個簡單的靜態網頁應用程式。此外稍微瞭解了 Pod、Deployment、Service 的概念,並且用 Kubernetes CLI 工具 kubectl 建立了相關的資源。