昨天我們完成了基礎的容器化,今天要開始規劃 AWS 基礎設施。從接案經驗來看,很多小團隊會直接使用公有子網和預設 VPC(我基本上都這麼作,但對於 SaaS 產品,我們需要更嚴謹的網路架構設計。
為什麼不能用預設 VPC?
什麼是 VPC?
VPC 是你在 AWS 雲端中的虛擬網路,完全隔離且可自訂。它提供:
CIDR 區塊選擇策略:
常見的私有 IP 範圍:
- 10.0.0.0/8 (10.0.0.0 ~ 10.255.255.255) - 大型企業
- 172.16.0.0/12 (172.16.0.0 ~ 172.31.255.255) - 中型企業
- 192.168.0.0/16 (192.168.0.0 ~ 192.168.255.255) - 小型網路
Kyo-System 選擇:10.0.0.0/16 (65,536 個 IP)
為什麼需要多 AZ 部署?
台灣客戶的 AZ 選擇建議:
# ap-northeast-1 (東京) - 主要選擇
ap-northeast-1a # 主要 AZ
ap-northeast-1c # 備援 AZ
# 原因:
# 1. 與台灣地理位置最近 (~2000km)
# 2. 延遲通常 30-50ms
# 3. 服務種類最齊全
# 4. 價格相對合理
┌─────────────────────────────────────────────────────────────┐
│ Internet Gateway │
│ (igw-kyo-main) │
└─────────────────────┬───────────────────────────────────────┘
│
┌─────────────────────┴───────────────────────────────────────┐
│ Public Subnets │
├─────────────────┬─────────────────┬─────────────────────────┤
│ ALB Subnet │ NAT Gateway │ Bastion Host │
│ 10.0.1.0/24 │ 10.0.2.0/24 │ (管理用途) │
│ (AZ-1a) │ (AZ-1a, 1c) │ │
└─────────────────┴─────────────────┴─────────────────────────┘
│
┌─────────────────────┴───────────────────────────────────────┐
│ Private Subnets │
├─────────────────┬─────────────────┬─────────────────────────┤
│ App Subnet │ App Subnet │ Database Subnet │
│ 10.0.10.0/24 │ 10.0.11.0/24 │ 10.0.20.0/24 │
│ (AZ-1a) │ (AZ-1c) │ 10.0.21.0/24 │
│ ECS Tasks │ ECS Tasks │ (AZ-1a, 1c) │
│ ElastiCache │ ElastiCache │ RDS Multi-AZ │
└─────────────────┴─────────────────┴─────────────────────────┘
1. Public Subnets (公有子網)
2. Private Subnets - App Layer (私有子網 - 應用層)
3. Private Subnets - Database Layer (私有子網 - 資料庫層)
AWS CDK 優勢:
// CDK 的型別安全和 IDE 支援
const vpc = new ec2.Vpc(this, 'KyoVpc', {
maxAzs: 2,
cidr: '10.0.0.0/16',
subnetConfiguration: [
{
cidrMask: 24,
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
}
]
});
與 Terraform 比較:
特性 | AWS CDK | Terraform |
---|---|---|
學習曲線 | 中等(需要 TypeScript) | 陡峭(HCL 語法) |
型別安全 | ✅ 完整支援 | ❌ 有限支援 |
AWS 整合 | ✅ 官方支援,更新最快 | ✅ 社群維護 |
多雲端支援 | ❌ 僅 AWS | ✅ 支援多雲端 |
狀態管理 | ✅ CloudFormation 託管 | 需要自行管理 |
學習投資 | 低(使用熟悉語言) | 高(新語法) |
1. 初始化 CDK 專案
# 安裝 AWS CDK
npm install -g aws-cdk
# 建立 CDK 專案
mkdir kyong-saas/infra && cd kyong-saas/infra
npx cdk init app --language typescript
# 安裝必要套件
npm install @aws-cdk/aws-ec2 @aws-cdk/aws-ecs @aws-cdk/aws-rds @aws-cdk/aws-elasticache
2. CDK 專案結構
kyong-saas/infra/
├── bin/
│ └── kyo-infra.ts # CDK App 入口點
├── lib/
│ ├── network-stack.ts # VPC 和網路設定
│ ├── database-stack.ts # RDS 和 ElastiCache
│ ├── compute-stack.ts # ECS 和 Fargate
│ └── pipeline-stack.ts # CI/CD Pipeline
├── cdk.json # CDK 配置
└── package.json
3. VPC 和網路設定
// lib/network-stack.ts
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
export class NetworkStack extends cdk.Stack {
public readonly vpc: ec2.Vpc;
public readonly albSecurityGroup: ec2.SecurityGroup;
public readonly ecsSecurityGroup: ec2.SecurityGroup;
public readonly rdsSecurityGroup: ec2.SecurityGroup;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// VPC 建立
this.vpc = new ec2.Vpc(this, 'KyoVpc', {
maxAzs: 2,
cidr: '10.0.0.0/16',
subnetConfiguration: [
{
cidrMask: 24,
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'PrivateApp',
subnetType: ec2.SubnetType.PRIVATE_WITH_NAT,
},
{
cidrMask: 24,
name: 'PrivateDB',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
}
],
natGateways: 2, // 每個 AZ 一個 NAT Gateway (高可用)
});
// ALB Security Group
this.albSecurityGroup = new ec2.SecurityGroup(this, 'AlbSecurityGroup', {
vpc: this.vpc,
description: 'Security group for Application Load Balancer',
allowAllOutbound: true,
});
// 允許來自網際網路的 HTTP/HTTPS 流量
this.albSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(80),
'Allow HTTP traffic from anywhere'
);
this.albSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(443),
'Allow HTTPS traffic from anywhere'
);
// ECS Security Group
this.ecsSecurityGroup = new ec2.SecurityGroup(this, 'EcsSecurityGroup', {
vpc: this.vpc,
description: 'Security group for ECS tasks',
allowAllOutbound: true,
});
// 只允許來自 ALB 的流量
this.ecsSecurityGroup.addIngressRule(
this.albSecurityGroup,
ec2.Port.tcp(3000),
'Allow traffic from ALB'
);
// RDS Security Group
this.rdsSecurityGroup = new ec2.SecurityGroup(this, 'RdsSecurityGroup', {
vpc: this.vpc,
description: 'Security group for RDS database',
allowAllOutbound: false, // 資料庫不需要對外連線
});
// 只允許來自 ECS 的資料庫連線
this.rdsSecurityGroup.addIngressRule(
this.ecsSecurityGroup,
ec2.Port.tcp(5432),
'Allow PostgreSQL access from ECS'
);
// ElastiCache Security Group
const cacheSecurityGroup = new ec2.SecurityGroup(this, 'CacheSecurityGroup', {
vpc: this.vpc,
description: 'Security group for ElastiCache',
allowAllOutbound: false,
});
cacheSecurityGroup.addIngressRule(
this.ecsSecurityGroup,
ec2.Port.tcp(6379),
'Allow Redis access from ECS'
);
// VPC Endpoints (節省 NAT Gateway 費用)
this.vpc.addGatewayEndpoint('S3Endpoint', {
service: ec2.GatewayVpcEndpointAwsService.S3,
subnets: [{ subnetType: ec2.SubnetType.PRIVATE_WITH_NAT }],
});
this.vpc.addInterfaceEndpoint('ECREndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.ECR,
subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT },
});
// 輸出重要資源 ID 供其他 Stack 使用
new cdk.CfnOutput(this, 'VpcId', {
value: this.vpc.vpcId,
exportName: 'KyoVpcId',
});
new cdk.CfnOutput(this, 'PrivateSubnetIds', {
value: this.vpc.privateSubnets.map(subnet => subnet.subnetId).join(','),
exportName: 'KyoPrivateSubnetIds',
});
}
}
NAT Gateway 費用組成:
每小時費用:$0.045 USD
資料處理費:$0.045 USD/GB
月度估算 (雙 AZ):
- 固定費用:$0.045 × 24 × 30 × 2 = $64.8 USD
- 資料傳輸:$0.045 × 100GB = $4.5 USD
- 總計:約 $70 USD/月
成本最佳化選項:
單一 NAT Gateway (成本優先)
VPC Endpoints (推薦)
NAT Instance (進階)
// 加入更多 VPC Endpoints 減少 NAT 費用
const endpoints = [
{ service: ec2.InterfaceVpcEndpointAwsService.ECR },
{ service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER },
{ service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS },
{ service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER },
{ service: ec2.InterfaceVpcEndpointAwsService.SSM },
];
endpoints.forEach((endpoint, index) => {
this.vpc.addInterfaceEndpoint(`VpcEndpoint${index}`, {
service: endpoint.service,
subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_NAT },
securityGroups: [vpcEndpointSecurityGroup],
});
});
1. ap-northeast-1 (東京) - 首選
優點:
✅ 延遲最低 (30-50ms)
✅ 服務最齊全
✅ 價格合理
✅ 合規支援佳
缺點:
❌ 天災風險 (地震)
❌ 日圓匯率波動
2. ap-southeast-1 (新加坡) - 備選
優點:
✅ 政治穩定
✅ 東南亞樞紐
✅ 延遲可接受 (60-80ms)
缺點:
❌ 某些服務較晚推出
❌ 價格稍高
3. 多區域部署考量
// 未來可考慮的多區域架構
const regions = {
primary: 'ap-northeast-1', // 主要服務
secondary: 'ap-southeast-1', // 災難備援
edge: 'ap-northeast-2', // 韓國客戶
};
// 重要的網路監控指標
const networkMetrics = [
'NetworkIn', // 網路輸入
'NetworkOut', // 網路輸出
'NetworkLatency', // 網路延遲
'ConnectionCount', // 連線數量
'PacketDropCount', // 封包遺失
];
// 建立 CloudWatch Dashboard
const dashboard = new cloudwatch.Dashboard(this, 'NetworkDashboard', {
dashboardName: 'Kyo-Network-Monitoring',
widgets: [
new cloudwatch.GraphWidget({
title: 'VPC Network Performance',
left: [
new cloudwatch.Metric({
namespace: 'AWS/EC2',
metricName: 'NetworkIn',
statistic: 'Average',
}),
],
}),
],
});
✅ VPC 架構設計:三層網路分離,安全性最佳化
✅ 多 AZ 部署:高可用性架構規劃
✅ Security Groups:最小權限原則實作
✅ 成本最佳化:VPC Endpoints 減少 NAT 費用
✅ CDK 基礎設施:型別安全的 IaC 實作
✅ 監控準備:網路效能指標規劃
明天(Day6)我們將建立 RDS PostgreSQL 和 ElastiCache Redis,包含:
對新手來說,region 直觀會選 ap-east-2 台北。
請教一下,為甚麼 region 不選 ap-east-2 台北呢?
之前的習慣,以前在公司使用aws時還沒有ap-east-2 台北,都是選擇東京,剛有看台北定價,確實都比東京還要低一些,感謝提醒~
不過台北是不是有些服務沒有提供呢,像cognito, 不知道有沒有region支援服務的列表呢?
https://aws.amazon.com/tw/about-aws/global-infrastructure/regional-product-services/
可以從這邊來查看每個地區所支援的產品服務