1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS ECS Fargate で Sysdig Workload Agent を動かす完全ガイド

Posted at

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"
      }
    }
  }
]

重要ポイント:

  1. SYSDIG_COLLECTOR: Sysdigリージョンに応じて変更(US: ingest-us2.app.sysdig.com, EU: ingest-eu1.app.sysdig.com, など)
  2. valueFrom: Step 1で取得したSecrets ManagerのARNを指定
  3. 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での確認

  1. Sysdig Secure UIにログイン
  2. InventoryHosts で、Fargateタスクが表示されることを確認
  3. PoliciesRuntime で、ポリシーが適用されていることを確認
  4. 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 へのアクセス権限がない

解決方法:

  1. Secrets Manager ARNを確認:
aws secretsmanager describe-secret \
  --secret-id your-project-sysdig-access-key \
  --region ap-northeast-1
  1. container_definitions.json の ARN が正しいか確認

  2. 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でアウトバウンドが制限されている

解決方法:

  1. NAT Gatewayの状態確認:
aws ec2 describe-nat-gateways \
  --filter "Name=vpc-id,Values=<your-vpc-id>" \
  --region ap-northeast-1
  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 に向いているか確認
  1. Security Group確認:
aws ec2 describe-security-groups \
  --group-ids <ecs-security-group-id> \
  --region ap-northeast-1

# Egress rule で 0.0.0.0/0 が許可されているか確認
  1. 手動でコンテナから接続テスト:
# 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の設定が間違っている

解決方法:

  1. Target Groupのヘルスチェック状態確認:
aws elbv2 describe-target-health \
  --target-group-arn <your-target-group-arn> \
  --region ap-northeast-1
  1. 期待される出力:
{
  "TargetHealthDescriptions": [
    {
      "Target": {
        "Id": "10.0.10.xxx",
        "Port": 80
      },
      "HealthCheckPort": "80",
      "TargetHealth": {
        "State": "healthy"
      }
    }
  ]
}
  1. ヘルスチェックが 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)

解決方法:

  1. キーペアが正しいか確認:
# EC2インスタンスに設定されているキーペアを確認
aws ec2 describe-instances \
  --instance-ids <bastion-instance-id> \
  --query 'Reservations[*].Instances[*].[KeyName]' \
  --region ap-northeast-1
  1. Security Groupでポート22が開いているか確認:
aws ec2 describe-security-groups \
  --group-ids <bastion-security-group-id> \
  --region ap-northeast-1
  1. 正しいユーザー名を使用:

    • Amazon Linux 2023: ec2-user
    • Ubuntu: ubuntu
  2. 秘密鍵の権限を確認:

chmod 400 your-key.pem

Issue 5: コンテナ間でPID名前空間が共有されていない

症状:
Sysdig Workload Agentのログに以下のようなメッセージ:

Warning: Cannot access application container processes

原因:

  • ECS Task DefinitionでPIDモードが設定されていない

解決方法:

main.tfaws_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/月

コスト削減のヒント:

  1. Bastionを停止: 使用しない時はBastionを停止(~$8節約)
  2. VPC Endpoints: S3やECR用のVPC Endpointsを使用してNAT Gateway通信を削減(データ転送料削減)
  3. Auto Scaling: トラフィックに応じてFargateタスク数を調整
  4. 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に簡単にデプロイ可能

本番運用に向けた次のステップ

  1. HTTPS/TLS化: ACM証明書を取得してHTTPSを有効化
  2. WAF統合: ALBにWAFを統合してアプリケーション層の保護を強化
  3. Auto Scaling: ECS ServiceのAuto Scalingを設定
  4. バックアップ: 重要なデータのバックアップ戦略を策定
  5. 監視・アラート: CloudWatchアラームとSysdigポリシーを設定
  6. CI/CD: CodePipelineやGitHub Actionsでデプロイ自動化

参考リンク

セキュアなECS Fargate環境で、Sysdigによる包括的なセキュリティ監視を実現できました!


この記事が役に立ったら、いいねとストックをお願いします!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?