附录与补遗 · K8S 实践 03

Hailiang Zhao | 2021/11/09

Categories: practice Tags: Kubernetes architecture


本文首先回顾了 Kubernetes 架构与工作流程,然后复习了资源对象和资源清单文件相关的内容,最后给出了安全认证的知识点梳理与实践。

Kubernetes 架构与工作流程

一个 Kubernetes 集群由控制节点(master)和工作节点(worker)组成。

图 1 给出了 Kubernetes 的全局架构。

图 1 Kubernetes 的全局架构。

接下来,以 Nginx 服务的部署来说明各个组件之间的调用关系:

  1. 当 Kubernetes 集群搭建成功之后,所有 master 节点和 worker 节点会将自身的信息存储到 etcd 中;

  2. Nginx 的部署请求由用户通过 kubectl(或其他客户端工具)提交,然后会被发往 api-server;

  3. api-server 调用 kube-scheduler 将 Nginx 调度到一个具体的 worker 节点上。具体地,api-server 首先生成资源对象的信息并存入 etcd,kube-scheduler 基于 watch 机制发现了 api-server 上的变动,于是开始调度并将调度结果更新到 api-server;

  4. Nginx 被成功调度之后,api-server 调用 controller-manager 创建对应的 controller。例如,当 Nginx 被以 Deployment 资源对象的方式部署时,一个 deployment 会被创建;

  5. 被调度到的节点通过 kubelet 来创建 Nginx Pod。具体地,kubelet 会通知 docker 来启动相应的 container。同样地,kubelet 会将创建结果发送给 api-server;

  6. 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 等概念。

图 2 Kubernetes 中的资源对象。

经常使用的资源对象有下面这些:

资源分类 资源名称 缩写 资源作用
集群级别资源 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 个部分:

安全认证

Kubernetes 中和 api-server 交互的客户端(账户)可分为如下两大类:

不论哪一类账户,访问 api-server 都需要经历如下步骤:

  1. Authentication(认证):身份鉴别,只有正确的账户才能够通过认证;
  2. Authorization(鉴权): 判断当前账户是否有权限对访问的资源执行特定的动作;
  3. Admission Control(准入控制):用于补充授权机制以实现更加精细的访问控制。

图 3 认证、授权与准入机制。

接下来将分别阐述这三个步骤。

认证

Kubernetes 提供了三种身份认证的方式:

鉴权

账户通过认证之后,api-server 会根据事先定义的授权策略来判定该账户是否有权限访问,这个过程就称为鉴权。 每个发送到 api-server 的请求都带上了用户和资源的信息,如发送请求的账户、请求的路径和操作等。 鉴权就是根据这些信息和授权策略进行比较,如果符合策略,则认为鉴权通过,否则会返回错误。

api-server 目前支持以下几种鉴权策略:

我们主要关注 RBAC。RBAC 的工作理念是:给哪些对象授予了哪些权限。 其中涉及到了下面几个概念:

图 4 RBAC。

典型的 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 国际许可协议 进行许可,转载时请注明原文链接。您必须给出适当的署名,并标明是否对本文作了修改。


  1. 控制器管理对象,controller-manager 管理控制器。 ↩︎

  2. 当然,当我们基于 Kubernetes 做开发时,主要和 Kubernetes 为所选语言所提供的客户端打交道。例如,Kubernetes 官方维护的 Python 客户端 https://github.com/kubernetes-client/python。 ↩︎

  3. 这里所谓的上下文是使用 “账户 @集群名称” 来描述的,默认账户是 kubernetes-admin,默认集群名称是 kubernetes。 ↩︎