Kubernetes进阶实践 一

Kubernetes进阶

学习Kubernetes的进阶内容,包含Kubernetes集群调度、CNI插件、认证授权安全体系、分布式存储的对接、Helm的使用等,得以更加深入的学习Kubernetes的核心

  • ETCD数据的访问

  • kube-scheduler调度策略实践

    • 预选与优选流程
    • 生产中常用的调度配置实践
  • k8s集群网络模型

    • CNI介绍及集群网络选型
    • Flannel网络模型的实现
      • vxlan Backend
      • hostgw Backend
  • 集群认证与授权

    • APIServer安全控制模型
    • Kubectl的认证授权
    • RBAC
    • kubelet的认证授权
    • Service Account
  • 使用Helm管理复杂应用的部署

    • Helm工作原理详解
    • Helm的模板开发
    • 实战:使用Helm部署Harbor仓库
  • kubernetes对接分部式存储

    • pv、pvc介绍
    • k8s集群如何使用cephfs作为分布式存储后端
    • 利用storageClass实现动态存储卷的管理
    • 实战:使用分部署存储实现有状态应用的部署

ETCD常用操作

$ docker ps | grep etcd
$ docker exec -ti 48cde8bcee1b which etcdctl
$ docker cp 48cde8bcee1b:/usr/local/bin/etcdctl /usr/bin/etcdctl

查看etcd集群的成员节点:

$ export ETCDCTL_API=3
$ etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key member list -w table

$ alias etcdctl='etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key'

$ etcdctl member list -w table

查看etcd集群节点状态:

$ etcdctl endpoint status -w table

$ etcdctl endpoint health -w table

设置key值:

$ etcdctl put jds 521
OK
$ etcdctl get jds
jds
521

查看所有key值:

$  etcdctl get / --prefix --keys-only

查看具体的key对应的数据:

$ etcdctl get /registry/services/specs/jds/myblog

添加定时任务做数据快照(重要!)

$ etcdctl snapshot save `hostname`-etcd_`date +%Y%m%d%H%M`.db

恢复快照:

  1. 停止etcd和apiserver

  2. 移走当前数据目录

    $ mv /var/lib/etcd/ /tmp
    
  3. 恢复快照

    $ etcdctl snapshot restore `hostname`-etcd_`date +%Y%m%d%H%M`.db --data-dir=/var/lib/etcd/
    

Kubernetes调度

为何要控制Pod 应该如何调度

  • 集群中有些机器的配置高(SSD,更好的内存等),我们希望核心的服务(比如说数据库)运行在上面
  • 某两个服务的网络传输很频繁,我们希望它们最好在同一台机器上
  • ......

Kubernetes Scheduler 的作用是将待调度的 Pod 按照一定的调度算法和策略绑定到集群中一个合适的 Worker Node 上,并将绑定信息写入到 etcd 中,之后目标 Node 中 kubelet 服务通过 API Server 监听到 Scheduler 产生的 Pod 绑定事件获取 Pod 信息,然后下载镜像启动容器

调度的过程

Scheduler 提供的调度流程分为预选 (Predicates) 和优选 (Priorities) 两个步骤:

  • 预选,K8S会遍历当前集群中的所有 Node,筛选出其中符合要求的 Node 作为候选

  • 优选,K8S将对候选的 Node 进行打分

经过预选筛选和优选打分之后,K8S选择分数最高的 Node 来运行 Pod,如果最终有多个 Node 的分数最高,那么 Scheduler 将从当中随机选择一个 Node 来运行 Pod

预选:

优选:

Cordon

cordon 停止调度
drain 驱逐节点

## 影响最小,只会将node调为SchedulingDisabled, 之后再发创建pod,不会被调度到该节点,旧有的pod不会受到影响,仍正常对外提供服务
$ kubectl cordon k8s-slave2

## 恢复调度
$ kubectl uncordon k8s-slave2


## 首先 驱逐node上的pod,其他节点重新创建 接着,将节点调为 SchedulingDisabled
$ kubectl drain k8s-slave2

恢复调度
$ kubectl uncordon node_name
NodeSelector

labelkubernetes中一个非常重要的概念,用户可以非常灵活的利用 label 来管理集群中的资源,POD 的调度可以根据节点的 label 进行特定的部署

查看节点的label:

$ kubectl get nodes --show-labels

为节点打label:

$ kubectl label node k8s-master disktype=ssd

当 node 被打上了相关标签后,在调度的时候就可以使用这些标签了,只需要在spec 字段中添加nodeSelector字段,里面是我们需要被调度的节点的 label

...
spec:
  hostNetwork: true    # 声明pod的网络模式为host模式,效果通docker run --net=host
  volumes:
  - name: mysql-data
    hostPath:
      path: /opt/mysql/data
  nodeSelector:   # 使用节点选择器将Pod调度到指定label的节点
    component: mysql
  containers:
  - name: mysql
      image: 10.2.2.10:5000/demo/mysql:5.7
...
nodeAffinity

节点亲和性 , 比上面的nodeSelector更加灵活,它可以进行一些简单的逻辑组合,不只是简单的相等匹配 。分为两种,硬策略软策略

requiredDuringSchedulingIgnoredDuringExecution: 硬策略,如果没有满足条件的节点的话,就不断重试直到满足条件为止,简单说就是你必须满足我的要求,不然我就不会调度Pod。

preferredDuringSchedulingIgnoredDuringExecution: 软策略,如果你没有满足调度要求的节点的话,Pod就会忽略这条规则,继续完成调度过程,说白了就是满足条件最好了,没有满足就忽略掉的策略。

## 要求 Pod 不能运行在11和12两个节点上,如果有节点满足disktype=ssd或者sas的话就优先调度到这类节点上
...
spec:
      containers:
      - name: demo
        image: 10.2.2.10:5000/demo/myblog:v1
        ports:
        - containerPort: 8002
      affinity:
          nodeAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
                nodeSelectorTerms:
                - matchExpressions:
                    - key: kubernetes.io/hostname
                      operator: NotIn
                      values:
                        - 10.2.2.11
                        - 10.2.2.12
            preferredDuringSchedulingIgnoredDuringExecution:
                - weight: 1
                  preference:
                    matchExpressions:
                    - key: disktype
                      operator: In
                      values:
                        - ssd
                        - sas
...

这里的匹配逻辑是 label 的值在某个列表中,现在Kubernetes提供的操作符有下面的几种:

  • In:label 的值在某个列表中
  • NotIn:label 的值不在某个列表中
  • Gt:label 的值大于某个值
  • Lt:label 的值小于某个值
  • Exists:某个 label 存在
  • DoesNotExist:某个 label 不存在

如果nodeSelectorTerms下面有多个选项组的话,满足任何一个条件就可以了;如果matchExpressions有多个选项的话,则必须同时满足这些条件才能正常调度 Pod

污点(Taints)与容忍(tolerations)

对于nodeAffinity无论是硬策略还是软策略方式,都是调度 Pod 到预期节点上,而Taints恰好与之相反,如果一个节点标记为 Taints ,除非 Pod 也被标识为可以容忍污点节点,否则该 Taints 节点不会被调度Pod。

Taints(污点)是Node的一个属性,设置了Taints(污点)后,因为有了污点,所以Kubernetes是不会将Pod调度到这个Node上的。于是Kubernetes就给Pod设置了个属性Tolerations(容忍),只要Pod能够容忍Node上的污点,那么Kubernetes就会忽略Node上的污点,就能够(不是必须)把Pod调度过去。

场景一:私有云服务中,某业务使用GPU进行大规模并行计算。为保证性能,希望确保该业务对服务器的专属性,避免将普通业务调度到部署GPU的服务器。

场景二:用户希望把 Master 节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊资源预留给某些 Pod,则污点就很有用了,Pod 不会再被调度到 taint 标记过的节点。taint 标记节点举例如下:

设置污点:

$ kubectl taint node [node_name] key=value:[effect]  
      其中[effect] 可取值: [ NoSchedule | PreferNoSchedule | NoExecute ]
       NoSchedule:一定不能被调度。
       PreferNoSchedule:尽量不要调度。
       NoExecute:不仅不会调度,还会驱逐Node上已有的Pod。

##示例:
$ kubectl taint node k8s-slave1 smoke=true:NoSchedule

去除污点:

去除指定key及其effect:
     kubectl taint nodes [node_name] key:[effect]-    #这里的key不用指定value
 
去除指定key所有的effect:
     kubectl taint nodes node_name key-
 
示例:
$ kubectl taint node k8s-master smoke=true:NoSchedule
$ kubectl taint node k8s-master smoke:NoExecute-
$ kubectl taint node k8s-master smoke-
 

污点演示:

## 给k8s-slave1打上污点,smoke=true:NoSchedule
$ kubectl taint node k8s-slave1 smoke=true:NoSchedule
node/k8s-slave1 tainted
$ kubectl taint node k8s-slave2 drunk=true:NoSchedule
node/k8s-slave2 tainted
 
 
## 扩容myblog的Pod,观察新Pod的调度情况
$ kuebctl -n jds scale deploy myblog --replicas=3
deployment.apps/myblog scaled

$ kubectl -n jds get po -w    ## pending

...
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) had taints that the pod didn't tolerate.
...

Pod容忍污点示例:myblog/deployment/deploy-myblog-taint.yaml

...
spec:
      containers:
      - name: demo
        image: 10.2.2.10:5000/demo/myblog:v1
      tolerations: #设置容忍性
      - key: "smoke"
        operator: "Equal"  #如果操作符为Exists,那么value属性可省略,不指定operator,默认为Equal
        value: "true"
        effect: "NoSchedule"
      - key: "drunk"
        operator: "Exists"  #如果操作符为Exists,那么value属性可省略,不指定operator,默认为Equal
      #意思是这个Pod要容忍的有污点的Node的key是smoke Equal true,效果是NoSchedule,
      #tolerations属性下各值必须使用引号,容忍的值都是设置Node的taints时给的值。
$ kubectl apply -f deploy-myblog-taint.yaml

$ kubectl -n jds edit deploy myblog
deployment.apps/myblog edited

舔狗无底线容忍度,在任何node节点上都能跑

spec:
      containers:
      - name: demo
        image: 10.2.2.10:5000/demo/myblog
      tolerations:
        - operator: "Exists"

Kubernetes集群的网络实现

CNI介绍及集群网络选型

容器网络接口(Container Network Interface),实现kubernetes集群的Pod网络通信及管理。包括:

  • CNI Plugin负责给容器配置网络,它包括两个基本的接口:
    配置网络: AddNetwork(net NetworkConfig, rt RuntimeConf) (types.Result, error)
    清理网络: DelNetwork(net NetworkConfig, rt RuntimeConf) error

  • IPAM Plugin负责给容器分配IP地址,主要实现包括host-local和dhcp

以上两种插件的支持,使得k8s的网络可以支持各式各样的管理模式,当前在业界也出现了大量的支持方案,其中比较流行的比如flannel、calico等

kubernetes配置了cni网络插件后,其容器网络创建流程为:

  • kubelet先创建pause容器生成对应的network namespace
  • 调用网络driver,因为配置的是CNI,所以会调用CNI相关代码,识别CNI的配置目录为/etc/cni/net.d
  • CNI driver根据配置调用具体的CNI插件,二进制调用,可执行文件目录为/opt/cni/bin,项目
  • CNI插件给pause容器配置正确的网络,pod中其他的容器都是用pause的网络

可以在此查看社区中的CNI实现,https://github.com/containernetworking/cni

通用类型:flannel、calico等,部署使用简单

其他:根据具体的网络环境及网络需求选择,比如

  • 公有云机器,可以选择厂商与网络插件的定制Backend,如AWS、阿里、腾讯针对flannel均有自己的插件,也有AWS ECS CNI

  • 私有云厂商,比如Vmware NSX-T等

  • 网络性能等,MacVlan

Flannel网络模型实现剖析

flannel实现overlay网络通常有多种实现:

  • udp
  • vxlan
  • host-gw
  • ...

不特殊指定的话,默认会使用vxlan技术作为Backend,可以通过如下查看:

$ kubectl -n kube-system get pods

$ kubectl -n kube-system exec kube-flannel-ds-amd64-j68kz cat /etc/kube-flannel/net-conf.json
{
  "Network": "10.244.0.0/16",
  "Backend": {
    "Type": "vxlan"
  }
}

vxlan介绍及点对点通信的实现

VXLAN 全称是虚拟可扩展的局域网( Virtual eXtensible Local Area Network),它是一种 overlay 技术,通过三层的网络来搭建虚拟的二层网络

它创建在原来的 IP 网络(三层)上,只要是三层可达(能够通过 IP 互相通信)的网络就能部署 vxlan。在每个端点上都有一个 vtep 负责 vxlan 协议报文的封包和解包,也就是在虚拟报文上封装 vtep 通信的报文头部。物理网络上可以创建多个 vxlan 网络,这些 vxlan 网络可以认为是一个隧道,不同节点的虚拟机能够通过隧道直连。每个 vxlan 网络由唯一的 VNI 标识,不同的 vxlan 可以不相互影响。

  • VTEP(VXLAN Tunnel Endpoints):vxlan 网络的边缘设备,用来进行 vxlan 报文的处理(封包和解包)。vtep 可以是网络设备(比如交换机),也可以是一台机器(比如虚拟化集群中的宿主机)

  • VNI(VXLAN Network Identifier):VNI 是每个 vxlan 的标识,一共有 2^24 = 16,777,216,一般每个 VNI 对应一个租户,也就是说使用 vxlan 搭建的公有云可以理论上可以支撑千万级别的租户

演示:在k8s-slave1和k8s-slave2两台机器间,利用vxlan的点对点能力,实现虚拟二层网络的通信

k8s-slave1节点:

## 创建vTEP设备,对端指向k8s-slave2节点,指定VNI及underlay网络使用的网卡
$ ip link add vxlan20 type vxlan id 20 remote 10.2.2.12 dstport 4789 dev ens33

$ ip -d link show vxlan20

## 启动设备
$ ip link set vxlan20 up

## 设置ip地址
$ ip addr add 10.244.244.11/24 dev vxlan20

k8s-slave2节点:

## 创建VTEP设备,对端指向k8s-slave1节点,指定VNI及underlay网络使用的网卡
$ ip link add vxlan20 type vxlan id 20 remote 10.2.2.11 dstport 4789 dev ens33

## 启动设备
$ ip link set vxlan20 up

## 设置ip地址
$ ip addr add 10.244.244.12/24 dev vxlan20

在k8s-slave1节点:

$ ping 10.244.244.12

隧道是一个逻辑上的概念,在 vxlan 模型中并没有具体的物理实体想对应。隧道可以看做是一种虚拟通道,vxlan 通信双方(图中的虚拟机)认为自己是在直接通信,并不知道底层网络的存在。从整体来说,每个 vxlan 网络像是为通信的虚拟机搭建了一个单独的通信通道,也就是隧道。

实现的过程:
虚拟机的报文通过 vtep 添加上 vxlan 以及外部的报文层,然后发送出去,对方 vtep 收到之后拆除 vxlan 头部然后根据 VNI 把原始报文发送到目的虚拟机。

## 查看k8s-slave1主机路由
$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.2.2.2        0.0.0.0         UG    100    0        0 ens33
10.2.2.0        0.0.0.0         255.255.255.0   U     100    0        0 ens33
10.244.0.0      10.244.0.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.1.0      0.0.0.0         255.255.255.0   U     0      0        0 cni0
10.244.2.0      10.244.2.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.244.0    0.0.0.0         255.255.255.0   U     0      0        0 vxlan20
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
...

## 到了vxlan的设备后
$ ip -d link show vxlan20
    vxlan id 20 remote 10.2.2.12 dev ens33 srcport 0 0 dstport 4789 ageing 300 ... 

 
## 查看fdb地址表,主要由MAC地址、VLAN号、端口号和一些标志域等信息组成,vtep 对端地址为 10.2.2.12,换句话说,如果接收到的报文添加上 vxlan 头部之后都会发到 10.2.2.12
$ bridge fdb show | grep vxlan20
00:00:00:00:00:00 dev vxlan20 dst 10.2.2.12 via ens33 self permanent

在k8s-slave2机器抓包,查看vxlan封装后的包:

## 在k8s-slave2机器执行
$ tcpdump -i ens33 host 10.2.2.11 -w vxlan.cap
 
# 在k8s-slave1机器执行
$ ping 10.244.244.12

使用wireshark分析ICMP类型的数据包

跨主机容器网络的通信

思考:容器网络模式下,vxlan设备该接在哪里?

基本的保证:目的容器的流量要通过vtep设备进行转发!

演示:利用vxlan实现跨主机容器网络通信, 为了不影响已有的网络,因此创建一个新的网桥,创建容器接入到新的网桥来演示效果

在k8s-slave1节点:

## 查看docker中已存在的网络
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
a69ab6143d0d        bridge              bridge              local
27ad16b8cfd3        host                host                local
a282a64cf3eb        none                null                local

## 创建新网桥,指定cidr段
$ docker network create --subnet 172.18.0.0/16 network-jds
c2d9369fdca0c90e7c31627316654be5b43fcd1a24682b2d7eb2969c0befba96
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
a69ab6143d0d        bridge              bridge              local
27ad16b8cfd3        host                host                local
c2d9369fdca0        network-jds         bridge              local
a282a64cf3eb        none                null                local

 
## 新建容器,接入到新网桥
$ docker run -d --name vxlan-test --net network-jds --ip 172.18.0.2 nginx:alpine
 
$ docker exec vxlan-test ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:12:00:02
          inet addr:172.18.0.2  Bcast:172.18.255.255  Mask:255.255.0.0
...

## br-c2d9369fdca0 就是新建的 network-jds 这个网桥,可以看到下面已经存在刚才创建的容器
$ brctl show
bridge name     bridge id               STP enabled     interfaces
br-c2d9369fdca0         8000.0242b5eee804       no              veth28e770b
cni0            8000.5250adb2fb08       no              veth54142afd
                                                        veth9e73dfcd
                                                        vethb60529da
                                                        vethebb510bd
                                                        vethf8c10f7a
docker0         8000.02428f2a23ab       no
virbr0          8000.5254006e1b99       yes             virbr0-nic

在k8s-slave2节点:

## 创建新网桥,指定cidr段
$ docker network create --subnet 172.18.0.0/16 network-jds

# 新建容器,接入到新网桥
$ docker run -d --name vxlan-test --net network-jds --ip 172.18.0.3 nginx:alpine

此时在 slave1 上执行ping测试:

$ docker exec vxlan-test ping 172.18.0.3

分析:数据到了网桥后,出不去。结合前面的示例,因此应该将流量由vtep设备转发,联想到网桥的特性,接入到桥中的端口,会由网桥负责转发数据,因此,相当于所有容器发出的数据都会经过到vxlan的端口,vxlan将流量转到对端的vtep端点,再次由网桥负责转到容器中。

k8s-slave1节点:

## 删除旧的vtep
$ ip link del vxlan20
 
## 新建vtep
$ ip link add vxlan_docker type vxlan id 100 remote 10.2.2.12 dstport 4789 dev ens33

$ ip -d link show vxlan_docker
$ ip link set vxlan_docker up
## 不用设置ip,因为目标是可以转发容器的数据即可
 
## 接入到网桥中
$ brctl addif br-904603a72dcd vxlan_docker

k8s-slave2节点:

## 删除旧的vtep
$ ip link del vxlan20
 
## 新建vtep
$ ip link add vxlan_docker type vxlan id 100 remote 10.2.2.11 dstport 4789 dev ens33

$ ip link set vxlan_docker up
## 不用设置ip,因为目标是可以转发容器的数据即可
 
## 接入到网桥中
$ brctl addif br-9ca02d84dce6 vxlan_docker

slave1 上再次执行ping测试:

$ docker exec vxlan-test ping 172.18.0.3

Flannel的vxlan实现

思考:k8s集群的网络环境和手动实现的跨主机的容器通信有哪些差别?

  1. 每个主机都需要运行大量容器,因此每个主机都需要一个10.244.0.1/24 地址段
  2. k8s集群内的通信不是vxlan点对点通信,因为集群内的所有节点之间都需要互联

flannel如何为每个节点分配Pod地址段:

$ kubectl -n kube-system exec kube-flannel-ds-amd64-j68kz cat /etc/kube-flannel/net-conf.json

## 查看节点的pod ip
$ kubectl -n jds get pods -o wide
 
## 查看k8s-slave1主机分配的地址段
$ cat /run/flannel/subnet.env

## kubelet启动容器的时候就可以按照本机的网段配置来为容器设置IP地址

vtep的设备在哪:

$ ip -d link show flannel.1
6: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/ether fe:d7:bc:27:0e:97 brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 1 local 10.2.2.10 dev ens33 srcport 0 0 dstport 8472 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535

## 没有remote ip,非点对点

Pod的流量如何转到vtep设备中

$ brctl show cni0
bridge name     bridge id               STP enabled     interfaces
cni0            8000.f2bf89afc534       no              veth7d8c421b
                                                        vethf5b027ef

## 每个Pod都会使用Veth pair来实现流量转到cni0网桥
 
$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.244.0.0      0.0.0.0         255.255.255.0   U     0      0        0 cni0
10.244.1.0      10.244.1.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.2.0      10.244.2.0      255.255.255.0   UG    0      0        0 flannel.1
...

vtep封包的时候,如何拿到目的vetp端的IP及MAC信息

## flanneld启动的时候会需要配置 --iface=ens33, 通过该配置可以将网卡的ip及Mac信息存储到ETCD中,
## 这样,flannel就知道所有的节点分配的IP段及vtep设备的IP和MAC信息,而且所有节点的flanneld都可以感知到节点的添加和删除操作,就可以动态的更新本机的转发配置

演示跨主机Pod通信的流量详细过程:

$ kubectl -n jds get pods -o wide
NAME                      READY   STATUS             RESTARTS   AGE     IP            NODE         NOMINATED NODE   READINESS GATES
myblog-79996fb478-6h6j5   1/1     Running            2          7d20h   10.244.2.37   k8s-slave2   <none>           <none>
myblog-79996fb478-h89ll   1/1     Running            2          7d20h   10.244.1.47   k8s-slave1   <none>           <none>
mysql-59bbdccc8c-ltkcw    1/1     Running            4          13d     10.244.1.46   k8s-slave1   <none>           <none>

 
$ kubectl -n jds exec myblog-79996fb478-6h6j5 -- ping 10.244.1.47
PING 10.244.1.47 (10.244.1.47) 56(84) bytes of data.
64 bytes from 10.244.1.47: icmp_seq=1 ttl=62 time=0.498 ms
64 bytes from 10.244.1.47: icmp_seq=2 ttl=62 time=0.385 ms
64 bytes from 10.244.1.47: icmp_seq=3 ttl=62 time=0.676 ms
64 bytes from 10.244.1.47: icmp_seq=4 ttl=62 time=0.411 ms
 
## 查看路由
$ kubectl -n jds exec myblog-79996fb478-6h6j5 -- route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.244.2.1      0.0.0.0         UG    0      0        0 eth0
10.244.0.0      10.244.2.1      255.255.0.0     UG    0      0        0 eth0
10.244.2.0      0.0.0.0         255.255.255.0   U     0      0        0 eth0

 
## 查看k8s-slave2 的veth pair 和网桥
$ brctl show
bridge name     bridge id               STP enabled     interfaces
cni0            8000.36883be178d7       no              veth0dd8d3b9
                                                        veth9c79c0f6
                                                        vethc7d17ee3
...

## 流量到了cni0后,查看slave2节点的route
$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.2.2.2        0.0.0.0         UG    100    0        0 ens33
10.2.2.0        0.0.0.0         255.255.255.0   U     100    0        0 ens33
10.244.0.0      10.244.0.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.1.0      10.244.1.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.2.0      0.0.0.0         255.255.255.0   U     0      0        0 cni0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-9ca02d84dce6
192.168.122.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0

 
## 流量转发到了flannel.1网卡,查看该网卡,其实是vtep设备
$ ip -d link show flannel.1
7: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT group default
    link/ether 7e:19:da:b5:7d:a3 brd ff:ff:ff:ff:ff:ff promiscuity 0
    vxlan id 1 local 10.2.2.12 dev ens33 srcport 0 0 dstport 8472 nolearning ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
 
## 该转发到哪里,通过etcd查询数据,然后本地缓存,流量不用走多播发送
$ bridge fdb show dev flannel.1
36:7c:47:96:86:8a dst 10.2.2.11 self permanent
36:68:b6:82:3f:8a dst 10.2.2.11 self permanent
5e:c4:91:af:75:9f dst 10.2.2.10 self permanent
 
## 对端的vtep设备接收到请求后做解包,取出源payload内容,查看k8s-slave1 的路由
$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.2.2.2        0.0.0.0         UG    100    0        0 ens33
10.2.2.0        0.0.0.0         255.255.255.0   U     100    0        0 ens33
10.244.0.0      10.244.0.0      255.255.255.0   UG    0      0        0 flannel.1
10.244.1.0      0.0.0.0         255.255.255.0   U     0      0        0 cni0
10.244.2.0      10.244.2.0      255.255.255.0   UG    0      0        0 flannel.1
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
172.18.0.0      0.0.0.0         255.255.0.0     U     0      0        0 br-c2d9369fdca0
192.168.122.0   0.0.0.0         255.255.255.0   U     0      0        0 virbr0

## 根据路由规则转发到cni0网桥,然后由网桥转到具体的Pod中
  • k8s-slave2 节点中的 pod-a(10.244.1.47)当中的 IP 包通过 pod-a 内的路由表被发送到eth0,进一步通过veth pair转到宿主机中的网桥 cni0

  • 到达 cni0 当中的 IP 包通过匹配节点 k8s-slave2 的路由表发现通往 10.244.1.47 的 IP 包应该交给 flannel.1 接口

  • flannel.1 作为一个 VTEP 设备,收到报文后将按照 VTEP 的配置进行封包,第一次会查询ETCD,知道10.244.1.47的vtep设备是k8s-slave1机器,IP地址是10.2.2.11,拿到MAC 地址进行 VXLAN 封包

  • 通过节点 k8s-slave2 跟 k8s-slave1之间的网络连接,VXLAN 包到达 k8s-slave1 的 ens33 接口

  • 通过端口 8472,VXLAN 包被转发给 VTEP 设备 flannel.1 进行解包

  • 解封装后的 IP 包匹配节点 k8s-slave1 当中的路由表(10.244.1.0),内核将 IP 包转发给cni0

  • cni0将 IP 包转发给连接在 cni0 上的 pod-b

利用host-gw模式提升集群网络性能

vxlan模式适用于三层可达的网络环境,对集群的网络要求很宽松,但是同时由于会通过VTEP设备进行额外封包和解包,因此给性能带来了额外的开销

网络插件的目的其实就是将本机的cni0网桥的流量送到目的主机的cni0网桥。实际上有很多集群是部署在同一二层网络环境下的,可以直接利用二层的主机当作流量转发的网关。这样的话,可以不用进行封包解包,直接通过路由表去转发流量。

为什么三层可达的网络不直接利用网关转发流量?

内核当中的路由规则,网关必须在跟主机当中至少一个 IP 处于同一网段。
由于k8s集群内部各节点均需要实现Pod互通,因此,也就意味着 host-gw 模式需要整个集群节点都在同一二层网络内。

修改flannel的网络后端:

$ kubectl edit cm kube-flannel-cfg -n kube-system
...
net-conf.json: |
    {
      "Network": "10.244.0.0/16",
      "Backend": {
        "Type": "host-gw"
      }
    }
kind: ConfigMap
...

重建Flannel的Pod

$ kubectl -n kube-system get pods | grep flannel

$ kubectl -n kube-system delete pods kube-flannel-ds-amd64-fql9t kube-flannel-ds-amd64-qgqjb kube-flannel-ds-amd64-s8lk5
 
# 等待Pod新启动后,查看日志,出现Backend type: host-gw字样
$ kubectl -n kube-system get pods | grep flannel

查看节点路由表:

route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.2.2.2        0.0.0.0         UG    100    0        0 ens33
10.2.2.0        0.0.0.0         255.255.255.0   U     100    0        0 ens33
10.244.0.0      0.0.0.0         255.255.255.0   U     0      0        0 cni0
10.244.1.0      10.2.2.11       255.255.255.0   UG    0      0        0 ens33
10.244.2.0      10.2.2.12       255.255.255.0   UG    0      0        0 ens33
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 docker0
  • k8s-slave2 节点中的 pod-a(10.244.1.47)当中的 IP 包通过 pod-a 内的路由表被发送到eth0,进一步通过veth pair转到宿主机中的网桥 cni0

  • 到达 cni0 当中的 IP 包通过匹配节点 k8s-slave2 的路由表发现通往 10.244.1.47 的 IP 包应该使用10.2.2.11这个网关进行转发

  • 包到达k8s-slave1节点(10.2.2.11)节点的eth0网卡,根据该节点的路由规则,转发给cni0网卡

  • cni0将 IP 包转发给连接在 cni0 上的 pod-b

Kubernetes认证与授权

APIServer安全控制

  • Authentication:身份认证

    1. 这个环节它面对的输入是整个http request,负责对来自client的请求进行身份校验,支持的方法包括:
    • basic auth
    • client证书验证(https双向验证)
    • jwt token(用于serviceaccount)
    1. APIServer启动时,可以指定一种Authentication方法,也可以指定多种方法。如果指定了多种方法,那么APIServer将会逐个使用这些方法对客户端请求进行验证, 只要请求数据通过其中一种方法的验证,APIServer就会认为Authentication成功;

    2. 使用kubeadm引导启动的k8s集群,apiserver的初始配置中,默认支持client证书验证和serviceaccount两种身份验证方式。 证书认证通过设置--client-ca-file根证书以及--tls-cert-file--tls-private-key-file来开启

    3. 在这个环节,apiserver会通过client证书或 http header中的字段(比如serviceaccount的jwt token)来识别出请求的用户身份,包括”user”、”group”等,这些信息将在后面的authorization环节用到

  • Authorization:鉴权,你可以访问哪些资源

    1. 这个环节面对的输入是http request context中的各种属性,包括:usergrouprequest path(比如:/api/v1/healthz/version等)、 request verb(比如:getlistcreate等)

    2. APIServer会将这些属性值与事先配置好的访问策略(access policy)相比较。APIServer支持多种authorization mode,包括Node、RBAC、Webhook

    3. APIServer启动时,可以指定一种authorization mode,也可以指定多种authorization mode,如果是后者,只要Request通过了其中一种mode的授权, 那么该环节的最终结果就是授权成功。在较新版本kubeadm引导启动的k8s集群的apiserver初始配置中,authorization-mode的默认配置是”Node,RBAC”

  • Admission Control:准入控制,一个控制链(层层关卡),用于拦截请求的一种方式。偏集群安全控制、管理方面

    • 为什么需要?

      认证与授权获取 http 请求 header 以及证书,无法通过body内容做校验

      Admission 运行在 API Server 的增删改查 handler 中,可以自然地操作 API resource

    • 举个栗子

      • 以NamespaceLifecycle为例, 该插件确保处于Termination状态的Namespace不再接收新的对象创建请求,并拒绝请求不存在的Namespace。该插件还可以防止删除系统保留的Namespace:default,kube-system,kube-public

      • LimitRanger,若集群的命名空间设置了LimitRange对象,若Pod声明时未设置资源值,则按照LimitRange的定义来未Pod添加默认值

        apiVersion: v1
        kind: LimitRange
        metadata:
          name: mem-limit-range
          namespace: luffy
        spec:
          limits:
          - default:
              memory: 512Mi
            defaultRequest:
              memory: 256Mi
            type: Container
        ---
        apiVersion: v1
        kind: Pod
        metadata:
          name: default-mem-demo-2
        spec:
          containers:
          - name: default-mem-demo-2-ctr
            image: nginx:alpine
        
        
      • NodeRestriction, 此插件限制kubelet修改Node和Pod对象,这样的kubelets只允许修改绑定到Node的Pod API对象,以后版本可能会增加额外的限制。开启Node授权策略后,默认会打开该项

    • 怎么用?

      APIServer启动时通过 --enable-admission-plugins --disable-admission-plugins 指定需要打开或者关闭的 Admission Controller

    • 场景

      • 自动注入sidecar容器或者initContainer容器
      • webhook admission,实现业务自定义的控制需求

kubectl的认证授权

kubectl的日志调试级别:

信息 描述
v=0 通常,这对操作者来说总是可见的。
v=1 当您不想要很详细的输出时,这个是一个合理的默认日志级别。
v=2 有关服务和重要日志消息的有用稳定状态信息,这些信息可能与系统中的重大更改相关。这是大多数系统推荐的默认日志级别。
v=3 关于更改的扩展信息。
v=4 调试级别信息。
v=6 显示请求资源。
v=7 显示 HTTP 请求头。
v=8 显示 HTTP 请求内容。
v=9 显示 HTTP 请求内容,并且不截断内容。
$ kubectl get nodes -v=7
I1014 07:05:48.681625   14557 loader.go:375] Config loaded from file:  /root/.kube/config
I1014 07:05:48.686749   14557 round_trippers.go:420] GET https://10.2.2.10:6443/api/v1/nodes?limit=500
I1014 07:05:48.686763   14557 round_trippers.go:427] Request Headers:
I1014 07:05:48.686768   14557 round_trippers.go:431]     Accept: application/json;as=Table;v=v1beta1;g=meta.k8s.io, application/json
I1014 07:05:48.686772   14557 round_trippers.go:431]     User-Agent: kubectl/v1.16.2 (linux/amd64) kubernetes/c97fe50
I1014 07:05:48.693798   14557 round_trippers.go:446] Response Status: 200 OK in 7 milliseconds
NAME         STATUS   ROLES    AGE   VERSION
k8s-master   Ready    master   27d   v1.16.2
k8s-slave1   Ready    <none>   27d   v1.16.2
k8s-slave2   Ready    <none>   27d   v1.16.2

kubeadm init启动完master节点后,会默认输出类似下面的提示内容:

... ...
Your Kubernetes master has initialized successfully!
 
To start using your cluster, you need to run the following as a regular user:
  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config
... ...

这些信息是在告知我们如何配置kubeconfig文件。按照上述命令配置后,master节点上的kubectl就可以直接使用$HOME/.kube/config的信息访问k8s cluster了。 并且,通过这种配置方式,kubectl也拥有了整个集群的管理员(root)权限。

问题:

  • kubectl使用这种kubeconfig方式访问集群时,Kuberneteskube-apiserver是如何对来自kubectl的访问进行身份验证(authentication)和授权(authorization)的呢?
  • 为什么来自kubectl的请求拥有最高的管理员权限呢?

查看/root/.kube/config文件:

前面提到过apiserver的authentication支持通过tls client certificate、basic auth、token等方式对客户端发起的请求进行身份校验, 从kubeconfig信息来看,kubectl显然在请求中使用了tls client certificate的方式,即客户端的证书

证书base64解码:

$ cat /root/.kube/config
$ echo xxxxxxxxxxxxxx |base64 -d > kubectl.crt

说明在认证阶段,apiserver会首先使用--client-ca-file配置的CA证书去验证kubectl提供的证书的有效性,基本的方式 :

$ ps -ef | grep kube-apiserver
$ openssl verify -CAfile /etc/kubernetes/pki/ca.crt kubectl.crt
kubectl.crt: OK

除了认证身份,还会取出必要的信息供授权阶段使用,文本形式查看证书内容:

$  openssl x509 -in kubectl.crt -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 1984150317045005173 (0x1b891e336c723b75)
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=kubernetes
        Validity
            Not Before: Sep 16 14:48:15 2020 GMT
            Not After : Sep 16 14:48:18 2021 GMT
        Subject: O=system:masters, CN=kubernetes-admin
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
        ...

认证通过后,提取出签发证书时指定的CN(Common Name),kubernetes-admin,作为请求的用户名 (User Name), 从证书中提取O(Organization)字段作为请求用户所属的组 (Group),group = system:masters,然后传递给后面的授权模块

kubeadm在init初始引导集群启动过程中,创建了许多默认的RBAC规则, 在k8s有关RBAC的官方文档中,我们看到下面一些default clusterrole列表:

其中第一个cluster-admin这个cluster role binding绑定了system:masters group,这和authentication环节传递过来的身份信息不谋而合。 沿着system:masters group对应的cluster-admin clusterrolebinding“追查”下去,真相就会浮出水面

查看一下这一binding:

$ kubectl describe clusterrolebinding cluster-admin
Name:         cluster-admin
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
Role:
  Kind:  ClusterRole
  Name:  cluster-admin
Subjects:
  Kind   Name            Namespace
  ----   ----            ---------
  Group  system:masters

看到在kube-system名字空间中,一个名为cluster-admin的clusterrolebinding将cluster-admin cluster role与system:masters Group绑定到了一起, 赋予了所有归属于system:masters Group中用户cluster-admin角色所拥有的权限。

再来查看一下cluster-admin这个role的具体权限信息:

$ kubectl  describe clusterrole cluster-admin
Name:         cluster-admin
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  -----
  *.*        []                 []              [*]
             [*]                []              [*]

非资源类,如查看集群健康状态

RBAC

Role-Based Access Control,基于角色的访问控制, apiserver启动参数添加--authorization-mode=RBAC 来启用RBAC认证模式,kubeadm安装的集群默认已开启。官方介绍

查看开启:

# master节点查看apiserver进程
$ ps aux |grep apiserver

RBAC模式引入了4个资源类型:

  • Role,角色
    一个Role只能授权访问单个namespace

    ## 示例定义一个名为pod-reader的角色,该角色具有读取default这个命名空间下的pods的权限
    kind: Role
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      namespace: default
      name: pod-reader
    rules:
    - apiGroups: [""] # "" indicates the core API group
      resources: ["pods"]
      verbs: ["get", "watch", "list"]
    
    ## apiGroups: "","apps", "autoscaling", "batch", kubectl api-versions
    ## resources: "services", "pods","deployments"... kubectl api-resources
    ## verbs: "get", "list", "watch", "create", "update", "patch", "delete", "exec"
    
    ## https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/
    
    
  • ClusterRole
    一个ClusterRole能够授予和Role一样的权限,但是它是集群范围内的

    ## 定义一个集群角色,名为secret-reader,该角色可以读取所有的namespace中的secret资源
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      # "namespace" omitted since ClusterRoles are not namespaced
      name: secret-reader
    rules:
    - apiGroups: [""]
      resources: ["secrets"]
      verbs: ["get", "watch", "list"]
    
  • Rolebinding
    将role中定义的权限分配给用户和用户组。RoleBinding包含主题(users,groups,或service accounts)和授予角色的引用。对于namespace内的授权使用RoleBinding,集群范围内使用ClusterRoleBinding

    ## 定义一个角色绑定,将pod-reader这个role的权限授予给jane这个User,使得jane可以在读取default这个命名空间下的所有的pod数据
    kind: RoleBinding
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      name: read-pods
      namespace: default
    subjects:
    - kind: User   #这里可以是User,Group,ServiceAccount
      name: jane
      apiGroup: rbac.authorization.k8s.io
    roleRef:
      kind: Role #这里可以是Role或者ClusterRole,若是ClusterRole,则权限也仅限于rolebinding的内部
      name: pod-reader # match the name of the Role or ClusterRole you wish to bind to
      apiGroup: rbac.authorization.k8s.io.
    

注意:rolebinding既可以绑定role,也可以绑定clusterrole,当绑定clusterrole的时候,subject的权限也会被限定于rolebinding定义的namespace内部,若想跨namespace,需要使用clusterrolebinding

## 定义一个角色绑定,将dave这个用户和secret-reader这个集群角色绑定,虽然secret-reader是集群角色,但是因为是使用rolebinding绑定的,因此dave的权限也会被限制在development这个命名空间内
apiVersion: rbac.authorization.k8s.io/v1
# This role binding allows "dave" to read secrets in the "development" namespace.
# You need to already have a ClusterRole named "secret-reader".
kind: RoleBinding
metadata:
  name: read-secrets
  #
  # The namespace of the RoleBinding determines where the permissions are granted.
  # This only grants permissions within the "development" namespace.
  namespace: development
subjects:
- kind: User
  name: dave # Name is case sensitive
  apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
  name: dave # Name is case sensitive
  namespace: luffy
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

考虑一个场景: 如果集群中有多个namespace分配给不同的管理员,每个namespace的权限是一样的,就可以只定义一个clusterrole,然后通过rolebinding将不同的namespace绑定到管理员身上,否则就需要每个namespace定义一个Role,然后做一次rolebinding。

  • ClusterRolebingding
    允许跨namespace进行授权

    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
    

kubelet的认证授权

查看kubelet进程

$ systemctl status kubelet -l
● kubelet.service - kubelet: The Kubernetes Node Agent
   Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled)
  Drop-In: /usr/lib/systemd/system/kubelet.service.d
           └─10-kubeadm.conf
   Active: active (running) since Wed 2020-10-14 06:45:11 PDT; 1h 58min ago
     Docs: https://kubernetes.io/docs/
 Main PID: 611 (kubelet)
    Tasks: 22
   Memory: 131.9M
   CGroup: /system.slice/kubelet.service
           └─611 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cgroup-driver=cgroupfs --network-plugin=cni --pod-infra-container-image=registry.aliyuncs.com/google_containers/pause:3.1

查看/etc/kubernetes/kubelet.conf,解析证书:

$ cd /etc/kubernetes/ && cat kubelet.conf
$ echo xxxxx |base64 -d > kubelet.crt
$ openssl x509 -in kubelet.crt -text

得到期望的内容:

Subject: O=system:nodes, CN=system:node:k8s-master
 

k8s会把O作为Group来进行请求,因此如果有权限绑定给这个组,肯定在clusterrolebinding的定义中可以找得到。因此尝试去找一下绑定了system:nodes组的clusterrolebinding

$ kubectl get clusterrolebinding | grep system:node
system:node                                            8d

$ kubectl get clusterrolebinding -oyaml | grep system:nodes
name: system:nodes

$ kubectl get clusterrolebinding|awk 'NR>1{print $1}'|xargs kubectl get clusterrolebinding -oyaml|grep -n10 system:nodes
94-    resourceVersion: "188"
95-    selfLink: /apis/rbac.authorization.k8s.io/v1/clusterrolebindings/kubeadm%3Anode-autoapprove-certificate-rotation
96-    uid: 59320e4d-3393-4e9e-a094-bcf4e9329079
97-  roleRef:
98-    apiGroup: rbac.authorization.k8s.io
99-    kind: ClusterRole
100-    name: system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
101-  subjects:
102-  - apiGroup: rbac.authorization.k8s.io
103-    kind: Group
104:    name: system:nodes
105-- apiVersion: rbac.authorization.k8s.io/v1
106-  kind: ClusterRoleBinding
107-  metadata:
108-    creationTimestamp: "2020-09-16T14:48:36Z"
109-    name: kubeadm:node-proxier
110-    resourceVersion: "215"
111-    selfLink: /apis/rbac.authorization.k8s.io/v1/clusterrolebindings/kubeadm%3Anode-proxier
112-    uid: 890dc128-0200-440c-b775-9d82dae0fd89
113-  roleRef:
114-    apiGroup: rbac.authorization.k8s.io

 
$ kubectl describe clusterrole system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
Name:         system:certificates.k8s.io:certificatesigningrequests:selfnodeclient
Labels:       kubernetes.io/bootstrapping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
PolicyRule:
  Resources                                                      Non-Resource URLs  Resource Names  Verbs
  ---------                                                      -----------------  --------------  -----
  certificatesigningrequests.certificates.k8s.io/selfnodeclient  []                 []              [create]

结局有点意外,除了system:certificates.k8s.io:certificatesigningrequests:selfnodeclient外,没有找到system相关的rolebindings,显然不一样。 尝试去找资料,发现了这么一段 :

Default ClusterRole Default ClusterRoleBinding Description
system:kube-scheduler system:kube-scheduler user Allows access to the resources required by the schedulercomponent.
system:volume-scheduler system:kube-scheduler user Allows access to the volume resources required by the kube-scheduler component.
system:kube-controller-manager system:kube-controller-manager user Allows access to the resources required by the controller manager component. The permissions required by individual controllers are detailed in the controller roles.
system:node None Allows access to resources required by the kubelet, including read access to all secrets, and write access to all pod status objects. You should use the Node authorizer and NodeRestriction admission plugin instead of the system:node role, and allow granting API access to kubelets based on the Pods scheduled to run on them. The system:node role only exists for compatibility with Kubernetes clusters upgraded from versions prior to v1.8.
system:node-proxier system:kube-proxy user Allows access to the resources required by the kube-proxycomponent.

大致意思是说:之前会定义system:node这个角色,目的是为了kubelet可以访问到必要的资源,包括所有secret的读权限及更新pod状态的写权限。如果1.8版本后,是建议使用 Node authorizer and NodeRestriction admission plugin 来代替这个角色的。

目前使用1.16,查看一下授权策略:

$ ps axu|grep apiserver
kube-apiserver --authorization-mode=Node,RBAC  --enable-admission-plugins=NodeRestriction
...

查看一下官网对Node authorizer的介绍:

Node authorization is a special-purpose authorization mode that specifically authorizes API requests made by kubelets.

In future releases, the node authorizer may add or remove permissions to ensure kubelets have the minimal set of permissions required to operate correctly.

In order to be authorized by the Node authorizer, kubelets must use a credential that identifies them as being in the system:nodes group, with a username of system:node:<nodeName>

Service Account及K8S Api调用

认证可以通过证书,也可以通过使用ServiceAccount(服务账户)的方式来做认证。大多数时候,在基于k8s做二次开发时都是选择通过ServiceAccount + RBAC 的方式。之前访问dashboard的时候,是如何做的?

## 新建一个名为admin的serviceaccount,并且把名为cluster-admin的这个集群角色的权限授予新建的
serviceaccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin
  namespace: kubernetes-dashboard
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: admin
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: admin
  namespace: kubernetes-dashboard

查看一下:

$ kubectl -n kubernetes-dashboard get serviceaccounts admin -oyaml
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2020-09-16T16:11:52Z"
  name: admin
  namespace: kubernetes-dashboard
  resourceVersion: "8143"
  selfLink: /api/v1/namespaces/kubernetes-dashboard/serviceaccounts/admin
  uid: c6784fe1-a6ee-476e-b196-24a6d4ca2603
secrets:
- name: admin-token-m8n5w

注意到serviceaccount上默认绑定了一个名为admin-token-m8n5w的secret,我们查看一下secret

$ kubectl -n kubernetes-dashboard describe secret admin-token-m8n5w
Name:         admin-token-m8n5w
Namespace:    kubernetes-dashboard
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: admin
              kubernetes.io/service-account.uid: c6784fe1-a6ee-476e-b196-24a6d4ca2603

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1025 bytes
namespace:  20 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6ImgtdkhSQmkwbzR5djJ2U0xnSnVLbEF0M1J3U21vTTFfVWdSNTZ6NFN4RFEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi1tOG41dyIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImM2Nzg0ZmUxLWE2ZWUtNDc2ZS1iMTk2LTI0YTZkNGNhMjYwMyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlcm5ldGVzLWRhc2hib2FyZDphZG1pbiJ9.Yz3gXjIiNBStUsK4kAeJHTH22wTC04bMmz9uyJyTa4EEcywye1myuANjy-Ha_VaWi-EEcdbJMKOmsOQ9Tepw4dKfNK3DuTgHi1bvpMeeZgsgODE6kUxA22JKIs6jn-1Frh9zC3ZRXxyHo9gGvXPj5yjYKSasLTarV7a3PtaSr0bNgrFIUqdeHqAjUdiAc4F2AxlTpndqsga0euOJZ2_cwh0C9h5a9ou_UqFrkSFRRHkd-lu4qSrtCjTDLudNBOxdeb_maVExatfJFdOKQ0-3FqiWTH1q-M7WjCUDP2Yvt-nIVTHHadHSayWHXs6d4DdC2-PPiGlBCqY77ydeVSXL0g

创建完 serviceaccounts后,默认的secret 哪里来的?

$ ps -aux | grep controller-manager
--service-account-private-key-file=/etc/kubernetes/pki/sa.key 
--use-service-account-credentials=true
...

## 自动开启TokenController, TokenController负责使用上述私钥生成一个Secret, 然后绑定到新建的sa中

$ kubectl get serviceaccount
NAME      SECRETS   AGE
default   1         28d

$ kubectl -n jds get serviceaccount
NAME      SECRETS   AGE
default   1         21d

## 每个命名空间创建后,都会默认生成一个default的sa,这个功能是由 ServiceAccountController 完成的,然后 TokenController 去生成Secret, 绑定到default这个sa中, 
同时 会绑定到每个pod的 /var/run/secrets/kubernetes.io/serviceaccount目录中, 用于同APIserver交互

k8s 可以从secret 中节解出 ServiceAccount 信息,去查找RBAC规则,进一步做授权验证

演示role的权限:

$ cat test-sa.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: test
  namespace: kubernetes-dashboard
 
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: test
  annotations:
    rbac.authorization.kubernetes.io/autoupdate: "true"
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
  name: test
  namespace: kubernetes-dashboard
$ kubectl -n kubernetes-dashboard get svc
NAME                        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)         AGE
dashboard-metrics-scraper   ClusterIP   10.99.113.113   <none>        8000/TCP        28d
kubernetes-dashboard        NodePort    10.98.10.35     <none>        443:31143/TCP   28d
## 先放访问dashboard

$ vim test-sa.yaml
$ kubectl create -f test-sa.yaml
serviceaccount/test created
clusterrolebinding.rbac.authorization.k8s.io/test created

$ kubectl -n kubernetes-dashboard get sa
NAME                   SECRETS   AGE
admin                  1         28d
default                1         28d
kubernetes-dashboard   1         28d
test                   1         101s

$ kubectl -n kubernetes-dashboard get secret
NAME                               TYPE                                  DATA   AGE
admin-token-m8n5w                  kubernetes.io/service-account-token   3      28d
default-token-7nzzj                kubernetes.io/service-account-token   3      28d
kubernetes-dashboard-certs         Opaque                                0      28d
kubernetes-dashboard-csrf          Opaque                                1      28d
kubernetes-dashboard-key-holder    Opaque                                2      28d
kubernetes-dashboard-token-wtzrr   kubernetes.io/service-account-token   3      28d
test-token-4vzfw                   kubernetes.io/service-account-token   3      2m21s

$ kubectl -n kubernetes-dashboard describe secret test-token-4vzfw
Name:         test-token-4vzfw
Namespace:    kubernetes-dashboard
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: test
              kubernetes.io/service-account.uid: dfa48879-2835-4a09-b888-81a04ccde3c8

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1025 bytes
namespace:  20 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6ImgtdkhSQmkwbzR5djJ2U0xnSnVLbEF0M1J3U21vTTFfVWdSNTZ6NFN4RFEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJ0ZXN0LXRva2VuLTR2emZ3Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6InRlc3QiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJkZmE0ODg3OS0yODM1LTRhMDktYjg4OC04MWEwNGNjZGUzYzgiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6dGVzdCJ9.GfE50KMhVGgEoHhT5VeMVH8Mrbsy5kafJO2_W1n8ZwIHqaRY2Wpn_-1cf0ixwNIwADvD508rOl8SDGckXWBwx8Fd9gNpd-6T3QGwTZ7OsH5RwPSwS8PzrDEvPEzNExhzQWiQNOnrGON_LAZ1LH-sLg8vhyDTcOOkJATk6dVJPuri1b_SokXeYlSA_MAL0L41F0B32zNWQ98OE7UxZ5agQ3QFZ65K2AlxGN1qgP5p9QVHbdm4seZFSIVB6vP1Gmr1uN7KnJFYSCEVxRxDk63JTxg7RbhVr7_gXfj_qqjIeDGJW81ypp-GK_-bXBIOEWW4nsmOWbHEUWqKdIslIE-gvw

## 再使用这个权限的key 访问 dashboard

curl演示:

$ kubectl -n kubernetes-dashboard get pods -v=7

$ curl -k  -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImgtdkhSQmkwbzR5djJ2U0xnSnVLbEF0M1J3U21vTTFfVWdSNTZ6NFN4RFEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJ0ZXN0LXRva2VuLTR2emZ3Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6InRlc3QiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJkZmE0ODg3OS0yODM1LTRhMDktYjg4OC04MWEwNGNjZGUzYzgiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6dGVzdCJ9.GfE50KMhVGgEoHhT5VeMVH8Mrbsy5kafJO2_W1n8ZwIHqaRY2Wpn_-1cf0ixwNIwADvD508rOl8SDGckXWBwx8Fd9gNpd-6T3QGwTZ7OsH5RwPSwS8PzrDEvPEzNExhzQWiQNOnrGON_LAZ1LH-sLg8vhyDTcOOkJATk6dVJPuri1b_SokXeYlSA_MAL0L41F0B32zNWQ98OE7UxZ5agQ3QFZ65K2AlxGN1qgP5p9QVHbdm4seZFSIVB6vP1Gmr1uN7KnJFYSCEVxRxDk63JTxg7RbhVr7_gXfj_qqjIeDGJW81ypp-GK_-bXBIOEWW4nsmOWbHEUWqKdIslIE-gvw" https://10.2.2.10:6443/api/v1/namespaces/kubernetes-dashboard/pods?limit=500
posted @ 2021-01-18 22:01  一墨无辰  阅读(396)  评论(0)    收藏  举报