なになれ

IT系のことを記録していきます

Terraform1.5で追加されたimportブロックを試す

importブロックは宣言的に既存のリソースをTerraform管理下にできるTerraformの構文です。今まではterraform import で既存のリソースをTerraform管理下にできましたが、一つ一つのリソースしかインポートできずに面倒でした。また、今まではできなかったHCLのコードを生成することも可能になっています。
developer.hashicorp.com

実践

EC2のインスタンスAWSマネジメントコンソールから作成し、それをimportブロックでTerraform管理下にします。

まず、importブロックのコードを書きます。toにはインポート先となるresourceブロックのIDを指定します。
idにはインポートする個々のリソースを識別するIDを設定します。これはリソースの種類によって異なります。EC2の場合はインスタンスIDになります。

imports.tf

import {
  to = aws_instance.example
  id = "i-00000000000000000"
}

この状態でterraform planを実行すると、toにあたるインポート先がないためにエラーになります。そして、terraform plan -generate-config-out=generated.tfでコードを生成することができる旨のメッセージが表示されます。親切です。これに従って、HCLのコードを生成します。

EC2の場合は、コードを生成すると、設定がコンフリクトしている旨のエラーが出力されます。

│ Error: Conflicting configuration arguments
│
│   with aws_instance.example,
│   on generated.tf line 14:
│   (source code not available)
│
│ "ipv6_address_count": conflicts with ipv6_addresses
╵
╷
│ Error: Conflicting configuration arguments
│
│   with aws_instance.example,
│   on generated.tf line 15:
│   (source code not available)
│
│ "ipv6_addresses": conflicts with ipv6_address_count

ipv6_address_countipv6_addressesはどちらか一方あればよい値です。コード生成をした場合はどちらも出力されてしまいます。
どちらかの記述を削除して、terraform planterraform applyを実行します。そうすると、importすることができます。

コードの生成は完璧ではありませんが、かなりの部分のコードを補完してくれるため、便利です。

感想

terraform importで行っていた既存リソースのインポートをTerraformのコードでできるようになり、操作性がよくなったと思います。また、コードの生成も便利なので、これからはimportブロックを使った書き方を採用していきたいと思います。

おわりに

Udemy講座を公開しています。AWSやTerraformの入門から実践的な内容までを学べるものになっています。ぜひ受講ください。

ローカル上のAWS LambdaのコードをTerraform化する

ローカル上のAWS LambdaのコードをTerraform化する方法について説明します。
ローカルにあるコードをTerraform化するにあたってはコードのzipファイル化が必要など、そのあたりのポイントを説明します。

説明

以下が全体のコードになります。

lambda.tf

resource "aws_lambda_function" "example" {
  function_name    = "lambda-terraform-example"
  handler          = "main.lambda_handler"
  runtime          = "python3.8"
  filename         = data.archive_file.lambda_function_payload.output_path
  source_code_hash = data.archive_file.lambda_function_payload.output_base64sha256
  role             = aws_iam_role.lambda_exec.arn
}

resource "aws_iam_role" "lambda_exec" {
  name = "lambda_exec_example"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_exec_basic" {
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
  role       = aws_iam_role.lambda_exec.name
}

data "archive_file" "lambda_function_payload" {
  type        = "zip"
  source_dir  = "${path.module}/src"
  output_path = "${path.module}/lambda_function_payload.zip"
}

src/main.py

import json

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

aws_lambda_functionのリソースにfilenameとsource_code_hashのパラメータを指定しているのがポイントです。こちらにはLambda関数コードが含まれるZIPアーカイブファイルのパスとハッシュを指定します。Lambda関数のソースコードを含むZIPアーカイブは、data archive_fileブロックで作成され、filenameおよびsource_code_hashパラメータに渡されます。

aws_iam_roleリソースは、Lambda関数が使用するIAMロールを作成します。このロールには、AWS Lambdaによる関数の実行に必要なポリシーが含まれます。最後に、aws_iam_role_policy_attachmentリソースを使用して、作成したIAMロールにAWSLambdaBasicExecutionRoleポリシーをアタッチしています。これは、Lambda関数が必要な最小限の権限を持つようにするためのものです。 path.module はTerraformで使用される予約済み変数の一つで、現在のモジュールのルートディレクトリへの絶対パスを表します。

これでローカル上のAWS LambdaのコードをTerraform化することができます。

おわりに

以下のUdemy講座を公開しています。AWSやTerraformの入門から実践的な内容までを学べるものになっています。ぜひ受講ください。

www.udemy.com

Amazon ECS 入門ハンズオンの環境をTerraformで作成する

本記事では、以下のAmazon ECS 入門ハンズオンの環境をTerraformで作成する具体的な手順を紹介します。

catalog.us-east-1.prod.workshops.aws

AWSでコンテナを用いたシステムを構築する際にはECSが有力な選択肢であるため、有用なハンズオンになっていると思います。 なお、環境構築をする際のオプション的な要素であるため、作業環境となるAWS Cloud9については環境構築の対象外としています。

全体のソースコードは以下にあります

github.com

手順

コンテナイメージの作成

最初に以下の内容の通りにECSで動かす対象となるコンテナのイメージを作成します。
コンテナイメージの作成

ECRにアップロード

コンテナイメージをアップロードする先になるECRのリポジトリを作成します。
リポジトリを作成するためのTerraformのコードは以下です。

ecr.tf

resource "aws_ecr_repository" "main" {
  name         = "h4b-ecs-helloworld"
  force_delete = true
}

output "repository_url" {
  value = aws_ecr_repository.main.repository_url
}

Terraformで環境を作成する際には、初回のみterraform initを実行し、環境を更新するたびにterraform applyを実行します。

$ terraform init
$ terraform apply

Outputs:

repository_url = "xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/h4b-ecs-helloworld"

リポジトリのURLが出力されますので、このURLに合わせてコンテナイメージのタグ名を変更して、ECRのリポジトリにアップロードできるようにします。

$ docker build -t xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/h4b-ecs-helloworld:0.0.1 .

コンテナイメージをリポジトリにアップロード(push)するために、docker login が必要です。成功した場合、Login Succeededという出力があります。

$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com

Login Succeeded

コンテナイメージをECRにアップロードします。

$ docker push xxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/h4b-ecs-helloworld:0.0.1

VPCの作成

ECS クラスターを作成するために、新しくVPC を作成します。Terraformのコードは以下です。
VPCとパブリックサブネットを2つ作成します。また、デフォルトのセキュリティグループの設定を変更し、HTTPのプロトコルでのアクセスを許可するようにします。

vpc.tf

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "h4b-ecs"
  }
}

resource "aws_subnet" "public1" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.0.0/20"
  availability_zone = "ap-northeast-1a"
  tags = {
    Name = "h4b-ecs-public1-ap-northeast-1a"
  }
}

resource "aws_subnet" "public2" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = "10.0.16.0/20"
  availability_zone = "ap-northeast-1c"
  tags = {
    Name = "h4b-ecs-public2-ap-northeast-1c"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id
  tags = {
    Name = "h4b-ecs"
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }
  tags = {
    Name = "h4b-ecs-rtb-public"
  }
}

resource "aws_route_table_association" "public1" {
  subnet_id      = aws_subnet.public1.id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "public2" {
  subnet_id      = aws_subnet.public2.id
  route_table_id = aws_route_table.public.id
}

resource "aws_default_security_group" "default" {
  vpc_id = aws_vpc.main.id

  ingress {
    protocol  = -1
    self      = true
    from_port = 0
    to_port   = 0
  }

  ingress {
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    from_port   = 80
    to_port     = 80
  }

  egress {
    protocol    = -1
    cidr_blocks = ["0.0.0.0/0"]
    from_port   = 0
    to_port     = 0
  }
}

ECSクラスターの作成

ECSクラスターは、コンテナを動かすための論理的なグループです。ECSを操作するために、まずは ECSクラスターを作成していきます。Terraformのコードは以下です。

ecs.tf

resource "aws_ecs_cluster" "main" {
  name = "h4b-ecs-cluster"
}

タスク定義の作成

タスク定義とは、アプリケーションを動かすために、どのようにコンテナを動かすか、コンテナの動作を定義したものです。Terraformのコードは以下です。

ecs.tf

locals {
  container_name = "apache-helloworld"
}

resource "aws_ecs_task_definition" "main" {
  family                   = "h4b-ecs-task-definition"
  cpu                      = 512
  memory                   = 1024
  requires_compatibilities = ["FARGATE"]
  execution_role_arn       = aws_iam_role.ecs_task_execution_role.arn
  network_mode             = "awsvpc"
  container_definitions    = <<-EOS
  [
    {
        "name": "${local.container_name}",
        "image": "${aws_ecr_repository.main.repository_url}:0.0.1",
        "cpu": 0,
        "portMappings": [
            {
                "name": "${local.container_name}",
                "containerPort": 80,
                "hostPort": 80,
                "protocol": "tcp",
                "appProtocol": "http"
            }
        ],
        "essential": true,
        "environment": [],
        "environmentFiles": [],
        "mountPoints": [],
        "volumesFrom": [],
        "logConfiguration": {
            "logDriver": "awslogs",
            "options": {
                "awslogs-create-group": "true",
                "awslogs-group": "/ecs/h4b-ecs-task-definition",
                "awslogs-region": "ap-northeast-1",
                "awslogs-stream-prefix": "ecs"
            }
        }
    }
  ]
  EOS
  runtime_platform {
    cpu_architecture        = "X86_64"
    operating_system_family = "LINUX"
  }
}

タスク定義ではタスク実行ロールというIAMロールを設定する必要があります。以下のTerraformのコードでそのIAMロールを作成します。
このIAMロールにより、ECRリポジトリからコンテナイメージを取得する、CloudWatch Logsにコンテナログを送信するといった許可を与えます。

iam.tf

resource "aws_iam_role" "ecs_task_execution_role" {
  name = "h4b-ecs-task-execution-role"

  assume_role_policy = <<-EOS
  {
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "ecs-tasks.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
  }
  EOS
}

resource "aws_iam_policy" "ecs_task_execution_role_policy" {
  name   = "h4b-ecs-task-execution-role-policy"
  policy = <<-EOS
  {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken",
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage",
                "logs:CreateLogStream",
                "logs:PutLogEvents",
                "logs:CreateLogGroup"
            ],
            "Resource": "*"
        }
    ]
  }
  EOS
}

resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
  role       = aws_iam_role.ecs_task_execution_role.name
  policy_arn = aws_iam_policy.ecs_task_execution_role_policy.arn
}

サービスの作成

サービスを使用すると、ECSクラスターで、指定した数のコンテナ群(タスク)を維持できます。一般的には、長期間動かすアプリケーションに適しています。
Terraformのコードは以下です。

ecs.tf

resource "aws_ecs_service" "main" {
  name            = "h4b-ecs-service"
  cluster         = aws_ecs_cluster.main.id
  task_definition = aws_ecs_task_definition.main.arn
  launch_type     = "FARGATE"
  desired_count   = 2
  network_configuration {
    subnets = [
      aws_subnet.public1.id,
      aws_subnet.public2.id
    ]
    security_groups = [
      aws_default_security_group.default.id
    ]
    assign_public_ip = true
  }
  load_balancer {
    target_group_arn = aws_lb_target_group.main.arn
    container_name   = local.container_name
    container_port   = 80
  }
}

サービスを作成する際にロードバランサーも必要になります。 ロードバランサーにアクセスが来ると、ロードバランサーを経由してECSサービスで起動するタスクに接続される形になります。

lb.tf

resource "aws_lb" "main" {
  name               = "h4b-ecs-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_default_security_group.default.id]
  subnets            = [aws_subnet.public1.id, aws_subnet.public2.id]
}

resource "aws_lb_listener" "main" {
  load_balancer_arn = aws_lb.main.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.main.arn
  }
}

resource "aws_lb_target_group" "main" {
  name        = "h4b-ecs-targetgroup"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = aws_vpc.main.id
  target_type = "ip"
}

これで環境作成は完了です。
サービス→ ネットワーキング→ロードバランサーDNS名にアクセスするとHello World! と表示されてアクセスできるはずです。

まとめ

この記事では、Amazon ECS 入門ハンズオンの環境をTerraformで作成する手順を紹介しました。
Terraformを使用することで、以下のメリットがあります。

  • インフラのコード化: 環境構築の手順をコードで管理することで、再現性と柔軟性が向上します。
  • バージョン管理: インフラの変更履歴をGitなどのバージョン管理システムで追跡できます。
  • 環境の再利用: 同じ環境を複数のプロジェクトやチームで再利用できます。
  • 簡易化されたデプロイ: Terraformを使うことで、インフラのデプロイが簡単になり、ミスを減らすことができます。

これらのメリットを活かし、ECSを利用したシステム構築を効率的に行うことができます。

宣伝

以下のUdemy講座を公開しています。AWSやTerraformの入門から実践的な内容までを学べるものになっています。ぜひ受講ください。

www.udemy.com