Step by Step!Kubernetes持續部署指南
在很久很久以前的一份工作中,我的任務是將老式的LAMP堆疊切換到Kubernetes上。那會兒我的老闆總是追逐新技術,認為只需要幾天時間就能夠完成新舊技術的迭代——鑑於那時我們甚至對容器的工作機制一無所知,所以不得不說老闆的想法真的很大膽。
在閱讀了官方檔案並且搜尋了很多資訊之後,我們開始感到不知所措——有許多新的概念需要學習:pod、容器以及replica等。對我而言,Kubernetes似乎只是為一群聰明的開發者而設計的。
然後我做了我在這一狀況下常做的事:通過實踐來學習。通過一個簡單的例子可以很好地理解錯綜複雜的問題,所以我自己一步一步完成了整個部署過程。
最後,我們做到了,雖然遠未達到規定的一週時間——我們花了將近一個月的時間來建立三個叢集,包括它們的開發、測試和生產。
本文我將詳細介紹如何將應用程式部署到Kubernetes。閱讀完本文之後,你將擁有一個高效的Kubernetes部署和持續交付工作流程。
持續整合與交付
持續整合是在每次應用程式更新時構建和測試的實踐。通過以少量的工作,更早地檢測到錯誤並立即解決。
整合完成並且所有測試都通過之後,我們就能夠新增持續交付到自動化釋出和部署的流程中。使用CI/CD的專案可以更頻繁、更可靠地釋出。
我們將使用Semaphore,這是一個快速、強大且易用地持續整合和交付(CI/CD)平臺,它能夠自動執行所有流程:
1、 安裝專案依賴項
2、 執行單元測試
3、 構建一個Docker映象
4、 Push映象到Docker Hub
5、 一鍵Kubernetes部署
對於應用程式,我們有一個Ruby Sinatra微服務,它暴露一些HTTP端點。該專案已包含部署所需的所有內容,但仍需要一些元件。
準備工作
在開始操作之前,你需要登入Github和Semaphore賬號。此外,為後續方便拉取或push Docker映象,你需要登入Docker Hub。
接下來,你需要在計算機上安裝一些工具:
- Git:處理程式碼
- curl:網路的“瑞士軍刀”
- kubectl:遠端控制你的叢集
當然,千萬不要忘了Kubernetes。大部分的雲供應商都以各種形式提供此服務,選擇適合你的需求的即可。最低端的機器配置和叢集大小足以執行我們示例的app。我喜歡從3個節點的叢集開始,但你可以只用1個節點的叢集。
叢集準備好之後,從你的供應商中下載kubeconfig檔案。有些允許你直接從其web控制檯下載,有些則需要幫助程式。我們需要此檔案才能連線到叢集。
有了這個,我們已經可以開始了。首先要做的是fork儲存庫。
Fork儲存庫
在這篇文章中fork我們將使用的演示應用程式。
- 訪問semaphore-demo-ruby-kubernetes儲存庫,並且點選右上方的Fork按鈕
- 點選Clone or download按鈕並且複製地址
- 複製儲存庫:$ git clone https://github.com/your_repository_path…
使用Semaphore連線新的儲存庫
1、 登入到你的Semaphore
2、 點選側邊欄的連結,建立一個新專案
3、 點選你的儲存庫旁【Add Repository】按鈕
使用Semaphore測試
持續整合讓測試變得有趣並且高效。一個完善的CI 流水線能夠建立一個快速反饋迴路以在造成任何損失之前發現錯誤。我們的專案附帶一些現成的測試。
開啟位於.semaphore/semaphore.yml的初始流水線檔案,並快速檢視。這個流水線描述了Semaphore構建和測試應用程式所應遵循的所有步驟。它從版本和名稱開始。
version: v1.0
name: CI
複製程式碼
接下來是agent,它是為job提供動力的虛擬機器器。我們可以從3種型別中選擇:
agent:
machine:
type: e1-standard-2
os_image: ubuntu1804
複製程式碼
Block(塊)、任務以及job定義了在流水線的每個步驟中要執行的操作。在Semaphore,block按照順序執行,與此同時,在block中的job也會並行執行。流水線包含2個block,一個是用於庫安裝,一個用於執行測試。
第一個block下載並安裝了Ruby gems。
- name: Install dependencies
task:
jobs:
- name: bundle install
commands:
- checkout
- cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-$SEMAPHORE_GIT_BRANCH,gems-master
- bundle install --deployment --path .bundle
- cache store gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock) .bundle
複製程式碼
Checkout複製了Github裡的程式碼。既然每個job都在完全隔離的機器裡執行,那麼我們必須依賴快取(cache)來在job執行之間儲存和檢索檔案。
blocks:
- name: Install dependencies
task:
jobs:
- name: bundle install
commands:
- checkout
- cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-master
- bundle install --deployment --path .bundle
- cache store gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock) .bundle
複製程式碼
第二個block進行測試。請注意我們重複使用了checkout和cache的程式碼以將初始檔案放入job中。最後一個命令用於啟動RSpec測試套件。
- name: Tests
task:
jobs:
- name: rspec
commands:
- checkout
- cache restore gems-$SEMAPHORE_GIT_BRANCH-$(checksum Gemfile.lock),gems-master
- bundle install --deployment --path .bundle
- bundle exec rspec
複製程式碼
最後一個部分我們來看看Promotion。Promotion能夠在一定條件下連線流水線以建立複雜的工作流程。所有job完成之後,我們使用 auto_promote_on來啟動下一個流水線。
promotions:
- name: Dockerize
pipeline_file: docker-build.yml
auto_promote_on:
- result: passed
複製程式碼
工作流程繼續執行下一個流水線。
構建Docker映象
我們可以在Kubernetes上執行任何東西,只要它打包在Docker映象中。在這一部分,我們將學習如何構建映象。
我們的Docker映象將包含應用程式的程式碼、Ruby以及所有的庫。讓我們先來看一下Dockerfile:
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y build-essential
ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile* $APP_HOME/
RUN bundle install --without development test
ADD . $APP_HOME
EXPOSE 4567
CMD ["bundle","exec","rackup","--host","0.0.0.0","-p","4567"]
複製程式碼
Dockerfile就像一個詳細的菜譜,包含所有構建容器映象所需要的步驟和命令:
1、 從預構建的ruby映象開始
2、 使用apt-get安裝構建工具
3、 複製Gemfile,因為它具有所有的依賴項
4、 用bundle安裝它們
5、 複製app的原始碼
6、 定義監聽埠和啟動命令
我們將在Semaphore環境中bake我們的生產映象。然而,如果你想要在計算機上進行一個快速的測試,那麼請輸入:
$ docker build . -t test-image
複製程式碼
使用Docker執行和暴露內部埠4567以在本地啟動伺服器:
$ docker run -p 4567:4567 test-image
複製程式碼
你現在可以測試一個可用的HTTP端點:
$ curl -w "\n" localhost:4567
hello world :))
複製程式碼
新增Docker Hub賬戶到Semaphore
Semaphore有一個安全的機制以儲存敏感資訊,如密碼、令牌或金鑰等。為了能夠push映象到你的Docker Hub映象倉庫中,你需要使用你的使用者名稱和密碼來建立一個Secret:
- 開啟你的Semaphore
- 在左側導航欄中,點選【Secret】
- 點選【Creat New Secret】
- Secret的名字應該是Dockerhub,鍵入登入資訊(如下圖所示),並儲存。
構建Docker流水線
這個流水線開始構建並且push映象到Docker Hub,它僅僅有1個block和1個job:
這次,我們需要使用更好的效能,因為Docker往往更加耗費資源。我們選擇具有四個CPU,8GB RAM和35GB磁碟空間的中端機器e1-standard-4:
version: v1.0
name: Docker build
agent:
machine:
type: e1-standard-4
os_image: ubuntu1804
複製程式碼
構建block通過登入到Docker Hub啟動,使用者名稱和密碼可以從我們剛建立的secret匯入。登入之後,Docker可以直接訪問映象倉庫。
下一個命令是docker pull,它試圖拉取最新映象。如果找到映象,那麼Docker可能能夠重新使用其中的一些層,以加速構建過程。如果沒有最新映象,也無需擔心,只是需要花費長一點的時間來構建。
最後,我們push新的映象。注意,這裡我們使用SEMAPHORE_WORKFLOW_ID 變數來標記映象。
blocks:
- name: Build
task:
secrets:
- name: dockerhub
jobs:
- name: Docker build
commands:
- echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
- checkout
- docker pull "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest || true
- docker build --cache-from "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest -t "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID .
- docker images
- docker push "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID
複製程式碼
當映象準備完畢,我們進入專案的交付階段。我們將用手動promotion來擴充套件我們的Semaphore 流水線。
promotions:
- name: Deploy to Kubernetes
pipeline_file: deploy-k8s.yml
複製程式碼
要進行第一次自動構建,請進行push:
$ touch test-build
$ git add test-build
$ git commit -m "initial run on Semaphore“
$ git push origin master
複製程式碼
映象準備完成之後,我們就可以進入部署階段。
部署到Kubernetes
自動部署是Kubernetes的強項。我們所需要做的就是告訴叢集我們最終的期望狀態,剩下的將由它來負責。
然而,在部署之前,你必須將kubeconfig檔案上傳到Semaphore。
上傳Kubeconfig到Semaphore
我們需要第二個secret:叢集的kubeconfig。這個檔案授予可以對它的管理訪問許可權。因此,我們不希望將檔案簽入儲存庫。
建立一個名為do-k8s的secret並且將kubeconfig檔案上傳到/home/semaphore/.kube/dok8s.yaml中:
部署清單
儘管Kubernetes已經是容器編排平臺,但是我們不直接管理容器。實際上,部署的最小單元是pod。一個pod就好像一群形影不離的朋友,總是一起去同一個地方。因此要保證在pod中的容器執行在同一個節點上並且有相同的IP。它們可以同步啟動和停止,並且由於它們在同一臺機器上執行,因此它們可以共享資源。
pod的問題在於它們可以隨時啟動和停止,我們沒辦法確定它們會被分配到的pod IP。要把使用者的http流量轉發,還需要提供一個公共IP和一個負載均衡器,它負責跟蹤pod和轉發客戶端的流量。
開啟位於deploymente.yml的檔案。這是一個部署我們應用程式的清單,它被3個dash分離成兩個資源。第一個,部署資源:
apiVersion: apps/v1
kind: Deployment
metadata:
name: semaphore-demo-ruby-kubernetes
spec:
replicas: 1
selector:
matchLabels:
app: semaphore-demo-ruby-kubernetes
template:
metadata:
labels:
app: semaphore-demo-ruby-kubernetes
spec:
containers:
- name: semaphore-demo-ruby-kubernetes
image: $DOCKER_USERNAME/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID
複製程式碼
這裡有幾個概念需要釐清:
- 資源都有一個名稱和幾個標籤,以便組織
- Spec定義了最終期望的狀態,template是用於建立Pod的模型。
- Replica設定要建立的pod的副本數。我們經常將其設定為叢集中的節點數。既然我們使用了3個節點,我將這一命令列更改為replicas:3
第二個資源是服務。它繫結到埠80並且將HTTP流量轉發到部署中的pod:
---
apiVersion: v1
kind: Service
metadata:
name: semaphore-demo-ruby-kubernetes-lb
spec:
selector:
app: semaphore-demo-ruby-kubernetes
type: LoadBalancer
ports:
- port: 80
targetPort: 4567
複製程式碼
Kubernetes將selector與標籤相匹配以便將服務與pod連線起來。因此,我們在同一個叢集中有許多服務和部署並且根據需要連線他們。
部署流水線
我們現在進入CI/CD配置的最後一個階段。這時,我們有一個定義在semaphore.yml的CI流水線,以及定義在docker-build.yml的Docker流水線。在這一步中,我們將部署到Kubernetes。
開啟位於.semaphore/deploy-k8s.yml的部署流水線:
version: v1.0
name: Deploy to Kubernetes
agent:
machine:
type: e1-standard-2
os_image: ubuntu1804
複製程式碼
兩個job組成最後的流水線:
Job 1開始部署。匯入kubeconfig檔案之後,envsubst將deployment.yaml中的佔位符變數替換為其實際值。然後,kubectl apply將清單傳送到叢集。
blocks:
- name: Deploy to Kubernetes
task:
secrets:
- name: do-k8s
- name: dockerhub
env_vars:
- name: KUBECONFIG
value: /home/semaphore/.kube/dok8s.yaml
jobs:
- name: Deploy
commands:
- checkout
- kubectl get nodes
- kubectl get pods
- envsubst < deployment.yml | tee deployment.yml
- kubectl apply -f deployment.yml
複製程式碼
Job 2將映象標記為最新,以讓我們能夠在下一次執行中將其作為快取使用。
- name: Tag latest release
task:
secrets:
- name: dockerhub
jobs:
- name: docker tag latest
commands:
- echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
- docker pull "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID
- docker tag "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest
- docker push "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest
複製程式碼
這是工作流程的最後一步了。
部署應用程式
讓我們教我們的Sinatra應用程式唱歌。在app.rb中的App類中新增以下程式碼:
get "/sing" do
"And now,the end is near
And so I face the final curtain..."
end
複製程式碼
推送修改的檔案到Github:
$ git add .semaphore/*
$ git add deployment.yml
$ git add app.rb
$ git commit -m "test deployment”
$ git push origin master
複製程式碼
等到docker構建流水線完成,你可以檢視Semaphore的進度:
是時候進行部署了,點選Promote按鈕,看它是否工作:
我們已經有了一個好的開始,現在就看Kubernetes的了。我們可以使用kubectl檢查部署狀態,初始狀態是三個所需的pod並且零可用:
$ kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
semaphore-demo-ruby-kubernetes 3 0 0 0 15m
複製程式碼
幾秒之後,pod已經啟動,reconciliation已經完成:
$ kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
semaphore-demo-ruby-kubernetes 3 3 3 3 15m
複製程式碼
使用get all獲得叢集的通用狀態,它顯示了pod、服務、部署以及replica:
$ kubectl get all
NAME READY STATUS RESTARTS AGE
pod/semaphore-demo-ruby-kubernetes-7d985f8b7c-454dh 1/1 Running 0 2m
pod/semaphore-demo-ruby-kubernetes-7d985f8b7c-4pdqp 1/1 Running 0 119s
pod/semaphore-demo-ruby-kubernetes-7d985f8b7c-9wsgk 1/1 Running 0 2m34s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.12.0.1 443/TCP 24m
service/semaphore-demo-ruby-kubernetes-lb LoadBalancer 10.12.15.50 35.232.70.45 80:31354/TCP 17m
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
deployment.apps/semaphore-demo-ruby-kubernetes 3 3 3 3 17m
NAME DESIRED CURRENT READY AGE
replicaset.apps/semaphore-demo-ruby-kubernetes-7d985f8b7c 3 3 3 2m3
複製程式碼
Service IP在pod之後展示。對於我來說,負載均衡器被分配到外部IP 35.232.70.45。需要將其更改為你的提供商分配給你的那個,然後我們來試試新的伺服器。
$ curl -w "\n" http://YOUR_EXTERNAL_IP/sing
複製程式碼
現在,離結束已經不遠了。
勝利近在咫尺
當你使用了正確的CI/CD解決方案之後,部署到Kubernetes並不是那麼困難。你現在擁有一個Kubernetes的完全自動的持續交付流水線啦。
這裡有幾個建議可以讓你在Kubernetes上隨意fork並玩轉semaphore-demo-ruby-kubernetes:
- 建立一個staging叢集
- 構建一個部署容器並且在裡面執行測試
- 使用更多微服務擴充套件專案