Swiftorial Logo
Home
Swift Lessons
Tutorials
Learn More
Career
Resources

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.

The 12-factor principles guide the design of cloud-native apps, optimized for AWS services and DevOps workflows.

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.

graph TD %% Styling for nodes classDef cicd fill:#42a5f5,stroke:#1e88e5,stroke-width:2px,rx:5,ry:5; classDef compute fill:#ff6f61,stroke:#c62828,stroke-width:2px,color:#ffffff,rx:5,ry:5; classDef config fill:#fbc02d,stroke:#f9a825,stroke-width:2px,rx:5,ry:5; classDef backing fill:#2ecc71,stroke:#1b5e20,stroke-width:2px,color:#ffffff,rx:5,ry:5; classDef monitoring fill:#9b59b6,stroke:#6a1b9a,stroke-width:2px,rx:5,ry:5; %% Flow A[(CodePipeline)] -->|Deploys| B[Lambda] A -->|Deploys| C[ECS] B -->|Reads| D[(SSM Parameter Store)] C -->|Reads| D B -->|Interacts| E[(RDS)] B -->|Interacts| F[(DynamoDB)] B -->|Enqueues| G[(SQS)] B -->|Publishes| H[(SNS)] C -->|Interacts| E C -->|Interacts| F C -->|Enqueues| G C -->|Publishes| H B -->|Logs/Metrics| I[(CloudWatch)] C -->|Logs/Metrics| I %% Subgraphs for grouping subgraph CI/CD A end subgraph Compute B C end subgraph Configuration D end subgraph Backing Services E F G H end subgraph Monitoring I end %% Apply styles class A cicd; class B,C compute; class D config; class E,F,G,H backing; class I monitoring; %% Annotations linkStyle 0,1 stroke:#405de6,stroke-width:2.5px; linkStyle 2,3 stroke:#ff6f61,stroke-width:2.5px; linkStyle 4,5,6,7,8,9,10,11 stroke:#2ecc71,stroke-width:2.5px; linkStyle 12,13 stroke:#9b59b6,stroke-width:2.5px;
AWS services align with 12-factor principles to deliver scalable, decoupled, and observable applications.

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) or requirements.txt (Lambda).
  • Config: Store environment-specific settings in SSM Parameter Store or Secrets Manager.
  • Backing Services: Use managed services like RDS, DynamoDB, SQS, and SNS as external resources.
  • Build, Release, Run: Automate with CodePipeline and CodeBuild for distinct stages.
  • Processes: Run stateless apps on Lambda or ECS, storing state in backing services.
  • Port Binding: Expose services via API Gateway for Lambda or ALB for ECS.
  • Concurrency: Scale horizontally with Lambda concurrency or ECS task scaling.
  • Disposability: Design for fast startup/shutdown with Lambda or Fargate on ECS.
  • Dev/Prod Parity: Use CloudFormation or Terraform to mirror environments.
  • Logs: Stream logs to CloudWatch Logs for centralized analysis.
  • Admin Processes: Run one-off tasks via Lambda or ECS 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).
Stateless design and automated pipelines ensure robust 12-factor apps on AWS.

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}
                
This Lambda function aligns with the Config factor by using SSM Parameter Store.

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 = "*"
      }
    ]
  })
}
                
This Terraform configuration supports the Build, Release, Run factor with ECS and CodePipeline.

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
                
This CloudFormation template provisions DynamoDB and SQS as decoupled backing services.