github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/cluster/pod.go (about)

     1  /*
     2  Copyright 2019 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cluster
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/api/resource"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/version"
    33  )
    34  
    35  const (
    36  	// kubernetes.io/arch and kubernetes.io/os are known node labels. See https://kubernetes.io/docs/reference/labels-annotations-taints/
    37  	nodeOperatingSystemLabel = "kubernetes.io/os"
    38  	nodeArchitectureLabel    = "kubernetes.io/arch"
    39  )
    40  
    41  func (b *Builder) kanikoPodSpec(artifact *latest.KanikoArtifact, tag string, platforms platform.Matcher) (*v1.Pod, error) {
    42  	args, err := kanikoArgs(artifact, tag, b.cfg.GetInsecureRegistries())
    43  	if err != nil {
    44  		return nil, fmt.Errorf("building args list: %w", err)
    45  	}
    46  
    47  	vm := v1.VolumeMount{
    48  		Name:      kaniko.DefaultEmptyDirName,
    49  		MountPath: kaniko.DefaultEmptyDirMountPath,
    50  	}
    51  
    52  	pod := &v1.Pod{
    53  		ObjectMeta: metav1.ObjectMeta{
    54  			Annotations:  b.ClusterDetails.Annotations,
    55  			GenerateName: "kaniko-",
    56  			Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
    57  			Namespace:    b.ClusterDetails.Namespace,
    58  		},
    59  		Spec: v1.PodSpec{
    60  			InitContainers: []v1.Container{{
    61  				Name:            initContainer,
    62  				Image:           artifact.InitImage,
    63  				ImagePullPolicy: v1.PullIfNotPresent,
    64  				Command:         []string{"sh", "-c", "while [ ! -f /tmp/complete ]; do sleep 1; done"},
    65  				VolumeMounts:    []v1.VolumeMount{vm},
    66  				Resources:       resourceRequirements(b.ClusterDetails.Resources),
    67  			}},
    68  			Containers: []v1.Container{{
    69  				Name:            kaniko.DefaultContainerName,
    70  				Image:           artifact.Image,
    71  				ImagePullPolicy: v1.PullIfNotPresent,
    72  				Args:            args,
    73  				Env:             b.env(artifact, b.ClusterDetails.HTTPProxy, b.ClusterDetails.HTTPSProxy),
    74  				VolumeMounts:    []v1.VolumeMount{vm},
    75  				Resources:       resourceRequirements(b.ClusterDetails.Resources),
    76  			}},
    77  			RestartPolicy: v1.RestartPolicyNever,
    78  			Volumes: []v1.Volume{{
    79  				Name: vm.Name,
    80  				VolumeSource: v1.VolumeSource{
    81  					EmptyDir: &v1.EmptyDirVolumeSource{},
    82  				},
    83  			}},
    84  		},
    85  	}
    86  
    87  	// Add secret for pull secret
    88  	if b.ClusterDetails.PullSecretName != "" {
    89  		addSecretVolume(pod, kaniko.DefaultSecretName, b.ClusterDetails.PullSecretMountPath, b.ClusterDetails.PullSecretName)
    90  	}
    91  
    92  	// Add host path volume for cache
    93  	if artifact.Cache != nil && artifact.Cache.HostPath != "" {
    94  		addHostPathVolume(pod, kaniko.DefaultCacheDirName, kaniko.DefaultCacheDirMountPath, artifact.Cache.HostPath)
    95  	}
    96  
    97  	if b.ClusterDetails.DockerConfig != nil {
    98  		// Add secret for docker config if specified
    99  		addSecretVolume(pod, kaniko.DefaultDockerConfigSecretName, kaniko.DefaultDockerConfigPath, b.ClusterDetails.DockerConfig.SecretName)
   100  	}
   101  
   102  	// Add Service Account
   103  	if b.ClusterDetails.ServiceAccountName != "" {
   104  		pod.Spec.ServiceAccountName = b.ClusterDetails.ServiceAccountName
   105  	}
   106  
   107  	// Add SecurityContext for runAsUser
   108  	if b.ClusterDetails.RunAsUser != nil {
   109  		if pod.Spec.SecurityContext == nil {
   110  			pod.Spec.SecurityContext = &v1.PodSecurityContext{}
   111  		}
   112  		pod.Spec.SecurityContext.RunAsUser = b.ClusterDetails.RunAsUser
   113  	}
   114  
   115  	// Add Tolerations for kaniko pod setup
   116  	if len(b.ClusterDetails.Tolerations) > 0 {
   117  		pod.Spec.Tolerations = b.ClusterDetails.Tolerations
   118  	}
   119  
   120  	// Add nodeSelector for kaniko pod setup
   121  	if b.ClusterDetails.NodeSelector != nil {
   122  		pod.Spec.NodeSelector = b.ClusterDetails.NodeSelector
   123  	}
   124  
   125  	// Add nodeSelector for image target platform.
   126  	// Kaniko doesn't support building cross platform images, so the pod platform needs to match the image target platform.
   127  	if len(platforms.Platforms) == 1 {
   128  		if pod.Spec.NodeSelector == nil {
   129  			pod.Spec.NodeSelector = make(map[string]string)
   130  		}
   131  		if _, found := pod.Spec.NodeSelector[nodeArchitectureLabel]; !found {
   132  			pod.Spec.NodeSelector[nodeArchitectureLabel] = platforms.Platforms[0].Architecture
   133  		}
   134  		if _, found := pod.Spec.NodeSelector[nodeOperatingSystemLabel]; !found {
   135  			pod.Spec.NodeSelector[nodeOperatingSystemLabel] = platforms.Platforms[0].OS
   136  		}
   137  	}
   138  
   139  	// Add used-defines Volumes
   140  	pod.Spec.Volumes = append(pod.Spec.Volumes, b.Volumes...)
   141  
   142  	// Add user-defined VolumeMounts
   143  	for _, vm := range artifact.VolumeMounts {
   144  		pod.Spec.InitContainers[0].VolumeMounts = append(pod.Spec.InitContainers[0].VolumeMounts, vm)
   145  		pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, vm)
   146  	}
   147  
   148  	return pod, nil
   149  }
   150  
   151  func (b *Builder) env(artifact *latest.KanikoArtifact, httpProxy, httpsProxy string) []v1.EnvVar {
   152  	env := []v1.EnvVar{{
   153  		// This should be same https://github.com/GoogleContainerTools/kaniko/blob/77cfb912f3483c204bfd09e1ada44fd200b15a78/pkg/executor/push.go#L49
   154  		Name:  "UPSTREAM_CLIENT_TYPE",
   155  		Value: fmt.Sprintf("UpstreamClient(skaffold-%s)", version.Get().Version),
   156  	}}
   157  
   158  	for _, v := range artifact.Env {
   159  		if v.Name != "" && v.Value != "" {
   160  			env = append(env, v)
   161  		}
   162  	}
   163  
   164  	if httpProxy != "" {
   165  		env = append(env, v1.EnvVar{
   166  			Name:  "HTTP_PROXY",
   167  			Value: httpProxy,
   168  		})
   169  	}
   170  
   171  	if httpsProxy != "" {
   172  		env = append(env, v1.EnvVar{
   173  			Name:  "HTTPS_PROXY",
   174  			Value: httpsProxy,
   175  		})
   176  	}
   177  
   178  	// if cluster.PullSecretName  is non-empty populate secret path and use as GOOGLE_APPLICATION_CREDENTIALS
   179  	// by default it is not empty, so need to
   180  	if b.ClusterDetails.PullSecretName != "" {
   181  		pullSecretPath := strings.Join(
   182  			[]string{b.ClusterDetails.PullSecretMountPath, b.ClusterDetails.PullSecretPath},
   183  			"/", // linux filepath separator.
   184  		)
   185  		env = append(env, v1.EnvVar{
   186  			Name:  "GOOGLE_APPLICATION_CREDENTIALS",
   187  			Value: pullSecretPath,
   188  		})
   189  	}
   190  	return env
   191  }
   192  
   193  func addSecretVolume(pod *v1.Pod, name, mountPath, secretName string) {
   194  	pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, v1.VolumeMount{
   195  		Name:      name,
   196  		MountPath: mountPath,
   197  	})
   198  
   199  	pod.Spec.Volumes = append(pod.Spec.Volumes, v1.Volume{
   200  		Name: name,
   201  		VolumeSource: v1.VolumeSource{
   202  			Secret: &v1.SecretVolumeSource{
   203  				SecretName: secretName,
   204  			},
   205  		},
   206  	})
   207  }
   208  
   209  func addHostPathVolume(pod *v1.Pod, name, mountPath, path string) {
   210  	pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, v1.VolumeMount{
   211  		Name:      name,
   212  		MountPath: mountPath,
   213  	})
   214  
   215  	pod.Spec.Volumes = append(pod.Spec.Volumes, v1.Volume{
   216  		Name: name,
   217  		VolumeSource: v1.VolumeSource{
   218  			HostPath: &v1.HostPathVolumeSource{
   219  				Path: path,
   220  			},
   221  		},
   222  	})
   223  }
   224  
   225  func resourceRequirements(rr *latest.ResourceRequirements) v1.ResourceRequirements {
   226  	req := v1.ResourceRequirements{}
   227  
   228  	if rr != nil {
   229  		if rr.Limits != nil {
   230  			req.Limits = v1.ResourceList{}
   231  			if rr.Limits.CPU != "" {
   232  				req.Limits[v1.ResourceCPU] = resource.MustParse(rr.Limits.CPU)
   233  			}
   234  
   235  			if rr.Limits.Memory != "" {
   236  				req.Limits[v1.ResourceMemory] = resource.MustParse(rr.Limits.Memory)
   237  			}
   238  
   239  			if rr.Limits.ResourceStorage != "" {
   240  				req.Limits[v1.ResourceStorage] = resource.MustParse(rr.Limits.ResourceStorage)
   241  			}
   242  
   243  			if rr.Limits.EphemeralStorage != "" {
   244  				req.Limits[v1.ResourceEphemeralStorage] = resource.MustParse(rr.Limits.EphemeralStorage)
   245  			}
   246  		}
   247  
   248  		if rr.Requests != nil {
   249  			req.Requests = v1.ResourceList{}
   250  			if rr.Requests.CPU != "" {
   251  				req.Requests[v1.ResourceCPU] = resource.MustParse(rr.Requests.CPU)
   252  			}
   253  			if rr.Requests.Memory != "" {
   254  				req.Requests[v1.ResourceMemory] = resource.MustParse(rr.Requests.Memory)
   255  			}
   256  			if rr.Requests.ResourceStorage != "" {
   257  				req.Requests[v1.ResourceStorage] = resource.MustParse(rr.Requests.ResourceStorage)
   258  			}
   259  			if rr.Requests.EphemeralStorage != "" {
   260  				req.Requests[v1.ResourceEphemeralStorage] = resource.MustParse(rr.Requests.EphemeralStorage)
   261  			}
   262  		}
   263  	}
   264  
   265  	return req
   266  }
   267  
   268  func kanikoArgs(artifact *latest.KanikoArtifact, tag string, insecureRegistries map[string]bool) ([]string, error) {
   269  	for reg := range insecureRegistries {
   270  		artifact.InsecureRegistry = append(artifact.InsecureRegistry, reg)
   271  	}
   272  
   273  	// Create pod spec
   274  	args, err := kaniko.Args(artifact, tag, fmt.Sprintf("dir://%s", kaniko.DefaultEmptyDirMountPath))
   275  	if err != nil {
   276  		return nil, fmt.Errorf("unable build kaniko args: %w", err)
   277  	}
   278  
   279  	log.Entry(context.TODO()).Trace("kaniko arguments are ", strings.Join(args, " "))
   280  
   281  	return args, nil
   282  }