Skip to content

Commit

Permalink
set cgroup parent on build child containers
Browse files Browse the repository at this point in the history
  • Loading branch information
bparees committed Jun 15, 2017
1 parent 265a587 commit 6fbb27b
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 52 deletions.
16 changes: 11 additions & 5 deletions pkg/build/builder/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ func execPostCommitHook(client DockerClient, postCommitSpec api.BuildPostCommitS
if err != nil {
return fmt.Errorf("read cgroup limits: %v", err)
}
parent, err := GetCgroupParent()
if err != nil {
return fmt.Errorf("read cgroup parent: %v", err)
}

return dockerRun(client, docker.CreateContainerOptions{
Name: containerName,
Expand All @@ -140,11 +144,13 @@ func execPostCommitHook(client DockerClient, postCommitSpec api.BuildPostCommitS
},
HostConfig: &docker.HostConfig{
// Limit container's resource allocation.
CPUShares: limits.CPUShares,
CPUPeriod: limits.CPUPeriod,
CPUQuota: limits.CPUQuota,
Memory: limits.MemoryLimitBytes,
MemorySwap: limits.MemorySwap,
// Though we are capped on memory and cpu at the cgroup parent level,
// some build containers care what their memory limit is so they can
// adapt, thus we need to set the memory limit at the container level
// too, so that information is available to them.
Memory: limits.MemoryLimitBytes,
MemorySwap: limits.MemorySwap,
CgroupParent: parent,
},
}, docker.AttachToContainerOptions{
// Stream logs to stdout and stderr.
Expand Down
9 changes: 6 additions & 3 deletions pkg/build/builder/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,13 +426,16 @@ func (d *DockerBuilder) dockerBuild(dir string, tag string, secrets []api.Secret
NetworkMode: string(getDockerNetworkMode()),
}

// Though we are capped on memory and cpu at the cgroup parent level,
// some build containers care what their memory limit is so they can
// adapt, thus we need to set the memory limit at the container level
// too, so that information is available to them.
if d.cgLimits != nil {
opts.Memory = d.cgLimits.MemoryLimitBytes
opts.Memswap = d.cgLimits.MemorySwap
opts.CPUShares = d.cgLimits.CPUShares
opts.CPUPeriod = d.cgLimits.CPUPeriod
opts.CPUQuota = d.cgLimits.CPUQuota
opts.CgroupParent = d.cgLimits.CgroupParent
}

if auth != nil {
opts.AuthConfigs = *auth
}
Expand Down
77 changes: 41 additions & 36 deletions pkg/build/builder/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"

docker "github.com/fsouza/go-dockerclient"
"github.com/opencontainers/runc/libcontainer/cgroups"

s2iapi "github.com/openshift/source-to-image/pkg/api"

Expand Down Expand Up @@ -88,49 +89,21 @@ func GetCGroupLimits() (*s2iapi.CGroupLimits, error) {
byteLimit = 92233720368547
}

// different docker versions seem to use different cgroup directories,
// check for all of them.

// seen on rhel systems
cpuDir := "/sys/fs/cgroup/cpuacct,cpu"

// seen on fedora systems with docker 1.9
// note that in this case there is also a /sys/fs/cgroup/cpu that symlinks
// to /sys/fs/cgroup/cpu,cpuacct, so technically the next check
// would be sufficient, but it seems better to rely on the real directory
// rather than a symlink.
if _, err := os.Stat("/sys/fs/cgroup/cpu,cpuacct"); err == nil {
cpuDir = "/sys/fs/cgroup/cpu,cpuacct"
}

// seen on debian systems with docker 1.10
if _, err := os.Stat("/sys/fs/cgroup/cpu"); err == nil {
cpuDir = "/sys/fs/cgroup/cpu"
}

cpuQuota, err := readInt64(filepath.Join(cpuDir, "cpu.cfs_quota_us"))
if err != nil {
return nil, fmt.Errorf("cannot determine cgroup limits: %v", err)
}

cpuPeriod, err := readInt64(filepath.Join(cpuDir, "cpu.cfs_period_us"))
if err != nil {
return nil, fmt.Errorf("cannot determine cgroup limits: %v", err)
}

cpuShares, err := readInt64(filepath.Join(cpuDir, "cpu.shares"))
parent, err := GetCgroupParent()
if err != nil {
return nil, fmt.Errorf("cannot determine cgroup limits: %v", err)
return fmt.Errorf("read cgroup parent: %v", err)
}

return &s2iapi.CGroupLimits{
CPUShares: cpuShares,
CPUPeriod: cpuPeriod,
CPUQuota: cpuQuota,
// Though we are capped on memory and cpu at the cgroup parent level,
// some build containers care what their memory limit is so they can
// adapt, thus we need to set the memory limit at the container level
// too, so that information is available to them.
MemoryLimitBytes: byteLimit,
// Set memoryswap==memorylimit, this ensures no swapping occurs.
// see: https://docs.docker.com/engine/reference/run/#runtime-constraints-on-cpu-and-memory
MemorySwap: byteLimit,
MemorySwap: byteLimit,
CgroupParent: parent,
}, nil
}

Expand Down Expand Up @@ -201,3 +174,35 @@ func addBuildLabels(labels map[string]string, build *buildapi.Build) {
labels[buildapi.DefaultDockerLabelNamespace+"build.name"] = build.Name
labels[buildapi.DefaultDockerLabelNamespace+"build.namespace"] = build.Namespace
}

// GetCgroupParent determines the parent cgroup for a container from
// within that container.
func GetCgroupParent() (string, error) {
cgroup, err := cgroups.ParseCgroupFile("/proc/self/cgroup")
if err != nil {
return err
}
glog.V(6).Infof("cgroup values: %v", cgroup)

memory, ok := cgroup["memory"]
if !ok {
return fmt.Errorf("could not find memory cgroup subsystem in %v", cgroup)
}
glog.V(6).Infof("cgroup filesystem memory subsystem value: %s", memory)

parts := strings.Split(memory, "/")
if len(parts) < 2 {
return fmt.Errorf("unprocessable cgroup memory value: %s", memory)
}

var string cgroupParent
if strings.HasSuffix(memory, ".scope") {
// systemd system, take the second to last segment.
cgroupParent = parts[len(parts)-2]
} else {
// non-systemd, take everything except the last segment.
cgroupParent = strings.Join(parts[:len(parts)-1], "/")
}
glog.V(5).Infof("running docker build under cgroup parent %v", cgroupParent)

}
3 changes: 0 additions & 3 deletions test/extended/builds/docker_quota.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ var _ = g.Describe("[builds][quota][Slow] docker build with a quota", func() {
o.Expect(err).NotTo(o.HaveOccurred())
o.Expect(out).To(o.ContainSubstring("MEMORY=209715200"))
o.Expect(out).To(o.ContainSubstring("MEMORYSWAP=209715200"))
o.Expect(out).To(o.ContainSubstring("SHARES=61"))
o.Expect(out).To(o.ContainSubstring("PERIOD=100000"))
o.Expect(out).To(o.ContainSubstring("QUOTA=6000"))
})
})
})
3 changes: 0 additions & 3 deletions test/extended/builds/s2i_quota.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ var _ = g.Describe("[builds][Conformance] s2i build with a quota", func() {
o.Expect(err).NotTo(o.HaveOccurred())
o.Expect(buildLog).To(o.ContainSubstring("MEMORY=209715200"))
o.Expect(buildLog).To(o.ContainSubstring("MEMORYSWAP=209715200"))
o.Expect(buildLog).To(o.ContainSubstring("SHARES=61"))
o.Expect(buildLog).To(o.ContainSubstring("PERIOD=100000"))
o.Expect(buildLog).To(o.ContainSubstring("QUOTA=6000"))

events, err := oc.KubeClient().Core().Events(oc.Namespace()).Search(kapi.Scheme, br.Build)
o.Expect(err).NotTo(o.HaveOccurred(), "Should be able to get events from the build")
Expand Down
1 change: 0 additions & 1 deletion test/extended/testdata/bindata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion test/extended/testdata/test-docker-build-quota.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"spec": {
"resources": {
"limits": {
"cpu": "60m",
"memory": "200Mi"
}
},
Expand Down

0 comments on commit 6fbb27b

Please sign in to comment.