Support for setting systemd properties via annotations
In case systemd is used to set cgroups for the container, it creates a scope unit dedicated to it (usually named `runc-$ID.scope`). This patch adds an ability to set arbitrary systemd properties for the systemd unit via runtime spec annotations. Initially this was developed as an ability to specify the `TimeoutStopUSec` property, but later generalized to work with arbitrary ones. Example usage: add the following to runtime spec (config.json): ``` "annotations": { "org.systemd.property.TimeoutStopUSec": "uint64 123456789", "org.systemd.property.CollectMode":"'inactive-or-failed'" }, ``` and start the container (e.g. `runc --systemd-cgroup run $ID`). The above will set the following systemd parameters: * `TimeoutStopSec` to 2 minutes and 3 seconds, * `CollectMode` to "inactive-or-failed". The values are in the gvariant format (see [1]). To figure out which type systemd expects for a particular parameter, see systemd sources. In particular, parameters with `USec` suffix require an `uint64` typed argument, while gvariant assumes int32 for a numeric values, therefore the explicit type is required. NOTE that systemd receives the time-typed parameters as *USec but shows them (in `systemctl show`) as *Sec. For example, the stop timeout should be set as `TimeoutStopUSec` but is shown as `TimeoutStopSec`. [1] https://developer.gnome.org/glib/stable/gvariant-text.html Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This commit is contained in:
parent
81ef5024f8
commit
4c5c3fb960
|
@ -245,6 +245,8 @@ func (m *LegacyManager) Apply(pid int) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
properties = append(properties, c.SystemdProps...)
|
||||||
|
|
||||||
statusChan := make(chan string, 1)
|
statusChan := make(chan string, 1)
|
||||||
if _, err := theConn.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
|
if _, err := theConn.StartTransientUnit(unitName, "replace", properties, statusChan); err == nil {
|
||||||
select {
|
select {
|
||||||
|
|
|
@ -128,6 +128,8 @@ func (m *UnifiedManager) Apply(pid int) error {
|
||||||
newProp("TasksMax", uint64(c.Resources.PidsLimit)))
|
newProp("TasksMax", uint64(c.Resources.PidsLimit)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
properties = append(properties, c.SystemdProps...)
|
||||||
|
|
||||||
// We have to set kernel memory here, as we can't change it once
|
// We have to set kernel memory here, as we can't change it once
|
||||||
// processes have been attached to the cgroup.
|
// processes have been attached to the cgroup.
|
||||||
if c.Resources.KernelMemory != 0 {
|
if c.Resources.KernelMemory != 0 {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
package configs
|
package configs
|
||||||
|
|
||||||
|
import (
|
||||||
|
systemdDbus "github.com/coreos/go-systemd/dbus"
|
||||||
|
)
|
||||||
|
|
||||||
type FreezerState string
|
type FreezerState string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -29,6 +33,11 @@ type Cgroup struct {
|
||||||
|
|
||||||
// Resources contains various cgroups settings to apply
|
// Resources contains various cgroups settings to apply
|
||||||
*Resources
|
*Resources
|
||||||
|
|
||||||
|
// SystemdProps are any additional properties for systemd,
|
||||||
|
// derived from org.systemd.property.xxx annotations.
|
||||||
|
// Ignored unless systemd is used for managing cgroups.
|
||||||
|
SystemdProps []systemdDbus.Property `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Resources struct {
|
type Resources struct {
|
||||||
|
|
|
@ -8,11 +8,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
systemdDbus "github.com/coreos/go-systemd/dbus"
|
||||||
|
"github.com/godbus/dbus"
|
||||||
"github.com/opencontainers/runc/libcontainer/cgroups"
|
"github.com/opencontainers/runc/libcontainer/cgroups"
|
||||||
|
|
||||||
"github.com/opencontainers/runc/libcontainer/configs"
|
"github.com/opencontainers/runc/libcontainer/configs"
|
||||||
"github.com/opencontainers/runc/libcontainer/seccomp"
|
"github.com/opencontainers/runc/libcontainer/seccomp"
|
||||||
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
|
libcontainerUtils "github.com/opencontainers/runc/libcontainer/utils"
|
||||||
|
@ -299,6 +301,31 @@ func createLibcontainerMount(cwd string, m specs.Mount) *configs.Mount {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// systemd property name check: latin letters only, at least 3 of them
|
||||||
|
var isValidName = regexp.MustCompile(`^[a-zA-Z]{3,}$`).MatchString
|
||||||
|
|
||||||
|
func initSystemdProps(spec *specs.Spec) ([]systemdDbus.Property, error) {
|
||||||
|
const keyPrefix = "org.systemd.property."
|
||||||
|
var sp []systemdDbus.Property
|
||||||
|
|
||||||
|
for k, v := range spec.Annotations {
|
||||||
|
name := strings.TrimPrefix(k, keyPrefix)
|
||||||
|
if len(name) == len(k) { // prefix not there
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !isValidName(name) {
|
||||||
|
return nil, fmt.Errorf("Annotation %s name incorrect: %s", k, name)
|
||||||
|
}
|
||||||
|
value, err := dbus.ParseVariant(v, dbus.Signature{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Annotation %s=%s value parse error: %v", k, v, err)
|
||||||
|
}
|
||||||
|
sp = append(sp, systemdDbus.Property{Name: name, Value: value})
|
||||||
|
}
|
||||||
|
|
||||||
|
return sp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func CreateCgroupConfig(opts *CreateOpts) (*configs.Cgroup, error) {
|
func CreateCgroupConfig(opts *CreateOpts) (*configs.Cgroup, error) {
|
||||||
var (
|
var (
|
||||||
myCgroupPath string
|
myCgroupPath string
|
||||||
|
@ -312,6 +339,14 @@ func CreateCgroupConfig(opts *CreateOpts) (*configs.Cgroup, error) {
|
||||||
Resources: &configs.Resources{},
|
Resources: &configs.Resources{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if useSystemdCgroup {
|
||||||
|
sp, err := initSystemdProps(spec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.SystemdProps = sp
|
||||||
|
}
|
||||||
|
|
||||||
if spec.Linux != nil && spec.Linux.CgroupsPath != "" {
|
if spec.Linux != nil && spec.Linux.CgroupsPath != "" {
|
||||||
myCgroupPath = libcontainerUtils.CleanPath(spec.Linux.CgroupsPath)
|
myCgroupPath = libcontainerUtils.CleanPath(spec.Linux.CgroupsPath)
|
||||||
if useSystemdCgroup {
|
if useSystemdCgroup {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus"
|
||||||
"github.com/opencontainers/runc/libcontainer/configs"
|
"github.com/opencontainers/runc/libcontainer/configs"
|
||||||
"github.com/opencontainers/runc/libcontainer/configs/validate"
|
"github.com/opencontainers/runc/libcontainer/configs/validate"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
@ -450,3 +451,82 @@ func TestNonZeroEUIDCompatibleSpecconvValidate(t *testing.T) {
|
||||||
t.Errorf("Expected specconv to produce valid rootless container config: %v", err)
|
t.Errorf("Expected specconv to produce valid rootless container config: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInitSystemdProps(t *testing.T) {
|
||||||
|
type inT struct {
|
||||||
|
name, value string
|
||||||
|
}
|
||||||
|
type expT struct {
|
||||||
|
isErr bool
|
||||||
|
name string
|
||||||
|
value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
in inT
|
||||||
|
exp expT
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: inT{"org.systemd.property.TimeoutStopUSec", "uint64 123456789"},
|
||||||
|
exp: expT{false, "TimeoutStopUSec", uint64(123456789)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: inT{"org.systemd.property.CollectMode", "'inactive-or-failed'"},
|
||||||
|
exp: expT{false, "CollectMode", "inactive-or-failed"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "unrelated property",
|
||||||
|
in: inT{"some.other.annotation", "0"},
|
||||||
|
exp: expT{false, "", ""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "too short property name",
|
||||||
|
in: inT{"org.systemd.property.Xo", "1"},
|
||||||
|
exp: expT{true, "", ""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid character in property name",
|
||||||
|
in: inT{"org.systemd.property.Number1", "1"},
|
||||||
|
exp: expT{true, "", ""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid property value",
|
||||||
|
in: inT{"org.systemd.property.ValidName", "invalid-value"},
|
||||||
|
exp: expT{true, "", ""},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
spec := &specs.Spec{}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
tc := tc
|
||||||
|
spec.Annotations = map[string]string{tc.in.name: tc.in.value}
|
||||||
|
|
||||||
|
outMap, err := initSystemdProps(spec)
|
||||||
|
//t.Logf("input %+v, expected %+v, got err:%v out:%+v", tc.in, tc.exp, err, outMap)
|
||||||
|
|
||||||
|
if tc.exp.isErr != (err != nil) {
|
||||||
|
t.Errorf("input %+v, expecting error: %v, got %v", tc.in, tc.exp.isErr, err)
|
||||||
|
}
|
||||||
|
expLen := 1 // expect a single item
|
||||||
|
if tc.exp.name == "" {
|
||||||
|
expLen = 0 // expect nothing
|
||||||
|
}
|
||||||
|
if len(outMap) != expLen {
|
||||||
|
t.Fatalf("input %+v, expected %d, got %d entries: %v", tc.in, expLen, len(outMap), outMap)
|
||||||
|
}
|
||||||
|
if expLen == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
out := outMap[0]
|
||||||
|
if tc.exp.name != out.Name {
|
||||||
|
t.Errorf("input %+v, expecting name: %q, got %q", tc.in, tc.exp.name, out.Name)
|
||||||
|
}
|
||||||
|
expValue := dbus.MakeVariant(tc.exp.value).String()
|
||||||
|
if expValue != out.Value.String() {
|
||||||
|
t.Errorf("input %+v, expecting value: %s, got %s", tc.in, expValue, out.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue