CodeBuildを使ったEC2インスタンス等のリソース状態変更処理

定型的なデプロイ作業。これの1つ1つを自動にしたいと考えていて。
大抵そういう場合Lambdaを使うが、わざわざリソースの状態を変更したいがために、Pythonなどを覚えて、エラーハンドリングまで考えて作りこむことは何か時間の無駄な気がしていて。awscliコマンドでできることを、何を頑張ってPythonやらのソースコードで作りこむのか？という疑問があり、そこからこの発想が思い浮かんだ。

https://www.seeds-std.co.jp/blog/creators/2021-03-11-154551/

LambdaもCodeBuildと同じくコンテナを動かしてコードを動かすが、Lambdaはawscliが利用できない。CodeBuildならそのあたりができるため、いざ活用。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/1f335406-8cce-2155-e49f-ad99fbbeb4c6.png)

「プロジェクトを作成」で簡単に作れます。

## 指定したEC2インスタンスのシャットダウン
前提として、EC2インスタンスの名前付与にNameタグを利用している。
プロジェクト作成で、CodeBuildをよしなに作る。OS等は既定値のAL2023を指定、最低スペックでよい。権限はこのビルドプロジェクト作成後にできるIAMロールに付与していく。EC2FullAccessを付与したり、お好みで最小権限などにしてみてください。
BuildSpec.ymlに以下を書くと、特定のEC2インスタンスに対するシャットダウン処理が完成。

```yaml:buildspec.yml
version: 0.2
phases:
   install:
     commands:
       - echo install aws cli v2
       - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
       - unzip -q awscliv2.zip
       - ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
   pre_build:
     commands:
       - echo ${EC2_NAME}
       - export INSTANCE_ID=`aws ec2 describe-instances --filter "Name=tag:Name,Values=${EC2_NAME}" --query "Reservations[].Instances[?State.Name!='terminated'].InstanceId" --output text | column -t`
       - export EC2_STATUS=`aws ec2 describe-instances --filter "Name=tag:Name,Values=${EC2_NAME}" --query "Reservations[].Instances[?State.Name!='terminated'].State.Name" --output text`
       - echo "EC2 instance ${EC2_NAME} is ${INSTANCE_ID} . The status is ${EC2_STATUS}"
   build:
     commands:
       - echo "EC2 instance ${EC2_NAME} is ${INSTANCE_ID}. Shutdown."
       - aws ec2 stop-instances --instance-ids ${INSTANCE_ID}
   post_build:
     commands:
       - sleep 10
       - export EC2_STATUS=`aws ec2 describe-instances --filter "Name=tag:Name,Values=${EC2_NAME}" --query "Reservations[].Instances[?State.Name!='terminated'].State.Name" --output text`
       - echo "EC2 instance ${EC2_NAME} is ${INSTANCE_ID} . The status is ${EC2_STATUS}"

```


CodeBuildの環境変数には以下を作る。
| 名前 | 値 | タイプ |
|:-----------|------------:|:------------:|
| EC2_NAME       | 検索するインスタンスNameタグ        | PLAINTEXT         |

<br>
<br>

Nameタグをつけておらず、インスタンスID直指定する場合は、以下のような環境変数を作る。
| 名前 | 値 | タイプ |
|:-----------|------------:|:------------:|
| INSTANCE_ID       | i-XXXX(対象のインスタンスID)        | PLAINTEXT         |

Nameタグをつけておらず、インスタンスID直指定する場合は、buildspecも以下に変更。
```yaml:buildspec.yml(インスタンスID直指定)
version: 0.2
phases:
   install:
     commands:
       - echo install aws cli v2
       - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
       - unzip -q awscliv2.zip
       - ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
   build:
     commands:
       - aws ec2 stop-instances --instance-ids ${INSTANCE_ID}
   post_build:
     commands:
       - echo "EC2 instance ${INSTANCE_ID}. Shutdown."
```

## よく使う細かいビルド設定
### 同時実行数の制限
今回のようなシャットダウン処理は多重化されると困るため、この設定を入れておく。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/38a3b5c3-9570-2b46-a28b-9c2ced348b8a.png)

### DockerLayerCache有効化
中身はDockerコンテナという事で、コンテナのキャッシュを有効にしておく。これにより繰り返し処理が早くなる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/36cfed7a-e333-db1e-4940-6e44684d21db.png)

### コンテナの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/4f7a907f-8886-8527-a8c9-8b1e080a066f.png)
基本的に上記のような既定値でよいが、イメージやイメージのバージョンは以下のように最新を使うようにする。変にバージョンを固定すると、起動が遅くなる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/87cf3f6f-11f8-3b39-639b-dc48f7bca037.png)

### カスタムイメージ
Dockerが作成できるなら、カスタムイメージも登録してデプロイ可能。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/f83064fe-926d-3731-250d-90c614a930ab.png)

作成したDockerイメージを、ECRに登録してCodeBuildで指定する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229539/455275b0-20fa-9500-9341-88e0b3bed768.png)

既定値のコンテナよりも高速に動くので、余裕がある人はカスタムイメージ作成をおすすめする。

## ELBのターゲット除外
これもbuildspecで以下のように記載できる。わたしの環境はターゲットグループの登録方法がIPアドレスなので、インスタンスで登録している場合は環境に応じてコマンドを要修正。
```yaml:buildspec.yml
version: 0.2
phases:
   install:
     commands:
       - echo install aws cli v2
       - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
       - unzip -q awscliv2.zip
       - ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
   pre_build:
     commands:
       - export TZ="Asia/Tokyo"
       - echo "elb target check"
       - aws elbv2 describe-target-health --target-group-arn ${ELBTG} --query 'TargetHealthDescriptions[].{Id:Target.Id,Port:Target.Port,State:TargetHealth.State}' --output text | column -t
   build:
     commands:
       - echo "ELBTG target deregister ${TARGET_IP}"
       - aws elbv2 deregister-targets --target-group-arn ${ELBTG} --targets Id=${TARGET_IP}
   post_build:
     commands:
       - echo "ELBTG target check"
       - aws elbv2 describe-target-health --target-group-arn ${ELBTG} --query 'TargetHealthDescriptions[].{Id:Target.Id,Port:Target.Port,State:TargetHealth.State}' --output text | column -t
```

CodeBuildの環境変数には以下を作る。
| 名前 | 値 | タイプ |
|:-----------|------------:|:------------:|
| ELBTG | ターゲットグループのARN(arn…) | PLAINTEXT |
| TARGET_IP | 除外するターゲットIP | PLAINTEXT |

<br>
<br>


## ELBのターゲット登録
ELBのターゲットグループからの除外とは反対に今度は登録。
これもIPアドレス登録の場合なので、インスタンス登録の場合は要修正。
```yaml:buildspec.yml
version: 0.2
phases:
   install:
     commands:
       - echo install aws cli v2
       - curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
       - unzip -q awscliv2.zip
       - ./aws/install --bin-dir /root/.pyenv/shims --install-dir /usr/local/aws-cli --update
   pre_build:
     commands:
       - export TZ="Asia/Tokyo"
       - echo "ELBTG target check"
       - aws elbv2 describe-target-health --target-group-arn ${ELBTG} --query 'TargetHealthDescriptions[].{Id:Target.Id,Port:Target.Port,State:TargetHealth.State}' --output text | column -t
   build:
     commands:
       - echo "ELBTG target register ${TARGET_IP}"
       - aws elbv2 register-targets --target-group-arn ${ELBTG} --targets Id=${TARGET_IP},Port=${PORT}
   post_build:
     commands:
       - echo "ELBTG target check"
       - aws elbv2 describe-target-health --target-group-arn ${ELBTG} --query 'TargetHealthDescriptions[].{Id:Target.Id,Port:Target.Port,State:TargetHealth.State}' --output text | column -t

```

CodeBuildの環境変数には以下を作る。
| 名前 | 値 | タイプ |
|:-----------|------------:|:------------:|
| ELBTG | ターゲットグループのARN(arn…) | PLAINTEXT |
| TARGET_IP | 登録するターゲットIP | PLAINTEXT |
| PORT | 登録するポート番号 | PLAINTEXT |

<br>

登録時はポート番号も必要のため、環境変数に忘れずに記載する。

<br>


## おわりに
このように、わざわざLambdaでプログラム言語を覚える間もなく、簡単にAWSCLIコマンドで定型的な処理を、冪等性を持たせて作ることができる。おすすめ。