[awslogs] Auto-detect region on EC2 instances

Signed-off-by: Samuel Karp <skarp@amazon.com>
This commit is contained in:
Samuel Karp 2015-09-28 06:40:44 +00:00
parent 480c9c0178
commit 8a6dfb26f3
4 changed files with 86 additions and 14 deletions

View File

@ -2,6 +2,7 @@
package awslogs package awslogs
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"runtime" "runtime"
@ -14,6 +15,7 @@ import (
"github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/defaults" "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/request"
"github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/docker/docker/daemon/logger" "github.com/docker/docker/daemon/logger"
@ -58,6 +60,10 @@ type api interface {
PutLogEvents(*cloudwatchlogs.PutLogEventsInput) (*cloudwatchlogs.PutLogEventsOutput, error) PutLogEvents(*cloudwatchlogs.PutLogEventsInput) (*cloudwatchlogs.PutLogEventsOutput, error)
} }
type regionFinder interface {
Region() (string, error)
}
type byTimestamp []*cloudwatchlogs.InputLogEvent type byTimestamp []*cloudwatchlogs.InputLogEvent
// init registers the awslogs driver and sets the default region, if provided // init registers the awslogs driver and sets the default region, if provided
@ -85,13 +91,17 @@ func New(ctx logger.Context) (logger.Logger, error) {
if ctx.Config[logStreamKey] != "" { if ctx.Config[logStreamKey] != "" {
logStreamName = ctx.Config[logStreamKey] logStreamName = ctx.Config[logStreamKey]
} }
client, err := newAWSLogsClient(ctx)
if err != nil {
return nil, err
}
containerStream := &logStream{ containerStream := &logStream{
logStreamName: logStreamName, logStreamName: logStreamName,
logGroupName: logGroupName, logGroupName: logGroupName,
client: newAWSLogsClient(ctx), client: client,
messages: make(chan *logger.Message, 4096), messages: make(chan *logger.Message, 4096),
} }
err := containerStream.create() err = containerStream.create()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -100,13 +110,38 @@ func New(ctx logger.Context) (logger.Logger, error) {
return containerStream, nil return containerStream, nil
} }
func newAWSLogsClient(ctx logger.Context) api { // newRegionFinder is a variable such that the implementation
// can be swapped out for unit tests.
var newRegionFinder = func() regionFinder {
return ec2metadata.New(nil)
}
// newAWSLogsClient creates the service client for Amazon CloudWatch Logs.
// Customizations to the default client from the SDK include a Docker-specific
// User-Agent string and automatic region detection using the EC2 Instance
// Metadata Service when region is otherwise unspecified.
func newAWSLogsClient(ctx logger.Context) (api, error) {
config := defaults.DefaultConfig config := defaults.DefaultConfig
if ctx.Config[regionKey] != "" { if ctx.Config[regionKey] != "" {
config = defaults.DefaultConfig.Merge(&aws.Config{ config = defaults.DefaultConfig.Merge(&aws.Config{
Region: aws.String(ctx.Config[regionKey]), Region: aws.String(ctx.Config[regionKey]),
}) })
} }
if config.Region == nil || *config.Region == "" {
logrus.Info("Trying to get region from EC2 Metadata")
ec2MetadataClient := newRegionFinder()
region, err := ec2MetadataClient.Region()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("Could not get region from EC2 metadata, environment, or log option")
return nil, errors.New("Cannot determine region for awslogs driver")
}
config.Region = &region
}
logrus.WithFields(logrus.Fields{
"region": *config.Region,
}).Debug("Created awslogs client")
client := cloudwatchlogs.New(config) client := cloudwatchlogs.New(config)
client.Handlers.Build.PushBackNamed(request.NamedHandler{ client.Handlers.Build.PushBackNamed(request.NamedHandler{
@ -118,7 +153,7 @@ func newAWSLogsClient(ctx logger.Context) api {
version.VERSION, runtime.GOOS, currentAgent)) version.VERSION, runtime.GOOS, currentAgent))
}, },
}) })
return client return client, nil
} }
// Name returns the name of the awslogs logging driver // Name returns the name of the awslogs logging driver
@ -312,12 +347,6 @@ func ValidateLogOpt(cfg map[string]string) error {
if cfg[logGroupKey] == "" { if cfg[logGroupKey] == "" {
return fmt.Errorf("must specify a value for log opt '%s'", logGroupKey) return fmt.Errorf("must specify a value for log opt '%s'", logGroupKey)
} }
if cfg[regionKey] == "" && os.Getenv(regionEnvKey) == "" {
return fmt.Errorf(
"must specify a value for environment variable '%s' or log opt '%s'",
regionEnvKey,
regionKey)
}
return nil return nil
} }

View File

@ -32,7 +32,10 @@ func TestNewAWSLogsClientUserAgentHandler(t *testing.T) {
}, },
} }
client := newAWSLogsClient(ctx) client, err := newAWSLogsClient(ctx)
if err != nil {
t.Fatal(err)
}
realClient, ok := client.(*cloudwatchlogs.CloudWatchLogs) realClient, ok := client.(*cloudwatchlogs.CloudWatchLogs)
if !ok { if !ok {
t.Fatal("Could not cast client to cloudwatchlogs.CloudWatchLogs") t.Fatal("Could not cast client to cloudwatchlogs.CloudWatchLogs")
@ -53,6 +56,25 @@ func TestNewAWSLogsClientUserAgentHandler(t *testing.T) {
} }
} }
func TestNewAWSLogsClientRegionDetect(t *testing.T) {
ctx := logger.Context{
Config: map[string]string{},
}
mockMetadata := newMockMetadataClient()
newRegionFinder = func() regionFinder {
return mockMetadata
}
mockMetadata.regionResult <- &regionResult{
successResult: "us-east-1",
}
_, err := newAWSLogsClient(ctx)
if err != nil {
t.Fatal(err)
}
}
func TestCreateSuccess(t *testing.T) { func TestCreateSuccess(t *testing.T) {
mockClient := newMockClient() mockClient := newMockClient()
stream := &logStream{ stream := &logStream{

View File

@ -49,6 +49,26 @@ func (m *mockcwlogsclient) PutLogEvents(input *cloudwatchlogs.PutLogEventsInput)
return output.successResult, output.errorResult return output.successResult, output.errorResult
} }
type mockmetadataclient struct {
regionResult chan *regionResult
}
type regionResult struct {
successResult string
errorResult error
}
func newMockMetadataClient() *mockmetadataclient {
return &mockmetadataclient{
regionResult: make(chan *regionResult, 1),
}
}
func (m *mockmetadataclient) Region() (string, error) {
output := <-m.regionResult
return output.successResult, output.errorResult
}
func test() { func test() {
_ = &logStream{ _ = &logStream{
client: newMockClient(), client: newMockClient(),

View File

@ -34,9 +34,10 @@ You can use the `--log-opt NAME=VALUE` flag to specify Amazon CloudWatch Logs lo
### awslogs-region ### awslogs-region
You must specify a region for the `awslogs` logging driver. You can specify the The `awslogs` logging driver sends your Docker logs to a specific region. Use
region with either the `awslogs-region` log option or `AWS_REGION` environment the `awslogs-region` log option or the `AWS_REGION` environment variable to set
variable: the region. By default, if your Docker daemon is running on an EC2 instance
and no region is set, the driver uses the instance's region.
docker run --log-driver=awslogs --log-opt awslogs-region=us-east-1 ... docker run --log-driver=awslogs --log-opt awslogs-region=us-east-1 ...