5주차 Module & Runner

본 스터디 내용 요약은 @CloudNet의 가시다님의 강의를 요약한 내용입니다.

1. 모듈

1.1 모듈 작성 기본 원칙

  • 모듈 디렉토리 형식은 terraform-<프로바이더이름>-<모듈이름> 으로 구성하면 된다.

  • 테라폼 구성은 궁극적으로 모듈화가 가능한 구조로 작성해야한다.

  • 각각의 모듈을 독립적으로 관리하기를 제안한다.

  • 공개된 테라폼 레지스트리의 모듈을 참고해라

루트 모듈 하위에 자식 모듈을 구성하는 경우, 종속성이 발생하므로, 루트 모듈 에 모듈 디렉터리를 생성한다. 예시는 밑에서 다룰 예정이다.

// module-directory
|___modules   // chile-modules
|   |_ main.tf
|   |_ output.tf
|___06-basic   // root-module
    |_ main.tf

1.2 모듈화 해보기

A. 모듈화 소개

  • 모듈의 기본 구조는 테라폼 구성으로 기존에 작성된 모듈을 다른 모듈에서 참조해서 사용할 수 있다.

  • Variables로 인풋을 받고 Outputs에서 외부 모듈이 참조할 수 있는 값을 정할 수 있다. (캡슐화)

B. [실습] 모듈 작성

  • 자식 모듈 생성

다음과 같은 디렉터리 구조를 생성합니다.

/module-directory
|___modules   // chile-modules
|   |_terraform-random-pwgen
|       |_ main.tf
|       |_ output.tf
|       |_ variable.tf
|___06-basic   // root-module
    |_ main.tf
# main.tf
resource "random_pet" "name" {
  keepers = {
    ami_id = timestamp()
  }
}

resource "random_password" "password" {
  length           = var.isDB ? 16 : 10
  special          = var.isDB ? true : false
  override_special = "!#$%*?"
}

# variable.tf
variable "isDB" {
  type        = bool
  default     = false
  description = "패스워드 대상의 DB 여부"
}

# output.tf
output "id" {
  value = random_pet.name.id
}

output "pw" {
  value = nonsensitive(random_password.password.result) 
}

  • 자식 모듈 호출 확인

# root 모듈을 설정 해 줍니다. 
# 06-module-traning/06-01-basic/main.tf 
```tf
module "mypw1" {
  source = "../modules/terraform-random-pwgen"
}

module "mypw2" {
  source = "../modules/terraform-random-pwgen"
  isDB   = true
}

output "mypw1" {
  value  = module.mypw1
}

output "mypw2" {
  value  = module.mypw2
}
```
terraform init && terraform plan && terraform apply -auto-approve
# 다음과 같은 결과를 확인 할 수 있습니다!
  • graph dot 파일을 확인하면 다음과 같이 나온다.

1.3 모듈 사용 방식

A. [실습] 모듈과 프로바이더

  • 모듈에서 사용되는 프로바이더에 대한 정의가 필요한데 정의를 모듈 안 혹은 밖에 할 수 있다.

a. 자식 모듈에서 프로바이더 정의 하는 경우

  • 모듈에서 사용하는 프로바이더 버전과 구성상세를 자식 모듈에서 고정한다.

  • 루트 모듈와 자식 모듈에서 서로 다른 버전을 가지고 합의가 되지 않으면, 오류가 발생하고 반복문이 실행이 되지 않는다.

  • 잘 사용되지 않는다.

b. 루트 모듈에서 프로바이더 정의

  • 자식 모듈은 루트 모듈의 프로바이더에 종속 되는 방식이다.

  • 자식 모듈이 사용하는 프로바이더 버전을 일괄적으로 적용하고, 자식 모듈들은 루트 모듈에서 정의하는 프로바이더에 맞게 업데이트 되어야한다.

// module-directory
|___modules   // child-modules
|   |_ main.tf
|   |_ output.tf
|___06-basic   // root-module
    |_ main.tf
# 자식 모듈 main.tf
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
  }
}

resource "aws_default_vpc" "default" {}

data "aws_ami" "default" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "owner-alias"
    values = ["amazon"]
  }

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

resource "aws_instance" "default" {
  depends_on    = [aws_default_vpc.default]
  ami           = data.aws_ami.default.id
  instance_type = var.instance_type

  tags = {
    Name = var.instance_name
  }
}

# variable.tf
variable "instance_type" {
  description = "vm 인스턴스 타입 정의"
  default     = "t2.micro"
}

variable "instance_name" {
  description = "vm 인스턴스 이름 정의"
  default     = "my_ec2"
}
# output.tf
output "private_ip" {
  value = aws_instance.default.private_ip
}

# main.tf 부모 모듈
provider "aws" {
  region = "ap-southeast-1"  
}

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

module "ec2_singapore" {
  source = "../modules/terraform-aws-ec2"
}

module "ec2_seoul" {
  source = "../modules/terraform-aws-ec2"
  providers = {
    aws = aws.seoul  // 다른 프로바이더 지정 
  }
  instance_type = "t3.small"
}

# output.tf
output "module_output_singapore" {
  value = module.ec2_singapore.private_ip
}

output "module_output_seoul" {
  value = module.ec2_seoul.private_ip
}

  • 모듈마다 다른 프로바이더 설정을 해주더라도 정상적으로 동작하는 것을 확인 할 수 있다.

B. [실습] 모듈의 반복문

  • 모듈 또한 반복문을 사용해서 원하는 수량을 프로비져닝 할 수 있다.

  • 모듈 없이 구성하는 것에 대비해 리소스 종속성 관리와 유지보수에 큰 도움이 된다.

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

module "ec2_seoul" {
  count  = 2  // 카운트로 2개 생성
  source = "../modules/terraform-aws-ec2"
  instance_type = "t3.small"
}

output "module_output" {
  value  = module.ec2_seoul[*].private_ip   
}
  • 일관된 구조일때는, count를 사용하지만, 필요한 인수값이 다른 경우는 for_each를 활용하면 된다.

  • 해당 tf 구문을 실행하면 다음과 같은 결과를 얻을 수 있다.

locals { // 원하는 다른 인수값에 따른 선언을 해주면 된다.
  env = {
    dev = {
      type = "t3.micro"
      name = "dev_ec2"
    }
    prod = {
      type = "t3.medium"
      name = "prod_ec2"
    }
  }
}

module "ec2_seoul" {
  for_each = local.env
  source = "../modules/terraform-aws-ec2"
  instance_type = each.value.type
  instance_name = each.value.name
}

output "module_output" {
  value  = [
    for k in module.ec2_seoul: k.private_ip
  ]
}

1.4 모듈 소스 관리

  • 모듈 블록에 정의된 소스 구성으로 모듈의 코드 위치를 정의한다.

  • init 할때, 지정 모듈을 다운로드해서 사용할 수 있다.

  • 보통은 해당 모듈의 소스를 포크 떠와서 각 환경에 맞게 사용한다고 한다.

a. 지원하는 경로들

  • 로컬 디렉터리

  • 테라폼 레지스트리

  • 깃허브

  • 비트버킷

  • s3, gcs 버킷

b. 로컬 디렉터리 경로 지정하기

module "local_module" {
  source = "../modules/my_local_module"
}

c. 테라폼 레지스트리로 경로 지정하기

  • 공식 공개된 테라폼 모듈을 사용할 수 있다.

  • 이 외에도 Terraform cloud 와 terraform enterprise와 같은 비공개 테라폼 모듈을 사용할 수 있다.

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws" // 네임스페이스/이름/프로바이더 형태
  version = "5.1.0"
}

d. 깃허브를 경로 지정하기

# main.tf  // github repository에 저장할 tf 파일
provider "aws" {
  region = "ap-southeast-1"  
}

module "ec2_seoul" {
  source = "github.com/jivebreaddev/terraform-module-repo/terraform-aws-ec2"
  instance_type = "t3.small"
}
  • 이렇게 github에서 가져오게 된다.

e. 이러한 모듈을 사용하는 이유는 무엇일까?

  • 이미 생성된 천줄 이상 복잡한 구성을 논리적으로 구성 요소화 가능합니다.

  • 별개의 논리적 구성 요소로 캡슐화가 가능합니다.

  • 잘 작성된 모듈을 재사용 할 수 있습니다.

  • 일관성이 있습니다.

f. [실습] 다른 모듈을 가져와서 사용하기

git clone https://github.com/hashicorp/learn-terraform-modules-use.git
cd learn-terraform-modules-use

- terraform.tf : 테라폼 블록 확인
- main.tf : 모듈 블록 확인(모듈 소스, 버전) - arguments 확인 , ec2 count 2 확인
  • 다음과 같은 리소스의 input 섹션에서 어떻게 리소스를 생성할지 알 수 있습니다.

  • input variable들을 variable.tf 에서 확인하고 수정해서 사용하면 됩니다.

저같은 경우에는 
variable "vpc_enable_nat_gateway" {
  description = "Enable NAT gateway for VPC"
  type        = bool
  default     = false
}


variable "vpc_azs" {
  description = "Availability zones for VPC"
  type        = list(string)
  default     = ["ap-northeast-2c"]
}

provider "aws" {
  region = "ap-northeast-2c"

  default_tags {
    tags = {
      hashicorp-learn = "module-use"
    }
  }
}

#  프로바이더 위치랑 리소스 위치를 전부 변경해줬습니다.
  • cat .terraform/modules/modules.json| jq 해당 커맨드로 init 이후 모듈 구조도 확인 가능합니다.

  • 다음과 같이 리소스들이 정상 삭제가 되는것을 확인할 수 있습니다.

  • ec2의 버전이 낮아서 해당 부분은 최신버전으로 변경해서 진행했습니다.

2. Terraform Runner

2.1 Atlantis

  • Atlantis는 Terraform을 관리하기 위한 runner 이자 workflow로 다음과 같은 플로우로 관리됩니다.

  1. Github으로 PR 이 MERGE가 됩니다.

  2. WEB HOOK을 ATLANTIS로 전송하고

  3. PLAN 결과를 PR 코멘트에 등록합니다.

  4. PR comment에 apply 하게 되면 web hook이 atlantis로 전송됩니다.

  5. apply 의 결과가 github에 전시됩니다.

  • 편한 방법으로 개발자들의 리뷰를 통한 리소스 할당과 설정이 가능합니다.

2.2 설치 관련 주의점

주의점

  • Locking 에 대한 이해를 정확히 해야한다.

  • Security에 대해서 취약한 지점을 정확히 알고 대비해둬야한다.

알아야 하는 전략들

  • atlantis. 설정 파일

  • pre workflow hook의 shell 보안 취약 주의

  • post workflow hook 도 확인가능하다.

  • prometheus로 메트릭 수집이 가능하다.

  • slack 알람 설정도 hook 설정 가능하다.

2.3 [실습] 직접 설치하고 자원 배포해보기

a. aws ec2 생성

  • EC2에 다음 스크립트를 돌리면 설정이 된다.

  • AWS CONFIGURE도 설정해주면 된다.

  • 외부 통신도 가능해야하니깐, 해당 EC2 의 OUTBOUND도 확인해주면 된다.

  • 4141 TCP 통신을 위해 포트 오픈도 해둬야 GITHUB 과 ATLANTIS의 웹훅 통신이 가능하다.


hostnamectl --static set-hostname Atlantis

# Config convenience
echo 'alias vi=vim' >> /etc/profile
echo "sudo su -" >> /home/ubuntu/.bashrc

# Install Packages & Terraform
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
apt update -qq && apt install tree jq unzip zip terraform -y

# Install aws cli version2
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# Install atlantis
wget https://github.com/runatlantis/atlantis/releases/download/v0.28.3/atlantis_linux_amd64.zip -P /root
unzip /root/atlantis_linux_amd64.zip -d /root && rm -rf /root/atlantis_linux_amd64.zip

b. aws ec2 ssh 접속 확인

  • 다음과 같이 git, atlantis, terraform 들이 정상적으로 다운로드 된것을 확인 할 수 있다.

c. 공인 IP 노출

URL="http://$(curl -s ipinfo.io/ip):4141"
echo $URL


// 다음과 같이 설정해서 결과값을 설정한다.

d. git repo를 private으로 생성

  • 간단하게 git repo를 private으로 생성해준다.

e. git token 생성

  • Github → Settings → Developer settings ⇒ Personal access tokens : Tokens (classic) ← Repo 제한 가능 Fine-grained tokens 사용 권장

  • 들어가서 atlantis web hook 통에서 사용될 git token을 추가합니다.

f. web hook 을 atlantis에 설정 및 atlantis 시작

./atlantis server \
--atlantis-url="$URL" \
--gh-user="$USERNAME" \
--gh-token="$TOKEN" \
--gh-webhook-secret="$SECRET" \
--repo-allowlist="$REPO_ALLOWLIST"

각 변수들을 선언해준뒤에 실행시키면 atlantis가 정상적으로 올라옵니다.

[실습] Atlantis 사용해보기

  • 다음과 같이 github 리포지토리에 branch 에 대해 pr을 날렸다.

  • 해당 링크를 눌러보면 TERRAFORM PLAN 에 대한 결과가 뜬다.

  • Atlantis 서버에는 plan 에 대한 수행 결과가 생겼다.

  • 다음과 같은 Lock도 설정된 것을 확인 할 수 있다.

  • 머지 후에 terraform 액션을 실행해보면 먹히지 않는다. 꼭, 머지전에 apply 를 해야한다.

[실습] IAM 유저를 만들어보자

  • 버킷을 생성하기

aws s3 mb s3://jivebreaddev --region ap-northeast-2
vi main.tf
----------
terraform {
  backend "s3" {
    bucket = "jivebreaddev"
    key    = "terraform.tfstate"   // s3 백엔드를 사용하면 된다.
    region = "ap-northeast-2"
  }
}

resource "aws_iam_user" "myuser" {
  name = "t101user"  // 생성할 유저
}
  • 해당 결과 확인

Last updated