[K8s]Kubernetes-集群管理

集群管理

关于创建和管理 Kubernetes 集群的底层细节。

集群管理概述面向任何创建和管理 Kubernetes 集群的读者人群。我们假设你大概了解一些核心的 Kubernetes 概念。

规划集群

查阅安装中的指导,获取如何规划、建立以及配置 Kubernetes 集群的示例。本文所列的文章称为发行版。

说明: 并非所有发行版都是被积极维护的。请选择使用最近 Kubernetes 版本测试过的发行版。

在选择一个指南前,有一些因素需要考虑:

  • 你是打算在你的计算机上尝试 Kubernetes,还是要构建一个高可用的多节点集群?请选择最适合你需求的发行版。
  • 你正在使用类似 Google Kubernetes Engine 这样的被托管的 Kubernetes 集群,还是管理你自己的集群?
  • 你的集群是在本地还是云(IaaS)上?Kubernetes 不能直接支持混合集群。作为代替,你可以建立多个集群。
  • 如果你在本地配置 Kubernetes,需要考虑哪种网络模型最适合。
  • 你的 Kubernetes 在裸金属硬件上还是虚拟机(VMs)上运行?
  • 你是想运行一个集群,还是打算参与开发 Kubernetes 项目代码?如果是后者,请选择一个处于开发状态的发行版。某些发行版只提供二进制发布版,但提供更多的选择。
  • 让你自己熟悉运行一个集群所需的组件。

管理集群

  • 学习如何管理节点。

  • 学习如何设定和管理集群共享的资源配额 。

保护集群

  • 生成证书节描述了使用不同的工具链生成证书的步骤。
  • Kubernetes 容器环境描述了 Kubernetes 节点上由 Kubelet 管理的容器的环境。
  • 控制到 Kubernetes API 的访问描述了如何为用户和 service accounts 建立权限许可。
  • 身份认证节阐述了 Kubernetes 中的身份认证功能,包括许多认证选项。
  • 鉴权与身份认证不同,用于控制如何处理 HTTP 请求。
  • 使用准入控制器阐述了在认证和授权之后拦截到 Kubernetes API 服务的请求的插件。
  • 在 Kubernetes 集群中使用 Sysctls 描述了管理员如何使用 sysctl 命令行工具来设置内核参数。
  • 审计描述了如何与 Kubernetes 的审计日志交互。

保护 kubelet

  • 主控节点通信
  • TLS 引导
  • Kubelet 认证/授权

可选集群服务

  • DNS 集成描述了如何将一个 DNS 名解析到一个 Kubernetes service。
  • 记录和监控集群活动阐述了 Kubernetes 的日志如何工作以及怎样实现。

1 - 证书

在使用客户端证书认证的场景下,你可以通过 easyrsa、openssl 或 cfssl 等工具以手工方式生成证书。

easyrsa

easyrsa 支持以手工方式为你的集群生成证书。

  1. 下载、解压、初始化打过补丁的 easyrsa3。

    curl -LO https://storage.googleapis.com/kubernetes-release/easy-rsa/easy-rsa.tar.gz
    tar xzf easy-rsa.tar.gz
    cd easy-rsa-master/easyrsa3
    ./easyrsa init-pki
    
  2. 生成新的证书颁发机构(CA)。参数 --batch 用于设置自动模式;参数 --req-cn 用于设置新的根证书的通用名称(CN)。

    ./easyrsa --batch "--req-cn=${MASTER_IP}@`date +%s`" build-ca nopass

  3. 生成服务器证书和秘钥。参数 --subject-alt-name 设置 API 服务器的 IP 和 DNS 名称。MASTER_CLUSTER_IP 用于 API 服务器和控制管理器,通常取 CIDR 的第一个 IP,由 --service-cluster-ip-range 的参数提供。参数 --days 用于设置证书的过期时间。下面的示例假定你的默认 DNS 域名为 cluster.local。

    ./easyrsa --subject-alt-name="IP:${MASTER_IP},"\
    "IP:${MASTER_CLUSTER_IP},"\
    "DNS:kubernetes,"\
    "DNS:kubernetes.default,"\
    "DNS:kubernetes.default.svc,"\
    "DNS:kubernetes.default.svc.cluster,"\
    "DNS:kubernetes.default.svc.cluster.local" \
    --days=10000 \
    build-server-full server nopass
    
  4. 拷贝文件 pki/ca.crt、pki/issued/server.crt 和 pki/private/server.key 到你的目录中。

  5. 在 API 服务器的启动参数中添加以下参数:

    --client-ca-file=/yourdirectory/ca.crt
    --tls-cert-file=/yourdirectory/server.crt
    --tls-private-key-file=/yourdirectory/server.key
    

openssl

openssl 支持以手工方式为你的集群生成证书。

  1. 生成一个 2048 位的 ca.key 文件

    openssl genrsa -out ca.key 2048

  2. 在 ca.key 文件的基础上,生成 ca.crt 文件(用参数 -days 设置证书有效期)

    openssl req -x509 -new -nodes -key ca.key -subj "/CN=${MASTER_IP}" -days 10000 -out ca.crt

  3. 生成一个 2048 位的 server.key 文件:

    openssl genrsa -out server.key 2048

  4. 创建一个用于生成证书签名请求(CSR)的配置文件。保存文件(例如:csr.conf)前,记得用真实值替换掉尖括号中的值(例如:<MASTER_IP>)。注意:MASTER_CLUSTER_IP 就像前一小节所述,它的值是 API 服务器的服务集群 IP。下面的例子假定你的默认 DNS 域名为 cluster.local。

    [ req ]
    default_bits = 2048
    prompt = no
    default_md = sha256
    req_extensions = req_ext
    distinguished_name = dn
    
    [ dn ]
    C = <country>
    ST = <state>
    L = <city>
    O = <organization>
    OU = <organization unit>
    CN = <MASTER_IP>
    
    [ req_ext ]
    subjectAltName = @alt_names
    
    [ alt_names ]
    DNS.1 = kubernetes
    DNS.2 = kubernetes.default
    DNS.3 = kubernetes.default.svc
    DNS.4 = kubernetes.default.svc.cluster
    DNS.5 = kubernetes.default.svc.cluster.local
    IP.1 = <MASTER_IP>
    IP.2 = <MASTER_CLUSTER_IP>
    
    [ v3_ext ]
    authorityKeyIdentifier=keyid,issuer:always
    basicConstraints=CA:FALSE
    keyUsage=keyEncipherment,dataEncipherment
    extendedKeyUsage=serverAuth,clientAuth
    subjectAltName=@alt_names
    
  5. 基于上面的配置文件生成证书签名请求:

    openssl req -new -key server.key -out server.csr -config csr.conf

  6. 基于 ca.key、ca.crt 和 server.csr 等三个文件生成服务端证书:

    openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
    -CAcreateserial -out server.crt -days 10000 \
    -extensions v3_ext -extfile csr.conf
    
  7. 查看证书签名请求:

    openssl req -noout -text -in ./server.csr

  8. 查看证书:

    openssl x509 -noout -text -in ./server.crt

最后,为 API 服务器添加相同的启动参数。

cfssl

cfssl 是另一个用于生成证书的工具。

  1. 下载、解压并准备如下所示的命令行工具。注意:你可能需要根据所用的硬件体系架构和 cfssl 版本调整示例命令。

    curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssl_1.5.0_linux_amd64 -o cfssl
    chmod +x cfssl
    curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssljson_1.5.0_linux_amd64 -o cfssljson
    chmod +x cfssljson
    curl -L https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssl-certinfo_1.5.0_linux_amd64 -o cfssl-certinfo
    chmod +x cfssl-certinfo
    
  2. 创建一个目录,用它保存所生成的构件和初始化 cfssl:

    mkdir cert
    cd cert
    ../cfssl print-defaults config > config.json
    ../cfssl print-defaults csr > csr.json
    
  3. 创建一个 JSON 配置文件来生成 CA 文件,例如:ca-config.json:

    {
      "signing": {
    	"default": {
    	  "expiry": "8760h"
    	},
    	"profiles": {
    	  "kubernetes": {
    		"usages": [
    		  "signing",
    		  "key encipherment",
    		  "server auth",
    		  "client auth"
    		],
    		"expiry": "8760h"
    	  }
    	}
      }
    }
    
  4. 创建一个 JSON 配置文件,用于 CA 证书签名请求(CSR),例如:ca-csr.json。确认用你需要的值替换掉尖括号中的值。

    {
      "CN": "kubernetes",
      "key": {
    	"algo": "rsa",
    	"size": 2048
      },
      "names":[{
    	"C": "<country>",
    	"ST": "<state>",
    	"L": "<city>",
    	"O": "<organization>",
    	"OU": "<organization unit>"
      }]
    }
    
  5. 生成 CA 秘钥文件(ca-key.pem)和证书文件(ca.pem):

    ../cfssl gencert -initca ca-csr.json | ../cfssljson -bare ca

  6. 创建一个 JSON 配置文件,用来为 API 服务器生成秘钥和证书,例如:server-csr.json。确认用你需要的值替换掉尖括号中的值。MASTER_CLUSTER_IP 是为 API 服务器指定的服务集群 IP,就像前面小节描述的那样。以下示例假定你的默认 DSN 域名为cluster.local。

    {
      "CN": "kubernetes",
      "hosts": [
    	"127.0.0.1",
    	"<MASTER_IP>",
    	"<MASTER_CLUSTER_IP>",
    	"kubernetes",
    	"kubernetes.default",
    	"kubernetes.default.svc",
    	"kubernetes.default.svc.cluster",
    	"kubernetes.default.svc.cluster.local"
      ],
      "key": {
    	"algo": "rsa",
    	"size": 2048
      },
      "names": [{
    	"C": "<country>",
    	"ST": "<state>",
    	"L": "<city>",
    	"O": "<organization>",
    	"OU": "<organization unit>"
      }]
    }
    
  7. 为 API 服务器生成秘钥和证书,默认会分别存储为server-key.pem 和 server.pem 两个文件。

    ../cfssl gencert -ca=ca.pem -ca-key=ca-key.pem \
    --config=ca-config.json -profile=kubernetes \
    server-csr.json | ../cfssljson -bare server
    

分发自签名的 CA 证书

客户端节点可能不认可自签名 CA 证书的有效性。对于非生产环境,或者运行在公司防火墙后的环境,你可以分发自签名的 CA 证书到所有客户节点,并刷新本地列表以使证书生效。

在每一个客户节点,执行以下操作:

sudo cp ca.crt /usr/local/share/ca-certificates/kubernetes.crt

sudo update-ca-certificates

Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d....
done.

证书 API

你可以通过 certificates.k8s.io API 提供 x509 证书,用来做身份验证,如本文档所述。

2 - 管理资源

你已经部署了应用并通过服务暴露它。然后呢?Kubernetes 提供了一些工具来帮助管理你的应用部署,包括扩缩容和更新。我们将更深入讨论的特性包括配置文件和标签。

组织资源配置

许多应用需要创建多个资源,例如 Deployment 和 Service。可以通过将多个资源组合在同一个文件中(在 YAML 中以 --- 分隔)来简化对它们的管理。例如:

apiVersion: v1
kind: Service
metadata:
  name: my-nginx-svc
  labels:
    app: nginx
spec:
  type: LoadBalancer
  ports:
  - port: 80
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

可以用创建单个资源相同的方式来创建多个资源:

kubectl apply -f https://k8s.io/examples/application/nginx-app.yaml

service/my-nginx-svc created
deployment.apps/my-nginx created

资源将按照它们在文件中的顺序创建。因此,最好先指定服务,这样在控制器(例如 Deployment)创建 Pod 时能够确保调度器可以将与服务关联的多个 Pod 分散到不同节点。

kubectl create 也接受多个 -f 参数:

kubectl apply -f https://k8s.io/examples/application/nginx/nginx-svc.yaml -f https://k8s.io/examples/application/nginx/nginx-deployment.yaml

还可以指定目录路径,而不用添加多个单独的文件:

kubectl apply -f https://k8s.io/examples/application/nginx/

kubectl 将读取任何后缀为 .yaml、.yml 或者 .json 的文件。

建议的做法是,将同一个微服务或同一应用层相关的资源放到同一个文件中,将同一个应用相关的所有文件按组存放到同一个目录中。如果应用的各层使用 DNS 相互绑定,那么你可以将堆栈的所有组件一起部署。

还可以使用 URL 作为配置源,便于直接使用已经提交到 Github 上的配置文件进行部署:

kubectl apply -f https://raw.githubusercontent.com/kubernetes/website/main/content/zh/examples/application/nginx/nginx-deployment.yaml

deployment.apps/my-nginx created

kubectl 中的批量操作

资源创建并不是 kubectl 可以批量执行的唯一操作。kubectl 还可以从配置文件中提取资源名,以便执行其他操作,特别是删除你之前创建的资源:

kubectl delete -f https://k8s.io/examples/application/nginx-app.yaml

deployment.apps "my-nginx" deleted
service "my-nginx-svc" deleted

在仅有两种资源的情况下,可以使用"资源类型/资源名"的语法在命令行中同时指定这两个资源:

kubectl delete deployments/my-nginx services/my-nginx-svc

对于资源数目较大的情况,你会发现使用 -l 或 --selector 指定筛选器(标签查询)能很容易根据标签筛选资源:

kubectl delete deployment,services -l app=nginx

deployment.apps "my-nginx" deleted
service "my-nginx-svc" deleted

由于 kubectl 用来输出资源名称的语法与其所接受的资源名称语法相同,你可以使用 $() 或 xargs 进行链式操作:

kubectl get $(kubectl create -f docs/concepts/cluster-administration/nginx/ -o name | grep service)

kubectl create -f docs/concepts/cluster-administration/nginx/ -o name | grep service | xargs -i kubectl get {}

NAME           TYPE           CLUSTER-IP   EXTERNAL-IP   PORT(S)      AGE
my-nginx-svc   LoadBalancer   10.0.0.208   <pending>     80/TCP       0s

上面的命令中,我们首先使用 examples/application/nginx/ 下的配置文件创建资源,并使用 -o name 的输出格式(以"资源/名称"的形式打印每个资源)打印所创建的资源。然后,我们通过 grep 来过滤 "service",最后再打印 kubectl get 的内容。

如果你碰巧在某个路径下的多个子路径中组织资源,那么也可以递归地在所有子路径上执行操作,方法是在 --filename,-f 后面指定 --recursive 或者 -R。

例如,假设有一个目录路径为 project/k8s/development,它保存开发环境所需的所有清单,并按资源类型组织:

project/k8s/development
├── configmap
│   └── my-configmap.yaml
├── deployment
│   └── my-deployment.yaml
└── pvc
    └── my-pvc.yaml

默认情况下,对 project/k8s/development 执行的批量操作将停止在目录的第一级,而不是处理所有子目录。如果我们试图使用以下命令在此目录中创建资源,则会遇到一个错误:

kubectl apply -f project/k8s/development

error: you must provide one or more resources by argument or filename (.json|.yaml|.yml|stdin)

正确的做法是,在 --filename,-f 后面标明 --recursive 或者 -R 之后:

kubectl apply -f project/k8s/development --recursive

configmap/my-config created
deployment.apps/my-deployment created
persistentvolumeclaim/my-pvc created

--recursive 可以用于接受 --filename,-f 参数的任何操作,例如: kubectl {create,get,delete,describe,rollout} 等。

有多个 -f 参数出现的时候,--recursive 参数也能正常工作:

kubectl apply -f project/k8s/namespaces -f project/k8s/development --recursive

namespace/development created
namespace/staging created
configmap/my-config created
deployment.apps/my-deployment created
persistentvolumeclaim/my-pvc created

有效地使用标签

到目前为止我们使用的示例中的资源最多使用了一个标签。在许多情况下,应使用多个标签来区分集合。

例如,不同的应用可能会为 app 标签设置不同的值。但是,类似 guestbook 示例这样的多层应用,还需要区分每一层。前端可以带以下标签:

 labels:
    app: guestbook
    tier: frontend

Redis 的主节点和从节点会有不同的 tier 标签,甚至还有一个额外的 role 标签:

 labels:
    app: guestbook
    tier: backend
    role: master

以及

 labels:
    app: guestbook
    tier: backend
    role: slave

标签允许我们按照标签指定的任何维度对我们的资源进行切片和切块:

kubectl apply -f examples/guestbook/all-in-one/guestbook-all-in-one.yaml

kubectl get pods -Lapp -Ltier -Lrole

NAME                           READY     STATUS    RESTARTS   AGE       APP         TIER       ROLE
guestbook-fe-4nlpb             1/1       Running   0          1m        guestbook   frontend   <none>
guestbook-fe-ght6d             1/1       Running   0          1m        guestbook   frontend   <none>
guestbook-fe-jpy62             1/1       Running   0          1m        guestbook   frontend   <none>
guestbook-redis-master-5pg3b   1/1       Running   0          1m        guestbook   backend    master
guestbook-redis-slave-2q2yf    1/1       Running   0          1m        guestbook   backend    slave
guestbook-redis-slave-qgazl    1/1       Running   0          1m        guestbook   backend    slave
my-nginx-divi2                 1/1       Running   0          29m       nginx       <none>     <none>
my-nginx-o0ef1                 1/1       Running   0          29m       nginx       <none>     <none>

kubectl get pods -lapp=guestbook,role=slave

NAME                          READY     STATUS    RESTARTS   AGE
guestbook-redis-slave-2q2yf   1/1       Running   0          3m
guestbook-redis-slave-qgazl   1/1       Running   0          3m

金丝雀部署(Canary Deployments)

另一个需要多标签的场景是用来区分同一组件的不同版本或者不同配置的多个部署。常见的做法是部署一个使用金丝雀发布来部署新应用版本(在 Pod 模板中通过镜像标签指定),保持新旧版本应用同时运行。这样,新版本在完全发布之前也可以接收实时的生产流量。

例如,你可以使用 track 标签来区分不同的版本。

主要稳定的发行版将有一个 track 标签,其值为 stable:

 name: frontend
 replicas: 3
 ...
 labels:
    app: guestbook
    tier: frontend
    track: stable
 ...
 image: gb-frontend:v3

然后,你可以创建 guestbook 前端的新版本,让这些版本的 track 标签带有不同的值(即 canary),以便两组 Pod 不会重叠:

 name: frontend-canary
 replicas: 1
 ...
 labels:
    app: guestbook
    tier: frontend
    track: canary
 ...
 image: gb-frontend:v4

前端服务通过选择标签的公共子集(即忽略 track 标签)来覆盖两组副本,以便流量可以转发到两个应用:

selector:
   app: guestbook
   tier: frontend

你可以调整 stable 和 canary 版本的副本数量,以确定每个版本将接收实时生产流量的比例(在本例中为 3:1)。一旦有信心,你就可以将新版本应用的 track 标签的值从 canary 替换为 stable,并且将老版本应用删除。

更新标签

有时,现有的 pod 和其它资源需要在创建新资源之前重新标记。这可以用 kubectl label 完成。例如,如果想要将所有 nginx pod 标记为前端层,运行:

kubectl label pods -l app=nginx tier=fe

pod/my-nginx-2035384211-j5fhi labeled
pod/my-nginx-2035384211-u2c7e labeled
pod/my-nginx-2035384211-u3t6x labeled

首先用标签 "app=nginx" 过滤所有的 Pod,然后用 "tier=fe" 标记它们。想要查看你刚才标记的 Pod,请运行:

kubectl get pods -l app=nginx -L tier

NAME                        READY     STATUS    RESTARTS   AGE       TIER
my-nginx-2035384211-j5fhi   1/1       Running   0          23m       fe
my-nginx-2035384211-u2c7e   1/1       Running   0          23m       fe
my-nginx-2035384211-u3t6x   1/1       Running   0          23m       fe

这将输出所有 "app=nginx" 的 Pod,并有一个额外的描述 Pod 的 tier 的标签列(用参数 -L 或者 --label-columns 标明)。

更新注解

有时,你可能希望将注解附加到资源中。注解是 API 客户端(如工具、库等)用于检索的任意非标识元数据。这可以通过 kubectl annotate 来完成。例如:

kubectl annotate pods my-nginx-v4-9gw19 description='my frontend running nginx'

kubectl get pods my-nginx-v4-9gw19 -o yaml

apiVersion: v1
kind: pod
metadata:
  annotations:
    description: my frontend running nginx
...

扩缩你的应用

当应用上的负载增长或收缩时,使用 kubectl 能够实现应用规模的扩缩。例如,要将 nginx 副本的数量从 3 减少到 1,请执行以下操作:

kubectl scale deployment/my-nginx --replicas=1

deployment.extensions/my-nginx scaled

现在,你的 Deployment 管理的 Pod 只有一个了。

kubectl get pods -l app=nginx

NAME                        READY     STATUS    RESTARTS   AGE
my-nginx-2035384211-j5fhi   1/1       Running   0          30m

想要让系统自动选择需要 nginx 副本的数量,范围从 1 到 3,请执行以下操作:

kubectl autoscale deployment/my-nginx --min=1 --max=3

horizontalpodautoscaler.autoscaling/my-nginx autoscaled

现在,你的 nginx 副本将根据需要自动地增加或者减少。

就地更新资源

有时,有必要对你所创建的资源进行小范围、无干扰地更新。

kubectl apply

建议在源代码管理中维护一组配置文件(参见配置即代码),这样,它们就可以和应用代码一样进行维护和版本管理。然后,你可以用 kubectl apply 将配置变更应用到集群中。

这个命令将会把推送的版本与以前的版本进行比较,并应用你所做的更改,但是不会自动覆盖任何你没有指定更改的属性。

kubectl apply -f https://k8s.io/examples/application/nginx/nginx-deployment.yaml
deployment.apps/my-nginx configured

注意,kubectl apply 将为资源增加一个额外的注解,以确定自上次调用以来对配置的更改。执行时,kubectl apply 会在以前的配置、提供的输入和资源的当前配置之间找出三方差异,以确定如何修改资源。

目前,新创建的资源是没有这个注解的,所以,第一次调用 kubectl apply 时将使用提供的输入和资源的当前配置双方之间差异进行比较。在第一次调用期间,它无法检测资源创建时属性集的删除情况。因此,kubectl 不会删除它们。

所有后续的 kubectl apply 操作以及其他修改配置的命令,如 kubectl replace 和 kubectl edit,都将更新注解,并允许随后调用的 kubectl apply 使用三方差异进行检查和执行删除。

说明: 想要使用 apply,请始终使用 kubectl apply 或 kubectl create --save-config 创建资源。

kubectl edit

或者,你也可以使用 kubectl edit 更新资源:

kubectl edit deployment/my-nginx

这相当于首先 get 资源,在文本编辑器中编辑它,然后用更新的版本 apply 资源:

kubectl get deployment my-nginx -o yaml > /tmp/nginx.yaml
vi /tmp/nginx.yaml
# do some edit, and then save the file

kubectl apply -f /tmp/nginx.yaml
deployment.apps/my-nginx configured

rm /tmp/nginx.yaml

这使你可以更加容易地进行更重大的更改。请注意,可以使用 EDITOR 或 KUBE_EDITOR 环境变量来指定编辑器。

kubectl patch

你可以使用 kubectl patch 来更新 API 对象。此命令支持 JSON patch、JSON merge patch、以及 strategic merge patch。

破坏性的更新

在某些情况下,你可能需要更新某些初始化后无法更新的资源字段,或者你可能只想立即进行递归更改,例如修复 Deployment 创建的不正常的 Pod。若要更改这些字段,请使用 replace --force,它将删除并重新创建资源。在这种情况下,你可以修改原始配置文件:

kubectl replace -f https://k8s.io/examples/application/nginx/nginx-deployment.yaml --force

deployment.apps/my-nginx deleted
deployment.apps/my-nginx replaced

在不中断服务的情况下更新应用

在某些时候,你最终需要更新已部署的应用,通常都是通过指定新的镜像或镜像标签,如上面的金丝雀发布的场景中所示。kubectl 支持几种更新操作,每种更新操作都适用于不同的场景。

我们将指导你通过 Deployment 如何创建和更新应用。

假设你正运行的是 1.14.2 版本的 nginx:

kubectl create deployment my-nginx --image=nginx:1.14.2

deployment.apps/my-nginx created

要更新到 1.16.1 版本,只需使用我们前面学到的 kubectl 命令将 .spec.template.spec.containers[0].image 从 nginx:1.14.2 修改为 nginx:1.16.1。

kubectl edit deployment/my-nginx

没错,就是这样!Deployment 将在后台逐步更新已经部署的 nginx 应用。它确保在更新过程中,只有一定数量的旧副本被开闭,并且只有一定基于所需 Pod 数量的新副本被创建。

3 - 集群网络系统

集群网络系统是 Kubernetes 的核心部分,但是想要准确了解它的工作原理可是个不小的挑战。下面列出的是网络系统的的四个主要问题:

  1. 高度耦合的容器间通信:这个已经被 Pods 和 localhost 通信解决了。
  2. Pod 间通信:这个是本文档的重点要讲述的。
  3. Pod 和服务间通信:这个已经在服务里讲述过了。
  4. 外部和服务间通信:这也已经在服务讲述过了。

Kubernetes 的宗旨就是在应用之间共享机器。通常来说,共享机器需要两个应用之间不能使用相同的端口,但是在多个应用开发者之间去大规模地协调端口是件很困难的事情,尤其是还要让用户暴露在他们控制范围之外的集群级别的问题上。

动态分配端口也会给系统带来很多复杂度 - 每个应用都需要设置一个端口的参数,而 API 服务器还需要知道如何将动态端口数值插入到配置模块中,服务也需要知道如何找到对方等等。与其去解决这些问题,Kubernetes 选择了其他不同的方法。

Kubernetes 网络模型

每一个 Pod 都有它自己的IP地址,这就意味着你不需要显式地在每个 Pod 之间创建链接,你几乎不需要处理容器端口到主机端口之间的映射。这将创建一个干净的、向后兼容的模型,在这个模型里,从端口分配、命名、服务发现、负载均衡、应用配置和迁移的角度来看,Pod 可以被视作虚拟机或者物理主机。

Kubernetes 对所有网络设施的实施,都需要满足以下的基本要求(除非有设置一些特定的网络分段策略):

  • 节点上的 Pod 可以不通过 NAT 和其他任何节点上的 Pod 通信
  • 节点上的代理(比如:系统守护进程、kubelet)可以和节点上的所有Pod通信

备注:仅针对那些支持 Pods 在主机网络中运行的平台(比如:Linux):

  • 那些运行在节点的主机网络里的 Pod 可以不通过 NAT 和所有节点上的 Pod 通信

这个模型不仅不复杂,而且还和 Kubernetes 的实现廉价的从虚拟机向容器迁移的初衷相兼容,如果你的工作开始是在虚拟机中运行的,你的虚拟机有一个 IP ,这样就可以和其他的虚拟机进行通信,这是基本相同的模型。

Kubernetes 的 IP 地址存在于 Pod 范围内 - 容器共享它们的网络命名空间 - 包括它们的 IP 地址和 MAC 地址。这就意味着 Pod 内的容器都可以通过 localhost 到达各个端口。这也意味着 Pod 内的容器都需要相互协调端口的使用,但是这和虚拟机中的进程似乎没有什么不同,这也被称为“一个 Pod 一个 IP”模型。

如何实现这一点是正在使用的容器运行时的特定信息。

也可以在 node 本身通过端口去请求你的 Pod(称之为主机端口),但这是一个很特殊的操作。转发方式如何实现也是容器运行时的细节。Pod 自己并不知道这些主机端口是否存在。

如何实现 Kubernetes 的网络模型

有很多种方式可以实现这种网络模型,本文档并不是对各种实现技术的详细研究,但是希望可以作为对各种技术的详细介绍,并且成为你研究的起点。

接下来的网络技术是按照首字母排序,顺序本身并无其他意义。

说明: 本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循 CNCF 网站指南,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读内容指南。

ACI

Cisco Application Centric Infrastructure 提供了一个集成覆盖网络和底层 SDN 的解决方案来支持容器、虚拟机和其他裸机服务器。ACI 为 ACI 提供了容器网络集成。

Antrea

Antrea 项目是一个开源的联网解决方案,旨在成为 Kubernetes 原生的网络解决方案。它利用 Open vSwitch 作为网络数据平面。Open vSwitch 是一个高性能可编程的虚拟交换机,支持 Linux 和 Windows 平台。Open vSwitch 使 Antrea 能够以高性能和高效的方式实现 Kubernetes 的网络策略。借助 Open vSwitch 可编程的特性,Antrea 能够在 Open vSwitch 之上实现广泛的联网、安全功能和服务。

Apstra 的 AOS

AOS 是一个基于意图的网络系统,可以通过一个简单的集成平台创建和管理复杂的数据中心环境。AOS 利用高度可扩展的分布式设计来消除网络中断,同时将成本降至最低。

AOS 参考设计当前支持三层连接的主机,这些主机消除了旧的两层连接的交换问题。这些三层连接的主机可以是 Linux(Debian、Ubuntu、CentOS)系统,它们直接在机架式交换机(TOR)的顶部创建 BGP 邻居关系。AOS 自动执行路由邻接,然后提供对 Kubernetes 部署中常见的路由运行状况注入(RHI)的精细控制。

AOS 具有一组丰富的 REST API 端点,这些端点使 Kubernetes 能够根据应用程序需求快速更改网络策略。进一步的增强功能将用于网络设计的 AOS Graph 模型与工作负载供应集成在一起,从而为私有云和公共云提供端到端管理系统。

AOS 支持使用包括 Cisco、Arista、Dell、Mellanox、HPE 在内的制造商提供的通用供应商设备,以及大量白盒系统和开放网络操作系统,例如 Microsoft SONiC、Dell OPX 和 Cumulus Linux。

想要更详细地了解 AOS 系统是如何工作的可以点击这里:https://www.apstra.com/products/how-it-works/

Kubernetes 的 AWS VPC CNI

AWS VPC CNI 为 Kubernetes 集群提供了集成的 AWS 虚拟私有云(VPC)网络。该 CNI 插件提供了高吞吐量和可用性,低延迟以及最小的网络抖动。此外,用户可以使用现有的 AWS VPC 网络和安全最佳实践来构建 Kubernetes 集群。这包括使用 VPC 流日志、VPC 路由策略和安全组进行网络流量隔离的功能。

使用该 CNI 插件,可使 Kubernetes Pod 拥有与在 VPC 网络上相同的 IP 地址。CNI 将 AWS 弹性网络接口(ENI)分配给每个 Kubernetes 节点,并将每个 ENI 的辅助 IP 范围用于该节点上的 Pod 。CNI 包含用于 ENI 和 IP 地址的预分配的控件,以便加快 Pod 的启动时间,并且能够支持多达 2000 个节点的大型集群。

此外,CNI 可以与用于执行网络策略的 Calico 一起运行。AWS VPC CNI 项目是开源的,请查看 GitHub 上的文档。

Kubernetes 的 Azure CNI

Azure CNI 是一个开源插件,将 Kubernetes Pods 和 Azure 虚拟网络(也称为 VNet)集成在一起,可提供与 VM 相当的网络性能。Pod 可以通过 Express Route 或者站点到站点的 VPN 来连接到对等的 VNet ,也可以从这些网络来直接访问 Pod。Pod 可以访问受服务端点或者受保护链接的 Azure 服务,比如存储和 SQL。你可以使用 VNet 安全策略和路由来筛选 Pod 流量。该插件通过利用在 Kubernetes 节点的网络接口上预分配的辅助 IP 池将 VNet 分配给 Pod。

Azure CNI 可以在 Azure Kubernetes Service (AKS) 中获得。

Big Switch Networks 的 Big Cloud Fabric

Big Cloud Fabric 是一个基于云原生的网络架构,旨在在私有云或者本地环境中运行 Kubernetes。它使用统一的物理和虚拟 SDN,Big Cloud Fabric 解决了固有的容器网络问题,比如负载均衡、可见性、故障排除、安全策略和容器流量监控。

在 Big Cloud Fabric 的虚拟 Pod 多租户架构的帮助下,容器编排系统(比如 Kubernetes、RedHat OpenShift、Mesosphere DC/OS 和 Docker Swarm)将与 VM 本地编排系统(比如 VMware、OpenStack 和 Nutanix)进行本地集成。客户将能够安全地互联任意数量的这些集群,并且在需要时启用他们之间的租户间通信。

在最新的 Magic Quadrant 上,BCF 被 Gartner 认为是非常有远见的。而 BCF 的一条关于 Kubernetes 的本地部署(其中包括 Kubernetes、DC/OS 和在不同地理区域的多个 DC 上运行的 VMware)也在这里被引用。

Calico

Calico 是一个开源的联网及网络安全方案,用于基于容器、虚拟机和本地主机的工作负载。Calico 支持多个数据面,包括:纯 Linux eBPF 的数据面、标准的 Linux 联网数据面以及 Windows HNS 数据面。Calico 在提供完整的联网堆栈的同时,还可与云驱动 CNIs 联合使用,以保证网络策略实施。

Cilium

Cilium 是一个开源软件,用于提供并透明保护应用容器间的网络连接。Cilium 支持 L7/HTTP,可以在 L3-L7 上通过使用与网络分离的基于身份的安全模型寻址来实施网络策略,并且可以与其他 CNI 插件结合使用。

华为的 CNI-Genie

CNI-Genie 是一个 CNI 插件,可以让 Kubernetes 在运行时使用不同的网络模型的实现同时被访问。这包括以 CNI 插件运行的任何实现,比如 Flannel、Calico、Romana、Weave-net。

CNI-Genie 还支持将多个 IP 地址分配给 Pod,每个都来自不同的 CNI 插件。

cni-ipvlan-vpc-k8s

cni-ipvlan-vpc-k8s 包含了一组 CNI 和 IPAM 插件来提供一个简单的、本地主机、低延迟、高吞吐量以及通过使用 Amazon 弹性网络接口(ENI)并使用 Linux 内核的 IPv2 驱动程序以 L2 模式将 AWS 管理的 IP 绑定到 Pod 中,在 Amazon Virtual Private Cloud(VPC)环境中为 Kubernetes 兼容的网络堆栈。

这些插件旨在直接在 VPC 中进行配置和部署,Kubelets 先启动,然后根据需要进行自我配置和扩展它们的 IP 使用率,而无需经常建议复杂的管理覆盖网络、BGP、禁用源/目标检查或调整 VPC 路由表以向每个主机提供每个实例子网的复杂性(每个 VPC 限制为50-100个条目)。简而言之,cni-ipvlan-vpc-k8s 大大降低了在 AWS 中大规模部署 Kubernetes 所需的网络复杂性。

Coil

Coil 是一个为易于集成、提供灵活的出站流量网络而设计的 CNI 插件。与裸机相比,Coil 的额外操作开销低,并允许针对外部网络的出站流量任意定义 NAT 网关。

Contiv

Contiv 为各种使用情况提供了一个可配置网络(使用了 BGP 的本地 L3,使用 vxlan、经典 L2 或 Cisco-SDN/ACI 的覆盖网络)。Contiv 是完全开源的。

Contrail/Tungsten Fabric

Contrail 是基于 Tungsten Fabric 的,真正开放的多云网络虚拟化和策略管理平台。Contrail 和 Tungsten Fabric 与各种编排系统集成在一起,例如 Kubernetes、OpenShift、OpenStack 和 Mesos,并为虚拟机、容器或 Pods 以及裸机工作负载提供了不同的隔离模式。

DANM

DANM 是一个针对在 Kubernetes 集群中运行的电信工作负载的网络解决方案。它由以下几个组件构成:

  • 能够配置具有高级功能的 IPVLAN 接口的 CNI 插件
  • 一个内置的 IPAM 模块,能够管理多个、群集内的、不连续的 L3 网络,并按请求提供动态、静态或无 IP 分配方案
  • CNI 元插件能够通过自己的 CNI 或通过将任务授权给其他任何流行的 CNI 解决方案(例如 SRI-OV 或 Flannel)来实现将多个网络接口连接到容器
  • Kubernetes 控制器能够集中管理所有 Kubernetes 主机的 VxLAN 和 VLAN 接口
  • 另一个 Kubernetes 控制器扩展了 Kubernetes 的基于服务的服务发现概念,以在 Pod 的所有网络接口上工作

通过这个工具集,DANM 可以提供多个分离的网络接口,可以为 Pod 使用不同的网络后端和高级 IPAM 功能。

Flannel

Flannel 是一个非常简单的能够满足 Kubernetes 所需要的覆盖网络。已经有许多人报告了使用 Flannel 和 Kubernetes 的成功案例。

Google Compute Engine (GCE)

对于 Google Compute Engine 的集群配置脚本,高级路由器用于为每个虚机分配一个子网(默认是 /24 - 254个 IP),绑定到该子网的任何流量都将通过 GCE 网络结构直接路由到虚机。这是除了分配给虚机的“主” IP 地址之外的一个补充,该 IP 地址经过 NAT 转换以用于访问外网。Linux 网桥(称为“cbr0”)被配置为存在于该子网中,并被传递到 Docker 的 --bridge 参数上。

Docker 会以这样的参数启动:

DOCKER_OPTS="--bridge=cbr0 --iptables=false --ip-masq=false"

这个网桥是由 Kubelet(由 --network-plugin=kubenet 参数控制)根据节点的 .spec.podCIDR 参数创建的。

Docker 将会从 cbr-cidr 块分配 IP。容器之间可以通过 cbr0 网桥相互访问,也可以访问节点。这些 IP 都可以在 GCE 的网络中被路由。而 GCE 本身并不知道这些 IP,所以不会对访问外网的流量进行 NAT。为了实现此目的,使用了 iptables 规则来伪装(又称为 SNAT,使数据包看起来好像是来自“节点”本身),将通信绑定到 GCE 项目网络(10.0.0.0/8)之外的 IP。

iptables -t nat -A POSTROUTING ! -d 10.0.0.0/8 -o eth0 -j MASQUERADE

最后,在内核中启用了 IP 转发(因此内核将处理桥接容器的数据包):

sysctl net.ipv4.ip_forward=1

所有这些的结果是所有 Pod 都可以互相访问,并且可以将流量发送到互联网。

Jaguar

Jaguar 是一个基于 OpenDaylight 的 Kubernetes 网络开源解决方案。Jaguar 使用 vxlan 提供覆盖网络,而 Jaguar CNIPlugin 为每个 Pod 提供一个 IP 地址。

k-vswitch

k-vswitch 是一个基于 Open vSwitch 的简易 Kubernetes 网络插件。它利用 Open vSwitch 中现有的功能来提供强大的网络插件,该插件易于操作,高效且安全。

Knitter

Knitter 是一个支持 Kubernetes 中实现多个网络系统的解决方案。它提供了租户管理和网络管理的功能。除了多个网络平面外,Knitter 还包括一组端到端的 NFV 容器网络解决方案,例如为应用程序保留 IP 地址、IP 地址迁移等。

Kube-OVN

Kube-OVN 是一个基于 OVN 的用于企业的 Kubernetes 网络架构。借助于 OVN/OVS,它提供了一些高级覆盖网络功能,例如子网、QoS、静态 IP 分配、流量镜像、网关、基于 openflow 的网络策略和服务代理。

Kube-router

Kube-router 是 Kubernetes 的专用网络解决方案,旨在提供高性能和易操作性。Kube-router 提供了一个基于 Linux LVS/IPVS 的服务代理、一个基于 Linux 内核转发的无覆盖 Pod-to-Pod 网络解决方案和基于 iptables/ipset 的网络策略执行器。

L2 networks and linux bridging

如果你具有一个“哑”的L2网络,例如“裸机”环境中的简单交换机,则应该能够执行与上述 GCE 设置类似的操作。请注意,这些说明仅是非常简单的尝试过-似乎可行,但尚未经过全面测试。如果您使用此技术并完善了流程,请告诉我们。

根据 Lars Kellogg-Stedman 的这份非常不错的“Linux 网桥设备” 使用说明来进行操作。

Multus (a Multi Network plugin)

Multus 是一个多 CNI 插件,使用 Kubernetes 中基于 CRD 的网络对象来支持实现 Kubernetes 多网络系统。

Multus 支持所有参考插件(比如: Flannel、 DHCP、 Macvlan )来实现 CNI 规范和第三方插件(比如: Calico、 Weave、 Cilium、 Contiv)。除此之外,Multus 还支持 SRIOV、DPDK、OVS-DPDK & VPP 的工作负载,以及 Kubernetes 中基于云的本机应用程序和基于 NFV 的应用程序。

NSX-T

VMware NSX-T 是一个网络虚拟化和安全平台。NSX-T 可以为多云及多系统管理程序环境提供网络虚拟化,并专注于具有异构端点和技术堆栈的新兴应用程序框架和体系结构。除了 vSphere 管理程序之外,这些环境还包括其他虚拟机管理程序,例如 KVM、容器和裸机。

NSX-T Container Plug-in (NCP) 提供了 NSX-T 与容器协调器(例如 Kubernetes)之间的结合,以及 NSX-T 与基于容器的 CaaS/PaaS 平台(例如 Pivotal Container Service(PKS)和 OpenShift)之间的集成。

Nuage Networks VCS (Virtualized Cloud Services)

Nuage 提供了一个高度可扩展的基于策略的软件定义网络(SDN)平台。Nuage 使用开源的 Open vSwitch 作为数据平面,以及基于开放标准构建具有丰富功能的 SDN 控制器。

Nuage 平台使用覆盖层在 Kubernetes Pod 和非 Kubernetes 环境(VM 和裸机服务器)之间提供基于策略的无缝联网。Nuage 的策略抽象模型在设计时就考虑到了应用程序,并且可以轻松声明应用程序的细粒度策略。该平台的实时分析引擎可为 Kubernetes 应用程序提供可见性和安全性监控。

OpenVSwitch

OpenVSwitch 是一个较为成熟的解决方案,但同时也增加了构建覆盖网络的复杂性。这也得到了几个网络系统的“大商店”的拥护。

OVN (开放式虚拟网络)

OVN 是一个由 Open vSwitch 社区开发的开源的网络虚拟化解决方案。它允许创建逻辑交换器、逻辑路由、状态 ACL、负载均衡等等来建立不同的虚拟网络拓扑。该项目有一个特定的 Kubernetes 插件和文档 ovn-kubernetes。

Romana

Romana 是一个开源网络和安全自动化解决方案。它可以让你在没有覆盖网络的情况下部署 Kubernetes。Romana 支持 Kubernetes 网络策略,来提供跨网络命名空间的隔离。

Weaveworks 的 Weave Net

Weave Net 是 Kubernetes 及其托管应用程序的弹性且易于使用的网络系统。Weave Net 可以作为 CNI 插件运行或者独立运行。在这两种运行方式里,都不需要任何配置或额外的代码即可运行,并且在两种情况下,网络都为每个 Pod 提供一个 IP 地址 -- 这是 Kubernetes 的标准配置。

4 - Kubernetes 系统组件指标

通过系统组件指标可以更好地了解系统组个内部发生的情况。系统组件指标对于构建仪表板和告警特别有用。

Kubernetes 组件以 Prometheus 格式生成度量值。这种格式是结构化的纯文本,旨在使人和机器都可以阅读。

Kubernetes 中组件的指标

在大多数情况下,可以通过 HTTP 访问组件的 /metrics 端点来获取组件的度量值。对于那些默认情况下不暴露端点的组件,可以使用 --bind-address 标志启用。

这些组件的示例:

  • kube-controller-manager
  • kube-proxy
  • kube-apiserver
  • kube-scheduler
  • kubelet

在生产环境中,你可能需要配置 Prometheus 服务器或某些其他指标搜集器以定期收集这些指标,并使它们在某种时间序列数据库中可用。

请注意,kubelet 还会在 /metrics/cadvisor,/metrics/resource 和 /metrics/probes 端点中公开度量值。这些度量值的生命周期各不相同。

如果你的集群使用了 RBAC,则读取指标需要通过基于用户、组或 ServiceAccount 的鉴权,要求具有允许访问 /metrics 的 ClusterRole。例如:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: prometheus
rules:
  - nonResourceURLs:
      - "/metrics"
    verbs:
      - get

指标生命周期

Alpha 指标 → 稳定的指标 → 弃用的指标 → 隐藏的指标 → 删除的指标

Alpha 指标没有稳定性保证。这些指标可以随时被修改或者删除。

稳定的指标可以保证不会改变。这意味着:

  • 稳定的、不包含已弃用(deprecated)签名的指标不会被删除(或重命名)
  • 稳定的指标的类型不会被更改

已弃用的指标最终将被删除,不过仍然可用。这类指标包含注解,标明其被废弃的版本。

例如:

  • 被弃用之前:

    # HELP some_counter this counts things
    # TYPE some_counter counter
    some_counter 0
    
  • 被启用之后:

    # HELP some_counter (Deprecated since 1.15.0) this counts things
    # TYPE some_counter counter
    some_counter 0
    

隐藏的指标不会再被发布以供抓取,但仍然可用。要使用隐藏指标,请参阅显式隐藏指标节。

删除的指标不再被发布,亦无法使用。

显示隐藏指标

如上所述,管理员可以通过设置可执行文件的命令行参数来启用隐藏指标,如果管理员错过了上一版本中已经弃用的指标的迁移,则可以把这个用作管理员的逃生门。

show-hidden-metrics-for-version 标志接受版本号作为取值,版本号给出你希望显示该发行版本中已弃用的指标。版本表示为 x.y,其中 x 是主要版本,y 是次要版本。补丁程序版本不是必须的,即使指标可能会在补丁程序发行版中弃用,原因是指标弃用策略规定仅针对次要版本。

该参数只能使用前一个次要版本。如果管理员将先前版本设置为 show-hidden-metrics-for-version,则先前版本中隐藏的度量值会再度生成。不允许使用过旧的版本,因为那样会违反指标弃用策略。

以指标 A 为例,此处假设 A 在 1.n 中已弃用。根据指标弃用策略,我们可以得出以下结论:

  • 在版本 1.n 中,这个指标已经弃用,且默认情况下可以生成。
  • 在版本 1.n+1 中,这个指标默认隐藏,可以通过命令行参数 show-hidden-metrics-for-version=1.n 来再度生成。
  • 在版本 1.n+2 中,这个指标就将被从代码中移除,不会再有任何逃生窗口。

如果你要从版本 1.12 升级到 1.13,但仍依赖于 1.12 中弃用的指标 A,则应通过命令行设置隐藏指标:--show-hidden-metrics=1.12,并记住在升级到 1.14 版本之前删除此指标依赖项。

禁用加速器指标

kubelet 通过 cAdvisor 收集加速器指标。为了收集这些指标,对于 NVIDIA GPU 之类的加速器,kubelet 在驱动程序上保持打开状态。这意味着为了执行基础结构更改(例如更新驱动程序),集群管理员需要停止 kubelet 代理。

现在,收集加速器指标的责任属于供应商,而不是 kubelet。供应商必须提供一个收集指标的容器,并将其公开给指标服务(例如 Prometheus)。

DisableAcceleratorUsageMetrics 特性门控禁止由 kubelet 收集的指标。关于何时会在默认情况下启用此功能也有一定规划。

组件指标

kube-controller-manager 指标

控制器管理器指标可提供有关控制器管理器性能和运行状况的重要洞察。这些指标包括通用的 Go 语言运行时指标(例如 go_routine 数量)和控制器特定的度量指标,例如可用于评估集群运行状况的 etcd 请求延迟或云提供商(AWS、GCE、OpenStack)的 API 延迟等。

从 Kubernetes 1.7 版本开始,详细的云提供商指标可用于 GCE、AWS、Vsphere 和 OpenStack 的存储操作。这些指标可用于监控持久卷操作的运行状况。

比如,对于 GCE,这些指标称为:

cloudprovider_gce_api_request_duration_seconds { request = "instance_list"}
cloudprovider_gce_api_request_duration_seconds { request = "disk_insert"}
cloudprovider_gce_api_request_duration_seconds { request = "disk_delete"}
cloudprovider_gce_api_request_duration_seconds { request = "attach_disk"}
cloudprovider_gce_api_request_duration_seconds { request = "detach_disk"}
cloudprovider_gce_api_request_duration_seconds { request = "list_disk"}

kube-scheduler 指标

FEATURE STATE: Kubernetes v1.21 [beta]

调度器会暴露一些可选的指标,报告所有运行中 Pods 所请求的资源和期望的约束值。这些指标可用来构造容量规划监控面板、访问调度约束的当前或历史数据、快速发现因为缺少资源而无法被调度的负载,或者将 Pod 的实际资源用量与其请求值进行比较。

kube-scheduler 组件能够辩识各个 Pod 所配置的资源请求和约束。在 Pod 的资源请求值或者约束值非零时,kube-scheduler 会以度量值时间序列的形式生成报告。该时间序列值包含以下标签:

  • 名字空间
  • Pod 名称
  • Pod 调度所处节点,或者当 Pod 未被调度时用空字符串表示
  • 优先级
  • 为 Pod 所指派的调度器
  • 资源的名称(例如,cpu)
  • 资源的单位,如果知道的话(例如,cores)

一旦 Pod 进入完成状态(其 restartPolicy 为 Never 或 OnFailure,且其处于 Succeeded 或 Failed Pod 阶段,或者已经被删除且所有容器都具有终止状态),该时间序列停止报告,因为调度器现在可以调度其它 Pod 来执行。这两个指标称作 kube_pod_resource_request 和 kube_pod_resource_limit。

指标暴露在 HTTP 端点 /metrics/resources,与调度器上的 /metrics 端点一样要求相同的访问授权。你必须使用 --show-hidden-metrics-for-version=1.20 标志才能暴露那些稳定性为 Alpha 的指标。

禁用指标

你可以通过命令行标志 --disabled-metrics 来关闭某指标。在例如某指标会带来性能问题的情况下,这一操作可能是有用的。标志的参数值是一组被禁止的指标(例如:--disabled-metrics=metric1,metric2)。

指标顺序性保证

在 Alpha 阶段,标志只能接受一组映射值作为可以使用的指标标签。每个映射值的格式为<指标名称>,<标签名称>=<可用标签列表>,其中 <可用标签列表> 是一个用逗号分隔的、可接受的标签名的列表。

最终的格式看起来会是这样:--allow-label-value <指标名称>,<标签名称>='<可用值1>,<可用值2>...', <指标名称2>,<标签名称>='<可用值1>, <可用值2>...', ....

下面是一个例子:

--allow-label-value number_count_metric,odd_number='1,3,5', number_count_metric,even_number='2,4,6', date_gauge_metric,weekend='Saturday,Sunday'

5 - 日志架构

应用日志可以让你了解应用内部的运行状况。日志对调试问题和监控集群活动非常有用。大部分现代化应用都有某种日志记录机制。同样容器引擎也被设计成支持日志记录。针对容器化应用,最简单且最广泛采用的日志记录方式就是写入标准输出和标准错误流。

但是,由容器引擎或运行时提供的原生功能通常不足以构成完整的日志记录方案。例如,如果发生容器崩溃、Pod 被逐出或节点宕机等情况,你可能想访问应用日志。在集群中,日志应该具有独立的存储和生命周期,与节点、Pod 或容器的生命周期相独立。这个概念叫集群级的日志

集群级日志架构需要一个独立的后端用来存储、分析和查询日志。Kubernetes 并不为日志数据提供原生的存储解决方案。相反,有很多现成的日志方案可以集成到 Kubernetes 中。下面各节描述如何在节点上处理和存储日志。

Kubernetes 中的基本日志记录

这里的示例使用包含一个容器的 Pod 规约,每秒钟向标准输出写入数据。

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args: [/bin/sh, -c,
            'i=0; while true; do echo "$i: $(date)"; i=$((i+1)); sleep 1; done']

用下面的命令运行 Pod:

kubectl apply -f https://k8s.io/examples/debug/counter-pod.yaml

输出结果为:

pod/counter created

像下面这样,使用 kubectl logs 命令获取日志:

kubectl logs counter

输出结果为:

0: Mon Jan  1 00:00:00 UTC 2001
1: Mon Jan  1 00:00:01 UTC 2001
2: Mon Jan  1 00:00:02 UTC 2001
...

你可以使用命令 kubectl logs --previous 检索之前容器实例的日志。如果 Pod 中有多个容器,你应该为该命令附加容器名以访问对应容器的日志。

节点级日志记录

image

容器化应用写入 stdout 和 stderr 的任何数据,都会被容器引擎捕获并被重定向到某个位置。例如,Docker 容器引擎将这两个输出流重定向到某个日志驱动(Logging Driver),该日志驱动在 Kubernetes 中配置为以 JSON 格式写入文件。

说明: Docker JSON 日志驱动将日志的每一行当作一条独立的消息。该日志驱动不直接支持多行消息。你需要在日志代理级别或更高级别处理多行消息。

默认情况下,如果容器重启,kubelet 会保留被终止的容器日志。如果 Pod 在工作节点被驱逐,该 Pod 中所有的容器也会被驱逐,包括容器日志。

节点级日志记录中,需要重点考虑实现日志的轮转,以此来保证日志不会消耗节点上全部可用空间。Kubernetes 并不负责轮转日志,而是通过部署工具建立一个解决问题的方案。例如,在用 kube-up.sh 部署的 Kubernetes 集群中,存在一个 logrotate,每小时运行一次。你也可以设置容器运行时来自动地轮转应用日志。

例如,你可以找到关于 kube-up.sh 为 GCP 环境的 COS 镜像设置日志的详细信息,脚本为 configure-helper 脚本。

当使用某 CRI 容器运行时时,kubelet 要负责对日志进行轮换,并管理日志目录的结构。kubelet 将此信息发送给 CRI 容器运行时,后者将容器日志写入到指定的位置。在 kubelet 配置文件中的两个 kubelet 参数 containerLogMaxSize 和 containerLogMaxFiles 可以用来配置每个日志文件的最大长度和每个容器可以生成的日志文件个数上限。

当运行 kubectl logs 时,节点上的 kubelet 处理该请求并直接读取日志文件,同时在响应中返回日志文件内容。

说明: 如果有外部系统执行日志轮转或者使用了 CRI 容器运行时,那么 kubectl logs 仅可查询到最新的日志内容。比如,对于一个 10MB 大小的文件,通过 logrotate 执行轮转后生成两个文件,一个 10MB 大小,一个为空,kubectl logs 返回最新的日志文件,而该日志文件在这个例子中为空。

系统组件日志

系统组件有两种类型:在容器中运行的和不在容器中运行的。例如:

  • 在容器中运行的 kube-scheduler 和 kube-proxy。
  • 不在容器中运行的 kubelet 和容器运行时。

在使用 systemd 机制的服务器上,kubelet 和容器容器运行时将日志写入到 journald 中。如果没有 systemd,它们将日志写入到 /var/log 目录下的 .log 文件中。容器中的系统组件通常将日志写到 /var/log 目录,绕过了默认的日志机制。他们使用 klog 日志库。你可以在日志开发文档找到这些组件的日志告警级别约定。

和容器日志类似,/var/log 目录中的系统组件日志也应该被轮转。通过脚本 kube-up.sh 启动的 Kubernetes 集群中,日志被工具 logrotate 执行每日轮转,或者日志大小超过 100MB 时触发轮转。

集群级日志架构

虽然 Kubernetes 没有为集群级日志记录提供原生的解决方案,但你可以考虑几种常见的方法。以下是一些选项:

  • 使用在每个节点上运行的节点级日志记录代理。
  • 在应用程序的 Pod 中,包含专门记录日志的边车(Sidecar)容器。
  • 将日志直接从应用程序中推送到日志记录后端。

使用节点级日志代理

image

你可以通过在每个节点上使用节点级的日志记录代理来实现群集级日志记录。日志记录代理是一种用于暴露日志或将日志推送到后端的专用工具。通常,日志记录代理程序是一个容器,它可以访问包含该节点上所有应用程序容器的日志文件的目录。

由于日志记录代理必须在每个节点上运行,通常可以用 DaemonSet 的形式运行该代理。节点级日志在每个节点上仅创建一个代理,不需要对节点上的应用做修改。

容器向标准输出和标准错误输出写出数据,但在格式上并不统一。节点级代理收集这些日志并将其进行转发以完成汇总。

使用 sidecar 容器运行日志代理

你可以通过以下方式之一使用边车(Sidecar)容器:

  • 边车容器将应用程序日志传送到自己的标准输出。
  • 边车容器运行一个日志代理,配置该日志代理以便从应用容器收集日志。

传输数据流的 sidecar 容器

image

利用边车容器向自己的 stdout 和 stderr 传输流的方式,你就可以利用每个节点上的 kubelet 和日志代理来处理日志。边车容器从文件、套接字或 journald 读取日志。每个边车容器向自己的 stdout 和 stderr 流中输出日志。

这种方法允许你将日志流从应用程序的不同部分分离开,其中一些可能缺乏对写入 stdout 或 stderr 的支持。重定向日志背后的逻辑是最小的,因此它的开销几乎可以忽略不计。另外,因为 stdout、stderr 由 kubelet 处理,你可以使用内置的工具 kubectl logs。

例如,某 Pod 中运行一个容器,该容器向两个文件写不同格式的日志。下面是这个 pod 的配置文件:

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}

不建议在同一个日志流中写入不同格式的日志条目,即使你成功地将其重定向到容器的 stdout 流。相反,你可以创建两个边车容器。每个边车容器可以从共享卷跟踪特定的日志文件,并将文件内容重定向到各自的 stdout 流。

下面是运行两个边车容器的 Pod 的配置文件:

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log-1
    image: busybox
    args: [/bin/sh, -c, 'tail -n+1 -f /var/log/1.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-log-2
    image: busybox
    args: [/bin/sh, -c, 'tail -n+1 -f /var/log/2.log']
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  volumes:
  - name: varlog
    emptyDir: {}

现在当你运行这个 Pod 时,你可以运行如下命令分别访问每个日志流:

kubectl logs counter count-log-1

输出为:

0: Mon Jan  1 00:00:00 UTC 2001
1: Mon Jan  1 00:00:01 UTC 2001
2: Mon Jan  1 00:00:02 UTC 2001
...

kubectl logs counter count-log-2

输出为:

Mon Jan  1 00:00:00 UTC 2001 INFO 0
Mon Jan  1 00:00:01 UTC 2001 INFO 1
Mon Jan  1 00:00:02 UTC 2001 INFO 2
...

集群中安装的节点级代理会自动获取这些日志流,而无需进一步配置。如果你愿意,你也可以配置代理程序来解析源容器的日志行。

注意,尽管 CPU 和内存使用率都很低(以多个 CPU 毫核指标排序或者按内存的兆字节排序),向文件写日志然后输出到 stdout 流仍然会成倍地增加磁盘使用率。如果你的应用向单一文件写日志,通常最好设置 /dev/stdout 作为目标路径,而不是使用流式的边车容器方式。

应用本身如果不具备轮转日志文件的功能,可以通过边车容器实现。该方式的一个例子是运行一个小的、定期轮转日志的容器。然而,还是推荐直接使用 stdout 和 stderr,将日志的轮转和保留策略交给 kubelet。

具有日志代理功能的边车容器

image

如果节点级日志记录代理程序对于你的场景来说不够灵活,你可以创建一个带有单独日志记录代理的边车容器,将代理程序专门配置为与你的应用程序一起运行。

说明:
在边车容器中使用日志代理会带来严重的资源损耗。此外,你不能使用 kubectl logs 命令访问日志,因为日志并没有被 kubelet 管理。

下面是两个配置文件,可以用来实现一个带日志代理的边车容器。第一个文件包含用来配置 fluentd 的 ConfigMap。

apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
data:
  fluentd.conf: |
    <source>
      type tail
      format none
      path /var/log/1.log
      pos_file /var/log/1.log.pos
      tag count.format1
    </source>

    <source>
      type tail
      format none
      path /var/log/2.log
      pos_file /var/log/2.log.pos
      tag count.format2
    </source>

    <match **>
      type google_cloud
    </match>

说明:
要进一步了解如何配置 fluentd,请参考 fluentd 官方文档。

第二个文件描述了运行 fluentd 边车容器的 Pod 。flutend 通过 Pod 的挂载卷获取它的配置数据。

apiVersion: v1
kind: Pod
metadata:
  name: counter
spec:
  containers:
  - name: count
    image: busybox
    args:
    - /bin/sh
    - -c
    - >
      i=0;
      while true;
      do
        echo "$i: $(date)" >> /var/log/1.log;
        echo "$(date) INFO $i" >> /var/log/2.log;
        i=$((i+1));
        sleep 1;
      done
    volumeMounts:
    - name: varlog
      mountPath: /var/log
  - name: count-agent
    image: k8s.gcr.io/fluentd-gcp:1.30
    env:
    - name: FLUENTD_ARGS
      value: -c /etc/fluentd-config/fluentd.conf
    volumeMounts:
    - name: varlog
      mountPath: /var/log
    - name: config-volume
      mountPath: /etc/fluentd-config
  volumes:
  - name: varlog
    emptyDir: {}
  - name: config-volume
    configMap:
      name: fluentd-config

在示例配置中,你可以将 fluentd 替换为任何日志代理,从应用容器内的任何来源读取数据。

从应用中直接暴露日志目录

image

从各个应用中直接暴露和推送日志数据的集群日志机制已超出 Kubernetes 的范围。

6 - 系统日志

系统组件的日志记录集群中发生的事件,这对于调试非常有用。你可以配置日志的精细度,以展示更多或更少的细节。日志可以是粗粒度的,如只显示组件内的错误,也可以是细粒度的,如显示事件的每一个跟踪步骤(比如 HTTP 访问日志、pod 状态更新、控制器动作或调度器决策)。

Klog

klog 是 Kubernetes 的日志库。klog 为 Kubernetes 系统组件生成日志消息。

有关 klog 配置的更多信息,请参见命令行工具参考。

klog 原始格式的示例:

I1025 00:15:15.525108       1 httplog.go:79] GET /api/v1/namespaces/kube-system/pods/metrics-server-v0.3.1-57c75779f-9p8wg: (1.512ms) 200 [pod_nanny/v0.0.0 (linux/amd64) kubernetes/$Format 10.56.1.19:51756]

结构化日志

FEATURE STATE: Kubernetes v1.19 [alpha]

警告:
到结构化日志消息的迁移是一个持续的过程。在此版本中,并非所有日志消息都是结构化的。解析日志文件时,你也必须要处理非结构化日志消息。

日志格式和值的序列化可能会发生变化。

结构化日志记录旨在日志消息中引入统一结构,以便以编程方式提取信息。你可以方便地用更小的开销来处理结构化日志。新的消息格式向后兼容,并默认启用。

结构化日志的格式:

<klog header> "<message>" <key1>="<value1>" <key2>="<value2>" ...

示例:

I1025 00:15:15.525108       1 controller_utils.go:116] "Pod status updated" pod="kube-system/kubedns" status="ready"

JSON 日志格式

FEATURE STATE: Kubernetes v1.19 [alpha]

警告:
JSON 输出并不支持太多标准 klog 参数。对于不受支持的 klog 参数的列表,请参见命令行工具参考。

并不是所有日志都保证写成 JSON 格式(例如,在进程启动期间)。如果你打算解析日志,请确保可以处理非 JSON 格式的日志行。

字段名和 JSON 序列化可能会发生变化。

--logging-format=json 参数将日志格式从 klog 原生格式改为 JSON 格式。JSON 日志格式示例(美化输出):

{
   "ts": 1580306777.04728,
   "v": 4,
   "msg": "Pod status updated",
   "pod":{
      "name": "nginx-1",
      "namespace": "default"
   },
   "status": "ready"
}

具有特殊意义的 key:

  • ts - Unix 时间风格的时间戳(必选项,浮点值)
  • v - 精细度(必选项,整数,默认值 0)
  • err - 错误字符串(可选项,字符串)
  • msg - 消息(必选项,字符串)

当前支持JSON格式的组件列表:

  • kube-controller-manager
  • kube-apiserver
  • kube-scheduler
  • kubelet

日志清理

FEATURE STATE: Kubernetes v1.20 [alpha]

警告: 日志清理可能会导致大量的计算开销,因此不应启用在生产环境中。

--experimental-logging-sanitization 参数可用来启用 klog 清理过滤器。如果启用后,将检查所有日志参数中是否有标记为敏感数据的字段(比如:密码,密钥,令牌),并且将阻止这些字段的记录。

当前支持日志清理的组件列表:

  • kube-controller-manager
  • kube-apiserver
  • kube-scheduler
  • kubelet

说明: 日志清理过滤器不会阻止用户工作负载日志泄漏敏感数据。

日志精细度级别

参数 -v 控制日志的精细度。增大该值会增大日志事件的数量。减小该值可以减小日志事件的数量。增大精细度会记录更多的不太严重的事件。精细度设置为 0 时只记录关键(critical)事件。

日志位置

有两种类型的系统组件:运行在容器中的组件和不运行在容器中的组件。例如:

  • Kubernetes 调度器和 kube-proxy 在容器中运行。
  • kubelet 和容器运行时,例如 Docker,不在容器中运行。

在使用 systemd 的系统中,kubelet 和容器运行时写入 journald。在别的系统中,日志写入 /var/log 目录下的 .log 文件中。容器中的系统组件总是绕过默认的日志记录机制,写入 /var/log 目录下的 .log 文件。与容器日志类似,你应该轮转 /var/log 目录下系统组件日志。在 kube-up.sh 脚本创建的 Kubernetes 集群中,日志轮转由 logrotate 工具配置。logrotate 工具,每天或者当日志大于 100MB 时,轮转日志。

7 - 追踪 Kubernetes 系统组件

FEATURE STATE: Kubernetes v1.22 [alpha]

系统组件追踪功能记录各个集群操作的时延信息和这些操作之间的关系。

Kubernetes 组件基于 gRPC 导出器的 OpenTelemetry 协议发送追踪信息,并用 OpenTelemetry Collector 收集追踪信息,再将其转交给追踪系统的后台。

追踪信息的收集

关于收集追踪信息、以及使用收集器的完整指南,可参见 Getting Started with the OpenTelemetry Collector。不过,还有一些特定于 Kubernetes 组件的事项值得注意。

默认情况下,Kubernetes 组件使用 gRPC 的 OTLP 导出器来导出追踪信息,将信息写到 IANA OpenTelemetry 端口。举例来说,如果收集器以 Kubernetes 组件的边车模式运行,以下接收器配置会收集 spans 信息,并将它们写入到标准输出。

receivers:
  otlp:
    protocols:
      grpc:
exporters:
  # 用适合你后端环境的导出器替换此处的导出器
  logging:
    logLevel: debug
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [logging]

组件追踪

kube-apiserver 追踪

kube-apiserver 为传入的 HTTP 请求、传出到 webhook 和 etcd 的请求以及重入的请求生成 spans。由于 kube-apiserver 通常是一个公开的端点,所以它通过出站的请求传播 W3C 追踪上下文,但不使用入站请求的追踪上下文。

在 kube-apiserver 中启用追踪

要启用追踪特性,需要启用 kube-apiserver 上的 APIServerTracing 特性门控。然后,使用 --tracing-config-file=<配置文件路径> 为 kube-apiserver 提供追踪配置文件。下面是一个示例配置,它为万分之一的请求记录 spans,并使用了默认的 OpenTelemetry 端口。

apiVersion: apiserver.config.k8s.io/v1alpha1
kind: TracingConfiguration
# default value
#endpoint: localhost:4317
samplingRatePerMillion: 100

有关 TracingConfiguration 结构体的更多信息,请参阅 API 服务器配置 API (v1alpha1)。

稳定性

追踪工具仍在积极开发中,未来它会以多种方式发生变化。这些变化包括:span 名称、附加属性、检测端点等等。此类特性在达到稳定版本之前,不能保证追踪工具的向后兼容性。

8 - Kubernetes 中的代理

本文讲述了 Kubernetes 中所使用的代理。

代理

用户在使用 Kubernetes 的过程中可能遇到几种不同的代理(proxy):

  1. kubectl proxy:

    • 运行在用户的桌面或 pod 中
    • 从本机地址到 Kubernetes apiserver 的代理
    • 客户端到代理使用 HTTP 协议
    • 代理到 apiserver 使用 HTTPS 协议
    • 指向 apiserver
    • 添加认证头信息
  2. apiserver proxy:

    • 是一个建立在 apiserver 内部的“堡垒”
    • 将集群外部的用户与群集 IP 相连接,这些IP是无法通过其他方式访问的
    • 运行在 apiserver 进程内
    • 客户端到代理使用 HTTPS 协议 (如果配置 apiserver 使用 HTTP 协议,则使用 HTTP 协议)
    • 通过可用信息进行选择,代理到目的地可能使用 HTTP 或 HTTPS 协议
    • 可以用来访问 Node、Pod 或 Service
    • 当用来访问 Service 时,会进行负载均衡
  3. kube proxy:

    • 在每个节点上运行
    • 代理 UDP、TCP 和 SCTP
    • 不支持 HTTP
    • 提供负载均衡能力
    • 只用来访问 Service
  4. apiserver 之前的代理/负载均衡器:

    • 在不同集群中的存在形式和实现不同 (如 nginx)
    • 位于所有客户端和一个或多个 API 服务器之间
    • 存在多个 API 服务器时,扮演负载均衡器的角色
  5. 外部服务的云负载均衡器:

    • 由一些云供应商提供 (如 AWS ELB、Google Cloud Load Balancer)
    • Kubernetes 服务类型为 LoadBalancer 时自动创建
    • 通常仅支持 UDP/TCP 协议
    • SCTP 支持取决于云供应商的负载均衡器实现
    • 不同云供应商的云负载均衡器实现不同

Kubernetes 用户通常只需要关心前两种类型的代理,集群管理员通常需要确保后面几种类型的代理设置正确。

请求重定向

代理已经取代重定向功能,重定向功能已被弃用。

9 - API 优先级和公平性

FEATURE STATE: Kubernetes v1.20 [beta]

对于集群管理员来说,控制 Kubernetes API 服务器在过载情况下的行为是一项关键任务。kube-apiserver 有一些控件(例如:命令行标志 --max-requests-inflight 和 --max-mutating-requests-inflight ),可以限制将要接受的未处理的请求,从而防止过量请求入站,潜在导致 API 服务器崩溃。但是这些标志不足以保证在高流量期间,最重要的请求仍能被服务器接受。

API 优先级和公平性(APF)是一种替代方案,可提升上述最大并发限制。APF 以更细粒度的方式对请求进行分类和隔离。它还引入了空间有限的排队机制,因此在非常短暂的突发情况下,API 服务器不会拒绝任何请求。通过使用公平排队技术从队列中分发请求,这样,一个行为不佳的控制器就不会饿死其他控制器(即使优先级相同)。

注意: 属于“长时间运行”类型的请求(主要是 watch)不受 API 优先级和公平性过滤器的约束。如果未启用 APF 特性,即便设置 --max-requests-inflight 标志,该类请求也不受约束。

启用/禁用 API 优先级和公平性

API 优先级与公平性(APF)特性由特性门控控制,默认情况下启用。有关特性门控的一般性描述以及如何启用和禁用特性门控,请参见特性门控。APF 的特性门控称为 APIPriorityAndFairness。此特性也与某个 API 组 相关:(a) 一个 v1alpha1 版本,默认被禁用;(b) 一个 v1beta1 版本,默认被启用。你可以在启动 kube-apiserver 时,添加以下命令行标志来禁用此功能门控及 v1beta1 API 组:

kube-apiserver \
--feature-gates=APIPriorityAndFairness=false \
--runtime-config=flowcontrol.apiserver.k8s.io/v1beta1=false \
  # ...其他配置不变

或者,你也可以通过 --runtime-config=flowcontrol.apiserver.k8s.io/v1alpha1=true 启用 API 组的 v1alpha1 版本。

命令行标志 --enable-priority-fairness=false 将彻底禁用 APF 特性,即使其他标志启用它也是无效。

概念

APF 特性包含几个不同的功能。传入的请求通过 FlowSchema 按照其属性分类,并分配优先级。每个优先级维护自定义的并发限制,加强了隔离度,这样不同优先级的请求,就不会相互饿死。在同一个优先级内,公平排队算法可以防止来自不同 flow 的请求相互饿死。该算法将请求排队,通过排队机制,防止在平均负载较低时,通信量突增而导致请求失败。

优先级

如果未启用 APF,API 服务器中的整体并发量将受到 kube-apiserver 的参数 --max-requests-inflight 和 --max-mutating-requests-inflight 的限制。启用 APF 后,将对这些参数定义的并发限制进行求和,然后将总和分配到一组可配置的优先级中。每个传入的请求都会分配一个优先级;每个优先级都有各自的配置,设定允许分发的并发请求数。

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

排队

即使在同一优先级内,也可能存在大量不同的流量源。在过载情况下,防止一个请求流饿死其他流是非常有价值的(尤其是在一个较为常见的场景中,一个有故障的客户端会疯狂地向 kube-apiserver 发送请求,理想情况下,这个有故障的客户端不应对其他客户端产生太大的影响)。公平排队算法在处理具有相同优先级的请求时,实现了上述场景。每个请求都被分配到某个流中,该流由对应的 FlowSchema 的名字加上一个 流区分项(Flow Distinguisher)来标识。这里的流区分项可以是发出请求的用户、目标资源的名称空间或什么都不是。系统尝试为不同流中具有相同优先级的请求赋予近似相等的权重。

将请求划分到流中之后,APF 功能将请求分配到队列中。分配时使用一种称为混洗分片(Shuffle-Sharding)的技术。该技术可以相对有效地利用队列隔离低强度流与高强度流。

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

豁免请求

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

默认值

APF 特性附带推荐配置,该配置对实验场景应该足够;如果你的集群有可能承受较大的负载,那么你应该考虑哪种配置最有效。推荐配置将请求分为五个优先级:

  • system 优先级用于 system:nodes 组(即 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 命令。

系统内置了两个 PriorityLevelConfiguration 和两个 FlowSchema,它们是不可重载的:

  • 特殊的 exempt 优先级的请求完全不受流控限制:它们总是立刻被分发。特殊的 exempt FlowSchema 把 system:masters 组的所有请求都归入该优先级组。如果合适,你可以定义新的 FlowSchema,将其他请求定向到该优先级。
  • 特殊的 catch-all 优先级与特殊的 catch-all FlowSchema 结合使用,以确保每个请求都分类。一般地,你不应该依赖于 catch-all 的配置,而应适当地创建自己的 catch-all FlowSchema 和 PriorityLevelConfigurations(或使用默认安装的 global-default 配置)。 为了帮助捕获部分请求未分类的配置错误,强制要求 catch-all 优先级仅允许一个并发份额,并且不对请求进行排队,使得仅与 catch-all FlowSchema 匹配的流量被拒绝的可能性更高,并显示 HTTP 429 错误。

健康检查并发豁免

推荐配置没有为本地 kubelet 对 kube-apiserver 执行健康检查的请求进行任何特殊处理 ——它们倾向于使用安全端口,但不提供凭据。在推荐配置中,这些请求将分配 global-default FlowSchema 和 global-default 优先级,这样其他流量可以排除健康检查。

如果添加以下 FlowSchema,健康检查请求不受速率限制。

注意: 进行此更改后,任何敌对方都可以发送与此 FlowSchema 匹配的任意数量的健康检查请求。如果你有 Web 流量过滤器或类似的外部安全机制保护集群的 API 服务器免受常规网络流量的侵扰,则可以配置规则,阻止所有来自集群外部的健康检查请求。

apiVersion: flowcontrol.apiserver.k8s.io/v1alpha1
kind: FlowSchema
metadata:
  name: health-for-strangers
spec:
  matchingPrecedence: 1000
  priorityLevelConfiguration:
    name: exempt
  rules:
  - nonResourceRules:
    - nonResourceURLs:
      - "/healthz"
      - "/livez"
      - "/readyz"
      verbs:
      - "*"
    subjects:
    - kind: Group
      group:
        name: system:unauthenticated

资源

流控 API 涉及两种资源。PriorityLevelConfigurations 定义隔离类型和可处理的并发预算量,还可以微调排队行为。FlowSchemas 用于对每个入站请求进行分类,并与一个 PriorityLevelConfigurations 相匹配。此外同一 API 组还有一个 v1alpha1 版本,其中包含语法和语义都相同的资源类别。

PriorityLevelConfiguration

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

PriorityLevelConfigurations 的并发限制不是指定请求绝对数量,而是在“并发份额”中指定。API 服务器的总并发量限制通过这些份额按例分配到现有 PriorityLevelConfigurations 中。集群管理员可以更改 --max-requests-inflight (或 --max-mutating-requests-inflight )的值,再重新启动 kube-apiserver 来增加或减小服务器的总流量,然后所有的 PriorityLevelConfigurations 将看到其最大并发增加(或减少)了相同的比例。

注意: 启用 APF 功能后,服务器的总并发量限制将设置为 --max-requests-inflight 和 --max-mutating-requests-inflight 之和。可变请求和不可变请求之间不再有任何区别;如果对于某种资源,你需要区别对待不同请求,请创建不同的 FlowSchema 分别匹配可变请求和不可变请求。

当入站请求的数量大于分配的 PriorityLevelConfigurations 中允许的并发级别时,type 字段将确定对额外请求的处理方式。Reject 类型,表示多余的流量将立即被 HTTP 429(请求过多)错误所拒绝。Queue 类型,表示对超过阈值的请求进行排队,将使用阈值分片和公平排队技术来平衡请求流之间的进度。

公平排队算法支持通过排队配置对优先级微调。可以在增强建议中阅读算法的详细信息,但总之:

  • queues 递增能减少不同流之间的冲突概率,但代价是增加了内存使用量。 值为 1 时,会禁用公平排队逻辑,但仍允许请求排队。
  • queueLengthLimit 递增可以在不丢弃任何请求的情况下支撑更大的突发流量, 但代价是增加了等待时间和内存使用量。
  • 修改 handSize 允许你调整过载情况下不同流之间的冲突概率以及单个流可用的整体并发性。

说明: 较大的 handSize 使两个单独的流程发生碰撞的可能性较小(因此,一个流可以饿死另一个流),但是更有可能的是少数流可以控制 apiserver。较大的 handSize 还可能增加单个高并发流的延迟量。单个流中可能排队的请求的最大数量为 handSize * queueLengthLimit 。

下表显示了有趣的随机分片配置集合,每行显示给定的老鼠(低强度流)被不同数量的大象挤压(高强度流)的概率。

随机分片
队列数
1 个大象
4 个大象
16 个大象
12
32
4.428838398950118e-09
0.11431348830099144
0.9935089607656024
10
32
1.550093439632541e-08
0.0626479840223545
0.9753101519027554
10
64
6.601827268370426e-12
0.00045571320990370776
0.49999929150089345
9
64
3.6310049976037345e-11
0.00045501212304112273
0.4282314876454858
8
64
2.25929199850899e-10
0.0004886697053040446
0.35935114681123076
8
128
6.994461389026097e-13
3.4055790161620863e-06
0.02746173137155063
7
128
1.0579122850901972e-11
6.960839379258192e-06
0.02406157386340147
7
256
7.597695465552631e-14
6.728547142019406e-08
0.0006709661542533682
6
256
2.7134626662687968e-12
2.9516464018476436e-07
0.0008895654642000348
6
512
4.116062922897309e-14
4.982983350480894e-09
2.26025764343413e-05
6
1024
6.337324016514285e-16
8.09060164312957e-11
4.517408062903668e-07

FlowSchema

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

注意: 对一个请求来说,只有首个匹配的 FlowSchema 才有意义。如果一个入站请求与多个 FlowSchema 匹配,则将基于 matchingPrecedence 值最高的请求进行筛选。如果一个请求匹配多个 FlowSchema 且 matchingPrecedence 的值相同,则按 name 的字典序选择最小,但是最好不要依赖它,而是确保不存在两个 FlowSchema 具有相同的 matchingPrecedence 值。

当给定的请求与某个 FlowSchema 的 rules 的其中一条匹配,那么就认为该请求与该 FlowSchema 匹配。判断规则与该请求是否匹配,不仅要求该条规则的 subjects 字段至少存在一个与该请求相匹配,而且要求该条规则的 resourceRules 或 nonResourceRules(取决于传入请求是针对资源URL还是非资源URL)字段至少存在一个与该请求相匹配。

对于 subjects 中的 name 字段和资源和非资源规则的 verbs,apiGroups,resources,namespaces 和 nonResourceURLs 字段,可以指定通配符 * 来匹配任意值,从而有效地忽略该字段。

FlowSchema 的 distinguisherMethod.type 字段决定了如何把与该模式匹配的请求分散到各个流中。可能是 ByUser,在这种情况下,一个请求用户将无法饿死其他容量的用户;或者是 ByNamespace,在这种情况下,一个名称空间中的资源请求将无法饿死其它名称空间的资源请求;或者它可以为空(或者可以完全省略 distinguisherMethod),在这种情况下,与此 FlowSchema 匹配的请求将被视为单个流的一部分。资源和你的特定环境决定了如何选择正确一个 FlowSchema。

问题诊断

启用了 APF 的 API 服务器,它每个 HTTP 响应都有两个额外的 HTTP 头:X-Kubernetes-PF-FlowSchema-UID 和 X-Kubernetes-PF-PriorityLevel-UID,注意与请求匹配的 FlowSchema 和已分配的优先级。如果请求用户没有查看这些对象的权限,则这些 HTTP 头中将不包含 API 对象的名称,因此在调试时,你可以使用类似如下的命令:

kubectl get flowschemas -o custom-columns="uid:{metadata.uid},name:{metadata.name}"
kubectl get prioritylevelconfigurations -o custom-columns="uid:{metadata.uid},name:{metadata.name}"

来获取 UID 到 FlowSchema 的名称和 UID 到 PriorityLevelConfigurations 的名称的映射。

可观察性

指标

说明: 在 Kubernetes v1.20 之前的版本中,标签 flow_schema 和 priority_level 的命名有时被写作 flowSchema 和 priorityLevel,即存在不一致的情况。如果你在运行 Kubernetes v1.19 或者更早版本,你需要参考你所使用的集群版本对应的文档。

当你开启了 APF 后,kube-apiserver 会暴露额外指标。监视这些指标有助于判断你的配置是否不当地限制了重要流量,或者发现可能会损害系统健康的,行为不良的工作负载。

  • apiserver_flowcontrol_rejected_requests_total 是一个计数器向量,记录被拒绝的请求数量(自服务器启动以来累计值),由标签 flow_chema(表示与请求匹配的 FlowSchema),priority_evel(表示分配给请该求的优先级)和 reason 来区分。reason 标签将具有以下值之一:

    • queue-full,表明已经有太多请求排队
    • concurrency-limit,表示将 PriorityLevelConfiguration 配置为 Reject 而不是 Queue
    • time-out,表示在其排队时间超期的请求仍在队列中。
  • apiserver_flowcontrol_dispatched_requests_total 是一个计数器向量,记录开始执行的请求数量(自服务器启动以来的累积值),由标签 flow_schema(表示与请求匹配的 FlowSchema)和 priority_level(表示分配给该请求的优先级)来区分。

  • apiserver_current_inqueue_requests 是一个表向量,记录最近排队请求数量的高水位线,由标签 request_kind 分组,标签的值为 mutating 或 readOnly。这些高水位线表示在最近一秒钟内看到的最大数字。它们补充说明了老的表向量 apiserver_current_inflight_requests(该量保存了最后一个窗口中,正在处理的请求数量的高水位线)。

  • apiserver_flowcontrol_read_vs_write_request_count_samples 是一个直方图向量,记录当前请求数量的观察值,由标签 phase(取值为 waiting 和 executing)和 request_kind(取值 mutating 和 readOnly)拆分。定期以高速率观察该值。

  • apiserver_flowcontrol_read_vs_write_request_count_watermarks 是一个直方图向量,记录请求数量的高/低水位线,由标签 phase(取值为 waiting 和 executing)和 request_kind(取值为 mutating 和 readOnly)拆分;标签 mark 取值为 high 和 low 。apiserver_flowcontrol_read_vs_write_request_count_samples 向量观察到有值新增,则该向量累积。这些水位线显示了样本值的范围。

  • apiserver_flowcontrol_current_inqueue_requests 是一个表向量,记录包含排队中的(未执行)请求的瞬时数量,由标签 priorityLevel 和 flowSchema 拆分。

  • apiserver_flowcontrol_current_executing_requests 是一个表向量,记录包含执行中(不在队列中等待)请求的瞬时数量,由标签 priority_level 和 flow_schema 进一步区分。

  • apiserver_flowcontrol_request_concurrency_in_use 是一个规范向量,包含占用座位的瞬时数量,由标签 priority_level 和 flow_schema 进一步区分。

  • apiserver_flowcontrol_priority_level_request_count_samples 是一个直方图向量,记录当前请求的观测值,由标签 phase(取值为waiting 和 executing)和 priority_level 进一步区分。每个直方图都会定期进行观察,直到相关类别的最后活动为止。观察频率高。

  • apiserver_flowcontrol_priority_level_request_count_watermarks 是一个直方图向量,记录请求数的高/低水位线,由标签 phase(取值为 waiting 和 executing)和 priority_level 拆分;标签 mark 取值为 high 和 low 。apiserver_flowcontrol_priority_level_request_count_samples 向量观察到有值新增,则该向量累积。这些水位线显示了样本值的范围。

  • apiserver_flowcontrol_request_queue_length_after_enqueue 是一个直方图向量,记录请求队列的长度,由标签 priority_level 和 flow_schema 进一步区分。每个排队中的请求都会为其直方图贡献一个样本,并在添加请求后立即上报队列的长度。请注意,这样产生的统计数据与无偏调查不同。

说明: 直方图中的离群值在这里表示单个流(即,一个用户或一个名称空间的请求,具体取决于配置)正在疯狂地向 API 服务器发请求,并受到限制。相反,如果一个优先级的直方图显示该优先级的所有队列都比其他优先级的队列长,则增加 PriorityLevelConfigurations 的并发份额是比较合适的。

  • apiserver_flowcontrol_request_concurrency_limit 是一个表向量,记录并发限制的计算值(基于 API 服务器的总并发限制和 PriorityLevelConfigurations 的并发份额),并按标签 priority_level 进一步区分。
  • apiserver_flowcontrol_request_wait_duration_seconds 是一个直方图向量,记录请求排队的时间,由标签 flow_schema(表示与请求匹配的 FlowSchema ),priority_level(表示分配该请求的优先级)和 execute(表示请求是否开始执行)进一步区分。

说明: 由于每个 FlowSchema 总会给请求分配 PriorityLevelConfigurations,因此你可以为一个优先级添加所有 FlowSchema 的直方图,以获取分配给该优先级的请求的有效直方图。

  • apiserver_flowcontrol_request_execution_seconds 是一个直方图向量,记录请求实际执行需要花费的时间,由标签 flow_schema(表示与请求匹配的 FlowSchema )和 priority_level(表示分配给该请求的优先级)进一步区分。

调试端点

启用 APF 特性后,kube-apiserver 会在其 HTTP/HTTPS 端口提供以下路径:

  • /debug/api_priority_and_fairness/dump_priority_levels —— 所有优先级及其当前状态的列表。你可以这样获取:

    kubectl get --raw /debug/api_priority_and_fairness/dump_priority_levels

    输出类似于:

    PriorityLevelName, ActiveQueues, IsIdle, IsQuiescing, WaitingRequests, ExecutingRequests,
    workload-low,      0,            true,   false,       0,               0,
    global-default,    0,            true,   false,       0,               0,
    exempt,            <none>,       <none>, <none>,      <none>,          <none>,
    catch-all,         0,            true,   false,       0,               0,
    system,            0,            true,   false,       0,               0,
    leader-election,   0,            true,   false,       0,               0,
    workload-high,     0,            true,   false,       0,               0,
    
  • /debug/api_priority_and_fairness/dump_queues —— 所有队列及其当前状态的列表。你可以这样获取:

    kubectl get --raw /debug/api_priority_and_fairness/dump_queues

    输出类似于:

    PriorityLevelName, Index,  PendingRequests, ExecutingRequests, VirtualStart,
    workload-high,     0,      0,               0,                 0.0000,
    workload-high,     1,      0,               0,                 0.0000,
    workload-high,     2,      0,               0,                 0.0000,
    ...
    leader-election,   14,     0,               0,                 0.0000,
    leader-election,   15,     0,               0,                 0.0000,
    
  • /debug/api_priority_and_fairness/dump_requests ——当前正在队列中等待的所有请求的列表。你可以这样获取:

    kubectl get --raw /debug/api_priority_and_fairness/dump_requests

    输出类似于:

    PriorityLevelName, FlowSchemaName, QueueIndex, RequestIndexInQueue, FlowDistingsher,       ArriveTime,
    exempt,            <none>,         <none>,     <none>,              <none>,                <none>,
    system,            system-nodes,   12,         0,                   system:node:127.0.0.1, 2020-07-23T15:26:57.179170694Z,
    

    针对每个优先级别,输出中还包含一条虚拟记录,对应豁免限制。

    你可以使用以下命令获得更详细的清单:

    kubectl get --raw '/debug/api_priority_and_fairness/dump_requests?includeRequestDetails=1'

    输出类似于:

    PriorityLevelName, FlowSchemaName, QueueIndex, RequestIndexInQueue, FlowDistingsher,       ArriveTime,                     UserName,              Verb,   APIPath,                                                     Namespace, Name,   APIVersion, Resource, SubResource,
    system,            system-nodes,   12,         0,                   system:node:127.0.0.1, 2020-07-23T15:31:03.583823404Z, system:node:127.0.0.1, create, /api/v1/namespaces/scaletest/configmaps,
    system,            system-nodes,   12,         1,                   system:node:127.0.0.1, 2020-07-23T15:31:03.594555947Z, system:node:127.0.0.1, create, /api/v1/namespaces/scaletest/configmaps,
    

10 - 安装扩展(Addons)

说明: 本部分链接到提供 Kubernetes 所需功能的第三方项目。Kubernetes 项目作者不负责这些项目。此页面遵循 CNCF 网站指南,按字母顺序列出项目。要将项目添加到此列表中,请在提交更改之前阅读内容指南。

Add-ons 扩展了 Kubernetes 的功能。

本文列举了一些可用的 add-ons 以及到它们各自安装说明的链接。

每个 Add-ons 按字母顺序排序 - 顺序不代表任何优先地位。

网络和网络策略

  • ACI 通过 Cisco ACI 提供集成的容器网络和安全网络。
  • Antrea 在第 3/4 层执行操作,为 Kubernetes 提供网络连接和安全服务。Antrea 利用 Open vSwitch 作为网络的数据面。
  • Calico 是一个安全的 L3 网络和网络策略驱动。
  • Canal 结合 Flannel 和 Calico,提供网络和网络策略。
  • Cilium 是一个 L3 网络和网络策略插件,能够透明的实施 HTTP/API/L7 策略。同时支持路由(routing)和覆盖/封装(overlay/encapsulation)模式。
  • CNI-Genie 使 Kubernetes 无缝连接到一种 CNI 插件,例如:Flannel、Calico、Canal、Romana 或者 Weave。
  • Contiv 为多种用例提供可配置网络(使用 BGP 的原生 L3,使用 vxlan 的覆盖网络,经典 L2 和 Cisco-SDN/ACI)和丰富的策略框架。Contiv 项目完全开源。安装工具同时提供基于和不基于 kubeadm 的安装选项。
  • 基于 Tungsten Fabric 的 Contrail 是一个开源的多云网络虚拟化和策略管理平台,Contrail 和 Tungsten Fabric 与业务流程系统(例如 Kubernetes、OpenShift、OpenStack和Mesos)集成在一起,为虚拟机、容器或 Pod 以及裸机工作负载提供了隔离模式。
  • Flannel 是一个可以用于 Kubernetes 的 overlay 网络提供者。
  • Knitter 是为 kubernetes 提供复合网络解决方案的网络组件。
  • Multus 是一个多插件,可在 Kubernetes 中提供多种网络支持,以支持所有 CNI 插件(例如 Calico,Cilium,Contiv,Flannel),而且包含了在 Kubernetes 中基于 SRIOV、DPDK、OVS-DPDK 和 VPP 的工作负载。
  • OVN-Kubernetes 是一个 Kubernetes 网络驱动,基于 OVN(Open Virtual Network)实现,是从 Open vSwitch (OVS) 项目衍生出来的虚拟网络实现。OVN-Kubernetes 为 Kubernetes 提供基于覆盖网络的网络实现,包括一个基于 OVS 实现的负载均衡器和网络策略。
  • OVN4NFV-K8S-Plugin 是一个基于 OVN 的 CNI 控制器插件,提供基于云原生的服务功能链条(Service Function Chaining,SFC)、多种 OVN 覆盖网络、动态子网创建、动态虚拟网络创建、VLAN 驱动网络、直接驱动网络,并且可以驳接其他的多网络插件,适用于基于边缘的、多集群联网的云原生工作负载。
  • NSX-T 容器插件(NCP)提供了 VMware NSX-T 与容器协调器(例如 Kubernetes)之间的集成,以及 NSX-T 与基于容器的 CaaS / PaaS 平台(例如关键容器服务(PKS)和 OpenShift)之间的集成。
  • Nuage 是一个 SDN 平台,可在 Kubernetes Pods 和非 Kubernetes 环境之间提供基于策略的联网,并具有可视化和安全监控。
  • Romana 是一个 pod 网络的第三层解决方案,并支持 NetworkPolicy API。Kubeadm add-on 安装细节可以在这里找到。
  • Weave Net 提供在网络分组两端参与工作的网络和网络策略,并且不需要额外的数据库。

服务发现

CoreDNS 是一种灵活的,可扩展的 DNS 服务器,可以安装为集群内的 Pod 提供 DNS 服务。

可视化管理

  • Dashboard 是一个 Kubernetes 的 Web 控制台界面。
  • Weave Scope 是一个图形化工具,用于查看你的容器、Pod、服务等。请和一个 Weave Cloud 账号一起使用,或者自己运行 UI。

基础设施

KubeVirt 是可以让 Kubernetes 运行虚拟机的 add-ons。通常运行在裸机集群上。

遗留 Add-ons

还有一些其它 add-ons 归档在已废弃的 cluster/addons 路径中。

维护完善的 add-ons 应该被链接到这里。欢迎提出 PRs!

posted @ 2022-01-01 15:23  jpSpaceX  阅读(325)  评论(0编辑  收藏  举报