GitLab CIでイメージビルドしECS上のサービスを更新する


GitLabとECSを組み合わせてCI/CDをしたい場合、以下が`.gitlab-ci.yml`で実装できれば実現できそうである。
- コンテナイメージのビルド＆格納
- 格納したイメージでECS上にコンテナを起動する

これを動作確認した時のメモ。

なお、前準備として以下を実施している。
- タスク定義を作るためにベースとなるコンテナを作成
- ECSにベースとなるタスク定義を登録し、サービスを起動

今回のパイプラインは上記の前準備で作ったものを更新していく。
前準備については長くなったので、付録に回している。


## CI/CDからイメージを更新する
ここの内容は以下のブログを参考にした。
- [GitLab CIでAmazon ECSのコンテナデプロイを自動化する](https://www.netone.co.jp/knowledge-center/netone-blog/20211004-1/)
- [describe で出力した ECS タスク定義をさくっと登録可能な形に整形する](https://blog.msysh.me/posts/2020/12/transform_task_definition_by_describe_to_be_able_to_register.html)

流れとしては以下となる。
1. タスク定義を作成する
2. タスク定義内のイメージ名を更新する
3. タスク定義を更新する
4. サービスを上げ直す

一度、上記の手順をローカルで確認する。
最初に、必要な環境変数を定義する。
```sh
# タスク定義内のfamily
export ECS_FAMILY=ecs-test-project
# ECSクラスタ名
export ECS_CLUSTER=imurata-ecs-cluster
# ECSクラスタ内のサービス名
export ECS_SERVICE=ecs-test-project
# kpack/TBSでのイメージ名
export K8S_IMAGE=hello-tanzu-ecs
# kpack/TBSでのイメージのNamespace
export K8S_NAMESPACE=cicd
```
タスク定義を取り出す。
```sh
aws ecs describe-task-definition --task-definition ${ECS_FAMILY} | jq '.taskDefinition | del (.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' > ./taskdef.json
```
イメージのハッシュ値を取得し、タスク定義に上書きする。
```sh
IMAGE=$(kubectl get image $K8S_IMAGE -n $K8S_NAMESPACE -o jsonpath={.status.latestImage})
sed -i "s|\"image\": .*,|\"image\": \"$IMAGE\",|g" taskdef.json
```
更新したタスク定義を登録する。
```sh
aws ecs register-task-definition --cli-input-json file://taskdef.json
```
ECSのサービスを更新する。
```sh
aws ecs update-service --cluster $ECS_CLUSTER --service ${ECS_SERVICE} --task-definition ${ECS_FAMILY}
```
ここまで実施し、タスク定義のリビジョンが更新されることと、新しいリビジョンでサービスが起動することを確認した。

次に、上記で確認した手順を`.gitlab-ci.yml`に書いていく。
awsコマンドをパイプラインから叩くにあたり、アクセスキー、シークレットアクセスキー、リージョンを環境変数として定義する必要があるが、`.gitlab-ci.yml`に記載すると事故の元なので、GitLab内に登録しておく。GitLabのUIから登録するか、以下のようにAPIでまとめて登録する。
```bash
export GITLAB_URL=https://gitlab.gitlab.hogeeee.info
export GITLAB_PROJECT_ID=45
export GITLAB_TOKEN="xxxxxxxx"
export AWS_ACCESS_KEY_ID=xxxxx
export AWS_SECRET_ACCESS_KEY=xxxxx
export AWS_DEFAULT_REGION=us-east-1
curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
     "${GITLAB_URL}/api/v4/projects/${GITLAB_PROJECT_ID}/variables" --form "key=AWS_ACCESS_KEY_ID" --form "value=$AWS_ACCESS_KEY_ID" --form "protected=true" --form "masked=true"
curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
     "${GITLAB_URL}/api/v4/projects/${GITLAB_PROJECT_ID}/variables" --form "key=AWS_SECRET_ACCESS_KEY" --form "value=$AWS_SECRET_ACCESS_KEY" --form "protected=true" --form "masked=true"
curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
     "${GITLAB_URL}/api/v4/projects/${GITLAB_PROJECT_ID}/variables" --form "key=AWS_DEFAULT_REGION" --form "value=$AWS_DEFAULT_REGION"
```

先程ローカルで試した手順を`.gitlab-ci.yml`に書き出す。最終的に`.gitlab-ci.yml`は以下のようになった。
```yaml
stages: 
  - build
  - deploy

build-image:
  image: docker:dind
  stage: build
  variables:
    BUILD_NAMESPACE: "cicd"
    HARBOR_USERNAME: "xxxx"
    HARBOR_PASSWORD: "xxxx"
    HARBOR_URL: "core.harbor.hogeeee.info"
    K8S_IMAGE: hello-tanzu-ecs
  before_script:
  - wget -q https://github.com/vmware-tanzu/kpack-cli/releases/download/v0.10.0/kp-linux-amd64-0.10.0 -O /usr/local/bin/kp && chmod +x /usr/local/bin/kp
  script:
  |- 
    set -x
    docker login -u $HARBOR_USERNAME -p $HARBOR_PASSWORD $HARBOR_URL
    kp image save $K8S_IMAGE --tag ${CI_REGISTRY_IMAGE} \
      --namespace ${BUILD_NAMESPACE} \
      --local-path ./ \
      --wait

deploy-image-to-ecs:
  image: python
  stage: deploy
  variables:
    KUBE_VERSION: 1.24.0
    ECS_FAMILY: ecs-test-project
    ECS_CLUSTER: imurata-ecs-cluster
    ECS_SERVICE: ecs-test-project
    K8S_IMAGE: hello-tanzu-ecs
    K8S_NAMESPACE: cicd
  before_script:
  - pip install awscli
  - wget -q https://storage.googleapis.com/kubernetes-release/release/v${KUBE_VERSION}/bin/linux/amd64/kubectl -O /usr/local/bin/kubectl && chmod +x /usr/local/bin/kubectl
  - wget -q https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 -O /usr/local/bin/jq && chmod +x /usr/local/bin/jq
  script:
  |-
    set -x
    aws ecs describe-task-definition --task-definition ${ECS_FAMILY} | jq '.taskDefinition | del (.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' > ./taskdef.json
    IMAGE=$(kubectl get image $K8S_IMAGE -n $K8S_NAMESPACE -o jsonpath={.status.latestImage})
    sed -i "s|\"image\": .*,|\"image\": \"$IMAGE\",|g" taskdef.json
    aws ecs register-task-definition --cli-input-json file://taskdef.json
    aws ecs update-service --cluster $ECS_CLUSTER --service ${ECS_SERVICE} --task-definition ${ECS_FAMILY}
```

これをコミットした状態で、ソースコードを変更する。
`src/main/java/org/example/tanzu/HelloServlet.java`を編集し、`Hello Tanzu!`を`Hello Tanzu! edited`に変更した。
```java
	void printHello(PrintWriter pw) {
		pw.print("<h3>");
		pw.print("Hello Tanzu! edited");
		pw.print("</h3>");
		pw.print("<p>");
```
これをコミットすると、パイプラインが実行され、最初にイメージが更新され、ECS上でそのイメージで起動する。
![1679992531761.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/9001cde0-6ae4-1c8c-63af-e00a92887759.png)

なお、パイプラインの実行完了後、サービスが起動するまで数分程度待つため、直ぐにアクセスしても変化がない点は注意となる。


## 付録：前準備
### サンプルイメージの作成

最初にサンプルコードをGitLabに登録する。[Hello Tanzu](https://github.com/imurata/hello-tanzu.git)というサンプルをGitLabにImportする。
次に、`.gitlab-ci.yml`を作成する。
`CI/CD`->`Editor`をクリックし、`Configure pipeline`をクリックする。
デフォルトの記述を削除し、コンテナイメージをビルドするよう記載する。ここではコンテナのビルドとpushに[kpack](https://github.com/pivotal/kpack)(Tanzu Build Service)を使うことを想定している。
```yaml
build-image:
  image: docker:dind
  stage: build
  variables:
    BUILD_NAMESPACE: "cicd"
    HARBOR_USERNAME: "xxxx"
    HARBOR_PASSWORD: "xxxx"
    HARBOR_URL: "core.harbor.hogeeee.info"
    HARBOR_REPO: "myapp"
    K8S_IMAGE: hello-tanzu-ecs
  before_script:
  - wget -q https://github.com/vmware-tanzu/kpack-cli/releases/download/v0.10.0/kp-linux-amd64-0.10.0 -O /usr/local/bin/kp && chmod +x /usr/local/bin/kp
  script:
  |- 
    set -x
    docker login -u $HARBOR_USERNAME -p $HARBOR_PASSWORD $HARBOR_URL
    kp image save $K8S_IMAGE --tag ${HARBOR_URL}/${HARBOR_REPO}/${K8S_IMAGE} \
      --namespace ${BUILD_NAMESPACE} \
      --local-path ./ \
      --wait
```
kpコマンドが叩ける`kpack/kp`というイメージがdockerhubに公開されているが、ローカルのファイルを使ってビルドする場合はdocker loginしておく必要がある(kpackがイメージレジストリにソースをuploadするため)ので、`kpack/kp`は使わず`docker:dind`にkpコマンドを取り込むようにし、kpコマンド実行前にdocker loginしている。
なお、イメージの格納先がGitLabの人は環境変数を定義せず、`CI_REGISTRY_IMAGE`などのCI上で使える環境変数を使ってログイン、pushするとよい。

kpackを使わずdocker build/pushを行う場合は[この辺り](https://gitlab-docs.creationline.com/ee/ci/docker/using_docker_build.html#docker%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%B3%E3%82%B0%E3%81%AE%E4%BD%BF%E7%94%A8)が参考になると思う。

上記ジョブをコミットして実行後、k8sクラスタからハッシュ値付きイメージ名を取得する。
```
$ kp image status hello-tanzu-ecs | grep Latest
LatestImage:    core.harbor.hogeeee.info/myapp/hello-tanzu-ecs@sha256:1b2799402dd34385f940412e1a624e0b42dc29c6289dd6a84c96b2ccb534c6f6
```

### ECS CLIの導入
[こちら](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_CLI.html)の手順に従ってインストールする。
```
sudo curl -Lo /usr/local/bin/ecs-cli https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-darwin-amd64-latest
brew install gnupg
cat << 'EOF' > /tmp/pubkey.txt
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2

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
=Gi5D
-----END PGP PUBLIC KEY BLOCK-----
EOF
gpg --import /tmp/pubkey.txt
curl -Lo ecs-cli.asc https://amazon-ecs-cli.s3.amazonaws.com/ecs-cli-darwin-amd64-latest.asc
gpg --verify ecs-cli.asc /usr/local/bin/ecs-cli
sudo chmod +x /usr/local/bin/ecs-cli
```

### ECSのタスク実行ロールの作成
[この辺](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_execution_IAM_role.html#create-task-execution-role)を参考にしつつロールを作成する。
```json
export TASK_ROLE_NAME=ecsTaskExecutionRole

cat << EOF > ./task-execution-assume-role.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ecs-tasks.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
aws iam create-role --role-name $TASK_ROLE_NAME --assume-role-policy-document file://task-execution-assume-role.json
aws iam attach-role-policy \
      --role-name $TASK_ROLE_NAME \
      --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
```

また、コンテナのイメージレジストリの認証情報をSecretManagerに登録している。これにアクセス出来るようにするために、ロールを追加する。
```json
export POLICY_NAME=ecsGetSecretPolicy
cat << EOF > ./secret-assume-role.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
                "arn:aws:secretsmanager:us-east-1:xxxxxxx:secret:harbor-secret-550KBB"
            ]
        }
    ]
}
EOF
aws iam put-role-policy \
      --role-name $TASK_ROLE_NAME \
      --policy-name $POLICY_NAME \
      --policy-document file://secret-assume-role.json
```
### ECSの初期設定
[こちら](https://qiita.com/Shoma0210/items/3eaad5d53d88b87362f3)をかなり参考にさせていただいた。

ECSクラスタの設定を登録する。
```sh
export ECS_CLUSTER=imurata-ecs-cluster
export ECS_CLUSTER_CONFIG=imurata-ecs-config
export ECS_LAUNCH_TYPE="FARGATE"
export AWS_REGION=us-east-1

ecs-cli configure  --region $AWS_REGION --cluster $ECS_CLUSTER --default-launch-type $ECS_LAUNCH_TYPE --config-name $ECS_CLUSTER_CONFIG
```
ECS CLI用のプロファイルを作成する
```sh
export ECS_PROFILE=imurata-ecs-profile

ecs-cli configure profile --access-key $AWS_ACCESS_KEY_ID --secret-key $AWS_SECRET_ACCESS_KEY --profile-name $ECS_PROFILE
```
ECSのクラスタを起動する。
```s
ecs-cli up --cluster-config $ECS_CLUSTER_CONFIG --ecs-profile $ECS_PROFILE
```
出力結果は以下。
```
INFO[0002] Created cluster                               cluster=imurata-ecs-cluster region=us-east-1
INFO[0003] Waiting for your cluster resources to be created...
INFO[0004] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
VPC created: vpc-00a6c03bd7210e5ca
Subnet created: subnet-07829826247054ae0
Subnet created: subnet-0274c60f646e087db
Cluster creation succeeded.
```
VPCとSubnetを変数化する。
```sh
export ECS_VPC=vpc-04109114dae7d0876
export ECS_SUBNET1=subnet-0688829722c4ed724
export ECS_SUBNET2=subnet-0211bfc69638fa8b4
```
VPCのセキュリティグループのIDを取得する。
```sh
aws ec2 describe-security-groups --filters Name=vpc-id,Values=$ECS_VPC | grep -i groupid
```
環境変数に設定する。
```sh
export ECS_SG=sg-083869eb63363ced2
```
セキュリティグループのインバウンドルールにPort80に対して自IPを通すよう設定する。
```sh
aws ec2 authorize-security-group-ingress --group-id $ECS_SG --ip-permissions IpProtocol=tcp,FromPort=80,ToPort=80,IpRanges=[{CidrIp=$(curl https://checkip.amazonaws.com 2> /dev/null  | tr -d '\r\n')/32}]
```
次にALBを作成する。
```sh
export ECS_LB=ecs-test-lb
aws elbv2 create-load-balancer --name $ECS_LB --subnets $ECS_SUBNET1 $ECS_SUBNET2 --security-groups $ECS_SG
```
LoadBalancerArnの値を変数化する。
```sh
export ECS_LB_ARN="arn:aws:elasticloadbalancing:us-east-1:xxxxx:loadbalancer/app/ecs-test-lb/b2dfddc492d8fed0"
```
ターゲットグループを作成する。
```sh
export ECS_TARGET=ecs-test-target
aws elbv2 create-target-group --name $ECS_TARGET --protocol HTTP --port 8080 --target-type ip --vpc-id $ECS_VPC
```
ターゲットグループのArnを変数化する。
```sh
export ECS_TARGET_ARN="arn:aws:elasticloadbalancing:us-east-1:xxxxx:targetgroup/ecs-test-target/9865f22ac7242fd7"
```
ALBのリスナーを作成する。
```sh
aws elbv2 create-listener --load-balancer-arn $ECS_LB_ARN --protocol HTTP --port 8080 --default-actions Type=forward,TargetGroupArn=$ECS_TARGET_ARN
```

ecs-cliを利用してタスク定義を生成してサービスを立ち上げるためにdocker-compose.ymlとecs-params.ymlを作成する。`image:`には事前に作成したコンテナイメージを指定する。

```yaml
cat << EOF > ./docker-compose.yml
version: '3'
services:
  web:
    image: core.harbor.hogeeee.info/myapp/hello-tanzu-ecs@sha256:1b2799402dd34385f940412e1a624e0b42dc29c6289dd6a84c96b2ccb534c6f6
    ports:
      - '80:80'
      - '8080:8080'
    logging:
      driver: awslogs
      options:
        awslogs-group: /etc/imurata-Logs
        awslogs-region: us-east-1
        awslogs-stream-prefix: ecs
EOF
```
```yaml
cat << EOF  > ./ecs-params.yml
version: 1
task_definition:
  task_execution_role: ecsTaskExecutionRole
  ecs_network_mode: awsvpc
  services:
    web:
      repository_credentials:
        credentials_parameter: arn:aws:secretsmanager:us-east-1:xxxxx:secret:harbor-secret-550KBB
  task_size:
    mem_limit: 1GB
    cpu_limit: 256
run_params:
  network_configuration:
    awsvpc_configuration:
      subnets:
        - $ECS_SUBNET1
        - $ECS_SUBNET2
      security_groups:
        - $ECS_SG
      assign_public_ip: ENABLED
EOF
```

docker-compose.ymlとecs-params.ymlがあるディレクトリで`ecs-cli compose`でコンテナを起動する。
```sh
export ECS_PROJECT=ecs-test-project

ecs-cli compose --project-name $ECS_PROJECT service up --create-log-groups --cluster-config $ECS_CLUSTER_CONFIG --ecs-profile $ECS_PROFILE --target-group-arn $ECS_TARGET_ARN --container-name web --container-port 8080
```

出力結果は以下。
```
INFO[0002] Using ECS task definition                     TaskDefinition="ecs-test-project:1"
WARN[0003] Failed to create log group /etc/imurata-Logs in us-east-1: The specified log group already exists
INFO[0003] Auto-enabling ECS Managed Tags
INFO[0010] (service ecs-test-project) has started 1 tasks: (task 1c94b09279e24abdb828bc9632db78b9).  timestamp="2023-03-26 06:11:04 +0000 UTC"
INFO[0037] (service ecs-test-project) registered 1 targets in (target-group arn:aws:elasticloadbalancing:us-east-1:xxxxx:targetgroup/ecs-test-target/7c71684bd9c1d55f)  timestamp="2023-03-26 06:11:31 +0000 UTC"
INFO[0042] Service status                                desiredCount=1 runningCount=1 serviceName=ecs-test-project
INFO[0042] ECS Service has reached a stable state        desiredCount=1 runningCount=1 serviceName=ecs-test-project
INFO[0042] Created an ECS service                        service=ecs-test-project taskDefinition="ecs-test-project:1"
```
接続先を確認する。
```sh
aws elbv2 describe-load-balancers --load-balancer-arns $ECS_LB_ARN | grep DNSName
```
ブラウザで上記の接続先+8080ポートにアクセスし、以下の画面が表示されることを確認する。
![1679985211885.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/09e7b6ac-42e6-861b-81be-7382c186f690.png)
以上で前準備が完了となる。