@@ -56,8 +56,10 @@ import (
5656 "github.com/aws/amazon-ecs-agent/ecs-agent/logger/field"
5757 "github.com/aws/amazon-ecs-agent/ecs-agent/utils/retry"
5858 "github.com/aws/amazon-ecs-agent/ecs-agent/utils/ttime"
59+ "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
5960 "github.com/aws/aws-sdk-go/aws"
6061 ep "github.com/aws/aws-sdk-go/aws/endpoints"
62+ "github.com/aws/smithy-go/ptr"
6163 "github.com/docker/docker/api/types"
6264 dockercontainer "github.com/docker/docker/api/types/container"
6365 "github.com/docker/docker/api/types/registry"
@@ -89,6 +91,8 @@ const (
8991 logDriverTypeFirelens = "awsfirelens"
9092 logDriverTypeFluentd = "fluentd"
9193 logDriverTypeAwslogs = "awslogs"
94+ awsLogsEndpointKey = "awslogs-endpoint"
95+ awsLogsRegionKey = "awslogs-region"
9296 logDriverTag = "tag"
9397 logDriverMode = "mode"
9498 logDriverBufferSize = "max-buffer-size"
@@ -1945,29 +1949,33 @@ func (engine *DockerTaskEngine) createContainer(task *apitask.Task, container *a
19451949
19461950 // This is a short term solution only for specific regions
19471951 if hostConfig .LogConfig .Type == logDriverTypeAwslogs {
1948- region := engine .cfg .AWSRegion
1949- if _ , ok := unresolvedIsolatedRegions [region ]; ok {
1950- endpoint := ""
1951- dnsSuffix := ""
1952- partition , ok := ep .PartitionForRegion (ep .DefaultPartitions (), region )
1953- if ! ok {
1954- logger .Warn ("No partition resolved for region. Using AWS default" , logger.Fields {
1955- "region" : region ,
1956- "defaultDNSSuffix" : ep .AwsPartition ().DNSSuffix (),
1957- })
1958- dnsSuffix = ep .AwsPartition ().DNSSuffix ()
1959- } else {
1960- resolvedEndpoint , err := partition .EndpointFor ("logs" , region )
1961- if err == nil {
1962- endpoint = resolvedEndpoint .URL
1952+ if engine .cfg .InstanceIPCompatibility .IsIPv6Only () {
1953+ engine .setAWSLogsDualStackEndpoint (task , container , hostConfig )
1954+ } else {
1955+ region := engine .cfg .AWSRegion
1956+ if _ , ok := unresolvedIsolatedRegions [region ]; ok {
1957+ endpoint := ""
1958+ dnsSuffix := ""
1959+ partition , ok := ep .PartitionForRegion (ep .DefaultPartitions (), region )
1960+ if ! ok {
1961+ logger .Warn ("No partition resolved for region. Using AWS default" , logger.Fields {
1962+ "region" : region ,
1963+ "defaultDNSSuffix" : ep .AwsPartition ().DNSSuffix (),
1964+ })
1965+ dnsSuffix = ep .AwsPartition ().DNSSuffix ()
19631966 } else {
1964- dnsSuffix = partition .DNSSuffix ()
1967+ resolvedEndpoint , err := partition .EndpointFor ("logs" , region )
1968+ if err == nil {
1969+ endpoint = resolvedEndpoint .URL
1970+ } else {
1971+ dnsSuffix = partition .DNSSuffix ()
1972+ }
19651973 }
1974+ if endpoint == "" {
1975+ endpoint = fmt .Sprintf ("https://logs.%s.%s" , region , dnsSuffix )
1976+ }
1977+ hostConfig .LogConfig .Config [awsLogsEndpointKey ] = endpoint
19661978 }
1967- if endpoint == "" {
1968- endpoint = fmt .Sprintf ("https://logs.%s.%s" , region , dnsSuffix )
1969- }
1970- hostConfig .LogConfig .Config ["awslogs-endpoint" ] = endpoint
19711979 }
19721980 }
19731981
@@ -2946,3 +2954,104 @@ func (engine *DockerTaskEngine) getDockerID(task *apitask.Task, container *apico
29462954 }
29472955 return dockerContainer .DockerID , nil
29482956}
2957+
2958+ // Sets CloudWatch Logs dual stack endpoint as "awslogs-endpoint" option in the logging config.
2959+ // This is needed because awslogs driver that we consume from Docker does not support
2960+ // an option to enable dual stack endpoints, so customers have no way to enable dual stack endpoints
2961+ // that are needed in an IPv6-only environment.
2962+ func (engine * DockerTaskEngine ) setAWSLogsDualStackEndpoint (
2963+ task * apitask.Task , container * apicontainer.Container , hostConfig * dockercontainer.HostConfig ,
2964+ ) {
2965+ // Helper function to populate common logger.Fields
2966+ withAdditionalLoggerFields := func (additionalFields logger.Fields ) logger.Fields {
2967+ fields := logger.Fields {field .TaskARN : task .Arn , field .ContainerName : container .Name }
2968+ for k , v := range additionalFields {
2969+ fields [k ] = v
2970+ }
2971+ return fields
2972+ }
2973+
2974+ // Do nothing if endpoint is already set
2975+ if hostConfig .LogConfig .Config [awsLogsEndpointKey ] != "" {
2976+ logger .Info (
2977+ fmt .Sprintf (
2978+ "%s is already set in awslogs config, skip resolving dual stack CloudWatch Logs endpoint" ,
2979+ awsLogsEndpointKey ),
2980+ withAdditionalLoggerFields (logger.Fields {}),
2981+ )
2982+ return
2983+ }
2984+
2985+ // Region is required to resolve endpoint
2986+ region := hostConfig .LogConfig .Config [awsLogsRegionKey ]
2987+ if region == "" {
2988+ logger .Warn (
2989+ fmt .Sprintf (
2990+ "%s not found in awslogs config, skip resolving dual stack CloudWatch Logs endpoint" ,
2991+ awsLogsRegionKey ),
2992+ withAdditionalLoggerFields (logger.Fields {}),
2993+ )
2994+ return
2995+ }
2996+
2997+ // Docker versions older than 18.09.0 do not support awslogs-endpoint
2998+ // option. So, skip endpoint resolution for those Docker versions.
2999+ dockerVersion , err := engine .Version ()
3000+ if err != nil {
3001+ logger .Error ("Failed to get Docker engine version. Skip resolving dual stack CloudWatch Logs endpoint." ,
3002+ withAdditionalLoggerFields (logger.Fields {field .Error : err }))
3003+ return
3004+ }
3005+ const thresholdVersion = "18.09.0"
3006+ dockerVersionIsCompatible , err := utils .Version (dockerVersion ).Matches (">=" + thresholdVersion )
3007+ if err != nil {
3008+ logger .Error ("Failed to determine if docker version is high enough" ,
3009+ withAdditionalLoggerFields (logger.Fields {
3010+ field .Error : err ,
3011+ field .DockerVersion : dockerVersion ,
3012+ "thresholdVersion" : thresholdVersion ,
3013+ }))
3014+ return
3015+ }
3016+ if ! dockerVersionIsCompatible {
3017+ logger .Warn (
3018+ fmt .Sprintf (
3019+ "Docker version does not support %s option. Skip resolving dual stack CloudWatch Logs endpoint." ,
3020+ awsLogsEndpointKey ),
3021+ withAdditionalLoggerFields (logger.Fields {
3022+ field .DockerVersion : dockerVersion ,
3023+ "thresholdVersion" : thresholdVersion ,
3024+ }),
3025+ )
3026+ return
3027+ }
3028+
3029+ // Resolve the endpoint
3030+ endpoint , err := getAWSLogsDualStackEndpoint (region )
3031+ if err != nil {
3032+ logger .Error (
3033+ "Failed to get CloudWatch Logs dual stack endpoint. Skipping setting it." ,
3034+ withAdditionalLoggerFields (logger.Fields {field .Region : region , field .Error : err }))
3035+ return
3036+ }
3037+
3038+ logger .Info ("Resolved CloudWatch Logs dual stack endpoint" ,
3039+ withAdditionalLoggerFields (logger.Fields {
3040+ field .Endpoint : endpoint ,
3041+ field .Region : region ,
3042+ }))
3043+ hostConfig .LogConfig .Config [awsLogsEndpointKey ] = endpoint
3044+ }
3045+
3046+ // Returns CloudWatch Logs dual stack endpoint for the given region.
3047+ func getAWSLogsDualStackEndpoint (region string ) (string , error ) {
3048+ endpoint , err := cloudwatchlogs .NewDefaultEndpointResolverV2 ().ResolveEndpoint (context .TODO (),
3049+ cloudwatchlogs.EndpointParameters {
3050+ UseDualStack : ptr .Bool (true ),
3051+ Region : ptr .String (region ),
3052+ })
3053+ if err != nil {
3054+ return "" , fmt .Errorf ("failed to resolve dual stack CloudWatch Logs endpoint for region '%s': %w" , region , err )
3055+ }
3056+ return endpoint .URI .String (), nil
3057+ }
0 commit comments