原文:Kubernetes初探[1]:部署你的第一個ASP.NET Core應用到k8s集羣html
Kubernetes是Google基於Borg開源的容器編排調度引擎,做爲CNCF(Cloud Native Computing Foundation)最重要的組件之一,它的目標不只僅是一個編排系統,而是提供一個規範,可讓你來描述集羣的架構,定義服務的最終狀態,Kubernetes能夠幫你將系統自動得達到和維持在這個狀態。node
更直白的說,Kubernetes可讓用戶經過編寫一個yaml或者json格式的配置文件,也能夠是經過工具/代碼生成或者是直接請求Kubernetes API來建立應用,該配置文件中包含了用戶想要應用程序保持的狀態,無論整個Kubernetes集羣中的個別主機發生什麼問題,都不會影響應用程序的狀態,你還能夠經過改變該配置文件或請求Kubernetes API來改變應用程序的狀態。git
這意味着開發人員不須要在乎節點的數目,也不須要在乎從哪裏運行容器以及如何與它們交流。開發人員也不須要管理硬件優化,或擔憂節點關閉(它們將遵循墨菲法則),由於新的節點會添加到Kubernetes集羣,同時Kubernetes會在其餘運行的節點中添加容器,Kubernetes會發揮最大的做用。github
總結:Kubernetes是容器控制平臺,能夠抽象全部的底層基礎設施(容器運行用到的基礎設施)。web
Kubernetes——讓容器應用進入大規模工業生產。json
Kubernetes另外一個深刻人心的特色是:它標準化了雲服務提供商。好比,有一個Azure、Google雲平臺或其餘雲服務提供商的專家,他擔任了一個搭建在全新的雲服務提供商的項目。這可能引發不少後果,好比說:他可能沒法在截止期限內完成;公司可能須要招聘更多相關的人員,等等。相對的,Kubernetes就沒有這個問題。由於不管是哪家雲服務提供商,你均可以在上面運行相同的命令,以既定的方式向Kubernetes API服務器發送請求,Kubernetes會負責抽象,並在不一樣的雲服務商中實現。api
對於公司來講,這意味着他們不須要綁定到任何一家雲服務商。他們能夠計算其餘雲服務商的開銷,而後轉移到別家,並依舊保留着原來的專家,原來的人員,他們還能夠花更少的錢。數組
Kubernetes有不少技術概念,同時對應不少API對象,最重要的也是最基礎的對象就是Pod。Pod是Kubernetes集羣中運行部署應用的最小單元,而且是支持多個容器的。瀏覽器
Pod的設計理念是支持多個容器在一個Pod中共享網絡地址和文件系統,能夠經過進程間通訊和文件共享這種簡單高效的方式組合完成服務。Pod對多容器的支持是Kubernetes最基礎的設計理念。好比你運行一個操做系統發行版的軟件倉庫,一個Nginx容器用來發布軟件,另外一個容器專門用來從源倉庫作同步,這兩個容器的鏡像不太多是一個團隊開發的,可是他們一起工做才能提供一個微服務;這種狀況下,不一樣的團隊各自開發構建本身的容器鏡像,在部署的時候組合成一個微服務對外提供服務。不過,在大多數狀況下,咱們只會在Pod中運行一個容器,本文中的例子也是這樣的。bash
Pod 的另外一個特徵是:若是咱們但願使用其餘 RKE
等技術的話,咱們能夠作到不依賴Docker容器。
Docker是kubernetes中最經常使用的容器運行時,可是Pod也支持其餘容器運行時。
總的來講,Pod的主要特徵包括:
每一個Pod能夠在Kubernetes集羣內擁有惟一的IP地址;
Pod能夠擁有多個容器。這些容器共享同一個端口空間,因此他們能夠經過localhost
交流(可想而知它們沒法使用相同的端口),與其餘Pod內容器的交流能夠經過結合Pod的IP來完成;
一個Pod內的容器共享同一個卷、同一個 IP、端口空間、IPC 命名空間。
以下,咱們定義一個最簡單的Pod:
apiVersion: v1 kind: Pod # 定義Kubernetes資源的類型爲Pod metadata: name: demo-web # 定義資源的名稱 labels: # 爲Pod貼上標籤,後面會介紹其用處 app: demo-web spec: # 定義資源的狀態,對於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。
Kubernetes中的Service資源能夠做爲一組提供相同服務的Pod的入口,這個資源肩負發現服務和平衡Pod之間負荷的重任。
在Kubernetes集羣中,咱們擁有提供不一樣服務的Pod,那麼Service如何知道該處理哪一個Pod呢?
這個問題就用標籤來解決的,具體分兩個步驟:
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
標籤。
如今,讓咱們爲剛纔建立的Pod定義一個Service:
apiVersion: v1 kind: Service # 定義Kubernetes資源的類型爲Service metadata: 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 <none> 80:30000/TCP 10s
如上,它有一個CLUSTER-IP爲10.105.132.214
,所以咱們能夠在集羣內使用10.105.132.214:80
來訪問該服務,若是是在集羣外部,可使用[nodeip]:30000
來訪問。
在上面咱們說到,Service肩負着發現服務和平衡Pod之間負荷的重任,那它是怎麼作的呢?讓咱們先再添加一個Pod:
apiVersion: v1 kind: Pod metadata: name: demo-web-copy labels: app: demo-web spec: containers: - name: web image: rainingnight/aspnetcore-web ports: - containerPort: 80
如上,其定義與以前的Pod同樣,只是把name
改爲了demo-web-copy
,而後建立Pod:
kubectl create -f demo-web-copy.pod.yaml # 查看Pod kubectl get pods # 輸出 # NAME READY STATUS RESTARTS AGE # demo-web 1/1 Running 0 10m # demo-web-copy 1/1 Running 0 29s
如今咱們使用NodeIP:3000
來訪問,打開兩個瀏覽器窗口,多刷新幾回,以便讓它們分別路由到不一樣的Node,最終顯示以下:
能夠看到,咱們新建立的Pod已經經過Servie來接受請求了,不須要修改爲任何程序代碼,Kubernetes就已經幫咱們實現了服務發現和負載均衡,是否是很是爽。
在上面咱們手動部署了兩個Pod,可是這只是單機的玩法,它與直接使用Docker容器相比並沒有太大優點,若是咱們須要部署一千個實例,那就是一個痛苦的過程,或者咱們又想快速更新和迅速回滾,這根本就是不可能的!
其實在k8s中,咱們不多直接使用Pod,更多的是使用Kubernetes的另一種資源:Deployment。
Deployment表示用戶對Kubernetes集羣的一次更新操做。能夠是建立一個新的服務或是更新一個新的服務,也能夠是滾動升級一個服務。Deployment能夠幫助每個應用程序的生命都保持相同的一點:那就是變化。此外,只有掛掉的應用程序纔會一塵不變,不然,新的需求會源源不斷地涌現,更多代碼會被開發出來、打包以及部署,這個過程當中的每一步都有可能出錯。Deployment能夠自動化應用程序從一版本升遷到另外一版本的過程,並保證服務不間斷,若是有意外發生,它可讓咱們迅速回滾到前一個版本。
如今,咱們使用Deployment來部署咱們的Pod,並實如今部署期間服務不間斷服務,能夠以下定義:
apiVersion: apps/v1 kind: Deployment # 定義Kubernetes資源的類型爲Deployment metadata: name: demo-web-deployment # 定義資源的名稱 labels: app: demo-web-deployment spec: # 定義資源的狀態。 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 <pod-name>
刪除一個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數量,保持爲咱們預期的個數。
如今咱們嘗試如下零停機部署,首先修改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 <none> # 2 kubectl apply --filename=demo-web-deployment.yaml --record=true
如上,能夠看到有2條歷史,那麼爲何第1條的CHANGE-CAUSE
是<none>
呢,這就是由於咱們第二次部署的時候使用了--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應用中調用它,造成一個最簡單的微服務模式。
與前面的Web應用的部署相似,就不用過多介紹,定義以下YAML:
apiVersion: apps/v1 kind: Deployment metadata: name: demo-api-deployment labels: app: demo-api-deployment spec: 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: v1 kind: Service metadata: name: demo-api-service spec: 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 <none> 80/TCP 26s # demo-web-service NodePort 10.105.132.214 <none> 80:30000/TCP 58m
可使用瀏覽器來訪問:10.111.25.49/api/values 來驗證一下是否部署成功。
那麼,還剩下最後一個問題,咱們的Web應用中如何獲取到Api應用的訪問地址呢?
咱們先看一下,在Web應用的代碼中是怎麼調用Api的:
// Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddHttpClient("api", _ => _.BaseAddress = new Uri(Configuration["ApiBaseUrl"])); } // FetchData.cshtml public class FetchDataModel : PageModel { private static HttpClient _client; public FetchDataModel(IHttpClientFactory httpClientFactory) { _client = httpClientFactory.CreateClient("api"); } public IList<WeatherForecast> Forecasts { get; set; } public async Task OnGetAsync() { var res = await _client.GetStringAsync("/api/SampleData/WeatherForecasts"); Forecasts = JsonConvert.DeserializeObject<IList<WeatherForecast>>(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"
應用該配置,而後刷新瀏覽器,能夠看到完美運行,這是由於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應用同一個命名空間中,因此能夠直接使用demo-api-service
來訪問。
本文帶領你們一步一步部署了一個最簡單的ASP.NET Core MVC + WebApi的微服務程序,介紹了Kubernetes中最基本的三個概念:Pod,Deployment,Service,相信你們對Kubernetes也有了一個全面的認識。
雖然Kubernetes整個體系是很是複雜的,可是不用擔憂,一開始咱們不用去求甚解,最重要的是先跑起來,後續我會和你們一塊兒逐步深刻,由簡到繁,愉快的掌握Kubernetes。
附本文所用示例代碼:https://github.com/RainingNight/AspNetCoreDocker。