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 }