歡迎光臨
每天分享高質量文章

Kubernetes部署教程

對於Kubernetes部署,有兩種基本的方法:使用一條條的kubectl執行命令,或者編寫宣告清單,再使用kubectl應用工具執行。前者有利於學習和與Kubernetes的交互實驗(類似於編程語言的REPL);後者適合於可重現重用的快速部署。例如在生產環境中,您可能仍然會使用一條條的kubectl命令來做除錯。
前提條件

 

本教程假設您已經具有了可訪問的Kubernetes集群環境。現在,您可以輕鬆地在您的機器上運行一個單節點集群,或者在您熟悉的雲環境中創建一個多節點集群。並確定您的機器上已經安裝並正確配置了kubectl。讓如下命令可以成功執行:
  1. kubectl cluster-info
還需確認您在預設命名空間內的權限是否足夠(可能與預設命名空間不同)。edit角色權限能夠滿足實驗需求。如果您為本教程創建了一個集群,那麼您可能就是管理員。或者讓其他人為您準備好環境。
備註:如果您不想用公共的鏡像檔案,而想自己構建並推送一個自定義的境像。那我們假定您和您的集群是可以訪問容器鏡像註冊器的。同樣的,也可以讓其他人為您準備好此環境。業界協作比較好的組合環境包括GKE與GCR、AKS與ACR等。另外,DockerHub和Quay也是非常流行的鏡像註冊器。如果不打算公開自己的容器鏡像,則需要在私有的命名空間中,給預設服務帳戶配置拉取鏡像密鑰。
構建和推送

 

不管是採用指令式部署還是宣告式部署,都需要有一個容器鏡像。(如果您使用現成的鏡像,如Nginx鏡像,可跳過此部分)。本教程中的一些步驟與後續構建的應用程式有直接關聯。同時,也可幫助您學習如何封裝應用程式,所以建議您不要忽略此部分。
應教學目的需要,我們將從一個簡單的Web應用原始碼開始教程。下麵是Node.js文件中的一個案例應用代碼(或採用您所喜歡的編程語言編寫一個類似的應用程式)。將以下的代碼複製到一個名為app.js的檔案中,並存放在一個空的檔案夾下。
  1. *// app.js
  2. const http = require('http');
  3. const os = require('os');
  4. const ip = '0.0.0.0';
  5. const port = 3000;
  6. const hostname = os.hostname();
  7. const whoami = process.env['WHOAMI'] || 'Anonymous';
  8. const server = http.createServer((req, res) => {
  9. res.statusCode = 200;
  10. res.setHeader('Content-Type', 'text/plain');
  11. res.end(`Hi, I’m ${whoami}, from ${hostname}.\n`);
  12. });
  13. server.listen(port, ip, () => {
  14. console.log(`Server running at http://${ip}:${port}/`);
  15. });*
我們對上述案例代碼做了一些調整:
  • 將服務器本機IP定義為0.0.0.0而不是127.0.0.1。因為後者僅僅適用於IP迴路尋址。而這段程式需監聽來自一個集群IP的請求(由0.0.0.0捕獲請求)。

  • 將“Hello World”訊息分拆為2個變數:主機名,提供請求響應的一個副本節點的標識;WHOAMI環境變數,在部署時設定,預設值為“Anonymous”。

如果您已經安裝了Node.js,那可以通過以下命令進行代碼的本地測試:
  1. node app.js # 打開鏈接 http://localhost:3000測試
備註:如果您在自己的Kubernetes集群環境進行測試,無需擔心TLS終端和授權問題,因為這個應用可以在集群的邊界運行,例如Ambassador。如果您採用Zero-trust,這段代碼可以運行在Istio之類服務網格集群的微服務內。
接下來將應用打包成一個Docker鏡像。複製如下代碼,儲存為Dockerfile的檔案:
  1. # Dockerfile
  2. FROM node:8
  3. COPY app.js .
  4. ENTRYPOINT [“node”, app.js”]
在檔案所在目錄下,執行命令:
  1. docker build -t myrepo:mytag .
請根據您的容器鏡像註冊器,替換 myrepo。例如:
採用GCR,就替換為 gcr.io/project-name/image-name,採用預設的DockerHub,就替換為 user-name/image-name,用非 latest的任何詞語,來替代 mytag。tag不能重覆,如果您是和多人一起學習本課程時,請確保這點。最後的那個點不能遺漏,它表示用當前目錄環境構建背景關係。
最後,將鏡像推到您的鏡像庫(也就是Kubernetes拉取鏡像的地方):
  1. docker push myrepo:mytag
命令式的配置方法

 

RUN
部署Kubernetes最簡捷的方式就是採用kubectl run命令。
  1. kubectl run myapp --image myrepo:mytag --replicas 2
如果是多人共享一個命名空間,請採用唯一的名稱替代myapp,採用上述步驟中的指定的庫地址和名稱(或Nginx)替代myrepo:mytag。
這個命令,看起來與本地啟動容器的docker run命令有些相似, 但相似的地方也就這些而已。(再後面的交互圖中也有體現)。
本質上Kubectl將用戶的命令陳述句轉換成一個Kubernetes的Deployment物件的宣告。而一個Deployment是一個能滾動升級的高級API。
  1. Kubectl將Deployment物件發送給運行在Kubernetes集群內的API服務器—— kube-apiserver。

  2. kube-apiserver將Deployment信息儲存到集群內分佈式鍵值對儲存etcd中,etcd為Kubectl請求提供響應。

  3. Kubernetes負責監控Deployment及其他事件的控制器管理器kube-controller-manager,將為Deployment創建一個ReplicasSet副本,並將其發送到kube-apiserver。一個ReplicaSet就是一個Deployment的版本。在滾動更新時,將創建一個新的ReplicaSet,並逐步擴展到預先配置的期望副本數,而舊的ReplicasSet數量逐步變為零。這整個過程都是採用異步機制。

  4. kube-apiserver將ReplicaSet信息儲存到etcd中。

  5. kube-controller-manager同樣以異步的方式為ReplicaSet創建2個或以上的Pod,並將其發送給kube-apiserver。Pod是Kubernetes的基本單元,是搭載1或多個共享Linux cgroup和命名空間的容器環境。

  6. kube-apiserver將Pod信息儲存到etcd中。

  7. 負責監視Pod事件的Kubernetes調度器kube-scheduler,將逐步更新每個Pod,為其分配到某個節點,並將更新的信息發送回kube-apiserver。

  8. kube-apiserver在etcd中更新Pod狀態。

  9. 最後,節點上運行的kubelet會實時監控Pod,併在Pod啟動了容器。

備註:容器,調度器和kubelet同樣會將自身的狀態信息發送給API Server。總而言之,我們可以將Kubernetes理解成一個不斷迴圈的CRUD API。如下是上述過程的交互圖: 
Get,Describe
kubectl run創建一個Deployment,然後又衍生創建ReplicaSet和Pod。但它們都運行在哪裡呢?我們可以採用kubectl get命令去列出您預設命名空間下所有的 Deployments,ReplicaSet和Pod:
  1. kubectl get deployments # plural or singular, or deploy for short
  2. kubectl get replicasets # or rs
  3. kubectl get pods # or po
在物件後面加上物件名可以列出單個的物件。如:
  1. kubectl get deployment myapp
匯出etcd中的物件狀態信息–output option (or -o):
  1. kubectl get deployment myapp -o yaml
要獲得更多的細節信息,包括與此物件相關的近期事件信息。可以採用kubectl describe命令:
  1. kubectl describe deployment myapp
上述命令與引數同樣適用於ReplicaSet 和Pod。
Label
標簽的作用非常大,一個標簽是一個字串的健值對。Kubernetes所有的物件都可以被標簽,並且可以按標簽做成選擇器。通過kubectl run命令加run=myapp的方式可以自動定位Deployment,ReplicaSet和Pod。您可以在YAML和描述中看到物件的標簽,也可以通過–show-labels引數,將標簽輸出。命令如下:
  1. kubectl get deployments --show-labels
  2. kubectl get replicasets --show-labels
  3. kubectl get pods --show-labels
可以採用 –label-columns ( -L) 引數將標簽以列的方式顯示。如:
  1. kubectl get replicasets -L run
一種重要用法是採用–selector (-l)引數進行標簽過濾。如:
  1. kubectl get pods -l run=myapp

還可以手動添加新的標簽。如:

  1. kubectl label deployment myapp foo=bar
Delete
Kubernetes API本質上是宣告性的,這意味著控制器總是在比較物件狀態和所期望的狀態。因此,如果我們刪除一個Pod,ReplicaSet控制器將創建一個新的Pod,以維護所期望的副本數。我們通過如下測試驗證一下:
  1. kubectl delete pods -l run=myapp
  2. # 等待一會兒後再運行
  3. kubectl get pods -l run=myapp
我們會發現Pod總數沒變,但通過隨機後綴發現是創建了新的Pod。這個機制同樣適用於ReplicaSet。如果我們刪除一個,控制器也將自動創建一個新的。但如果我們將Deployment刪除了,那就表示刪除這個應用,這時候就不會再有新的創建動作。
Port-Forward
到目前為止,我們已經看到了Kubernetes為響應kubectl run而創建的Pod物件。但Pod之所以隨時可以響應,是因為容器中有行程正在運行著。我們可以定義一個活性探針(Liveness probe)來驗證。但是現在,我們先通過手動操作來讓應用程式正常工作。即通過kubectl port-forward命令,讓API服務將一個本地端口代理成Pod的端口(如果本地和API服務器的網絡是互聯網的話,需要採用TLS加密)。kubectl port-forward命令非常靈活,如果您同時提供了Pod名和端口號,kubectl就直接按提供的值進行映射。但由於Pod名通常是自動生成的,名字帶有隨機數,所以方便起見,建議提供的名字是型別/名字的鍵值對格式。如:
  1. kubectl port-forward deployment/myapp 3000
瀏覽器打開如下鏈接,您將看到“Hello World”的訊息。
http://localhost:3000/
由於上述方式只能映射一個Pod,所以當您重新加載後,不能修改主機名。同時請註意,採用kubectl run命令時,我們不能通過–env引數修改WHOAMI 環境變數。只能通過kubectl set 或kubectl patch命令才行的(在稍後介紹)。
如果本地端口與Pod端口不一致的話,需在命令內註明,如:
  1. kubectl port-forward deployment/myapp 5000:3000
上述所配置的端口代理可以通過Ctrl+C停止。如果代理連接不活躍的話,系統也會被自動關閉這個代理連接。
Expose
port-forward可以讓一個應用運行起來,但不支持多個應用同時代理。而且,我們用到的Pod IP和DNS記錄,會因為Pod生命周期很短,而導致不斷變化。因此,我們需要一種方法來與Deployment或其他型別的Pod組(而非單個Pod)保持通信。這就是服務發現。
Kubernetes的Service物件就是將流量路由到一組Pod上,而這組Pod與Service的標簽選擇器相匹配。如果多個Pod與標簽選擇器匹配,那它們都將被偵聽,並接收到所路由的流量。如果發佈一個Deployment,我們可以簡單地將它的標簽選擇器(run=app)作為Service的標簽選擇器。
The kubectl expose命令能夠自動根據Deployment、ReplicaSet、其他的Service或單個Pod來創建Service。它會自動從給定物件中查找標簽選擇器、服務端口和標的Pod端口,除非有特定選項指定。如:
  1. kubectl expose deployment myapp --port 80
  2. # 因為本地設備端口不能用,採用標準的HTTP端口
確認一下所創建Service 的信息:
  1. kubectl get service # or svc
可在Service描述中或所控制的Endpoints中,看到監聽Pod的IP,這方法可以用於網絡問題的診斷。
  1. kubectl describe service myapp
  2. kubectl get endpoints myapp
如下是啟動一個帶有交互式終端的臨時Pod,來實際呼叫服務:
  1. kubectl run mytest -it --rm --image alpine # Alpine很輕量
  2. # inside mytest:
  3. apk add curl
  4. curl http://myapp # “Hello World…”
  5. exit
備註:myapp的Deployment需提前創建。
  • -t或–tty,分配一個TTY終端

  • -i或–stdin保持stdin打開

  • –rm退出時刪除Deployment

 

Logs
如應用程式有問題,通常要檢查日誌。容器日誌通常是由容器運行時(Docker守護行程)儲存在節點上。預設情況下,當日誌檔案超過10MB時,日誌會自動迴圈改寫。可以配置一個日誌代理,將日誌推送到後端的持久儲存上。請記住一點,kubectl logs命令只能看到節點上上的日誌。
可以通過Pod名獲取一個Pod的日誌,也可以通過型別/名字獲取一組Pod的日誌。如:
  1. kubectl logs deployment/myapp

在我們的教學中,日誌較少,但實際生產應用的日誌會詳細且多很多。尤其是出問題的時候。可以通行引數,按日誌量—since (1m),日誌時間–since-time (2018–11–01T16:30:00)或尾部記錄數 –tail (20)來設定輸出結果。其他的引數選項還包括 –follow(-f),–previous(容器持續崩潰),–timestamps(應用沒有記錄時間戳)。最後將日誌輸出到檔案和進行您所需的分析。如:

  1. kubectl logs deployment/myapp --since 5m > log.txt
  2. grep error log.txt
  3. # more grep
Exec,Copy
kubectl還提供了一些其他的工具來幫助我們除錯正在運行的容器。kubectl exec是在容器中執行命令,kubectl cp在容器之間複製檔案和目錄。這兩個命令使用顯式的Pod名稱,而不是Deployment名。如下是用–output jsonpath在shell變數中儲存Pod名稱的用法:
  1. POD_NAME=$(kubectl get pods -l run=myapp -o
  2. jsonpath={.items[0].metadata.name})
我們可以將這個變數用到其他命令內:
  1. kubectl exec $POD_NAME -it sh # 在容器內打開一個交互式shell。
  2. # 容器內的執行段樣本:
  3. node --version
  4. echo $WHOAMI
  5. exit
  6. # 傳回到本地機器
  7. kubectl cp $POD_NAME:app.js remote-app.js # 用於理解容器內運行的樣本
Set,Scale,Patch
如前面提到的,我們需要用kubectl set或kubectl patch來設置WHOAMI環境變數。在此過程中,我們還同步學習如何使用kubectl get -watch(或-w)查看資源更改,並實時觀察多個物件的滾動更新:
  1. kubectl get deployment myapp -w
  2. # 在第二個終端:
  3. kubectl get replicasets -w -l run=myapp
  4. # 在第三個終端:
  5. kubectl get pods -w -l run=myapp
  6. # 在第四個終端:
  7. kubectl set env deployment/myapp WHOAMI="HAL 9000"
在前三個終端中,可觀察新ReplicaSet的創建,以及新/舊Pod按容器編排進行的創建/刪除的過程。還將看到與狀態更改所對應的資料行的變化(Pod的期望數、當前數、更新日期、可用/準備)。在進行另一個變更之前,先擴展一下Deployment的ReplicaSet數量:
  1. kubectl scale --replicas 3 deployment myapp
kubectl set命令僅限於設置環境變數、圖像、資源請求/限制、標簽選擇器、ServiceAccounts和RoleBindings(基於角色的訪問控制,或RBAC)。
kubectl patch命令則更為通用。它接受JSON或YAML來替換或合併特定的欄位。舉個簡單的例子,我們可以繞過調度程式,強制讓所有Pod運行在一個節點上,命令如下:
首先,看看我們的應用程式的副本目前運行在不同的節點:
  1. kubectl get pods -l run=myapp -o wide # 查看NODE列
選定一個節點名,並以patch的方式調整Deployment:
  1. NODE_NAME=$(kubectl get pods -l run=myapp -o
  2. jsonpath={.items[0].spec.nodeName})
  3. kubectl patch deployment myapp -p
  4. '{"spec":{"template":{"spec:{"nodeName":"'$NODE_NAME'"}}}}'
確認所有Pod被分配到同一個節點上:
  1. kubectl get pods -l run=myapp -o wide
實際上,還有更好的方式調整調度器,例如標簽中心節點,使用node selector,affinities和anti-affinities,taints和tolerations等等。
宣告式的配置方法

 

前面所演練的使Kubernetes知道如何運行,我們只是告訴Kubernetes我們要什麼。執行的是只讀的get,describe,logs命令,然後獲取預期的結果,還有除錯命令如port-forward,exec,cp和delect(用於替換而非修複Pod),這些命令幫助您理解了Kubernetes的重要概念。在實際應用上,這些命令使用並不多。
Kubernetes的強大主要是它的宣告式API和控制器,接下來學習如何告訴Kubernetes要乾什麼。
您只需要使用kubectl apply和YAML(或JSON)就來展現Kubernetes儲存在etcd中的狀態,我們稱之為manifest。
您可以簡單地通過如下命令,獲取在運行物件的manifest:
  1. kubectl get -o yaml --export:
  2. kubectl get deployment myapp -o yaml --export > myapp-deployment.yaml
  3. kubectl get service myapp -o yaml --export > myapp-service.yaml
  4. # ReplicaSet和pod是被控制物件,所以不需要進行manifest。
一個manifest實際上,並不需要儲存所有狀態。其中一些是由Kubernetes補充的。export選項自動刪除了部分status和元資料(UID、創建時間戳等),但您可能按需要刪除更多的配置資料,例如預設值等。
以下提供了Deployment和Service的標準示例,內容為:
  1. # myapp-deployment.yaml
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5. name: myapp
  6. spec:
  7. replicas: 3
  8. selector:
  9. matchLabels:
  10. run: myapp
  11. template:
  12. metadata:
  13. labels:
  14. run: myapp
  15. spec:
  16. containers:
  17. - name: myapp
  18. image: psdocker/myapp:mytag
  19. env:
  20. - name: WHOAMI
  21. value: "HAL 9000"
  22. # myapp-service.yaml
  23. apiVersion: v1
  24. kind: Service
  25. metadata:
  26. name: myapp
  27. spec:
  28. selector:
  29. run: myapp
  30. ports:
  31. - port: 80
  32. targetPort: 3000
為了證明它們的工作原理,我們先刪除全部物件,然後再應用manifest來重新生成物件:
  1. kubectl delete deployment myapp
  2. kubectl delete service myapp
  3. kubectl apply -f myapp-deployment.yaml -f myapp-service.yaml
如果我們沒有刪除Deployment和Service,那它們只是會進行更新來匹配manifest。
kubectl apply命令具有冪等的特征。即我們可以多次編輯manifest,然後再次去應用它。例如,修改了副本數配置後再應用。
如需進一步瞭解Kubectl apply,可以參考Kubernetes的API[1]參考。
對於具有多個環境的複雜應用,直接對manifest進行管理會非常困難。更好的是通過工具幫助我們管理配置。目前已有不錯的工具有kustomize(從1.14版開始就成為kubectl的一部分)、Helm和Jsonne等。對於整體構建和部署過程,並需要在manifest註入帶標記的鏡像,skaffold是一個值得推薦的工具。
總結

 

在本教程中,我們討論了Kubernetes上無狀態應用程式的基本組建:Deployment、ReplicaSet、Pods和Service。以及圍繞它們進行的狀態獲取,執行等功能的命令和宣告方法。我們只是以最基本的方式使用了 Deployment。在生產環境中,您應該需要設置CPU和記憶體請求和限制,並且您可能還對自動縮放和其他功能會感興趣。
有幾本好書可以用於學習Kubernetes。我讀過且毫不猶豫推薦的唯一一本書是《Kubernetes in Action[2]》(Manning著)。
當然,官方文件[3]是一個很好的參考。
最後就是正確地使用大多數谷歌搜索的頂部結果。
相關鏈接:
  1. https://kubernetes.io/docs/reference/#api-reference

  2. https://www.manning.com/books/kubernetes-in-action

  3. https://kubernetes.io/docs

原文鏈接:https://medium.com/payscale-tech/imperative-vs-declarative-a-kubernetes-tutorial-4be66c5d8914
赞(0)

分享創造快樂