前提条件
まずは「AWS上でサーバレスなコンテナまで動かせた!」なところまでできていれば。
拙筆ながらこの記事あたりをやっていることを前提としている。
あと、↑の記事中のシンプルなSpring Boot アプリをAWS Code シリーズを利用して自動デプロイするハンズオンも、最後まで走らせて、CodePipelineでEC2にデプロイできるところまでやった上でこの記事を書いている。
まずはWindowsでの統合開発環境構築から
イマドキならIntelliJ+SpringBootかもしれないけど、裾野の広さからEclipseでやってみる。
あと、AWS公式のドキュメントがあるのがEclipseだからという理由も。
[Java]EclipseでSpring Bootを使ってみる。
この辺を見ながらWindowsでEclipse+SpringBootな環境を作っていく。
Javaな人たちからすると当たり前すぎるからか、あんまり書かれていないのだけど、Javaのパス情報(PATHとJAVA_PATHの環境変数)が正しくないとGradleを使ったビルドが通らなかったりするので、設定しておく。GRADLE_PATHあたりも入れておかないと怪しい(ハマりポイント)。
参考情報は以下。
Let's プログラミング PATHの設定及び環境変数JAVA_HOMEの設定
さらに、今回はCodeCommitと連携することを目的に、以下を見ながらAWS Toolkit for Eclipseの設定を入れる。
【AWS公式】Eclipse と AWS CodeCommit の統合
インストールしようとすると、以下のようなエラーになったりする(Eclipseを日本語化していない場合、"No repository found containing" な感じで出力されていると思う)。
インストールする項目の収集中にエラーが発生しました
セッション・コンテキスト:(プロファイル=C__Users_NERU_eclipse_jee-2019-12_eclipse、フェーズ=org.eclipse.equinox.internal.p2.engine.phases.Collect、オペランド=、アクション=)。
含まれているリポジトリーが見つかりません: osgi.bundle,org.eclipse.jgit,5.6.1.202002131546-r
含まれているリポジトリーが見つかりません: osgi.bundle,org.eclipse.jgit.archive,5.6.1.202002131546-r
含まれているリポジトリーが見つかりません: osgi.bundle,org.eclipse.jgit.http.apache,5.6.1.202002131546-r
含まれているリポジトリーが見つかりません: osgi.bundle,org.eclipse.jgit.ssh.apache,5.6.1.202002131546-r
含まれているリポジトリーが見つかりません: org.eclipse.update.feature,org.eclipse.jgit,5.6.1.202002131546-r
含まれているリポジトリーが見つかりません: org.eclipse.update.feature,org.eclipse.jgit.http.apache,5.6.1.202002131546-r
含まれているリポジトリーが見つかりません: org.eclipse.update.feature,org.eclipse.jgit.ssh.apache,5.6.1.202002131546-r
エラーになったプラグインをググって、ダウンロードサイトを「ヘルプ」⇒「新規ソフトウェアのインストール」で開いたウィンドウでサイトを入力する。たとえば、↑のjgitとかは http://download.eclipse.org/egit/updates
を入れれば見つけることができる。全てのプラグインがこの方法でできるかは不明。見つけられないものもあった。謎。
あと、AWS Toolkit for Eclipseのリージョンはデフォルトで東京リージョンになっていない。↑のサイトを読んでも、
リポジトリが表示されない場合は、フラグアイコンを選択して AWS リージョンメニューを開き、リポジトリが作成された AWS リージョンを選択します。
としか書かれていなくて意味が分からない。「フラグアイコンって何だよ!」って思ったら、
これのことだった。東京リージョンを選択すると
無事、東京リージョンでお試しで作ってみたCodeCommitのプロジェクトにアクセスすることができた。
あとは、手順に従ってローカルにチェックアウトしてくればOK。
ローカル環境からgit commit&pushしてみる
こんな感じ(画像の蛍光ペンの場所)でindex.htmlを編集してみて、右クリックからの「チーム」⇒「コミット」でGitステージングのタブを開き、
作成者やコミッター、コミット・メッセージを入れて、「コミットおよびプッシュ」をクリック!
この時はCommit時にキャプチャを取り忘れたけど、コミット・メッセージには Commit From Eclipse
と入れている。
で、正常終了してからAWSのマネジメントコンソールを見てみると、
ちゃんとCommitされている。ということは、
無事、パイプラインの起動によるビルド&デプロイも成功!
EC2向けのパイプラインをECS+Fargateに組み替える
以下の書籍を参考にしながら。
CodeBuildでECRにpushするようにBuildSpecファイルを組み替える
まずは、ビルド結果をECRにPUSHするので、CodeBuildで使うIAMロールにECRにアクセスするためのポリシを設定しておく。CloudWatch Logsにログを出すならそのポリシ(CloudWatchLogsFullAccess)も必要。
ビルド環境は何でも良いかと思いきや、Amazon Linux2の全バージョンと、Ubuntu Standard 4.0についてはOpenJDKが入っていないので注意(Corettoになっている)。
特権モードとか、何も考えずにEC2向けのCodeBuildの設定と同じと考えていると、書籍内で「ハマるから気を付けて」と書いてある箇所は100%ハマるので、事前にしっかり読み込んでおくか、ハマったら読み返してみるといい。
Buildspecファイルはこんな感じで。元のハンズオンのbuildspec.ymlを汚したくないのであれば、別名で作って、ビルドプロジェクトの設定でファイル指定すると良い。アーティファクトが元のハンズオンのままだけど、今回はECRのリポジトリに登録すれば終わりなので不要な気がする。この時点では書いてあってあっても困らない(S3にファイル置かれるくらい)ので、このままでも良い。後でハマりポイントになるので注意。
version: 0.2
env:
variables:
AWS_REGION_NAME: ap-northeast-1
ECR_REPOSITORY_NAME: my-greeting-web
phases:
install:
runtime-versions:
java: openjdk11
pre_build:
commands:
- $(aws ecr get-login --region ${AWS_REGION_NAME} --no-include-email)
- AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
- REPOSITORY_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION_NAME}.amazonaws.com/${ECR_REPOSITORY_NAME}
- IMAGE_TAG=$(git rev-parse --short HEAD)
build:
commands:
- echo Build started on `date`
- mvn test package
- docker build -t ${REPOSITORY_URI}:latest .
- docker tag ${REPOSITORY_URI}:latest ${REPOSITORY_URI}:${IMAGE_TAG}
post_build:
commands:
- docker push ${REPOSITORY_URI}:${IMAGE_TAG}
artifacts:
files:
- target/my-greeting-web-0.1.0.jar
- appspec.yml
- deploy/*
discard-paths: yes
cache:
paths:
- '/root/.m2/**/*'
あと、Buildspecファイルと同じディレクトリにDockerfileも必要なので注意。
さて、これでビルドが通るようになったはずなので、ビルドの実行をして確認してみる。
上手くいくと、ECRのリポジトリが更新されているはず。
CodeDeployを組み替える
例によってCodeDeployが使うIAMロールがECSにアクセスできるようにポリシ(AWSCodeDeployRoleForECS)を設定する。
さらに、ECSにCodeDeployする場合はBlue/Greenデプロイメントになるので、ALBにもターゲットグループを追加して、2つのターゲットグループを打つようにしておく。
また、CodeDeploy用にECSのサービスを起動しておく必要があるので、これまでローリングアップデートで起動する設定の場合は、以下のように変更する。というか、デプロイメントの設定は作成時にしかできないため、新しくサービスを作る。
このときも、ECSのIAMロールにも、CodeDeployのアプリケーションを作成するためのポリシ(AWSCodeDeployRoleForECS)を設定しておく。うーん、↑のポリシと同じなのが本当に正しいのかはよく分からない……。
新しいサービスが上手く作れると、CodeDeployにこんな感じでアプリケーションが作成される。
このアプリケーションに対して、デプロイを作成してみる。
ここまでの流れだと、Appspecファイルをまだ登場させていないので、JSONかYAMLを直接以下のように定義。
ググったり↑の書籍では<TASK_DEFINITION>というプレースホルダが使えたりすると書いているが、これはCodePipelineで置換されるプレースホルダであるため、CodeDeploy単独でお試ししている現時点では使えない。
{
"version": 1,
"Resources": [
{
"TargetService": {
"Type": "AWS::ECS::Service",
"Properties": {
"TaskDefinition": "arn:aws:ecs:ap-northeast-1:[アカウントID]:task-definition/my-greeting-web:[タスクのバージョン番号]",
"LoadBalancerInfo": {
"ContainerName": "my-greeting-web",
"ContainerPort": 8080
}
}
}
}
]
}
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "arn:aws:ecs:ap-northeast-1:[アカウントID]:task-definition/my-greeting-web:2"
LoadBalancerInfo:
ContainerName: "my-greeting-web"
ContainerPort: 8080
デプロイに成功すると、↓こんな感じで成功の画面が出る。この前はプログレスバーが青かったので、BlueがGreenに切り替わるのが視覚的に分かって良い感じ。
右上の「元のタスクセットの終了」を押すと、ちゃんとタスクを停止してくれる。
パイプラインを繋ぐ
ここからが難しい。パイプラインの中では、新規にECRに登録したタグのタスク定義をする必要があるため、そのCloudFormationの定義ファイルを作っておく必要がある。
{
"executionRoleArn": "arn:aws:iam::[アカウントID]:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::[アカウントID]:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "my-greeting-web",
"image": "<IMAGE1_NAME>",
"portMappings": [
{
"containerPort": 8080,
"hostPort": 8080,
"protocol": "tcp"
}
],
"essential": true,
"cpu": 512,
"memoryReservation": 1024,
}
],
"requiresCompatibilities": ["FARGATE"],
"networkMode": "awsvpc",
"cpu": "512",
"memory": "1024",
"family": "my-greeting-web"
}
Appspecファイルも、以下のような感じでプレースホルダを設定しておく。
元のハンズオンではEC2で起動するための設定になっているので、以下の様に修正。Buildspecファイル同様、元のファイルを残しておきたいならファイル名を変更して、後でファイル名を指定して読み込ませる。
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: "my-greeting-web"
ContainerPort: 8080
CodePipelineから起動するECSのBlue/GreenデプロイメントのDeployフェーズは、↑で勝手に作られたCodeDeployのアプリケーションとアプリケーショングループを選択すればOK。
ただし、パイプラインを繋ぐ際に、単純にCodeBuildで設定したBuildSpecを使うのではダメ。
以下の様に、IMAGE_TAG
の設定をCodeBuildから呼ばれた際のバージョンに置き換えたり、アーティファクトの出力を変更したりする必要がある。特に、このアーティファクトの出力が曲者で、アーティファクトが大きすぎるとエラーになる。実際には、ECRにちゃんとコンテナが登録されていれば、imageDetail.json
さえ出力されていれば良い(後で小細工するからだけど)。
imageDetailは、コンテナイメージの詳細情報。これが、taskdef.json
の<IMAGE1_NAME>
のプレースホルダに使われることになる。
version: 0.2
env:
variables:
AWS_REGION_NAME: ap-northeast-1
ECR_REPOSITORY_NAME: my-greeting-web
phases:
install:
runtime-versions:
java: openjdk11
pre_build:
commands:
- $(aws ecr get-login --region ${AWS_REGION_NAME} --no-include-email)
- AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query 'Account' --output text)
- REPOSITORY_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION_NAME}.amazonaws.com/${ECR_REPOSITORY_NAME}
- IMAGE_TAG=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | cut -c 1-7)
build:
commands:
- echo Build started on `date`
- mvn test package
- docker build -t ${REPOSITORY_URI}:latest .
- docker tag ${REPOSITORY_URI}:latest ${REPOSITORY_URI}:${IMAGE_TAG}
post_build:
commands:
- docker push ${REPOSITORY_URI}:${IMAGE_TAG}
- printf '{"name":"%s","ImageURI":"%s"}' $ECR_REPOSITORY_NAME $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
artifacts:
files:
- imageDetail.json
discard-paths: yes
cache:
paths:
- '/root/.m2/**/*'
さらに、CodePipelineのデプロイでは、なぜか作成時点ではSource Artifactが指定できない部分があるので、パイプラインを作成した後に、以下のような感じで変更を行う。
これで、EclipseでCommitしたらCodePipelineが起動してBlue/Greenデプロイメントをしてくれるはず。
その他Tips的なもの
CodeBuildの実態
ちょっと調べてみたときに、CodeBuildはGradleのバージョンが古いからとかなんとかいう記事を見つけて、そもそもCodeBuildってどうやって動いているのよ、というのが気になった。
SlideShareで過去のBlackBeltオンラインセミナーの資料が公開されていて、それによるとビルド環境のDockerコンテナを起動してその上でビルドを走らせているようだ。
探してみると、GitHubにCodeBulildビルド環境のDockerfileがあった。Gradleは、この記事を書いた2020年3月時点で
INSTALLED_GRADLE_VERSIONS="4.10.3 5.4.1" \
GRADLE_VERSION=5.4.1 \
な感じのバージョンが入っている。環境変数でバージョンを変えられるということか。
これで、いちいちググらなくても自力でバージョンを調べられる。
CodePipelineの無効化
CodePipelineのパイプラインそのものを無効化することはできない。
消さないと勝手に全部走っちゃうのかよ!と思いきや、CodeCommitからCodeBuildを繋ぐ部分を無効化すれば、基本的に動かないようになっている。はず。