こんにちは。ただいま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
$
前提条件
などを終えておくこと。
作る環境
こんなかんじになります。
やってみる
Dockerイメージを格納する、ECRの作成
まずは自前の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のコンソール画面でみても
うん、できていそうです。
ECRへアップロード
つづいて作成したECRへ、先のDockerイメージをアップロードします。アップロードにはECRへのログインなど多少の前準備が必要ですので、順番にやっていきます。
あらためてイメージの確認。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
myorg/spring-boot-sample-tomcat 0.1.1-SNAPSHOT cdcd3af69190 4 days ago 363MB
アップロードしたいのはこのイメージ(IMAGE 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
おなじIMAGE 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サービスを削除
さてコードを修正する前に、前回作成したnginxのECSサービスが別のタスク定義を参照していて競合するため、まずはECSサービス関連を削除します。CDKで
$ cd cdk-samples
cdk-samples$ yarn cdk destroy ECSServiceStack ECSServiceELBStack
....
cdk-samples$
って削除すればOK。
タスク定義等の書き換え
さて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
でOK。おわったら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サービスを動かすことができました。
おつかれさまでした。