mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-09-03 22:27:52 +00:00
The OCICNI port format has one big problem: It does not support ranges. So if a users forwards a range of 1k ports with podman run -p 1001-2000 we have to store each of the thousand ports individually as array element. This bloats the db and makes the JSON encoding and decoding much slower. In many places we already use a better port struct type which supports ranges, e.g. `pkg/specgen` or the new network interface. Because of this we have to do many runtime conversions between the two port formats. If everything uses the new format we can skip the runtime conversions. This commit adds logic to replace all occurrences of the old format with the new one. The database will automatically migrate the ports to new format when the container config is read for the first time after the update. The `ParsePortMapping` function is `pkg/specgen/generate` has been reworked to better work with the new format. The new logic is able to deduplicate the given ports. This is necessary the ensure we store them efficiently in the DB. The new code should also be more performant than the old one. To prove that the code is fast enough I added go benchmarks. Parsing 1 million ports took less than 0.5 seconds on my laptop. Benchmark normalize PortMappings in specgen: Please note that the 1 million ports are actually 20x 50k ranges because we cannot have bigger ranges than 65535 ports. ``` $ go test -bench=. -benchmem ./pkg/specgen/generate/ goos: linux goarch: amd64 pkg: github.com/containers/podman/v3/pkg/specgen/generate cpu: Intel(R) Core(TM) i7-10850H CPU @ 2.70GHz BenchmarkParsePortMappingNoPorts-12 480821532 2.230 ns/op 0 B/op 0 allocs/op BenchmarkParsePortMapping1-12 38972 30183 ns/op 131584 B/op 9 allocs/op BenchmarkParsePortMapping100-12 18752 60688 ns/op 141088 B/op 315 allocs/op BenchmarkParsePortMapping1k-12 3104 331719 ns/op 223840 B/op 3018 allocs/op BenchmarkParsePortMapping10k-12 376 3122930 ns/op 1223650 B/op 30027 allocs/op BenchmarkParsePortMapping1m-12 3 390869926 ns/op 124593840 B/op 4000624 allocs/op BenchmarkParsePortMappingReverse100-12 18940 63414 ns/op 141088 B/op 315 allocs/op BenchmarkParsePortMappingReverse1k-12 3015 362500 ns/op 223841 B/op 3018 allocs/op BenchmarkParsePortMappingReverse10k-12 343 3318135 ns/op 1223650 B/op 30027 allocs/op BenchmarkParsePortMappingReverse1m-12 3 403392469 ns/op 124593840 B/op 4000624 allocs/op BenchmarkParsePortMappingRange1-12 37635 28756 ns/op 131584 B/op 9 allocs/op BenchmarkParsePortMappingRange100-12 39604 28935 ns/op 131584 B/op 9 allocs/op BenchmarkParsePortMappingRange1k-12 38384 29921 ns/op 131584 B/op 9 allocs/op BenchmarkParsePortMappingRange10k-12 29479 40381 ns/op 131584 B/op 9 allocs/op BenchmarkParsePortMappingRange1m-12 927 1279369 ns/op 143022 B/op 164 allocs/op PASS ok github.com/containers/podman/v3/pkg/specgen/generate 25.492s ``` Benchmark convert old port format to new one: ``` go test -bench=. -benchmem ./libpod/ goos: linux goarch: amd64 pkg: github.com/containers/podman/v3/libpod cpu: Intel(R) Core(TM) i7-10850H CPU @ 2.70GHz Benchmark_ocicniPortsToNetTypesPortsNoPorts-12 663526126 1.663 ns/op 0 B/op 0 allocs/op Benchmark_ocicniPortsToNetTypesPorts1-12 7858082 141.9 ns/op 72 B/op 2 allocs/op Benchmark_ocicniPortsToNetTypesPorts10-12 2065347 571.0 ns/op 536 B/op 4 allocs/op Benchmark_ocicniPortsToNetTypesPorts100-12 138478 8641 ns/op 4216 B/op 4 allocs/op Benchmark_ocicniPortsToNetTypesPorts1k-12 9414 120964 ns/op 41080 B/op 4 allocs/op Benchmark_ocicniPortsToNetTypesPorts10k-12 781 1490526 ns/op 401528 B/op 4 allocs/op Benchmark_ocicniPortsToNetTypesPorts1m-12 4 250579010 ns/op 40001656 B/op 4 allocs/op PASS ok github.com/containers/podman/v3/libpod 11.727s ``` Signed-off-by: Paul Holzinger <pholzing@redhat.com>
305 lines
9.1 KiB
Go
305 lines
9.1 KiB
Go
package generate
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
|
|
buildahDefine "github.com/containers/buildah/define"
|
|
"github.com/containers/common/pkg/config"
|
|
"github.com/containers/podman/v3/libpod"
|
|
"github.com/containers/podman/v3/libpod/define"
|
|
"github.com/containers/podman/v3/pkg/domain/entities"
|
|
"github.com/containers/podman/v3/pkg/rootless"
|
|
"github.com/containers/podman/v3/pkg/specgen"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func buildPauseImage(rt *libpod.Runtime, rtConfig *config.Config) (string, error) {
|
|
version, err := define.GetVersion()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
imageName := fmt.Sprintf("localhost/podman-pause:%s-%d", version.Version, version.Built)
|
|
|
|
// First check if the image has already been built.
|
|
if _, _, err := rt.LibimageRuntime().LookupImage(imageName, nil); err == nil {
|
|
return imageName, nil
|
|
}
|
|
|
|
// NOTE: Having the pause binary in its own directory keeps the door
|
|
// open for replacing the image building with using an overlay root FS.
|
|
// The latter turned out to be complex and error prone (see #11956) but
|
|
// we may be able to come up with a proper solution at a later point in
|
|
// time.
|
|
pausePath, err := rtConfig.FindHelperBinary("pause/pause", false)
|
|
if err != nil {
|
|
return "", fmt.Errorf("finding pause binary: %w", err)
|
|
}
|
|
|
|
buildContent := fmt.Sprintf(`FROM scratch
|
|
COPY %s /pause
|
|
ENTRYPOINT ["/pause"]`, pausePath)
|
|
|
|
tmpF, err := ioutil.TempFile("", "pause.containerfile")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if _, err := tmpF.WriteString(buildContent); err != nil {
|
|
return "", err
|
|
}
|
|
if err := tmpF.Close(); err != nil {
|
|
return "", err
|
|
}
|
|
defer os.Remove(tmpF.Name())
|
|
|
|
buildOptions := buildahDefine.BuildOptions{
|
|
CommonBuildOpts: &buildahDefine.CommonBuildOptions{},
|
|
Output: imageName,
|
|
Quiet: true,
|
|
IIDFile: "/dev/null", // prevents Buildah from writing the ID on stdout
|
|
}
|
|
if _, _, err := rt.Build(context.Background(), buildOptions, tmpF.Name()); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return imageName, nil
|
|
}
|
|
|
|
func pullOrBuildInfraImage(p *entities.PodSpec, rt *libpod.Runtime) error {
|
|
if p.PodSpecGen.NoInfra {
|
|
return nil
|
|
}
|
|
|
|
rtConfig, err := rt.GetConfigNoCopy()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// NOTE: we need pull down the infra image if it was explicitly set by
|
|
// the user (or containers.conf) to the non-default one.
|
|
imageName := p.PodSpecGen.InfraImage
|
|
if imageName == "" {
|
|
imageName = rtConfig.Engine.InfraImage
|
|
}
|
|
|
|
if imageName != config.DefaultInfraImage {
|
|
_, err := rt.LibimageRuntime().Pull(context.Background(), imageName, config.PullPolicyMissing, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
name, err := buildPauseImage(rt, rtConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("building local pause image: %w", err)
|
|
}
|
|
imageName = name
|
|
}
|
|
|
|
p.PodSpecGen.InfraImage = imageName
|
|
p.PodSpecGen.InfraContainerSpec.RawImageName = imageName
|
|
|
|
return nil
|
|
}
|
|
|
|
func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) {
|
|
if err := p.PodSpecGen.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := pullOrBuildInfraImage(p, rt); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !p.PodSpecGen.NoInfra && p.PodSpecGen.InfraContainerSpec != nil {
|
|
var err error
|
|
p.PodSpecGen.InfraContainerSpec, err = MapSpec(&p.PodSpecGen)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
options, err := createPodOptions(&p.PodSpecGen, rt, p.PodSpecGen.InfraContainerSpec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pod, err := rt.NewPod(context.Background(), p.PodSpecGen, options...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !p.PodSpecGen.NoInfra && p.PodSpecGen.InfraContainerSpec != nil {
|
|
if p.PodSpecGen.InfraContainerSpec.Name == "" {
|
|
p.PodSpecGen.InfraContainerSpec.Name = pod.ID()[:12] + "-infra"
|
|
}
|
|
_, err = CompleteSpec(context.Background(), rt, p.PodSpecGen.InfraContainerSpec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.PodSpecGen.InfraContainerSpec.User = "" // infraSpec user will get incorrectly assigned via the container creation process, overwrite here
|
|
rtSpec, spec, opts, err := MakeContainer(context.Background(), rt, p.PodSpecGen.InfraContainerSpec)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
spec.Pod = pod.ID()
|
|
opts = append(opts, rt.WithPod(pod))
|
|
spec.CgroupParent = pod.CgroupParent()
|
|
infraCtr, err := ExecuteCreate(context.Background(), rt, rtSpec, spec, true, opts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pod, err = rt.AddInfra(context.Background(), pod, infraCtr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
// SavePod is used to save the pod state and trigger a create event even if infra is not created
|
|
err := rt.SavePod(pod)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return pod, nil
|
|
}
|
|
|
|
func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime, infraSpec *specgen.SpecGenerator) ([]libpod.PodCreateOption, error) {
|
|
var (
|
|
options []libpod.PodCreateOption
|
|
)
|
|
if !p.NoInfra { //&& infraSpec != nil {
|
|
options = append(options, libpod.WithInfraContainer())
|
|
nsOptions, err := GetNamespaceOptions(p.SharedNamespaces, p.InfraContainerSpec.NetNS.IsHost())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
options = append(options, nsOptions...)
|
|
// Use pod user and infra userns only when --userns is not set to host
|
|
if !p.InfraContainerSpec.UserNS.IsHost() && !p.InfraContainerSpec.UserNS.IsDefault() {
|
|
options = append(options, libpod.WithPodUser())
|
|
}
|
|
}
|
|
if len(p.CgroupParent) > 0 {
|
|
options = append(options, libpod.WithPodCgroupParent(p.CgroupParent))
|
|
}
|
|
if len(p.Labels) > 0 {
|
|
options = append(options, libpod.WithPodLabels(p.Labels))
|
|
}
|
|
if len(p.Name) > 0 {
|
|
options = append(options, libpod.WithPodName(p.Name))
|
|
}
|
|
if p.PodCreateCommand != nil {
|
|
options = append(options, libpod.WithPodCreateCommand(p.PodCreateCommand))
|
|
}
|
|
|
|
if len(p.Hostname) > 0 {
|
|
options = append(options, libpod.WithPodHostname(p.Hostname))
|
|
}
|
|
|
|
return options, nil
|
|
}
|
|
|
|
// MapSpec modifies the already filled Infra specgenerator,
|
|
// replacing necessary values with those specified in pod creation
|
|
func MapSpec(p *specgen.PodSpecGenerator) (*specgen.SpecGenerator, error) {
|
|
if len(p.PortMappings) > 0 {
|
|
ports, err := ParsePortMapping(p.PortMappings, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.InfraContainerSpec.PortMappings = ports
|
|
}
|
|
switch p.NetNS.NSMode {
|
|
case specgen.Default, "":
|
|
if p.NoInfra {
|
|
logrus.Debugf("No networking because the infra container is missing")
|
|
break
|
|
}
|
|
if rootless.IsRootless() {
|
|
logrus.Debugf("Pod will use slirp4netns")
|
|
if p.InfraContainerSpec.NetNS.NSMode != "host" {
|
|
p.InfraContainerSpec.NetworkOptions = p.NetworkOptions
|
|
p.InfraContainerSpec.NetNS.NSMode = specgen.NamespaceMode("slirp4netns")
|
|
}
|
|
} else {
|
|
logrus.Debugf("Pod using bridge network mode")
|
|
}
|
|
case specgen.Bridge:
|
|
p.InfraContainerSpec.NetNS.NSMode = specgen.Bridge
|
|
logrus.Debugf("Pod using bridge network mode")
|
|
case specgen.Host:
|
|
logrus.Debugf("Pod will use host networking")
|
|
if len(p.InfraContainerSpec.PortMappings) > 0 ||
|
|
p.InfraContainerSpec.StaticIP != nil ||
|
|
p.InfraContainerSpec.StaticMAC != nil ||
|
|
len(p.InfraContainerSpec.CNINetworks) > 0 ||
|
|
p.InfraContainerSpec.NetNS.NSMode == specgen.NoNetwork {
|
|
return nil, errors.Wrapf(define.ErrInvalidArg, "cannot set host network if network-related configuration is specified")
|
|
}
|
|
p.InfraContainerSpec.NetNS.NSMode = specgen.Host
|
|
case specgen.Slirp:
|
|
logrus.Debugf("Pod will use slirp4netns")
|
|
if p.InfraContainerSpec.NetNS.NSMode != "host" {
|
|
p.InfraContainerSpec.NetworkOptions = p.NetworkOptions
|
|
p.InfraContainerSpec.NetNS.NSMode = specgen.NamespaceMode("slirp4netns")
|
|
}
|
|
case specgen.NoNetwork:
|
|
logrus.Debugf("Pod will not use networking")
|
|
if len(p.InfraContainerSpec.PortMappings) > 0 ||
|
|
p.InfraContainerSpec.StaticIP != nil ||
|
|
p.InfraContainerSpec.StaticMAC != nil ||
|
|
len(p.InfraContainerSpec.CNINetworks) > 0 ||
|
|
p.InfraContainerSpec.NetNS.NSMode == "host" {
|
|
return nil, errors.Wrapf(define.ErrInvalidArg, "cannot disable pod network if network-related configuration is specified")
|
|
}
|
|
p.InfraContainerSpec.NetNS.NSMode = specgen.NoNetwork
|
|
default:
|
|
return nil, errors.Errorf("pods presently do not support network mode %s", p.NetNS.NSMode)
|
|
}
|
|
|
|
libpod.WithPodCgroups()
|
|
if len(p.InfraCommand) > 0 {
|
|
p.InfraContainerSpec.Entrypoint = p.InfraCommand
|
|
}
|
|
|
|
if len(p.HostAdd) > 0 {
|
|
p.InfraContainerSpec.HostAdd = p.HostAdd
|
|
}
|
|
if len(p.DNSServer) > 0 {
|
|
var dnsServers []net.IP
|
|
dnsServers = append(dnsServers, p.DNSServer...)
|
|
|
|
p.InfraContainerSpec.DNSServers = dnsServers
|
|
}
|
|
if len(p.DNSOption) > 0 {
|
|
p.InfraContainerSpec.DNSOptions = p.DNSOption
|
|
}
|
|
if len(p.DNSSearch) > 0 {
|
|
p.InfraContainerSpec.DNSSearch = p.DNSSearch
|
|
}
|
|
if p.StaticIP != nil {
|
|
p.InfraContainerSpec.StaticIP = p.StaticIP
|
|
}
|
|
if p.StaticMAC != nil {
|
|
p.InfraContainerSpec.StaticMAC = p.StaticMAC
|
|
}
|
|
if p.NoManageResolvConf {
|
|
p.InfraContainerSpec.UseImageResolvConf = true
|
|
}
|
|
if len(p.CNINetworks) > 0 {
|
|
p.InfraContainerSpec.CNINetworks = p.CNINetworks
|
|
}
|
|
if p.NoManageHosts {
|
|
p.InfraContainerSpec.UseImageHosts = p.NoManageHosts
|
|
}
|
|
|
|
if len(p.InfraConmonPidFile) > 0 {
|
|
p.InfraContainerSpec.ConmonPidFile = p.InfraConmonPidFile
|
|
}
|
|
|
|
if p.InfraImage != config.DefaultInfraImage {
|
|
p.InfraContainerSpec.Image = p.InfraImage
|
|
}
|
|
return p.InfraContainerSpec, nil
|
|
}
|