03-Kubernetes 控制平面组件:API Server

一、API Server 的概念

  kube-apiserver是Kubernetes最重要的核心组件之一,主要提供以下的功能:

      提供集群管理的REST API接口,包括认证授权、数据校验以及集群状态变更等;

    提供其他模块之间的数据交互和通信的枢纽(其他模块通过API Server查询或修改数据,只有API Server才直接操作etcd)。

  Kubernete sAPI 的每个请求都会经过多阶段的访问控制之后才会被接受,这包括认证、授权以及 准入控制 (Admission Control) 等。

    请求进入: API Request 会进入 API HTTP handler

    认证&鉴权:然后通过认证和鉴权,认证是确认访问者身份,鉴权鉴定其有没有这个权限

    变形:可以为请求设置一些值,除了 kubernetes 内置的 mutating 外,还可以定义自己的 webhook,加上自己的设置

    对象验证:由于对请求加了一些设置的值,因此需要再做一次验证,校验对象是否合法

    验证:如果还想做一些其他额外的校验,可以通过 Validating admission plugin 来调用自定义的 webhook

    存储:最后将数据存储在 etcd

    

        

  再展开一点,访问控制细节,实际上就是代码层级的分解:

    panic recovery:他会启动多个 goroutine 来处理不同的请求,当 apiserver 接收到请求后,如果出现了 panic 后,panic recovery 就要确保某个 goroutine panic 不会把整个 httpserver 弄死,因此这里就有了 panic recovery 机制。

    request-timeout:处理的超时时间

    authentication:认证

    audit:审计,记录谁对哪些对象做了哪些操作

    impersonation:可以给 request 加 header,可以使用 header 来标记这个请求是给谁用的

    max-in-flight:限流,是指有多少请求正在路上,如果超过了上限,就会被拒绝

    authorization:鉴权

    kube-aggregator:apiserver 本身是一个 request 处理器,那么其就可以将 request 转走,因此 aggregator 会去判断这个 request 是不是标准的 kubernetes 对象,如果是标准的 kubernetes 对象,那么默认的 apiserver 就处理掉了,但是如果自己自定义了一些 kubernetes 对象,有另外的 API Server 来支撑这些新的对象,那么就可以再 kube-aggregator 中做一些配置,那么其就会将 request 转到自定义的 apiserver 中。

    decoding:对 json 做反序列化,序列化为 go 语言的对象

    request conversion & defaulting:看对象是什么类型的,是 pod、namespace还是什么,根据对象的类型,然后将外部的结构转为内部的结构。kubernetes 任何的对象都有 External version 和 Internal version,External version 是面向用户的,而 Internal version 是面向实现的。也就是将请求对象转为 Internal version,然后通过 Internal version 将对象存入 etcd。

    admission:先去看有没有 mutating webhook,有就调用,如果没有,就走内置的 validating 的流程。这个是 kubernetes 自己实现的,例如 pod,其里面就有 podstorage 这样的一个对象,这里面会去定义 update registry 和 create registry,其实就是定义了在创建或更新这个对象之前要做什么事情,做哪些校验,因此其就会调用这些方法来校验 pod 是否合法,例如 image 没有提供等。做完这些 validating 后,就会去看是否有附加的 validating webhook,如果有的话,就去调用。

    storage:当上面的流程都通过后,就将请求存入 etcd,然后返回结果给客户端。

        

二、认证

  APIServer 启动的时候,有两种不同的和安全相关的配置,一个是 insecure-port,一个是 secure-port,早期大家习惯把 insecure-port 打开,经过 insecure-port 的端口没有任何的安全校验,也就是不走认证鉴权逻辑,那么就会比较危险,因为不管是谁发的请求,都会被处理,就不能保证没有而已请求,所以现在都会把 insecure-port 端口关闭;而 secure-port 的端口是要走认证和鉴权这些内容的。 

  开启TLS时,所有的请求都需要首先认证。Kubernetes支持多种认证机制,并支持同时开启多个认证插件(只要有一个认证通过即可)。如果认证成功,则用户的 username 会传入授权模块做进一步授权验证;而对于认证失败的请求则返回 HTTP 401。

(一)认证插件:

  X509证书:

    证书一般会有 key 和 cert,cert 在签发的时候就说明前发给哪个机构,当带着证书来访问 APIServer 时,APIServer 会有签发证书的 ca文件,可以解析出 certificate,就能知道请求者是谁,然后就可以验证请求是合法的还是不合法的。

    使用 X509 客户端证书只需要 API Server 启动时配置 --client-ca-file=SOMEFILE。在证书认证时,其 CN 域用作用户名,而组织机构域则用作 group 名。

  静态Token文件:

    使用静态 Token 文件认证只需要 API Server 启动时配置 --token-auth-file=SOMEFILE。

    该文件为 csv 格式,每行至少包括三列 token、username、user id、 toker、user、uid、"group1,group2,group3"

    其实其就像一个简化的数据库,在启动的时候加载了该文件,当请求来时,直接根据文件中的配置进行认证和鉴权。

  引导Token

    为了支持平滑地启动引导新的集群,Kubernetes 包含了一种动态管理的持有者令牌类型,称作启动引导令牌(Bootstrap Token)。

    这些令牌以 Secret 的形式保存在 kube-system 名字空间中,可以被动态管理和创建。

    控制器管理器包含的 TokenCleaner 控制器能够在启动引导令牌过期时将其删除。

    在使用 kubeadm 部署 Kubernetes 时,可通过 kubeadm token list 命令查询。

  静态密码文件:

    需要API Server启动时配 --basic-auth-file=SOMEFILE,文件格式为csv,每行至少三列 password, user, uid,后面是可选的 group 名 password、user、uid、 "group1,group2,group3“。

    其实这种方式和静态 token 文件非常类似,只不过这里存储的是密码,那么在认证的时候,就需要同时传进来用户名和密码。

  ServiceAccount:

    系统账号。当创建任何一个 namespace 的时候,kubernetes 有一个控制器,看到创建了 namespace,那么他就会去创建 ServiceAccount,那么该 ServiceAccount 就会生成一个针对该 ServiceAccount 的 token,这个 token 所签发的内容表示这个 token 是签发给哪个 namespace 的哪个 ServiceAccount 的,因为这个 token 是 APIServer 签发的,所以就知道这个 token 代表的是谁,因此就可以做认证和鉴权。

    ServiceAccount是Kubernetes自动生成的’并会自动挂载到容器的 /run/secrets/kubernetes.io/serviceaccount 目录中。

  OpenlD:

    还支持一些扩展,例如通过 OpenID、Webhook 等方式。

    OAuth 2.0 的认证机制。

  Webhook令牌身份认证:

    --authentication-token-webhook-config-file 指向一个配置文件,其中描述如何访问远程的 Webhook 服务。

    --authentication-token-webhook-cache-ttl 用来设定身份认证决定的缓存时间。默认时长为2分钟。

  匿名请求:

    如果使用 AlwaysAUow 以外的认证模式,则匿名请求默认开启,但可用 --anonymous-auth=false 禁止匿名请求。

(二)基于Webhook的认证服务集成

  构建符合Kubernetes规范的认证服务:需要依照Kubernetes规范,构建认证服务,用来认证 tokenreview request。

  APIServer 会去看是否开启了 webhook,如果开启了,就将 post 请求发送给 webhook 中配置的 url。

构建认证服务:认证服务需要满足如下Kubernetes的规范:
URL: httDS://authn?examul/?com/authrnticate
Method: POST
Input:
Output:

   认证服务的 request 也是遵循了 kubernetes 的 API 对象的约定,例如有 apiVersion、kind、spec 等,kind 是 TokenReview,TokenReview 对象虽然不直接面向用户,但是 APIServer 在跟 webhook 集成的时候就组装了一个 TokenReview 对象,这个对象里面有一个 spec,spec 里面只有一个 token,就允许用户通过传递 token,然后将 token 组装成一个请求对象,发给 webhook 配置的认证地址。

{"apiVersion": "authentication.k8s.io/v1 betal",
  "kind": "TokenReview",
  "spec": {"token": "(BEARERTOKEN)"}
}

  这个认证的地址就需要我们自己去开发并作认证,如果认证通过,就要返回一个 TokenReview 对象,里面包含了认证后的信息。

 "apiVersion": "authentication.k8s.io/v1 betal",
  "kindH": "TokenReview",
  "status": {
    "authenticated": true,
    "user": {
      "username": "janedoe@example.com",
      "uid": "42",
      "groups":[
          "developers",
          "qa"
      ]
    }
  }

(三)开发认证服务  

  1、编写认证服务

  (1)主服务:首先启动一个服务,请求路径为 /authenticate,绑定端口为 3000

func main() {
    http.HandleFunc("/authenticate", func(w http.ResponseWriter, r *http.Request) {
        decoder := json.NewDecoder(r.Body)
                ......
        // Check User
        ts := oauth2.StaticTokenSource(
            &oauth2.Token{AccessToken: tr.Spec.Token},
        )
            ......
        w.WriteHeader(http.StatusOK)
        ......
    log.Println(http.ListenAndServe(":3000", nil))
}    

  (2)解码认证请求:将请求转为 TokenReview

    decoder := json.NewDecoder(r.Body)
    var tr authentication.TokenReview
    err := decoder.Decode(&tr)
    if err != nil {
        log.Println("[Error]", err.Error())
        w.WriteHeader(http.StatusBadRequest)
        json.NewEncoder(w).Encode(map[string]interface{}{
            "apiVersion": "authentication.k8s.io/v1beta1",
            "kind": "TokenReview",
            "status": authentication.TokenReviewStatus{ Authenticated: false,
            },
        })
        return
    }

  (3)转发认证请求至认证服务器:这里可以调用一个具体的认证服务,例如下面代码中,使用了 github 的认证服务

    // Check User
    ts := oauth2.StaticTokenSource(
        &oauth2.Token{AccessToken: tr.Spec.Token},
    )
    tc := oauth2.NewClient(oauth2.NoContext, ts)
    client := github.NewClient(tc)
    user, _, err := client.Users.Get(context.Background(),"")
    if err != nil {
        log.Println("[Error]", err.Error())
        w.WriteHeader(http.StatusUnauthorized)
        json.NewEncoder(w).Encode(map[string]interface{}{
            "apiVersion": "authentication.k8s.io/v1beta1", 
            "kind": "TokenReview",
            "status": authentication.TokenReviewStatus{
                Authenticated: false,
            },
        })
        return
    }

  (4)认证结果返回给API Server

    w.WriteHeader(http.StatusOK)
    trs := authentication.TokenReviewStatus{
        Authenticated: true,
        User: authentication.UserInfo{
            Username: *user.Login,
            UID: *user.Login,
        },
    }
    json.NewEncoder(w).Encode(map[string]interface{}{
        "apiVersion": "authentication.k8s.io/v1beta1", 
        "kind": "TokenReview",
        "status": trs, 
    })

  2、配置认证服务:配置 webhook-config.json,将外部认证写入 webhook 的配置文件中,包括认证服务地址、用户等信息

    {
        "kind": "Config",
        "apiVersion": "v1",
        "preferences": {},
        "clusters":[
            {
                "name": "github-authn",
                "cluster": {
                    "server": "http://localhost:3000/authenticate" 
                }
            }
        ],
        "users": [
            {
                "name": "authn-apiserver",
                "user": {
                    "token": "secret"
                }
            }
        ],
        "contexts":[
            {
                "name": "webhook",
                "context":    {
                    "cluster": "github-authn", 
                    "user": "authn-apiserver"
                }
            }
        ],
        "current-context": "webhook"
    }
   3、然后上 github 上生成一个 access token,然后将 token 配置在 .kube/config 中,然后使用该配置的用户去访问 pod,其就能拿到 github 的用户名,然后去访问。

  4、配置 apiserver

    然后修改 kube-apiserver.yml 文件,修改其中的 --authentication-token-webhook-config-file 指向上面的配置文件。

    认证可以是任何认证系统,例如上面演示的 github,还有像 Keystone、LDAP、AD。用户认证完成后,生成代表用户身份的token,该token通常是有失效时间的。

    修改apiserver设置,开启认证服务,apiserver 保证将所有收到的请求中的token信息,发给认证服务进行验证。修改 kube-apiserver.yml 文件:

      --authentication-token-webhook-config-file,指向上面的配置文件;

      --authentication-token-webhook-cache-ttL 默认2分钟。

    配置文件需要 mount 进 Pod。

    配置文件中的服务器地址需要指向 authService

  5、生产系统中遇到的陷阱:

    对于 kubernetes 来说,所有的请求都是如果失败就要重试的,kubernetes 也做了指数级的 back off,即 1s、2s、4s、8s、16s 这样的重试,但是如果引入三方类库,就要防止没有做这样的操作,那么在开发的时候,一方面要防止请求侧没有做指数级 back off,另一方面在统一认证平台处,要做限流操作,防止被打垮。

    基于 Keystone 的认证插件导致 Keystone 故障且无法恢复。

    Keystone 是企业关键服务。

    Kubernetes 以 Keystone 作为认证插件。

    Keystone 在出现故障后会抛出 401 错误。

    Kubernetes 发现 401 错误后会尝试重新认证。

    大多数 controller 都有指数级 back off,重试间隔越来越慢。

    但 gophercloud 针对过期 token 会一直 retry

    大量的 request 积压在 Keystone 导致服务无法恢复。 Kubernete s成为压死企业认证服务的最后一根稻草。

三、鉴权

  1、授权

    授权主要是用于对集群资源的访问控制,通过检查请求包含的相关属性值,与相对应的访问策略相比较,API请求必须满足某些策略才能被处理。

    跟认证类似,Kubernetes 也支持多种授权机制,并支持同时开启多个授权插件(只要有一个验 证通过即可)。

    如果授权成功,则用户的请求会发送到准入控制模块做进一步的请求验证;对于授权失败的请求 则返回 HTTP 403。

    Kubernetes授权仅处理以下的请求属性:

      user, group, extra(request 里面所带的用户信息、用户组信息、扩展信息,例如要知道请求发起人是谁)

      API、请求方法(如 get、post、update, patch和delete)和请求路径(如/api) (通过什么样的方法操作对象)

      请求资源和子资源(用户请求的对象)

      Namespace

      API Group

    目前,Kubernetes支持以下授权插件:

      ABAC

      RBAC

      Webhook

      Node

  2、RBAC vs ABAC

    最常用的两种鉴权方式是 ABAC 和 RBAC。

    ABAC (Attribute Based Access Control) 有点类似上面认证中提到的静态密码、静态 token 的方式,也是去配置一个 csv,里面写哪个用户有哪个对象的什么操作权限,配好之后将配置文件交给 APIServer,APIServer 重启后,鉴权策略就存在这里了,相当于是 kubernetes 把纯文本文件作为了一个简单的数据库使用。

    ABAC 本来是不错的概念,但是在 Kubernetes 中的实现较难于管理和理解,而且需要对 Master 所在节点的 SSH 和文件系统权限,要使得对授权的变更成功生效,还需要重新启动 API Server。

    而RBAC的授权策略可以利用 kubectl 或者 Kubernetes API 直接进行配置。RBAC可以授权给用户,让用户有权进行授权管理,这样就可以无需接触节点,直接进行授权管理。RBAC在 Kubernetes中被映射为API资源和操作。

    RBAC老图:

      RBAC 是以 Role 为核心的鉴权体系,RBAC 并非 Kubernetes 的特有,其是一种通用的模式。

      permission:权限。首先要去定义权限,即定义要操作哪些对象,通过什么样的方式去操作这些对象,操作和对象的组合就叫做 permission

      role:角色,就是将一堆的 permission 整合到一个角色

      user:用户和角色会有一个绑定关系

        

    RBAC新解

      在 Kubernetes 里面也是同样的思想,也是定义谁可以对什么对象做什么操作

        who:也就是 subject,可以是自定义的 user,也可以是系统内的  ServiceAccount

        what:可以操作哪些对象,Role 中的对象,例如哪个 APIGroup,哪个 Resource,哪个 SubResource

        how:可以做哪些操作,Role 中的 verbs,例如 watch、get、list 等操作

      然后使用 RoleBindings 将 Subject 和 Role 做关联。

      Role 和 RoleBingdings 是需要指定 namespace 的,而 ClusterRole 和 ClusterRoleBingdings 是指得整个集群,而 RoleBingdings 也可以去绑定 ClusterRole。

      对于 RBAC 的这种权限管理,权限是可以传递的,例如给 A 分配了某个权限,那么 A 就可以给 B 分配这个权限,这样就做到了分级管理。

      

       

   3、Role 与 ClusterRole

    Role (角色)是一系列权限的集合,例如一个角色可以包含读取 Pod 的权限和列出 Pod 的权限。

    Role 只能用来给某个特定 namespace 中的资源作鉴权,对多 namespace 和集群级的资源或者是非资源类的 API (如 /healthz) 使用 ClusterRole。

# Role示例
kind: Rote
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: default
  name: pod-reader
rules:
  - apiGroups: [""]  # "" 表示 core group
    resources: ["pods"] # 表示可以对 pod 进行操作
    verbs: ["get", "watch", "List"] # 表示可以进行什么操作
# ClusterRoLe 示例
kind: CLusterRoLe
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  # "namespace" omitted since CLusterRoLes are not namespaced
  name: secret-reader
rutes:
  - apiGroups:[""]
    resources: ["secrets"]
    verbs: ["get", "watch", "List"]

  4、RoLeBinding

# RoLeBinding 示例(引用 CLusterRoLe)
#This rote binding allows "dave" to read secrets in the "development" namespace, 
kind: RoLeBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: read-secrets
  namespace: development     # This onLy grants permissions within the "development" namespace, 
subjects:
  - kind: User
    name: dave 授权给谁
    apiGroup: rbac.authorization.k8s.io
roteRef:
  kind: CLusterRoLe
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

 

    通过这种方式,如果使用 dave 的 token 或者以他的 key 和 cert X509 的证书访问,访问到 APIServer 的时候,APIServer 就知道其有没有操作权限了。        

          

  5、账户/组的管理

    角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组用户。

    它包含若干主体(用户、组或服务账户)的列表和对这些主体所获得的角色的引用。 组的概念:

      当与外部认证系统对接时,用户信息(Userinfo)可包含 Group 信息,授权可针对用户群组;

      当对 ServiceAccount 授权时, Group 代表某个 namespace 下的所有 ServiceAccount

        

     针对群组授权其实与用户授权差别不大,roleRef 是一样的,只是 subject 的 kind 是 group,即为组授权,下面的第一段配置是给 name 为 manager 的授权,第二段配置是给 serviceaccount 的 group配置权限。

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-secrets-global
subjects:
- kind: Group
  name∶manager #'name'是区分大小写的
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name:secret-reader
  apiGroup: rbac.authorization.k8s.io
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: read-secrets-global
subjects:
- kind: Group
  name: system:serviceaccounts:qa # 表示 qa 的 namespace 下的所有 serviceaccount 都有权限
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

  6、规划系统角色

    User:对于 User 一般会设置管理员和普通用户,那么其权限怎么设置,还是要根据具体情况来看的。

      管理员是否需要拥有所有资源的所有权限?这个是不一定的,例如每个用户在自己的 namespace 里面有 secret,这里面记录了业务相关的密码和认证信息等,对于平台管理员来说,其实是不应该看这些信息的,那么这些权限就应该被排除掉。

      普通用户应该考虑集群的场景,是强隔离还是若隔离,如果是强隔离,那么用户就不能看到全局的配置信息,也就看不到有多少 namespace,只能看自己 namespace 中的内容,如果是若隔离,就会把敏感信息排出后,让其可以读公共配置,例如有多少 namespace 等信息,但是写只能写自己的 namespace。

    SystemAccount:对于 serviceaccount 也一样,有的部门会自建自己的 serviceaccount,通过 serviceaccount 的授权来实现整个子的权限管理。

      SystemAccount 是开发者 (kubernetes developer 或者 domain developer) 创建应用后, 应用于 apiserver 通讯需要的身份。

      用户可以创建自定的 ServiceAccount,Kubernetes 也为每个 namespace 创建 default ServiceAccount

      Default ServiceAccount 通常需要给定权限以后才能对 apiserver 做写操作。

  7、自动化多集群权限管控实现方案

    规划了一个角色叫做 namespace admin,要达到的目的是,一个用户创建了 namespace 后,其对于 namespace 的所有资源都有操作权限,这样就可以把 namespace 的操作权限下发,做到分级权限控制。具体做法如下:

      在 cluster 创建时,创建自定义的 role,比如 namespace-creator,Namespace-creator role 定义用户可操作的对象和对应的读写操作。

      创建自定义的 namespace admission controller:对 namespace 做变形,创建了一个 mutating 的 webhook,当namespace创建请求被处理时,获取当前用户信息并 annotate 到 namespace,记录该 namespace 是谁创建的。

      创建 RBAC controller:

        Watch namespace 的创建事件;

        获取当前 namespace 的创建者信息;

        在当前 namespace 创建 rolebinding 对象,并将 namespace-creator 角色和用户绑定。

  8、与权限相关的其他最佳实践

    ClusterRole 是非 namespace 绑定的,针对整个集群生效,通常需要创建一个管理员角色,并且绑定给开发运营团队成员。

    ThirdPartyResource 和 CustomResourceDefinition 是全局资源,普通用户创建 ThirdPartyResource 以后,需要管理员授予相应权限后才能真正操作该对象。

    针对所有的角色管理,建议创建spec,用源代码驱动:虽然可以通过 edit 操作来修改权限,但后期会导致权限管理混乱,可能会有很多临时创建出来的角色和角色绑定对象,重复绑定某一个资源权限。

    权限是可以传递的,用户 A 可以将其对某对象的某操作,抽取成一个权限,并赋给用户B;

    防止海量的角色和角色绑定对象,因为大量的对象会导致鉴权效率低,同时给apiserver增加负担。

    ServiceAccount 也需要授权的,否则你的 component 可能无法操作某对象。

    Tips: SSH 到 master 节点通过 insecure port 访问 apiserver 可绕过鉴权,当需要做管理操作又没有权限时可以使用(不推荐)。

  9、运营过程中出现的陷阱

    案例1:

      研发人员为提高系统效率,将update方法修改为patch,研发人员本地非安全测试环境测试通过,上生产,发现不work

      原因:忘记更新rolebinding,对应的serviceaccount没有patch权限。

    案例2:

      研发人员创建CRD,并针对该CRD编程,上生产后不工作;

      原因,该CRD未授权,对应的组件get不到对应的CRD资源。

四、准入

(一)准入控制

  为资源增加自定义属性:作为多租户集群方案中的一环,我们需要在 namespace 的准入控制中,获取用户信息,并将用户信息更新的 namespace 的 annotation 。只有当 namespace 中有有效用户信息时,我们才可以在 namespace 创建时,自动绑定用户权限,namespace才可用。

  准入最多的应用场景是做配额管理,用来限制每个 namespace 可以使用多少 CPU、多少内存等,除了这个,etcd 也是有配额的,需要去限制每个 namespace 可以使用多少配额。

  方案:预定义每个 namespace 的 ResourceQuota,并把 spec 保存为 configmap

      用户可以创建多少个Pod:BestEffortPod(没标明资源使用的 pod)、QoSPod(有标明资源使用的 pod)

      用户可以创建多少个service;

      用户可以创建多少个ingress;

      用户可以创建多少个service VIP;

  创建 ResourceQuota Controller:监控 namespace 创建事件,当 namespace 创建时,在该 namespace 创建对应的 ResourceQuota 对象。

  下面配置文件表示 default namespace 下只能创建一个 configmap

apiVersion: v1
kind: ResourceQuota
metadata:
  name: object-counts
  namespace: default
spec:
  hard:
    configmaps: "1"

    apiserver 中开启 ResourceQuota 的 admission plugin。

  准入控制(Admission Control)在授权后对请求做进一步的验证或添加默认参数。不同于授权 和认证只关心请求的用户和操作,准入控制还处理请求的内容,并且仅对创建、更新、删除或连 接(如代理)等有效,而对读操作无效。

  准入控制支持同时开启多个插件,它们依次调用,只有全部插件都通过的请求才可以放过进入系统。

(二)准入控制插件:

  AlwaysAdmit:接受所有请求。

  AlwaysPullImages:总是拉取最新镜像,在多租户场景下非常有用。

  DenyEscalatingExec:禁止特权容器的 exec 和 attach 操作。

  ImagePolicyWebhook:通过 Webhook 决定 image 策略,需要同时配置 --admission- control-config-file;也就是说在创建 pod 的时候,来看容器的镜像是否扫描通过,如果失败就拦下来,如果通过就放行。(可以使用一些扫描软件对镜像进行扫描,扫描的结果会存储下来)

  ServiceAccount:自动创建默认 ServiceAccount,并确保 Pod 引用的 ServiceAccount 已经存 在。

  SecurityContextDeny:拒绝包含非法 Securitycontext 配置的容器。

  ResourceQuota:限制 Pod 的请求不会超过配额,需要在 namespace 中创建一个 ResourceQuota 对象。

  LimitRange:为 Pod 设置默认资源请求和限制,需要在 namespace 中创建一个 LimitRange 对象。

  InitialResources:根据镜像的历史使用记录/为容器设置默认资源请求和限制

  NamespaceLifecycle:确保处于 termination 状态的 namespace 不再接收新的对象创建请求,并拒绝请求不存在的 namespace。

  DefaultStorageClass:为 PVC 设置默认 Storageclass。

  DefaultTolerationSeconds:设置 Pod 的默认 forgiveness toleration 为5分钟。

  PodSecurityPolicy:使用 Pod Security Policies 时必须开启。

  NodeRestriction:限制 kubelet 仅可访问 node、endpoint, pod、service 以及 secret、 configmap、PV和PVC等相关的资源。

(三)准入控制插件的开发

  准入控制插件

    除默认的准入控制插件以外,Kubernetes预留了准入控制插件的扩展点,用户可自定义准入控制插件实现自定义准入功能。

    MutatingWebhookConfiguration:变形插件,支持对准入对象的修改。

    ValidatingWebhookConfiguratior:校验插件,只能对准入对象合法性进行校验,不能修改。

        

  准入插件的开发和认证服务的开发一样,也是启动一个 server,接受请求后,将请求转为 MutatingWebhookConfiguration,然后对其做修改并返回。

  对于配置文件,如下所示,这里需要注意一点,url 是需要使用 https 的请求,因此需要配置 caBundle,使用 base64 进行加密

    # {{if eq .k8snode_validating "enabled"}} 
    apiVersion: admissionregistration. k8s.io/v1betal 
    kind: MutatingWebhookConfiguration
    metadata:
        name: ns-mutating.webhook.k8s.io 
        webhooks: 
        - clientConfig: 
          caBundle: {{.serverca_base64}} 
          url:https://admission, loca.tess.io/apis/admission.k8s.io/v1alphal/ ns-mutating
          failurePolicy: Fail 
          name: ns-mutating.webhook.k8s.io 
          namespaceSelector: {} 
          rules:
          - apiGroups:
              - ""
          - apiVersions:
            - '*'
          operations:
              - CREATE
          resources:
              - nodes
          sideEffects: Unknown
    #{{end}}

五、限流

  1、限流算法

  (1)计数器固定窗口算法:原理就是对一段固定时间窗口内的请求进行计数,如果请求数超过了阈值,则舍弃该请求; 如果没有达到设定的阈值,则接受该请求,且计数加1。当时间窗口结束时,重置计数器为0。

        

  (2)计数器滑动窗口算法:在固定窗口的基础上,将一个计时窗口分成了若干个小窗口,然后每个小窗口维护一个独立的计数器。当请求的时间大于当前窗口的最大时间时,则将计时窗口向前平移一个小窗口。平移时,将第一个小窗口的数据丢弃,然后将第二个小窗口设置为第一个小窗口,同时在最后面 新增一个小窗口,将新的请求放在新增的小窗口中。同时要保证整个窗口中所有小窗口的请求数目之后不能超过设定的阈值。

        

  (3)漏斗算法:漏斗算法的原理也很容易理解。请求来了之后会首先进到漏斗里,然后漏斗以恒定的速率将请求 流出进行处理,从而起到平滑流量的作用。当请求的流量过大时'漏斗达到最大容量时会溢出,此时请求被丢弃。在系统看来,请求永远是以平滑的传输速率过来,从而起到了保护系统的作用。

        

  (4)令牌桶算法:令牌桶算法是对漏斗算法的一种改进,除了能够起到限流的作用外,还允许一定程度的流量突发。 在令牌桶算法中,存在一个令牌桶,算法中存在一种机制以恒定的速率向令牌桶中放入令牌。令牌桶也有一定的容量,如果满了令牌就无法放进去了。当请求来时,会首先到令牌桶中去拿令牌,如果拿到了令牌,则该请求会被处理,并消耗掉拿到 的令牌;如果令牌桶为空,则该请求会被丢弃。

        

  2、API Server中的限流:

    max-requests-inflight:在给定时间内的最大 non-mutating 请求数,即最大在途请求数。

    max-mutating-requests-inflight:在给定时间内的最大 mutating 请求数,调整 apiserver 的 流控qos。即最大在途写操作请求数。

      参考代码:staging/src/k8s.io/apiserver/pkg/server/filters/maxinflight.go:WithMaxlnFlightLimit()

    以下是这两个参数的默认值以及在不同节点数的集群中的建议值(要根据自己的情况进行压测验证)。

      

    以上传统限流方法的局限性:

      粒度粗:无法为不同用户,不同场景设置不通的限流

      单队列:共享限流窗口、桶,一个坏用户可能会将整个系统堵塞,其他正常用户的请求无法被及时处理。

      不公平:正常用户的请求会被排到队尾,无法及时处理而饿死。

      无优先级:重要的系统指令一并被限流,系统故障难以恢复。

  3、API Priority and Fairness:

    APF 以更细粒度的方式对请求进行分类和隔离。

    它还引入了空间有限的排队机制,因此在非常短暂的突发情况下,API服务器不会拒绝任何请求。

    通过使用公平排队技术从队列中分发请求,这样, —个行为不佳的控制器就不会饿死其他控制器(即使优先级相同)。

    APF 的核心:

      多等级:对于请求分级,分为不同的 FlowSchema,对不不同等级分别进行限流

      多队列:对于同一个 FlowSchema,会有多个队列,每个队列单独限流。

        

    APF 的实现依赖两个非常重要的资源 FlowSchema、PriorityLevelConfiguration

    APF 对请求进行更细粒度的分类,每一个请求分类对应一个 FlowSchema (FS),FS 内的请求又会根据 distinguisher 进一步划分为不同的 Flow。

    FS 会设置一个优先级 (Priority Level,PL) ,不同优先级的并发资源是隔离的。所以不同优先级的资源不会相互排挤。特定优先级的请求可以被高优处理。

    一个 PL 可以对应多个 FS,PL 中维护了一个 QueueSet,用于缓存不能及时处理的请求,请求不会因为超出 PL 的并发限制而被丢弃。

    FS 中的每个 Flow 通过 shuffle sharding 算法从 QueueSet 选取特定的 queues 缓存请求,每次从 QueueSet 中取请求执行时,会先应用 fair queuing 算法从 QueueSet 中选中一个 queue,然后从这个 queue 中取出 oldest 请求执行。所以即使是同一个 PL 内的请求,也不会出现一个 Flow 内的请求一直占用资源的不公平现象。

    概念:

      传入的请求通过按照其属性分类,并分配优先级。

      每个优先级维护自定义的并发限制,加强了隔离度,这样不同优先级的请求,就不会相互饿死。

      在同一个优先级内,公平排队算法可以防止来自不同的请求相互饿死。

      该算法将请求排队,通过排队机制,防止在平均负载较低时,通信量突增而导致请求失败。

  4、优先级:

    如果未启用APF,API 服务器中的整体并发量将受到 kube-apiserver 的参数 --max- requests-inflight 和 --max-mutating-requests-inflight 的限制。

    启用 APF 后,将对这些参数定义的并发限制进行求和,然后将总和分配到一组可配置的优免级中。每个传入的请求都会分配一个优先级;

    每个优先级都有各自的配置,设定允许分发的并发请求数。

    例如,默认配置包括针对领导者选举请求、内置控制器请求和 Pod 请求都单独设置优先级。 这表示即使异常的 Pod 向 API 服务器发送大量请求,也无法阻止领导者选举或内置控制器的操作执行成功。

    排队:

      即使在同一优先级内,也可能存在大量不同的流量源。

      在过载情况下,防止一个请求流饿死其他流是非常有价值的(尤其是在一个较为常见的场景中,一个有故障的客户端会疯狂地向 kube-apiserver 发送请求,理想情况下,这个有故障的客户端不应对其他客户端产生太大的影响)。

      公平排队算法在处理具有相同优先级的请求时,实现了上述场景。

      每个请求都被分配到某个流中,该流由对应的 FlowSchema 的名字加上一个流区分项 (Flow Distinguisher,例如根据 namespace 或者根据 用户来分流) 来标识。

      这里的流区分项可以是发出请求的用户、目标资源的名称空间或什么都不是。

      系统尝试为不同流中具有相同优先级的请求赋予近似相等的权重。

      将请求划分到流中之后,APF 功能将请求分配到队列中。

      分配时使用一种称为混洗分片 (Shuffle-Sharding) 的技术。该技术可以相对有效地利用队列隔离低强度流与局强度流。

      排队算法的细节可针对每个优先等级进行调整,并允许管理员在内存占用、公平性(当总流量 超标时/各个独立的流将都会取得进展)、突发流量的容忍度以及排队引发的额外延迟之间进行权衡。

  5、豁免请求:某些特别重要的请求不受制于此特性施加的任何限制。这些豁免可防止不当的流控配置完全禁用 API 服务器

  默认配置:

    system:用于systemmodes组(即kubelets)的请求;kubelets必须能连上API服务器,以便工作负载能够调度到其上。

    leader-election:用于内置控制器的领导选举的请求(特别是来自 kube-system 名称空间中 system:kube-controller- manager 和 system:kube-scheduler 用户和服务账号,针对 endpoints、configmaps 或 leases 的请求);将这些请求与其他流量相隔离非常重要,因为领导者选举失败会导致控制器发生故障并重新启动,这反过来会导致新启动的控制器在同步信息时,流量开销更大。

    workload-high:优先级用于内置控制器的请求。

    workload-low:优先级适用于来自任何服务帐户的请求,通常包括来自 Pods 中运行的控制器的所有请求。

    global-default:优先级可处理所有其他流量,例如:非特权用户运行的交互式 kubectl 命令。

    exempt:优先级的请求完全不受流控限制:它们总是立刻被分发。特殊的 exempt FlowSchema 把 system:masters 组的所有请求都归入该优先级组。

    catch-all:优先级与特殊的 catch-all FlowSchema 结合使用,以确保每个请求都分类。—般不应该依赖于 catch-all 的配置,而应适当地创建自己的 catch-all FlowSchema 和 PriorityLevelConfigurations (或使用默认安装的 global-default 配置)。为了帮助捕获部分请求未分类的配置错误,强制要求 catch-all 优先级仅允许5个并发份额,并且不对请求进行排队,使得仅与 catch-all FlowSchema 匹配的流量被拒绝的可能性更高,并显示HTTP 429错误。

  PriorityLevelConfiguration:一个 PriorityLevelConfiguration 表示单个隔离类型。每个 PriorityLevelConfigurations 对未完成的请求数有各自的限制,对排队中的请求数也有限制。

      

  FLowSchema:FLowSchema 匹配一些入站请求,并将它们分配给优先级。每个入站请求都会对所有 Flowschema 测试是否匹配,首先从 matchingPrecedence 数值最低的匹配开始(我们认为这是逻辑上匹配度最高),然后依次进行,直到首个匹配出现。

        

  调试:

    /debug/api_priority_and_fairness/dump_priority_levels  所有优先级及当前状态的列表

    kubectl get --raw /debug/api_priority_and_fairness/dump_priority_levels

      /debug/api_priority_and_fairness/dump_queues  所有队列及当前状态的列表

    kubectl get --raw /debug/api_priority_and_fairness/dump_queues

      /debug/api_priority_and_fairness/dump_requests  当前正在队列中等待的所有请求的列表

    kubectl get --raw /debug/api_priority_and_fairness/dump_requests

六、高可用 API Server

  1、启动 apiserver 示例,其实随着 kubernetes 版本的提升,很多配置都是默认配置的,就不需要再配置这么多内容。

kube-apiserver --feature-gates=AHAlpha=true --runtime-config=api/all=true \
  --requestheader-allowed-names=front-proxy-client \
  --client-ca-file=/etc/kubernetes/pki/ca.crt \
  --allow-privileged=true \
  --experimental-bootstrap-token-auth=true \
  --storage-backend=etcd3 \
  --requestheader-username-headers=X-Remote-User \
  --requestheader-extra-headers-prefix=X-Remote-Extra- \
  --service-account-key-file=/etc/kubernetes/pki/sa.pub \
  --tls-cert-file=/etc/kubernetes/pki/apiserver.crt \
  --tls-private-key-file=/etc/kubernetes/pki/apiserver.key \
  --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt \
  --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt \
  --enabled-hooks=NamespaceLifecycle,LimitRanger,ServiceAccount/PersistentVoLumeLabel,DefaultStorageClass7ResourceQuota\
  --requestheader-group-headers=X-Remote-Group \
  --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key \
  --secure-port=6443 \
  --kubelet-preferred-address-types=lnternallP,ExternallP7Hostname \
  --service-cluster-ip-range=10.96.0.0/12 \
  --advertise-address=192.168.0.20 --etcd-servers=http://127.0.0.1:2379

    构建高可用的多副本 apiserver:apiserver 是无状态的 Rest Server,所以方便 Scale Up / down,那么构架高可用 apiserver 就可以使用 多副本 + 负载均衡的方式。

    负载均衡:在多个 apiserver 实例之上,配置负载均衡;证书可能需要加上 Loadbalancer VIP 重新生成

  2、预留充足的 CPU、内存资源:

    随着集群中节点数量不断增多,API Server 对 CPU 和内存的开销也不断增大。过少的 CPU 资源会降低其处理效率,过少的内存资源会导致 Pod 被 OOMKilled,直接导致服务不可用。在规划 API Server 资源时,不能仅看当下需求,也要为未来预留充分。

  3、善用速率限制(RateLimit):

    APIServer 的参数 --max-requests-inflightn 和 --max-mutating-requests-inflight,支持在给定时间内限制并行处理读请求(包括 Get、List 和 Watch 操作)和写请求(包括 Create、Delete、Update 和 Patch 操作)的最大数量。

    当 APIServer 接收到的请求超过这两个参数设定的值时,再接收到的请求将会被直接拒绝。通过速率限制机制,可以有效地控制 APIServer 内存的使用。

    如果该值配置过低,会经常出现请求超过限制的错误,如果配置过高,则 APIServer 可能会因为占用过多内存而被强制终止,因此需要根据实际的运行环境,结合实时用户请求数量和 APIServer 的资源配置进行调优。

    客户端在接收到拒绝请求的返回值后,应等待一段时间再发起重试,无间隔的重试会加重 APIServer 的压力,导致性能进一步降低。

    针对并行处理请求数的过滤颗粒度太大,在请求数量比较多的场景,重要的消息可能会被拒绝掉,自1.18版本开始,社区引入了优先级和公平保证 (Priority and Fairness) 功能,以提供更细粒度地客户端请求控制。

    该功能支持将不同用户或不同类型的请求进行优先级归类,保证高优先级的请求总是能够更快得到处理,从而不受低优先级请求的影响。

  4、设置合适的缓存大小:

    API Server 与 etcd 之间基于 gRPC 协议进行通信,gRPC 协议保证了二者在大规模集群中的数据高速传输。gRPC 基于连接复用的 HTTP/2 协议,即针对相同分组的对象,API Server 和 etcd 之间共享相同的 TCP 连接,不同请求由不同的 stream 传输。

    一个 HTTP/2 连接有其 stream 配额,配额的大小限制了能支持的并发请求。

    API Server 提供了集群对象的缓存机制,当客户端发起查询请求时,API Server 默认会将其缓存 直接返回给客户端。缓存区大小可以通过参数 //-watch-cache-sizes/, 设置。

    针对访问请求比较多的对象,适当设置缓存的大小,极大降低对 etcd 的访问频率,节省了网络调用,降低了对 etcd 集群的读写压力,从而提高对象访问的性能。

    但是 API Server 也是允许客户端忽略缓存的,例如客户端请求中 ListOption 中没有设置 resourceversion,这时 API Server 直接从 etcd 拉取最新数据返回给客户端。

    客户端应尽量避免此操作,应在 ListOption 中设置 resourceversion 为 0,API Server 则将从缓存里面读取数据,而不会直接访问 etcd。

  5、客户端尽量使用长连接:

    当查询请求的返回数据较大且此类请求并发量较大时,容易引发 TCP 链路的阻塞,导致其他查询操作超时。

    因此基于 Kubernetes 开发组件时,例如某些 DaemonSet 和 Controller,如果要查询某类对象,应尽量通过长连接 ListWatch 监听对象变更,避免全量从 API Server 获取资源。

    如果在同一应用程序中,如果有多个 Informer 监听 API Server 资源变化,可以将这些 Informer 合并,减少和 API Server 的长连接数,从而降低对 API Server 的压力。

  6、如何访问 API Server:

    高可用就是冗余部署+负载均衡,负载均衡在不同的云环境中配置是不一样的,在内部是使用 service 的 cluster IP 去访问的,有些时候会在集群外部配一个硬件的负载均衡,比如 F5 等,这个硬件的负载均衡上面有一个虚 IP,然后负载均衡配上后端的服务器,这就导致同一个 APIServer 会有不同的入口,即一个从负载均衡过来的,一个是从 kube-proxy 过来的。

    控制面的组件希望用同一个访问入口,要么全用负载均衡的 VIP,要么全用 kube-proxy 提供的 service cluster ip,防止一半控制平面组件可以访问一半不能访问,因为整个集群是自动化运维的,这些平面组件访问时如果一半工作一半不工作,就会导致比如状态汇报不上来,节点就会宕机,但是控制器又或者,就会去驱逐 pod。

        

  7、搭建多租户的 Kubernetes 集群:

  (1)授信:

    认证:禁止匿名访问,只允许可信用户做操作。

    授权:基于授信的操作,防止多用户之间互相影响,比如普通用户删除 Kubernetes 核心服务,或者 A 用户删除或修改 B 用户的应用。

  (2)隔离:

    可见行隔离:用户只关心自己的应用,无需看到其他用户的服务和部署。

    资源隔离:有些关键项目对资源需求较高/需要专有设备,不与其他人共享。

    应用访问隔离:用户创建的服务,按既定规则允许其他用户访问。

  (3)资源管理:

    Quota 管理:谁能用多少资源

  (4)认证:

    与企业现有认证系统集成:很多企业基于 Microsoft Active Directory 提供认证服务

    选择认证插件:选择 webhook 作为认证插件(*以此为例展开);也可以选择 Keystone 作为认证插件,以 Microsoft Ad 作为 backend 搭建 keystone 服务

    一旦认证完成,Kubernetes 即可获取当前用户信息(主要是用户名),并针对该用户做授权。 授权和准入控制完成后,该用户的请求完成。 

  (5)注册 API Service

apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
labels:
  kube-aggregator.kubernetes.io/automanaged: onstart
  name: v1.
spec:
  groupPriorityMinimum: 18000
  version: v1
  versionpriority: 1
status:
  conditions:
  -lastTransitionTime: "2020-08-16T05:35:33Z"
    message: Local APIServices are always available
    reason: Local
    status: "True"
    type: Available

   (5)授权:

    ABAC 有期局限性,针对每个 account 都需要做配置,并且需要重启 apiservero;RBAC 更灵活,更符合我们通常熟知的权限管理。

    RBAC:

        

  8、规划系统角色:

    User:管理员:所有资源的所有权限;普通用户:是否有该用户创建的namespace下的所有object的操作权限,对其他用户的namespace资源是否可读,是否可写

    SystemAccount:SystemAccount 是开发者 (kubernetes developer 或者 domain developer) 创建应用后, 应用于 apiserver 通讯需要的身份;用户可以创建自定的 ServiceAccount,Kubernetes 也为每个 namespace 创建 default ServiceAccount;Default ServiceAccount 通常需要给定权限以后才能对apiserver做写操作。

    实现方案:

      在 cluster 创建时 (kube-up.sh),创建自定义的 role,比如 namespace-creator, namespace-creator role定义用户可操作的对象和对应的读写操作。

      创建自定义的 namespace admission controller:当 namespace 创建请求被处理时,获取当前用户信息并 annotate (注释) 到 namespace。

      创建 RBAC controller: Watch namespace 的创建事件;获取当前 namespace 的创建者信息;在当前 namespace 创建 rolebinding 对象,并将 namespace-creator 角色和用户绑定。

    与权限相关的其他最佳实践:

      ClusterRole 是非 namespace 绑定的/针对整个集群生效。

      通常需要创建一个管理员角色,并且绑定给开发运营团队成员。

      ThirdPartyResource 和 CustomResourceDefinition 是全局资源,普通用户创建 ThirdPartyResource 以后,需要管理员授予相应权限后才能真正操作该对象。

      针对所有的角色管理,建议创建 spec,用源代码驱动:虽然可以通过 edit 操作来修改权限,但后期会导致权限管理混乱,可能会有很多临时创建出 来的角色和角色绑定对象,重复绑定某一个资源权限。

      权限是可以传递的,用户 A 可以将其对某对象的某操作,抽取成一个权限,并赋给用户 B 防止海量的角色和角色绑定对象,因为大量的对象会导致鉴权效率低,同时给 apiserver 增加负担。

      ServiceAccount 也需要授权的,否则你的 component 可能无法操作某对象

      Tips: SSH 到 master 节点通过 insecure port 访问 apiserver 可绕过鉴权,当需要做管理操作又没有权限时可以使用(不推荐)。

七、apimachinery

  apimachinery 是 APIServer 的运作机制,其本身也是 kubernetes 周边的一个项目,其会提供一些标准的 Libary 让你使用。

  Kubernetes 中对象的重要属性 TypeMedate,包含GKV:Group、Kind、Version。

  这里再说一下 Version,社区的代码都是从 v1alha1 开始,在初始阶段,在该版本中设计了一些对象和能力,但是随着版本的推进,对象可能会发生变更,但是对于 kubernetes 来说,版本升级是需要向前兼容的,那么就需要一种机制来实现向前兼容,就是版本转换。前面说的 v1alha1 是 External version,是面向 kubernetes 集群外部的,即作为 apiserver 客户端访问 apiserver 时,都是使用的 External version,在 kubernetes 内部,这个叫做 Internel version,在外部发过来的 External version 存储 etcd 之前,都会将其转换为 Internel version,其实在 etcd 中还有一个 storage version。做对象转换时要实现一个方法 Conversion,这里面定义的是从 Externel version 到 Internel version 是怎么转换的,同样的,其会有一个反向的 Conversion,从 Internal version 往不同的 Externel version 转换是怎么做的,这样就做到了对多个版本的兼容。

  1、如何定义Group

    在不同的 group 里面都有一个 pkg/apis/core/register.go

# 定义 groupversion,定义了group name 和 版本
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionlnternal}
# 定义 SchemeBuilder
var (
    SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
    AddToScheme = SchemeBuilder.AddToScheme
)
# 将对象加入SchemeBuild
func addKnownTypes(scheme *runtime.Scheme) error (
    if err := scheme.AddlgnoredConversionType(&metav1.TypeMeta{}, &metav1 .TypeMeta{}); err != nil {
        return err
    }
   # 定义有哪些已知对象 scheme.AddKnownTypes(SchemeGroupVersion,
&Pod{}, &PodList{}, }}

  定义对象类型 types.go:对象分为 List 对象和单一对象

    List 对象就是有一个标准的 List,比如 Pod 有 Pod List,Service 有 Service List,他们的属性都是一样的,只有一个 Atom 的数组,数组中存储的就是一个个的 Pod 或 Service 对象,所以他只是用来做汇总的;

    单一对象的数据结构包括:TypeMeta、ObjectMeta、Spec、Status

  2、定义 Tags

    在 kubernetes 源码中有 core/types.go、v1/types.go 等,core 目录的是 Internal version,v1 目录下的是 Externel version,v1 目录中的对象是带有 json tag 和 protobuf tag 的,go 语言可以通过反射机制来读取某一个属性的 tag,那么 json tag 就是在约定 json 里面属性所对应的名字可能是什么。

    任何的对象我们都是会有一些通用的需求,例如第一,定义数据结构,第二,需要一些通用访问这些数据的方法, kubernetes 就提供了一个项目叫 Code Generator 去做代码生成,让我们只需要关注对象设计,只把 strust 设计好就可以,定义好之后,通过 Code Generator 就可以把怎么访问 Pod 的 API 客户端生成。

    那 Code Generator 是如何工作的呢,实际在我们写代码的时候,会定义一个带着 "+" 的注释,就是 tag,CodeGenerator 会来读这些 tag,基于 tag 里面定义的属性来决定这个工具的方法和这个 Library 如何生成,tag 包括 Global tag 和 local tag。

    Group tag 是对整个 group 生效的,如下面的代码表示当前 package 里面,所有的对象都要生成 deepcopy 的方法,deep-copy 方法表示深拷贝的方法。

GlobalTags,定义在doc.go中
// +k8s:deepcopy-gen=package

    local tag:针对每一个对象都有一个 local tag,// +gendient 表示要生成 clientset,但是也有一些区分,例如 nonNamespaced 表示非 namespace,即全局对象,noVerbs 表示不加任何方法,onlyVerbs 表示只创建哪些方法,skipVerbs 表示不创建哪些方法

Local Tags,定义在types.go中的每个对象里
// +k8s:deepcopy-gen:interfaces=k8sjo/apimachinery/pkg/runtime.Object
// +gendient
// +genclient:nonNamespaced
// +genclient:noVerbs
// +genclient:onlyVerbs=create,delete
// +genclient:skipVerbs=get,list,create,update,patch,delete,deleteCollection,watch
//+genclient:method=Create7verb=create7result=k8s.io/apimachinery/pkg/apis/meta/v1.Status

   3、实现 etcd storage

    对于任何的对象都要有一份存储的实现,例如 configmap,这种对象都会有一个 storage.go,storage.go 中最重要的是定义了 CreateStrategy、UpdateStrategy、DeleteStrategy,这里面定义了对象的新增、修改、删除要采用什么样的策略。还有一个比较重要的是 StoreOptions,APIServer 本身使用了一个 RingBuffer 来做数据的缓存,对于缓存数据的入口就是 StoreOptions。

pkg/registry/core/configmap/storage/storage.go
func NewREST(optsGetter generic.RESTOptionsGetter) *REST {
    store := &genericregistry.Store{
        NewFunc: func() runtime.Object (return &api.ConfigMap{} },
        NewListFunc: func() runtime.Object (return &api.ConfigMapList{} },
        DefaultQualifiedResource: api.Resource(nconfigmapsn),
        CreateStrategy: config map.Strategy,
        UpdateStrategy: config map.Strategy,
        DeleteStrategy: config map.Strategy,
        TableConvertor: printerstorage.TableConvertor(TableGenerator: printers.NewTableGenerator().With(printersinternaLAddHandlers)},
    {
    options := &generic.StoreOptions(RESTOptions: opts Getter}
    if err := store.CompleteWithOptions(options); err != nil {
        panic(err) // TODO: Propagate error up
    }
    return &REST{store}
}

  4、创建和更新对象时的业务逻辑-Strategy

    其实就是定义在真正创建的时候或者更新的时候要做哪些准备工作。

func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
    _= obj.(*api.ConfigMap)
}
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
    cfg := obj.(*api.ConfigMap)
    return validation.ValidateConfigMap(cfg)
}
func (strategy) PrepareForllpdate(ctx context.Context, newObj, oldObj runtime.Object) { 
    _= oldObj.(*api.ConfigMap)
    _= newObj.(*api.ConfigMap)
}

  5、subresource:

    什么是subresource,内嵌在Kubernetes对象中,有独立的操作逻辑的属性集合,也就是在对象中嵌套了一个子对象,如 podstatus。

statusStore.UpdateStrategy = pod.Statusstrategy 
var Statusstrategy = podStatusStrategy(Strategy}
func (podStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
    newPod := obj.(*api.Pod)
    oldPod := old.(*api.Pod)
    newPod.Spec = oldPod.Spec
    newPod. DeletionTimestamp = nil
    // don't allow the pods/status endpoint to touch owner references since old kubelets corrupt them in a way
    // that breaks garbage collection
    newPod.OwnerReferences = oldPod.OwnerReferences
}

  6、注册 APIGroup

# 定义 Storage
configMapStorage := config mapstore.NewREST(restOptionsGetter)
restStorageMap := map[string]rest.Storage{
    "config Maps": configMapStorage,
}
# 定义对象的StorageMap
apiGrouplnfo.VersionedResourcesStorageMap["v1"] = restStorageMap
# 将对象注册至API Server (挂载handler)
if err := m.GenericAPIServer.lnstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGrouplnfo); err != nil {
    klog.Fatalf(nError in registering group versions: %v, err
}

  7、代码生成:

    deepcopy-gen:为对象生成 DeepCopy 方法,用于创建对象副本。

    client-gen:创建 Clientset,用于操作对象的 CRUD。

    informer-gen:为对象创建 Informer 框架,用于监听对象变化。

    lister-gen:为对象构建 Lister 框架,用于为 Get 和 List 操作,构建客户端缓存。

    coversion-gen:为对象构建 Conversion 方法,用于内外版本转换以及不同版本号的转换。

    参考链接:httDS://github・com/kubernetes/code-generator

  8、hack/update-codegen.sh:

依赖
BUILD_TARGETS=(
    vendor/k8s.io/code-generator/cmd/client-gen 
    vendor/k8s.io/code-generator/cmd/lister-gen 
    vendor/k8s.io/code-generator/cmd/informer-gen
)
生成命令
$(GOPATH}/bin/deepcopy-gen --input-dirs (versioned-package-pach}
-O zz_generated.deepcopy \
--bounding-dirs (output-package-path} \
--go-header-file $(SCRIPT_ROOT}/hack/boilerplate.go.txt

 

 

 

 

posted @ 2022-11-29 18:47  李聪龙  阅读(592)  评论(0编辑  收藏  举报