12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Amazon CloudWatch Logsにログを保存するようなAWS Fargateクラスタを、Terraformで構築する

Last updated at Posted at 2020-05-24

What?

やりたいこと。

  • AWS Fargateを使って、クラスタを作る
  • コンテナのログは、Amazon CloudWatch Logsに送る
  • 以上のことを、Terraformで実現する

というのを書いていきます。

環境

利用するTerraformと、AWS Providerのバージョンです。

$ terraform version
Terraform v0.12.25
+ provider.aws v2.63.0

構築する内容

まっさらなところから、VPCを作ってAWS Fargateクラスタを構築、簡単な動作確認を行うところまでやります。

  • シングルAZ
  • 動作させるコンテナはnginx
  • ALBはHTTP

Terraformでの実装内容は、すべてmain.tfに書いています。

TerraformとAWS Providerのバージョン指定

terraform {
  required_version = "=  0.12.25"
}

provider "aws" {
  version = "2.63.0"
}

AWSのクレデンシャルは、環境変数で設定。

export AWS_ACCESS_KEY_ID=.....
export AWS_SECRET_ACCESS_KEY=.....
export AWS_DEFAULT_REGION=.....

この状態で、進めていきましょう。

VPC〜ALBまで

このあたりは、今回のお題であるFargateやCloudWatch Logsの前提となる環境を作っていくのでコミュニティモジュールを積極的に使って、簡単に。

VPC

VPCは、こちらのコミュニティモジュールを使用して定義。

AWS VPC Terraform module

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "2.33.0"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["ap-northeast-1a", "ap-northeast-1c"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]

  map_public_ip_on_launch = true

  enable_nat_gateway     = true
  single_nat_gateway     = true
  one_nat_gateway_per_az = false
}

シングルAZにするので、NAT Gatewayはひとつ。

セキュリティグループ

セキュリティグループは、ALBとFargateに割り当てるものをそれぞれ定義します。コミュニティモジュールは、こちらを利用。

AWS EC2-VPC Security Group Terraform module

module "load_balancer_sg" {
  source  = "terraform-aws-modules/security-group/aws//modules/http-80"
  version = "3.10.0"

  name   = "load-balancer-sg"
  vpc_id = module.vpc.vpc_id

  ingress_cidr_blocks = ["0.0.0.0/0"]
}

module "nginx_cluster_sg" {
  source  = "terraform-aws-modules/security-group/aws//modules/http-80"
  version = "3.10.0"

  name   = "nginx-cluster-sg"
  vpc_id = module.vpc.vpc_id

  ingress_cidr_blocks = ["10.0.0.0/16"]
}

vpc_idに関しては、先ほどのVPCを作った時のモジュールのOutputを利用します。

  vpc_id = module.vpc.vpc_id

ALB

HTTPを受け付ける、ALBを定義します。リスナーや、ターゲットグループも同時に定義することになります。コミュニティモジュールは、こちらを利用。

AWS Application and Network Load Balancer (ALB & NLB) Terraform module

module "load_balancer" {
  source  = "terraform-aws-modules/alb/aws"
  version = "5.6.0"

  name = "nginx"

  vpc_id             = module.vpc.vpc_id
  load_balancer_type = "application"
  internal           = false

  subnets         = module.vpc.public_subnets
  security_groups = [module.load_balancer_sg.this_security_group_id]

  target_groups = [
    {
      backend_protocol = "HTTP"
      backend_port     = 80
      target_type      = "ip"

      health_check = {
        interval = 20
      }
    }
  ]

  http_tcp_listeners = [
    {
      port     = 80
      protocol = "HTTP"
    }
  ]
}

確認の都合上、ヘルスチェックの時間はちょっと短めにしておきました。

VPCで作成したパブリックサブネットと、ALB用のセキュリティグループの情報を使っています。

ここまでの結果で、Fargate構築の際に利用するもの

ここまで作成したVPCやセキュリティグループ、ALBの情報のうち、この後のFargateの構築で使うものをローカル変数に切り出しておきます。

locals {
  vpc_id = module.vpc.vpc_id

  private_subnets                = [module.vpc.private_subnets[0]]
  ecs_service_security_groups    = [module.nginx_cluster_sg.this_security_group_id]
  load_balancer_target_group_arn = module.load_balancer.target_group_arns[0]
}

単純に、こうやって分けた方が記事を読む時にわかりやすいかな、と。

CloudWatch Logs

CloudWatch Logs側は、ロググループを定義。

resource "aws_cloudwatch_log_group" "nginx_cluster_log_group" {
  name = "nginx-cluster-log-group"
}

IAMロール

コンテナからCloudWatch Logsにログを送る際に、権限が必要になります。このため、IAMロールを定義しましょう。

今回は、AmazonECSTaskExecutionRolePolicyというポリシーをそのまま利用します。

Amazon ECS タスク実行 IAM ロール

AmazonECSTaskExecutionRolePolicyでどんなことができるか、見てみましょう。

arnを調べます。

$ aws iam list-policies | grep ECS
            "PolicyName": "AWSCodeDeployRoleForECS",
            "Arn": "arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS",
            "PolicyName": "AmazonECSServiceRolePolicy",
            "Arn": "arn:aws:iam::aws:policy/aws-service-role/AmazonECSServiceRolePolicy",
            "PolicyName": "AWSCodeDeployRoleForECSLimited",
            "Arn": "arn:aws:iam::aws:policy/AWSCodeDeployRoleForECSLimited",
            "PolicyName": "AmazonECS_FullAccess",
            "Arn": "arn:aws:iam::aws:policy/AmazonECS_FullAccess",
            "PolicyName": "AWSApplicationAutoscalingECSServicePolicy",
            "Arn": "arn:aws:iam::aws:policy/aws-service-role/AWSApplicationAutoscalingECSServicePolicy",
            "PolicyName": "AmazonECSTaskExecutionRolePolicy",
            "Arn": "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",


$ aws iam get-policy --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
{
    "Policy": {
        "PolicyName": "AmazonECSTaskExecutionRolePolicy",
        "PolicyId": "ANPAJG4T4G4PV56DE72PY",
        "Arn": "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
        "Path": "/service-role/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "Description": "Provides access to other AWS service resources that are required to run Amazon ECS tasks",
        "CreateDate": "2017-11-16T18:48:22+00:00",
        "UpdateDate": "2017-11-16T18:48:22+00:00"
    }
}

確認。

$ aws iam get-policy-version --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy --version-id v1
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Action": [
                        "ecr:GetAuthorizationToken",
                        "ecr:BatchCheckLayerAvailability",
                        "ecr:GetDownloadUrlForLayer",
                        "ecr:BatchGetImage",
                        "logs:CreateLogStream",
                        "logs:PutLogEvents"
                    ],
                    "Resource": "*"
                }
            ]
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2017-11-16T18:48:22+00:00"
    }
}

まあ、同じ内容はドキュメントにも書かれているのですが、あらためて見た感じですね。

今回必要なのは、logs:CreateLogStreamlogs:PutLogEventsです。

で、定義したのがこちら。

data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ecs-tasks.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "ecs_task" {
  name               = "MyEcsTaskRole"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

resource "aws_iam_role_policy_attachment" "ecs_task" {
  role       = aws_iam_role.ecs_task.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

Assume Roleについても、ドキュメントに書かれています(信頼関係という表現になっていますが)。

CloudWatch Logsに関するロールをタスクに付与しなかった場合、コンテナの作成時にこんなエラーを見ることになります。

Error: ClientException: Fargate requires task definition to have execution role ARN to support log driver awslogs.

Fargateクラスタ

最後に、Fargateクラスタの定義を行います。

  • クラスタ定義
  • タスク定義
  • サービス定義

の3つを行います。

resource "aws_ecs_cluster" "nginx" {
  name = "nginx-cluster"
}

resource "aws_ecs_task_definition" "nginx" {
  family                   = "nginx-task-definition"
  cpu                      = "256"
  memory                   = "512"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  execution_role_arn       = aws_iam_role.ecs_task.arn

  container_definitions = <<JSON
[
  {
    "name": "nginx",
    "image": "nginx:1.17.10",
    "essential": true,
    "portMappings": [
      {
        "protocol": "tcp",
        "containerPort": 80
      }
    ],
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-region": "ap-northeast-1",
        "awslogs-group": "nginx-cluster-log-group",
        "awslogs-stream-prefix": "nginx-container-log-stream"
      }
    }
  }
]
  JSON
}

resource "aws_ecs_service" "nginx" {
  name             = "nginx-service"
  cluster          = aws_ecs_cluster.nginx.arn
  task_definition  = aws_ecs_task_definition.nginx.arn
  desired_count    = 3
  launch_type      = "FARGATE"
  platform_version = "1.4.0"

  network_configuration {
    assign_public_ip = false
    security_groups  = local.ecs_service_security_groups
    subnets          = local.private_subnets
  }

  load_balancer {
    target_group_arn = local.load_balancer_target_group_arn
    container_name   = "nginx"
    container_port   = 80
  }
}

先ほど定義したIAMロールは、タスク定義で使います。


resource "aws_ecs_task_definition" "nginx" {
...

  execution_role_arn       = aws_iam_role.ecs_task.arn
...

また、コンテナがCloudWatch Logsにログを送信するという設定は、コンテナ定義のlogConfigurationで行います。awslogs-groupに、ログ送信対象となるCloudWatch Logsのロググループを指定しましょう。

  container_definitions = <<JSON
[
  {
    "name": "nginx",
    "image": "nginx:1.17.10",
    "essential": true,
    "portMappings": [
      {
        "protocol": "tcp",
        "containerPort": 80
      }
    ],
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-region": "ap-northeast-1",
        "awslogs-group": "nginx-cluster-log-group",
        "awslogs-stream-prefix": "nginx-container-log-stream"
      }
    }
  }
]
  JSON

こちらを参考に。

awslogs ログドライバーを使用する

あとは、このタスク定義や、ALB、セキュリティグループの定義を設定しておしまいです。

resource "aws_ecs_service" "nginx" {
  name             = "nginx-service"
  cluster          = aws_ecs_cluster.nginx.arn
  task_definition  = aws_ecs_task_definition.nginx.arn
  desired_count    = 3
  launch_type      = "FARGATE"
  platform_version = "1.4.0"

  network_configuration {
    assign_public_ip = false
    security_groups  = local.ecs_service_security_groups
    subnets          = local.private_subnets
  }

  load_balancer {
    target_group_arn = local.load_balancer_target_group_arn
    container_name   = "nginx"
    container_port   = 80
  }
}

動作確認

terraform applyして

$ terraform apply -auto-approve

構築されたALBの情報から、DNS名を取得します。

$ aws elbv2 describe-load-balancers --names nginx

取得したDNSNameから、curlで確認。

$ curl [DNSName]
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

この時の、CloudWatch Logsをtailしてみます。

$ aws logs tail nginx-cluster-log-group --follow

ログがCloudWatch Logsに登録されているのが確認できました。

2020-05-24T09:47:46.409000+00:00 nginx-container-log-stream/nginx/194c1f45-f056-4e4c-8cbf-8306c8ca52b0 10.0.101.4 - - [24/May/2020:09:47:46 +0000] "GET / HTTP/1.1" 200 612 "-" "ELB-HealthChecker/2.0" "-"
2020-05-24T09:47:46.409000+00:00 nginx-container-log-stream/nginx/a89ccf53-31a2-4a24-bb55-e643b30ea3a4 10.0.101.4 - - [24/May/2020:09:47:46 +0000] "GET / HTTP/1.1" 200 612 "-" "ELB-HealthChecker/2.0" "-"
2020-05-24T09:47:54.271000+00:00 nginx-container-log-stream/nginx/00f8ba07-8ac4-4885-854b-88b79bdd0789 10.0.101.4 - - [24/May/2020:09:47:54 +0000] "GET / HTTP/1.1" 200 612 "-" "ELB-HealthChecker/2.0" "-"
2020-05-24T09:48:00.197000+00:00 nginx-container-log-stream/nginx/00f8ba07-8ac4-4885-854b-88b79bdd0789 10.0.101.4 - - [24/May/2020:09:48:00 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.58.0" "xxx.xxx.xxx.xxx"

OKですね。

12
8
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
12
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?