本文是做者經過親身實踐,從零基礎開始,一步一步總結出來的Kubernetes持續部署工做流程。文章從前期的工具準備開始,到復刻存儲庫、測試、構建鏡像、構建流水線最後進行部署,全部的工做流程都一一展示在文章中,對於想要擁有全自動持續交付流水線的用戶將有很大的借鑑意義。node
在好久好久之前的一份工做中,個人任務是將老式的LAMP堆棧切換到Kubernetes上。那會兒個人老闆老是追逐新技術,認爲只須要幾天時間就可以完成新舊技術的迭代——鑑於那時咱們甚至對容器的工做機制一無所知,因此不得不說老闆的想法真的很大膽。git
在閱讀了官方文檔而且搜索了不少信息以後,咱們開始感到不知所措——有許多新的概念須要學習:pod、容器以及replica等。對我而言,Kubernetes彷佛只是爲一羣聰明的開發者而設計的。github
而後我作了我在這一情況下常作的事:經過實踐來學習。經過一個簡單的例子能夠很好地理解錯綜複雜的問題,因此我本身一步一步完成了整個部署過程。web
最後,咱們作到了,雖然遠未達到規定的一週時間——咱們花了將近一個月的時間來建立三個集羣,包括它們的開發、測試和生產。docker
本文我將詳細介紹如何將應用程序部署到Kubernetes。閱讀完本文以後,你將擁有一個高效的Kubernetes部署和持續交付工做流程。ubuntu
持續集成是在每次應用程序更新時構建和測試的實踐。經過以少許的工做,更早地檢測到錯誤並當即解決。api
集成完成而且全部測試都經過以後,咱們就可以添加持續交付到自動化發佈和部署的流程中。使用CI/CD的項目能夠更頻繁、更可靠地發佈。緩存
咱們將使用Semaphore,這是一個快速、強大且易用地持續集成和交付(CI/CD)平臺,它可以自動執行全部流程:安全
一、 安裝項目依賴項ruby
二、 運行單元測試
三、 構建一個Docker鏡像
四、 Push鏡像到Docker Hub
五、 一鍵Kubernetes部署
對於應用程序,咱們有一個Ruby Sinatra微服務,它暴露一些HTTP端點。該項目已包含部署所需的全部內容,但仍須要一些組件。
在開始操做以前,你須要登陸Github和Semaphore帳號。此外,爲後續方便拉取或push Docker鏡像,你須要登陸Docker Hub。
接下來,你須要在計算機上安裝一些工具:
固然,千萬不要忘了Kubernetes。大部分的雲供應商都以各類形式提供此服務,選擇適合你的需求的便可。最低端的機器配置和集羣大小足以運行咱們示例的app。我喜歡從3個節點的集羣開始,但你能夠只用1個節點的集羣。
集羣準備好以後,從你的供應商中下載kubeconfig文件。有些容許你直接從其web控制檯下載,有些則須要幫助程序。咱們須要此文件才能鏈接到集羣。
有了這個,咱們已經能夠開始了。首先要作的是fork存儲庫。
在這篇文章中fork咱們將使用的演示應用程序。
使用Semaphore鏈接新的存儲庫
一、 登陸到你的Semaphore
二、 點擊側邊欄的連接,建立一個新項目
三、 點擊你的存儲庫旁【Add Repository】按鈕
持續集成讓測試變得有趣而且高效。一個完善的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-$SEMAPHORE_GIT_BRANCH,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-$SEMAPHORE_GIT_BRANCH,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
工做流程繼續執行下一個流水線。
咱們能夠在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就像一個詳細的菜譜,包含全部構建容器鏡像所須要的步驟和命令:
一、 從預構建的ruby鏡像開始
二、 使用apt-get安裝構建工具
三、 複製Gemfile,由於它具備全部的依賴項
四、 用bundle安裝它們
五、 複製app的源代碼
六、 定義監聽端口和啓動命令
咱們將在Semaphore環境中bake咱們的生產鏡像。然而,若是你想要在計算機上進行一個快速的測試,那麼請輸入:
$ docker build . -t test-image
使用Docker運行和暴露內部端口4567以在本地啓動服務器:
$ docker run -p 4567:4567 test-image
你如今能夠測試一個可用的HTTP端點:
$ curl -w "\n" localhost:4567 hello world :))
Semaphore有一個安全的機制以存儲敏感信息,如密碼、令牌或密鑰等。爲了可以push鏡像到你的Docker Hub鏡像倉庫中,你須要使用你的用戶名和密碼來建立一個Secret:
這個流水線開始構建而且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的強項。咱們所須要作的就是告訴集羣咱們最終的指望狀態,剩下的將由它來負責。
然而,在部署以前,你必須將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
這裏有幾個概念須要釐清:
第二個資源是服務。它綁定到端口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: