[Terraform] CloudWatch Agent를 EC2 인스턴스에서 사용하는 아키텍처 자동화

HYEOB KIM·2022년 6월 15일
1

Terraform

목록 보기
7/11

개요

  • CloudWatch AgentEC2 인스턴스에 설치하고 지표를 수집할 수 있도록 하는 전반적인 과정을 테라폼을 통해 자동화 해보았습니다.

과정

  1. 인스턴스에 연결할 역할을 생성합니다. 역할에는 CloudWatch agent와 ssm과 연관된 정책이 연결됩니다.
  2. 인스턴스를 위한 전반적인 네트워크 인프라를 구성합니다. VPC, Subnet, IGW, Routing Table, Network Interface, Security Group 등이 있습니다.
  3. 키 파일을 생성하고 다운로드 받습니다.
  4. 인스턴스를 생성합니다. 인스턴스에는 역할, Network Interface, 키 파일, 프로비저닝 스크립트가 포함됩니다.

디렉토리 구조

Terraform 코드

role.tf

  • 먼저 인스턴스에 연결할 역할을 생성합니다. 역할은 EC2 서비스 용도이기 때문에 assume_role_policyec2 서비스에 대해서만 허용되어야 합니다.
# ssm, cloudwatch agent를 허용하는 역할 생성
resource "aws_iam_role" "EC2InstacneToSSMandCloudwatchAgent" {
  name = "test_EC2InstacneToSSMandCloudwatchAgent"
  path = "/"
  assume_role_policy = jsonencode(
    {
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Principal" : {
            "Service" : "ec2.amazonaws.com"
          },
          "Action" : "sts:AssumeRole"
        }
      ]
    }
  )
}
  • 역할을 인스턴스에 연결하기 위해서는 인스턴스 프로파일을 생성해야 합니다. aws_iam_instance_profile 리소스를 이용하면 됩니다.
# 역할을 ec2 인스턴스에 연결하기 위해 인스턴스 프로파일 생성
resource "aws_iam_instance_profile" "ec2_profile" {
  name = "ec2_profile"
  role = aws_iam_role.EC2InstacneToSSMandCloudwatchAgent.name
}
  • 인스턴스에 ssm을 허용하는 정책인 AmazonSSMManagedInstanceCore를 불러와서 역할에 붙여줍니다.
# ssm 접근 허용 정책(EC2 용)
data "aws_iam_policy" "AmazonSSMManagedInstanceCore" {
  name = "AmazonSSMManagedInstanceCore"
}

resource "aws_iam_role_policy_attachment" "AmazonSSMManagedInstanceCore" {
  role       = aws_iam_role.EC2InstacneToSSMandCloudwatchAgent.name
  policy_arn = data.aws_iam_policy.AmazonSSMManagedInstanceCore.arn
}
  • 인스턴스에 cloudwatch agent을 허용하는 정책인 CloudWatchAgentServerPolicy를 불러와서 역할에 붙여줍니다.
# cloudwatch agent 허용 정책(EC2 용)
data "aws_iam_policy" "CloudWatchAgentServerPolicy" {
  name = "CloudWatchAgentServerPolicy"
}

resource "aws_iam_role_policy_attachment" "CloudWatchAgentServerPolicy" {
  role       = aws_iam_role.EC2InstacneToSSMandCloudwatchAgent.name
  policy_arn = data.aws_iam_policy.CloudWatchAgentServerPolicy.arn
}
  • 전체 코드는 아래와 같습니다.
# ssm, cloudwatch agent를 허용하는 역할 생성
resource "aws_iam_role" "EC2InstacneToSSMandCloudwatchAgent" {
  name = "test_EC2InstacneToSSMandCloudwatchAgent"
  path = "/"
  assume_role_policy = jsonencode(
    {
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Effect" : "Allow",
          "Principal" : {
            "Service" : "ec2.amazonaws.com"
          },
          "Action" : "sts:AssumeRole"
        }
      ]
    }
  )
}


# 역할을 ec2 인스턴스에 연결하기 위해 인스턴스 프로파일 생성
resource "aws_iam_instance_profile" "ec2_profile" {
  name = "ec2_profile"
  role = aws_iam_role.EC2InstacneToSSMandCloudwatchAgent.name
}

# ssm 접근 허용 정책(EC2 용)
data "aws_iam_policy" "AmazonSSMManagedInstanceCore" {
  name = "AmazonSSMManagedInstanceCore"
}

resource "aws_iam_role_policy_attachment" "AmazonSSMManagedInstanceCore" {
  role       = aws_iam_role.EC2InstacneToSSMandCloudwatchAgent.name
  policy_arn = data.aws_iam_policy.AmazonSSMManagedInstanceCore.arn
}

# cloudwatch agent 허용 정책(EC2 용)
data "aws_iam_policy" "CloudWatchAgentServerPolicy" {
  name = "CloudWatchAgentServerPolicy"
}

resource "aws_iam_role_policy_attachment" "CloudWatchAgentServerPolicy" {
  role       = aws_iam_role.EC2InstacneToSSMandCloudwatchAgent.name
  policy_arn = data.aws_iam_policy.CloudWatchAgentServerPolicy.arn
}

vpc.tf

  • 먼저 VPC를 생성합니다. 여기서 enable_dns_hostnamesenable_dns_support 속성을 true로 설정하면 이후 인스턴스를 이 VPC에 연결할 때 Public DNS가 붙게 됩니다.
# VPC 생성
resource "aws_vpc" "test_hyeob_vpc" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "default"

  # 인스턴스에 public DNS가 표시되도록 하는 속성
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = local.tags
}
  • 서브넷을 생성합니다. 서브넷은 VPC 내에 생성하고, cidr가용 영역을 설정해줍니다.
# 서브넷 생성
resource "aws_subnet" "test_hyeob_subnet" {
  vpc_id            = aws_vpc.test_hyeob_vpc.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "ap-northeast-2a"

  tags = local.tags
}
  • 인터넷 게이트웨이(IGW)를 생성합니다. IGW는 VPC에 연결합니다.
  • IGW는 생성하면서 VPC에 연결하면 됩니다. 따로 attach 리소스를 이용할 필요는 없습니다.
# 인터넷 게이트웨이 생성 후 VPC에 연결
resource "aws_internet_gateway" "gw" {
  vpc_id = aws_vpc.test_hyeob_vpc.id

  tags = local.tags
}
  • 라우팅 테이블을 생성하고 IGW서브넷에 연결합니다.
# 라우팅 테이블 생성 후 igw에 연결
resource "aws_route_table" "rt" {
  vpc_id = aws_vpc.test_hyeob_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.gw.id
  }

  tags = local.tags
}

# 서브넷과 라우팅 테이블 연결
resource "aws_route_table_association" "rta" {
  subnet_id      = aws_subnet.test_hyeob_subnet.id
  route_table_id = aws_route_table.rt.id
}
  • 보안 그룹을 생성합니다. 저는 22, 80 포트의 인바운드를 허용하고, 모든 포트의 아웃바운드를 허용했습니다. 보안 그룹은 VPC에 연결해줍니다.
resource "aws_security_group" "test_hyeob_sg" {
  name        = "test_hyeob_sg"
  description = "Allow SSH inbound traffic"
  vpc_id      = aws_vpc.test_hyeob_vpc.id

  # 인바운드: ingress
  ingress {
    description = "SSH from VPC"

    # 포트 범위: from_port ~ to_port
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTP"

    # 포트 범위: from_port ~ to_port
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # 아웃바운드: egress
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = local.tags
}
  • 네트워크 인터페이스를 생성합니다. 원하는 정적 사설 IP를 지정할 수 있습니다.
  • 네트워크 인터페이스를 생성할 땐 주의해야 할 것이 있습니다. 네트워크 인터페이스를 생성하게 되면 네트워크 인터페이스 내에 security_groups 속성에 보안 그룹을 입력해서 연결합니다. 이후 인스턴스에서 따로 vpc_security_group_ids 속성을 이용할 필요가 없고, network_interface 속성만 연결해주면 보안 그룹도 함께 연결됩니다.
resource "aws_network_interface" "ni" {
  subnet_id       = aws_subnet.test_hyeob_subnet.id
  private_ips     = ["10.0.1.10"]
  security_groups = [aws_security_group.test_hyeob_sg.id]

  tags = local.tags
}
  • 전체 코드는 아래와 같습니다.
# VPC 생성
resource "aws_vpc" "test_hyeob_vpc" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "default"

  # 인스턴스에 public DNS가 표시되도록 하는 속성
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = local.tags
}

# 서브넷 생성
resource "aws_subnet" "test_hyeob_subnet" {
  vpc_id            = aws_vpc.test_hyeob_vpc.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "ap-northeast-2a"

  tags = local.tags
}

# 인터넷 게이트웨이 생성 후 VPC에 연결
resource "aws_internet_gateway" "gw" {
  vpc_id = aws_vpc.test_hyeob_vpc.id

  tags = local.tags
}

# 라우팅 테이블 생성 후 igw에 연결
resource "aws_route_table" "rt" {
  vpc_id = aws_vpc.test_hyeob_vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.gw.id
  }

  tags = local.tags
}

# 서브넷과 라우팅 테이블 연결
resource "aws_route_table_association" "rta" {
  subnet_id      = aws_subnet.test_hyeob_subnet.id
  route_table_id = aws_route_table.rt.id
}

resource "aws_security_group" "test_hyeob_sg" {
  name        = "test_hyeob_sg"
  description = "Allow SSH inbound traffic"
  vpc_id      = aws_vpc.test_hyeob_vpc.id

  # 인바운드: ingress
  ingress {
    description = "SSH from VPC"

    # 포트 범위: from_port ~ to_port
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "HTTP"

    # 포트 범위: from_port ~ to_port
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # 아웃바운드: egress
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = local.tags
}

resource "aws_network_interface" "ni" {
  subnet_id       = aws_subnet.test_hyeob_subnet.id
  private_ips     = ["10.0.1.10"]
  security_groups = [aws_security_group.test_hyeob_sg.id]

  tags = local.tags
}

keypair.tf

  • Harsicorp 자체 리소스tls_private_key를 이용하여 개인 키(private key)를 생성합니다.
# RSA 알고리즘을 이용해 private 키 생성.
resource "tls_private_key" "pk" {
  algorithm = "RSA"
  rsa_bits  = 4096
}
  • 이제 private key를 가지고 AWS에서 키 페어를 생성합니다.
# private 키를 가지고 keypair 파일 생성.
resource "aws_key_pair" "kp" {
  key_name   = "test_hyeob_keypair"
  public_key = tls_private_key.pk.public_key_openssh
}
  • Harsicorp 자체 리소스local_file을 이용해 .pem 키를 다운 받습니다.
# 키 파일을 생성하고 로컬에 다운로드.
resource "local_file" "ssh_key" {
  filename = "${aws_key_pair.kp.key_name}.pem"
  content = tls_private_key.pk.private_key_pem
}
  • 전체 코드는 아래와 같습니다.
# RSA 알고리즘을 이용해 private 키 생성.
resource "tls_private_key" "pk" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

# private 키를 가지고 keypair 파일 생성.
resource "aws_key_pair" "kp" {
  key_name   = "test_hyeob_keypair"
  public_key = tls_private_key.pk.public_key_openssh
}

# 키 파일을 생성하고 로컬에 다운로드.
resource "local_file" "ssh_key" {
  filename = "${aws_key_pair.kp.key_name}.pem"
  content = tls_private_key.pk.private_key_pem
}

ec2.tf

  • amazon linux 2 이미지를 불러옵니다.
# amazon linux 2 이미지 불러오기
data "aws_ami" "amazon-linux-2" {
  most_recent = true

  owners = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-ebs"]
  }
}
  • ec2 인스턴스를 생성합니다. 불러온 이미지를 연결하고, 네트워크 인터페이스, 키 페어 파일을 연결합니다. user_data에는 쉘 스크립트 파일이 들어가는데, 인스턴스가 생성되고 Cloudwatch agent의 설치부터 시작까지의 과정을 프로비저닝하는 코드가 들어있습니다.

프로비저닝하는 쉘 스크립트 코드는 [AWS] CloudWatch Agent 설치부터 시작까지 스크립트로 자동화를 참고하세요.

  • 앞서 언급 드린 것처럼, 네트워크 인터페이스를 사용하게 되면, 보안 그룹은 네트워크 인터페이스에 연결되어 있기 때문에 인스턴스를 생성할 때 따로 보안 그룹을 위해 vpc_security_group_ids를 사용할 필요가 없습니다.
resource "aws_instance" "test_hyeob_instance" {
  ami           = data.aws_ami.amazon-linux-2.id
  instance_type = "t2.micro"

  network_interface {
    network_interface_id = aws_network_interface.ni.id
    device_index         = 0
  }

  # network_interface를 인스턴스에 붙이지 않는다면 아래 속성으로 보안 그룹을 붙여줍니다.
  #   vpc_security_group_ids = [aws_security_group.test_hyeob_sg.id]

  # 역할(인스턴스 프로파일)을 붙여줍니다.
  iam_instance_profile = aws_iam_instance_profile.ec2_profile.name

  # 키 파일을 이용해 생성
  key_name = aws_key_pair.kp.key_name

  # cloudwatch agent 설치부터 구성 파일 생성, 데몬 시작까지 자동화 스크립트 실행
  user_data = "${file("cloudwatch_agent.sh")}"

  tags = local.tags
}
  • 탄력적 IP를 인스턴스에 붙여줍니다.
# 탄력적 IP 붙여주기
resource "aws_eip" "lb" {
  instance = aws_instance.test_hyeob_instance.id
  vpc      = true
}
  • 전체 코드는 아래와 같습니다.
# amazon linux 2 이미지 불러오기
data "aws_ami" "amazon-linux-2" {
  most_recent = true

  owners = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-ebs"]
  }
}

resource "aws_instance" "test_hyeob_instance" {
  ami           = data.aws_ami.amazon-linux-2.id
  instance_type = "t2.micro"

  network_interface {
    network_interface_id = aws_network_interface.ni.id
    device_index         = 0
  }

  # network_interface를 인스턴스에 붙이지 않는다면 아래 속성으로 보안 그룹을 붙여줍니다.
  #   vpc_security_group_ids = [aws_security_group.test_hyeob_sg.id]

  # 역할(인스턴스 프로파일)을 붙여줍니다.
  iam_instance_profile = aws_iam_instance_profile.ec2_profile.name

  # 키 파일을 이용해 생성
  key_name = aws_key_pair.kp.key_name

  # cloudwatch agent 설치부터 구성 파일 생성, 데몬 시작까지 자동화 스크립트 실행
  user_data = "${file("cloudwatch_agent.sh")}"

  tags = local.tags
}

# 탄력적 IP 붙여주기
resource "aws_eip" "lb" {
  instance = aws_instance.test_hyeob_instance.id
  vpc      = true
}
profile
Devops Engineer

0개의 댓글