Kube: Add liveness probe for containers.

Signed-off-by: flouthoc <flouthoc.git@gmail.com>
This commit is contained in:
flouthoc
2021-07-16 14:47:03 +05:30
parent f0cd16cb32
commit 81e32b1808
2 changed files with 195 additions and 0 deletions

View File

@ -6,10 +6,12 @@ import (
"fmt"
"net"
"strings"
"time"
"github.com/containers/common/libimage"
"github.com/containers/common/pkg/parse"
"github.com/containers/common/pkg/secrets"
"github.com/containers/image/v5/manifest"
ann "github.com/containers/podman/v3/pkg/annotations"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/containers/podman/v3/pkg/specgen/generate"
@ -129,6 +131,10 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener
}
setupSecurityContext(s, opts.Container)
err := setupLivenessProbe(s, opts.Container, opts.RestartPolicy)
if err != nil {
return nil, errors.Wrap(err, "Failed to configure livenessProbe")
}
// 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
@ -332,6 +338,95 @@ func parseMountPath(mountPath string, readOnly bool) (string, []string, error) {
return dest, options, nil
}
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
// append `exit 1` to `cmd` so healthcheck can be marked as `unhealthy`.
// append `kill 1` to `cmd` if appropriate restart policy is configured.
if restartPolicy == "always" || restartPolicy == "onfailure" {
// container will be restarted so we can kill init.
failureCmd = "kill 1"
}
// configure healthcheck on the basis of Handler Actions.
if probeHandler.Exec != nil {
execString := strings.Join(probeHandler.Exec.Command, " ")
commandString = fmt.Sprintf("%s || %s", execString, failureCmd)
} else if probeHandler.HTTPGet != nil {
commandString = fmt.Sprintf("curl %s://%s:%d/%s || %s", probeHandler.HTTPGet.Scheme, probeHandler.HTTPGet.Host, probeHandler.HTTPGet.Port.IntValue(), probeHandler.HTTPGet.Path, failureCmd)
} else if probeHandler.TCPSocket != nil {
commandString = fmt.Sprintf("nc -z -v %s %d || %s", probeHandler.TCPSocket.Host, probeHandler.TCPSocket.Port.IntValue(), failureCmd)
}
s.HealthConfig, err = makeHealthCheck(commandString, probe.PeriodSeconds, probe.FailureThreshold, probe.TimeoutSeconds, probe.InitialDelaySeconds)
if err != nil {
return err
}
return nil
}
return nil
}
func makeHealthCheck(inCmd string, interval int32, retries int32, timeout int32, startPeriod int32) (*manifest.Schema2HealthConfig, error) {
// Every healthcheck requires a command
if len(inCmd) == 0 {
return nil, errors.New("Must define a healthcheck command for all healthchecks")
}
// first try to parse option value as JSON array of strings...
cmd := []string{}
if inCmd == "none" {
cmd = []string{"NONE"}
} else {
err := json.Unmarshal([]byte(inCmd), &cmd)
if err != nil {
// ...otherwise pass it to "/bin/sh -c" inside the container
cmd = []string{"CMD-SHELL"}
cmd = append(cmd, strings.Split(inCmd, " ")...)
}
}
hc := manifest.Schema2HealthConfig{
Test: cmd,
}
if interval < 1 {
//kubernetes interval defaults to 10 sec and cannot be less than 1
interval = 10
}
hc.Interval = (time.Duration(interval) * time.Second)
if retries < 1 {
//kubernetes retries defaults to 3
retries = 3
}
hc.Retries = int(retries)
if timeout < 1 {
//kubernetes timeout defaults to 1
timeout = 1
}
timeoutDuration := (time.Duration(timeout) * time.Second)
if timeoutDuration < time.Duration(1) {
return nil, errors.New("healthcheck-timeout must be at least 1 second")
}
hc.Timeout = timeoutDuration
startPeriodDuration := (time.Duration(startPeriod) * time.Second)
if startPeriodDuration < time.Duration(0) {
return nil, errors.New("healthcheck-start-period must be 0 seconds or greater")
}
hc.StartPeriod = startPeriodDuration
return &hc, nil
}
func setupSecurityContext(s *specgen.SpecGenerator, containerYAML v1.Container) {
if containerYAML.SecurityContext == nil {
return

View File

@ -9,6 +9,7 @@ import (
"strconv"
"strings"
"text/template"
"time"
"github.com/containers/podman/v3/pkg/util"
. "github.com/containers/podman/v3/test/utils"
@ -67,6 +68,75 @@ spec:
shareProcessNamespace: true
status: {}
`
var livenessProbePodYaml = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: liveness-probe
labels:
app: alpine
spec:
replicas: 1
selector:
matchLabels:
app: alpine
template:
metadata:
labels:
app: alpine
spec:
containers:
- command:
- top
- -d
- "1.5"
name: alpine
image: quay.io/libpod/alpine:latest
ports:
- containerPort: 80
livenessProbe:
exec:
command:
- echo
- hello
initialDelaySeconds: 5
periodSeconds: 5
`
var livenessProbeUnhealthyPodYaml = `
apiVersion: apps/v1
kind: Deployment
metadata:
name: liveness-unhealthy-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
ports:
- containerPort: 80
livenessProbe:
exec:
command:
- cat
- /randomfile
initialDelaySeconds: 0
periodSeconds: 1
`
var selinuxLabelPodYaml = `
apiVersion: v1
@ -1061,6 +1131,36 @@ var _ = Describe("Podman play kube", func() {
Expect(sharednamespaces).To(ContainSubstring("pid"))
})
It("podman play kube support container liveness probe", func() {
err := writeYaml(livenessProbePodYaml, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))
inspect := podmanTest.Podman([]string{"inspect", "liveness-probe-pod-0-alpine", "--format", "'{{ .Config.Healthcheck }}'"})
inspect.WaitWithDefaultTimeout()
healthcheckcmd := inspect.OutputToString()
// check if CMD-SHELL based equivalent health check is added to container
Expect(healthcheckcmd).To(ContainSubstring("CMD-SHELL"))
})
It("podman play kube liveness probe should fail", func() {
err := writeYaml(livenessProbeUnhealthyPodYaml, kubeYaml)
Expect(err).To(BeNil())
kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
kube.WaitWithDefaultTimeout()
Expect(kube).Should(Exit(0))
time.Sleep(2 * time.Second)
hc := podmanTest.Podman([]string{"healthcheck", "run", "liveness-unhealthy-probe-pod-0-alpine"})
hc.WaitWithDefaultTimeout()
hcoutput := hc.OutputToString()
Expect(hcoutput).To(ContainSubstring("unhealthy"))
})
It("podman play kube fail with nonexistent authfile", func() {
err := generateKubeYaml("pod", getPod(), kubeYaml)
Expect(err).To(BeNil())