はじめに
ECS Execは、稼働中のECSタスクのコンテナに直接入れる便利な機能だが、実際に使うときのコマンドは以下のように長い。
aws ecs execute-command \
--cluster my-cluster \
--task 1234567890abcdef1234567890abcdef \
--container my-container \
--command "/bin/bash" \
--interactive
クラスター名はまだしも、タスクIDは毎回aws ecs list-tasksなどで調べて貼り付ける必要があり、地味に面倒である。
サービスが複数あるプロジェクトだと「あれ、このサービスのクラスター名なんだっけ」となることもよくある。
この面倒さを解消するために、クラスター・サービス・タスク・コンテナを対話的に選択するだけで接続できるシェルスクリプト「ecs-gangway」を作った。
ecs-gangwayとは
aws ecs execute-commandを打つまでに必要な情報(クラスター名・サービス名・タスクID・コンテナ名)を、bashのselectを使って選択肢から選ぶだけで揃えられるようにしたラッパースクリプト。
コンテナという船に乗り込むための「タラップ(gangway)」とECSを合わせてecs-gangwayとした。
特徴
- クラスター・サービス・タスク・コンテナを対話的に選択(候補が1つしかない場合は自動決定)
- 環境名(
prod/stgなど)でクラスター・サービスを絞り込み可能 - 本番環境(
prd/prodを含む名前)に接続する際は、確認入力なしでは接続できないガードレール付き - コンテナに
bashが入っていなくてもshにフォールバックして接続 - 複数のAWSプロファイル・リージョンに対応
- UI表示の日本語/英語切り替え(
ja/en)に対応
使い方
前提として以下が必要になる。
- AWS CLI v2
- Session Manager Plugin
- 対象のECSタスク定義でECS Execが有効化されていること
- 実行するIAMユーザー/ロールに
ecs:ExecuteCommandなどの権限があること
インストールはリポジトリをcloneして実行権限を付けるだけ。
git clone https://github.com/st-man-hori/ecs-gangway.git
cd ecs-gangway
chmod +x ecs-gangway.sh
実行時の引数はENV・REGION・PROFILE・LANGの4つで、いずれも省略可能。位置引数とオプション引数(--envなど)の両方に対応している。
# 位置引数
./ecs-gangway.sh [ENV] [REGION] [PROFILE] [LANG]
# オプション引数(推奨)
./ecs-gangway.sh [--env ENV] [--region REGION] [--profile PROFILE] [--lang ja|en]
| 引数 | オプション形式 | 必須 | 説明 |
|---|---|---|---|
| ENV |
-e, --env
|
- | クラスター/サービス名を絞り込むキーワード(例: prod, stg, dev)。省略時は全件表示 |
| REGION |
-r, --region
|
- | AWSリージョン。省略時はap-northeast-1
|
| PROFILE |
-p, --profile
|
- | AWS CLIのプロファイル名。省略時はデフォルトプロファイル/環境変数を使用 |
| LANG |
-l, --lang
|
- | UI言語。jaまたはen。省略時はLC_ALL/LANGから自動判定 |
# 全クラスターから対話的に選ぶ
./ecs-gangway.sh
# "stg" を含むクラスター/サービスに絞り込む
./ecs-gangway.sh stg
# リージョンとプロファイルを指定
./ecs-gangway.sh prod us-east-1 my-profile
# UI表示を英語に固定
./ecs-gangway.sh prod us-east-1 my-profile en
# LANGだけ指定して英語UIで開始(他は対話選択/デフォルト)
./ecs-gangway.sh --lang en
# オプション形式で明示指定
./ecs-gangway.sh --env prod --region us-east-1 --profile my-profile --lang en
実行すると、クラスター→サービス→タスク→コンテナの順に選択肢が出てくる。候補が1つしかないステップは自動で決定されるので、サービスが1つしかないクラスターであれば実質2〜3ステップで接続できる。
中身の工夫点
実装上工夫したところをいくつか紹介する。
候補が1件なら選択肢を出さない
クラスター・サービス・タスク・コンテナのどのステップも、絞り込んだ結果が1件しかなければselectを出さずにそのまま採用している。逆に0件ならエラーで止める。
if [ ${#FILTERED_CLUSTERS[@]} -eq 1 ]; then
CLUSTER_NAME="${FILTERED_CLUSTERS[0]}"
echo "🏗️ 対象クラスター(自動決定): ${CLUSTER_NAME}"
else
echo "🏗️ クラスターを選択してください:"
select cluster_opt in "${FILTERED_CLUSTERS[@]}"; do
if [ -n "${cluster_opt}" ]; then
CLUSTER_NAME="${cluster_opt}"
break
else
echo "❌ 無効な選択です。再入力してください。"
fi
done
fi
このパターンをクラスター・サービス・タスク・コンテナの4箇所で繰り返している。サービスが1つしかない小さめの環境だと、ほぼノータイムで対象コンテナまでたどり着ける。
ENVキーワードでの絞り込みは「ヒットしなければ全件表示」にフォールバック
ENV引数はクラスター名・サービス名の部分一致でフィルタするために使っているが、キーワードが何にもヒットしない場合はエラーにせず全件を選択肢として出すようにしている。
FILTERED_CLUSTERS=()
if [ -n "${ENV}" ]; then
for cluster in "${ALL_CLUSTERS[@]}"; do
if [[ "${cluster}" == *"${ENV}"* ]]; then
FILTERED_CLUSTERS+=("${cluster}")
fi
done
fi
if [ ${#FILTERED_CLUSTERS[@]} -eq 0 ]; then
FILTERED_CLUSTERS=("${ALL_CLUSTERS[@]}")
fi
「絞り込みキーワードを間違えて何も出てこない」というのが一番ストレスなので、ヒットしなければ全件出して手動で選べるようにしてある。また、ENVを省略してクラスターを直接選んだ場合は、選ばれたクラスター名からprd/stg/devを推測し、以降のサービス絞り込みや本番判定に使い回している。
本番環境への接続にはガードレールを入れる
クラスター名かサービス名、あるいは判定したENVにprd/prodが含まれる場合は、接続前に赤字で警告を出し、yesと入力しない限り接続をキャンセルするようにしている。
if [ ${IS_PRD} -eq 1 ]; then
echo -e "${RED}本番環境のコンテナに入りますか?${NC}"
read -p "問題なければ [yes] と入力してください: " CONFIRM
if [ "${CONFIRM}" != "yes" ]; then
echo -e "${YELLOW}❌ 接続を安全にキャンセルしました。${NC}"
exit 0
fi
echo -e "${RED}🚀 本番環境に接続します...${NC}"
fi
対話的に選べて便利、で終わらせると「選択を1つ間違えて気づいたら本番に入っていた」という事故が起きうるので、対話操作の手軽さと本番への安全装置は両立させたかった。
bashが無いイメージへのフォールバック
Alpineベースなどbashが入っていないイメージも普通にあるので、接続先でbashが使えるか確認し、無ければshにフォールバックするようにしている。
SHELL_CMD="if command -v bash >/dev/null 2>&1; then exec bash; else exec sh; fi"
aws ecs execute-command \
--cluster "${CLUSTER_NAME}" \
--task "${TASK_ID}" \
--container "${CONTAINER_NAME}" \
--command "sh -c '${SHELL_CMD}'" \
--interactive \
"${AWS_OPTS[@]}"
--commandにそのまま/bin/bashを渡すと、bashが無いコンテナでは接続自体が失敗してしまう。判定用のワンライナーをsh -c経由で渡すことで、どちらのシェルしか無くても同じコマンドで接続できるようにしている。
UI文言をt()関数でja/en切り替え
本番ガードレールのメッセージなど、スクリプト内の表示文言はすべて日本語決め打ちだったが、英語話者のメンバーにも配布したくなったので、キーを渡すと言語に応じた文字列を返すt()関数を用意した。gettextのような本格的なi18nライブラリを入れるほどでもない規模だったので、連想配列っぽくcase文で分岐させるだけの素朴な作りにしている。
t() {
local key="$1"
if [ "${UI_LANG}" = "ja" ]; then
case "${key}" in
fetching_clusters) echo "🔍 クラスター一覧を AWS から取得中..." ;;
choose_cluster) echo "🏗️ クラスターを選択してください:" ;;
# ...
esac
else
case "${key}" in
fetching_clusters) echo "🔍 Fetching clusters from AWS..." ;;
choose_cluster) echo "🏗️ Select a cluster:" ;;
# ...
esac
fi
}
%sを含む文言はprintf経由で埋め込む形にして、echo "$(t fetching_clusters)"やprintf "$(t cluster_auto)\n" "${CLUSTER_NAME}"のように呼び出す。UI_LANGは--langオプションで明示指定できるほか、未指定ならLC_ALL/LANG環境変数の値がjaで始まるかどうかで自動判定するようにしていて、明示指定が無い日本語環境ではこれまで通り何も意識せず日本語UIになる。
さいごに
ECS Execは便利な機能だが、コマンドの組み立てが地味に面倒で、結局よく使うクラスターだけ別スクリプトを個別に用意する、みたいなことをやりがちだった。ecs-gangwayを使うようになってからは、環境名を1つ渡すだけでほぼ迷わず対象コンテナまでたどり着けるようになった。
複数クラスター・複数サービスのECS環境を触っていて、毎回タスクIDを調べるのが面倒だと感じている人の参考になれば幸いです。