Allow configure sidecars in Jenkins pod

This commit is contained in:
Tomasz Sęk
2019-05-17 16:35:18 +02:00
parent 127992eb96
commit af10a97299
12 changed files with 519 additions and 221 deletions
@@ -373,7 +373,7 @@ func isPodTerminating(pod corev1.Pod) bool {
func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMasterPod corev1.Pod) bool {
if version.Version != r.jenkins.Status.OperatorVersion {
r.logger.Info(fmt.Sprintf("Jenkins Operator version has changed, actual '%+v' new '%+v' - recreating pod",
r.logger.Info(fmt.Sprintf("Jenkins Operator version has changed, actual '%+v' new '%+v', recreating pod",
r.jenkins.Status.OperatorVersion, version.Version))
return true
}
@@ -385,13 +385,9 @@ func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMa
return true
}
if r.jenkins.Spec.Master.Image != currentJenkinsMasterPod.Spec.Containers[0].Image {
r.logger.Info(fmt.Sprintf("Jenkins image has changed to '%+v', recreating pod", r.jenkins.Spec.Master.Image))
return true
}
if r.jenkins.Spec.Master.ImagePullPolicy != currentJenkinsMasterPod.Spec.Containers[0].ImagePullPolicy {
r.logger.Info(fmt.Sprintf("Jenkins image pull policy has changed to '%+v', recreating pod", r.jenkins.Spec.Master.ImagePullPolicy))
if !reflect.DeepEqual(r.jenkins.Spec.Master.NodeSelector, currentJenkinsMasterPod.Spec.NodeSelector) {
r.logger.Info(fmt.Sprintf("Jenkins pod node selector has changed, actual '%+v' required '%+v', recreating pod",
currentJenkinsMasterPod.Spec.NodeSelector, r.jenkins.Spec.Master.NodeSelector))
return true
}
@@ -401,47 +397,119 @@ func (r *ReconcileJenkinsBaseConfiguration) isRecreatePodNeeded(currentJenkinsMa
return true
}
if !reflect.DeepEqual(r.jenkins.Spec.Master.Resources, currentJenkinsMasterPod.Spec.Containers[0].Resources) {
r.logger.Info(fmt.Sprintf("Jenkins pod resources have changed, actual '%+v' required '%+v' - recreating pod",
currentJenkinsMasterPod.Spec.Containers[0].Resources, r.jenkins.Spec.Master.Resources))
if (len(r.jenkins.Spec.Master.Containers) + 1) != len(currentJenkinsMasterPod.Spec.Containers) {
r.logger.Info(fmt.Sprintf("Jenkins amount of containers has changed to '%+v', recreating pod", len(r.jenkins.Spec.Master.Containers)+1))
return true
}
if !reflect.DeepEqual(r.jenkins.Spec.Master.ReadinessProbe, currentJenkinsMasterPod.Spec.Containers[0].ReadinessProbe) {
r.logger.Info(fmt.Sprintf("Jenkins pod readinessProbe have changed, actual '%+v' required '%+v' - recreating pod",
currentJenkinsMasterPod.Spec.Containers[0].ReadinessProbe, r.jenkins.Spec.Master.ReadinessProbe))
return true
for _, actualContainer := range currentJenkinsMasterPod.Spec.Containers {
if actualContainer.Name == resources.JenkinsMasterContainerName {
if changed := r.compareContainers(resources.NewJenkinsMasterContainer(r.jenkins), actualContainer); changed {
return true
}
continue
}
var expectedContainer *corev1.Container
for _, jenkinsContainer := range r.jenkins.Spec.Master.Containers {
if jenkinsContainer.Name == actualContainer.Name {
tmp := resources.ConvertJenkinsContainerToKubernetesContainer(jenkinsContainer)
expectedContainer = &tmp
}
}
if expectedContainer == nil {
r.logger.Info(fmt.Sprintf("Container '%+v' not found in pod, recreating pod", actualContainer))
return true
}
if changed := r.compareContainers(*expectedContainer, actualContainer); changed {
return true
}
}
if !reflect.DeepEqual(r.jenkins.Spec.Master.LivenessProbe, currentJenkinsMasterPod.Spec.Containers[0].LivenessProbe) {
r.logger.Info(fmt.Sprintf("Jenkins pod livenessProbe have changed, actual '%+v' required '%+v' - recreating pod",
currentJenkinsMasterPod.Spec.Containers[0].LivenessProbe, r.jenkins.Spec.Master.LivenessProbe))
return false
}
func (r *ReconcileJenkinsBaseConfiguration) compareContainers(expected corev1.Container, actual corev1.Container) bool {
if !reflect.DeepEqual(expected.Args, actual.Args) {
r.logger.Info(fmt.Sprintf("Arguments have changed to '%+v' in container '%s', recreating pod", expected.Args, expected.Name))
return true
}
if !reflect.DeepEqual(r.jenkins.Spec.Master.NodeSelector, currentJenkinsMasterPod.Spec.NodeSelector) {
r.logger.Info(fmt.Sprintf("Jenkins pod node selector has changed, actual '%+v' required '%+v' - recreating pod",
currentJenkinsMasterPod.Spec.NodeSelector, r.jenkins.Spec.Master.NodeSelector))
if !reflect.DeepEqual(expected.Command, actual.Command) {
r.logger.Info(fmt.Sprintf("Command has changed to '%+v' in container '%s', recreating pod", expected.Command, expected.Name))
return true
}
requiredEnvs := resources.GetJenkinsMasterPodBaseEnvs()
requiredEnvs = append(requiredEnvs, r.jenkins.Spec.Master.Env...)
if !reflect.DeepEqual(requiredEnvs, currentJenkinsMasterPod.Spec.Containers[0].Env) {
r.logger.Info(fmt.Sprintf("Jenkins env have changed, actual '%+v' required '%+v' - recreating pod",
currentJenkinsMasterPod.Spec.Containers[0].Env, requiredEnvs))
if !reflect.DeepEqual(expected.Env, actual.Env) {
r.logger.Info(fmt.Sprintf("Env has changed to '%+v' in container '%s', recreating pod", expected.Env, expected.Name))
return true
}
if !reflect.DeepEqual(expected.EnvFrom, actual.EnvFrom) {
r.logger.Info(fmt.Sprintf("EnvFrom has changed to '%+v' in container '%s', recreating pod", expected.EnvFrom, expected.Name))
return true
}
if !reflect.DeepEqual(expected.Image, actual.Image) {
r.logger.Info(fmt.Sprintf("Image has changed to '%+v' in container '%s', recreating pod", expected.Image, expected.Name))
return true
}
if !reflect.DeepEqual(expected.ImagePullPolicy, actual.ImagePullPolicy) {
r.logger.Info(fmt.Sprintf("Image pull policy has changed to '%+v' in container '%s', recreating pod", expected.ImagePullPolicy, expected.Name))
return true
}
if !reflect.DeepEqual(expected.Lifecycle, actual.Lifecycle) {
r.logger.Info(fmt.Sprintf("Lifecycle has changed to '%+v' in container '%s', recreating pod", expected.Lifecycle, expected.Name))
return true
}
if !reflect.DeepEqual(expected.LivenessProbe, actual.LivenessProbe) {
r.logger.Info(fmt.Sprintf("Liveness probe has changed to '%+v' in container '%s', recreating pod", expected.LivenessProbe, expected.Name))
return true
}
if !reflect.DeepEqual(expected.Ports, actual.Ports) {
r.logger.Info(fmt.Sprintf("Ports have changed to '%+v' in container '%s', recreating pod", expected.Ports, expected.Name))
return true
}
if !reflect.DeepEqual(expected.ReadinessProbe, actual.ReadinessProbe) {
r.logger.Info(fmt.Sprintf("Readiness probe has changed to '%+v' in container '%s', recreating pod", expected.ReadinessProbe, expected.Name))
return true
}
if !reflect.DeepEqual(expected.Resources, actual.Resources) {
r.logger.Info(fmt.Sprintf("Resources have changed to '%+v' in container '%s', recreating pod", expected.Resources, expected.Name))
return true
}
if !reflect.DeepEqual(expected.SecurityContext, actual.SecurityContext) {
r.logger.Info(fmt.Sprintf("Security context has changed to '%+v' in container '%s', recreating pod", expected.SecurityContext, expected.Name))
return true
}
if !reflect.DeepEqual(expected.WorkingDir, actual.WorkingDir) {
r.logger.Info(fmt.Sprintf("Working directory has changed to '%+v' in container '%s', recreating pod", expected.WorkingDir, expected.Name))
return true
}
if !CompareContainerVolumeMounts(expected, actual) {
r.logger.Info(fmt.Sprintf("Volume mounts has changed to '%+v' in container '%s', recreating pod", expected.VolumeMounts, expected.Name))
return true
}
return false
}
// CompareContainerVolumeMounts returns true if two containers volume mounts are the same
func CompareContainerVolumeMounts(expected corev1.Container, actual corev1.Container) bool {
var withoutServiceAccount []corev1.VolumeMount
for _, volumeMount := range actual.VolumeMounts {
if volumeMount.MountPath != "/var/run/secrets/kubernetes.io/serviceaccount" {
withoutServiceAccount = append(withoutServiceAccount, volumeMount)
}
}
return reflect.DeepEqual(expected.VolumeMounts, withoutServiceAccount)
}
func (r *ReconcileJenkinsBaseConfiguration) restartJenkinsMasterPod(meta metav1.ObjectMeta) error {
currentJenkinsMasterPod, err := r.getJenkinsMasterPod(meta)
r.logger.Info(fmt.Sprintf("Terminating Jenkins Master Pod %s/%s", currentJenkinsMasterPod.Namespace, currentJenkinsMasterPod.Name))
if err != nil {
return err
}
r.logger.Info(fmt.Sprintf("Terminating Jenkins Master Pod %s/%s", currentJenkinsMasterPod.Namespace, currentJenkinsMasterPod.Name))
return stackerr.WithStack(r.k8sClient.Delete(context.TODO(), currentJenkinsMasterPod))
}
@@ -461,11 +529,20 @@ func (r *ReconcileJenkinsBaseConfiguration) waitForJenkins(meta metav1.ObjectMet
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
}
containersReadyCount := 0
for _, containerStatus := range jenkinsMasterPodStatus.Status.ContainerStatuses {
if !containerStatus.Ready {
r.logger.V(log.VDebug).Info("Jenkins master pod not ready, readiness probe failed")
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
if containerStatus.State.Terminated != nil {
r.logger.Info(fmt.Sprintf("Container '%s' is terminated, status '%+v', recreating pod", containerStatus.Name, containerStatus))
return reconcile.Result{Requeue: true}, r.restartJenkinsMasterPod(meta)
}
if !containerStatus.Ready {
r.logger.V(log.VDebug).Info(fmt.Sprintf("Container '%s' not ready, readiness probe failed", containerStatus.Name))
} else {
containersReadyCount++
}
}
if containersReadyCount != len(jenkinsMasterPodStatus.Status.ContainerStatuses) {
return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 5}, nil
}
return reconcile.Result{}, nil
@@ -0,0 +1,83 @@
package base
import (
"testing"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
)
func TestCompareContainerVolumeMounts(t *testing.T) {
t.Run("happy with service account", func(t *testing.T) {
expectedContainer := corev1.Container{
VolumeMounts: []corev1.VolumeMount{
{
Name: "volume-name",
MountPath: "/mount/path",
},
},
}
actualContainer := corev1.Container{
VolumeMounts: []corev1.VolumeMount{
{
Name: "volume-name",
MountPath: "/mount/path",
},
{
Name: "jenkins-operator-example-token-dh4r9",
MountPath: "/var/run/secrets/kubernetes.io/serviceaccount",
ReadOnly: true,
},
},
}
got := CompareContainerVolumeMounts(expectedContainer, actualContainer)
assert.True(t, got)
})
t.Run("happy without service account", func(t *testing.T) {
expectedContainer := corev1.Container{
VolumeMounts: []corev1.VolumeMount{
{
Name: "volume-name",
MountPath: "/mount/path",
},
},
}
actualContainer := corev1.Container{
VolumeMounts: []corev1.VolumeMount{
{
Name: "volume-name",
MountPath: "/mount/path",
},
},
}
got := CompareContainerVolumeMounts(expectedContainer, actualContainer)
assert.True(t, got)
})
t.Run("different volume mounts", func(t *testing.T) {
expectedContainer := corev1.Container{
VolumeMounts: []corev1.VolumeMount{
{
Name: "volume-name",
MountPath: "/mount/path",
},
},
}
actualContainer := corev1.Container{
VolumeMounts: []corev1.VolumeMount{
{
Name: "jenkins-operator-example-token-dh4r9",
MountPath: "/var/run/secrets/kubernetes.io/serviceaccount",
ReadOnly: true,
},
},
}
got := CompareContainerVolumeMounts(expectedContainer, actualContainer)
assert.False(t, got)
})
}
@@ -11,9 +11,11 @@ import (
)
const (
jenkinsHomeVolumeName = "home"
jenkinsPath = "/var/jenkins"
jenkinsHomePath = jenkinsPath + "/home"
// JenkinsMasterContainerName is the Jenkins master container name in pod
JenkinsMasterContainerName = "jenkins-master"
jenkinsHomeVolumeName = "home"
jenkinsPath = "/var/jenkins"
jenkinsHomePath = jenkinsPath + "/home"
jenkinsScriptsVolumeName = "scripts"
jenkinsScriptsVolumePath = jenkinsPath + "/scripts"
@@ -72,13 +74,111 @@ func GetJenkinsMasterPodBaseEnvs() []corev1.EnvVar {
}
}
// NewJenkinsMasterContainer returns Jenkins master Kubernetes container
func NewJenkinsMasterContainer(jenkins *v1alpha1.Jenkins) corev1.Container {
envs := GetJenkinsMasterPodBaseEnvs()
envs = append(envs, jenkins.Spec.Master.Env...)
return corev1.Container{
Name: JenkinsMasterContainerName,
Image: jenkins.Spec.Master.Image,
ImagePullPolicy: jenkins.Spec.Master.ImagePullPolicy,
Command: []string{
"bash",
fmt.Sprintf("%s/%s", jenkinsScriptsVolumePath, initScriptName),
},
LivenessProbe: jenkins.Spec.Master.LivenessProbe,
ReadinessProbe: jenkins.Spec.Master.ReadinessProbe,
Ports: []corev1.ContainerPort{
{
Name: httpPortName,
ContainerPort: constants.DefaultHTTPPortInt32,
Protocol: corev1.ProtocolTCP,
},
{
Name: slavePortName,
ContainerPort: constants.DefaultSlavePortInt32,
Protocol: corev1.ProtocolTCP,
},
},
Env: envs,
Resources: jenkins.Spec.Master.Resources,
VolumeMounts: []corev1.VolumeMount{
{
Name: jenkinsHomeVolumeName,
MountPath: jenkinsHomePath,
ReadOnly: false,
},
{
Name: jenkinsScriptsVolumeName,
MountPath: jenkinsScriptsVolumePath,
ReadOnly: true,
},
{
Name: jenkinsInitConfigurationVolumeName,
MountPath: jenkinsInitConfigurationVolumePath,
ReadOnly: true,
},
{
Name: jenkinsBaseConfigurationVolumeName,
MountPath: JenkinsBaseConfigurationVolumePath,
ReadOnly: true,
},
{
Name: jenkinsUserConfigurationVolumeName,
MountPath: JenkinsUserConfigurationVolumePath,
ReadOnly: true,
},
{
Name: jenkinsOperatorCredentialsVolumeName,
MountPath: jenkinsOperatorCredentialsVolumePath,
ReadOnly: true,
},
{
Name: userConfigurationSecretVolumeName,
MountPath: UserConfigurationSecretVolumePath,
ReadOnly: true,
},
},
}
}
// ConvertJenkinsContainerToKubernetesContainer converts Jenkins container to Kubernetes container
func ConvertJenkinsContainerToKubernetesContainer(container v1alpha1.Container) corev1.Container {
return corev1.Container{
Name: container.Name,
Image: container.Image,
Command: container.Command,
Args: container.Args,
WorkingDir: container.WorkingDir,
Ports: container.Ports,
EnvFrom: container.EnvFrom,
Env: container.Env,
Resources: container.Resources,
VolumeMounts: container.VolumeMounts,
LivenessProbe: container.LivenessProbe,
ReadinessProbe: container.ReadinessProbe,
Lifecycle: container.Lifecycle,
ImagePullPolicy: container.ImagePullPolicy,
SecurityContext: container.SecurityContext,
}
}
func newContainers(jenkins *v1alpha1.Jenkins) (containers []corev1.Container) {
containers = append(containers, NewJenkinsMasterContainer(jenkins))
for _, container := range jenkins.Spec.Master.Containers {
containers = append(containers, ConvertJenkinsContainerToKubernetesContainer(container))
}
return
}
// NewJenkinsMasterPod builds Jenkins Master Kubernetes Pod resource
func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins) *corev1.Pod {
runAsUser := jenkinsUserUID
objectMeta.Annotations = jenkins.Spec.Master.Annotations
envs := GetJenkinsMasterPodBaseEnvs()
envs = append(envs, jenkins.Spec.Master.Env...)
return &corev1.Pod{
TypeMeta: buildPodTypeMeta(),
@@ -91,68 +191,7 @@ func NewJenkinsMasterPod(objectMeta metav1.ObjectMeta, jenkins *v1alpha1.Jenkins
RunAsGroup: &runAsUser,
},
NodeSelector: jenkins.Spec.Master.NodeSelector,
Containers: []corev1.Container{
{
Name: "jenkins-master",
Image: jenkins.Spec.Master.Image,
ImagePullPolicy: jenkins.Spec.Master.ImagePullPolicy,
Command: []string{
"bash",
fmt.Sprintf("%s/%s", jenkinsScriptsVolumePath, initScriptName),
},
LivenessProbe: jenkins.Spec.Master.LivenessProbe,
ReadinessProbe: jenkins.Spec.Master.ReadinessProbe,
Ports: []corev1.ContainerPort{
{
Name: httpPortName,
ContainerPort: constants.DefaultHTTPPortInt32,
},
{
Name: slavePortName,
ContainerPort: constants.DefaultSlavePortInt32,
},
},
Env: envs,
Resources: jenkins.Spec.Master.Resources,
VolumeMounts: []corev1.VolumeMount{
{
Name: jenkinsHomeVolumeName,
MountPath: jenkinsHomePath,
ReadOnly: false,
},
{
Name: jenkinsScriptsVolumeName,
MountPath: jenkinsScriptsVolumePath,
ReadOnly: true,
},
{
Name: jenkinsInitConfigurationVolumeName,
MountPath: jenkinsInitConfigurationVolumePath,
ReadOnly: true,
},
{
Name: jenkinsBaseConfigurationVolumeName,
MountPath: JenkinsBaseConfigurationVolumePath,
ReadOnly: true,
},
{
Name: jenkinsUserConfigurationVolumeName,
MountPath: JenkinsUserConfigurationVolumePath,
ReadOnly: true,
},
{
Name: jenkinsOperatorCredentialsVolumeName,
MountPath: jenkinsOperatorCredentialsVolumePath,
ReadOnly: true,
},
{
Name: userConfigurationSecretVolumeName,
MountPath: UserConfigurationSecretVolumePath,
ReadOnly: true,
},
},
},
},
Containers: newContainers(jenkins),
Volumes: []corev1.Volume{
{
Name: jenkinsHomeVolumeName,
@@ -18,15 +18,14 @@ var (
// Validate validates Jenkins CR Spec.master section
func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha1.Jenkins) (bool, error) {
if jenkins.Spec.Master.Image == "" {
r.logger.V(log.VWarn).Info("Image not set")
if !r.validateContainer(jenkins.Spec.Master.Container) {
return false, nil
}
if !dockerImageRegexp.MatchString(jenkins.Spec.Master.Image) && !docker.ReferenceRegexp.MatchString(jenkins.Spec.Master.Image) {
r.logger.V(log.VWarn).Info("Invalid image")
return false, nil
for _, container := range jenkins.Spec.Master.Containers {
if !r.validateContainer(container) {
return false, nil
}
}
if !r.validatePlugins(jenkins.Spec.Master.OperatorPlugins, jenkins.Spec.Master.Plugins) {
@@ -40,6 +39,26 @@ func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha1.Jenkins)
return true, nil
}
func (r *ReconcileJenkinsBaseConfiguration) validateContainer(container v1alpha1.Container) bool {
logger := r.logger.WithValues("container", container.Name)
if container.Image == "" {
logger.V(log.VWarn).Info("Image not set")
return false
}
if !dockerImageRegexp.MatchString(container.Image) && !docker.ReferenceRegexp.MatchString(container.Image) {
r.logger.V(log.VWarn).Info("Invalid image")
return false
}
if container.ImagePullPolicy == "" {
logger.V(log.VWarn).Info("Image pull policy not set")
return false
}
return true
}
func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() bool {
baseEnvs := resources.GetJenkinsMasterPodBaseEnvs()
baseEnvNames := map[string]string{}
@@ -74,10 +74,12 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) {
jenkins := v1alpha1.Jenkins{
Spec: v1alpha1.JenkinsSpec{
Master: v1alpha1.JenkinsMaster{
Env: []v1.EnvVar{
{
Name: "SOME_VALUE",
Value: "",
Container: v1alpha1.Container{
Env: []v1.EnvVar{
{
Name: "SOME_VALUE",
Value: "",
},
},
},
},
@@ -92,10 +94,12 @@ func TestValidateJenkinsMasterPodEnvs(t *testing.T) {
jenkins := v1alpha1.Jenkins{
Spec: v1alpha1.JenkinsSpec{
Master: v1alpha1.JenkinsMaster{
Env: []v1.EnvVar{
{
Name: "JENKINS_HOME",
Value: "",
Container: v1alpha1.Container{
Env: []v1.EnvVar{
{
Name: "JENKINS_HOME",
Value: "",
},
},
},
},
@@ -119,16 +119,18 @@ func jenkinsCustomResource() *v1alpha1.Jenkins {
},
Spec: v1alpha1.JenkinsSpec{
Master: v1alpha1.JenkinsMaster{
Image: "jenkins/jenkins",
Annotations: map[string]string{"test": "label"},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("300m"),
corev1.ResourceMemory: resource.MustParse("500Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("2"),
corev1.ResourceMemory: resource.MustParse("2Gi"),
Container: v1alpha1.Container{
Image: "jenkins/jenkins",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("300m"),
corev1.ResourceMemory: resource.MustParse("500Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("2"),
corev1.ResourceMemory: resource.MustParse("2Gi"),
},
},
},
},
+43 -7
View File
@@ -252,7 +252,7 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha1.Jenkins, logger logr.Lo
Scheme: corev1.URISchemeHTTP,
},
},
InitialDelaySeconds: int32(30),
InitialDelaySeconds: int32(80),
TimeoutSeconds: int32(5),
FailureThreshold: int32(12),
}
@@ -276,12 +276,8 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha1.Jenkins, logger logr.Lo
changed = true
jenkins.Spec.Master.Plugins = map[string][]string{"simple-theme-plugin:0.5.1": {}}
}
_, requestCPUSet := jenkins.Spec.Master.Resources.Requests[corev1.ResourceCPU]
_, requestMemporySet := jenkins.Spec.Master.Resources.Requests[corev1.ResourceMemory]
_, limitCPUSet := jenkins.Spec.Master.Resources.Limits[corev1.ResourceCPU]
_, limitMemporySet := jenkins.Spec.Master.Resources.Limits[corev1.ResourceMemory]
if !limitCPUSet || !limitMemporySet || !requestCPUSet || !requestMemporySet {
logger.Info("Setting default Jenkins master pod resource requirements")
if isResourceRequirementsNotSet(jenkins.Spec.Master.Resources) {
logger.Info("Setting default Jenkins master container resource requirements")
changed = true
jenkins.Spec.Master.Resources = corev1.ResourceRequirements{
Requests: corev1.ResourceList{
@@ -318,9 +314,49 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha1.Jenkins, logger logr.Lo
Port: constants.DefaultSlavePortInt32,
}
}
for i, container := range jenkins.Spec.Master.Containers {
if setDefaultsForContainer(jenkins, i, logger.WithValues("container", container.Name)) {
changed = true
}
}
if changed {
return errors.WithStack(r.client.Update(context.TODO(), jenkins))
}
return nil
}
func setDefaultsForContainer(jenkins *v1alpha1.Jenkins, containerIndex int, logger logr.Logger) bool {
changed := false
if len(jenkins.Spec.Master.Containers[containerIndex].ImagePullPolicy) == 0 {
logger.Info(fmt.Sprintf("Setting default container image pull policy: %s", corev1.PullAlways))
changed = true
jenkins.Spec.Master.Containers[containerIndex].ImagePullPolicy = corev1.PullAlways
}
if isResourceRequirementsNotSet(jenkins.Spec.Master.Containers[containerIndex].Resources) {
logger.Info("Setting default container resource requirements")
changed = true
jenkins.Spec.Master.Containers[containerIndex].Resources = corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("50m"),
corev1.ResourceMemory: resource.MustParse("50Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("100m"),
corev1.ResourceMemory: resource.MustParse("100Mi"),
},
}
}
return changed
}
func isResourceRequirementsNotSet(requirements corev1.ResourceRequirements) bool {
_, requestCPUSet := requirements.Requests[corev1.ResourceCPU]
_, requestMemporySet := requirements.Requests[corev1.ResourceMemory]
_, limitCPUSet := requirements.Limits[corev1.ResourceCPU]
_, limitMemorySet := requirements.Limits[corev1.ResourceMemory]
return !limitCPUSet || !limitMemorySet || !requestCPUSet || !requestMemporySet
}
+11 -9
View File
@@ -387,16 +387,18 @@ func jenkinsCustomResource() *v1alpha1.Jenkins {
},
Spec: v1alpha1.JenkinsSpec{
Master: v1alpha1.JenkinsMaster{
Image: "jenkins/jenkins",
Annotations: map[string]string{"test": "label"},
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("300m"),
corev1.ResourceMemory: resource.MustParse("500Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("2"),
corev1.ResourceMemory: resource.MustParse("2Gi"),
Container: v1alpha1.Container{
Image: "jenkins/jenkins",
Resources: corev1.ResourceRequirements{
Requests: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("300m"),
corev1.ResourceMemory: resource.MustParse("500Mi"),
},
Limits: corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("2"),
corev1.ResourceMemory: resource.MustParse("2Gi"),
},
},
},
},