The Application Migration scenario for serving static
content.
Single Page applications and static websites are quite
popular for hosting code. By their nature, they are extremely portable and can
run from filesystems as well as internet open directories. Yet, security and
performance are not always properly considered for their deployments. This articled delves into an architectural
pattern to host static website content in the public cloud.
We choose Amazon AWS as the public cloud for this scenario
but the pattern is universal and applies across most major public clouds.
When static content is hosted on AWS, the recommended
approach is to use an S3 bucket as the origin and the CloudFront to distribute
the content geographically. There are two primary benefits to this solution.
The first is the convenience of caching static content at edge locations. The
second involves defining web access control lists for the CloudFront
distribution. This helps to secure
requests to the content with minimal configuration and administrative overhead.
The only limitation to this standard recommended approach is
that in some cases, virtual firewall appliances may need to be deployed in a
virtual private cloud to inspect all content. The standard approach does not
route traffic through the virtual private cloud. Therefore, an alternative is
needed that still uses a CloudFront distribution to serve static content in an
S3 bucket but the traffic is routed through the VPC by using an Application
Load Balancer. An AWS Lambda function then retrieves and returns the content
from the S3 bucket.
The resources in this pattern must be in a single AWS
region, but they can be provisioned in different AWS accounts. The limits apply
to maximum request and response size that the Lambda function can receive and
send, respectively.
There must be a good balance between performance,
scalability, security and cost-effectiveness when using this approach. While
Lambda can scale for high availability, the number of concurrent executions
must not exceed the maximum quota otherwise requests will be denied.
The architecture lays out the CloudFront as facing the
client and communicating with a firewall and two load balancers – one in each
availability zone of the region hosting all these resources. The load balancers
are created in the public subnet within a virtual private cloud and both
communicate with a Lambda function that serves content from a private S3
bucket. When the client requests a URL, the CloudFront distribution forwards
the request to a firewall which filters the request using the web ACLs applied
to the CloudFront distribution. If the request cannot be served from the
internal cache within the CloudFront, it is forwarded to the load balancer
which has a listener associated with the target group based on a Lambda
function. When the Lambda function is invoked, it performs a GetObject
operation on the S3 bucket and returns the content as a response.
The deployment of the static content can be updated using a
Continuous Integration / Continuous Deployment facilitating pipeline.
The Lambda function introduced in this pattern can scale to
meet the load from all the load balancers and its security can be tightened by
specifying the origin as the S3 bucket and similary for the distribution to
have the origin as the load balancer. Instead of IP, dns name can be used to
refer to the resources.
The following code demonstrates the Lambda function:
var AWS = require('aws-sdk');
exports.handler = function(event, context, callback) {
var bucket = process.env.S3_BUCKET;
var key = event.path.replace('/', '');
if (key == '') { key = 'index.html'; }
// Fetch from S3 var s3 = new AWS.S3();
return s3.getObject({Bucket: bucket, Key: key},
function(err, data) {
if (err) { return err; }
var isBase64Encoded = false;
var encoding = 'utf8';
if (data.ContentType.indexOf('image/')
> -1) {
isBase64Encoded =
true;
encoding =
'base64'
}
var resp = { statusCode: 200,
headers: {
'Content-Type':
data.ContentType,
},
body: new
Buffer(data.Body).toString(encoding),
isBase64Encoded: isBase64Encoded
};
callback(null,
resp);
} );
};
No comments:
Post a Comment