Overview

This article based on k8s the release 1.17 branch code, the code is located in the plugin/PKG/admission/serviceaccount directory, code: admission. Go.

Api-server, as a common server application, includes Authentication module, Authorization module and Admission Plugin(which can be understood as the middleware pipeline of request module). And storage dependency Etcd. For the access plug-in, enable-admission-plugins must contain the ServiceAccount access controller to enable the middleware when the API-server process is started. See the official document: Enable – admission – plugins. The ServiceAccount Admission Plugin provides the following functions:

  • If the submitted POD Yaml does not specify a spec.serviceAccountName field value, the plug-in will add the defaultdefaultServiceAccount;
  • Check whether the service account specified by spec.serviceAccountName exists. If the service account does not exist, reject the request.
  • Create a volume for the POD. The volume source is SecretVolumeSource. The secret comes from the Secret referenced by the Service Account object.
  • If the submitted POD yamL does not specify the spec.ImagePullSecrets field value, the service Account object reference ImagePullSecrets field value will be filled, and the volume will be mounted to the pod/var/run/secrets/kubernetes.io/serviceaccountDirectory;

For example, submit a POD object to the apI-server process:

echo > pod.yaml <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: serviceaccount-admission-plugin
  labels:
    app: serviceaccount-admission-plugin
spec:
  containers:
    - name: serviceaccount-admission-plugin
      image: nginx:1.17.8
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 80
          name: "http-server"
EOF

kubectl apply -f ./pod.yaml
kubectl get pod/serviceaccount-admission-plugin -o yaml
kubectl get sa default -o yaml
Copy the code

After the Pod object is processed by the ServiceAccount Admission Plugin, spec.serviceAccountName specifies the default ServiceAccount; Added a SecretVolumeSource Volume. The Volume name is the name of ServiceAccount secrets. The mount to the pod/var/run/secrets/kubernetes. IO/serviceaccount directory; Because pod and default service account do not specify the ImagePullSecrets value, pod spec.ImagePullSecrets has no value:

In addition, the secret name specified by volume is the name value of the secrets of the default service account:

So, how does the ServiceAccount Admission Controller, or ServiceAccount middleware, do it?

The source code parsing

The ApI-Server framework defines Admission Controllers in plug-in form and calls the plug-in’s Admit() method, just like the middleware modules commonly used by server-side frameworks. To determine whether the current request goes through the access controller.

AdmissionController Indicates the access controller instance

Note the following: If MountServiceAccountToken is true, the mount volume operation is performed by default and the volume is mounted to the pod default directory. The current access controller is performed only when the resource operation is the Create operation. See code l103-L121:

// Register with plugin chain
func Register(plugins *admission.Plugins) {
  plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
    serviceAccountAdmission := NewServiceAccount()
    return serviceAccountAdmission, nil})}// Controller is initialized
func NewServiceAccount(a) *Plugin {
    return &Plugin{
        Handler: admission.NewHandler(admission.Create), // This plugin is executed when Create manipulates resources
        LimitSecretReferences: false,
        MountServiceAccountToken: true,
        RequireAPIToken: true,
        generateName: names.SimpleNameGenerator.GenerateName, // Required when generating volume mount name}}Copy the code

Admit operation

The Admit operation is the core logic of the middleware, and the main work has been described in detail above. Here we learn from the point of view of the code, see: L160-L248.

ServiceAccount check

If pod Yaml does not specify a ServiceAccount, set the default ServiceAccount object to the default ServiceAccount object, and check whether the ServiceAccount exists in the current namespace:

func (s *Plugin) Admit(/ *... * /) (err error) {
     // ... 
    // If not specified, set the default value
    if len(pod.Spec.ServiceAccountName) == 0 {
        pod.Spec.ServiceAccountName = DefaultServiceAccountName
    }
    // Check whether the ServiceAccount really exists
    serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)

    // Check whether the volume can be mounted. The default value is yes
    if s.MountServiceAccountToken && shouldAutomount(serviceAccount, pod) {
      / / will create a new secret source type of volume, and the mount to each container "/ var/run/secrets/kubernetes. IO/serviceaccount" directory
      iferr := s.mountServiceAccountToken(serviceAccount, pod); err ! =nil {
        // ...}}ImagePullSecrets = ImagePullSecrets = ImagePullSecrets = ImagePullSecrets = ImagePullSecrets = ImagePullSecrets
    if len(pod.Spec.ImagePullSecrets) == 0 {
      pod.Spec.ImagePullSecrets = make([]api.LocalObjectReference, len(serviceAccount.ImagePullSecrets))
      for i := 0; i < len(serviceAccount.ImagePullSecrets); i++ {
        pod.Spec.ImagePullSecrets[i].Name = serviceAccount.ImagePullSecrets[i].Name
      }
    }
    
    // Check whether the ServiceAccount really exists
    return s.Validate(ctx, a, o)
}
Copy the code

The ServiceAccount check logic is simple. The main purpose of the ServiceAccount check is to fill in the ServiceAccount value for the POD. The ServiceAccount is used by the POD to call the apI-server process

Mount Volume

The Mount Volume core creates a Volume and mounts it to a specified directory in each pod container. This directory contains ca. CRT, namespace, and token files for pod to use when calling apI-server. How to create volume and mount L426-L567:

const (
    DefaultAPITokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
)
// Create a secret Source volume and mount it to the pod object
func (s *Plugin) mountServiceAccountToken(serviceAccount *corev1.ServiceAccount, pod *api.Pod) error {
    Secret = serviceAccount.secrets = secret
    Kubernetes. IO /service-account-token ="kubernetes. IO /service-account-token"
    Type ="kubernetes. IO /service-account-token" https://kubernetes.io/zh/docs/concepts/configuration/secret/#service-account-token-secrets
    serviceAccountToken, err := s.getReferencedServiceAccountToken(serviceAccount)
    
    // If the volumes in pod already reference secret as the volume, skip it
    // ...

    // Determine a volume name for the ServiceAccountTokenSecret in case we need it
    if len(tokenVolumeName) == 0 {
        // The serviceAccountToken prefix is added with a random character string to generate a volume name
    }

    / / here to mount to pod each container of the mount path is "/ var/run/secrets/kubernetes. IO/serviceaccount"
    volumeMount := api.VolumeMount{
        Name:      tokenVolumeName,
        ReadOnly:  true,
        MountPath: DefaultAPITokenMountPath,
    }

    // InitContainers and Containers need to mount the new volume
    needsTokenVolume := false
    for i, container := range pod.Spec.InitContainers {
        // ...
    }
    for i, container := range pod.Spec.Containers {
        // ...
    }

    // Add the new volume to pod Volumes
    if! hasTokenVolume && needsTokenVolume { pod.Spec.Volumes =append(pod.Spec.Volumes, s.createVolume(tokenVolumeName, serviceAccountToken))
    }
    return nil
}
// Create a volume object
func (s *Plugin) createVolume(tokenVolumeName, secretName string) api.Volume {
    // ...
  return api.Volume{
      Name: tokenVolumeName,
      VolumeSource: api.VolumeSource{
          Secret: &api.SecretVolumeSource{
          SecretName: secretName,
        },
      },
    }
  }
Copy the code

Mount Volume logic is also simple. Create a Volume for pod and Mount it to the specified path of each container. The data in this volume is obtained from the secrets data referenced by the ServiceAccount, that is, ca.crt, Namespace, and Token data files. The data is the authentication data required for invoking apI-server. The token data has already been signed by the private key file.

So, where do these Secret objects come from when you create ServiceAccount? The token file in secret has been signed by the private key, so apI-server must need the corresponding public key file to verify the signature. The secret object is created by the TokenController of the ServiceAccount module in Kube-Controller-Manager, and is signed with a private key. So kube – controller – the manager must take the private key parameters when starting – service – account – private – key – file, concrete website service – account – private – key – the file; Kube-apiserver starts with the public key parameter service-account-key-file. Kube-apiserver starts with the public key parameter service-account-key-file. Kube-apiserver starts with the public key parameter service-account-key-file

conclusion

This paper analyzes the main business logic of ServiceAccount Admission Controller middleware, and how to supplement ServiceAccount and imagePullSecrets field data for POD objects. Create and mount a Service Account Volume for POD to call apI-server. The overall logic is relatively simple, the source code is worth learning, for their secondary development of K8S reference learning.

Reference documentation

Serviceaccounts – Controller source code official website parsing

Configure the service account for the Pod

Service account token Secret

admission.go

Kubernetes Proposal – Admission Control