详解K8S--声明式API

23-0-声明式API

这些API对象,有的描述应用,有的为应用提供服务。但为使用这些API对象提供的能力,都要编写对应YAML文件交给k8s。

这YAML正是k8s声明式API必备要素。

1 用YAML代替命令行操作,就是声明式API?

Swarm编排操作都是命令行操作:

# 创建两个容器
$ docker service create --name nginx --replicas 2  nginx
# 把它们“滚动更新”为一个新镜像
$ docker service update --image nginx:1.7.9 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在k8s创建这Deployment对象:

$ kubectl create -f nginx.yaml

更新这两个Pod使用的Nginx镜像,可用kubectl set image、kubectl edit直接修改k8s里的API对象。

Q:能否通过修改本地YAML完成操作?这样我的改动就会体现在本地YAML。

A:可!可修改这YAML里的Pod模板部分,把Nginx容器的镜像改成1.7.9:

...
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9

执行kubectl replace完成这Deployment的更新:

$ kubectl replace -f nginx.yaml

Q:这种基于YAML的操作方式,是“声明式API”吗?

A:不是。

这种先kubectl create,再replace的操作称为命令式配置文件操作。和前面Docker Swarm那两句命令,无本质区别。只是把Docker命令行的参数写在配置文件。

啥才是“声明式API”

kubectl apply命令。

kubectl apply创建Deployment:

$ kubectl apply -f nginx.yaml

Nginx的Deployment就被创建,看起来跟kubectl create效果一样。

再修改nginx.yaml里定义的镜像:

...
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9

修改完这YAML后,不用kubectl replace更新,而是继续执行kubectl apply:

$ kubectl apply -f nginx.yaml

k8s会立即触发这Deployment的“滚动更新”。

kubectl apply V.S kubectl replace

  • kubectl replace执行过程,是用新YAML中的API对象, 替换原有API对象
  • kubectl apply,执行一个 对原有API对象的PATCH操作

类似地,kubectl set image和kubectl edit也是对已有API对象的修改。

即kube-apiserver在响应:

  • 命令式请求(如kubectl replace)时,一次只能处理一个写请求,否则会有冲突可能
  • 而对声明式请求(比如,kubectl apply), 一次能处理多个写操作,并具备Merge能力

正因要照顾到这样的API设计,做同样一件事,k8s需要步骤比其他项目多。

声明式API的重要意义

2017年5月,Google、IBM和Lyft公司共同宣布Istio诞生,掀起“微服务”热潮,把Service Mesh新编排的概念推到风口浪尖。

Istio就是基于k8s的微服务治理框架:

alt

Istio最根本组件,是运行在每个应用Pod里的Envoy容器:Lyft推出的高性能C++网络代理,也是L对Istio项目唯一贡献。

而Istio则把这代理服务以sidecar容器方式,运行在每个被治理的应用Pod。Pod里的所有容器共享同一Network Namespace。所以,Envoy就能通过配置Pod里的iptables规则,把整个Pod的进出流量接管。

这时,Istio的控制层(Control Plane)里的Pilot组件,就能够通过调用每个Envoy容器的API,对这Envoy代理进行配置,从而实现微服务治理。

灰度发布

假设这Istio架构图左边Pod是已运行应用,右边Pod是刚上线的应用新版本。这时,Pilot通过调节这两Pod里的Envoy容器的配置,将90%流量分配给旧版本应用,10%分给新版本应用,且后续还能随时调整。如Istio可调节这流量从90%-10%,改到80%-20%,再到50%-50%,最后到0%-100%,完成灰度发布。

整个微服务治理过程,无论对Envoy容器的部署,还是像上面对Envoy代理的配置,用户和应用完全“无感”。

Istio项目需要在每个Pod安装一个Envoy容器,又咋做到“无感”?Istio利用k8s重要功能 - Dynamic Admission Control。

k8s中,当某Pod或任一API对象被提交给APIServer后,总有一些“初始化”性质的工作需要在它们被k8s正式处理前进行。如自动为所有Pod加标签(Labels)。

而这“初始化”操作的实现,借助Admission,是k8s里一组Admission Controller代码,可选择性编译进APIServer,在API对象创建后会被立刻调用。

但这意味着,若你想添加一些自定义规则到Admission Controller,得重新编译并重启APIServer,对Istio影响太大。

所以,k8s额外提供“热插拔”式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 k8s! && sleep 3600']

这Pod里只有一个用户容器:myapp-container。

Istio就是在这Pod YAML被提交给k8s后,在它对应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 k8s! && sleep 3600']
  - name: envoy
    image: lyft/envoy:845747b88f102c0fd262ab234308e9e22f693a1
    command: ["/usr/local/bin/envoy"]
    ...

被Istio处理后的Pod,除用户自定义的myapp-container容器,多个envoy容器,即Istio要使用的Envoy代理。

Istio咋在用户不知情完成这操作?

Istio要编写个为Pod“自动注入”Envoy容器的Initializer。

Istio会将这Envoy容器本身的定义,以ConfigMap保存在k8s。这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

这ConfigMap的data正是Pod对象的一部分定义。Envoy容器对应的containers字段及一个用来声明Envoy配置文件的volumes字段。

Initializer

就是把这部分Envoy相关字段,自动添加到用户提交的Pod的API对象。可用户提交的Pod里本就有containers字段和volumes字段,所以k8s在处理这样的更新请求时,须使用类似git merge的操作,才能将这两部分内容合并。

所以,Initializer更新用户的Pod对象时,须用PATCH API完成,这正是声明式API最主要能力。

接下来,Istio将编写好的Initializer,作为一个Pod部署在k8s。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使用envoy-initializer:0.0.1镜像,即事先编写好的“自定义控制器”(Custom Controller)。

控制器的主要功能

一个k8s的控制器,就是个“死循环”:不断获取“实际状态”,然后与“期望状态”对比,并以此为依据决定下一步。

而Initializer的控制器,不断获取到的“实际状态”,就是用户新创建的Pod。而它的“期望状态”,则是:这个Pod里被添加了Envoy容器的定义。

Go伪代码描述控制逻辑:

for {
  // 获取新建的Pod
  pod := client.GetLatestPod()
  // Diff一下,检查是否已初始化
  if !isInitialized(pod) {
    // 没有?那就初始化
    doSomething(pod)
  }
}
  • 若这Pod里已添加过Envoy容器,就“放过”这Pod,进入下个检查周期
  • 若还没添加过Envoy容器,就要进行Initialize操作,修改该Pod的API对象(doSomething函数)

Istio要往这Pod里合并的字段,正是之前保存在envoy-initializer这个ConfigMap里的数据(data字段值)。

所以,Initializer控制器的工作逻辑里,先从APIServer中拿到这ConfigMap:

func doSomething(pod) {
  cm := client.Get(ConfigMap, "envoy-initializer")
}

然后,把这ConfigMap里存储的containers和volumes字段,直接加进一个空Pod对象:

func doSomething(pod) {
  cm := client.Get(ConfigMap, "envoy-initializer")
  
  newPod := Pod{}
  newPod.Spec.Containers = cm.Containers
  newPod.Spec.Volumes = cm.Volumes
}

k8s的API可直接使用新旧两个Pod对象,生成一个TwoWayMergePatch:

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)
}

有这TwoWayMergePatch后,Initializer代码就可使用这patch的数据,调用k8s的Client,发起一个PATCH请求。

这样,一个用户提交的Pod对象里,就会被自动加上Envoy容器相关字段。

k8s还允许你通过配置指定要对什么样的资源进行这Initialize操作:

apiVersion: admissionregistration.k8s.io/v1alpha1
kind: InitializerConfiguration
metadata:
  name: envoy-config
initializers:
  // 这名字须至少包括两个 "."
  - name: envoy.initializer.k8s.io
    rules:
      - apiGroups:
          - "" // ""就是core API Group
        apiVersions:
          - v1
        resources:
          - pods

这配置意味着k8s要对所有Pod进行这Initialize操作,且指定负责这操作的Initializer:envoy-initializer。

一旦这InitializerConfiguration被创建,k8s就会把这Initializer的名字,加在所有新创建的Pod的Metadata:

apiVersion: v1
kind: Pod
metadata:
  initializers:
    pending:
      - name: envoy.initializer.k8s.io
  name: myapp-pod
  labels:
    app: myapp
...

每个新创建的Pod,都会自动携带metadata.initializers.pending的Metadata信息。

这Metadata就是Initializer的控制器判断这Pod有无执行过自己所负责的初始化操作的重要依据(isInitialized()方法)。

即当你在Initializer里完成要做的操作后,记得将这metadata.initializers.pending标志清除。编写Initializer代码时要注意。

还可在具体Pod的Annotation里添加如下字段,声明要使用某Initializer:

apiVersion: v1
kind: Pod
metadata
  annotations:
    "initializer.k8s.io/envoy": "true"
    ...

这就会使用前面定义的envoy-initializer。

小结

Istio核心就是由无数个运行在应用Pod中的Envoy容器组成的服务代理网格。正是Service Mesh含义。

GitHub链接。

这机制得以实现的原理,正是借助k8s能对API对象进行在线更新,这也是k8s“声明式API”独特之处:

  • 首先,所谓“声明式”,指的就是我只需要提交一个定义好的API对象来“声明”,我所期望的状态是什么样子。
  • 其次,“声明式API”允许有多个API写端,以PATCH的方式对API对象进行修改,而无需关心本地原始YAML文件的内容。
  • 最后,也是最重要的,有了上述两个能力,k8s项目才可以基于对API对象的增、删、改、查,在完全无需外界干预的情况下,完成对“实际状态”和“期望状态”的调谐(Reconcile)过程。

所以说,声明式API,才是k8s项目编排能力“赖以生存”的核心所在,希望你能够认真理解。

无论是对sidecar容器的巧妙设计,还是对Initializer的合理利用,Istio设计与实现都依托于k8s的声明式API和它所提供的各种编排能力。Istio是在k8s项目使用上的一位“集大成者”。

一个Istio项目部署完成后,会在k8s里创建约43个API对象。

Istio多火热,就说明k8s这套“声明式API”多成功。这既是Google Cloud喜闻乐见的事情,也是Istio一出就被Google公司和整个技术圈儿热捧原因。

而在使用Initializer的流程中,最核心的步骤,莫过于Initializer“自定义控制器”的编写过程。它遵循的,正是标准的“k8s编程范式”,即:

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

这,也正是我要在后面文章中为你详细讲解的内容。

总结

讲解了k8s声明式API的含义。并且,通过对Istio项目的剖析,说明了它使用k8s的Initializer特性,完成Envoy容器“自动注入”的原理。

从“使用k8s部署代码”,到“使用k8s编写代码”的蜕变过程,正是你从一个k8s用户,到k8s玩家晋级之路。

尽管Istio项目一直宣称它可以运行在非k8s环境中,但我并不建议你花太多时间去做这个尝试。无论是从技术实现还是在社区运作上,Istio与k8s项目之间都唇齿相依。如脱离k8s,这条原本就不算平坦的“微服务”之路更困难。

获取更多干货内容,记得关注我哦。

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/57950.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何策划一场战争?

前言 这个世界并不和平,我们只是生活在一个和平的国家。克服恐惧,最好的办法就是面对它。本文结合作者对于《孙子兵法》和毛泽东军事思想的部分了解,介绍了一般战争的发起、过程和结束的情况。 零、战争定义 首先要说明战争的定义是什么。《…

【Windows】X-DOC:无需NAS使用Windows也能安装Jellyfin玩私人影音媒体平台

【Windows】X-DOC:无需NAS使用Windows也能安装Jellyfin玩私人影音媒体平台 1、前言2、Jellyfin服务搭建2.1 Jellyfin简介2.2 Jellyfin下载2.3 Jellyfin安装2.4 Jellyfin设置2.5 Jellyfin使用 3、终端访问3.1 浏览器访问 4、内网穿透 1、前言 下载收藏高清电影、电视…

Rust的enum枚举的强大用法

在Rust中,enum(枚举)是一种非常强大的类型,它可以包含多个变体(variants),每个变体可以是不同的类型,包括复杂类型。这使得enum在Rust中不仅用于表示简单的状态或选项集合&#xff0…

jmeter基础01-2_环境准备-Mac系统安装jdk

Step1. 查看系统类型 方法:苹果菜单 - 关于本机,看到本机为M1芯片。(Mac系统芯片有M系列和Intel两种) Step2. 官网下载安装包 https://www.oracle.com/java/technologies/downloads/ 根据芯片类型,选择安装包进行下…

引起what(): basic_string::_M_replace_aux问题的一个原因以及解决方法

自己在做一个项目的时候,报了下面的这个问题: terminate called after throwing an instance of std::length_error what(): basic_string::_M_replace_aux 经过自己的研究,发现是在读取文件的时候没有加上错误判断。 通过网站直接访问一个…

论文阅读:Computational Long Exposure Mobile Photography (一)

这篇文章是谷歌发表在 2023 ACM transaction on Graphic 上的一篇文章,介绍如何在手机摄影中实现长曝光的一些拍摄效果。 Abstract 长曝光摄影能拍出令人惊叹的影像,用运动模糊来呈现场景中的移动元素。它通常有两种模式,分别产生前景模糊或…

【canal 中间件】canal 常见的启动方式

文章目录 一、安装 canal-admin1.1 拉取镜像1.2 启动 canal-admin 容器(使用脚本)1.2.1 下载脚本1.2.2 执行脚本1.2.3 初始化元数据库(可选) 1.3 启动 canal-admin 容器(直接使用 Docker 命令)1.3.1 启动容器1.3.2 查看启动日志 1.4 访问页面 二、 安装 canal-server2.1 拉取镜…

Python复习1:

一、数据类型 1.数字:int、float、bool 2.字符串:string 3.列表:list 4.集合:set 5.字典:dictionary 二、Test 1.print输出固定格式 num110 str1"hello world" #输出的固定格式 print("num1%d&…

不容错过的10个CSS与JS悬停效果,提升网站互动性

文章目录 前言正文1.悬停时照片效果2.快速强大的图像效果3.悬停标题滑出效果4.展示你的照片效果5.现实扭曲悬停效果6.分割图像悬停效果7.简约优雅图像效果8.动态图像效果9.大图像悬停画廊10.图像揭示效果 总结 前言 悬停效果是一种简单有效的网页互动方式,尤其在图…

微服务设计模式 — 补偿事务模式(Compensating Transaction Pattern)

微服务设计模式 — 补偿事务模式(Compensating Transaction Pattern) 定义 在云计算和分布式系统中,管理跨多个微服务或组件的事务一致性是一项极具挑战性的任务,补偿事务模式Compensating Transaction Pattern)是一种…

Java实战项目-基于SpringBoot+Vue的二手车交易系统的研究与实现

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…

[mysql]数据定义语言DDL和数据操作语言DCL

目录 前文提要 数据定义语言DDL 数据操作语言DML 数据控制语言DCL 基础知识: 标识符(命名规则): 数据定义语言DDL 创建和管理数据库.: 管理数据库 切换数据库 修改数据库 更改数据库字符集 删除数据库 如何创建表 方式1:”白手起家的方式”创建表 方式2:已经有…

webpack使用详解

摘要:webpack作为一款主流的构建工具,对比后来者Vite虽然存在一些缺点,例如启动慢,配置复杂等。在很多项目中使用依然基于webpack构建,有必要掌握其概念、构建流程和配置方法。 1 webpack概述 1.1 基本概念 webpack …

基于YOLO11/v10/v8/v5深度学习的维修工具检测识别系统设计与实现【python源码+Pyqt5界面+数据集+训练代码】

《博主简介》 小伙伴们好,我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源,可关注公-仲-hao:【阿旭算法与机器学习】,共同学习交流~ 👍感谢小伙伴们点赞、关注! 《------往期经典推…

Zypher Network:全栈式 Web3 游戏引擎,服务器抽象叙事的引领者

近期,《黑神话:悟空》的爆火不仅让 AAA 游戏重回焦点,也引发了玩家与开发者的热议。Web2 游戏的持续成功导致部分 Web3 玩家们的倒戈,对比之下 Web3 游戏存在生命周期短且商业模式难以明确的问题,尤其在当前加密市场环…

H7-TOOL自制Flash读写保护算法系列,为兆易创新GD32E23X制作使能和解除算法,支持在线烧录和脱机烧录使用(2024-10-29)

说明: 很多IC厂家仅发布了内部Flash算法文件,并没有提供读写保护算法文件,也就是选项字节算法文件,需要我们制作。 实际上当前已经发布的TOOL版本,已经自制很多了。但是依然有些厂家还没自制,所以陆续开始…

flutter 写个简单的界面

起因, 目的: 来源: 客户需求。 着急要,我随便写的,应付一下。 过程: 略,直接看代码,看注释。 代码 1 xxx import package:flutter/material.dart;void main() {runApp(const MyApp()); }// # class MyApp extends…

.NET 8 中 Entity Framework Core 的使用

本文代码:https://download.csdn.net/download/hefeng_aspnet/89935738 概述 Entity Framework Core (EF Core) 已成为 .NET 开发中数据访问的基石工具,为开发人员提供了强大而多功能的解决方案。随着 .NET 8 和 C# 10 中引入的改进,开发人…

推荐一款可视化和检查原始数据的工具:RawDigger

RawDigger是一款强大的工具,旨在可视化和检查相机记录的原始数据。它被称为一种“显微镜”,使用户能够深入分析原始图像数据,而不对其进行任何更改。RawDigger并不是一个原始转换器,而是一个帮助用户查看将由转换器使用的数据的工…

第三十三章 Vue路由进阶路由模块封装

目录 一、引言 二、完整代码 main.js index.js App.vue Find.vue My.vue 一、引言 在上一个章节中,我们将所有的路由配置都堆在main.js中来实现路径组件的路由,这样做的话非常不利于我们后期对项目的维护。因此正确的做法是将路由模块抽离出来&a…