internal/controllers/synthesis/pod.go (188 lines of code) (raw):
package synthesis
import (
"slices"
"github.com/imdario/mergo"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
apiv1 "github.com/Azure/eno/api/v1"
"github.com/Azure/eno/internal/manager"
)
const (
compositionNameLabelKey = "eno.azure.io/composition-name"
compositionNamespaceLabelKey = "eno.azure.io/composition-namespace"
synthesisIDLabelKey = "eno.azure.io/synthesis-uuid"
)
func newPod(cfg *Config, comp *apiv1.Composition, syn *apiv1.Synthesizer) *corev1.Pod {
pod := &corev1.Pod{}
pod.GenerateName = "synthesis-"
pod.Namespace = cfg.PodNamespace
pod.Labels = map[string]string{
compositionNameLabelKey: comp.Name,
compositionNamespaceLabelKey: comp.Namespace,
synthesisIDLabelKey: comp.Status.InFlightSynthesis.UUID,
manager.ManagerLabelKey: manager.ManagerLabelValue,
}
for k, v := range syn.Spec.PodOverrides.Labels {
pod.Labels[k] = v
}
pod.Annotations = map[string]string{}
for k, v := range syn.Spec.PodOverrides.Annotations {
pod.Annotations[k] = v
}
env := []corev1.EnvVar{
{
Name: "COMPOSITION_NAME",
Value: comp.Name,
},
{
Name: "COMPOSITION_NAMESPACE",
Value: comp.Namespace,
},
{
Name: "SYNTHESIS_UUID",
Value: comp.Status.InFlightSynthesis.UUID,
},
{
Name: "IMAGE",
Value: syn.Spec.Image,
},
}
for _, ev := range filterEnv(env, comp.Spec.SynthesisEnv) {
env = append(env, corev1.EnvVar{Name: ev.Name, Value: ev.Value})
}
pod.Spec = corev1.PodSpec{
ServiceAccountName: cfg.PodServiceAccount,
RestartPolicy: corev1.RestartPolicyOnFailure,
Affinity: &corev1.Affinity{
PodAntiAffinity: &corev1.PodAntiAffinity{
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{{
Weight: 100,
PodAffinityTerm: corev1.PodAffinityTerm{
TopologyKey: "kubernetes.io/hostname",
LabelSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{
manager.ManagerLabelKey: manager.ManagerLabelValue,
},
},
},
}},
},
},
InitContainers: []corev1.Container{{
Name: "synth-installer",
Image: cfg.ExecutorImage,
Command: []string{"/eno-controller", "install-executor"},
VolumeMounts: []corev1.VolumeMount{{
Name: "sharedfs",
MountPath: "/eno",
}},
}},
Containers: []corev1.Container{{
Name: "executor",
Image: syn.Spec.Image,
Command: []string{"/eno/executor"},
VolumeMounts: []corev1.VolumeMount{{
Name: "sharedfs",
MountPath: "/eno",
}},
Resources: syn.Spec.PodOverrides.Resources,
Env: env,
SecurityContext: &corev1.SecurityContext{
AllowPrivilegeEscalation: ptr.To(false),
ReadOnlyRootFilesystem: ptr.To(true),
RunAsUser: ptr.To(int64(65532)),
RunAsGroup: ptr.To(int64(65532)),
RunAsNonRoot: ptr.To(true),
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{"ALL"},
},
SeccompProfile: &corev1.SeccompProfile{
Type: corev1.SeccompProfileTypeRuntimeDefault,
},
},
}},
Volumes: []corev1.Volume{{
Name: "sharedfs",
VolumeSource: corev1.VolumeSource{
EmptyDir: &corev1.EmptyDirVolumeSource{
Medium: corev1.StorageMediumMemory,
},
},
}},
}
if cfg.TaintTolerationKey != "" {
toleration := corev1.Toleration{
Key: cfg.TaintTolerationKey,
Operator: corev1.TolerationOpExists,
Effect: corev1.TaintEffectNoSchedule,
}
if cfg.TaintTolerationValue != "" {
toleration.Operator = corev1.TolerationOpEqual
toleration.Value = cfg.TaintTolerationValue
}
pod.Spec.Tolerations = append(pod.Spec.Tolerations, toleration)
}
if cfg.NodeAffinityKey != "" {
expr := corev1.NodeSelectorRequirement{
Key: cfg.NodeAffinityKey,
Operator: corev1.NodeSelectorOpExists,
}
if cfg.NodeAffinityValue != "" {
expr.Values = []string{cfg.NodeAffinityValue}
expr.Operator = corev1.NodeSelectorOpIn
}
pod.Spec.Affinity.NodeAffinity = &corev1.NodeAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
NodeSelectorTerms: []corev1.NodeSelectorTerm{{
MatchExpressions: []corev1.NodeSelectorRequirement{expr},
}},
},
}
}
// now that taints/toleration defaults have been set, time to merge in any overrides specified on the synthesizer
if syn.Spec.PodOverrides.Affinity != nil {
// do the merge
// easy one first
if syn.Spec.PodOverrides.Affinity.PodAffinity != nil {
pod.Spec.Affinity.PodAffinity = syn.Spec.PodOverrides.Affinity.PodAffinity
}
// merge in antiaffinity
if syn.Spec.PodOverrides.Affinity.PodAntiAffinity != nil {
// preferred is specified above so we want to append to that if specified
_ = mergo.Merge(&pod.Spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
syn.Spec.PodOverrides.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
mergo.WithAppendSlice,
mergo.WithoutDereference,
mergo.WithSliceDeepCopy)
// we can just overwrite the required one if specified
pod.Spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution = syn.Spec.PodOverrides.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution
}
if syn.Spec.PodOverrides.Affinity.NodeAffinity != nil {
// only need to merge the nodeaffinity terms if cfg.NodeAffinity was specified
// easy way to check is if it's not empty
if pod.Spec.Affinity.NodeAffinity != nil {
_ = mergo.Merge(&pod.Spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms,
syn.Spec.PodOverrides.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms,
mergo.WithAppendSlice,
mergo.WithoutDereference,
mergo.WithSliceDeepCopy)
}
} else {
// cfg.NodeAffinity was not specified, so we can just overwrite the nodeaffinity
pod.Spec.Affinity.NodeAffinity = syn.Spec.PodOverrides.Affinity.NodeAffinity
}
}
return pod
}
// filterEnv returns env taking out any items that have the same name as
// any item in filter.
func filterEnv(filter []corev1.EnvVar, env []apiv1.EnvVar) []apiv1.EnvVar {
res := []apiv1.EnvVar{}
for _, ev := range env {
if slices.ContainsFunc(filter, func(f corev1.EnvVar) bool {
return f.Name == ev.Name
}) {
continue
}
res = append(res, ev)
}
return res
}
func findContainerImage(pod *corev1.Pod) string {
for _, c := range pod.Spec.Containers {
if c.Name == "executor" {
return c.Image
}
}
return ""
}