侧边栏壁纸
  • 累计撰写 28 篇文章
  • 累计创建 23 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

K8s 入门与配置5

zhanjie.me
2020-11-03 / 0 评论 / 0 点赞 / 1 阅读 / 0 字

5.1 kube-proxy 组件详解

在前面我们讲解过提供相同服务的一组 Pod 可以抽象成为一个 Service,通过 Service 提供的统一入口(一个虚拟的 Cluster IP 地址)来提供服务,Service 在这里起到了负载均衡器的作用,它会将接收到的请求转发给后端的 Pod。在大部分情况下,Service 只是一个概念,而真正起作用的是在各个 Node 节点上运行的 kube-proxy 服务进程。本实验将会介绍 kube-proxy 的原理和机制,方便大家能够更加深入的理解 Service 背后的实现逻辑。

在 Kubernetes 集群中的每个 Node 节点上都会运行一个 kube-proxy 服务进程,可以把这个进程看作是 Service 的透明代理兼负载均衡器,它的核心功能是将到某个 Service 的访问请求转发到后端的多个 Pod 实例上。

kube-proxy 提供了三种服务负载模式:

  • 基于用户态的 userspace 模式:早期的代理模式
  • iptables 模式:默认内核级代理模式
  • ipvs 模式:处于实验性阶段的代理模式

5.2 userspace 模式

userspace 模式是 kube-proxy 使用的第一代模式,该模式在 Kubernetes v1.0 版本开始支持使用。

userspace 模式的实现原理图示如下:

image-gfvzzxlj.png

kube-proxy 会为每个 Service 随机监听一个端口 (proxy port),并增加一条 iptables 规则。所以通过 ClusterIP:Port 访问 Service 的报文都 redirect 到 proxy port,kube-proxy 从它监听的 proxy port 收到报文以后,走 round robin(默认) 或是 session affinity(会话亲和力,即同一 client IP 都走同一链路给同一 pod 服务),分发给对应的 pod。

由于 userspace 模式会造成所有报文都走一遍用户态(也就是 Service 请求会先从用户空间进入内核 iptables,然后再回到用户空间,由 kube-proxy 完成后端 Endpoints 的选择和代理工作),需要在内核空间和用户空间转换,流量从用户空间进出内核会带来性能损耗,所以这种模式效率低、性能不高,不推荐使用。

5.3 iptables 模式

iptables 模式是 kube-proxy 使用的第二代模式,该模式在 Kubernetes v1.1 版本开始支持,从 v1.2 版本开始成为 kube-proxy 的默认模式。

iptables 模式的负载均衡模式是通过底层 netfilter/iptables 规则来实现的,通过 Informer 机制 Watch 接口实时跟踪 Service 和 Endpoint 的变更事件,并触发对 iptables 规则的同步更新。

iptables 模式的实现原理图示如下:

image-rxbcquup.png

通过图示可以发现在 iptables 模式下,kube-proxy 只是作为 controller,而不是 server,真正服务的是内核的 netfilter,体现用户态的是 iptables。所以整体的效率会比 userspace 模式高。

使用一个例子来查看 iptables 模式下的转发规则。

新建 nginx.yaml 文件,并向文件中写入如下内容:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - name: my-nginx
          image: nginx
          ports:
            - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    run: my-nginx

在这个 YAML 文件中使用 Deployment 创建了两个 nginx pod 副本,并定义了名为 my-nginx 的服务,选择所有标签为 run: my-nginx 的 pod,指定端口为 80。

然后执行资源创建:

$ kubectl create -f nginx.yaml
deployment.apps/my-nginx created
service/my-nginx created

创建成功以后查看新建的 Pod 和 Service 的 IP 地址:

$ kubectl get pods -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP              NODE           NOMINATED NODE   READINESS GATES
my-nginx-75897978cd-2fzzv   1/1     Running   0          68s   10.20.177.106   kubesphere03   <none>           <none>
my-nginx-75897978cd-gd9tf   1/1     Running   0          68s   10.20.138.38    kubesphere02   <none>           <none>
$ kubectl get svc
NAME       TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
my-nginx   ClusterIP   10.96.205.52   <none>        80/TCP    94s
$ kubectl describe svc my-nginx
Name:              my-nginx
Namespace:         user-test
Labels:            run=my-nginx
Annotations:       <none>
Selector:          run=my-nginx
Type:              ClusterIP
IP:                10.96.205.52
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.20.138.38:80,10.20.177.106:80
Session Affinity:  None
Events:            <none>

可以在任意一个节点,访问 my-nginx Service 的 ClusterIP 地址:

$ curl 10.96.205.52:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

可以看到能够通过 Service 的 ClusterIP 地址实现成功的访问服务背后对应的 Pod。同样的,使用 curl 访问 Endpoints 对应的地址也可以访问 nginx 服务。

分别在 kubesphere02 节点和kubesphere03 节点执行 iptables-save 命令查看这两个节点的 iptables:

# kubesphere02,对应的 Pod Endpoints 为 10.20.138.38:80
1.  -A KUBE-SERVICES -d 10.96.205.52/32 -p tcp -m comment --comment "user-test/my-nginx: cluster IP" -m tcp --dport 80 -j KUBE-SVC-UHUOVEJNVDXKG6EJ

2.  -A KUBE-SVC-UHUOVEJNVDXKG6EJ -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-YVUBAMI67C67Q2JG
3.  -A KUBE-SVC-UHUOVEJNVDXKG6EJ -j KUBE-SEP-EIB3624RTWHIQCRJ

4.  -A KUBE-SEP-YVUBAMI67C67Q2JG -s 10.20.138.38/32 -j KUBE-MARK-MASQ
5.  -A KUBE-SEP-YVUBAMI67C67Q2JG -p tcp -m tcp -j DNAT --to-destination 10.20.138.38:80

# kubesphere03,对应的 Pod Endpoints 为 10.20.177.106:80
1.  -A KUBE-SERVICES -d 10.96.205.52/32 -p tcp -m comment --comment "user-test/my-nginx: cluster IP" -m tcp --dport 80 -j KUBE-SVC-UHUOVEJNVDXKG6EJ

2.  -A KUBE-SVC-UHUOVEJNVDXKG6EJ -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-YVUBAMI67C67Q2JG
3.  -A KUBE-SVC-UHUOVEJNVDXKG6EJ -j KUBE-SEP-EIB3624RTWHIQCRJ

4.  -A KUBE-SEP-EIB3624RTWHIQCRJ -s 10.20.177.106/32 -j KUBE-MARK-MASQ
5.  -A KUBE-SEP-EIB3624RTWHIQCRJ -p tcp -m tcp -j DNAT --to-destination 10.20.177.106:80

从第 1 条规则中可以看到 my-nginx clusterIP,Node 节点上不需要有这个 ip 地址,iptables 在看到目的地址为 clusterIP 的符合规则的 tcp 报文会走 KUBE-SVC-UHUOVEJNVDXKG6EJ 规则。

第 2、3 条规则,KUBE-SVC-UHUOVEJNVDXKG6EJ 链实现了按 50% 的统计概率随机匹配到两条规则。

第 4、5 条规则是成对的两组规则,将报文转给了真正服务的 Pod。

上面的整个过程,从物理 Node 收到目的地址为 10.20.138.38:80,端口号为 80 的报文开始,到 my-nignx Pod 收到报文并响应,形成了一个完整的链路。可以发现整个报文链路上没有经过任何用户态进程,因此效率和稳定性都比较高。

iptables 模式实现起来比较简单,但存在无法避免的缺陷:当集群中的 Service 和 Pod 大量增加以后,iptables 中的规则会急剧增加,导致性能显著下降,在某些极端情况下甚至会出现规则丢失的情况,并且这种故障难以重现和排查。

iptables 模式相关的常用参数有:

  • --iptables-masquerade-bit:标记数据包将进行 SNAT 的 fwmark 位设置,有效范围为 [0,31],默认值为 14
  • --iptables-min-sync-period:刷新 iptables 规则的最小时间间隔
  • --iptables-sync-period:刷新 iptables 规则的最大时间间隔,必须大于 0,默认值为 30s

5.4 ipvs 模式

ipvs 模式被 kube-proxy 采纳为第三代模式,模式在 Kubernetes v1.8 版本开始引入,在 v1.9 版本中处于 beta 阶段,在 v1.11 版本中正式开始使用。

ipvs(IP Virtual Server) 实现了传输层负载均衡,也就是 4 层 LAN 交换,作为 Linux 内核的一部分。ipvs 运行在主机上,在真实服务器前充当负载均衡器。ipvs 可以将基于 TCP 和 UDP 的服务请求转发到真实服务器上,并使真实服务器上的服务在单个 IP 地址上显示为虚拟服务。

ipvs 模式的实现原理图示如下:

image-bakzfldg.png

ipvs 和 iptables 都是基于 netfilter 的,那么 ipvs 模式有哪些更好的性能呢?

  • ipvs 为大型集群提供了更好的可拓展性和性能
  • ipvs 支持比 iptables 更复杂的负载均衡算法(包括:最小负载、最少连接、加权等)
  • ipvs 支持服务器健康检查和连接重试等功能
  • 可以动态修改 ipset 的集合,即使 iptables 的规则正在使用这个集合

ipvs 依赖于 iptables。ipvs 会使用 iptables 进行包过滤、airpin-masquerade tricks(地址伪装)、SNAT 等功能,但是使用的是 iptables 的扩展 ipset,并不是直接调用 iptables 来生成规则链。通过 ipset 来存储需要 DROP 或 masquerade 的流量的源或目标地址,用于确保 iptables 规则的数量是恒定的,这样我们就不需要关心有多少 Service 或是 Pod 了。

使用 ipset 相较于 iptables 有什么优点呢?iptables 是线性的数据结构,而 ipset 引入了带索引的数据结构,当规则很多的时候,ipset 依然可以很高效的查找和匹配。我们可以将 ipset 简单理解为一个 IP(段) 的集合,这个集合的内容可以是 IP 地址、IP 网段、端口等,iptables 可以直接添加规则对这个“可变的集合进行操作”,这样就可以大大减少 iptables 规则的数量,从而减少性能损耗。

举一个例子,如果我们要禁止成千上万个 IP 访问我们的服务器,如果使用 iptables 就需要一条一条的添加规则,这样会在 iptables 中生成大量的规则;如果用 ipset 就只需要将相关的 IP 地址(网段)加入到 ipset 集合中,然后只需要设置少量的 iptables 规则就可以实现这个目标。

下面的表格是 ipvs 模式下维护的 ipset 表集合:

设置名称 成员 用法
KUBE-CLUSTER-IP 所有服务 IP + 端口 在 masquerade-all=true 或 clusterCIDR 指定的情况下对 Service Cluster IP 地址进行伪装,解决数据包欺骗问题
KUBE-LOOP-BACK 所有服务 IP + 端口 + IP 解决数据包欺骗问题
KUBE-EXTERNAL-IP 服务外部 IP + 端口 将数据包伪装成 Service 的外部 IP 地址
KUBE-LOAD-BALANCER 负载均衡器入口 IP + 端口 将数据包伪装成 Load Balancer 类型的 Service
KUBE-LOAD-BALANCER-LOCAL 负载均衡器入口 IP + 端口 以及 externalTrafficPolicy=local 接受数据包到 Load Balancer externalTrafficPolicy=local
KUBE-LOAD-BALANCER-FW 负载均衡器入口 IP + 端口 以及 loadBalancerSourceRanges 使用指定的 loadBalancerSourceRanges 丢弃 Load Balancer 类型 Service 的数据包
KUBE-LOAD-BALANCER-SOURCE-CIDR 负载均衡器入口 IP + 端口 + 源 CIDR 接受 Load Balancer 类型 Service 的数据包,并指定 loadBalancerSourceRanges
KUBE-NODE-PORT-TCP NodePort 类型服务 TCP 端口 将数据包伪装成 NodePort(TCP)
KUBE-NODE-PORT-LOCAL-TCP NodePort 类型服务 TCP 端口,带有 externalTrafficPolicy=local 接受数据包到 NodePort 服务,使用 externalTrafficPolicy=local
KUBE-NODE-PORT-UDP NodePort 类型服务 UDP 端口 将数据包伪装成 NodePort(UDP)
KUBE-NODE-PORT-LOCAL-UDP NodePort 类型服务 UDP 端口,使用 externalTrafficPolicy=local 接受数据包到 NodePort 服务,使用 externalTrafficPolicy=local

ipvs 模式相关的常用参数有:

  • --cleanup-ipvs:值为 true 时清除在 ipvs 模式下创建的 ipvs 配置和 iptables 规则
  • --proxy-mode:通过 --proxy-mode = ipvs 进行设置,隐式使用 ipvs NAT 模式进行服务端口映射
  • --ipvs-scheduler:指定 ipvs 负载均衡算法,如果未配置,则默认为 round-robin(rr) 算法
  • --ipvs-sync-period:刷新 ipvs 规则的最大间隔时间(比如 5s、1m),必须大于 0
  • --ipvs-min-sync-period:刷新 ipvs 规则的最小间隔时间间隔(比如 5s、1m),必须大于 0
  • --ipvs-sync-period:刷新 ipvs 规则的最大时间间隔,必须大于 0,默认值为 30s
  • --ipvs-exclude-cidrs:清除 ipvs 规则时 ipvs 代理不应触及的 CIDR 的逗号分隔列表,因为 ipvs 代理无法区分 kube-proxy 创建的 ipvs 规则和用户原始 ipvs 规则,如果在环境中使用 ipvs 代理模式和自己的 ipvs 规则,则应指定此参数,否则将清除原始规则

5.5 kube-proxy 启动参数

kube-proxy 其它启动参数有:

  • --bind-address:kube-proxy 绑定主机的 IP 地址,默认值为 0.0.0.0,表示绑定所有 IP 地址
  • --cleanup:设置为 true 表示在清除 iptables 规则和 ipvs 规则后退出
  • --cluster-cidr:集群中 Pod 的 CIDR 地址范围,用于桥接集群外部流量到内部
  • --config:kube-proxy 的主配置文件
  • --config-sync-period:从 API Server 更新配置的时间间隔,必须大于 0,默认值为 15m0s
  • --conntrack-max-per-core:跟踪每个 CPU core 的 NAT 连接的最大数量(设置为 0 表示无限制,并忽略 conntrack-min 的值),默认值为 32768
  • --conntrack-min:最小 conntrack 条目的分配数量,默认值为 131072
  • --conntrack-tcp-timeout-close-wait:当 TCP 连接处于 CLOSE_WAIT 状态时的 NAT 超时时间,默认值为 1h0m0s
  • --conntrack-tcp-timeout-established:建立 TCP 连接的超时时间,设置为 0 表示无限制,默认值为 24h0m0s
  • --healthz-bind-address:healthz 服务绑定主机 IP 地址,设置为 0.0.0.0 表示使用所有 IP 地址,默认值为 0.0.0.0:10256
  • --healthz-port:healthz 服务监听的主机端口号,设置为 0 表示不启用,默认值为 10256
  • --hostname-override:设置本 Node 在集群中的主机名,不设置时将使用本机 hostname
  • --kube-api-burst:每秒发送到 API Server 的请求的数量,默认值为 10
  • --kubeconfig:kubeconfig 配置文件路径,在配置文件中包括 Master 地址信息及必要的认证信息
  • --masquerade-all:设置为 true 表示使用纯 iptables 代理,所有网络包都将进行 SNAT 转换
  • --master:API Server 的地址
  • --metrics-bind-address:Metrics Server 的监听地址,设置为 0.0.0.0 表示使用所有 IP 地址,默认值为 127.0.0.1:10249
  • --proxy-mode:代理模式,可选项为 userspace、iptables、ipvs,默认值为 iptables,当操作系统内核版本或 iptables 版本不够新时,将自动降级为 userspace 模式

5.6 API Server 概述

Kubernetes API Server 是 Kubernetes 集群最重要的核心组件之一,是唯一能够与存储 etcd 交互通信的组件,主要提供以下功能:

  • 整个集群管理的 API 接口:所有对集群进行的查询和管理都要通过 API 来进行
  • 集群内部各个模块之间通信的枢纽:所有模块之间不会互相调用,都是通过与 API Server 交互实现对应的功能
  • 集群安全控制:API Server 通过提供的验证和授权确保整个集群的安全

多种方式访问 API Server

Kubernetes API Server 通过运行在 Master 节点上的名为 kube-apiserver 的进程提供服务:

$ kubectl get pods -n kube-system|grep kube-apiserver
kube-apiserver-kubesphere01                1/1     Running   0          65d

默认情况下,kube-apiserver 进程在 Master 节点的 8080/6443 端口提供 REST 服务,在启动服务之后可以查看 Kubernetes API 版本信息:

$ kubectl get --raw /api
{"kind":"APIVersions","versions":["v1"],"serverAddressByClientCIDRs":[{"clientCIDR":"0.0.0.0/0","serverAddress":"192.168.0.31:6443"}]}

也可以查看 API Server 提供了哪些接口:

$ kubectl get --raw /
{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/",
    "/apis/admissionregistration.k8s.io",
    "/apis/admissionregistration.k8s.io/v1",
    "/apis/admissionregistration.k8s.io/v1beta1",
    "/apis/apiextensions.k8s.io",
    "/apis/apiextensions.k8s.io/v1",
    "/apis/apiextensions.k8s.io/v1beta1",
    "/apis/apiregistration.k8s.io",
    "/apis/apiregistration.k8s.io/v1",
    "/apis/apiregistration.k8s.io/v1beta1",
    "/apis/app.k8s.io",
    "/apis/app.k8s.io/v1beta1",
    "/apis/apps",
    "/apis/apps/v1",
    "/apis/authentication.istio.io",
    "/apis/authentication.istio.io/v1alpha1",
    "/apis/authentication.k8s.io",
    "/apis/authentication.k8s.io/v1",
    "/apis/authentication.k8s.io/v1beta1",
    "/apis/authorization.k8s.io",
    "/apis/authorization.k8s.io/v1",
    "/apis/authorization.k8s.io/v1beta1",
    "/apis/autoscaling",
    "/apis/autoscaling/v1",
    "/apis/autoscaling/v2beta1",
    "/apis/autoscaling/v2beta2",
    "/apis/batch",
    "/apis/batch/v1",
    "/apis/batch/v1beta1",
    "/apis/certificates.k8s.io",
    "/apis/certificates.k8s.io/v1beta1",
    "/apis/config.istio.io",
    "/apis/config.istio.io/v1alpha2",
    "/apis/coordination.k8s.io",
    "/apis/coordination.k8s.io/v1",
    "/apis/coordination.k8s.io/v1beta1",
    "/apis/crd.projectcalico.org",
    "/apis/crd.projectcalico.org/v1",
    "/apis/devops.kubesphere.io",
    "/apis/devops.kubesphere.io/v1alpha1",
    "/apis/events.k8s.io",
    "/apis/events.k8s.io/v1beta1",
    "/apis/extensions",
    "/apis/extensions/v1beta1",
    "/apis/jaegertracing.io",
    "/apis/jaegertracing.io/v1",
    "/apis/logging.kubesphere.io",
    "/apis/logging.kubesphere.io/v1alpha1",
    "/apis/metrics.k8s.io",
    "/apis/metrics.k8s.io/v1beta1",
    "/apis/monitoring.coreos.com",
    "/apis/monitoring.coreos.com/v1",
    "/apis/networking.istio.io",
    "/apis/networking.istio.io/v1alpha3",
    "/apis/networking.k8s.io",
    "/apis/networking.k8s.io/v1",
    "/apis/networking.k8s.io/v1beta1",
    "/apis/node.k8s.io",
    "/apis/node.k8s.io/v1beta1",
    "/apis/policy",
    "/apis/policy/v1beta1",
    "/apis/rbac.authorization.k8s.io",
    "/apis/rbac.authorization.k8s.io/v1",
    "/apis/rbac.authorization.k8s.io/v1beta1",
    "/apis/rbac.istio.io",
    "/apis/rbac.istio.io/v1alpha1",
    "/apis/scheduling.k8s.io",
    "/apis/scheduling.k8s.io/v1",
    "/apis/scheduling.k8s.io/v1beta1",
    "/apis/servicemesh.kubesphere.io",
    "/apis/servicemesh.kubesphere.io/v1alpha2",
    "/apis/storage.k8s.io",
    "/apis/storage.k8s.io/v1",
    "/apis/storage.k8s.io/v1beta1",
    "/apis/tenant.kubesphere.io",
    "/apis/tenant.kubesphere.io/v1alpha1",
    "/healthz",
    "/healthz/autoregister-completion",
    "/healthz/etcd",
    "/healthz/log",
    "/healthz/ping",
    "/healthz/poststarthook/apiservice-openapi-controller",
    "/healthz/poststarthook/apiservice-registration-controller",
    "/healthz/poststarthook/apiservice-status-available-controller",
    "/healthz/poststarthook/bootstrap-controller",
    "/healthz/poststarthook/ca-registration",
    "/healthz/poststarthook/crd-informer-synced",
    "/healthz/poststarthook/generic-apiserver-start-informers",
    "/healthz/poststarthook/kube-apiserver-autoregistration",
    "/healthz/poststarthook/rbac/bootstrap-roles",
    "/healthz/poststarthook/scheduling/bootstrap-system-priority-classes",
    "/healthz/poststarthook/start-apiextensions-controllers",
    "/healthz/poststarthook/start-apiextensions-informers",
    "/healthz/poststarthook/start-kube-aggregator-informers",
    "/healthz/poststarthook/start-kube-apiserver-admission-initializer",
    "/livez",
    "/livez/autoregister-completion",
    "/livez/etcd",
    "/livez/log",
    "/livez/ping",
    "/livez/poststarthook/apiservice-openapi-controller",
    "/livez/poststarthook/apiservice-registration-controller",
    "/livez/poststarthook/apiservice-status-available-controller",
    "/livez/poststarthook/bootstrap-controller",
    "/livez/poststarthook/ca-registration",
    "/livez/poststarthook/crd-informer-synced",
    "/livez/poststarthook/generic-apiserver-start-informers",
    "/livez/poststarthook/kube-apiserver-autoregistration",
    "/livez/poststarthook/rbac/bootstrap-roles",
    "/livez/poststarthook/scheduling/bootstrap-system-priority-classes",
    "/livez/poststarthook/start-apiextensions-controllers",
    "/livez/poststarthook/start-apiextensions-informers",
    "/livez/poststarthook/start-kube-aggregator-informers",
    "/livez/poststarthook/start-kube-apiserver-admission-initializer",
    "/logs",
    "/metrics",
    "/openapi/v2",
    "/readyz",
    "/readyz/autoregister-completion",
    "/readyz/etcd",
    "/readyz/log",
    "/readyz/ping",
    "/readyz/poststarthook/apiservice-openapi-controller",
    "/readyz/poststarthook/apiservice-registration-controller",
    "/readyz/poststarthook/apiservice-status-available-controller",
    "/readyz/poststarthook/bootstrap-controller",
    "/readyz/poststarthook/ca-registration",
    "/readyz/poststarthook/crd-informer-synced",
    "/readyz/poststarthook/generic-apiserver-start-informers",
    "/readyz/poststarthook/kube-apiserver-autoregistration",
    "/readyz/poststarthook/rbac/bootstrap-roles",
    "/readyz/poststarthook/scheduling/bootstrap-system-priority-classes",
    "/readyz/poststarthook/start-apiextensions-controllers",
    "/readyz/poststarthook/start-apiextensions-informers",
    "/readyz/poststarthook/start-kube-aggregator-informers",
    "/readyz/poststarthook/start-kube-apiserver-admission-initializer",
    "/readyz/shutdown",
    "/version"
  ]
}

5.7 API Server 工作原理

API Server 的架构如下图所示:

image-yqrvvsah.png

整个 API Server 从上往下大致可以分为如下 4 层:

  1. API 层:主要以 REST 方式提供各种 API 接口,包括:对 Kubernetes 资源对象进行 CRUD 和 watch 等操作的核心/分组 API,提供健康检查、UI、日志、性能指标等运维监控相关的 API。
  2. 访问控制层:当客户端访问 API 接口时,访问控制层负责对用户身份鉴权,验明用户身份,核准用户对 Kubernetes 资源对象的访问权限,然后根据配置的各种资源访问许可逻辑 (Admisson Control),判断是否允许访问。
  3. 注册表层:kubernetes 把所有资源对象都保存在注册表 (Registery) 中,并且对于所有的资源对象都定义了:资源对象的类型、如何创建资源对象、如何转换资源的不同版本、以及如何将资源编码和解码为 JSON 或 ProtoBuf 格式进行存储。
  4. etcd 数据库:用于持久化存储 Kubernetes 资源对象的 key-value 数据库。

集群功能模块之间的通信

那么集群中其它的重要的功能模块具体是如何与 API Server 进行交互的呢?

kubelet 与 API Server 的交互:

  • 每个 Node 节点上的 kubelet 每隔一段时间向 API Server 的 REST 接口发送自身的状态信息;
  • API Server 收到信息后更新到 etcd 中;
  • kubelet 通过 API Server 的 watch 接口监听 Pod 的实时变化信息,如果监听到有新的 Pod 对象绑定到这个节点,就创建 Pod;如果是删除某个 Pod,也执行对应的操作。

kube-scheduler 与 API Server 的交互:

  • kube-scheduler 通过 API Server 的 watch 接口监听到新建 Pod 副本的信息后,会检索所有符合该 Pod 要求的 Node 列表,开始执行 Pod 调度逻辑,调度成功后将 Pod 绑定到目标节点上。

kube-controller-manager 与 API Server 的交互:

  • kube-controller-manager 中的 Node Controller 模块通过 API Server 的 watch 接口实时监控 Node 的信息,并做对应的处理。

为了缓解 API Server 过高的访问负荷,kubernetes 系统提供了缓存机制。各功能模块定时从 API Server 获取指定的资源对象信息(使用 List/Watch 方法),然后保存到本地缓存,各功能模块在某些情况下通过访问缓存获取需要的信息,以便减轻 API Server 的压力。

5.8 访问控制

kubernetes API 的每个请求都会经过多阶段的访问控制之后才会被接受,整个阶段包括:认证 (Authentication)、授权 (Authorization)、准入控制 (Admisson Control)。

流程如下图所示:

image-mzvusfho.png

  1. 认证 (Authentication):开启 TLS 后,所有请求都必须要经过认证。kubernetes 支持多种认证机制,并支持同时开启多个认证插件(只要有一个认证通过即可)。如果认证成功,用户的 username 会被传入授权模块做授权验证,如果认证失败会返回 HTTP 401。目前常用的方式有两种:Token 或是 SSL。
  2. 授权 (Authorization):通过认证的请求就传递到了授权模块,kubernetes 支持多种授权机制,并支持同时开启多个授权插件(只要有一个授权通过即可)。如果认证成功,用户的请求会发送到准入控制模块进行请求验证,如果授权失败会返回 HTTP 403。目前常用的方式有四种:ABAC(基于属性的访问控制)、RBAC(基于角色的访问控制)、NODE(基于节点的访问控制)、Webhook(自定义 HTTP 回调的访问控制)
  3. 准入控制 (Admisson Control):对请求进一步验证或添加默认参数,认证和授权分别处理了请求的用户和操作,准入控制模块则处理请求的内容,它只对创建、更新、删除或连接(如代理)等有效,对读操作无效。准入控制也支持同时开启多个插件,它们依次调用,只有全部插件都通过的请求才能够获取到最终的数据。

5.9 常用参数介绍

kube-apiserver 常用且比较重要的参数如下所示:

  • --advertise-address:向集群成员通知 apiserver 消息的 IP 地址。这个地址必须能够被集群中其他成员访问。如果 IP 地址为空,将会使用 --bind-address,如果未指定 --bind-address,将会使用主机的默认接口地址
  • --allow-privileged:是否允许特权容器运行,默认值为 false
  • --admission-control:控制资源进入集群的准入控制插件的顺序列表,逗号分隔的 NamespaceLifecycle 列表,默认值为 [AlwaysAdmit]
  • --authorization-mode:在安全端口上进行权限验证的插件的顺序列表,以逗号分隔的列表,包括:AlwaysAllow、AlwaysDeny、ABAC、Webhook、RBAC、Node,默认值为 [AlwaysAllow]
  • --bind-address:HTTPS 安全接口的监听地址,监听 --seure-port 的 IP 地址。如果为空,则将使用所有接口(0.0.0.0),默认值为 0.0.0.0
  • --secure-port:HTTPS 安全接口的监听端口,用于监听具有认证授权功能的 HTTPS 协议的端口。如果为 0,则不会监听 HTTPS 协议,默认值为 6443
  • --cert-dir:存放 TLS 证书的目录,如果提供了 --tls-cert-file 和--tls-private-key-file 选项,该标志将被忽略,默认值为 "/var/run/kubernetes"
  • --etcd-prefix:附加到所有 etcd 中资源路径的前缀,默认值为 "/registry"
  • --etcd-servers:连接的 etcd 服务器列表,形式为(scheme://ip:port),使用逗号分隔
  • --insecure-bind-address:HTTP 访问的地址,用于监听 --insecure-port 的 IP 地址,设置成 0.0.0.0 表示监听所有接口,默认值为 127.0.0.1
  • --insecure-port:HTTP 访问的端口,用于监听不安全和为认证访问的端口。这个配置假设你已经设置了防火墙规则,使得这个端口不能从集群外访问,对集群的公共地址的 443 端口的访问将被代理到这个端口,默认设置中使用 nginx 实现,默认值为 8080
  • --service-cluster-ip-range:CIDR 表示的 IP 范围,服务的 cluster ip 将从中分配,一定不要和分配给 nodes 和 pods 的 IP 范围产生重叠

可以查看环境中 kube-apiserver 的启动参数:

$ ss -lnp|grep -E "8080|6443"
tcp    LISTEN     0      128    [::]:6443               [::]:*                   users:(("kube-apiserver",pid=2807,fd=3))
$ ps -ef|grep -v grep|grep kube-apiserver
root      2807  2751  4 11月02 ?      01:00:22 kube-apiserver --advertise-address=192.168.0.31 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/etc/kubernetes/pki/ca.crt --enable-admission-plugins=NodeRestriction --enable-bootstrap-token-auth=true --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key --etcd-servers=https://127.0.0.1:2379 --insecure-port=0 --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key --requestheader-allowed-names=front-proxy-client --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User --secure-port=6443 --service-account-key-file=/etc/kubernetes/pki/sa.pub --service-cluster-ip-range=10.96.0.0/16 --tls-cert-file=/etc/kubernetes/pki/apiserver.crt --tls-private-key-file=/etc/kubernetes/pki/apiserver.key

5.10 Kubernetes Proxy API 接口

kubernetes API Server 最主要的 REST 接口是对资源对象执行 CRUD 操作的接口,但是它还有一类很特殊的 REST 接口 -- Kubernetes Proxy API 接口,这类接口的作用是代理 REST 请求,即 Kubernetes API Server 把收到的 REST 请求转发到某个 Node 节点上的 kubelet 守护进程的 REST 端口,由该 kubelet 进程负责响应。

Node 相关接口

与 Node 相关接口的 REST 路径为 /api/v1/nodes/{name}/proxy,其中 {name} 是节点的名称或是 IP 地址。

比如通过 kubectl get --raw /api/v1/nodes/kubesphere01/proxy/pods 获取 kubesphere01 节点上所有的 pod 信息,结果如下所示:(注意这里获取的数据信息来自于 Node 而不是 etcd 数据库,所以数据有时可能存在一定偏差)

$ kubectl get --raw /api/v1/nodes/kubesphere01/proxy/pods
{"kind":"PodList","apiVersion":"v1","metadata":{},"items":[{"metadata":{"name":"openldap-0","generateName":"openldap-","namespace":"kubesphere-system","selfLink":"/api/v1/namespaces/kubesphere-system/pods/openldap-0","uid":"794d6648-6788-471e-9211-9fb9d75b7019","resourceVersion":"32043","creationTimestamp":"2020-08-30T07:39:49Z","labels":{"app.kubernetes.io/instance":"ks-openldap","app.kubernetes.io/name":"openldap-ha","controller-revision-hash":"openldap-5b89576789","statefulset.kubernetes.io/pod-name":"openldap-0"},"annotations":{"cni.projectcalico.org/podIP":"10.20.143.195/32","kubernetes.io/config.seen":"2020-08-30T15:39:49.928479871+08:00","kubernetes.io/config.source":"api"},"ownerReferences":[{"apiVersion":"apps/v1","kind":"StatefulSet","name":"openldap","uid":"ecacad0e-bd3a-4076-b585-561ecff8705a","controller":true,"blockOwnerDeletion":true}]},"spec":{"volumes":[{"name":"openldap-pvc","persistentVolumeClaim":{"claimName":"openldap-pvc-openldap-0"}},{"name":"default-token-5b68z","secret":{"secretName":"default-token-5b68z","defaultMode":420}}],"containers":[{"name":"openldap-ha","image":"osixia/openldap:1.3.0","args":["--copy-service","--loglevel=warning"],"ports":[{"name":"ldap","containerPort":389,"protocol":"TCP"}],"env":[{"name":"LDAP_ORGANISATION","value":"kubesphere"},{"name":"LDAP_DOMAIN","value":"kubesphere.io"},{"name":"LDAP_CONFIG_PASSWORD","value":"admin"},{"name":"LDAP_ADMIN_PASSWORD","value":"admin"},{"name":"LDAP_REPLICATION","value":"false"},{"name":"LDAP_TLS","value":"false"},{"name":"LDAP_REMOVE_CONFIG_AFTER_SETUP","value":"true"},{"name":"MY_POD_NAME","valueFrom":{"fieldRef":{"apiVersion":"v1","fieldPath":"metadata.name"}}},{"name":"HOSTNAME","value":"$(MY_POD_NAME).openldap"}],"resources":{},"volumeMounts":[{"name":"openldap-pvc","mountPath":"/var/lib/ldap","subPath":"ldap-data"},{"name":"openldap-pvc","mountPath":"/etc/ldap/slapd.d","subPath":"ldap-config"},{"name":"default-token-5b68z","readOnly":true,"mountPath":"/var/run/secrets/kubernetes.io/serviceaccount"}],"livenessProbe":{"tcpSocket":{"port":389},"initialDelaySeconds":30,"timeoutSeconds":1,"periodSeconds":15,"successThreshold":1,"failureThreshold":3},"readinessProbe":{"tcpSocket":{"port":389},"initialDelaySeconds":30,"timeoutSeconds":1,"periodSeconds":15,"successThreshold":1,"failureThreshold":3},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"IfNotPresent"}],....

还有一些其它的接口:

/api/v1/nodes/{name}/proxy/pods   # 列出指定节点内所有 pod 的信息
/api/v1/nodes/{name}/proxy/stats  # 列出指定节点内物理资源的统计信息
/api/v1/nodes/{name}/proxy/spec   # 列出指定节点的概要信息
/api/v1/nodes/{name}/proxy/logs   # 列出指定节点的各类日志信息
/api/v1/nodes/{name}/proxy/metrics # 列出指定节点的 Metrics 信息

Pod 相关接口

通过 Pod proxy 接口,我们可以访问 Pod 里面某个容器提供的服务。通常与 Pod 相关接口的 REST 路径为:

/api/v1/namespaces/{namespace}/pods/{name}/proxy  # 访问 Pod
/api/v1/namespaces/{namespace}/pods/{name}/proxy/{path:*}  # 访问 Pod 服务的 URL 路径

比如我们部署前面提过的 nginx 的 pod:

$ kubectl get pods
NAME                        READY   STATUS              RESTARTS   AGE
my-nginx-75897978cd-g7c8h   0/1     ContainerCreating   0          4s
my-nginx-75897978cd-n4lx7   0/1     ContainerCreating   0          4s

通过Proxy API访问nginx的首页:

$ kubectl get --raw /api/v1/namespaces/user-test/pods/my-nginx-75897978cd-g7c8h/proxy
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Service 相关接口

与 Service 相关接口的 REST 路径为 /api/v1/namespaces/{namespace}/services/{name}/proxy

$ kubectl get --raw /api/v1/namespaces/user-test/services/my-nginx/proxy
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
0

评论区