Amazon Web Services ブログ

Amazon ECS on AWS Fargate で設定可能な Linux パラメータの追加

この記事は Announcing additional Linux controls for Amazon ECS tasks on AWS Fargate (記事公開日 : 2023 年 8 月 9 日) の翻訳です。

導入

Amazon Elastic Container Service (Amazon ECS) タスクは、同時かつ同一の AWS Fargate インスタンスまたは Amazon EC2 コンテナインスタンスにスケジューリングされる、1 つ以上のコンテナで構成されます。コンテナでは Linux namespace を使用してワークロードの分離を実現するため、Amazon ECS タスク内で複数のコンテナが一緒にスケジューリングされた場合にも、コンテナ同士、あるいはコンテナとホスト間は分離されます。

本日より、AWS Fargate 上の ECS タスクで Linux カーネルパラメータを調整できるようになりました。Linux カーネルパラメータを調整することで、コンテナ化されたネットワークプロキシのネットワークスループットを最適化したり、不要な接続を適切に終了するように TCP キープアライブパラメータを調整し、ワークロードの信頼性を向上させることが可能になります。今回の発表により、AWS Fargate を使用する際にも、Amazon EC2 を使用する場合とより近い形で ECS タスクを実行できるようになりました。

さらに、本日より AWS Fargate 上の ECS タスク内のすべてのコンテナ間で、プロセス ID (PID) namespace を共有できるようになりました。ECS タスク内のコンテナ間で PID namespace を共有することで、AWS Fargate ワークロードの可観測性を実現する手段が広がります。例えば、コンテナランタイムセキュリティツールなどの可観測性ツールをサイドカーコンテナとして実行し、同じ ECS タスク内のコンテナのプロセスを監視することが容易になります。awsvpc ネットワークモードを使用した場合の Network namespace に加えて、PID namespace も、AWS Fargate 上の ECS タスク内のコンテナ間で共有可能な namespace の一員となりました。

この記事では、AWS Fargate の System Controls と PID namespace の共有について深堀りしていきます。

System controls

Linux システムでは、コマンドラインユーティリティ sysctl でカーネルパラメータを調整できます。Dockerfinch などのコマンドラインインターフェースを使用してコンテナをローカルで起動する場合、--sysctl フラグを指定することでカーネルパラメータを変更できます。ECS タスクの場合、タスク定義systemControl キーでパラメータを定義できます。

コンテナ化されたネットワークプロキシを AWS Fargate 上で実行しているお客様からは、ワークロードにおいてより高いスループットを実現するために net.* カーネルパラメータを調整する必要がある、というを頂いていました。特に要望が多かったカーネルパラメータには、接続要求を格納するキューの深さ net.core.somaxconn や、TCP/UDP 接続時に使用する一時的なエフェメラルポートの範囲 net.ipv4.ip_local_port_range などがあります。

信頼性の高いワークロードを設計する際に、AWS Fargate 上の ECS タスクの TCP キープアライブパラメータをカスタマイズしたい、という声も頂いていました。短い TCP キープアライブタイムアウトを設定することで、アプリケーションはネットワーク障害を迅速に検知し、失敗した接続を閉じることができます。TCP キープアライブを調整するユースケースの例としては、コンテナワークロードが Amazon Aurora PostgreSQL クラスターと通信している場合や、Amazon VPC NAT Gateway のトラブルシューティングを行う場合などが考えられます。

以前は、EC2 上で ECS タスクを実行する場合にのみ systemControl キーを設定可能でしたが、本日より AWS Fargate 上の ECS タスクにおいても、同様に設定可能となります。2 つのカーネルパラメータを調整する Amazon ECS タスク定義の例を以下に示します。

{
    ...
    "containerDefinitions": [
        {
            "name": "myproxy",
            "image": "111222333444.dkr.ecr.eu-west-1.amazonaws.com/myproxy:latest",
            "essential": true,
            "systemControls": [
                {
                    "namespace": "net.core.somaxconn",
                    "value": "6000"
                },
                {
                    "namespace": "net.ipv4.ip_local_port_range",
                    "value": "1024    65000"
                }
            ]
        }
    ]
}

AWS Fargate / Amazon EC2 上の ECS タスクで設定可能なすべてのパラメータは、Amazon ECS ドキュメントで確認できます。

IPC namespace のカーネルパラメータを使用する場合、IPC namespace はタスク内のコンテナ間で共有されないため、各コンテナに固有の値を設定できます。一方、Network namespace のカーネルパラメータを使用する場合、1 つのコンテナにパラメータを設定すると、タスク内のすべてのコンテナのパラメータが変更されます。具体的には、以下のように設定が適用されます。

  • コンテナ 1 に net.ipv4.tcp_keepalive_time=100 を設定した場合、この変更はコンテナ 2 にも反映されます。
  • コンテナ 1 に net.ipv4.tcp_keepalive_time=100 を、コンテナ 2 に net.ipv4.tcp_keepalive_time=200 を設定した場合、タスク内で最後に起動するコンテナのパラメータのみが適用されます。

PID namespace の共有

PID namespace は、コンテナ内のプロセスが参照可能なプロセスを制限します。デフォルトでは、コンテナ内のプロセスは同じコンテナ内のプロセスのみを参照でき、他のコンテナ内、または実行基盤となるホスト上のプロセスは参照できません。コンテナ間で PID namespace を共有する一般的なユースケースとして、可観測性ツールがあります。コンテナランタイムセキュリティツールは、多くの場合サイドカーコンテナで実行されます。このとき、サイドカーコンテナ内のプロセスがアプリケーションコンテナ内のプロセスを監視し、不審なシステムコールが実行されていないかどうかを確認します。

今回の発表により、タスク定義内の pidMode キーの値を task に設定することで、AWS Fargate においても ECS タスク内のコンテナ間で PID namespace を共有できるようになりました。

ウォークスルー

このウォークスルーでは、まず PID namespace を共有した ECS タスクを作成します。このタスクには、アプリケーションコンテナ (nginx) とサイドカーコンテナ (デモ用の sleep プロセス) の 2 つのコンテナが含まれています。その後、サイドカーコンテナ内のプロセスが、アプリケーションコンテナ内のプロセスとどのようにやり取りできるかを確認します。

前提条件

  • このウォークスルーでは、Amazon VPC 内に作成した Amazon ECS クラスターを使用します。自身の AWS アカウントで新たに作成する場合は、Amazon ECS getting started guide を参照してください。
  • AWS アカウントとコマンド実行環境の両方において、ECS exec の前提条件を満たしていることを確認してください。

AWS Fargate 上で ECS タスクを実行する

1. pidMode を有効化した、2 つのコンテナを含むタスク定義を作成します。以下の例では、タスク定義内の IAM ロール (executionRoleArntaskRoleArn) を、前提条件で作成した IAM ロールに置き換えてください。

$ cat <<EOF > taskdef.json 
{
    "family": "fargatepidsharing",
    "executionRoleArn": "arn:aws:iam::111222333444:role/ecsTaskExecutionRole",
    "taskRoleArn": "arn:aws:iam::111222333444:role/ecsTaskExecRole",
    "networkMode": "awsvpc",
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "containerDefinitions": [
        {
            "name": "nginx",
            "image": "public.ecr.aws/nginx/nginx:1.25-perl",
            "essential": true
        },
        {
            "name": "sleeper",
            "image": "public.ecr.aws/amazonlinux/amazonlinux:2",
            "essential": true,
            "command": [
                "sleep",
                "infinity"
            ],
            "linuxParameters": {
                "initProcessEnabled": true
            }
        }
    ],
    "cpu": "256",
    "memory": "512",
    "pidMode": "task"
}
EOF

2. aws ecs register-task-definition コマンドを実行し、タスク定義を登録します。

$ aws ecs register-task-definition \
    --cli-input-json file://taskdef.json

3. aws ecs run-task コマンドを実行し、AWS Fargate 上で ECS タスクを実行します。以下の例では、ECS クラスター名、VPC サブネット、セキュリティグループの値を置き換える必要があります。外部からこのタスクにアクセスする必要はないので、プライベートサブネットと、イングレスルールを持たないセキュリティグループ (デフォルトのセキュリティグループなど) で十分です。

$ aws ecs \
    run-task \
    --count 1 \
    --launch-type FARGATE \
    --task-definition fargatepidsharing \
    --cluster mycluster \
    --enable-execute-command \
    --network-configuration "awsvpcConfiguration={subnets=[subnet-07bd4d10ea848a008],securityGroups=[sg-061b33f4ed6b97c34],assignPublicIp=DISABLED}"

4. ECS タスクが正常に起動したら、aws ecs execute-command コマンドを実行して、ECS タスク内のサイドカーコンテナでターミナルセッションを開始します。このコマンド実行時にエラーが発生する場合は、amazon-ecs-exec-checker スクリプトを使用して、すべての前提条件を満たしていることを確認してください。

# ECS タスク ID を取得する
$ aws ecs list-tasks \
     --cluster mycluster
{
    "taskArns": [
        "arn:aws:ecs:us-west-2:111222333444:task/moira-prod/5ce56f226dd4477a9f57918a98fc852f"
    ]
}

# 実行中の ECS タスクで bash シェルを起動する
$ aws ecs execute-command \
    --cluster mycluster \
    --task 5ce56f226dd4477a9f57918a98fc852f \
    --container sleeper \
    --interactive \
    --command "/bin/bash"

5. ECS exec のターミナルセッションで、共有した PID namespace を確認できます。そのために、サイドカーコンテナ内に診断ツールをインストールします。

$ yum install procps strace -y

6. ps コマンド (上記でインストールした procps パッケージに含まれます) を使用することで、共有した PID namespace で実行中のすべてのプロセスを確認できます。このコマンドの出力には、サイドカーコンテナのプロセスだけでなく、アプリケーションコンテナの nginx プロセスも表示されます。また、ECS exec の機能を提供する AWS Systems Manager Session Manager のプロセスも表示されます。

$ ps -aef --forest
UID        PID  PPID  C STIME TTY          TIME CMD
root        38     0  0 09:53 ?        00:00:00 /managed-agents/execute-command/amazon-ssm-agent
root        72    38  0 09:53 ?        00:00:00  \_ /managed-agents/execute-command/ssm-agent-worker
root        34     0  0 09:53 ?        00:00:00 /managed-agents/execute-command/amazon-ssm-agent
root        73    34  0 09:53 ?        00:00:00  \_ /managed-agents/execute-command/ssm-agent-worker
root       266    73  0 09:58 ?        00:00:00      \_ /managed-agents/execute-command/ssm-session-worker ecs-execute-command-0147ec3fd84d94d24
root       276   266  0 09:58 pts/1    00:00:00          \_ /bin/bash
root       286   276  0 09:59 pts/1    00:00:00              \_ ps -aef –forest
root         8     0  0 09:53 ?        00:00:00 /dev/init -- sleep infinity
root        19     8  0 09:53 ?        00:00:00  \_ sleep infinity
root         7     0  0 09:53 ?        00:00:00 nginx: master process nginx -g daemon off;
101         56     7  0 09:53 ?        00:00:00  \_ nginx: worker process
101        285     7  0 09:59 ?        00:00:00  \_ nginx: worker process
root         1     0  0 09:53 ?        00:00:00 /pause

7. PID namespace を共有することで、アプリケーションコンテナ内のプロセスが実行するシステムコールを監視することができます。ステップ 5 でインストールした strace パッケージを使用して、メインの nginx プロセスを監視してみましょう。システムコールを生成するには、kill コマンドで nginx ワーカープロセスを強制終了します。以下の例では、メインの nginx プロセスは PID 7 で、ワーカープロセスは PID 56 です。これらの値は実行する環境によって異なる場合があるため、自身の環境に合わせて値を置き換えてください。

# プロセスの監視を開始する
$ strace -p 7 -o straceoutput.txt &

# nginx ワーカープロセスを強制終了する
$ kill -9 56

# プロセスの監視ログを表示する
$ cat straceoutput.txt
rt_sigsuspend([], 8)                    = ? ERESTARTNOHAND (To be restarted if no handler)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=56, si_uid=101, si_status=SIGKILL, si_utime=0, si_stime=0} ---
gettid()
...

このウォークスルーでは、PID namespace を共有することで、サイドカーコンテナ内のプロセスが ECS タスク内の他のコンテナで実行中のすべてのプロセスを参照し、監視できることを確認しました。ステップ 7 では、アプリケーションコンテナ内のプロセスを強制停止させ、それに伴ってアプリケーションコンテナ内で実行されるシステムコールをサイドカーコンテナから確認しました。

後片付け

以下の手順を実行し、作成したリソースを削除します。

1. ECS Exec のターミナルセッションで exit コマンドを実行し、ECS exec を終了します。

2. aws ecs stop-task コマンドを実行し、ECS タスクを停止します。

$ aws ecs stop-task \
    --cluster mycluster \
    --task 5ce56f226dd4477a9f57918a98fc852f

3. aws ecs deregister-task-definition コマンドを実行し、ECS タスク定義を登録解除します。

aws ecs deregister-task-definition \
    --task-definition fargatepidsharing:1

PID namespace を共有する際の注意点

AWS Fargate 上の ECS タスク内のコンテナ間で PID namespace を共有できるようになりましたが、これには注意すべき点がいくつかあります。それらの注意点を、ウォークスルーで作成したアプリケーション/サイドカーコンテナの例に沿って説明します。

  • サイドカーコンテナ内のプロセスは、アプリケーションコンテナ内のプロセスを監視するだけでなく、停止または再起動できます。
  • サイドカーコンテナ内のプロセスは、アプリケーションコンテナのファイルシステムに部分的にアクセスできます。例えば、アプリケーションが PID 7 として実行されている場合、サイドカーコンテナ内では /proc/7/root/ にあるアプリケーションコンテナのファイルシステムにアクセスできます。このとき、Unix ファイルパーミッションが、アプリケーションコンテナのファイルシステムを保護する唯一の仕組みとなります。
  • ECS タスクで PID namespace を共有する場合、pause プロセスが PID 1 として新しく実行されます。
  • アプリケーションコンテナ内で実行されるプロセスの完全なトレーサビリティを実現するためには、ECS タスクに SYS_PTRACE Linux capability を追加することを検討してください。

まとめ

今回の発表によって、AWS Fargate 上でより多くのワークロードを実行されることを楽しみにしています。PID namespace の共有とカーネルパラメータの調整は、AWS Containers Roadmap でリクエストされていた機能です。私たちは皆様の声を大切にしており、GitHub issue による追加の機能要望や改善に関するフィードバックを心待ちにしています。AWS Fargate のセキュリティアーキテクチャの詳細については、AWS Fargate セキュリティホワイトペーパーを参照してください。