はじめに
CX事業本部の佐藤智樹です。最近のアップデートでECS on FargateでPIDの名前空間をタスク内のコンテナ間で共有できるようになりました!以下の記事にアップデート後の出来ることや嬉しい点が書かれています。
上記記事で紹介されているLinuxカーネルパラメータの設定に関しては、以下の記事でも解説されております。本記事はpidMode
の方にフォーカスしてご紹介します。
このアップデートの何が嬉しいのか
これまでは本機能がなかったのでサイドカー側から本体のコンテナのプロセスやシステムコールの取得が難しくかったです。代表的な商用製品だとサイドカーコンテナ上で証跡取得用のプロセスを動かし、サイドカーのボリュームを本体側でマウントして動かす必要がありました。なので、以前までは商用製品を利用しないと、本体側のコンテナをReadOnly状態で運用したとしても、不審なプロセスやシステムコール、想定外の外部ネットワークへのアクセスが発生しているかなどの証跡管理、不審な動作時の検知などを行うことがかなり難しかったです。商用製品を購入できない場合、ECS on EC2を選択するしかないパターンもあったかと思います。
今回のアップデートにより、サイドカー側からプロセスやシステムコールを簡単に確認できるようになりました。これにより、証跡の取得は比較的簡単にできます。今後この機能を使って多くの便利な商用製品が出てくることも期待できます。また注意点を最後まで読むと分かるのですが、ディレクトリも共有されるので、書き込み可能領域に不審なファイルが増えていないかの監視もできます。
やってみた
ここからは実際にPID共有がどのように表示されるのか、サイドカーからのプロセス停止などを試してみます。検証用の環境はCDKで構築します。
検証環境
項目名 | バージョン |
AWS CDK | 2.91.0 |
Node.js | 18.16.1 |
検証環境用のコード
検証のため、VPC、ECSタスク実行ロール、ECSタスクロール、ECSクラスター/サービス/タスクを作成します。ECSタスクのコンテナは検証用なのでPublicサブネットに配置します。現状CDKではpidModeを指定できないので、デプロイ後にタスク定義を修正して更新します。
lib/fargate_shared_namespace_test-stack.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as iam from "aws-cdk-lib/aws-iam";
export class FargateSharedNamespaceTestStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const myVpc = new ec2.Vpc(this, `${id}-Vpc`, {
ipAddresses: ec2.IpAddresses.cidr("10.100.0.0/16"),
maxAzs: 2,
natGateways: 1,
flowLogs: {},
subnetConfiguration: [
{
cidrMask: 24,
name: "Public",
subnetType: ec2.SubnetType.PUBLIC,
}
],
});
// Roles
const executionRole = new iam.Role(this, `${id}-EcsTaskExecutionRole`, {
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonECSTaskExecutionRolePolicy")],
});
const serviceTaskRole = new iam.Role(this, `${id}-EcsServiceTaskRole`, {
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMFullAccess")],
});
// ECS Task
const serviceTaskDefinition = new ecs.FargateTaskDefinition(this, `${id}-ServiceTaskDefinition`, {
cpu: 512,
memoryLimitMiB: 1024,
executionRole: executionRole,
taskRole: serviceTaskRole,
});
serviceTaskDefinition.addContainer(`${id}-nginxContainer`, {
containerName: "main",
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
});
serviceTaskDefinition.addContainer(`${id}-sleeperContainer`, {
containerName: "sleeper",
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
command: ["sleep", "infinity"],
});
const cluster = new ecs.Cluster(this, `${id}-Cluster`, {
vpc: myVpc,
});
const fargateService = new ecs.FargateService(this, `${id}-FargateService`, {
cluster,
vpcSubnets: myVpc.selectSubnets({ subnetGroupName: "Public" }),
taskDefinition: serviceTaskDefinition,
desiredCount: 1,
maxHealthyPercent: 200,
minHealthyPercent: 50,
enableExecuteCommand: true,
assignPublicIp: true,
});
}
}
上記デプロイ完了後、一度SSMでサイドカーコンテナにログインし、PIDが共有されていないことを確認します。
% aws ecs execute-command \
--cluster "clusterName" \
--task xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
--container sleeper \
--interactive \
--command "/bin/bash"
bash-4.2% yum -y install procps
bash-4.2% ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 4299 749 ? Ss 07:01 0:00 sleep infinity
root 6 0.0 0.1 1322999 14999 ? Ssl 07:01 0:00 /managed-agents/execute-command/amazon-ssm-agent
root 21 0.0 0.3 1408999 24999 ? Sl 07:01 0:00 /managed-agents/execute-command/ssm-agent-worker
root 30 0.1 0.2 1326999 21999 ? Sl 08:33 0:00 /managed-agents/execute-command/ssm-session-worker ecs-execute-command-xxxxxxxxxxxxxxxxx
root 39 0.0 0.0 13999 3999 pts/0 Ss 08:33 0:00 /bin/sh
root 57 0.0 0.0 53999 3999 pts/0 R+ 08:36 0:00 ps -aux
まだ設定変更していないので、メインのコンテナのプロセスは見えません。
次に、今回の機能を有効化します。タスク定義に"pidMode":"task"
を足してタスク定義の新しいリビジョンを作成します。
{
"family": "familyDummyName",
"containerDefinitions": [
{
"name": "main",
"image": "amazon/amazon-ecs-sample",
~(省略)~
},
{
"name": "sleeper",
"image": "amazon/amazon-ecs-sample",
~(省略)~
}
],
"taskRoleArn": "arn:aws:iam::123456789012:role/FargateSharedNamespaceTes-FargateSharedNamespaceTe-XXXXXXXXX",
"executionRoleArn": "arn:aws:iam::123456789012:role/FargateSharedNamespaceTes-FargateSharedNamespaceTe-XXXXXX",
"networkMode": "awsvpc",
"requiresCompatibilities": [
"FARGATE"
],
"cpu": "512",
"memory": "1024",
"pidMode": "task"
}
上記のタスク定義でデプロイするようサービスを更新します。5分程度するとタスクが更新されます。
新しいコンテナにログインしてみます。
% aws ecs execute-command \
--cluster "clusterName" \
--task xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
--container sleeper \
--interactive \
--command "/bin/bash"
bash-4.2% yum -y install procps
bash-4.2% ps -auxf
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 25 0.1 0.1 1246999 14999 ? Ssl 08:57 0:00 /managed-agents/execute-command/amazon-ssm-agent
root 92 0.0 0.3 1332999 24999 ? Sl 08:57 0:00 \_ /managed-agents/execute-command/ssm-agent-worker
root 19 0.1 0.1 1321999 14999 ? Ssl 08:57 0:00 /managed-agents/execute-command/amazon-ssm-agent
root 93 0.0 0.3 1408999 26999 ? Sl 08:57 0:00 \_ /managed-agents/execute-command/ssm-agent-worker
root 109 0.1 0.2 1326999 21999 ? Sl 08:58 0:00 \_ /managed-agents/execute-command/ssm-session-worker ecs-execute-command-xxxxxxxxxxxxxxxx
root 119 0.0 0.0 13999 3999 pts/0 Ss 08:58 0:00 \_ /bin/bash
root 137 0.0 0.0 53999 3999 pts/0 R+ 09:00 0:00 \_ ps -auxf
root 13 0.1 0.2 334999 19999 ? Ss 08:57 0:00 /usr/sbin/apache2 -D FOREGROUND
apache 41 0.0 0.1 615999 9199 ? Sl 08:57 0:00 \_ /usr/sbin/apache2 -D FOREGROUND
apache 46 0.0 0.1 385999 9199 ? Sl 08:57 0:00 \_ /usr/sbin/apache2 -D FOREGROUND
apache 64 0.0 0.1 385999 9199 ? Sl 08:57 0:00 \_ /usr/sbin/apache2 -D FOREGROUND
apache 67 0.0 0.1 385999 9199 ? Sl 08:57 0:00 \_ /usr/sbin/apache2 -D FOREGROUND
apache 73 0.0 0.1 385999 9199 ? Sl 08:57 0:00 \_ /usr/sbin/apache2 -D FOREGROUND
root 7 0.1 0.0 4999 799 ? Ss 08:57 0:00 sleep infinity
root 1 0.1 0.0 991 6 ? Ss 08:57 0:00 /pause
これでmainコンテナ側のプロセスも見えるようになりました!注意にある通り、PID 1はpauseというプロセスになるので、タスクの終了時は注意が必要になるかもしれないです。いくつか検証してみます。
サイドカーからmain側のコンテナのファイルを確認
後述しますが注意事項まで読むと、共有しているコンテナのファイルへのアクセスが可能と書いてあります。なので、別コンテナのファイルを覗いてみます。今回は、まずは先にmainコンテナ側でダミーファイルを作成します。その後サイドカーからmainコンテナのファイルを見るためPIDの13を覗いてみます。
# mainコンテナ側
% aws ecs execute-command \
--cluster "clusterName" \
--task xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \
--container main \
--interactive \
--command "/bin/bash"
bash-4.2% touch /tmp/hoge.txt
次にサイドカーからファイルを確認してみます。
# サイドカーコンテナ側
bash-4.2% ls -l /proc/13/root/tmp
total 0
-rw-r--r-- 1 root root 0 Aug 20 09:26 hoge.txt
上記より、名前空間の共有だけでなくファイルの閲覧権限なども共有されることがわかりました。ここはLinux上の権限に注意が必要そうです。
サイドカーからプロセスの停止
今度はサイドカーからプロセスの停止を試してみます。まずはmain側でsleepコマンドを動かして、その後にサイドカー側から該当のプロセスを停止してみます。
# mainコンテナ側
bash-4.2% sleep 100000
# サイドカーコンテナ側
bash-4.2% ps -auxf
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 25 0.1 0.1 1246999 14999 ? Ssl 08:57 0:00 /managed-agents/execute-command/amazon-ssm-agent
root 92 0.0 0.3 1332999 24999 ? Sl 08:57 0:00 \_ /managed-agents/execute-command/ssm-agent-worker
root 163 0.0 0.2 1326999 20999 ? Sl 09:10 0:01 \_ /managed-agents/execute-command/ssm-session-worker ecs-execute-command-yyyyyyyyyyyyyyyyy
root 173 0.0 0.0 13999 3199 pts/0 Ss 09:10 0:00 \_ /bin/bash
root 245 0.0 0.0 6999 749 pts/0 S+ 09:39 0:00 \_ sleep 100000
bash-4.2% kill -9 245
bash-4.2% ps -auxf
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 25 0.1 0.1 1246999 14999 ? Ssl 08:57 0:00 /managed-agents/execute-command/amazon-ssm-agent
root 92 0.0 0.3 1332999 24999 ? Sl 08:57 0:00 \_ /managed-agents/execute-command/ssm-agent-worker
root 163 0.0 0.2 1326999 20999 ? Sl 09:10 0:01 \_ /managed-agents/execute-command/ssm-session-worker ecs-execute-command-yyyyyyyyyyyyyyyyy
root 173 0.0 0.0 13999 3199 pts/0 Ss 09:10 0:00 \_ /bin/bash
プロセスの停止が可能なことが確認できました。今度は、公式記事にあるようにstraceを使いながら同じ操作を行ってみます。
# mainコンテナ側
bash-4.2% sleep 100000
# サイドカーコンテナ側
bash-4.2% yum install strace -y
bash-4.2% cd /tmp
bash-4.2% ps -auxf | grep "sleep 100000"
root 252 0.0 0.0 6164 776 pts/0 S+ 09:43 0:00 \_ sleep 100000
root 264 0.0 0.0 10920 904 pts/0 S+ 09:45 0:00 \_ grep sleep 100000
bash-4.2% strace -p 252 -o straceoutput.txt &
bash-4.2% kill -9 252
bash-4.2% cat straceoutput.txt
restart_syscall(<... resuming interrupted read ...>) = ?
+++ killed by SIGKILL +++
[1]+ Done strace -p 252 -o straceoutput.txt
最後にmainコンテナのプロセスをトレースしながらサイドカーからkillしてみます。ここでkillしたタイミングでタスクごと落ちてタスク再起動となりました。
# サイドカーコンテナ側
bash-4.2% ps -auxf | grep "0:00 /usr/sbin/apache2 -D FOREGROUND"
root 276 0.0 0.0 10920 904 pts/0 S+ 09:51 0:00 \_ grep 0:00 /usr/sbin/apache2 -D FOREGROUND
root 13 0.0 0.2 334999 19999 ? Ss 08:57 0:00 /usr/sbin/apache2 -D FOREGROUND
bash-4.2% strace -p 13 -o main-straceoutput.txt &
bash-4.2% kill -9 13
注意点
元の記事にも記載はありますが、この機能を有効化する場合いくつか注意事項があります。例を省略した意訳を記載します。
- サイドカーコンテナ内のプロセスから、アプリコンテナ内のプロセスを監視、停止、または再起動できる
- サイドカーコンテナ内のプロセスは、アプリコンテナのファイルシステムを表示できます。ファイルシステムの保護は、Unix ファイルのアクセス許可を通じて行われます
- ECSタスクでプロセスを共有する場合、新しいpauseプロセスはタスク全体の PID 1 として実行されます。
- アプリコンテナ内で実行されているプロセスの完全なトレーサビリティを提供するために、ECSタスクにSYS_PTRACE Linux capabillityの追加が必要な場合もあります。
特にPIDの共有というタイトルだけだとファイルの共有までされるというイメージがなかったので、サイドカーからアプリのコンテナに見られたくないものはLinuxの権限に注意する必要があります。
所感
今回のPID共有およびファイル共有によって、サイドカーのコンテナから証跡を取得できる範囲はかなり増えたと感じます。ただサイドカーの権限がかなり強くなるので注意は必要です。自作で証跡の収集などは可能なようですが、速度の問題が起きないかやサイドカーが信頼できるコンテナであるかがより重要になりそうです。そうなると個人で証跡管理を実装するというよりは、商用製品がより作られやすくなることや既存の製品がより便利になることを期待したいと思えました。