feat: execute jobs on Kubernetes
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
executionv1alpha1 "git.ddupan.top/panxiao81/ayatori/api/execution/v1alpha1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const (
|
||||
ControllerName = "execution.ayatori.ddupan.top/kubernetes"
|
||||
ReferenceType = "Job"
|
||||
JobUIDLabel = "execution.ayatori.ddupan.top/job-uid"
|
||||
)
|
||||
|
||||
var ayatoriJobGVK = schema.GroupVersionKind{
|
||||
Group: executionv1alpha1.GroupVersion.Group,
|
||||
Version: executionv1alpha1.GroupVersion.Version,
|
||||
Kind: "Job",
|
||||
}
|
||||
|
||||
// BuildJob translates the stable execution API into the Kubernetes adapter's
|
||||
// backend object. It intentionally does not accept or expose a PodSpec.
|
||||
func BuildJob(
|
||||
job *executionv1alpha1.Job,
|
||||
parameters *executionv1alpha1.KubernetesExecutionParameters,
|
||||
resources executionv1alpha1.ExecutionResourceRequirements,
|
||||
) *batchv1.Job {
|
||||
backoffLimit := int32(0)
|
||||
controller := true
|
||||
blockOwnerDeletion := true
|
||||
|
||||
//nolint:modernize // ObjectMeta is promoted through embedded TypeMeta; embedlit produces invalid Go here.
|
||||
return &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: job.Name,
|
||||
Namespace: job.Namespace,
|
||||
Labels: map[string]string{
|
||||
JobUIDLabel: string(job.UID),
|
||||
},
|
||||
OwnerReferences: []metav1.OwnerReference{{
|
||||
APIVersion: ayatoriJobGVK.GroupVersion().String(),
|
||||
Kind: ayatoriJobGVK.Kind,
|
||||
Name: job.Name,
|
||||
UID: job.UID,
|
||||
Controller: &controller,
|
||||
BlockOwnerDeletion: &blockOwnerDeletion,
|
||||
}},
|
||||
},
|
||||
Spec: batchv1.JobSpec{
|
||||
BackoffLimit: &backoffLimit,
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{JobUIDLabel: string(job.UID)}},
|
||||
Spec: corev1.PodSpec{
|
||||
RestartPolicy: corev1.RestartPolicyNever,
|
||||
ServiceAccountName: parameters.Spec.ServiceAccountName,
|
||||
RuntimeClassName: optionalString(parameters.Spec.RuntimeClassName),
|
||||
NodeSelector: parameters.Spec.Scheduling.NodeSelector,
|
||||
Tolerations: parameters.Spec.Scheduling.Tolerations,
|
||||
SecurityContext: parameters.Spec.PodSecurityContext,
|
||||
ImagePullSecrets: job.Spec.Task.ImagePullSecrets,
|
||||
Containers: []corev1.Container{{
|
||||
Name: "task",
|
||||
Image: job.Spec.Task.Image,
|
||||
ImagePullPolicy: parameters.Spec.ImagePullPolicy,
|
||||
Command: job.Spec.Task.Command,
|
||||
Args: job.Spec.Task.Args,
|
||||
WorkingDir: job.Spec.Task.WorkingDir,
|
||||
Env: environment(job.Spec.Task.Env),
|
||||
Resources: resourceRequirements(resources),
|
||||
}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func ValidateOwnership(owner *executionv1alpha1.Job, backend *batchv1.Job) error {
|
||||
if backend.Labels[JobUIDLabel] != string(owner.UID) {
|
||||
return fmt.Errorf("backend Job %s/%s is not owned by Ayatori Job UID %s", backend.Namespace, backend.Name, owner.UID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func environment(values []executionv1alpha1.EnvVar) []corev1.EnvVar {
|
||||
result := make([]corev1.EnvVar, 0, len(values))
|
||||
for _, value := range values {
|
||||
env := corev1.EnvVar{Name: value.Name}
|
||||
if value.Value != nil {
|
||||
env.Value = *value.Value
|
||||
}
|
||||
if value.ValueFrom != nil {
|
||||
env.ValueFrom = &corev1.EnvVarSource{
|
||||
SecretKeyRef: value.ValueFrom.SecretKeyRef,
|
||||
ConfigMapKeyRef: value.ValueFrom.ConfigMapKeyRef,
|
||||
}
|
||||
}
|
||||
result = append(result, env)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func resourceRequirements(resources executionv1alpha1.ExecutionResourceRequirements) corev1.ResourceRequirements {
|
||||
return corev1.ResourceRequirements{
|
||||
Requests: resourceList(resources.Requests),
|
||||
Limits: resourceList(resources.Limits),
|
||||
}
|
||||
}
|
||||
|
||||
func resourceList(values executionv1alpha1.ResourceValues) corev1.ResourceList {
|
||||
result := corev1.ResourceList{}
|
||||
if values.CPU != nil {
|
||||
result[corev1.ResourceCPU] = values.CPU.DeepCopy()
|
||||
}
|
||||
if values.Memory != nil {
|
||||
result[corev1.ResourceMemory] = values.Memory.DeepCopy()
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func optionalString(value string) *string {
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
return &value
|
||||
}
|
||||
Reference in New Issue
Block a user