Merge pull request #10716 from cdoern/podFlags

Podman Pod Create --cpus and --cpuset-cpus flags
This commit is contained in:
OpenShift Merge Robot 2021-06-23 15:51:29 -04:00 committed by GitHub
commit 3f3feaa015
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 326 additions and 29 deletions

View file

@ -222,7 +222,6 @@ func createInit(c *cobra.Command) error {
}
cliVals.Env = env
}
if c.Flag("cgroups").Changed && cliVals.CGroupsMode == "split" && registry.IsRemote() {
return errors.Errorf("the option --cgroups=%q is not supported in remote mode", cliVals.CGroupsMode)
}
@ -292,6 +291,8 @@ func createPodIfNecessary(s *specgen.SpecGenerator, netOpts *entities.NetOptions
Net: netOpts,
CreateCommand: os.Args,
Hostname: s.ContainerBasicConfig.Hostname,
Cpus: cliVals.CPUS,
CpusetCpus: cliVals.CPUSetCPUs,
}
// Unset config values we passed to the pod to prevent them being used twice for the container and pod.
s.ContainerBasicConfig.Hostname = ""

View file

@ -5,9 +5,13 @@ import (
"fmt"
"io/ioutil"
"os"
"runtime"
"sort"
"strconv"
"strings"
"github.com/containers/common/pkg/completion"
"github.com/containers/common/pkg/sysinfo"
"github.com/containers/podman/v3/cmd/podman/common"
"github.com/containers/podman/v3/cmd/podman/parse"
"github.com/containers/podman/v3/cmd/podman/registry"
@ -16,6 +20,7 @@ import (
"github.com/containers/podman/v3/pkg/errorhandling"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/util"
"github.com/docker/docker/pkg/parsers"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
@ -55,6 +60,14 @@ func init() {
common.DefineNetFlags(createCommand)
cpusetflagName := "cpuset-cpus"
flags.StringVar(&createOptions.CpusetCpus, cpusetflagName, "", "CPUs in which to allow execution")
_ = createCommand.RegisterFlagCompletionFunc(cpusetflagName, completion.AutocompleteDefault)
cpusflagName := "cpus"
flags.Float64Var(&createOptions.Cpus, cpusflagName, 0.000, "set amount of CPUs for the pod")
_ = createCommand.RegisterFlagCompletionFunc(cpusflagName, completion.AutocompleteDefault)
cgroupParentflagName := "cgroup-parent"
flags.StringVar(&createOptions.CGroupParent, cgroupParentflagName, "", "Set parent cgroup for the pod")
_ = createCommand.RegisterFlagCompletionFunc(cgroupParentflagName, completion.AutocompleteDefault)
@ -185,6 +198,46 @@ func create(cmd *cobra.Command, args []string) error {
}
}
numCPU := sysinfo.NumCPU()
if numCPU == 0 {
numCPU = runtime.NumCPU()
}
if createOptions.Cpus > float64(numCPU) {
createOptions.Cpus = float64(numCPU)
}
copy := createOptions.CpusetCpus
cpuSet := createOptions.Cpus
if cpuSet == 0 {
cpuSet = float64(sysinfo.NumCPU())
}
ret, err := parsers.ParseUintList(copy)
copy = ""
if err != nil {
errors.Wrapf(err, "could not parse list")
}
var vals []int
for ind, val := range ret {
if val {
vals = append(vals, ind)
}
}
sort.Ints(vals)
for ind, core := range vals {
if core > int(cpuSet) {
if copy == "" {
copy = "0-" + strconv.Itoa(int(cpuSet))
createOptions.CpusetCpus = copy
break
} else {
createOptions.CpusetCpus = copy
break
}
} else if ind != 0 {
copy += "," + strconv.Itoa(core)
} else {
copy = "" + strconv.Itoa(core)
}
}
response, err := registry.ContainerEngine().PodCreate(context.Background(), createOptions)
if err != nil {
return err

View file

@ -23,6 +23,22 @@ Add a host to the /etc/hosts file shared between all containers in the pod.
Path to cgroups under which the cgroup for the pod will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist.
#### **--cpus**=*amount*
Set the total number of CPUs delegated to the pod. Default is 0.000 which indicates that there is no limit on computation power.
#### **--cpuset-cpus**=*amount*
Limit the CPUs to support execution. First CPU is numbered 0. Unlike --cpus this is of type string and parsed as a list of numbers
Format is 0-3,0,1
Examples of the List Format:
0-4,9 # bits 0, 1, 2, 3, 4, and 9 set
0-2,7,12-14 # bits 0, 1, 2, 7, 12, 13, and 14 set
#### **--dns**=*ipaddr*
Set custom DNS servers in the /etc/resolv.conf file that will be shared between all containers in the pod. A special option, "none" is allowed which disables creation of /etc/resolv.conf for the pod.

View file

@ -131,6 +131,5 @@ func (c *Container) validate() error {
if c.config.User == "" && (c.config.Spec.Process.User.UID != 0 || c.config.Spec.Process.User.GID != 0) {
return errors.Wrapf(define.ErrInvalidArg, "please set User explicitly via WithUser() instead of in OCI spec directly")
}
return nil
}

View file

@ -51,6 +51,12 @@ type InspectPodData struct {
// Containers gives a brief summary of all containers in the pod and
// their current status.
Containers []InspectPodContainerInfo `json:"Containers,omitempty"`
// CPUPeriod contains the CPU period of the pod
CPUPeriod uint64 `json:"cpu_period,omitempty"`
// CPUQuota contains the CPU quota of the pod
CPUQuota int64 `json:"cpu_quota,omitempty"`
// CPUSetCPUs contains linux specific CPU data for the pod
CPUSetCPUs string `json:"cpuset_cpus,omitempty"`
}
// InspectPodInfraConfig contains the configuration of the pod's infra
@ -91,6 +97,12 @@ type InspectPodInfraConfig struct {
Networks []string
// NetworkOptions are additional options for each network
NetworkOptions map[string][]string
// CPUPeriod contains the CPU period of the pod
CPUPeriod uint64 `json:"cpu_period,omitempty"`
// CPUQuota contains the CPU quota of the pod
CPUQuota int64 `json:"cpu_quota,omitempty"`
// CPUSetCPUs contains linux specific CPU data for the container
CPUSetCPUs string `json:"cpuset_cpus,omitempty"`
}
// InspectPodContainerInfo contains information on a container in a pod.

View file

@ -20,6 +20,7 @@ import (
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -559,7 +560,6 @@ func WithMaxLogSize(limit int64) CtrCreateOption {
if ctr.valid {
return define.ErrRuntimeFinalized
}
ctr.config.LogSize = limit
return nil
@ -867,7 +867,6 @@ func WithMountNSFrom(nsCtr *Container) CtrCreateOption {
if err := checkDependencyContainer(nsCtr, ctr); err != nil {
return err
}
ctr.config.MountNsCtr = nsCtr.ID()
return nil
@ -2359,3 +2358,42 @@ func WithVolatile() CtrCreateOption {
return nil
}
}
// WithPodCPUPAQ takes the given cpu period and quota and inserts them in the proper place.
func WithPodCPUPAQ(period uint64, quota int64) PodCreateOption {
return func(pod *Pod) error {
if pod.valid {
return define.ErrPodFinalized
}
if pod.CPUPeriod() != 0 && pod.CPUQuota() != 0 {
pod.config.InfraContainer.ResourceLimits.CPU = &specs.LinuxCPU{
Period: &period,
Quota: &quota,
}
} else {
pod.config.InfraContainer.ResourceLimits = &specs.LinuxResources{}
pod.config.InfraContainer.ResourceLimits.CPU = &specs.LinuxCPU{
Period: &period,
Quota: &quota,
}
}
return nil
}
}
// WithPodCPUSetCPUS computes and sets the Cpus linux resource string which determines the amount of cores, from those available, we are allowed to execute on
func WithPodCPUSetCPUs(inp string) PodCreateOption {
return func(pod *Pod) error {
if pod.valid {
return define.ErrPodFinalized
}
if pod.ResourceLim().CPU.Period != nil {
pod.config.InfraContainer.ResourceLimits.CPU.Cpus = inp
} else {
pod.config.InfraContainer.ResourceLimits = &specs.LinuxResources{}
pod.config.InfraContainer.ResourceLimits.CPU = &specs.LinuxCPU{}
pod.config.InfraContainer.ResourceLimits.CPU.Cpus = inp
}
return nil
}
}

View file

@ -1,12 +1,14 @@
package libpod
import (
"context"
"net"
"time"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/lock"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
@ -110,6 +112,7 @@ type InfraContainerConfig struct {
InfraCommand []string `json:"infraCommand,omitempty"`
Slirp4netns bool `json:"slirp4netns,omitempty"`
NetworkOptions map[string][]string `json:"network_options,omitempty"`
ResourceLimits *specs.LinuxResources `json:"resource_limits,omitempty"`
}
// ID retrieves the pod's ID
@ -128,6 +131,45 @@ func (p *Pod) Namespace() string {
return p.config.Namespace
}
// ResourceLim returns the cpuset resource limits for the pod
func (p *Pod) ResourceLim() *specs.LinuxResources {
resCopy := &specs.LinuxResources{}
if err := JSONDeepCopy(p.config.InfraContainer.ResourceLimits, resCopy); err != nil {
return nil
}
if resCopy != nil && resCopy.CPU != nil {
return resCopy
}
empty := &specs.LinuxResources{
CPU: &specs.LinuxCPU{},
}
return empty
}
// CPUPeriod returns the pod CPU period
func (p *Pod) CPUPeriod() uint64 {
resCopy := &specs.LinuxResources{}
if err := JSONDeepCopy(p.config.InfraContainer.ResourceLimits, resCopy); err != nil {
return 0
}
if resCopy != nil && resCopy.CPU != nil && resCopy.CPU.Period != nil {
return *resCopy.CPU.Period
}
return 0
}
// CPUQuota returns the pod CPU quota
func (p *Pod) CPUQuota() int64 {
resCopy := &specs.LinuxResources{}
if err := JSONDeepCopy(p.config.InfraContainer.ResourceLimits, resCopy); err != nil {
return 0
}
if resCopy != nil && resCopy.CPU != nil && resCopy.CPU.Quota != nil {
return *resCopy.CPU.Quota
}
return 0
}
// Labels returns the pod's labels
func (p *Pod) Labels() map[string]string {
labels := make(map[string]string)
@ -208,7 +250,31 @@ func (p *Pod) CgroupPath() (string, error) {
if err := p.updatePod(); err != nil {
return "", err
}
if p.state.CgroupPath != "" {
return p.state.CgroupPath, nil
}
if !p.HasInfraContainer() {
return "", errors.Wrap(define.ErrNoSuchCtr, "pod has no infra container")
}
id := p.state.InfraContainerID
if id != "" {
ctr, err := p.runtime.state.Container(id)
if err != nil {
return "", errors.Wrapf(err, "could not get infra")
}
if ctr != nil {
ctr.Start(context.Background(), false)
cgroupPath, err := ctr.CGroupPath()
if err != nil {
return "", errors.Wrapf(err, "could not get container cgroup")
}
p.state.CgroupPath = cgroupPath
p.save()
return cgroupPath, nil
}
}
return p.state.CgroupPath, nil
}

View file

@ -538,6 +538,9 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
infraConfig.StaticMAC = p.config.InfraContainer.StaticMAC.String()
infraConfig.NoManageResolvConf = p.config.InfraContainer.UseImageResolvConf
infraConfig.NoManageHosts = p.config.InfraContainer.UseImageHosts
infraConfig.CPUPeriod = p.CPUPeriod()
infraConfig.CPUQuota = p.CPUQuota()
infraConfig.CPUSetCPUs = p.ResourceLim().CPU.Cpus
if len(p.config.InfraContainer.DNSServer) > 0 {
infraConfig.DNSServer = make([]string, 0, len(p.config.InfraContainer.DNSServer))
@ -581,6 +584,9 @@ func (p *Pod) Inspect() (*define.InspectPodData, error) {
SharedNamespaces: sharesNS,
NumContainers: uint(len(containers)),
Containers: ctrs,
CPUSetCPUs: p.ResourceLim().CPU.Cpus,
CPUPeriod: p.CPUPeriod(),
CPUQuota: p.CPUQuota(),
}
return &inspectData, nil

View file

@ -146,7 +146,6 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
options = append(options, WithExitCommand(p.config.InfraContainer.ExitCommand))
}
}
g.SetRootReadonly(true)
g.SetProcessArgs(infraCtrCommand)
@ -173,7 +172,6 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
// Ignore mqueue sysctls if not sharing IPC
if !p.config.UsePodIPC && strings.HasPrefix(sysctlKey, "fs.mqueue.") {
logrus.Infof("Sysctl %s=%s ignored in containers.conf, since IPC Namespace for pod is unused", sysctlKey, sysctlVal)
continue
}
@ -188,7 +186,6 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
logrus.Infof("Sysctl %s=%s ignored in containers.conf, since UTS Namespace for pod is unused", sysctlKey, sysctlVal)
continue
}
g.AddLinuxSysctl(sysctlKey, sysctlVal)
}
@ -200,7 +197,11 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
if len(p.config.InfraContainer.ConmonPidFile) > 0 {
options = append(options, WithConmonPidFile(p.config.InfraContainer.ConmonPidFile))
}
newRes := new(spec.LinuxResources)
newRes.CPU = new(spec.LinuxCPU)
newRes.CPU = p.ResourceLim().CPU
g.Config.Linux.Resources.CPU = newRes.CPU
return r.newContainer(ctx, g.Config, options...)
}
@ -211,7 +212,6 @@ func (r *Runtime) createInfraContainer(ctx context.Context, p *Pod) (*Container,
if !r.valid {
return nil, define.ErrRuntimeStopped
}
imageName := p.config.InfraContainer.InfraImage
if imageName == "" {
imageName = r.config.Engine.InfraImage

View file

@ -7,6 +7,8 @@ import (
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/util"
"github.com/opencontainers/runtime-spec/specs-go"
)
type PodKillOptions struct {
@ -116,13 +118,35 @@ type PodCreateOptions struct {
Name string
Net *NetOptions
Share []string
Cpus float64
CpusetCpus string
}
type PodCreateReport struct {
Id string //nolint
}
func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) {
func (p *PodCreateOptions) CPULimits() *specs.LinuxCPU {
cpu := &specs.LinuxCPU{}
hasLimits := false
if p.Cpus != 0 {
period, quota := util.CoresToPeriodAndQuota(p.Cpus)
cpu.Period = &period
cpu.Quota = &quota
hasLimits = true
}
if p.CpusetCpus != "" {
cpu.Cpus = p.CpusetCpus
hasLimits = true
}
if !hasLimits {
return cpu
}
return cpu
}
func (p *PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) error {
// Basic Config
s.Name = p.Name
s.Hostname = p.Hostname
@ -156,6 +180,21 @@ func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) {
// Cgroup
s.CgroupParent = p.CGroupParent
// Resource config
cpuDat := p.CPULimits()
if s.ResourceLimits == nil {
s.ResourceLimits = &specs.LinuxResources{}
s.ResourceLimits.CPU = &specs.LinuxCPU{}
}
if cpuDat != nil {
s.ResourceLimits.CPU = cpuDat
if p.Cpus != 0 {
s.CPUPeriod = *cpuDat.Period
s.CPUQuota = *cpuDat.Quota
}
}
return nil
}
type PodPruneOptions struct {

View file

@ -347,7 +347,6 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
options = append(options, libpod.WithLogDriver(s.LogConfiguration.Driver))
}
}
// Security options
if len(s.SelinuxOpts) > 0 {
options = append(options, libpod.WithSecLabels(s.SelinuxOpts))

View file

@ -54,6 +54,14 @@ func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime) ([]libpod
if len(p.Name) > 0 {
options = append(options, libpod.WithPodName(p.Name))
}
if p.ResourceLimits != nil && p.ResourceLimits.CPU != nil && p.ResourceLimits.CPU.Period != nil && p.ResourceLimits.CPU.Quota != nil {
if *p.ResourceLimits.CPU.Period != 0 || *p.ResourceLimits.CPU.Quota != 0 {
options = append(options, libpod.WithPodCPUPAQ((*p.ResourceLimits.CPU.Period), (*p.ResourceLimits.CPU.Quota)))
}
}
if p.ResourceLimits != nil && p.ResourceLimits.CPU != nil && p.ResourceLimits.CPU.Cpus != "" {
options = append(options, libpod.WithPodCPUSetCPUs(p.ResourceLimits.CPU.Cpus))
}
if len(p.Hostname) > 0 {
options = append(options, libpod.WithPodHostname(p.Hostname))
}

View file

@ -2,6 +2,8 @@ package specgen
import (
"net"
spec "github.com/opencontainers/runtime-spec/specs-go"
)
// PodBasicConfig contains basic configuration options for pods.
@ -155,6 +157,16 @@ type PodSpecGenerator struct {
PodBasicConfig
PodNetworkConfig
PodCgroupConfig
PodResourceConfig
}
type PodResourceConfig struct {
// ResourceLimits contains linux specific CPU data for the pod
ResourceLimits *spec.LinuxResources `json:"resource_limits,omitempty"`
// CPU period of the cpuset, determined by --cpus
CPUPeriod uint64 `json:"cpu_period,omitempty"`
// CPU quota of the cpuset, determined by --cpus
CPUQuota int64 `json:"cpu_quota,omitempty"`
}
// NewPodSpecGenerator creates a new pod spec

View file

@ -470,6 +470,10 @@ type ContainerResourceConfig struct {
// that are used to configure cgroup v2.
// Optional.
CgroupConf map[string]string `json:"unified,omitempty"`
// CPU period of the cpuset, determined by --cpus
CPUPeriod uint64 `json:"cpu_period,omitempty"`
// CPU quota of the cpuset, determined by --cpus
CPUQuota int64 `json:"cpu_quota,omitempty"`
}
// ContainerHealthCheckConfig describes a container healthcheck with attributes

View file

@ -5,9 +5,12 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/containers/common/pkg/sysinfo"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/util"
. "github.com/containers/podman/v3/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@ -515,4 +518,45 @@ ENTRYPOINT ["sleep","99999"]
Expect(create.ExitCode()).To(BeZero())
})
It("podman pod create --cpus", func() {
podName := "testPod"
numCPU := float64(sysinfo.NumCPU())
period, quota := util.CoresToPeriodAndQuota(numCPU)
numCPUStr := strconv.Itoa(int(numCPU))
podCreate := podmanTest.Podman([]string{"pod", "create", "--cpus", numCPUStr, "--name", podName})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate.ExitCode()).To(Equal(0))
contCreate := podmanTest.Podman([]string{"container", "create", "--pod", podName, "alpine"})
contCreate.WaitWithDefaultTimeout()
Expect(podCreate.ExitCode()).To(Equal(0))
podInspect := podmanTest.Podman([]string{"pod", "inspect", podName})
podInspect.WaitWithDefaultTimeout()
Expect(podInspect.ExitCode()).To(Equal(0))
podJSON := podInspect.InspectPodToJSON()
Expect(podJSON.CPUPeriod).To(Equal(period))
Expect(podJSON.CPUQuota).To(Equal(quota))
})
It("podman pod create --cpuset-cpus", func() {
podName := "testPod"
ctrName := "testCtr"
numCPU := float64(sysinfo.NumCPU())
numCPUStr := strconv.Itoa(int(numCPU))
in := "0-" + numCPUStr
podCreate := podmanTest.Podman([]string{"pod", "create", "--cpuset-cpus", in, "--name", podName})
podCreate.WaitWithDefaultTimeout()
Expect(podCreate.ExitCode()).To(Equal(0))
contCreate := podmanTest.Podman([]string{"container", "create", "--name", ctrName, "--pod", podName, "alpine"})
contCreate.WaitWithDefaultTimeout()
Expect(podCreate.ExitCode()).To(Equal(0))
podInspect := podmanTest.Podman([]string{"pod", "inspect", podName})
podInspect.WaitWithDefaultTimeout()
Expect(podInspect.ExitCode()).To(Equal(0))
podJSON := podInspect.InspectPodToJSON()
Expect(podJSON.CPUSetCPUs).To(Equal(in))
})
})