当前位置:   article > 正文

k8s API Server 中的认证、鉴权、准入、限流总结分享_kubernetes 超时流量控制

kubernetes 超时流量控制

概述

kube-apiserver 是k8s 最重要的控制组件之一,主要提供以下功能:

  • 提供集群管理的REST API 接口, 包括认证授权、数据校验以及集群状态变更等
  • k8s 中所有模块与 etcd 的数据交互都需要走 API Server ,禁止直接和 etcd 通信

API Server 请求流程概览
k8s API 的每个请求都需要经过多阶段的访问控制后才会被接受,包括认证、授权以及准入控制等:
image.png

  • 每一个API 请求都会有一个对应的 handler 处理,并在访问控制的部分阶段对外提供了 webhook , 可以根据用户自身的需求来对接校验,比如说公司有自己的 LDAP 或者 RBAC 等统一权限控制系统,就可以通过自己实现 webhook 去进行控制。

API Server 访问控制细节
image.png
访问控制的细节还是比较复杂的:

  • panic recover : k8s 是用 Go 语言写的,API Server 其实就是启动了一个 http 服务, 当来一个 REST 请求就会启动一个 goroutine 来对应处理一系列逻辑,为了保证 API Server 的稳定运行,就使用了异常恢复机制。
  • request-timeout : 请求超时限制。
  • authentication :认证。
  • audit : 审计,对用户的请求动作进行审计,方便日后出现问题进行排查。
  • impersonation : 将用户信息放入请求 header 中,方便后续进行权限校验。
  • max-in-flight : 限流。
  • authorization : 鉴权。
  • kube-aggregator & CRDs : 读取kubectl 配置文件判断后续的控制流程是走自定义控制对象(aggregated apiservers)、还是 k8s 本身默认控制对象(mux)。
  • resource handler :resource handler 是对资源对象的真正操作处理,假设此次请求为对资源对象 pod 的操作。
    • decoding: 需要将 json 请求体反序列化为 Go 结构体,k8s 中的每一个资源对象都是一个结构体。

    • request conversion:将请求对象转换为内部对象进行后续处理,k8s 中的对象分为外部对象(external version)和内部对象(internal version)。

      通过 decoding 和 conversion 就将请求json 转换为了 Pod 资源对象进行处理,当处理完毕后在通过encoding 和 conversion 转换为外部对象返回。

    • admission: 准入控制,会先将请求 Schema 对象进行变形(mutating) , 变成符合后续逻辑处理的结构体对象,然后进行合法校验。

      其中提供了mutating 和 validating 的 webhook 用户可以实现这些 webhook 进行自定义准入控制。

    • REST logic : 对资源对象具体的操作逻辑,有些操作动作有预处理逻辑,比如说更新或者创建 Pod。

    • storage conversion: 存储转换,转换为 etcd 的存储格式存储。

认证

k8s的请求有两种模式:
非安全模式insecure-port

  • 该模式下所有请求都不会经过认证,不建议开启。

安全模式(secure-port)

  • 该模式下开启TLS时,所有请求都需要经过认证。k8s 支持多种认证机制,并支持同时开启多个认证插件(只要有一个认证通过即可)。
    如果认证成功,则用户的username 会传入授权模块进一步授权验证;对于失败认证将返回401状态码。

认证插件

x509 证书

  • 就是常说的ca证书, 需要在 API Server 启动时配置 --client-ca-file=SOMEFILE。

静态 Token 文件

  • 需要在 API Server 启动时配置 --token-auth-file=SOMEFILE。
  • 该文件为CSV文件,每行至少包括三列 token, username, user id。

引导 Token

  • 为了支持平滑的启动引导新的集群,k8s 包含了一种动态管理持有者令牌类型,称作启动引导令牌(Bootstrap Token)。
  • 这些令牌以 Secret 的形式保存在 kube-system 命名空间中,可以被动态管理和创建。
  • 控制器管理器包含的 TokenCleaner 控制器能够在启动引导令牌过期时将其删除。
  • 在使用 kubeadn 部署 k8s 时,可通过 kubeadm token list 命令查询。

静态密码文件

  • 需要在 API Server 启动时配置 --basic-auth-file=SOMEFILE, 文件格式为 csv,每行至少三列 password, user, uid

ServiceAccount

  • sa(ServiceAccount) 是 k8s 比较常见的一种认证方式,每一个命名空间在创建的时候都会有一个默认的 sa, 每个 sa 都包含对应的 token。

    sa 是 k8s 自动生成的,并会自动挂载到容器的 /run/secret/kubenetes.io/serviceaccount 目录中。

OpenID

  • OAuth 2.0 的认证机制。

Webhook 令牌身份认证

  • –authentication-token-webhook-config-file 指向一个配置文件,描述如何访问远程的 webhook 服务。
  • –authentication-token-webhook-cache-ttl 用来设定身份认证决定的缓存时间。默认时长为 2 分钟。

匿名请求

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

基于静态token的认证服务实践

下边所有操作都是在** root 用户**下操作。

  1. 在master 节点上创建存放静态 token 文件的目录

    mkdir -p /etc/kubernetes/auth
    vim /etc/kubernetes/auth/static-token
    
    • 1
    • 2
    admin-token,admin,1005
    
    • 1

    需要注意static-token 中配置的用户名和用户id 一定要是存在的,否则apiserver 启动不了:
    image.png

  2. 备份原有 apiserver 配置文件

    cp /etc/kubernetes/manifests/kube-apiserver.yaml ~/kube-apiserver.yaml.$(date +%F)
    
    • 1
  3. 修改 apiserver 配置文件将静态 token 路径配置进去 vim /etc/kubernetes/manifests/kube-apiserver.yaml

    apiVersion: v1
    kind: Pod
    metadata:
      annotations:
        kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 192.168.146.189:6443
      creationTimestamp: null
      labels:
        component: kube-apiserver
        tier: control-plane
      name: kube-apiserver
      namespace: kube-system
    spec:
      containers:
      - command:
        - kube-apiserver
        ......
        # 配置静态token
        - --token-auth-file=/etc/kubernetes/auth/static-token
        image: registry.k8s.io/kube-apiserver:v1.25.4
        imagePullPolicy: IfNotPresent
        ......
        # 将静态 token 文件挂载进容器
        - mountPath: /etc/kubernetes/auth
          name: auth-files
          readOnly: true
      hostNetwork: true
      priorityClassName: system-node-critical
      securityContext:
        seccompProfile:
          type: RuntimeDefault
      volumes:
      ......
      # 将静态 token 文件挂载进容器
      - hostPath:
          path: /etc/kubernetes/auth
          type: DirectoryOrCreate
        name: auth-files
    status: {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 在apiserver 启动命令中添加 --token-auth-file=/etc/kubernetes/auth/static-token
    • 将静态 token 文件挂载进容器
  4. 发送curl 请求验证静态 token 认证是否成功

    [admin@ali-jkt-dc-bnc-airflow-test02 ~]$   curl https://192.168.146.189:6443/api/v1/namespaces/default -H "Authorization: Bearer admin-token" -k
    {
      "kind": "Status",
      "apiVersion": "v1",
      "metadata": {},
      "status": "Failure",
      "message": "namespaces \"default\" is forbidden: User \"admin\" cannot get resource \"namespaces\" in API group \"\" in the namespace \"default\"",
      "reason": "Forbidden",
      "details": {
        "name": "default",
        "kind": "namespaces"
      },
      "code": 403
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    发现返回的是403 权限校验失败,说明认证是已经通过了的。

基于X509证书认证实践

  1. 创建私钥

    openssl genrsa -out myuser.key 2048
    openssl req -new -key myuser.key -out myuser.csr
    
    • 1
    • 2
  2. 进行base64编码

    cat myuser.csr | base64 | tr -d "\n"
    
    • 1
  3. 创建k8s csr

    cat <<EOF | kubectl apply -f -
    apiVersion: certificates.k8s.io/v1
    kind: CertificateSigningRequest
    metadata:
      name: myuser
    spec:
      request: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlJQ3ZqQ0NBYVlDQVFBd2VURUxNQWtHQTFVRUJoTUNRMDR4RGpBTUJnTlZCQWdNQlhkMWFHRnVNUTR3REFZRApWUVFIREFWM2RXaGhiakVQTUEwR0ExVUVDZ3dHZW1odmRYcDVNUTh3RFFZRFZRUUxEQVo2YUc5MWVua3hEekFOCkJnTlZCQU1NQm5wb2IzVjZlVEVYTUJVR0NTcUdTSWIzRFFFSkFSWUlZVUJ4Y1M1amIyMHdnZ0VpTUEwR0NTcUcKU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRGVmOFZpL3d0MGY1TmczejRyOEdMK0x5bHNnMGhhQnVoMAphTjhFMkRNcVhURGZSc3hweVRoc25Iak5CeW9zZUhtRDEybnI0WHVXTlJUWjVCYjQ1czNidnlSMWV6ZnJ5VzgyCklRTlRxWXJKczhoNEhuQnVmSnRHSTIwbkJSR0Y5eDI0a2JWL0lCMm9lano5WFp3bE9wbXpKVjkxUjZrQnZ1ZjYKRWliL3FCVk5SUzFwUGVzYi90bW0zekZnYkY4NUR4ZkFGOU9SdWgveWdSNHlNNHR1a01pekZLMHBEVzA4UDFYeAprVmpSUG1iTnY2K3ptcE1IZmJxNzBvKzlwRlhVcVd3dSsxUG5DS0E3M2F2RExEQkdkVHl4TXMyYlpaanU3N1gzClI2Q21HcytQWkdSKzZtTkVFbllBYmJBbGNLdHJLYzJPWDRtUElEazhHNlpVQnFRS2lmVGhBZ01CQUFHZ0FEQU4KQmdrcWhraUc5dzBCQVFzRkFBT0NBUUVBVEZjSkN5Mm95TmtMUTZUUjhHbVk1UXJwUDZEY01IRHNEL2NDTk9kNQpuNVZJSzlBSWVtVlgzWVZsTENGeTdwSHRoaXd0Vm9EUHJ2enJhMnpSU296bDF1UTU1dXU2U3R0K0VRdU9OSzFVCm5SRzh2Rysydi9ZM3ZlQVBWZzU1WTRjMUNKYXZHWTAxSjlTbGFhN3pwVGpmN3hjc1NFRUpTYzFkTFpzNHBoTkIKQUtBT0IxYVRrdWZVaHBCdk9rT2plRGE3K0JxdktVZmdOR1NEaHNuNXF3OXhYT0ZzT1JNaHpBRUVUaGg1QTFKWgpKVVNuMWo4YVdZRHFnODREY3hQWTdvbjB5cUpnR0FFNzl2UmtrNmV3Qk9ITi9WdUhqdVdPREVsWlRvaGkvZVhYCkI1WEo2SkJlTllGc0tKUTZyOXN6R2xqOGtDWVpJMkt1UnF4QktkbzRLT2VSVXc9PQotLS0tLUVORCBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0K
      signerName: kubernetes.io/kube-apiserver-client
      expirationSeconds: 86400  # one day
      usages:
      - client auth
    EOF
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    创建完后的csr 处于pending 状态,该证书还未被批准:
    image.png

  4. 批准CSR

    kubectl certificate approve myuser
    
    • 1

    批准完的证书如下:
    image.png

  5. 导出证书

    kubectl get csr myuser -o jsonpath='{.status.certificate}'| base64 -d > myuser.crt
    
    • 1
  6. 创建用户myuser绑定证书

    kubectl config set-credentials myuser --client-key=myuser.key --client-certificate=myuser.crt --embed-certs=true
    kubectl config set-context myuser --cluster=kubernetes --user=myuser
    
    • 1
    • 2
  7. 测试认证是否通过

    [admin@ali-jkt-dc-bnc-airflow-test02 ~]$ kubectl get pod --user=myuser
    Error from server (Forbidden): pods is forbidden: User "zhouzy" cannot list resource "pods" in API group "" in the namespace "default"
    
    • 1
    • 2

    通过返回的信息可以发现认证已经通过,但是权限校验没有通过。

  8. 创建角色并授权

    kubectl create role developer --verb=create --verb=get --verb=list --verb=update --verb=delete --resource=pods
    kubectl create rolebinding developer-binding-myuser --role=developer --user=myuser
    
    • 1
    • 2

    在通过myuser 访问可以发现鉴权通过:
    image.png

基于webhook认证实践

自定义认证服务规范

  • URL:https://authn.example.com/authenticate

  • Method: POST

  • Input:

    k8s 会将 token 信息以TokenReview对象发送至自定义认证服务

    {
    	"apiVersion": "authentication.k8s.io/v1beta1",
    	"kind": "TokenReview",
    	"spec": {
    		"token": "(BEARERTOKEN)"
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  • Output:

    • 认证服务接收到TokenReview 后可以解析出token, 拿到token 后我们就可以解析 token 来进行认证,一般都是集成公司自己的 LDAP 进行认证,但是为了简单,这里使用 github 生成用户token 信息,然后将token 配置到k8s, k8s 再拿该 token 通过 webhook 认证服务去认证 。
    • 自定义认证服务校验通过后,需要将认证结果信息返回给 k8s
    {
    	"apiVersion": "authentication.k8s.io/v1beta1",
    	"kind": "TokenReview",
    	"status": {
      	"authenticated": true,
      	"user": {
        	"username": "zhouzy@qq.com",
        	"uid": "42",
        	"groups": ["dev", "qa"]
      	}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

自定义认证服务代码

package main

import (
	"context"
	"encoding/json"
	"log"
	"net/http"

	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
	authentication "k8s.io/api/authentication/v1beta1"
)

func main() {
	http.HandleFunc("/authenticate", func(w http.ResponseWriter, r *http.Request) {
		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
		}
		log.Print("receving request")
		// Check User
		ts := oauth2.StaticTokenSource(
			&oauth2.Token{AccessToken: tr.Spec.Token},
		)
		tc := oauth2.NewClient(context.Background(), 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
		}
		log.Printf("[Success] login as %s", *user.Login)
		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,
		})
	})
	log.Println(http.ListenAndServe(":3000", nil))
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

运行认证服务

  1. 编译运行认证服务

    go mod init 当前目录
    go mod tidy
    go run main.go
    
    • 1
    • 2
    • 3
  2. 创建 webhook config

    vim /etc/kubernetes/webhook/webhook-config.json
    
    • 1

    配置文件如下,需要将ip 替换为启动认证服务所在机器 ip (最好不要在master 机器上)

    {
      "kind": "Config",
      "apiVersion": "v1",
      "preferences": {},
      "clusters": [
        {
          "name": "github-authn",
          "cluster": {
            "server": "http://192.168.146.188:3000/authenticate"
          }
        }
      ],
      "users": [
        {
          "name": "authn-apiserver",
          "user": {
            "token": "secret"
          }
        }
      ],
      "contexts": [
        {
          "name": "webhook",
          "context": {
            "cluster": "github-authn",
            "user": "authn-apiserver"
          }
        }
      ],
      "current-context": "webhook"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
  3. 备份旧apiserver yaml 文件

    sudo cp /etc/kubernetes/manifests/kube-apiserver.yaml ~/kube-apiserver.yaml.$(date +%F)
    
    • 1
  4. 修改 apiserver yaml 文件

    sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml
    
    • 1

    启动命令增加 webhook 配置, 并且将配置文件挂载至容器内部,下边是我的k8s 的apiserver 的yaml 配置文件,需要注意应该修改自己原有的apiserver yaml 不能直接复制下边的。

    apiVersion: v1
    kind: Pod
    metadata:
      annotations:
        kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 192.168.146.189:6443
      creationTimestamp: null
      labels:
        component: kube-apiserver
        tier: control-plane
      name: kube-apiserver
      namespace: kube-system
    spec:
      containers:
      - command:
        ......
        # 配置 webhook 认证
        - --authentication-token-webhook-config-file=/etc/kubernetes/webhook/webhook-config.json
        image: registry.k8s.io/kube-apiserver:v1.25.4
        imagePullPolicy: IfNotPresent
        ......
        # 将 webhook 配置文件挂载进容器
        - name: webhook-config
          mountPath: /etc/kubernetes/webhook
          readOnly: true
      hostNetwork: true
      priorityClassName: system-node-critical
      securityContext:
        seccompProfile:
          type: RuntimeDefault
      volumes:
      ......
     # 将 webhook 配置文件挂载进容器
      - hostPath:
          path: /etc/kubernetes/webhook
          type: DirectoryOrCreate
        name: webhook-config
    status: {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
  5. 在 github 上生成 token

image.png

  1. 配置kubeconfig,添加user

    # cat ~/.kube/config 
    apiVersion: v1
    ......
    users:
    - name: zhouzy-token
      user:
        token: ghp_jevHquU4g43m46nczWS0ojxxxxxxxxx
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
  2. 验证认证是否通过

    发现认证已经通过,但是没有权限查看
    image.png
    在查看下认证服务日志, 发现登录github 成功, 说明认证成功:

    [admin@ali-jkt-dc-bnc-airflow-test01 auth]$ go run main.go 
    2023/02/03 02:34:25 receving request
    2023/02/03 02:34:25 [Success] login as itnoobzzy
    
    • 1
    • 2
    • 3

鉴权

k8s 支持多种授权机制,并支持同时开启多个授权插件(只要有一个验证通过即可)。
如果鉴权成功的请求将被发送到准入模块做进一步的请求验证;鉴权失败的请求则返回403。

鉴权对象

  • 用户信息:user, group, extra
  • API、请求方法(get, post 等)和请求路径(如/api)
  • 请求资源和子资源
  • Namespace
  • API Group

鉴权插件

  • ABAC
  • RBAC
  • Webhook
  • Node

RABC(基于角色的权限控制),是目前比较流行的鉴权方式,可以将权限和角色绑定,然后将角色分配给用户,这样用户就可以自己进行授权。
casbin 是Go语言目前比较流行的鉴权通用框架。

k8s 中 RBAC 的使用

  • k8s 中的 RBAC 主要包括两种大类型,一种是基于集群的,权限将作用于整个集群的 Namespaces; 另一种是基于 Namespace 的,权限将作用于当前 Namespace

image.png

  • Role 只能作用于单个ns, ClusterRole 可以作用于多个 ns 和集群级的资源

    image.png

  • 角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组鉴权对象,该鉴权对象可以是用户,组,或者 ServiceAccount.

    image.png
    eg: 将 “pod-reader” 角色与 “default” namespace 绑定 ,并将该权限授予给"jane"

    apiVersion: rbac.authorization.k8s.io/v1
    # This role binding allows "jane" to read pods in the "default" namespace.
    # You need to already have a Role named "pod-reader" in that namespace.
    kind: RoleBinding
    metadata:
      name: read-pods
      namespace: default
    subjects:
    # You can specify more than one "subject"
    - kind: User
      name: jane # "name" is case sensitive
      apiGroup: rbac.authorization.k8s.io
    roleRef:
      # "roleRef" specifies the binding to a Role / ClusterRole
      kind: Role #this must be Role or ClusterRole
      name: pod-reader # this must match the name of the Role or ClusterRole you wish to bind to
      apiGroup: rbac.authorization.k8s.io
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • ClusterRoleBinding 是对整个集群的授权

    eg: 授权 “manager” group 组下的所有用户对集群所有 ns 的 secret-reader 权限:

    apiVersion: rbac.authorization.k8s.io/v1
    # This cluster role binding allows anyone in the "manager" group to read secrets in any namespace.
    kind: ClusterRoleBinding
    metadata:
      name: read-secrets-global
    subjects:
    - kind: Group
      name: manager # Name is case sensitive
      apiGroup: rbac.authorization.k8s.io
    roleRef:
      kind: ClusterRole
      name: secret-reader
      apiGroup: rbac.authorization.k8s.io
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 当对组进行授权的时候,subjects name 有规定的前缀写法:需要注意的是我们在定义对象名称的时候要避免和 k8s 内置的 name 冲突

    对 qa ns 下的所有 sa 进行授权

    subjects:
    - kind: Group
      name: system:serviceaccounts:qa
      apiGroup: rbac.authorization.k8s.io
    
    • 1
    • 2
    • 3
    • 4

    对所有 ns 下的 sa 进行授权

    subjects:
    - kind: Group
      name: system:serviceaccounts
      apiGroup: rbac.authorization.k8s.io
    
    • 1
    • 2
    • 3
    • 4

    对所有认证的用户进行授权

    subjects:
    - kind: Group
      name: system:authenticated
      apiGroup: rbac.authorization.k8s.io
    
    • 1
    • 2
    • 3
    • 4

    对所有未认证的用户进行授权

    subjects:
    - kind: Group
      name: system:unauthenticated
      apiGroup: rbac.authorization.k8s.io
    
    • 1
    • 2
    • 3
    • 4

    对所有用户进行授权

    subjects:
    - kind: Group
      name: system:authenticated
      apiGroup: rbac.authorization.k8s.io
    - kind: Group
      name: system:unauthenticated
      apiGroup: rbac.authorization.k8s.io
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

授权实践

上边通过 webhook 实现了结合第三方 github 认证, 但是我们还没有对其进行授权,接下来对其进行授权。
可以先看下集群默认管理员角色的权限:

[admin@ali-jkt-dc-bnc-airflow-test02 ~]$ kubectl get clusterrole cluster-admin -oyaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
  creationTimestamp: "2022-11-15T05:29:14Z"
  labels:
    kubernetes.io/bootstrapping: rbac-defaults
  name: cluster-admin
  resourceVersion: "72"
  uid: 9b2bce8b-ef75-4714-a65f-72276e7c480e
rules:
- apiGroups:
  - '*'
  resources:
  - '*'
  verbs:
  - '*'
- nonResourceURLs:
  - '*'
  verbs:
  - '*'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

解下来就需要将该角色与上边创建的已认证用户 zhouzy-token 进行绑定:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: default-admin
  namespace: default
subjects:
  - kind: User
  # 基于 webhook 认证的用户,这里需要注意的是,这里的用户是登录 github 的用户
    name: itnoobzzy		
    apiGroup: rbac.authorization.k8s.io
#  - kind: ServiceAccount
#    name: default
#    namespace: kube-system
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

验证:

[admin@ali-jkt-dc-bnc-airflow-test02 auth]$ kubectl get pod --user=zhouzy-token
NAME                           READY   STATUS    RESTARTS   AGE
host-pvc-pod                   1/1     Running   0          81d
nfs-static-pod                 1/1     Running   0          81d
ngx-dep-6b9d9dd879-hwt6j       1/1     Running   0          77d
ngx-dep-6b9d9dd879-jn9bc       1/1     Running   0          77d
ngx-dep-6b9d9dd879-pm4vh       1/1     Running   0          77d
ngx-hpa-dep-75b9d99c9b-djcwh   1/1     Running   0          77d
ngx-hpa-dep-75b9d99c9b-nvbwk   1/1     Running   0          77d
test                           1/1     Running   0          77d
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

准入

请求经过认证,鉴权后的最后一步就是准入,请求拥有权限不代表是合法的,比如说当资源有限的时候该如何进行配额管理。

准入控制在授权后对请求做进一步的验证或添加参数。

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

场景

为资源增加自定义属性
准入控制一般都会先将请求变形,为请求增加一些自定义属性,比如说只有当namespace中有有效用户信息时,才可以在创建 namespace 时,自动绑定用户权限,namespace 才可用。

  • 作为多租户集群方案中的一环,我们需要在namespace的准入控制中,获取用户信息,并将用户信息更新到namespace的annotation中。

配额管理
资源有限,如何限定某个用户有多少资源?

  • 预定义每个namespace的 ResourceQuota, 并把 spec 保存为 configmap;
    • 用户可以创建多少个Pod
    • 用户可以创建多少个service
    • 用户可以创建多少个ingress
    • 用户可以创建多少个service VIP
  • 创建 ResourceQuota Controller: 监控namespace创建事件,当namespace创建时,在该namespace创建对应的ResourceQuota 对象。
  • apiserver 中开启 ResourceQuota 的 admission plugin。

配额管理实践

限制在 default namespace 下最多可以创建3个configmap
创建quota.yaml 并生成对应的 RQ 对象:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: object-counts
  namespace: default
spec:
  hard:
    configmaps: "3"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

查看 default ns 下已有两个configmap:

[admin@ali-jkt-dc-bnc-airflow-test02 k8s]$ kubectl get configmap
NAME               DATA   AGE
kube-root-ca.crt   1      84d
ngx-conf           1      84d
  • 1
  • 2
  • 3
  • 4

准备两个新的configmap yaml 文件生成configmap 对象:

apiVersion: v1
data:
  default.conf: |
    server {
      listen 80;
      location / {
        default_type text/plain;
        return 200
          'srv : $server_addr:$server_port\nhost: $hostname\nuri : $request_method $host $request_uri\ndate: $time_iso8601\n';
      }
    }
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"default.conf":"server {\n  listen 80;\n  location / {\n    default_type text/plain;\n    return 200\n      'srv : $server_addr:$server_port\\nhost: $hostname\\nuri : $request_method $host $request_uri\\ndate: $time_iso8601\\n';\n  }\n}\n"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"ngx-conf","namespace":"default"}}
  creationTimestamp: "2022-11-15T15:33:05Z"
  name: ngx-conf1
  namespace: default
  resourceVersion: "47125"
  uid: 3e3e39f0-d1ca-47d7-9a48-734e46d75ccb
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
apiVersion: v1
data:
  default.conf: |
    server {
      listen 80;
      location / {
        default_type text/plain;
        return 200
          'srv : $server_addr:$server_port\nhost: $hostname\nuri : $request_method $host $request_uri\ndate: $time_iso8601\n';
      }
    }
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"default.conf":"server {\n  listen 80;\n  location / {\n    default_type text/plain;\n    return 200\n      'srv : $server_addr:$server_port\\nhost: $hostname\\nuri : $request_method $host $request_uri\\ndate: $time_iso8601\\n';\n  }\n}\n"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"ngx-conf","namespace":"default"}}
  creationTimestamp: "2022-11-15T15:33:05Z"
  name: ngx-conf2
  namespace: default
  resourceVersion: "47125"
  uid: 3e3e39f0-d1ca-47d7-9a48-734e46d75ccb
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

如果依次生成按照预期应该是只能在创建一个,另一个会创建失败,验证果然在创建第4个configmap 的时候创建失败了:
image.png
此时可以查看下rq 对象:

[admin@ali-jkt-dc-bnc-airflow-test02 k8s]$ kubectl get resourcequota -o yaml
apiVersion: v1
items:
- apiVersion: v1
  kind: ResourceQuota
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"v1","kind":"ResourceQuota","metadata":{"annotations":{},"name":"object-counts","namespace":"default"},"spec":{"hard":{"configmaps":"3"}}}
    creationTimestamp: "2023-02-08T00:54:11Z"
    name: object-counts
    namespace: default
    resourceVersion: "9976427"
    uid: e1217bcf-8317-498b-8ac1-f1e90bf055b2
  spec:
    hard:
      configmaps: "3"
  status:
    hard:
      configmaps: "3"
    used:
      configmaps: "3"
kind: List
metadata:
  resourceVersion: ""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

spec 是我们设置的资源限额,status 是对应 ns 中的资源使用情况。

插件

社区目前已经有很多准入插件,下边是一些常用的插件:
AlwaysAdmit: 接收所有请求
AlwaysPullImages: 总是拉取最新镜像,在多租户场景下非常有用。
AlwaysPullImages: 通过 webhook 决定 image 策略,需要同时配置 --admission-control-config-file。
ServiceAccount: 自动创建默认 ServiceAccount, 并确保 Pod 引用的 ServiceAccount 已经存在。
SecurityContextDeny: 拒绝包含非法 SecurityContext 配置的容器。
ResourceQuota:限制 Pod 的请求不会超过配额,需要在 namespace 中创建一个 ResourceQuota 对象。
LimitRanger:为 Pod 设置默认资源请求和限制,需要在 namespace 中创建一个 LimitRange 对象。
InitialResource:根据镜像的历史使用记录,为容器设置默认资源请求和限制。
NamespaceLifecycle:确保处于 termination 状态的 namespace 不再接收新的对象创建请求,并拒绝请求不存在的 namespace。
DefaultStorageClass:为PVC 设置默认 StorageClass。
DefaultTolerationSeconds: 设置 Pod 的默认forgiveness toleration 为5分钟。
查看已有的默认插件配置:

[admin@ali-jkt-dc-bnc-airflow-test02 ~]$ kubectl -n kube-system get pod
NAME                                                    READY   STATUS    RESTARTS         AGE
coredns-565d847f94-46b8q                                1/1     Running   0                84d
coredns-565d847f94-slw2m                                1/1     Running   0                84d
etcd-ali-jkt-dc-bnc-airflow-test02                      1/1     Running   0                84d
kube-apiserver-ali-jkt-dc-bnc-airflow-test02            1/1     Running   0                4d23h
kube-controller-manager-ali-jkt-dc-bnc-airflow-test02   1/1     Running   14 (4d23h ago)   84d
kube-proxy-48k6j                                        1/1     Running   0                84d
kube-proxy-grdwp                                        1/1     Running   0                21d
kube-proxy-m5m24                                        1/1     Running   0                84d
kube-scheduler-ali-jkt-dc-bnc-airflow-test02            1/1     Running   13 (4d23h ago)   84d
metrics-server-d55786594-7wr2c                          1/1     Running   0                78d


[admin@ali-jkt-dc-bnc-airflow-test02 ~]$ kubectl -n kube-system exec -it kube-apiserver-ali-jkt-dc-bnc-airflow-test02 -- kube-apiserver -h
The Kubernetes API server validates and configures data
for the api objects which include pods, services, replicationcontrollers, and
......
      --disable-admission-plugins strings                                                                                                                                                                                                    
                admission plugins that should be disabled although they are in the default enabled plugins list (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, PodSecurity, Priority, DefaultTolerationSeconds,
                DefaultStorageClass, StorageObjectInUseProtection, PersistentVolumeClaimResize, RuntimeClass, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, MutatingAdmissionWebhook,
                ValidatingAdmissionWebhook, ResourceQuota). Comma-delimited list of admission plugins: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass,
                DefaultStorageClass, DefaultTolerationSeconds, DenyServiceExternalIPs, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook,
                NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodSecurity, PodTolerationRestriction,
                Priority, ResourceQuota, RuntimeClass, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter.
      --enable-admission-plugins strings                                                                                                                                                                                                     
                admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, PodSecurity, Priority, DefaultTolerationSeconds, DefaultStorageClass,
                StorageObjectInUseProtection, PersistentVolumeClaimResize, RuntimeClass, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, MutatingAdmissionWebhook, ValidatingAdmissionWebhook,
                ResourceQuota). Comma-delimited list of admission plugins: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, DefaultStorageClass,
                DefaultTolerationSeconds, DenyServiceExternalIPs, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision,
                NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodSecurity, PodTolerationRestriction, Priority, ResourceQuota,
                RuntimeClass, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter.
......

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

插件开发

除了默认的准入控制插件以外,k8s 预留了准入控制插件的扩展点,用户可自定义准入控制插件实现自定义准入功能。
MutatingWebhookConfiguration: 变形插件,支持对准入对象的修改。
ValidatingWebhookConfiguration:校验插件,只能对准入对象合法性进行校验,不能修改。
image.png
自定义 webhook 与 apiserver 间的准入控制是通过 AdmissionReview 对象来交互的。

当配置了 MutatingWebhookConfiguration 或者 ValidatingWebhookConfiguration 后 k8s 会将整个Pod的请求对象AdmissionReview 发送至对应的 webhook 去进行变形或者校验。

需要注意的是所有的准入控制的 webhook server 端都必须是 https 的访问方式。

自定义插件说明

  • 作用:当创建Pod 时,会校验是否设置了 runAsNonRoot, 如果没有设置默认为False。

    当为True 时,容器的用户 id 不能为 0

  • 代码地址: https://github.com/cncamp/admission-controller-webhook-demo

  • 部署步骤

    # 拉取代码
    [admin@ali-jkt-dc-bnc-airflow-test02 k8s]$ git clone https://github.com/cncamp/admission-controller-webhook-demo.git
    Cloning into 'admission-controller-webhook-demo'...
    remote: Enumerating objects: 641, done.
    remote: Counting objects: 100% (63/63), done.
    remote: Compressing objects: 100% (37/37), done.
    remote: Total 641 (delta 33), reused 26 (delta 26), pack-reused 578
    Receiving objects: 100% (641/641), 2.43 MiB | 0 bytes/s, done.
    Resolving deltas: 100% (140/140), done.
    
    # 执行部署脚本
    [admin@ali-jkt-dc-bnc-airflow-test02 admission-controller-webhook-demo]$ ./deploy.sh 
    Generating TLS keys ...
    Generating a 2048 bit RSA private key
    .............................................................................+++
    ..+++
    writing new private key to 'ca.key'
    -----
    Generating RSA private key, 2048 bit long modulus
    ............................................................................+++
    .......+++
    e is 65537 (0x10001)
    Signature ok
    subject=/CN=webhook-server.webhook-demo.svc
    Getting CA Private Key
    Creating Kubernetes objects ...
    namespace/webhook-demo created
    secret/webhook-server-tls created
    deployment.apps/webhook-server created
    service/webhook-server created
    mutatingwebhookconfiguration.admissionregistration.k8s.io/demo-webhook created
    The webhook server has been deployed and configured!
    
    # 验证webhook 服务启动成功
    [admin@ali-jkt-dc-bnc-airflow-test02 admission-controller-webhook-demo]$ kubectl -n webhook-demo get pods
    NAME                             READY   STATUS    RESTARTS   AGE
    webhook-server-88ccccd9f-pbh56   1/1     Running   0          4m32s
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
  • 部署 runAsNonRoot nor runAsUser yaml 文件验证:

    # A pod with no securityContext specified.
    # Without the webhook, it would run as user root (0). The webhook mutates it
    # to run as the non-root user with uid 1234.
    apiVersion: v1
    kind: Pod
    metadata:
      name: pod-with-defaults
      labels:
        app: pod-with-defaults
    spec:
      restartPolicy: OnFailure
      containers:
        - name: busybox
          image: busybox
          command: ["sh", "-c", "echo I am running as user $(id -u)"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    [admin@ali-jkt-dc-bnc-airflow-test02 admission-controller-webhook-demo]$ kubectl create -f examples/pod-with-defaults.yaml
    pod/pod-with-defaults created
    
    # 发现 pod spec/securityContext 下多了runAsNonRoot 和 runAsUser 信息
    # 这就是经过自定义 mutatingwebhook 变形后的信息
    [admin@ali-jkt-dc-bnc-airflow-test02 admission-controller-webhook-demo]$ kubectl get pod/pod-with-defaults -o yaml
    apiVersion: v1
    kind: Pod
    metadata:
      creationTimestamp: "2023-02-09T01:03:27Z"
      labels:
        app: pod-with-defaults
      name: pod-with-defaults
      namespace: default
      resourceVersion: "10106854"
      uid: 8b83e295-c36c-48c6-ae67-9ad3573cc826
    spec:
      ......
      securityContext:
        runAsNonRoot: true
        runAsUser: 1234
      ......
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 部署一个可以用 root 运行的 pod:

    # A pod with a securityContext explicitly allowing it to run as root.
    # The effect of deploying this with and without the webhook is the same. The
    # explicit setting however prevents the webhook from applying more secure
    # defaults.
    apiVersion: v1
    kind: Pod
    metadata:
      name: pod-with-override
      labels:
        app: pod-with-override
    spec:
      restartPolicy: OnFailure
      securityContext:
        runAsNonRoot: false
      containers:
        - name: busybox
          image: busybox
          command: ["sh", "-c", "echo I am running as user $(id -u)"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    $ kubectl create -f examples/pod-with-override.yaml
    $ kubectl get pod/pod-with-override -o yaml
    ...
      securityContext:
        runAsNonRoot: false
    ...
    $ kubectl logs pod-with-override
    I am running as user 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 部署一个冲突的pod: 非root 运行但是用户id 为0的:

    # A pod with a conflicting securityContext setting: it has to run as a non-root
    # user, but we explicitly request a user id of 0 (root).
    # Without the webhook, the pod could be created, but would be unable to launch
    # due to an unenforceable security context leading to it being stuck in a
    # `CreateContainerConfigError` status. With the webhook, the creation of
    # the pod is outright rejected.
    apiVersion: v1
    kind: Pod
    metadata:
      name: pod-with-conflict
      labels:
        app: pod-with-conflict
    spec:
      restartPolicy: OnFailure
      securityContext:
        runAsNonRoot: true
        runAsUser: 0
      containers:
        - name: busybox
          image: busybox
          command: ["sh", "-c", "echo I am running as user $(id -u)"]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    发现会提示错误:

    [admin@ali-jkt-dc-bnc-airflow-test02 admission-controller-webhook-demo]$ kubectl create -f examples/pod-with-conflict.yaml
    Error from server: error when creating "examples/pod-with-conflict.yaml": admission webhook "webhook-server.webhook-demo.svc" denied the request: runAsNonRoot specified, but runAsUser set to 0 (the root user)
    
    • 1
    • 2

限流

常见限流算法

  1. 计数器固定窗口算法:
  2. 计数器滑动窗口算法
  3. 漏斗算法
  4. 令牌桶算法

API Server 中的限流

  • max-requests-inflight: 在给定时间内的最大 non-mutating 请求数
  • max-mutating-requests-inflight: 在给定时间内最大 mutating 请求数,调整 apiserver 的流控 qos.
  • APF 限流
    • 传入的请求通过 FlowSchema 按照其属性分类,并分配优先级。
    • 每个优先级维护自定义的并发限制,加强了隔离度,这样不同优先级的请求,就不会相互饿死。
    • 在同一个优先级内,公平排队算法可以防止来自不同 flow 的的请求相互饿死。
    • 该算法将请求排队,通过排队机制,防止在平均负载较低时,通信量突增而导致请求失败。
  • 如果未启用 APF, API 服务器中的整体并发量将受到 kube-apiserver 的参数 --max-requests-inflight 和 --max-mutating-requests-inflight 的限制。
  • 启用 APF 后,将对这些参数定义的并发限制进行求和,然后将总和分配到一组可配置的优先级中。每个传入的请求都会分配一个优先级。

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

传统限流方法的局限性

  • 粒度粗:无法为不同用户,不同场景设置不同的限流。
  • 单队列:共享限流窗口/桶,一个坏用户可能会将整个系统堵塞,其他正常用户的请求无法被及时处理。
  • 不公平:正常用户的请求会被排到队尾,无法及时处理而饿死。
  • 无优先级:重要的系统指令一并被限流,系统故障难以恢复。

API Priority and Fairness

  • APF 以更细粒度的方式对请求进行分类和隔离。
  • 引入了空间有限的排队机制,在非常短暂的突发情况下,API 服务器不会拒绝任何请求。
  • 采用多队列,多等级的技术,这样当一个坏的请求即使是高优先级,也不会将所有队列全部堵塞。

image.png
image.png

  • APF 的实现依赖两个非常重要的资源 FlowSchema, PriorityLevelConfiguration
  • APF 对请求进行更细粒度的分类,每一个请求分类对应一个 FlowSchema(FS)
  • FS 内的请求又会根据 distinguish 进一步划分为不同的 Flow
  • FS 会设置一个优先级(Priority Level, PL)不同优先级的并发资源是隔离的。所以不同优先级的资源不会相互排挤。特定优先级的请求可以被高优处理。
  • 一个 PL 可以对应多个FS, PL 中维护了一个 QueueSet , 用于缓存不能及时处理的请求,请求不会因为超出PL的并发限制而丢弃
  • FS 中的每个 Flow 通过 shuffle sharding 算法从 QueueSet 中选取特定的缓存请求。

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

APF 中的排队

当大量相同优先级请求进入同一队列时,如果有一个坏请求阻塞了队列,将会导致所有请求失败,例如一个有故障的客户端疯狂像 kube-apiserver 发送请求,占满了队列,导致其他客户端无法请求。
APF 采用公平队列算法处理具有相同优先级的请求:

  • 每个请求都被分配到某个中,该流由对应的 FlowSchema 的名字加上一个流区分项(FlowDistinguisher)来标识。

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

  • 系统尝试为不同流中具有相同优先级的请求赋予相等的权重,将请求划分到流中后,APF 功能将请求分配到队列中。
  • 分配时使用一种称为混洗分片(Shuffle-Sharding)的技术。该技术可以相对有效地利用队列隔离低强度流和高强度流。
  • 排队算法的细节可针对每个优先等级进行调整,并允许管理员在内存占用、公平性(当总流量超标时,各个独立的流将都会取得进展)、突发流量的容忍度以及排队引发的额外延迟之间进行权衡。

豁免请求

某些特别重要的请求可以无视 APF 排队,直接插队。这些豁免可防止不当的流控配置完全禁用API 服务器。

FlowSchema 与 PriorityLevelConfiguration(队列权重配置)

flowschema 默认配置如下:

[admin@ali-jkt-dc-bnc-airflow-test02 ~]$ kubectl get flowschema
NAME                           PRIORITYLEVEL     MATCHINGPRECEDENCE   DISTINGUISHERMETHOD   AGE   MISSINGPL
exempt                         exempt            1                    <none>                87d   False
probes                         exempt            2                    <none>                87d   False
system-leader-election         leader-election   100                  ByUser                87d   False
endpoint-controller            workload-high     150                  ByUser                87d   False
workload-leader-election       leader-election   200                  ByUser                87d   False
system-node-high               node-high         400                  ByUser                87d   False
system-nodes                   system            500                  ByUser                87d   False
kube-controller-manager        workload-high     800                  ByNamespace           87d   False
kube-scheduler                 workload-high     800                  ByNamespace           87d   False
kube-system-service-accounts   workload-high     900                  ByNamespace           87d   False
service-accounts               workload-low      9000                 ByUser                87d   False
global-default                 global-default    9900                 ByUser                87d   False
catch-all                      catch-all         10000                ByUser                87d   False
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

其中前两条 PL 级别外 exempt 的就是豁免请求,还有对应选举的、节点的、控制面的、调度、service等请求的 FlowSchema, 从 1 至 10000 权重递减,DISTINGUISHERMETHOD 决定根据用户信息还是命名空间来划分至不同的 Flow 流中。

FlowSchema 匹配一些入站请求,并将它们分配给优先级。

每个入站请求都会对所有FlowSchema 测试是否匹配,首先从 matchingPrecedence 数值最低的匹配开始(逻辑上匹配度最高),然后依次进行,直到首个匹配出现。

查看一个具体的FlowSchema 的yaml 说明:
image.png
默认的队列权重配置(PLC)如下:
system

  • 用于 system:nodes 组(即 kubelets)的请求;

leader-election

  • 用于leader 节点选举的请求。
  • 将这些请求与其他流量相隔离非常重要,如果leader 选举失败,将会导致控制器重启,控制器重启需要同步大量信息,流量开销更大。

workload-high

  • 优先级用于内置控制器的请求

workload-low

  • 优先级适用于来自任何服务账户的请求,通常包括来自 Pods 中运行的控制器的所有请求

global-default

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

exempt

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

catch-all

  • 优先级与特殊的 catch-all FlowSchema 结合使用,以确保每个请求都分类。

查看一个具体的PLC 说明:
image.png

调试命令

上边的所有FlowSchema 和 PLC 的配置都是默认的,当生产环境 k8s 落地时需要根据具体的集群规模和请求量来进行对应的压测、修改配置,调试至最佳状态。
查看所有优先级及其当前状态

[admin@ali-jkt-dc-bnc-airflow-test02 ~]$ kubectl get --raw /debug/api_priority_and_fairness/dump_priority_levels
PriorityLevelName, ActiveQueues, IsIdle, IsQuiescing, WaitingRequests, ExecutingRequests
leader-election,   0,            true,   false,       0,               0
node-high,         0,            true,   false,       0,               0
system,            0,            true,   false,       0,               0
workload-high,     0,            true,   false,       0,               0
workload-low,      0,            true,   false,       0,               0
catch-all,         0,            true,   false,       0,               0
exempt,            <none>,       <none>, <none>,      <none>,          <none>
global-default,    0,            true,   false,       0,               0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

查看所有队列及其当前状态的列表

[admin@ali-jkt-dc-bnc-airflow-test02 ~]$ kubectl get --raw /debug/api_priority_and_fairness/dump_queues
PriorityLevelName, Index,  PendingRequests, ExecutingRequests, SeatsInUse, NextDispatchR,   InitialSeatsSum, MaxSeatsSum, TotalWorkSum
workload-high,     0,      0,               0,                 0,          796.18416512ss,  0,               0,           0.00000000ss
workload-high,     1,      0,               0,                 0,          591.03703740ss,  0,               0,           0.00000000ss
workload-high,     2,      0,               0,                 0,          0.00000000ss,    0,               0,           0.00000000ss
workload-high,     3,      0,               0,                 0,          796.20406911ss,  0,               0,           0.00000000ss
workload-high,     4,      0,               0,                 0,          0.00000000ss,    0,               0,           0.00000000ss
workload-high,     5,      0,               0,                 0,          796.19769904ss,  0,               0,           0.00000000ss
......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

查看当前正在队列中等待的所有请求

[admin@ali-jkt-dc-bnc-airflow-test02 ~]$ kubectl get --raw /debug/api_priority_and_fairness/dump_requests
PriorityLevelName, FlowSchemaName, QueueIndex, RequestIndexInQueue, FlowDistingsher, ArriveTime, InitialSeats, FinalSeats, AdditionalLatency
  • 1
  • 2

上边的命令都是在我搭建的测试环境的 k8s 集群中执行的,没有请求,所以看到的都是空的。

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号