本文首先回顾了 Kubernetes 架构与工作流程,然后复习了资源对象和资源清单文件相关的内容,最后给出了安全认证的知识点梳理与实践。
Kubernetes 架构与工作流程
一个 Kubernetes 集群由控制节点(master)和工作节点(worker)组成。
- Master 节点是集群的控制面,负责集群行为的决策和管理。 其上运行的组件有:
- api-server:资源操作的唯一入口,接收用户输入的命令,提供认证、授权、API 注册和发现等机制;
- kube-scheduler:负责集群资源调度,按照预定的调度策略将 Pod 调度到相应的节点上;
- controller-manger:负责维护集群的状态,通过管理各种资源对象(Pod、Deployment、ReplicaSet、Service、Ingress、Volume 等)的控制器 1 实现部署安排、故障检测、自动扩展、滚动更新等功能;
- etcd:负责存储集群中各种资源对象的信息,是一个分布式数据库。
- Worker 节点是集群的数据面,负责为容器提供运行环境。 其上运行的组件有:
- kubelet:负责维护容器的生命周期,即通过控制 docker 来创建、更新、销毁容器;
- kube-proxy:负责提供集群内部的服务发现和负载均衡;
- docker:负责节点上容器的各种操作,是容器运行时。
图 1 给出了 Kubernetes 的全局架构。
接下来,以 Nginx 服务的部署来说明各个组件之间的调用关系:
-
当 Kubernetes 集群搭建成功之后,所有 master 节点和 worker 节点会将自身的信息存储到 etcd 中;
-
Nginx 的部署请求由用户通过 kubectl(或其他客户端工具)提交,然后会被发往 api-server;
-
api-server 调用 kube-scheduler 将 Nginx 调度到一个具体的 worker 节点上。具体地,api-server 首先生成资源对象的信息并存入 etcd,kube-scheduler 基于 watch 机制发现了 api-server 上的变动,于是开始调度并将调度结果更新到 api-server;
-
Nginx 被成功调度之后,api-server 调用 controller-manager 创建对应的 controller。例如,当 Nginx 被以 Deployment 资源对象的方式部署时,一个 deployment 会被创建;
-
被调度到的节点通过 kubelet 来创建 Nginx Pod。具体地,kubelet 会通知 docker 来启动相应的 container。同样地,kubelet 会将创建结果发送给 api-server;
-
Nginx Pod 成功运行,api-server 同步更新 Pod 的状态。此时,发往该 Pod 的流量会通过所在节点上的 kube-proxy 被路由到 Nginx Pod 中处理。
Kubernetes 中的资源对象
在 Kubernetes 中,所有内容都被抽象为资源对象。Kubernetes 的最小管理单元是 Pod,一个 Pod 是一个或多个容器和沙箱环境的集合。 Kubernertes 并不直接管理 Pod,而是通过各种控制器来管理。目前的 Pod 控制器有 ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、CronJob 等。 这些控制器额外引入了一些功能,使得伸缩扩容、滚动更新、删除自愈等功能可以实现。 当 Pod 被成功部署后,为了高效且统一地引导流量访问这些 Pod,Kubernetes 引入了 Service 和 Ingress 的概念;对于 Pod 中需要持久化的数据,Kubernetes 引入了 Volume 的概念,还进一步引入了 PV 和 PVC 等概念。
经常使用的资源对象有下面这些:
资源分类 | 资源名称 | 缩写 | 资源作用 |
---|---|---|---|
集群级别资源 | nodes | no | 集群组成部分 |
命名空间 | namespaces | ns | 隔离 Pod |
容器组 | pods | po | 装载容器 |
pod 资源控制器 | replicationcontrollers | rc | 控制 pod 资源 |
pod 资源控制器 | replicasets | rs | 控制 pod 资源 |
pod 资源控制器 | deployments | deploy | 控制 pod 资源 |
pod 资源控制器 | daemonsets | ds | 控制 pod 资源 |
pod 资源控制器 | jobs | 控制 pod 资源 | |
pod 资源控制器 | cronjobs | cj | 控制 pod 资源 |
pod 资源控制器 | horizontalpodautoscalers | hpa | 控制 pod 资源 |
pod 资源控制器 | statefulsets | sts | 控制 pod 资源 |
服务发现资源 | services | svc | 统一 pod 对外接口 |
服务发现资源 | ingress | ing | 统一 pod 对外接口 |
存储资源 | volumeattachments | 存储 | |
存储资源 | persistentvolumes | pv | 存储 |
存储资源 | persistentvolumeclaims | pvc | 存储 |
配置资源 | configmaps | cm | 配置 |
配置资源 | secrets | 配置 |
目前,Kubernetes 原生对批处理任务和有依赖关系的任务的支持尚不友好,未来或许会出现诸如 BatchJob、StreamJob、Workflow 之类的控制器:-D。
作为 Kubernetes 的用户,我们与 Kubernetes 打交道的最主要方式是通过命令行工具 kubectl 2。经常使用的 kubectl 命令总结如下:
命令 | 命令作用 |
---|---|
create |
创建一个资源 |
edit |
编辑一个资源 |
get |
获取一个资源 |
patch |
更新一个资源 |
delete |
删除一个资源 |
explain |
展示资源文档 |
run |
在集群中运行一个指定的镜像 |
expose |
暴露资源为 Service |
describe |
显示资源内部信息 |
logs |
输出容器在 Pod 中的日志 |
attach |
进入运行中的容器 |
exec |
执行容器中的一个命令 |
cp |
在 Pod 内外复制文件 |
rollout |
管理资源的发布 |
scale |
扩(缩)容 Pod 的数量 |
autoscale |
自动调整 Pod 的数量 |
apply |
通过资源清单文件对资源进行配置 |
label |
更新资源上的标签 |
cluster-info |
显示集群信息 |
version |
显示当前 Server 和 Client 的版本 |
api-resources |
显示支持的资源对象和所在的 api 组 |
资源清单文件
在 Kubernetes 中,大多数资源对象的一级属性是类似的,基本都会包含如下 5 个部分:
apiVersion
:当前集群版本。由 Kubernetes 内部定义,版本号必须可以用kubectl api-versions
查询得到;kind
:当前待创建的资源对象的类型。可以用kubectl api-resources
查询得到;metadata
:元数据。用于对当前资源对象进行标识和说明,常用的有name
、namespace
、labels
、annotations
等;spec
:详细描述。这是配置中最重要的一部分,里面是对各种资源配置的详细描述;status
:当前资源对象的状态。由 Kubernetes 自动生成。
安全认证
Kubernetes 中和 api-server 交互的客户端(账户)可分为如下两大类:
- Service Account:Kubernetes 管理的账户,用于为 Pod 中的服务进程在访问 Kubernetes 时提供身份标识。
- User Account:一般是独立于 Kubernetes 之外的其他服务管理的用户账户。
不论哪一类账户,访问 api-server 都需要经历如下步骤:
- Authentication(认证):身份鉴别,只有正确的账户才能够通过认证;
- Authorization(鉴权): 判断当前账户是否有权限对访问的资源执行特定的动作;
- Admission Control(准入控制):用于补充授权机制以实现更加精细的访问控制。
接下来将分别阐述这三个步骤。
认证
Kubernetes 提供了三种身份认证的方式:
-
HTTP Base 认证:通过 “用户名 + 密码” 的方式认证。
把 “用户名: 密码” 用 BASE64 算法进行编码后的字符串放在 HTTP 请求中的 Header Authorization 域里发送给服务端。服务端收到后进行解码,获取用户名及密码,然后进行用户身份认证。
-
HTTP Token 认证:通过一个 Token 来认证。
用一个很长的、难以被模仿的字符串 Token 来表明客户身份的一种方式。每个 Token 对应一个用户名,当客户端发起 API 调用请求时,需要在 HTTP Header 里放入 Token,API Server 接到 Token 后会跟服务器中保存的 Token 进行比对,然后进行用户身份认证。
-
HTTPS 证书认证:基于 CA 根证书签名的双向数字证书认证。
无需更多解读。关于 HTTPS 的加密原理,推荐阅读 知乎 - 彻底搞懂 HTTPS 的加密原理。
鉴权
账户通过认证之后,api-server 会根据事先定义的授权策略来判定该账户是否有权限访问,这个过程就称为鉴权。 每个发送到 api-server 的请求都带上了用户和资源的信息,如发送请求的账户、请求的路径和操作等。 鉴权就是根据这些信息和授权策略进行比较,如果符合策略,则认为鉴权通过,否则会返回错误。
api-server 目前支持以下几种鉴权策略:
- AlwaysDeny:拒绝所有请求,一般用于测试;
- AlwaysAllow:允许所有请求,相当于集群不需要授权流程(Kubernetes 默认的策略);
- ABAC(Attribute-based Access Control):基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制;
- Webhook:通过调用外部 REST 服务对用户进行授权;
- Node:是一种专用模式,用于对 kubelet 发出的请求进行访问控制;
- RBAC(Role-based Access Control):基于角色的访问控制(kubeadm 安装方式下的默认选项)。
我们主要关注 RBAC。RBAC 的工作理念是:给哪些对象授予了哪些权限。 其中涉及到了下面几个概念:
- 对象:包括 User、Group 和 ServiceAccount。
- 角色:包括 Role 和 ClusterRole,代表着一组定义在资源上的可操作动作(权限)的集合。不带 Cluster 的角色是命名空间级别的,带上 Cluster 则是集群级别的。
- 绑定:包括 RoleBinding 和 ClusterRoleBinding,将定义好的角色跟对象绑定在一起。不带 Cluster 的角色是命名空间级别的,带上 Cluster 则是集群级别的。
典型的 Role 的资源清单文件如下:
# Role 只能对命名空间内的资源进行授权,需要指定 namespace
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: dev # 需要指定命名空间(或默认)
name: authorization-role
rules:
- apiGroups: [""] # 支持的 API 组列表。有"","apps", "autoscaling", "batch",空字符串表示核心 API 群
resources: ["pods"] # 支持的资源对象列表
verbs: ["get", "watch", "list"] # 允许的、对资源对象的操作方法列表。有 "get", "list", "watch", "create", "update", "patch", "delete", "exec"
典型的 ClusterRole 的资源清单文件如下:
# ClusterRole 可以对集群范围内资源、跨 namespaces 的范围资源、非资源类型进行授权
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: authorization-clusterrole
# rules 与 Role 并无区别
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
典型的 RoleBinding 的资源清单文件如下:
# RoleBinding 可以将同一 namespace 中的对象绑定到某个 Role 下,使得该对象获得该 Role 定义的权限
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: authorization-role-binding
namespace: dev # 需要指定命名空间(或默认)
subjects: # 待绑定的对象
- kind: User
name: hliangzhao
apiGroup: rbac.authorization.k8s.io
roleRef: # 待绑定的角色
kind: Role # 可以是 Role,也可以是 ClusterRole
name: authorization-role
apiGroup: rbac.authorization.k8s.io
在 RoleBinding 中,待绑定的角色可以是 Role,也可以是 ClusterRole。如果是 ClusterRole,则被绑定对象只能获得该 ClusterRole 在指定命名空间下的权限。
典型的 ClusterRoleBinding 的资源清单文件如下:
# ClusterRoleBinding 在整个集群级别和所有 namespaces 中将特定的对象与 ClusterRole 绑定并授予权限
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: authorization-clusterrole-binding
subjects:
- kind: User
name: hliangzhao
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole # 只能与 ClusterRole 绑定
name: authorization-clusterrole
apiGroup: rbac.authorization.k8s.io
接下来,我们创建一个账户 devman
,隶属于组 devgroup
,然后将上下文 3 从默认的 kubernetes-admin@kubernetes
切换到 devman@kubernetes
。
# 当前节点的用户 k8s,其配置为 / home/k8s/.kube/config
# 我们首先切换到当前节点的 root 用户,然后使用该用户创建账户 devman。这样相关的配置就会被记录在 / root/.kube/config 中
k8s@ubuntu:~$ sudo -i
[sudo] password for k8s:
root@ubuntu:~# cd /etc/kubernetes/pki/
# 生成一个叫做 devman.key 的私钥
root@ubuntu:/etc/kubernetes/pki# (umask 077;openssl genrsa -out devman.key 2048)
Generating RSA private key, 2048 bit long modulus (2 primes)
......................+++++
.........................................................+++++
e is 65537 (0x010001)
# 以私钥 desvman.key 生成证书请求文件 devman.csr,申请主体是用户 devman、组 devgroup
root@ubuntu:/etc/kubernetes/pki# openssl req -new -key devman.key -out devman.csr -subj "/CN=devman/O=devgroup"
# 以证书请求文件 devman.csr 向 kubernetes 集群申请 / 签署证书(ca.crt 和 ca.key 位于当前路径)
root@ubuntu:/etc/kubernetes/pki# openssl x509 -req -in devman.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out devman.crt -days 3650
Signature ok
subject=CN = devman, O = devgroup
Getting CA Private Key
# 证书已生成(注意 Nov 10 生成的四个文件)
root@ubuntu:/etc/kubernetes/pki# ll
total 84
drwxr-xr-x 3 root root 4096 Nov 10 11:26 ./
drwxr-xr-x 4 root root 4096 Oct 12 14:30 ../
-rw-r--r-- 1 root root 1155 Oct 12 14:30 apiserver-etcd-client.crt
-rw------- 1 root root 1679 Oct 12 14:30 apiserver-etcd-client.key
-rw-r--r-- 1 root root 1164 Oct 12 14:30 apiserver-kubelet-client.crt
-rw------- 1 root root 1679 Oct 12 14:30 apiserver-kubelet-client.key
-rw-r--r-- 1 root root 1281 Oct 12 14:30 apiserver.crt
-rw------- 1 root root 1675 Oct 12 14:30 apiserver.key
-rw-r--r-- 1 root root 1099 Oct 12 14:30 ca.crt
-rw------- 1 root root 1679 Oct 12 14:30 ca.key
-rw-r--r-- 1 root root 41 Nov 10 11:26 ca.srl
-rw-r--r-- 1 root root 1013 Nov 10 11:26 devman.crt
-rw-r--r-- 1 root root 911 Nov 10 11:26 devman.csr
-rw------- 1 root root 1679 Nov 10 11:25 devman.key
drwxr-xr-x 2 root root 4096 Oct 12 14:30 etcd/
-rw-r--r-- 1 root root 1115 Oct 12 14:30 front-proxy-ca.crt
-rw------- 1 root root 1679 Oct 12 14:30 front-proxy-ca.key
-rw-r--r-- 1 root root 1119 Oct 12 14:30 front-proxy-client.crt
-rw------- 1 root root 1675 Oct 12 14:30 front-proxy-client.key
-rw------- 1 root root 1675 Oct 12 14:30 sa.key
-rw------- 1 root root 451 Oct 12 14:30 sa.pub
# 设置集群为 kubernetes,用户为 devman
root@ubuntu:/etc/kubernetes/pki# kubectl config set-cluster kubernetes --embed-certs=true --certificate-authority=/etc/kubernetes/pki/ca.crt --server=https://192.168.23.160:6443
Cluster "kubernetes" set.
root@ubuntu:/etc/kubernetes/pki# kubectl config set-credentials devman --embed-certs=true --client-certificate=/etc/kubernetes/pki/devman.crt --client-key=/etc/kubernetes/pki/devman.key
User "devman" set.
# 切换上下文为 devman@kubernetes
root@ubuntu:/etc/kubernetes/pki# kubectl config use-context devman@kubernetes
Switched to context "devman@kubernetes".
root@ubuntu:/etc/kubernetes/pki# kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://192.168.23.160:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: devman
name: devman@kubernetes
current-context: devman@kubernetes
kind: Config
preferences: {}
users:
- name: devman
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
# 此时尚未给对象(User devman)绑定角色,因此其不具备任何操作权限
root@ubuntu:/etc/kubernetes/pki# kubectl get pods -n dev
Error from server (Forbidden): pods is forbidden: User "devman" cannot list resource "pods" in API group ""in the namespace"dev"
接下来,我们将用户切换回 k8s
,然后将集群上下文切换回 kubernetes-admin@kubernetes
。因为 kubernetes-admin@kubernetes
是被记录到用户 k8s
的 kube config 中的,因此只有切换回用户 k8s
才可以找到 kubernetes-admin@kubernetes
这个上下文。切换回来之后,我们作为 kubernetes-admin
账户为 devman
绑定角色:
# binding.yaml
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: dev
name: dev-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: authorization-role-binding
namespace: dev
subjects:
- kind: User
name: devman
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: dev-role
apiGroup: rbac.authorization.k8s.io
我们作为 kubernetes-admin
通过 apply
命令创建上述资源对象,然后切换上下文查看 devman
是否可以执行 get
操作:
k8s@ubuntu:~/learn-k8s/15-rbac$ k apply -f binding.yaml
role.rbac.authorization.k8s.io/dev-role created
# 以 kubernetes-admin 的身份在 dev 命名空间中创建一个 deployment,控制三个 nginx Pod
k8s@ubuntu:~/learn-k8s/15-rbac$ k apply -f ../07-deployment/nginx-deployment.yaml
deployment.apps/nginx-deployment created
# 如前所述,devman@kubernetes 是被记录在 root 用户下的 /.kube/config 中的,因此要先切换为 root,再执行上下文切换
k8s@ubuntu:~/learn-k8s/15-rbac$ k config use-context devman@kubernetes
error: no context exists with the name: "devman@kubernetes"
k8s@ubuntu:~/learn-k8s/15-rbac$ sudo -i
[sudo] password for k8s:
root@ubuntu:~# kubectl config use-context devman@kubernetes
Switched to context "devman@kubernetes".
# get po 成功!
root@ubuntu:~# kubectl get po -n dev
NAME READY STATUS RESTARTS AGE
nginx-deployment-7848d4b86f-7bzgm 1/1 Running 0 62s
nginx-deployment-7848d4b86f-d88dt 1/1 Running 0 62s
nginx-deployment-7848d4b86f-rprsc 1/1 Running 0 62s
上述实验结果测试了 RBAC 的使用。
准入控制
通过了前面的认证和授权之后,还需要经过准入控制处理通过之后,api-server 才会处理这个请求。 准入控制是一个可配置的控制器列表,可以通过在 api-server 上通过命令行设置选择执行哪些准入控制器。 准入控制的工作逻辑是一票否决机制,即只要不满足列表中的一个规则,请求就会被拒绝。 每一个准入控制器对应了一个插件,我们要使用对应的准入控制器,首先需要启用对应的插件。 比较常用的准入控制器有 LimitRanger、ResourceQuota、ServiceAccount、PodSecurityPolicy 等。此处不再展开。
参考
本文部分改写自 黑马 K8S 视频教程 的相关章节,感兴趣的读者可自行前往学习。
转载申请
本作品采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接。您必须给出适当的署名,并标明是否对本文作了修改。
-
控制器管理对象,controller-manager 管理控制器。 ↩︎
-
当然,当我们基于 Kubernetes 做开发时,主要和 Kubernetes 为所选语言所提供的客户端打交道。例如,Kubernetes 官方维护的 Python 客户端 https://github.com/kubernetes-client/python。 ↩︎
-
这里所谓的上下文是使用 “账户 @集群名称” 来描述的,默认账户是
kubernetes-admin
,默认集群名称是kubernetes
。 ↩︎