Terraform : Web-DB

Gyullbb·2021년 5월 21일
0

Cloud

목록 보기
2/3

Terraform

0. 구성도

1. Install

1-1. 저장소 구성(hashicorp 저장소 사용)
# curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -

# sudo apt-add-repository "deb [arch=$(dpkg --print-architecture)] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
1-2. 버전 확인
# apt policy terraform
1-3. 원하는 버전 설치
# sudo apt install terraform={{version}}

2. Terraform 개념

Terraform 공식 사이트 : provider

- provider

원격 시스템과 상호작용하기 위한 플러그인.

- resource

Terraform에서 새로운 인프라 구성 요소를 생성하거나 관리할 때 사용한다.

- data

기존 AWS의 리소스 데이터를 읽어올 때 사용한다.

- hcl

Terraform에서 리소스 등을 정의할 때 쓰는 언어. .tf 확장자이다.

- plan

Terraform에서는 진짜 적용하기 전 어떻게 변경이 되는지를 보여주는 기능을 제공한다. 실행은 terraform plan명령어로 진행한다.

- apply

Terraform에서 실제로 파일에 적은 값을 적용할 때 사용하는 명령어이다. 실행은 terraform apply명령어로 진행한다.

3. Provider 설정

AWS 리소스를 활용할 것이기 때문에 AWS Provider를 설정한다.

provider "aws" {
  access_key = "<AWS_ACCESS_KEY>"
  secret_key = "<AWS_SECRET_KEY>"
  region = "ap-northeast-2"
}
# terraform init

초기 설정과 함께 설정한 provider가 설치된다.

# terraform version
Terraform v0.15.4
on linux_amd64
+ provider registry.terraform.io/hashicorp/aws v3.41.0

버전을 확인하면 provider정보도 함께 나타남을 확인할 수 있다.

4. EC2 생성 및 접속

4-1. 키 페어 생성

EC2를 생성한 후 실행하기 위해서는 키 페어가 필요하다. Terraform을 이용하여 키 페어를 생성할 수 있다.

우선 ssh-key를 생성한다.

ssh-keygen -t rsa -b 4096 -C "rbfl9611@gmail.com" -f "$HOME/.ssh/ec2_key_pair" -N ""

이 후 AWS에 공개키를 업로드 할 수있도록 tf파일을 작성한다.

resource "aws_key_pair" "ec2_key_pair"{
  key_name = "ec2_key_pair"
  public_key = file("~/.ssh/ec2_key_pair.pub")
}

resource 다음에 오는 첫 번째 문자열은 리소스 타입이다. 이는 프로바이더에서 제공하는 리소스 타입이다.

두 번째 문자열은 해당 리소스 타입의 이름을 지정하는 공간이다.

tf파일을 작성하였으면, AWS상에서 Terraform에 작성한 파일들이 정상 작동할 것인지를 확인해야한다.

# terraform plan
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_key_pair.ec2_key_pair will be created
  + resource "aws_key_pair" "ec2_key_pair" {
      + arn         = (known after apply)
      + fingerprint = (known after apply)
      + id          = (known after apply)
      + key_name    = "ec2_key_pair"
      + key_pair_id = (known after apply)
      + public_key  = ""
      + tags_all    = (known after apply)
    }

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

계획에 한 개의 리소스(키페어)를 생성할 계획이라고 뜬 것을 볼 수 있다.

이제 실제로 AWS상에 키 페어를 올려보자.

# terraform apply

apply를 하면 plan이 실행된 후 진짜로 이 행동을 취할것이냐 묻는다. yes라고 대답하면 리소스가 생성된다.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_key_pair.ec2_key_pair: Creating...
aws_key_pair.ec2_key_pair: Creation complete after 0s [id=ec2_key_pair]

AWS>EC2>키페어에서 확인해보면 실제 키가 생성된 것을 볼 수 있다.

이렇게 생성된 작업들은 작업 디랙토리 내에 terraform.tfstate파일 내에 기록되어있다.

4-2. 보안 그룹 설정

EC2에 접속하기 위해서 보안 그룹을 설정해야 한다. 외부에서 22번 포트로 접근할 수 있도록 설정한다.

아까 ec2_key_pair를 정의한 tf파일 아래에 다음 내용을 덧붙인다.

resource "aws_security_group" "ssh"{
  name = "allow_ssh_from_all"
  description = "Allow SSH port from all"
  ingress{
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

아까와 마찬가지로 terraform plan으로 확인한 후 apply로 적용시킨다.

AWS>EC2>보안그룹에서 설정한 보안 그룹이 생성됨을 확인할 수 있다.

보안 그룹에는 추가한 보안 그룹 외 default 보안 그룹이 있는데, 이 클라우드 상에 정의되어 있는 리소스를 데이터 소스로 불러와야 한다.

앞의 코드와는 다르게 resource가 아닌 data를 사용한다.

data "aws_security_group" "default"{
  name = "default"
}

Terraform resource vs data

Whereas a resource causes Terraform to create and manage a new infrastructure component, data sources present read-only views into pre-existing data, or they compute new values on the fly within Terraform itself.

resource = 새로운 인프라 구성 요소 생성 및 관리

data = 기존 데이터를 읽어오거나 자체의 새로운 값을 읽어옴

4-3. VPC, subnet설정

기본 VPC의 subnet을 사용하기로 하자. 기본 VPC의 값은 data를 통해 받아오고, 해당 VPC에 subnet은 신규로 생성한다. (가정 : VPC의 Name이 default라 지정되어 있음.)

#aws default vpc
data "aws_vpc" "default"{
  tags = {
    Name = "default"
  }
}

#VPC내 기본 subnet 없을 시 subnet 생성
resource"aws_subnet" "seoul1"{
  vpc_id = data.aws_vpc.default.id
  cidr_block = "172.31.0.0/16"
  availability_zone = "ap-northeast-2a"
  map_public_ip_on_launch = true
}

기본 VPC의 cidr_block이 172.31.0.0/16이므로 이에 맞게 서브넷의 cidr_block을 작성해야 한다.

Ubuntu 20.04의 t2.micro는 ap-northeast-2aap-northeast-2c에서만 생성되기 때문에 서브넷을 생성할 때 이를 명시해야 한다.

또한 서브넷 내에서 ec2를 생성했을 때 ec2의 public IP가 자동으로 할당되도록 하려면 map_public_ip_on_launch를 true로 설정해야한다. (default = false)

4-4. IGW, 라우팅 테이블 설정

외부에서 서브넷 내의 EC2에 접근하기 위해서는 IGW가 필요하다. 외부와 연결되는 IGW를 생성해야 한다.

resource "aws_internet_gateway" "default-gw"{
  vpc_id = data.aws_vpc.default.id
  tags = {
    Name = "default-gw"
  }
}

IGW를 생성했다면 외부에서 IGW로 라우팅해주는 라우팅 테이블을 생성해야한다.

#IGW, 외부 연결 라우팅 테이블 생성
resource "aws_route_table" "terra-public1"{
  vpc_id = data.aws_vpc.default.id
  route{
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.default-gw.id
  }
  tags = {
    Name = "terra-public1"
  }
}

라우팅 테이블을 이전에 생성한 서브넷과 연결하여 외부에서 서브넷으로 접속이 가능하도록 설정한다.

#라우팅 테이블과 서브넷 연결
resource "aws_route_table_association" "terra-public1-route"{
  subnet_id = aws_subnet.seoul1.id
  route_table_id = aws_route_table.terra-public1.id
}
4-5. EC2 인스턴스 생성

EC2 인스턴스를 정의하는 리소스인 aws_instance를 이용하여 인스턴스를 생성한다.

구성은 Ubuntu Server 20.04 LTS (HVM), SSD Volume Type / t2.micro이다.

#ec2 생성
resource "aws_instance" "web1"{
  ami = "ami-04876f29fd3a5e8ba"
  instance_type = "t2.micro"
  subnet_id = aws_subnet.seoul1.id
  key_name = aws_key_pair.ec2_key_pair.key_name
  vpc_security_group_ids = [
    aws_security_group.ssh.id,
    data.aws_security_group.default.id
  ]
  tags = {
    Name = "web1"
  }
}

이를 완료하면 web1이라는 이름의 ec2 인스턴스가 생성된다.

4-6. EC2 접속

terraform console명령어를 통해 생성한 ec2의 public ip를 확인한다.

# terraform console
> aws_instance.web1.public_ip
"13.125.243.3"

이 public ip를 통해 ssh로 ec2 인스턴스에 접속한다.

# ssh -v -i ~/.ssh/ec2_key_pair ubuntu@13.125.243.3
Last login: Thu May 20 06:02:07 2021 from 221.151.49.81
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@ip-172-31-229-56:~$

5. RDS 생성 및 접속

5-1. private 서브넷 생성

private 서브넷에 RDS를 생성하기 위해 우선 private 서브넷을 생성한다. AWS의 경우 RDS를 생성하려면 최소 2개 이상의 서브넷이 필요하다. 각 서브넷은 서로 다른 가용존에 위치해야한다.

위의 public 서브넷과는 다르게 자동 public IP가 할당될 필요가 없으므로 map_public_ip_on_launch = true를 없앤다.

#db생성하기 위한 subnet 추가 생성
resource "aws_subnet" "private1"{
  vpc_id = data.aws_vpc.default.id
  cidr_block = "172.31.2.0/24"
  availability_zone = "ap-northeast-2c"
}

resource "aws_subnet" "private2"{
  vpc_id = data.aws_vpc.default.id
  cidr_block = "172.31.3.0/24"
  availability_zone = "ap-northeast-2a"
}
5-2. NAT 게이트웨이 생성 및 라우팅 테이블 설정

NAT 게이트웨이를 통해서 외부로부터의 직접 접근은 막으며 rds가 외부와 통신할 수 있도록 허용한다. NAT Gateway를 생성하기 위해서는 탄력적 IP가 필요하기 때문에, 탄력적 IP를 생성한 후 NAT 게이트웨이를 생성한다.

NAT게이트웨이에 탄력적 IP가 필요한 이유

관련 내용 링크

NAT 장치에 동적 주소가 할당된다면 주소가 변경 될 시 진행 중인 세션이 중단되게 된다. 그렇기 때문에 고정된 정적 주소가 필요하며, 정적 주소를 할당하는 방법이 탄력적 IP를 할당하는 것이다.

*EIP(Elastic IP) = 고정 IP

#NAT Gateway에 할당하기위한 탄력적 IP생성[eip]
resource "aws_eip" "nat"{
  vpc = true
}

프라이빗 서브넷 인스턴스에서 라우팅테이블을 기반으로 NAT 게이트웨이로 인터넷 트래픽을 보내면 NAT 게이트웨이는 public 서브넷 내에서 탄력적 IP주소를 소스IP주소로 사용하여 IGW로 트래픽을 보낸다.

우선 public 서브넷 내에 NAT 게이트웨이를 생성하고, NAT 게이트웨이가 외부와 연결될 수 있도록 라우팅 테이블을 작성한다. 이후 프라이빗 서브넷과 NAT 게이트웨이를 연결시켜 트래픽 전송이 가능하도록 설정한다. 관련 내용 링크

#public subnet과 연결을 위한 NAT Gateway 생성
resource "aws_nat_gateway" "public-nat1"{
  allocation_id = aws_eip.nat.id
  subnet_id = aws_subnet.seoul1.id
  tags = {
    Name = "public-nat1"
  }
}

#NAT Gateway, 외부 연결 라우팅 테이블 생성
resource "aws_route_table" "terra-private1"{
  vpc_id = data.aws_vpc.default.id
  route{
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_nat_gateway.public-nat1.id
  }
  tags = {
    Name = "terra-private1"
  }
}

#라우팅 테이블과 private subnet 연결
resource "aws_route_table_association" "terra-private1-route"{
  subnet_id = aws_subnet.private1.id
  route_table_id = aws_route_table.terra-private1.id
}

resource "aws_route_table_association" "terra-private2-route"{
  subnet_id = aws_subnet.private2.id
  route_table_id = aws_route_table.terra-private1.id
}
5-3. RDS 생성

rds를 앞에서 설정한 private subnet에 생성하기 위해서는 private subnet group을 생성한 후 이를 rds와 연결해야한다.

우선 private subnet group을 생성한다.

#rds private subnet group 생성
resource "aws_db_subnet_group" "db_group"{
  name = "db_group"
  subnet_ids = [aws_subnet.private1.id,aws_subnet.private2.id]
  tags = {
    Name = "db subnet group"
  }
}

private subnet group을 생성했으면 해당 그룹에 rds를 생성한다. 이 때 주의해야할 점은 현재 AWS에서 권장하는 engine_version을 사용해야한다는 것이다. 이번 실습에서는 mysql을 사용했는데, AWS에 따르면 5.6버전은 권장하지 않는다고 한다. 5.7.33버전을 사용하였다.

#rds 생성
resource "aws_db_instance" "web1-db1"{
  allocated_storage = 8
  engine = "mysql"
  engine_version = "5.7.33"
  instance_class = "db.t2.micro"
  username = "admin"
  password = "<DB_PASSWORD>"
  skip_final_snapshot = true
  db_subnet_group_name = aws_db_subnet_group.db_group.id
  allow_major_version_upgrade = true
}

만약에 엔진 버전을 잘못 설정했다면?

allow_major_versoin_upgrade = true 설정 값을 넣은 후 terraform apply를 실행시킨다. 이 후 engine version을 바꾸고 다시 terraform apply를 실행시킨다. (아니면 AWS콘솔에서 직접 변경한다.)

RDS비밀번호를 랜덤으로 설정하고 싶다면?

Terraform random_password참조

5-4. RDS 접속

이제 퍼블릭 서브넷에 있는 ec2를 통해 rds에 접속해보자.

# ssh -v -i ~/.ssh/ec2_key_pair ubuntu@52.78.47.203

ec2에 접속했으면 mysql 엔진을 설치한다.

> apt-get install mysql-server

Could not get lock /var/lib/dpkg/lock frontend - open 에러가 발생했다면?관련링크

  1. sudo killall apt apt-get

    - 진행중인 프로세스가 없다라고 뜨면, 아래와 같이 하나하나씩 디렉토리를 삭제한다.

  2. sudo rm /var/lib/apt/lists/lock

  3. sudo rm /var/cache/apt/archives/lock

  4. sudo rm /var/lib/dpkg/lock*

  5. sudo dpkg --configure -a

  6. sudo apt update

RDS접속을 위해서 terraform console명령어로 RDS 엔드포인트를 확인한다.

root@ubuntu:~/terraform# terraform console
> aws_db_instance.web1-db1.endpoint
"terraform-20210521012815330200000001.cv3tugvyuy2b.ap-northeast-2.rds.amazonaws.com:3306"

이후 ec2내에서 mysql을 접속한다.

ubuntu@ip-172-31-0-232:~$ sudo mysql -u admin -p --port 3306 --host terraform-20210521012815330200000001.cv3tugvyuy2b.ap-northeast-2.rds.amazonaws.com
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 17
Server version: 5.7.33 Source distribution

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

위처럼 나오면 접속이 성공한 것이다.

6. 전체 코드

- provider.tf
provider "aws" {
  access_key = ""
  secret_key = ""
  region = "ap-northeast-2"
}
- infrastructure.tf
#키페어 생성
resource "aws_key_pair" "ec2_key_pair"{
  key_name = "ec2_key_pair"
  public_key = file("~/.ssh/ec2_key_pair.pub")
}

#외부 - 22번 포트 보안 그룹 생성
resource "aws_security_group" "ssh"{
  name = "allow_ssh_from_all"
  description = "Allow SSH port from all"
  ingress{
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

#aws default 보안 그룹
data "aws_security_group" "default"{
  name = "default"
}

#aws default vpc
data "aws_vpc" "default"{
  tags = {
    Name = "default"
  }
}

#VPC내 기본 subnet 없을 시 subnet 생성
resource"aws_subnet" "seoul1"{
  vpc_id = data.aws_vpc.default.id
  cidr_block = "172.31.0.0/24"
  availability_zone = "ap-northeast-2a"
  map_public_ip_on_launch = true
}

#외부 접근을 위한 VPC IGW 생성
resource "aws_internet_gateway" "default-gw"{
  vpc_id = data.aws_vpc.default.id
  tags = {
    Name = "default-gw"
  }
}

#IGW, 외부 연결 라우팅 테이블 생성
resource "aws_route_table" "terra-public1"{
  vpc_id = data.aws_vpc.default.id
  route{
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.default-gw.id
  }
  tags = {
    Name = "terra-public1"
  }
}

#라우팅 테이블과 subnet 연결
resource "aws_route_table_association" "terra-public1-route"{
  subnet_id = aws_subnet.seoul1.id
  route_table_id = aws_route_table.terra-public1.id
}

#ec2 생성
resource "aws_instance" "web1"{
  ami = "ami-04876f29fd3a5e8ba"
  instance_type = "t2.micro"
  subnet_id = aws_subnet.seoul1.id
  key_name = aws_key_pair.ec2_key_pair.key_name
  vpc_security_group_ids = [
    aws_security_group.ssh.id,
    data.aws_security_group.default.id
  ]
  tags = {
    Name = "web1"
  }
}
- rds.tf
#db생성하기 위한 subnet 추가 생성
resource "aws_subnet" "private1"{
  vpc_id = data.aws_vpc.default.id
  cidr_block = "172.31.2.0/24"
  availability_zone = "ap-northeast-2c"
}

resource "aws_subnet" "private2"{
  vpc_id = data.aws_vpc.default.id
  cidr_block = "172.31.3.0/24"
  availability_zone = "ap-northeast-2a"
}

#NAT Gateway에 할당하기위한 탄력적 IP생성[eip]
resource "aws_eip" "nat"{
  vpc = true
}

#public subnet과 연결을 위한 NAT Gateway 생성
resource "aws_nat_gateway" "public-nat1"{
  allocation_id = aws_eip.nat.id
  subnet_id = aws_subnet.seoul1.id
  tags = {
    Name = "public-nat1"
  }
}

#NAT Gateway, 외부 연결 라우팅 테이블 생성
resource "aws_route_table" "terra-private1"{
  vpc_id = data.aws_vpc.default.id
  route{
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_nat_gateway.public-nat1.id
  }
  tags = {
    Name = "terra-private1"
  }
}

#라우팅 테이블과 private subnet 연결
resource "aws_route_table_association" "terra-private1-route"{
  subnet_id = aws_subnet.private1.id
  route_table_id = aws_route_table.terra-private1.id
}

resource "aws_route_table_association" "terra-private2-route"{
  subnet_id = aws_subnet.private2.id
  route_table_id = aws_route_table.terra-private1.id
}


#rds private subnet group 생성
resource "aws_db_subnet_group" "db_group"{
  name = "db_group"
  subnet_ids = [aws_subnet.private1.id,aws_subnet.private2.id]
  tags = {
    Name = "db subnet group"
  }
}

#rds 생성
resource "aws_db_instance" "web1-db1"{
  allocated_storage = 8
  engine = "mysql"
  engine_version = "5.7.33"
  instance_class = "db.t2.micro"
  username = "admin"
  password = "<DB_PASSWORD>"
  skip_final_snapshot = true
  db_subnet_group_name = aws_db_subnet_group.db_group.id
  allow_major_version_upgrade = true
}

7. 기타

7-1. 리소스 삭제

- 리소스 전체를 삭제하고 싶을 때는 destroy명령어를 사용한다.

​ 삭제를 하기 전 상태를 확인할 때는 terraform plan -destroy 명령어를 사용한다.

​ 실제 전체 삭제를 할 때는 terraform destroy명령어를 사용한다.

- 리소스 중 일부만 삭제하고 싶을 때는 terraform destroy -target RESOURCE_TYPE.NAME를 사용한다.

​ [예시]

terraform destroy -target aws_db_instance.web1-db1
7-2. 리소스 전체 상태 확인
# terraform state list

* 출처

profile
🎈도전 속의 여유를 즐기자🎈

0개의 댓글