Serve multiple services with single load balancer on AWS
Using a microservices architecture, serve multiple services from a single Application Load balancer On AWS. Here is how you can have a single domain serve multiple services where each service is hosted as a separate instace of either containers, EC2 instances or AWS Lambda.
The scenario
Lets assume you have a booking platform and you have divided your service into multiple Bounded Contexts. Assuming your domain is https://api.my-domain.com
, following are your resources under the same:
- Accomodation -
https://api.my-domain.com/accommodation/
- User -
https://api.my-domain.com/users/
- Concert -
https://api.my-domain.com/concerts/
A traditional approach would be hosting all this under a single application which has this one Visual Studio solution with multiple projects within, each concentrating on a Bounded Context
MyDomain.Service.Accommodation
- Everything related to Accommodation domainMyDomain.Service.Users
- Everything related to User domainMyDomain.Service.Concerts
- Everything related to Concert domainMyDomain.API
- MVC application with Controllers for Users, Concerts & Accommodation which has reference to the services above.
You would deploy this single MVC application with some autoscaling rules. This is great, but imagine you have separate teams, each working on one of the Bounded Contexts. Then you are left with multiple teams trying to work on the same source and a change to one Bounded Context means you have to re-deploy the whole thing. Life as a Developer/DevOps does not need to be this complex.
The alternative
On AWS, you could have a single Load Balancer which can then redirect requests to different services based on the the path of the request. The following illustration shows how to achieve this:
Let me walk you through what it does:
- A request comes to the loadbalancer
https://api.my-domain.com/accommodation/
. - The loadbalancer has a HTTPS Listener Rule which says if the host header is
api.my-domain.com
and the path matches the pattern/accommodation/*
, the request should be forwarded to a target group which has a bunch of EC2 instances which serve data related to Accommodation booking. - Likewise, a request
https://api.my-domain.com/users/
resolves to the rule which has host headerapi.my-domain.com
and the path matching the pattern/users/*
, which then gets forwarded to an Elastic Container Service target group, having multiple docker containers dealing with data related to Users. - And finally the last rule says if the path matches the pattern
/concerts/*
the request gets forwarded to a target group which points to a AWS Lambda.
Other than balancing your traffic, there are further benefits to this kind of approach:
- You are not restricted to using a single technology for all your services. You can have the Users service built in .Net while Concerts service built using Node. In the above illustration, the docker containers might be running a ASP.Net Web API while the Lambda, serving Concerts, is a Node lambda.
- Even if splitting your monolith into microservices seems like a massive task, you can still use this architecture where you deploy the monolith to multiple target groups each serving one Bounded Context. Though each instance will be running the whole monolith, requests coming to it will be dealing with a single Bounded Context. In fact, this is one of the mechanisms I used recently to troubleshoot a degrading Service which eventually was down to one endpoint which had non-performant code.
Creativity involves breaking out of established patterns in order to look at things in a different way. - Edward de Bono
Finally, If you know me well, you know I love Infrastructure As Code, so here is a sample Cloudformation template which demonstrates the illustration earlier:
Sample cloudformation template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
AWSTemplateFormatVersion: 2010-09-09
Description: Template to serve different endpoints of a domain with different physical services
Parameters:
ExistingVPC:
Type: String
Description: 'Existing VPC Id. REQUIRED'
LoadBalancerListenerARN:
Type: String
Description: 'ARN for the Load balancer'
AccommodationAMIId:
Type: String
Description: 'AMI Id of the image which has Accommodation service'
UsersAPIImagePath:
Type: String
Description: 'Docker Image path for Users Service'
ECSClusterARN:
Type: String
Description: 'ARN of ECS Cluster'
ArtifactsBucket:
Type: String
Description: 'The name of the bucket where the lambda build artifacts exist'
LambdaCodePackageObjectKey:
Type: String
Description: 'The s3 object key of the Concerts Lambda artifacts.'
Resources:
# Resources related to the Accommodation service
AccommodationLaunchConfiguration:
Type: AWS::AutoScaling::LaunchConfiguration
Properties:
ImageId: !Ref AccommodationAMIId
# Configuration ommitted for brevity
...
AccommodationScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
LaunchConfigurationName: !Ref AccommodationLaunchConfiguration
TargetGroupARNs:
- !Ref AccommodationEC2TargetGroup
HealthCheckType: ELB
HealthCheckGracePeriod: 60
AccommodationListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- TargetGroupArn: !Ref AccommodationEC2TargetGroup
Type: 'forward'
Conditions:
- Field: 'host-header'
Values:
- 'api.my-domain.com'
- Field: 'path-pattern'
Values: ['/accommodation/*']
ListenerArn: !Ref LoadBalancerListenerARN
Priority: 1
AccommodationEC2TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 60
HealthCheckPath: '/accommodation/healthcheck'
Port: 80
Protocol: 'HTTP'
VpcId: !Ref ExistingVPC
# Resources related to the Users service
UsersAPIECSTask:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Sub ${AWS::StackName}-api
ContainerDefinitions:
- Name: 'UsersAPI'
Image: !Ref UsersAPIImagePath
MemoryReservation: 128
Memory: 256
LogConfiguration:
LogDriver: "awslogs"
Options:
awslogs-group: !Ref APILogGroup
awslogs-region: !Ref "AWS::Region"
PortMappings:
- ContainerPort: 80
HostPort: 0
UsersAPIECSService:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref ECSClusterARN
DesiredCount: 1
TaskDefinition: !Ref UsersAPIECSTask
LoadBalancers:
- ContainerName: "UsersAPI"
ContainerPort: 80
TargetGroupArn: !Ref UsersECSTargetGroup
UsersListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- TargetGroupArn: !Ref UsersECSTargetGroup
Type: 'forward'
Conditions:
- Field: 'host-header'
Values:
- 'api.my-domain.com'
- Field: 'path-pattern'
Values: ['/users/*']
ListenerArn: !Ref LoadBalancerListenerARN
Priority: 2
UsersECSTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckIntervalSeconds: 60
HealthCheckPath: '/users/healthcheck'
Port: 80
Protocol: "HTTP"
VpcId: !Ref ExistingVPC
# Resources related to the Concerts service
ConcertsLambda:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Code:
S3Bucket: !Ref ArtifactsBucket
S3Key: !Ref LambdaCodePackageObjectKey
Runtime: nodejs12.x
Timeout: 30
MemorySize: 128
ConcertsListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
Actions:
- TargetGroupArn: !Ref ConcertsLambdaTargetGroup
Type: 'forward'
Conditions:
- Field: 'host-header'
Values:
- 'api.my-domain.com'
- Field: 'path-pattern'
Values: ['/concerts/*']
ListenerArn: !Ref LoadBalancerListenerARN
Priority: 3
ConcertsLambdaTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
HealthCheckEnabled: false
Name: ConcertsLambdaTarget
TargetType: lambda
Targets:
- Id: !GetAtt [ ConcertsLambda, Arn ]
Next steps
So you ask what next?
Well, I would suggest embracing modern trends and move on to Exploding your Monolith to Microservices. I will write up a post soon on this, until then Happy Coding!