使用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
 
- 今回追加したコード差分
 
    