0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

CDKで構築するECS+ALB環境:Spring BootでHelloWorldアプリをデプロイ

Last updated at Posted at 2024-09-23

はじめに

この記事ではSpring BootでHello Worldアプリを作成し、CDKで構築したECS+ALB環境で動作させたいと思います。

前提

  • VPC・Subnetは既に作成されている
  • 動作環境はM1 MacBook

目次

  1. リソース構成
  2. HelloWorldアプリを作成
  3. CDKでインフラを定義する
  4. CDKを使ったdeploy
  5. まとめ
  6. 参考文献

リソース構成

HelloWorldアプリを作成

spring initializrで雛形を作成します
spring initializr
記事作成時は以下を設定しgenerateしました

Project:Gradle-Groovy
Artifact:HelloWorld
Spring Boot:3.3
Packaging:Jar
Java:21
Dependencies:Spring Web

HelloWorldを返すControllerを作成します
main/java/com/example/HelloWorld以下にIndexController.javaを作成します

IndexController.java
package com.example.HelloWorld;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/")
public class IndexController {
    @RequestMapping
    public String index() {
        return "HelloWorld";
    }
}

Dockerfileを作成します

FROM openjdk:21
EXPOSE 8080
RUN microdnf install findutils
WORKDIR /var/www/html/server
COPY .. /var/www/html/server
RUN ./gradlew build
ENTRYPOINT ["java", "-jar" , "build/libs/HelloWorld-0.0.1-SNAPSHOT.jar"]

動作確認
以下のコマンドを実行します
curl実行後にHelloWorldが出力されればOKです

% docker build -f app/Dockerfile -t hello-world .
% docker run --rm --name hello-world-container -p 8080:8080 -it hello-world
% curl http://localhost:8080

CDKでインフラを定義する

CDKのテンプレートを作成します
以下のコマンドを実行すると、typescriptファイルが作成されます

% npx cdk init --language=typescript

stackを定義します

cdk-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import  * as ecr from 'aws-cdk-lib/aws-ecr';
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as ecs from "aws-cdk-lib/aws-ecs";

export class HelloWorldStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const projectName = "hello-world";
    const vpcId = "{vpcのid}";
    const availabilityZones = ["{vpcで定義されているavailabilityZone}"];
    const publicSubnetIds = ["{vpcで定義されているpublicSubnetId}"];
    const publicSubnetRouteTableIds = ["{public subnetに紐づくRouteTableId}"];

    // Create ECR Repository
    const ecrRepository = new ecr.Repository(this, "EcrRepo", {
      repositoryName: `${projectName}-ecr-repo`,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      emptyOnDelete: true,
    });

    // find VPC
    const vpc = ec2.Vpc.fromVpcAttributes(this, "Vpc", {
        vpcId: vpcId,
        availabilityZones: availabilityZones,
        publicSubnetIds: publicSubnetIds,
        publicSubnetRouteTableIds: publicSubnetRouteTableIds
    });

    // Create ECS Cluster
    const cluster = new ecs.Cluster(this, "EcsCluster", {
      clusterName: `${projectName}-cluster`,
      vpc: vpc,
    });

    // Create ALB and ECS Fargate Service
    const service = new cdk.aws_ecs_patterns.ApplicationLoadBalancedFargateService(
        this,
        "FargateService",
        {
          loadBalancerName: `${projectName}-lb`,
          publicLoadBalancer: true,
          cluster: cluster,
          serviceName: `${projectName}-service`,
          cpu: 256,
          memoryLimitMiB: 512,
          assignPublicIp: true,
          taskSubnets: {subnetType: ec2.SubnetType.PUBLIC},
          taskImageOptions: {
            family: `${projectName}-taskdef`,
            containerName: `${projectName}-container`,
            image: ecs.ContainerImage.fromEcrRepository(ecrRepository, "latest"),
            logDriver: new ecs.AwsLogDriver({
              streamPrefix: `container`,
            }),
            containerPort: 8080,
          },
          runtimePlatform: {
            cpuArchitecture: ecs.CpuArchitecture.ARM64
          },
        }
    );
  }
}

基本的には
ECSとECRのコンテナ構成をCDKで実装してみたの記事通りに設定しているのですが、何点かハマったところがあるので解説していきます。

VPCの取得が上手くいかなかった

// find VPC
const vpc = ec2.Vpc.fromVpcAttributes(this, "Vpc", {
    vpcId: vpcId,
    availabilityZones: availabilityZones,
    publicSubnetIds: publicSubnetIds,
    publicSubnetRouteTableIds: publicSubnetRouteTableIds
});

VPCを取得するコードです
fromVpcAttributesにはpublicSubnetIdspublicSubnetRouteTableIdsが必須ではないですが、publicSubnetIdsがない状態だと、diff実行時にVPCが適切に取得できず以下のエラーを出力する場合があります

Error: There are no 'Public' subnet groups in this VPC. Available types:

また、publicSubnetRouteTableIdsを指定しない場合はrouteTableを認識できていないため以下のwarningが出力されます

[Warning at /CdkStack/Vpc/PublicSubnet1] No routeTableId was provided to the subnet ''. Attempting to read its .routeTable.routeTableId will return null/undefined. 

実行環境の指定

runtimePlatform: {
    cpuArchitecture: ecs.CpuArchitecture.ARM64
}

実行環境をARM64に設定しています
ECS FargateのデフォルトがX86_64のようなので、M1 MacBookでイメージをビルドしている関係上、そちらに合わせる必要があります
ECS実行時に以下のエラーが出力されていると、上記が原因の可能性があるので、確認してみてください

exec /usr/java/openjdk-21/bin/java: exec format error

portの指定

containerPort: 8080

今回アプリケーションを8080ポートで実行しているので、ALBからECSへの8080ポートを通過可能にする必要があります
設定していなければ、HealthCheckがずっと通らずにECSのdeployが終わらないので、deployがなかなか終わらない時に確認してみてください

CDKを使ったdeploy

deployしていくのですが、初回実行時に注意点があるので、初回実行時と2回目以降で分けて解説していきます

初回実行時

初回実行時にそのままdeployをしてしまうと、ECRにイメージがpushされていないので、ECSがイメージを取得できずエラーになってしまいます
そのため、まずはECRのみを作成→イメージをpush→ECS+ALBをdeployするという手順を踏む必要があります
cdk-ecr-deploymentを使うと、そこも上手くやってくれるようなのですが、今回は上手く動作させることができなかったので別々の手順を踏みます

  1. ECR以外をコメントアウトしdeployを行います
  2. ECRにビルドしたイメージをpushします
    コンソールでECRへ遷移し「プッシュコマンドの表示」で表示されたコマンドを実行していきます
  3. コメントアウトを外し、deployを行います
  4. 実行完了後、ALBのDNS名が表示されるので、そちらをcurlコマンドで実行し、HelloWorldと表示されれば動作確認完了です
% cdk deploy
Outputs:
CdkStack.FargateServiceLoadBalancerDNSXXXXXXXX = <ALBのDNS名>
CdkStack.FargateServiceServiceURLXXXXXXXX = <ECSサービスURL>
% curl <ALBのDNS名>
HelloWorld ← 表示されれば動作確認完了

2回目以降

2回目以降はECRにイメージが既に存在するので、先に実行する必要はありませんが、ECSはlatestタグのものを参照するようになっているので、先ほどと同じようにlatestタグでpushしdeployしてしまうと変更がされません
そのためタグをlatestではないimageをpush→ECSの参照先を変える→deployという手順を踏む必要があります

  1. ECRにタグ名を変えたイメージをpushします
    「プッシュコマンドの表示」で表示されたコマンドの、latestを任意の名前に変更し実行します
  2. ECSの参照先を変更します
    cdk-stack.ts
    image: ecs.ContainerImage.fromEcrRepository(ecrRepository, "latest")
    
    この部分で参照しているので、latestの文字列を先ほど指定したタグ名に変更します
  3. deployを実行、変更が反映されていれば完了です

まとめ

今回はCDKでECS+ALBの環境を作成してみました
ApplicationLoadBalancedFargateServiceがよしなに必要なものを作成してくれるので、セキュリティグループを1つずつ作成する手間もなく、思ったよりも簡単に作成することができました

cdk-ecr-deploymentを上手く動作させれなかったのでdeployの部分が煩雑になりましたが、上手く動いていればdeployするだけで良いので、次の機会では動作させたいと思います

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?