Kubernetes簡介
Kubernetes是Google基於Borg開源的容器編排排程引擎,作為CNCF(Cloud Native Computing Foundation)最重要的元件之一,它的標的不僅僅是一個編排系統,而是提供一個規範,可以讓你來描述叢集的架構,定義服務的最終狀態,Kubernetes可以幫你將系統自動得達到和維持在這個狀態。
更直白的說,Kubernetes可以讓使用者透過編寫一個yaml或者json格式的配置檔案,也可以是透過工具/程式碼生成或者是直接請求Kubernetes API來建立應用,該配置檔案中包含了使用者想要應用程式保持的狀態,不管整個Kubernetes叢集中的個別主機發生什麼問題,都不會影響應用程式的狀態,你還可以透過改變該配置檔案或請求Kubernetes API來改變應用程式的狀態。
這意味著開發人員不需要在意節點的數目,也不需要在意從哪裡執行容器以及如何與它們交流。開發人員也不需要管理硬體最佳化,或擔心節點關閉(它們將遵循墨菲法則),因為新的節點會新增到Kubernetes叢集,同時Kubernetes會在其他執行的節點中新增容器,Kubernetes會發揮最大的作用。
總結:Kubernetes是容器控制平臺,可以抽象所有的底層基礎設施(容器執行用到的基礎設施)。
Kubernetes——讓容器應用進入大規模工業生產。
Kubernetes另一個深入人心的特點是:它標準化了雲服務提供商。比如,有一個Azure、Google雲平臺或其他雲服務提供商的專家,他擔任了一個搭建在全新的雲服務提供商的專案。這可能引起很多後果,比如說:他可能無法在截止期限內完成;公司可能需要招聘更多相關的人員,等等。相對的,Kubernetes就沒有這個問題。因為不論是哪家雲服務提供商,你都可以在上面執行相同的命令,以既定的方式向Kubernetes API伺服器傳送請求,Kubernetes會負責抽象,併在不同的雲服務商中實現。
對於公司來說,這意味著他們不需要系結到任何一家雲服務商。他們可以計算其他雲服務商的開銷,然後轉移到別家,並依舊保留著原來的專家,原來的人員,他們還可以花更少的錢。
Pod
Kubernetes有很多技術概念,同時對應很多API物件,最重要的也是最基礎的物件就是Pod。Pod是Kubernetes叢集中執行部署應用的最小單元,並且是支援多個容器的。
Pod的設計理念是支援多個容器在一個Pod中共享網路地址和檔案系統,可以透過行程間通訊和檔案共享這種簡單高效的方式組合完成服務。Pod對多容器的支援是Kubernetes最基礎的設計理念。比如你執行一個作業系統發行版的軟體倉庫,一個Nginx容器用來釋出軟體,另一個容器專門用來從源倉庫做同步,這兩個容器的映象不太可能是一個團隊開發的,但是他們一塊兒工作才能提供一個微服務;這種情況下,不同的團隊各自開發構建自己的容器映象,在部署的時候組合成一個微服務對外提供服務。不過,在大多數情況下,我們只會在Pod中執行一個容器,本文中的例子也是這樣的。
Pod 的另一個特徵是:如果我們希望使用其他 RKE
等技術的話,我們可以做到不依賴Docker容器。
Docker是kubernetes中最常用的容器執行時,但是Pod也支援其他容器執行時。
總的來說,Pod的主要特徵包括:
-
每個Pod可以在Kubernetes叢集內擁有唯一的IP地址;
-
Pod可以擁有多個容器。這些容器共享同一個埠空間,所以他們可以透過
localhost
交流(可想而知它們無法使用相同的埠),與其他Pod內容器的交流可以透過結合Pod的IP來完成; -
一個Pod內的容器共享同一個捲、同一個 IP、埠空間、IPC 名稱空間。
定義一個Pod
如下,我們定義一個最簡單的Pod:
apiVersion: v1kind: Pod # 定義Kubernetes資源的型別為Podmetadata: name: demo-web # 定義資源的名稱 labels: # 為Pod貼上標簽,後面會介紹其用處 app: demo-webspec: # 定義資源的狀態,對於Pod來說,最重要屬性就是containers containers: # containers一個陣列型別,如果你希望部署多個容器,可以新增多項 - name: web # 定義本Pod中該容器的名稱 image: rainingnight/aspnetcore-web # 定義Pod啟動的容器映象地址 ports: - containerPort: 80 # 定義容器監聽的埠(與Dockerfile中的EXPOSE類似,只是為了提供檔案資訊)
然後儲存,我這裡命名為demo-web-pod.yaml
。
現在我們可以在終端中輸入以下命令來建立該Pod:
kubectl create -f demo-web-pod.yaml# 輸出# pod/demo-web created
可以使用如下命令,來檢視kubernetes中的Pod串列:
kubectl get pods# 輸出# NAME READY STATUS RESTARTS AGE# demo-web 1/1 Running 0 65s
如果該Pod還處於ContainerCreating
狀態的話,你可以在執行命令的時候加入--watch
引數,這樣當Pod變成執行狀態的時候,會自動顯示在終端中。
訪問應用程式
在上面,我們成功部署了一個ASP.NET Core Mvc程式的Pod,那麼如何訪問它呢?如果只是為了除錯,我們可以使用轉髮埠的方式來快速訪問:
kubectl port-forward demo-web 8080:80# 輸出# Forwarding from 127.0.0.1:8080 -> 80
然後我們再瀏覽器中訪問:127.0.0.1:8080,顯示如下:
如上,還展示了Pod的主機名和IP,這是因為我在應用中新增瞭如下程式碼:
public void OnGet(){
HostName = Dns.GetHostName();
HostIP = Dns.GetHostEntry(HostName).AddressList.FirstOrDefault(x => x.AddressFamily == AddressFamily.InterNetwork).ToString();
}
不過,埠轉發的方式只能在本機訪問,為了從外部訪問應用程式,我們需要建立Kubernetes中的另外一種資源:Service。
Service
Kubernetes中的Service資源可以作為一組提供相同服務的Pod的入口,這個資源肩負發現服務和平衡Pod之間負荷的重任。
在Kubernetes叢集中,我們擁有提供不同服務的Pod,那麼Service如何知道該處理哪個Pod呢?
這個問題就用標簽來解決的,具體分兩個步驟:
- 給所有需要Service處理的物件Pod貼上標簽。
- 在Service中使用一個選擇器(
Label Selector
),該選擇器定義了所有貼有對應的標簽的物件Pod。
標簽
標簽提供了一種簡單的方法用於管理Kubernetes中的資源。它們用一對鍵值表示,且可以用於所有資源。
其實在上面的Pod定義中,我們已經定義了標簽:
metadata: name: demo-web labels: app: demo-web
如上,我們為Pod附加了標簽app:demo-web
,在檢視Pod的時候,可以使用--show-labels
引數來顯示Pod對應的標簽:
kubectl get pods --show-labels# 輸出# NAME READY STATUS RESTARTS AGE LABELS# demo-web 1/1 Running 0 1m52s app=demo-web
可以看到,我們的Pod都擁有一個app=demo-web
標簽。
定義Service
現在,讓我們為剛才建立的Pod定義一個Service:
apiVersion: v1kind: Service # 定義Kubernetes資源的型別為Servicemetadata: name: demo-web-service # 定義資源的名稱spec: selector: # 指定對應的Pod app: demo-web # 指定Pod的標簽為demo-web ports: - protocol: TCP # 協議型別 port: 80 # 指定Service訪問的埠 targetPort: 80 # 指定Service轉發請求的埠 nodePort: 30000 type: NodePort # 指定Service的型別,在這裡使用NodePort來對外訪問
如上,我們使用selector
屬性來選擇相應的標簽,並把服務型別(type)設定為NodePort
,type
的取值有以下4種:
-
ClusterIP:預設值,透過叢集的內部IP暴露服務,該樣式下,服務只能夠在叢集內部可以訪問。
-
NodePort:透過每個Node上的IP和靜態埠(NodePort)暴露服務,NodePort服務會路由到ClusterIP服務,這個ClusterIP服務會自動建立。
-
LoadBalancer:使用雲提供商的負載均衡器,可以向外部暴露服務,外部的負載均衡器可以路由到NodePort服務和ClusterIP服務。
-
ExternalName:透過傳回CNAME和它的值,可以將服務對映到
externalName
欄位的內容(如:foo.bar.example.com)。沒有任何型別代理被建立,這隻有 Kubernetes 1.7 或更高版本的kube-dns才支援。
對於服務型別我們先瞭解這麼多就可以了,後續會再來詳細介紹。
然後使用如下命令建立Service:
kubectl create -f demo-web-service.yaml# 輸出# service/demo-web-service created
使用如下命令來檢查服務的狀態:
kubectl get services# 輸出# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# demo-web-service NodePort 10.105.132.214 80:30000/TCP 10s
如上,它有一個CLUSTER-IP為10.105.132.214
,因此我們可以在叢集內使用10.105.132.214:80
來訪問該服務,如果是在叢集外部,可以使用NodeIP:30000
來訪問。
服務發現與負載均衡
在上面我們說到,Service肩負著發現服務和平衡Pod之間負荷的重任,那它是怎麼做的呢?讓我們先再新增一個Pod:
apiVersion: v1kind: Podmetadata: name: demo-web-copy labels: app: demo-webspec: containers: - name: web image: rainingnight/aspnetcore-web ports: - containerPort: 80
如上,其定義與之前的Pod一樣,只是把name
改成了demo-web-copy
,然後建立Pod:
kubectl create -f demo-web-copy.pod.yaml# 檢視Podkubectl get pods# 輸出# NAME READY STATUS RESTARTS AGE# demo-web 1/1 Running 0 10m# demo-web-copy 1/1 Running 0 29s
現在我們使用NodeIP:30000
來訪問,開啟兩個瀏覽器視窗,多掃清幾次,以便讓它們分別路由到不同的Pod,最終顯示如下:
可以看到,我們新建立的Pod已經透過Servie來接受請求了,不需要修改成任何程式程式碼,Kubernetes就已經幫我們實現了服務發現和負載均衡,是不是非常爽。
Deployment
在上面我們手動部署了兩個Pod,但是這隻是單機的玩法,它與直接使用Docker容器相比並無太大優勢,如果我們需要部署一千個實體,那就是一個痛苦的過程,或者我們又想快速更新和迅速回滾,這根本就是不可能的!
其實在k8s中,我們很少直接使用Pod,更多的是使用Kubernetes的另外一種資源:Deployment。
Deployment表示使用者對Kubernetes叢集的一次更新操作。可以是建立一個新的服務或是更新一個新的服務,也可以是滾動升級一個服務。Deployment可以幫助每一個應用程式的生命都保持相同的一點:那就是變化。此外,只有掛掉的應用程式才會一塵不變,否則,新的需求會源源不斷地湧現,更多程式碼會被開發出來、打包以及部署,這個過程中的每一步都有可能出錯。Deployment可以自動化應用程式從一版本升遷到另一版本的過程,並保證服務不間斷,如果有意外發生,它可以讓我們迅速回滾到前一個版本。
Deployment定義
現在,我們使用Deployment來部署我們的Pod,並實現在部署期間服務不間斷服務,可以如下定義:
apiVersion: apps/v1kind: Deployment # 定義Kubernetes資源的型別為Deploymentmetadata: name: demo-web-deployment # 定義資源的名稱 labels: app: demo-web-deploymentspec: # 定義資源的狀態。 replicas: 2 # 定義我們想執行多少個Pod,在這裡我們希望執行2個 selector: matchLabels: # 定義該部署匹配哪些Pod app: demo-web minReadySeconds: 5 # 可選,指定Pod可以變成可用狀態的最小秒數,預設是0 strategy: # 指定更新版本時,部署使用的策略 type: RollingUpdate # 策略型別,使用RollingUpdate可以保證部署期間服務不間斷 rollingUpdate: maxUnavailable: 1 # 部署時最大允許停止的Pod數量(與replicas相比) maxSurge: 1 # 部署時最大允許建立的Pod數量(與replicas相比) template: # 用來指定Pod的模板,與Pod的定義類似 metadata: labels: # 根據模板建立的Pod會被貼上該標簽,與上面的matchLabels對應 app: demo-web spec: containers: - name: web image: rainingnight/aspnetcore-web imagePullPolicy: Always # 預設是IfNotPresent,如果設定成Always,則每一次部署都會重新拉取容器映像(否則,如果本地存在指定的映象版本,就不會再去拉取) ports: - containerPort: 80
儲存為demo-web-deployment.yaml
,然後輸入以下命令來建立Deployment:
kubectl create -f demo-web-deployment.yaml# 輸出# deployment.apps/demo-web-deployent created
現在我們再來檢視以下Pod:
kubectl get pods# 輸出# NAME READY STATUS RESTARTS AGE# demo-web 1/1 Running 0 4h28m# demo-web-copy 1/1 Running 0 18m# demo-web-deployment-745f7997c4-d24bb 1/1 Running 0 16s# demo-web-deployment-745f7997c4-jk9jn 1/1 Running 0 16s
如上,我們有4個執行中的Pod,其中前二個是我們手動建立的,其他兩個是使用Deployment建立的。
我們可以使用kubectl delete pod
刪除一個Deployment建立的Pod,看看結果會怎樣:
kubectl delete pod demo-web-deployment-745f7997c4-d24bb# 輸出# pod "demo-web-deployment-745f7997c4-d24bb" deleted
再次檢視Pod串列:
kubectl get pods# 輸出# NAME READY STATUS RESTARTS AGE# demo-web 1/1 Running 0 31m# demo-web-copy 1/1 Running 0 22m# demo-web-deployment-745f7997c4-jk9jn 1/1 Running 0 3m39s# demo-web-deployment-745f7997c4-mrrw6 1/1 Running 0 11s
可以看到,又重新建立了一個Pod:demo-web-deployment-745f7997c4-mrrw6
,Deployment會監控我們的Pod數量,保持為我們預期的個數。
零停機時間部署(Zero-downtime)
現在我們嘗試以下零停機部署,首先修改Deployment中的image
為:rainingnight/aspnetcore-web:1.0.0
,然後執行如下命令:
kubectl apply -f demo-web-deployment.yaml --record# 輸出# deployment.apps/demo-web-deployment configured
將kubectl的--record
設定為true
可以在annotation
中記錄當前命令建立或者升級了該資源。這在未來會很有用,例如,檢視在每個 Deployment revision 中執行了哪些命令。
除了修改yaml外,還可以直接執行
kubectl set image deployment demo-web-deployment web=rainingnight/aspnetcore-web:1.0.0 --record
來達到同樣的效果。
然後使用如下命令檢查服務更新狀態:
kubectl rollout status deployment demo-web-deployment# 輸出# Waiting for deployment "demo-web-deployment" rollout to finish: 1 old replicas are pending termination...# Waiting for deployment "demo-web-deployment" rollout to finish: 1 old replicas are pending termination...# Waiting for deployment "demo-web-deployment" rollout to finish: 1 old replicas are pending termination...# Waiting for deployment "demo-web-deployment" rollout to finish: 1 old replicas are pending termination...# Waiting for deployment "demo-web-deployment" rollout to finish: 1 of 2 updated replicas are available...# Waiting for deployment "demo-web-deployment" rollout to finish: 1 of 2 updated replicas are available...# deployment "demo-web-deployment" successfully rolled out
如上可以看到,新版本已經成功上線,併在這個過程中副本被逐個替換,也就意味著應用程式始終線上。
現在我們掃清一下瀏覽器,可以看到標題已經變成了Home page - Web-v1
:
回滾到前一個狀態
如果突然發現新上線的版本有Bug,需要緊急回滾到上一個版本,那對Kubernetes來說也是非常簡單的。
我們首先執行如下命令來檢視歷史版本:
kubectl rollout history deployment demo-web-deployment# 輸出# deployment.extensions/demo-web-deployment # REVISION CHANGE-CAUSE# 1 # 2 kubectl apply --filename=demo-web-deployment.yaml --record=true
如上,可以看到有2條歷史,那麼為什麼第1條的CHANGE-CAUSE
是呢,這就是因為我們第二次部署的時候使用了
--record=true
引數。現在,我們想回滾到第一個版本,只需執行如下命令:
kubectl rollout undo deployment demo-web-deployment --to-revision=1# 輸出# deployment.extensions/demo-web-deployment rolled back
再次掃清瀏覽器,標題又變回了Home page - Web
。
部署多個應用
現在我們再部署一個ASP.NET Core WebApi程式,併在剛才的Web應用中呼叫它,形成一個最簡單的微服務樣式。
部署WebApi
與前面的Web應用的部署類似,就不用過多介紹,定義如下YAML:
apiVersion: apps/v1kind: Deploymentmetadata: name: demo-api-deployment labels: app: demo-api-deploymentspec: replicas: 2 selector: matchLabels: app: demo-api minReadySeconds: 5 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 maxSurge: 1 template: metadata: labels: app: demo-api spec: containers: - name: api image: rainingnight/aspnetcore-api imagePullPolicy: Always ports: - containerPort: 80
然後建立Deployment:
kubectl create -f demo-api-deployment.yaml
檢視部署情況:
kubectl get pods# 輸出# NAME READY STATUS RESTARTS AGE# demo-api-deployment-66575d644-9wk7g 1/1 Running 0 75s# demo-api-deployment-66575d644-fknpx 1/1 Running 0 75s# demo-web-deployment-745f7997c4-h7fr8 1/1 Running 0 9m23s# demo-web-deployment-745f7997c4-kvptm 1/1 Running 0 9m23s
前面手動建立的2個Pod已經被我刪除了,因為用Deployment建立的Pod就好了。
現在我們為Api應用也建立一個Service,以便在Web應用中訪問它:
apiVersion: v1kind: Servicemetadata: name: demo-api-servicespec: selector: app: demo-api ports: - protocol: TCP port: 80 targetPort: 80
因為我們的Api應用是不需要在叢集外部訪問的,因此服務型別(type)不需要設定,使用預設的ClusterIP
就可以了。
部署Servcie:
kubectl create -f demo-api-service.yaml
然後檢視Servie:
kubectl get services# 輸出# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE# demo-api-service ClusterIP 10.111.25.49 80/TCP 26s# demo-web-service NodePort 10.105.132.214 80:30000/TCP 58m
可以使用瀏覽器來訪問:10.111.25.49/api/values 來驗證一下是否部署成功。
呼叫服務
那麼,還剩下最後一個問題,我們的Web應用中如何獲取到Api應用的訪問地址呢?
我們先看一下,在Web應用的程式碼中是怎麼呼叫Api的:
// Startup.cspublic void ConfigureServices(IServiceCollection services){
services.AddHttpClient("api", _ => _.BaseAddress = new Uri(Configuration["ApiBaseUrl"]));
}// FetchData.cshtmlpublic class FetchDataModel : PageModel{ private static HttpClient _client; public FetchDataModel(IHttpClientFactory httpClientFactory) {
_client = httpClientFactory.CreateClient("api");
} public IList Forecasts { get; set; } public async Task OnGetAsync() { var res = await _client.GetStringAsync("/api/SampleData/WeatherForecasts");
Forecasts = JsonConvert.DeserializeObject>(res);
}
}
如上,我們首先註冊了一個HttpClient
,並從配置檔案中讀取ApiBaseUrl
做為BaseAddress
,然後在FetchData
頁面中使用HttpClient
呼叫Api服務。
因為在Asp.Net Core中,預設情況下,環境變數中的配置是會改寫appsettings.json
中的配置的,因此我們可以使用新增環境變數的方式來配置ApiBaseUrl
。
修改demo-web-deployment.yaml
,新增env
屬性,如下:
containers: - name: web image: rainingnight/aspnetcore-web imagePullPolicy: Always ports: - containerPort: 80 env: - name: ApiBaseUrl value: "http://10.111.25.49"
然後在Kubernetes中應用該配置:
kubectl apply -f demo-web-deployment.yaml
等待滾動更新完成,掃清瀏覽器,點選FetchData
選單上,可以看到資料成功傳回,但是直接使用叢集IP10.111.25.49
,這也有點太低階了,其實我們可以直接使用域名:http://demo-api-service
,修改後如下:
env: - name: ApiBaseUrl value: "http://demo-api-service"
提交到Kubernetes,然後掃清瀏覽器,可以看到完美執行,這是因為CoreDNS(Kube-DNS)幫我們完成了域名解析。
在Kubernetes 1.11中,CoreDNS已經實現了基於DNS的服務發現的GA,可作為kube-dns外掛的替代品。這意味著CoreDNS將作為各種安裝工具未來釋出版本中的一個選項來提供。事實上,kubeadm團隊選擇將其作為Kubernetes 1.11的預設選項。
CoreDNS是一個通用的、權威的DNS伺服器,提供與Kubernetes後向相容但可擴充套件的整合。它解決了kube-dns所遇到的問題,並提供了許多獨特的功能,可以解決各種各樣的用例。
DNS伺服器監控kubernetes建立服務的API, 併為每個服務建立一組dns記錄。如果在整個群集中啟用了dns, 所有Pod都會使用它作為DNS伺服器。比如我們的demo-api-service
服務,DNS伺服器會建立一條”my-service.my-ns”也就是10.111.25.49:demo-api-service.default
的dns記錄,因為我們的Web應用和Api應用在同一個名稱空間(default)中,所以可以直接使用demo-api-service
來訪問。
總結
本文帶領大家一步一步部署了一個最簡單的ASP.NET Core MVC + WebApi的微服務程式,介紹了Kubernetes中最基本的三個概念:Pod,Deployment,Service,相信大家對Kubernetes也有了一個全面的認識。
雖然Kubernetes整個體系是非常複雜的,但是不用擔心,一開始我們不用去求甚解,最重要的是先跑起來,後續我會和大家一起逐步深入,由簡到繁,愉快的掌握Kubernetes。
附本文所用示例程式碼:https://github.com/RainingNight/AspNetCoreDocker。
相關資料:
-
Kubernetes Concepts
-
使用Kubeadm(1.13+)快速搭建Kubernetes叢集
-
kubernetes-dashboard(1.8.3)部署與踩坑