Merge pull request #2398 from crazy-max/ulimit

Add support for ulimit
master
Tõnis Tiigi 2021-10-08 10:50:44 -07:00 committed by GitHub
commit 7671a84fc2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1026 additions and 416 deletions

View File

@ -108,6 +108,7 @@ func TestIntegration(t *testing.T) {
testSecretMounts,
testExtraHosts,
testShmSize,
testUlimit,
testNetworkMode,
testFrontendMetadataReturn,
testFrontendUseSolveResults,
@ -560,6 +561,47 @@ func testShmSize(t *testing.T, sb integration.Sandbox) {
require.Contains(t, string(dt), `size=131072k`)
}
func testUlimit(t *testing.T, sb integration.Sandbox) {
c, err := New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()
busybox := llb.Image("busybox:latest")
st := llb.Scratch()
run := func(cmd string, ro ...llb.RunOption) {
st = busybox.Run(append(ro, llb.Shlex(cmd), llb.Dir("/wd"))...).AddMount("/wd", st)
}
run(`sh -c "ulimit -n > first"`, llb.AddUlimit(llb.UlimitNofile, 1062, 1062))
run(`sh -c "ulimit -n > 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.Equal(t, `1062`, strings.TrimSpace(string(dt)))
dt2, err := ioutil.ReadFile(filepath.Join(destDir, "second"))
require.NoError(t, err)
require.NotEqual(t, `1062`, strings.TrimSpace(string(dt2)))
}
func testNetworkMode(t *testing.T, sb integration.Sandbox) {
c, err := New(sb.Context(), sb.Address())
require.NoError(t, err)

View File

@ -213,6 +213,23 @@ func (e *ExecOp) Marshal(ctx context.Context, c *Constraints) (digest.Digest, []
meta.ShmSize = *shmSize
}
ulimits, err := getUlimit(e.base)(ctx, c)
if err != nil {
return "", nil, nil, nil, err
}
if len(ulimits) > 0 {
addCap(&e.constraints, pb.CapExecMetaUlimit)
ul := make([]*pb.Ulimit, len(ulimits))
for i, u := range ulimits {
ul[i] = &pb.Ulimit{
Name: u.Name,
Soft: u.Soft,
Hard: u.Hard,
}
}
meta.Ulimit = ul
}
network, err := getNetwork(e.base)(ctx, c)
if err != nil {
return "", nil, nil, nil, err
@ -513,6 +530,12 @@ func WithShmSize(kb int64) RunOption {
})
}
func AddUlimit(name UlimitName, soft int64, hard int64) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.AddUlimit(name, soft, hard)
})
}
func With(so ...StateOption) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.With(so...)
@ -682,3 +705,23 @@ const (
SecurityModeInsecure = pb.SecurityMode_INSECURE
SecurityModeSandbox = pb.SecurityMode_SANDBOX
)
type UlimitName string
const (
UlimitCore UlimitName = "core"
UlimitCPU UlimitName = "cpu"
UlimitData UlimitName = "data"
UlimitFsize UlimitName = "fsize"
UlimitLocks UlimitName = "locks"
UlimitMemlock UlimitName = "memlock"
UlimitMsgqueue UlimitName = "msgqueue"
UlimitNice UlimitName = "nice"
UlimitNofile UlimitName = "nofile"
UlimitNproc UlimitName = "nproc"
UlimitRss UlimitName = "rss"
UlimitRtprio UlimitName = "rtprio"
UlimitRttime UlimitName = "rttime"
UlimitSigpending UlimitName = "sigpending"
UlimitStack UlimitName = "stack"
)

View File

@ -18,13 +18,15 @@ var (
keyArgs = contextKeyT("llb.exec.args")
keyDir = contextKeyT("llb.exec.dir")
keyEnv = contextKeyT("llb.exec.env")
keyUser = contextKeyT("llb.exec.user")
keyHostname = contextKeyT("llb.exec.hostname")
keyExtraHost = contextKeyT("llb.exec.extrahost")
keyHostname = contextKeyT("llb.exec.hostname")
keyShmSize = contextKeyT("llb.exec.shmsize")
keyPlatform = contextKeyT("llb.platform")
keyNetwork = contextKeyT("llb.network")
keySecurity = contextKeyT("llb.security")
keyUlimit = contextKeyT("llb.exec.ulimit")
keyUser = contextKeyT("llb.exec.user")
keyPlatform = contextKeyT("llb.platform")
keyNetwork = contextKeyT("llb.network")
keySecurity = contextKeyT("llb.security")
)
func AddEnvf(key, value string, v ...interface{}) StateOption {
@ -253,6 +255,35 @@ func getShmSize(s State) func(context.Context, *Constraints) (*int64, error) {
}
}
func ulimit(name UlimitName, soft int64, hard int64) StateOption {
return func(s State) State {
return s.withValue(keyUlimit, func(ctx context.Context, c *Constraints) (interface{}, error) {
v, err := getUlimit(s)(ctx, c)
if err != nil {
return nil, err
}
return append(v, pb.Ulimit{
Name: string(name),
Soft: soft,
Hard: hard,
}), nil
})
}
}
func getUlimit(s State) func(context.Context, *Constraints) ([]pb.Ulimit, error) {
return func(ctx context.Context, c *Constraints) ([]pb.Ulimit, error) {
v, err := s.getValue(keyUlimit)(ctx, c)
if err != nil {
return nil, err
}
if v != nil {
return v.([]pb.Ulimit), nil
}
return nil, nil
}
}
func Network(v pb.NetMode) StateOption {
return func(s State) State {
return s.WithValue(keyNetwork, v)

View File

@ -401,6 +401,10 @@ func (s State) WithShmSize(kb int64) State {
return shmSize(kb)(s)
}
func (s State) AddUlimit(name UlimitName, soft int64, hard int64) State {
return ulimit(name, soft, hard)(s)
}
func (s State) isFileOpCopyInput() {}
type output struct {

View File

@ -19,6 +19,7 @@ type Meta struct {
ReadonlyRootFS bool
ExtraHosts []HostIP
ShmSize int64
Ulimit []*pb.Ulimit
NetMode pb.NetMode
SecurityMode pb.SecurityMode
}

View File

@ -72,6 +72,12 @@ func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mou
return nil, nil, err
}
if rlimitsOpts, err := generateRlimitOpts(meta.Ulimit); err == nil {
opts = append(opts, rlimitsOpts...)
} else {
return nil, nil, err
}
hostname := defaultHostname
if meta.Hostname != "" {
hostname = meta.Hostname
@ -100,13 +106,16 @@ func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mou
return nil, nil, err
}
if len(meta.Ulimit) == 0 {
// reset open files limit
s.Process.Rlimits = nil
}
// set the networking information on the spec
if err := namespace.Set(s); err != nil {
return nil, nil, err
}
s.Process.Rlimits = nil // reset open files limit
sm := &submounts{}
var releasers []func() error

View File

@ -4,6 +4,8 @@ package oci
import (
"context"
"fmt"
"strings"
"github.com/containerd/containerd/containers"
"github.com/containerd/containerd/oci"
@ -78,6 +80,29 @@ func generateIDmapOpts(idmap *idtools.IdentityMapping) ([]oci.SpecOpts, error) {
}, nil
}
func generateRlimitOpts(ulimits []*pb.Ulimit) ([]oci.SpecOpts, error) {
if len(ulimits) == 0 {
return nil, nil
}
var rlimits []specs.POSIXRlimit
for _, u := range ulimits {
if u == nil {
continue
}
rlimits = append(rlimits, specs.POSIXRlimit{
Type: fmt.Sprintf("RLIMIT_%s", strings.ToUpper(u.Name)),
Hard: uint64(u.Hard),
Soft: uint64(u.Soft),
})
}
return []oci.SpecOpts{
func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
s.Process.Rlimits = rlimits
return nil
},
}, nil
}
// withDefaultProfile sets the default seccomp profile to the spec.
// Note: must follow the setting of process capabilities
func withDefaultProfile() oci.SpecOpts {

View File

@ -35,3 +35,10 @@ func generateIDmapOpts(idmap *idtools.IdentityMapping) ([]oci.SpecOpts, error) {
}
return nil, errors.New("no support for IdentityMapping on Windows")
}
func generateRlimitOpts(ulimits []*pb.Ulimit) ([]oci.SpecOpts, error) {
if len(ulimits) == 0 {
return nil, nil
}
return nil, errors.New("no support for POSIXRlimit on Windows")
}

View File

@ -14,6 +14,7 @@ import (
"strings"
"github.com/containerd/containerd/platforms"
"github.com/docker/go-units"
controlapi "github.com/moby/buildkit/api/services/control"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
@ -55,6 +56,7 @@ const (
keyOverrideCopyImage = "override-copy-image" // remove after CopyOp implemented
keyShmSize = "shm-size"
keyTargetPlatform = "platform"
keyUlimit = "ulimit"
// Don't forget to update frontend documentation if you add
// a new build-arg: frontend/dockerfile/docs/syntax.md
@ -122,6 +124,11 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) {
return nil, errors.Wrap(err, "failed to parse shm size")
}
ulimit, err := parseUlimits(opts[keyUlimit])
if err != nil {
return nil, errors.Wrap(err, "failed to parse ulimit")
}
defaultNetMode, err := parseNetMode(opts[keyForceNetwork])
if err != nil {
return nil, err
@ -434,6 +441,7 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) {
PrefixPlatform: exportMap,
ExtraHosts: extraHosts,
ShmSize: shmSize,
Ulimit: ulimit,
ForceNetMode: defaultNetMode,
OverrideCopyImage: opts[keyOverrideCopyImage],
LLBCaps: &caps,
@ -693,6 +701,30 @@ func parseShmSize(v string) (int64, error) {
return kb, nil
}
func parseUlimits(v string) ([]pb.Ulimit, error) {
if v == "" {
return nil, nil
}
out := make([]pb.Ulimit, 0)
csvReader := csv.NewReader(strings.NewReader(v))
fields, err := csvReader.Read()
if err != nil {
return nil, err
}
for _, field := range fields {
ulimit, err := units.ParseUlimit(field)
if err != nil {
return nil, err
}
out = append(out, pb.Ulimit{
Name: ulimit.Name,
Soft: ulimit.Soft,
Hard: ulimit.Hard,
})
}
return out, nil
}
func parseNetMode(v string) (pb.NetMode, error) {
if v == "" {
return llb.NetModeSandbox, nil

View File

@ -60,6 +60,7 @@ type ConvertOpt struct {
PrefixPlatform bool
ExtraHosts []llb.HostIP
ShmSize int64
Ulimit []pb.Ulimit
ForceNetMode pb.NetMode
OverrideCopyImage string
LLBCaps *apicaps.CapSet
@ -391,6 +392,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State,
targetPlatform: platformOpt.targetPlatform,
extraHosts: opt.ExtraHosts,
shmSize: opt.ShmSize,
ulimit: opt.Ulimit,
copyImage: opt.OverrideCopyImage,
llbCaps: opt.LLBCaps,
sourceMap: opt.SourceMap,
@ -522,6 +524,7 @@ type dispatchOpt struct {
buildPlatforms []ocispecs.Platform
extraHosts []llb.HostIP
shmSize int64
ulimit []pb.Ulimit
copyImage string
llbCaps *apicaps.CapSet
sourceMap *llb.SourceMap
@ -789,6 +792,12 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
opt = append(opt, networkOpt)
}
if dopt.llbCaps != nil && dopt.llbCaps.Supports(pb.CapExecMetaUlimit) == nil {
for _, u := range dopt.ulimit {
opt = append(opt, llb.AddUlimit(llb.UlimitName(u.Name), u.Soft, u.Hard))
}
}
shlex := *dopt.shlex
shlex.RawQuotes = true
shlex.SkipUnsetEnv = true
@ -804,6 +813,7 @@ func dispatchRun(d *dispatchState, c *instructions.RunCommand, proxy *llb.ProxyE
if dopt.shmSize > 0 {
opt = append(opt, llb.WithShmSize(dopt.shmSize))
}
d.state = d.state.Run(opt...).Root()
return commitToHistory(&d.image, "RUN "+runCommandString(args, d.buildArgs, shell.BuildEnvs(env)), true, &d.state)
}

View File

@ -115,6 +115,8 @@ var allTests = []integration.Test{
testWildcardRenameCache,
testDockerfileInvalidInstruction,
testBuildInfo,
testShmSize,
testUlimit,
}
var fileOpTests = []integration.Test{
@ -5264,6 +5266,96 @@ COPY --from=buildx /buildx /usr/libexec/docker/cli-plugins/docker-buildx
assert.Equal(t, "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c", bi["sources"][3].Pin)
}
func testShmSize(t *testing.T, sb integration.Sandbox) {
f := getFrontend(t, sb)
dockerfile := []byte(`
FROM busybox AS base
RUN mount | grep /dev/shm > /shmsize
FROM scratch
COPY --from=base /shmsize /
`)
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{
"shm-size": "131072",
},
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, "shmsize"))
require.NoError(t, err)
require.Contains(t, string(dt), `size=131072k`)
}
func testUlimit(t *testing.T, sb integration.Sandbox) {
f := getFrontend(t, sb)
dockerfile := []byte(`
FROM busybox AS base
RUN ulimit -n > /ulimit
FROM scratch
COPY --from=base /ulimit /
`)
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{
"ulimit": "nofile=1062:1062",
},
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, "ulimit"))
require.NoError(t, err)
require.Equal(t, `1062`, strings.TrimSpace(string(dt)))
}
func tmpdir(appliers ...fstest.Applier) (string, error) {
tmpdir, err := ioutil.TempDir("", "buildkit-dockerfile")
if err != nil {

2
go.mod
View File

@ -21,6 +21,7 @@ require (
// docker: the actual version is replaced in replace()
github.com/docker/docker v20.10.7+incompatible // master (v21.xx-dev)
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0
github.com/gofrs/flock v0.7.3
github.com/gogo/googleapis v1.4.0
github.com/gogo/protobuf v1.3.2
@ -92,7 +93,6 @@ require (
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/uuid v1.2.0 // indirect

View File

@ -316,6 +316,7 @@ func (e *execOp) Exec(ctx context.Context, g session.Group, inputs []solver.Resu
ReadonlyRootFS: p.ReadonlyRootFS,
ExtraHosts: extraHosts,
ShmSize: e.op.Meta.ShmSize,
Ulimit: e.op.Meta.Ulimit,
NetMode: e.op.Network,
SecurityMode: e.op.Security,
}

View File

@ -35,22 +35,22 @@ const (
CapBuildOpLLBFileName apicaps.CapID = "source.buildop.llbfilename"
CapExecMetaBase apicaps.CapID = "exec.meta.base"
CapExecMetaProxy apicaps.CapID = "exec.meta.proxyenv"
CapExecMetaNetwork apicaps.CapID = "exec.meta.network"
CapExecMetaSecurity apicaps.CapID = "exec.meta.security"
CapExecMetaSetsDefaultPath apicaps.CapID = "exec.meta.setsdefaultpath"
CapExecMountBind apicaps.CapID = "exec.mount.bind"
CapExecMountBindReadWriteNoOuput apicaps.CapID = "exec.mount.bind.readwrite-nooutput"
CapExecMountCache apicaps.CapID = "exec.mount.cache"
CapExecMountCacheSharing apicaps.CapID = "exec.mount.cache.sharing"
CapExecMountSelector apicaps.CapID = "exec.mount.selector"
CapExecMountTmpfs apicaps.CapID = "exec.mount.tmpfs"
CapExecMountSecret apicaps.CapID = "exec.mount.secret"
CapExecMountSSH apicaps.CapID = "exec.mount.ssh"
CapExecCgroupsMounted apicaps.CapID = "exec.cgroup"
CapExecMetaBase apicaps.CapID = "exec.meta.base"
CapExecMetaNetwork apicaps.CapID = "exec.meta.network"
CapExecMetaProxy apicaps.CapID = "exec.meta.proxyenv"
CapExecMetaSecurity apicaps.CapID = "exec.meta.security"
CapExecMetaSecurityDeviceWhitelistV1 apicaps.CapID = "exec.meta.security.devices.v1"
CapExecMetaSetsDefaultPath apicaps.CapID = "exec.meta.setsdefaultpath"
CapExecMetaUlimit apicaps.CapID = "exec.meta.ulimit"
CapExecMountBind apicaps.CapID = "exec.mount.bind"
CapExecMountBindReadWriteNoOuput apicaps.CapID = "exec.mount.bind.readwrite-nooutput"
CapExecMountCache apicaps.CapID = "exec.mount.cache"
CapExecMountCacheSharing apicaps.CapID = "exec.mount.cache.sharing"
CapExecMountSelector apicaps.CapID = "exec.mount.selector"
CapExecMountTmpfs apicaps.CapID = "exec.mount.tmpfs"
CapExecMountSecret apicaps.CapID = "exec.mount.secret"
CapExecMountSSH apicaps.CapID = "exec.mount.ssh"
CapExecCgroupsMounted apicaps.CapID = "exec.cgroup"
CapFileBase apicaps.CapID = "file.base"
CapFileRmWildcard apicaps.CapID = "file.rm.wildcard"
@ -236,6 +236,12 @@ func init() {
Status: apicaps.CapStatusExperimental,
})
Caps.Init(apicaps.Cap{
ID: CapExecMetaUlimit,
Enabled: true,
Status: apicaps.CapStatusExperimental,
})
Caps.Init(apicaps.Cap{
ID: CapExecMountBind,
Enabled: true,

File diff suppressed because it is too large Load Diff

View File

@ -59,6 +59,18 @@ message Meta {
repeated HostIP extraHosts = 6;
string hostname = 7;
int64 shmSize = 8;
repeated Ulimit ulimit = 9;
}
message HostIP {
string Host = 1;
string IP = 2;
}
message Ulimit {
string Name = 1;
int64 Soft = 2;
int64 Hard = 3;
}
enum NetMode {
@ -244,11 +256,6 @@ message Definition {
Source Source = 3;
}
message HostIP {
string Host = 1;
string IP = 2;
}
message FileOp {
repeated FileAction actions = 2;
}