mirror of https://github.com/containers/podman.git
Merge pull request #19173 from hedayat/support-port-name-in-probes
Better support for Kubernetes health probes
This commit is contained in:
commit
960a764c59
2
go.mod
2
go.mod
|
@ -62,6 +62,7 @@ require (
|
|||
github.com/vbauerster/mpb/v8 v8.4.0
|
||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||
go.etcd.io/bbolt v1.3.7
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
|
||||
golang.org/x/net v0.12.0
|
||||
golang.org/x/sync v0.3.0
|
||||
golang.org/x/sys v0.10.0
|
||||
|
@ -182,7 +183,6 @@ require (
|
|||
go.opentelemetry.io/otel/trace v1.15.0 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
|
||||
golang.org/x/mod v0.10.0 // indirect
|
||||
golang.org/x/oauth2 v0.9.0 // indirect
|
||||
golang.org/x/tools v0.9.3 // indirect
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
"github.com/containers/podman/v4/pkg/domain/entities"
|
||||
v1 "github.com/containers/podman/v4/pkg/k8s.io/api/core/v1"
|
||||
"github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/api/resource"
|
||||
"github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/util/intstr"
|
||||
"github.com/containers/podman/v4/pkg/specgen"
|
||||
"github.com/containers/podman/v4/pkg/specgen/generate"
|
||||
systemdDefine "github.com/containers/podman/v4/pkg/systemd/define"
|
||||
|
@ -35,6 +36,7 @@ import (
|
|||
"github.com/docker/go-units"
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/slices"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
|
@ -563,10 +565,11 @@ func parseMountPath(mountPath string, readOnly bool, propagationMode *v1.MountPr
|
|||
return dest, opts, nil
|
||||
}
|
||||
|
||||
func probeToHealthConfig(probe *v1.Probe) (*manifest.Schema2HealthConfig, error) {
|
||||
func probeToHealthConfig(probe *v1.Probe, containerPorts []v1.ContainerPort) (*manifest.Schema2HealthConfig, error) {
|
||||
var commandString string
|
||||
failureCmd := "exit 1"
|
||||
probeHandler := probe.Handler
|
||||
host := "localhost" // Kubernetes default is host IP, but with Podman currently we run inside the container
|
||||
|
||||
// configure healthcheck on the basis of Handler Actions.
|
||||
switch {
|
||||
|
@ -583,7 +586,6 @@ func probeToHealthConfig(probe *v1.Probe) (*manifest.Schema2HealthConfig, error)
|
|||
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
|
||||
}
|
||||
|
@ -591,13 +593,38 @@ func probeToHealthConfig(probe *v1.Probe) (*manifest.Schema2HealthConfig, error)
|
|||
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)
|
||||
portNum, err := getPortNumber(probeHandler.HTTPGet.Port, containerPorts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commandString = fmt.Sprintf("curl -f %s://%s:%d%s || %s", uriScheme, host, portNum, path, failureCmd)
|
||||
case probeHandler.TCPSocket != nil:
|
||||
commandString = fmt.Sprintf("nc -z -v %s %d || %s", probeHandler.TCPSocket.Host, probeHandler.TCPSocket.Port.IntValue(), failureCmd)
|
||||
portNum, err := getPortNumber(probeHandler.TCPSocket.Port, containerPorts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if probeHandler.TCPSocket.Host != "" {
|
||||
host = probeHandler.TCPSocket.Host
|
||||
}
|
||||
commandString = fmt.Sprintf("nc -z -v %s %d || %s", host, portNum, failureCmd)
|
||||
}
|
||||
return makeHealthCheck(commandString, probe.PeriodSeconds, probe.FailureThreshold, probe.TimeoutSeconds, probe.InitialDelaySeconds)
|
||||
}
|
||||
|
||||
func getPortNumber(port intstr.IntOrString, containerPorts []v1.ContainerPort) (int, error) {
|
||||
var portNum int
|
||||
if port.Type == intstr.String && port.IntValue() == 0 {
|
||||
idx := slices.IndexFunc(containerPorts, func(cp v1.ContainerPort) bool { return cp.Name == port.String() })
|
||||
if idx == -1 {
|
||||
return 0, fmt.Errorf("unknown port: %s", port.String())
|
||||
}
|
||||
portNum = int(containerPorts[idx].ContainerPort)
|
||||
} else {
|
||||
portNum = port.IntValue()
|
||||
}
|
||||
return portNum, nil
|
||||
}
|
||||
|
||||
func setupLivenessProbe(s *specgen.SpecGenerator, containerYAML v1.Container, restartPolicy string) error {
|
||||
var err error
|
||||
if containerYAML.LivenessProbe == nil {
|
||||
|
@ -605,7 +632,7 @@ func setupLivenessProbe(s *specgen.SpecGenerator, containerYAML v1.Container, re
|
|||
}
|
||||
emptyHandler := v1.Handler{}
|
||||
if containerYAML.LivenessProbe.Handler != emptyHandler {
|
||||
s.HealthConfig, err = probeToHealthConfig(containerYAML.LivenessProbe)
|
||||
s.HealthConfig, err = probeToHealthConfig(containerYAML.LivenessProbe, containerYAML.Ports)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -624,7 +651,7 @@ func setupStartupProbe(s *specgen.SpecGenerator, containerYAML v1.Container, res
|
|||
}
|
||||
emptyHandler := v1.Handler{}
|
||||
if containerYAML.StartupProbe.Handler != emptyHandler {
|
||||
healthConfig, err := probeToHealthConfig(containerYAML.StartupProbe)
|
||||
healthConfig, err := probeToHealthConfig(containerYAML.StartupProbe, containerYAML.Ports)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"testing"
|
||||
|
||||
v1 "github.com/containers/podman/v4/pkg/k8s.io/api/core/v1"
|
||||
"github.com/containers/podman/v4/pkg/k8s.io/apimachinery/pkg/util/intstr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -39,3 +40,26 @@ func TestParseMountPathRO(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.NotContains(t, options, "ro")
|
||||
}
|
||||
|
||||
func TestGetPortNumber(t *testing.T) {
|
||||
portSpec := intstr.IntOrString{Type: intstr.Int, IntVal: 3000, StrVal: "myport"}
|
||||
cp1 := v1.ContainerPort{Name: "myport", ContainerPort: 4000}
|
||||
cp2 := v1.ContainerPort{Name: "myport2", ContainerPort: 5000}
|
||||
i, e := getPortNumber(portSpec, []v1.ContainerPort{cp1, cp2})
|
||||
assert.NoError(t, e)
|
||||
assert.Equal(t, i, int(portSpec.IntVal))
|
||||
|
||||
portSpec.Type = intstr.String
|
||||
i, e = getPortNumber(portSpec, []v1.ContainerPort{cp1, cp2})
|
||||
assert.NoError(t, e)
|
||||
assert.Equal(t, i, 4000)
|
||||
|
||||
portSpec.StrVal = "not_valid"
|
||||
_, e = getPortNumber(portSpec, []v1.ContainerPort{cp1, cp2})
|
||||
assert.Error(t, e)
|
||||
|
||||
portSpec.StrVal = "6000"
|
||||
i, e = getPortNumber(portSpec, []v1.ContainerPort{cp1, cp2})
|
||||
assert.NoError(t, e)
|
||||
assert.Equal(t, i, 6000)
|
||||
}
|
||||
|
|
|
@ -1247,6 +1247,131 @@ func TestHttpLivenessProbe(t *testing.T) {
|
|||
true,
|
||||
"http://localhost:80/",
|
||||
},
|
||||
{
|
||||
"HttpLivenessProbeNamedPort",
|
||||
specgen.SpecGenerator{},
|
||||
v1.Container{
|
||||
LivenessProbe: &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
HTTPGet: &v1.HTTPGetAction{
|
||||
Port: intstr.FromString("httpPort"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Ports: []v1.ContainerPort{
|
||||
{Name: "servicePort", ContainerPort: 7000},
|
||||
{Name: "httpPort", ContainerPort: 8000},
|
||||
},
|
||||
},
|
||||
"always",
|
||||
true,
|
||||
"http://localhost:8000/",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
err := setupLivenessProbe(&test.specGenerator, test.container, test.restartPolicy)
|
||||
if err == nil {
|
||||
assert.Equal(t, err == nil, test.succeed)
|
||||
assert.Contains(t, test.specGenerator.ContainerHealthCheckConfig.HealthConfig.Test, test.expectedURL)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTCPLivenessProbe(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
specGenerator specgen.SpecGenerator
|
||||
container v1.Container
|
||||
restartPolicy string
|
||||
succeed bool
|
||||
expectedHost string
|
||||
expectedPort string
|
||||
}{
|
||||
{
|
||||
"TCPLivenessProbeNormal",
|
||||
specgen.SpecGenerator{},
|
||||
v1.Container{
|
||||
LivenessProbe: &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
TCPSocket: &v1.TCPSocketAction{
|
||||
Host: "127.0.0.1",
|
||||
Port: intstr.FromInt(8080),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"always",
|
||||
true,
|
||||
"127.0.0.1",
|
||||
"8080",
|
||||
},
|
||||
{
|
||||
"TCPLivenessProbeHostUsesDefault",
|
||||
specgen.SpecGenerator{},
|
||||
v1.Container{
|
||||
LivenessProbe: &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
TCPSocket: &v1.TCPSocketAction{
|
||||
Port: intstr.FromInt(200),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"always",
|
||||
true,
|
||||
"localhost",
|
||||
"200",
|
||||
},
|
||||
{
|
||||
"TCPLivenessProbeUseNamedPort",
|
||||
specgen.SpecGenerator{},
|
||||
v1.Container{
|
||||
LivenessProbe: &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
TCPSocket: &v1.TCPSocketAction{
|
||||
Port: intstr.FromString("servicePort"),
|
||||
Host: "myservice.domain.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
Ports: []v1.ContainerPort{
|
||||
{ContainerPort: 6000},
|
||||
{Name: "servicePort", ContainerPort: 4000},
|
||||
{Name: "2ndServicePort", ContainerPort: 3000},
|
||||
},
|
||||
},
|
||||
"always",
|
||||
true,
|
||||
"myservice.domain.com",
|
||||
"4000",
|
||||
},
|
||||
{
|
||||
"TCPLivenessProbeInvalidPortName",
|
||||
specgen.SpecGenerator{},
|
||||
v1.Container{
|
||||
LivenessProbe: &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
TCPSocket: &v1.TCPSocketAction{
|
||||
Port: intstr.FromString("3rdservicePort"),
|
||||
Host: "myservice.domain.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
Ports: []v1.ContainerPort{
|
||||
{ContainerPort: 6000},
|
||||
{Name: "servicePort", ContainerPort: 4000},
|
||||
{Name: "2ndServicePort", ContainerPort: 3000},
|
||||
},
|
||||
},
|
||||
"always",
|
||||
false,
|
||||
"myservice.domain.com",
|
||||
"4000",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@ -1254,7 +1379,10 @@ func TestHttpLivenessProbe(t *testing.T) {
|
|||
t.Run(test.name, func(t *testing.T) {
|
||||
err := setupLivenessProbe(&test.specGenerator, test.container, test.restartPolicy)
|
||||
assert.Equal(t, err == nil, test.succeed)
|
||||
assert.Contains(t, test.specGenerator.ContainerHealthCheckConfig.HealthConfig.Test, test.expectedURL)
|
||||
if err == nil {
|
||||
assert.Contains(t, test.specGenerator.ContainerHealthCheckConfig.HealthConfig.Test, test.expectedHost)
|
||||
assert.Contains(t, test.specGenerator.ContainerHealthCheckConfig.HealthConfig.Test, test.expectedPort)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue