Expose services using a LoadBalancer

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2020-04-16 15:15:39 +02:00
parent 17f3ff9db1
commit a44ee2a4ed
5 changed files with 175 additions and 9 deletions

View File

@ -76,10 +76,10 @@ func ComposeCommand(clusteropts *clusterOptions) *cobra.Command {
return cmd
}
func UpCommand(clusteropts *clusterOptions, opts *compose.ProjectOptions) *cobra.Command {
func UpCommand(clusteropts *clusterOptions, projectOpts *compose.ProjectOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "up",
RunE: compose.WithProject(opts, func(project *compose.Project, args []string) error {
RunE: compose.WithProject(projectOpts, func(project *compose.Project, args []string) error {
client, err := amazon.NewClient(clusteropts.profile, clusteropts.cluster, clusteropts.region)
if err != nil {
return err

View File

@ -6,6 +6,7 @@ import (
"github.com/aws/aws-sdk-go/service/cloudwatchlogs"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/docker/ecs-plugin/pkg/compose"
)
@ -31,6 +32,7 @@ func NewClient(profile string, cluster string, region string) (compose.API, erro
sess: sess,
ECS: ecs.New(sess),
EC2: ec2.New(sess),
ELB: elbv2.New(sess),
CW: cloudwatchlogs.New(sess),
IAM: iam.New(sess),
}, nil
@ -42,6 +44,7 @@ type client struct {
sess *session.Session
ECS *ecs.ECS
EC2 *ec2.EC2
ELB *elbv2.ELBV2
CW *cloudwatchlogs.CloudWatchLogs
IAM *iam.IAM
}

View File

@ -9,6 +9,11 @@ import (
)
func (c *client) ComposeDown(project *compose.Project) error {
err := c.DeleteLoadBalancer(project)
if err != nil {
return err
}
services := []*string{}
// FIXME we should be able to retrieve services by tags, so we don't need the initial compose file to run "down"
for _, service := range project.Services {
@ -23,18 +28,17 @@ func (c *client) ComposeDown(project *compose.Project) error {
if err != nil {
return err
}
logrus.Debugf("Service deleted %q\n", *out.Service.ServiceName)
services = append(services, out.Service.ServiceName)
services = append(services, out.Service.ServiceArn)
}
logrus.Info("All services stopped")
err := c.ECS.WaitUntilServicesInactive(&ecs.DescribeServicesInput{
logrus.Info("Stopping services...")
err = c.ECS.WaitUntilServicesInactive(&ecs.DescribeServicesInput{
Services: services,
})
if err != nil {
return err
}
logrus.Info("All services stopped")
logrus.Debug("Deleting security groups")
groups, err := c.EC2.DescribeSecurityGroups(&ec2.DescribeSecurityGroupsInput{

View File

@ -0,0 +1,133 @@
package amazon
import (
"fmt"
"github.com/docker/ecs-plugin/pkg/compose"
"github.com/sirupsen/logrus"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/elbv2"
"github.com/compose-spec/compose-go/types"
)
func (c client) CreateLoadBalancer(project *compose.Project, subnets []*string) (*string, error) {
logrus.Debug("Create Load Balancer")
alb, err := c.ELB.CreateLoadBalancer(&elbv2.CreateLoadBalancerInput{
IpAddressType: nil,
Name: aws.String(fmt.Sprintf("%s-LoadBalancer", project.Name)),
Subnets: subnets,
Type: aws.String(elbv2.LoadBalancerTypeEnumNetwork),
Tags: []*elbv2.Tag{
{
Key: aws.String("com.docker.compose.project"),
Value: aws.String(project.Name),
},
},
})
if err != nil {
return nil, err
}
return alb.LoadBalancers[0].LoadBalancerArn, nil
}
func (c client) DeleteLoadBalancer(project *compose.Project) error {
logrus.Debug("Delete Load Balancer")
// FIXME We can tag LoadBalancer but not search by tag ?
loadBalancer, err := c.ELB.DescribeLoadBalancers(&elbv2.DescribeLoadBalancersInput{
Names: aws.StringSlice([]string{fmt.Sprintf("%s-LoadBalancer", project.Name)}),
})
if err != nil {
return err
}
arn := loadBalancer.LoadBalancers[0].LoadBalancerArn
err = c.DeleteListeners(arn)
if err != nil {
return err
}
err = c.DeleteTargetGroups(arn)
if err != nil {
return err
}
_, err = c.ELB.DeleteLoadBalancer(&elbv2.DeleteLoadBalancerInput{LoadBalancerArn: arn})
return err
}
func (c client) CreateTargetGroup(name string, vpc *string, port types.ServicePortConfig) (*string, error) {
logrus.Debugf("Create Target Group %d/%s\n", port.Target, port.Protocol)
group, err := c.ELB.CreateTargetGroup(&elbv2.CreateTargetGroupInput{
Name: aws.String(name),
Port: aws.Int64(int64(port.Target)),
Protocol: aws.String(strings.ToUpper(port.Protocol)),
TargetType: aws.String("ip"),
VpcId: vpc,
})
if err != nil {
return nil, err
}
arn := group.TargetGroups[0].TargetGroupArn
return arn, nil
}
func (c client) DeleteTargetGroups(loadBalancer *string) error {
groups, err := c.ELB.DescribeTargetGroups(&elbv2.DescribeTargetGroupsInput{
LoadBalancerArn: loadBalancer,
})
if err != nil {
return err
}
for _, group := range groups.TargetGroups {
logrus.Debugf("Delete Target Group %s\n", *group.TargetGroupArn)
_, err := c.ELB.DeleteTargetGroup(&elbv2.DeleteTargetGroupInput{
TargetGroupArn: group.TargetGroupArn,
})
if err != nil {
return err
}
}
return nil
}
func (c client) CreateListener(port types.ServicePortConfig, arn *string, target *string) error {
logrus.Debugf("Create Listener %d\n", port.Published)
_, err := c.ELB.CreateListener(&elbv2.CreateListenerInput{
DefaultActions: []*elbv2.Action{
{
ForwardConfig: &elbv2.ForwardActionConfig{
TargetGroups: []*elbv2.TargetGroupTuple{
{
TargetGroupArn: target,
},
},
},
Type: aws.String(elbv2.ActionTypeEnumForward),
},
},
LoadBalancerArn: arn,
Port: aws.Int64(int64(port.Published)),
Protocol: aws.String(strings.ToUpper(port.Protocol)),
})
return err
}
func (c client) DeleteListeners(loadBalancer *string) error {
listeners, err := c.ELB.DescribeListeners(&elbv2.DescribeListenersInput{
LoadBalancerArn: loadBalancer,
})
if err != nil {
return err
}
for _, listener := range listeners.Listeners {
logrus.Debugf("Delete Listener %s\n", *listener.ListenerArn)
_, err := c.ELB.DeleteListener(&elbv2.DeleteListenerInput{
ListenerArn: listener.ListenerArn,
})
if err != nil {
return err
}
}
return nil
}

View File

@ -1,6 +1,7 @@
package amazon
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/compose-spec/compose-go/types"
@ -40,13 +41,37 @@ func (c *client) ComposeUp(project *compose.Project) error {
return err
}
loadBalancer, err := c.CreateLoadBalancer(project, subnets)
if err != nil {
return err
}
logGroup, err := c.GetOrCreateLogGroup(project)
if err != nil {
return err
}
for _, mapping := range mappings {
_, err = c.CreateService(project, mapping.service, mapping.task, securityGroup, subnets, logGroup)
ingress := []*ecs.LoadBalancer{}
for _, port := range mapping.service.Ports {
name := fmt.Sprintf("%s-%s-%d-%s", project.Name, mapping.service.Name, port.Target, port.Protocol)
targetgroup, err := c.CreateTargetGroup(name, vpc, port)
if err != nil {
return err
}
ingress = append(ingress, &ecs.LoadBalancer{
ContainerName: aws.String(mapping.service.Name),
ContainerPort: aws.Int64(int64(port.Target)),
TargetGroupArn: targetgroup,
})
err = c.CreateListener(port, loadBalancer, targetgroup)
if err != nil {
return err
}
}
_, err = c.CreateService(project, mapping.service, mapping.task, securityGroup, subnets, logGroup, ingress)
if err != nil {
return err
}
@ -54,7 +79,7 @@ func (c *client) ComposeUp(project *compose.Project) error {
return nil
}
func (c *client) CreateService(project *compose.Project, service *types.ServiceConfig, task *ecs.RegisterTaskDefinitionInput, securityGroup *string, subnets []*string, logGroup *string) (*string, error) {
func (c *client) CreateService(project *compose.Project, service *types.ServiceConfig, task *ecs.RegisterTaskDefinitionInput, securityGroup *string, subnets []*string, logGroup *string, ingress []*ecs.LoadBalancer) (*string, error) {
role, err := c.GetEcsTaskExecutionRole(service)
if err != nil {
return nil, err
@ -88,6 +113,7 @@ func (c *client) CreateService(project *compose.Project, service *types.ServiceC
ServiceName: aws.String(service.Name),
SchedulingStrategy: aws.String(ecs.SchedulingStrategyReplica),
TaskDefinition: arn,
LoadBalancers: ingress,
})
for _, port := range service.Ports {