1. 概念
1.1 命令式命令行操作
Docker Swarm的編排操作
$ docker service create --name nginx --replicas 2 nginx
$ docker service update --image nginx:1.7.9 nginx
1.2 命令式配置文件操作
Kubernetes中,通過編寫yaml進行容器的創(chuàng)建與更新
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
$ kubectl create -f nginx.yaml
創(chuàng)建出來一個Pod之后,更新容器鏡像版本:修改yaml文件的Pod template部分
...
spec:
containers:
- name: nginx
image: nginx:1.7.9
$ kubectl replace -f nginx.yaml
1.3 聲明式API
對于1.2中的yaml文件枣申,進行kubectl apply操作
$ kubectl apply -f nginx.yaml
同樣的,修改過鏡像版本后胧洒,執(zhí)行kubectl apply谅海,更新pod,觸發(fā)滾動更新每篷。
$ kubectl apply -f nginx.yaml
kubelet apply和kubelet replace區(qū)別
- replace 是使用新的yaml文件中的API對象,替換原有API對象
- apply 是對原有API對象PATCH操作
此外端圈,kubetcl set image, kubectl edit也是對原有API對象的修改
這就意味著焦读,kube-apiserver在響應式命令請求的時候,一次只能處理一個舱权,否則多個替換操作的結果就可能導致沖突矗晃,而對于對于聲明式請求來說,一次可以進行多個寫操作宴倍,具備前面提到過的Merge能力张症。
所以什么是“聲明式API”?
其實就是鸵贬,通過給一個配置文件(期望的最終狀態(tài))俗他,然后通過使用支持patch的命令模式,去疊加的演進對象的最終狀態(tài)的這樣一種做事情的方式阔逼。
2. 聲明式API的在實際項目中的重要意義(Istio)
2.1 Isotio概念
是一個基于Kubernetes項目的微服務治理框架兆衅,架構如下所示。
2.2 Envoy容器
從架構圖中看出,Istio項目的最根本組件羡亩,是運行在每個應用Pod中的Envoy容器摩疑。這個應用容器是Lyft公司推出的一個高性能C++網(wǎng)絡代理,它以sidecar容器的方式畏铆,運行在每一個治理的應用Pod中雷袋。他的作用是通過配置Pod中的iptables規(guī)則,把整個Pod的進出流量接管下來辞居。
這樣片排,Istio控制層的Pilot組件,就能夠通過調用每個Envoy容器的API速侈,對這個Envoy進行配置率寡,實現(xiàn)微服務治理。
什么是微服務治理倚搬?
2.3 Enovy的產(chǎn)生與應用場景
舉例灰度發(fā)布冶共。
仍然以2.1中的Istio架構為例,假設左邊的應用是一個正在運行的舊版本應用每界,右邊的應用Pod是這個版本新上線應用捅僵。通過Pilot調節(jié)兩個Pod里Envoy容器的配置,進而調節(jié)外部請求到兩個Pod的流量比例(比如新版本10%眨层,舊版本應用Pod90%)庙楚,之后通過逐步過渡比例,完成灰度發(fā)布的過程趴樱。
在整個微服務治理的過程中馒闷,無論是對Envoy的部署,還是對Envoy代理的配置叁征,這些對于用戶和應用都是完全“無感”的纳账。
這就有一個問題,既然Envoy是在每個應用Pod安裝的(我們知道捺疼,可以把一個Pod疏虫,理解成一個實際的應用,而Envoy容器的聲明和配置修改啤呼,都不會在用戶的yaml文件中寫明卧秘,單Envoy容器最終又是切切實實運行在最終的應用Pod當中),那么對于Envoy在Pod中實現(xiàn)和配置修改官扣,Istio怎么做到無感的翅敌?
實現(xiàn)這種Envoy容器無感修改的技術,是Kubernetes中一個叫做Dynamic Admission Control的功能醇锚,這個功能也叫做Initializer哼御。
后面就可以看到坯临,Dynamic Admission Control焊唬,也就是Initilizer恋昼,其實也是一個容器起來的Pod。他的功能就是將存在Etcd中的ConfigMap類型的關于Envoy容器的配置赶促,和用戶的Pod API對象通過Kubernetes的PATCH API做merge液肌。
實際上,在Kubernetes項目中鸥滨,每當一個Pod或者任何一個API對象通過命令等方式提交給APIServer之后嗦哆,總要有一些“初始化”性質的工作需要他們被Kubernetes項目正式處理之前進行。這個操作婿滓,是一個叫做Admission的功能老速。這是Kubernetes中一段Admission Controller的代碼,可以被選擇性地變異進入API Server凸主,在API對象創(chuàng)建后會被立刻調用橘券。但是,如果Envoy的配置修改想用這個功能的話卿吐,那么每次都要動態(tài)的修改代碼重新build并重啟APIServer旁舰,顯然這是不可接受的。所以就有了“熱更新”方式的Admission機制嗡官,就是上面提到的動態(tài)Admission Control技術(小伙子箭窜,該去看代碼了= =)
具體到例子來分析,看看Istio的效果衍腥。
受限磺樱,用戶有一個應用Pod的配置如下
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox
command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
Istio要做的事情,就是上面這個Pod被提交給Kubernetes之后婆咸,在對應的API對象中加上Envoy容器的配置(這里就是無感了)坊罢,使API對象對應的yaml文件如下變成下面這樣
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox
command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
- name: envoy
image: lyft/envoy:845747b88f102c0fd262ab234308e9e22f693a1
command: ["/usr/local/bin/envoy"]
...
那么,對這個API對象無感的修改擅耽,是怎么通過Initializer來實現(xiàn)的呢活孩?
- Istio將這個Envoy容器本身的定義,以ConfigMap的方式乖仇,存在Etcd當中憾儒,這個ConfigMap(叫做envoy-initializer)的定義舉例如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: envoy-initializer
data:
config: |
containers:
- name: envoy
image: lyft/envoy:845747db88f102c0fd262ab234308e9e22f693a1
command: ["/usr/local/bin/envoy"]
args:
- "--concurrency 4"
- "--config-path /etc/envoy/envoy.json"
- "--mode serve"
ports:
- containerPort: 80
protocol: TCP
resources:
limits:
cpu: "1000m"
memory: "512Mi"
requests:
cpu: "100m"
memory: "64Mi"
volumeMounts:
- name: envoy-conf
mountPath: /etc/envoy
volumes:
- name: envoy-conf
configMap:
name: envoy
data的部分就是envoy容器的對應配置字段和volumes的配置。而Initializer的工作乃沙,就是把這個部分配置起趾,自動添加到用戶的POD API對象中。這就用到了Kubernetes的PATCH API警儒,而這種patch操作训裆,正是聲明式API的主要能力眶根。
- Istio把一個編寫好的Initializer作為一個Pod部署在Kubernetes集群中。如下:
apiVersion: v1
kind: Pod
metadata:
labels:
app: envoy-initializer
name: envoy-initializer
spec:
containers:
- name: envoy-initializer
image: envoy-initializer:0.0.1
imagePullPolicy: Always
這里envoy-initializer的使用的鏡像envoy-initializer:0.0.1就是一個自定義控制器(Custom Controller)边琉,是可以自己事先編寫好的属百。
而這個控制器同樣也是遵循控制器模型的,他的實際狀態(tài)是獲取到的用戶新建的Pod的配置情況变姨,期望狀態(tài)就是這個Pod里被加入了Envoy容器的定義族扰。偽代碼如下:
for {
// 獲取新創(chuàng)建的 Pod
pod := client.GetLatestPod()
// Diff 一下,檢查是否已經(jīng)初始化過
if !isInitialized(pod) {
// 沒有定欧?那就來初始化一下
doSomething(pod)
}
}
如果獲取到的Pod的API對象中渔呵,沒有Envoy的容器的定義,那么就開始進行doSomething的初始化砍鸠,具體做的事情就是前面提到過的扩氢,把Etcd中存儲的那個關于Envoy容器配置的ConfigMap對象加到一個空的Pod里面去,然后調用Kubernetes的API庫中TwoWayMerge
Patch方法爷辱,把用戶Pod對象和新Pod對象 merge录豺。
- 至此,Istio對通過聲明式API的Patch特性實現(xiàn)Dynamic Adminssion Control功能托嚣,進而支持Envoy容器在用戶和應用側無感的能力就實現(xiàn)了巩检。
2.4 Dynamic Admission Control的配置
Dynamic Admission Control,即Initializer的相關配置(注意示启,這里不是指的Initializer本身兢哭,而是Initializer的配置文件)。
我們可以通過配置這個配置對象夫嗓,來指明對什么類型的資源進行Initialize的操作迟螺。比如下面這段代碼,就是對全部的pods都做初始化操作舍咖。
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: InitializerConfiguration
metadata:
name: envoy-config
initializers:
// 這個名字必須至少包括兩個 "."
- name: envoy.initializer.kubernetes.io
rules:
- apiGroups:
- "" // 前面說過矩父, "" 就是 core API Group 的意思
apiVersions:
- v1
resources:
- pods
同時,只要這個對象一杯kubectl apply/create出來之后排霉,Kubernetes就會把這個Initializer的名字(envoy.initializer)打在所有新創(chuàng)建Pod的metada上窍株,如下:
apiVersion: v1
kind: Pod
metadata:
initializers:
pending:
- name: envoy.initializer.kubernetes.io
name: myapp-pod
labels:
app: myapp
...
這個字段,就是在Initializer的控制器模型中攻柠,根據(jù)什么區(qū)判斷有沒有被初始化過的依據(jù)球订,換言之,每當我們自定義的Initialzer在做完Initialize的操作之后瑰钮,要把metadata.initializers.pending標識刪除掉C疤病!浪谴!
除了Kubernetes在我們創(chuàng)建出來kind是InitializerConfiguration的對象后开睡,會自動給所有新建的Pod都打這個標簽之外因苹,如果我們沒有新建這個InitializerConfiguration對象,但是又想讓我的某個Pod去使用某個Initializer的話篇恒,這么干:給對應Pod打metadata.annotations字段實例如下:
apiVersion: v1
kind: Pod
metadata
annotations:
"initializer.kubernetes.io/envoy": "true"
...
而Istio項目的核心扶檐,就是用無數(shù)個運行在應用Pod中的Envoy容器組成的服務代理網(wǎng)格。這也是Service Mesh的含義婚度。
總地來說蘸秘,從上到下官卡,灰度升級的切流場景->(Service Mesh)->Envoy們組成的服務代理網(wǎng)格->Envoy的實現(xiàn)->Kubernetes通過聲明式API支持的Patch修改Pod的能力->聲明式API好哇蝗茁,聲明式API棒哇(少林功夫好哇,好寻咒!少林功夫棒哇哮翘,棒!我系鐵頭功毛秘,無敵鐵頭功....喂饭寺,跑題了,回來了=行)
3. 小結
聲明式API的獨特之處:
- 聲明式API赌结,就是說我們來提供一個好的API對象進行聲明绞佩,我們期望的狀態(tài)是啥樣子的
- 其次,聲明式API允許多個API對象寫端,并在Kubernetes中支持以Patch的方式拿穴,對API對象進行修改以達到最終期望的實際狀態(tài),而無需關心最初的YAML文件的內容
- 基于以上兩個能力特性(提供API明確的API對象來聲明最終的期望狀態(tài)+支持多個API對象聲明并以PATCH的能力使控制器模型拿到的是最終的期望狀態(tài))案训,Kubernetes能在無需外部干預的情況下凡傅,只要你給我明確的若干個API對象,我就能夠給你無感調諧到你最終期望的狀態(tài)
而在上面我們提到的Initializer的實現(xiàn)時瓶蚂,最核心的糖埋,還是Initializer里面那個image,自定義編程的編寫過程窃这,這是遵循“Kubernetes范式編程”瞳别,即
如何使用控制器模式,同 Kubernetes 里 API 對象的“增杭攻、刪祟敛、改、查”進行協(xié)作朴上,進而完成用戶業(yè)務邏輯的編寫過程垒棋。
所以,之后要學習的一個核心痪宰,就是如何通過“Kuberbetes范式編程”完成使用Kubernetes部署代碼的Kubernetes用戶叼架,到使用Kubernetes編寫代碼的Kubernetes玩家的晉級之路畔裕。