From 6d2f4a0813fd66064a098c12ec35c04dfb2afd0a Mon Sep 17 00:00:00 2001 From: Tait Clarridge Date: Tue, 21 Nov 2017 05:06:03 -0500 Subject: [PATCH] Add health check label to ECS --- autogen/gentemplates/gen.go | 42 ++++++++++----------- docs/configuration/backends/ecs.md | 2 + healthcheck/healthcheck.go | 12 +++--- provider/ecs/ecs.go | 15 ++++++++ provider/ecs/ecs_test.go | 59 ++++++++++++++++++++++++++++++ server/server.go | 2 + templates/ecs.tmpl | 5 +++ 7 files changed, 111 insertions(+), 26 deletions(-) diff --git a/autogen/gentemplates/gen.go b/autogen/gentemplates/gen.go index 940fec7db..db1cc12b1 100644 --- a/autogen/gentemplates/gen.go +++ b/autogen/gentemplates/gen.go @@ -117,7 +117,7 @@ func templatesDockerTmpl() (*asset, error) { return a, nil } -var _templatesEcsTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x53\x4d\x6f\xdb\x30\x0c\xbd\xe7\x57\x10\x46\x8e\xad\x7a\x2f\x90\xc3\xba\x0f\x74\x40\x37\x04\xeb\x61\x87\xa0\x07\xc5\x66\x6c\x21\xae\x54\x88\xf4\xb6\x40\xd0\x7f\x1f\x64\x49\x96\xbd\x64\x40\xb6\x93\x25\xf2\x3d\x8a\xef\x91\xde\xed\x65\x7d\x44\xdd\xd0\x8b\x73\x56\xea\x16\x61\x4d\x68\x7f\xa8\x1a\xbf\xca\x57\xbc\x81\xb5\xd2\xc4\x52\xd7\x48\x70\xbf\x01\xf1\x1c\x73\xe4\xfd\x0a\x60\xe2\x8a\x74\xb8\x75\x6e\x41\x07\xef\x45\x6f\x64\xb3\x97\x7d\x28\x61\x5f\x56\x00\x00\xaf\xc8\x9d\x69\x60\x03\x95\x73\xd0\x22\x3f\x19\xd9\x3c\x24\xc4\x97\x98\x2b\xaf\x7a\x5f\x8d\x24\x62\x55\x1f\x4f\xb0\x81\x73\xce\x73\x4c\xcd\x39\x23\xc5\x39\x75\x80\x4e\xd2\x98\x57\x1a\x89\x9e\xe4\x1e\xfb\x05\x10\x46\xe4\x3f\x0b\x11\x34\xd5\x8c\x9a\x00\x6a\x63\x8e\x2a\x82\x47\x65\x2d\x72\x79\xf8\x7d\x49\x9e\x4b\x73\x0e\x75\xe3\xfd\x6a\x15\x8e\x69\x06\x4a\x37\xf8\x2b\xb8\x1f\x5c\x3f\x53\x76\xb9\x5f\x25\x72\xab\xa1\x73\xb4\x94\xbe\xcb\x64\xbc\x7c\xfe\x00\xde\xe7\xd6\x07\xdb\x97\x69\x6c\xad\x61\x53\x9b\x60\x13\x78\x7f\x7f\x77\x17\xc3\x8f\x86\x38\x85\x12\xce\xd8\x14\xa8\x52\x99\x9f\xa8\xda\x8e\xa7\x11\x7d\x8f\xd7\xb5\x1a\x9b\xce\x22\x27\xb1\xbb\x83\x35\x9a\xff\x73\xf3\x32\xe3\xa0\x7a\x46\xfb\x29\x57\xba\xe0\xd4\xf4\x8a\xc8\xa7\x0b\xb3\xcd\x46\x24\x37\x83\x19\x7f\x5f\x84\x2c\xf7\x4d\x12\x05\x57\x1e\x51\x36\x68\x27\xd9\xdb\x65\x58\xa4\x46\x00\xde\xac\x32\x56\x71\xd9\xe1\x6d\x0e\x14\x0c\x6a\xb6\xa7\xad\x51\x9a\x09\x36\xb0\xcb\x32\x03\xfa\xe3\x2c\x55\x08\x95\x73\xc2\xfb\xea\x66\xbe\x48\x45\x0d\xa9\xfa\xdd\xc0\xdd\xbc\x54\x8b\xfc\x30\xc5\xaf\xa9\x73\x9d\x83\xc2\x9a\x81\x91\xe2\xe7\xf6\x0a\xab\xed\xd0\x97\x1f\x25\x0f\xf0\x5b\x08\x8a\x68\xf1\x1f\x0b\xf3\x3b\x00\x00\xff\xff\x86\x8c\x3e\xf5\xa8\x04\x00\x00") +var _templatesEcsTmpl = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x9c\x54\x4d\x6f\xdb\x30\x0c\xbd\xe7\x57\x10\x46\x8e\xad\x7a\x2f\x90\xc3\xda\x6d\x48\x81\x6e\x30\xd6\xc3\x0e\x41\x0f\x8a\xcd\xd8\x42\x5c\xa9\x90\x98\x6e\x85\xa0\xff\x3e\xc8\xfa\xb2\x17\x0f\xc8\x7a\xb2\x4c\xf2\x91\xef\x51\xcf\xde\xed\x79\x73\x44\xd9\x9a\x67\x6b\x35\x97\x1d\xc2\xda\xa0\x7e\x13\x0d\x7e\xe7\x2f\x78\x05\x6b\x21\x0d\x71\xd9\xa0\x81\xdb\x0d\xb0\xa7\x90\x33\xce\xad\x00\x32\x96\xc5\xc3\xb5\xb5\x33\x38\x38\xc7\x06\xc5\xdb\x3d\x1f\x7c\x0b\xfd\xbc\x02\x00\x78\x41\xea\x55\x0b\x1b\xa8\xac\x85\x0e\xe9\x51\xf1\xf6\x2e\x56\x7c\x0b\xb9\x32\xd5\xb9\x6a\x04\x19\x12\xcd\xf1\x1d\x36\x70\x8e\x79\x0a\xa9\x29\x66\x84\x58\x2b\x0e\xd0\x73\x33\xe6\x85\x44\x63\x1e\xf9\x1e\x87\x59\x21\x8c\x95\xff\x2d\x84\x99\xdc\x33\x68\x02\x68\x94\x3a\x8a\x50\x3c\x2a\xeb\x90\xca\xe0\xfb\x92\x3c\x97\x66\x2d\xca\x36\x73\x86\x40\x7a\x8b\x7c\xa0\xfe\xbe\xc7\xe6\x38\xb2\x36\xd3\x9b\x88\xc5\x17\xd1\xee\xc7\x46\x8d\x6f\x94\xa8\xbe\x72\xea\x33\xc9\xc9\xa0\xda\xc7\x67\x63\xaa\x88\x10\x92\x50\xbf\xf1\x61\x09\xf5\x90\x72\x0b\xc8\x24\x6d\xe5\x8f\xd1\x5e\x42\xb6\xf8\xdb\x1b\xcb\x1b\xea\xec\xd2\x96\x35\x09\x96\xe4\x78\x75\xa8\x4d\x7c\xce\x93\xe1\xe5\xe1\x33\x38\x97\xa4\x9e\xf4\x50\x8c\x56\x6b\x45\xaa\x51\x9e\x29\x38\x77\x7b\x73\x13\xc2\x5b\x65\x28\x86\x62\x9d\xd2\x31\x90\xf4\xff\x42\xd1\xf5\x94\xdd\xf7\x33\xbc\xae\xc5\x48\x3a\x89\xcc\x62\x77\x07\xad\x24\x7d\xf0\xa3\x4a\x88\x83\x18\x08\xf5\xd7\xd4\x69\x61\x53\x79\x0a\x4b\xa7\x85\xfb\x4f\x8b\x88\xdb\xf4\xcb\xf8\xb7\x59\xaa\x6c\x10\x63\xfc\x56\xb6\xc8\x5b\xd4\x59\x76\x3d\x0f\xb3\x48\x04\xe0\x55\x0b\xa5\x05\x95\xcf\xb3\x4e\x81\x52\x83\x92\xf4\x7b\xad\x84\x24\x03\x1b\xd8\x25\x99\xbe\xfa\xcb\x24\x55\x00\x95\xb5\xcc\xb9\xea\x6a\x6a\xa4\xa2\xc6\x88\xe6\xd3\x69\xb4\x71\x6e\xd5\x21\xdd\xe5\xf8\x25\x7d\x2e\xdb\x20\xd3\xea\x44\x68\xc2\xe3\xfa\x82\x55\xeb\xd3\x50\xfe\x01\xe9\x02\x7f\xf8\x20\x0b\x2b\xfe\xcb\x30\x7f\x02\x00\x00\xff\xff\xca\x00\x13\x83\x83\x05\x00\x00") func templatesEcsTmplBytes() ([]byte, error) { return bindataRead( @@ -132,7 +132,7 @@ func templatesEcsTmpl() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "templates/ecs.tmpl", size: 1192, mode: os.FileMode(436), modTime: time.Unix(1509884496, 0)} + info := bindataFileInfo{name: "templates/ecs.tmpl", size: 1411, mode: os.FileMode(436), modTime: time.Unix(1509884496, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -330,15 +330,15 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ "templates/consul_catalog.tmpl": templatesConsul_catalogTmpl, - "templates/docker.tmpl": templatesDockerTmpl, - "templates/ecs.tmpl": templatesEcsTmpl, - "templates/eureka.tmpl": templatesEurekaTmpl, - "templates/kubernetes.tmpl": templatesKubernetesTmpl, - "templates/kv.tmpl": templatesKvTmpl, - "templates/marathon.tmpl": templatesMarathonTmpl, - "templates/mesos.tmpl": templatesMesosTmpl, - "templates/notFound.tmpl": templatesNotfoundTmpl, - "templates/rancher.tmpl": templatesRancherTmpl, + "templates/docker.tmpl": templatesDockerTmpl, + "templates/ecs.tmpl": templatesEcsTmpl, + "templates/eureka.tmpl": templatesEurekaTmpl, + "templates/kubernetes.tmpl": templatesKubernetesTmpl, + "templates/kv.tmpl": templatesKvTmpl, + "templates/marathon.tmpl": templatesMarathonTmpl, + "templates/mesos.tmpl": templatesMesosTmpl, + "templates/notFound.tmpl": templatesNotfoundTmpl, + "templates/rancher.tmpl": templatesRancherTmpl, } // AssetDir returns the file names below a certain @@ -380,19 +380,18 @@ type bintree struct { Func func() (*asset, error) Children map[string]*bintree } - var _bintree = &bintree{nil, map[string]*bintree{ "templates": &bintree{nil, map[string]*bintree{ "consul_catalog.tmpl": &bintree{templatesConsul_catalogTmpl, map[string]*bintree{}}, - "docker.tmpl": &bintree{templatesDockerTmpl, map[string]*bintree{}}, - "ecs.tmpl": &bintree{templatesEcsTmpl, map[string]*bintree{}}, - "eureka.tmpl": &bintree{templatesEurekaTmpl, map[string]*bintree{}}, - "kubernetes.tmpl": &bintree{templatesKubernetesTmpl, map[string]*bintree{}}, - "kv.tmpl": &bintree{templatesKvTmpl, map[string]*bintree{}}, - "marathon.tmpl": &bintree{templatesMarathonTmpl, map[string]*bintree{}}, - "mesos.tmpl": &bintree{templatesMesosTmpl, map[string]*bintree{}}, - "notFound.tmpl": &bintree{templatesNotfoundTmpl, map[string]*bintree{}}, - "rancher.tmpl": &bintree{templatesRancherTmpl, map[string]*bintree{}}, + "docker.tmpl": &bintree{templatesDockerTmpl, map[string]*bintree{}}, + "ecs.tmpl": &bintree{templatesEcsTmpl, map[string]*bintree{}}, + "eureka.tmpl": &bintree{templatesEurekaTmpl, map[string]*bintree{}}, + "kubernetes.tmpl": &bintree{templatesKubernetesTmpl, map[string]*bintree{}}, + "kv.tmpl": &bintree{templatesKvTmpl, map[string]*bintree{}}, + "marathon.tmpl": &bintree{templatesMarathonTmpl, map[string]*bintree{}}, + "mesos.tmpl": &bintree{templatesMesosTmpl, map[string]*bintree{}}, + "notFound.tmpl": &bintree{templatesNotfoundTmpl, map[string]*bintree{}}, + "rancher.tmpl": &bintree{templatesRancherTmpl, map[string]*bintree{}}, }}, }} @@ -442,3 +441,4 @@ func _filePath(dir, name string) string { cannonicalName := strings.Replace(name, "\\", "/", -1) return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) } + diff --git a/docs/configuration/backends/ecs.md b/docs/configuration/backends/ecs.md index 857e58572..ab38416fe 100644 --- a/docs/configuration/backends/ecs.md +++ b/docs/configuration/backends/ecs.md @@ -134,6 +134,8 @@ Labels can be used on task containers to override default behaviour: | `traefik.backend.loadbalancer.stickiness=true` | enable backend sticky sessions | | `traefik.backend.loadbalancer.stickiness.cookieName=NAME` | Manually set the cookie name for sticky sessions | | `traefik.backend.loadbalancer.sticky=true` | enable backend sticky sessions (DEPRECATED) | +| `traefik.backend.healthcheck.path=/health` | enable health checks for the backend, hitting the container at `path` | +| `traefik.backend.healthcheck.interval=1s` | configure the health check interval | | `traefik.frontend.rule=Host:test.traefik.io` | override the default frontend rule (Default: `Host:{containerName}.{domain}`). | | `traefik.frontend.passHostHeader=true` | forward client `Host` header to the backend. | | `traefik.frontend.priority=10` | override default frontend priority | diff --git a/healthcheck/healthcheck.go b/healthcheck/healthcheck.go index d0dcadd75..b3c6ace09 100644 --- a/healthcheck/healthcheck.go +++ b/healthcheck/healthcheck.go @@ -28,10 +28,11 @@ func GetHealthCheck() *HealthCheck { // Options are the public health check options. type Options struct { - Path string - Port int - Interval time.Duration - LB LoadBalancer + Path string + Port int + Transport http.RoundTripper + Interval time.Duration + LB LoadBalancer } func (opt Options) String() string { @@ -146,7 +147,8 @@ func (backend *BackendHealthCheck) newRequest(serverURL *url.URL) (*http.Request func checkHealth(serverURL *url.URL, backend *BackendHealthCheck) bool { client := http.Client{ - Timeout: backend.requestTimeout, + Timeout: backend.requestTimeout, + Transport: backend.Options.Transport, } req, err := backend.newRequest(serverURL) if err != nil { diff --git a/provider/ecs/ecs.go b/provider/ecs/ecs.go index c3756e8e6..5f5e47174 100644 --- a/provider/ecs/ecs.go +++ b/provider/ecs/ecs.go @@ -195,6 +195,9 @@ func (p *Provider) generateECSConfig(services map[string][]ecsInstance) (*types. "getPassHostHeader": p.getPassHostHeader, "getPriority": p.getPriority, "getEntryPoints": p.getEntryPoints, + "hasHealthCheckLabels": p.hasHealthCheckLabels, + "getHealthCheckPath": p.getHealthCheckPath, + "getHealthCheckInterval": p.getHealthCheckInterval, } return p.GetConfiguration("templates/ecs.tmpl", ecsFuncMap, struct { Services map[string][]ecsInstance @@ -526,6 +529,18 @@ func (p *Provider) getLoadBalancerMethod(instances []ecsInstance) string { return "wrr" } +func (p *Provider) hasHealthCheckLabels(instances []ecsInstance) bool { + return p.getHealthCheckPath(instances) != "" +} + +func (p *Provider) getHealthCheckPath(instances []ecsInstance) string { + return p.getFirstInstanceLabel(instances, types.LabelBackendHealthcheckPath) +} + +func (p *Provider) getHealthCheckInterval(instances []ecsInstance) string { + return p.getFirstInstanceLabel(instances, types.LabelBackendHealthcheckInterval) +} + // Provider expects no more than 100 parameters be passed to a DescribeTask call; thus, pack // each string into an array capped at 100 elements func (p *Provider) chunkedTaskArns(tasks []*string) [][]*string { diff --git a/provider/ecs/ecs_test.go b/provider/ecs/ecs_test.go index 69e60f862..01e3e5619 100644 --- a/provider/ecs/ecs_test.go +++ b/provider/ecs/ecs_test.go @@ -643,6 +643,65 @@ func TestGenerateECSConfig(t *testing.T) { }, }, }, + { + desc: "config parsed successfully with health check labels", + services: map[string][]ecsInstance{ + "testing": { + { + Name: "instance-1", + containerDefinition: &ecs.ContainerDefinition{ + DockerLabels: map[string]*string{ + types.LabelBackendHealthcheckPath: func(s string) *string { return &s }("/health"), + types.LabelBackendHealthcheckInterval: func(s string) *string { return &s }("1s"), + }, + }, + machine: &ec2.Instance{ + PrivateIpAddress: func(s string) *string { return &s }("10.0.0.1"), + }, + container: &ecs.Container{ + NetworkBindings: []*ecs.NetworkBinding{ + { + HostPort: func(i int64) *int64 { return &i }(1337), + }, + }, + }, + }, + }, + }, + exp: &types.Configuration{ + Backends: map[string]*types.Backend{ + "backend-instance-1": { + Servers: map[string]types.Server{ + "server-instance-1": { + URL: "http://10.0.0.1:1337", + }, + }, + }, + "backend-testing": { + LoadBalancer: &types.LoadBalancer{ + Method: "wrr", + }, + HealthCheck: &types.HealthCheck{ + Path: "/health", + Interval: "1s", + }, + }, + }, + Frontends: map[string]*types.Frontend{ + "frontend-testing": { + EntryPoints: []string{}, + Backend: "backend-testing", + Routes: map[string]types.Route{ + "route-frontend-testing": { + Rule: "Host:instance-1.", + }, + }, + PassHostHeader: true, + BasicAuth: []string{}, + }, + }, + }, + }, } for _, test := range tests { diff --git a/server/server.go b/server/server.go index a3281753e..0839e14b7 100644 --- a/server/server.go +++ b/server/server.go @@ -1019,6 +1019,7 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf hcOpts := parseHealthCheckOptions(rebalancer, frontend.Backend, config.Backends[frontend.Backend].HealthCheck, globalConfiguration.HealthCheck) if hcOpts != nil { log.Debugf("Setting up backend health check %s", *hcOpts) + hcOpts.Transport = server.defaultForwardingRoundTripper backendsHealthCheck[entryPointName+frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts) } lb = middlewares.NewEmptyBackendHandler(rebalancer, lb) @@ -1040,6 +1041,7 @@ func (server *Server) loadConfig(configurations types.Configurations, globalConf hcOpts := parseHealthCheckOptions(rr, frontend.Backend, config.Backends[frontend.Backend].HealthCheck, globalConfiguration.HealthCheck) if hcOpts != nil { log.Debugf("Setting up backend health check %s", *hcOpts) + hcOpts.Transport = server.defaultForwardingRoundTripper backendsHealthCheck[entryPointName+frontend.Backend] = healthcheck.NewBackendHealthCheck(*hcOpts) } lb = middlewares.NewEmptyBackendHandler(rr, lb) diff --git a/templates/ecs.tmpl b/templates/ecs.tmpl index a1777470a..b469969ae 100644 --- a/templates/ecs.tmpl +++ b/templates/ecs.tmpl @@ -6,6 +6,11 @@ [backends.backend-{{ $serviceName }}.loadbalancer.stickiness] cookieName = "{{getStickinessCookieName $instances}}" {{end}} + {{ if hasHealthCheckLabels $instances }} + [backends.backend-{{ $serviceName }}.healthcheck] + path = "{{getHealthCheckPath $instances }}" + interval = "{{getHealthCheckInterval $instances }}" + {{end}} {{range $index, $i := $instances}} [backends.backend-{{ $i.Name }}.servers.server-{{ $i.Name }}{{ $i.ID }}]