AWS 12-Factor App Cloud Native View
Introduction to AWS 12-Factor App Cloud Native View
The AWS 12-Factor App Cloud Native View implements the 12-factor methodology for building scalable, maintainable, and portable applications on AWS. It leverages Lambda and ECS for compute, SSM Parameter Store for configuration, CodePipeline for CI/CD, and decoupled backing services like RDS and DynamoDB. This approach ensures stateless, automated, and resilient cloud-native applications that align with modern software development practices, enabling rapid deployment and scalability across AWS environments.
12-Factor App Architecture Diagram
The diagram illustrates a 12-factor app on AWS: CodePipeline automates deployments of Lambda or ECS applications, with configurations stored in SSM Parameter Store. Applications interact with decoupled services like RDS, DynamoDB, SQS, and SNS. CloudWatch monitors logs and metrics. Arrows are color-coded: blue for CI/CD flow, green for application interactions, orange for configuration access, and purple for monitoring.
12-Factor Principles on AWS
The 12-factor methodology is implemented using AWS services as follows:
- Codebase: Store code in
CodeCommitor Git for a single, versioned repository. - Dependencies: Declare dependencies in
Dockerfiles(ECS) orrequirements.txt(Lambda). - Config: Store environment-specific settings in
SSM Parameter StoreorSecrets Manager. - Backing Services: Use managed services like
RDS,DynamoDB,SQS, andSNSas external resources. - Build, Release, Run: Automate with
CodePipelineandCodeBuildfor distinct stages. - Processes: Run stateless apps on
LambdaorECS, storing state in backing services. - Port Binding: Expose services via
API Gatewayfor Lambda orALBfor ECS. - Concurrency: Scale horizontally with
Lambdaconcurrency orECStask scaling. - Disposability: Design for fast startup/shutdown with
LambdaorFargateon ECS. - Dev/Prod Parity: Use
CloudFormationorTerraformto mirror environments. - Logs: Stream logs to
CloudWatch Logsfor centralized analysis. - Admin Processes: Run one-off tasks via
LambdaorECS RunTask.
Benefits of AWS 12-Factor App Cloud Native View
Adopting the 12-factor methodology on AWS provides significant advantages:
- Scalability: Horizontal scaling with Lambda and ECS supports dynamic workloads.
- Portability: Stateless design and backing services enable multi-cloud or hybrid deployments.
- Automation: CodePipeline and IaC streamline CI/CD and infrastructure management.
- Resilience: Decoupled services and disposability ensure fault tolerance.
- Consistency: Config management and dev/prod parity reduce environment drift.
- Observability: CloudWatch integration provides logs, metrics, and alerts.
- Cost Efficiency: Pay-per-use compute and optimized backing services minimize costs.
- Developer Productivity: Standardized practices accelerate development and onboarding.
Implementation Considerations
Implementing a 12-factor app on AWS requires addressing key considerations:
- Configuration Management: Secure sensitive data in SSM Parameter Store or Secrets Manager with KMS encryption.
- Stateless Design: Store session data in DynamoDB or ElastiCache to ensure process disposability.
- CI/CD Pipeline: Configure CodePipeline with testing, staging, and production stages for safe deployments.
- Scaling Policies: Use auto-scaling for ECS or provisioned concurrency for Lambda to handle load spikes.
- Security Practices: Apply least-privilege IAM roles, enable VPC for private access, and scan Docker images.
- Cost Optimization: Monitor usage with Cost Explorer and optimize Lambda duration or ECS task sizes.
- Logging Strategy: Use CloudWatch Logs Insights for querying and set retention policies for cost control.
- Testing Approach: Test locally with SAM (Lambda) or ECS CLI and simulate backing service interactions.
- Monitoring and Alerts: Set CloudWatch Alarms for key metrics (e.g., error rate, latency) and integrate with SNS.
- Compliance Requirements: Enable CloudTrail and configure logging for auditability (e.g., SOC 2, HIPAA).
Example Configuration: Lambda with SSM Parameter Store
Below is a Python Lambda function that retrieves configuration from SSM Parameter Store.
import json
import boto3
import os
ssm_client = boto3.client('ssm', region_name='us-west-2')
def lambda_handler(event, context):
try:
# Retrieve configuration from SSM Parameter Store
param = ssm_client.get_parameter(
Name='/my-app/db-url',
WithDecryption=True
)
db_url = param['Parameter']['Value']
# Example: Process event using configuration
data = json.loads(event['body'])
result = process_data(data, db_url)
return {
'statusCode': 200,
'body': json.dumps({'result': result})
}
except Exception as e:
print(f"Error: {e}")
return {
'statusCode': 500,
'body': json.dumps({'error': str(e)})
}
def process_data(data, db_url):
# Simulate database interaction
return {'id': data['id'], 'status': 'processed', 'db': db_url}
Example Configuration: ECS Task with CodePipeline
Below is a Terraform configuration for an ECS task with CodePipeline for CI/CD.
provider "aws" {
region = "us-west-2"
}
resource "aws_ecs_cluster" "my_cluster" {
name = "my-cluster"
}
resource "aws_ecs_task_definition" "my_task" {
family = "my-task"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "256"
memory = "512"
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
container_definitions = jsonencode([
{
name = "my-app"
image = "my-app:latest"
essential = true
portMappings = [
{
containerPort = 8080
hostPort = 8080
}
]
environment = [
{
name = "SSM_PARAM_PATH"
value = "/my-app/"
}
]
}
])
}
resource "aws_ecs_service" "my_service" {
name = "my-service"
cluster = aws_ecs_cluster.my_cluster.id
task_definition = aws_ecs_task_definition.my_task.arn
desired_count = 2
launch_type = "FARGATE"
network_configuration {
subnets = ["subnet-12345678", "subnet-87654321"]
security_groups = ["sg-12345678"]
}
}
resource "aws_codepipeline" "my_pipeline" {
name = "my-app-pipeline"
role_arn = aws_iam_role.codepipeline_role.arn
artifact_store {
location = aws_s3_bucket.pipeline_bucket.bucket
type = "S3"
}
stage {
name = "Source"
action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "CodeCommit"
version = "1"
output_artifacts = ["SourceArtifact"]
configuration = {
RepositoryName = "my-app-repo"
BranchName = "main"
}
}
}
stage {
name = "Build"
action {
name = "Build"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
version = "1"
input_artifacts = ["SourceArtifact"]
output_artifacts = ["BuildArtifact"]
configuration = {
ProjectName = aws_codebuild_project.my_build.name
}
}
}
stage {
name = "Deploy"
action {
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "ECS"
version = "1"
input_artifacts = ["BuildArtifact"]
configuration = {
ClusterName = aws_ecs_cluster.my_cluster.name
ServiceName = aws_ecs_service.my_service.name
FileName = "imagedefinitions.json"
}
}
}
}
resource "aws_s3_bucket" "pipeline_bucket" {
bucket = "my-pipeline-bucket-123"
}
resource "aws_iam_role" "ecs_task_execution_role" {
name = "ecs-task-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "ecs_task_policy" {
name = "ecs-task-policy"
role = aws_iam_role.ecs_task_execution_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ssm:GetParameters",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "*"
}
]
})
}
resource "aws_iam_role" "codepipeline_role" {
name = "codepipeline-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "codepipeline.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "codepipeline_policy" {
name = "codepipeline-policy"
role = aws_iam_role.codepipeline_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:*",
"codecommit:*",
"codebuild:*",
"ecs:*",
"iam:PassRole"
]
Resource = "*"
}
]
})
}
resource "aws_codebuild_project" "my_build" {
name = "my-app-build"
service_role = aws_iam_role.codebuild_role.arn
artifacts {
type = "CODEPIPELINE"
}
environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/standard:5.0"
type = "LINUX_CONTAINER"
}
source {
type = "CODEPIPELINE"
}
}
resource "aws_iam_role" "codebuild_role" {
name = "codebuild-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "codebuild.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "codebuild_policy" {
name = "codebuild-policy"
role = aws_iam_role.codebuild_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"logs:*",
"s3:*",
"ecs:*"
]
Resource = "*"
}
]
})
}
Example Configuration: CloudFormation for Backing Services
Below is a CloudFormation template to provision DynamoDB and SQS as backing services.
AWSTemplateFormatVersion: '2010-09-09'
Description: Provisions DynamoDB and SQS for 12-factor app
Resources:
MyDynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: MyAppTable
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
Tags:
- Key: Environment
Value: production
MySQSQueue:
Type: AWS::SQS::Queue
Properties:
QueueName: MyAppQueue
Tags:
- Key: Environment
Value: production
MyIAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: AppBackingServiceRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: BackingServiceAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:PutItem
- dynamodb:GetItem
- sqs:SendMessage
- sqs:ReceiveMessage
Resource:
- !GetAtt MyDynamoDBTable.Arn
- !GetAtt MySQSQueue.Arn
Outputs:
TableArn:
Value: !GetAtt MyDynamoDBTable.Arn
QueueArn:
Value: !GetAtt MySQSQueue.Arn
