3-13-1-认证、授权和准入控制器
1. Kubernetes访问控制
API Server作为Kubernetes集群系统的网关 , 是访问及管理资源 对象的唯一入口 , 它默认监听TCP的6443端口 , 通过HTTPS协议暴露了一个RESTful风格的接口。所有需要访问集群资源的集群组件或客户端 , 包括kube-controller-manager、kube-scheduler、kubelet和 kube-proxy等集群基础组件 , CoreDNS等集群的附加组件 , 以及此前使用的kubectl命令等都必须要经此网关请求与集群进行通信。所有客户端均要经由API Server访问或改变集群状态以及完成数据存储 , 并且 API Server会对每一次的访问请求进行合法性检验 , 包括用户身份鉴别、操作权限验证以及操作是否符合全局规范的约束等。所有检查均正常完成且对象配置信息合法性检验无误之后才能访问或存入数据到后端存储系统etcd中。

客户端认证操作由API Server配置的一到多个认证插件完成。收到请求后 , API Server依次调用配置的认证插件来校验客户端身份 , 直到其中一个插件可以识别出请求者的身份为止。授权操作则由一到多个授权插件完成 , 这些插件负责确定通过认证的用户是否有权限执 行发出的资源操作请求 , 该类操作包括创建、读取、删除或修改指定的对象等。随后 , 通过授权检测的用户请求修改相关的操作还要经由 一到多个准入控制插件的遍历式检测 , 例如使用默认值补足要创建的 目标资源对象中未定义的各字段、检查目标Namespace资源对象是否存在、检查请求创建的Pod对象是否违反系统资源限制等 , 而其中任何的检查失败都可能会导致写入操作失败。
1.1 用户账户与用户组
Kubernetes系统上的用户账户及用户组的实现机制与常规应用略有不同。Kubernetes集群将那些通过命令行工具kubectl、客户端库或 者直接使用RESTful接口向API Server发起请求的客户端上的请求主体分为两个不同的类别 : 现实中的 " 人" 和Pod对象 , 它们的用户身份分 别对应用户账户(User Account , 也称为普通用户)和服务账户 ( Service Account , 简称SA) 。
用户账户 : 其使用主体往往是 " 人" , 一般由外部的用户管理 系统存储和管理 ,
Kubernetes本身并不维护这一类的任何用户账户信 息 , 它们不会存储到API Server之上 , 仅仅用于检验用户是否有权限执行其所请求的操作。服务账户 : 其使用主体是 " 应用程序" , 专用于为
Pod资源中 的服务进程提供访问Kubernetes API时的身份标识(identity) ;ServiceAccount资源通常要绑定到特定的名称空间 , 它们由API Server自动创建或通过API调用 , 由管理员手动创建 , 通常附带着一组访问API Server的认证凭据——Secret, 可由同一名称的Pod应用访问API Server时使用。
用户账户通常用于复杂的业务逻辑管控 , 作用于系统全局 , 因而名称必须全局唯一。Kubernetes并不会存储由认证插件从客户端请求中提取的用户及所属的组信息 , 因而也就没有办法对普通用户进行身份认证 , 它们仅仅用于检验该操作主体是否有权限执行其所请求的操 作。相比较来说 , 服务账户则隶属于名称空间级别 , 仅用于实现某些特定操作任务 , 因此功能上要轻量得多。这两类账户都可以隶属于一 个或多个用户组。
用户组只是用户账户的逻辑集合 , 它本身没有执行系统操作的能力 , 但附加于组上的权限可由其内部的所有用户继承 , 以实现高效的 授权管理机制。Kubernetes有以下几个内置用于特殊目的的组。
system:unauthenticated: 未能通过任何一个授权插件检验的账户的、所有未通过认证测试的用户统一隶属的用户组。system:authenticated: 认证成功后的用户自动加入的一个专用组 , 用于快捷引用所有正常通过认证的用户账户。system:serviceaccounts: 所有名称空间中的所有ServiceAccount对象。system:serviceaccounts:<namespace>: 特定名称空间内所有的ServiceAccount对象。
对API Server来说 , 来自客户端的请求要么与用户账户绑定 , 要么以某个服务账户的身份进行 , 要么被视为匿名请求。这意味着群集 内部或外部的每个进程 , 包括由人类用户使用kubectl , 以及各节点上 运行的kubelet进程 , 再到控制平面的成员组件 , 必须在向API Server 发出请求时进行身份验证 , 否则即被视为匿名用户。
1.2 认证、授权与准入控制基础
如前所述 , Kubernetes使用身份验证插件对API请求进行身份验证 , 它允许管理员自定义服务账户和用户账户要启用或禁用的插件 , 并支持各自同时启用多种认证机制。具体设定时 , 至少应该为服务账户和用户账户各自启用一个认证插件。
如果启用了多种认证机制 , 账号认证过程由认证插件以串行方式 进行 , 直到其中一种认证机制成功完成即结束。若认证失败 , 服务器则响应以401状态码 , 反之 , 请求者就会被Kubernetes识别为某个具体的用户(以其用户名进行标识), 并且该连接上随后的操作都会以此用户身份进行。API Server对于接收到的每个访问请求会调用认证插件 , 尝试将以下属性与访问请求相关联。
Username: 用户名 , 例如kubernetes-admin等。UID: 用户的数字标签符 , 用于确保用户身份的唯一性。Groups: 用户所属的组 , 用于权限指派和继承。Extra: 键值数据类型的字符串 , 用于提供认证需要用到的额外 信息。
1.2.1 认证
Kubernetes支持的认证方式包括X.509数字证书、承载令牌 ( bearer token , 也称为不记名令牌) 、身份验证代理 ( authenticating proxy) 和HTTP Basic认证等。具体来说 , API Server支持以下几种具体的认证方式 , 其中所有的令牌认证机制通常 被统称为 " 承载令牌认证" 。
静态密码文件认证 : 将用户名和密码等信息以明文形式存储在
CSV格式的文件中 , 由kube-apiserver在启动时通过--basic-auth-file选项予以加载 , 添加或删除用户都需要重启API Server; 客户端 通过在HTTP Basic认证(Authorization: Basic<base64-encoded username:password>标头)方式中将用户名和密码编码后对该文件进行认证 ; 显然 , 该认证方式应该在非生产性的环境中使用。提示 : 静态密码文件认证插件自
Kubernetes v1.20版本中预以弃用 , 因而 , 后面1.3节中使用该插件的测试操作部分在v1.20及之后的版本 上不可用 , 但在v1.19及之前的版本中 , 仍然可用。静态令牌文件认证 : 即保存用于认证的令牌信息的静态文件 , 由
kube-apiserver的命令行选项--token-auth-file加载 , 且API Sever进程启动后不可更改 ;HTTP协议的客户端能基于承载令牌(Authorization: Bearer <token>标头) 对静态令牌文件进行身份验 证 , 它将令牌编码后通过请求报文中的Authorization头部承载并传递 给API Server即可 ; 建议仅将该认证方式用于非生产性环境中。X509客户端证书认证: 客户端在请求报文中携带X.509格式的 数字证书用于认证 , 其认证过程类似于HTTPS协议通信模型 ; 认证通过 后 , 证书中的主体标识 (Subject) 将被识别为用户标识 , 其中的字段CN(Common Name) 的值是用户名 , 字段O(Organization) 的值是用户所属的组。例如/CN=ilinux/O=opmasters/O=admin中 , 用户名为ilinux, 它属于opmasters和admin两个组 ; 该认证方式可通过--client-ca-file=SOMEFILE选项启用。引导令牌 (
Bootstrap Token) 认证 : 一种动态管理承载令牌 进行身份认证的方式 , 常用于简化组建新Kubernetes集群时将节点加入集群的认证过程 , 需要由kube-apiserver通过--experimental bootstrap-token-auth选项启用 ; 新的工作节点首次加入时 ,Master使用引导令牌确认节点身份的合法性之后自动为其签署数字证书以用 于后续的安全通信 ,kubeadm初始化的集群也是这种认证方式 ; 这些令 牌作为Secrets存储在kube-system命名空间中 , 可以动态管理和创建它们 , 并由TokenCleaner控制器负责删除过期的引导令牌。ServiceAccount令牌认证: 该认证方式会由kube-apiserver程序自动启用 , 它同样使用签名的承载令牌来验证请求 ; 该认证方式还 支持通过可选项--service-account-key-file加载签署承载令牌的密钥文件 , 未指定时将使用API Server自己的TLS私钥 ;ServiceAccount通常由API Server自动创建 , 并通过ServiceAccount准入控制器将其注入Pod对象 , 包括ServiceAccount上的承载令牌 , 容器中的应用程序请求API Server的服务时以此完成身份认证。OpenID Connect令牌认证: 简称为OIDC, 是OAuth 2协议的一 种扩展 , 由Azure AD、Salesforce和Google Accounts等OAuth 2服务商所支持 , 协议的主要扩展是返回的附加字段 , 其中的访问令牌也称为ID令牌 ; 它属于JSON Web令牌 (JWT) 类型 , 有服务器签名过的常用 字段 , 例如email等 ;kube-apiserver启用这种认证功能的相关选项较多。Webhook令牌认证:Webhook身份认证是用于验证承载令牌的钩 子 ;HTTP协议的身份验证允许将服务器的URL注册为Webhook, 并接收 带有承载令牌的POST请求进行身份认证 ; 客户端使用kubeconfig格式的配置文件 , 在文件中 ,users指的是API Server的Webhook, 而clusters则指的是API Server。代理认证:
API Server支持从请求头部的值中识别用户 , 例如 常用的X-Remote-User、X-Remote-Group和几个以X-Remote-Extra-开 头的头部 , 它旨在与身份验证代理服务相结合 , 由该代理设置相应的 请求头部 ; 为了防止头欺骗 , 在检查请求标头之前 , 需要身份认证代理服务向API Server提供有效的客户端证书 , 以验证指定CA( 由选项--requestheader-client-ca-file等进行指定 ) 的代理服务是否合法。
那些未能被任何验证插件明确拒绝的请求中的用户即为匿名用 户 , 该类用户会被冠以system:anonymous用户名 , 隶属于 system:unauthenticated用户组。若API Server启用了除AlwaysAllow 以外的认证机制 , 则匿名用户处于启用状态。但是 , 出于安全因素的考虑 , 建议管理员通过--anonymous-auth=false选项将其禁用。
提示 :
API Server还允许用户通过模拟头部冒充另一个用户 , 这些请求可以以手动方式覆盖请求中用于身份验证的用户信息。例如 , 管理员可以使用此功能临时模拟其他用户来查看请求是否被拒绝 , 以进行授 权策略调试。
2.2.2 授权
除了身份信息 , 请求报文还需要提供操作方法及其目标对象 , 例 如针对某Pod资源对象进行的创建、查看、修改或删除操作等。具体来 说 , 它包含如下信息。
API: 用于定义请求的目标是否为一个API资源。Request path: 请求的非资源型路径 , 例如/api或/healthz。API group: 要访问的API组 , 仅对资源型请求有效 ; 默认为core API group。Namespace: 目标资源所属的名称空间 , 仅对隶属于名称空间类 型的资源有效。API request verb:API请求类的操作 , 即资源型请求 ( 对资源 执行的操作 ) , 包括get、list、create、update、patch、watch、proxy、redirect、delete和deletecollection等。HTTP request verb:HTTP请求类的操作 , 即非资源型请求要执行的操作 , 如get、post、put和delete。Resource: 请求的目标资源的ID或名称。Subresource: 请求的子资源。
为了核验用户的操作许可 , 成功通过身份认证后的操作请求还需 要转交给授权插件进行许可权限检查 , 以确保其拥有执行相应操作的 许可。API Server主要支持使用4类内置的授权插件来定义用户的操作 权限。
Node: 基于Pod资源的目标调度节点来实现对kubelet的访问控制。ABAC:Attribute-based access control, 基于属性的访问控制。RBAC:Role-based access control, 基于角色的访问控制。Webhook: 基于HTTP回调机制实现外部REST服务检查 , 确认用户 授权的访问控制。
另外 , 还有AlwaysDeny和AlwaysAllow两个特殊的授权插件 , 其中 AlwaysDeny ( 总是拒绝 ) 仅用于测试 , 而AlwaysAllow ( 总是允许 ) 则 用于不期望进行授权检查时直接在授权检查阶段放行所有的操作请 求。--authorization-mode选项用于定义API Server要启用的授权机 制 , 多个选项值彼此间以逗号进行分隔。
1.2.3 准入控制器
准入控制器 ( admission controller ) 则用于在客户端请求 经过身份验证和授权检查之后 , 将对象持久化存储到etcd之前拦截请求 , 从而实现在资源的创建、更新和删除操作期间强制执行对象的语 义验证等功能 , 而读取资源信息的操作请求则不会经由准入控制器检 查。API Server内置了许多准入控制器 , 常用的包含下面列出的几 种。不过 , 其中的个别控制器仅在较新版本的Kubernetes中才被支 持。
AlwaysAdmit和AlwaysDeny: 前者允许所有请求 , 后者则拒绝 所有请求。(AlwaysAdmit和AlwaysDeny已被弃用。)AlwaysPullImages: 总是下载镜像 , 即每次创建Pod对象之前 都要去下载镜像 , 常用于多租户环境中 , 以确保私有镜像仅能够由拥 有权限的用户使用。NamespaceLifecycle: 拒绝在不存在的名称空间中创建资源 , 而删除名称空间则会级联删除其下的所有其他资源。LimitRanger: 可用资源范围界定 , 用于对设置了LimitRange的对象所发出的所有请求进行监控 , 以确保其资源请求不会超限。ServiceAccount: 用于实现服务账户管控机制的自动化 , 实现 创建Pod对象时自动为其附加相关的Service Account对象。PersistentVolumeLabel: 为那些由云计算服务商提供的PV自 动附加region或zone标签 , 以确保这些存储卷能正确关联且仅能关联 到所属的region或zone。DefaultStorageClass: 监控所有创建PVC对象的请求 , 以保证 那些没有附加任何专用StorageClass的请求会被自动设定一个默认 值。ResourceQuota: 用于为名称空间设置可用资源上限 , 并确保当其中创建的任何设置了资源限额的对象时 , 不会超出名称空间的资 源配额。DefaultTolerationSeconds: 如果Pod对象上不存在污点宽容期限 , 则为它们设置默认的宽容期 , 以宽容notready:NoExecute和unreachable:NoExecute类的污点5分钟时间。ValidatingAdmissionWebhook: 并行调用匹配当前请求的所有验证类的Webhook, 任何一个校验失败 , 请求即失败。MutatingAdmissionWebhook: 串行调用匹配当前请求的所有变异类的Webhook, 每个调用都可能会更改对象。
早期的准入控制器代码需要由管理员编译进kube-apiserver中才能使用 , 实现方式缺乏灵活性。于是 , Kubernetes自v1.7版本引入了Initializers和External Admission Webhooks来尝试突破此限制 , 而 且自v1.9版本起 , External Admission Webhooks被分为Mutating AdmissionWebhook和ValidatingAdmissionWebhook两种类型 , 分别用于在API中执行对象配置的变异和验证操作。检查期间 , 仅那些顺利通过所有准入控制器检查的资源操作请求的结果才能保存到etcd中 , 而 任何一个准入控制器的拒绝都将导致写入请求失败。
1.3 测试使用API Server的访问控制机制
如前所述 , 认证、授权和准入控制功能都以API Server插件形式 存在 , 它们都可由kube-apiserver相应的选项进行启用和配置。由 kubeadm部署的Kubernetes集群的API Server以静态Pod形式运行 , 相关配置清单 ( /etc/kubernetes/manifests/kube-apiserver.yaml ) 中 配置kube-apiserver如下的一部分选项
containers: - command:
- kube-apiserver
- --secure-port=6443 # API Server监听的安全端口 ( HTTPS协议 )
- --insecure-port=0 # API Server监听的非安全端口 ( HTTP协议 ) , 0表示禁用
- ……
- --client-ca-file=/etc/kubernetes/pki/ca.crt # 启用X509数字证书认证
- --authorization-mode=Node,RBAC # 启用Node和RBAC授权插件
- --enable-admission-plugins=NodeRestriction # 额外启用的准入控制器列表
- --enable-bootstrap-token-auth=true # 启用Bootstrap Token认证
- ……
- --requestheader-extra-headers-prefix=X-Remote-Extra- #代理认证相关的配置
- --requestheader-group-headers=X-Remote-Group
- --requestheader-username-headers=X-Remote-User
- --service-account-key-file=/etc/kubernetes/pki/sa.pubAPI Server的几乎每个认证插件都有自己专用的一至多个配置选项 , 例如上面配置中启用的X509证书认证、Bootstrap Token认证和代理认证 , Server Account认证默认处于启用状态 , --service-account-key-file选项仅指定用于签署承载令牌的密钥文件 , 未配置 时默认使用API Server的TLS私钥。认证操作由各插件以 " 短路" 方式 进行 , 客户端请求会依次经由认证插件进行检查 , 任何一个插件认证成功即终止认证过程。
启用的授权插件需要显式指定 , 它们需要以列表值的格式统一定 义在--authorization-mode选项之上 , 例如上面配置中启用了Node和 RBAC授权插件。启用的各授权插件同样以 " 短路" 机制运行 , 任何一个授权插件鉴权成功即可结束授权检查过程。
API Server默认会启用一部分准入控制器 , 额外需要启用的准入 控制器需要以列表值的形式定义在--enable-admission-plugins选项 之上 , 需要显式禁用准入控制器以列表值的形式定义在--disable-admission-plugins选项之上。与认证和授权不同的是 , 一个请求必须 要成功通过所有准入控制器的检查 , 否则即会被拒绝。
提示 : 不同版本的
Kubernetes默认启用的准入控制器可能存在区别 , 要了解当前使用的版本上默认启用的准入控制器 , 可在kube-system名称空间中API Server相关的静态Pod内部运行 "kube-apiserver -h | grep 'enable-admission-plugins'" 命令获取。
测试或研发环境中 , 使用静态密码文件认证是添加普通用户的快捷途径。API Server的静态密码认证文件遵循CSV格式 , 每一行存储一 个用户账户信息 , 格式为 password,user,uid,"group1,group2,group3" , 用户组一段可以省略 , 而多个用户组也以逗号分隔 , 但需要使用双引号将所有用户组进 行整体引用。例如 , 我们可以在Master节点上 的/etc/kubernetes/authfiles目录中创建拥有类似如下内容的文件 passwd.csv , 它提供了ilinux和ik8s两个用户 , 二者都属于kubeusers 用户组。
ilinux@MageEdu,ilinux,1009,"kubeusers,defaultadmin"
ik8s@MageEdu,ik8s,1010,"kubeusers,defaultadmin"API Server的--basic-auth-file选项要通过本地路径加载该文件以启用静态密码文件认证方式 , 这里采用hostPath存储卷的方式将宿主机目录/etc/kubernetes/authfiles/关联到静态Pod资源kube-apiserver的相同目录下。修改Master节点上的 /etc/kubernetes/manifests/kube-apiserver.yaml文件 , 添加 hostPath存储卷及卷挂载配置 , 需要改动的配置部分如下。
containers: - command: # 在command中为kube-apiserver添加--basic-auth-file选项
- kube-apiserver
- --basic-auth-file=/etc/kubernetes/authfiles/passwd.csv
……
volumeMounts: # 为容器指定额外多挂载的hostPath存储卷 , 存储有静态密码文件
……
- mountPath: /etc/kubernetes/authfiles
name: static-auth-files
readOnly: true
volumes:
……
- hostPath: # 将宿主机上存储有静态密码文件的目录作为hostPath存储卷
path: /etc/kubernetes/authfiles
type: DirectoryOrCreate
name: static-auth-fileskubelet的调谐循环会监视节点上指定的用于加载静态Pod配置清单的目录 , 默认为/etc/kubernetes/manifests。该目录中的任何清单发生变动时 , 都会由kubelet自动做出相应的处理 , 例如重新创建相关的Pod资源 , 因而配置清单修改完成后很快就会由kubelet重新创建 kube-apiserver相关的静态Pod对象 , 从而启动静态密码文件认证机 制。
注意 : 安全起见 ,
passwd.csv文件仅应该让必要用户拥有访问权限 , 并 拒绝其他一切用户的任何操作 , 即将该文件属主和属组设置为root, 并设定其权限模型为0400。
API Server默认监听TCP的6443端口 , 未指定用户身份并以curl命令向Master节点的该端口上HTTPS服务的根路径发起请求测试 , 会被识 别为匿名用户 , 但会出现该用户不具有访问相应资源权限的错误信 息。
~$ curl -s -k https://k8s-master01.ilinux.io:6443/ | grep message
"message": "forbidden: User \"system:anonymous\" cannot get path \"/\"",但以静态密码文件passwd.csv中的某一个用户名及其密码发起访 问请求 , 则会由API Server标识出成功认证并识别的用户名 , 例如下 面的测试命令及响应结果所示 , 但该用户同样因不具有指定资源的访 问权限而被拒绝。
~$ curl -s -u ilinux:ilinux@MageEdu -k https://172.29.9.1:6443/ | grep message
"message": "forbidden: User \"ilinux\" cannot get path \"/\"",提示 : 事实上 , 使用来自
HTTP/HTTPS客户端的基本身份验证时 ,API Server需要客户端提供一个特定标头Authorization, 它的值采用Basic BASE64ENCODED (USER:PASSWORD)格式。
为了让ilinux用户获取资源操作权限 , 我们可以将API Server的--authorization-mode的选项值直接修改为AlwaysAllow , 但这样也会将所有权限开放给任意用户 , 包括匿名用户 , 这将为系统引入无法预 料的风险。因而 , 我们接下来基于RBAC授权插件 , 将默认的集群角色 ( ClusterRole ) admin通过角色绑定 ( RoleBinding ) 机制 , 为ilinux 用户授权default名称空间的管理权限来测试认证用户的授权功能。
~$ kubectl create rolebinding default-ns-admin --clusterrole=admin --user=ilinux -n default
rolebinding.rbac.authorization.k8s.io/default-ns-admin created此时 , ilinux用户拥有default名称空间及其内部资源对象的管理 权限 , 我们可以通过该用户尝试访问该名称空间 , 以及该名称空间下 的所有资源、指定类型的资源或指定的资源对象 , 例如下面的命令获取到了default名称空间的资源规范。
~$ curl -s -u ilinux:ilinux@MageEdu -k
https://172.29.9.1:6443/api/v1/namespaces/default/
{
"kind": "Namespace",
"apiVersion": "v1",
"metadata": {
"name": "default",
……
}但同样能够认证通过的ik8s用户因未被授权 , 便不具有访问 default名称空间的权限 , 如下面的测试命令及结果所示。
~$ curl -s -u ik8s:ik8s@MageEdu -k https://k8s-master01.ilinux.io:6443/api/v1/namespaces/default/
……
"status": "Failure",
"message": "namespaces \"default\" is forbidden: User \"ik8s\"
cannot get resource \"namespaces\" in API group \"\" in the
namespace \"default\"",
……另外 , 静态令牌文件同样是CSV格式的文件 , 它与静态密码文件的不同之处仅在于第一个字段提供的是令牌而非密码字符串 , 该令牌采用[a-z0-9]{6}.[a-z0-9]{16}的格式 , 第一部分代表令牌ID , 第二部 分则是令牌密钥。客户端认证时 , 直接在HTTP中以Authorization: Bearer <token>标头完成认证。我们可以在前面静态密码文件认证配 置的基础上完成静态令牌文件认证的测试。
步骤1: 执行类似如下命令 , 在/etc/kubernetes/authfiles目录下创建token.csv文件 , 生成相关的用户配置。
~$ sudo echo "$(openssl rand -hex 3).$(openssl rand -hex
8),ilinux,1009,\"kubeusers,defaultadmin\"" \
>> /etc/kubernetes/authfiles/token.csv
~$ sudo echo "$(openssl rand -hex 3).$(openssl rand -hex
8),ik8s,1010,\"kubeusers,defaultadmin\"" \
>> /etc/kubernetes/authfiles/token.csv
~$ sudo chmod 400 /etc/kubernetes/authfiles/token.csv步骤2: 编辑/etc/kubernetes/manifests/kube-apiserver.yaml 配置清单 , 为kube-apiserver添加配置选项--token-auth file=/etc/kubernetes/authfiles/token.csv。
步骤3: 以ilinux用户承载令牌认证的方式向API Server发起资源 操作请求进行访问测试 , 由下面的命令及结果可知 , 认证和授权均得 以成功完成。
~$ TOKEN=$(sudo awk -F "," '$2=="ilinux"{print $1}' /etc/kubernetes/authfiles/token.csv)
~$ curl -s -H "Authorization: Bearer $TOKEN" -k \
https://172.29.9.1:6443/api/v1/namespaces/default/
{
"kind": "Namespace",
"apiVersion": "v1",
"metadata": {
"name": "default",
……
}由此可见 , 静态密码文件认证及静态令牌文件认证配置简单 , 很适合学习和试用Kubernetes的访问控制功能 , 但它们包含明文密码或令牌信息 , 且改动配置需要重启API Server , 添加用户代价较大 , 不 安全且不灵活 , 因此不建议用于生产环境。
2. ServiceAccount及认证
Kubernetes原生的应用程序意味着专为运行于Kubernetes系统之 上而开发的应用程序 , 这些程序托管运行在Kubernetes之上 , 能够直 接与API Server进行交互(见下图) , 并进行资源状态的查询或更新 , 例如Flannel和CoreDNS等。显然 , API Server同样需要对这类来 自Pod资源中的客户端程序进行身份验证 , 服务账户也是专用于这类场 景的账号。ServiceAccount资源一般由用户身份信息及保存了认证信息的Secret对象组成。

2.1 ServiceAccount自动化
当创建Pod时 , 如果没有指定服务账号 , Pod会被指定给命名空间中default服务账号。可以通过查看Pod的原始JSON或YAML ( 例如 : kubectl get pods/podname -o yaml ) , 可以看到spec.serviceAccountName字段已经被自动设置了。且会挂载到容器的/var/run/secrets/kubernetes.io/serviceaccount目录中 , 如下信息取自某Pod对象的信息
# kubectl get pods deployment-demo-cb69c57b7-7wl9k -o yaml
spec:
......
serviceAccount: default
serviceAccountName: default
# kubectl describe pods deployment-demo-cb69c57b7-7wl9k
......
Containers:
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-bbbhw (ro)
......
Volumes:
kube-api-access-bbbhw:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true各容器的该挂载点目录中通常存在3个文件 : ca.crt、namespace和token , 其中 , token文件保存了ServiceAccount的认证令牌 , 容器中的进程使用该账户认证到API Server , 进而由认证插件完成用户认证并将其用户名传递给授权插件。
# kubectl exec deployment-demo-cb69c57b7-7wl9k -- ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt
namespace
token每个Pod对象只有一个服务账户 , 若创建Pod资源时未予明确指定 , 则ServiceAccount准入控制器会为其自动附加当前名称空间中默认的服务账户 , 其名称通常为default。下面的命令显示了default这个服务账户的详细信息。
# kubectl describe serviceaccount default -n default
Name: default
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: default-token-xcj5s
Tokens: default-token-xcj5s
Events: <none>Kubernetes系统通过3个独立的组件间相互协作实现了上面描述的Pod对象服务账户的自动化过程 : ServiceAccount准入控制器、令牌控制器和ServiceAccount控制器。ServiceAccount控制器负责为名称空间管理相应的资源对象 , 它需要确保每个名称空间中都存在一个名为default的服务账户对象。ServiceAccount准入控制器内置在API Server中 , 负责在创建或更新Pod时按需进行ServiceAccount资源对象相关信息的修改 , 这包括如下操作。
若
Pod没有显式定义使用的ServiceAccount对象 , 则将其设置为default。若
Pod显式引用了ServiceAccount, 则负责检查被引用的对象是否存在 , 不存在时将拒绝Pod资源的创建请求。若
Pod中不包含ImagePullSecerts, 则把ServiceAccount的ImagePullSecrets附加其上。若服务账号的
automountServiceAccountToken或Pod的automountServiceAccountToken都未显式设置为false, 则为Pod创建一个volume, 在其中包含用来访问API的令牌。为
Pod对象中的每个容器添加一个volumeMounts, 将ServiceAccount的存储卷挂载至/var/run/secrets/kubernetes.io/serviceaccount。
令牌控制器是控制平面组件Controller Manager中的一个专用控制器 , 它工作于异步模式 , 负责完成如下任务 :
监控
ServiceAccount的创建操作 , 并为其添加用于访 问API的Secret对象 ;监控
ServiceAccount的删除操作 , 并删除其相关的所有ServiceAccount令牌密钥 ;监控
Secret对象的添加操作 , 确保其引用的ServiceAccount存在 , 并在必要时为Secret对象添加认证令牌 ;监控
Secret对象的删除操作 , 以确保删除每个ServiceAccount对此Secret的引用。
需要注意的是 , 为确保完整性等 , 必须为kube-controller-manager使用--service-account-private-key-file选项指定一个私钥文件 , 用于对生成的ServiceAccount令牌进行签名 , 该私钥文件必须是PEM格式。同时 , 要使用--service-account-key-file为kube-apiserver指定与前面的私钥配对的公钥文件 , 实现在认证期间对认证令牌进行校验。
放弃 API 凭据的自动挂载
如果你不希望 kubelet 自动挂载某 ServiceAccount 的 API 访问凭据 , 你可以选择不采用这一默认行为。 通过在 ServiceAccount 对象上设置 automountServiceAccountToken: false , 可以放弃在 /var/run/secrets/kubernetes.io/serviceaccount/token 处自动挂载该服务账号的 API 凭据。
例如 :
apiVersion: v1
kind: ServiceAccount
metadata:
name: build-robot
automountServiceAccountToken: false
...你也可以选择不给特定 Pod 自动挂载 API 凭据 :
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
serviceAccountName: build-robot
automountServiceAccountToken: false
...如果 ServiceAccount 和 Pod 的 .spec 都设置了 automountServiceAccountToken 值 , 则 Pod 上 spec 的设置优先于服务账号的设置。
2.2 ServiceAccount基础应用
ServiceAccount是Kubernetes API上的一种资源类型 , 它属于名称空间级别 , 用于让Pod对象内部的应用程序在与API Server通信时完成身份认证。如前所述 , 同样名为ServiceAccount的准入控制器实现了服务账户自动化 , 该准入控制器为每个名称空间都自动生成了一个名为default的默认资源对象。
每个Pod对象可附加其所属名称空间中的一个ServiceAccount资源 , 且只能附加一个。不过 , 一个ServiceAccount资源可由其所属名称空间中的多个Pod对象共享使用。创建Pod资源时 , 用户可使用 spec.serviceAccountName属性直接指定要使用的ServiceAccount对象 , 或者省略此字段而由准入控制器自动附加当前名称空间中默认的ServiceAccount , 以确保每个Pod对象至少基于该服务账户有权限读取当前名称空间中其他资源对象的元数据信息。
Kubernetes也支持用户按需创建ServiceAccount资源并将其指定到特定应用的Pod对象之上 , 结合集群启用的授权机制为该 ServiceAccount资源赋予所需要的更多权限 , 从而构建出更加灵活的权限委派模型。
2.2.1 命令式ServiceAccount资源创建
kubectl create serviceaccount命令能够快速创建自定义的ServiceAccount资源 , 我们仅需要在命令后给出目标ServiceAccount资源的名称。
# kubectl create serviceaccount my-service-account
serviceaccount/my-service-account createdKubernetes会为创建的ServiceAccount资源自动生成并附加一个Secret对象 , 该对象以ServiceAccount资源名称为前缀。
# kubectl get serviceaccounts/my-service-account -o jsonpath={.secrets[0].name}
my-service-account-token-kvhmm该Secret对象属于特殊的kubernetes.io/service-account-token类型 , 它包含ca.crt、namespace和token这3个数据项 , 它们分别包含 Kubernetes Root CA证书、Secret对象所属的名称空间和访问API Server的令牌。由Pod对象以存储卷的方式挂载至/var/run/secrets/kubernetes.io/serviceaccount目录后 , 这3个数据项映射为同名的3个文件。
# kubectl get secrets/my-service-account-token-kvhmm -o yaml
apiVersion: v1
data:
ca.crt: LS0tLS1CRUdJTiBDR......
namespace: ZGVmYXVsdA==
token: ZXlKaGJHY2lPaUpTVX......
kind: Secret
metadata:
annotations:
kubernetes.io/service-account.name: my-service-account
kubernetes.io/service-account.uid: cb666c0c-6e32-4361-b5c9-f4e41111e771
......
type: kubernetes.io/service-account-tokenkubernetes v1.24.0 更新之后进行创建 ServiceAccount 不会自动生成 Secret 需要对其手动创建
root@k8s-master01:/opt/k8s-data/yaml/deployment# cat<<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
type: kubernetes.io/service-account-token
metadata:
name: my-service-account
annotations:
kubernetes.io/service-account.name: "my-service-account"
EOF2.2.2 ServiceAccount资源清单
以资源规范形式创建Secret对象时 , 以类似如上命令结果的形式 , 为Secret对象使用资源注解kubernetes.io/serviceaccount.name引用一个现存的ServiceAccount对象 , 并指定资源类型为特定的kubernetes.io/service-account-token , 便可以将指定的ServiceAccount对象引用的Secret对象予以置换 , 该Secret对象同样会自动生成固定的3个数据项。
更完善地创建ServiceAccount资源的方式是使用资源规范 , 该规范比较简单 , 它没有spec字段 , 而是将几个关键定义直接通过一级字段给出 , 具体的规范格式如下所示。
apiVersion: v1 # ServiceAccount所属的API群组及版本
kind: ServiceAccount # 资源类型标识
metadata:
name <string> # 资源名称
namespace <string> # ServiceAccount是名称空间级别的资源
automountServiceAccountToken <boolean> # 是否让Pod自动挂载API令牌
secrets <[]Object> # 以该SA运行的Pod所要使用的Secret对象组成的列表
apiVersion <string> # 引用的Secret对象所属的API群组及版本 , 可省略
kind <string> # 引用的资源的类型 , 这里是指Secret , 可省略
name <string> # 引用的Secret对象的名称 , 通常仅给出该字段即可
namespace <string> # 引用的Secret对象所属的名称空间
uid <string> # 引用的Secret对象的标识符 ;
imagePullSecrets <[]Object> # 引用的用于下载Pod中容器镜像的Secret对象列表
name <string> # docker-registry类型的Secret资源的名称下面的配置清单是一个ServiceAccount资源示例 , 它位于 serviceaccount-demo.yaml文件中 , 它仅指定了资源名称 , 以及允许 Pod对象将其自动挂载为存储卷 , 引用的Secret对象则交由系统自动生 成。
编写资源清单
serviceaccount-demo.yamlapiVersion: v1 kind: ServiceAccount metadata: name: namespace-admin namespace: default automountServiceAccountToken: true创建并查看该资源信息 ,
ServiceAccount控制器会自动为其附加以资源名称为前缀的Secret对象。# kubectl apply -f serviceaccount-demo.yaml serviceaccount/namespace-admin created # kubectl get serviceaccount/namespace-admin NAME SECRETS AGE namespace-admin 1 46s # kubectl get serviceaccounts/namespace-admin -o jsonpath='{.secrets[0].name}' namespace-admin-token-tvhbc
另外 , ServiceAccount资源还可以基于spec.imagePullSecret字 段附带一个由下载镜像专用的Secret资源组成的列表 , 让Pod对象在创 建容器时且从私有镜像仓库下载镜像文件之前完成身份认证。下面的 示例定义了一个从本地私有镜像仓库Harbor下载镜像文件时的Secret 对象信息的ServiceAccount。
apiVersion: v1
kind: ServiceAccount
metadata:
name: eshop-sa
namespace: eshop
imagePullSecrets:
- name: local-harbor-secret其中 , local-harbor-secret是docker-registry类型的Secret对象 , 包含目标Docker Registry的服务入口、用户名、密码及用户的电 子邮件等信息 , 它必须要由用户提前手动创建。该Pod资源所在节点上的kubelet进程使用Secret对象中的令牌认证到目标Docker Registry , 以下载运行容器所需要的镜像文件。
2.3 Pod资源上的服务账户
借助权限分配模型 , 按需应用 " 最小权限法则" 将不同的资源操作权限配置给不同的账户 , 是有效降低安全风险的法则之一。有相当一部分Kubernetes原生应用程序依赖的权限都会大于从Pod默认ServiceAccount继承到的权限 , 且彼此间各有不同 , 为这类应用定制一个专用的ServiceAccount并授予所需的全部权限是主流的解决方案。
示例:
这里直接使用上面创建的ServiceAccount资源namespace-admin
编写资源清单文件
pod-with-sa.yamlapiVersion: v1 kind: Pod metadata: name: pod-with-sa namespaces: default spec: containers: - name: adminbox image: vvoo/admin-toolbox:v1.0 imagePullPolicy: IfNotPresent serviceAccountName: namespace-admin创建该
Pod资源 , 并查看其挂载的存储卷 , 可以看到它会自动把ca.crt、namespace、token放在一个存储卷中然后挂载到容器中。# kubectl apply -f pod-with-sa.yaml pod/pod-with-sa created # kubectl get pods pod-with-sa -o yaml ...... spec: containers: - image: vvoo/admin-toolbox:v1.0 ...... volumeMounts: - mountPath: /var/run/secrets/kubernetes.io/serviceaccount name: kube-api-access-rzl8t readOnly: true ...... volumes: - name: kube-api-access-rzl8t projected: defaultMode: 420 sources: - serviceAccountToken: expirationSeconds: 3607 path: token - configMap: items: - key: ca.crt path: ca.crt name: kube-root-ca.crt - downwardAPI: items: - fieldRef: apiVersion: v1 fieldPath: metadata.namespace path: namespace
默认的挂载路径是/var/run/secrets/kubernetes.io/serviceaccount。与API Server 交互时 , 工作负载进程会使用该目录下的ca.crt证书文件验证API Server的服务器证书是否为自己信任的证书颁发机构 ( 所在集群的 kubernetes-ca ) 所签发 ; 验证服务器身份成功通过后 , 工作负载向 API Server请求操作namespace文件指定的名称空间中的资源时 , 会将token文件中的令牌以承载令牌的认证方式提交给API Server进行验证 , 权限校验则由授权插件完成。下面在pods/pod-with-sa的交互式接口中进行访问测试。
切换到
pods/pod-with-sa的adminbox容器的存储卷挂载点为工作目录 , 以便加载所需要的文件。# kubectl exec -it pod-with-sa -- /bin/sh [root@pod-with-sa /]# cd /var/run/secrets/kubernetes.io/serviceaccount/在容器中使用
curl命令向API Server发起访问请求 ,--cacert选项用于指定验证服务器端的CA证书 , 而-H选项用于自定义头部 , 它指定了使用的承载令牌 ; 下面的命令使用了 " 命令引用" 机制来加载token和namespace文件的内容 , 其结果显示容器进程使用指定的ServiceAccount进行身份认证成功。[root@pod-with-sa /run/secrets/kubernetes.io/serviceaccount]# curl --cacert ./ca.crt -H "Authorization: Bearer $(cat ./token)" https://192.168.102.101:6443/api/v1/namespaces/$(cat ./namespace) { "kind": "Status", "apiVersion": "v1", "metadata": {}, "status": "Failure", "message": "namespaces \"default\" is forbidden: User \"system:serviceaccount:default:namespace-admin\" cannot get resource \"namespaces\" in API group \"\" in the namespace \"default\"", "reason": "Forbidden", "details": { "name": "default", "kind": "namespaces" }, "code": 403 }
接下来 , 单独向serviceaccount/namespace-admin授予default名称空间的管理权限 , pods/pod-with-sa中的进程便能借助该 ServiceAccount的身份管理相应名称空间下的资源。
切换至
kubectl管理终端运行如下资源创建命令# kubectl create rolebinding namespace-admin-binding-admin --clusterrole=admin --serviceaccount=default:namespace-admin -n default rolebinding.rbac.authorization.k8s.io/namespace-admin-binding-admin created回到
pods/pod-with-sa的adminbox容器中再次运行访问测试命令即可验证授权结果 , 如下面的命令表示namespace-admin用户已然有权限访问default名称空间。事实上 , 它拥有该名称空间中所有资源的CRUD权限。[root@pod-with-sa /run/secrets/kubernetes.io/serviceaccount]# curl --cacert ./ca.crt -H "Authorization: Bearer $(cat ./token)" https://192.168.102.101:6443/api/v1/namespaces/$(cat ./namespace)/ { "kind": "Namespace", "apiVersion": "v1", "metadata": { "name": "default", ...... "status": { "phase": "Active" } }
但是 , default名称空间引用了serviceaccounts/default资源中Pod的容器进程却不具有如上权限 , 因为它们并未获得相应的授权。事实上 , kube-system名称空间中的许多应用都使用了专用的 ServiceAccount资源 , 例如Flannel、CoreDNS、kube-proxy以及多种控制器等 , 可以通过命令了解相应的ServiceAccount资源信息。
3. X509数字证书认证
X509数字证书认证常用的方式有 " 单向认证" 和 " 双向认证" 。 SSL / TLS最常见的应用场景是将X.509数字证书与服务器端关联 , 但客户端不使用证书。单向认证是客户端能够验证服务端的身份 , 但服 务端无法验证客户端的身份 , 至少不能通过SSL/TLS协议进行。之所以如此 , 是因为SSL / TLS安全性最初是为互联网应用开发 , 保护客户端是高优先级的需求 , 它可以让客户端确保目标服务器不会被冒名顶 替 ,

基于其他机制 ( 如HTTP基本认证 ) 验证客户端身份可能更容易些 , 而且这些机制没有生成和分发X.509数字证书的高昂开销。但安全性要求较高的场景中 , 使用组织私有的证书分发系统也一样能够借助数字证书完成客户端认证。图展示了服务端与客户端的双向认证机 制。

双向认证的场景中 , 服务端与客户端需各自配备一套数字证书 , 并拥有信任的签证机构的证书列表。使用私有签证机构颁发的数字证书时 , 除了证书管理和分发 , 通常还要依赖用户手动将此私有签证机 构的证书添加到信任的签证机构列表中。X509数字证书认证是 Kubernetes默认使用的认证机制 , 采用双向认证模式。
3.1 Kubernetes的X509数字证书认证体系
构建安全基础通信环境的Kubernetes集群时 , 需要用到PKI基础设施以完成独立HTTPS安全通信及X509数字证书认证的场景有多种 , 如下图所示。API Server是整个Kubernetes集群的通信网关 , controller-manager、scheduler、kubelet及kube-proxy等API Server的客户端均需要经由API Server与etcd通信 , 完成资源状态信息获取及更新等。同样出于安全通信的目的 , Master的各组件 ( API Server、controller-manager和scheduler ) 需要基于SSL/TLS向外提供服务 , 而且与集群内部组件间通信时 ( 主要是各节点上的kubelet和 kube-proxy ) 还需要进行双向身份验证。

Kubernetes集群中存在3个需要独立完成X509数字证书认证和HTTPS通信的体系 : 一是etcd集群成员、服务器及其客户端 ; 二是API Server及其客户端 , 以及kubelet API及其客户端 ; 三是Kubernetes认证代理体系中的服务器和客户端。这3个独立的体系各自需要一个独立证书颁发机构为体系内的服务器和客户端颁发证书 , 完成体系内的组件身份认证同时又彼此隔离。
etcd集群CA及相关的数字证书Kubernetes的API Server将集群的状态数据存储到集群存储服务etcd中 , 包括含有敏感数据的Secret资源对象。出于提升服务可用性、数据冗余及安全性等目的 , 生产环境通常应该配置有3、5或7个节点的etcd集群 , 集群内各节点间基于HTTPS协议进行通信 , 它们使用Peer类型的数字证书进行通信时的身份认证。而且 , 各etcd节点提供Server类型的数字证书与客户端建立安全连接 , 并验证其客户端Client类型的数字证书。Kubernetes集群各组件中 ,kube-apiserver是唯一一个可直接与集群存储通信的组件 , 它是etcd服务的客户端。Kubernetes集群CA及相关的数字证书Kubernetes集群的其他各组件均需要通过kube-apiserver访问集群资源 , 同样出于安全性等目的 ,API Server也要借助HTTPS协议与其客户端通信 , 而X509双向数字证书认证仅是API Server支持的认证方式中的一种 , 客户端也可能会使用HTTP Basic或Bearer Token认证方式接入到API Server。另外 ,
kubelet也通过HTTPS端点暴露了一组API, 这些API提供了多个不同级别的敏感数据接口 , 并支持来自客户端的请求在节点和容器上执行不同级别的操作。默认情况下 , 匿名请求将自动隶属于system:unauthenticated用户组 , 其用户名为system:anonymous。不过 ,kubelet可使用--anonymous-auth=false选项拒绝匿名访问 , 并通过--client-ca-file选项指定CA方式验证客户端身份。kubelet可直接使用kubernetes-ca, 同时应该为kube-apiserver使用--kubeletclient-certificate和--kubelet-client-key选项指定认证到kubelet的客户端证书与私钥。认证代理服务体系
CA及相关的数字证书API Server支持将认证功能交由外部的其他认证服务代为完成 , 这些服务通过特定的响应头部返回身份验证的结果状态 ,API Server扩展服务就是认证代理的最常见应用场景之一。
除了API Server提供的核心API , Kubernetes还支持通过聚合层 ( aggregation layer ) 对其进行扩展。简单来说 , 聚合层允许管理员在群集中部署使用其他Kubernetes风格的API , 例如service catalog 或用户自定义的API Server等。聚合层本身打包在kube-apiserver程序中 , 并作为进程的一部分运行 , 但仅在管理员通过指定的 APIService对象注册扩展资源之后 , 它才会代理转发相应的请求。而 APIService则会由运行在Kubernetes集群上的Pod中的extention-apiserver实现。
创建一个APIService资源时 , 作为注册和发现过程的一部分 , kube-aggregator控制器 ( 位于kube-apiserver内部 ) 将与extention-apiserver的HTTP2连接 , 而后将经过身份验证的用户请求经由此连接代理到extention-apiserver上 , 于是 , kube-aggregator被设置为执行RequestHeader客户端认证。
不过 , 只有kube-apiserver在启动时使用了如下选项时 , 才能启用其内置的聚合层 :
--requestheader-client-ca-file=<path to aggregator CA cert>--requestheader-allowed-names=front-proxy-client--requestheader-extra-headers-prefix=X-Remote-Extra---requestheader-group-headers=X-Remote-Group--requestheader-username-headers=X-Remote-User--proxy-client-cert-file=<path to aggregator proxy cert>--proxy-client-key-file=<path to aggregator proxy key>
proxy-client-cert-file和proxy-client-key-file包含kube-aggregator执行客户端证书身份验证的证书/密钥对 , 它使用 requestheader-client-ca-file中指定的CA文件对聚合器证书进行签名。requestheader-allowed-names包含允许充当伪装前端代理的身份/名称列表 ( 客户端证书中使用的CN ) , 而requestheader-username-headers、requestheader-group-headers和requestheader-extraheaders-prefix携带一个HTTP头的列表 , 用于携带远程用户信息。
完整运行的Kubernetes系统需要为etcd、API Server及前端代理 ( front proxy ) 生成多个数字证书。
另外 , 其他集群上运行的应用 ( Pod ) 同其客户端的通信经由不可信的网络传输时也可能需要用到TLS/SSL协议 , 例如Nginx Pod与其客户端间的通信 , 客户端来自于互联网时 , 此处通常需配置一个公信的服务端证书。
显然 , 普通用户使用这种认证方式的前提是 , 它们各自拥有自己的数字证书 , 证书中的CN和O属性分别提供了准确的用户标识和用户组。API Server可接受或拒绝这些证书 , 评估标准在于证书是否由API Server信任的客户端证书CA ( 由选项--client-ca-file指定 , 默认为kubernetes-ca ) 所签发 , 但API Server自身并不了解这些证书 , 因此也不了解各个用户 , 它仅知道负责为各个客户端颁发证书的CA。因此 , 相较于静态密码文件认证和静态令牌文件认证来说 , X509数字证书认证实现了用户管理与Kubernetes集群的分离 , 且有着更好的安全性。
X509数字证书认证因其可不依赖第三方服务、有着更好的安全性以及与API Server相分离等优势 , 成为Kubernetes系统内部默认使用的认证方式。但是 , 将X509数字证书用于普通用户认证的缺陷也是显而易见的 , 它主要表现在如下两个方面。
证书的到期时间在颁发时设定 , 其生命周期往往很长 ( 数月甚至数年 ) , 且事实上的身份验证功能也是在颁发时完成 , 若撤销用户的可用身份只能使用证书吊销功能完成。
现实使用中 , 证书通常由一些通用的签证机构签发 , 而
API Server需要信任该CA; 显然 , 获得该CA使用权限的用户便能够授予自己可认证到的Kubernetes的任意凭据或身份 , 因而集群管理员必须自行集中管理证书 , 这任务往往并不轻松。
对于大型组织来说 , Kubernetes系统用户量大且变动频繁 , 静态密码文件和静态令牌文件认证方式动辄需要重启API Server , 而X509 认证中的证书维护开销较高且无法灵活变动凭据生效期限 , 因此这些认证方式都非理想选择。实践中 , 人们通常使用ID Token进行 Kubernetes的普通用户身份认证 , API Server的OpenID Connect令牌认证插件即用于该场景。
3.2. X509数字证书身份凭证
kubeadm部署Kubernetes集群的过程中会自动生成多个kubeconfig文件 , 它们是默认位于/etc/kubernetes目录下以.conf为后缀名的文件 , 前缀名称代表了它的适用场景 , 其中的admin.conf中保存了以X509数字证书格式提供身份凭据的kubernetes-admin用户 , 该用户能够以管理员的身份对当前集群发起资源操作请求。
由kubeadm初始化的Kubernetes集群上 , kube-apiserver默认信任的CA就是集群自己的kubernetes-ca , 该CA的数字证书是Master节点之上的/etc/kubernetes/pki/ca.crt文件。于是 , 客户端按需生成证书签署请求 , 再由管理员通过kubernetes-ca为客户端签署证书 , 便可让客户端以其证书中的CN为用户名认证到API Server上。
示例:
下面将客户端生成私钥和证书签署请求 , 服务器签署该请求 , 以及客户端将证书配置为kubeconfig文件的步骤统一进行说明 , 所有操作都在Master节点上运行。
步骤1:
以客户端的身份 , 生成目标用户账号
user1的私钥及证书签署请求 , 保存在用户主目录下的.certs目录中。生成秘钥文件 , 其权限设置为
600以阻止其它用户读取。# mkdir $HOME/.certs # (umask 077; openssl genrsa -out $HOME/.certs/user1.key 2048)创建证书签署请求 ,
-subj选项中CN的值将被API Server识别为用户名 ,O的值将被识别为用户组。# openssl req -new -key $HOME/.certs/user1.key -out $HOME/.certs/user1.csr -subj "/CN=user1/O=developers"
步骤2:
以
kubernetes-ca的身份签署user1的证书请求 , 这里直接读取相关的CSR文件 , 并将签署后的证书仍然保存在当前系统用户目录下的.certs中。基于
kubernetes-ca签署证书 , 并为其设置合理的生效时长 , 例如这里设置的为10年。# openssl x509 -req -days 36550 -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -in $HOME/.certs/user1.csr -out $HOME/.certs/user1.crt Signature ok subject=/CN=user1/O=developers Getting CA Private Key #如果是二进制安装, 默认生成的是ca.pem文件,用以下命令 root@harbor:/etc/kubeasz/clusters/k8s-cluster/ssl# openssl x509 -req -days 36550 -CAform PEM -CA ./ca.pem -CAkeyform PEM -CAkey ./ca-key.pem -CAcreateserial -in ./user1.csr -out ./user1.crt Certificate request self-signature ok subject=CN = user1, O = developers可以验证生成的数字证书的相关信息。
# openssl x509 -in $HOME/.certs/user1.crt -text -noout Certificate: Data: Version: 1 (0x0) Serial Number: b9:d6:c2:1c:f4:b8:72:2f Signature Algorithm: sha256WithRSAEncryption Issuer: CN=kubernetes Validity Not Before: Jun 16 17:48:36 2022 GMT Not After : Jul 12 17:48:36 2122 GMT Subject: CN=user1, O=developers Subject Public Key Info: Public Key Algorithm: rsaEncryption Public-Key: (2048 bit) ......
步骤3:
这里使用
kubectl config set-cluster新设置一个集群 , 只不过还是访问这个kubernetes集群 , 通过上面设置的user1用户去认证 , 并生成一个新的配置文件。新创建一个集群 , 并将配置文件保存在
/tmp/mykubeconfig文件中。# kubectl config set-cluster kubernetes --server=https://192.168.102.101:6443 --embed-certs --certificate-authority=/etc/kubernetes/pki/ca.crt --kubeconfig=/tmp/mykubeconfig Cluster "kubernetes" set. #二进制安装用以下命令 root@harbor:/etc/kubeasz/clusters/k8s-cluster/ssl# kubectl config set-cluster kubernetes --server=https://192.168.71.29:6443 --embed-certs --certificate-authority=./ca.pem --kubeconfig=/tmp/mykubeconfig Cluster "kubernetes" set.根据上面生成的
X509证书及私钥创建身份凭证 , 列表项名称同目标用户名。其中--embed-certs表示将证书隐藏起来 , 通过kubectl config view查看配置文件时证书文件处于隐藏状态。# kubectl config set-credentials user1 --client-certificate=$HOME/.certs/user1.crt --client-key=$HOME/.certs/user1.key --embed-certs=true --kubeconfig=/tmp/mykubeconfig User "user1" set. // 查看生成的配置文件 # kubectl config view --kubeconfig=/tmp/mykubeconfig apiVersion: v1 clusters: - cluster: certificate-authority-data: DATA+OMITTED server: https://192.168.102.101:6443 name: kubernetes contexts: null current-context: "" kind: Config preferences: {} users: - name: user1 user: client-certificate-data: REDACTED client-key-data: REDACTED配置
context, 将user1的身份凭证与定义的kubernetes集群建立关系 , 该context的名称为user1@kubernetes。# kubectl config set-context 'user1@kubernetes' --user=user1 --cluster=kubernetes --kubeconfig=/tmp/mykubeconfig Context "user1@kubernetes" created. # kubectl config view --kubeconfig=/tmp/mykubeconfig apiVersion: v1 clusters: - cluster: certificate-authority-data: DATA+OMITTED server: https://192.168.102.101:6443 name: kubernetes contexts: - context: cluster: kubernetes user: user1 name: user1@kubernetes current-context: "" kind: Config preferences: {} users: - name: user1 user: client-certificate-data: REDACTED client-key-data: REDACTED将当前上下文切换为
user1@kubernetes, 或直接在kubectl命令上使用--context='user1@kubernetes'以完成该用户的认证测试。# kubectl config use-context user1@kubernetes --kubeconfig=/tmp/mykubeconfig Switched to context "user1@kubernetes".使用
/tmp/mykubeconfig配置文件 , 验证user1用户是否能被API Server正确识别。# kubectl get namespaces/default --kubeconfig=/tmp/mykubeconfig Error from server (Forbidden): namespaces "default" is forbidden: User "user1" cannot get resource "namespaces" in API group "" in the namespace "default"
通过上面的访问测试结果 , 可以看出 , user1用户已经可以通过认证了 , 由于还没有进行授权 , 所以这里返回的结果是forbidden。
上面配置示例中 , 是将新的集群 , 以及用户信息都保存在了一个新的配置文件中。而也可以直接保存在默认配置文件$HOME/.kube/config中 , 且当我们配置了多个集群配置文件等 , 也可以将多个文件信息合并等。
3.3 TLS Bootstrapping机制
TLS Bootstrapping机制有什么用途呢?新的工作节点接入 Kubernetes集群时需要事先配置好相关的证书和私钥等以进行安全通信 , 管理员可以手动管理这些证书 , 也可以选择由kubelet自行生成私钥和自签证书。集群略具规模后 , 第一种方式无疑会为管理员带来不 小的负担 , 但对于保障集群安全运行却又必不可少。第二种方式降低 了管理员的工作量 , 却也损失了PKI本身具有的诸多优势。取而代之 , Kubernetes采用了由kubelet自行生成私钥和证书签署请求 , 而后发送 给集群上的证书签署进程 ( CA ) , 由管理员审核后予以签署或直接由 控制器进程自动统一签署。这种方式就是kubelet TLS Bootstrapping 机制 , 它实现了前述第一种方式的功能 , 却基本不增加管理员工作 量。
然而 , 一旦开启TLS Bootstrapping功能 , 任何kubelet进程都可以向API Server发起验证请求并加入到集群中 , 包括那些非计划或非 授权主机 , 这必将增大管理验证操作时的审核工作量。为此 , API Server设计了可经由--enable-bootstrap-token-auth选项启用的Bootstrap Token ( 引导令牌 ) 认证插件。该插件用于加强TLS Bootstrapping机制 , 仅那些通过Bootstrap Token认证的请求才可以 使用TLS Bootstrapping发送证书签署请求给控制平面 , 并由相应的审 批控制器 ( approval controller ) 完成证书签署和分发。
提示 :
kubeadm启用了节点加入集群时的证书自动签署功能 , 因此加入 过程在kubeadm join命令成功后即完成。
Kubelet会把签署后的证书及配对的私钥存储到--cert-dir选项指 定的目录下 , 并以之生成kubeconfig格式的配置文件 , 该文件的保存 路径以--kubeconfig选项指定 , 它保存有API Server的地址以及认证 凭据。若指定的kubeocnfig配置文件不存在 , kubelet会转而使用 Bootstrap Token , 从API Server自动请求完成TLS Bootstrapping过 程。
kube-controller-manager内部有一个用于证书颁发的控制循环 , 它采用了类似于cfssl签证器格式的自动签证器 , 颁发的所有证书默认 具有一年有效期限。正常使用中的Kubernetes集群需要在证书过期之 前完成更新 , 以免集群服务不可用。较新版本的kubeadm部署工具已经能够自动完成更新 , 如下第一条命令用于检测证书有效期限 , 在接近 过期时间的情况下 , 即可使用第二条命令进行更新。
~# kubeadm alpha certs check-expiration
~# kubeadm alpha certs renew all Kubernetes 1.8之后的版本中使用的csrapproving审批控制器内 置于kube-controller-manager , 并且默认为启用状态。此审批控制器 使用SubjectAccessview API确认给定的用户是否有权限请求CSR ( 证书签署请求 ) , 而后根据授权结果判定是否予以签署。不过 , 为了避 免同其他审批器冲突 , 内置的审批器并不显式拒绝CSR , 而只是忽略它 们。
4. kubeconfig配置文件
基于无状态协议HTTP/HTTPS的API Server需要验证每次连接请求中的用户身份 , 因而kube-controller-manager、kube-scheduler和kube-proxy等各类客户端组件必须能自动完成身份认证信息的提交 , 但通过程序选项来提供这些信息会导致敏感信息泄露。另外 , 管理员还面临着使用kubectl工具分别接入不同集群时的认证及认证信息映射难题。为此 , Kubernetes设计了一种称为 kubeconfig的配置文件 , 它保存有接入一到多个Kubernetes集群的相关配置信息 , 并允许管理员按需在各配置间灵活切换 , 如下图所示。

客户端程序可通过默认路径、--kubeconfig选项或者KUBECONFIG环境变量自定义要加载的kubeconfig文件 , 从而能够在每次的访问请求中可认证到目标API Server。
4.1 kubeconfig文件格式
kubeconfig文件中 , 各集群的接入端点以列表形式定义在clusters配置段中 , 每个列表项代表一个Kubernetes集群 , 并拥有名称标识 ; 各身份认证信息 ( credentials ) 定义在users配置段中 , 每个列表项代表一个能够认证到某Kubernetes集群的凭据。将身份凭据与集群分开定义以便复用 , 具体使用时还要以context ( 上下文 ) 在二者之间按需建立映射关系 , 各context以列表形式定义在contexts配置段中 , 而当前使用的映射关系则定义在current-context配置段中。kubeconfig文件的格式如下图所示。

使用kubeadm初始化Kubernetes集群过程中 , 在Master节点上生成 的/etc/kubernetes/admin.conf文件就是一个kubeconfig格式的文件 , 它由 kubeadm init命令自动生成 , 可由kubectl加载后接入当前集群的API Server。kubectl加载kubeconfig文件的默认路径为$HOME/.kube/config , 在 kubeadm init命令初始化集群过程中有一个步骤便是将/etc/kubernetes/admin.conf复制为该默认搜索路径上的文件。当然 , 我们也可以通过--kubeconfig选项或KUBECONFIG环境变量将其修改为其他路径。
kubectl config view命令能打印kubeconfig文件的内容 , 下面的命令结果显示了默认路径下的文件配置 , 包括集群列表、用户列表、上下文列表以及当前使用的上下文 ( current-context ) 等。
# kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://192.168.102.101:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED也可以在kubeconfig配置文件中按需自定义相关的配置信息 , 以实现使用不同的用户账户接入集群等功能。kubeconfig是一个文本文件 , 尽管可以使用文本处理工具直接编辑它 , 但强烈建议使用kubectl config及其子命令进行该文件的设定 , 以便利用其他自动进行语法检测等额外功能。kubectl config的常用子命令有如下几项。
view: 打印kubeconfig文件内容。set-cluster: 设定新的集群信息 , 以单独的列表项保存于cluster配置段。set-credentials: 设置认证凭据 , 保存为users配置段的一个列表项。set-context: 设置新的上下文信息 , 保存为contexts配置段的一个列表项。use-context: 设定current-context配置段 , 确定当前以哪个用户的身份接入到哪个集群之中。delete-cluster: 删除clusters中指定的列表项。delete-context: 删除contexts中指定的列表项。get-clusters: 获取clusters中定义的集群列表。get-contexts: 获取contexts中定义的上下文列表。
kubectl config命令的相关操作将针对加载的单个kubeconfig文件进行 , 它根据其优先级由高到低 , 依次搜索--kubeconfig选项指定的文件、KUBECONFIG环境变量指定的文件和默认的.HOME/.kube/config文件 , 以其中任何一种方式加载到配置文件后即可终止搜索过程。不过 , kubectl config命令支持同时使用多个kubeconfig文件 , 以及将多个配置文件合并为一个。
4.2 自定义kubeconfig文件
通常 , 一个完整kubeconfig配置的定义至少应该包括集群、身份凭据、上 下文及当前上下文4项 , 但在保存有集群和身份凭据的现有kubeconfig文件基础上添加新的上下文时 , 可能只需要提供身份凭据而复用已有的集群定义 , 具体的操作步骤要按实际情况进行判定。
例如 , 我们下面尝试创建一个新的kubeconfig文件 , 设定它使用此前定义 的基于静态密码文件认证的ilinux用户接入到现有的Kubernetes集群 , 该集群 API Server的网络端点为https://k8s-api.ilinux.io:6443 , 相关的CA证书保存在Master节点上的/etc/kubernetes/pki/ca.crt文件中 , 而配置结果则使用--kubeconfig选项保存在当前用户主目录下的.kube/kube-dev.config文件中。
步骤1 : 添加集群配置 , 包括集群名称、
API Server URL和信任的CA的证 书 ;clusters配置段中的各列表项名称需要唯一。~$ kubectl config set-cluster kube-dev --embed-certs=true \ --certificate-authority=/etc/kubernetes/pki/ca.crt \ --server="https://k8s-api.ilinux.io:6443" \ --kubeconfig=$HOME/.kube/kube-dev.config Cluster "kube-dev" set.步骤2 : 添加身份凭据 , 使用静态密码文件认证的客户端提供用户名和密码即可。
~$ kubectl config set-credentials ilinux \ --username=ilinux --password=ilinux@MageEdu \ --kubeconfig=$HOME/.kube/kube-dev.config User "ilinux" set.步骤3 : 以用户
ilinux的身份凭据与kube-dev集群建立映射关系。~$ kubectl config set-context ilinux@kube-dev \ --cluster=kube-dev --user=ilinux \ --kubeconfig=$HOME/.kube/kube-dev.config Context "ilinux@kube-dev" created.步骤4 : 设置当前上下文为
ilinux@kube-dev。~$ kubectl config use-context ilinux@kube-dev --kubeconfig=$HOME/.kube/kube-dev.config Switched to context "ilinux@kube-dev".步骤5 : 预览
kube-dev.config文件 , 确认其配置信息。~$ kubectl config view --kubeconfig=$HOME/.kube/kube-dev.config步骤6 : 使用该
kubeconfig中的当前上下文进行测试访问 ; 该用户仅被授权了default名称空间的所有权限 , 因而不具有列出集群级别资源的权限 , 但 能查看default名称空间的状态。~$ kubectl get namespaces --kubeconfig=$HOME/.kube/kube-dev.config Error from server (Forbidden): namespaces is forbidden: User "ilinux" cannot list resource "namespaces" in API group "" at the cluster scope ~$ kubectl get namespaces/default --kubeconfig=$HOME/.kube/kube-dev.config NAME STATUS AGE default Active 3d上面的第6步确认了自定义配置中的
ilinux用户有效可用 , 它被API Server借助静态密码文件认证插件完成认证并标识为ilinux用户 , 从而拥有该用户的资源操作权限。为了进一步测试并了解kubeconfig的使用格式 , 下面把基于令牌文件认证的ik8s用户添加进同一个kubeconfig文件中。ik8s用户同ilinux用户位于同一集群上 , 因此 , 我们可省略添加集群的步骤而直接复用它。步骤7 : 添加身份凭据 , 使用静态令牌文件认证的客户端认证时只需要提供静态令牌信息 ;
~$ TOKEN=$(sudo awk -F "," '$2=="ik8s"{print $1}' /etc/kubernetes/authfiles/token.csv) ~$ kubectl config set-credentials ik8s --token="$TOKEN" \ --kubeconfig=$HOME/.kube/kube-dev.config User "ik8s" set.步骤8 : 为用户
ik8s的身份凭据与kube-dev集群建立映射关系。~$ kubectl config set-context ik8s@kube-dev \ --cluster=kube-dev --user=ik8s \ --kubeconfig=$HOME/.kube/kube-dev.config Context "ik8s@kube-dev" created.步骤9 : 将当前上下文切换为
ik8s@kube-dev。~$ kubectl config use-context ik8s@kube-dev --kubeconfig=$HOME/.kube/kube-dev.config Switched to context "ik8s@kube-dev".步骤
10: 预览kube-dev.config文件 , 确认ik8s用户相关的各项配置信息。~$ kubectl config view --kubeconfig=$HOME/.kube/kube-dev.config步骤11 : 依旧使用当前上下文发起集群访问测试 ,
ik8s用户未获得任何授 权 , 但它能够被系统识别为ik8s用户 , 这表示身份认证请求成功返回 ; 我们这次使用kubectl的whoami插件进行测试 :~ $ kubectl whoami --kubeconfig=$HOME/.kube/kube-dev.config ik8s
提示 事实上除了静态令牌 , 客户端认证到
API Server的各种令牌都可以使用 前面的这种方式添加到kubeconfig文件中 , 包括ServiceAccount令牌、OpenID Connnect令牌和Bootstrap令牌等。
当kubectl引用了拥有两个及以上context的kubeconfig文件时 , 可随时通 过kubectl config use-context命令在不同上下文之间切换 , 它们可能使用不 同的身份凭据接入相同的集群或不同的集群之上。如下命令结果表示当前加载 的配置文件中共有两个context , 而拥有星号标识的是当前使用的context , 即 current-context。
~$ kubectl config get-contexts --kubeconfig=$HOME/.kube/kube-dev.config
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* ik8s@kube-dev kube-dev ik8s
ilinux@kube-dev kube-dev ilinux实践中 , API Server支持的X509数字证书认证和OpenID Connect令牌认证 才是客户端使用最多的认证方式 , 我们后面使用一节的篇幅来专门介绍它们的 配置。
4.3 多kubeconfig文件与合并
kubectl config一次仅能使用单个kubeconfig文件。 事实上 , 若将两个文件路径以冒号分隔并赋值给KUBECONFIG环境变量 , 也能够让kubectl config一次加载多个文件信息 , 优先级由高到低为各文件自左而右的次序。下面两条命令的结果显示 , 左侧文件拥有较高的优先级 , 因而其配置的current-context成为默认使用的context。这里使用的第二个文件是在X509数字证书身份凭证创建的/tmp/mykubeconfig文件。
# export KUBECONFIG="$HOME/.kube/config:/tmp/mykubeconfig"
# kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* kubernetes-admin@kubernetes kubernetes kubernetes-admin
user1@kubernetes kubernetes user1联合使用多个kubeconfig时 , 同样可以按需调整当前使用的context , 其实现方式同使用单个kubeconfig文件并没有不同之处。
# kubectl config use-context user1@kubernetes
Switched to context "user1@kubernetes".
# kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
kubernetes-admin@kubernetes kubernetes kubernetes-admin
* user1@kubernetes kubernetes user1kubectl config view命令会将多个配置文件的内容按给定的次序连接并输出 , 其风格类似于Linux系统的cat命令。但我们也能够在view命令中将加载的多个配置文件展平为单个配置文件的格式予以输出 , 并将结果保存在指定路径下便能将多个kubeconfig文件合并为一。例如 , 下面的命令便将KUBECONFIG环境变量中指定的两个kubeconfig文件合并成了单个配置文件。
# kubectl config view --merge --flatten > $HOME/.kube/kube.config此时 , 切换kubectl config加载新生成的kubeconfig配置文件 , 它便直接拥有了此前两个文件中定义的所有信息 , 且current-context亦遵循此前命令中的设定 , 即user1@kubernetes。
# KUBECONFIG="$HOME/.kube/kube.config"
# kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
kubernetes-admin@kubernetes kubernetes kubernetes-admin
* user1@kubernetes kubernetes user1这里测试完毕 , 还是把context切换回集群管理员kubernetes-admin@kubernetes。
# kubectl config use-context kubernetes-admin@kubernetes
Switched to context "kubernetes-admin@kubernetes".5. 基于角色的访问控制 : RBAC
DAC ( 自主访问控制 ) 、MAC ( 强制访问控制 ) 、RBAC ( 基于角色的访问控制 ) 和ABAC ( 基于属性的访问控制 ) 这4种主流的权限管理模型中 , Kubernetes支持使用后两种完成普通账户和服务账户的权限管理 , 另外支持的权限管理模型还有Node和Webhook两种。
RBAC是一种新型、灵活且使用广泛的访问控制机制 , 它将权限授予角色 , 通过让 " 用户" 扮演一到多个 " 角色" 完成灵活的权限管理 , 这有别于传统访问控制机制中将权限直接赋予使用者的方式。相对于Kubernetes支持的ABAC和Webhook等授权机制 , RBAC具有如下优势。
对集群中的资源和非资源型
URL的权限实现了完整覆盖。整个
RBAC完全由少数几个API对象实现 , 而且与其他API对象一样可以用kubectl或API调用进行操作。支持权限的运行时调整 , 无须重新启动
API Server。
提示 :
Kubernetes自1.5版本起引入RBAC,1.6版本中将其升级为Beta级别 , 并成为kubeadm部署集群时的默认选项。而后 , 直到1.8版本 , 它 才正式升级为Stable级别。
5.1 RBAC 授权模型
RBAC是一种特定的权限管理模型 , 它把可以施加在 " 资源对象" 上的 " 动作" 称为 " 许可权限" , 这些许可权限能够按需组合在一起构建出 " 角色" 及其职能 , 并通过为 " 用户账户或组账户" 分配一到多个角色完成权限委派。这些能够发出动作的用户在RBAC中也称为 " 主体" 。如下图展现了RBAC中用户、角色与权限之间的关系。

RBAC访问控制模型中 , 授权操作只能通过角色完成 , 主体只有在分配到角色之后才能行使权限 , 且仅限于从其绑定的各角色之上继承而来的权限。换句话说 , 用户的权限仅能够通过角色分配获得 , 未能得到显式角色委派的用户则不具有任何权限。
简单来说 , RBAC就是一种访问控制模型 , 它以角色为中心界定 " 谁" ( subject ) 能够 " 操作" ( verb ) 哪个或哪类 " 对象" ( object ) 。动作的发出者即 " 主体" , 通常以 " 账号" 为载体 , 在Kubernetes系统上 , 它可以是普通账户 , 也可以是服务账户。 " 动作" 用于表明要执行的具体操作 , 包括创建、删除、修改和查看等行为 , 对于API Server来说 , 即PUT、POST、DELETE和GET等请求方法。 而 " 对象" 则是指管理操作能够施加的目标实体 , 对Kubernetes API 来说主要指各类资源对象以及非资源型URL。

API Server是RESTful风格的API , 各类客户端由认证插件完成身份验证 , 而后通过HTTP协议的请求方法指定对目标对象的操作请求 , 并由授权插件进行授权检查 , 而操作的对象则是URL路径指定的REST资源。HTTP方法和API Server资源操作的对应关系。
Kubernetes系统的RBAC授权插件将角色分为Role和ClusterRole两类 , 它们都是Kubernetes内置支持的API资源类型 , 其中Role作用于名称空间级别 , 用于承载名称空间内的资源权限集合 , 而ClusterRole则能够同时承载名称空间和集群级别的资源权限集合。Role无法承载集群级别的资源类型的操作权限 , 这类的资源包括集群级别的资源 ( 例如Nodes ) 、非资源类型的端点 ( 例如/healthz ) , 以及作用于所有名称空间的资源 ( 例如跨名称空间获取任何资源的权限 ) 等。
利用Role和ClusterRole两类角色进行赋权时 , 需要用到另外两种资源RoleBinding和ClusterRoleBinding , 它们同样是由API Server内置支持的资源类型。RoleBinding用于将Role绑定到一个或一组用户之上 , 它隶属于且仅能作用于其所在的单个名称空间。RoleBinding可以引用同一名称中的Role , 也可以引用集群级别的ClusterRole , 但引用 ClusterRole的许可权限会降级到仅能在RoleBinding所在的名称空间生效。而ClusterRoleBinding则用于将ClusterRole绑定到用户或组 , 它作用于集群全局 , 且仅能够引用ClusterRole。四者之间的关系如下图所示。

上图中的内容说明 :
1.全局作用范围的User2因通过A名称空间中的RoleBinding关联至Role-A上 , 因而它仅能在NamespaceA名称空间中发挥作用。
2.名称空间B中的ServiceAccount1通过RoleBinding关联至集群级别的ClusterRole-M上 , 对该账户来说 , ClusterRole-M上的操作权限也仅限于该名称空间。
3.全局级别的用户User1通过ClusterRoleBinding关联到ClusterRole-M上 , 因而 , 该用户将在集群级别行使该角色的权限。
通常 , 我们可以把Kubernetes集群用户大体规划为集群管理员、名称空间管理员和用户 ( 通常为开发人员 ) 3类。
集群管理员可以创建、读取、更新和删除任何策略对象 , 能够创建命名空间并将其分配给名称空间管理员 ; 此角色适用于在整个集群中管理所有租户或项目的管理员。
名称空间管理员可以管理其名称空间中的用户 , 此角色适用于特定单一租户或项目的管理员。
开发者用户可以创建、读取、更新和删除名称空间内的非策略对象 , 如
Pod、Job和Ingress等 , 但只在它们有权访问的名称空间中拥有这些权限。
另外 , 有些特殊的应用程序可能还会需要一些比较特殊的权限集合 , 例如集群或名称空间级别的只读权限等 , 我们可以在必要时按需定义这些较为特别的角色。
Kubernetes的RBAC授权知识点
角色 :
Role : 角色 , 名称空间级别
ClusterRole : 集群角色 , 全局级别
角色绑定 :
RoleBinding : 角色绑定 , 指将用户与角色关联起来
ClusterRoleBinding : 集群角色绑定 , 让用户扮演指定的集群角色
主体 ( subject ) :
User : 用户
Group : 用户组
ServiceAccount : 服务账号5.2 Role与ClusterRole
Role和ClusterRole是API Server内置的两种资源类型 , 它们在本质上都只是一组许可权限的集合。Role和ClusterRole的资源规范完全相同 , 该规范没有使用spec字段 , 而是直接使用rules字段嵌套授权规则列表。规则的基本要素是动作 ( verb ) 和相关的目标资源 , 后者支持指定一个或多个资源类型、特定资源类型下的单个或多个具体的资源 , 以及非资源类型的URL等。在Role和ClusterRole资源上定义的rules也称为PolicyRule , 即策略规则 , 它可以内嵌的字段有如下几个。
apiGroups <[]string>: 目标资源的API群组名称 , 支持列表格式指定多个组 , 空值 ("") 表示核心群组。nonResourceURLs <[]string>: 用于定义用户有权限访问的网址列表 , 它并非名称空间级别的资源 , 因此只能应用于ClusterRole,Role支持此字段仅是为了格式上的兼容 ; 该字段在一条规则中与resources和resourceNames互斥。resourceNames <[]string>: 可选字段 , 指定操作适用的具体目标资源名称。resources <[]string>: 规则应用的目标资源类型 , 例如pods、services、deployments和daemonsets等 , 未同时使用resourceNames字段时 , 表示指定类型下的所有资源。ResourceAll表示所有资源。verbs <[]string>: 可应用在此规则匹配到的所有资源类型的操作列表 , 可用选项有get、list、create、update、patch、watch、proxy、redirect、delete和deletecollection; 此为必选字段。
Role创建示例 :
编写资源清单文件
pods-reader-rbac.yaml,这里的清单内容表示在default名称空间定义了一个名为pods-reader的Role资源 , 设定了读取、列出及监视pods和services资源 , 以及pods/log子资源的许可权限。kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: namespace: default name: pods-reader rules: - apiGroups: [""] # ""表示核心API群组 resources: ["pods", "services", "pods/log"] verbs: ["get", "list", "watch"]创建该资源
# kubectl apply -f pods-reader-rbac.yaml role.rbac.authorization.k8s.io/pods-reader created查看
role资源的详细信息 , 可以清楚看出定义的角色 , 对哪些资源有哪些操作。# kubectl describe role pods-reader Name: pods-reader Labels: <none> Annotations: <none> PolicyRule: Resources Non-Resource URLs Resource Names Verbs --------- ----------------- -------------- ----- pods/log [] [] [get list watch] pods [] [] [get list watch] services [] [] [get list watch]
ClusterRole创建示例 :
ClusterRole资源隶属于集群级别 , 它引用名称空间级别的资源意味着相关的操作权限能够在所有名称空间生效 , 同时 , 它也能够引用Role所不支持的集群级别的资源类型 , 例如nodes和 persistentvolumes等。
编写资源清单文件
nodes-admin-rbac.yaml定义了ClusterRole资源nodes-admin, 它拥有管理集群节点信息的权限。ClusterRole不属于名称空间 , 所以其配置不能够使用metadata.namespace字段。apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: nodes-admin rules: - apiGroups: [""] resources: ["nodes"] verbs: ["*"]创建该资源
# kubectl apply -f nodes-admin-rbac.yaml clusterrole.rbac.authorization.k8s.io/nodes-admin created查看
ClusterRole详细信息 , 同样也可以看到具体的权限信息等。# kubectl describe clusterrole nodes-admin Name: nodes-admin Labels: <none> Annotations: <none> PolicyRule: Resources Non-Resource URLs Resource Names Verbs --------- ----------------- -------------- ----- nodes [] [] [*]
kubectl命令也分别提供了创建Role和ClusterRole资源的命令式命令 , create role和create clusterrole , 它们支持如下几个关键选项。
--verb: 指定可施加于目标资源的动作 , 支持以逗号分隔的列表值 , 也支持重复使用该选项分别指定不同的动作 , 例如--verb=get,list,watch, 或者--verb=get --verb=list --verb=watch。--resource: 指定目标资源类型 , 使用格式类似于--verb选项。--resource-name: 指定目标资源 , 使用格式类似于--verb选项。--non-resource-url: 指定非资源类型的URL, 使用格式类似于--verb选项 , 但仅适用于clusterrole资源。
命令式创建role和clusterrole示例 :
下面创建一个dev名称空间 , 然后再该名称空间分别创建了一个具有所有资源管理权限的roles/admin资源 , 一个有PVC和PV资源管理权限的clusterroles/pv-admin资源。
# kubectl create namespace dev
namespace/dev created
# kubectl create role admin -n dev --resource="*.*" --verb="get,list,watch,create,delete,deletecollection,patch,update"
role.rbac.authorization.k8s.io/admin created
# kubectl create clusterrole pv-admin -n dev --resource="persistentvolumeclaims,persistentvolumes" --verb="*"
clusterrole.rbac.authorization.k8s.io/pv-admin created查看验证创建的权限
# kubectl describe roles/admin clusterroles/pv-admin -n dev
Name: admin
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
*.* [] [] [get list watch create delete deletecollection patch update]
Name: pv-admin
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
persistentvolumeclaims [] [] [*]
persistentvolumes [] [] [*]但是 , Role或ClusterRole对象本身并不能作为动作的执行主体 , 它们需要 " 绑定" 到主体 ( 例如User、Group或Service Account ) 之上完成赋权 , 而后由相应主体执行资源操作。
5.3 RoleBinding与ClusterRoleBinding
RoleBinding负责在名称空间级别向普通账户、服务账户或组分配Role或ClusterRole , 而ClusterRoleBinding则只能用于在集群级别分配ClusterRole。但二者的配置规范格式完全相同 , 它们没有spec字段 , 直接使用subjects和roleRef两个嵌套的字段。其中 , subjects的值是一个对象列表 , 用于给出要绑定的主体 , 而roleRef的值是单个对象 , 用于指定要绑定的Role或ClusterRole资源。
subjects字段的可嵌套字段如下 :
apiGroup <string>: 要引用的主体所属的API群组 , 对于ServiceAccount类的主体来说默认为"", 而User和Group类主体的默认值为"rbac.authorization.k8s.io"。kind <string>: 要引用的资源对象 ( 主体 ) 所属的类别 , 可用值为User、Group和ServiceAccount, 必选字段。name <string>: 引用的主体的名称 , 必选字段。namespace <string>: 引用的主体所属的名称空间 , 对于非名称空间类型的主体 , 例如User和Group, 其值必须为空 , 否则授权插件将返回错误信息。
roleRef的可嵌套字段如下 :
apiGroup <string>: 引用的资源 (Role或ClusterRole) 所属的API群组 , 必选字段。kind <string>: 引用的资源所属的类别 , 可用值为Role或ClusterRole, 必选字段。name <string>: 引用的资源 (Role或ClusterRole) 的名称。
RoleBinding创建示例 :
注意 : RoleBinding仅能够引用同一名称空间的Role资源。
编写资源清单文件
rolebinding-demo.yaml, 下面配置清单中的RoleBinding在default名称空间中把pods-reader角色分配给用户user1, 表示user1用户拥有了role资源pods-reader的权限。kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: read-pods namespace: default subjects: - kind: User name: user1 apiGroup: rbac.authorization.k8s.io roleRef: # "roleRef" 指定与某 Role 或 ClusterRole 的绑定关系 kind: Role # 此字段必须是 Role 或 ClusterRole name: pods-reader # 此字段必须与你要绑定的 Role 或 ClusterRole 的名称匹配 apiGroup: rbac.authorization.k8s.io创建该资源 , 并查看其详细信息。
# kubectl apply -f rolebinding-demo.yaml rolebinding.rbac.authorization.k8s.io/read-pods created # kubectl describe rolebinding read-pods Name: read-pods Labels: <none> Annotations: <none> Role: Kind: Role Name: pods-reader Subjects: Kind Name Namespace ---- ---- --------- User user1验证权限 , 这里使用在
X509数字证书认证中创建的user1用户进行验证。# kubectl get pods --kubeconfig=/tmp/mykubeconfig NAME READY STATUS RESTARTS AGE deployment-demo-cb69c57b7-7wl9k 1/1 Running 1 2d23h deployment-demo-cb69c57b7-frpc9 1/1 Running 1 2d23h deployment-demo-cb69c57b7-kzmq5 1/1 Running 1 2d23h deployment-demo-cb69c57b7-r7rwp 1/1 Running 1 2d23h pod-with-sa 1/1 Running 0 2d13h # kubectl get deployment --kubeconfig=/tmp/mykubeconfig Error from server (Forbidden): deployments.apps is forbidden: User "user1" cannot list resource "deployments" in API group "apps" in the namespace "default" # kubectl get nodes --kubeconfig=/tmp/mykubeconfig Error from server (Forbidden): nodes is forbidden: User "user1" cannot list resource "nodes" in API group "" at the cluster scope # kubectl get pods --kubeconfig=/tmp/mykubeconfig -n dev Error from server (Forbidden): pods is forbidden: User "user1" cannot list resource "pods" in API group "" in the namespace "dev"
因为前面定义的role只是对默认default名称空间的pods、services、pods/log对象有get、list、watch权限。所以通过上面测试结果可以看出 , user1确实只有在default名称空间下对指定的资源有get等权限 , 而get其他名称空间 ( dev ) 时 , 则直接返回错误。
ClusterRoleBinding创建示例 :
编写资源清单文件
clusterrolebinding-demo.yaml, 下面配置清单中表示使用ClusterRoleBinding为用户user1分配了cluster-admin这一集群角色。cluster-admin是系统默认的集群角色 , 也是超级权限 , 这里仅测试 , 生产环境需留意。--- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: super-admin subjects: - kind: User name: user1 apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io创建该资源 , 并查看其详细信息
# kubectl apply -f clusterrolebinding-demo.yaml clusterrolebinding.rbac.authorization.k8s.io/super-admin created # kubectl describe clusterrolebinding/super-admin Name: super-admin Labels: <none> Annotations: <none> Role: Kind: ClusterRole Name: cluster-admin Subjects: Kind Name Namespace ---- ---- --------- User user1验证权限 , 这里使用在
X509数字证书认证中创建的user1用户进行验证。# kubectl get pods --kubeconfig=/tmp/mykubeconfig NAME READY STATUS RESTARTS AGE deployment-demo-cb69c57b7-7wl9k 1/1 Running 1 3d deployment-demo-cb69c57b7-frpc9 1/1 Running 1 3d deployment-demo-cb69c57b7-kzmq5 1/1 Running 1 3d deployment-demo-cb69c57b7-r7rwp 1/1 Running 1 3d pod-with-sa 1/1 Running 0 2d13h # kubectl get nodes --kubeconfig=/tmp/mykubeconfig NAME STATUS ROLES AGE VERSION k8s-master Ready control-plane,master 25d v1.23.1 k8s-node1 Ready <none> 25d v1.23.1 k8s-node2 Ready <none> 25d v1.23.1 # kubectl run demoapp --image="vvoo/demoapp:v1.0" -n dev --kubeconfig=/tmp/mykubeconfig pod/demoapp created # kubectl get pods -n dev --kubeconfig=/tmp/mykubeconfig NAME READY STATUS RESTARTS AGE demoapp 1/1 Running 0 23s
通过上面结果可以看出 , 此时user1用户拥有所有权限 , 包括各个名称空间的查看、创建等等。
kubectl同样提供了分别创建RoleBinding和ClusterRoleBinding资源的命令式命令 : create rolebinding和create clusterrolebinding , 他们使用的选项基本相同 , 常用的选项如下 :
--role='': 绑定的角色 , 仅RoleBinding支持--clusterrole='': 绑定的集群角色 ,RoleBinding和ClusterRoleBinding均支持--group=[]: 绑定的组 , 支持逗号分隔的列表格式--user=[]: 绑定的普通账号 , 支持逗号分隔的列表格式--serviceaccount=[]: 绑定的服务账号 , 支持逗号分隔的列表格式
命令式创建clusterrolebinding示例 :
下面的命令为用户组kubernetes分配了集群角色nodes-admin , 从而该组的所有用户均自动继承该角色上的所有许可权限。
# kubectl create clusterrolebinding kubenetes-nodes-admin --clusterrole='nodes-admin' --group='kubernetes'
clusterrolebinding.rbac.authorization.k8s.io/kubenetes-nodes-admin created5.4 聚合型ClusterRole
Kubernetes自1.9版本开始支持在ClusterRole的rules字段中嵌套 aggregationRule字段来整合其他ClusterRole资源的规则 , 这种类型 的ClusterRole对象的实际可用权限受控于控制器 , 具体许可授权由所有被标签选择器匹配到的ClusterRole的聚合授权规则合并生成。
下面的配置清单中首先定义了两个拥有标签的集群角色globalresources-view和global-resources-edit , 而后在第三个集群角色资源global-resources-admin上使用聚合规则的标签选择器来匹配前两个资源的标签 , 因此 , 集群角色global-resources-admin的权限将由匹配到的其他ClusterRole资源的规则列表自动聚合而成。
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: global-resources-view
labels:
rbac.ilinux.io/aggregate-to-global-admin: "true"
rules:
- apiGroups: [""]
resources: ["nodes", "namespaces", "persistentvolumes"]
verbs: ["get", "list", "watch"]
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: global-resources-edit
labels:
rbac.ilinux.io/aggregate-to-global-admin: "true"
rules:
- apiGroups: [""]
resources: ["nodes", "namespaces", "persistentvolumes"]
verbs: ["create", "delete", "deletecollection", "patch", "update"]
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: global-resources-admin
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.ilinux.io/aggregate-to-global-admin: "true"
rules: []任何能够被示例中clusterrole/globa-resources-admin资源的标签选择器匹配到的Cluster-Role资源的相关规则将一同合并为它的授权规则 , 并且相关作用域内的任何ClusterRole资源的变动都将实时反馈到聚合资源之上。因而 , 聚合型ClusterRole的规则会随着标签选择器的匹配结果动态变化。
事实上 , Kubernetes系统上面向用户的内置ClusterRole admin和 edit也是聚合型的ClusterRole对象 , 因为这可以使得默认角色中包含自定义资源的相关规则 , 例如由CustomResourceDefinitions或 Aggregated API服务器提供的规则等。
5.5. 内置的ClusterRole
API Server内置了一组默认的ClusterRole和ClusterRoleBinding 资源预留给系统使用 , 其中大多数都以system:为前缀。另外有一些不以system:为前缀的默认的ClusterRole资源是为面向用户的需求而设计 , 包括集群管理员角色cluster-admin , 以及专用于授予特定名称空间级别权限的集群角色admin、edit和view , 如下图所示。

内置的clusterroles/cluster-admin资源拥有管理集群所有资源的权限 , 而内置的clusterrolebindings/cluster-admin将该角色分配给了system:masters用户组 , 这意味着所有隶属于该组的用户都将自动具有集群的超级管理权限。kubeadm安装设置集群时 , 自动创建的配置文件/etc/kubernetes/admin.conf中定义的用户kubernetes-admin 使用证书文件/etc/kubernetes/pki/apiserver-kubelet-client.crt 向API Server进行验证。而该数字证书的Subject属性值为/O=system:masters , API Server会在成功验证该用户的身份之后将其识别为system: master用户组的成员。
# openssl x509 -in /etc/kubernetes/pki/apiserver-kubelet-client.crt -noout -subject
subject= /O=system:masters/CN=kube-apiserver-kubelet-client于是 , 为Kubernetes集群自定义超级管理员的方法至少有两种 : 一是将用户归入system:masters组 , 二是通过ClusterRoleBinding直接将用户绑定至内置的集群角色cluster-admin上。
另外 , 在多租户、多项目或多环境等使用场景中 , 用户通常应该获得名称空间级别绝大多数资源的管理 ( admin ) 、只读 ( view ) 或编辑 ( edit ) 权限 , 可通过在指定的名称空间中创建RoleBinding资源引用内置的ClusterRole资源进行这类权限的快速授予。例如 , 在名称空间dev中创建一个RoleBinding资源 , 为user1用户分配集群角色admin , 将使得该用户具有管理dev名称空间中除了名称空间本身及资源配额之外的所有资源的权限。
# kubectl create rolebinding user1-admin --clusterrole=admin --user=user1 -n dev
rolebinding.rbac.authorization.k8s.io/user1-admin created若仅需要授予编辑或只读权限 , 在创建RoleBinding时引用 ClusterRole的edit或view便能实现。典型的面向用户的内置ClusterRole及其功用如下。
另外 , API Server默认创建的以system:为前缀的大多数 ClusterRole和ClusterRoleBinding专为Kubernetes系统的基础架构而设计 , 修改这些资源可能会导致集群功能不正常。例如 , 若修改了为kubelet赋权的system:node将会导致kubelet无法正常工作。所有默认的ClusterRole和ClusterRoleBinding都打上了kubernetes.io/bootstrapping=rbac-defaults标签。
每次启动时 , API Server都会自动为所有默认的ClusterRole重新赋予缺失的权限 , 同时为默认的ClusterRoleBinding绑定缺失的主体。这种机制给了集群从意外修改中自动恢复的能力 , 以及升级版本后自动将ClusterRole和ClusterRoleBinding升级到满足新版本需求的能力。
提示 : 必要时 , 在默认的
ClusterRole或ClusterRoleBinding上设置annnotation中的rbac.authorization.kubernetes.io/autoupdate属性的值为false, 即可禁止这种自动恢复功能。
另外 , 启用RBAC后 , Kubernetes系统的各核心组件、附加组件 , 以及由controller-manager运行的核心控制器等 , 几乎都要依赖于合理的授权才能正常运行。因而 , RBAC权限模型为这些组件内置了可获得最小化的资源访问授权的ClusterRole和ClusterRoleBinding , 例如 system:kube-sheduler、system:kube-controller-manager、 system:node、system:node-proxier和system:kube-dns等 , 其中大多数组件都可以做到见名知义 , 可以参考官网。
6. 准入控制器
API Server中的准入控制器同样以插件形式存在 , 它们会拦截所有已完成认证的 , 且与资源创建、更新和删除操作相关的请求 , 以强制实现控制器中定义的功能 , 包括执行对象的语义验证和设置缺失字 段的默认值等 , 具体功能取决于API Server启用的插件。目前 , Kubernetes内置了30多个准入控制器。
6.1. 准入控制器概述
准入控制器的相关代码必须要由管理员编译进kube-apiserver中才能使用 , 实现方式缺乏灵活性。于是 , Kubernetes自 1.7版本引入了Initializers和External Admission Webhooks来尝试突破此限制 , 而且自1.9版本起 , External Admission Webhooks又被分为MutatingAdmissionWebhook和ValidatingAdmissionWebhook两种类型 , 分别用于在API中执行对象配置的 " 变异" 和 " 验证" 操作 , 前一种类型的控制器会 " 改动" 和 " 验证" 资源规范 , 而后一种类型仅 " 验证" 资源规范是否合规。
在具体的代码实现上 , 一个准入控制器可以是验证型、变异型或兼具此两项功能。例如 , LimitRanger准入控制器可以使用默认资源请求和限制 ( 变异阶段 ) 来扩展Pod , 也能够校验有着显式资源需求定义的Pod是否超出LimitRange对象 ( 验证阶段 ) 的定义。而在具体运行时 , 准入控制也会根据准入控制器类型分阶段运行 , 第一个阶段串行运行各变异型控制器 , 第二阶段则串行运行各验证型控制器 , 如下图所示。在此过程中 , 任何控制器拒绝请求都将导致整个请求被即刻拒绝 , 并将错误信息返回给客户端。

Kubernetes集群内置功能的某些方面实际上就是由准入控制器控制的 , 例如 , 删除名称空间并进入Terminating状态时 , NamespaceLifecycle准入控制器将会阻止在该名称空间中创建任何新的资源对象。甚至于 , 必须启用准入控制器才能使用Kubernetes 集群的某些更高级的安全功能 , 例如在整个命名空间上强制实施安全配置基线的Pod安全策略等。
API Server默认便会启用部分准入控制器 , 它也支持通过--enable-admission-plugins选项显式指定要加载的准入控制器 , 使用--disable-admission-plugins选项显式指定要禁用的准入控制器。
Kubernetes正是依赖LimitRange资源和相应的LimitRanger准入控制器、ResourceQuota资源和同名的准入控制器 , 以及PodSecurityPolicy资源和同名的准入控制器为多租户或多项目的集群环境提供了基础的安全策略框架。
6.2 LimitRange
尽管可以为容器或Pod资源指定资源需求及资源限制 , 但这并非强制性要求 , 那些未明确定义资源限制的容器应用很可能会因程序Bug或真实需求而吞掉本地工作节点上的所有可用计算资源。因此妥当的做法是 , 使用LimitRange资源在每个名称空间中限制每个容器的最小及最大计算资源用量 , 以及为未显式定义计算资源使用区间的容器设置默认的计算资源需求和计算资源限制。一旦在名称空间上定义了LimitRange对象 , 客户端创建或修改资源对象的操作必将受到 LimitRange控制器的 " 验证" , 任何违反LimitRange对象定义的资源最大用量的请求都将被直接拒绝。
LimitRange支持在Pod级别与容器级别分别设置CPU和内存两种计算资源的可用范围 , 它们对应的资源范围限制类型分别为Pod和 Container。一旦在名称空间上启用LimitRange , 该名称空间中的Pod 或容器的requests和limits的各项属性值必须在对应的可用资源范围内 , 否则将会被拒绝 , 这是验证型准入控制器的功能。以Pod级别的CPU资源为例 , 若某个LimitRange资源为其设定了[0.5,4]的区间 , 则相应名称空间下任何Pod资源的requests.cpu的属性值必须要大于等于500m , 同时 , limits.cpu的属性值也必须要小于等于4。而未显式指定request和limit属性的容器 , 将会从LimitRange资源上分别自动继承相应的默认设置 , 这是变异型准入控制器的功能 , 如下图所示。

另外 , LimitRange也支持在PersistentVolumeClaim资源级别设定存储空间的范围限制 , 它用于限制相应名称空间中创建的PVC对象请求使用的存储空间不能逾越指定的范围。未指定requests和limits属性的PVC规范 , 将在创建时自动继承LimitRange上配置的默认值。
LimitRanger示例:
编写资源清单文件
limitrange-demo.yaml, 该资源清单为dev名称空间中的Pod、Container和PresistentVolumeClaim资源定义了各自的资源范围 , 并为后两者指定了相应的可用资源规范的limits和requests属性上的默认值。其中用到的各配置属性中 ,default用于定义limits的默认值 ,defaultRequest定义requests的默认值 ,min定义最小资源用量 , 而最大资源用量可用使用max给出固定值 , 也可以使用maxLimitRequestRatio设定最小用量的指定倍数 , 同时定义二者时 , 其意义要相符。apiVersion: v1 kind: LimitRange metadata: name: core-resource-limits namespace: dev spec: limits: - type: Pod max: cpu: "4" memory: "4Gi" min: cpu: "500m" memory: "16Mi" - type: Container max: cpu: "4" memory: "1Gi" min: cpu: "100m" memory: "4Mi" default: cpu: "2" memory: "512Mi" defaultRequest: cpu: "500m" memory: "64Mi" maxLimitRequestRatio: cpu: "4" - type: PersistentVolumeClaim max: storage: "10Gi" min: storage: "1Gi" default: storage: "5Gi" defaultRequest: storage: "1Gi" maxLimitRequestRatio: storage: "5"注意 :
LimitRange仅在Container资源类型上可为CPU与内存设置default(limits属性的默认值 ) 和defaultrequest(requests属性 的默认值 ) ,Pod资源类型不支持。创建该资源
# kubectl apply -f limitrange-demo.yaml limitrange/core-resource-limits created查看该资源的详细信息 ,
LimitRange资源的详细描述会以非常直观、清晰的方式输出相关的资源限制及默认值的定义。# kubectl describe limitrange core-resource-limits -n dev Name: core-resource-limits Namespace: dev Type Resource Min Max Default Request Default Limit Max Limit/Request Ratio ---- -------- --- --- --------------- ------------- ----------------------- Pod cpu 500m 4 - - - Pod memory 16Mi 4Gi - - - Container cpu 100m 4 500m 2 4 Container memory 4Mi 1Gi 64Mi 512Mi - PersistentVolumeClaim storage 1Gi 10Gi 1Gi 5Gi 5创建
Pod进行测试验证 , 先创建一个仅包含一个容器且没有默认系统资源需求和限制的Pod对象。# kubectl run test-pod1 --image="vvoo/demoapp:v1.0" -n dev pod/test-pod1 created # kubectl get pods/test-pod1 -o yaml -n dev ...... spec: containers: - image: vvoo/demoapp:v1.0 imagePullPolicy: IfNotPresent name: test-pod1 resources: limits: cpu: "2" memory: 512Mi requests: cpu: 500m memory: 64Mi ......通过上面创建的
Pod并查看详细信息可以看出 ,test-pod1资源规范中被自动添加了CPU和内存资源的requests和limits属性。再次创建
Pod进行测试验证 , 这个Pod创建时候指定requests和limits。# cat <<EOF | kubectl apply -n dev -f - apiVersion: v1 kind: Pod metadata: name: test-pod2 spec: containers: - name: demoapp image: vvoo/demoapp:v1.0 resources: limits: memory: "1.5Gi" cpu: 2 requests: memory: "8Mi" cpu: 2 EOF Error from server (Forbidden): error when creating "STDIN": pods "test-pod2" is forbidden: [minimum memory usage per Pod is 16Mi, but request is 8388608, maximum memory usage per Container is 1Gi, but limit is 1536Mi]
通过上面创建可以结果可以看出 , 直接被LimitRanger准入控制器拒绝了 , 因为在该资源清单中 , requests.memory和requests.memory都违背了limitrange/core-resource-limits中的定义。
需要注意的是 , LimitRange生效于名称空间级别 , 它需要定义在每个名称空间之上 ; 另外 , 定义的限制仅对该资源创建后的Pod和PVC资源创建请求有效 , 对之前已然存在的资源无效 ; 再者 , 不建议在生效于同一名称空间的多个LimitRange资源上 , 对同一个计算资源限制进行分别定义 , 以免产生歧义或导致冲突。
6.3. ResourceQuota
尽管LimitRange资源能在名称空间上限制单个容器、Pod或PVC相关的系统资源用量 , 但用户依然可以创建出无数的资源对象 , 进而侵占集群上所有的可用系统资源。ResourceQuota资源能够定义名称空间级别的资源配额 , 从而在名称空间上限制聚合资源消耗的边界 , 它支持以资源类型来限制用户可在本地名称空间中创建的相关资源的对象数量 , 以及这些对象可消耗的计算资源总量等。
而同名的ResourceQuota准入控制器负责观察传入的请求 , 并确保它没有违反相应名称空间中ResourceQuota资源定义的任何约束。 ResourceQuota准入控制器属于 " 验证" 类型的控制器 , 用户创建或更新资源的操作违反配额约束时将会被拒绝 , API Server会响应以HTTP 状态代码403 FORBIDDEN , 并显示一条消息以提示违反的约束条件。
ResourceQuota资源可限制名称空间中处于非终止状态的所有Pod对象的计算资源需求及计算资源限制总量。
cpu或requests.cpu:CPU资源相关请求的总量限额。memory或requests.memory: 内存资源相关请求的总量限额。limits.cpu:CPU资源相关限制的总量限额。limits.memory: 内存资源相关限制的总量限额。
ResourceQuota资源还支持为本地名称空间中的PVC存储资源的需求总量和限制总量设置限额 , 它能够分别从名称空间中的全部PVC、隶属于特定存储类的PVC以及基于本地临时存储的PVC分别进行定义。
requests.storage: 所有PVC存储需求的总量限额。persistentvolumeclaims: 可以创建的PVC总数限额。<storage-class-name>.storageclass.storage.k8s.io/requests.storage: 特定存储类上可使用的所有PVC存储需求的总量限额<storage-class-name>.storageclass.storage.k8s.io/persistentvolumeclaims: 特定存储类上课使用的PVC总数限额。requests.ephemeral-storage: 所有Pod可以使用的本地临时存储资源的requests总量。limits.ephemeral-storage: 所有Pod可以使用的本地临时存储资源的limits总量。
在v1.9版本之前的Kubernetes系统上 , ResourceQuota仅支持在有限的几种资源集上设定对象计数配额 , 例如pods、services和 configmaps等 , 而自v1.9版本起开始支持以count/<resource>.<group>的格式对所有资源类型对象的计数配额 , 例如 count/deployments.apps、count/deployments.extensions和 count/services等。
示例:
编写资源清单文件
resourcequota-demo.yamlapiVersion: v1 kind: ResourceQuota metadata: name: resourcequota-demo namespace: dev spec: hard: pods: "5" # 在dev名称空间中Pod总数不超过5个 count/service: "5" # 在dev名称空间中configmap总数不超过5个 court/configmaps: "5" count/secrets: "5" count/cronjobs.batch: "2" requests.cpu: "2" # 在dev名称空间中所有Pod的CPU请求总和不超过2 CPU requests.memory: "4Gi" # 在dev名称空间中所有Pod的内存请求总和不超过4 GiB limits.cpu: "4" # 在dev名称空间中所有Pod的CPU限制总和不超过4 CPU limits.memory: "8Gi" # 在dev名称空间中所有Pod的内存限制总和不超过8 GiB count/deployments.apps: "2" # 在dev名称空间中创建的deployment资源总数不超过2个 count/statefulsets.apps: "2" persistentvolumeclaims: "6" # 在dev名称空间中PVC资源总数不超过6个 requests.storage: "20Gi" # 在dev名称空间中存储资源用量不超过20GiB csi-rbd-sc.storageclass.storage.k8s.io/requests.storage: "20Gi" # 在dev名称空间对存储卷csi-rbd-sc的使用总量不超过20GiB csi-rbd-sc.storageclass.storage.k8s.io/persistentvolumeclaims: "6" # 在dev名称空间对存储卷csi-rbd-sc创建PVC不超过6个创建该资源对象
# kubectl apply -f resourcequota-demo.yaml resourcequota/resourcequota-demo created查看
resourcequota详细信息 , 与LimitRange不同的是 ,ResourceQuota会计入指定范围内 , 先前的资源对象对系统资源和资源对象的限额占用情况 , 因此将resourceqouta-demo创建到集群上之后 ,dev名称空间中现有的资源会立即分去限额内的一部分可用空间。# kubectl describe resourcequota/resourcequota-demo -n dev Name: resourcequota-demo Namespace: dev Resource Used Hard -------- ---- ---- count/cronjobs.batch 0 2 count/deployments.apps 0 2 count/secrets 2 5 count/service 0 5 count/statefulsets.apps 0 2 court/configmaps 0 5 csi-rbd-sc.storageclass.storage.k8s.io/persistentvolumeclaims 0 6 csi-rbd-sc.storageclass.storage.k8s.io/requests.storage 0 20Gi limits.cpu 2 4 limits.memory 512Mi 8Gi persistentvolumeclaims 0 6 pods 2 5 requests.cpu 500m 2 requests.memory 64Mi 4Gi requests.storage 0 20Gi验证测试 , 上面的结果中
Pod资源限额已经被前面的Pod对象消耗了2/5, 与此同时还有别的资源也有被使用了的。随后 , 在dev名称空间中创建Pod资源时 ,requests.cpu、requests.memory、limit.cpu、limits.memory和Pods等任何一个限额的超出都会导致创建操作失败 , 如下面创建示例。# kubectl run testpod-$RANDOM --image='vvoo/demoapp:v1.0' -n dev Error from server (Forbidden): pods "testpod-2233" is forbidden: exceeded quota: resourcequota-demo, requested: limits.cpu=2, used: limits.cpu=4, limited: limits.cpu=4
每个ResourceQuota资源对象上还支持定义一组作用域 , 用于定义资源上的配额仅生效于这组作用域交集范围内的对象 , 目前适用范围包括Terminating、NotTerminating、BestEffort和NotBestEffort。
Terminating: 匹配.spec.activeDeadlineSeconds的属性值大于等于0的所有Pod对象。NotTerminating: 匹配.spec.activeDeadlineSeconds的属性值为空的所有Pod对象。BestEffort: 匹配所有位于BestEffort QoS类别的Pod对象。NotBestEffort: 匹配所有非BestEffort QoS类别的Pod对象。