Infrastructure Model (AWS) in ConnectSoft Templates¶
Purpose & Overview¶
Infrastructure Model (AWS) in ConnectSoft Templates refers to the Infrastructure as Code (IaC) project that defines, provisions, and manages AWS infrastructure resources using Pulumi. This project provides a programmatic, version-controlled, and repeatable way to define the infrastructure required to run applications in AWS cloud environments.
The AWS Infrastructure Model enables:
- Infrastructure as Code: Infrastructure defined in code, versioned with application code
- Cloud Resource Provisioning: Automated creation and management of AWS resources (ECS, EKS, RDS, DocumentDB, ElastiCache, etc.)
- Environment Consistency: Same infrastructure definitions across dev, staging, and production
- Reproducibility: Recreate infrastructure from code at any time
- Stack Management: Isolated infrastructure stacks for different environments
- Dependency Management: Infrastructure dependencies managed programmatically
- Configuration Management: Infrastructure configuration externalized and parameterized
AWS Infrastructure Model Philosophy
Infrastructure should be treated as code—defined, versioned, tested, and deployed just like application code. The AWS Infrastructure Model project provides a single source of truth for AWS cloud infrastructure, enabling teams to manage infrastructure changes with the same rigor as application changes.
Architecture Overview¶
Infrastructure Model Project Structure¶
InfrastructureModel/
├── Program.cs # Pulumi program entry point (supports Azure/AWS)
├── AzureInfrastructureStack.cs # Azure infrastructure stack definition
├── AWSInfrastructureStack.cs # AWS infrastructure stack definition
├── Pulumi.yaml # Pulumi project configuration
├── Pulumi.dev.yaml # Development stack configuration (Azure)
├── Pulumi.aws-dev.yaml # Development stack configuration (AWS)
└── *.csproj # Project file with Pulumi dependencies
AWS Infrastructure Stack Architecture¶
AWS Infrastructure Stack
├── Networking Infrastructure
│ ├── VPC (10.0.0.0/16)
│ ├── Internet Gateway
│ ├── NAT Gateway
│ ├── Public Subnets (ALB)
│ ├── Private Subnets (ECS Services)
│ ├── Database Subnets (RDS, DocumentDB)
│ ├── Cache Subnets (ElastiCache)
│ ├── Route Tables
│ └── Security Groups (ALB, ECS, RDS, DocumentDB, ElastiCache, EKS)
├── Compute Infrastructure
│ ├── ECS Cluster
│ ├── ECS Task Definitions (API, Worker)
│ ├── ECS Services (API, Worker) - Fargate
│ ├── Application Load Balancer (ALB)
│ ├── Target Groups
│ ├── EKS Cluster (Orleans Silo)
│ └── EKS Node Group
├── Database Infrastructure
│ ├── RDS Subnet Group
│ ├── RDS SQL Server Instance
│ ├── DocumentDB Subnet Group
│ ├── DocumentDB Cluster
│ └── DocumentDB Instance
├── Cache Infrastructure
│ ├── ElastiCache Subnet Group
│ └── ElastiCache Redis Cluster
├── Messaging Infrastructure
│ ├── SQS Queues (default-queue, worker-queue)
│ └── SNS Topic
├── Security Infrastructure
│ ├── Secrets Manager Secrets
│ ├── IAM Roles (ECS Task Execution, ECS Task, EKS Cluster, EKS Node)
│ └── IAM Policies
├── Monitoring Infrastructure
│ └── CloudWatch Log Groups (ECS, EKS)
└── Storage Infrastructure
└── S3 Bucket (with versioning and encryption)
Pulumi Stack Model¶
Pulumi Organization
├── Stack: dev (Azure)
│ └── InfrastructureModel (dev environment - Azure)
├── Stack: aws-dev (AWS)
│ └── InfrastructureModel (dev environment - AWS)
├── Stack: staging (Azure)
│ └── InfrastructureModel (staging environment - Azure)
└── Stack: prod (Azure)
└── InfrastructureModel (production environment - Azure)
Project Structure¶
InfrastructureModel Project¶
Project File (ConnectSoft.MicroserviceTemplate.InfrastructureModel.csproj):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup Label="Pulumi Libraries">
<PackageReference Include="Pulumi" />
<PackageReference Include="Pulumi.AzureNative" />
<PackageReference Include="Pulumi.Aws" />
</ItemGroup>
</Project>
Dependencies:
- Pulumi: Core Pulumi SDK for .NET
- Pulumi.AzureNative: Azure Native provider for Azure resources
- Pulumi.Aws: AWS Native provider for AWS resources
Program.cs¶
Entry Point with Multi-Cloud Support:
namespace ConnectSoft.MicroserviceTemplate.InfrastructureModel
{
using System;
using System.Threading.Tasks;
using Pulumi;
public static class Program
{
public static Task<int> Main()
{
// Determine which stack to deploy based on stack name or environment variable
var stackName = Deployment.Instance.StackName;
var cloudProvider = Environment.GetEnvironmentVariable("CLOUD_PROVIDER")
?? (stackName.Contains("aws", StringComparison.OrdinalIgnoreCase) ? "aws" : "azure");
return cloudProvider.ToLowerInvariant() switch
{
"aws" => Deployment.RunAsync<AWSInfrastructureStack>(),
"azure" => Deployment.RunAsync<AzureInfrastructureStack>(),
_ => throw new InvalidOperationException($"Unknown cloud provider: {cloudProvider}. Use 'azure' or 'aws'."),
};
}
}
}
Purpose: - Entry point for Pulumi program execution - Determines cloud provider from stack name or environment variable - Initializes and runs the appropriate stack (Azure or AWS) - Returns exit code (0 for success, non-zero for failure)
AWSInfrastructureStack.cs¶
Stack Definition:
The AWSInfrastructureStack class creates a complete AWS infrastructure stack with all required resources for the microservice template. It uses direct resource creation for self-contained infrastructure provisioning.
Key Components:
- Stack Class: Inherits from Pulumi.Stack to define infrastructure resources
- Config: Reads configuration from Pulumi stack config files
- Direct Resource Creation: Creates all resources directly in the stack
- IAM Roles: Task execution roles, task roles, and EKS roles with appropriate policies
- Secrets Management: All connection strings stored in AWS Secrets Manager
- Environment-Aware: Configurable instance types and settings based on environment (dev/prod)
- VPC Architecture: Multi-tier network architecture with public/private subnets
Resources Created:
- Networking: VPC, Internet Gateway, NAT Gateway, Subnets (Public, Private, Database, Cache), Route Tables, Security Groups
- Compute: ECS Cluster, ECS Task Definitions (API, Worker), ECS Services, Application Load Balancer, Target Groups, EKS Cluster, EKS Node Group
- Databases: RDS SQL Server Instance, DocumentDB Cluster and Instance
- Cache: ElastiCache Redis Cluster
- Messaging: SQS Queues, SNS Topic
- Security: Secrets Manager Secrets, IAM Roles and Policies
- Monitoring: CloudWatch Log Groups
- Storage: S3 Bucket with versioning and encryption
Pulumi.yaml¶
Project Configuration:
name: ConnectSoft.MicroserviceTemplate.InfrastructureModel
description: Infrastructure as a code for ConnectSoft.MicroserviceTemplate
runtime: dotnet
config:
pulumi:tags:
value:
pulumi:template: multi-cloud-csharp
Stack Configuration Files¶
Pulumi.aws-dev.yaml (AWS Development Stack):
config:
ConnectSoft.MicroserviceTemplate.InfrastructureModel:region: "us-east-1"
ConnectSoft.MicroserviceTemplate.InfrastructureModel:environment: "dev"
# VPC Configuration
ConnectSoft.MicroserviceTemplate.InfrastructureModel:vpcCidr: "10.0.0.0/16"
# RDS Configuration
ConnectSoft.MicroserviceTemplate.InfrastructureModel:rdsInstanceClass: "db.t3.small"
ConnectSoft.MicroserviceTemplate.InfrastructureModel:rdsMasterUsername: "sqladmin"
ConnectSoft.MicroserviceTemplate.InfrastructureModel:rdsMasterPassword:
secure: AAABAgAAAQAAAAA... # Set using: pulumi config set --secret rdsMasterPassword "YourPassword123!"
ConnectSoft.MicroserviceTemplate.InfrastructureModel:rdsDatabaseName: "MicroserviceTemplateDb"
# DocumentDB Configuration
ConnectSoft.MicroserviceTemplate.InfrastructureModel:documentDbInstanceClass: "db.t3.medium"
ConnectSoft.MicroserviceTemplate.InfrastructureModel:documentDbMasterPassword:
secure: AAABAgAAAQAAAAA... # Set using: pulumi config set --secret documentDbMasterPassword "YourPassword123!"
# ElastiCache Configuration
ConnectSoft.MicroserviceTemplate.InfrastructureModel:elasticacheNodeType: "cache.t3.small"
# ECS Configuration
ConnectSoft.MicroserviceTemplate.InfrastructureModel:ecsTaskCpu: 512
ConnectSoft.MicroserviceTemplate.InfrastructureModel:ecsTaskMemory: 1024
# EKS Configuration
ConnectSoft.MicroserviceTemplate.InfrastructureModel:eksNodeInstanceType: "t3.small"
Pulumi.aws-prod.yaml (AWS Production Stack):
Similar structure with production-specific values and larger instance types.
Environment-Specific Configuration¶
The stack supports environment-aware configuration:
- Development: Smaller instance types (t3.small), single-AZ databases, single ElastiCache node, basic EKS setup
- Production: Larger instance types (t3.medium+), multi-AZ databases, ElastiCache cluster mode, high availability EKS
Example Environment Differences:
| Resource | Development | Production |
|---|---|---|
| RDS Instance Class | db.t3.small | db.t3.medium+ |
| RDS Multi-AZ | No | Yes |
| DocumentDB Instance Class | db.t3.medium | db.r5.large |
| ElastiCache Node Type | cache.t3.small | cache.t3.medium+ |
| ElastiCache Cluster Mode | Single node | Multi-node cluster |
| ECS Task CPU | 512 | 1024+ |
| ECS Task Memory | 1024 MB | 2048+ MB |
| EKS Node Instance Type | t3.small | t3.medium+ |
| EKS Node Count | 1 | 2+ |
Resource Architecture¶
VPC Architecture¶
The AWS infrastructure uses a multi-tier VPC architecture:
VPC (10.0.0.0/16)
├── Public Subnet 1 (10.0.1.0/24) - Availability Zone 1 - ALB
├── Public Subnet 2 (10.0.2.0/24) - Availability Zone 2 - ALB
├── Private Subnet 1 (10.0.10.0/24) - Availability Zone 1 - ECS Services
├── Private Subnet 2 (10.0.11.0/24) - Availability Zone 2 - ECS Services
├── Database Subnet 1 (10.0.20.0/24) - Availability Zone 1 - RDS, DocumentDB
├── Database Subnet 2 (10.0.21.0/24) - Availability Zone 2 - RDS, DocumentDB
├── Cache Subnet 1 (10.0.30.0/24) - Availability Zone 1 - ElastiCache
└── Cache Subnet 2 (10.0.31.0/24) - Availability Zone 2 - ElastiCache
Network Components: - Internet Gateway: Provides internet access for public subnets - NAT Gateway: Provides outbound internet access for private subnets - Route Tables: Separate routing for public and private subnets - Security Groups: Network-level firewall rules
IAM Roles and Policies¶
All application services use IAM roles for secure access to AWS resources:
- ECS Task Execution Role: Pulls container images from ECR, writes to CloudWatch Logs
- ECS Task Role: Accesses Secrets Manager, SQS, SNS, S3, RDS, DocumentDB, ElastiCache
- EKS Cluster Role: Service role for EKS cluster
- EKS Node Role: Instance role for EKS worker nodes
IAM Policy Example:
var ecsTaskRolePolicy = new Aws.Iam.RolePolicy(
"ecsTaskRolePolicy",
new Aws.Iam.RolePolicyArgs
{
Role = ecsTaskRole.Id,
Policy = Output.JsonSerialize(Output.Create(new
{
Version = "2012-10-17",
Statement = new[]
{
new
{
Effect = "Allow",
Action = new[]
{
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
},
Resource = "*",
},
// ... additional statements
},
})),
});
Secrets Management¶
All connection strings and secrets are stored in AWS Secrets Manager:
- SQL Connection String
- DocumentDB Connection String
- Redis Connection String
- S3 Bucket Name
Applications access secrets using IAM roles, eliminating the need for hardcoded credentials.
Secrets Manager Example:
var sqlConnectionStringSecret = new Aws.SecretsManager.Secret(
"sqlConnectionStringSecret",
new Aws.SecretsManager.SecretArgs
{
Name = $"applicationtemplate-{environment}-sql-connection-string",
Description = "SQL Server connection string",
Tags = commonTags,
});
var sqlConnectionStringSecretVersion = new Aws.SecretsManager.SecretVersion(
"sqlConnectionStringSecretVersion",
new Aws.SecretsManager.SecretVersionArgs
{
SecretId = sqlConnectionStringSecret.Id,
SecretString = Output.Tuple(rdsInstance.Endpoint, rdsDatabaseName, rdsMasterUsername, rdsMasterPassword)
.Apply(t => $"Server={t.Item1};Database={t.Item2};User Id={t.Item3};Password={t.Item4};Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"),
});
AWS Resource Definitions¶
VPC and Networking¶
VPC Definition:
var vpc = new Aws.Ec2.Vpc(
"vpc",
new Aws.Ec2.VpcArgs
{
CidrBlock = "10.0.0.0/16",
EnableDnsHostnames = true,
EnableDnsSupport = true,
Tags = MergeTags(commonTags, new Dictionary<string, string> { { "Name", "applicationtemplate-vpc" } }),
});
Internet Gateway:
var internetGateway = new Aws.Ec2.InternetGateway(
"internetGateway",
new Aws.Ec2.InternetGatewayArgs
{
VpcId = vpc.Id,
Tags = MergeTags(commonTags, new Dictionary<string, string> { { "Name", "applicationtemplate-igw" } }),
});
NAT Gateway:
var natGatewayEip = new Aws.Ec2.Eip(
"natGatewayEip",
new Aws.Ec2.EipArgs
{
Domain = "vpc",
Tags = MergeTags(commonTags, new Dictionary<string, string> { { "Name", "applicationtemplate-nat-eip" } }),
});
var natGateway = new Aws.Ec2.NatGateway(
"natGateway",
new Aws.Ec2.NatGatewayArgs
{
AllocationId = natGatewayEip.Id,
SubnetId = publicSubnet1.Id,
Tags = MergeTags(commonTags, new Dictionary<string, string> { { "Name", "applicationtemplate-nat-gateway" } }),
});
Security Groups:
var albSecurityGroup = new Aws.Ec2.SecurityGroup(
"albSecurityGroup",
new Aws.Ec2.SecurityGroupArgs
{
Name = "applicationtemplate-alb-sg",
Description = "Security group for Application Load Balancer",
VpcId = vpc.Id,
Ingress = new[]
{
new Aws.Ec2.Inputs.SecurityGroupIngressArgs
{
Description = "HTTP",
FromPort = 80,
ToPort = 80,
Protocol = "tcp",
CidrBlocks = new[] { "0.0.0.0/0" },
},
new Aws.Ec2.Inputs.SecurityGroupIngressArgs
{
Description = "HTTPS",
FromPort = 443,
ToPort = 443,
Protocol = "tcp",
CidrBlocks = new[] { "0.0.0.0/0" },
},
},
Egress = new[]
{
new Aws.Ec2.Inputs.SecurityGroupEgressArgs
{
FromPort = 0,
ToPort = 0,
Protocol = "-1",
CidrBlocks = new[] { "0.0.0.0/0" },
},
},
Tags = MergeTags(commonTags, new Dictionary<string, string> { { "Name", "applicationtemplate-alb-sg" } }),
});
ECS (Elastic Container Service)¶
ECS Cluster:
var ecsCluster = new Aws.Ecs.Cluster(
"ecsCluster",
new Aws.Ecs.ClusterArgs
{
Name = $"applicationtemplate-{environment}-cluster",
Settings = new[]
{
new Aws.Ecs.Inputs.ClusterSettingArgs
{
Name = "containerInsights",
Value = "enabled",
},
},
Tags = commonTags,
});
ECS Task Definition:
var apiTaskDefinition = new Aws.Ecs.TaskDefinition(
"apiTaskDefinition",
new Aws.Ecs.TaskDefinitionArgs
{
Family = $"applicationtemplate-{environment}-api",
NetworkMode = "awsvpc",
RequiresCompatibilities = new[] { "FARGATE" },
Cpu = "512",
Memory = "1024",
ExecutionRoleArn = ecsTaskExecutionRole.Arn,
TaskRoleArn = ecsTaskRole.Arn,
ContainerDefinitions = Output.JsonSerialize(Output.Create(new[]
{
new
{
name = "api",
image = "mcr.microsoft.com/dotnet/aspnet:9.0",
essential = true,
portMappings = new[]
{
new
{
containerPort = 8080,
protocol = "tcp",
},
},
secrets = new[]
{
new { name = "SqlConnectionString", valueFrom = sqlConnectionStringSecret.Arn },
new { name = "CosmosConnectionString", valueFrom = documentDbConnectionStringSecret.Arn },
new { name = "RedisConnectionString", valueFrom = redisConnectionStringSecret.Arn },
},
logConfiguration = new
{
logDriver = "awslogs",
options = new Dictionary<string, string>
{
{ "awslogs-group", apiLogGroup.Name },
{ "awslogs-region", region },
{ "awslogs-stream-prefix", "ecs" },
},
},
},
})),
Tags = commonTags,
});
ECS Service:
var apiService = new Aws.Ecs.Service(
"apiService",
new Aws.Ecs.ServiceArgs
{
Name = $"applicationtemplate-{environment}-api",
Cluster = ecsCluster.Id,
TaskDefinition = apiTaskDefinition.Arn,
DesiredCount = environment == "prod" ? 2 : 1,
LaunchType = "FARGATE",
NetworkConfiguration = new Aws.Ecs.Inputs.ServiceNetworkConfigurationArgs
{
AssignPublicIp = false,
Subnets = new[] { privateSubnet1.Id, privateSubnet2.Id },
SecurityGroups = new[] { ecsSecurityGroup.Id },
},
LoadBalancers = new[]
{
new Aws.Ecs.Inputs.ServiceLoadBalancerArgs
{
TargetGroupArn = apiTargetGroup.Arn,
ContainerName = "api",
ContainerPort = 8080,
},
},
Tags = commonTags,
});
Application Load Balancer¶
ALB Definition:
var alb = new Aws.LB.LoadBalancer(
"alb",
new Aws.LB.LoadBalancerArgs
{
Name = $"applicationtemplate-{environment}-alb",
Internal = false,
LoadBalancerType = "application",
SecurityGroups = new[] { albSecurityGroup.Id },
Subnets = new[] { publicSubnet1.Id, publicSubnet2.Id },
EnableDeletionProtection = environment == "prod",
Tags = commonTags,
});
Target Group:
var apiTargetGroup = new Aws.LB.TargetGroup(
"apiTargetGroup",
new Aws.LB.TargetGroupArgs
{
Name = $"applicationtemplate-{environment}-api-tg",
Port = 8080,
Protocol = "HTTP",
VpcId = vpc.Id,
TargetType = "ip",
HealthCheck = new Aws.LB.Inputs.TargetGroupHealthCheckArgs
{
Enabled = true,
HealthyThreshold = 2,
UnhealthyThreshold = 3,
Timeout = 5,
Interval = 30,
Path = "/health",
Protocol = "HTTP",
Matcher = "200",
},
Tags = commonTags,
});
Listener:
var albListener = new Aws.LB.Listener(
"albListener",
new Aws.LB.ListenerArgs
{
LoadBalancerArn = alb.Arn,
Port = 80,
Protocol = "HTTP",
DefaultActions = new[]
{
new Aws.LB.Inputs.ListenerDefaultActionArgs
{
Type = "forward",
TargetGroupArn = apiTargetGroup.Arn,
},
},
});
EKS (Elastic Kubernetes Service)¶
EKS Cluster:
var eksClusterRole = new Aws.Iam.Role(
"eksClusterRole",
new Aws.Iam.RoleArgs
{
Name = "applicationtemplate-eks-cluster-role",
AssumeRolePolicy = Output.JsonSerialize(Output.Create(new
{
Version = "2012-10-17",
Statement = new[]
{
new
{
Effect = "Allow",
Principal = new { Service = "eks.amazonaws.com" },
Action = "sts:AssumeRole",
},
},
})),
Tags = commonTags,
});
var eksCluster = new Aws.Eks.Cluster(
"eksCluster",
new Aws.Eks.ClusterArgs
{
Name = $"applicationtemplate-{environment}-orleans",
RoleArn = eksClusterRole.Arn,
Version = "1.28",
VpcConfig = new Aws.Eks.Inputs.ClusterVpcConfigArgs
{
SubnetIds = new[] { privateSubnet1.Id, privateSubnet2.Id },
EndpointPrivateAccess = true,
EndpointPublicAccess = true,
},
EnabledClusterLogTypes = new[] { "api", "audit", "authenticator", "controllerManager", "scheduler" },
Tags = commonTags,
});
EKS Node Group:
var eksNodeGroup = new Aws.Eks.NodeGroup(
"eksNodeGroup",
new Aws.Eks.NodeGroupArgs
{
ClusterName = eksCluster.Name,
NodeGroupName = $"applicationtemplate-{environment}-orleans-nodegroup",
NodeRoleArn = eksNodeRole.Arn,
SubnetIds = new[] { privateSubnet1.Id, privateSubnet2.Id },
InstanceTypes = new[] { "t3.small" },
ScalingConfig = new Aws.Eks.Inputs.NodeGroupScalingConfigArgs
{
DesiredSize = environment == "prod" ? 2 : 1,
MaxSize = environment == "prod" ? 4 : 2,
MinSize = 1,
},
Tags = commonTags,
});
RDS (Relational Database Service)¶
RDS Subnet Group:
var rdsSubnetGroup = new Aws.Rds.SubnetGroup(
"rdsSubnetGroup",
new Aws.Rds.SubnetGroupArgs
{
Name = "applicationtemplate-rds-subnet-group",
SubnetIds = new[] { dbSubnet1.Id, dbSubnet2.Id },
Tags = commonTags,
});
RDS SQL Server Instance:
var rdsInstance = new Aws.Rds.Instance(
"rdsInstance",
new Aws.Rds.InstanceArgs
{
Identifier = $"applicationtemplate-{environment}-sql",
Engine = "sqlserver-ex",
EngineVersion = "15.00",
InstanceClass = rdsInstanceClass,
AllocatedStorage = environment == "prod" ? 100 : 20,
StorageType = "gp3",
StorageEncrypted = true,
DbName = rdsDatabaseName,
Username = rdsMasterUsername,
Password = rdsMasterPassword,
VpcSecurityGroupIds = new[] { rdsSecurityGroup.Id },
DbSubnetGroupName = rdsSubnetGroup.Name,
BackupRetentionPeriod = environment == "prod" ? 7 : 1,
MultiAz = environment == "prod",
PubliclyAccessible = false,
SkipFinalSnapshot = environment != "prod",
Tags = MergeTags(commonTags, new Dictionary<string, string> { { "Name", $"applicationtemplate-{environment}-sql" } }),
});
DocumentDB¶
DocumentDB Subnet Group:
var documentDbSubnetGroup = new Aws.DocDB.SubnetGroup(
"documentDbSubnetGroup",
new Aws.DocDB.SubnetGroupArgs
{
Name = "applicationtemplate-documentdb-subnet-group",
SubnetIds = new[] { dbSubnet1.Id, dbSubnet2.Id },
Tags = commonTags,
});
DocumentDB Cluster:
var documentDbCluster = new Aws.DocDB.Cluster(
"documentDbCluster",
new Aws.DocDB.ClusterArgs
{
ClusterIdentifier = $"applicationtemplate-{environment}-documentdb",
Engine = "docdb",
MasterUsername = "docdbadmin",
MasterPassword = config.RequireSecret("documentDbMasterPassword"),
DbSubnetGroupName = documentDbSubnetGroup.Name,
VpcSecurityGroupIds = new[] { documentDbSecurityGroup.Id },
BackupRetentionPeriod = environment == "prod" ? 7 : 1,
PreferredBackupWindow = "03:00-04:00",
PreferredMaintenanceWindow = "mon:04:00-mon:05:00",
StorageEncrypted = true,
EnabledCloudwatchLogsExports = new[] { "audit", "profiler" },
Tags = MergeTags(commonTags, new Dictionary<string, string> { { "Name", $"applicationtemplate-{environment}-documentdb" } }),
});
DocumentDB Instance:
var documentDbInstance = new Aws.DocDB.ClusterInstance(
"documentDbInstance",
new Aws.DocDB.ClusterInstanceArgs
{
Identifier = $"applicationtemplate-{environment}-documentdb-instance",
ClusterIdentifier = documentDbCluster.Id,
InstanceClass = documentDbInstanceClass,
Tags = MergeTags(commonTags, new Dictionary<string, string> { { "Name", $"applicationtemplate-{environment}-documentdb-instance" } }),
});
ElastiCache (Redis)¶
ElastiCache Subnet Group:
var elasticacheSubnetGroup = new Aws.ElastiCache.SubnetGroup(
"elasticacheSubnetGroup",
new Aws.ElastiCache.SubnetGroupArgs
{
Name = "applicationtemplate-elasticache-subnet-group",
SubnetIds = new[] { cacheSubnet1.Id, cacheSubnet2.Id },
});
ElastiCache Redis Cluster:
var elasticacheCluster = new Aws.ElastiCache.ReplicationGroup(
"elasticacheCluster",
new Aws.ElastiCache.ReplicationGroupArgs
{
ReplicationGroupId = $"applicationtemplate-{environment}-redis",
Description = "Redis cluster for applicationtemplate",
NodeType = elasticacheNodeType,
Port = 6379,
ParameterGroupName = "default.redis7",
NumCacheClusters = environment == "prod" ? 2 : 1,
AutomaticFailoverEnabled = environment == "prod",
MultiAzEnabled = environment == "prod",
SubnetGroupName = elasticacheSubnetGroup.Name,
SecurityGroupIds = new[] { elasticacheSecurityGroup.Id },
AtRestEncryptionEnabled = true,
TransitEncryptionEnabled = true,
Tags = commonTags,
});
SQS and SNS¶
SQS Queue:
var defaultQueue = new Aws.Sqs.Queue(
"defaultQueue",
new Aws.Sqs.QueueArgs
{
Name = $"applicationtemplate-{environment}-default-queue",
VisibilityTimeoutSeconds = 300,
MessageRetentionSeconds = 86400,
Tags = commonTags,
});
SNS Topic:
var snsTopic = new Aws.Sns.Topic(
"snsTopic",
new Aws.Sns.TopicArgs
{
Name = $"applicationtemplate-{environment}-topic",
Tags = commonTags,
});
S3 Bucket¶
S3 Bucket with Versioning and Encryption:
var s3Bucket = new Aws.S3.BucketV2(
"s3Bucket",
new Aws.S3.BucketV2Args
{
Bucket = $"applicationtemplate-{environment}-{Deployment.Instance.StackName}",
Tags = commonTags,
});
var s3BucketVersioning = new Aws.S3.BucketVersioningV2(
"s3BucketVersioning",
new Aws.S3.BucketVersioningV2Args
{
Bucket = s3Bucket.Id,
VersioningConfiguration = new Aws.S3.Inputs.BucketVersioningV2VersioningConfigurationArgs
{
Status = "Enabled",
},
});
var s3BucketEncryption = new Aws.S3.BucketServerSideEncryptionConfigurationV2(
"s3BucketEncryption",
new Aws.S3.BucketServerSideEncryptionConfigurationV2Args
{
Bucket = s3Bucket.Id,
Rules = new[]
{
new Aws.S3.Inputs.BucketServerSideEncryptionConfigurationV2RuleArgs
{
ApplyServerSideEncryptionByDefault = new Aws.S3.Inputs.BucketServerSideEncryptionConfigurationV2RuleApplyServerSideEncryptionByDefaultArgs
{
SseAlgorithm = "AES256",
},
},
},
});
CloudWatch Logs¶
CloudWatch Log Group:
var apiLogGroup = new Aws.CloudWatch.LogGroup(
"apiLogGroup",
new Aws.CloudWatch.LogGroupArgs
{
Name = "/ecs/applicationtemplate-api",
RetentionInDays = environment == "prod" ? 30 : 7,
Tags = commonTags,
});
Configuration Management¶
Stack Configuration¶
Reading Configuration:
var config = new Config();
// Read string value
var region = config.Get("region") ?? "us-east-1";
// Read required value (throws if missing)
var rdsMasterUsername = config.Require("rdsMasterUsername");
// Read secret value (encrypted in state)
var rdsMasterPassword = config.RequireSecret("rdsMasterPassword");
// Read integer value
var ecsTaskCpu = config.GetInt32("ecsTaskCpu") ?? 512;
Configuration Files¶
Pulumi.aws-dev.yaml:
config:
ConnectSoft.MicroserviceTemplate.InfrastructureModel:region: "us-east-1"
ConnectSoft.MicroserviceTemplate.InfrastructureModel:environment: "dev"
ConnectSoft.MicroserviceTemplate.InfrastructureModel:rdsMasterUsername: "sqladmin"
ConnectSoft.MicroserviceTemplate.InfrastructureModel:rdsMasterPassword:
secure: AAABAgAAAQAAAAA...
Setting Configuration via CLI:
# Set string value
pulumi config set region us-east-1
# Set secret value (encrypted)
pulumi config set --secret rdsMasterPassword "MyPassword123!"
# Set integer value
pulumi config set ecsTaskCpu 1024
Stack Outputs¶
Defining Outputs¶
Stack Outputs:
The stack exports all important resource identifiers, endpoints, and connection details:
public class AWSInfrastructureStack : Stack
{
// Networking outputs
[Output] public Output<string> VpcId { get; set; }
[Output] public Output<ImmutableArray<string>> PublicSubnetIds { get; set; }
[Output] public Output<ImmutableArray<string>> PrivateSubnetIds { get; set; }
// Compute outputs
[Output] public Output<string> AlbDnsName { get; set; }
[Output] public Output<string> EcsClusterName { get; set; }
[Output] public Output<string> ApiServiceName { get; set; }
[Output] public Output<string> WorkerServiceName { get; set; }
[Output] public Output<string> EksClusterName { get; set; }
// Database outputs
[Output] public Output<string> RdsEndpoint { get; set; }
[Output] public Output<string> DocumentDbEndpoint { get; set; }
// Cache outputs
[Output] public Output<string> ElasticacheEndpoint { get; set; }
// Messaging outputs
[Output] public Output<string> DefaultQueueUrl { get; set; }
[Output] public Output<string> WorkerQueueUrl { get; set; }
[Output] public Output<string> SnsTopicArn { get; set; }
// Security outputs
[Output] public Output<string> SqlConnectionStringSecretArn { get; set; }
[Output] public Output<string> DocumentDbConnectionStringSecretArn { get; set; }
[Output] public Output<string> RedisConnectionStringSecretArn { get; set; }
// Storage outputs
[Output] public Output<string> S3BucketName { get; set; }
// Monitoring outputs
[Output] public Output<string> ApiLogGroupName { get; set; }
[Output] public Output<string> WorkerLogGroupName { get; set; }
[Output] public Output<string> EksLogGroupName { get; set; }
}
Using Outputs¶
Reading Stack Outputs:
# Get all outputs
pulumi stack output
# Get specific output
pulumi stack output AlbDnsName
# Use in scripts
ALB_DNS=$(pulumi stack output AlbDnsName)
echo "ALB DNS: $ALB_DNS"
Deployment¶
Local Deployment¶
Prerequisites:
# Install Pulumi CLI
# Windows: choco install pulumi
# macOS: brew install pulumi
# Linux: curl -fsSL https://get.pulumi.com | sh
# Login to Pulumi Cloud (or use self-hosted)
pulumi login
# Configure AWS credentials
aws configure
# Or use environment variables:
# export AWS_ACCESS_KEY_ID=your-access-key
# export AWS_SECRET_ACCESS_KEY=your-secret-key
# export AWS_REGION=us-east-1
Deploy Stack:
# Select AWS stack
pulumi stack select aws-dev
# Or set environment variable
export CLOUD_PROVIDER=aws
pulumi stack select dev
# Preview changes (dry-run)
pulumi preview
# Deploy infrastructure
pulumi up
# Destroy infrastructure
pulumi destroy
CI/CD Integration¶
Azure DevOps Pipeline:
# azure-pipelines-infrastructure-model.yml
trigger:
branches:
include:
- master
paths:
include:
- ConnectSoft.MicroserviceTemplate.InfrastructureModel
- azure-pipelines-infrastructure-model.yml
steps:
- task: UseDotNet@2
displayName: 'Install .NET Core SDK'
inputs:
version: '9.x'
- task: PulumiCLI@1
displayName: 'Install Pulumi'
inputs:
versionSpec: 'latest'
- task: AWSShellScript@1
displayName: 'Configure AWS Credentials'
inputs:
awsCredentials: 'AWS Service Connection'
regionName: 'us-east-1'
- task: PulumiCLI@1
displayName: 'Pulumi Preview'
inputs:
command: 'preview'
stack: 'aws-dev'
cwd: 'ConnectSoft.MicroserviceTemplate.InfrastructureModel'
env:
CLOUD_PROVIDER: 'aws'
- task: PulumiCLI@1
displayName: 'Pulumi Up'
inputs:
command: 'up'
stack: 'aws-dev'
cwd: 'ConnectSoft.MicroserviceTemplate.InfrastructureModel'
args: '--yes'
env:
CLOUD_PROVIDER: 'aws'
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'))
GitHub Actions:
name: AWS Infrastructure Deployment
on:
push:
branches: [master]
paths:
- 'ConnectSoft.MicroserviceTemplate.InfrastructureModel/**'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-dotnet@v3
with:
dotnet-version: '9.x'
- uses: pulumi/actions@v3
with:
pulumi-version: latest
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy Infrastructure
run: |
cd ConnectSoft.MicroserviceTemplate.InfrastructureModel
export CLOUD_PROVIDER=aws
pulumi stack select aws-dev
pulumi up --yes
State Management¶
Pulumi State¶
State Storage Options:
- Pulumi Cloud (default):
- Managed state storage
- Automatic backups
- Team collaboration
-
Stack history
-
Self-Hosted State Backend:
- AWS S3
- Azure Blob Storage
- Google Cloud Storage
- Local file system (development only)
Configure State Backend:
# AWS S3
pulumi login --cloud-url s3://pulumi-state-bucket
# Azure Blob Storage
pulumi login --cloud-url azblob://pulumi-state
# Local file system (development only)
pulumi login --local
Best Practices¶
Do's¶
-
Version Control Infrastructure Code
-
Use IAM Roles for Service Access
-
Use Secrets Manager for Sensitive Values
-
Export Important Outputs
-
Use Environment-Specific Configuration
-
Preview Before Deploying
Don'ts¶
-
Don't Commit Secrets
-
Don't Hardcode Values
-
Don't Hardcode Resource Names
-
Don't Skip State Management
-
Don't Deploy Without Testing
Testing¶
Unit Testing¶
Test Infrastructure Code:
[TestMethod]
public async Task Stack_ShouldCreateEcsCluster()
{
// Arrange
var mocks = new Mocks();
var stack = new AWSInfrastructureStack();
// Act
var resources = await Testing.RunAsync(mocks, stack);
// Assert
var ecsCluster = resources.OfType<Aws.Ecs.Cluster>().FirstOrDefault();
Assert.IsNotNull(ecsCluster);
Assert.IsTrue(ecsCluster.Name.Apply(n => n.Contains("applicationtemplate")));
}
Integration Testing¶
Test Stack Deployment:
# Create test stack
pulumi stack init aws-test
# Deploy test stack
export CLOUD_PROVIDER=aws
pulumi up --yes
# Verify resources
aws ecs list-clusters --query "clusterArns[?contains(@, 'applicationtemplate')]"
# Clean up
pulumi destroy --yes
pulumi stack rm aws-test --yes
Troubleshooting¶
Issue: Stack Reference Not Found¶
Symptoms: Stack selection fails or wrong stack is selected.
Solutions:
1. Verify stack name contains "aws" for AWS stacks
2. Set CLOUD_PROVIDER environment variable explicitly
3. Check stack exists: pulumi stack ls
4. Verify stack configuration
Issue: Resource Creation Fails¶
Symptoms: pulumi up fails with resource creation errors.
Solutions:
1. Check AWS credentials: aws sts get-caller-identity
2. Verify resource quotas and limits
3. Check resource name availability (AWS requires unique names in some regions)
4. Review AWS service health status
5. Verify IAM permissions for resource creation
Issue: State Locked¶
Symptoms: "stack is currently locked" error.
Solutions:
1. Wait for previous operation to complete
2. Force unlock (if safe): pulumi cancel
3. Check for stuck operations in Pulumi Cloud
4. Verify no other processes are using the stack
Issue: Configuration Not Found¶
Symptoms: Config.Require() throws exception.
Solutions:
1. Check stack configuration: pulumi config
2. Set missing configuration: pulumi config set key value
3. Verify configuration key name matches exactly
4. Check stack context: pulumi stack
Issue: VPC CIDR Conflicts¶
Symptoms: VPC creation fails due to CIDR conflicts.
Solutions:
1. Verify VPC CIDR doesn't conflict with existing VPCs
2. Use different CIDR block: pulumi config set vpcCidr "10.1.0.0/16"
3. Check for overlapping CIDR blocks in the region
Integration with Application¶
Passing Infrastructure Outputs to Application¶
Option 1: Environment Variables
// In infrastructure stack
this.AlbDnsName = alb.DnsName;
// In deployment pipeline
ALB_DNS=$(pulumi stack output AlbDnsName)
aws ecs update-service --cluster my-cluster --service api-service --environment ALB_DNS=$ALB_DNS
Option 2: AWS Systems Manager Parameter Store
var albDnsParameter = new Aws.Ssm.Parameter(
"albDnsParameter",
new Aws.Ssm.ParameterArgs
{
Name = $"/applicationtemplate/{environment}/alb-dns",
Type = "String",
Value = alb.DnsName,
Tags = commonTags,
});
Option 3: Secrets Manager
var connectionStringSecret = new Aws.SecretsManager.Secret(
"connectionStringSecret",
new Aws.SecretsManager.SecretArgs
{
Name = $"applicationtemplate-{environment}-connection-string",
Description = "Application connection string",
Tags = commonTags,
});
Quick Start Guide¶
Prerequisites¶
-
Install Pulumi CLI:
-
Install .NET 9.0 SDK
-
AWS CLI (for authentication):
-
Pulumi Account: Sign up at pulumi.com or configure self-hosted backend
Initial Setup¶
-
Navigate to Infrastructure Project:
-
Login to Pulumi:
-
Select or Create Stack:
-
Configure Required Settings:
# Set AWS region pulumi config set region us-east-1 # Set RDS master password (encrypted) pulumi config set --secret rdsMasterPassword "YourSecurePassword123!" # Set DocumentDB master password (encrypted) pulumi config set --secret documentDbMasterPassword "YourSecurePassword123!" # Verify configuration pulumi config -
Preview Infrastructure:
-
Deploy Infrastructure:
-
View Stack Outputs:
Complete Resource List¶
The AWS infrastructure stack creates the following AWS resources:
Networking Resources: - 1 VPC - 1 Internet Gateway - 1 NAT Gateway - 1 Elastic IP - 8 Subnets (2 Public, 2 Private, 2 Database, 2 Cache) - 2 Route Tables (Public, Private) - 4 Route Table Associations - 6 Security Groups (ALB, ECS, RDS, DocumentDB, ElastiCache, EKS)
Compute Resources: - 1 ECS Cluster - 2 ECS Task Definitions (API, Worker) - 2 ECS Services (API, Worker) - 1 Application Load Balancer - 1 Target Group - 1 Listener - 1 EKS Cluster - 1 EKS Node Group
Database Resources: - 1 RDS Subnet Group - 1 RDS SQL Server Instance - 1 DocumentDB Subnet Group - 1 DocumentDB Cluster - 1 DocumentDB Instance
Cache & Messaging: - 1 ElastiCache Subnet Group - 1 ElastiCache Redis Replication Group - 2 SQS Queues - 1 SNS Topic
Security & Secrets: - 4 Secrets Manager Secrets - 4 Secrets Manager Secret Versions - 4 IAM Roles (ECS Task Execution, ECS Task, EKS Cluster, EKS Node) - 4 IAM Role Policy Attachments - 1 IAM Role Policy
Monitoring & Storage: - 3 CloudWatch Log Groups - 1 S3 Bucket - 1 S3 Bucket Versioning - 1 S3 Bucket Encryption
Total: ~50+ AWS resources per environment
AWS-Specific Considerations¶
Cost Optimization¶
Development Environment: - Use smaller instance types (t3.small) - Single-AZ deployments - Single ElastiCache node - Minimal EKS node count - Consider NAT Instance instead of NAT Gateway for cost savings
Production Environment: - Use appropriate instance types for workload - Multi-AZ deployments for high availability - ElastiCache cluster mode for scalability - Auto-scaling for EKS node groups - NAT Gateway for reliability
Service Limits¶
AWS Service Quotas: - VPCs per region: 5 (default) - Elastic IPs per region: 5 (default) - NAT Gateways per Availability Zone: 5 (default) - RDS instances per region: 40 (default) - ECS services per cluster: 1000 (default) - EKS clusters per account: 100 (default)
Requesting Quota Increases:
# Request quota increase via AWS Console or CLI
aws service-quotas request-service-quota-increase \
--service-code vpc \
--quota-code L-0263D0A3 \
--desired-value 10
Security Best Practices¶
- Network Security:
- Use private subnets for application workloads
- Restrict security group rules to minimum required
-
Use VPC endpoints for AWS service access (reduces NAT Gateway costs)
-
IAM Security:
- Follow principle of least privilege
- Use IAM roles instead of access keys
- Enable MFA for sensitive operations
-
Regularly rotate secrets
-
Data Security:
- Enable encryption at rest for all databases
- Enable encryption in transit (TLS/SSL)
- Use AWS Secrets Manager for all secrets
- Enable S3 bucket encryption
High Availability¶
Multi-AZ Deployment: - RDS: Enable Multi-AZ for production - DocumentDB: Deploy across multiple AZs - ElastiCache: Use cluster mode with multiple nodes - EKS: Deploy nodes across multiple AZs - ALB: Automatically spans multiple AZs
Backup and Recovery: - RDS automated backups: 7 days (production) - DocumentDB automated backups: 7 days (production) - S3 versioning enabled - CloudWatch Logs retention: 30 days (production)
Summary¶
AWS Infrastructure Model in the ConnectSoft ConnectSoft Templates provides:
- ✅ Infrastructure as Code: Version-controlled infrastructure definitions
- ✅ Pulumi Integration: Modern IaC tool with .NET support
- ✅ Stack Management: Isolated infrastructure per environment
- ✅ Direct Resource Creation: Self-contained, portable infrastructure stacks
- ✅ AWS Native Provider: Full AWS resource support
- ✅ Configuration Management: Environment-specific configurations
- ✅ IAM Roles: Secure, passwordless authentication
- ✅ Secrets Management: All secrets stored in Secrets Manager
- ✅ Output Management: Exported values for integration
- ✅ CI/CD Integration: Automated infrastructure deployment
- ✅ State Management: Reliable state storage and backup
- ✅ Testing Support: Unit and integration testing capabilities
- ✅ Complete Stack: All required resources for microservice template
- ✅ Multi-Cloud Support: Same project supports both Azure and AWS
By following these patterns, teams can:
- Manage Infrastructure as Code: Version, test, and deploy infrastructure like application code
- Ensure Consistency: Same infrastructure definitions across environments
- Enable Collaboration: Multiple team members can work on infrastructure safely
- Automate Deployments: Infrastructure changes deployed through CI/CD pipelines
- Maintain Traceability: Complete history of infrastructure changes
- Reduce Errors: Infrastructure validated before deployment
- Enable Rollbacks: Revert infrastructure changes when needed
- Support Multi-Cloud: Deploy to Azure or AWS from the same codebase
The AWS Infrastructure Model ensures that cloud infrastructure is managed with the same rigor, automation, and version control as application code, enabling reliable, repeatable, and scalable infrastructure deployments on AWS.