From 8d3c77a0b979db71f1fe96f4504b366799536d24 Mon Sep 17 00:00:00 2001 From: Luke Petre Date: Thu, 5 Jan 2017 14:24:17 +0000 Subject: [PATCH] Add an ECS provider --- configuration.go | 10 ++ docs/index.md | 2 +- docs/toml.md | 75 ++++++++ glide.lock | 11 +- glide.yaml | 8 + provider/ecs.go | 414 +++++++++++++++++++++++++++++++++++++++++++ provider/ecs_test.go | 223 +++++++++++++++++++++++ server.go | 3 + templates/ecs.tmpl | 17 ++ traefik.sample.toml | 61 +++++++ 10 files changed, 820 insertions(+), 4 deletions(-) create mode 100644 provider/ecs.go create mode 100644 provider/ecs_test.go create mode 100644 templates/ecs.tmpl diff --git a/configuration.go b/configuration.go index 7ae5d71ee..717b73eb4 100644 --- a/configuration.go +++ b/configuration.go @@ -50,6 +50,7 @@ type GlobalConfiguration struct { Kubernetes *provider.Kubernetes `description:"Enable Kubernetes backend"` Mesos *provider.Mesos `description:"Enable Mesos backend"` Eureka *provider.Eureka `description:"Enable Eureka backend"` + ECS *provider.ECS `description:"Enable ECS backend"` } // DefaultEntryPoints holds default entry points @@ -391,6 +392,14 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { defaultMesos.ExposedByDefault = true defaultMesos.Constraints = types.Constraints{} + //default ECS + var defaultECS provider.ECS + defaultECS.Watch = true + defaultECS.ExposedByDefault = true + defaultECS.RefreshSeconds = 15 + defaultECS.Cluster = "default" + defaultECS.Constraints = types.Constraints{} + defaultConfiguration := GlobalConfiguration{ Docker: &defaultDocker, File: &defaultFile, @@ -403,6 +412,7 @@ func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration { Boltdb: &defaultBoltDb, Kubernetes: &defaultKubernetes, Mesos: &defaultMesos, + ECS: &defaultECS, Retry: &Retry{}, } return &TraefikConfiguration{ diff --git a/docs/index.md b/docs/index.md index 180eabc95..96bd2e5e8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,7 +11,7 @@ Træfɪk is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease. -It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), Rest API, file...) to manage its configuration automatically and dynamically. +It supports several backends ([Docker](https://www.docker.com/), [Swarm](https://docs.docker.com/swarm), [Mesos/Marathon](https://mesosphere.github.io/marathon/), [Consul](https://www.consul.io/), [Etcd](https://coreos.com/etcd/), [Zookeeper](https://zookeeper.apache.org), [BoltDB](https://github.com/boltdb/bolt), [Amazon ECS](https://aws.amazon.com/ecs/), Rest API, file...) to manage its configuration automatically and dynamically. ## Overview diff --git a/docs/toml.md b/docs/toml.md index 80d3fb8c8..8e0695dc5 100644 --- a/docs/toml.md +++ b/docs/toml.md @@ -1347,3 +1347,78 @@ delay = "1m" ``` Please refer to the [Key Value storage structure](/user-guide/kv-config/#key-value-storage-structure) section to get documentation on traefik KV structure. + + +## ECS backend + +Træfɪk can be configured to use Amazon ECS as a backend configuration: + + +```toml +################################################################ +# ECS configuration backend +################################################################ + +# Enable ECS configuration backend +# +# Optional +# +[ecs] + +# ECS Cluster Name +# +# Optional +# Default: "default" +# +Cluster = "default" + +# Enable watch ECS changes +# +# Optional +# Default: true +# +Watch = true + +# Polling interval (in seconds) +# +# Optional +# Default: 15 +# +RefreshSeconds = 15 + +# Expose ECS services by default in traefik +# +# Optional +# Default: true +# +ExposedByDefault = false + +# Region to use when connecting to AWS +# +# Optional +# +# Region = "us-east-1" + +# AccessKeyID to use when connecting to AWS +# +# Optional +# +# AccessKeyID = "abc" + +# SecretAccessKey to use when connecting to AWS +# +# Optional +# +# SecretAccessKey = "123" + +``` + +Labels can be used on task containers to override default behaviour: + +- `traefik.protocol=https`: override the default `http` protocol +- `traefik.weight=10`: assign this weight to the container +- `traefik.enable=false`: disable this container in Træfɪk +- `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 +- `traefik.frontend.entryPoints=http,https`: assign this frontend to entry points `http` and `https`. Overrides `defaultEntryPoints`. diff --git a/glide.lock b/glide.lock index ab7e76114..4539ab00f 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 2a2f73984ec9cccd62220c0eba5ddc82dec96b0044ffed48b25d964e2f3eee05 -updated: 2017-02-05T17:22:02.550162979+01:00 +hash: a0b0abed2162e490cbe75a6a36ebaaf39e748ee80e419e879e7253679a0bc134 +updated: 2017-02-05T18:09:09.856588042Z imports: - name: bitbucket.org/ww/goautoneg version: 75cd24fc2f2c2a2088577d12123ddee5f54e0675 @@ -35,12 +35,17 @@ imports: - aws/session - aws/signer/v4 - private/protocol + - private/protocol/ec2query + - private/protocol/json/jsonutil + - private/protocol/jsonrpc - private/protocol/query - private/protocol/query/queryutil - private/protocol/rest - private/protocol/restxml - private/protocol/xml/xmlutil - private/waiter + - service/ec2 + - service/ecs - service/route53 - service/sts - name: github.com/Azure/azure-sdk-for-go @@ -735,7 +740,7 @@ testImports: subpackages: - specs-go - name: github.com/pkg/errors - version: 248dadf4e9068a0b3e79f02ed0a610d935de5302 + version: 01fa4104b9c248c8945d14d9f128454d5b28d595 - name: github.com/vbatts/tar-split version: 6810cedb21b2c3d0b9bb8f9af12ff2dc7a2f14df subpackages: diff --git a/glide.yaml b/glide.yaml index 78dddf637..a13917963 100644 --- a/glide.yaml +++ b/glide.yaml @@ -119,7 +119,15 @@ import: - package: github.com/aws/aws-sdk-go version: v1.6.18 subpackages: + - aws + - aws/credentials + - aws/defaults + - aws/ec2metadata - aws/endpoints + - aws/request + - aws/session + - service/ec2 + - service/ecs - package: cloud.google.com/go version: v0.6.0 subpackages: diff --git a/provider/ecs.go b/provider/ecs.go new file mode 100644 index 000000000..be38d79d0 --- /dev/null +++ b/provider/ecs.go @@ -0,0 +1,414 @@ +package provider + +import ( + "context" + "fmt" + "strconv" + "strings" + "text/template" + "time" + + "github.com/BurntSushi/ty/fun" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/defaults" + "github.com/aws/aws-sdk-go/aws/ec2metadata" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/cenk/backoff" + "github.com/containous/traefik/job" + "github.com/containous/traefik/log" + "github.com/containous/traefik/safe" + "github.com/containous/traefik/types" +) + +var _ Provider = (*ECS)(nil) + +// ECS holds configurations of the ECS provider. +type ECS struct { + BaseProvider `mapstructure:",squash"` + + Domain string `description:"Default domain used"` + ExposedByDefault bool `description:"Expose containers by default"` + RefreshSeconds int `description:"Polling interval (in seconds)"` + + // ECS lookup parameters + Cluster string `description:"ECS Cluster Name"` + Region string `description:"The AWS region to use for requests"` + AccessKeyID string `description:"The AWS credentials access key to use for making requests"` + SecretAccessKey string `description:"The AWS credentials access key to use for making requests"` +} + +type ecsInstance struct { + Name string + ID string + task *ecs.Task + taskDefinition *ecs.TaskDefinition + container *ecs.Container + containerDefinition *ecs.ContainerDefinition + machine *ec2.Instance +} + +type awsClient struct { + ecs *ecs.ECS + ec2 *ec2.EC2 +} + +func (provider *ECS) createClient() (*awsClient, error) { + sess := session.New() + ec2meta := ec2metadata.New(sess) + if provider.Region == "" { + log.Infoln("No EC2 region provided, querying instance metadata endpoint...") + identity, err := ec2meta.GetInstanceIdentityDocument() + if err != nil { + return nil, err + } + provider.Region = identity.Region + } + + cfg := &aws.Config{ + Region: &provider.Region, + Credentials: credentials.NewChainCredentials( + []credentials.Provider{ + &credentials.StaticProvider{ + Value: credentials.Value{ + AccessKeyID: provider.AccessKeyID, + SecretAccessKey: provider.SecretAccessKey, + }, + }, + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{}, + defaults.RemoteCredProvider(*(defaults.Config()), defaults.Handlers()), + }), + } + + return &awsClient{ + ecs.New(sess, cfg), + ec2.New(sess, cfg), + }, nil +} + +// Provide allows the provider to provide configurations to traefik +// using the given configuration channel. +func (provider *ECS) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error { + + provider.Constraints = append(provider.Constraints, constraints...) + + handleCanceled := func(ctx context.Context, err error) error { + if ctx.Err() == context.Canceled || err == context.Canceled { + return nil + } + return err + } + + pool.Go(func(stop chan bool) { + ctx, cancel := context.WithCancel(context.Background()) + go func() { + select { + case <-stop: + cancel() + } + }() + + operation := func() error { + aws, err := provider.createClient() + if err != nil { + return err + } + + configuration, err := provider.loadECSConfig(ctx, aws) + if err != nil { + return handleCanceled(ctx, err) + } + + configurationChan <- types.ConfigMessage{ + ProviderName: "ecs", + Configuration: configuration, + } + + if provider.Watch { + reload := time.NewTicker(time.Second * time.Duration(provider.RefreshSeconds)) + defer reload.Stop() + for { + select { + case <-reload.C: + configuration, err := provider.loadECSConfig(ctx, aws) + if err != nil { + return handleCanceled(ctx, err) + } + + configurationChan <- types.ConfigMessage{ + ProviderName: "ecs", + Configuration: configuration, + } + case <-ctx.Done(): + return handleCanceled(ctx, ctx.Err()) + } + } + } + + return nil + } + + notify := func(err error, time time.Duration) { + log.Errorf("ECS connection error %+v, retrying in %s", err, time) + } + err := backoff.RetryNotify(safe.OperationWithRecover(operation), job.NewBackOff(backoff.NewExponentialBackOff()), notify) + if err != nil { + log.Errorf("Cannot connect to ECS api %+v", err) + } + }) + + return nil +} + +func wrapAws(ctx context.Context, req *request.Request) error { + req.HTTPRequest = req.HTTPRequest.WithContext(ctx) + return req.Send() +} + +func (provider *ECS) loadECSConfig(ctx context.Context, client *awsClient) (*types.Configuration, error) { + var ecsFuncMap = template.FuncMap{ + "filterFrontends": provider.filterFrontends, + "getFrontendRule": provider.getFrontendRule, + } + + instances, err := provider.listInstances(ctx, client) + if err != nil { + return nil, err + } + + instances = fun.Filter(provider.filterInstance, instances).([]ecsInstance) + + return provider.getConfiguration("templates/ecs.tmpl", ecsFuncMap, struct { + Instances []ecsInstance + }{ + instances, + }) +} + +// Find all running ECS tasks in a cluster, also collect the task definitions (for docker labels) +// and the EC2 instance data +func (provider *ECS) listInstances(ctx context.Context, client *awsClient) ([]ecsInstance, error) { + var taskArns []*string + req, _ := client.ecs.ListTasksRequest(&ecs.ListTasksInput{ + Cluster: &provider.Cluster, + DesiredStatus: aws.String(ecs.DesiredStatusRunning), + }) + + for ; req != nil; req = req.NextPage() { + if err := wrapAws(ctx, req); err != nil { + return nil, err + } + + taskArns = append(taskArns, req.Data.(*ecs.ListTasksOutput).TaskArns...) + } + + req, taskResp := client.ecs.DescribeTasksRequest(&ecs.DescribeTasksInput{ + Tasks: taskArns, + Cluster: &provider.Cluster, + }) + + if err := wrapAws(ctx, req); err != nil { + return nil, err + } + + containerInstanceArns := make([]*string, 0) + byContainerInstance := make(map[string]int) + + taskDefinitionArns := make([]*string, 0) + byTaskDefinition := make(map[string]int) + + for _, task := range taskResp.Tasks { + if _, found := byContainerInstance[*task.ContainerInstanceArn]; !found { + byContainerInstance[*task.ContainerInstanceArn] = len(containerInstanceArns) + containerInstanceArns = append(containerInstanceArns, task.ContainerInstanceArn) + } + if _, found := byTaskDefinition[*task.TaskDefinitionArn]; !found { + byTaskDefinition[*task.TaskDefinitionArn] = len(taskDefinitionArns) + taskDefinitionArns = append(taskDefinitionArns, task.TaskDefinitionArn) + } + } + + machines, err := provider.lookupEc2Instances(ctx, client, containerInstanceArns) + if err != nil { + return nil, err + } + + taskDefinitions, err := provider.lookupTaskDefinitions(ctx, client, taskDefinitionArns) + if err != nil { + return nil, err + } + + var instances []ecsInstance + for _, task := range taskResp.Tasks { + + machineIdx := byContainerInstance[*task.ContainerInstanceArn] + taskDefIdx := byTaskDefinition[*task.TaskDefinitionArn] + + for _, container := range task.Containers { + + taskDefinition := taskDefinitions[taskDefIdx] + var containerDefinition *ecs.ContainerDefinition + for _, def := range taskDefinition.ContainerDefinitions { + if *container.Name == *def.Name { + containerDefinition = def + break + } + } + + instances = append(instances, ecsInstance{ + fmt.Sprintf("%s-%s", strings.Replace(*task.Group, ":", "-", 1), *container.Name), + (*task.TaskArn)[len(*task.TaskArn)-12:], + task, + taskDefinition, + container, + containerDefinition, + machines[machineIdx], + }) + } + } + + return instances, nil +} + +func (provider *ECS) lookupEc2Instances(ctx context.Context, client *awsClient, containerArns []*string) ([]*ec2.Instance, error) { + req, containerResp := client.ecs.DescribeContainerInstancesRequest(&ecs.DescribeContainerInstancesInput{ + ContainerInstances: containerArns, + Cluster: &provider.Cluster, + }) + + if err := wrapAws(ctx, req); err != nil { + return nil, err + } + + order := make(map[string]int) + for i, arn := range containerArns { + order[*arn] = i + } + + instanceIds := make([]*string, len(containerArns)) + for i, container := range containerResp.ContainerInstances { + order[*container.Ec2InstanceId] = order[*container.ContainerInstanceArn] + instanceIds[i] = container.Ec2InstanceId + } + + req, instancesResp := client.ec2.DescribeInstancesRequest(&ec2.DescribeInstancesInput{ + InstanceIds: instanceIds, + }) + + if err := wrapAws(ctx, req); err != nil { + return nil, err + } + + instances := make([]*ec2.Instance, len(containerArns)) + for _, r := range instancesResp.Reservations { + instances[order[*r.Instances[0].InstanceId]] = r.Instances[0] + } + return instances, nil +} + +func (provider *ECS) lookupTaskDefinitions(ctx context.Context, client *awsClient, taskDefArns []*string) ([]*ecs.TaskDefinition, error) { + taskDefinitions := make([]*ecs.TaskDefinition, len(taskDefArns)) + for i, arn := range taskDefArns { + + req, resp := client.ecs.DescribeTaskDefinitionRequest(&ecs.DescribeTaskDefinitionInput{ + TaskDefinition: arn, + }) + + if err := wrapAws(ctx, req); err != nil { + return nil, err + } + + taskDefinitions[i] = resp.TaskDefinition + } + return taskDefinitions, nil +} + +func (i ecsInstance) label(k string) string { + if v, found := i.containerDefinition.DockerLabels[k]; found { + return *v + } + return "" +} + +func (provider *ECS) filterInstance(i ecsInstance) bool { + if len(i.container.NetworkBindings) == 0 { + log.Debugf("Filtering ecs instance without port %s (%s)", i.Name, i.ID) + return false + } + + label := i.label("traefik.enable") + enabled := provider.ExposedByDefault && label != "false" || label == "true" + if !enabled { + log.Debugf("Filtering disabled ecs instance %s (%s) (traefik.enabled = '%s')", i.Name, i.ID, label) + return false + } + + return true +} + +func (provider *ECS) filterFrontends(instances []ecsInstance) []ecsInstance { + byName := make(map[string]bool) + + return fun.Filter(func(i ecsInstance) bool { + if _, found := byName[i.Name]; !found { + byName[i.Name] = true + return true + } + + return false + }, instances).([]ecsInstance) +} + +func (provider *ECS) getFrontendRule(i ecsInstance) string { + if label := i.label("traefik.frontend.rule"); label != "" { + return label + } + return "Host:" + strings.ToLower(strings.Replace(i.Name, "_", "-", -1)) + "." + provider.Domain +} + +func (i ecsInstance) Protocol() string { + if label := i.label("traefik.protocol"); label != "" { + return label + } + return "http" +} + +func (i ecsInstance) Host() string { + return *i.machine.PrivateIpAddress +} + +func (i ecsInstance) Port() string { + return strconv.FormatInt(*i.container.NetworkBindings[0].HostPort, 10) +} + +func (i ecsInstance) Weight() string { + if label := i.label("traefik.weight"); label != "" { + return label + } + return "0" +} + +func (i ecsInstance) PassHostHeader() string { + if label := i.label("traefik.frontend.passHostHeader"); label != "" { + return label + } + return "true" +} + +func (i ecsInstance) Priority() string { + if label := i.label("traefik.frontend.priority"); label != "" { + return label + } + return "0" +} + +func (i ecsInstance) EntryPoints() []string { + if label := i.label("traefik.frontend.entryPoints"); label != "" { + return strings.Split(label, ",") + } + return []string{} +} diff --git a/provider/ecs_test.go b/provider/ecs_test.go new file mode 100644 index 000000000..cf2a9b090 --- /dev/null +++ b/provider/ecs_test.go @@ -0,0 +1,223 @@ +package provider + +import ( + "reflect" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ecs" +) + +func makeEcsInstance(containerDef *ecs.ContainerDefinition) ecsInstance { + container := &ecs.Container{ + Name: containerDef.Name, + NetworkBindings: make([]*ecs.NetworkBinding, len(containerDef.PortMappings)), + } + + for i, pm := range containerDef.PortMappings { + container.NetworkBindings[i] = &ecs.NetworkBinding{ + HostPort: pm.HostPort, + ContainerPort: pm.ContainerPort, + Protocol: pm.Protocol, + BindIP: aws.String("0.0.0.0"), + } + } + + return ecsInstance{ + Name: "foo-http", + ID: "123456789abc", + task: &ecs.Task{ + Containers: []*ecs.Container{container}, + }, + taskDefinition: &ecs.TaskDefinition{ + ContainerDefinitions: []*ecs.ContainerDefinition{containerDef}, + }, + container: container, + containerDefinition: containerDef, + machine: &ec2.Instance{ + PrivateIpAddress: aws.String("10.0.0.0"), + }, + } +} + +func simpleEcsInstance(labels map[string]*string) ecsInstance { + return makeEcsInstance(&ecs.ContainerDefinition{ + Name: aws.String("http"), + PortMappings: []*ecs.PortMapping{{ + HostPort: aws.Int64(80), + ContainerPort: aws.Int64(80), + Protocol: aws.String("tcp"), + }}, + DockerLabels: labels, + }) +} + +func TestEcsProtocol(t *testing.T) { + cases := []struct { + expected string + instanceInfo ecsInstance + }{ + { + expected: "http", + instanceInfo: simpleEcsInstance(map[string]*string{}), + }, + { + expected: "https", + instanceInfo: simpleEcsInstance(map[string]*string{ + "traefik.protocol": aws.String("https"), + }), + }, + } + + for _, c := range cases { + value := c.instanceInfo.Protocol() + if value != c.expected { + t.Fatalf("Should have been %s, got %s", c.expected, value) + } + } +} + +func TestEcsHost(t *testing.T) { + cases := []struct { + expected string + instanceInfo ecsInstance + }{ + { + expected: "10.0.0.0", + instanceInfo: simpleEcsInstance(map[string]*string{}), + }, + } + + for _, c := range cases { + value := c.instanceInfo.Host() + if value != c.expected { + t.Fatalf("Should have been %s, got %s", c.expected, value) + } + } +} + +func TestEcsPort(t *testing.T) { + cases := []struct { + expected string + instanceInfo ecsInstance + }{ + { + expected: "80", + instanceInfo: simpleEcsInstance(map[string]*string{}), + }, + } + + for _, c := range cases { + value := c.instanceInfo.Port() + if value != c.expected { + t.Fatalf("Should have been %s, got %s", c.expected, value) + } + } +} + +func TestEcsWeight(t *testing.T) { + cases := []struct { + expected string + instanceInfo ecsInstance + }{ + { + expected: "0", + instanceInfo: simpleEcsInstance(map[string]*string{}), + }, + { + expected: "10", + instanceInfo: simpleEcsInstance(map[string]*string{ + "traefik.weight": aws.String("10"), + }), + }, + } + + for _, c := range cases { + value := c.instanceInfo.Weight() + if value != c.expected { + t.Fatalf("Should have been %s, got %s", c.expected, value) + } + } +} + +func TestEcsPassHostHeader(t *testing.T) { + cases := []struct { + expected string + instanceInfo ecsInstance + }{ + { + expected: "true", + instanceInfo: simpleEcsInstance(map[string]*string{}), + }, + { + expected: "false", + instanceInfo: simpleEcsInstance(map[string]*string{ + "traefik.frontend.passHostHeader": aws.String("false"), + }), + }, + } + + for _, c := range cases { + value := c.instanceInfo.PassHostHeader() + if value != c.expected { + t.Fatalf("Should have been %s, got %s", c.expected, value) + } + } +} + +func TestEcsPriority(t *testing.T) { + cases := []struct { + expected string + instanceInfo ecsInstance + }{ + { + expected: "0", + instanceInfo: simpleEcsInstance(map[string]*string{}), + }, + { + expected: "10", + instanceInfo: simpleEcsInstance(map[string]*string{ + "traefik.frontend.priority": aws.String("10"), + }), + }, + } + + for _, c := range cases { + value := c.instanceInfo.Priority() + if value != c.expected { + t.Fatalf("Should have been %s, got %s", c.expected, value) + } + } +} + +func TestEcsEntryPoints(t *testing.T) { + cases := []struct { + expected []string + instanceInfo ecsInstance + }{ + { + expected: []string{}, + instanceInfo: simpleEcsInstance(map[string]*string{}), + }, + { + expected: []string{"http"}, + instanceInfo: simpleEcsInstance(map[string]*string{ + "traefik.frontend.entryPoints": aws.String("http"), + }), + }, + { + expected: []string{"http", "https"}, + instanceInfo: simpleEcsInstance(map[string]*string{ + "traefik.frontend.entryPoints": aws.String("http,https"), + }), + }, + } + + for _, c := range cases { + value := c.instanceInfo.EntryPoints() + if !reflect.DeepEqual(value, c.expected) { + t.Fatalf("Should have been %s, got %s", c.expected, value) + } + } +} diff --git a/server.go b/server.go index 7445b91af..3694a65b0 100644 --- a/server.go +++ b/server.go @@ -373,6 +373,9 @@ func (server *Server) configureProviders() { if server.globalConfiguration.Eureka != nil { server.providers = append(server.providers, server.globalConfiguration.Eureka) } + if server.globalConfiguration.ECS != nil { + server.providers = append(server.providers, server.globalConfiguration.ECS) + } } func (server *Server) startProviders() { diff --git a/templates/ecs.tmpl b/templates/ecs.tmpl new file mode 100644 index 000000000..83abdb008 --- /dev/null +++ b/templates/ecs.tmpl @@ -0,0 +1,17 @@ +[backends]{{range .Instances}} + [backends.backend-{{ .Name }}.servers.server-{{ .Name }}{{ .ID }}] + url = "{{ .Protocol }}://{{ .Host }}:{{ .Port }}" + weight = {{ .Weight }} +{{end}} + +[frontends]{{range filterFrontends .Instances}} + [frontends.frontend-{{ .Name }}] + backend = "backend-{{ .Name }}" + passHostHeader = {{ .PassHostHeader }} + priority = {{ .Priority }} + entryPoints = [{{range .EntryPoints }} + "{{.}}", + {{end}}] + [frontends.frontend-{{ .Name }}.routes.route-frontend-{{ .Name }}] + rule = "{{getFrontendRule .}}" +{{end}} diff --git a/traefik.sample.toml b/traefik.sample.toml index 30c1c8c2b..fc0d6f38f 100644 --- a/traefik.sample.toml +++ b/traefik.sample.toml @@ -868,6 +868,67 @@ # filename = "boltdb.tmpl" +################################################################ +# ECS configuration backend +################################################################ + +# Enable ECS configuration backend +# +# Optional +# +# [ecs] + +# ECS Cluster Name +# +# Optional +# Default: "default" +# +# Cluster = "default" + +# Enable watch ECS changes +# +# Optional +# Default: true +# +# Watch = true + +# Polling interval (in seconds) +# +# Optional +# Default: 15 +# +# RefreshSeconds = 15 + +# Expose ECS services by default in traefik +# +# Optional +# Default: true +# +# ExposedByDefault = false + +# Region to use when connecting to AWS +# +# Optional +# +# Region = "us-east-1" + +# AccessKeyID to use when connecting to AWS +# +# Optional +# +# AccessKeyID = "abc" + +# SecretAccessKey to use when connecting to AWS +# +# Optional +# +# SecretAccessKey = "123" + +# Override default configuration template. For advanced users :) +# +# Optional +# +# filename = "ecs.tmpl" ################################################################