I am trying to get my head around Kubernetes Admission Controllers and trying to mutate a pod to include an init container and a volume in which all containers will mount.
The volume gets created and the init container mounts this volume but the original containers I can not get to mount the new volume. I am using Golang to write the webhook and using Json patch to mutate the pod. If anyone could point me in the right direction that would be great!
func serve(w http.ResponseWriter, r *http.Request) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
glog.Error(err.Error())
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if len(body) == 0 {
glog.Error("the request body is empty")
http.Error(w, "the request body is empty", http.StatusBadRequest)
return
}
if r.Header.Get("Content-Type") != "application/json" {
glog.Errorf("the content-type is %s, but expect application/json", r.Header.Get("Content-Type"))
http.Error(w, "invalid content-type, expect `application/json`", http.StatusUnsupportedMediaType)
return
}
request := v1beta1.AdmissionReview{}
if _, _, err := universalDeserializer.Decode(body, nil, &request); err != nil {
glog.Errorf("could not deserialize request: %s", err)
w.WriteHeader(http.StatusBadRequest)
return
}
if request.Request == nil {
glog.Error("could not find admission review in the request body")
w.WriteHeader(http.StatusBadRequest)
return
}
response := v1beta1.AdmissionReview{Response: &v1beta1.AdmissionResponse{UID: request.Request.UID}}
response.Response.Allowed = true
var pod corev1.Pod
if err := json.Unmarshal(request.Request.Object.Raw, &pod); err != nil {
glog.Errorf("Could not unmarshal raw object to a pod object: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
required, err := mutationRequired(&pod.ObjectMeta)
if err != nil {
glog.Error(err)
}
if required {
pbytes, _ := createPatch(&pod)
response.Response.Patch = pbytes
}
reviewBytes, err := json.Marshal(response)
if err != nil {
glog.Error(err)
http.Error(w, fmt.Sprintf("problem marshaling json: %v", err), http.StatusInternalServerError)
}
if _, err := w.Write(reviewBytes); err != nil {
glog.Errorf("error writting response back to kubernetes: %v", err)
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
}
}
func mutationRequired(m *metav1.ObjectMeta) (bool, error) {
for _, n := range config.AllowedNamespaces {
if m.Namespace != n {
glog.Infof("request from pod %s is from namespace %s which is allowed", m.Name, m.Namespace)
a := m.GetAnnotations()
if len(a) < 1 {
glog.Infof("pod %s has no annotations so mutation is not required", m.Name)
return false, nil
}
if _, ok := a[enableKey]; ok {
glog.Infof("annotation: %s found with value %s", enableKey, a[enableKey])
b, err := strconv.ParseBool(a[enableKey])
if err != nil {
glog.Errorf("was unable to parse the annotation value: %s", err)
return false, err
}
return b, nil
}
glog.Infof("pod %s has does not have the enable annotation so mutation not required", m.Name)
}
}
glog.Infof("request from %s is coming from namespace %s which is not allowed", m.Name, m.Namespace)
return false, nil
}
func addCredentialVolume() (patch patchOperation) {
volume := corev1.Volume{
Name: "vault-creds",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{
Medium: "Memory",
},
},
}
value := []corev1.Volume{volume}
return patchOperation{
Op: "add",
Path: "/spec/volumes",
Value: value,
}
}
// this is the part not working
func updateVolumeMounts(p *corev1.Pod) (patch patchOperation) {
vmount := corev1.VolumeMount{
Name: "vault-creds",
MountPath: "/creds",
ReadOnly: true,
}
for _, c := range p.Spec.Containers {
c.VolumeMounts = append(c.VolumeMounts, vmount)
}
return patchOperation{
Op: "replace",
Path: "/spec/containers",
Value: p.Spec.Containers,
}
}
func addInitContainer(p *corev1.Pod) (patch patchOperation) {
req := corev1.ResourceList{
"cpu": resource.MustParse("10m"),
"memory": resource.MustParse("20Mi"),
}
lim := corev1.ResourceList{
"cpu": resource.MustParse("30m"),
"memory": resource.MustParse("50Mi"),
}
vault := corev1.Container{
Name: "vault-init",
Image: config.Image,
ImagePullPolicy: "Always",
Resources: corev1.ResourceRequirements{
Requests: req,
Limits: lim,
},
VolumeMounts: []corev1.VolumeMount{
corev1.VolumeMount{
Name: "vault-creds",
MountPath: "/creds",
},
},
Command: []string{"echo", "'Hello' > /creds/cred.txt"},
}
p.Spec.InitContainers = append(p.Spec.InitContainers, vault)
return patchOperation{
Op: "add",
Path: "/spec/initContainers",
Value: p.Spec.InitContainers,
}
}
func createPatch(p *corev1.Pod) ([]byte, error) {
var patch []patchOperation
patch = append(patch, addInitContainer(p))
patch = append(patch, addCredentialVolume())
patch = append(patch, updateVolumeMounts(p))
return json.Marshal(patch)
}