Build your Service Mesh: Admission Controller

Creating the Admission Controller

Before kube-apiserver persists the object and later be scheduled to a node,
the Admission Controller can validate and mutate the object.

The Mutation Admission Controller is going to mutate the pods that h…


This content originally appeared on DEV Community and was authored by Ramón Berrutti

Creating the Admission Controller

Before kube-apiserver persists the object and later be scheduled to a node,
the Admission Controller can validate and mutate the object.

The Mutation Admission Controller is going to mutate the pods that have the the
annotation diy-service-mesh: true.

Let's dig into how the Admission Controller works.

Admission Controller Flow

Admission Controller Flow

  1. kube-controller or kubectl sends a request to the kube-apiserver to create a pod.
  2. The kube-apiserver sends the request to the Admission Controller. In this case the proxy-injector.
  3. The proxy-injector returns the mutated patch to the kube-apiserver.
  4. Kube-apiserver persists the object in the etcd if the object is valid.
  5. Kube-scheduler will schedule the pod to a node.
  6. Kube-scheduler returns an available node to the kube-apiserver or an error if the pod can't be scheduled.
  7. The kube-apiserver will store the object in the etcd with the selected node.
  8. The kubelet in the selected node will create the pod in the container runtime.

Admission Controller Code

Full code of the Admission Controller: injector

The mutate function processes the AdmissionReview object and returns the AdmissionResponse object with the mutated patch.

func mutate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
    req := ar.Request
    // Ignore all requests other than pod creation.
    if req.Operation != admissionv1.Create || req.Kind.Kind != "Pod" {
        return &admissionv1.AdmissionResponse{
            UID:     req.UID,
            Allowed: true,
        }
    }

    var pod v1.Pod
    // Unmarshal the raw object to the pod.
    if err := json.Unmarshal(req.Object.Raw, &pod); err != nil {
        return &admissionv1.AdmissionResponse{
            UID: req.UID,
            Result: &metav1.Status{
                Message: err.Error(),
            },
        }
    }

    // Check if the pod contains the inject annotation.
    if v, ok := pod.Annotations["diy-service-mesh/inject"]; !ok || strings.ToLower(v) != "true" {
        return &admissionv1.AdmissionResponse{
            UID:     req.UID,
            Allowed: true,
        }
    }

    // Add the initContainer to the pod.
    pod.Spec.InitContainers = append(pod.Spec.InitContainers, v1.Container{
        Name:            "proxy-init",
        Image:           os.Getenv("IMAGE_TO_DEPLOY_PROXY_INIT"),
        ImagePullPolicy: v1.PullAlways,
        SecurityContext: &v1.SecurityContext{
            Capabilities: &v1.Capabilities{
                Add:  []v1.Capability{"NET_ADMIN", "NET_RAW"},
                Drop: []v1.Capability{"ALL"},
            },
        },
    })

    // Add the sidecar container to the pod.
    pod.Spec.Containers = append(pod.Spec.Containers, v1.Container{
        Name:            "proxy",
        Image:           os.Getenv("IMAGE_TO_DEPLOY_PROXY"),
        ImagePullPolicy: v1.PullAlways,
        SecurityContext: &v1.SecurityContext{
            RunAsUser:    func(i int64) *int64 { return &i }(1337),
            RunAsNonRoot: func(b bool) *bool { return &b }(true),
        },
    })

    patch := []map[string]any{
        {
            "op":    "replace",
            "path":  "/spec/initContainers",
            "value": pod.Spec.InitContainers,
        },
        {
            "op":    "replace",
            "path":  "/spec/containers",
            "value": pod.Spec.Containers,
        },
    }

    podBytes, err := json.Marshal(patch)
    if err != nil {
        return &admissionv1.AdmissionResponse{
            UID: req.UID,
            Result: &metav1.Status{
                Message: err.Error(),
            },
        }
    }

    patchType := admissionv1.PatchTypeJSONPatch
    return &admissionv1.AdmissionResponse{
        UID:     req.UID,
        Allowed: true,
        AuditAnnotations: map[string]string{
            "proxy-injected": "true",
        },
        Patch:     podBytes,
        PatchType: &patchType,
    }
}

IMAGE_TO_DEPLOY_PROXY_INIT and IMAGE_TO_DEPLOY_PROXY are environment variables that tilt will update with the last proxy-init and proxy image respectively.

For complex patch use thi library: https://github.com/evanphx/json-patch

Deploying the Admission Controller

MutatingWebhookConfiguration tells the kube-apiserver to send the pod creation requests to the injector.

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: service-mesh-injector-webhook
webhooks:
- name: service-mesh-injector.service-mesh.svc
  clientConfig:
    service:
      name: service-mesh-injector
      namespace: service-mesh
      path: "/inject"
  rules:
  - operations: ["CREATE"]
    apiGroups: [""]
    apiVersions: ["v1"]
    resources: ["pods"]
  admissionReviewVersions: ["v1"]
  sideEffects: None
  timeoutSeconds: 5

Some important points:

  • caBundle in the clientConfig is missing. This is a necessary field because the kube-apiserver only calls the webhook if the certificate is valid.
  • Two jobs, injector-admission-create injector-admission-patch are going generate the certificates and patch the MutatingWebhookConfiguration with the caBundle.
  • The rules options allow to filter the objects that are going to be sent to the injector.

The file injector.yaml contains the nesesary resources
including the Service Account, Role, RoleBinding, ClusterRole, ClusterRoleBinding,
Service, Deployment and the Job to generate the certificates.

Testing the Admission Controller

Let's modify the http-client and http-server deployments to add the annotation diy-service-mesh/inject: "true".

spec:
  replicas: 1
  selector:
    matchLabels:
      app: http-client
  template:
    metadata:
      labels:
        app: http-client
      annotations:
        diy-service-mesh/inject: "true"
    spec:

Important: the annotation needs to be added to the pod template and not to the deployment.


This content originally appeared on DEV Community and was authored by Ramón Berrutti


Print Share Comment Cite Upload Translate Updates
APA

Ramón Berrutti | Sciencx (2024-06-22T20:26:49+00:00) Build your Service Mesh: Admission Controller. Retrieved from https://www.scien.cx/2024/06/22/build-your-service-mesh-admission-controller/

MLA
" » Build your Service Mesh: Admission Controller." Ramón Berrutti | Sciencx - Saturday June 22, 2024, https://www.scien.cx/2024/06/22/build-your-service-mesh-admission-controller/
HARVARD
Ramón Berrutti | Sciencx Saturday June 22, 2024 » Build your Service Mesh: Admission Controller., viewed ,<https://www.scien.cx/2024/06/22/build-your-service-mesh-admission-controller/>
VANCOUVER
Ramón Berrutti | Sciencx - » Build your Service Mesh: Admission Controller. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/06/22/build-your-service-mesh-admission-controller/
CHICAGO
" » Build your Service Mesh: Admission Controller." Ramón Berrutti | Sciencx - Accessed . https://www.scien.cx/2024/06/22/build-your-service-mesh-admission-controller/
IEEE
" » Build your Service Mesh: Admission Controller." Ramón Berrutti | Sciencx [Online]. Available: https://www.scien.cx/2024/06/22/build-your-service-mesh-admission-controller/. [Accessed: ]
rf:citation
» Build your Service Mesh: Admission Controller | Ramón Berrutti | Sciencx | https://www.scien.cx/2024/06/22/build-your-service-mesh-admission-controller/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.