From 0b331d9e5dede975d62ca382b2dd7dff89019f6e Mon Sep 17 00:00:00 2001 From: james song Date: Thu, 20 May 2021 00:52:33 +0900 Subject: [PATCH] Fixed, #1385 (#1386) Add support for more readiness args --- docs/user-guide.md | 43 ++++++++++++++++++++++++++ pkg/kobject/kobject.go | 2 ++ pkg/loader/compose/compose_test.go | 34 +++++++++++++++++++- pkg/loader/compose/utils.go | 4 +++ pkg/loader/compose/v3.go | 24 ++++++++++++-- pkg/transformer/kubernetes/k8sutils.go | 9 ++++++ 6 files changed, 112 insertions(+), 4 deletions(-) diff --git a/docs/user-guide.md b/docs/user-guide.md index 1dbe4a1a..8e1b74df 100755 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -165,6 +165,13 @@ The currently supported options are: | kompose.controller.type | deployment / daemonset / replicationcontroller | | kompose.image-pull-policy | kubernetes pods imagePullPolicy | | kompose.image-pull-secret | kubernetes secret name for imagePullSecrets | +| kompose.service.healthcheck.readiness.test | kubernetes readiness exec command | +| kompose.service.healthcheck.readiness.interval | kubernetes readiness interval value | +| kompose.service.healthcheck.readiness.timeout | kubernetes readiness timeout value | +| kompose.service.healthcheck.readiness.retries | kubernetes readiness retries value | +| kompose.service.healthcheck.readiness.start_period | kubernetes readiness start_period | +| kompose.service.healthcheck.liveness.http_get_path | kubernetes liveness httpGet path | +| kompose.service.healthcheck.liveness.http_get_port | kubernetes liveness httpGet port | **Note**: `kompose.service.type` label should be defined with `ports` only (except for headless service), otherwise `kompose` will fail. @@ -289,6 +296,42 @@ services: kompose.image-pull-policy: "Never" ``` + +For example: + +```yaml +version: '2' +services: + example-service: + image: example-image + labels: + kompose.service.healthcheck.liveness.http_get_path: /health/ping + kompose.service.healthcheck.liveness.http_get_port: 8080 + healthcheck: + interval: 10s + timeout: 10s + retries: 3 + start_period: 30s +``` + +- `kompose.service.healthcheck.liveness` defines Kubernetes [liveness HttpRequest](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-a-liveness-http-request), If you use healthcheck without liveness labels, have to define `test` in healcheck it's work to Kubernetes [liveness command](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes) + +For example: + +```yaml +version: '2' +services: + example-service: + image: example-image + labels: + kompose.service.healthcheck.readiness.test: CMD curl -f "http://localhost:8080/health/ping" + kompose.service.healthcheck.readiness.interval: 10s + kompose.service.healthcheck.readiness.timeout: 10s + kompose.service.healthcheck.readiness.retries: 3 + kompose.service.healthcheck.readiness.start_period: 30s +``` +- `kompose.service.healthcheck.readiness` defines Kubernetes [readiness](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#define-readiness-probes) + ## Restart If you want to create normal pods without controller you can use `restart` construct of docker-compose to define that. Follow table below to see what happens on the `restart` value. diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 915d1ed8..9965e83e 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -164,6 +164,8 @@ type HealthCheck struct { Retries int32 StartPeriod int32 Disable bool + HTTPPath string + HTTPPort int32 } // EnvVar holds the environment variable struct of a container diff --git a/pkg/loader/compose/compose_test.go b/pkg/loader/compose/compose_test.go index 7a8986ff..7b556705 100644 --- a/pkg/loader/compose/compose_test.go +++ b/pkg/loader/compose/compose_test.go @@ -57,7 +57,39 @@ func TestParseHealthCheck(t *testing.T) { Retries: 2, StartPeriod: 3, } - output, err := parseHealthCheck(check) + output, err := parseHealthCheck(check, nil) + if err != nil { + t.Errorf("Unable to convert HealthCheckConfig: %s", err) + } + + if !reflect.DeepEqual(output, expected) { + t.Errorf("Structs are not equal, expected: %v, output: %v", expected, output) + } +} + +func TestParseHttpHealthCheck(t *testing.T) { + helperValue := uint64(2) + check := types.HealthCheckConfig{ + Timeout: durationTypesPtr(1 * time.Second), + Interval: durationTypesPtr(2 * time.Second), + Retries: &helperValue, + StartPeriod: durationTypesPtr(3 * time.Second), + } + label := types.Labels{ + HealthCheckLivenessHTTPGetPath: "ping", + HealthCheckLivenessHTTPGetPort: "80", + } + + // CMD-SHELL or SHELL is included Test within docker/cli, thus we remove the first value in Test + expected := kobject.HealthCheck{ + HTTPPath: "ping", + HTTPPort: 80, + Timeout: 1, + Interval: 2, + Retries: 2, + StartPeriod: 3, + } + output, err := parseHealthCheck(check, label) if err != nil { t.Errorf("Unable to convert HealthCheckConfig: %s", err) } diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go index 2ea2189e..c67a7166 100644 --- a/pkg/loader/compose/utils.go +++ b/pkg/loader/compose/utils.go @@ -56,6 +56,10 @@ const ( HealthCheckReadinessRetries = "kompose.service.healthcheck.readiness.retries" // HealthCheckReadinessStartPeriod defines readiness health check start period HealthCheckReadinessStartPeriod = "kompose.service.healthcheck.readiness.start_period" + // HealthCheckLivenessHTTPGetPath defines liveness health check HttpGet path + HealthCheckLivenessHTTPGetPath = "kompose.service.healthcheck.liveness.http_get_path" + // HealthCheckLivenessHTTPGetPort defines liveness health check HttpGet port + HealthCheckLivenessHTTPGetPort = "kompose.service.healthcheck.liveness.http_get_port" // ServiceTypeHeadless ... ServiceTypeHeadless = "Headless" diff --git a/pkg/loader/compose/v3.go b/pkg/loader/compose/v3.go index 42bfba16..3b3e5eb4 100644 --- a/pkg/loader/compose/v3.go +++ b/pkg/loader/compose/v3.go @@ -288,8 +288,11 @@ func parseHealthCheckReadiness(labels types.Labels) (kobject.HealthCheck, error) /* Convert the HealthCheckConfig as designed by Docker to a Kubernetes-compatible format. */ -func parseHealthCheck(composeHealthCheck types.HealthCheckConfig) (kobject.HealthCheck, error) { +func parseHealthCheck(composeHealthCheck types.HealthCheckConfig, labels types.Labels) (kobject.HealthCheck, error) { var timeout, interval, retries, startPeriod int32 + var test []string + var httpPort int32 + var httpPath string // Here we convert the timeout from 1h30s (example) to 36030 seconds. if composeHealthCheck.Timeout != nil { @@ -320,9 +323,24 @@ func parseHealthCheck(composeHealthCheck types.HealthCheckConfig) (kobject.Healt startPeriod = int32(parse.Seconds()) } + if composeHealthCheck.Test != nil { + test = composeHealthCheck.Test[1:] + } + + for key, value := range labels { + switch key { + case HealthCheckLivenessHTTPGetPath: + httpPath = value + case HealthCheckLivenessHTTPGetPort: + httpPort = cast.ToInt32(value) + } + } + // Due to docker/cli adding "CMD-SHELL" to the struct, we remove the first element of composeHealthCheck.Test return kobject.HealthCheck{ - Test: composeHealthCheck.Test[1:], + Test: test, + HTTPPath: httpPath, + HTTPPort: httpPort, Timeout: timeout, Interval: interval, Retries: retries, @@ -384,7 +402,7 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose // HealthCheck Liveness if composeServiceConfig.HealthCheck != nil && !composeServiceConfig.HealthCheck.Disable { var err error - serviceConfig.HealthChecks.Liveness, err = parseHealthCheck(*composeServiceConfig.HealthCheck) + serviceConfig.HealthChecks.Liveness, err = parseHealthCheck(*composeServiceConfig.HealthCheck, *&composeServiceConfig.Labels) if err != nil { return kobject.KomposeObject{}, errors.Wrap(err, "Unable to parse health check") } diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index 0ff96cd2..f3cfbd32 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -44,6 +44,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" ) /** @@ -511,6 +512,14 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic Command: service.HealthChecks.Liveness.Test, }, } + } else if !reflect.ValueOf(service.HealthChecks.Liveness.HTTPPath).IsZero() && + !reflect.ValueOf(service.HealthChecks.Liveness.HTTPPort).IsZero() { + probe.Handler = api.Handler{ + HTTPGet: &api.HTTPGetAction{ + Path: service.HealthChecks.Liveness.HTTPPath, + Port: intstr.FromInt(int(service.HealthChecks.Liveness.HTTPPort)), + }, + } } else { return errors.New("Health check must contain a command") }