はじめに

猫大好きエンジニアの福田です。

AWS ECS Execで起動中のコンテナに接続しSE作業や調査などをすることがあると思います。
私は以下記事を参考に接続コマンドをメモ帳などにスニペットとして保存し、日々利用していました。

AWS FargateでECS Execの有効化、Linuxコンテナへログインまでのコマンドを簡単にまとめました(fish shellユーザ向け)

この記事は fish shell ユーザー向けですが、私は zsh を利用していますので、以下の「今までの実行方法」ような感じでスニペット化していました。

今までの実行方法

#
# 接続するスニペット
#
ACCOUNT_ID=000000000000
STAGE=stg
PROFILE=myporject-${STAGE}

CLUSTERNAME=arn:aws:ecs:ap-northeast-1:${ACCOUNT_ID}:cluster/myproject-${STAGE}-container
SERVICENAME=arn:aws:ecs:ap-northeast-1:${ACCOUNT_ID}:service/myproject-${STAGE}-container/myproject-${STAGE}-service
TASKDEFNAME=myproject-${STAGE}-task
TASKNAME=$(aws ecs list-tasks --cluster $CLUSTERNAME --service-name $SERVICENAME --query "taskArns[0]" --output text --profile ${PROFILE})
CONTAINERNAME=myproject-${STAGE}-container

aws --profile ${PROFILE} ecs execute-command --cluster ${CLUSTER} --task ${TASK} --container ${CONTAINER} --interactive --command '/bin/bash'"

問題

このコマンドでも問題なく実行することはできるのですが、融通が効かなかったりと少し面倒でした。

  • 違う環境(ステージ)に接続したい場合、書き換えが必要
  • 違うサービスに接続したい場合、別途スニペットが必要
    • タスク内に複数コンテナがある場合も同様
  • ピンポイントで特定のコンテナに接続が難しい
  • 色々なプロジェクトがある場合、その分スニペットが必要

解決方法

fzf (コマンドラインでインタラクティブに曖昧検索できるツール)を利用しzshの関数にすることで、
以下のようなコマンドを打つだけで良くなりました🙌
では、実際に手元で以下コマンドが利用できるようにしていきたいと思います。

aee --profile {AWSのプロフィール名} {オプション:検索文字列(コンテナ名やクラスタ名、サービス名など)}

# 例1) aee --profile myproject-dev
# 例2) aee --profile myproject-dev rails-container
# ※aee は aws exec execute-command の頭文字です。

2023-05-10_18.27.05

前提条件

  • ECS Execコマンドが実行できる環境であること
  • デフォルトシェルがzshであること
  • fzfがインストールされていること
    • brewが利用できる場合は brew install fzf でインストールできます

設定方法

以下コードを ~/.zshrc などに追記し、source ~/.zshrcコマンドなどで反映します。

処理の流れ

  • クラスタ名一覧を取得
  • クラスタ名に紐づくサービス名を取得
  • サービス名に紐づくタスク名を取得
  • タスク名に紐づくコンテナ名を取得
  • {クラスタ名}/{サービス名}/{タスク名}/{コンテナ名}一覧を作成
  • 作成した一覧をfzfに渡す(引数で渡された検索文字列があれば絞る)
  • 接続したいコンテナを選択しエンターキーで、aws ecs exec-command のコマンドをターミナルに出力

最後のコマンドの出力について
出力せずにコマンドを実行することはできるのですが、あえてprint -zでコマンド自体を出力しています。
理由は、一度接続するコンテナを確認することができるのと、コマンドヒストリーにコマンドを残すことができるためです。
再度同じコンテナに接続する場合は、カーソルの↑やCtrl + rで検索し接続ができますのでそのようにしています。

※タスク数やコンテナ数が多い場合、応答するまで少し時間がかかる場合があります。また権限が不足している場合は追加が必要になります。

function fzf_aws_ecs_exec() {
  zparseopts -D -E -A opthash -- -profile:

  local profile="${opthash[--profile]}"
  local query="$*"
  local selections
  local clusters services tasks containers

  # cluster > service > taskdef > taskid > container
  clusters=`aws --profile ${profile} ecs list-clusters --no-paginate | jq -r '.clusterArns[]' | sort | cut -d '/' -f 2`
  clusters=(`echo ${clusters}`)
  for cluster in "${clusters[@]}"; do
    # echo "cluster: ${cluster}"
    services=`aws --profile ${profile} ecs list-services --no-paginate --cluster ${cluster} | jq -r '.serviceArns[]' | sort | cut -d '/' -f 3`
    services=(`echo ${services}`)
    for service in "${services[@]}"; do
      # echo "service: ${service}"
      tasks=`aws --profile ${profile} ecs list-tasks --no-paginate --cluster ${cluster} --service-name ${service} | jq -r '.taskArns[]' | sort | cut -d '/' -f 3`
      tasks=(`echo ${tasks}`)
      for task in "${tasks[@]}"; do
        # echo "task: ${task}"
        containers=`aws --profile ${profile} ecs describe-tasks --no-paginate --cluster ${cluster} --tasks ${task} | jq -r '.tasks[].containers[].name' | sort`
        containers=(`echo ${containers}`)
        for container in "${containers[@]}"; do
          selections+="${cluster}/${service}/${task}/${container}\n"
        done
      done
    done
  done

  local selected=`echo -n ${selections} | sort | fzf --query "${query}" | tr '/' '\n'`
  if [ -z $selected ]; then
    return 0
  fi

  selected=(`echo ${selected}`)
  print -z "aws --profile ${profile} ecs execute-command --cluster ${selected[1]} --task ${selected[3]} --container ${selected[4]} --interactive --command '/bin/bash'"
}

alias aee=fzf_aws_ecs_exec

まとめ

コマンドも短くなり、だいぶ楽になったと思いますがいかがでしょうか。
毎回コマンドを作って実行していた方は是非使ってみてください。

また、fzf は便利なのでecs execコマンド以外でも活用できると思いますので、
これを参考に他の便利コマンドを作成してみるのも良いと思います。

以上、aws ecs execute-command を fzf で楽に接続する方法をご紹介しました。
どなたかの参考になれば幸いです。