Merge pull request #688 from docker/resolv

Configure /etc/resolv.conf before container start by an init container
This commit is contained in:
Nicolas De loof 2020-10-05 10:56:16 +02:00 committed by GitHub
commit 408ab6d43a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 202 additions and 33 deletions

View File

@ -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
}

View File

@ -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]
}

View File

@ -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
ecs/resolv/Dockerfile Normal file
View File

@ -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
ecs/resolv/main/main.go Normal file
View File

@ -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
ecs/resolv/resolv.go Normal file
View File

@ -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
ecs/resolv/resolv_test.go Normal file
View File

@ -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)
}

View File

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

View File

@ -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,