WindowsのEclipseからCodePipelineを起動してEC2にデプロイ＆ECS on Fargateにアプリをデプロイする

# 前提条件
まずは「AWS上でサーバレスなコンテナまで動かせた！」なところまでできていれば。
拙筆ながら[この記事](https://qiita.com/neruneruo/items/ff6747a4129a5b52e6c6)あたりをやっていることを前提としている。
あと、↑の記事中の[シンプルなSpring Boot アプリをAWS Code シリーズを利用して自動デプロイするハンズオン](https://blog.nijot.com/aws/autodeployment-spring-boot-app-with-aws-code-series/)も、最後まで走らせて、CodePipelineでEC2にデプロイできるところまでやった上でこの記事を書いている。

# まずはWindowsでの統合開発環境構築から
イマドキならIntelliJ＋SpringBootかもしれないけど、裾野の広さからEclipseでやってみる。
あと、AWS公式のドキュメントがあるのがEclipseだからという理由も。

[[Java]EclipseでSpring Bootを使ってみる。](https://dev.classmethod.jp/server-side/java/using_spring_boot_1/)

この辺を見ながらWindowsでEclipse＋SpringBootな環境を作っていく。
Javaな人たちからすると当たり前すぎるからか、あんまり書かれていないのだけど、Javaのパス情報(PATHとJAVA_PATHの環境変数)が正しくないとGradleを使ったビルドが通らなかったりするので、設定しておく。GRADLE_PATHあたりも入れておかないと怪しい(ハマりポイント)。
参考情報は以下。

[Let's プログラミング PATHの設定及び環境変数JAVA_HOMEの設定](https://www.javadrive.jp/start/install/index4.html)

さらに、今回はCodeCommitと連携することを目的に、以下を見ながらAWS Toolkit for Eclipseの設定を入れる。

【AWS公式】[Eclipse と AWS CodeCommit の統合](https://docs.aws.amazon.com/ja_jp/codecommit/latest/userguide/setting-up-ide-ec.html)

インストールしようとすると、以下のようなエラーになったりする(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 リージョンを選択します。

としか書かれていなくて意味が分からない。「フラグアイコンって何だよ！」って思ったら、

![キャプチャ3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/87cc9074-6dc7-6ebf-b27e-ae00c1d7a35d.png)

これのことだった。東京リージョンを選択すると

![キャプチャ4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/b08dfc6c-3354-83e5-dfd5-93509199ad5f.png)

無事、東京リージョンでお試しで作ってみたCodeCommitのプロジェクトにアクセスすることができた。

あとは、手順に従ってローカルにチェックアウトしてくればOK。

# ローカル環境からgit commit&pushしてみる

![キャプチャ5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/23620e3b-2ead-dce3-ccea-95e5436f1c8f.png)

こんな感じ(画像の蛍光ペンの場所)でindex.htmlを編集してみて、右クリックからの「チーム」⇒「コミット」でGitステージングのタブを開き、

![キャプチャ6.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/c5f24078-a64a-c742-c5ed-6b5d2f1619ea.png)

作成者やコミッター、コミット・メッセージを入れて、「コミットおよびプッシュ」をクリック！
この時はCommit時にキャプチャを取り忘れたけど、コミット・メッセージには ```Commit From Eclipse```と入れている。

で、正常終了してからAWSのマネジメントコンソールを見てみると、

![キャプチャ7.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/5069e7b5-46a7-74f9-984f-c2f014009a65.png)

ちゃんとCommitされている。ということは、

![キャプチャ8.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/e8de6bcb-d77d-d712-8962-b8942dd70643.png)

無事、パイプラインの起動によるビルド＆デプロイも成功！

# EC2向けのパイプラインをECS＋Fargateに組み替える
以下の書籍を参考にしながら。

[クラウドネイティブファーストストーリー](https://booth.pm/ja/items/1866872)

## 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にファイル置かれるくらい)ので、このままでも良い。後でハマりポイントになるので注意。

![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/482c048f-87a2-bf87-ba64-538355fbf15b.png)

```YAML:buildspec_container.yml
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のサービスを起動しておく必要があるので、これまでローリングアップデートで起動する設定の場合は、以下のように変更する。というか、デプロイメントの設定は作成時にしかできないため、新しくサービスを作る。
![キャプチャ2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/4909e9cf-1665-7cea-61e5-f496ecbebd24.png)
このときも、ECSのIAMロールにも、CodeDeployのアプリケーションを作成するためのポリシ(AWSCodeDeployRoleForECS)を設定しておく。うーん、↑のポリシと同じなのが本当に正しいのかはよく分からない……。

新しいサービスが上手く作れると、CodeDeployにこんな感じでアプリケーションが作成される。
![キャプチャ3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/bbf69c77-eaa1-8006-1d04-6f8e0fee7de1.png)

このアプリケーションに対して、デプロイを作成してみる。
ここまでの流れだと、Appspecファイルをまだ登場させていないので、JSONかYAMLを直接以下のように定義。
ググったり↑の書籍では\<TASK_DEFINITION\>というプレースホルダが使えたりすると書いているが、これはCodePipelineで置換されるプレースホルダであるため、CodeDeploy単独でお試ししている現時点では使えない。

```JSON:JSON
{
  "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
          }
        }
      }
    }
  ]
}
```

```YAML:YAML
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に切り替わるのが視覚的に分かって良い感じ。

![キャプチャ4.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/b881fd07-dfa8-c2e3-47f4-e5fc363ed7a3.png)

右上の「元のタスクセットの終了」を押すと、ちゃんとタスクを停止してくれる。

## パイプラインを繋ぐ
ここからが難しい。パイプラインの中では、新規にECRに登録したタグのタスク定義をする必要があるため、そのCloudFormationの定義ファイルを作っておく必要がある。

```JSON:taskdef.json
{
  "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ファイル同様、元のファイルを残しておきたいならファイル名を変更して、後でファイル名を指定して読み込ませる。

```YAML:appspec_container.yml
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>```のプレースホルダに使われることになる。

```YAML:buildspec_container.yml
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が指定できない部分があるので、パイプラインを作成した後に、以下のような感じで変更を行う。

![キャプチャ5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/9973fe64-4017-1973-4906-ce99dd0b3c54.png)

![キャプチャ6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/11ad1ce8-7b1d-595c-ba9a-c69c6b5e02b3.png)

![キャプチャ7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/f44ab764-c7ee-93a3-965c-e9250caab61d.png)

これで、EclipseでCommitしたらCodePipelineが起動してBlue/Greenデプロイメントをしてくれるはず。

# その他Tips的なもの
## CodeBuildの実態
ちょっと調べてみたときに、CodeBuildはGradleのバージョンが古いからとかなんとかいう記事を見つけて、そもそもCodeBuildってどうやって動いているのよ、というのが気になった。

SlideShareで[過去のBlackBeltオンラインセミナーの資料](https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-online-seminar-2017-aws-code-services-codecommit-codebuild)が公開されていて、それによるとビルド環境のDockerコンテナを起動してその上でビルドを走らせているようだ。

探してみると、[GitHubにCodeBulildビルド環境のDockerfileがあった](https://github.com/aws/aws-codebuild-docker-images/blob/master/ubuntu/standard/4.0/Dockerfile)。Gradleは、この記事を書いた2020年3月時点で

```
    INSTALLED_GRADLE_VERSIONS="4.10.3 5.4.1" \
    GRADLE_VERSION=5.4.1 \
```

な感じのバージョンが入っている。環境変数でバージョンを変えられるということか。

これで、いちいちググらなくても自力でバージョンを調べられる。

## CodePipelineの無効化
CodePipelineのパイプラインそのものを無効化することはできない。
消さないと勝手に全部走っちゃうのかよ！と思いきや、CodeCommitからCodeBuildを繋ぐ部分を無効化すれば、基本的に動かないようになっている。はず。

![tempsnip.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/c918dd82-9423-1526-b5f4-abf93809d9ee.png)