Skip to content

Commit

Permalink
feat(ko): GCB remote builds for ko builder
Browse files Browse the repository at this point in the history
Enables remote builds on Google Cloud Build when using the Skaffold `ko`
builder.

Since Skaffold embeds ko as a module, the Cloud Build step uses the
`skaffold` binary to build and push the image. This means that we don't
need to create and maintain a separate `ko` builder image.

Tracking: GoogleContainerTools#7131
Related: GoogleContainerTools#7258
  • Loading branch information
halvards committed Jul 15, 2022
1 parent dfefaaa commit 715b585
Show file tree
Hide file tree
Showing 14 changed files with 360 additions and 22 deletions.
16 changes: 8 additions & 8 deletions docs-v2/content/en/docs/pipeline-stages/builders/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Skaffold supports different tools for building images:
| **Jib Maven and Gradle** | [Yes]({{< relref "/docs/pipeline-stages/builders/jib#jib-maven-and-gradle-locally" >}}) | - | [Yes]({{< relref "/docs/pipeline-stages/builders/jib#remotely-with-google-cloud-build" >}}) |
| **Cloud Native Buildpacks** | [Yes]({{< relref "/docs/pipeline-stages/builders/buildpacks" >}}) | - | [Yes]({{< relref "/docs/pipeline-stages/builders/buildpacks" >}}) |
| **Bazel** | [Yes]({{< relref "/docs/pipeline-stages/builders/bazel" >}}) | - | - |
| **ko** | [Yes]({{< relref "/docs/pipeline-stages/builders/ko" >}}) | - | - |
| **ko** | [Yes]({{< relref "/docs/pipeline-stages/builders/ko" >}}) | - | [Yes]({{< relref "/docs/pipeline-stages/builders/ko#remote-builds" >}}) |
| **Custom Script** | [Yes]({{<relref "/docs/pipeline-stages/builders/custom#custom-build-script-locally" >}}) | [Yes]({{<relref "/docs/pipeline-stages/builders/custom#custom-build-script-in-cluster" >}}) | - |

**Configuration**
Expand Down Expand Up @@ -184,13 +184,13 @@ The final list of target platforms need to ultimately be supported by the target

| | Local Build | In Cluster Build | Remote on Google Cloud Build |
|----|:-----------:|:----------------:|:----------------------------:|
| **Dockerfile** | Cross platform supported | Cross platform supported but platform should match cluster node running the pod. | Cross platform supported |
| **Jib Maven and Gradle** | Cross platform supported | - | Cross platform supported |
| **Dockerfile** | Cross-platform supported | Cross-platform supported but platform should match cluster node running the pod. | Cross-platform supported |
| **Jib Maven and Gradle** | Cross-platform and multi-platform supported | - | Cross-platform and multi-platform supported |
| **Cloud Native Buildpacks** | Only supports `linux/amd64` | - | Only supports `linux/amd64` |
| **Bazel** | Cross platform supported but requires explicit platform specific rules. Not yet implemented | - | - |
| **ko** | Cross platform supported | - | - |
| **Custom Script** | Cross platform supported but requires user to implement it in the build script | Cross platform supported but requires user to implement it in the build script | - |
| **Bazel** | Cross-platform supported but requires explicit platform specific rules. Not yet implemented | - | - |
| **ko** | Cross-platform and multi-platform supported | - | Cross-platform and multi-platform supported |
| **Custom Script** | Cross-platform and multi-platform supported but requires user to implement it in the build script | Cross-platform and multi-platform supported but requires user to implement it in the build script | - |

{{< alert title="Note" >}}
Multi-arch image build is not yet supported for any builders other than the [jib builder]({{<relref "/docs/pipeline-stages/builders/jib" >}}), the [ko builder]({{<relref "/docs/pipeline-stages/builders/ko">}}) and the [custom builder]({{<relref "/docs/pipeline-stages/builders/custom" >}}) in Skaffold.
{{< /alert >}}
Skaffold supports multi-platform image builds using the [jib builder]({{<relref "/docs/pipeline-stages/builders/jib" >}}), the [ko builder]({{<relref "/docs/pipeline-stages/builders/ko">}}) and the [custom builder]({{<relref "/docs/pipeline-stages/builders/custom" >}}).
{{< /alert >}}
5 changes: 3 additions & 2 deletions docs-v2/content/en/docs/pipeline-stages/builders/ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,9 +411,10 @@ To learn more about how Skaffold debugs Go applications, read the

File `sync` is not supported while the ko builder feature is in Alpha.

### Remote builders
### Remote builds

Only `local` builds are supported while the ko builder feature is in Alpha.
The `ko` builder supports remote builds on Google Cloud Build. See the
[example](https://github.com/GoogleContainerTools/skaffold/tree/main/examples/ko).

### Using the `custom` builder

Expand Down
16 changes: 8 additions & 8 deletions docs/content/en/docs/pipeline-stages/builders/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Skaffold supports different tools for building images:
| **Jib Maven and Gradle** | [Yes]({{< relref "/docs/pipeline-stages/builders/jib#jib-maven-and-gradle-locally" >}}) | - | [Yes]({{< relref "/docs/pipeline-stages/builders/jib#remotely-with-google-cloud-build" >}}) |
| **Cloud Native Buildpacks** | [Yes]({{< relref "/docs/pipeline-stages/builders/buildpacks" >}}) | - | [Yes]({{< relref "/docs/pipeline-stages/builders/buildpacks" >}}) |
| **Bazel** | [Yes]({{< relref "/docs/pipeline-stages/builders/bazel" >}}) | - | - |
| **ko** | [Yes]({{< relref "/docs/pipeline-stages/builders/ko" >}}) | - | - |
| **ko** | [Yes]({{< relref "/docs/pipeline-stages/builders/ko" >}}) | - | [Yes]({{< relref "/docs/pipeline-stages/builders/ko#remote-builds" >}}) |
| **Custom Script** | [Yes]({{<relref "/docs/pipeline-stages/builders/custom#custom-build-script-locally" >}}) | [Yes]({{<relref "/docs/pipeline-stages/builders/custom#custom-build-script-in-cluster" >}}) | - |

**Configuration**
Expand Down Expand Up @@ -184,13 +184,13 @@ The final list of target platforms need to ultimately be supported by the target

| | Local Build | In Cluster Build | Remote on Google Cloud Build |
|----|:-----------:|:----------------:|:----------------------------:|
| **Dockerfile** | Cross platform supported | Cross platform supported but platform should match cluster node running the pod. | Cross platform supported |
| **Jib Maven and Gradle** | Cross platform supported | - | Cross platform supported |
| **Dockerfile** | Cross-platform supported | Cross-platform supported but platform should match cluster node running the pod. | Cross-platform supported |
| **Jib Maven and Gradle** | Cross-platform and multi-platform supported | - | Cross-platform and multi-platform supported |
| **Cloud Native Buildpacks** | Only supports `linux/amd64` | - | Only supports `linux/amd64` |
| **Bazel** | Cross platform supported but requires explicit platform specific rules. Not yet implemented | - | - |
| **ko** | Cross platform supported | - | - |
| **Custom Script** | Cross platform supported but requires user to implement it in the build script | Cross platform supported but requires user to implement it in the build script | - |
| **Bazel** | Cross-platform supported but requires explicit platform specific rules. Not yet implemented | - | - |
| **ko** | Cross-platform and multi-platform supported | - | Cross-platform and multi-platform supported |
| **Custom Script** | Cross-platform and multi-platform supported but requires user to implement it in the build script | Cross-platform and multi-platform supported but requires user to implement it in the build script | - |

{{< alert title="Note" >}}
Multi-arch image build is not yet supported for any builders other than the [jib builder]({{<relref "/docs/pipeline-stages/builders/jib" >}}), the [ko builder]({{<relref "/docs/pipeline-stages/builders/ko">}}) and the [custom builder]({{<relref "/docs/pipeline-stages/builders/custom" >}}) in Skaffold.
{{< /alert >}}
Skaffold supports multi-platform image builds using the [jib builder]({{<relref "/docs/pipeline-stages/builders/jib" >}}), the [ko builder]({{<relref "/docs/pipeline-stages/builders/ko">}}) and the [custom builder]({{<relref "/docs/pipeline-stages/builders/custom" >}}).
{{< /alert >}}
5 changes: 3 additions & 2 deletions docs/content/en/docs/pipeline-stages/builders/ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,9 +411,10 @@ To learn more about how Skaffold debugs Go applications, read the

File `sync` is not supported while the ko builder feature is in Alpha.

### Remote builders
### Remote builds

Only `local` builds are supported while the ko builder feature is in Alpha.
The `ko` builder supports remote builds on Google Cloud Build. See the
[example](https://github.com/GoogleContainerTools/skaffold/tree/main/examples/ko).

### Using the `custom` builder

Expand Down
11 changes: 10 additions & 1 deletion integration/examples/ko/skaffold.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ kind: Config
build:
artifacts:
- image: skaffold-ko
ko: {}
ko:
dependencies:
paths:
- "**/*.go"
- go.*
profiles:
- name: gcb
build:
googleCloudBuild:
koImage: gcr.io/k8s-skaffold/skaffold:v1.37.2-lts
141 changes: 141 additions & 0 deletions pkg/skaffold/build/gcb/ko.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
Copyright 2022 The Skaffold Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package gcb

import (
"context"
"fmt"
"strings"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
schema "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v2beta28" // must be a version supported by a public Skaffold image
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/yaml"
"google.golang.org/api/cloudbuild/v1"
)

// koBuildSpec creates a Cloud Build configuration using the `ko` builder.
//
// Because Skaffold embeds ko as a library, the `skaffold` binary builds the container image on Cloud Build.
//
// The method uses the artifact input argument to generate a Skaffold Config manifest.
func (b *Builder) koBuildSpec(ctx context.Context, artifact *latest.Artifact, tag string, platforms platform.Matcher) (cloudbuild.Build, error) {
imageName, imageTag := splitImageNameAndTag(tag)
insecureRegistries := getKeys(b.cfg.GetInsecureRegistries())
skaffoldConfig := createSkaffoldConfig(artifact, imageName, platforms.Array(), insecureRegistries)
skaffoldYaml, err := yaml.Marshal(skaffoldConfig)
if err != nil {
return cloudbuild.Build{}, fmt.Errorf("marshalling Skaffold Config YAML for %s: %w", tag, err)
}
log.Entry(ctx).Debugf("Skaffold config for Cloud Build:\n%s\n", skaffoldYaml)
verbosity := log.GetLevel().String()
return cloudBuildConfig(b.KoImage, skaffoldYaml, imageTag, verbosity, artifact.KoArtifact.Env...), nil
}

func splitImageNameAndTag(imageNameWithTag string) (string, string) {
nameAndTag := strings.Split(imageNameWithTag, ":")
imageName := nameAndTag[0]
imageTag := "latest"
if len(nameAndTag) > 1 {
imageTag = nameAndTag[1]
}
return imageName, imageTag
}

func getKeys(in map[string]bool) []string {
var keys []string
for v := range in {
keys = append(keys, v)
}
return keys
}

// createSkaffoldConfig creates a Skaffold Config manifest for use in a build step on Cloud Build.
//
// The manifest uses a specific schema version that is known to be supported by a public Skaffold image on GCR/AR.
// The latest schema version is not used for two reasons:
//
// 1. There could be a mismatch between the versions of Skaffold used locally and on Cloud Build.
//
// For instance, a team could choose to use an LTS image for builds on Cloud Build.
//
// 2. The local version of Skaffold may not be available as a public image on GCR/AR.
//
// The manifest does not include artifact fields that are irrelevant for a remote build, such as Dependencies, LifecycleHooks, and Sync.
func createSkaffoldConfig(artifact *latest.Artifact, imageName string, platforms []string, insecureRegistries []string) *schema.SkaffoldConfig {
return &schema.SkaffoldConfig{
APIVersion: schema.Version,
Kind: "Config",
Pipeline: schema.Pipeline{
Build: schema.BuildConfig{
Artifacts: []*schema.Artifact{{
// Replace `ImageName` since we need the fully resolved name (with the Skaffold default repo).
ImageName: imageName,
// Copy values from the `artifact` function argument.
ArtifactType: schema.ArtifactType{
KoArtifact: &schema.KoArtifact{
BaseImage: artifact.KoArtifact.BaseImage,
Dir: artifact.KoArtifact.Dir,
Env: artifact.KoArtifact.Env,
Flags: artifact.KoArtifact.Flags,
Labels: artifact.KoArtifact.Labels,
Ldflags: artifact.KoArtifact.Ldflags,
Main: artifact.KoArtifact.Main,
},
},
Platforms: artifact.Platforms, // platforms defined for the artifact
Workspace: artifact.Workspace,
}},
InsecureRegistries: insecureRegistries,
Platforms: platforms, // platforms provided via command-line flag, or envvar
},
},
}
}

// cloudBuildConfig creates a single step build configuraration using the provided image and Skaffold config.
//
// The build step writes out the generated Skaffold Config manifest to a temporary file.
// Skaffold uses this Config to build and push the image.
func cloudBuildConfig(koImage string, skaffoldYaml []byte, imageTag string, verbosity string, env ...string) cloudbuild.Build {
return cloudbuild.Build{
Steps: []*cloudbuild.BuildStep{{
Name: koImage,
Entrypoint: "sh",
Args: []string{"-c", strings.Join(
[]string{
"skaffoldConfigFile=$(mktemp)",
// here document with quoted end marker to ensure no subsitution or expansion
fmt.Sprintf("cat << 'EOF' > $skaffoldConfigFile\n%s\nEOF", skaffoldYaml),
fmt.Sprintf("skaffold build --filename $skaffoldConfigFile --tag %s --verbosity %s", imageTag, verbosity),
},
"\n",
)},
Env: skaffoldGCBEnv(env...),
}},
}
}

func skaffoldGCBEnv(env ...string) []string {
defaultEnv := []string{
"SKAFFOLD_DETECT_MINIKUBE=false",
"SKAFFOLD_INTERACTIVE=false",
"SKAFFOLD_UPDATE_CHECK=false",
}
return append(defaultEnv, env...)
}
Loading

0 comments on commit 715b585

Please sign in to comment.