From 35d16ea0273b1111f3f1d134afd2e5c0ce34a8cd Mon Sep 17 00:00:00 2001 From: Urvashi Mohnani Date: Mon, 6 Feb 2023 10:13:11 -0500 Subject: [PATCH] Add ulimit annotation to kube gen & play Add a podman ulimit annotation to kube generate and play. If a container has a container with ulimits set, kube gen will add those as an annotation to the generated yaml. If kube play encounters the ulimit annotation, it will set ulimits for the container being played. Signed-off-by: Urvashi Mohnani --- libpod/define/annotations.go | 4 ++++ libpod/kube.go | 18 +++++++++++++++ pkg/specgen/generate/kube/kube.go | 12 ++++++++++ test/e2e/generate_kube_test.go | 37 ++++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/libpod/define/annotations.go b/libpod/define/annotations.go index 50bc52571a..a70f83f785 100644 --- a/libpod/define/annotations.go +++ b/libpod/define/annotations.go @@ -140,6 +140,10 @@ const ( // of the init container. InitContainerType = "io.podman.annotations.init.container.type" + // UlimitAnnotation is used by kube play when playing a kube yaml to specify the ulimits + // of the container + UlimitAnnotation = "io.podman.annotations.ulimit" + // MaxKubeAnnotation is the max length of annotations allowed by Kubernetes. MaxKubeAnnotation = 63 ) diff --git a/libpod/kube.go b/libpod/kube.go index 240ab4e322..b5ff66fd44 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -531,6 +531,24 @@ func simplePodWithV1Containers(ctx context.Context, ctrs []*Container) (*v1.Pod, } } + if ctr.config.Spec.Process != nil { + var ulimitArr []string + defaultUlimits := util.DefaultContainerConfig().Ulimits() + for _, ulimit := range ctr.config.Spec.Process.Rlimits { + finalUlimit := strings.ToLower(strings.ReplaceAll(ulimit.Type, "RLIMIT_", "")) + "=" + strconv.Itoa(int(ulimit.Soft)) + ":" + strconv.Itoa(int(ulimit.Hard)) + // compare ulimit with default list so we don't add it twice + if cutil.StringInSlice(finalUlimit, defaultUlimits) { + continue + } + + ulimitArr = append(ulimitArr, finalUlimit) + } + + if len(ulimitArr) > 0 { + kubeAnnotations[define.UlimitAnnotation] = strings.Join(ulimitArr, ",") + } + } + if !ctr.HostNetwork() { hostNetwork = false } diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 4a3d6648e8..09fe5fc7c6 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -273,6 +273,18 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener s.ResourceLimits.Memory.Reservation = &memoryRes } + ulimitVal, ok := opts.Annotations[define.UlimitAnnotation] + if ok { + ulimits := strings.Split(ulimitVal, ",") + for _, ul := range ulimits { + parsed, err := units.ParseUlimit(ul) + if err != nil { + return nil, err + } + s.Rlimits = append(s.Rlimits, spec.POSIXRlimit{Type: parsed.Name, Soft: uint64(parsed.Soft), Hard: uint64(parsed.Hard)}) + } + } + // TODO: We don't understand why specgen does not take of this, but // integration tests clearly pointed out that it was required. imageData, err := opts.Image.Inspect(ctx, nil) diff --git a/test/e2e/generate_kube_test.go b/test/e2e/generate_kube_test.go index 0f14b2985c..00b68ee551 100644 --- a/test/e2e/generate_kube_test.go +++ b/test/e2e/generate_kube_test.go @@ -72,7 +72,6 @@ var _ = Describe("Podman kube generate", func() { Expect(pod.Spec.Containers[0].SecurityContext).To(BeNil()) Expect(pod.Spec.Containers[0].Env).To(BeNil()) Expect(pod).To(HaveField("Name", "top-pod")) - Expect(pod.Annotations).To(HaveLen(0)) numContainers := 0 for range pod.Spec.Containers { @@ -1330,4 +1329,40 @@ USER test1` Expect(pod.Spec.Volumes[0].Secret).To(BeNil()) }) + + It("podman generate & play kube with --ulimit set", func() { + ctrName := "ulimit-ctr" + ctrNameInKubePod := ctrName + "-pod-" + ctrName + session1 := podmanTest.Podman([]string{"run", "-d", "--name", ctrName, "--ulimit", "nofile=1231:3123", ALPINE, "sleep", "1000"}) + session1.WaitWithDefaultTimeout() + Expect(session1).Should(Exit(0)) + + outputFile := filepath.Join(podmanTest.RunRoot, "pod.yaml") + kube := podmanTest.Podman([]string{"kube", "generate", ctrName, "-f", outputFile}) + kube.WaitWithDefaultTimeout() + Expect(kube).Should(Exit(0)) + + b, err := os.ReadFile(outputFile) + Expect(err).ShouldNot(HaveOccurred()) + pod := new(v1.Pod) + err = yaml.Unmarshal(b, pod) + Expect(err).ToNot(HaveOccurred()) + Expect(pod.Annotations).To(HaveKey(define.UlimitAnnotation)) + Expect(pod.Annotations[define.UlimitAnnotation]).To(ContainSubstring("nofile=1231:3123")) + + rm := podmanTest.Podman([]string{"pod", "rm", "-t", "0", "-f", ctrName}) + rm.WaitWithDefaultTimeout() + Expect(rm).Should(Exit(0)) + + play := podmanTest.Podman([]string{"kube", "play", outputFile}) + play.WaitWithDefaultTimeout() + Expect(play).Should(Exit(0)) + + inspect := podmanTest.Podman([]string{"inspect", ctrNameInKubePod}) + inspect.WaitWithDefaultTimeout() + Expect(inspect).Should(Exit(0)) + Expect(inspect.OutputToString()).To(ContainSubstring("RLIMIT_NOFILE")) + Expect(inspect.OutputToString()).To(ContainSubstring("1231")) + Expect(inspect.OutputToString()).To(ContainSubstring("3123")) + }) })