Merge pull request #759 from cdrage/add-healthcheck

Adds healthcheck
This commit is contained in:
Shubham 2017-08-27 09:09:54 +05:30 committed by GitHub
commit 4f6b92f6c8
11 changed files with 391 additions and 3 deletions

View File

@ -49,7 +49,7 @@ __Glossary:__
| external_links | X | X | X | | Kubernetes uses a flat-structure for all containers and thus external_links does not have a 1-1 conversion |
| extra_hosts | N | N | N | | |
| group_add | ✓ | ✓ | ✓ | | |
| healthcheck | - | N | N | | |
| healthcheck | - | N | | | |
| image | ✓ | ✓ | ✓ | Deployment.Spec.Containers.Image | |
| isolation | X | X | X | | Not applicable as this applies to Windows with HyperV support |
| labels | ✓ | ✓ | ✓ | Metadata.Annotations | |

View File

@ -99,8 +99,20 @@ type ServiceConfig struct {
Dockerfile string `compose:"dockerfile"`
Replicas int `compose:"replicas"`
GroupAdd []int64 `compose:"group_add"`
// Volumes is a struct which contains all information about each volume
Volumes []Volumes `compose:""`
Volumes []Volumes `compose:""`
HealthChecks HealthCheck `compose:""`
}
// HealthCheck the healthcheck configuration for a service
// "StartPeriod" is not yet added to compose, see:
// https://github.com/docker/cli/issues/116
type HealthCheck struct {
Test []string
Timeout int32
Interval int32
Retries int32
StartPeriod int32
Disable bool
}
// EnvVar holds the environment variable struct of a container

View File

@ -32,6 +32,34 @@ import (
"github.com/pkg/errors"
)
func TestParseHealthCheck(t *testing.T) {
helperValue := uint64(2)
check := types.HealthCheckConfig{
Test: []string{"CMD-SHELL", "echo", "foobar"},
Timeout: "1s",
Interval: "2s",
Retries: &helperValue,
StartPeriod: "3s",
}
// 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)
if err != nil {
t.Errorf("Unable to convert HealthCheckConfig: %s", err)
}
if !reflect.DeepEqual(output, expected) {
t.Errorf("Structs are not equal, expected: %s, output: %s", expected, output)
}
}
func TestLoadV3Volumes(t *testing.T) {
vol := types.ServiceVolumeConfig{
Type: "volume",
@ -66,6 +94,7 @@ func TestLoadV3Ports(t *testing.T) {
if output[0] != expected {
t.Errorf("Expected %v, got %v", expected, output[0])
}
}
// Test if service types are parsed properly on user input

View File

@ -20,6 +20,7 @@ import (
"io/ioutil"
"strconv"
"strings"
"time"
libcomposeyaml "github.com/docker/libcompose/yaml"
@ -162,6 +163,52 @@ func loadV3Ports(ports []types.ServicePortConfig) []kobject.Ports {
return komposePorts
}
/* Convert the HealthCheckConfig as designed by Docker to
a Kubernetes-compatible format.
*/
func parseHealthCheck(composeHealthCheck types.HealthCheckConfig) (kobject.HealthCheck, error) {
var timeout, interval, retries, startPeriod int32
// Here we convert the timeout from 1h30s (example) to 36030 seconds.
if composeHealthCheck.Timeout != "" {
parse, err := time.ParseDuration(composeHealthCheck.Timeout)
if err != nil {
return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check timeout variable")
}
timeout = int32(parse.Seconds())
}
if composeHealthCheck.Interval != "" {
parse, err := time.ParseDuration(composeHealthCheck.Interval)
if err != nil {
return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check interval variable")
}
interval = int32(parse.Seconds())
}
if *composeHealthCheck.Retries != 0 {
retries = int32(*composeHealthCheck.Retries)
}
if composeHealthCheck.StartPeriod != "" {
parse, err := time.ParseDuration(composeHealthCheck.StartPeriod)
if err != nil {
return kobject.HealthCheck{}, errors.Wrap(err, "unable to parse health check startPeriod variable")
}
startPeriod = int32(parse.Seconds())
}
// 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:],
Timeout: timeout,
Interval: interval,
Retries: retries,
StartPeriod: startPeriod,
}, nil
}
func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.KomposeObject, error) {
// Step 1. Initialize what's going to be returned
@ -198,8 +245,18 @@ func dockerComposeToKomposeMapping(composeObject *types.Config) (kobject.Kompose
// Deploy keys
//
// mode:
serviceConfig.DeployMode = composeServiceConfig.Deploy.Mode
// HealthCheck
if composeServiceConfig.HealthCheck != nil && !composeServiceConfig.HealthCheck.Disable {
var err error
serviceConfig.HealthChecks, err = parseHealthCheck(*composeServiceConfig.HealthCheck)
if err != nil {
return kobject.KomposeObject{}, errors.Wrap(err, "Unable to parse health check")
}
}
if (composeServiceConfig.Deploy.Resources != types.Resources{}) {
// memory:

View File

@ -381,6 +381,33 @@ func (k *Kubernetes) UpdateKubernetesObjects(name string, service kobject.Servic
template.Spec.Containers[0].TTY = service.Tty
template.Spec.Volumes = volumes
// Configure the HealthCheck
// We check to see if it's blank
if !reflect.DeepEqual(service.HealthChecks, kobject.HealthCheck{}) {
probe := api.Probe{}
if len(service.HealthChecks.Test) > 0 {
probe.Handler = api.Handler{
Exec: &api.ExecAction{
Command: service.HealthChecks.Test,
},
}
} else {
return errors.New("Health check must contain a command")
}
probe.TimeoutSeconds = service.HealthChecks.Timeout
probe.PeriodSeconds = service.HealthChecks.Interval
probe.FailureThreshold = service.HealthChecks.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.StartPeriod
template.Spec.Containers[0].LivenessProbe = &probe
}
if service.StopGracePeriod != "" {
template.Spec.TerminationGracePeriodSeconds, err = DurationStrToSecondsInt(service.StopGracePeriod)
if err != nil {

View File

@ -392,6 +392,15 @@ convert::expect_success "kompose --provider=openshift convert --stdout -j" "/tmp
# Return back to the original path
cd $CURRENT_DIR
# Test HealthCheck
cmd="kompose convert --stdout -j -f $KOMPOSE_ROOT/script/test/fixtures/healthcheck/docker-compose.yaml"
sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" "$KOMPOSE_ROOT/script/test/fixtures/healthcheck/output-k8s-template.json" > /tmp/output-k8s.json
convert::expect_success "kompose convert --stdout -j -f $KOMPOSE_ROOT/script/test/fixtures/healthcheck/docker-compose.yaml" "/tmp/output-k8s.json"
cmd="kompose convert --stdout -j --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/healthcheck/docker-compose.yaml"
sed -e "s;%VERSION%;$version;g" -e "s;%CMD%;$cmd;g" "$KOMPOSE_ROOT/script/test/fixtures/healthcheck/output-os-template.json" > /tmp/output-os.json
convert::expect_success "kompose convert --stdout -j --provider=openshift -f $KOMPOSE_ROOT/script/test/fixtures/healthcheck/docker-compose.yaml" "/tmp/output-os.json"
# Test V3 Support of Docker Compose
# Test deploy mode: global

View File

@ -0,0 +1,10 @@
version: "3"
services:
redis:
image: redis
healthcheck:
test: echo "hello world"
interval: 10s
timeout: 1s
retries: 5

View File

@ -0,0 +1,86 @@
{
"kind": "List",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "redis",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "redis"
},
"annotations": {
"kompose.cmd": "%CMD%",
"kompose.version": "%VERSION%"
}
},
"spec": {
"ports": [
{
"name": "headless",
"port": 55555,
"targetPort": 0
}
],
"selector": {
"io.kompose.service": "redis"
},
"clusterIP": "None"
},
"status": {
"loadBalancer": {}
}
},
{
"kind": "Deployment",
"apiVersion": "extensions/v1beta1",
"metadata": {
"name": "redis",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "redis"
},
"annotations": {
"kompose.cmd": "%CMD%",
"kompose.version": "%VERSION%"
}
},
"spec": {
"replicas": 1,
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"io.kompose.service": "redis"
}
},
"spec": {
"containers": [
{
"name": "redis",
"image": "redis",
"resources": {},
"livenessProbe": {
"exec": {
"command": [
"echo \"hello world\""
]
},
"timeoutSeconds": 1,
"periodSeconds": 10,
"failureThreshold": 5
}
}
],
"restartPolicy": "Always"
}
},
"strategy": {}
},
"status": {}
}
]
}

View File

@ -0,0 +1,138 @@
{
"kind": "List",
"apiVersion": "v1",
"metadata": {},
"items": [
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "redis",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "redis"
},
"annotations": {
"kompose.cmd": "%CMD%",
"kompose.version": "%VERSION%"
}
},
"spec": {
"ports": [
{
"name": "headless",
"port": 55555,
"targetPort": 0
}
],
"selector": {
"io.kompose.service": "redis"
},
"clusterIP": "None"
},
"status": {
"loadBalancer": {}
}
},
{
"kind": "DeploymentConfig",
"apiVersion": "v1",
"metadata": {
"name": "redis",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "redis"
},
"annotations": {
"kompose.cmd": "%CMD%",
"kompose.version": "%VERSION%"
}
},
"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": " ",
"resources": {},
"livenessProbe": {
"exec": {
"command": [
"echo \"hello world\""
]
},
"timeoutSeconds": 1,
"periodSeconds": 10,
"failureThreshold": 5
}
}
],
"restartPolicy": "Always"
}
}
},
"status": {}
},
{
"kind": "ImageStream",
"apiVersion": "v1",
"metadata": {
"name": "redis",
"creationTimestamp": null,
"labels": {
"io.kompose.service": "redis"
}
},
"spec": {
"tags": [
{
"name": "latest",
"annotations": null,
"from": {
"kind": "DockerImage",
"name": "redis"
},
"generation": null,
"importPolicy": {}
}
]
},
"status": {
"dockerImageRepository": ""
}
}
]
}

View File

@ -197,6 +197,16 @@
"mountPath": "/tmp"
}
],
"livenessProbe": {
"exec": {
"command": [
"echo \"hello world\""
]
},
"timeoutSeconds": 1,
"periodSeconds": 10,
"failureThreshold": 5
},
"securityContext": {
"capabilities": {
"add": [

View File

@ -197,6 +197,16 @@
"mountPath": "/tmp"
}
],
"livenessProbe": {
"exec": {
"command": [
"echo \"hello world\""
]
},
"timeoutSeconds": 1,
"periodSeconds": 10,
"failureThreshold": 5
},
"securityContext": {
"capabilities": {
"add": [