使用AWS CDK进行基础架构即代码编写:SpringBoot第二部分
你好。我正在努力学习AWS CDK。
接下来是前几节的延续。由于已经成功创建了Docker镜像,所以我们将使用该镜像来启动ECS。
顺便说一下,上次创建的 Docker 镜像就是这个。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myorg/spring-boot-sample-tomcat 0.1.1-SNAPSHOT cdcd3af69190 3 days ago 363MB
$
前提条件 (Paraphrase in Chinese): 此事的前提是
-
- AWS CDK で Infrastructure as Code する: ECS編
- AWS CDK で Infrastructure as Code する: SpringBoot編1
在完成等等。
创造环境
会是这种情况。
试一试
创建ECR以存储Docker镜像。
首先,创建一个存储自己的Docker镜像的Amazon ECR(Amazon Elastic Container Registry)仓库。虽然ECR可以在AWS CDK中创建,但这次我们会使用命令行快速创建。仓库名称将设为spring-boot-sample-tomcat。
$ aws ecr create-repository \
--repository-name spring-boot-sample-tomcat \
--image-scanning-configuration scanOnPush=true \
--region ap-northeast-1
{
"repository": {
"repositoryArn": "arn:aws:ecr:ap-northeast-1:xxxxx:repository/spring-boot-sample-tomcat",
"registryId": "xxxxx",
"repositoryName": "spring-boot-sample-tomcat",
"repositoryUri": "xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/spring-boot-sample-tomcat",
"createdAt": "2023-10-09T11:32:52+09:00",
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": true
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
}
看起来已经完成了。即使在AWS控制台上查看也是这样。
嗯,看起来可以做到。
使用Amazon ECR时,请参考AWS CLI。
将文件上传到 ECR。
接下来,将先前创建的Docker镜像上传到ECR中。由于上传需要登录ECR和进行一些准备工作,所以需要按照顺序进行操作。
再次确认想象中的情景。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myorg/spring-boot-sample-tomcat 0.1.1-SNAPSHOT cdcd3af69190 4 days ago 363MB
我想要上传的是这个图像(图像ID:cdcd3af69190)。
首先,登录到AWS的ECR。
$ ECR_REPOSITORY_NAME=spring-boot-sample-tomcat ← 先ほど作成したECRのリポジトリ名
$ version=0.1.1-SNAPSHOT ← バージョンはTAG名
$ AWS_REGION_NAME=ap-northeast-1
$ AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
↑ 使用しているプロファイルのアカウントIDを取得している
$ aws ecr --region ${AWS_REGION_NAME} get-login-password | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION_NAME}.amazonaws.com
WARNING! Your password will be stored unencrypted in /home/xxx/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
$
接下来,我们要为之前的图像添加标签。
$ REPOSITORY_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION_NAME}.amazonaws.com/${ECR_REPOSITORY_NAME}
$ docker image tag cdcd3af69190 ${REPOSITORY_URI}:${version}
↑ cdcd3af69190 というのは先ほど確認した IMAGE IDです
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/spring-boot-sample-tomcat 0.1.1-SNAPSHOT cdcd3af69190 4 days ago 363MB
myorg/spring-boot-sample-tomcat 0.1.1-SNAPSHOT cdcd3af69190 4 days ago 363MB
可以为具有相同的图像ID的图像添加标签(也就是说,可以给图像取别名)。
最后,将镜像上传(docker image push)。
$ docker image push ${REPOSITORY_URI}:${version}
The push refers to repository [xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/spring-boot-sample-tomcat]
7e1014d42cd2: Pushed
34f7184834b2: Pushed
5836ece05bfd: Pushed
72e830a4dff5: Pushed
0.1.1-SNAPSHOT: digest: sha256:37e2111ef45a00373393b766bd920d1648d5900448738b2434387566c4e3aef7 size: 1163
$
似乎已经完成了。我们来在屏幕上看一下吧。
听起来不错。
以上是ECR相关工作完成了。
获取CDK的源代码
既然可以将SpringBoot的Docker镜像上传到ECR,那么接下来我们将配置ECS服务来使用它。
由于仍然使用CDK,因此将获取上次(将nginx服务化为ECS时)的CDK源代码。
$ git clone -b ecs_first_nginx https://github.com/masatomix/cdk-samples.git
$
删除上一个ECS服务。
删除与ECS服务相关的部分之前,需要修复代码。因为之前创建的nginx ECS服务引用了另一个任务定义并造成冲突。首先,在CDK中删除ECS服务相关的内容。
$ cd cdk-samples
cdk-samples$ yarn cdk destroy ECSServiceStack ECSServiceELBStack
....
cdk-samples$
如果删除了,就可以了。
任务定义等的改写
现在是更改ECS使用的Docker镜像的源代码部分。
-
- image名としてnginxのイメージを指定していた箇所を、ECRのURLに変更
-
- コンテナのポート番号を80 → 8080へ変更(nginxとSpringBootはデフォで使用するポートがちがう)
- ヘルスチェックのURLを変更
以下是一些例子。具体来说如下:
import { App, ScopedAws, Stack, StackProps } from 'aws-cdk-lib'
import { ContainerInfo, getProfile } from './Utils'
import { CfnTaskDefinition } from 'aws-cdk-lib/aws-ecs'
import { CfnRole } from 'aws-cdk-lib/aws-iam'
type AppTaskdefinitionStackProps = StackProps & {
ecsTaskRole: CfnRole
ecsTaskExecutionRole: CfnRole
containerInfo: ContainerInfo
}
export class AppTaskdefinitionStack extends Stack {
public readonly taskDef: CfnTaskDefinition
constructor(scope: App, id: string, props: AppTaskdefinitionStackProps) {
super(scope, id, props)
const p = getProfile(this)
const { accountId, region } = new ScopedAws(this)
this.taskDef = new CfnTaskDefinition(this, 'ECSTaskDefinition', {
family: `${props.containerInfo.name}-taskdefinition${p.name}`,
containerDefinitions: [
{
essential: true,
// image: 'nginx',
ココ→ image: `${accountId}.dkr.ecr.${region}.amazonaws.com/spring-boot-sample-tomcat:0.1.1-SNAPSHOT`,
name: props.containerInfo.name,
logConfiguration: {
logDriver: 'awslogs',
options: {
'awslogs-create-group': 'true',
'awslogs-group': `/ecs/app-taskdefinition${p.name}`,
'awslogs-region': `${region}`,
'awslogs-stream-prefix': 'ecs',
},
},
memoryReservation: 100,
portMappings: [
{
containerPort: props.containerInfo.port,
hostPort: props.containerInfo.port,
protocol: 'tcp',
},
],
},
],
taskRoleArn: props.ecsTaskRole.attrArn,
executionRoleArn: props.ecsTaskExecutionRole.attrArn,
networkMode: 'awsvpc',
requiresCompatibilities: ['FARGATE'],
cpu: '256',
memory: '512',
})
}
}
#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from 'aws-cdk-lib'
import { VPCStack } from '../lib/VPCStack'
import { ELBStack } from '../lib/ELBStack'
import { ClusterStack } from '../lib/ClusterStack'
import { ECSRoleStack } from '../lib/ECSRoleStack'
import { ECSServiceStack } from '../lib/ECSServiceStack'
import { BastionStack } from '../lib/BastionStack'
import { ECSSecurityGroupStack } from '../lib/ECSSecurityGroupStack'
import { ECSServiceELBStack } from '../lib/ECSServiceELBStack'
import { AppTaskdefinitionStack } from '../lib/AppTaskdefinitionStack'
import { ContainerInfo, ServiceInfo } from '../lib/Utils'
const main = () => {
const app = new cdk.App()
const vpcStack = new VPCStack(app, 'VPCStack')
const sgStack = new ECSSecurityGroupStack(app, 'ECSSecurityGroupStack', { vpc: vpcStack.vpc })
const clusterStack = new ClusterStack(app, 'ClusterStack')
const ecsRoleStack = new ECSRoleStack(app, 'ECSRoleStack')
const elbStack = new ELBStack(app, 'ELBStack', {
subnets: vpcStack.publicSubnets,
elbSecuriyGroup: sgStack.ELBSecurityGroup,
})
const serviceInfo: ServiceInfo = {
serviceName: 'app-service',
listenerPort: 8080,
testListenerPort: 9080,
}
const containerInfo: ContainerInfo = {
name: 'app',
// port: 80,
// healthCheckPath: '/',
ココ→ port: 8080, // コンテナが利用するポート番号 が8080
ココ→ healthCheckPath: '/actuator/health', // ヘルスチェックもURLが異なる
}
const serviceStackELB = new ECSServiceELBStack(app, 'ECSServiceELBStack', {
loadbalancer: elbStack.loadbalancer,
vpc: vpcStack.vpc,
containerInfo,
serviceInfo,
})
const appTaskdefinition = new AppTaskdefinitionStack(app, 'AppTaskdefinitionStack', {
ecsTaskRole: ecsRoleStack.ecsTaskRole,
ecsTaskExecutionRole: ecsRoleStack.ecsTaskExecutionRole,
containerInfo,
})
const serviceStack = new ECSServiceStack(app, 'AppServiceStack', {
cluster: clusterStack.cluster,
subnets: vpcStack.privateSubnets, // ECSを配置するネットはPrivate Subnet
taskDef: appTaskdefinition.taskDef,
ecsSecurityGroup: sgStack.ECSSecurityGroup,
targetGroup: serviceStackELB.targetGroup,
containerInfo,
serviceInfo,
})
}
main()
运行CDK
现在开始运行CDK。像往常一样。
cdk-samples$ yarn cdk deploy --all
可以。完成后,我会查看URL并尝试访问。
$ aws elbv2 describe-load-balancers \
--query "LoadBalancers[*].[LoadBalancerName,DNSName]" \
--output table
---------------------------------------------------------------------------------------------------------------------
| DescribeLoadBalancers |
+---------------------------------+---------------------------------------------------------------------------------+
| ............ | xx.elb.ap-northeast-1.amazonaws.com |
| app-ELB-dev-20230827 | app-ELB-dev-20230827-1197243654.ap-northeast-1.elb.amazonaws.com |
+---------------------------------+---------------------------------------------------------------------------------+
$
既知道URL了,那就访问一下之前健康检查的URL吧。
$ curl http://app-ELB-dev-20230827-1197243654.ap-northeast-1.elb.amazonaws.com:8080/actuator/health -i
HTTP/1.1 200
Date: Mon, 09 Oct 2023 16:03:32 GMT
Content-Type: application/vnd.spring-boot.actuator.v3+json
Transfer-Encoding: chunked
Connection: keep-alive
Set-Cookie: AWSALB=oxq+VQxZTBSHBSgxLHBN2oiz1zRR69Qc2bPpyjCEdISbun1MGNh5YeJS/PqBdUtMyJUAb9HSmwyObePK/lNgblon9o0knhmgEw/AEuNaUAV8s0d82+6aw2s0zjfd; Expires=Mon, 16 Oct 2023 16:03:32 GMT; Path=/
Set-Cookie: AWSALBCORS=oxq+VQxZTBSHBSgxLHBN2oiz1zRR69Qc2bPpyjCEdISbun1MGNh5YeJS/PqBdUtMyJUAb9HSmwyObePK/lNgblon9o0knhmgEw/AEuNaUAV8s0d82+6aw2s0zjfd; Expires=Mon, 16 Oct 2023 16:03:32 GMT; Path=/; SameSite=None
{"status":"UP"}
$
好像SpringBoot应用正在运行!
通过使用以ECR为基础的SpringBoot图像,我们成功地运行了ECS服务。
辛苦了
相关链接 (Simplified Chinese)
-
- AWS CDKのTIPS集
-
- AWS CDK で Infrastructure as Code する: VPC編
-
- AWS CDK で Infrastructure as Code する: EC2編
-
- AWS CDK で Infrastructure as Code する: ECS編
-
- AWS CDK で Infrastructure as Code する: SpringBoot編1
- 今回追加したコード差分