使用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

在完成等等。

创造环境

会是这种情况。

env

试一试

创建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控制台上查看也是这样。

VYsRsNs

嗯,看起来可以做到。

使用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
$

似乎已经完成了。我们来在屏幕上看一下吧。

yNgUftj

听起来不错。
以上是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

 

    今回追加したコード差分
广告
将在 10 秒后关闭
bannerAds