命令式 vs 声明式

命令式

1
2
# kubectl create -f nginx.yaml
# kubectl replace -f nginx.yaml

声明式

1
# kubectl apply -f nginx.yaml
  1. 所谓声明式,即只需要提交一个定义好的API对象声明所期待的状态即可
  2. 声明式API允许多个API写端,以PATCH的方式对API对象进行修改,而无需关心本地原始YAML文件的内容
  • 声明式API最主要的能力:_PATCH API_
  1. 声明式API是Kubernetes编排能力赖以生存的核心所在

本质区别

kubectl replace kubectl apply
执行过程 使用新API对象替换旧API对象 执行对旧API对象的PATCH操作
类似:kubectl set image、kubectl edit
kube-apiserver 一次只能处理一个写请求,否则可能产生冲突 一次能处理多个写请求,具备Merge能力

Kubernetes编程范式

使用控制器模式,与Kubernetes里API对象的『增、删、改、查』进行协作,进而完成用户业务逻辑的编写

Istio

Istio是基于Kubernetes微服务治理框架

架构

  1. Envoy是一个高性能的C++网络代理,以Sidecar容器的方式运行在每个被治理的应用Pod中
  2. Pod里所有的容器都共享同一个Network Namespace
    • Envoy容器可以通过配置Pod里的iptables规则,接管整个Pod的进出流量
  3. Control Plane里的Pilot组件,能够通过调用每个Envoy容器的API来对Envoy代理进行配置,从而实现微服务治理
  4. 对Envoy容器的部署对Envoy代理的配置,对用户和应用来说是透明的 – 借助于Kubernetes的Dynamic Admission Control

Initializer – Dynamic Admission Control

Admission编译进APIServer):当一个API对象被提交给APIServer后,被Kubernetes正式处理前的一些初始化工作

myapp-pod.yaml

Pod里目前只有一个用户容器(myapp-container

myapp-pod.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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的任务:myapp-pod.yaml被提交给Kubernetes后,对应的API对象里会自动加上Envoy容器的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
...

envoy-initializer – 容器定义

  1. 首先,将Envoy容器本身的定义,以ConfigMap的方式保存在Kubernetes中
  2. ConfigMap的data字段是一个Pod对象的一部分定义
  3. Initializer控制器的任务
    • 把Envoy相关的字段自动添加到用户提交的Pod的API对象里
    • 但用户提交的Pod里本来就有containers字段和volumes字段 – PATCH API声明式API最主要的能力
envoy-initializer.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

envoy-initializer:0.0.1 – 控制器

  1. envoy-initializer:0.0.1自定义控制器
  2. 控制循环:不断获取实际状态,然后与期望状态对比,以此作为依据来决定下一步的操作
  3. Initializer控制器:实际状态(用户新创建的Pod),期望状态(该Pod里被添加了Envoy容器的定义
1
2
3
4
5
6
7
8
9
for {
// 获取新创建的Pod
pod := client.GetLatestPod()
// 该Pod里是否已经添加过Envoy容器
if !isInitialized(pod) {
// 还没添加Envoy容器,则修改该Pod的API对象,往Pod里合并envoy-initializer这个ConfigMap的data字段
doSomething(pod)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
func doSomething(pod) {
cm := client.Get(ConfigMap, "envoy-initializer")

newPod := Pod{}
newPod.Spec.Containers = cm.Containers
newPod.Spec.Volumes = cm.Volumes

// 生成patch数据
patchBytes := strategicpatch.CreateTwoWayMergePatch(pod, newPod)

// 发起PATCH请求,修改这个pod对象
client.Patch(pod.Name, patchBytes)
}

将Initializer作为Pod部署

envoy-initializer-pod.yaml
1
2
3
4
5
6
7
8
9
10
11
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

配置需要进行Initialize的资源

envoy-config

Kubernetes要对所有的Pod进行Initialize操作,名称为envoy-initializer(envoy.initializer.kubernetes.io)

envoy-config.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: admissionregistration.k8s.io/v1alpha1
kind: InitializerConfiguration
metadata:
name: envoy-config
initializers:
- name: envoy.initializer.kubernetes.io # name必须至少包含2个'.'
rules:
- apiGroups:
- '' # Core Api Group
apiVersions:
- v1
resources:
- pods

metadata.initializers

  1. 一旦该InitializerConfiguration被创建,所有新创建的Podmetadata.initializers.pending上会出现该Initializer的名字
  2. 该Initializer会借助Pod的metadata.initializers.pending来判断该Pod有没有执行过自己所负责的初始化操作(isInitialized()
  3. 当Initializer完成了初始化操作后,要负责清除metadata.initializers.pending标志
1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: Pod
metadata:
initializers:
pending:
- name: envoy.initializer.kubernetes.io
name: myapp-pod
labels:
app: myapp
...

metadata.annotations

在Pod的Annotation声明使用了某个Initializer,如使用了envoy-initializer

1
2
3
4
5
6
apiVersion: v1
kind: Pod
metadata
annotations:
"initializer.kubernetes.io/envoy": "true"
...

参考资料

  1. 深入剖析Kubernetes