[awslogs] Auto-detect region on EC2 instances

Signed-off-by: Samuel Karp <skarp@amazon.com>
Upstream-commit: 8a6dfb26f30331231839c420f8cf8889c9ef35a8
Component: engine
This commit is contained in:
Samuel Karp
2015-10-27 23:47:28 +00:00
parent 32f8320544
commit 069f98c935
4 changed files with 86 additions and 14 deletions
@@ -2,6 +2,7 @@
package awslogs
import (
"errors"
"fmt"
"os"
"runtime"
@@ -14,6 +15,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"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/service/cloudwatchlogs"
"github.com/docker/docker/daemon/logger"
@@ -58,6 +60,10 @@ type api interface {
PutLogEvents(*cloudwatchlogs.PutLogEventsInput) (*cloudwatchlogs.PutLogEventsOutput, error)
}
type regionFinder interface {
Region() (string, error)
}
type byTimestamp []*cloudwatchlogs.InputLogEvent
// 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] != "" {
logStreamName = ctx.Config[logStreamKey]
}
client, err := newAWSLogsClient(ctx)
if err != nil {
return nil, err
}
containerStream := &logStream{
logStreamName: logStreamName,
logGroupName: logGroupName,
client: newAWSLogsClient(ctx),
client: client,
messages: make(chan *logger.Message, 4096),
}
err := containerStream.create()
err = containerStream.create()
if err != nil {
return nil, err
}
@@ -100,13 +110,38 @@ func New(ctx logger.Context) (logger.Logger, error) {
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
if ctx.Config[regionKey] != "" {
config = defaults.DefaultConfig.Merge(&aws.Config{
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.Handlers.Build.PushBackNamed(request.NamedHandler{
@@ -118,7 +153,7 @@ func newAWSLogsClient(ctx logger.Context) api {
version.VERSION, runtime.GOOS, currentAgent))
},
})
return client
return client, nil
}
// Name returns the name of the awslogs logging driver
@@ -312,12 +347,6 @@ func ValidateLogOpt(cfg map[string]string) error {
if cfg[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
}
@@ -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)
if !ok {
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) {
mockClient := newMockClient()
stream := &logStream{
@@ -49,6 +49,26 @@ func (m *mockcwlogsclient) PutLogEvents(input *cloudwatchlogs.PutLogEventsInput)
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() {
_ = &logStream{
client: newMockClient(),
@@ -34,9 +34,10 @@ You can use the `--log-opt NAME=VALUE` flag to specify Amazon CloudWatch Logs lo
### awslogs-region
You must specify a region for the `awslogs` logging driver. You can specify the
region with either the `awslogs-region` log option or `AWS_REGION` environment
variable:
The `awslogs` logging driver sends your Docker logs to a specific region. Use
the `awslogs-region` log option or the `AWS_REGION` environment variable to set
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 ...