🎯 EC2 운영 가이드 - 인스턴스 변경과 Elastic IP 실전

📑 목차


1. EC2 생성 코드

💻 AMI 자동 조회 (data source)

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]
 
  filter {
    name   = "name"
    values = ["al2023-ami-2023*-kernel-*-arm64"]
  }
 
  filter {
    name   = "state"
    values = ["available"]
  }
}

data source란?

AWS에 이미 존재하는 리소스를 조회하는 블록. 위 코드는 “Amazon Linux 2023 ARM64 최신 AMI”를 자동으로 찾아줌. 하드코딩 대신 사용하면 항상 최신 AMI를 참조 가능.

💻 SSH Key Pair 등록

# 로컬에서 키 생성
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N "" -C "aws-terraform"
resource "aws_key_pair" "main" {
  key_name   = "${var.project_name}-key"
  public_key = file("~/.ssh/id_ed25519.pub")
 
  tags = {
    Name    = "${var.project_name}-key"
    Project = var.project_name
  }
}

💻 Bastion EC2 인스턴스

resource "aws_instance" "bastion" {
  ami                    = data.aws_ami.amazon_linux.id
  instance_type          = "t4g.micro"
  key_name               = aws_key_pair.main.key_name
  subnet_id              = aws_subnet.public[0].id
  vpc_security_group_ids = [aws_security_group.bastion.id]
 
  root_block_device {
    volume_size = 8
    volume_type = "gp3"
  }
 
  tags = {
    Name    = "${var.project_name}-bastion"
    Project = var.project_name
    Role    = "bastion"
  }
 
  lifecycle {
    ignore_changes = [ami]  # AMI 업데이트로 재생성 방지
  }
}

ignore_changes = [ami]가 없으면?

data source가 최신 AMI를 조회할 때마다 plan에서 -/+ replace가 뜸. → apply 하면 인스턴스 삭제 후 재생성 → 데이터 소실!

💻 Output 설정

output "bastion_public_ip" {
  description = "Bastion Host Elastic IP (고정)"
  value       = aws_eip.bastion.public_ip
}
 
output "bastion_instance_id" {
  description = "Bastion Instance ID"
  value       = aws_instance.bastion.id
}
 
output "bastion_ssh_command" {
  description = "SSH 접속 명령어"
  value       = "ssh -i ~/.ssh/id_ed25519 ec2-user@${aws_eip.bastion.public_ip}"
}

2. EC2 변경 위험도 분류

📊 안전한 변경 (서비스 영향 없음, 중단 없음)

속성Terraform 동작설명
tags~ update in-place즉시 반영
security_groups~ update in-place즉시 반영
monitoring~ update in-place즉시 반영
iam_instance_profile~ update in-place즉시 반영
volume_size 확장~ update in-place확장만 가능, 축소 불가

📊 다운타임 있는 변경 (데이터 유지, 잠시 중단)

속성Terraform 동작설명
instance_type~ update in-place중지 → 변경 → 재시작

instance_type 변경 시 발생하는 일

  1. Terraform이 EC2 Stop API 호출
  2. 인스턴스 중지 대기
  3. ModifyInstanceAttribute로 타입 변경
  4. EC2 Start API 호출
  5. 인스턴스 시작 대기

소요 시간: 약 1분 EBS 데이터: 유지 Private IP: 유지 Public IP: EIP 없으면 변경됨!

📊 위험한 변경 (인스턴스 삭제 재생성)

속성Terraform 동작설명
ami-/+ replace새 OS로 재생성
subnet_id-/+ replace다른 서브넷으로 이동 불가
availability_zone-/+ replace다른 AZ로 이동 불가
key_name-/+ replaceSSH 키 변경
user_data-/+ replace부트스트랩 스크립트 변경

재생성 시 잃는 것

  • Instance ID 변경
  • Public/Private IP 변경 (EIP 제외)
  • EBS root volume 삭제 (delete_on_termination=true)
  • 인스턴스 내부의 모든 설치 패키지, 설정, 데이터
  • 메모리 상태, 실행 중인 프로세스
  • /tmp, /var/log 등 임시 데이터

📊 plan 읽기 실전

# 안전 (태그 추가)
  ~ resource "aws_instance" "bastion" {
      ~ tags = {
          + "Env" = "dev"
        }
    }
 
# 다운타임 (타입 변경)
  ~ resource "aws_instance" "bastion" {
      ~ instance_type = "t4g.micro" -> "t4g.small"
    }
 
# 위험! (AMI 변경 → 재생성)
-/+ resource "aws_instance" "bastion" {
      ~ ami = "ami-aaa" -> "ami-bbb"  # forces replacement
      ~ id  = "i-xxx" -> (known after apply)  ← ID 바뀜 = 새 인스턴스
    }

핵심 판단 기준

  • ~ = 안전
  • -/+ + forces replacement = 위험, 멈추고 생각
  • id = (known after apply) = 새 인스턴스 = 데이터 소실

3. 변경 전 체크리스트

📋 instance_type 변경 전

□ 1. EIP 연결 여부 확인
     EIP 없으면 → Public IP 바뀜 → SSH 끊김, DNS 깨짐

□ 2. 같은 아키텍처인지 확인
     ARM(t4g) → ARM(t4g): OK
     ARM(t4g) → x86(t3): AMI 불일치로 replace 발생!

□ 3. 현재 AZ에서 지원되는 타입인지 확인
     같은 패밀리(t4g.micro → t4g.small): 보통 OK
     다른 패밀리: AZ 지원 여부 확인 필요

□ 4. 실행 중인 프로세스 확인
     중지되므로 진행 중인 작업 중단됨

□ 5. plan에서 ~ (update) 인지 확인
     -/+ (replace) 가 뜨면 멈출 것

📋 위험한 변경 (ami, subnet 등) 전

□ 1. plan에서 -/+ 또는 forces replacement 확인
□ 2. 데이터 백업 (EBS 스냅샷)
□ 3. 별도 EBS 볼륨은 delete_on_termination=false 확인
□ 4. Elastic IP 연결 여부 (재연결 가능)
□ 5. Security Group, IAM Role 재적용 여부
□ 6. User Data로 자동 설정 가능한지 (수동 설치 최소화)

4. instance_type 변경 실습

📋 실습 시나리오

t4g.micro (메모리 1GB) → t4g.small (메모리 2GB) → t4g.micro (원복)

💻 변경 전 상태 확인 (SSH)

ssh -i ~/.ssh/id_ed25519 ec2-user@<PUBLIC_IP>

인스턴스 내부에서 확인하는 명령어들:

# 메타데이터로 인스턴스 정보 확인 (IMDSv2)
TOKEN=$(curl -s -X PUT 'http://169.254.169.254/latest/api/token' \
  -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600')
 
# 인스턴스 타입
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/instance-type
 
# 인스턴스 ID
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/instance-id
 
# Public IP
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/public-ipv4
 
# Private IP
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/local-ipv4
 
# CPU 수
nproc
 
# 메모리
free -h | grep Mem
 
# 디스크
df -h /
 
# 가동 시간
uptime

EC2 메타데이터 서비스 (IMDS)

  • 169.254.169.254는 AWS 메타데이터 서버 (Link-local 주소)
  • 인스턴스 내부에서만 접근 가능
  • IMDSv2는 토큰 기반 (보안 강화)
  • 인스턴스 타입, ID, IP, IAM Role 등 조회 가능
  • 트러블슈팅 시 매우 유용

💻 데이터 유지 테스트용 파일 생성

# 변경 전에 테스트 파일 생성
echo '이 파일이 살아있으면 데이터 유지 성공' > ~/test-survival.txt

💻 Terraform 코드 변경

# ec2.tf
# 변경 전
instance_type = "t4g.micro"
 
# 변경 후
instance_type = "t4g.small"

💻 Plan 확인

terraform plan
  ~ resource "aws_instance" "bastion" {
        id            = "i-05da54eaec43bb6a8"        ← ID 유지!
      ~ instance_type = "t4g.micro" -> "t4g.small"    ← 변경
      ~ public_ip     = "..." -> (known after apply)  ← IP 바뀔 수 있음
    }

Plan: 0 to add, 1 to change, 0 to destroy.

확인 포인트:

✅ ~ update in-place (재생성 아님)
✅ id 유지됨
✅ 0 to destroy
⚠️ public_ip → (known after apply): EIP 없으면 바뀜

💻 Apply

terraform apply -auto-approve
# 약 1분 소요 (중지 → 변경 → 재시작)

📊 실습 결과 (EIP 없을 때)

항목변경 전변경 후
Instance Typet4g.microt4g.small
Instance IDi-05da54eaec43bb6a8i-05da54eaec43bb6a8 (유지)
CPU2 vCPU2 vCPU
Memory916MB1.8GB (2배)
Private IP10.0.1.6810.0.1.68 (유지)
Public IP43.201.104.11813.124.102.92 (변경!)
테스트 파일있음있음 (유지)
Uptime7분0분 (리부팅)

5. Elastic IP 구성

💡 왜 필요한가?

EIP 없이 instance_type 변경 → Public IP 바뀜
  → SSH 접속 주소 변경
  → DNS A레코드 깨짐
  → 외부 연동 서비스에 IP 재등록 필요

EIP 있으면 → 어떤 변경을 해도 IP 유지

💻 Terraform 코드

# Elastic IP 생성
resource "aws_eip" "bastion" {
  domain = "vpc"
 
  tags = {
    Name    = "${var.project_name}-bastion-eip"
    Project = var.project_name
  }
}
 
# EC2에 연결
resource "aws_eip_association" "bastion" {
  instance_id   = aws_instance.bastion.id
  allocation_id = aws_eip.bastion.id
}

EIP vs eip_association 분리하는 이유

  • EIP와 EC2를 독립적으로 관리 가능
  • EC2가 재생성되어도 EIP는 유지
  • association만 다시 걸면 됨
  • 한 EIP를 다른 인스턴스로 옮길 수도 있음

📊 EIP 비용

상태비용
실행 중인 EC2에 연결무료
EC2 중지 상태$0.005/h (~$3.6/월)
미연결 (방치)$0.005/h (~$3.6/월)
2번째 EIP (같은 인스턴스)$0.005/h

EIP 비용 함정

  • EC2 끌 때 EIP 과금 시작
  • 안 쓰는 EIP 방치하면 계속 과금
  • terraform destroy 하면 EIP도 해제되므로 비용 걱정 없음
  • 단, destroy 후 re-apply 시 새 IP 할당 (고정 IP 변경)

💻 Plan 확인

terraform plan
+ aws_eip.bastion           ← EIP 생성
+ aws_eip_association       ← EC2에 연결

Plan: 2 to add, 0 to change, 0 to destroy.

EC2에 영향 없음 (중지 없음!) — EIP 붙이는 건 인스턴스를 건드리지 않습니다.


6. EIP + instance_type 변경 검증

📋 핵심 테스트: EIP 붙인 상태에서 타입 변경하면 IP 유지되는가?

t4g.small (EIP: 43.201.xxx.xxx)
    ↓ instance_type 변경
t4g.micro (EIP: 43.201.xxx.xxx)  ← 유지!

📊 실습 결과

항목EIP 없이 타입 변경EIP 있고 타입 변경
Instance ID유지유지
Private IP유지유지
Public IP변경됨!유지!
데이터유지유지
다운타임~1분~1분

📊 전체 IP 변화 추적 (실제 실습 기록)

단계Public IPInstance Type비고
최초 생성43.201.104.118t4g.micro자동 할당
type → small13.124.102.92t4g.smallIP 바뀜!
EIP 연결43.201.xxx.xxxt4g.small고정 IP 할당
type → micro43.201.xxx.xxxt4g.microIP 유지!

7. SSH 접속 및 확인 방법

💻 기본 접속

ssh -i ~/.ssh/id_ed25519 ec2-user@<EIP_주>

💻 원라이너로 확인 (접속하지 않고 실행)

# 인스턴스 타입만 확인
ssh -i ~/.ssh/id_ed25519 ec2-user@43.201.xxx.xxx \
  "TOKEN=\$(curl -s -X PUT 'http://169.254.169.254/latest/api/token' -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600') && curl -s -H \"X-aws-ec2-metadata-token: \$TOKEN\" http://169.254.169.254/latest/meta-data/instance-type"

💻 전체 상태 확인 스크립트

#!/bin/bash
# check-instance.sh
HOST=${1:-43.201.xxx.xxx}
KEY=~/.ssh/id_ed25519
 
ssh -i $KEY -o StrictHostKeyChecking=no ec2-user@$HOST "
TOKEN=\$(curl -s -X PUT 'http://169.254.169.254/latest/api/token' \
  -H 'X-aws-ec2-metadata-token-ttl-seconds: 21600')
 
echo '=== Instance Type ==='
curl -s -H \"X-aws-ec2-metadata-token: \$TOKEN\" \
  http://169.254.169.254/latest/meta-data/instance-type
 
echo ''
echo '=== Instance ID ==='
curl -s -H \"X-aws-ec2-metadata-token: \$TOKEN\" \
  http://169.254.169.254/latest/meta-data/instance-id
 
echo ''
echo '=== Public IP ==='
curl -s -H \"X-aws-ec2-metadata-token: \$TOKEN\" \
  http://169.254.169.254/latest/meta-data/public-ipv4
 
echo ''
echo '=== Private IP ==='
curl -s -H \"X-aws-ec2-metadata-token: \$TOKEN\" \
  http://169.254.169.254/latest/meta-data/local-ipv4
 
echo ''
echo '=== CPU ==='
nproc
 
echo ''
echo '=== Memory ==='
free -h | grep Mem
 
echo ''
echo '=== Disk ==='
df -h /
 
echo ''
echo '=== Uptime ==='
uptime
"

💻 Terraform으로 확인 (SSH 없이)

# 현재 인스턴스 상태
terraform state show aws_instance.bastion
 
# Output 값만
terraform output bastion_public_ip
terraform output bastion_ssh_command
 
# AWS CLI로 직접
aws ec2 describe-instances \
  --instance-ids i-05da54eaec43bb6a8 \
  --query 'Reservations[0].Instances[0].[InstanceType,State.Name,PublicIpAddress,PrivateIpAddress]' \
  --output table

8. 실습 결과 요약

📊 변경 유형별 대응 전략

변경동작IP 영향데이터대응
tags즉시 반영없음유지그냥 apply
SG즉시 반영없음유지그냥 apply
instance_type중지/시작EIP 없으면 변경유지EIP 필수
ami삭제 재생성전부 변경소실ignore_changes
subnet_id삭제 재생성전부 변경소실절대 변경 금지

📋 실무 필수 설정

resource "aws_instance" "app" {
  # ...
 
  lifecycle {
    ignore_changes  = [ami, user_data]  # 재생성 방지
    prevent_destroy = true               # 삭제 차단
  }
}
 
# 고정 IP 필수
resource "aws_eip" "app" {
  domain = "vpc"
}
 
resource "aws_eip_association" "app" {
  instance_id   = aws_instance.app.id
  allocation_id = aws_eip.app.id
}

📋 현재 인프라 상태

VPC:     vpc-0478120058493042f (10.0.0.0/16)
Bastion: i-05da54eaec43bb6a8 (t4g.micro)
EIP:     43.201.xxx.xxx (고정)
SSH:     ssh -i ~/.ssh/id_ed25519 ec2-user@43.201.xxx.xxx

문서 정보