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 default
default
ServiceAccount; - 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/serviceaccount
Directory;
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