Skip to content

Commit

Permalink
podman play kube support container startup probe
Browse files Browse the repository at this point in the history
* podman kube play support startup probe
* make probe use json string array instead of CMD-SHELL

Signed-off-by: Liang Chu-Xuan <[email protected]>
  • Loading branch information
karta0807913 committed Dec 10, 2022
1 parent 6e2e9ab commit 7bd1dbb
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 29 deletions.
98 changes: 70 additions & 28 deletions pkg/specgen/generate/kube/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
if err != nil {
return nil, fmt.Errorf("failed to configure livenessProbe: %w", err)
}
err = setupStartupProbe(s, opts.Container, opts.RestartPolicy)
if err != nil {
return nil, fmt.Errorf("failed to configure startupProbe: %w", err)
}

// Since we prefix the container name with pod name to work-around the uniqueness requirement,
// the seccomp profile should reference the actual container name from the YAML
Expand Down Expand Up @@ -511,45 +515,83 @@ func parseMountPath(mountPath string, readOnly bool, propagationMode *v1.MountPr
return dest, opts, nil
}

func probeToHealthConfig(probe *v1.Probe) (*manifest.Schema2HealthConfig, error) {
var commandString string
failureCmd := "exit 1"
probeHandler := probe.Handler

// configure healthcheck on the basis of Handler Actions.
switch {
case probeHandler.Exec != nil:
cmd, err := json.Marshal(probeHandler.Exec.Command)
if err != nil {
return nil, err
}
commandString = string(cmd)
case probeHandler.HTTPGet != nil:
// set defaults as in https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#http-probes
uriScheme := v1.URISchemeHTTP
if probeHandler.HTTPGet.Scheme != "" {
uriScheme = probeHandler.HTTPGet.Scheme
}
host := "localhost" // Kubernetes default is host IP, but with Podman there is only one node
if probeHandler.HTTPGet.Host != "" {
host = probeHandler.HTTPGet.Host
}
path := "/"
if probeHandler.HTTPGet.Path != "" {
path = probeHandler.HTTPGet.Path
}
commandString = fmt.Sprintf("curl -f %s://%s:%d%s || %s", uriScheme, host, probeHandler.HTTPGet.Port.IntValue(), path, failureCmd)
case probeHandler.TCPSocket != nil:
commandString = fmt.Sprintf("nc -z -v %s %d || %s", probeHandler.TCPSocket.Host, probeHandler.TCPSocket.Port.IntValue(), failureCmd)
}
return makeHealthCheck(commandString, probe.PeriodSeconds, probe.FailureThreshold, probe.TimeoutSeconds, probe.InitialDelaySeconds)
}

func setupLivenessProbe(s *specgen.SpecGenerator, containerYAML v1.Container, restartPolicy string) error {
var err error
if containerYAML.LivenessProbe == nil {
return nil
}
emptyHandler := v1.Handler{}
if containerYAML.LivenessProbe.Handler != emptyHandler {
var commandString string
failureCmd := "exit 1"
probe := containerYAML.LivenessProbe
probeHandler := probe.Handler

// configure healthcheck on the basis of Handler Actions.
switch {
case probeHandler.Exec != nil:
execString := strings.Join(probeHandler.Exec.Command, " ")
commandString = fmt.Sprintf("%s || %s", execString, failureCmd)
case probeHandler.HTTPGet != nil:
// set defaults as in https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#http-probes
uriScheme := v1.URISchemeHTTP
if probeHandler.HTTPGet.Scheme != "" {
uriScheme = probeHandler.HTTPGet.Scheme
}
host := "localhost" // Kubernetes default is host IP, but with Podman there is only one node
if probeHandler.HTTPGet.Host != "" {
host = probeHandler.HTTPGet.Host
}
path := "/"
if probeHandler.HTTPGet.Path != "" {
path = probeHandler.HTTPGet.Path
}
commandString = fmt.Sprintf("curl -f %s://%s:%d%s || %s", uriScheme, host, probeHandler.HTTPGet.Port.IntValue(), path, failureCmd)
case probeHandler.TCPSocket != nil:
commandString = fmt.Sprintf("nc -z -v %s %d || %s", probeHandler.TCPSocket.Host, probeHandler.TCPSocket.Port.IntValue(), failureCmd)
s.HealthConfig, err = probeToHealthConfig(containerYAML.LivenessProbe)
if err != nil {
return err
}
s.HealthConfig, err = makeHealthCheck(commandString, probe.PeriodSeconds, probe.FailureThreshold, probe.TimeoutSeconds, probe.InitialDelaySeconds)
// if restart policy is in place, ensure the health check enforces it
if restartPolicy == "always" || restartPolicy == "onfailure" {
s.HealthCheckOnFailureAction = define.HealthCheckOnFailureActionRestart
}
return nil
}
return nil
}

func setupStartupProbe(s *specgen.SpecGenerator, containerYAML v1.Container, restartPolicy string) error {
if containerYAML.StartupProbe == nil {
return nil
}
emptyHandler := v1.Handler{}
if containerYAML.StartupProbe.Handler != emptyHandler {
healthConfig, err := probeToHealthConfig(containerYAML.StartupProbe)
if err != nil {
return err
}

// currently, StartupProbe still an optional feature, and it requires HealthConfig.
if s.HealthConfig == nil {
probe := containerYAML.StartupProbe
s.HealthConfig, err = makeHealthCheck("exit 0", probe.PeriodSeconds, probe.FailureThreshold, probe.TimeoutSeconds, probe.InitialDelaySeconds)
if err != nil {
return err
}
}
s.StartupHealthConfig = &define.StartupHealthCheck{
Schema2HealthConfig: *healthConfig,
Successes: int(containerYAML.StartupProbe.SuccessThreshold),
}
// if restart policy is in place, ensure the health check enforces it
if restartPolicy == "always" || restartPolicy == "onfailure" {
s.HealthCheckOnFailureAction = define.HealthCheckOnFailureActionRestart
Expand Down
73 changes: 72 additions & 1 deletion test/e2e/play_kube_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,48 @@ spec:
periodSeconds: 1
`

var startupProbePodYaml = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: startup-healthy-probe
labels:
app: alpine
spec:
replicas: 1
selector:
matchLabels:
app: alpine
template:
metadata:
labels:
app: alpine
spec:
restartPolicy: Never
containers:
- command:
- top
- -d
- "1.5"
name: alpine
image: quay.io/libpod/alpine:latest
startupProbe:
exec:
command:
- /bin/sh
- -c
- cat /testfile
initialDelaySeconds: 0
periodSeconds: 1
livenessProbe:
exec:
command:
- echo
- liveness probe
initialDelaySeconds: 0
periodSeconds: 1
`

var selinuxLabelPodYaml = `
apiVersion: v1
kind: Pod
Expand Down Expand Up @@ -1712,7 +1754,7 @@ var _ = Describe("Podman play kube", func() {
inspect.WaitWithDefaultTimeout()
healthcheckcmd := inspect.OutputToString()
// check if CMD-SHELL based equivalent health check is added to container
Expect(healthcheckcmd).To(ContainSubstring("CMD-SHELL"))
Expect(healthcheckcmd).To(ContainSubstring("[echo hello]"))
})

It("podman play kube liveness probe should fail", func() {
Expand All @@ -1730,6 +1772,35 @@ var _ = Describe("Podman play kube", func() {
Expect(hcoutput).To(ContainSubstring(define.HealthCheckUnhealthy))
})

It("podman play kube support container startup probe", func() {
ctrName := "startup-healthy-probe-pod-0-alpine"
err := writeYaml(startupProbePodYaml, kubeYaml)
Expect(err).ToNot(HaveOccurred())

kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))

time.Sleep(2 * time.Second)
inspect := podmanTest.InspectContainer(ctrName)
Expect(inspect[0].State.Health).To(HaveField("Status", "starting"))

hc := podmanTest.Podman([]string{"healthcheck", "run", ctrName})
hc.WaitWithDefaultTimeout()
Expect(hc).Should(Exit(1))

exec := podmanTest.Podman([]string{"exec", ctrName, "sh", "-c", "echo 'startup probe success' > /testfile"})
exec.WaitWithDefaultTimeout()
Expect(exec).Should(Exit(0))

hc = podmanTest.Podman([]string{"healthcheck", "run", ctrName})
hc.WaitWithDefaultTimeout()
Expect(hc).Should(Exit(0))

inspect = podmanTest.InspectContainer(ctrName)
Expect(inspect[0].State.Health).To(HaveField("Status", define.HealthCheckHealthy))
})

It("podman play kube fail with nonexistent authfile", func() {
err := generateKubeYaml("pod", getPod(), kubeYaml)
Expect(err).ToNot(HaveOccurred())
Expand Down

0 comments on commit 7bd1dbb

Please sign in to comment.