From d55071e9d673262ae11e4e8bf09d4841a7631e99 Mon Sep 17 00:00:00 2001 From: ichx Date: Wed, 3 Nov 2021 23:30:38 +0800 Subject: [PATCH] Support tcp/http liveness/readiness probe (#1449) --- docs/user-guide.md | 5 + pkg/kobject/kobject.go | 3 +- pkg/loader/compose/compose_test.go | 188 +++-- pkg/loader/compose/utils.go | 8 + pkg/loader/compose/v3.go | 39 +- pkg/transformer/kubernetes/k8sutils.go | 59 +- pkg/transformer/kubernetes/k8sutils_test.go | 113 ++- pkg/transformer/kubernetes/kubernetes.go | 2 - pkg/transformer/kubernetes/kubernetes_test.go | 78 +++ pkg/transformer/kubernetes/podspec.go | 117 ++-- script/test/cmd/tests_new.sh | 8 + .../docker-compose-healthcheck.yaml | 69 ++ .../healthcheck/output-healthcheck-k8s.json | 401 +++++++++++ .../healthcheck/output-healthcheck-os.json | 660 ++++++++++++++++++ 14 files changed, 1529 insertions(+), 221 deletions(-) create mode 100644 script/test/fixtures/healthcheck/docker-compose-healthcheck.yaml create mode 100644 script/test/fixtures/healthcheck/output-healthcheck-k8s.json create mode 100644 script/test/fixtures/healthcheck/output-healthcheck-os.json diff --git a/docs/user-guide.md b/docs/user-guide.md index 3ff992b2..61754b3a 100755 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -167,13 +167,18 @@ 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.disable | kubernetes readiness disable | | kompose.service.healthcheck.readiness.test | kubernetes readiness exec command | +| kompose.service.healthcheck.readiness.http_get_path | kubernetes readiness httpGet path | +| kompose.service.healthcheck.readiness.http_get_port | kubernetes readiness httpGet port | +| kompose.service.healthcheck.readiness.tcp_port | kubernetes readiness tcpSocket port | | 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 | +| kompose.service.healthcheck.liveness.tcp_port | kubernetes liveness tcpSocket port | **Note**: `kompose.service.type` label should be defined with `ports` only (except for headless service), otherwise `kompose` will fail. diff --git a/pkg/kobject/kobject.go b/pkg/kobject/kobject.go index 15d8195e..05704d87 100644 --- a/pkg/kobject/kobject.go +++ b/pkg/kobject/kobject.go @@ -165,7 +165,7 @@ type HealthChecks struct { } // HealthCheck the healthcheck configuration for a service -// "StartPeriod" is not yet added to compose, see: +// "StartPeriod" was added to v3.4 of the compose, see: // https://github.com/docker/cli/issues/116 type HealthCheck struct { Test []string @@ -176,6 +176,7 @@ type HealthCheck struct { Disable bool HTTPPath string HTTPPort int32 + TCPPort 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 aed7a518..7574d7cb 100644 --- a/pkg/loader/compose/compose_test.go +++ b/pkg/loader/compose/compose_test.go @@ -41,61 +41,157 @@ func durationTypesPtr(value time.Duration) *types.Duration { func TestParseHealthCheck(t *testing.T) { helperValue := uint64(2) - check := types.HealthCheckConfig{ - Test: []string{"CMD-SHELL", "echo", "foobar"}, - Timeout: durationTypesPtr(1 * time.Second), - Interval: durationTypesPtr(2 * time.Second), - Retries: &helperValue, - StartPeriod: durationTypesPtr(3 * time.Second), + type input struct { + healthCheck types.HealthCheckConfig + labels types.Labels + } + testCases := map[string]struct { + input input + expected kobject.HealthCheck + }{ + "Exec": { + input: input{ + healthCheck: types.HealthCheckConfig{ + Test: []string{"CMD-SHELL", "echo", "foobar"}, + Timeout: durationTypesPtr(1 * time.Second), + Interval: durationTypesPtr(2 * time.Second), + Retries: &helperValue, + StartPeriod: durationTypesPtr(3 * time.Second), + }, + }, + // CMD-SHELL or SHELL is included Test within docker/cli, thus we remove the first value in Test + expected: kobject.HealthCheck{ + Test: []string{"echo", "foobar"}, + Timeout: 1, + Interval: 2, + Retries: 2, + StartPeriod: 3, + }, + }, + "HTTPGet": { + input: input{ + healthCheck: types.HealthCheckConfig{ + Timeout: durationTypesPtr(1 * time.Second), + Interval: durationTypesPtr(2 * time.Second), + Retries: &helperValue, + StartPeriod: durationTypesPtr(3 * time.Second), + }, + labels: types.Labels{ + "kompose.service.healthcheck.liveness.http_get_path": "/health", + "kompose.service.healthcheck.liveness.http_get_port": "8080", + }, + }, + expected: kobject.HealthCheck{ + HTTPPath: "/health", + HTTPPort: 8080, + Timeout: 1, + Interval: 2, + Retries: 2, + StartPeriod: 3, + }, + }, + "TCPSocket": { + input: input{ + healthCheck: types.HealthCheckConfig{ + Timeout: durationTypesPtr(1 * time.Second), + Interval: durationTypesPtr(2 * time.Second), + Retries: &helperValue, + StartPeriod: durationTypesPtr(3 * time.Second), + }, + labels: types.Labels{ + "kompose.service.healthcheck.liveness.tcp_port": "8080", + }, + }, + expected: kobject.HealthCheck{ + TCPPort: 8080, + Timeout: 1, + Interval: 2, + Retries: 2, + StartPeriod: 3, + }, + }, } - // CMD-SHELL or SHELL is included Test within docker/cli, thus we remove the first value in Test - expected := kobject.HealthCheck{ - Test: []string{"echo", "foobar"}, - Timeout: 1, - Interval: 2, - Retries: 2, - StartPeriod: 3, - } - output, err := parseHealthCheck(check, nil) - if err != nil { - t.Errorf("Unable to convert HealthCheckConfig: %s", err) - } + for name, testCase := range testCases { + t.Log("Test case:", name) + output, err := parseHealthCheck(testCase.input.healthCheck, testCase.input.labels) + 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) + if !reflect.DeepEqual(output, testCase.expected) { + t.Errorf("Structs are not equal, expected: %v, output: %v", testCase.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", +func TestParseHealthCheckReadiness(t *testing.T) { + testCases := map[string]struct { + input types.Labels + expected kobject.HealthCheck + }{ + "Exec": { + input: types.Labels{ + "kompose.service.healthcheck.readiness.test": "echo foobar", + "kompose.service.healthcheck.readiness.timeout": "1s", + "kompose.service.healthcheck.readiness.interval": "2s", + "kompose.service.healthcheck.readiness.retries": "2", + "kompose.service.healthcheck.readiness.start_period": "3s", + }, + expected: kobject.HealthCheck{ + Test: []string{"echo", "foobar"}, + Timeout: 1, + Interval: 2, + Retries: 2, + StartPeriod: 3, + }, + }, + "HTTPGet": { + input: types.Labels{ + "kompose.service.healthcheck.readiness.http_get_path": "/ready", + "kompose.service.healthcheck.readiness.http_get_port": "8080", + "kompose.service.healthcheck.readiness.timeout": "1s", + "kompose.service.healthcheck.readiness.interval": "2s", + "kompose.service.healthcheck.readiness.retries": "2", + "kompose.service.healthcheck.readiness.start_period": "3s", + }, + expected: kobject.HealthCheck{ + HTTPPath: "/ready", + HTTPPort: 8080, + Timeout: 1, + Interval: 2, + Retries: 2, + StartPeriod: 3, + }, + }, + "TCPSocket": { + input: types.Labels{ + "kompose.service.healthcheck.readiness.tcp_port": "8080", + "kompose.service.healthcheck.readiness.timeout": "1s", + "kompose.service.healthcheck.readiness.interval": "2s", + "kompose.service.healthcheck.readiness.retries": "2", + "kompose.service.healthcheck.readiness.start_period": "3s", + }, + expected: kobject.HealthCheck{ + TCPPort: 8080, + Timeout: 1, + Interval: 2, + Retries: 2, + StartPeriod: 3, + }, + }, } - // 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) - } + for name, testCase := range testCases { + t.Log("Test case:", name) + output, err := parseHealthCheckReadiness(testCase.input) + 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) + if !reflect.DeepEqual(output, testCase.expected) { + t.Errorf("Structs are not equal, expected: %v, output: %v", testCase.expected, output) + } } } diff --git a/pkg/loader/compose/utils.go b/pkg/loader/compose/utils.go index d9be422f..e2dea0e5 100644 --- a/pkg/loader/compose/utils.go +++ b/pkg/loader/compose/utils.go @@ -60,10 +60,18 @@ const ( HealthCheckReadinessRetries = "kompose.service.healthcheck.readiness.retries" // HealthCheckReadinessStartPeriod defines readiness health check start period HealthCheckReadinessStartPeriod = "kompose.service.healthcheck.readiness.start_period" + // HealthCheckReadinessHTTPGetPath defines readiness health check HttpGet path + HealthCheckReadinessHTTPGetPath = "kompose.service.healthcheck.readiness.http_get_path" + // HealthCheckReadinessHTTPGetPort defines readiness health check HttpGet port + HealthCheckReadinessHTTPGetPort = "kompose.service.healthcheck.readiness.http_get_port" + // HealthCheckReadinessTCPPort defines readiness health check tcp port + HealthCheckReadinessTCPPort = "kompose.service.healthcheck.readiness.tcp_port" // 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" + // HealthCheckLivenessTCPPort defines liveness health check tcp port + HealthCheckLivenessTCPPort = "kompose.service.healthcheck.liveness.tcp_port" // ServiceTypeHeadless ... ServiceTypeHeadless = "Headless" diff --git a/pkg/loader/compose/v3.go b/pkg/loader/compose/v3.go index eaf9fd79..35aa3f2e 100644 --- a/pkg/loader/compose/v3.go +++ b/pkg/loader/compose/v3.go @@ -245,9 +245,9 @@ func loadV3Ports(ports []types.ServicePortConfig, expose []string) []kobject.Por a Kubernetes-compatible format. */ func parseHealthCheckReadiness(labels types.Labels) (kobject.HealthCheck, error) { - // initialize with CMD as default to not break at return (will be ignored if no test is informed) - test := []string{"CMD"} - var timeout, interval, retries, startPeriod int32 + var test []string + var httpPath string + var httpPort, tcpPort, timeout, interval, retries, startPeriod int32 var disable bool for key, value := range labels { @@ -258,6 +258,12 @@ func parseHealthCheckReadiness(labels types.Labels) (kobject.HealthCheck, error) if len(value) > 0 { test, _ = shlex.Split(value) } + case HealthCheckReadinessHTTPGetPath: + httpPath = value + case HealthCheckReadinessHTTPGetPort: + httpPort = cast.ToInt32(value) + case HealthCheckReadinessTCPPort: + tcpPort = cast.ToInt32(value) case HealthCheckReadinessInterval: parse, err := time.ParseDuration(value) if err != nil { @@ -281,17 +287,22 @@ func parseHealthCheckReadiness(labels types.Labels) (kobject.HealthCheck, error) } } - if test[0] == "NONE" { - disable = true - test = test[1:] - } - if test[0] == "CMD" || test[0] == "CMD-SHELL" { - test = test[1:] + if len(test) > 0 { + if test[0] == "NONE" { + disable = true + test = test[1:] + } + // Due to docker/cli adding "CMD-SHELL" to the struct, we remove the first element of composeHealthCheck.Test + if test[0] == "CMD" || test[0] == "CMD-SHELL" { + test = test[1:] + } } - // Due to docker/cli adding "CMD-SHELL" to the struct, we remove the first element of composeHealthCheck.Test return kobject.HealthCheck{ Test: test, + HTTPPath: httpPath, + HTTPPort: httpPort, + TCPPort: tcpPort, Timeout: timeout, Interval: interval, Retries: retries, @@ -304,9 +315,8 @@ func parseHealthCheckReadiness(labels types.Labels) (kobject.HealthCheck, error) a Kubernetes-compatible format. */ func parseHealthCheck(composeHealthCheck types.HealthCheckConfig, labels types.Labels) (kobject.HealthCheck, error) { - var timeout, interval, retries, startPeriod int32 + var httpPort, tcpPort, 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. @@ -348,12 +358,15 @@ func parseHealthCheck(composeHealthCheck types.HealthCheckConfig, labels types.L httpPath = value case HealthCheckLivenessHTTPGetPort: httpPort = cast.ToInt32(value) + case HealthCheckLivenessTCPPort: + tcpPort = 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: test, + TCPPort: tcpPort, HTTPPath: httpPath, HTTPPort: httpPort, Timeout: timeout, @@ -426,7 +439,7 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose // HealthCheck Readiness var readiness, errReadiness = parseHealthCheckReadiness(*&composeServiceConfig.Labels) - if readiness.Test != nil && len(readiness.Test) > 0 && len(readiness.Test[0]) > 0 && !readiness.Disable { + if !readiness.Disable { serviceConfig.HealthChecks.Readiness = readiness if errReadiness != nil { return kobject.KomposeObject{}, errors.Wrap(errReadiness, "Unable to parse health check") diff --git a/pkg/transformer/kubernetes/k8sutils.go b/pkg/transformer/kubernetes/k8sutils.go index 8312bff0..774150d9 100644 --- a/pkg/transformer/kubernetes/k8sutils.go +++ b/pkg/transformer/kubernetes/k8sutils.go @@ -45,7 +45,6 @@ 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" ) /** @@ -534,62 +533,8 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic template.Spec.Volumes = append(template.Spec.Volumes, volumes...) template.Spec.Affinity = ConfigAffinity(service) // Configure the HealthCheck - // We check to see if it's blank - if !reflect.DeepEqual(service.HealthChecks.Liveness, kobject.HealthCheck{}) { - probe := api.Probe{} - - if len(service.HealthChecks.Liveness.Test) > 0 { - probe.Handler = api.Handler{ - Exec: &api.ExecAction{ - 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") - } - - probe.TimeoutSeconds = service.HealthChecks.Liveness.Timeout - probe.PeriodSeconds = service.HealthChecks.Liveness.Interval - probe.FailureThreshold = service.HealthChecks.Liveness.Retries - - // See issue: https://github.com/docker/cli/issues/116 - // StartPeriod has been added to docker/cli however, it is not yet added - // to compose. Once the feature has been implemented, this will automatically work - probe.InitialDelaySeconds = service.HealthChecks.Liveness.StartPeriod - - template.Spec.Containers[0].LivenessProbe = &probe - } - if !reflect.DeepEqual(service.HealthChecks.Readiness, kobject.HealthCheck{}) { - probeHealthCheckReadiness := api.Probe{} - if len(service.HealthChecks.Readiness.Test) > 0 { - probeHealthCheckReadiness.Handler = api.Handler{ - Exec: &api.ExecAction{ - Command: service.HealthChecks.Readiness.Test, - }, - } - } else { - return errors.New("Health check must contain a command") - } - - probeHealthCheckReadiness.TimeoutSeconds = service.HealthChecks.Readiness.Timeout - probeHealthCheckReadiness.PeriodSeconds = service.HealthChecks.Readiness.Interval - probeHealthCheckReadiness.FailureThreshold = service.HealthChecks.Readiness.Retries - - // See issue: https://github.com/docker/cli/issues/116 - // StartPeriod has been added to docker/cli however, it is not yet added - // to compose. Once the feature has been implemented, this will automatically work - probeHealthCheckReadiness.InitialDelaySeconds = service.HealthChecks.Readiness.StartPeriod - - template.Spec.Containers[0].ReadinessProbe = &probeHealthCheckReadiness - } + template.Spec.Containers[0].LivenessProbe = configProbe(service.HealthChecks.Liveness) + template.Spec.Containers[0].ReadinessProbe = configProbe(service.HealthChecks.Readiness) if service.StopGracePeriod != "" { template.Spec.TerminationGracePeriodSeconds, err = DurationStrToSecondsInt(service.StopGracePeriod) diff --git a/pkg/transformer/kubernetes/k8sutils_test.go b/pkg/transformer/kubernetes/k8sutils_test.go index 293f3b62..2dd3c841 100644 --- a/pkg/transformer/kubernetes/k8sutils_test.go +++ b/pkg/transformer/kubernetes/k8sutils_test.go @@ -352,41 +352,96 @@ func TestIsDir(t *testing.T) { } } -// TestServiceWithoutPort this tests if Headless Service is created for services without Port. +// TestServiceWithHealthCheck this tests if Headless Service is created for services with HealthCheck. func TestServiceWithHealthCheck(t *testing.T) { - service := kobject.ServiceConfig{ - ContainerName: "name", - Image: "image", - ServiceType: "Headless", - HealthChecks: kobject.HealthChecks{ - Readiness: kobject.HealthCheck{ - Test: []string{"arg1", "arg2"}, - Timeout: 10, - Interval: 5, - Retries: 3, - StartPeriod: 60, + testCases := map[string]struct { + service kobject.ServiceConfig + }{ + "Exec": { + service: kobject.ServiceConfig{ + ContainerName: "name", + Image: "image", + ServiceType: "Headless", + HealthChecks: kobject.HealthChecks{ + Readiness: kobject.HealthCheck{ + Test: []string{"arg1", "arg2"}, + Timeout: 10, + Interval: 5, + Retries: 3, + StartPeriod: 60, + }, + Liveness: kobject.HealthCheck{ + Test: []string{"arg1", "arg2"}, + Timeout: 11, + Interval: 6, + Retries: 4, + StartPeriod: 61, + }, + }, }, - Liveness: kobject.HealthCheck{ - Test: []string{"arg1", "arg2"}, - Timeout: 11, - Interval: 6, - Retries: 4, - StartPeriod: 61, + }, + "HTTPGet": { + service: kobject.ServiceConfig{ + ContainerName: "name", + Image: "image", + ServiceType: "Headless", + HealthChecks: kobject.HealthChecks{ + Readiness: kobject.HealthCheck{ + HTTPPath: "/health", + HTTPPort: 8080, + Timeout: 10, + Interval: 5, + Retries: 3, + StartPeriod: 60, + }, + Liveness: kobject.HealthCheck{ + HTTPPath: "/ready", + HTTPPort: 8080, + Timeout: 11, + Interval: 6, + Retries: 4, + StartPeriod: 61, + }, + }, + }, + }, + "TCPSocket": { + service: kobject.ServiceConfig{ + ContainerName: "name", + Image: "image", + ServiceType: "Headless", + HealthChecks: kobject.HealthChecks{ + Readiness: kobject.HealthCheck{ + TCPPort: 8080, + Timeout: 10, + Interval: 5, + Retries: 3, + StartPeriod: 60, + }, + Liveness: kobject.HealthCheck{ + TCPPort: 8080, + Timeout: 11, + Interval: 6, + Retries: 4, + StartPeriod: 61, + }, + }, }, }, } - komposeObject := kobject.KomposeObject{ - ServiceConfigs: map[string]kobject.ServiceConfig{"app": service}, - } - k := Kubernetes{} - - objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 1}) - if err != nil { - t.Error(errors.Wrap(err, "k.Transform failed")) - } - if err := testutils.CheckForHealthCheckLivenessAndReadiness(objects); err != nil { - t.Error(err) + for _, testCase := range testCases { + k := Kubernetes{} + komposeObject := kobject.KomposeObject{ + ServiceConfigs: map[string]kobject.ServiceConfig{"app": testCase.service}, + } + objects, err := k.Transform(komposeObject, kobject.ConvertOptions{CreateD: true, Replicas: 1}) + if err != nil { + t.Error(errors.Wrap(err, "k.Transform failed")) + } + if err := testutils.CheckForHealthCheckLivenessAndReadiness(objects); err != nil { + t.Error(err) + } } } diff --git a/pkg/transformer/kubernetes/kubernetes.go b/pkg/transformer/kubernetes/kubernetes.go index bcf86802..c271bebe 100644 --- a/pkg/transformer/kubernetes/kubernetes.go +++ b/pkg/transformer/kubernetes/kubernetes.go @@ -1363,8 +1363,6 @@ func (k *Kubernetes) Transform(komposeObject kobject.KomposeObject, opt kobject. ImagePullPolicy(name, service), RestartPolicy(name, service), SecurityContext(name, service), - LivenessProbe(service), - ReadinessProbe(service), HostName(service), DomainName(service), ResourcesLimits(service), diff --git a/pkg/transformer/kubernetes/kubernetes_test.go b/pkg/transformer/kubernetes/kubernetes_test.go index 2d68a63e..ab04da0a 100644 --- a/pkg/transformer/kubernetes/kubernetes_test.go +++ b/pkg/transformer/kubernetes/kubernetes_test.go @@ -744,6 +744,84 @@ func TestServiceAccountNameOnMultipleContainers(t *testing.T) { } } +func TestHealthCheckOnMultipleContainers(t *testing.T) { + groupName := "pod_group" + + createHealthCheck := func(TCPPort int32) kobject.HealthCheck { + return kobject.HealthCheck{ + TCPPort: TCPPort, + } + } + + createConfig := func(name string, livenessTCPPort, readinessTCPPort int32) kobject.ServiceConfig { + config := newSimpleServiceConfig() + config.Labels = map[string]string{compose.LabelServiceGroup: groupName} + config.Name = name + config.ContainerName = name + config.HealthChecks.Liveness = createHealthCheck(livenessTCPPort) + config.HealthChecks.Readiness = createHealthCheck(readinessTCPPort) + return config + } + + testCases := map[string]struct { + komposeObject kobject.KomposeObject + opt kobject.ConvertOptions + expectedContainers map[string]api.Container + }{ + "Converted multiple containers to Deployments": { + kobject.KomposeObject{ + ServiceConfigs: map[string]kobject.ServiceConfig{ + "app1": createConfig("app1", 8081, 9091), + "app2": createConfig("app2", 8082, 9092), + }, + }, + kobject.ConvertOptions{ServiceGroupMode: "label", CreateD: true}, + map[string]api.Container{ + "app1": { + LivenessProbe: configProbe(createHealthCheck(8081)), + ReadinessProbe: configProbe(createHealthCheck(9091)), + }, + "app2": { + LivenessProbe: configProbe(createHealthCheck(8082)), + ReadinessProbe: configProbe(createHealthCheck(9092)), + }, + }, + }, + } + + for name, test := range testCases { + t.Log("Test case:", name) + k := Kubernetes{} + // Run Transform + objs, err := k.Transform(test.komposeObject, test.opt) + if err != nil { + t.Error(errors.Wrap(err, "k.Transform failed")) + } + + // Check results + for _, obj := range objs { + if deployment, ok := obj.(*appsv1.Deployment); ok { + if len(deployment.Spec.Template.Spec.Containers) != len(test.expectedContainers) { + t.Errorf("Containers len is not equal, expected %d, got %d", + len(deployment.Spec.Template.Spec.Containers), len(test.expectedContainers)) + } + for _, result := range deployment.Spec.Template.Spec.Containers { + expected, ok := test.expectedContainers[result.Name] + if !ok { + t.Errorf("Container %s doesn't expected", result.Name) + } + if !reflect.DeepEqual(result.LivenessProbe, expected.LivenessProbe) { + t.Errorf("Container %s: LivenessProbe expected %v returned, got %v", result.Name, expected.LivenessProbe, result.LivenessProbe) + } + if !reflect.DeepEqual(result.ReadinessProbe, expected.ReadinessProbe) { + t.Errorf("Container %s: ReadinessProbe expected %v returned, got %v", result.Name, expected.ReadinessProbe, result.ReadinessProbe) + } + } + } + } + } +} + func TestCreatePVC(t *testing.T) { storageClassName := "custom-storage-class-name" k := Kubernetes{} diff --git a/pkg/transformer/kubernetes/podspec.go b/pkg/transformer/kubernetes/podspec.go index ddb6abc7..0591c7ed 100644 --- a/pkg/transformer/kubernetes/podspec.go +++ b/pkg/transformer/kubernetes/podspec.go @@ -34,14 +34,16 @@ func AddContainer(service kobject.ServiceConfig, opt kobject.ConvertOptions) Pod } podSpec.Containers = append(podSpec.Containers, api.Container{ - Name: name, - Image: image, - Env: envs, - Command: service.Command, - Args: service.Args, - WorkingDir: service.WorkingDir, - Stdin: service.Stdin, - TTY: service.Tty, + Name: name, + Image: image, + Env: envs, + Command: service.Command, + Args: service.Args, + WorkingDir: service.WorkingDir, + Stdin: service.Stdin, + TTY: service.Tty, + LivenessProbe: configProbe(service.HealthChecks.Liveness), + ReadinessProbe: configProbe(service.HealthChecks.Readiness), }) podSpec.Affinity = ConfigAffinity(service) @@ -258,75 +260,44 @@ func DomainName(service kobject.ServiceConfig) PodSpecOption { } } -func LivenessProbe(service kobject.ServiceConfig) PodSpecOption { - return func(podSpec *PodSpec) { - // Configure the HealthCheck - // We check to see if it's blank - if !reflect.DeepEqual(service.HealthChecks.Liveness, kobject.HealthCheck{}) { - probe := api.Probe{} - - if len(service.HealthChecks.Liveness.Test) > 0 { - probe.Handler = api.Handler{ - Exec: &api.ExecAction{ - 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 { - panic(errors.New("Health check must contain a command")) - } - - probe.TimeoutSeconds = service.HealthChecks.Liveness.Timeout - probe.PeriodSeconds = service.HealthChecks.Liveness.Interval - probe.FailureThreshold = service.HealthChecks.Liveness.Retries - - // See issue: https://github.com/docker/cli/issues/116 - // StartPeriod has been added to docker/cli however, it is not yet added - // to compose. Once the feature has been implemented, this will automatically work - probe.InitialDelaySeconds = service.HealthChecks.Liveness.StartPeriod - - for i := range podSpec.Containers { - podSpec.Containers[i].LivenessProbe = &probe - } - } +func configProbe(healthCheck kobject.HealthCheck) *api.Probe { + probe := api.Probe{} + // We check to see if it's blank or disable + if reflect.DeepEqual(healthCheck, kobject.HealthCheck{}) || healthCheck.Disable { + return nil } -} -func ReadinessProbe(service kobject.ServiceConfig) PodSpecOption { - return func(podSpec *PodSpec) { - if !reflect.DeepEqual(service.HealthChecks.Readiness, kobject.HealthCheck{}) { - probeHealthCheckReadiness := api.Probe{} - if len(service.HealthChecks.Readiness.Test) > 0 { - probeHealthCheckReadiness.Handler = api.Handler{ - Exec: &api.ExecAction{ - Command: service.HealthChecks.Readiness.Test, - }, - } - } else { - panic(errors.New("Health check must contain a command")) - } - - probeHealthCheckReadiness.TimeoutSeconds = service.HealthChecks.Readiness.Timeout - probeHealthCheckReadiness.PeriodSeconds = service.HealthChecks.Readiness.Interval - probeHealthCheckReadiness.FailureThreshold = service.HealthChecks.Readiness.Retries - - // See issue: https://github.com/docker/cli/issues/116 - // StartPeriod has been added to docker/cli however, it is not yet added - // to compose. Once the feature has been implemented, this will automatically work - probeHealthCheckReadiness.InitialDelaySeconds = service.HealthChecks.Readiness.StartPeriod - - for i := range podSpec.Containers { - podSpec.Containers[i].ReadinessProbe = &probeHealthCheckReadiness - } + if len(healthCheck.Test) > 0 { + probe.Handler = api.Handler{ + Exec: &api.ExecAction{ + Command: healthCheck.Test, + }, } + } else if !reflect.ValueOf(healthCheck.HTTPPath).IsZero() && !reflect.ValueOf(healthCheck.HTTPPort).IsZero() { + probe.Handler = api.Handler{ + HTTPGet: &api.HTTPGetAction{ + Path: healthCheck.HTTPPath, + Port: intstr.FromInt(int(healthCheck.HTTPPort)), + }, + } + } else if !reflect.ValueOf(healthCheck.TCPPort).IsZero() { + probe.Handler = api.Handler{ + TCPSocket: &api.TCPSocketAction{ + Port: intstr.FromInt(int(healthCheck.TCPPort)), + }, + } + } else { + panic(errors.New("Health check must contain a command")) } + + probe.TimeoutSeconds = healthCheck.Timeout + probe.PeriodSeconds = healthCheck.Interval + probe.FailureThreshold = healthCheck.Retries + + // See issue: https://github.com/docker/cli/issues/116 + // StartPeriod has been added to v3.4 of the compose + probe.InitialDelaySeconds = healthCheck.StartPeriod + return &probe } func ServiceAccountName(serviceAccountName string) PodSpecOption { diff --git a/script/test/cmd/tests_new.sh b/script/test/cmd/tests_new.sh index 6de48208..16fa3863 100755 --- a/script/test/cmd/tests_new.sh +++ b/script/test/cmd/tests_new.sh @@ -171,3 +171,11 @@ k8s_output="$KOMPOSE_ROOT/script/test/fixtures/multiple-files/output-k8s.json" ocp_output="$KOMPOSE_ROOT/script/test/fixtures/multiple-files/output-ocp.json" convert::expect_success_and_warning "$k8s_cmd" "$k8s_output" convert::expect_success "$ocp_cmd" "$ocp_output" + +# test health check +k8s_cmd="kompose -f $KOMPOSE_ROOT/script/test/fixtures/healthcheck/docker-compose-healthcheck.yaml convert --stdout -j --service-group-mode=label --with-kompose-annotation=false" +os_cmd="kompose --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/healthcheck/docker-compose-healthcheck.yaml convert --stdout -j --service-group-mode=label --with-kompose-annotation=false" +k8s_output="$KOMPOSE_ROOT/script/test/fixtures/healthcheck/output-healthcheck-k8s.json" +os_output="$KOMPOSE_ROOT/script/test/fixtures/healthcheck/output-healthcheck-os.json" +convert::expect_success "$k8s_cmd" "$k8s_output" +convert::expect_success "$os_cmd" "$os_output" \ No newline at end of file diff --git a/script/test/fixtures/healthcheck/docker-compose-healthcheck.yaml b/script/test/fixtures/healthcheck/docker-compose-healthcheck.yaml new file mode 100644 index 00000000..91df90fc --- /dev/null +++ b/script/test/fixtures/healthcheck/docker-compose-healthcheck.yaml @@ -0,0 +1,69 @@ +version: "3.3" +services: + # test exec + redis: + image: redis + ports: + - "6379" + healthcheck: + test: echo "liveness" + interval: 10s + timeout: 1s + retries: 5 + labels: + kompose.service.healthcheck.readiness.test: echo "liveness" + kompose.service.healthcheck.readiness.interval: 10s + kompose.service.healthcheck.readiness.timeout: 1s + kompose.service.healthcheck.readiness.retries: 5 + + # test http get + postgresql: + image: postgresql + ports: + - "5432" + healthcheck: + interval: 10s + timeout: 1s + retries: 5 + labels: + kompose.service.healthcheck.liveness.http_get_path: /health + kompose.service.healthcheck.liveness.http_get_port: 8080 + kompose.service.healthcheck.readiness.http_get_path: /ready + kompose.service.healthcheck.readiness.http_get_port: 8080 + kompose.service.healthcheck.readiness.interval: 10s + kompose.service.healthcheck.readiness.timeout: 1s + kompose.service.healthcheck.readiness.retries: 5 + + # test tcp socket + mongo: + image: mongo + ports: + - "27017" + healthcheck: + interval: 10s + timeout: 1s + retries: 5 + labels: + kompose.service.group: "my-group" + kompose.service.healthcheck.liveness.tcp_port: 8080 + kompose.service.healthcheck.readiness.tcp_port: 9090 + kompose.service.healthcheck.readiness.interval: 10s + kompose.service.healthcheck.readiness.timeout: 1s + kompose.service.healthcheck.readiness.retries: 5 + + # test multiple service merge + mysql: + image: mysql + ports: + - "3306" + healthcheck: + interval: 11s + timeout: 2s + retries: 6 + labels: + kompose.service.group: "my-group" + kompose.service.healthcheck.liveness.tcp_port: 8081 + kompose.service.healthcheck.readiness.tcp_port: 9091 + kompose.service.healthcheck.readiness.interval: 11s + kompose.service.healthcheck.readiness.timeout: 2s + kompose.service.healthcheck.readiness.retries: 6 \ No newline at end of file diff --git a/script/test/fixtures/healthcheck/output-healthcheck-k8s.json b/script/test/fixtures/healthcheck/output-healthcheck-k8s.json new file mode 100644 index 00000000..ef5a29b1 --- /dev/null +++ b/script/test/fixtures/healthcheck/output-healthcheck-k8s.json @@ -0,0 +1,401 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "mongo", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "my-group" + }, + "annotations": { + "kompose.service.group": "my-group", + "kompose.service.healthcheck.liveness.tcp_port": "8080", + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.tcp_port": "9090", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "ports": [ + { + "name": "27017", + "port": 27017, + "targetPort": 27017 + } + ], + "selector": { + "io.kompose.service": "my-group" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "mysql", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "my-group" + }, + "annotations": { + "kompose.service.group": "my-group", + "kompose.service.healthcheck.liveness.tcp_port": "8081", + "kompose.service.healthcheck.readiness.interval": "11s", + "kompose.service.healthcheck.readiness.retries": "6", + "kompose.service.healthcheck.readiness.tcp_port": "9091", + "kompose.service.healthcheck.readiness.timeout": "2s" + } + }, + "spec": { + "ports": [ + { + "name": "3306", + "port": 3306, + "targetPort": 3306 + } + ], + "selector": { + "io.kompose.service": "my-group" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "postgresql", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "postgresql" + }, + "annotations": { + "kompose.service.healthcheck.liveness.http_get_path": "/health", + "kompose.service.healthcheck.liveness.http_get_port": "8080", + "kompose.service.healthcheck.readiness.http_get_path": "/ready", + "kompose.service.healthcheck.readiness.http_get_port": "8080", + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "ports": [ + { + "name": "5432", + "port": 5432, + "targetPort": 5432 + } + ], + "selector": { + "io.kompose.service": "postgresql" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "redis", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis" + }, + "annotations": { + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.test": "echo \"liveness\"", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "ports": [ + { + "name": "6379", + "port": 6379, + "targetPort": 6379 + } + ], + "selector": { + "io.kompose.service": "redis" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Deployment", + "apiVersion": "apps/v1", + "metadata": { + "name": "my-group", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "my-group" + }, + "annotations": { + "kompose.service.group": "my-group", + "kompose.service.healthcheck.liveness.tcp_port": "8081", + "kompose.service.healthcheck.readiness.interval": "11s", + "kompose.service.healthcheck.readiness.retries": "6", + "kompose.service.healthcheck.readiness.tcp_port": "9091", + "kompose.service.healthcheck.readiness.timeout": "2s" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "io.kompose.service": "my-group" + } + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "my-group" + }, + "annotations": { + "kompose.service.group": "my-group", + "kompose.service.healthcheck.liveness.tcp_port": "8080", + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.tcp_port": "9090", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "containers": [ + { + "name": "mongo", + "image": "mongo", + "ports": [ + { + "containerPort": 27017 + } + ], + "resources": {}, + "livenessProbe": { + "tcpSocket": { + "port": 8080 + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + }, + "readinessProbe": { + "tcpSocket": { + "port": 9090 + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + } + }, + { + "name": "mysql", + "image": "mysql", + "ports": [ + { + "containerPort": 3306 + } + ], + "resources": {}, + "livenessProbe": { + "tcpSocket": { + "port": 8081 + }, + "timeoutSeconds": 2, + "periodSeconds": 11, + "failureThreshold": 6 + }, + "readinessProbe": { + "tcpSocket": { + "port": 9091 + }, + "timeoutSeconds": 2, + "periodSeconds": 11, + "failureThreshold": 6 + } + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + }, + { + "kind": "Deployment", + "apiVersion": "apps/v1", + "metadata": { + "name": "postgresql", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "postgresql" + }, + "annotations": { + "kompose.service.healthcheck.liveness.http_get_path": "/health", + "kompose.service.healthcheck.liveness.http_get_port": "8080", + "kompose.service.healthcheck.readiness.http_get_path": "/ready", + "kompose.service.healthcheck.readiness.http_get_port": "8080", + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "io.kompose.service": "postgresql" + } + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "postgresql" + }, + "annotations": { + "kompose.service.healthcheck.liveness.http_get_path": "/health", + "kompose.service.healthcheck.liveness.http_get_port": "8080", + "kompose.service.healthcheck.readiness.http_get_path": "/ready", + "kompose.service.healthcheck.readiness.http_get_port": "8080", + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "containers": [ + { + "name": "postgresql", + "image": "postgresql", + "ports": [ + { + "containerPort": 5432 + } + ], + "resources": {}, + "livenessProbe": { + "httpGet": { + "path": "/health", + "port": 8080 + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + }, + "readinessProbe": { + "httpGet": { + "path": "/ready", + "port": 8080 + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + } + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + }, + { + "kind": "Deployment", + "apiVersion": "apps/v1", + "metadata": { + "name": "redis", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis" + }, + "annotations": { + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.test": "echo \"liveness\"", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "io.kompose.service": "redis" + } + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis" + }, + "annotations": { + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.test": "echo \"liveness\"", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "containers": [ + { + "name": "redis", + "image": "redis", + "ports": [ + { + "containerPort": 6379 + } + ], + "resources": {}, + "livenessProbe": { + "exec": { + "command": [ + "echo \"liveness\"" + ] + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + }, + "readinessProbe": { + "exec": { + "command": [ + "echo", + "liveness" + ] + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + } + } + ], + "restartPolicy": "Always" + } + }, + "strategy": {} + }, + "status": {} + } + ] +} \ No newline at end of file diff --git a/script/test/fixtures/healthcheck/output-healthcheck-os.json b/script/test/fixtures/healthcheck/output-healthcheck-os.json new file mode 100644 index 00000000..bc733936 --- /dev/null +++ b/script/test/fixtures/healthcheck/output-healthcheck-os.json @@ -0,0 +1,660 @@ +{ + "kind": "List", + "apiVersion": "v1", + "metadata": {}, + "items": [ + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "mongo", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mongo" + }, + "annotations": { + "kompose.service.group": "my-group", + "kompose.service.healthcheck.liveness.tcp_port": "8080", + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.tcp_port": "9090", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "ports": [ + { + "name": "27017", + "port": 27017, + "targetPort": 27017 + } + ], + "selector": { + "io.kompose.service": "mongo" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "mysql", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mysql" + }, + "annotations": { + "kompose.service.group": "my-group", + "kompose.service.healthcheck.liveness.tcp_port": "8081", + "kompose.service.healthcheck.readiness.interval": "11s", + "kompose.service.healthcheck.readiness.retries": "6", + "kompose.service.healthcheck.readiness.tcp_port": "9091", + "kompose.service.healthcheck.readiness.timeout": "2s" + } + }, + "spec": { + "ports": [ + { + "name": "3306", + "port": 3306, + "targetPort": 3306 + } + ], + "selector": { + "io.kompose.service": "mysql" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "postgresql", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "postgresql" + }, + "annotations": { + "kompose.service.healthcheck.liveness.http_get_path": "/health", + "kompose.service.healthcheck.liveness.http_get_port": "8080", + "kompose.service.healthcheck.readiness.http_get_path": "/ready", + "kompose.service.healthcheck.readiness.http_get_port": "8080", + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "ports": [ + { + "name": "5432", + "port": 5432, + "targetPort": 5432 + } + ], + "selector": { + "io.kompose.service": "postgresql" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "Service", + "apiVersion": "v1", + "metadata": { + "name": "redis", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis" + }, + "annotations": { + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.test": "echo \"liveness\"", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "ports": [ + { + "name": "6379", + "port": 6379, + "targetPort": 6379 + } + ], + "selector": { + "io.kompose.service": "redis" + } + }, + "status": { + "loadBalancer": {} + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "mongo", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mongo" + }, + "annotations": { + "kompose.service.group": "my-group", + "kompose.service.healthcheck.liveness.tcp_port": "8080", + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.tcp_port": "9090", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "strategy": { + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "mongo" + ], + "from": { + "kind": "ImageStreamTag", + "name": "mongo:latest" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "mongo" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mongo" + } + }, + "spec": { + "containers": [ + { + "name": "mongo", + "image": " ", + "ports": [ + { + "containerPort": 27017 + } + ], + "resources": {}, + "livenessProbe": { + "tcpSocket": { + "port": 8080 + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + }, + "readinessProbe": { + "tcpSocket": { + "port": 9090 + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + } + } + ], + "restartPolicy": "Always" + } + } + }, + "status": { + "latestVersion": 0, + "observedGeneration": 0, + "replicas": 0, + "updatedReplicas": 0, + "availableReplicas": 0, + "unavailableReplicas": 0 + } + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "mongo", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mongo" + } + }, + "spec": { + "lookupPolicy": { + "local": false + }, + "tags": [ + { + "name": "", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "mongo" + }, + "generation": null, + "importPolicy": {}, + "referencePolicy": { + "type": "" + } + } + ] + }, + "status": { + "dockerImageRepository": "" + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "mysql", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mysql" + }, + "annotations": { + "kompose.service.group": "my-group", + "kompose.service.healthcheck.liveness.tcp_port": "8081", + "kompose.service.healthcheck.readiness.interval": "11s", + "kompose.service.healthcheck.readiness.retries": "6", + "kompose.service.healthcheck.readiness.tcp_port": "9091", + "kompose.service.healthcheck.readiness.timeout": "2s" + } + }, + "spec": { + "strategy": { + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "mysql" + ], + "from": { + "kind": "ImageStreamTag", + "name": "mysql:latest" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "mysql" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mysql" + } + }, + "spec": { + "containers": [ + { + "name": "mysql", + "image": " ", + "ports": [ + { + "containerPort": 3306 + } + ], + "resources": {}, + "livenessProbe": { + "tcpSocket": { + "port": 8081 + }, + "timeoutSeconds": 2, + "periodSeconds": 11, + "failureThreshold": 6 + }, + "readinessProbe": { + "tcpSocket": { + "port": 9091 + }, + "timeoutSeconds": 2, + "periodSeconds": 11, + "failureThreshold": 6 + } + } + ], + "restartPolicy": "Always" + } + } + }, + "status": { + "latestVersion": 0, + "observedGeneration": 0, + "replicas": 0, + "updatedReplicas": 0, + "availableReplicas": 0, + "unavailableReplicas": 0 + } + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "mysql", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "mysql" + } + }, + "spec": { + "lookupPolicy": { + "local": false + }, + "tags": [ + { + "name": "", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "mysql" + }, + "generation": null, + "importPolicy": {}, + "referencePolicy": { + "type": "" + } + } + ] + }, + "status": { + "dockerImageRepository": "" + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "postgresql", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "postgresql" + }, + "annotations": { + "kompose.service.healthcheck.liveness.http_get_path": "/health", + "kompose.service.healthcheck.liveness.http_get_port": "8080", + "kompose.service.healthcheck.readiness.http_get_path": "/ready", + "kompose.service.healthcheck.readiness.http_get_port": "8080", + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "strategy": { + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "postgresql" + ], + "from": { + "kind": "ImageStreamTag", + "name": "postgresql:latest" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "postgresql" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "postgresql" + } + }, + "spec": { + "containers": [ + { + "name": "postgresql", + "image": " ", + "ports": [ + { + "containerPort": 5432 + } + ], + "resources": {}, + "livenessProbe": { + "httpGet": { + "path": "/health", + "port": 8080 + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + }, + "readinessProbe": { + "httpGet": { + "path": "/ready", + "port": 8080 + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + } + } + ], + "restartPolicy": "Always" + } + } + }, + "status": { + "latestVersion": 0, + "observedGeneration": 0, + "replicas": 0, + "updatedReplicas": 0, + "availableReplicas": 0, + "unavailableReplicas": 0 + } + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "postgresql", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "postgresql" + } + }, + "spec": { + "lookupPolicy": { + "local": false + }, + "tags": [ + { + "name": "", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "postgresql" + }, + "generation": null, + "importPolicy": {}, + "referencePolicy": { + "type": "" + } + } + ] + }, + "status": { + "dockerImageRepository": "" + } + }, + { + "kind": "DeploymentConfig", + "apiVersion": "v1", + "metadata": { + "name": "redis", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis" + }, + "annotations": { + "kompose.service.healthcheck.readiness.interval": "10s", + "kompose.service.healthcheck.readiness.retries": "5", + "kompose.service.healthcheck.readiness.test": "echo \"liveness\"", + "kompose.service.healthcheck.readiness.timeout": "1s" + } + }, + "spec": { + "strategy": { + "resources": {} + }, + "triggers": [ + { + "type": "ConfigChange" + }, + { + "type": "ImageChange", + "imageChangeParams": { + "automatic": true, + "containerNames": [ + "redis" + ], + "from": { + "kind": "ImageStreamTag", + "name": "redis:latest" + } + } + } + ], + "replicas": 1, + "test": false, + "selector": { + "io.kompose.service": "redis" + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis" + } + }, + "spec": { + "containers": [ + { + "name": "redis", + "image": " ", + "ports": [ + { + "containerPort": 6379 + } + ], + "resources": {}, + "livenessProbe": { + "exec": { + "command": [ + "echo \"liveness\"" + ] + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + }, + "readinessProbe": { + "exec": { + "command": [ + "echo", + "liveness" + ] + }, + "timeoutSeconds": 1, + "periodSeconds": 10, + "failureThreshold": 5 + } + } + ], + "restartPolicy": "Always" + } + } + }, + "status": { + "latestVersion": 0, + "observedGeneration": 0, + "replicas": 0, + "updatedReplicas": 0, + "availableReplicas": 0, + "unavailableReplicas": 0 + } + }, + { + "kind": "ImageStream", + "apiVersion": "v1", + "metadata": { + "name": "redis", + "creationTimestamp": null, + "labels": { + "io.kompose.service": "redis" + } + }, + "spec": { + "lookupPolicy": { + "local": false + }, + "tags": [ + { + "name": "", + "annotations": null, + "from": { + "kind": "DockerImage", + "name": "redis" + }, + "generation": null, + "importPolicy": {}, + "referencePolicy": { + "type": "" + } + } + ] + }, + "status": { + "dockerImageRepository": "" + } + } + ] +} \ No newline at end of file