《Kubernetes與云原生應用》系列之實踐案例“單節點多容器模式”
《Kubernetes與云原生應用》專欄是InfoQ向輕元科技首席架構師王昕約稿的系列文章。本專欄包含8篇內容,將會從介紹和分析Kubernetes系統以及云原生應用入手,逐步推出基于Kubernetes的容器設計模式實踐案例,希望對計劃應用Kubernetes的朋友有所幫助。本文是該專欄的第四篇,閱讀本系列全部內容請在細說云計算微信公眾號(CloudNote)回復K8s。
1. Kubernetes系統架構與設計理念
2. 云原生應用的設計理念與挑戰
3. Kubernetes與云原生應用的容器設計模式
4. Kubernetes容器設計模式實踐案例-單節點多容器模式
5. Kubernetes容器設計模式實踐案例-多節點選舉模式
6. Kubernetes容器設計模式實踐案例-工作隊列模式
7. Kubernetes容器設計模式實踐案例-分散收集模式
8. 云原生應用的容器設計模式綜述與展望
K8s與容器設計模式
目前K8s社區推出的容器設計模式主要分為三大類:
第一類,單容器管理模式;
第二類,單節點多容器模式;
第三類,多節點多容器模式;
一類比一類更復雜。根據復雜性的不同,本系列文章給出不同篇幅的實踐案例介紹。
對于第一類,只在本文中用一小節給與介紹;對于第二類,在本文中,針對每一種典型設計模式分一個小節給與介紹;對于較復雜的第三類,每一種典型設計模式將用一篇文章給與介紹。
單容器管理模式
K8s的最大特色是支持多容器的微服務實例。當然,單容器的模式也是支持的,只不過這種模式并不能突出K8s的特色和強大。很多人對K8s一直以來的印象是:功能強大,但入門較難。其實,單單就啟動一個單容器微服務實例,K8s的命令行操作跟Docker原生命令一樣簡單。
運行一個nginx容器
[root@demo-k8s ~]# kubectl run nginx --image=nginx
deployment "nginx" created
[root@demo-k8s ~]# kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx 1 1 1 1 24s
[root@demo-k8s ~]# kubectl get rs
NAME DESIRED CURRENT AGE
nginx-3137573019 1 1 1m
由上面的例子可以看到,K8s只要一個命令既可以啟動以nginx為鏡像的一個微服務實例。與此同時,K8s的強大之處在于,方便用戶用一個命令的同時,仍然保證了K8s應用體系的完整性和規范性。也就是說,雖然用戶只運行了一個命令,但K8s為用戶自動創建了四種API對象,包括:Deployment,ReplicaSet(RS),Pod和Container要想擴展伸縮同一服務的實例個數也非常簡單。
[root@demo-k8s ~]# kubectl scale deployment nginx --replicas=3
deployment "nginx" scaled
[root@demo-k8s ~]# kubectl get rs
NAME DESIRED CURRENT AGE
nginx-3137573019 3 3 22m
依靠這種兼顧易用性和模型一致性的設計理念,K8s使自己既適合簡單場景也適合復雜場景。
單節點多容器模式
從單節點多容器模式開始的容器設計模式,是真正體現K8s設計特點的地方,也就是基于多容器微服務模型的分布式應用模型。在K8s體系中,Pod是一個輕量級的節點,同一個Pod中的容器可以共享同一塊存儲空間和同一個網絡地址空間,這使得我們可以實現一些組合多個容器在同一節點工作的模式。既然Pod的特點是共享存儲空間和網絡地址,那么單節點多容器模式一定是利用這兩種特性的。
挎斗模式(Sidecar pattern)
第一種單節點多容器模式是挎斗模式。這種模式主要是利用在同一Pod中的容器可以共享存儲空間的能力。
一個典型的挎斗應用場景如圖所示:一個工具容器寫文件到共享的文件目錄,應用主容器從共享的文件目錄讀文件。例如,我們可以用Nginx構建一個代碼發布倉庫,簡單的將代碼放到某個本地目錄即可。為了保持同步,我們同時用一個裝有Git客戶端的容器定時到原始代碼倉庫同步下拉最新的代碼。這種模式的好處是,工具容器的鏡像,也就是打包有Git客戶端的鏡像可以重用,而不需要跟應用的容器打包在一起。同樣的應用,應用主容器不用Nginx也可以用Apache Httpd,都可以跟工具容器組合起來形成微服務。
圖:工具容器寫文件應用容器讀文件
另一個典型的挎斗模式如圖所示:一個工具容器讀文件,應用容器寫文件。例如:一個基于Nginx的Web應用向系統文件系統寫入日志,而一個收集日志的容器從共享目錄讀出日志,并輸出到集群的日志系統。這一模式的好處在于,工具容器的鏡像是可以重用的,不需要在每次更新應用容器打包的時候,把工具容器的執行文件打包進去。
圖:工具容器讀文件應用容器寫文件
外交官模式(Ambassador pattern)
第二種單節點多容器模式是外交官模式。這種模式主要利用同一Pod中的容器可以共享網絡地址空間的特性。如圖所示,在一個Pod中給應用容器搭配一個工具容器作為代理服務器。工具容器幫助應用容器訪問外部服務,使得應用容器訪問服務時不需要使用外網的IP地址,而只需要用localhost訪問本地服務。在這種模式下,作為代理服務器的工具容器好像外部服務派駐在Pod中的“外交官”,使得應用容器辦理業務時只需要跟本Pod的外交官打交道,而不需要出國了,因此而得名。
圖:外交官模式的邏輯結構
基于外交官模式的Redis訪問演示案例
我們這里用一個訪問Redis服務的簡單案例,來實踐體驗一下Ambassador模式和K8s單節點多容器模式的應用細節。
圖:基于外交官模式的Redis訪問演示案例架構
1.創建一個初始的Redis Master實例
先創建一個Redis Master節點的Pod用于初始化Redis集群。
kubectl create -f examples/redis/redis-master.yaml
2.創建redis的服務
創建一個redis的服務,這個服務可以在前段作為多個redis的Pod節點的負載均衡。
kubectl create -f examples/redis/redis-service.yaml
3.創建redis的replication controller
創建控制多個redis服務Pod的RC,當然也可以用Deployment或ReplicaSet來創建。
kubectl create -f examples/redis/redis-controller.yaml
[root@demo-k8s ~]# kubectl get rc
NAME DESIRED CURRENT AGE
redis 1 1 19h
創建完后可以用kubectl get命令查看rc和Pod,會發現并沒有產生新的Redis Pod,這是因為原來的Pod redis-master已經滿足了replica=1的要求。
4.創建redis-sentinel的服務
創建redis sentinel服務。
kubectl create -f examples/redis/redis-sentinel-service.yaml
5.將redis-sentinel的replication controller
創建控制多個redis sentinel服務Pod的RC。
kubectl create -f examples/redis/redis-sentinel-controller.yaml
6.將redis實例和redis-sentinel實例擴展成3個
kubectl scale rc redis --replicas=3
kubectl scale rc redis-sentinel --replicas=3
7.刪除掉手工啟動的redis實例redis-master
刪除掉我們已開始手工創建的redis master的Pod,redis的rc會自動啟動新的redis以滿足replicas=3的要求。同時,Redis sentinel節點會選舉出一個新的節點作為master節點。
kubectl delete pods redis-master
8.Redis集群的驗證方法
查詢redis集群中所有redis實例的IP。
[root@demo-k8s ~]# kubectl describe pod -l name=redis | grep IP
IP: 10.120.44.3
IP: 10.120.63.3
IP: 10.120.80.5
我們知道,這3個redis實例中,只有一個是Master節點,是可寫的,可以調用SET命令和GET命令;其他兩個節點是Slave節點,是只讀的,只能調用GET命令。我們可以用下面的命令測試三個redis節點。
[root@demo-k8s ~]# echo -e "SET test1 SET test1 8\r\nQUIT\r\n" | curl telnet://10.120.44.3:6379
-READONLY You can't write against a read only slave.
+OK
[root@demo-k8s ~]# echo -e "SET test1 8\r\nQUIT\r\n" | curl telnet://10.120.63.3:6379
-READONLY You can't write against a read only slave.
+OK
[root@demo-k8s ~]# echo -e "SET test1 8\r\nQUIT\r\n" | curl telnet://10.120.80.5:6379
+OK
+OK
通過上面的測試我們可以知道只有IP為10.120.80.5的redis pod是Master節點,其他兩個是slave節點。
對于只讀的操作,我們可以利用redis的service IP,通過K8s的kube-proxy來訪問,如下,我們得到redis的CLUSTER-IP為10.123.248.129,可以用這個IP來讀取Redis數據。那么,如果用這個IP來寫數據將怎么樣呢,后面將看到。
[root@demo-k8s ~]# kubectl get svc -l name=redis
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
redis 10.123.248.129 <none> 6379/TCP 5d
[root@demo-k8s ~]# echo -e "GET test1\r\nQUIT\r\n" | curl telnet://10.123.248.129:6379
$1
8
+OK
9.制作Ambassador容器鏡像
截至目前,我們還沒有用到外交官模式。下面我們用Haproxy制作一個外交官代理,用來訪問Redis服務,使得跟該容器在同一個Pod里的容器,在訪問Redis讀寫服務的時候,都只需要訪問本地localhost服務。本例中的文件在 Github 上可以找到。
Dockerfile文件清單:Dockerfile
FROM haproxy:1.5
COPY tmpl-haproxy.cfg /
COPY starthaproxy.sh /
CMD ["sh"Node.js "-c"Node.js "/starthaproxy.sh"]
啟動haproxy的文件清單:starthaproxy.sh
echo
"Proxying localhost ${INPUT_PORT} to ${TARGET_IP}:
${TARGET_PORT}"
cat tmpl-haproxy.cfg |
sed -e "s/INPUT_PORT/${INPUT_PORT}/" -e "s/TARGET_IP/${TARGET_IP}/" -
e "s/TARGET_PORT/${TARGET_PORT}/" > /haproxy.cfg
haproxy -f /haproxy.cfg
通過這里的Dockerfile構建的代理服務器容器,根據輸入的環境變量INPUT_PORT、TARGET_IP和TARGET_PORT,可以將發向本地INPUT_PORT的服務請求轉發到目標TARGET_IP:TARGET_PORT上去。
因此這個容器可以作為一個簡單方便的本地代理服務器使用。
10.使用Ambassador代理服務器的pod
使用Ambassador代理服務的Pod文件清單:demo-redis-amb-centos.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
name: demo-redis-amb-centos
name: demo-redis-amb-centos
spec:
containers:
- name: app-main-container
image: centos:7
args:
- sleep
- "1000000"
- name: redis-amb-read
image: xwangqingyuan/port-forward-haproxy
env:
- name: INPUT_PORT
value: "6379"
- name: TARGET_IP
value: "10.123.248.129"
- name: TARGET_PORT
value: "6379"
ports:
- containerPort: 6379
- name: redis-amb-write
image: xwangqingyuan/port-forward-haproxy
env:
- name: INPUT_PORT
value: "16379"
- name: TARGET_IP
value: "10.120.80.5"
- name: TARGET_PORT
value: "6379"
ports:
- containerPort: 16379
volumes:
- name: data
emptyDir: {}
在這個Pod中,我們用一個CentOS容器作為應用的主容器來使用Ambassador工具容器,當然也可以用實際的應用容器Tomcat、Node.js、PHP等等,因為這里只是為了演示測試Ambassador代理,我們用CentOS作為應用容器。其中我們有兩個外交官容器,一個用來從Redis集群讀數據,容器的名字為redis-amb-read,它將發向本地6379端口的請求轉發到redis服務的CLUSTER-IP,最終會輪訓地發送給任意一個redis實例;另一個用來向Redis集群寫數據,容器的名字為redis-amb-write,它將發向本地16379端口的請求轉發到redis master Pod的IP。
11.測試一個使用Ambassador模式的pod
創建一個使用Ambassador代理的Pod,并登陸到主容器進行測試。
kubectl create -f /home/centos/worktemp/redis/demo-redis-amb-centos.yaml
kubectl exec -it demo-redis-amb-centos /bin/bash
測試對讀容器本地Ambassador的訪問。
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:6379
-READONLY You can't write against a read only slave.
+OK
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:6379
-READONLY You can't write against a read only slave.
+OK
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:6379
+OK
+OK
echo -e "GET test1\r\nQUIT\r\n" | curl telnet://localhost:6379
$2
10
+OK
echo -e "GET test1\r\nQUIT\r\n" | curl telnet://localhost:6379
$2
10
+OK
echo -e "GET test1\r\nQUIT\r\n" | curl telnet://localhost:6379
$2
10
+OK
從測試結果可以發現,對讀容器執行GET操作時,操作總是成功的,說明3個redis pod都可以讀取數據。而對讀容器執行SET操作時,3個操作只有一個是成功的,也就是說只有負載均衡將請求發給redis master pod時,操作能夠成功。
測試對寫容器本地Ambassador的訪問。
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:16379
+OK
+OK
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:16379
+OK
+OK
echo -e "SET test1 10\r\nQUIT\r\n" | curl telnet://localhost:16379
+OK
+OK
從測試結果可以發現,對寫容器執行SET操作時,操作總是成功的,說明redis-amb-write容器將所有請求都轉發給了redis master pod。
適配器模式(Adapter pattern)
第三種單節點多容器模式是適配器模式。這種模式對于監控和管理分布式系統尤為重要。對分布式系統的一種理想設計目標,就是能夠實現“分布地執行和存儲,統一的監控和管理”。要想實現“統一的監控和管理”,應用和監控管理交互的接口需要是統一的,而且其接口是依照“統一的監控服務”的接口模式來實現。這和面向對象設計模式中的“適配器模式”也非常相似。
圖:統一監控服務和監控適配器模式
圖:利用Prometheus作為監控服務的分布式系統
一個典型的可以采用適配器模式的系統,是利用Prometheus作為監控服務的分布式系統。在Prometheus周邊項目中,有諸多適用于不同應用系統的監控數據輸出器(Exporter),負責收集跟特定應用相關的監控數據,使得Prometheus服務可以以統一的數據模式收集不同應用系統的監控數據,每個Exporter同時也都是一個適配器模式的實現。
總結
本文主要介紹了K8s集群中,單節點單容器模式和基于Pod模型所支持的幾種單節點多容器模式,包括挎斗模式、外交官模式和適配器模式。并且對于外交官模式,利用一個redis集群案例演示了如何利用外交官模式以訪問本地服務的模式訪問網絡服務。
后續,K8s社區肯定還會發展出其他的容器設計模式,但不論如何,單節點多容器模式主要是利用同一Pod中的容器可以共享存儲空間和網絡地址空間的特點。本文案例中的代碼只能用來演示,還比較簡單,有很多的地方可以增強。例如:用Replication Controller的地方,可以用Deployment或Replica Set來部署,這是K8s社區更為推薦的方式;對于案例中的外交官容器,Master Pod的IP和Redis服務的IP未必需要寫死,而是可以通過腳本從指定的服務讀取;此外,演示中所用的代理服務器容器也可以增強為支持多個端口映射的代理服務器。
答疑環節
問:考慮到性能,分布式文件存儲可能不會適合,K8s有沒有針對database的略微完美的部署方案?
王昕:完美的應該沒有。有兩個K8s功能可能跟database的存儲有關,一個是nodeAffinity一個是PetSet。
nodeAffinity的作用是讓你可以為Pod選定特定的節點運行,例如你想要的高性能的掛載了SSD的節點。PetSet的作用是讓Pod可用于有狀態服務,可以固定跟某個存儲綁定。這兩個功能都處于非常早的階段還很不成熟,但可以肯定這些功能是K8s社區專門解決有狀態服務和存儲問題的方案。
在nodeAffinity和PetSet設計穩定之前,還有一個nodeSelector也是可以利用的。它可以讓用戶選定特定的node部署Pod,同樣,你可以選定掛有高性能存儲的節點。
問:希望更深層次了解一下Overlay的網絡原理,能否介紹一下?
王昕:Overlay也就是覆蓋網絡,跟隧道技術是緊密相關的,通過網絡封裝技術模擬二層網絡,跟V*N所采用的技術是類似的。
簡單說,比如A和B之間是沒有網絡直接用以太網連接的,就是所謂的二層不連通。但是通過三層的IP層可以連通,也就可以建立四層的連接,比如UDP可以連通,也就是四層連通,二層不連通。但是我可以,通過四層的連通傳輸二層以太網的數據包,模擬出一個二層連通的以太網來。這個模擬出來的連通的二層網絡就叫Overlay,下面真實的物理網絡叫做Underlay。
所以說,Overlay的技術每一層封裝會增加一些開銷,會影響一定性能。此外,各個層次對網絡丟包延遲的問題處理不好,也會影響性能。K8s比較流行的基于Overlay的組網技術有Flannel和Weave,Weave目前看性能是比較差的。Flannel有兩種隧道封裝技術,UDP和VxLAN,一般來說VxLAN性能比UDP好很多。Overlay有一定的性能劣勢,也有優勢,主要是降低對下面物理網絡的依賴,很容易搭建。
來自:http://www.infoq.com/cn/articles/kubernetes-and-cloud-native-applications-part03