ServiceAccount深度解析

ServiceAccount为Pod钟的进程提供身份信息。当用户访问集群时(例如使用kubectl命令的时候),apiserver会将用户认证为一个特定的User Account(目前通常是admin,除非系统管理员自定义了集群配置)。Pod容器中的进程也可以与apiserver联系,当他们在连接apiserver的时候,他们会被认证为一个特定的service account(例如default)。

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(除非指定了其他ServiceAccount)

    • 验证Pod引用的service account已经存在,否则拒绝创建

    • 如果Pod没有指定ImagePullSecretes,则把service account的ImagePullSecretes加到Pod中

      什么是ImagePullSecretes?

      是k8s中用于拉去私有容器镜像的机制。当你在集群中部署使用私有容器镜像的Pod时,通常需要提供身份验证凭据以获取访问权限。

      ImagePullSecrets 是一个或多个凭据的集合,用于访问私有的 Docker 镜像仓库。这些凭据通常是通过 Kubernetes 中的 Secret 对象来存储,并在 Pod 配置中引用。Pod 使用这些凭据来获取镜像仓库中的镜像,以确保能够成功拉取私有镜像。

      以下是一个示例,展示了如何在 Pod 配置中使用 ImagePullSecrets

      yamlCopy codeapiVersion: v1
      kind: Pod
      metadata:
        name: my-app
      spec:
        containers:
          - name: my-container
            image: private-registry.com/my-image:latest
        imagePullSecrets:
          - name: my-secret
      

      在上面的示例中,imagePullSecrets 部分指定了一个名为 my-secretSecret,这个 Secret 包含了访问私有镜像仓库所需的凭据。这样,Pod 就可以使用这个凭据来拉取 private-registry.com/my-image:latest 镜像。

      要使用 ImagePullSecrets,你需要执行以下步骤:

      1. 创建一个 Secret 对象,其中包含访问私有镜像仓库的凭据(如用户名和密码)。
      2. 在 Pod 的配置中的 imagePullSecrets 部分引用这个 Secret
      3. 当 Pod 启动时,Kubernetes 将使用 ImagePullSecrets 中的凭据来拉取所需的私有镜像。

      总之,ImagePullSecrets 是 Kubernetes 中用于访问私有容器镜像的机制,它允许在 Pod 配置中引用存储在 Secret 中的凭据,以确保能够拉取私有镜像。

    • 每个container启动后都会挂载该service account的token和ca.crt到/var/run/secrets/kubernetes.io/serviceaccount/

在 Kubernetes 中,每个 Service Account 都有一个关联的安全令牌(Token),用于对 Kubernetes API 进行身份验证,以及一个与集群通信的根证书(ca.crt)。这些元素有助于确保 Service Account 在与 Kubernetes API 交互时的安全性。

  1. Token(令牌):每个 Service Account 都会被分配一个安全令牌,用于身份验证。这个令牌是一个长字符串,它允许 Service Account 访问 Kubernetes API 中特定于命名空间的资源。当 Service Account 的 Pod 尝试与 API 服务器通信时,它会使用这个令牌进行身份验证,以获取访问权限。令牌会自动挂载到 Pod 中的 /var/run/secrets/kubernetes.io/serviceaccount/token 文件中,供应用程序使用。
  2. ca.crt(根证书):与每个 Service Account 关联的根证书用于验证与 Kubernetes API 服务器之间的通信的安全性。根证书是集群的 CA(Certificate Authority)颁发的,用于加密和验证通信。在与 Kubernetes API 服务器进行通信时,Pod 可以使用这个根证书来验证服务器证书的有效性,确保通信的机密性和完整性。

这两个元素结合在一起,确保了 Service Account 的安全性和通信的安全性。Pod 使用令牌来证明自己的身份,同时使用根证书来验证服务器的身份,以确保与 Kubernetes API 服务器的交互是安全的。

请注意,Service Account 的 Token 和根证书是自动生成的,并在创建 Service Account 时为其分配。这些元素对于运行在 Kubernetes 集群中的应用程序来说是透明的,但它们在底层提供了身份验证和通信安全性的支持。

使用默认的Service Account访问API Server

当创建 pod 的时候,如果没有指定一个 service account,系统会自动得在与该pod 相同的 namespace 下为其指派一个default service account。如果获取刚创建的 pod 的原始 json 或 yaml 信息(例如使用kubectl get pods podename -o yaml命令),将看到spec.serviceAccountName字段已经被设置为 default。

[root@k8s-master ~]# kubectl get pods
NAME                     READY     STATUS    RESTARTS   AGE
filebeat-ds-hxgdx        1/1       Running   1          34d
filebeat-ds-s466l        1/1       Running   2          34d
myapp-0                  1/1       Running   0          3h
myapp-1                  1/1       Running   0          3h
myapp-2                  1/1       Running   0          4h
myapp-3                  1/1       Running   0          4h
pod-vol-demo             2/2       Running   0          2d
redis-5b5d6fbbbd-q8ppz   1/1       Running   1          2d
[root@k8s-master ~]# kubectl get pods/myapp-0 -o yaml |grep "serviceAccountName"
  serviceAccountName: default

[root@k8s-master ~]# kubectl describe pods myapp-0
Name:               myapp-0
Namespace:          default
......
Volumes:
  ......
  default-token-j5pf5:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-j5pf5
    Optional:    false

从上面可以看到每个Pod无论定义与否都会有一个存储卷,这个存储卷为default-token-*** token令牌,这就是service account是为Pod中的进程与apiserver联系提供身份认证信息。通过secret进行定义,由于认证信息属于敏感信息,所以需要保存在secret资源当中,并以存储卷的方式挂载到Pod当中。从而让Pod内运行的应用通过对应的secret中的service account来连接apiserver,并完成认证。每个 namespace 中都有一个默认的叫做 default 的 service account资源。可以使用kubectl get secret命令当前名称空间内的secret,也可以看到对应的default-token。可以使用的预制认证信息让当前名称空间中所有的pod连接至apiserver,从而保证pod与apiserver之间的通信。

[root@k8s-master ~]# kubectl get sa
NAME      SECRETS   AGE
default    1         50d
[root@k8s-master ~]# kubectl get sa -n ingress-nginx  #前期创建的ingress-nginx名称空间也存在这样的serviceaccount
NAME                           SECRETS   AGE
default                        1         11d
nginx-ingress-serviceaccount   1         11d
[root@k8s-master ~]# kubectl get secret
NAME                    TYPE                                  DATA      AGE
default-token-j5pf5     kubernetes.io/service-account-token   3         50d
mysecret                Opaque                                2         1d
tomcat-ingress-secret   kubernetes.io/tls                     2         10d
[root@k8s-master ~]# kubectl get secret -n ingress-nginx
NAME                                       TYPE                                  DATA      AGE
default-token-zl49j                        kubernetes.io/service-account-token   3         11d
nginx-ingress-serviceaccount-token-mcsf4   kubernetes.io/service-account-token   3         11d

默认的service account仅仅只能获取当前Pod自身的相关属性,无法观察到其他名称空间Pod的相关属性信息。如果想要拓展Pod,假设有一个Pod需要用于管理其他Pod或者是其他资源对象(例如dashboard),是无法通过自身的名称空间的default service account进行获取其他Pod的相关属性信息的,此时就需要进行手动创建一个serviceaccount,并在创建Pod时进行定义。

创建一个ServiceAccount

[root@master-1 app]# vim serviceaccount.yaml 
apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin
  namespace: default

[root@master-1 app]# kubectl apply -f serviceaccount.yaml 
serviceaccount/admin created

[root@master-1 app]# kubectl get sa
NAME              SECRETS   AGE
admin             1         31s
default           1         20d
nfs-provisioner   1         18d

[root@master-1 app]# kubectl get secret
NAME                          TYPE                                  DATA   AGE
admin-token-j7n8j             kubernetes.io/service-account-token   3      27m
default-token-zmv4x           kubernetes.io/service-account-token   3      20d
mysecret                      Opaque                                2      19d
nfs-provisioner-token-xjn7p   kubernetes.io/service-account-token   3      18d

[root@master-1 app]# 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: "2019-02-12T03:07:07Z"
  name: admin
  namespace: default
  resourceVersion: "2606053"
  selfLink: /api/v1/namespaces/default/serviceaccounts/admin
  uid: 48250903-2e73-11e9-a8c7-d8490b8af3ae
secrets:
- name: admin-token-j7n8j

看到有一个 token 已经被自动创建,只需要在 pod 的spec.serviceAccountName 字段中将name设置为您想要用的 service account 名字即可。在 pod 创建之初 service account 就必须已经存在,否则创建将被拒绝。需要注意的是不能更新已创建的 pod 的 service account

ServiceAccount的自定义使用

这里在default名称空间创建了一个serviceaccount为admin,可以看到已经自动生成了一个Tokens:admin-token-j7n8j,下面展示如何使用自定义的serviceaccount

[root@master-1 app]# vim pod-sa-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-sa-demo
  namespace: default
  labels:
    app: myapp
    tier: frontend
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    ports:
    - name: http
      containerPort: 80
  serviceAccountName: admin
[root@master-1 app]# kubectl apply -f pod-sa-demo.yaml 
pod/pod-sa-demo created
[root@master-1 app]# kubectl describe pods pod-sa-demo
......
Volumes:
  admin-token-j7n8j:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  admin-token-j7n8j
    Optional:    false
......

User Account的定义以及使用

在K8S集群当中,每一个用户对资源的访问都是需要通过apiserver进行通信认证才能进行访问的,那么在此机制当中,对资源的访问可以是token,也可以是通过配置文件的方式进行保存和使用认证信息,kubectl命令行工具使用kubeconfig文件来查找选择群集并与群集的APIserver进行通信。可以通过kubectl config进行查看编辑kubeconfig配置文件,配置文件路径$HOME/.kube/config文件,eg:/root/.kube/config 如下:

[root@k8s-master mainfests]# kubectl config view
apiVersion: v1
clusters:  #集群列表
- cluster:
    certificate-authority-data: REDACTED
    server: https://192.168.56.11: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

在上面的配置文件当中,定义了集群、上下文以及用户。其中Config也是K8S的标准资源之一,在该配置文件当中定义了一个集群列表,指定的集群可以有多个;用户列表也可以有多个,指明集群中的用户;而在上下文列表当中,是进行定义可以使用哪个用户对哪个集群进行访问,以及当前使用的上下文是什么。如图:定义了用户kubernetes-admin可以对kubernetes该集群的访问,用户kubernetes-user1对Cluster1集群的访问。

在这里插入图片描述

自建证书和账号进行访问apiserver演示

注意:此步骤在/etc/kubernetes/pki路径下执行,因为需要用到Kubernetes CA证书。

  1. 生成证书

    [root@k8s-master pki]# (umask 077;openssl genrsa -out magedu.key 2048)
    Generating RSA private key, 2048 bit long modulus
    ............................................................................................+++
    ...................................................................................+++
    e is 65537 (0x10001)
    
    [root@k8s-master pki]# ll magedu.key 
    -rw------- 1 root root 1675 Oct 12 23:52 magedu.key
    
    
  2. 使用ca.crt进行签署

    [root@k8s-master pki]# openssl req -new -key magedu.key -out magedu.csr -subj "/CN=magedu"  证书签署请求(CN表示用户名,O表示组)
    
    [root@k8s-master pki]# openssl x509 -req -in magedu.csr -CA ./ca.crt -CAkey ./ca.key -CAcreateserial -out magedu.crt -days 365  #证书签署
    Signature ok
    subject=/CN=magedu
    Getting CA Private Key
    
    [root@k8s-master pki]# openssl x509 -in magedu.crt -text -noout
    

    生成证书和签署证书是证书颁发的两个不同步骤,这两个步骤涉及到不同的操作和角色,用于创建和验证数字证书的完整新和合法性。

    • 生成证书:在这个步骤中,证书的申请者(通常是实体,服务器胡总和客户端)生成一个密钥对,包括一个私钥和一个公钥。 私钥用于加密和解密数据,而公钥用于加密和验证签名。然后证书申请者会将公约和一些标识信息(如域名,组织名称等)提供给证书颁发机构(CA)
    • 签署证书:在这个步骤中,证书颁发机构(CA)使用其自己的私钥来对证书申请者提供的公钥和标识信息进行签名。这个签名包括一些元数据,比如颁发者,有效期,颁发日期等。
  3. 添加到用户认证

    [root@k8s-master pki]# kubectl config set-credentials magedu --client-certificate=./magedu.crt --client-key=./magedu.key --embed-certs=true
    User "magedu" set.
    
    [root@k8s-master pki]# kubectl config set-context magedu@kubernetes --cluster=kubernetes --user=magedu
    Context "magedu@kubernetes" created.
    
    [root@k8s-master pki]# kubectl config view
    apiVersion: v1
    clusters:
    - cluster:
        certificate-authority-data: REDACTED
        server: https://192.168.56.11:6443
      name: kubernetes
    contexts:
    - context:
        cluster: kubernetes
        user: kubernetes-admin
      name: kubernetes-admin@kubernetes
    - context:
        cluster: kubernetes
        user: magedu
      name: magedu@kubernetes
    current-context: kubernetes-admin@kubernetes
    kind: Config
    preferences: {}
    users:
    - name: kubernetes-admin
      user:
        client-certificate-data: REDACTED
        client-key-data: REDACTED
    - name: magedu
      user:
        client-certificate-data: REDACTED
        client-key-data: REDACTED
    
    [root@k8s-master pki]# kubectl config use-context magedu@kubernetes
    Switched to context "magedu@kubernetes".
    
    [root@k8s-master pki]# kubectl get pods
    No resources found.
    Error from server (Forbidden): pods is forbidden: User "magedu" cannot list pods in the namespace "default"