본문 바로가기
Terraform/Study

Terraform-101-Study Week3 - [조건문, 함수, 프로비저너, terraform_data, moved, 프로바이더]

by 식사법 2023. 7. 22.

Github를 통해 코드 관리

https://github.com/chadness12/Terraform-101-Study/tree/main/week3

1. 도전과제

1) 조건문을 활용하여 AWS 리소스를 배포하는 코드를 작성해보자

  • 지정된 이름으로만 인스턴스를 생상가능하게 하는 AWS 리로스 배포 코드 입니다

(1) variable.tf

# 이름 체크를 위한 변수
variable "check_name" {
  type        = list(string)
  description = "which name we need to allow"
  default     = ["chad", "megan", "jason"]
}

# 이름을 입력하기 위한 변수 
variable "input_name" {
  type        = list(string)
  description = "which name we input"
  default     = ["chad", "test", "jason"]
}

(2) main.tf - 조건문 사용 확인

# amazon linux2 ami이미지
data "aws_ami" "myamzonlinux2" {
  most_recent = true
  filter {
    name   = "owner-alias"
    values = ["amazon"]
  }

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

  owners = ["amazon"]
}

resource "aws_instance" "Challenge1" {
  instance_type = "t2.micro"
  count         = length(var.input_name)

    # (a) : check_name 변수 안에 input_name의 값이 존재하지 않으면 ec2를 생성하지 않음
  ami           = contains(var.check_name, var.input_name[count.index]) ? data.aws_ami.myamzonlinux2.id : "error"

  tags = { Name = var.input_name[count.index] }

    # (b) : check_name의 길이와 input_name의 길이가 같지 않으면 오류 생성
  lifecycle {
    precondition {
      condition     = length(var.input_name) == length(var.check_name)
      error_message = "The number of your inputs is not the same as the number of check_name"
    }
  }
}
  • (a) : check_name 변수 안에 input_name의 값이 존재하지 않으면 ec2를 생성하지 않음
  • (b) : check_name의 길이와 input_name의 길이가 같지 않으면 오류 생성

 

(3) Termianl & Console 확인

 

a. Termianl 확인

  • input_name 의 2번 째 인덱스인 test가 check_name 에 없기 때문에 에러가 뜨는것을 확인할 수 있음

b. Console 확인

  • chad, jason만 생성된 것을 확인 가능

 

 

2) 내장 함수를 활용하여 리소스를 배포

  • 특정한 네이밍 규칙에 따라 이름을 지정해야지만 subnet이 생성가능한 AWS 리소스 배포 코드입니다.

(1) variable.tf - regex , can 내장함수 사용 확인

variable "subnet_name" {
  validation {
        # (a) can - error가 출력되는 상황이라면 bool값을 false로 반환해주는 함수
        # (b) regex - 입력값이 정규표현식과 매칭되는지 확인해주는 함수
    condition = can(regex("^terraform-(public|private)-(az_a|az_b|az_c|az_d)-subnet", var.subnet_name))
    error_message = "your subnet name is not match with naming-rule"
  }
}
  • (a) can - error가 출력되는 상황이라면 bool값을 false로 반환해주는 함수
  • (b) regex - 입력값이 정규표현식과 매칭되는지 확인해주는 함수
    • 네이밍 규칙을 세우기 위해 함수를 사용하였다
    • 위의 코드에서는 terraform-(publcir, private)-(az_a, az_b, az_c, az_d)-subnet 으로 Subnet이름을 지정하지않는다면 에러를 출력한다.

(2) main.tf

#vpc 선언
resource "aws_vpc" "Challange2_vpc" {
  cidr_block = "192.168.0.0/16"

  tags = {
    Name = "Challange2"
  }
}

#data 소스를 활용하여 리전내 가용영역을 가져옴
data "aws_availability_zones" "available" {
  state = "available"
}

#availability_zones_a 에 subnet 영역 생성
resource "aws_subnet" "Challange2_subnet_a" {
  vpc_id            = aws_vpc.Challange2_vpc.id
  cidr_block        = "192.168.0.0/24"
  availability_zone = data.aws_availability_zones.available.names[0]
  tags = {
    Name = var.subnet_name
  }
}

(3) Terminal 확인

 

a. 정규식에 맞게 변수를 설정하였을때

  • 정상적으로 배포가 되는 것을 확인할 수 있음

b. 정규식에 맞지 않게 변수를 설정하였을때

  • 설정한 에러 메세지가 출력되는 것을 확인할 수 있음

 

 

3) AWS_EC2를 remote-exec/file 프로비저너를 활용하여 배포해보기

  • 프로비저너를 활용하여 웹서버를 배포하는 코드입니다.

(1) main.tf - file, remoete-exec 사용 확인

data "aws_ami" "myamzonlinux2" {
  most_recent = true
  filter {
    name   = "owner-alias"
    values = ["amazon"]
  }

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

  owners = ["amazon"]
}

resource "aws_instance" "Challenge3" {

  instance_type = "t2.micro"
  ami           = data.aws_ami.myamzonlinux2.id
  key_name      = "enterprise"
  tags          = { Name = "Challenge3" }

    # (a) ec2와 연결할 방식에 대한 설명
    connection {
    type        = "ssh"
    user        = "ec2-user"
    private_key = file("./enterprise.pem")
    host        = self.public_ip
  }

    # (b) file 프로비저너를 사용하여 쉘스크립트를 전송함
  provisioner "file" {
    source      = "html.sh"
    destination = "/home/ec2-user/html.sh"
  }

    # (c) remote-exec 프로비저너를 사용하여 해당 쉘스크립트를 실행함
  provisioner "remote-exec" {
    inline = ["bash /home/ec2-user/html.sh"]

  }
}
  • (a) ec2와 연결할 방식에 대한 설명
  • (b) file 프로비저너를 사용하여 쉘스크립트를 전송함
  • (c) remote-exec 프로비저너를 사용하여 해당 쉘스크립트를 실행함

(2) html.sh

  • 웹서버를 실행시키는 코드
#!/bin/bash
sudo yum update -y 

sudo yum install httpd -y

sudo systemctl start httpd

sudo systemctl enable httpd

sleep 10

sudo tee /var/www/html/index.html <<EOF
<h1>remote-exe </h1>
EOF

sudo systemctl restart httpd

(3) Console , 웹페이지 확인

 

a. Console 및 서버 내부 html.sh 파일 존재 여부 확인

 

b. 웹페이지 확인

 

 

4) terraform_data 리소스와 trigger_replace 를 사용한 배포

  • ec2에 대해서 ssh 포트를 지속해서 변경하여 배포하는 코드입니다. variable.tf 만 수정한다면 자동으로 ec2 내부 ssh 포트 변경, inbound 포트를 변경합니다.

(1) variable.tf

  • index - 1번 값만 바꿔준다면 ssh 포트 변경이 가능합니다
  • list를 사용한 이유는 terraform_data에서 remote 프로비저너를 사용시 이전 포트 정보가 존재해야하기 때문입니다.
variable ssh_port { default = [22,22] }

(2) vpc.tf

#vpc 선언
resource "aws_vpc" "Challange4_vpc" {
  cidr_block = "192.168.0.0/16"

  tags = {
    Name = "Challange4"
  }
}

data "aws_availability_zones" "available" {
  state = "available"
}

#availability_zones_a 에 subnet 영역 생성
resource "aws_subnet" "Challange4_subnet_a" {
  vpc_id            = aws_vpc.Challange4_vpc.id
  cidr_block        = "192.168.0.0/24"
  availability_zone = data.aws_availability_zones.available.names[0]
  tags = {
    Name = "Challange4_subnet_a"
  }
}

#internet gateway 생성
resource "aws_internet_gateway" "Challange4_igw" {
  vpc_id = aws_vpc.Challange4_vpc.id

  tags = {
    Name = "Challange4_igw"
  }
}

#라우팅 테이블 생성
resource "aws_route_table" "Challange4_rtb" {
  vpc_id = aws_vpc.Challange4_vpc.id

  tags = {
    Name = "Challange4_rtb_Public_a"
  }
}

resource "aws_route" "igw" {
  route_table_id         = aws_route_table.Challange4_rtb.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.Challange4_igw.id
}

#라우팅 테이블 과 subnet_a 와 연결
resource "aws_route_table_association" "asso_with_subnet_a" {
  subnet_id      = aws_subnet.Challange4_subnet_a.id
  route_table_id = aws_route_table.Challange4_rtb.id
}

resource "aws_security_group" "Challange4_sg" {
  vpc_id      = aws_vpc.Challange4_vpc.id
  name        = "Challange4_sg"
}

# (a) var.ssh_port[1]을 참조하여 인바운드 포트 지정
resource "aws_security_group_rule" "Challange4_inbound" {
  type              = "ingress"
  from_port         = var.ssh_port[1]
  to_port           = var.ssh_port[1]
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.Challange4_sg.id
}

resource "aws_security_group_rule" "Challange4_outbound" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.Challange4_sg.id
}
  • (a) var.ssh_port[1]을 참조하여 인바운드 포트 지정

(3) main.tf - terraform_data 사용 확인

resource "aws_instance" "Challange4_ec2" {
  ami                         = "ami-00d253f3826c44195"
  associate_public_ip_address = true
  instance_type               = "t2.micro"
  subnet_id                   = aws_subnet.Challange4_subnet_a.id
  vpc_security_group_ids      = ["${aws_security_group.Challange4_sg.id}"]
  key_name                    = "enterprise"
  tags = {
    Name = "Challange4_ec2"
  }
  user_data = <<-EOF
  #!/bin/bash
  touch /etc/ssh/sshd_config.d/Port.conf
  EOF

}

# (a) terraform apply 가 실행될 때마다 실행되는 terraform_data
resource "terraform_data" "test" {
    triggers_replace = [
        timestamp()
    ]
    connection {
      type = "ssh"
      port= var.ssh_port[0]
      user = "ec2-user"
      private_key = file("./enterprise.pem")
      host = aws_instance.Challange4_ec2.public_ip
    }

    provisioner "remote-exec" {
      inline = [ 
        "echo 'Port = ${var.ssh_port[1]}' | sudo tee /etc/ssh/sshd_config.d/Port.conf",
        "sudo systemctl restart sshd"
       ]
    }

    provisioner "local-exec" {
        command = "echo 'variable ssh_port { default = [${var.ssh_port[1]}, ${var.ssh_port[1]}] > ./variable.tf }' "

    }

}
  • (a) terraform apply 가 실행될 때마다 실행되는 terraform_data
    • connetion.port = var.ssh_port[0] 을 참조하는 이유 : var.ssh_port[1] 이 변경되어 apply 가 실행된다면 connection이 이루어 질 수 없음
    • provisioner "remote-exec" : ec2내의 ssh 설정을 var.ssh_port[1]을 참조하여 변경
    • provisioner "local-exec" : 변경된 포트 설정을 모두 variable.tf 에 저장

 

(4) Console 확인 및 ssh 접속 테스트

 

a. 22번 포트로 설정 시

  • Console 확인

  • ssh 접속 확인

 

b. 2222번 포트 설정시

  • Console 확인
    • inbound 포트 2222 변경 확인

  • ssh 접속 테스트
    • 2222번 포트로 접속 가능함을 확인할 수 있음

 

 

5) moved 블록을 사용한 테라폼 코드 리팩터링

(1) variable.tf

variable "cidr_list" {
  default = {
    subnet_a = {
      az         = "ap-northeast-2a"
      cidr_block = "192.168.0.0/24"
      name       = "subnet_a"
    }
    subnet_b = {
      az         = "ap-northeast-2b"
      cidr_block = "192.168.1.0/24"
      name       = "subnet_b"
    }
  }
}

(2) main.tf - moved 사용확인

  • 기존에 생성했던 단일 서브넷 리소스를 다중 서브넷 리소스이 포함하게하는 moved 블록 활용입니다.
data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_vpc" "Challange5_vpc" {
  cidr_block = "192.168.0.0/16"

  tags = {
    Name = "Challange5"
  }
}

# #availability_zones_a 에 subnet 영역 생성
# resource "aws_subnet" "Challange5_subnet" {
#   vpc_id            = aws_vpc.Challange5_vpc.id
#   cidr_block        = "192.168.0.0/24"
#   availability_zone = data.aws_availability_zones.available.names[0]
#   tags = {
#     Name = "Challange5_subnet_a"
#   }
# }

resource "aws_subnet" "Challange5_subnet" {
  for_each = var.cidr_list

  vpc_id            = aws_vpc.Challange5_vpc.id
  cidr_block        = each.value.cidr_block
  availability_zone = each.value.az
  tags = {
    Name = "Challange5_${each.value.name}"
  }
}
# (a) 기존에 생성했넌 subent을 aws_subnet.Challange5_subnet["subnet_a"]으로 옮김
moved {
  from = aws_subnet.Challange5_subnet
  to   = aws_subnet.Challange5_subnet["subnet_a"]
}

(3) moved 블록 적용 확인

 

a. 적용전 state

b. 정상적용 확인

  • state 확인 - subnet_a 의 arn이 적용전 subnet의 arn과 같음을 확인 가능

 

6) AWS 2개의 리전에 s3 배포

  • AWS 도쿄, 서울 리전 각각에 s3를 배포하는 코드입니다

(1) provider.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  alias  = "region-1"
  region = "ap-northeast-2"
}

provider "aws" {
  alias  = "region-2"
  region = "ap-northeast-1"
}

(2) main.tf

resource "aws_s3_bucket" "region_1-s3" {
  provider = aws.region-1

  bucket = "leechad-korea-s3"
}

resource "aws_s3_bucket" "region_2-s3" {
  provider = aws.region-2

  bucket = "leechad-japan-s3"
}

(3) Console 확인

 

7. GCP 프로바이더를 활용하여 리소스 배포

  • gcp 프로바이더를 활용하여 vm하나를 띄우는 코드입니다.

(1) provider.tf

provider "google" {
 credentials = "${file("./chadness12-int-230214-0a219cbc7a0e.json")}"
 project     = "chadness12-int-230214"
 region      = "us-central1"
}

(2) main.tf

resource "google_compute_instance" "default" {
  name         = "terraform"
  machine_type = "f1-micro"
  zone         = "us-central1-a"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-11"
    }
  }

  network_interface {
    network = "default"
    access_config {
    }
  }
}

 

(3) Console 확인