我们分享了很多Kuberenets的API对象,这些API对象,有的用来描述应用,有的为K8S本身提供服务,但是,无一例外,都需要编写一个对应的YAML文件来交给Kubernetes

是不是,使用这个YAML文件,就是声明式API了呢?

举个例子,Docker Swarm的编排操作是基于命令行的

$ docker service create –name nginx –replicas 2  nginx

$ docker service update –image nginx:1.7.9 nginx

那么这两个命令,利用Swarm启动了两个Nginx容器,然后在进行滚动更新了他们

这种使用方式,称为命令式命令行操作

如果是上面的操作在K8S中,应该如何实现呢?

说到底还是需要Deployment来控制管理YAML文件

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

这样,两个Nginx的Pod就会运行了,如果需要修改这个Pod的Nginx,那么怎么办,可以使用kubectl set image和kubectl edit命令,来直接修改kubernetes的API对象

或者直接修改本地文件并且进行重新的创建

我们将接下来的YAML文件中的镜像改为1.7.9

spec:

containers:

– name: nginx

image: nginx:1.7.9

然后执行一句kubectl replace 操作,来完成Deployment 的更新

这种基于YAML的操作方式,是声明式API吗,并不是

基于kubectl create kubectl replace的操作,其实本质上应该成为命令式配置文件操作

这种操作的方式,和前面的Docker Swarm的命令,没啥本质上的区别,只不过将命令的参数,写在了配置文件中

那么,什么是声明式API?

kubectl apply,应该算是一个,这个命令是我们推荐使用来替代kubectl create命令的

那么,我们创建了一个Deployment

kubectl apply -f nginx.yaml

然后修改一下nginx.yaml中的定义的镜像

还是可以使用kubectl apply -f nginx.yaml

这样和之前的replace的区别,是在于replace是使用新的YAML文件的API对象进行替换了原有的API对象,而kubectl apply,则是执行了一个对原有API对象的PATCH的操作

进一步的,就意味着kube-apiserver在响应命令式请求的时候,一次只能处理一个写请求,不然会有产生冲突的可能性,对于声明式请求,可以处理多个写操作,并且具有Merge的能力

接下来我们看一下Istio项目中使用的声明式API在实际使用中的重要意义

Istio开源项目的诞生,掀起了一个名为微服务的浪潮,将Service Mesh这个新的编排概念推到风口浪尖

图片

上面是关于Istio的整体架构,Istio最根本的组件,是运行在每一个Pod中的Envoy的容器

在Istio项目,将这个代理服务以sidecar容器的方式,放在了每一个被治理的Pod中,在Pod中,所有的容器都共享一个Network Namespace,所以Envoy容器还能够通过配置Pod的iptabls规则,将整个Pod的进出入流量进行接管

这时候,Istio的控制层里的Pilot组件,可以通过调用每个Envoy容器的API,对这个Envoy代理进行配置,实现微服务治理

假设,这个Istio架构图左边的Pod是已经在运行的应用,右边的Pod是新上线的版本,那么可以通过调节这两个Pod的Envoy容器的配置,先将10%的流量分给新版本,然后逐渐增大

然后,在整个未付治理的过程,无论是对Envoy容器的部署,还是上面的代理,用户和应用都是无感的

那么Istio是如何提前安装Envoy容器,做到无感呢?

Istio项目使用的,是Kubernetes中一个非常重要的功能,叫做Dynamic Admission Control

在Kubernetes项目中,有一个Pod或者任何一个API对象被提交给APIServer之后,都需要做一些初始化的工作,再其正式开始使用前进行,比如,自动为所有的Pod加上某些Labels

这些初始化的操作实现,使用的是一个Admission功能,其实是Kubernetes项目中一组被称为Admission Controller的代码,可以选择性的被编译到ApiServer中,在API对象被创建后会立刻被调用到

但是,这样,说明,如果需要自己添加一些新的规则进入Admission Controller的话,会比较困难,需要重新编译并且重启APIServer,那么,Kubernetes提供了一种热插拔的Admission的机制,就是Dynamic Admission Control,或称Initializer

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’]

比如,我们就是要在这个Pod Yaml被提交给kubernetes之后,在对应的API对象上自动加上Envoy容器的配置,使其变成下面的样子

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的容器呢?

Istio是如何在用户完全不知情的前提下注入的呢?

Istio会将Envoy容器本身的定义,以configMap的方式保存在Kuberentes当中,例如下面

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中定义了一个Pod对象的定义,

那么Initializer要做的工作,就是讲这部分Envoy相关的字段,添加到用户提交的Pod的API对象,用户提交的Pod的本来就有containers字段和volumes字段,所以需要考虑要有类似git merge这样的操作,将两部分合并在一起

Initializer在更新用户的Pod对象时候,必须要使用PATCH API来完成,这种PATCH API,正是声明式API的主要能力

接下来,Istio将一个编写好的Initialzer,作为一个Pod部署在Kubernetes中,这个Pod的定义很简单,

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使用的镜像,是一个定义好的自定义控制器,这个控制器的功能和标准控制器类似

就是一个死循环,不断的获取实际状态,和期望状态进行对比,决定下一步的操作

Initializer的控制器,就是不断的获取到实际状态(用户自定义的Pod),和期望状态(是否添加了Envoy容器)进行对比

那么这一步的PATCH操作,利用的就是envoy-initializer中的ConfigMap中的数据

那如何判断是否需要进行PATCH操作呢?

我们先进行一个Initialize的配置,将所有需要匹配的对象,进行关联

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

这个配置,意味着Kubernetes要对所有的Pod进行匹配,而且指定了负责的操作的Initializer,名为envoy-initializer

这个InitializerConfiguration被应用,那所有新创建的Pod的Metadata字段,会被加上这个Initializer的名字

apiVersion: v1

kind: Pod

metadata:

initializers:

pending:

– name: envoy.initializer.kubernetes.io

name: myapp-pod

labels:

app: myapp

每一个新创建的Pod的自动携带的metadata.initializers.pending的Metadata信息

这个Metadata信息,就是Initializer判断Pod是否有执行过自己负责的初始化的依据

这也是Initializer做完了要做的操作后,要将这个pending的标志清除掉,这一点,要在编写Initializer代码要非常的注意

除了上面的配置方式,还可以再具体的Pod的Annotation中添加一个表明自己要用什么Initializer的字段

比如下面

apiVersion: v1

kind: Pod

metadata

annotations:

“initializer.kubernetes.io/envoy”: “true”

就是在annotations中声明使用了initializer.kubernetes.io/envoy的initializer

而在Patch的过程中

我们需要先从ConfigMap中拿到这个Pod的定义,然后将这个ConfigMap中存储的containers和volumes字段添加到一个新的Pod对象中

然后我们利用Kubernetes的API苦,来讲两个Pod进行合并,生成一个TwoWayMergePatch,有了这个Patch,就可以调用kubernetes的Client发起一个PATCH请求,这样这个用户提交的Pod,就会被加上Envoy的字段

这样,就是Istio项目的组成,由无数个运行在Pod中Envoy容器组成的服务代理网格,也是Service Mesh的定义

而这个操作的实现原理,就是Kubernetes声明式API的独特之处

我们可以提交一个定义好的API来声明,期望状态

而这个API,可以有多个写段,以PATCH的方式对API对象进行修改,无需关心本地的YAML文件的内容

最重要的是,有了上面的能力,可以进行API的增删改查

这也是Kubernetes项目编排能力赖以生存的核心所在

而整个Istio项目部署完成,大约会创建43个API对象,而整体部署,都依赖了Kubernetes的声明式API

而Initializer的使用过程汇总,主要是对于Initializer自定义控制器的编写过程,遵循的正是Kuberenetes的编程范式,即如何使用控制器模式,通过Kubernetes中API对象的增删改查进行写作,完成用户的业务逻辑

本章中,我们重点讲解了Kubernetes声明式API的问题,并通过对Istio项目的解析,说明了Kuberenetes中的Initializer的特性,完成了Envoy容器自动注入的原理

这样,就是从Kubernetes部署代码,到使用Kuberenets编写代码的一个蜕变之路

那么,本章思考一下,为何Envoy能击败Nginx和HAProxy,称为Service Mesh体系的核心

因为基于声明式API吧,暴露API方便二次开发和调试

发表评论

邮箱地址不会被公开。 必填项已用*标注