20

I have a web app which runs behind Amazon AWS Elastic Load Balancer with 3 instances attached. The app has a /refresh endpoint to reload reference data. It need to be run whenever new data is available, which happens several times a week.

What I have been doing is assigning public address to all instances, and do refresh independently (using ec2-url/refresh). I agree with Michael's answer on a different topic, EC2 instances behind ELB shouldn't allow direct public access. Now my problem is how can I make elb-url/refresh call reaching all instances behind the load balancer?

And it would be nice if I can collect HTTP responses from multiple instances. But I don't mind doing the refresh blindly for now.

Community
  • 1
  • 1
Click2Death
  • 736
  • 1
  • 5
  • 18
  • If you add a public IP address to the ec2 how do you perform that refresh call? – Piyush Patil Sep 14 '16 at 19:47
  • @error2007s each of my instances has public DNS name like `ec2-123-123-123-123.compute-1.amazonaws.com`, I have to call `/refresh` on all of them independently. – Click2Death Sep 14 '16 at 19:53
  • And you do that manually each time when there is new data available? – Piyush Patil Sep 14 '16 at 19:55
  • @error2007s yes, I have to do that every time when new data comes in. When I call `/refresh` on ELB, only one server gets updated. Hence I'm looking for a solution to do all the leg work for me :) – Click2Death Sep 14 '16 at 20:10
  • For what it worth, here is [another question](http://stackoverflow.com/questions/35151042/is-it-possible-to-send-a-broadcast-message-to-all-instances-behind-the-elb) for the same thing. – Click2Death Sep 14 '16 at 20:19

8 Answers8

12

one of the way I'd solve this problem is by

  1. writing the data to an AWS s3 bucket
  2. triggering a AWS Lambda function automatically from the s3 write
  3. using AWS SDK to to identify the instances attached to the ELB from the Lambda function e.g. using boto3 from python or AWS Java SDK
  4. call /refresh on individual instances from Lambda
  5. ensuring when a new instance is created (due to autoscaling or deployment), it fetches the data from the s3 bucket during startup
  6. ensuring that the private subnets the instances are in allows traffic from the subnets attached to the Lambda
  7. ensuring that the security groups attached to the instances allow traffic from the security group attached to the Lambda

the key wins of this solution are

  • the process is fully automated from the instant the data is written to s3,
  • avoids data inconsistency due to autoscaling/deployment,
  • simple to maintain (you don't have to hardcode instance ip addresses anywhere),
  • you don't have to expose instances outside the VPC
  • highly available (AWS ensures the Lambda is invoked on s3 write, you don't worry about running a script in an instance and ensuring the instance is up and running)

hope this is useful.

redoc
  • 146
  • 1
  • 3
  • This is a good answer. I just want to point out that in this case, the lambda will need to be inside the VPC, otherwise it won't be able to reach the instances by their private IPs. When you do this, don't forget to allow outbound traffic on your lambda security group. – Kevin Heidt Aug 11 '18 at 17:04
7

While this may not be possible given the constraints of your application & circumstances, its worth noting that best practice application architecture for instances running behind an AWS ELB (particularly if they are part of an AutoScalingGroup) is ensure that the instances are not stateful.

The idea is to make it so that you can scale out by adding new instances, or scale-in by removing instances, without compromising data integrity or performance.

One option would be to change the application to store the results of the reference data reload into an off-instance data store, such as a cache or database (e.g. Elasticache or RDS), instead of in-memory.

If the application was able to do that, then you would only need to hit the refresh endpoint on a single server - it would reload the reference data, do whatever analysis and manipulation is required to store it efficiently in a fit-for-purpose way for the application, store it to the data store, and then all instances would have access to the refreshed data via the shared data store.

While there is a latency increase adding a round-trip to a data store, it is often well worth it for the consistency of the application - under your current model, if one server lags behind the others in refreshing the reference data, if the ELB is not using sticky sessions, requests via the ELB will return inconsistent data depending on which server they are allocated to.

Chris Simon
  • 6,185
  • 1
  • 22
  • 31
6

You can't make these requests through the load balancer, So you will have to open up the security group of the instances to allow incoming traffic from source other than the ELB. That doesn't mean you need to open it to all direct traffic though. You could simply whitelist an IP address in the security group to allow requests from your specific computer.

If you don't want to add public IP addresses to these servers then you will need to run something like a curl command on an EC2 instance inside the VPC. In that case you would only need to open the security group to allow traffic from some server (or group of servers) that exist in the VPC.

Mark B
  • 183,023
  • 24
  • 297
  • 295
  • Ah... maybe I'll make a new endpoint, whichever machine picked up the request will run a bunch of curl's hitting original `/refresh`. They are already in the same VPC. Thank for the quick answer, @Mark B. – Click2Death Sep 14 '16 at 20:02
  • 1
    Note that you will still have to open the security group up some. If you have one EB server sending the request to all the other EB servers, you would add an inbound rule in the security group the instances belong to, specifying that they allow inbound traffic from other instances in the same security group. – Mark B Sep 14 '16 at 20:20
  • Yup, I know exactly what you talking about. Adding a new rule in the security group is not a big deal. – Click2Death Sep 14 '16 at 20:24
  • I am considering having all of my applications that require this kind of "refresh" activity subscribe to a "refresh" topic on an enterprise message bus. – jkerak May 17 '17 at 16:57
5

I solved it differently, without opening up new traffic in security groups or resorting to external resources like S3. It's flexible in that it will dynamically notify instances added through ECS or ASG.

The ELB's Target Group offers a feature of periodic health check to ensure instances behind it are live. This is a URL that your server responds on. The endpoint can include a timestamp parameter of the most recent configuration. Every server in the TG will receive the health check ping within the configured Interval threshold. If the parameter to the ping changes it signals a refresh.

A URL may look like: /is-alive?last-configuration=2019-08-27T23%3A50%3A23Z

Above I passed a UTC timestamp of 2019-08-27T23:50:23Z

A service receiving the request will check if the in-memory state is at least as recent as the timestamp parameter. If not, it will refresh its state and update the timestamp. The next health-check will result in a no-op since your state was refreshed.

Implementation notes

If refreshing the state can take more time than the interval window or the TG health timeout, you need to offload it to another thread to prevent concurrent updates or outright service disruption as the health-checks need to return promptly. Otherwise the node will be considered off-line.

If you are using traffic port for this purpose, make sure the URL is secured by making it impossible to guess. Anything publicly exposed can be subject to a DoS attack.

enter image description here

Slawomir
  • 3,194
  • 1
  • 30
  • 36
2

As you are using S3 you can automate your task by using the ObjectCreated notification for S3.

https://docs.aws.amazon.com/AmazonS3/latest/dev/NotificationHowTo.html

https://docs.aws.amazon.com/cli/latest/reference/s3api/put-bucket-notification.html

You can install AWS CLI and write a simple Bash script that will monitor that ObjectCreated notification. Start a Cron job that will look for the S3 notification for creation of new object.

Setup a condition in that script file to curl "http: //127.0.0.1/refresh" when the script file detects new object created in S3 it will curl the 127.0.0.1/refresh and done you don't have to do that manually each time.

Piyush Patil
  • 14,512
  • 6
  • 35
  • 54
  • The data packages are ~500MB each, quite expensive to reload on a regular bases. To make it even more complicated, the `/refresh` takes an arg to specify which package to load, and they are all huge. – Click2Death Sep 14 '16 at 20:23
  • How do you detect that there is a new data available? – Piyush Patil Sep 14 '16 at 20:24
  • @error2007s a different team handles creating those files and drop into S3. – Click2Death Sep 14 '16 at 20:31
  • @error2007s thank you, will also look into S3 triggers. – Click2Death Sep 14 '16 at 21:05
  • I'm a little confused about how a bash script would monitor for push notifications? Is this assuming there's an SQS queue or something receiving the pushed notifications? What exactly is the bash script polling against? – Kevin Heidt Aug 11 '18 at 17:07
1

I personally like the answer by @redoc, but wanted to give another alternative for anyone that is interested, which is a combination of his and the accepted answer. Using SEE object creation events, you can trigger a lambda, but instead of discovering the instances and calling them, which requires the lambda to be in the vpc, you could have the lambda use SSM (aka Systems Manager) to execute commands via a powershell or bash document on EC2 instances that are targeted via tags. The document would then call 127.0.0.1/reload like the accepted answer has. The benefit of this is that your lambda doesn't have to be in the vpc, and your EC2s don't need inbound rules to allow the traffic from lambda. The downside is that it requires the instances to have the SSM agent installed, which sounds like more work than it really is. There's AWS AMIs already optimized with SSM agent stuff, but installing it yourself in the user data is very simple. Another potential downside, depending on your use case, is that it uses an exponential ramp up for simultaneous executions, which means if you're targeting 20 instances, it runs one 1, then 2 at once, then 4 at once, then 8, until they are all done, or it reaches what you set for the max. This is because of the error recovery stuff it has built in. It doesn't want to destroy all your stuff if something is wrong, like slowly putting your weight on some ice.

Kevin Heidt
  • 1,135
  • 1
  • 11
  • 14
0

You could make the call multiple times in rapid succession to call all the instances behind the Load Balancer. This would work because the AWS Load Balancers use round-robin without sticky sessions by default, meaning that each call handled by the Load Balancer is dispatched to the next EC2 Instance in the list of available instances. So if you're making rapid calls, you're likely to hit all the instances.

Another option is that if your EC2 instances are fairly stable, you can create a Target Group for each EC2 Instance, and then create a listener rule on your Load Balancer to target those single instance groups based on some criteria, such as a query argument, URL or header.

carlin.scott
  • 6,214
  • 3
  • 30
  • 35
0

This is how we solved this:

  • this solution is not aws specific, so it can be ported to other cloud platforms.
  • when the instance starts, a separate thread is started just to take care of such state changes
  • what this thread does: every n seconds it makes an S3 get call with If-modified-since header. The If-modified-since header helps to skip the updates in case if the file is not modified. Whenever the data needs to be updated, the S3 file is updated by external tools / users.
  • if the file is modified, this thread loads the new data from the file
  • it is important to note, for those n seconds all your instances will be working with different data. So you can use this method only for those cases where such a difference does not matter (eventually consistent).
ramu
  • 1,415
  • 1
  • 13
  • 12