😀

GitHub ActionsでECS Exec用のコンテナを起動してみた

2024/07/19に公開

ECSを使用していると、DBへの踏み台サーバーなどの用途でECSタスクを起動したくなる時があると思います。

毎回、手動でコマンドを実行し、ECSタスクを起動・接続するのがかなり面倒だったのでGitHub ActionsでECSタスクの起動と接続コマンドの表示を実装することにしました。

コードはこちら

name: ECS Exec

on:
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}
  cancel-in-progress: true

env:
  AWS_REGION: [リージョン名]
  AWS_ECS_CLUSTER: [ECSクラスター名]
  AWS_ECS_TASK: [ECSタスク]

jobs:
  run-ecs-task:
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}

    - name: Get latest task definition revision
      id: get-latest-task-def
      env:
        TASK_FAMILY: ${{ env.AWS_ECS_TASK }}
      run: |
        LATEST_TASK_DEF=$(aws ecs list-task-definitions --family-prefix ${{ env.TASK_FAMILY }} --sort DESC --max-items 1 --query "taskDefinitionArns[0]")
        echo "latest_task_def=$LATEST_TASK_DEF" >> $GITHUB_OUTPUT

    - name: Get ECS service network configuration
      id: get-network-config
      env:
        CLUSTER_NAME: ${{ env.AWS_ECS_CLUSTER }}
        SERVICE_NAME: ${{ env.AWS_ECS_TASK }}
      run: |
        NETWORK_CONFIG=$(aws ecs describe-services --cluster ${{ env.CLUSTER_NAME }} --services ${{ env.SERVICE_NAME }} --query "services[0].networkConfiguration.awsvpcConfiguration" --output json)
        SUBNET=$(echo $NETWORK_CONFIG | jq -r '.subnets[0]')
        SECURITY_GROUP=$(echo $NETWORK_CONFIG | jq -r '.securityGroups[0]')
        echo "subnet=${SUBNET}" >> $GITHUB_OUTPUT
        echo "security_group=${SECURITY_GROUP}" >> $GITHUB_OUTPUT

    - name: Run ECS Task
      id: run-ecs-task
      env:
        LATEST_TASK_DEF: ${{ steps.get-latest-task-def.outputs.latest_task_def }}
        ECS_TASK_SUBNET: ${{ steps.get-network-config.outputs.subnet }}
        ECS_TASK_SECURITY_GROUP: ${{ steps.get-network-config.outputs.security_group }}
        CONTAINER_NAME: ${{ env.AWS_ECS_TASK }}
      run: |
        result=$(aws ecs run-task \
          --cluster ${{ env.AWS_ECS_CLUSTER }} \
          --launch-type FARGATE \
          --enable-execute-command \
          --task-definition ${{ env.LATEST_TASK_DEF }} \
          --network-configuration 'awsvpcConfiguration={subnets=["${{ env.ECS_TASK_SUBNET }}"],securityGroups=["${{ env.ECS_TASK_SECURITY_GROUP }}"],assignPublicIp="DISABLED"}' \
          --tags key=StartedBy,value=GitHubActions \
          --overrides '{"containerOverrides": [{"name": "${{ env.CONTAINER_NAME }}", "command": ["sleep", "7200"]}]}' \
          --count 1)
        echo "Run Task result: $result"

    - name: Echo ECS Exec command
      id: echo-ecs-exec-command
      env:
        TASK_ARN: ${{ steps.run-ecs-task.outputs.task_arn }}
      run: |
        echo "ECSタスクへログインするコマンド aws ecs execute-command --cluster ${{ env.AWS_ECS_CLUSTER }} --task ${{ env.TASK_ARN }} --container ${{ env.AWS_ECS_TASK }} --interactive  --command /bin/sh"

事前処理

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}

AWSにアクセスするための認証情報を設定しています。

検証のため、簡易的にアクセスキーを使用していますが、IAMロール認証を推奨します。

最新のタスク定義を取得

    - name: Get latest task definition revision
      id: get-latest-task-def
      env:
        TASK_FAMILY: ${{ env.AWS_ECS_TASK }}
      run: |
        LATEST_TASK_DEF=$(aws ecs list-task-definitions --family-prefix ${{ env.TASK_FAMILY }} --sort DESC --max-items 1 --query "taskDefinitionArns[0]")
        echo "latest_task_def=$LATEST_TASK_DEF" >> $GITHUB_OUTPUT

ECSタスクの起動に使用したいタスク定義の最新のARNを取得しています。

サブネット、セキュリティグループを取得

    - name: Get ECS service network configuration
      id: get-network-config
      env:
        CLUSTER_NAME: ${{ env.AWS_ECS_CLUSTER }}
        SERVICE_NAME: ${{ env.AWS_ECS_TASK }}
      run: |
        NETWORK_CONFIG=$(aws ecs describe-services --cluster ${{ env.CLUSTER_NAME }} --services ${{ env.SERVICE_NAME }} --query "services[0].networkConfiguration.awsvpcConfiguration" --output json)
        SUBNET=$(echo $NETWORK_CONFIG | jq -r '.subnets[0]')
        SECURITY_GROUP=$(echo $NETWORK_CONFIG | jq -r '.securityGroups[0]')
        echo "subnet=${SUBNET}" >> $GITHUB_OUTPUT
        echo "security_group=${SECURITY_GROUP}" >> $GITHUB_OUTPUT

手動で設定してもいいですが、面倒なのでネットワーク設定も取得するようにしました。

これはクラスターにサービスタスクが稼働している前提です。

稼働しているサービスタスクの設定からサブネット、セキュリティグループの情報を取得しています。

タスク起動

    - name: Run ECS Task
      id: run-ecs-task
      env:
        LATEST_TASK_DEF: ${{ steps.get-latest-task-def.outputs.latest_task_def }}
        ECS_TASK_SUBNET: ${{ steps.get-network-config.outputs.subnet }}
        ECS_TASK_SECURITY_GROUP: ${{ steps.get-network-config.outputs.security_group }}
        CONTAINER_NAME: ${{ env.AWS_ECS_TASK }}
      run: |
        result=$(aws ecs run-task \
          --cluster ${{ env.AWS_ECS_CLUSTER }} \
          --launch-type FARGATE \
          --enable-execute-command \
          --task-definition ${{ env.LATEST_TASK_DEF }} \
          --network-configuration 'awsvpcConfiguration={subnets=["${{ env.ECS_TASK_SUBNET }}"],securityGroups=["${{ env.ECS_TASK_SECURITY_GROUP }}"],assignPublicIp="DISABLED"}' \
          --tags key=StartedBy,value=GitHubActions \
          --overrides '{"containerOverrides": [{"name": "${{ env.CONTAINER_NAME }}", "command": ["sleep", "7200"]}]}' \
          --count 1)
        echo "Run Task result: $result"

前述の処理で取得した最新のタスク定義、サブネット、セキュリティグループを使ってタスクを起動します。

ECS Execを使用するため、--enable-execute-commandをオプションに追加しています。

起動したコンテナを放置しても勝手に停止するように、--overrides '{"containerOverrides": [{"name": "${{ env.CONTAINER_NAME }}", "command": ["sleep", "7200"]}]}'で起動時のコマンドをsleepに上書きしています。

コンテナに接続するコマンドを表示

    - name: Echo ECS Exec command
      id: echo-ecs-exec-command
      env:
        TASK_ARN: ${{ steps.run-ecs-task.outputs.task_arn }}
      run: |
        echo "ECSタスクへログインするコマンド aws ecs execute-command --cluster ${{ env.AWS_ECS_CLUSTER }} --task ${{ env.TASK_ARN }} --container ${{ env.AWS_ECS_TASK }} --interactive  --command /bin/sh"

起動したタスクに接続するためのコマンドをログに表示します。

CloudShellなどからコマンドを実行してタスクに接続します。

slackなどに通知するようにしても良さそうです。

まとめ

こういうワークフローを作成しているとAWS CLIって本当になんでもできそうな気がしています。

もっと活用して、作業効率をどんどん上げていきたいと思います!

SMARTCAMP Engineer Blog

Discussion