Bastion host와 NAT Gateway를 통한 Private EC2 인스턴스의 외부 통신 구성

Chori·2025년 1월 3일
0
post-thumbnail

스스로 구축하는 AWS 클라우드 인프라 - 기본편을 수강하며 AWS 인프라를 Terraform으로 작성한 내용입니다.


Public Subnet에 있는 EC2 인스턴스 구성 변경

Public EC2의 구성을 위한 Provisioner

  • Public EC2 인스턴스가 생성되면 index.php 파일과 Private EC2에 접속하기 위한 Private Key를 로컬에서 인스턴스로 복사
  • terraform_data.tf 파일을 만들고 아래와 같이 작성
resource "terraform_data" "copy_index" {
  depends_on = [aws_instance.public_ec2]

  count = length(var.cidr_numeral_public)

  connection {
    type = "ssh"
    user = "ec2-user"
    host = element(aws_eip.public_ec2.*.public_ip, count.index)
    private_key = tls_private_key.ec2_private_key.*.private_key_pem[0]
  }

  provisioner "remote-exec" {
    inline = [
      "while [ ! -d /var/www/html ]; do sleep 5; done",
      "echo '/var/www/html is ready'",
      "sudo chown -R ec2-user:apache /var/www/html",
      "sudo chmod -R 775 /var/www/html"
    ]
  }

  provisioner "file" {
    source = "${path.module}/index.php"
    destination = "/var/www/html/index.php"
  }
}

resource "terraform_data" "copy_key" {
  depends_on = [local_file.private_ec2_key, aws_ami_from_instance.public_ec2_ami]

  count = length(var.cidr_numeral_public)

  connection {
    type = "ssh"
    user = "ec2-user"
    host = element(aws_eip.public_ec2.*.public_ip, count.index)
    private_key = tls_private_key.ec2_private_key.*.private_key_pem[0]
  }

  provisioner "remote-exec" {
    inline = [
      "mkdir /home/ec2-user/keys"
    ]
  }

  provisioner "file" {
    source = "${path.module}/${local_file.private_ec2_key.filename}"
    destination = "/home/ec2-user/keys/${local_file.private_ec2_key.filename}"
  }

  provisioner "remote-exec" {
    inline = [
      "chmod 600 /home/ec2-user/keys/${local_file.private_ec2_key.filename}"
    ]
  }
}
  • Public EC2 인스턴스는 Private EC2 인스턴스에 접속하기 위한 중개 서버 역할을 하게 되며 Public EC2 인스턴스가 Bastion host가 됨
  • Bastion host는 외부 네트워크와 외부 네트워크를 연결하는 게이트웨이 역할을 함

Private Subnet에 EC2 인스턴스 생성

Key Pair 만들기

  • Private EC2 인스턴스에 사용할 Key Pair 생성
  • key_pair.tf 파일을 아래와 같이 변경
# Create a PEM formatted private key
resource "tls_private_key" "ec2_private_key" {
  count = 2
  algorithm = "RSA"
  rsa_bits = 4096
}

# Public ec2 key pair
resource "aws_key_pair" "public_ec2_key_pair" {
  key_name = "public_ec2_key_pair"
  public_key = tls_private_key.ec2_private_key[0].public_key_openssh
}

# Generate a local file about public ec2 key
resource "local_file" "public_ec2_key" {
  filename = "public_ec2_key_pair.pem"
  content = tls_private_key.ec2_private_key[0].private_key_pem
}

# Private ec2 key pair
resource "aws_key_pair" "private_ec2_key_pair" {
  key_name = "private_ec2_key_pair"
  public_key = tls_private_key.ec2_private_key[1].public_key_openssh
}

# Generate a local file about private ec2 key
resource "local_file" "private_ec2_key" {
  filename = "private_ec2_key_pair.pem"
  content = tls_private_key.ec2_private_key[1].private_key_pem
}

Security group 만들기

  • security_group.tf 파일에 아래 내용 추가
# Security group for private ec2
resource "aws_security_group" "private_ec2_sg" {
  name = "private-ec2-sg"
  description = "Security group for private ec2 instance"
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "private-ec2-sg"
  }
}

# Inbound rule allowing SSH for private ec2
resource "aws_vpc_security_group_ingress_rule" "allow_ssh_for_private_ec2" {
  security_group_id = aws_security_group.private_ec2_sg.id
  cidr_ipv4 = "0.0.0.0/0"
  from_port = 22
  ip_protocol = "tcp"
  to_port = 22
}

# Inbound rule allowing HTTP for private ec2
resource "aws_vpc_security_group_ingress_rule" "allow_http_for_private_ec2" {
  security_group_id = aws_security_group.private_ec2_sg.id
  cidr_ipv4 = "0.0.0.0/0"
  from_port = 80
  ip_protocol = "tcp"
  to_port = 80
}

# Inbound rule allowing HTTPS for private ec2
resource "aws_vpc_security_group_ingress_rule" "allow_https_for_private_ec2" {
  security_group_id = aws_security_group.private_ec2_sg.id
  cidr_ipv4 = "0.0.0.0/0"
  from_port = 443
  ip_protocol = "tcp"
  to_port = 443
}

# Outbound rule allowing all traffic for private ec2
resource "aws_vpc_security_group_egress_rule" "allow_all_outbound_traffic_for_private_ec2" {
  security_group_id = aws_security_group.private_ec2_sg.id
  cidr_ipv4 = "0.0.0.0/0"
  ip_protocol = "-1"
}

EC2 인스턴스 만들기

  • ami_from_instance.tf 파일을 아래와 같이 변경
resource "aws_ami_from_instance" "public_ec2_ami" {
  depends_on = [terraform_data.copy_index]
  
  name = "public-ec2-ami"
  source_instance_id = aws_instance.public_ec2.*.id[0]
  snapshot_without_reboot = false
}
  • ec2.tf 파일에 아래 내용 추가
# Private ec2
resource "aws_instance" "private_ec2" {
  count = length(var.cidr_numeral_private)
  ami = aws_ami_from_instance.public_ec2_ami.id # public ec2 ami
  instance_type = "t2.micro"
  key_name = aws_key_pair.private_ec2_key_pair.key_name
  vpc_security_group_ids = [aws_security_group.private_ec2_sg.id]
  subnet_id = element(aws_subnet.private.*.id, count.index)

  tags = {
    Name = "private-ec2-${count.index}-${var.vpc_name}"
  }

  metadata_options {
    http_endpoint = "enabled"
    http_put_response_hop_limit = 1
    http_tokens = "optional"
    instance_metadata_tags = "enabled"
  }
}

NAT Gateway 생성

  • Private EC2 인스턴스가 외부 네트워크와 통신하기 위해서 NAT Gateway가 필요
  • NAT Gateway에 Elastic IP를 할당하기 위해 eip.tf 파일에 다음을 추가
# Elastic IP for nat gateway
resource "aws_eip" "nat_gateway" {
  count = length(var.cidr_numeral_public)
  instance = element(aws_nat_gateway.main.*.id, count.index)
  domain = "vpc"
  
  tags = {
    Name = "eip-nat-gateway-${count.index}"
  }
}
  • NAT Gateway를 만들기 위해 nat_gateway.tf 파일을 생성하고 아래와 같이 작성
resource "aws_nat_gateway" "main" {
  count = length(var.cidr_numeral_public)
  subnet_id = element(aws_subnet.public.*.id, count.index)

  tags = {
    Name = "nat-gw-${count.index}-${var.vpc_name}"
  }
}

Private Subnet의 Route Table 수정

  • Route Table에 Nat Gateway 경로 추가
  • route_table.tf 파일에 아래 내용 추가
# Route for private subnets
resource "aws_route" "private_nat" {
  count = length(var.cidr_numeral_private)
  route_table_id = element(aws_route_table.private.*.id, count.index)
  destination_cidr_block = "0.0.0.0/0"
  gateway_id = aws_nat_gateway.main.id
}
  • 외부로 향하는 트래픽은 Public Subnet에 생성한 NAT Gateway를 통과하게 됨

트러블슈팅

  • Public EC2 인스턴스가 생성된 후에 provisioner "file"로 로컬에 있는 index.php 파일을 EC2 인스턴스에 복사할 때 발생한 문제는 다음과 같음

1. No such file or directory

  • 원인: Provisioner가 실행되는 시점에 Public EC2 인스턴스가 아직 user_data 스크립트가 실행 중이어서 /var/www/html 디렉토리가 아직 생성되지 않았음
  • 해결책: remote-exec Privisioner로 디렉토리가 생성될 때까지 대기하다가 디렉토리가 만들어진 것을 확인하면 파일 복사

2. Upload failed: scp: /var/www/html/index.php: Permission denied

  • 원인: /var/www/html 디렉토리에 대한 쓰기 권한이 없음
  • 해결책: /var/www/html 디렉토리의 소유권을 apache 그룹의 ec2-user로 변경하고 디렉토리의 권한을 775로 변경
    • 775: 소유자와 그룹 사용자는 해당 파일에 대해 읽기, 쓰기, 실행 권한을 가지며 그 외 사용자는 읽기, 실행 권한을 가짐
  provisioner "remote-exec" {
    inline = [
      "while [ ! -d /var/www/html ]; do sleep 5; done",
      "echo '/var/www/html is ready'",
      "sudo chown -R ec2-user:apache /var/www/html",
      "sudo chmod -R 775 /var/www/html"
    ]
  }

  provisioner "file" {
    source = "${path.module}/index.php"
    destination = "/var/www/html/index.php"
  }
profile
전부인 것처럼, 전부가 아닌 것처럼

0개의 댓글