Skip to content

Commit

Permalink
cgroup-parent support
Browse files Browse the repository at this point in the history
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
  • Loading branch information
crazy-max committed Oct 27, 2021
1 parent 33fb83e commit c82ef27
Show file tree
Hide file tree
Showing 15 changed files with 391 additions and 192 deletions.
46 changes: 46 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func TestIntegration(t *testing.T) {
testExtraHosts,
testShmSize,
testUlimit,
testCgroupParent,
testNetworkMode,
testFrontendMetadataReturn,
testFrontendUseSolveResults,
Expand Down Expand Up @@ -605,6 +606,51 @@ func testUlimit(t *testing.T, sb integration.Sandbox) {
require.NotEqual(t, `1062`, strings.TrimSpace(string(dt2)))
}

func testCgroupParent(t *testing.T, sb integration.Sandbox) {
if sb.Rootless() {
t.SkipNow()
}

c, err := New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()

img := llb.Image("alpine:latest")
st := llb.Scratch()

run := func(cmd string, ro ...llb.RunOption) {
st = img.Run(append(ro, llb.Shlex(cmd), llb.Dir("/wd"))...).AddMount("/wd", st)
}

run(`sh -c "cat /proc/self/cgroup > first"`, llb.WithCgroupParent("foocgroup"))
run(`sh -c "cat /proc/self/cgroup > second"`)

def, err := st.Marshal(sb.Context())
require.NoError(t, err)

destDir, err := ioutil.TempDir("", "buildkit")
require.NoError(t, err)
defer os.RemoveAll(destDir)

_, err = c.Solve(sb.Context(), def, SolveOpt{
Exports: []ExportEntry{
{
Type: ExporterLocal,
OutputDir: destDir,
},
},
}, nil)
require.NoError(t, err)

dt, err := ioutil.ReadFile(filepath.Join(destDir, "first"))
require.NoError(t, err)
require.Contains(t, strings.TrimSpace(string(dt)), `/foocgroup/buildkit/`)

dt2, err := ioutil.ReadFile(filepath.Join(destDir, "second"))
require.NoError(t, err)
require.NotContains(t, strings.TrimSpace(string(dt2)), `/foocgroup/buildkit/`)
}

func testNetworkMode(t *testing.T, sb integration.Sandbox) {
c, err := New(sb.Context(), sb.Address())
require.NoError(t, err)
Expand Down
22 changes: 17 additions & 5 deletions client/llb/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,12 +186,18 @@ func (e *ExecOp) Marshal(ctx context.Context, c *Constraints) (digest.Digest, []
return "", nil, nil, nil, err
}

cgrpParent, err := getCgroupParent(e.base)(ctx, c)
if err != nil {
return "", nil, nil, nil, err
}

meta := &pb.Meta{
Args: args,
Env: env.ToArray(),
Cwd: cwd,
User: user,
Hostname: hostname,
Args: args,
Env: env.ToArray(),
Cwd: cwd,
User: user,
Hostname: hostname,
CgroupParent: cgrpParent,
}

extraHosts, err := getExtraHosts(e.base)(ctx, c)
Expand Down Expand Up @@ -554,6 +560,12 @@ func AddUlimit(name UlimitName, soft int64, hard int64) RunOption {
})
}

func WithCgroupParent(cp string) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.WithCgroupParent(cp)
})
}

func With(so ...StateOption) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.With(so...)
Expand Down
34 changes: 27 additions & 7 deletions client/llb/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ import (
type contextKeyT string

var (
keyArgs = contextKeyT("llb.exec.args")
keyDir = contextKeyT("llb.exec.dir")
keyEnv = contextKeyT("llb.exec.env")
keyExtraHost = contextKeyT("llb.exec.extrahost")
keyHostname = contextKeyT("llb.exec.hostname")
keyUlimit = contextKeyT("llb.exec.ulimit")
keyUser = contextKeyT("llb.exec.user")
keyArgs = contextKeyT("llb.exec.args")
keyDir = contextKeyT("llb.exec.dir")
keyEnv = contextKeyT("llb.exec.env")
keyExtraHost = contextKeyT("llb.exec.extrahost")
keyHostname = contextKeyT("llb.exec.hostname")
keyUlimit = contextKeyT("llb.exec.ulimit")
keyCgroupParent = contextKeyT("llb.exec.cgroup.parent")
keyUser = contextKeyT("llb.exec.user")

keyPlatform = contextKeyT("llb.platform")
keyNetwork = contextKeyT("llb.network")
Expand Down Expand Up @@ -263,6 +264,25 @@ func getUlimit(s State) func(context.Context, *Constraints) ([]pb.Ulimit, error)
}
}

func cgroupParent(cp string) StateOption {
return func(s State) State {
return s.WithValue(keyCgroupParent, cp)
}
}

func getCgroupParent(s State) func(context.Context, *Constraints) (string, error) {
return func(ctx context.Context, c *Constraints) (string, error) {
v, err := s.getValue(keyCgroupParent)(ctx, c)
if err != nil {
return "", err
}
if v != nil {
return v.(string), nil
}
return "", nil
}
}

func Network(v pb.NetMode) StateOption {
return func(s State) State {
return s.WithValue(keyNetwork, v)
Expand Down
4 changes: 4 additions & 0 deletions client/llb/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,10 @@ func (s State) AddUlimit(name UlimitName, soft int64, hard int64) State {
return ulimit(name, soft, hard)(s)
}

func (s State) WithCgroupParent(cp string) State {
return cgroupParent(cp)(s)
}

func (s State) isFileOpCopyInput() {}

type output struct {
Expand Down
13 changes: 1 addition & 12 deletions executor/containerdexecutor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
Expand Down Expand Up @@ -162,18 +161,8 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
opts = append(opts, containerdoci.WithRootFSReadonly())
}

if w.cgroupParent != "" {
var cgroupsPath string
lastSeparator := w.cgroupParent[len(w.cgroupParent)-1:]
if strings.Contains(w.cgroupParent, ".slice") && lastSeparator == ":" {
cgroupsPath = w.cgroupParent + id
} else {
cgroupsPath = filepath.Join("/", w.cgroupParent, "buildkit", id)
}
opts = append(opts, containerdoci.WithCgroup(cgroupsPath))
}
processMode := oci.ProcessSandbox // FIXME(AkihiroSuda)
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, processMode, nil, w.apparmorProfile, w.traceSocket, opts...)
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.cgroupParent, processMode, nil, w.apparmorProfile, w.traceSocket, opts...)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Meta struct {
ReadonlyRootFS bool
ExtraHosts []HostIP
Ulimit []*pb.Ulimit
CgroupParent string
NetMode pb.NetMode
SecurityMode pb.SecurityMode
}
Expand Down
18 changes: 17 additions & 1 deletion executor/oci/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package oci
import (
"context"
"path"
"path/filepath"
"strings"
"sync"

"github.com/containerd/containerd/containers"
Expand Down Expand Up @@ -37,11 +39,25 @@ const (

// GenerateSpec generates spec using containerd functionality.
// opts are ignored for s.Process, s.Hostname, and s.Mounts .
func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mount, id, resolvConf, hostsFile string, namespace network.Namespace, processMode ProcessMode, idmap *idtools.IdentityMapping, apparmorProfile string, tracingSocket string, opts ...oci.SpecOpts) (*specs.Spec, func(), error) {
func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mount, id, resolvConf, hostsFile string, namespace network.Namespace, cgroupParent string, processMode ProcessMode, idmap *idtools.IdentityMapping, apparmorProfile string, tracingSocket string, opts ...oci.SpecOpts) (*specs.Spec, func(), error) {
c := &containers.Container{
ID: id,
}

if len(meta.CgroupParent) > 0 {
cgroupParent = meta.CgroupParent
}
if cgroupParent != "" {
var cgroupsPath string
lastSeparator := cgroupParent[len(cgroupParent)-1:]
if strings.Contains(cgroupParent, ".slice") && lastSeparator == ":" {
cgroupsPath = cgroupParent + id
} else {
cgroupsPath = filepath.Join("/", cgroupParent, "buildkit", id)
}
opts = append(opts, oci.WithCgroup(cgroupsPath))
}

// containerd/oci.GenerateSpec requires a namespace, which
// will be used to namespace specs.Linux.CgroupsPath if generated
if _, ok := namespaces.Namespace(ctx); !ok {
Expand Down
13 changes: 1 addition & 12 deletions executor/runcexecutor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
Expand Down Expand Up @@ -250,17 +249,7 @@ func (w *runcExecutor) Run(ctx context.Context, id string, root executor.Mount,
}
}

if w.cgroupParent != "" {
var cgroupsPath string
lastSeparator := w.cgroupParent[len(w.cgroupParent)-1:]
if strings.Contains(w.cgroupParent, ".slice") && lastSeparator == ":" {
cgroupsPath = w.cgroupParent + id
} else {
cgroupsPath = filepath.Join("/", w.cgroupParent, "buildkit", id)
}
opts = append(opts, containerdoci.WithCgroup(cgroupsPath))
}
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.processMode, w.idmap, w.apparmorProfile, w.tracingSocket, opts...)
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.cgroupParent, w.processMode, w.idmap, w.apparmorProfile, w.tracingSocket, opts...)
if err != nil {
return err
}
Expand Down
2 changes: 2 additions & 0 deletions frontend/dockerfile/builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const (
keyFilename = "filename"
keyCacheFrom = "cache-from" // for registry only. deprecated in favor of keyCacheImports
keyCacheImports = "cache-imports" // JSON representation of []CacheOptionsEntry
keyCgroupParent = "cgroup-parent"
keyContextSubDir = "contextsubdir"
keyForceNetwork = "force-network-mode"
keyGlobalAddHosts = "add-hosts"
Expand Down Expand Up @@ -442,6 +443,7 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) {
ExtraHosts: extraHosts,
ShmSize: shmSize,
Ulimit: ulimit,
CgroupParent: opts[keyCgroupParent],
ForceNetMode: defaultNetMode,
OverrideCopyImage: opts[keyOverrideCopyImage],
LLBCaps: &caps,
Expand Down
9 changes: 9 additions & 0 deletions frontend/dockerfile/dockerfile2llb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ type ConvertOpt struct {
ExtraHosts []llb.HostIP
ShmSize int64
Ulimit []pb.Ulimit
CgroupParent string
ForceNetMode pb.NetMode
OverrideCopyImage string
LLBCaps *apicaps.CapSet
Expand Down Expand Up @@ -393,6 +394,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
extraHosts: opt.ExtraHosts,
shmSize: opt.ShmSize,
ulimit: opt.Ulimit,
cgroupParent: opt.CgroupParent,
copyImage: opt.OverrideCopyImage,
llbCaps: opt.LLBCaps,
sourceMap: opt.SourceMap,
Expand Down Expand Up @@ -525,6 +527,7 @@ type dispatchOpt struct {
extraHosts []llb.HostIP
shmSize int64
ulimit []pb.Ulimit
cgroupParent string
copyImage string
llbCaps *apicaps.CapSet
sourceMap *llb.SourceMap
Expand Down Expand Up @@ -817,6 +820,12 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
}
}

if dopt.llbCaps != nil && dopt.llbCaps.Supports(pb.CapExecMetaCgroupParent) == nil {
if len(dopt.cgroupParent) > 0 {
opt = append(opt, llb.WithCgroupParent(dopt.cgroupParent))
}
}

d.state = d.state.Run(opt...).Root()
return commitToHistory(&d.image, "RUN "+runCommandString(args, d.buildArgs, shell.BuildEnvs(env)), true, &d.state)
}
Expand Down
50 changes: 50 additions & 0 deletions frontend/dockerfile/dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ var allTests = []integration.Test{
testBuildInfo,
testShmSize,
testUlimit,
testCgroupParent,
}

var fileOpTests = []integration.Test{
Expand Down Expand Up @@ -5356,6 +5357,55 @@ COPY --from=base /ulimit /
require.Equal(t, `1062`, strings.TrimSpace(string(dt)))
}

func testCgroupParent(t *testing.T, sb integration.Sandbox) {
if sb.Rootless() {
t.SkipNow()
}

f := getFrontend(t, sb)
dockerfile := []byte(`
FROM alpine AS base
RUN cat /proc/self/cgroup > /out
FROM scratch
COPY --from=base /out /
`)

dir, err := tmpdir(
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)
require.NoError(t, err)
defer os.RemoveAll(dir)

c, err := client.New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()

destDir, err := ioutil.TempDir("", "buildkit")
require.NoError(t, err)
defer os.RemoveAll(destDir)

_, err = f.Solve(sb.Context(), c, client.SolveOpt{
FrontendAttrs: map[string]string{
"cgroup-parent": "foocgroup",
},
LocalDirs: map[string]string{
builder.DefaultLocalNameDockerfile: dir,
builder.DefaultLocalNameContext: dir,
},
Exports: []client.ExportEntry{
{
Type: client.ExporterLocal,
OutputDir: destDir,
},
},
}, nil)
require.NoError(t, err)

dt, err := ioutil.ReadFile(filepath.Join(destDir, "out"))
require.NoError(t, err)
require.Contains(t, strings.TrimSpace(string(dt)), `/foocgroup/buildkit/`)
}

func tmpdir(appliers ...fstest.Applier) (string, error) {
tmpdir, err := ioutil.TempDir("", "buildkit-dockerfile")
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions solver/llbsolver/ops/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@ func (e *execOp) Exec(ctx context.Context, g session.Group, inputs []solver.Resu
ReadonlyRootFS: p.ReadonlyRootFS,
ExtraHosts: extraHosts,
Ulimit: e.op.Meta.Ulimit,
CgroupParent: e.op.Meta.CgroupParent,
NetMode: e.op.Network,
SecurityMode: e.op.Security,
}
Expand Down
Loading

0 comments on commit c82ef27

Please sign in to comment.