« ^ »

AWS ECS Execを使用してFargate上のコンテナとのセッションを開始する

所要時間: 約 6分

TL;DR

AWS ECS Execがを使用してFargate上のコンテナとのセッションを開始する。同様ことができる従来のSSMセッションマネージャーとの比較を行う。タイムアウトでセッションを閉じないようにするためのEmacs Lispを書く。


AWS CLIでaws ecs execute-commandを使えるようにする

AWS ECS Exec は現時点では AWS CLI を使って設定や利用を行う。サービスの設定時にも必要だが、セッションの開始にも必要になる。

AWS CLIをインストールする

v1系は1.19.28以降、v2系は2.1.31以降が必要となる。

brew install awscli

バージョンを確認する。

aws --version
aws-cli/2.13.28 Python/3.11.6 Darwin/22.6.0 source/x86_64 prompt/off

Session Manager pluginをインストールする

Session Manager pluginも必要になるのでインストールする。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html

以前の作業ログ。 https://blog.symdon.info/posts/1616379820/

IAMの設定

AWS ECS Execの実行にSessionManagerの権限が必要になる。

ポリシー

SessionManagerの必要な権限を付与したIAMポリシーを作成する。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssmmessages:CreateControlChannel",
                "ssmmessages:CreateDataChannel",
                "ssmmessages:OpenControlChannel",
                "ssmmessages:OpenDataChannel"
            ],
            "Resource": "*"
        }
    ]
}

ロール

ECSのタスクロールに先程作成したポリシーをアタッチする。ECSを実行したことがある場合ecsTaskExecutionRoleというロールが既に存在する。今回はそのロールにアタッチした。

ECSの設定

タスク定義

前述で作成したロールをタスクロールとして設定する。

クラスタ

サービス

サービスに対してAWS ECS Execの機能を有効化する必要がある。ただしAWS Console上からそれを設定することはできない。そのためAWS CLIで --enable-execute-command オプションを使って設定の変更を行う。

aws ecs update-service --cluster sample --service sample1 --enable-execute-command

再起動

設定がもろもろ済んだらタスクの再起動を行う。サービスの更新メニューから 強制的にデプロイする のチェックをいれて更新すれば良い。

実行中のタスクのAWS ECS Exec機能が有効になっているかを確認する。

aws ecs describe-tasks コマンドで機能が有効になっているかを確認できる。実行中のタスク情報の enableExecuteCommandtrue になっていれば有効になっている。

aws ecs describe-tasks --cluster sample --tasks 12334567890abcdef1234567890abcde

false になっている場合はなんらかの設定が足りていない。サービスの設定などをAWS CLIを用いて確認してみると良い。

接続方法

aws ecs execute-command でセッションを開始できる。

aws ecs execute-command --cluster sample --task 2d4a10c81ddf473d851e5daf3566f1c4 --container test --interactive --command "/bin/bash"

主に指定するオプション引数を以下に示す。

オプション引数の有無引数の例説明
–clusterありsampleECSクラスター名を指定する。
–taskあり2d4a10c81ddf473d851e5daf3566f1c4セッションを開始するタスク名を指定する。
–containerありtestセッションを開始するタスク内のコンテナを指定する。
–interactiveなしインタラクティブな操作がひつような場合に指定する。
–commandあり"/bin/bash"実行するコマンドを指定する。

SessionManagerを使ってセッションを開始する方法との違い

AWS ECS Execは内部でSessionManagerを使っているらしい。本質的に違いがあるわけではなさそうだが、利用者からみての際をまとめた。

項目ECS ExecSessnManager
AWS CLI必要。必要ではない。
AWS Consoleからのサービス設定できない。できる。
AWS Consoleからのセッションの開始できない。できる。
SSM AgentDockerイメージの中に梱包し、コンテナ起動時に起動させておく。
Session Manager plugin必要。必要ではない。
アクティベーションID必要なし。事前に生成し、環境変数などで渡す。SSMAgentの起動時に指定する。
アクティベーションコード必要なし。事前に生成し、環境変数などで渡す。SSMAgentの起動時に指定する。
インスタンスティアの切り替え必要なし。スタンダードからアドバンスドに切り替える必要がある。
起動時に必要な情報クラスタ名、タスクID、コンテナ名インスタンスID

環境

以下の環境を前提とする。

macOS

sw_vers
ProductName:	macOS
ProductVersion:	11.2.3
BuildVersion:	20D91

Homebrew

brew -v
Homebrew 3.0.10
Homebrew/homebrew-core (git revision 7b6bcedba2; last commit 2021-04-01)
Homebrew/homebrew-cask (git revision b3cd76ab00; last commit 2021-04-01)

実行までの流れ

コンテナに入るまでにはいくつかの情報を取得する必要がある。全てAWS Console上で確認できる。ここではAWS CLIを使用し、それらを探しだす方法を示す。必要な情報は、クラスタ名、タスク名、コンテナ名だ。これらを取得するためには、それぞれにコマンドを実行する必要がある。またその場合、それらのコマンドを実行できる権限を持っていることが前提となる。

必要な情報取得するコマンド
クラスタ名aws ecs list-clusters
タスク名aws ecs list-tasks
コンテナ名aws ecs describe-tasks
必要な情報と情報を取得するコマンド

まずはクラスタ名を取得する。ここではクラスタの一覧を取得し、目的のクラスタを探す。

aws ecs list-clusters
クラスターを探す

クラスタを見つけたら、クラスタ内で実行中のタスクの一覧を取得する。どのタスクを対象にするかは、必要に応じて決める。

aws ecs list-tasks --cluster sample
タスクを探す

タスクを見つけたら、そのタスク内で実行中のコンテナを探す。タスクの情報を取得することで、コンテナの情報も探すことができる。

aws ecs describe-tasks --cluster sample --task 12334567890abcdef1234567890abcde
コンテナを探す。

クラスタ名、コンテナ名はあらかじめ決っているため、探す必要がないかもしれない。一方タスク名は動的に決められるため、都度確認する必要がある。

必要な情報がそろったら aws ecs execute-command でコンテナに入ることができる。

aws ecs execute-command --cluster sample --task 12334567890abcdef1234567890abcde --container mycontaineer --interactive --command "/bin/bash"

ECS上で作業をするためのツール

ecsでの作業を補助するラッパーツールもいくつかある。

ecskはどうやってファイルのコピーを実現しているのか?

ecskにはコンテナとローカルの間でのファイル転送を実現している。 aws ecs execute-command には、そのような機能はないため、どうやって実現しているのか気になった。

この点についてはREADME.mdにも記載されているのだが、S3に一時的にアップロードし、そこからダウンロードするという形で実現していた。コードの一部を抜粋する。

	s3Client := s3.NewFromConfig(cfg)

	if fromLocal {
		_, err := store.Download(ctx, s3Client, bucket, key, path)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	} else {
		_, err := store.Upload(ctx, s3Client, bucket, key, path)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}
ecsk/cmd/cp/main.go 抜粋

ecsk/pkg/store/s3.go には、ここで呼び出しているDownloadやら、Uploadやらの関数が実装されている。内部では github.com/aws/aws-sdk-go-v2/service/s3 を用いて、S3とデータのやりとりをしていた。

aws ecs execute-commandのセッションを維持させる

aws ecs execute-command を用いてECS上のコンテナに接続し、暫く放置すると接続が切断されてしまう。それを防ぐ為のEmacs Lispを書いた。

;;; keep-alive-session --- Keep Alive for AWS ECS Exec. -*- lexical-binding: t -*-

;; Copyright (C) 2024 TakesxiSximada

;; Author: TakesxiSximada <[email protected]>
;; Maintainer: TakesxiSximada <[email protected]>
;; Version: 3
;; Package-Version: 20240319.0000
;; Package-Requires: ((emacs "29.1"))
;; Date: 2024-03-19

;; This file is not part of GNU Emacs.

;;; License:

;; This program is free software: you can redistribute it and/or
;; modify it under the terms of the GNU Affero General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; Affero General Public License for more details.

;; You should have received a copy of the GNU Affero General Public
;; License along with this program.  If not, see
;; <https://www.gnu.org/licenses/>.

;;; Code:

(defgroup keep-alive-session nil
  "String to send to maintain session"
  :group 'lisp
  :group 'extensions)

;;;###autoload
(defcustom keep-alive-session-event " "
  "String to send to maintain session"
  :group 'keep-alive-session
  :type 'string)

(defvar keep-alive-session-process nil)
(defvar keep-alive-session-timer nil)

(defun keep-alive-session-send-event ()
  (process-send-string keep-alive-session-process
		       keep-alive-session-event))

;;;###autoload
(defun keep-alive-session ()
  (interactive)
  (setq keep-alive-session-process
	(get-buffer-process (current-buffer)))
  (setq keep-alive-session-timer
	(run-at-time 1 t #'keep-alive-session-send-event))
  (set-process-sentinel keep-alive-session-process
			#'keep-alive-session-stop))

;;;###autoload
(defun keep-alive-session-stop (&optional process event)
  (interactive)
  (cancel-timer keep-alive-session-timer))

(provide 'keep-alive-session)
;; keep-alive-session.el ends here