• 【Kubernetes】声明式API与Kubernetes编程范式


      什么是声明式API呢?

      答案是,kubectl apply命令。

    举个栗子

      在本地编写一个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 apply创建这个Deployment
    $ kubectl apply -f nginx.yaml
    
    
    ### 倘若修改一下nginx里定义的镜像
    ...
        spec:
          containers:
          - name: nginx
            image: nginx:1.7.9
    
    
    ###可以继续执行一条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: 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容器对应的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.1
          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对象的“增、删、改、查”进行协作,进而完成用户业务逻辑的编写过程

  • 相关阅读:
    ElasticSearch原理
    redis master配置了密码进行主从同步
    redis sentinel 高可用(HA)方案部署,及python应用示例
    Linux Redis集群搭建与集群客户端实现
    字符串倒序
    单链表反转
    【python】面试常考数据结构算法
    面试中的排序算法总结
    Memcached 真的过时了吗?
    Activity生命周期
  • 原文地址:https://www.cnblogs.com/yuxiaoba/p/9801161.html
Copyright © 2020-2023  润新知