我們的 ECS Container 不會只有單存一個服務自己跑,通常都會需要與資料庫做一個連結,所以今天就來說明如何讓 ECS Container 與 RDS 連結
先建立一個 RDS 與前面不一樣的地方是不直接指定密碼,而是讓 RDS 直接產生密碼到 AWS Secrets Manager,如此是比較安全的做法因為我們的程式不會保管密碼,也不會再部署的過程中看到密碼,要取得密碼就需要經過 Secrets Manager
const rdsInstance = new rds.DatabaseInstance(this, "Database", {
engine: rds.DatabaseInstanceEngine.mysql({
version: rds.MysqlEngineVersion.VER_8_0_19,
}),
vpc,
deleteAutomatedBackups: true,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.BURSTABLE3,
ec2.InstanceSize.MICRO
),
allocatedStorage: 10,
credentials: {
username: "admin",
},
});
要取得資料庫需要經過 Secrets Manager 而取得的過程需要使用 secretArn
new cdk.CfnOutput(this, "DatabaseSecretArn", {
value: rdsInstance.secret!.secretArn,
});
如此資料庫的地方就完畢了
因為測試使用就建立一個 ASG 就好
const cluster = new ecs.Cluster(this, "EcsCluster", { vpc });
const autoScalingGroup = cluster.addCapacity(
"DefaultAutoScalingGroupCapacity",
{
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MICRO
),
minCapacity: 1,
desiredCapacity: 1,
maxCapacity: 6,
machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
spotPrice: "0.0136",
spotInstanceDraining: true,
}
);
autoScalingGroup.scaleOnCpuUtilization("KeepCpuHalfwayLoaded", {
targetUtilizationPercent: 50,
});
這邊我們使用 DockerImageAsset 來讓本機跑一個 Dockerfile 編譯一個 Docker Image 上傳到 ECR 上面
const asset = new DockerImageAsset(this, "BuildImage", {
directory: path.join(__dirname, "../", "image"),
});
而這邊為了方便理解 Secrets Manager 是如何讓 ECS 吃到的密碼所以我寫了一個簡單的 Web,可以用來看所有 Docker 吃進去的 environment
所以只要建立一個資料夾 image 並且下載 web-app-env 就可以了
mkdir image
git clone https://github.com/clarencetw/web-app-env.git
Github: web-app-env
上傳成功的 Docker 可以在 ECR 看到它
這次的 Task 主要定義了普通的 environment
與 secret 而 secret 可以使用 ecs.Secret.fromSecretsManager(rdsInstance.secret!)
可以直接解出整個 rdsInstance.secret 的 JSON,而我這邊就直接把 rdsInstance 裡面的 JSON 直接解出來在程式使用上比較方便
const taskDefinition = new ecs.Ec2TaskDefinition(this, "TaskDef");
const container = taskDefinition.addContainer("DefaultContainer", {
image: ecs.ContainerImage.fromDockerImageAsset(asset),
memoryLimitMiB: 16,
logging: ecs.LogDrivers.awsLogs({ streamPrefix: "cdk-ecs" }),
environment: {
NODE_ENV: "production",
},
secrets: {
DB_ENGINE: ecs.Secret.fromSecretsManager(rdsInstance.secret!, "engine"),
DB_HOST: ecs.Secret.fromSecretsManager(rdsInstance.secret!, "host"),
DB_PORT: ecs.Secret.fromSecretsManager(rdsInstance.secret!, "port"),
DB_USERNAME: ecs.Secret.fromSecretsManager(
rdsInstance.secret!,
"username"
),
DB_PASSWORD: ecs.Secret.fromSecretsManager(
rdsInstance.secret!,
"password"
),
},
});
container.addPortMappings({
containerPort: 80,
});
設定完的 Task 可以直接在 ECS 看到指定成 Secrets Manager
const ecsService = new ecs.Ec2Service(this, "Service", {
cluster,
taskDefinition,
});
其實我們的服務只要在 CDK 建立成 service 在 LB 就很好控制了,我們只要在 targets 把 ecsService 放進去就可以了
const lb = new elbv2.ApplicationLoadBalancer(this, "LB", {
vpc,
internetFacing: true,
});
const listener = lb.addListener("Listener", { port: 80 });
const targetGroup = listener.addTargets("ECS", {
port: 80,
targets: [ecsService],
});
這邊要注意一下我們需要允許 ECS Service 可以存取 RDS,如果沒有設定會不能連線的
rdsInstance.connections.allowFrom(ecsService, ec2.Port.tcp(3306));
const vpc = new ec2.Vpc(this, "Vpc", { maxAzs: 3, natGateways: 1 });
const rdsInstance = new rds.DatabaseInstance(this, "Database", {
engine: rds.DatabaseInstanceEngine.mysql({
version: rds.MysqlEngineVersion.VER_8_0_19,
}),
vpc,
deleteAutomatedBackups: true,
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.BURSTABLE3,
ec2.InstanceSize.MICRO
),
allocatedStorage: 10,
credentials: {
username: "admin",
},
});
const cluster = new ecs.Cluster(this, "EcsCluster", { vpc });
const autoScalingGroup = cluster.addCapacity(
"DefaultAutoScalingGroupCapacity",
{
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.MICRO
),
minCapacity: 1,
desiredCapacity: 1,
maxCapacity: 6,
machineImage: ecs.EcsOptimizedImage.amazonLinux2(),
spotPrice: "0.0136",
spotInstanceDraining: true,
}
);
autoScalingGroup.scaleOnCpuUtilization("KeepCpuHalfwayLoaded", {
targetUtilizationPercent: 50,
});
const asset = new DockerImageAsset(this, "BuildImage", {
directory: path.join(__dirname, "../", "image"),
});
const taskDefinition = new ecs.Ec2TaskDefinition(this, "TaskDef");
const container = taskDefinition.addContainer("DefaultContainer", {
image: ecs.ContainerImage.fromDockerImageAsset(asset),
memoryLimitMiB: 16,
logging: ecs.LogDrivers.awsLogs({ streamPrefix: "cdk-ecs" }),
environment: {
NODE_ENV: "production",
},
secrets: {
DB_ENGINE: ecs.Secret.fromSecretsManager(rdsInstance.secret!, "engine"),
DB_HOST: ecs.Secret.fromSecretsManager(rdsInstance.secret!, "host"),
DB_PORT: ecs.Secret.fromSecretsManager(rdsInstance.secret!, "port"),
DB_USERNAME: ecs.Secret.fromSecretsManager(
rdsInstance.secret!,
"username"
),
DB_PASSWORD: ecs.Secret.fromSecretsManager(
rdsInstance.secret!,
"password"
),
},
});
container.addPortMappings({
containerPort: 80,
});
const ecsService = new ecs.Ec2Service(this, "Service", {
cluster,
taskDefinition,
});
const lb = new elbv2.ApplicationLoadBalancer(this, "LB", {
vpc,
internetFacing: true,
});
const listener = lb.addListener("Listener", { port: 80 });
const targetGroup = listener.addTargets("ECS", {
port: 80,
targets: [ecsService],
});
rdsInstance.connections.allowFrom(ecsService, ec2.Port.tcp(3306));
今天的服務主要是教大家如何在 ECS 使用一個可以連接 RDS 的服務,希望有幫助到大家 ~
文章內容主要是網路或是程式開發類型的文章
本文同步刊載於 Clarence 部落格:Day 21 - CDK 建置 Amazon Elastic Container Service(ECS)Service - EC2 與 RDS
「AWS CDK 完全學習手冊:打造雲端基礎架構程式碼 IaC」
本書改編並延伸自第 12 屆 iT 邦幫忙鐵人賽獲得 DevOps 組冠軍的《用 CDK 定義 AWS 架構》系列文章,以簡單、好讀的行文風格詳述技術細節,並提供完整的程式碼範例與說明,一步一步帶領新手從零開始踏上 AWS CDK 技術達人之路。有興趣的朋友歡迎至天瓏書局選購!