对于资源对象的操作都是通过APIServer进行的,那集群是如何知道请求就是合法的,这就需要RBAC(基于角色的权限控制)

管理员可以通过k8s API动态配置策略来启动RBAC,需要在kube-apiserver中添加参数--authorization-mode=RBAC,如果使用kubeadm安装的集群那么默认开启了RBAC,可以通过Master节点上apiserver的静态Pod配置文件:

  1. $ cat /etc/kubernetes/manifests/kube-apiserver.yaml
  2. ...
  3. - --authorization-mode=Node,RBAC
  4. ...

如果是二进制的方式搭建的集群,添加这个参数过后,记得要重启 kube-apiserver 服务。

API对象

在k8s集群中,k8s对象是持久化的实体,就是最终存入etcd中的数据,集群中通过这些实体来表示整个集群的状态。前面都是通过直接编写YAML文件,通过kubectl来提交资源清单文件,然后创建的对应的资源对象,那么它究竟是如何将yaml文件转换成集群中的一个API对象呢?

这就需要了解下声明式API的设计,k8s api是一个以JSON为主要序列化方式HTTP服务,除此之外也支持Protocol Buffers序列化方式,主要用于集群内部组件间的通信。为了可扩展性,k8s在不同的API路径(比如/api/v1或者/apis/batch)下面支持了多个API版本,不同的API版本意味着不同级别的稳定性和和支持:

  • Alpha级别,例如v1alpha1默认情况下是被禁用的,可以随时删除对功能的支持,所以要谨慎使用。
  • Beta级别,比如v2beta1默认情况下是启用的,表示代码已经经过很好的测试,但是对象的语义可能会在随后的版本中以不兼容的方式更改。
  • 稳定级别,比如v1表示已经是稳定版本了,也会出现在后续的很多版本中。

在k8s集群中,一个API对象在etcd里面的完整资源路径,是由:GROUP(API组)Version(API版本)Resource(API资源类型)三部分组成。通过这样的结构,整个k8s里的所有API对象,实际上可以用如下的树状结构表示出来:
image.png

从上图可以看出k8s的API对象的组织方式,在顶层,可以看到有一个核心组(由于历史原因,是/api/v1下的所有内容而不是在/apis/core/v1下面)和命名组(路径/apis/$NAME/$VERSION)和系统范围内的实体,比如/metrics。查看集群中API组织形式的命令:

$ kubectl get --raw /
{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/",
    ......
    "/version"
  ]
}

比如查看批处理这个操作,在当前版本中存在两个版本的操作:/apis/batch/v1和/apis/batcg/v1beta1,分别暴露了可以查询和操作的不同实体集合,也可以使用kubectl来查询对应对象下面的数据:

$ kubectl get --raw /apis/batch/v1 | python -m json.tool
{
    "apiVersion": "v1",
    "groupVersion": "batch/v1",
    "kind": "APIResourceList",
    "resources": [
        {
            "categories": [
                "all"
            ],
            "kind": "Job",
            "name": "jobs",
            "namespaced": true,
            "singularName": "",
            "storageVersionHash": "mudhfqk/qZY=",
            "verbs": [
                "create",
                "delete",
                "deletecollection",
                "get",
                "list",
                "patch",
                "update",
                "watch"
            ]
        },
        {
            "kind": "Job",
            "name": "jobs/status",
            "namespaced": true,
            "singularName": "",
            "verbs": [
                "get",
                "patch",
                "update"
            ]
        }
    ]
}

也可以通过kubectl proxy命令开启对apiserver的访问:

$ kubectl proxy
Starting to serve on 127.0.0.1:8001

然后重新开启一个新的终端,我们可以通过如下方式来访问批处理的 API 服务:

$ curl http://127.0.0.1:8001/apis/batch/v1
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "batch/v1",
  "resources": [
    {
      "name": "jobs",
      "singularName": "",
      "namespaced": true,
      "kind": "Job",
      "verbs": [
        "create",
        "delete",
        "deletecollection",
        "get",
        "list",
        "patch",
        "update",
        "watch"
      ],
      "categories": [
        "all"
      ],
      "storageVersionHash": "mudhfqk/qZY="
    },
    {
      "name": "jobs/status",
      "singularName": "",
      "namespaced": true,
      "kind": "Job",
      "verbs": [
        "get",
        "patch",
        "update"
      ]
    }
  ]
}

同样也可以去访问另外一个版本的对象数据:

$ curl http://127.0.0.1:8001/apis/batch/v1beta1
......

通常,k8s API支持通过标准HTTP POSTPUTDELETEGET在指定PATH路径上创建、更新、删除和检索操作,并使用JSON作为默认的数据交互格式。

比如创建一个Deployment对象,那么yaml文件的声明就需要写:

apiVersion: apps/v1
kind: Deployment

其中Deployment就是这个API对象的资源类型(Resource)、apps就是它的组(Group)、v1就是它的版本(Version)。API Group、Verison和资源就唯一定义了一个HTTP路径,然后在kube-apiserver端对这个url进行监听,然后把对应的请求传递给对应的控制器进行处理。

RBAC

k8s所有的资源对象都是模型化的API对象,允许执行CRUD(Create、Read、Update、Delete)操作,比如下面的这些资源:

  • Pods
  • ConfigMaps
  • Deployments
  • Nodes
  • Secrets
  • Namespaces
  • ……

对于上面这些资源对象可能存在的操作有:

  • create
  • get
  • delete
  • list
  • update
  • edit
  • watch
  • exec
  • patch
  • ……

在更上层,这些资源和API Group进行关联,比如Pods属于Core API Group,而Deployment属于apps API Group,现在在k8s中通过RBAC来对资源进行权限管理,除了上面的这些资源和操作以外,还需要了解另外一个概念:

  • Rule:规则,规则是一组属于不同API Group资源上的一组操作的集合
  • RoleClusterRole:角色和集群角色,这两个对象都包含上面的Rules元素,二者的区别在于,在Role中,定义的规则只适用于单个命名空间,也就是和namespace关联的,而ClusterRole是在集群范围内的,因此定义的规则不受命名空间的约束。另外Role和ClusterRole在k8s中都被定义为集群内部的API资源,和之前的Pod、Deployment这些对象类似,都是集群的对象,所以同样可以使用YAML文件来描述,kubectl工具来管理
  • Subject:主体,对应集群中尝试操作的对象,集群中定义了3中类型的主题资源:
    • User Account:用户,这是有外部独立服务进行管理的,管理员进行私钥的分配,用户可以使用KeyStone或者Google账号,甚至一个用户名和密码的文件列表也可以。对于用户的管理,集群内部没有一个关联的资源对象,所以用户不能通过集群内部的API来进行管理。
    • Group:组,这是用来关联多个账户的,集群中有一些默认创建的组,比如cluster-admin。
    • Service Account:服务账号,通过k8s API来管理的一些用户账号,和namespace进行关联的,适用于集群内部运行的应用程序,需要通过API来完成权限认证,所以在集群内部进行权限操作。
  • RoleBindingClusterRoleBinding:角色绑定和集群角色绑定,简单的来说就是把声明的Subject和Role进行绑定的过程(给某个用户绑定上操作的权限),二者的区别也是作用范围的区别:RoleBinding只会影响到当前namespace下面的资源操作权限,而ClusterRoleBinding会影响到所有的namespace。

只能访问某个namespace的普通用户

创建一个只能访问kube-system的User Account,对应的用户信息如下:

username: cnych
group: youdianzhishi

创建用户凭证

使用OpenSSL证书创建一个User,也可以使用cfssl工具创建:

给用户cnych创建一个私钥,明明成cnych.key:

$ openssl genrsa -out cnych.key 2048
Generating RSA private key, 2048 bit long modulus
..............................................................................+++
..............................................................................................................................................+++
e is 65537 (0x10001)

使用刚创建的私钥,创建一个证书签名请求文件:cnych.csr,要注意需要确保-subj参数中指定用户名和组(CN表示用户名,O表示组):

$ openssl req -new -key cnych.key -out cnych.csr -subj "/CN=cnych/O=youdianzhishi"

利用CA的证书ca.crt和ca.key来批准上面的证书请求(kubeadm安装的默认位置:/etc/kubernetes/pki/),生成最终的证书文件,这里设置证书的有效期为500天:

$ openssl x509 -req -in cnych.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out cnych.crt -days 500
Signature ok
subject=/CN=cnych/O=youdianzhishi
Getting CA Private Key

查看当前目录下是否生成一个证书文件:

$ ls
cnych.crt  cnych.csr  cnych.key

使用刚刚创建的证书文件和私钥文件在集群中创建新的凭证和上下文(Context):

$ kubectl config set-credentials cnych --client-certificate=cnych.crt --client-key=cnych.key
User "cnych" set.

用户cnych已创建,然后为这个用户设置新的Context,指定一个特定的namespace:

$ kubectl config set-context cnych-context --cluster=kubernetes --namespace=kube-system --user=cnych
Context "cnych-context" created.

到这里,用户cnych已经创建成功,现在使用当前的配置文件来操作kubectl命令会出错,,因为还没有给该用户定义任何操作的权限:

$ kubectl get pods --context=cnych-context
Error from server (Forbidden): pods is forbidden: User "cnych" cannot list resource "pods" in API group "" in the namespace "kube-system"

创建角色

用户创建完成后,接下来就需要给该用户添加操作权限,创建一个允许用户操作Deployment、Pod、ReplicaSet的角色的yaml文件,如下(cnych-role.yaml):

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cnych-role
  namespace: kube-system
rule:
- apiGroups: ["", "apps"]
  resources: ["deployments", "replicasets", "pods"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # 也可以使用['*']

其中Pod属于core这个APIGroup,在YAML中用空字符就可以,而Deployment和ReplicaSet现在都属于apps这个APIGroup(如果不知道则可以用kubectl explain命令查看),所以rules下面的APIGroups就综合了这几个资源的APIGroup: ["", "apps"],其中verbs就是上面提到的可以对这些资源对象执行的操作,如果需要所有的操作方法,可以用[‘*’]来替代。
直接创建这个Role:

$ kubectl create -f cnych-role.yaml
role.rbac.authorization.k8s.io/cnych-role created

这里没有使用上面的cnych-context这个上下文,因为暂时没有权限。

创建角色权限绑定

Role创建完成后,但是这个Role没有和用户cnych绑定,这里就需要创建一个RoleBinding对象,在kube-system这个命名空间下将cnych-role角色和用户cnych进行绑定(cnych-rolebinding.yaml):

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name cnych-rolebinding
  namespace: kube-system
subjects:
- kind: User
  name: cnych
  apiGroup: ""
roleRef:
  kind: Role
  name: cnych-role
  apiGroup: rbac.authorization.k8s.io # 流控字符串也可以,则使用当前的apiGroup

上面的YAML文件中看到了subjects字段,这里就是上面提到的用来尝试操作集群的对象,对应上面的User账号cnych,直接创建上面的资源对象:

$ kubectl create -f cnych-rolebinding.yaml
rolebinding.rbac.authorization.k8s.io/cnych-rolebinding created

测试

现在可以使用上面的cnych-context上下文来操作集群:

$ kubectl get pods --context=cnych-context
kubectl get pods --context=cnych-context
NAME                                         READY   STATUS    RESTARTS   AGE
coredns-667f964f9b-pr44s                     1/1     Running   0          13d
coredns-667f964f9b-vh55b                     1/1     Running   1          13d
etcd-ydzs-master                             1/1     Running   0          13d
kube-apiserver-ydzs-master                   1/1     Running   1          6d5h
kube-controller-manager-ydzs-master          1/1     Running   3          13d
......
$ kubectl --context=cnych-context get rs,deploy
NAME                                                    DESIRED   CURRENT   READY   AGE
replicaset.apps/coredns-667f964f9b                      2         2         2       13d
replicaset.apps/metrics-server-5d4dbb78bb               1         1         1       2d21h
replicaset.apps/metrics-server-6886856d7c               0         0         0       2d21h
replicaset.apps/metrics-server-6dcfdf89b5               0         0         0       2d21h
replicaset.apps/traefik-ingress-controller-54f665bb97   0         0         0       47h

NAME                                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/coredns                      2/2     2            2           13d
deployment.apps/metrics-server               1/1     1            1           2d21h
deployment.apps/traefik-ingress-controller   1/1     1            1           2d6h

可以看到使用kubectl的时候并没有指定namespace,这是因为上面创建这个context的时候就绑定在了kube-system这个命名空间下。
尝试查看default命名空间下:

$ kubectl --context=cnych-context get pods --namespace=default
Error from server (Forbidden): pods is forbidden: User "cnych" cannot list resource "pods" in API group "" in the namespace "default"

查看其他资源对象:

$ kubectl --context=cnych-context get svc
Error from server (Forbidden): services is forbidden: User "cnych" cannot list resource "services" in API group "" in the namespace "kube-system"

看到是没有权限的,因为并没有为当前操作用户指定其他对象资源的访问权限。

只能访问某个namespace的ServiceAccount

上面创建了一个只能访问某个命名空间下面的普通用户,subjects下面还有一种类型的主题资源:ServiceAccount,现在创建一个集群内部的用户只能操作kube-system这个命名空间下面的Pods和Deployments。
首先创建一个ServiceAccount对象:

$ kubectl create sa cnych-sa -n kube-system

也可以使用YAML文件来创建,对应的YAML文件:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: cnych-sa
  namespace: kube-system

然后新建一个Role对象(cnych-sa-role.yaml):

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: cnych-sa-role
  namespace: kube-system
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

上面的pod没有创建,删除,更新的权限,创建该资源对象:

$ kubectl create -f cnych-sa-role.yaml

然后创建一个RoleBinding对象,将上面的cnych-sa和角色cnych-sa-role进行绑定(cnych-sa-rolebinding.yaml):

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: cnych-sa-rolebinding
  namespace: kube-system
subjects:
- kind: ServiceAccount
  name: cnych-sa
  namespace: kube-system
roleRef:
  kind: Role
  name: cnych-sa-role
  apiGroup: rbac.authorization.k8s.io

由于ServiceAccount会生成一个Secret对象和它映射,这个Secret里面包含一个token,可以使用这个token去登录Dashboard,去验证功能:

$ kubectl get secret -n kube-system |grep cnych-sa
cnych-sa-token-nxgqx                  kubernetes.io/service-account-token   3         47m
$ kubectl get secret cnych-sa-token-nxgqx -o jsonpath={.data.token} -n kube-system |base64 -d
# 会生成一串很长的base64后的字符串

可以全局访问的ServiceAccount

创建可以操作全局的ServiceAccount,需要使用到ClusterRole和ClusterRoleBinding这两种资源对象(cnych-sa2.yaml):

apiVersion: v1
kind: ServiceAccount
metadata:
  name: cnych-sa2
  namespace: kube-system

创建:

$ kubectl create -f cnych-sa2.yaml

然后创建一个ClusterRoleBinding对象(cnych-clusterrolebinding.yaml):

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: cnych-sa2-clusterrolebinding
subjects:
- kind: ServiceAccount
  name: cnych-sa2
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io

上面的资源对象没有声明namespace,因为这是一个ClusterRoleBinding资源对象,是作用于整个集群,也没有新建一个ClusterRole对象,而是使用cluster-admin对象,这是k8s集群内置的clusterrole对象,使用kubectl get clusterrolekubectl get clusterrolebinding查看系统内置的一些集群角色和集群角色绑定,这里使用的 cluster-admin 这个集群角色是拥有最高权限的集群角色,所以一般需要谨慎使用该集群角色。

创建上面集群角色绑定资源对象,创建完成后同样使用 ServiceAccount 对应的 token 去登录 Dashboard 验证下:

$ kubectl create -f cnych-clusterolebinding.yaml
$ kubectl get secret -n kube-system |grep cnych-sa2
cnych-sa2-token-nxgqx                  kubernetes.io/service-account-token   3         47m
$ kubectl get secret cnych-sa2-token-nxgqx -o jsonpath={.data.token} -n kube-system |base64 -d
# 会生成一串很长的base64后的字符串

image.png