AWS ECS Fargate で Sysdig Workload Agent を動かす完全ガイド
はじめに
AWS ECS Fargate上でセキュリティ監視を実装する際、多くのチュートリアルではパブリックサブネットに直接Fargateタスクをデプロイする簡易的な構成が紹介されています。しかし、本番環境ではプライベートサブネット構成が必須です。
本記事では、Sysdig Workload Agentをサイドカーとして動作させながら、セキュアなネットワーク構成を実現する方法を実践的に解説します。
対象読者
- AWS ECS Fargateでセキュアなアーキテクチャを構築したい方
- Sysdig Workload Agentの導入を検討している方
- コンテナランタイムセキュリティに興味がある方
- Terraformでインフラをコード化したい方
この記事で学べること
- プライベートサブネット + ALB + NAT Gateway + Bastionのセキュアなアーキテクチャ設計
- Sysdig Workload Agentのサイドカー構成の実装方法
- ECS Fargate特有のネットワーク設定のベストプラクティス
- AWS Secrets Managerを使った安全な認証情報管理
- 実際に動作するTerraformコードの全容
なぜプライベートサブネット構成が必要なのか?
パブリックサブネット構成の問題点
[Internet] → [Public Subnet] → [Fargate Task (Public IP)]
├── Application Container
└── Sysdig Workload Agent
セキュリティリスク:
- Fargateタスクに直接パブリックIPが割り当てられる
- インターネットから直接アクセス可能
- セキュリティグループのみが防御層
- 攻撃対象領域が広い
セキュアなプライベートサブネット構成
[Internet] → [ALB (Public)] → [Fargate Task (Private)]
├── Application Container
└── Sysdig Workload Agent
↓ (Outbound via NAT)
[Sysdig Collector]
[管理者] → [Bastion (Public)] → SSH → [Private Resources]
セキュリティの向上:
- Fargateタスクはプライベートサブネットに配置
- パブリックIPなし、インターネットから直接アクセス不可
- ALBが公開エンドポイントとして機能(WAF統合可能)
- NAT Gatewayで制御されたアウトバウンド通信
- Bastionを経由した安全な管理アクセス
- 多層防御の実現
アーキテクチャ設計
ネットワーク構成
VPC: 10.0.0.0/16
├── Public Subnet 1a: 10.0.1.0/24 (ALB, NAT Gateway, Bastion)
├── Public Subnet 1c: 10.0.2.0/24 (ALB)
├── Private Subnet 1a: 10.0.10.0/24 (Fargate Tasks)
└── Private Subnet 1c: 10.0.11.0/24 (Fargate Tasks)
トラフィックフロー
1. インバウンド(ユーザー → アプリケーション)
Internet
↓ HTTP/HTTPS
[ALB in Public Subnet]
↓ Security Group: ALB → ECS
[Target Group]
↓ Port 80
[Fargate Task in Private Subnet]
└── nginx-app Container
2. アウトバウンド(Sysdig Agent → Collector)
[Fargate Task in Private Subnet]
└── sysdig-workload-agent Container
↓ HTTPS (Port 6443)
[NAT Gateway in Public Subnet]
↓
[Internet Gateway]
↓
[ingest-us2.app.sysdig.com]
3. 管理アクセス(管理者 → インフラ)
[管理者]
↓ SSH (Port 22)
[Bastion Host in Public Subnet]
↓ Security Group: Bastion → ECS
[Private Subnet Resources]
- ECS Tasks
- 他のプライベートリソース
デプロイされるAWSリソース(全31個)
| カテゴリ | リソース | 数量 |
|---|---|---|
| ネットワーク | VPC | 1 |
| Internet Gateway | 1 | |
| NAT Gateway + Elastic IP | 1 + 1 | |
| Subnets (Public/Private) | 4 | |
| Route Tables + Associations | 3 + 4 | |
| ロードバランシング | Application Load Balancer | 1 |
| Target Group | 1 | |
| Listener | 1 | |
| セキュリティ | Security Groups (ALB/ECS/Bastion) | 3 |
| IAM Roles + Policies | 2 + 2 | |
| Secrets Manager Secret | 1 | |
| コンピューティング | ECS Cluster | 1 |
| ECS Task Definition | 1 | |
| ECS Service | 1 | |
| EC2 Instance (Bastion) | 1 | |
| ログ | CloudWatch Log Groups | 2 |
Sysdig Workload Agentとは?
特徴
- eBPF非依存: カーネルモジュールやeBPFを必要としないため、Fargate環境でも動作
- サイドカー構成: アプリケーションコンテナと同じECSタスク内で動作
- 軽量: リソース消費が少ない(CPU: 0.25 vCPU, Memory: 512 MB)
- リアルタイム監視: プロセス、ネットワーク、ファイルシステムアクティビティを監視
アーキテクチャ上のポイント
{
"pidMode": "task",
"containerDefinitions": [
{
"name": "nginx-app",
"image": "nginx:latest"
},
{
"name": "sysdig-workload-agent",
"image": "quay.io/sysdig/workload-agent:latest"
}
]
}
-
pidMode: "task": 全コンテナが同じPID名前空間を共有(Sysdig Agentが他のコンテナのプロセスを監視可能) - サイドカーパターン: アプリケーションコードの変更不要
- Secrets Manager統合: Sysdig Access Keyを安全に管理
実装手順
前提条件
- AWS CLI設定済み
- Terraform v1.0以上
- Sysdigアカウント(Access Key取得済み)
- SSH鍵ペア作成済み(EC2 Key Pair)
ディレクトリ構成
.
├── main.tf # メインのTerraformコード
├── variables.tf # 変数定義
├── outputs.tf # 出力定義
├── terraform.tfvars # 変数値(機密情報含む)
├── container_definitions.json # ECSコンテナ定義
└── README.md # ドキュメント
Step 1: Secrets Managerにアクセスキーを登録
aws secretsmanager create-secret \
--name your-project-sysdig-access-key \
--description "Sysdig Access Key for ECS Fargate" \
--secret-string "your-sysdig-access-key" \
--region ap-northeast-1
出力例:
{
"ARN": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:your-project-sysdig-access-key-XXXXXX",
"Name": "your-project-sysdig-access-key"
}
この ARN を後ほど container_definitions.json で使用します。
Step 2: Terraform変数ファイルの作成
terraform.tfvars:
sysdig_access_key = "your-sysdig-access-key"
key_name = "your-ec2-key-pair-name"
region = "ap-northeast-1"
注意: このファイルには機密情報が含まれるため、.gitignoreに追加してください。
Step 3: コンテナ定義ファイルの作成
container_definitions.json:
[
{
"name": "nginx-app",
"image": "nginx:latest",
"essential": true,
"portMappings": [
{
"containerPort": 80,
"hostPort": 80
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/your-project-app",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "nginx-app"
}
}
},
{
"name": "sysdig-workload-agent",
"image": "quay.io/sysdig/workload-agent:latest",
"essential": true,
"environment": [
{ "name": "SYSDIG_COLLECTOR", "value": "ingest-us2.app.sysdig.com" },
{ "name": "SYSDIG_COLLECTOR_PORT", "value": "6443" },
{ "name": "SYSDIG_SIDECAR", "value": "auto" }
],
"secrets": [
{
"name": "SYSDIG_ACCESS_KEY",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:your-project-sysdig-access-key-XXXXXX"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/your-project-workload",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "workload-agent"
}
}
}
]
重要ポイント:
-
SYSDIG_COLLECTOR: Sysdigリージョンに応じて変更(US:ingest-us2.app.sysdig.com, EU:ingest-eu1.app.sysdig.com, など) -
valueFrom: Step 1で取得したSecrets ManagerのARNを指定 -
essential: true: 両方のコンテナを必須に設定(どちらかが落ちたらタスク全体が再起動)
Step 4: Terraformコードの作成
main.tf の主要セクション:
VPCとサブネット
# VPC
resource "aws_vpc" "this" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "your-project-vpc"
}
}
# Public Subnet 1a (ALB, NAT Gateway, Bastion用)
resource "aws_subnet" "public_1a" {
vpc_id = aws_vpc.this.id
cidr_block = "10.0.1.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
map_public_ip_on_launch = true
tags = {
Name = "your-project-public-1a"
}
}
# Public Subnet 1c (ALB用)
resource "aws_subnet" "public_1c" {
vpc_id = aws_vpc.this.id
cidr_block = "10.0.2.0/24"
availability_zone = data.aws_availability_zones.available.names[1]
map_public_ip_on_launch = true
tags = {
Name = "your-project-public-1c"
}
}
# Private Subnet 1a (Fargate用)
resource "aws_subnet" "private_1a" {
vpc_id = aws_vpc.this.id
cidr_block = "10.0.10.0/24"
availability_zone = data.aws_availability_zones.available.names[0]
tags = {
Name = "your-project-private-1a"
}
}
# Private Subnet 1c (Fargate用)
resource "aws_subnet" "private_1c" {
vpc_id = aws_vpc.this.id
cidr_block = "10.0.11.0/24"
availability_zone = data.aws_availability_zones.available.names[1]
tags = {
Name = "your-project-private-1c"
}
}
Internet Gateway と NAT Gateway
# Internet Gateway
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.this.id
tags = {
Name = "your-project-igw"
}
}
# Elastic IP for NAT Gateway
resource "aws_eip" "nat" {
domain = "vpc"
depends_on = [aws_internet_gateway.igw]
tags = {
Name = "your-project-nat-eip"
}
}
# NAT Gateway (Public Subnet 1aに配置)
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public_1a.id
depends_on = [aws_internet_gateway.igw]
tags = {
Name = "your-project-nat"
}
}
ルートテーブル
# Public Route Table
resource "aws_route_table" "public" {
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "your-project-public-rt"
}
}
# Private Route Table (NAT Gateway経由)
resource "aws_route_table" "private" {
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat.id
}
tags = {
Name = "your-project-private-rt"
}
}
# Route Table Associations
resource "aws_route_table_association" "public_1a" {
subnet_id = aws_subnet.public_1a.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "public_1c" {
subnet_id = aws_subnet.public_1c.id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private_1a" {
subnet_id = aws_subnet.private_1a.id
route_table_id = aws_route_table.private.id
}
resource "aws_route_table_association" "private_1c" {
subnet_id = aws_subnet.private_1c.id
route_table_id = aws_route_table.private.id
}
セキュリティグループ
# ALB Security Group
resource "aws_security_group" "alb" {
name = "your-project-alb-sg"
description = "Security group for ALB"
vpc_id = aws_vpc.this.id
ingress {
description = "HTTP from Internet"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "All outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "your-project-alb-sg"
}
}
# ECS Security Group
resource "aws_security_group" "ecs" {
name = "your-project-ecs-sg"
description = "Security group for ECS tasks"
vpc_id = aws_vpc.this.id
ingress {
description = "HTTP from ALB"
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
ingress {
description = "SSH from Bastion"
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.bastion.id]
}
egress {
description = "All outbound (for Sysdig Collector)"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "your-project-ecs-sg"
}
}
# Bastion Security Group
resource "aws_security_group" "bastion" {
name = "your-project-bastion-sg"
description = "Security group for Bastion host"
vpc_id = aws_vpc.this.id
ingress {
description = "SSH from anywhere (本番環境では特定IPに制限推奨)"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
description = "All outbound"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "your-project-bastion-sg"
}
}
Application Load Balancer
# ALB
resource "aws_lb" "this" {
name = "your-project-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = [aws_subnet.public_1a.id, aws_subnet.public_1c.id]
enable_deletion_protection = false
tags = {
Name = "your-project-alb"
}
}
# Target Group
resource "aws_lb_target_group" "this" {
name = "your-project-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.this.id
target_type = "ip"
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 2
}
tags = {
Name = "your-project-tg"
}
}
# Listener
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.this.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.this.arn
}
}
ECS Cluster と Task Definition
# ECS Cluster
resource "aws_ecs_cluster" "this" {
name = "your-project-ecs"
setting {
name = "containerInsights"
value = "enabled"
}
tags = {
Name = "your-project-ecs"
}
}
# CloudWatch Log Groups
resource "aws_cloudwatch_log_group" "app" {
name = "/ecs/your-project-app"
retention_in_days = 7
tags = {
Name = "your-project-app-logs"
}
}
resource "aws_cloudwatch_log_group" "workload" {
name = "/ecs/your-project-workload"
retention_in_days = 7
tags = {
Name = "your-project-workload-logs"
}
}
# ECS Task Definition
resource "aws_ecs_task_definition" "app_with_workload" {
family = "your-project-app-with-workload"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "512"
memory = "1024"
execution_role_arn = aws_iam_role.ecs_task_execution.arn
task_role_arn = aws_iam_role.ecs_task.arn
# 重要: Sysdig Workload Agentがアプリケーションコンテナを監視するために必要
pid_mode = "task"
container_definitions = file("container_definitions.json")
tags = {
Name = "your-project-task"
}
}
重要: pid_mode = "task" によって、全コンテナが同じPID名前空間を共有します。これにより、Sysdig Workload Agentがアプリケーションコンテナのプロセスを監視できます。
ECS Service
resource "aws_ecs_service" "app" {
name = "your-project-app-svc"
cluster = aws_ecs_cluster.this.id
task_definition = aws_ecs_task_definition.app_with_workload.arn
desired_count = 1
launch_type = "FARGATE"
network_configuration {
subnets = [aws_subnet.private_1a.id, aws_subnet.private_1c.id]
assign_public_ip = false # プライベートサブネットなのでfalse
security_groups = [aws_security_group.ecs.id]
}
load_balancer {
target_group_arn = aws_lb_target_group.this.arn
container_name = "nginx-app"
container_port = 80
}
depends_on = [aws_lb_listener.http]
tags = {
Name = "your-project-svc"
}
}
重要ポイント:
-
subnets: プライベートサブネットを指定 -
assign_public_ip = false: パブリックIPを割り当てない(NAT Gateway経由で外部通信) -
load_balancer: ALBのTarget Groupに登録
Bastion Host
# Latest Amazon Linux 2023 AMI
data "aws_ami" "amazon_linux_2023" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["al2023-ami-*-x86_64"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
# Bastion EC2 Instance
resource "aws_instance" "bastion" {
ami = data.aws_ami.amazon_linux_2023.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public_1a.id
vpc_security_group_ids = [aws_security_group.bastion.id]
key_name = var.key_name
associate_public_ip_address = true
tags = {
Name = "your-project-bastion"
}
}
IAM Roles
# ECS Task Execution Role (コンテナ起動時にECRやSecrets Managerにアクセス)
resource "aws_iam_role" "ecs_task_execution" {
name = "your-project-ecs-task-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_task_execution_policy" {
role = aws_iam_role.ecs_task_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
# Secrets Manager アクセス用カスタムポリシー
resource "aws_iam_policy" "secrets_access" {
name = "your-project-secrets-access"
description = "Allow ECS tasks to access Secrets Manager"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue"
]
Resource = aws_secretsmanager_secret.sysdig_key.arn
}
]
})
}
resource "aws_iam_role_policy_attachment" "secrets_access" {
role = aws_iam_role.ecs_task_execution.name
policy_arn = aws_iam_policy.secrets_access.arn
}
# ECS Task Role (コンテナ実行時にAWSサービスにアクセス)
resource "aws_iam_role" "ecs_task" {
name = "your-project-ecs-task-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
}
Secrets Manager
resource "aws_secretsmanager_secret" "sysdig_key" {
name = "your-project-sysdig-access-key"
description = "Sysdig Access Key for Workload Agent"
tags = {
Name = "your-project-sysdig-key"
}
}
resource "aws_secretsmanager_secret_version" "sysdig_key" {
secret_id = aws_secretsmanager_secret.sysdig_key.id
secret_string = var.sysdig_access_key
}
Step 5: 変数定義と出力定義
variables.tf:
variable "sysdig_access_key" {
description = "Sysdig Access Key"
type = string
sensitive = true
}
variable "key_name" {
description = "EC2 Key Pair name for Bastion host"
type = string
}
variable "region" {
description = "AWS Region"
type = string
default = "ap-northeast-1"
}
outputs.tf:
output "alb_dns_name" {
description = "DNS name of the Application Load Balancer"
value = aws_lb.this.dns_name
}
output "bastion_public_ip" {
description = "Public IP address of the Bastion host"
value = aws_instance.bastion.public_ip
}
output "ecs_cluster_name" {
description = "Name of the ECS cluster"
value = aws_ecs_cluster.this.name
}
output "ecs_service_name" {
description = "Name of the ECS service"
value = aws_ecs_service.app.name
}
output "nat_gateway_public_ip" {
description = "Public IP address of the NAT Gateway"
value = aws_eip.nat.public_ip
}
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.this.id
}
output "private_subnet_ids" {
description = "IDs of the private subnets"
value = [aws_subnet.private_1a.id, aws_subnet.private_1c.id]
}
output "public_subnet_ids" {
description = "IDs of the public subnets"
value = [aws_subnet.public_1a.id, aws_subnet.public_1c.id]
}
Step 6: デプロイ実行
# 初期化
terraform init
# 計画確認
terraform plan
# デプロイ実行
terraform apply
terraform apply の出力例:
Apply complete! Resources: 31 added, 0 changed, 0 destroyed.
Outputs:
alb_dns_name = "your-project-alb-1234567890.ap-northeast-1.elb.amazonaws.com"
bastion_public_ip = "52.xxx.xxx.xxx"
ecs_cluster_name = "your-project-ecs"
ecs_service_name = "your-project-app-svc"
nat_gateway_public_ip = "13.xxx.xxx.xxx"
private_subnet_ids = [
"subnet-xxxxxxxxxxxx1",
"subnet-xxxxxxxxxxxx2",
]
public_subnet_ids = [
"subnet-xxxxxxxxxxxx3",
"subnet-xxxxxxxxxxxx4",
]
vpc_id = "vpc-xxxxxxxxxxxx"
動作確認
1. アプリケーションへのアクセス確認
# ALB経由でアクセス
curl http://your-project-alb-1234567890.ap-northeast-1.elb.amazonaws.com
# 期待される出力: Nginxのデフォルトページ
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
2. ECS タスクの状態確認
# ECSサービスの状態確認
aws ecs describe-services \
--cluster your-project-ecs \
--services your-project-app-svc \
--region ap-northeast-1
# タスクの詳細確認
aws ecs list-tasks \
--cluster your-project-ecs \
--service-name your-project-app-svc \
--region ap-northeast-1
# タスクIDを取得して詳細を確認
aws ecs describe-tasks \
--cluster your-project-ecs \
--tasks <task-id> \
--region ap-northeast-1
正常な状態:
{
"lastStatus": "RUNNING",
"desiredStatus": "RUNNING",
"healthStatus": "HEALTHY",
"containers": [
{
"name": "nginx-app",
"lastStatus": "RUNNING",
"healthStatus": "HEALTHY"
},
{
"name": "sysdig-workload-agent",
"lastStatus": "RUNNING",
"healthStatus": "HEALTHY"
}
]
}
3. Sysdig Workload Agentのログ確認
# CloudWatch Logsから確認
aws logs tail /ecs/your-project-workload \
--follow \
--region ap-northeast-1
正常な接続ログ:
Sysdig Workload Agent version 6.1.0
Connecting to Sysdig collector: ingest-us2.app.sysdig.com:6443
Successfully connected to Sysdig collector
Cloud platform detected: ECS
Sidecar mode: Active
Starting process monitoring...
4. Sysdig UIでの確認
- Sysdig Secure UIにログイン
- Inventory → Hosts で、Fargateタスクが表示されることを確認
- Policies → Runtime で、ポリシーが適用されていることを確認
- Events でリアルタイムのイベントを確認
トラブルシューティング
Issue 1: ECS タスクが起動しない(ResourceInitializationError)
症状:
Reason: ResourceInitializationError: unable to pull secrets or registry auth: execution resource retrieval failed
原因:
- Secrets Manager ARNが間違っている
- IAM Roleに Secrets Manager へのアクセス権限がない
解決方法:
- Secrets Manager ARNを確認:
aws secretsmanager describe-secret \
--secret-id your-project-sysdig-access-key \
--region ap-northeast-1
-
container_definitions.jsonの ARN が正しいか確認 -
IAM Roleに正しいポリシーがアタッチされているか確認:
aws iam list-attached-role-policies \
--role-name your-project-ecs-task-execution-role
Issue 2: Sysdig Workload Agentが接続できない
症状:
CloudWatch Logsに以下のようなエラー:
Error connecting to Sysdig collector: connection timeout
原因:
- NAT Gatewayの設定が間違っている
- プライベートサブネットのルートテーブルが正しくない
- Security Groupでアウトバウンドが制限されている
解決方法:
- NAT Gatewayの状態確認:
aws ec2 describe-nat-gateways \
--filter "Name=vpc-id,Values=<your-vpc-id>" \
--region ap-northeast-1
- ルートテーブル確認:
# プライベートサブネットのルートテーブルを確認
aws ec2 describe-route-tables \
--filters "Name=vpc-id,Values=<your-vpc-id>" \
--region ap-northeast-1
# 0.0.0.0/0 が NAT Gateway に向いているか確認
- Security Group確認:
aws ec2 describe-security-groups \
--group-ids <ecs-security-group-id> \
--region ap-northeast-1
# Egress rule で 0.0.0.0/0 が許可されているか確認
- 手動でコンテナから接続テスト:
# Bastionにログイン
ssh -i your-key.pem ec2-user@<bastion-public-ip>
# ECS Execを有効にしている場合(オプション)
aws ecs execute-command \
--cluster your-project-ecs \
--task <task-id> \
--container sysdig-workload-agent \
--interactive \
--command "/bin/sh"
# コンテナ内から接続テスト
curl -v https://ingest-us2.app.sysdig.com:6443
Issue 3: ALB経由でアプリケーションにアクセスできない
症状:
curl http://your-alb-dns-name
curl: (7) Failed to connect to ... Connection refused
原因:
- ALB Target Groupにタスクが登録されていない
- Health Checkが失敗している
- Security Groupの設定が間違っている
解決方法:
- Target Groupのヘルスチェック状態確認:
aws elbv2 describe-target-health \
--target-group-arn <your-target-group-arn> \
--region ap-northeast-1
- 期待される出力:
{
"TargetHealthDescriptions": [
{
"Target": {
"Id": "10.0.10.xxx",
"Port": 80
},
"HealthCheckPort": "80",
"TargetHealth": {
"State": "healthy"
}
}
]
}
- ヘルスチェックが
unhealthyの場合:- ECSタスクのログを確認
- Security GroupでALB → ECSの通信が許可されているか確認
- Target Groupのヘルスチェック設定(path, timeout, interval)を確認
Issue 4: Bastionにログインできない
症状:
ssh -i your-key.pem ec2-user@<bastion-ip>
Permission denied (publickey)
解決方法:
- キーペアが正しいか確認:
# EC2インスタンスに設定されているキーペアを確認
aws ec2 describe-instances \
--instance-ids <bastion-instance-id> \
--query 'Reservations[*].Instances[*].[KeyName]' \
--region ap-northeast-1
- Security Groupでポート22が開いているか確認:
aws ec2 describe-security-groups \
--group-ids <bastion-security-group-id> \
--region ap-northeast-1
-
正しいユーザー名を使用:
- Amazon Linux 2023:
ec2-user - Ubuntu:
ubuntu
- Amazon Linux 2023:
-
秘密鍵の権限を確認:
chmod 400 your-key.pem
Issue 5: コンテナ間でPID名前空間が共有されていない
症状:
Sysdig Workload Agentのログに以下のようなメッセージ:
Warning: Cannot access application container processes
原因:
- ECS Task DefinitionでPIDモードが設定されていない
解決方法:
main.tf の aws_ecs_task_definition に以下を追加:
resource "aws_ecs_task_definition" "app_with_workload" {
# ... 他の設定 ...
pid_mode = "task" # この行を追加
# ... 他の設定 ...
}
再デプロイ:
terraform apply
aws ecs update-service \
--cluster your-project-ecs \
--service your-project-app-svc \
--force-new-deployment \
--region ap-northeast-1
コスト試算
以下は東京リージョン(ap-northeast-1)での月額コスト見積もりです(2024年10月時点):
| リソース | 仕様 | 月額コスト(USD) |
|---|---|---|
| Fargate | 0.5 vCPU, 1GB RAM, 24時間稼働 | ~$15 |
| ALB | 1台、処理量最小 | ~$23 |
| NAT Gateway | 1台、データ転送1GB/日 | ~$45 |
| Elastic IP | NAT Gateway用 | $0 (使用中は無料) |
| Elastic IP | Bastion用(停止時のみ課金) | $0 |
| EC2 (Bastion) | t3.micro、必要時のみ起動 | ~$8 |
| CloudWatch Logs | 1GB/月 | ~$1 |
| Secrets Manager | 1シークレット | ~$0.4 |
| 合計 | 約 $92/月 |
コスト削減のヒント:
- Bastionを停止: 使用しない時はBastionを停止(~$8節約)
- VPC Endpoints: S3やECR用のVPC Endpointsを使用してNAT Gateway通信を削減(データ転送料削減)
- Auto Scaling: トラフィックに応じてFargateタスク数を調整
- Savings Plans: 1年または3年契約で最大40%割引
セキュリティのベストプラクティス
1. Bastionへのアクセス制限
現在の設定では全IP(0.0.0.0/0)からBastionへのSSHを許可していますが、本番環境では特定IPに制限してください:
resource "aws_security_group" "bastion" {
# ...
ingress {
description = "SSH from office"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["YOUR-OFFICE-IP/32"] # 会社のIPなど
}
# ...
}
2. HTTPS/TLS化
本番環境ではHTTPSを使用してください:
# ACM証明書を取得してALBに設定
resource "aws_lb_listener" "https" {
load_balancer_arn = aws_lb.this.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS13-1-2-2021-06"
certificate_arn = "arn:aws:acm:region:account:certificate/certificate-id"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.this.arn
}
}
# HTTPをHTTPSにリダイレクト
resource "aws_lb_listener" "http_redirect" {
load_balancer_arn = aws_lb.this.arn
port = "80"
protocol = "HTTP"
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
}
3. VPC Flow Logsの有効化
ネットワークトラフィックを監視:
resource "aws_flow_log" "vpc" {
iam_role_arn = aws_iam_role.vpc_flow_log.arn
log_destination = aws_cloudwatch_log_group.vpc_flow_log.arn
traffic_type = "ALL"
vpc_id = aws_vpc.this.id
}
resource "aws_cloudwatch_log_group" "vpc_flow_log" {
name = "/aws/vpc/your-project-vpc"
retention_in_days = 7
}
4. WAF(Web Application Firewall)の追加
ALBをSQLインジェクションやXSSから保護:
resource "aws_wafv2_web_acl" "this" {
name = "your-project-waf"
scope = "REGIONAL"
default_action {
allow {}
}
rule {
name = "AWSManagedRulesCommonRuleSet"
priority = 1
override_action {
none {}
}
statement {
managed_rule_group_statement {
name = "AWSManagedRulesCommonRuleSet"
vendor_name = "AWS"
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "AWSManagedRulesCommonRuleSetMetric"
sampled_requests_enabled = true
}
}
visibility_config {
cloudwatch_metrics_enabled = true
metric_name = "your-project-waf"
sampled_requests_enabled = true
}
}
resource "aws_wafv2_web_acl_association" "this" {
resource_arn = aws_lb.this.arn
web_acl_arn = aws_wafv2_web_acl.this.arn
}
5. Secrets Rotationの有効化
定期的にSysdig Access Keyをローテーション:
resource "aws_secretsmanager_secret_rotation" "sysdig_key" {
secret_id = aws_secretsmanager_secret.sysdig_key.id
rotation_lambda_arn = aws_lambda_function.rotate_secret.arn
rotation_rules {
automatically_after_days = 90
}
}
まとめ
本記事では、AWS ECS Fargateでプライベートサブネット構成によるセキュアなアーキテクチャを実装し、Sysdig Workload Agentをサイドカーとして動作させる方法を解説しました。
実装したアーキテクチャの利点
✅ セキュリティの向上
- Fargateタスクがプライベートサブネットに配置され、直接インターネットアクセスを防止
- ALBによる集約された公開エンドポイント(WAF統合可能)
- NAT Gatewayによる制御されたアウトバウンド通信
- Bastionによる安全な管理アクセス
✅ 可用性の向上
- マルチAZ構成による高可用性
- ALBによる自動的な負荷分散とヘルスチェック
✅ 運用性の向上
- Terraformによる完全なInfrastructure as Code
- CloudWatch Logsによる集約されたログ管理
- Sysdigによるリアルタイムセキュリティ監視
✅ 拡張性
- Auto Scalingで容易にスケール可能
- 追加のマイクロサービスを同じVPCに簡単にデプロイ可能
本番運用に向けた次のステップ
- HTTPS/TLS化: ACM証明書を取得してHTTPSを有効化
- WAF統合: ALBにWAFを統合してアプリケーション層の保護を強化
- Auto Scaling: ECS ServiceのAuto Scalingを設定
- バックアップ: 重要なデータのバックアップ戦略を策定
- 監視・アラート: CloudWatchアラームとSysdigポリシーを設定
- CI/CD: CodePipelineやGitHub Actionsでデプロイ自動化
参考リンク
セキュアなECS Fargate環境で、Sysdigによる包括的なセキュリティ監視を実現できました!
この記事が役に立ったら、いいねとストックをお願いします!