Skip to content

Commit

Permalink
feat(driver/kubernetes): support mount buildkit.toml and qemu installing
Browse files Browse the repository at this point in the history
Signed-off-by: Morlay <[email protected]>
  • Loading branch information
morlay committed Jul 21, 2021
1 parent d9ee3b1 commit b79b9af
Show file tree
Hide file tree
Showing 10 changed files with 290 additions and 50 deletions.
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,11 @@ generate-authors:
./hack/generate-authors

.PHONY: vendor lint shell binaries install binaries-cross validate-all generate-authors validate-docs docs

debug:
mkdir -p ~/.docker/cli-plugins
go build -o ~/.docker/cli-plugins/docker-buildx ./cmd/buildx
docker buildx rm sg || true
KUBECONFIG=~/.kube/config--hw-sg.yaml docker buildx create --platform=linux/amd64,linux/arm64 --name=sg --driver=kubernetes --driver-opt=qemu.install=true
docker buildx inspect sg --bootstrap
docker buildx ls
2 changes: 2 additions & 0 deletions docs/reference/buildx_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ Passes additional driver-specific options. Details for each driver:
- `nodeselector="label1=value1,label2=value2"` - Sets the kv of `Pod` nodeSelector. No Defaults. Example `nodeselector=kubernetes.io/arch=arm64`
- `rootless=(true|false)` - Run the container as a non-root user without `securityContext.privileged`. [Using Ubuntu host kernel is recommended](https://github.com/moby/buildkit/blob/master/docs/rootless.md). Defaults to false.
- `loadbalance=(sticky|random)` - Load-balancing strategy. If set to "sticky", the pod is chosen using the hash of the context path. Defaults to "sticky"
- `qemu.install=(true|false)` - Install QEMU emulation for multi platforms support.
- `qemu.image=IMAGE` - Sets the QEMU emulation image. Defaults to `tonistiigi/binfmt:latest`

### <a name="leave"></a> Remove a node from a builder (--leave)

Expand Down
74 changes: 74 additions & 0 deletions docs/reference/buildx_create_for_multi_arch_build_in_k8s.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Buildx create for multi-arch builds in k8s

## With QEMU emulation

```console
$ KUBECONFIG=${KUBECONFIG} \
docker buildx create \
--name=builder \
--platform=linux/amd64,linux/arm64 \
--driver=kubernetes \
--driver-opt=namespace=buildkit,withqemu=true
```

### Known Issues

QEUM only work well for `executing`, but for `compiling`, it will be very slow and may be panic.

In the mode, if still want to compile binaries in docker.
please use `FROM --platform=${BUILDPLATFORM}` to disable QEUM for compiling stage.

Example for golang:
```
FROM --platform=${BUILDPLATFORM} golang:1.6 as builder
ARG TARGETARCH
RUN GOARCH=${TARGETARCH} go build -o /bin/app-linux-${TARGETARCH} ./path/to/cmd/app
FROM scratch
ARG TARGETARCH
COPY --from=builder /bin/app-linux-${TARGETARCH} /bin/app
```

## With native nodes

```console
# create builder `builder` and add native x86_64 node
$ KUBECONFIG=${KUBECONFIG} \
docker buildx create \
--name=builder \
--platform=linux/amd64 \
--node=builder-amd64 \
--driver=kubernetes \
--driver-opt=namespace=buildkit,nodeselector="beta.kubernetes.io/arch=amd64"

# append node to same builder with native aarch64 node
$ KUBECONFIG=${KUBECONFIG} \
docker buildx create \
--name=builder --append \
--platform=linux/arm64 \
--node=builder-arm64 \
--driver=kubernetes \
--driver-opt=namespace=buildkit,nodeselector="beta.kubernetes.io/arch=arm64"
```

* `KUBECONFIG` could be different.
* `buildx create` executing on a pod of multi-arch cluster, `KUBECONFIG` could be unset, but make sure the pod `serviceAccount` could access `deplopments,pods,configmaps` of assigned `namespace`

### Known Issues

In this mode, docker build for different arch on matched native host.
The build time may be longer.
Even `FROM --platform=${BUILDPLATFORM}` defined, all stages will build for each arch.

However, it is totally native.
Projects, witch needs to build on native system, will be happy with the mode.

## Tips

Once `buildx create` in k8s, the created deployments will not be removed until `buildx rm` called.
So we could set `RUN --mount=type=cache` for sharing common caches for different projects.

However, For nodejs user.
Don't shared `npm` or `yarn` global caches (`pnpm` will be better), restoring caches may be slower than reinstalling.
1 change: 1 addition & 0 deletions driver/bkimage/bkimage.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ package bkimage

const (
DefaultImage = "moby/buildkit:buildx-stable-1" // TODO: make this verified
QemuImage = "tonistiigi/binfmt:latest" // TODO: make this verified
DefaultRootlessImage = DefaultImage + "-rootless"
)
35 changes: 33 additions & 2 deletions driver/kubernetes/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"github.com/moby/buildkit/util/tracing/detect"
"github.com/pkg/errors"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
clientappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
Expand All @@ -39,15 +41,18 @@ type Driver struct {
factory driver.Factory
minReplicas int
deployment *appsv1.Deployment
configMap *corev1.ConfigMap
clientset *kubernetes.Clientset
deploymentClient clientappsv1.DeploymentInterface
podClient clientcorev1.PodInterface
configMapClient clientcorev1.ConfigMapInterface
podChooser podchooser.PodChooser
}

func (d *Driver) IsMobyDriver() bool {
return false
}

func (d *Driver) Config() driver.InitConfig {
return d.InitConfig
}
Expand All @@ -56,7 +61,24 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
return progress.Wrap("[internal] booting buildkit", l, func(sub progress.SubLogger) error {
_, err := d.deploymentClient.Get(ctx, d.deployment.Name, metav1.GetOptions{})
if err != nil {
// TODO: return err if err != ErrNotFound
if !apierrors.IsNotFound(err) {
return errors.Wrapf(err, "error for bootstrap %q", d.deployment.Name)
}

if d.configMap != nil {
// create ConfigMap first if exists
_, err = d.configMapClient.Create(ctx, d.configMap, metav1.CreateOptions{})
if err != nil {
if !apierrors.IsAlreadyExists(err) {
return errors.Wrapf(err, "error while calling configMapClient.Create for %q", d.configMap.Name)
}
_, err = d.configMapClient.Update(ctx, d.configMap, metav1.UpdateOptions{})
if err != nil {
return errors.Wrapf(err, "error while calling configMapClient.Update for %q", d.configMap.Name)
}
}
}

_, err = d.deploymentClient.Create(ctx, d.deployment, metav1.CreateOptions{})
if err != nil {
return errors.Wrapf(err, "error while calling deploymentClient.Create for %q", d.deployment.Name)
Expand Down Expand Up @@ -145,7 +167,16 @@ func (d *Driver) Stop(ctx context.Context, force bool) error {

func (d *Driver) Rm(ctx context.Context, force bool, rmVolume bool) error {
if err := d.deploymentClient.Delete(ctx, d.deployment.Name, metav1.DeleteOptions{}); err != nil {
return errors.Wrapf(err, "error while calling deploymentClient.Delete for %q", d.deployment.Name)
if !apierrors.IsNotFound(err) {
return errors.Wrapf(err, "error while calling deploymentClient.Delete for %q", d.deployment.Name)
}
}
if d.configMap != nil {
if err := d.configMapClient.Delete(ctx, d.configMap.Name, metav1.DeleteOptions{}); err != nil {
if !apierrors.IsNotFound(err) {
return errors.Wrapf(err, "error while calling configMapClient.Delete for %q", d.configMap.Name)
}
}
}
return nil
}
Expand Down
39 changes: 33 additions & 6 deletions driver/kubernetes/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package kubernetes

import (
"context"
"os"
"strconv"
"strings"

Expand Down Expand Up @@ -59,11 +60,13 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
if err != nil {
return nil, err
}

d := &Driver{
factory: f,
InitConfig: cfg,
clientset: clientset,
}

deploymentOpt := &manifest.DeploymentOpt{
Name: deploymentName,
Image: bkimage.DefaultImage,
Expand All @@ -72,12 +75,25 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
Rootless: false,
Platforms: cfg.Platforms,
}

deploymentOpt.Qemu.Image = bkimage.QemuImage

if cfg.ConfigFile != "" {
buildkitConfig, err := os.ReadFile(cfg.ConfigFile)
if err != nil {
return nil, err
}
deploymentOpt.BuildkitConfig = buildkitConfig
}

loadbalance := LoadbalanceSticky
imageOverride := ""

for k, v := range cfg.DriverOpts {
switch k {
case "image":
imageOverride = v
if v != "" {
deploymentOpt.Image = v
}
case "namespace":
namespace = v
case "replicas":
Expand Down Expand Up @@ -117,20 +133,31 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
return nil, errors.Errorf("invalid loadbalance %q", v)
}
loadbalance = v
case "qemu.install":
deploymentOpt.Qemu.Install, err = strconv.ParseBool(v)
if err != nil {
return nil, err
}
case "qemu.image":
if v != "" {
deploymentOpt.Qemu.Image = v
}
default:
return nil, errors.Errorf("invalid driver option %s for driver %s", k, DriverName)
}
}
if imageOverride != "" {
deploymentOpt.Image = imageOverride
}
d.deployment, err = manifest.NewDeployment(deploymentOpt)

d.deployment, d.configMap, err = manifest.NewDeployment(deploymentOpt)
if err != nil {
return nil, err
}

d.minReplicas = deploymentOpt.Replicas

d.deploymentClient = clientset.AppsV1().Deployments(namespace)
d.podClient = clientset.CoreV1().Pods(namespace)
d.configMapClient = clientset.CoreV1().ConfigMaps(namespace)

switch loadbalance {
case LoadbalanceSticky:
d.podChooser = &podchooser.StickyPodChooser{
Expand Down
Loading

0 comments on commit b79b9af

Please sign in to comment.