浏览代码

Merge pull request #688 from docker/resolv

Configure /etc/resolv.conf before container start by an init container
Nicolas De loof 5 年之前
父节点
当前提交
408ab6d43a

+ 1 - 1
ecs/cloudformation.go

@@ -93,7 +93,7 @@ func (b *ecsAPIService) convert(project *types.Project, resources awsResources)
 		taskExecutionRole := b.createTaskExecutionRole(project, service, template)
 		taskRole := b.createTaskRole(project, service, template)
 
-		definition, err := b.createTaskExecution(project, service)
+		definition, err := b.createTaskDefinition(project, service)
 		if err != nil {
 			return nil, err
 		}

+ 19 - 5
ecs/cloudformation_test.go

@@ -56,8 +56,12 @@ services:
 x-aws-logs_retention: 10
 `)
 	def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition)
-	logging := def.ContainerDefinitions[0].LogConfiguration
-	assert.Equal(t, logging.Options["awslogs-datetime-pattern"], "FOO")
+	logging := getMainContainer(def, t).LogConfiguration
+	if logging != nil {
+		assert.Equal(t, logging.Options["awslogs-datetime-pattern"], "FOO")
+	} else {
+		t.Fatal("Logging not configured")
+	}
 
 	logGroup := template.Resources["LogGroup"].(*logs.LogGroup)
 	assert.Equal(t, logGroup.RetentionInDays, 10)
@@ -72,7 +76,7 @@ services:
       - testdata/input/envfile
 `)
 	def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition)
-	env := def.ContainerDefinitions[0].Environment
+	env := getMainContainer(def, t).Environment
 	var found bool
 	for _, pair := range env {
 		if pair.Name == "FOO" {
@@ -94,7 +98,7 @@ services:
       - "FOO=ZOT"
 `)
 	def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition)
-	env := def.ContainerDefinitions[0].Environment
+	env := getMainContainer(def, t).Environment
 	var found bool
 	for _, pair := range env {
 		if pair.Name == "FOO" {
@@ -358,7 +362,7 @@ services:
     working_dir: "working_dir"
 `)
 	def := template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition)
-	container := def.ContainerDefinitions[0]
+	container := getMainContainer(def, t)
 	assert.Equal(t, container.Image, "image")
 	assert.Equal(t, container.Command[0], "command")
 	assert.Equal(t, container.EntryPoint[0], "entrypoint")
@@ -446,3 +450,13 @@ func loadConfig(t *testing.T, yaml string) *types.Project {
 	assert.NilError(t, err)
 	return model
 }
+
+func getMainContainer(def *ecs.TaskDefinition, t *testing.T) ecs.TaskDefinition_ContainerDefinition {
+	for _, c := range def.ContainerDefinitions {
+		if c.Essential {
+			return c
+		}
+	}
+	t.Fail()
+	return def.ContainerDefinitions[0]
+}

+ 11 - 13
ecs/convert.go

@@ -26,7 +26,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/aws/aws-sdk-go/aws"
+	"github.com/docker/compose-cli/ecs/secrets"
 
 	ecsapi "github.com/aws/aws-sdk-go/service/ecs"
 	"github.com/awslabs/goformation/v4/cloudformation"
@@ -34,13 +34,12 @@ import (
 	"github.com/compose-spec/compose-go/types"
 	"github.com/docker/cli/opts"
 	"github.com/joho/godotenv"
-
-	"github.com/docker/compose-cli/ecs/secrets"
 )
 
 const secretsInitContainerImage = "docker/ecs-secrets-sidecar"
+const searchDomainInitContainerImage = "docker/ecs-searchdomain-sidecar"
 
-func (b *ecsAPIService) createTaskExecution(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefinition, error) {
+func (b *ecsAPIService) createTaskDefinition(project *types.Project, service types.ServiceConfig) (*ecs.TaskDefinition, error) {
 	cpu, mem, err := toLimits(service)
 	if err != nil {
 		return nil, err
@@ -48,15 +47,6 @@ func (b *ecsAPIService) createTaskExecution(project *types.Project, service type
 	_, memReservation := toContainerReservation(service)
 	credential := getRepoCredentials(service)
 
-	// override resolve.conf search directive to also search <project>.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),
-		}))
-
 	logConfiguration := getLogConfiguration(service, project)
 
 	var (
@@ -74,6 +64,14 @@ func (b *ecsAPIService) createTaskExecution(project *types.Project, service type
 		mounts = append(mounts, secretsMount)
 	}
 
+	initContainers = append(initContainers, ecs.TaskDefinition_ContainerDefinition{
+		Name:             fmt.Sprintf("%s_ResolvConf_InitContainer", normalizeResourceName(service.Name)),
+		Image:            searchDomainInitContainerImage,
+		Essential:        false,
+		Command:          []string{b.Region + ".compute.internal", project.Name + ".local"},
+		LogConfiguration: logConfiguration,
+	})
+
 	var dependencies []ecs.TaskDefinition_ContainerDependency
 	for _, c := range initContainers {
 		dependencies = append(dependencies, ecs.TaskDefinition_ContainerDependency{

+ 23 - 0
ecs/resolv/Dockerfile

@@ -0,0 +1,23 @@
+#   Copyright 2020 Docker Compose CLI authors
+
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+
+#       http://www.apache.org/licenses/LICENSE-2.0
+
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+FROM golang:1.14.4-alpine AS builder
+WORKDIR $GOPATH/src/github.com/docker/compose-cli/ecs/resolv
+COPY . .
+RUN GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/resolv main/main.go
+RUN chmod +x /go/bin/resolv
+
+FROM scratch
+COPY --from=builder /go/bin/resolv /resolv
+ENTRYPOINT ["/resolv"]

+ 39 - 0
ecs/resolv/main/main.go

@@ -0,0 +1,39 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package main
+
+import (
+	"fmt"
+	"os"
+
+	"github.com/docker/compose-cli/ecs/resolv"
+)
+
+const resolvconf = "/etc/resolv.conf"
+
+func main() {
+	if len(os.Args) < 2 {
+		fmt.Fprint(os.Stderr, "usage: resolv DOMAIN [DOMAIN]")
+		os.Exit(1)
+	}
+
+	err := resolv.SetSearchDomains(resolvconf, os.Args[1:]...)
+	if err != nil {
+		fmt.Fprint(os.Stderr, err.Error())
+		os.Exit(1)
+	}
+}

+ 35 - 0
ecs/resolv/resolv.go

@@ -0,0 +1,35 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package resolv
+
+import (
+	"os"
+	"strings"
+)
+
+// SetSearchDomains appends a `search` directive to resolv.conf file for domains
+func SetSearchDomains(file string, domains ...string) error {
+	search := strings.Join(domains, " ")
+
+	f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+	if err != nil {
+		return err
+	}
+	defer f.Close() //nolint:errcheck
+	_, err = f.WriteString("\nsearch " + search)
+	return err
+}

+ 48 - 0
ecs/resolv/resolv_test.go

@@ -0,0 +1,48 @@
+/*
+   Copyright 2020 Docker Compose CLI authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package resolv
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"gotest.tools/v3/assert"
+	"gotest.tools/v3/fs"
+	"gotest.tools/v3/golden"
+)
+
+func TestSetDomain(t *testing.T) {
+	dir := fs.NewDir(t, "resolv").Path()
+	f := filepath.Join(dir, "resolv.conf")
+	touch(t, f)
+
+	err := SetSearchDomains(f, "foo", "bar", "zot")
+	assert.NilError(t, err)
+
+	got, err := ioutil.ReadFile(f)
+	assert.NilError(t, err)
+	golden.Assert(t, string(got), "resolv.conf.golden")
+}
+
+func touch(t *testing.T, f string) {
+	file, err := os.Create(f)
+	assert.NilError(t, err)
+	err = file.Close()
+	assert.NilError(t, err)
+}

+ 2 - 0
ecs/resolv/testdata/resolv.conf.golden

@@ -0,0 +1,2 @@
+
+search foo bar zot

+ 24 - 14
ecs/testdata/simple/simple-cloudformation-conversion.golden

@@ -226,21 +226,31 @@
       "Properties": {
         "ContainerDefinitions": [
           {
-            "Environment": [
+            "Command": [
+              ".compute.internal",
+              "TestSimpleConvert.local"
+            ],
+            "Essential": "false",
+            "Image": "docker/ecs-searchdomain-sidecar",
+            "LogConfiguration": {
+              "LogDriver": "awslogs",
+              "Options": {
+                "awslogs-group": {
+                  "Ref": "LogGroup"
+                },
+                "awslogs-region": {
+                  "Ref": "AWS::Region"
+                },
+                "awslogs-stream-prefix": "TestSimpleConvert"
+              }
+            },
+            "Name": "Simple_ResolvConf_InitContainer"
+          },
+          {
+            "DependsOn": [
               {
-                "Name": "LOCALDOMAIN",
-                "Value": {
-                  "Fn::Join": [
-                    "",
-                    [
-                      {
-                        "Ref": "AWS::Region"
-                      },
-                      ".compute.internal",
-                      " TestSimpleConvert.local"
-                    ]
-                  ]
-                }
+                "Condition": "SUCCESS",
+                "ContainerName": "Simple_ResolvConf_InitContainer"
               }
             ],
             "Essential": true,