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
CodeCommit
or Git for a single, versioned repository. - Dependencies: Declare dependencies in
Dockerfiles
(ECS) orrequirements.txt
(Lambda). - Config: Store environment-specific settings in
SSM Parameter Store
orSecrets Manager
. - Backing Services: Use managed services like
RDS
,DynamoDB
,SQS
, andSNS
as external resources. - Build, Release, Run: Automate with
CodePipeline
andCodeBuild
for distinct stages. - Processes: Run stateless apps on
Lambda
orECS
, storing state in backing services. - Port Binding: Expose services via
API Gateway
for Lambda orALB
for ECS. - Concurrency: Scale horizontally with
Lambda
concurrency orECS
task scaling. - Disposability: Design for fast startup/shutdown with
Lambda
orFargate
on ECS. - Dev/Prod Parity: Use
CloudFormation
orTerraform
to mirror environments. - Logs: Stream logs to
CloudWatch Logs
for centralized analysis. - Admin Processes: Run one-off tasks via
Lambda
orECS 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