Kubernetes微服務軟體開發架構之應用實踐
谷歌於2015年正式推出的Kubernetes開源專案目前已經吸引了眾多IT公司的關注,這些公司包括Redhat、CoreOS、IBM、惠普等知名IT公司,也包括國內如華為、時速雲等公司。為什麼Kubernetes會引發這麼多公司的關注?最根本的原因是Kubernetes是新一代的基於先進容器技術的微服務架構平臺,它將當前火爆的容器技術與微服務架構兩大吸引眼球的技術點完美的融為一體,並且切切實實的解決了傳統分散式系統開發過程中長期存在的痛點問題。
本文假設您已經很熟悉並掌握了Docker技術,這裡不會再花費篇幅介紹它。正是通過輕量級的容器隔離技術,Kubernetes實現了“微服務”化的特性,同時藉助於Docker提供的基礎能力,使得平臺的自動化能力得以實現。
概念與原理
作為一個架構師來說,我們做了這麼多年的分散式系統,其實我們真正關心的並不是伺服器、交換機、負載均衡器、監控與部署這些事物,我們真正關心的是“服務”本身,並且在內心深處,我們渴望能實現圖1所示的下面的這段“願景”:
我的系統中有ServiceA、ServiceB、ServiceC三種服務,其中ServiceA需要部署3個例項、而ServiceB與ServiceC各自需要部署5個例項,我希望有一個平臺(或工具)幫我自動完成上述13個例項的分散式部署,並且持續監控它們。當發現某個伺服器宕機或者某個服務例項故障的時候,平臺能夠自我修復,從而確保在任何時間點,正在執行的服務例項的數量都是我所預期的。這樣一來,我和我的團隊只需關注服務開發本身,而無需再為頭疼的基礎設施和運維監控的事情而煩惱了。
直到Kubernetes出現之前,沒有一個公開的平臺聲稱實現了上面的“願景”,這一次,又是谷歌的神作驚豔了我們。Kubernetes讓團隊有更多的時間去關注與業務需求和業務相關的程式碼本身,從而在很大程度上提升了整個軟體團隊的工作效率與投入產出比。
Kubernetes裡核心的概念只有以下幾個:
- Service
- Pod
- Deployments(RC)
Service表示業務系統中的一個“微服務”,每個具體的Service背後都有分佈在多個機器上的程序例項來提供服務,這些程序例項在Kubernetes裡被封裝為一個個Pod,Pod基本等同於Docker Container,稍有不同的是Pod其實是一組密切捆綁在一起並且“同生共死”的Docker Container,從模型設計的角度來說,的確存在一個服務例項需要多個程序來提供服務並且它們需要“在一起” 的情況。
Kubernetes的Service與我們通常所說的“Service”有一個明顯的的不同,前者有一個虛擬IP地址,稱之為“ClusterIP”,服務與服務之間“ClusterIP+服務埠”的方式進行訪問,而無需一個複雜的服務發現的API。這樣一來,只要知道某個Service的ClusterIP,就能直接訪問該服務,為此,Kubernetes提供了兩種方式來解決ClusterIP的發現問題:
- 第一種方式是通過環境變數,比如我們定義了一個名稱為ORDER_SERVICE 的Service ,分配的ClusterIP為10.10.0.3 ,則在每個服務例項的容器中,會自動增加服務名到ClusterIP對映的環境變數:ORDER_SERVICE_SERVICE_HOST=10.10.0.3,於是程式裡可以通過服務名簡單獲得對應的ClusterIP。
- 第二種方式是通過DNS,這種方式下,每個服務名與ClusterIP的對映關係會被自動同步到Kubernetes叢集裡內建的DNS元件裡,於是直接通過對服務名的DNS Lookup機制就找到對應的ClusterIP了,這種方式更加直觀。
由於Kubernetes的Service這一獨特設計實現思路,使得所有以TCP /IP 方式進行通訊的分散式系統都能很簡單的遷移到Kubernetes平臺上了。如圖2所示,當客戶端訪問某個Service的時候,Kubernetes內建的元件kube-proxy透明的實現了到後端Pod的流量負載均衡、會話保持、故障自動恢復等高階特性。
Kubernetes是如何繫結Service與Pod的呢?它如何區分哪些Pod對應同一個Service?答案也很簡單——“貼標籤”。每個Pod都可以貼一個或多個不同的標籤(Label),而每個Service都一個“標籤選擇器”,標籤選擇器(Label Selector)確定了要選擇擁有哪些標籤的物件,比如下面這段YAML格式的內容定義了一個稱之為ku8-redis-master的Service,它的標籤選擇器的內容為“app: ku8-redis-master”,表明擁有“app= ku8-redis-master”這個標籤的Pod都是為它服務的。
apiVersion: v1
kind: Service
metadata:
name: ku8-redis-master
spec:
ports:
- port: 6379
selector:
app: ku8-redis-master
下面是對應的Pod的定義,注意到它的labels屬性的內容:
apiVersion: v1
kind: Pod
metadata:
name: ku8-redis-master
labels:
app: ku8-redis-master
spec:
containers:
- name: server
image: redis
ports:
- containerPort: 6379
restartPolicy: Never
最後,我們來看看Deployment/RC的概念,它的作用是用來告訴Kubernetes,某種型別的Pod(擁有某個特定標籤的Pod)需要在叢集中建立幾個副本例項,Deployment/RC的定義其實是Pod建立模板(Template)+Pod副本數量的宣告(replicas):
apiVersion: v1
kind: ReplicationController
metadata:
name: ku8-redis-slave
spec:
replicas: 2
template:
metadata:
labels:
app: ku8-redis-slave
spec:
containers:
- name: server
image: devopsbq/redis-slave
env:
- name: MASTER_ADDR
value: ku8-redis-master
ports:
- containerPort: 6379
Kubernetes開發指南
本節我們以一個傳統的Java應用為例,來說明如何將其改造遷移到Kubernetes的先進微服務架構平臺上來。
如圖3所示,我們的這個示例程式是一個跑在Tomcat裡的Web應用,為了簡化,沒有用任何框架,直接在JSP頁面裡通過JDBC操作資料庫。
上述系統中,我們將MySQL服務與Web應用分別建模為Kubernetes中的一個Service,其中MySQL服務的Service定義如下:
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
ports:
- port: 3306
selector:
app: mysql_pod
MySQL服務對應的Deployment/RC的定義如下:
apiVersion: v1
kind: ReplicationController
metadata:
name: mysql-deployment
spec:
replicas: 1
template:
metadata:
labels:
app: mysql_pod
spec:
containers:
- name: mysql
image: mysql
imagePullPolicy: IfNotPresent
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
下一步,我們需要改造Web應用中獲取MySQL地址的這段程式碼,從容器的環境變數中獲取上述MySQL服務的IP與Port:
String ip=System.getenv("MYSQL_SERVICE_HOST");
String port=System.getenv("MYSQL_SERVICE_PORT");
ip=(ip==null)?"localhost":ip;
port=(port==null)?"3306":port;
conn = java.sql.DriverManager.getConnection("jdbc:mysql://"+ip+":"+port+"?useUnicode=true&characterEncoding=UTF-8", "root","123456");
接下來,將此Web應用打包為一個標準的Docker映象,名字為k8s_myweb_image,這個映象直接從官方Tomcat映象上新增我們的Web應用目錄demo到webapps目錄下即可,Dockerfile比較簡單,如下所示:
FROM tomcat
MAINTAINER bestme <bestme@hpe.com>
ADD demo /usr/local/tomcat/webapps/demo
類似之前的MySQL服務定義,下面是這個Web應用的Service定義:
apiVersion: v1
kind: Service
metadata:
name: hpe-java-web
spec:
type: NodePort
ports:
- port: 8080
nodePort: 31002
selector:
app: hpe_java_web_pod
我們看到這裡用了一個特殊的語法:NodePort,並且賦值為31002,詞語法的作用是讓此Web應用容器裡的8080埠被NAT對映到kuberntetes裡每個Node上的31002埠,從而我們可以用Node的IP和埠31002來訪問Tomcat的8080埠,比如我本機的可以通過http://192.168.18.137:31002/demo/來訪問這個Web應用。
下面是Web應用的Service對應的Deployment/RC的定義:
apiVersion: v1
kind: ReplicationController
metadata:
name: hpe-java-web-deployement
spec:
replicas: 1
template:
metadata:
labels:
app: hpe_java_web_pod
spec:
containers:
- name: myweb
image: k8s_myweb_image
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
定義好所有Service與對應的Deployment/RC描述檔案後(總共4個yaml檔案),我們可以通過Kubernetes的命令列工具kubectrl –f create xxx.yaml提交到叢集裡,如果一切正常,Kubernetes會在幾分鐘內自動完成部署,你會看到相關的資源物件都已經建立成功:
-bash-4.2# kubectl get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hpe-java-web 10.254.183.22 nodes 8080/TCP 36m
kubernetes 10.254.0.1 <none> 443/TCP 89d
mysql 10.254.170.22 <none> 3306/TCP 36m
-bash-4.2# kubectl get pods
NAME READY STATUS RESTARTS AGE
hpe-java-web-deployement-q8t9k 1/1 Running 0 36m
mysql-deployment-5py34 1/1 Running 0 36m
-bash-4.2# kubectl get rc
NAME DESIRED CURRENT AGE
hpe-java-web-deployement 1 1 37m
mysql-deployment 1 1 37m
結束語
從上面步驟來看,傳統應用遷移改造到Kubernetes上還是比較容易的,而藉助於Kubernetes的優勢,即使一個小的開發團隊,也能在系統架構和運維能力上迅速接近一個大的研發團隊的水平。
此外,為了降低Kubernetes的應用門檻,我們(惠普中國CMS研發團隊)開源了一個Kubernetes的管理平臺Ku8 eye,專案地址為https://github.com/bestcloud/ku8eye,Ku8 eye很適合用作為小公司的內部PaaS應用管理平臺,其功能架構類似圖4所示的Ku8 Manager企業版,Ku8 eye採用Java開發完成,是目前唯一一個開源的Kubernetes圖形化管理系統,也希望更多愛好開源和有能力的同行參與進來,將它打造成為國內最好的雲端計算領域的開源軟體。