什么是声明式API呢?

  答案是,kubectl apply命令。

举个栗子

  在本地编写一个Deployment的YAML文件:

apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas:
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort:
###然后用kubectl apply创建这个Deployment
$ kubectl apply -f nginx.yaml ### 倘若修改一下nginx里定义的镜像
...
spec:
containers:
- name: nginx
image: nginx:1.7. ###可以继续执行一条kubectl apply命令,触发滚动更新
$ kubectl apply -f nginx.yaml

  kubectl apply执行了一个对原有API对象的PATCH(补丁)操作。而kubectl replace的执行过程,是使用新的YAML文件中的API对象,替换原来的API对象。

  这意味着kube-apiserver在响应命令式请求(kubectl replace)的时候,一次只能处理一个写请求,否则会有产生冲突的可能。而对于声明式请求(kubectl apply),一次能处理多个写操作,并且具备Merge能力。

那声明式API在实际使用中有何重要意义呢?举起第二个栗子

  Istio是一个基于Kubernetes项目的微服务治理框架。它最根本的组件是允许在每一个应用Pod里的Envoy容器,Envoy是一个高性能的C++网络代理,Istio把这个代理服务以sidecar容器的方式,运行在了每一个被治理的应用Pod中,因Pod里的所有容器共享同一个Network Namespace,所以Envoy容器就能够通过配置Pod里的iptables规则,把整个Pod的进出流量接管下来。这时候Istio的控制层里的Pilot组件就能够通过调用每个Envoy容器的API,对这个Envoy代理进行配置,从而实现微服务治理。

  更重要的是,在整个微服务治理过程中,无论是对Envoy容器的部署,还是对Envoy代理的配置,用户和应用都是无感的。

  那istio明明需要在每个Pod里安装一个Envoy容器,又怎么能做到无感呢?  

  实际上,istio项目使用的是,Kubernetes中一个非常重要的功能,叫做Dynamic Admission Control。

  在Kubernetes中,当一个Pod或者任何一个API对象被提交给APIServer之后,总有一些初始化性质的工作需要在被Kubernetes项目正式处理之间进行(如自动为所有Pod加上某些标签),而这个初始化操作的实现,借助的是一个叫做Admission的功能,它其实是接祖Kubernetes项目里一组被称为Admission Controller的代码,可以选择性地被编译进APIServer中,在API对象创建之后会被立刻调用到。但这就意味着如果现在想要添加一些自己的规则到Admission Controller会比较困难,因为这要求重新编译并重启APIServer。显然这个对istio影响太大。

  因此Kubernetes提供了一种热插拔的Admission机制,它就是Dynamic Admission Control,也叫做Initializer。

  那加入有这么一个应用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']

  可以看到,这个Pod里面只有一个用户容器:myapp-container

  istio要做的就是在这个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做的就是编写一个用来为Pod自动注入Envoy容器的Initializer。

  首先Istio会将这个Envoy容器本身的定义,以ConfigMap的方式保存在Kubernetes中

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:
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

  这个ConfigMap的data部分,正是一个Pod对象的一部分定义,其中可以看到Envoy容器对应的Container字段,以及一个用来声明Envoy配置文件的volumes字段。Initializer要做的就是把这部分Envoy相关的字段,自动添加到用户提交的Pod的API对象里。但是用户提交的Pod里本来就有containers和volumes字段,所以Kubernetes在处理这样的更新请求时,就必须使用类似于git merge这样的操作,才能将这两部分内容合并在一起。即Initializer更新用户的Pod对象时,必须使用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.
imagePullPolicy: Always

  envoy-initializer:0.0.1镜像时一个自定义控制器(Custom Controller)。Kubernetes的控制器实际上是一个死循环:它不断地获取实际状态,然后与期望状态作对比,并以此为依据决定下一步的操作。

  对Initializer控制器,不断获取的实际状态,就是用户新创建的Pod,它期望的状态就是这个Pod里被添加了Envoy容器的定义。它的控制逻辑如下:

for {
// 获取新创建的 Pod
pod := client.GetLatestPod()
// Diff 一下,检查是否已经初始化过
if !isInitialized(pod) {
// 没有?那就来初始化一下
//istio要往这个Pod里合并的字段,就是ConfigMap里data字段的值
doSomething(pod)
}
} func doSomething(pod) {
//调用APIServer拿到ConfigMap
cm := client.Get(ConfigMap, "envoy-initializer") //把ConfigMap里存在的containers和volumes字段,直接添加进一个空的Pod对象
newPod := Pod{}
newPod.Spec.Containers = cm.Containers
newPod.Spec.Volumes = cm.Volumes // Kubernetes的API库,提供一个方法使我们可以直接使用新旧两个Pod对象,生成 patch 数据
patchBytes := strategicpatch.CreateTwoWayMergePatch(pod, newPod) // 发起 PATCH 请求,修改这个 pod 对象
client.Patch(pod.Name, patchBytes)
}

  所以Envoy机制得以实现正是借助了Kubernetes能够对API对象进行在线更新的能力,这也是Kubernetes声明式API的独特之处

    • 声明式:指只需要提交一个定义好的API对象来声明期望的状态是什么样子的
    • 声明式API允许有多个API写短,以PATCH的方式对API对象进行修改,无需关心本地原始YAML文件的内容
    • 有了上述两个能力,Kubernetes才可以基于对API对象的增删改查,在完全无需外界干预的情况下,完成对实际状态和期望状态的调谐过程。

  声明式API才算Kubernetes项目编排能力赖以生存的核心所在

  Kubernetes编程范式即:如何使用控制器模式,同Kubernetes里的API对象的“增、删、改、查”进行协作,进而完成用户业务逻辑的编写过程

【Kubernetes】声明式API与Kubernetes编程范式的更多相关文章

  1. Kubernetes声明式API与编程范式

    声明式API vs 命令时API 计算机系统是分层的,也就是下层做一些支持的工作,暴露接口给上层用.注意:语言的本质是一种接口. 计算机的最下层是CPU指令,其本质就是用"变量定义+顺序执行 ...

  2. 【Kubernetes】深入解析声明式API

    在Kubernetes中,一个API对象在Etcd里的完整资源路径,是由:Group(API组).Version(API版本)和Resource(API资源类型)三个部分组成的. 通过这样的结构,整个 ...

  3. 声明式API replica controller vs replica set 对比

    1.在命令式API中,你可以直接发出服务器要执行的命令,例如: “运行容器”.“停止容器”等. 在声明性API中,你声明系统要执行的操作,系统将不断向该状态驱动. 可以想象成手动驾驶和自动驾驶系统.( ...

  4. Kubernetes探索学习004--深入Kubernetes的Pod

    深入研究学习Pod 首先需要认识到Pod才是Kubernetes项目中最小的编排单位原子单位,凡是涉及到调度,网络,存储层面的,基本上都是Pod级别的!官方是用这样的语言来描述的: A Pod is ...

  5. SpringCloud学习笔记:声明式调用Feign(4)

    1. Feign简介 Feign采用声明式API接口的风格,将Java HTTP客户端绑定到它的内部. Feign的首要目标是简化Java HTTP客户端调用过程. 2.Feign客户端示例 Feig ...

  6. spring boot 2.0.3+spring cloud (Finchley)3、声明式调用Feign

    Feign受Retrofix.JAXRS-2.0和WebSocket影响,采用了声明式API接口的风格,将Java Http客户端绑定到他的内部.Feign的首要目标是将Java Http客户端调用过 ...

  7. React 核心思想之声明式渲染

    React 发展很快,概念也多,本文目的在于帮助初学者理清 React 核心概念. React 及 React 生态 React 的核心概念只有 2 点: 声明式渲染(Declarative) 基于组 ...

  8. spring cloud深入学习(四)-----eureka源码解析、ribbon解析、声明式调用feign

    基本概念 1.Registe 一一服务注册当eureka Client向Eureka Server注册时,Eureka Client提供自身的元数据,比如IP地址.端口.运行状况指标的Uri.主页地址 ...

  9. SpringCloud 源码系列(6)—— 声明式服务调用 Feign

    SpringCloud 源码系列(1)-- 注册中心 Eureka(上) SpringCloud 源码系列(2)-- 注册中心 Eureka(中) SpringCloud 源码系列(3)-- 注册中心 ...

随机推荐

  1. git命令收集

    $ git clone ... $ git status 查看状态 $ git commit -am "XXX" 提交信息 $ git commit -am "XXXX& ...

  2. this详解,对执行上下文说 Yes

    this 指向多变,很多隐蔽的 bug 都缘于它.与此同时,this 强大灵活,如果能熟练驾驭,就会写出更简洁.优雅的代码. 社区上对于 this 的讲解虽然不少,但缺乏统一梳理. this 相关知识 ...

  3. MoveWindow和SetWindowPos

    SetWindowPos即使里面使用的是一样的矩形参数,有时候SetWindowPos还是会改变窗口的大小,真是坑爹!!! MoveWindow就不会改变 mark一下

  4. 绘制surfaceView 基础类

    public class SurfaceViewTempalte extends SurfaceView implements Callback, Runnable { private Surface ...

  5. C# 操作字符串,在某些特定的字符后面或前面添加其它字符

    C# 操作字符串,在某些特定的字符后面或前面添加其它字符,解决方法: 字符串替换或正则表达式替换即可. 示例:实现的是在每个“第”前面添加一个逗号,在每个“方案”后面添加一个冒号. string s ...

  6. X11/extensions/XShm.h: No such file or directory

    CentOS 编译一些开源项目提示:X11/extensions/XShm.h: No such file or directory. 运行命令:yum install libXext-devel就可 ...

  7. Java代理设计模式(Proxy)的四种具体实现:静态代理和动态代理

    面试问题:Java里的代理设计模式(Proxy Design Pattern)一共有几种实现方式?这个题目很像孔乙己问"茴香豆的茴字有哪几种写法?" 所谓代理模式,是指客户端(Cl ...

  8. 1653: Champion of the Swordsmanship

    1653: Champion of the Swordsmanship Time Limit: 1 Sec  Memory Limit: 64 MBSubmit: 11  Solved: 8[Subm ...

  9. softmax_loss的归一化问题

    cnn网络中,网络更新一次参数是根据loss反向传播来,这个loss是一个batch_size的图像前向传播得到的loss和除以batch_size大小得到的平均loss. softmax_loss前 ...

  10. 参考别人的代码写的aes加密,记录一下(AES,ECB模式,填充PKCS5Padding,数据块128位,偏移量无,以hex16进制输出)

    package org.jimmy.autosearch2019.test; import java.security.SecureRandom; import javax.crypto.Cipher ...