GitLab + CodePipeline + CodeDeploy で SpringBootApp を自動デプロイ

# GitLab + CodePipeline + CodeDeploy で SpringBootApp を自動デプロイ

GitLab は CodePipeline のトリガーに指定することができないので S3 を噛ませて Codeシリーズを起動するサンプル。

1. 修正内容を GitLab に push
2. GitLab CI/CD で S3 にプロジェクトをアップロード
3. CodePipeline で S3 のアップロードを検知
4. CodeBuild で S3 にアーティファクトを保存
5. CodeDeploy で EC2 インスタンスにデプロイ

の流れを試してみる。


## 手順

1. SpringBoot のプロジェクトを作成
2. IAMユーザとS3バケットの作成
3. GitLab CI/CD の設定
4. デプロイ用EC2インスタンスを作成/設定
5. CodeBuild の設定
6. CodeDeploy の設定
7. CodePipeline の設定

SpringProject に加えて追加するファイルは以下のように配置にする。

```
.
├─ .gitlab-ci.yml
├─ buildspec.yml
├─ appspec.yml
└─ hooks
  └─ restart.sh
```

各役割をまとめると・・・

|ファイル名|役割|
|:--|:--|
|.gitlab-ci.yml|GitLabのCI設定ファイル。今回はS3にアップロードする手順を記載する|
|buildspec.yml|CodeBuildでSpringBootをビルドしてJarを生成する手順を記載する|
|appspec.yml|CodeDeployで生成したJarをEC2に配置する手順を記載する|
|restart.sh|CodeDeployでデプロイが完了した後にサービスの再起動などを行うスクリプト|

では早速始めよう。


## SpringBoot のプロジェクトを作成

[Spring Initializr](https://start.spring.io/) でプロジェクトを作成する。
今回は以下の条件で作成してみた。

|key|value|
|:--|:--|
|Project|Gradle Project|
|Language|Java|
|Spring Boot|2.2.5|
|Project Metadata - Group|com.example|
|Project Metadata - Artifact|cisample|
|Options - Name|cisample|
|Options - Description|cisample project for Spring Boot|
|Options - Package name|com.example.cisample|
|Options - Packaging|Jar|
|Options - Java|11|
|Dependencies|Spring Web|

設定が終わったら Generate で作成/DLして展開しておく。
簡単なテキストを表示するために `CisampleApplication.java` を変更しておく。

```diff:CisampleApplication
 package com.example.cisample; 

 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;

+@RestController
 @SpringBootApplication
 public class CisampleApplication { 

 	public static void main(String[] args) {
 		SpringApplication.run(CisampleApplication.class, args);
 	} 

+	@RequestMapping("/")
+	String home() {
+		return "Hello World ver 1.0";
+	} 

 }
```

展開できたらコンテナを起動して動作確認をする。
※ 面倒ならスキップしてもOK

```bash
# コンテナの起動/接続
docker run --rm -it -p 8080:8080 -v $(pwd):/app openjdk:11 bash

# jarのビルド
cd /app
./gradlew build

# jarの実行
java -jar ./build/libs/cisample-0.0.1-SNAPSHOT.jar --server.port=8080
```

ブラウザから `http://localhost:8080` にアクセスして画面が表示されれば成功。
コンテナは停止してOK
次にS3バケットとIAMユーザを作成する。


## S3バケットとIAMユーザの作成

S3にプロジェクトをzip形式で保存するためのS3バケットを作成する。S3はバージョニングを有効にしておくこと。
また、アップロード権限を持ったIAMユーザも作成する。

1. AWSコンソールにログイン
2. S3バケットの作成
    1. サービスから S3 を検索
    2. バケットを作成
    3. バケット名を入力 ex) `cisample-backet`
    4. リージョンを入力 ex) `ap-northeast-1`
    5. パブリックアクセスをすべてブロックをチェック
    6. バケット作成ボタンでバケットを作成
    7. 作成したバケットを選択
    8. プロパティタブ
    9. バージョニング
    10. バージョニングの有効化を選択
    11. 保存
3. IAMユーザの作成
    1. サービスから IAM を検索
    2. 左メニューのユーザー
    3. ユーザーを追加ボタン
    4. ユーザー名を入力 ex) `cisample-user`
    5. プログラムによるアクセスをチェック
    6. 次のステップ
    7. 既存のポリシーを直接アタッチ
    8. `AmazonS3FullAccess` を検索してチェック
    9. 次のステップ
    10. タグは任意で設定して次のステップ
    11. ユーザーの作成ボタン
    12. アクセスキーIDとシークレットアクセスキーを控えておく

S3バケットとIAMユーザができた。
次に公開用のデプロイ先インスタンスを作成する。


## デプロイ用EC2インスタンスを作成/設定

httpアクセスできるネットワークにインスタンスを作成してPublicIPを付与する。
jarが起動できればなんでもいいので、細かい手順はよしなに作成する。
デプロイ時に使うので条件として2つ

1. デプロイグループの設定で使うのでタグを設定する ex) key:`DeployGroup` val:`cisample`
1. ロールに `S3FullAccess` をつけておく ※必要に応じてアクセス制限をつけてね

作成したらsshで接続して設定する。

```bash
# CodeDeploy用エージェントをインストール & 起動
sudo -i
yum update -y && yum install -y ruby wget
wget https://aws-codedeploy-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/latest/install
chmod +x ./install
./install auto
systemctl enable codedeploy-agent
systemctl start codedeploy-agent


# jar(配置予定地)を起動するサービスを作成して権限を付与しておく
mkdir /usr/local/app
cat <<EOS > /usr/local/app/start.sh
#!/bin/sh
java -jar /usr/local/app/cisample-0.0.1-SNAPSHOT.jar --server.port=80
EOS
chmod +x /usr/local/app/start.sh

cat <<EOS > /etc/systemd/system/cisample.service
[Unit]
Description = ci sample daemon

[Service]
ExecStart = /usr/local/app/start.sh
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target
EOS

systemctl enable cisample


# 適当な場所に java をインストールする
cd /opt
wget https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_linux-x64_bin.tar.gz
tar -zxf openjdk-11.0.2_linux-x64_bin.tar.gz
ln -s /opt/jdk-11.0.2/bin/java /usr/bin/java

# インストールの確認
java -version
```

とりあえず一旦Javaが動けばOK
次にプロジェクトをアップロードする GitLab CI の設定を追加する。


## GitLab CI/CD の設定

pushを検知したら AWS の S3 にプロジェクトの内容をアップロードする処理を追加する。
AWSCLIの認証情報はファイルに書いてしまうと悪い人に見つかってしまうので、GitLab側に変数として値を保持しておいて実行時に参照するように設定しておく。

1. GitLabにログイン 
2. テスト用のリポジトリを作成
3. さっきのプロジェクトをpushしておく
4. プロジェクトのページに移動
5. 左メニューの 設定 > CI/CD
6. Variables メニューを展開
7. 以下の値を設定して変数を保存する

|type|key|val|
|:--|:--|:--|
|Variable|aws_access_key_id|IAMユーザのアクセスキー|
|Variable|aws_secret_access_key|IAMユーザのシークレットアクセスキー|
|Variable|backet_name|作成したバケット名|

GitLab CIの設定ファイルを追記する。

```yml:.gitlab-ci.yml
image: "alpine:3"

stages:
  - build
  - push

build:
  stage: build
  script:
    - apk --no-cache add zip
    - zip -qr src.zip *
    - zip -u src.zip .gitlab-ci.yml
  artifacts:
    paths: 
      - src.zip

deploy:
  stage: push
  script:
    - AWS_CLI_VERSION="1.18.31"
    - |-
      apk --no-cache add \
        python \
        py-pip \
        mailcap
    - pip install --upgrade awscli==$AWS_CLI_VERSION python-magic
    - mkdir ~/.aws
    - |-
      cat << EOS > ~/.aws/config
      [default]
      region = ap-northeast-1
      output = json
      EOS
    - |-
      cat << EOS > ~/.aws/credentials
      [default]
      aws_access_key_id = ${aws_access_key_id}
      aws_secret_access_key = ${aws_secret_access_key}
      EOS
    - aws s3 cp ./src.zip s3://${backet_name}/src.zip
```

apline で awscli の2系がうまく動かなかったので1系で動かす。
バージョンは[こちら](https://github.com/aws/aws-cli/blob/master/CHANGELOG.rst)で確認する。
独自のRunnerを使いたい場合は別途設定をすること。
作成したらGitLabにpushしてパイプラインを眺めましょう。
ステータス: 成功 が確認できたらS3に `src.zip` があることを確認する。

続いてこの `src.zip` をビルドするためにCodeBuildの設定に進もう。


## CodeBuild の設定

ビルドするコンピューティングリソースなど、ビルドの設定を作成する。

1. サービスから CodeBuild を検索
2. ビルドプロジェクトを作成する   
3. プロジェクト名を入力 ex) `cisample-build`
4. 送信元をソースプロバイダを `Amazon S3` に設定
5. バケット名を入力 ex) `cisample-backet`
6. S3 オブジェクトキーまたは S3 フォルダ を入力 ex) `src.zip`
7. 環境イメージにマネージド型イメージを選択
8. OSに Amazon Linux 2 を選択
9. ランタイムの Standard を選択
10. イメージとバージョンは最新を選択
11. 環境タイプは Linux を選択
12. サービスロールは新しいサービスロールを選択
13. ロール名を入力 ex) `CiSampleBuildRole`
14. ビルド仕様は buildspec ファイルを使用するを選択
15. アーティファクトタイプは Amazon S3 を選択
16. バケット名を入力 ex) `cisample-backet`
17. 名前を入力 ex) `artifact.zip`
18. アーティファクトのパッケージ化はZipを選択
19. CloudWatch Logs をチェック
20. ビルドプロジェクトを作成する

作成したロールに権限を追加する

1. サービスから IAM を検索
2. 左メニューのロールを選択
3. 作成したロールを検索する ex) `CiSampleBuildRole`
4. ポリシーをアタッチします
5. `AmazonS3FullAccess` をチェックしてポリシーのアタッチ


次にビルド内容の定義ファイルを作成する。

```yml:buildspec.yml
version: 0.2

phases:
  build:
    commands:
      - echo start build at `date`
      - ./gradlew build
artifacts:
  type: zip
  files:
    - ./appspec.yml
    - ./hooks/restart.sh
    - ./build/libs/cisample-0.0.1-SNAPSHOT.jar
  name: artifact
  discard-paths: yes
```

`appspec.yml` と `hooks/restart.sh` は後ほど追加するので気にしない。
これでビルド設定ができた。
ビルドの開始でビルドを実行して、ステータス: 成功 なるまで待つ。
ビルドに成功したら S3に `artifact.zip` があることを確認して、デプロイの設定にはいる。


## CodeDeploy の設定

push されたプロジェクトをビルドするまでできたので、ビルド結果をデプロイする部分を作成する。

手順は4段階
1. デプロイ用ロールを作成
2. デプロイ用アプリケーションを作成
3. デプロイ設定の作成
4. デプロイ設定ファイルの追加

まずはデプロイ用ロールを作成する。

1. サービスから IAM を詮索
2. ロールを選択
3. ロールの作成
4. AWS サービス > CodeDeploy > CodeDeploy を選択して次のステップ
5. `AWSCodeDeployRole` が表示されている状態で次のステップ
6. タグを任意で設定する
7. ロール名を入力する ex) `CisampleDeployRole`
8. ロールの作成

次にデプロイ用アプリケーションを作成する。

1. サービスから CodeDeploy を検索
3. アプリケーションの作成
4. アプリケーションの名前を入力 ex) `cisample-app`
5. コンピューティングプラットフォームを選択 ex) `EC2/オンプレミス`
6. アプリケーションの作成

続いてデプロイの設定を作成する。

1. デプロイグループの作成
    1. デプロイグループの作成
    2. デプロイグループ名を入力 ex) `cisample-dg`
    4. サービスロールのarnを入力 ex) `arn:aws:lam::xxxxxxxxxxxx:role/CisampleDeployRole`
    5. デプロイタイプを選択 ex) `インプレース`
    6. 環境設定は `Amazon EC2 インスタンス` を選択
    7. タグは key: `DeployGroup` val: `cisample` を追加
    8. 一意に一致したインスタンス が 1 になっていることを確認する
    9. デプロイ設定は `CodeDeployDefault.AllAtOnce` を選択
    10. ロードバランシングを有効にする のチェックを外す
    11. デプロイグループの作成
2. デプロイの作成
    1. デプロイの作成
    2. デプロイグループを入力 ex) `cisample-dg`
    3. リビジョンタイプは `Amazon S3` を選択
    4. リビジョンの場所を入力 ex) `s3://cisample-backet/artifact.zip`
    5. リビジョンファイルの種類は `.zip` を選択
    6. デプロイの作成

このままでは設定ファイルがなくてデプロイが失敗してしまうので、設定ファイルを追加する。

```yml:appspec.yml
version: 0.0
os: linux
files:
  - source: /
    destination: /usr/local/app
hooks:
  AfterInstall:
    - location: restart.sh
      timeout: 180
```

新jarのデプロイ後に再起動するスクリプトも追加しておく

```bash:hooks/restart.sh
#!/bin/sh

sudo systemctl restart cisample
```

これでデプロイが通ってEC2インスタンスにビルド結果の `artifact.zip` の内容が展開される。
サービスも起動しているはずなのでブラウザからEC2インスタンスにアクセスして` Hello World ver 1.0`が表示されることを確認する。

最後に S3の更新を検知して CodeBuild と CodeDeploy を起動するための CodePipelineを作成する。


## CodePipeline の設定

1. サービスから CodePipeline を検索
2. パイプラインを作成する
3. パイプライン名を入力 ex) `cisample-pipeline`
4. 新しいサービスロールを選択
5. ロール名を入力 ex) `CiSamplePipelineRole`
6. 高度な設定を展開する
7. アーティファクトストアは カスタムロケーション を選択
8. S3バケット名を入力 ex) `cisample-backet`
9. 次に
10. ソースプロバイダーに `Amazon S3` を選択
11. バケット名を入力 ex) `cisample-backet`
12. S3 オブジェクトキーを入力 ex) `src.zip`
13. 検出オプションを変更する は `Amazon CloudWatch Events` を選択
14. 次に
15. プロバイダーを構築するは `AWS CodeBuild` を選択
16. リージョンを選択 ex) `アジアパシフィック（東京）`
17. プロジェクト名を入力 ex) `cisample-build`
18. 環境変数は今回は未設定で次に
19. デプロイプロバイダーは `AWS CodeDeploy` を選択
20. リージョンを選択 ex) `アジアパシフィック（東京）`
21. アプリケーション名を入力 ex) `cisample-app`
22. デプロイグループを入力 ex) `cisample-dg`
23. 次に
24. パイプラインを作成する

これでS3の変更を検知して一連の流れが通るはず。
Pipelineが成功したらインスタンスにアクセスして
`Hello World ver 1.0` が出ることを確認する。

できた！！


## おわりに

今回の手順で以下のリソースができているはず。

+ S3バケット
+ IAMユーザ
+ IAM Role
+ IAM ポリシー
+ EC2 Instance
+ CodeBuild
+ CodeDeploy
+ CodePipeline

不要な場合は削除しちゃいましょ。
画像がないのでそのうちCLI操作のコマンドでも確認してみましょうかね。