가시다(gasida) 님이 진행하는 Terraform T101 4기 실습 스터디 게시글입니다.
책 '테라폼으로 시작하는 IaC' 를 참고하였고, 스터디하면서 도움이 될 내용들을 추가적으로 정리하였습니다.
2.3 (참고) CLI 구성 파일
- 파일 내에서 사용 가능한 설정 값
- plugin_cache_dir: terraform init
→ 활용 시 프로바이더의 다운로드 시간되 디스크 공간을 줄일 수 있음
(수정의견)
→ Workspace를 복수로 사용 시 공통으로 활용되어 저장공간과 다운로드 시간을 단축할 수 있음
Terraform은 하시코프에서 제공하는 인프라 환경 배포 툴입니다.
Terraform은 Infrastructure as a Code (코드로서 인프라를 정의하기), Reproducible Infrastructure (재현 가능한 인프라) 특성을 가지고 있음
AWS든, GCP든, Azure든 클라우드 인프라 환경을 코드로서 정의해 사용함으로써 동일한 인프라를 재현할 수 있음
클라우드 인프라를 코드로 명시적으로 정의하니 수십번을 실행해도 동일한 환경이 생성됨
Terraform - AWS 리소스 생성 및 관리
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "us-east-1"
}
# Create a VPC
resource "aws_vpc" "example" {
cidr_block = "10.0.0.0/16"
}
용어설명
- 프로비저닝[Provisioning]
어떤 프로세스나 서비스를 실행하기 위한 준비 단계. 주로 테라폼은 네트워크나 컴퓨팅 자원을 준비하는 작업을 주로 다룸
- 프로바이더[Provider]
테라폼과 외부 서비스를 연결해주는 기능을 하는 모듈. 테라폼으로 서비스에 접근하여 자원 생성등의 작업을 하기 위해서는 Provider가 먼저 셋업되어야 한다. AWS외 GCP, Azure와 같은 범용 클라우드 서비스 등 다양한 서비스를 지원
- 자원[Resource]
특정 프로바이더가 제공해주는 조작 가능한 대상의 최소 단위. 예를 들어 AWS 프로바이더는 aws_instance 리소스 타입을 제공하고, 이 리소스 타입을 사용해 Amazon EC2의 가상 머신 리소스를 선언하고 조작하는 것이 가능
- HCL[Hashicorp Configuration Language]
HCL은 테라폼에서 사용하는 설정 언어이다. 파일의 확장자는 .tf를 사용하며 테라폼에서 모든 설정과 리소스 선언은 HCL을 사용해 이루어짐
- 계획[Plan]
프로젝트 디렉터리 아래의 모든 .tf 파일의 내용을 실제로 적용 가능한지 확인하는 작업. 명령어를 실행하면 어떤 리소스가 생성되고, 수정되고, 삭제될지 계획을 보여줌
- 적용[Apply]
Plan이 실제로 적용가능한지 확인하는 작업이라면, Apply는 실제로 적용하는 명령어
- 삭제[Destroy]
tf파일로 생성된 모든 리소스들을 제거
Terraform 주요 명령어
❯ terraform init : 프로젝트를 초기화 하며 생성된 프로바이더에 맞춰 필요한 플러그인들이 설치
❯ terraform plan : 변경되는 상황을 확인(+ 추가, - 삭제, ~ 변경) 시켜줌
❯ terraform appy : 변경사항 적용됨
❯ terraform state list : 생성된 자원상태 조회
❯ terraform output : output으로 지정한 결과값을 조회
❯ terraform destroy : 자원 삭제
tfenv로 Terraform binary 설치 추천(실습환경: macOS)
# tfenv 설치
❯ brew install tfenv
# 설치 가능 버전 리스트 확인
❯ tfenv list-remote
# 테라폼 1.8.5 버전 설치
❯ tfenv install 1.8.5
# 테라폼 1.5.1 버전 사용 설정
❯ tfenv use 1.8.5
# tfenv로 설치한 버전 확인
❯ tfenv list
# 테라폼 버전 정보 확인
❯ terraform version
Terraform v1.8.5
on darwin_arm64
+ provider registry.terraform.io/hashicorp/local v2.5.1
# 자동완성
❯ terraform -install-autocomplete
# 참고 .zshrc 에 아래 추가됨
❯ cat ~/.zshrc
...
autoload -U +X bashcompinit && bashcompinit
complete -o nospace -C /usr/local/bin/terraform terraform
Study에서는 1.8.5 을 사용하기로 함
IDE : vscode 사용 - 자동 저장 설정 추천 (Editor 창에서 코드 수정 후 터미널창에서 명령어 실행 시 편리함)
awscli 는 2.x 이상 버전 사용 필요
awscli Page Display - Disable 방법
(Tip) 실습간 Terraform 빠른 명령어 실행을 위해 Alias 명령어 활용
❯ alias tfb="terraform init && terraform validate && terraform plan -out tfplan && terraform apply tfplan"
❯ alias tfd="terraform destroy -auto-approve && rm -rf .terraform* && rm tfplan"
최신 Amazon Linux 2 AMI ID 가져오기
# AWS CLI
❯ AL2ID=`aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" "Name=state,Values=available" --query 'Images|sort_by(@, &CreationDate)[-1].[ImageId]' --output text`
❯ echo $AL2ID
ami-0ebb3f23647161078
# SSM 이용
❯ aws ssm get-parameters-by-path --path /aws/service/ami-amazon-linux-latest --query 'Parameters[*].[Value, Name]' --output text
ami-0450ec15bbf42649e /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-arm64
ami-0edc5427d49d09d2a /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64
...
# IAM Console에서 devops 계정 생성시 AdministratorAccess 권한부여, Access key 생성
# CLI Credential 구성
❯ aws configure
AWS Access Key ID [*************]:
AWS Secret Access Key [****************]:
Default region name [ap-northeast-2]:
Default output format [json]:
❯ aws sts get-caller-identity
{
"UserId": "AIDA**",
"Account": "53****617837*",
"Arn": "arn:aws:iam::53****617837*:user/devops"
}
# Default VPC 없는 상태 확인
❯ aws ec2 describe-vpcs --filter 'Name=isDefault,Values=true' | jq '.Vpcs[0].VpcId'
null
# Default VPC 실행 명령어
❯ aws ec2 create-default-vpc
# 생성된 VPC 확인
❯ aws ec2 describe-vpcs --filter 'Name=isDefault,Values=true' | jq
{
"Vpcs": [
{
"CidrBlock": "172.31.0.0/16",
"DhcpOptionsId": "dopt-6bf6b600",
"State": "available",
"VpcId": "vpc-026116460be671c3d",
"OwnerId": "53****617837",
"InstanceTenancy": "default",
"CidrBlockAssociationSet": [
{
"AssociationId": "vpc-cidr-assoc-048ffb7e67b83afae",
"CidrBlock": "172.31.0.0/16",
"CidrBlockState": {
"State": "associated"
}
}
],
"IsDefault": true
}
]
}
# Default Subnet 확인
❯ aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=vpc-026116460be671c3d"
{
"Subnets": [
{
"AvailabilityZone": "ap-northeast-2a",
"AvailabilityZoneId": "apne2-az1",
"AvailableIpAddressCount": 4091,
"CidrBlock": "172.31.0.0/20",
"DefaultForAz": true,
"MapPublicIpOnLaunch": true,
"MapCustomerOwnedIpOnLaunch": false,
"State": "available",
"SubnetId": "subnet-0dc5a51c96d2db619",
"VpcId": "vpc-026116460be671c3d",
"OwnerId": "53****617837",
"AssignIpv6AddressOnCreation": false,
"Ipv6CidrBlockAssociationSet": [],
"SubnetArn": "arn:aws:ec2:ap-northeast-2:53****617837:subnet/subnet-0dc5a51c96d2db619",
"EnableDns64": false,
"Ipv6Native": false,
"PrivateDnsNameOptionsOnLaunch": {
"HostnameType": "ip-name",
"EnableResourceNameDnsARecord": false,
"EnableResourceNameDnsAAAARecord": false
}
},
{
"AvailabilityZone": "ap-northeast-2b",
"AvailabilityZoneId": "apne2-az2",
...
}
EC2 생성 모니터링
# 별도 터미널 창을 생성 후 EC2 생성 모니터링
❯ export AWS_PAGER=""
❯ while true; do aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text ; echo "------------------------------" ; sleep 1; done
EC2 생성
❯ cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "$AL2ID"
instance_type = "t2.micro"
tags = {
Name = "t101-study"
}
}
EOT
❯ tfb
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.54.1
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Success! The configuration is valid.
aws_instance.example: Refreshing state... [id=i-0a238bacf2447df7e]
Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_instance.example will be updated in-place
~ resource "aws_instance" "example" {
id = "i-0a238bacf2447df7e"
~ tags = {
+ "Name" = "t101-study"
}
~ tags_all = {
+ "Name" = "t101-study"
}
# (38 unchanged attributes hidden)
# (8 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
───────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
aws_instance.example: Modifying... [id=i-0a238bacf2447df7e]
aws_instance.example: Modifications complete after 1s [id=i-0a238bacf2447df7e]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
생성된 EC2 TAG 수정
❯ cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "$AL2ID"
instance_type = "t2.micro"
tags = {
Name = "t101-study"
Owner = "sjkim"
}
}
EOT
❯ cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "$AL2ID"
instance_type = "t2.micro"
tags = {
Name = "t101-study"
Owner = "sjkim"
}
}
EOT
❯ tfb
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.54.1
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Success! The configuration is valid.
aws_instance.example: Refreshing state... [id=i-0a238bacf2447df7e]
Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_instance.example will be updated in-place
~ resource "aws_instance" "example" {
id = "i-0a238bacf2447df7e"
~ tags = {
"Name" = "t101-study"
+ "Owner" = "sjkim"
}
~ tags_all = {
+ "Owner" = "sjkim"
# (1 unchanged element hidden)
}
# (38 unchanged attributes hidden)
# (8 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
───────────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
aws_instance.example: Modifying... [id=i-0a238bacf2447df7e]
aws_instance.example: Modifications complete after 1s [id=i-0a238bacf2447df7e]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
EC2 삭제
❯ terraform destroy -auto-approve
aws_instance.example: Refreshing state... [id=i-0a238bacf2447df7e]
Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# aws_instance.example will be destroyed
- resource "aws_instance" "example" {
- ami = "ami-0ebb3f23647161078" -> null
- arn = "arn:aws:ec2:ap-northeast-2:53****617837:instance/i-0a238bacf2447df7e" -> null
- associate_public_ip_address = true -> null
- availability_zone = "ap-northeast-2a" -> null
- cpu_core_count = 1 -> null
- cpu_threads_per_core = 1 -> null
- disable_api_stop = false -> null
- disable_api_termination = false -> null
- ebs_optimized = false -> null
- get_password_data = false -> null
- hibernation = false -> null
- id = "i-0a238bacf2447df7e" -> null
- instance_initiated_shutdown_behavior = "stop" -> null
- instance_state = "running" -> null
- instance_type = "t2.micro" -> null
- ipv6_address_count = 0 -> null
- ipv6_addresses = [] -> null
- monitoring = false -> null
- placement_partition_number = 0 -> null
- primary_network_interface_id = "eni-0dadaa55ff26aba61" -> null
- private_dns = "ip-172-31-9-51.ap-northeast-2.compute.internal" -> null
- private_ip = "172.31.9.51" -> null
- public_dns = "ec2-52-79-105-86.ap-northeast-2.compute.amazonaws.com" -> null
- public_ip = "52.79.105.86" -> null
- secondary_private_ips = [] -> null
- security_groups = [
- "default",
] -> null
- source_dest_check = true -> null
- subnet_id = "subnet-0dc5a51c96d2db619" -> null
- tags = {
- "Name" = "t101-study"
- "Owner" = "sjkim"
} -> null
- tags_all = {
- "Name" = "t101-study"
- "Owner" = "sjkim"
} -> null
- tenancy = "default" -> null
- user_data_replace_on_change = false -> null
- vpc_security_group_ids = [
- "sg-0e6cdaaea796e7bbd",
] -> null
# (8 unchanged attributes hidden)
- capacity_reservation_specification {
- capacity_reservation_preference = "open" -> null
}
- cpu_options {
- core_count = 1 -> null
- threads_per_core = 1 -> null
# (1 unchanged attribute hidden)
}
- credit_specification {
- cpu_credits = "standard" -> null
}
- enclave_options {
- enabled = false -> null
}
- maintenance_options {
- auto_recovery = "default" -> null
}
- metadata_options {
- http_endpoint = "enabled" -> null
- http_protocol_ipv6 = "disabled" -> null
- http_put_response_hop_limit = 1 -> null
- http_tokens = "optional" -> null
- instance_metadata_tags = "disabled" -> null
}
- private_dns_name_options {
- enable_resource_name_dns_a_record = false -> null
- enable_resource_name_dns_aaaa_record = false -> null
- hostname_type = "ip-name" -> null
}
- root_block_device {
- delete_on_termination = true -> null
- device_name = "/dev/xvda" -> null
- encrypted = true -> null
- iops = 100 -> null
- kms_key_id = "arn:aws:kms:ap-northeast-2:53****617837:key/50a438fb-1baf-4094-b63e-706141a94d9a" -> null
- tags = {} -> null
- tags_all = {} -> null
- throughput = 0 -> null
- volume_id = "vol-050af0ecf73e052ff" -> null
- volume_size = 8 -> null
- volume_type = "gp2" -> null
}
}
Plan: 0 to add, 0 to change, 1 to destroy.
aws_instance.example: Destroying... [id=i-0a238bacf2447df7e]
aws_instance.example: Still destroying... [id=i-0a238bacf2447df7e, 10s elapsed]
aws_instance.example: Still destroying... [id=i-0a238bacf2447df7e, 20s elapsed]
aws_instance.example: Still destroying... [id=i-0a238bacf2447df7e, 30s elapsed]
aws_instance.example: Still destroying... [id=i-0a238bacf2447df7e, 40s elapsed]
aws_instance.example: Destruction complete after 40s
Destroy complete! Resources: 1 destroyed.
terraform state : Terraform에 의해 생선된 자원 목록과 내용 조회
❯ terraform state list
local_file.abc
❯ terraform state show local_file.abc
# local_file.abc:
resource "local_file" "abc" {
content = "step7 file ok"
content_base64sha256 = "AIR3m2m5d5xNW5ovCHjOSHaCz6wUf8bYGfjNCcnZKho="
content_base64sha512 = "DgtP0+WsBErpthJ07+GHq2uO4bdaNADPBWepAnDoTtX5kozHIz8WGkLMLoRM/zoJ8pSlLF2IMC8Y1bFCydUZYw=="
content_md5 = "e7ce0fcc3a0760160958f8a55cdcedf0"
content_sha1 = "653353c86e0b78f26dfe85f8e86f4f9d07c0d505"
content_sha256 = "0084779b69b9779c4d5b9a2f0878ce487682cfac147fc6d819f8cd09c9d92a1a"
content_sha512 = "0e0b4fd3e5ac044ae9b61274efe187ab6b8ee1b75a3400cf0567a90270e84ed5f9928cc7233f161a42cc2e844cff3a09f294a52c5d88302f18d5b142c9d51963"
directory_permission = "0777"
file_permission = "0777"
filename = "./step7.txt"
id = "653353c86e0b78f26dfe85f8e86f4f9d07c0d505"
}
특정 리소스만 재생성
terraform -replace=local_file.abc -auto-approbe
❯ terraform apply -replace=local_file.abc -auto-approve
local_file.dev: Refreshing state... [id=15f946cb27f0730866cefea4f0923248d9366cb0]
local_file.abc: Refreshing state... [id=5678fb68a642f3c6c8004c1bdc21e7142087287b]
Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# local_file.abc will be replaced, as requested
-/+ resource "local_file" "abc" {
~ content_base64sha256 = "U+Dv8yBGJvPiVspjZXLXzN+OtaGQyd76P6VnvGOGa3Y=" -> (known after apply)
~ content_base64sha512 = "J873Ugx5HyDEnYsjdX8iMBjn4I3gft82udsl3lNeWEoqwmNE3mvUZNNz4QRqQ3iaT5SW1y9p3e1Xn2txEBapKg==" -> (known after apply)
~ content_md5 = "4edb03f55c86d5e0a76f5627fa506bbf" -> (known after apply)
~ content_sha1 = "5678fb68a642f3c6c8004c1bdc21e7142087287b" -> (known after apply)
~ content_sha256 = "53e0eff3204626f3e256ca636572d7ccdf8eb5a190c9defa3fa567bc63866b76" -> (known after apply)
~ content_sha512 = "27cef7520c791f20c49d8b23757f223018e7e08de07edf36b9db25de535e584a2ac26344de6bd464d373e1046a43789a4f9496d72f69dded579f6b711016a92a" -> (known after apply)
~ id = "5678fb68a642f3c6c8004c1bdc21e7142087287b" -> (known after apply)
# (4 unchanged attributes hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
local_file.abc: Destroying... [id=5678fb68a642f3c6c8004c1bdc21e7142087287b]
local_file.abc: Destruction complete after 0s
local_file.abc: Creating...
local_file.abc: Creation complete after 0s [id=5678fb68a642f3c6c8004c1bdc21e7142087287b]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
❯ ls -al
total 64
drwxr-xr-x 10 sjkim staff 320 Jun 15 17:36 .
drwxr-xr-x 5 sjkim staff 160 Jun 15 17:11 ..
drwxr-xr-x 3 sjkim staff 96 Jun 15 17:15 .terraform
-rw-r--r-- 1 sjkim staff 1153 Jun 15 17:15 .terraform.lock.hcl
-rwxr-xr-x 1 sjkim staff 4 Jun 15 17:36 abc.txt
-rwxr-xr-x 1 sjkim staff 4 Jun 15 17:33 def.txt
-rw-r--r-- 1 sjkim staff 175 Jun 15 17:32 main.tf
-rw-r--r-- 1 sjkim staff 2962 Jun 15 17:36 terraform.tfstate
-rw-r--r-- 1 sjkim staff 2962 Jun 15 17:36 terraform.tfstate.backup
-rw-r--r-- 1 sjkim staff 4742 Jun 15 17:35 tfplan
busybox 이용하여 단순 웹서버 동작 가능 (nohup busybox httpd -f -p 8080 & )
Ubuntu 22.04 최신 AMI ID 확인
❯ aws ec2 describe-images --owners 099720109477 \
--filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" "Name=state,Values=available" \
--query 'Images|sort_by(@, &CreationDate)[-1].[ImageId, Name]' --output text
ami-0bcdae8006538619a ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20240614
# 변수 지정
UBUNTUID=ami-0bcdae8006538619a
Ubuntu EC2 생성
❯ echo $UBUNTUID
ami-0bcdae8006538619a
❯ cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "$UBUNTUID"
instance_type = "t2.micro"
user_data = <<-EOF
#!/bin/bash
echo "Hello, T101 Study" > index.html
nohup busybox httpd -f -p 8080 &
EOF
tags = {
Name = "terraform-Study-101"
}
}
EOT
❯ tfb
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.54.1
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Success! The configuration is valid.
Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.example will be created
+ resource "aws_instance" "example" {
+ ami = "ami-0bcdae8006538619a"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ iam_instance_profile = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_lifecycle = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t2.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ spot_instance_request_id = (known after apply)
+ subnet_id = (known after apply)
+ tags = {
+ "Name" = "terraform-Study-101"
}
+ tags_all = {
+ "Name" = "terraform-Study-101"
}
+ tenancy = (known after apply)
+ user_data = "d91ca31904077f0b641b5dd5a783401396ffbf3f"
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = false
+ vpc_security_group_ids = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
aws_instance.example: Creating...
aws_instance.example: Still creating... [10s elapsed]
aws_instance.example: Still creating... [20s elapsed]
aws_instance.example: Still creating... [30s elapsed]
aws_instance.example: Still creating... [40s elapsed]
aws_instance.example: Still creating... [50s elapsed]
aws_instance.example: Creation complete after 51s [id=i-0e849c8e935e39294]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Terraform state 확인
❯ terraform state list
aws_instance.example
❯ terraform state show aws_instance.example
# aws_instance.example:
resource "aws_instance" "example" {
ami = "ami-0bcdae8006538619a"
arn = "arn:aws:ec2:ap-northeast-2:53****617837:instance/i-0e849c8e935e39294"
associate_public_ip_address = true
availability_zone = "ap-northeast-2a"
cpu_core_count = 1
cpu_threads_per_core = 1
disable_api_stop = false
disable_api_termination = false
ebs_optimized = false
get_password_data = false
hibernation = false
host_id = null
iam_instance_profile = null
id = "i-0e849c8e935e39294"
instance_initiated_shutdown_behavior = "stop"
instance_lifecycle = null
instance_state = "running"
instance_type = "t2.micro"
ipv6_address_count = 0
ipv6_addresses = []
key_name = null
monitoring = false
outpost_arn = null
password_data = null
placement_group = null
placement_partition_number = 0
primary_network_interface_id = "eni-0ecb833a51d9976d1"
private_dns = "ip-172-31-14-154.ap-northeast-2.compute.internal"
private_ip = "172.31.14.154"
public_dns = "ec2-54-180-132-65.ap-northeast-2.compute.amazonaws.com"
public_ip = "54.180.132.65"
secondary_private_ips = []
security_groups = [
"default",
]
source_dest_check = true
spot_instance_request_id = null
subnet_id = "subnet-0dc5a51c96d2db619"
tags = {
"Name" = "terraform-Study-101"
}
tags_all = {
"Name" = "terraform-Study-101"
}
tenancy = "default"
user_data = "d91ca31904077f0b641b5dd5a783401396ffbf3f"
user_data_replace_on_change = false
vpc_security_group_ids = [
"sg-0e6cdaaea796e7bbd",
]
capacity_reservation_specification {
capacity_reservation_preference = "open"
}
cpu_options {
amd_sev_snp = null
core_count = 1
threads_per_core = 1
}
credit_specification {
cpu_credits = "standard"
}
enclave_options {
enabled = false
}
maintenance_options {
auto_recovery = "default"
}
metadata_options {
http_endpoint = "enabled"
http_protocol_ipv6 = "disabled"
http_put_response_hop_limit = 1
http_tokens = "optional"
instance_metadata_tags = "disabled"
}
private_dns_name_options {
enable_resource_name_dns_a_record = false
enable_resource_name_dns_aaaa_record = false
hostname_type = "ip-name"
}
root_block_device {
delete_on_termination = true
device_name = "/dev/sda1"
encrypted = true
iops = 100
kms_key_id = "arn:aws:kms:ap-northeast-2:53****617837:key/50a438fb-1baf-4094-b63e-706141a94d9a"
tags = {}
tags_all = {}
throughput = 0
volume_id = "vol-0ccc4d8689ff8f341"
volume_size = 8
volume_type = "gp2"
}
}
웹서버 접속 시도
❯ PIP=54.180.132.65
❯ while true; do curl --connect-timeout 1 http://$PIP:8080/ ; echo "------------------------------"; date; sleep 1; done
curl: (28) Failed to connect to 54.180.132.65 port 8080 after 1005 ms: Timeout was reached
------------------------------
Sat Jun 15 17:56:12 KST 2024
curl: (28) Failed to connect to 54.180.132.65 port 8080 after 1004 ms: Timeout was reached
------------------------------
Sat Jun 15 17:56:14 KST 2024
Security Group 정책 추가 후 코드 재접속 테스트
❯ cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "$UBUNTUID"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
user_data = <<-EOF
#!/bin/bash
echo "Hello, T101 Study" > index.html
nohup busybox httpd -f -p 8080 &
EOF
tags = {
Name = "Single-WebSrv"
}
}
resource "aws_security_group" "instance" {
name = var.security_group_name
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
variable "security_group_name" {
description = "The name of the security group"
type = string
default = "terraform-example-instance"
}
output "public_ip" {
value = aws_instance.example.public_ip
description = "The public IP of the Instance"
}
EOT
❯ tfb
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.54.1
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Success! The configuration is valid.
aws_instance.example: Refreshing state... [id=i-0e849c8e935e39294]
Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
+ create
~ update in-place
Terraform will perform the following actions:
# aws_instance.example will be updated in-place
~ resource "aws_instance" "example" {
id = "i-0e849c8e935e39294"
~ tags = {
~ "Name" = "terraform-Study-101" -> "Single-WebSrv"
}
~ tags_all = {
~ "Name" = "terraform-Study-101" -> "Single-WebSrv"
}
~ vpc_security_group_ids = [
- "sg-0e6cdaaea796e7bbd",
] -> (known after apply)
# (38 unchanged attributes hidden)
# (8 unchanged blocks hidden)
}
# aws_security_group.instance will be created
+ resource "aws_security_group" "instance" {
+ arn = (known after apply)
+ description = "Managed by Terraform"
+ egress = (known after apply)
+ id = (known after apply)
+ ingress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ from_port = 8080
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 8080
# (1 unchanged attribute hidden)
},
]
+ name = "terraform-example-instance"
+ name_prefix = (known after apply)
+ owner_id = (known after apply)
+ revoke_rules_on_delete = false
+ tags_all = (known after apply)
+ vpc_id = (known after apply)
}
Plan: 1 to add, 1 to change, 0 to destroy.
Changes to Outputs:
+ public_ip = "54.180.132.65"
────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
aws_security_group.instance: Creating...
aws_security_group.instance: Creation complete after 2s [id=sg-050429d8493a5e0fa]
aws_instance.example: Modifying... [id=i-0e849c8e935e39294]
aws_instance.example: Modifications complete after 2s [id=i-0e849c8e935e39294]
Apply complete! Resources: 1 added, 1 changed, 0 destroyed.
Outputs:
public_ip = "54.180.132.65"
❯ PIP=$(terraform output -raw public_ip)
❯ while true; do curl --connect-timeout 1 http://$PIP:8080/ ; echo "------------------------------"; date; sleep 1; done
Hello, T101 Study
------------------------------
Sat Jun 15 18:06:59 KST 2024
Hello, T101 Study
------------------------------
Sat Jun 15 18:07:00 KST 2024
Terraform graph
❯ terraform graph
digraph G {
rankdir = "RL";
node [shape = rect, fontname = "sans-serif"];
"aws_instance.example" [label="aws_instance.example"];
"aws_security_group.instance" [label="aws_security_group.instance"];
"aws_instance.example" -> "aws_security_group.instance";
}
❯ terraform graph > graph.dot
graph 확인 > 파일 선택 후 오른쪽 상단 DOT 클릭
EC2 User Data 수정 시 Relacement 가 되버림
cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "$UBUNTUID"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
user_data = <<-EOF
#!/bin/bash
echo "Hello, T101 Study 9090" > index.html
nohup busybox httpd -f -p 9090 &
EOF
user_data_replace_on_change = true
tags = {
Name = "Single-WebSrv"
}
}
resource "aws_security_group" "instance" {
name = var.security_group_name
ingress {
from_port = 9090
to_port = 9090
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
variable "security_group_name" {
description = "The name of the security group"
type = string
default = "terraform-example-instance"
}
output "public_ip" {
value = aws_instance.example.public_ip
description = "The public IP of the Instance"
}
EOT
❯ tfb
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.54.1
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Success! The configuration is valid.
aws_security_group.instance: Refreshing state... [id=sg-050429d8493a5e0fa]
aws_instance.example: Refreshing state... [id=i-0e849c8e935e39294]
Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
~ update in-place
-/+ destroy and then create replacement
Terraform will perform the following actions:
# aws_instance.example must be replaced
-/+ resource "aws_instance" "example" {
~ arn = "arn:aws:ec2:ap-northeast-2:53****617837:instance/i-0e849c8e935e39294" -> (known after apply)
~ associate_public_ip_address = true -> (known after apply)
~ availability_zone = "ap-northeast-2a" -> (known after apply)
~ cpu_core_count = 1 -> (known after apply)
~ cpu_threads_per_core = 1 -> (known after apply)
~ disable_api_stop = false -> (known after apply)
~ disable_api_termination = false -> (known after apply)
~ ebs_optimized = false -> (known after apply)
- hibernation = false -> null
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ iam_instance_profile = (known after apply)
~ id = "i-0e849c8e935e39294" -> (known after apply)
~ instance_initiated_shutdown_behavior = "stop" -> (known after apply)
+ instance_lifecycle = (known after apply)
~ instance_state = "running" -> (known after apply)
~ ipv6_address_count = 0 -> (known after apply)
~ ipv6_addresses = [] -> (known after apply)
+ key_name = (known after apply)
~ monitoring = false -> (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
~ placement_partition_number = 0 -> (known after apply)
~ primary_network_interface_id = "eni-0ecb833a51d9976d1" -> (known after apply)
~ private_dns = "ip-172-31-14-154.ap-northeast-2.compute.internal" -> (known after apply)
~ private_ip = "172.31.14.154" -> (known after apply)
~ public_dns = "ec2-54-180-132-65.ap-northeast-2.compute.amazonaws.com" -> (known after apply)
~ public_ip = "54.180.132.65" -> (known after apply)
~ secondary_private_ips = [] -> (known after apply)
~ security_groups = [
- "terraform-example-instance",
] -> (known after apply)
+ spot_instance_request_id = (known after apply)
~ subnet_id = "subnet-0dc5a51c96d2db619" -> (known after apply)
tags = {
"Name" = "Single-WebSrv"
}
~ tenancy = "default" -> (known after apply)
~ user_data = "d91ca31904077f0b641b5dd5a783401396ffbf3f" -> "efd980ae7b7a847bd5772af117f6a53cc7824934" # forces replacement
+ user_data_base64 = (known after apply)
~ user_data_replace_on_change = false -> true
# (6 unchanged attributes hidden)
- capacity_reservation_specification {
- capacity_reservation_preference = "open" -> null
}
- cpu_options {
- core_count = 1 -> null
- threads_per_core = 1 -> null
# (1 unchanged attribute hidden)
}
- credit_specification {
- cpu_credits = "standard" -> null
}
- enclave_options {
- enabled = false -> null
}
- maintenance_options {
- auto_recovery = "default" -> null
}
- metadata_options {
- http_endpoint = "enabled" -> null
- http_protocol_ipv6 = "disabled" -> null
- http_put_response_hop_limit = 1 -> null
- http_tokens = "optional" -> null
- instance_metadata_tags = "disabled" -> null
}
- private_dns_name_options {
- enable_resource_name_dns_a_record = false -> null
- enable_resource_name_dns_aaaa_record = false -> null
- hostname_type = "ip-name" -> null
}
- root_block_device {
- delete_on_termination = true -> null
- device_name = "/dev/sda1" -> null
- encrypted = true -> null
- iops = 100 -> null
- kms_key_id = "arn:aws:kms:ap-northeast-2:53****617837:key/50a438fb-1baf-4094-b63e-706141a94d9a" -> null
- tags = {} -> null
- tags_all = {} -> null
- throughput = 0 -> null
- volume_id = "vol-0ccc4d8689ff8f341" -> null
- volume_size = 8 -> null
- volume_type = "gp2" -> null
}
}
# aws_security_group.instance will be updated in-place
~ resource "aws_security_group" "instance" {
id = "sg-050429d8493a5e0fa"
~ ingress = [
- {
- cidr_blocks = [
- "0.0.0.0/0",
]
- from_port = 8080
- ipv6_cidr_blocks = []
- prefix_list_ids = []
- protocol = "tcp"
- security_groups = []
- self = false
- to_port = 8080
# (1 unchanged attribute hidden)
},
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ from_port = 9090
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 9090
# (1 unchanged attribute hidden)
},
]
name = "terraform-example-instance"
tags = {}
# (8 unchanged attributes hidden)
}
Plan: 1 to add, 1 to change, 1 to destroy.
Changes to Outputs:
~ public_ip = "54.180.132.65" -> (known after apply)
────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
aws_instance.example: Destroying... [id=i-0e849c8e935e39294]
aws_instance.example: Still destroying... [id=i-0e849c8e935e39294, 10s elapsed]
aws_instance.example: Still destroying... [id=i-0e849c8e935e39294, 20s elapsed]
aws_instance.example: Still destroying... [id=i-0e849c8e935e39294, 30s elapsed]
aws_instance.example: Still destroying... [id=i-0e849c8e935e39294, 40s elapsed]
aws_instance.example: Destruction complete after 40s
aws_security_group.instance: Modifying... [id=sg-050429d8493a5e0fa]
aws_security_group.instance: Modifications complete after 1s [id=sg-050429d8493a5e0fa]
aws_instance.example: Creating...
aws_instance.example: Still creating... [10s elapsed]
aws_instance.example: Still creating... [20s elapsed]
aws_instance.example: Still creating... [30s elapsed]
aws_instance.example: Creation complete after 32s [id=i-07df1a72af3369d83]
Apply complete! Resources: 1 added, 1 changed, 1 destroyed.
Outputs:
public_ip = "52.79.199.167"
❯ PIP=$(terraform output -raw public_ip)
❯ while true; do curl --connect-timeout 1 http://$PIP:9090/ ; echo "------------------------------"; date; sleep 1; done
Hello, T101 Study 9090
------------------------------
Sat Jun 15 18:24:25 KST 2024
Hello, T101 Study 9090
------------------------------
Sat Jun 15 18:24:26 KST 2024
Hello, T101 Study 9090
------------------------------
Sat Jun 15 18:24:27 KST 2024
User Data, SG 정책 삭제후 생성되지 않도록 하는 방법
aws_instance Block에 user_data_replace_on_change = false 코드 추가 << EC2 교체 대신 재기동
~ update in-place 로 동작 (change, Modifying)
EC2 재기동 됨(Instance ID유지), User Data 정상 변경 됨,
EIP 미사용 시 Public IP는 교체 됨, Terrafrom 재실행 필요함
Production 환경에서 필요한 옵션
user_data_replace_on_change = true 생략 시 기본 옵션 값
aws_instance Block에 user_data_replace_on_change = true 코드 추가 << EC2 교체
aws_security_group에 lifecycle { create_before_destroy = true } 코드 추가
❯ cat main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "ami-0bcdae8006538619a"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
user_data = <<-EOF
#!/bin/bash
echo "Hello, T101 Study 8080" > index.html
nohup busybox httpd -f -p 8080 &
EOF
user_data_replace_on_change = false
tags = {
Name = "Single-WebSrv"
}
}
resource "aws_security_group" "instance" {
name = var.security_group_name
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
lifecycle {
create_before_destroy = true
}
}
variable "security_group_name" {
description = "The name of the security group"
type = string
default = "terraform-example-instance"
}
output "public_ip" {
value = aws_instance.example.public_ip
description = "The public IP of the Instance"
}
❯ terraform plan
aws_security_group.instance: Refreshing state... [id=sg-050429d8493a5e0fa]
aws_instance.example: Refreshing state... [id=i-07df1a72af3369d83]
Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_instance.example will be updated in-place
~ resource "aws_instance" "example" {
id = "i-07df1a72af3369d83"
tags = {
"Name" = "Single-WebSrv"
}
~ user_data = "efd980ae7b7a847bd5772af117f6a53cc7824934" -> "06b7595b2ec1e6bfaf498a14a900d01bbebab3a3"
# (39 unchanged attributes hidden)
# (8 unchanged blocks hidden)
}
# aws_security_group.instance will be updated in-place
~ resource "aws_security_group" "instance" {
id = "sg-050429d8493a5e0fa"
~ ingress = [
- {
- cidr_blocks = [
- "0.0.0.0/0",
]
- from_port = 9090
- ipv6_cidr_blocks = []
- prefix_list_ids = []
- protocol = "tcp"
- security_groups = []
- self = false
- to_port = 9090
# (1 unchanged attribute hidden)
},
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ from_port = 8080
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 8080
# (1 unchanged attribute hidden)
},
]
name = "terraform-example-instance"
tags = {}
# (8 unchanged attributes hidden)
}
Plan: 0 to add, 2 to change, 0 to destroy.
────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to
take exactly these actions if you run "terraform apply" now.
❯ terraform apply -auto-approve
aws_security_group.instance: Refreshing state... [id=sg-050429d8493a5e0fa]
aws_instance.example: Refreshing state... [id=i-07df1a72af3369d83]
Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_instance.example will be updated in-place
~ resource "aws_instance" "example" {
id = "i-07df1a72af3369d83"
tags = {
"Name" = "Single-WebSrv"
}
~ user_data = "efd980ae7b7a847bd5772af117f6a53cc7824934" -> "06b7595b2ec1e6bfaf498a14a900d01bbebab3a3"
# (39 unchanged attributes hidden)
# (8 unchanged blocks hidden)
}
# aws_security_group.instance will be updated in-place
~ resource "aws_security_group" "instance" {
id = "sg-050429d8493a5e0fa"
~ ingress = [
- {
- cidr_blocks = [
- "0.0.0.0/0",
]
- from_port = 9090
- ipv6_cidr_blocks = []
- prefix_list_ids = []
- protocol = "tcp"
- security_groups = []
- self = false
- to_port = 9090
# (1 unchanged attribute hidden)
},
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ from_port = 8080
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 8080
# (1 unchanged attribute hidden)
},
]
name = "terraform-example-instance"
tags = {}
# (8 unchanged attributes hidden)
}
Plan: 0 to add, 2 to change, 0 to destroy.
aws_security_group.instance: Modifying... [id=sg-050429d8493a5e0fa]
aws_security_group.instance: Modifications complete after 1s [id=sg-050429d8493a5e0fa]
aws_instance.example: Modifying... [id=i-07df1a72af3369d83]
aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 10s elapsed]
aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 20s elapsed]
aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 30s elapsed]
aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 40s elapsed]
aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 50s elapsed]
aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 1m0s elapsed]
aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 1m10s elapsed]
aws_instance.example: Modifications complete after 1m11s [id=i-07df1a72af3369d83]
Apply complete! Resources: 0 added, 2 changed, 0 destroyed.
Outputs:
public_ip = "52.79.199.167"
❯
❯ terraform apply -auto-approve
aws_security_group.instance: Refreshing state... [id=sg-050429d8493a5e0fa]
aws_instance.example: Refreshing state... [id=i-07df1a72af3369d83]
Changes to Outputs:
~ public_ip = "52.79.199.167" -> "43.201.57.99"
You can apply this plan to save these new output values to the Terraform state, without
changing any real infrastructure.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
public_ip = "43.201.57.99"
user-data.web
#!/bin/bash
apt-get update -y
apt install apache2 -y
sudo systemctl start apache2.service
echo '<html><h1>SJKIM - Web Server!</h1></html>' > /var/www/html/index.html
main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "web" {
ami = "ami-0bcdae8006538619a"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
user_data = file("user-data.web")
user_data_replace_on_change = true
tags = {
Name = "ec2-study-apache-web"
}
}
resource "aws_security_group" "instance" {
name = var.security_group_name
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
}
variable "security_group_name" {
description = "The name of the security group"
type = string
default = "SG-study-web"
}
output "public_ip" {
value = aws_instance.web.public_ip
description = "The public IP of the Instance"
}
코드 실행
❯ tfb
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.54.1
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Success! The configuration is valid.
Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ ami = "ami-0bcdae8006538619a"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_stop = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ host_resource_group_arn = (known after apply)
+ iam_instance_profile = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_lifecycle = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t2.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ monitoring = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ spot_instance_request_id = (known after apply)
+ subnet_id = (known after apply)
+ tags = {
+ "Name" = "ec2-study-apache-web"
}
+ tags_all = {
+ "Name" = "ec2-study-apache-web"
}
+ tenancy = (known after apply)
+ user_data = "b7331c1e682f52f3e90cc165fe240f8c8633be6a"
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = true
+ vpc_security_group_ids = (known after apply)
}
# aws_security_group.instance will be created
+ resource "aws_security_group" "instance" {
+ arn = (known after apply)
+ description = "Managed by Terraform"
+ egress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "-1"
+ security_groups = []
+ self = false
+ to_port = 0
# (1 unchanged attribute hidden)
},
]
+ id = (known after apply)
+ ingress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ from_port = 80
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 80
# (1 unchanged attribute hidden)
},
]
+ name = "SG-study-web"
+ name_prefix = (known after apply)
+ owner_id = (known after apply)
+ revoke_rules_on_delete = false
+ tags_all = (known after apply)
+ vpc_id = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ public_ip = (known after apply)
────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
aws_security_group.instance: Creating...
aws_security_group.instance: Creation complete after 3s [id=sg-023e1eeaec43b1129]
aws_instance.web: Creating...
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Still creating... [20s elapsed]
aws_instance.web: Still creating... [30s elapsed]
aws_instance.web: Still creating... [40s elapsed]
aws_instance.web: Creation complete after 41s [id=i-0dec33d676696103a]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
public_ip = "13.125.197.36"
테스트
❯ PIP=$(terraform output -raw public_ip)
❯ curl http://$PIP
<html><h1>SJKIM - Web Server!</h1></html>
get-bucket-name.sh
#!/bin/bash
# Exit if any of the intermediate steps fail
set -e
#t1=`hostname | cut -f1 -d'.'`
t1=iac-dev
t2=`date +%s%N`
BUCKET_NAME=`printf "s3-%s-tfstate-%s" $t1 $t2 | awk '{print tolower($0)}'`
jq -n --arg bn "$BUCKET_NAME" '{"Name":$bn}'
s3-bucket.tf
data "external" "bucket_name" {
program = ["bash", "get-bucket-name.sh"]
}
output "Name" {
value = data.external.bucket_name.result.Name
# value = "s3-brain-dev-tfstate-1709366921n"
}
resource "aws_s3_bucket" "terraform_state" {
bucket = data.external.bucket_name.result.Name
# This is only here so we can destroy the bucket as part of automated tests.
# You should not copy this for production usage
force_destroy = true
lifecycle {
ignore_changes = [bucket]
}
}
resource "aws_s3_bucket_versioning" "terraform_state_versioning" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state_sse" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
resource "aws_s3_bucket_public_access_block" "terraform_state_versioning_pab" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
코드 실행
❯ tfb
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/tls from the dependency lock file
...
data.external.bucket_name: Reading...
data.external.bucket_name: Read complete after 0s [id=-]
Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_s3_bucket.terraform_state will be created
+ resource "aws_s3_bucket" "terraform_state" {
+ acceleration_status = (known after apply)
+ acl = (known after apply)
+ arn = (known after apply)
+ bucket = "s3-iac-dev-tfstate-1718491103n"
+ bucket_domain_name = (known after apply)
+ bucket_prefix = (known after apply)
+ bucket_regional_domain_name = (known after apply)
+ force_destroy = true
+ hosted_zone_id = (known after apply)
+ id = (known after apply)
+ object_lock_enabled = (known after apply)
+ policy = (known after apply)
+ region = (known after apply)
+ request_payer = (known after apply)
+ tags_all = (known after apply)
+ website_domain = (known after apply)
+ website_endpoint = (known after apply)
}
# aws_s3_bucket_public_access_block.terraform_state_versioning_pab will be created
+ resource "aws_s3_bucket_public_access_block" "terraform_state_versioning_pab" {
+ block_public_acls = true
+ block_public_policy = true
+ bucket = (known after apply)
+ id = (known after apply)
+ ignore_public_acls = true
+ restrict_public_buckets = true
}
# aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse will be created
+ resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state_sse" {
+ bucket = (known after apply)
+ id = (known after apply)
+ rule {
+ apply_server_side_encryption_by_default {
+ sse_algorithm = "AES256"
# (1 unchanged attribute hidden)
}
}
}
# aws_s3_bucket_versioning.terraform_state_versioning will be created
+ resource "aws_s3_bucket_versioning" "terraform_state_versioning" {
+ bucket = (known after apply)
+ id = (known after apply)
+ versioning_configuration {
+ mfa_delete = (known after apply)
+ status = "Enabled"
}
}
Plan: 9 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ Name = "s3-iac-dev-tfstate-1718491103n"
+ public_ip = (known after apply)
────────────────────────────────────────────────────────────────────────────────────────
Saved the plan to: tfplan
To perform exactly these actions, run the following command to apply:
terraform apply "tfplan"
tls_private_key.ec2_key: Creating...
aws_security_group.instance: Creating...
aws_s3_bucket.terraform_state: Creating...
tls_private_key.ec2_key: Creation complete after 2s [id=778f75a1910a0fb0a9afbc2266cbcc343be0ba3c]
aws_key_pair.ec2_keypair: Creating...
local_file.ec2_key_local: Creating...
local_file.ec2_key_local: Creation complete after 0s [id=612dde1bc937f145b5a23999cb7bcf3bd1c68814]
aws_s3_bucket.terraform_state: Creation complete after 2s [id=s3-iac-dev-tfstate-1718491103n]
aws_s3_bucket_public_access_block.terraform_state_versioning_pab: Creating...
aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse: Creating...
aws_s3_bucket_versioning.terraform_state_versioning: Creating...
aws_key_pair.ec2_keypair: Creation complete after 0s [id=mykey]
aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse: Creation complete after 0s [id=s3-iac-dev-tfstate-1718491103n]
aws_security_group.instance: Creation complete after 3s [id=sg-01a4ed3b885e12279]
aws_instance.web: Creating...
aws_s3_bucket_public_access_block.terraform_state_versioning_pab: Creation complete after 1s [id=s3-iac-dev-tfstate-1718491103n]
aws_s3_bucket_versioning.terraform_state_versioning: Creation complete after 1s [id=s3-iac-dev-tfstate-1718491103n]
aws_instance.web: Still creating... [10s elapsed]
aws_instance.web: Still creating... [20s elapsed]
aws_instance.web: Still creating... [30s elapsed]
aws_instance.web: Creation complete after 31s [id=i-06c92b3a1ce57e2d6]
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Outputs:
Name = "s3-iac-dev-tfstate-1718491103n"
❯ terraform state list
data.external.bucket_name
aws_s3_bucket.terraform_state
aws_s3_bucket_public_access_block.terraform_state_versioning_pab
aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse
aws_s3_bucket_versioning.terraform_state_versioning
버킷 확인
❯ aws s3 ls | grep tfstate
2024-06-16 07:38:28 s3-iac-dev-tfstate-1718491103n
S3 버킷 정보
❯ terraform graph > graph.dot
dynamodb-tables.tf
resource "aws_dynamodb_table" "terraform_locks" {
depends_on = [aws_s3_bucket.terraform_state]
name = "ddb-iac-dev-terraform_challenge-locks"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
코드 실행 및 결과
❯ tfb
Initializing the backend...
..
Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_dynamodb_table.terraform_locks will be created
+ resource "aws_dynamodb_table" "terraform_locks" {
+ arn = (known after apply)
+ billing_mode = "PAY_PER_REQUEST"
+ hash_key = "LockID"
+ id = (known after apply)
+ name = "ddb-iac-dev-terraform_challenge-locks"
+ read_capacity = (known after apply)
+ stream_arn = (known after apply)
+ stream_label = (known after apply)
+ stream_view_type = (known after apply)
+ tags_all = (known after apply)
+ write_capacity = (known after apply)
+ attribute {
+ name = "LockID"
+ type = "S"
}
}
Plan: 1 to add, 0 to change, 0 to destroy.
...
❯ terraform state list | grep dynamodb
aws_dynamodb_table.terraform_locks
main.tf
terraform {
required_version = "~> 1.8"
required_providers {
aws = {
source = "hashicorp/aws"
# Lock version to avoid unexpected problems
version = "~> 5.39"
}
null = {
source = "hashicorp/null"
version = "~> 3.2.2"
}
}
backend "s3" {
bucket = "s3-iac-dev-tfstate-1718491103n"
key = "terraform/ddb-iac-dev-terraform_challenge_challenge.tfstate"
region = "ap-northeast-2"
dynamodb_table = "ddb-iac-dev-terraform_challenge-locks"
encrypt = "true"
}
}
provider "aws" {
region = "ap-northeast-2"
shared_credentials_files = ["~/.aws/credentials"]
profile = "default"
}
코드 실행결과 - tfstate과 s3로 이관 됨
❯ terraform init
Initializing the backend...
Do you want to copy existing state to the new backend?
Pre-existing state was found while migrating the previous "local" backend to the
newly configured "s3" backend. No existing state was found in the newly
configured "s3" backend. Do you want to copy this state to the new "s3"
backend? Enter "yes" to copy and "no" to start with an empty state.
Enter a value: yes
Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Finding hashicorp/null versions matching "~> 3.2.2"...
- Reusing previous version of hashicorp/tls from the dependency lock file
- Reusing previous version of hashicorp/local from the dependency lock file
- Reusing previous version of hashicorp/external from the dependency lock file
- Reusing previous version of hashicorp/aws from the dependency lock file
- Installing hashicorp/null v3.2.2...
- Installed hashicorp/null v3.2.2 (signed by HashiCorp)
- Using previously-installed hashicorp/tls v4.0.5
- Using previously-installed hashicorp/local v2.5.1
- Using previously-installed hashicorp/external v2.3.3
- Using previously-installed hashicorp/aws v5.54.1
Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
❯ terraform plan
data.external.bucket_name: Reading...
tls_private_key.ec2_key: Refreshing state... [id=778f75a1910a0fb0a9afbc2266cbcc343be0ba3c]
local_file.ec2_key_local: Refreshing state... [id=612dde1bc937f145b5a23999cb7bcf3bd1c68814]
data.external.bucket_name: Read complete after 0s [id=-]
aws_key_pair.ec2_keypair: Refreshing state... [id=mykey]
aws_security_group.instance: Refreshing state... [id=sg-01a4ed3b885e12279]
aws_s3_bucket.terraform_state: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
aws_instance.web: Refreshing state... [id=i-06c92b3a1ce57e2d6]
aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
aws_s3_bucket_public_access_block.terraform_state_versioning_pab: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
aws_s3_bucket_versioning.terraform_state_versioning: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
aws_dynamodb_table.terraform_locks: Refreshing state... [id=ddb-iac-dev-terraform_challenge-locks]
Changes to Outputs:
~ Name = "s3-iac-dev-tfstate-1718494211n" -> "s3-iac-dev-tfstate-1718495056n"
You can apply this plan to save these new output values to the Terraform state,
without changing any real infrastructure.
──────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
❯ terraform apply -auto-approve
data.external.bucket_name: Reading...
tls_private_key.ec2_key: Refreshing state... [id=778f75a1910a0fb0a9afbc2266cbcc343be0ba3c]
local_file.ec2_key_local: Refreshing state... [id=612dde1bc937f145b5a23999cb7bcf3bd1c68814]
data.external.bucket_name: Read complete after 0s [id=-]
aws_key_pair.ec2_keypair: Refreshing state... [id=mykey]
aws_security_group.instance: Refreshing state... [id=sg-01a4ed3b885e12279]
aws_s3_bucket.terraform_state: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
aws_instance.web: Refreshing state... [id=i-06c92b3a1ce57e2d6]
aws_s3_bucket_versioning.terraform_state_versioning: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
aws_s3_bucket_public_access_block.terraform_state_versioning_pab: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
aws_dynamodb_table.terraform_locks: Refreshing state... [id=ddb-iac-dev-terraform_challenge-locks]
Changes to Outputs:
~ Name = "s3-iac-dev-tfstate-1718494211n" -> "s3-iac-dev-tfstate-1718495080n"
You can apply this plan to save these new output values to the Terraform state,
without changing any real infrastructure.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
Name = "s3-iac-dev-tfstate-1718495080n"
public_ip = "43.203.239.25"
keypair.tf
resource "tls_private_key" "ec2_key" {
algorithm = "RSA"
rsa_bits = 4096
}
resource "aws_key_pair" "ec2_keypair" {
key_name = var.key_name
public_key = tls_private_key.ec2_key.public_key_openssh
}
resource "local_file" "ec2_key_local" {
filename = "${var.key_name}.pem"
content = tls_private_key.ec2_key.private_key_pem
file_permission = "0400"
}
variable "key_name" {
description = "The name of the keypair"
type = string
default = "mykey"
}
main.tf의 key_name과 SG Inbound 정책 수정
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "web" {
ami = "ami-0bcdae8006538619a"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
user_data = file("user-data.web")
key_name = var.key_name ## 추가한 내용
user_data_replace_on_change = true
tags = {
Name = "ec2-study-apache-web"
}
}
resource "aws_security_group" "instance" {
name = var.security_group_name
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# MyPC에서만 원격 접속하도록 정책 추가
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.10.10.10/32"]
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
}
variable "security_group_name" {
description = "The name of the security group"
type = string
default = "SG-study-web"
}
output "public_ip" {
value = aws_instance.web.public_ip
description = "The public IP of the Instance"
}
코드 실행 후 mykey.pem 생성 확인
❯ ls -al mykey.pem
-r-------- 1 sjkim staff 3243 Jun 16 02:35 mykey.pem
서버 원격접속
❯ ssh -i "mykey.pem" ubuntu@ec2-13-125-235-67.ap-northeast-2.compute.amazonaws.com
The authenticity of host 'ec2-13-125-235-67.ap-northeast-2.compute.amazonaws.com (13.125.235.67)' can't be established.
ED25519 key fingerprint is SHA256:R1/tcitDGuGyhbsI6JgUgykK6nWejK+Vl5JhsA0G3S0.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'ec2-13-125-235-67.ap-northeast-2.compute.amazonaws.com' (ED25519) to the list of known hosts.
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 6.5.0-1020-aws x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
System information as of Sat Jun 15 17:36:53 UTC 2024
System load: 0.0 Processes: 102
Usage of /: 23.6% of 7.57GB Users logged in: 0
Memory usage: 23% IPv4 address for eth0: 172.31.1.13
Swap usage: 0%
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
ubuntu@ip-172-31-1-13:~$ cat /var/www/html/index.html
<html><h1>SJKIM - Web Server!</h1></html>