概述
Service Account是什么呢,顾名思义,服务账号,一种给服务使用的账号,它不是给Kubernetes的集群的用户(系统管理员、运维人员、租户用户等)使用,而是给运行在Pod里的进程用的,它为Pod中的进程提供必要的身份证明。
当用户访问集群(例如使用kubectl命令)时,apiserver会将用户认证为一个特定的User Account(目前通常是admin,除非系统管理员自定义了集群配置)。Pod 容器中的进程也可以与apiserver联系,但Pod 中访问 Kubernetes API Server 服务的时候,是以 Service 方式访问服务名为 kubernetes 这个服务的,而kubernetes服务只在 HTTPS 安全端口 443 上提供服务,那么如何进行身份认证呢?其实它们之间是通过一个特定的Service Account(例如default)进行认证访问。
这是在用一种类似HTTP Token的新的认证方式–ServiceAccount Auth,Pod中的服务调用Kubernetes API的时候,在HTTP Header中传递了一个 Token 字符串,这类似于之前提到的HTTP Token认证方式,存在以下几个不同点:
-
该Token(/run/secrets/kubernetes.io/serviceaccount/token)是动态生成的,确切的说,是由KubernetesController进程用API Server的私钥(–service-account-private-key-file指定的私钥)签名生成的一个JWT Secret。
-
官方提供的客户端REST框架代码里,通过HTTPS方式与API Server建立链接后,会用Pod里指定路径下的一个CA证书(/run/secrets/kubernetes.io/serviceaccount/ca.crt)验证API Server发来的证书,验证是否是被CA证书签名的合法证书。
-
API Server收到这个Token以后,采用自己的私钥(实际是使用参数service-account-key-file指定的私钥,如果此参数没有设置,则默认采用tls-private-key-file指定的参数,即自己的私钥),对token进行合法性验证。
认证过程中涉及到Pod中的三个文件,如下:
/run/secrets/kubernetes.io/serviceaccount/token
/run/secrets/kubernetes.io/serviceaccount/ca.crt
/run/secrets/kubernetes.io/serviceaccount/namespace(客户端采用这里指定的namespace作为参数调用Kubernetes API)
这三个文件由于参与到Pod进程与API Server认证的过程中,起到了类似Secret(私密凭据)的作用,所以他们被称为Kubernetes Secret对象。Secret从属于ServiceAccount资源对象,属于Service Account的一部分,一个ServiceAccount对象里面可以包括多个不同的Secret对象,分别用于不同目的的认证活动。
user account 和service account的区别:
- User account是为人设计的,而service account则是为Pod中的进程调用Kubernetes API而设计;
- User account是跨namespace的,而service account则是仅局限它所在的namespace;
- 每个namespace都会自动创建一个default service account
- Token controller检测service account的创建,并为它们创建secret
- 开启ServiceAccount Admission Controller后
- 每个Pod在创建后都会自动设置spec.serviceAccount为default(除非单独指定使用其他ServiceAccout)
- 验证Pod引用的service account已经存在,否则拒绝创建
- 如果Pod没有指定ImagePullSecrets,则把service account的ImagePullSecrets加到Pod中
- 每个container启动后都会挂载该Service Account的token和ca.crt到/var/run/secrets/kubernetes.io/serviceaccount/目录下
分析系统自动生成的serviceaccount
查看系统中ServiceAccount对象,可以看到每个命名空间下都有一个名为default的Service Account对象,并且都包含一个名为default-token-xxx的Secret,这个Secret同时是“Mountable secrets”,表明他
是需要被Mount到Pod上的。
[root@k8s-m1 pki]# kubectl describe sa default
Name: default
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: default-token-glxls
Tokens: default-token-glxls
Events: <none>kubectl describe serviceaccounts
[root@k8s-m1 pki]# kubectl describe secrets default-token-glxls
default-token-xxx包括三个数据项依次为:
ca.crt
namespace
token
而查看每个命名空间下任意一个没有具体指明使用其他serviceAccount的pod,如下
[root@k8s-m1 pki]# kubectl get pod my-nginx-7ff446c4f4-2d9gz -o yaml|grep -i serviceaccount
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
serviceAccount: default
serviceAccountName: default
[root@k8s-m1 pki]# kubectl get pod my-nginx-7ff446c4f4-2d9gz -o yaml|grep -i token
name: default-token-glxls
- name: default-token-glxls
secretName: default-token-glxls
[root@k8s-m1 dashboard]# kubectl exec -it my-nginx-7ff446c4f4-2d9gz -- /bin/bash
root@my-nginx-7ff446c4f4-2d9gz:/# ls /var/run/secrets/kubernetes.io/serviceaccount/ -l
total 0
lrwxrwxrwx 1 root root 13 Jul 1 01:31 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Jul 1 01:31 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Jul 1 01:31 token -> ..data/token
root@my-nginx-7ff446c4f4-2d9gz:/#
可以发现使用的 serviceAccount是default,token也是使用的名为defaul-token-***的token,且目录/var/run/secrets/kubernetes.io/serviceaccount/
下有我们上面在default-token里面看到的三个数据项(ca.crt、namespace、token)。综上:每个namespace下有一个名为default的默认的ServiceAccount对象,这个ServiceAccount里有一个名为Tokens的可以作作为Volume一样被Mount到Pod里的Secret,当Pod启动时这个Secret会被自动Mount到Pod的指定目录下,可以使用的secret的相关信息让当前名称空间中所有的pod连接至apiserver,从而保证pod与apiserver之间的通信。
-
一个ServiceAccount可以包括多个Secrets对象:
-
名为Tokens的Secret用于访问API Server的Secret,也被称为ServiceAccountSecret;
-
名为Image Pull secrets的Secret用于下载容器镜像时的认证过程,通常镜像库运行在Insecure模式下,所以这个Secret为空;
用户自定义的其他ServiceAccount,用于权限设置
默认的service account 仅仅只能获取当前Pod自身的相关属性,无法观察到其他名称空间Pod的相关属性信息。如果想要扩展Pod,假设有一个Pod需要用于管理其他Pod或者是其他资源对象(例如dashboard),是无法通过自身的名称空间的default service account进行获取其他Pod的相关属性信息的,此时就需要进行手动创建一个serviceaccount,并在创建Pod时进行定义使用。如下在部署kubernetes-dashboard时我们一般就创建了一个叫做kubernetes-dashboard的Service Account,同时还创建了一个与之对应的secret。
......
containers:
- name: dashboard-metrics-scraper
image: kubernetesui/metrics-scraper:v1.0.4
ports:
- containerPort: 8000
protocol: TCP
livenessProbe:
httpGet:
scheme: HTTP
path: /
port: 8000
initialDelaySeconds: 30
timeoutSeconds: 30
volumeMounts:
- mountPath: /tmp
name: tmp-volume
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 1001
runAsGroup: 2001
serviceAccountName: kubernetes-dashboard
.....
service account的创建
可以通过kubectl explain sa
查看serviceAccount的yaml文件格式。
#yaml文件
[root@k8s-m1 k8s-serviceaccount]# cat serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin
namespace: default
#创建
[root@k8s-m1 k8s-serviceaccount]# kubectl apply -f serviceaccount.yaml
serviceaccount/admin created
#查看
[root@k8s-m1 k8s-serviceaccount]# kubectl get sa admin
NAME SECRETS AGE
admin 1 21s
[root@k8s-m1 k8s-serviceaccount]# kubectl get secrets
NAME TYPE DATA AGE
admin-token-r7w27 kubernetes.io/service-account-token 3 36s
#查看详细信息
[root@k8s-m1 k8s-serviceaccount]# kubectl get sa admin -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"name":"admin","namespace":"default"}}
creationTimestamp: "2023-07-12T03:22:21Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:secrets:
.: {}
k:{"name":"admin-token-r7w27"}:
.: {}
f:name: {}
manager: kube-controller-manager
operation: Update
time: "2023-07-12T03:22:21Z"
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:metadata:
f:annotations:
.: {}
f:kubectl.kubernetes.io/last-applied-configuration: {}
manager: kubectl-client-side-apply
operation: Update
time: "2023-07-12T03:22:21Z"
name: admin
namespace: default
resourceVersion: "86037692"
selfLink: /api/v1/namespaces/default/serviceaccounts/admin
uid: d4568461-5942-433b-a1c5-228e1b2e8125
secrets:
- name: admin-token-r7w27
#可以看到secret就是上面看到的admin开头的secret
可以发现有一个 secret已经被自动创建(其实secret也可以自己手动创建,可参考部署dashboard时使用的yaml文件),使用时需要在 pod 的spec.serviceAccountName 字段中将name设置为您想要用的 service account 名字即可。在 pod 创建之初 service account 就必须已经存在,否则创建将被拒绝。并且需要注意的是不能更新已创建的 pod 的 service account。
serviceaccount的引用
上面在default名称空间创建了一个serviceaccount为admin,可以看到已经自动生成了一个secret(包含token信息) admin-token-r7w27,下面展示如何使用自定义的serviceaccount
[root@k8s-m1 k8s-serviceaccount]# cat use-sa-pod.yml
apiVersion: v1
kind: Pod
metadata:
name: use-sa-pod
namespace: default
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- name: http
containerPort: 80
serviceAccountName: admin
[root@k8s-m1 k8s-serviceaccount]# kubectl apply -f use-sa-pod.yml
pod/use-sa-pod created
[root@k8s-m1 k8s-serviceaccount]# kubectl describe po use-sa-pod
......
Volumes:
admin-token-r7w27:
Type: Secret (a volume populated by a Secret)
SecretName: admin-token-r7w27
Optional: false
......
User Account的定义及使用
在K8S集群当中,每一个用户对资源的访问都是需要通过apiserver进行通信认证才能进行访问的,那么在此机制当中,对资源的访问凭证可以是用token保存,也可以是通过配置文件的方式进行保存,kubectl命令行工具使用kubeconfig文件来查找选择群集并与群集的APIserver进行通信。可以通过kubectl config进行查看编辑kubeconfig配置文件,配置文件路径$HOME/.kube/config文件,eg:/root/.kube/config 如下:
[root@k8s-m1 k8s-serviceaccount]# kubectl config view
apiVersion: v1
clusters: #集群列表
- cluster:
certificate-authority-data: DATA+OMITTED
server: https://192.168.2.250:8443
name: kubernetes
contexts: #上下文列表
- context: #定义哪个集群被哪个用户访问
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
- context:
cluster: kubernetes
user: margu
name: margu@kubernetes
current-context: kubernetes-admin@kubernetes #当前上下文
kind: Config
preferences: {}
users: #用户列表
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
- name: margu
user:
client-certificate-data: REDACTED
在上面的配置文件当中,定义了集群、上下文以及用户。其中Config也是K8S的标准资源之一,在该配置文件当中定义了一个集群列表,指定的集群可以有多个;用户列表也可以有多个,指明集群中的用户;而在上下文列表当中,是进行定义可以使用哪个用户对哪个集群进行访问,以及当前使用的上下文是什么。如图:定义了用户kubernetes-admin可以对kubernetes该集群的访问,用户kubernetes-user1对Cluster1集群的访问。
自建证书和账号进行访问apiserver演示
可用于对不同群体对集群的访问权限控制。过程请参考https://blog.csdn.net/margu_168/article/details/131564406
关于Service Account与Secret相关的一些运行机制
Controller manager创建了ServiceAccountController与Token Controllerl两个安全相关的控制器。
其中ServiceAccountController一直监听Service Account和Namespace的事件,如果一个Namespace中没有default Service Account,那么Service Account Controller就会为该Namespace创建一个默认的(default)的Service Account,这就是我们之前看到的每个namespace下都有一个名为default的ServiceAccount的原因。
如果Controller manager进程在启动时指定了API Server私钥(service-account-private-key-file)参数,那么Controller manager会创建Token Controller。
Token Controller也监听Service Account的事件,如果发现新建的Service Account里没有对应的Service Account Secret,则会用API Server私钥创建一个Token(JWT Token),并用该Token、CA证书Namespace名称等三个信息产生一个新的Secret对象,然后放入刚才的Service Account中;如果监听到的事件是删除Service Account事件,则自动删除与该Service Account相关的所有Secret。
此外,Token Controller对象同时监听Secret的创建、修改和删除事件,并根据事件的不同做不同的处理。
当我们在API Server的鉴权过程中启用了Service Account类型的准入控制器,即在kube-apiserver的启动参数中包括下面的内容时(kubeadm部署的):
–enable-admission-plugins=ServiceAccount
则针对Pod新增或修改的请求,Service Account准入控制器会验证Pod里Service Account是否合法。
- 如果spec.serviceAccount域没有被设置,则Kubernetes默认为其制定名字为default的Serviceaccount;
- 如果Pod的spec.serviceAccount域指定了default以外的ServiceAccount,而该ServiceAccount没有事先被创建,则该Pod将创建失败;
- 如果在Pod中没有单独指定“ImagePullSecrets”,那么该spec.serviceAccount域指定的ServiceAccount的“ImagePullSecrets”会被加入该Pod;
- 给Pod添加一个新的Volume,在该Volume中包含ServiceAccountSecret中的Token,并将Volume挂载到Pod中所有容器的指定目录下(/var/run/secrets/kubernetes.io/serviceaccount);
所以,通过上面的介绍我们知道ServiceAccount正常运行需要以下几个控制器:
Admission Controller
Service Account Controller
Token Controller
更多关于kubernetes的知识分享,请前往博客主页。编写过程中,难免出现差错,敬请指出