diff --git a/ecs/README.md b/ecs/README.md index f15fd25f5..afa354d3e 100644 --- a/ecs/README.md +++ b/ecs/README.md @@ -39,3 +39,27 @@ file do not include unsupported features. * _Convert_ produces a CloudFormation template to define all resources required to implement the application model on AWS. * _Apply_ phase do apply the CloudFormation template, either by exporting to a stack file or to deploy on AWS. +## Application model + +### Services + +Compose services are mapped to ECS services. Compose specification has no support for multi-container services, nor +does it support sidecars. When an ECS feature requires a sidecar, we introduce custom Compose extension (`x-aws-*`) +to actually expose ECS feature as a service-level feature, not plumbing details. + +### Networking + +We map the "network" abstraction from Compose model to AWS SecurityGroups. The whole application is created within a +single VPC, SecurityGroups are created per networks, including the implicit `default` one. Services are attached +according to the networks declared in Compose model. Doing so, services attached to a common security group can +communicate together, while services from distinct SecurityGroups can't. We just can't set service aliasses per network. + +A CloudMap private namespace is created for application as `{project}.local`. Services get registered so that we +get service discovery and DNS round-robin (equivalent for Compose's `endpoint_mode: dnsrr`). Hostname-only service +discovery is enabled by running application containers with `LOCALDOMAIN={project}.local` +(see [resolv.conf(5)](http://man7.org/linux/man-pages/man5/resolv.conf.5.html)). This works out-of-the-box for +debian-based Docker images. Alpine images have to include a tiny entrypoint script to replicate this feature: +```shell script +if [ $LOCALDOMAIN ]; then echo "search ${LOCALDOMAIN}" >> /etc/resolv.conf; fi +``` + diff --git a/ecs/go.mod b/ecs/go.mod index 7d04a3de6..7eedd06bb 100644 --- a/ecs/go.mod +++ b/ecs/go.mod @@ -5,7 +5,7 @@ require ( github.com/Microsoft/hcsshim v0.8.7 // indirect github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d // indirect github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect - github.com/aws/aws-sdk-go v1.28.9 + github.com/aws/aws-sdk-go v1.30.22 github.com/awslabs/goformation/v4 v4.8.0 github.com/bitly/go-hostpool v0.1.0 // indirect github.com/bitly/go-simplejson v0.5.0 // indirect @@ -24,7 +24,6 @@ require ( github.com/docker/go v1.5.1-1 // indirect github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect - github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/gofrs/uuid v3.2.0+incompatible // indirect github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/mock v1.4.3 diff --git a/ecs/go.sum b/ecs/go.sum index 2151851e9..1e4fd8fd4 100644 --- a/ecs/go.sum +++ b/ecs/go.sum @@ -21,6 +21,9 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-sdk-go v1.28.9 h1:grIuBQc+p3dTRXerh5+2OxSuWFi0iXuxbFdTSg0jaW0= github.com/aws/aws-sdk-go v1.28.9/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.30.2/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.30.22 h1:wImJ8jQrplgmxaTeUY7FrJFn4te/VtWq+mmmJ1TnWAg= +github.com/aws/aws-sdk-go v1.30.22/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/awslabs/goformation/v4 v4.8.0 h1:UiUhyokRy3suEqBXTnipvY8klqY3Eyl4GCH17brraEc= github.com/awslabs/goformation/v4 v4.8.0/go.mod h1:GcJULxCJfloT+3pbqCluXftdEK2AD/UqpS3hkaaBntg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -161,6 +164,8 @@ github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo= github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -300,6 +305,7 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0= github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY= @@ -357,6 +363,7 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 h1:KaQtG+aDELoNmXYas3TVkGNYR golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= diff --git a/ecs/pkg/amazon/convert.go b/ecs/pkg/amazon/convert.go index 1ccb0b020..13e90690a 100644 --- a/ecs/pkg/amazon/convert.go +++ b/ecs/pkg/amazon/convert.go @@ -1,11 +1,12 @@ package amazon import ( - "errors" + "fmt" "strconv" "strings" "time" + "github.com/aws/aws-sdk-go/aws" ecsapi "github.com/aws/aws-sdk-go/service/ecs" "github.com/awslabs/goformation/v4/cloudformation" "github.com/awslabs/goformation/v4/cloudformation/ecs" @@ -21,9 +22,17 @@ func Convert(project *compose.Project, service types.ServiceConfig) (*ecs.TaskDe } credential := getRepoCredentials(service) + // override resolve.conf search directive to also search .local + // TODO remove once ECS support hostname-only service discovery + service.Environment["LOCALDOMAIN"] = aws.String( + cloudformation.Join("", []string{ + cloudformation.Ref("AWS::Region"), + ".compute.internal", + fmt.Sprintf(" %s.local", project.Name), + })) + return &ecs.TaskDefinition{ ContainerDefinitions: []ecs.TaskDefinition_ContainerDefinition{ - // Here we can declare sidecars and init-containers using https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definition_dependson { Command: service.Command, DisableNetworking: service.NetworkMode == "none", @@ -50,7 +59,6 @@ func Convert(project *compose.Project, service types.ServiceConfig) (*ecs.TaskDe "awslogs-stream-prefix": service.Name, }, }, - MountPoints: nil, Name: service.Name, PortMappings: toPortMappings(service.Ports), Privileged: service.Privileged, @@ -68,7 +76,7 @@ func Convert(project *compose.Project, service types.ServiceConfig) (*ecs.TaskDe }, }, Cpu: cpu, - Family: project.Name, + Family: fmt.Sprintf("%s-%s", project.Name, service.Name), IpcMode: service.Ipc, Memory: mem, NetworkMode: ecsapi.NetworkModeAwsvpc, // FIXME could be set by service.NetworkMode, Fargate only supports network mode ‘awsvpc’. @@ -77,7 +85,6 @@ func Convert(project *compose.Project, service types.ServiceConfig) (*ecs.TaskDe ProxyConfiguration: nil, RequiresCompatibilities: []string{ecsapi.LaunchTypeFargate}, Tags: nil, - Volumes: []ecs.TaskDefinition_Volume{}, }, nil } @@ -122,7 +129,7 @@ func toLimits(service types.ServiceConfig) (string, string, error) { } } } - return "", "", errors.New("unable to find cpu/mem for the required resources") + return "", "", fmt.Errorf("unable to find cpu/mem for the required resources") } func toRequiresCompatibilities(isolation string) []*string { @@ -198,8 +205,7 @@ func toTmpfs(tmpfs types.StringList) []ecs.TaskDefinition_Tmpfs { for _, path := range tmpfs { o = append(o, ecs.TaskDefinition_Tmpfs{ ContainerPath: path, - MountOptions: nil, - Size: 0, + Size: 100, // size is required on ECS, unlimited by the compose spec }) } return o