변수
모든 변수가 한 파일에 모든 것이 있는 것은 좋지않다. 변수를 다양한 방법으로 활용해야한다.
- aws 자격 증명 정보는 git에 올리고 싶지 않을 것이다. 따라서 해당 정보는 별도의 파일로 두어야한다.
- 변경될 수 있는 요소를 위해 변수를 활용한다. AMIs 지역에 따라 달라질 수 있다.
- 변수를 사용해 테라폼 파일을 쉽게 재사용할 수 있다.
아래 instance.tf 파일을 나누어보자. 모든 정보를 하나의 파일에 다 정의한 모습이다.
provider "aws" {
access_key = "..."
secret_key = "..."
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "ami-0c9c942bd7bf113a2"
instance_type = "t2.micro"
}
# provider.tf
- 공급자(aws), 시크릿, 지역만 정의한다.
provider "aws" {
access_key = "${var.AWS_ACCESS_KEY}"
secret_key = "${var.AWS_SECRET_KEY}"
region = "${var.AWS_REGION}"
}
# vars.tf
- 변수 정의
- 중괄호가 공백이면, 값을 정의하지 않는다는 뜻
- 정의되지 않은 값은 .tfvars 파일에서 찾을 것이다.
variable "AWS_ACCESS_KEY" {}
variable "AWS_SECRET_KEY" {}
variable "AWS_REGION" {
default = "ap-northeast-2"
}
# terraform.tfvars
- 기본값 정의
- 여기에 key값들을 정의하고, git ignore 시킨다.
AWS_ACCESS_KEY="..."
AWS_SECRET_KEY="..."
instance.tf
- 변경사항이 없는 값들이 포함될 것이다.
resource "aws_instance" "example" {
ami = "ami-0c9c942bd7bf113a2"
instance_type = "t2.micro"
}
instance.tf의 ami 정보까지 변수로 옮겨보자.
# vars.tf
variable "AWS_ACCESS_KEY" {}
variable "AWS_SECRET_KEY" {}
variable "AWS_REGION" {
default = "ap-northeast-2"
}
variable "AMIS" {
type=map(string)
default={
ap-northeast-2 = "ami-0c9c942bd7bf113a2"
}
}
# instance.tf
resource "aws_instance" "example" {
ami = "${lookup(var.AMIS, var.AWS_REGION)}"
instance_type = "t2.micro"
}
- {lookup} 은 맵 변수 "AMIS" 에서 변수 "AWS_REGION" 가 가지고 있는 값("ap-northeast-2")을 key로 변수를 찾을 것이다.
잘 적용이 되었는지 terraform plan으로 확인해보자.
Terraform will perform the following actions:
# aws_instance.example will be created
+ resource "aws_instance" "example" {
+ ami = "ami-0c9c942bd7bf113a2"
+ get_password_data = false
+ instance_type = "t2.micro"
...
}
소프트웨어 프로비저닝
인스턴스에서 소프트웨어 프로비저닝을 하는 방법은 두 가지가 있다.
1. 사용자 정의 AMI 빌드 (packer 툴 사용)
2. 표준화된 AMIs를 부팅한 후, 필요한 소프트웨어 설치
두 번째 방법을 알아보자. 파일 업로드를 통해서 필요한 소프트웨어를 설치할 수 있다. 원격으로 파일을 실행하는 것이다.
파일 업로드
provisioner "file" 을 사용한다. destination은 인스턴스 내 목적지를 의미한다.
# instance.tf
resource "aws_instance" "example" {
ami = "${lookup(var.AMIS, var.AWS_REGION)}"
instance_type = "t2.micro"
provisioner "file" {
source = "app.conf"
destination = "/etc/myapp.conf"
}
}
파일 전송은 SSH로 전송되기 때문에 connection 정보를 정의해야한다. (connection의 기본 type이 SSH이다.)
# instance.tf
resource "aws_instance" "example" {
ami = "${lookup(var.AMIS, var.AWS_REGION)}"
instance_type = "t2.micro"
provisioner "file" {
source = "app.conf"
destination = "/etc/myapp.conf"
connection {
user = "${var.instance_username}"
password = "${var.instance_password}"
}
}
}
일반적으로 AWS에서는 password가 아닌 SSH key pair를 사용한다. SSH 를 key pair를 사용하려면 또 다른 리소스 aws_key_pair 를 사용해야한다.
# instance.tf
resource "aws_key_pair" "edward-key" {
key_name = "mykey"
public_key = "ssh-rsa my-public-key ..."
}
resource "aws_instance" "example" {
ami = "${lookup(var.AMIS, var.AWS_REGION)}"
instance_type = "t2.micro"
provisioner "file" {
source = "script.sh"
destination = "/opt/script.sh"
key_name = "${aws_key_pair.mykey.key_name}"
connection {
user = "${var.instance_username}"
private_key = "${file(${var.path_to_private_key})}"
}
}
}
- aws_key_pair 리소스 추가
- key이름을 지정하고, 로그인을 위한 public_key를 삽입한다.
- provisioner 에 key_name 지정
- aws가 해당 공개키를 인스턴스에 설치하게된다.
- connection에 private_key 지정
- SSH를 통해 인스턴스에 로그인하고 파일을 업로드하는데 사용
인스턴스에서 파일을 실행하는 것은 provisioner "remote-exec" 을 통해 할 수 있다.
# instance.tf
resource "aws_instance" "example" {
ami = "${lookup(var.AMIS, var.AWS_REGION)}"
instance_type = "t2.micro"
provisioner "file" {
source = "script.sh"
destination = "/opt/script.sh"
}
provisioner "remote-exec" {
inline = [
"chmod +x /opt/script.sh",
"/opt/script.sh arguments"
]
}
}
terraform을 통해 인스턴스를 생성하고, ssh key를 등록하고, 파일 업로드를 통해 해당 인스턴스에 nginx를 설치해보자.
Key 생성
ssh-keygen 을 사용해 인스턴스에 원격 접속시 사용할 SSH key pair를 생성한다.
$ ssh-keygen -f mykey
private-key인 mykey와 public-key인 mykey.pub이 생성되었을 것이다.
SSH 22 port 허용
추가로, 인스턴스에 SSH 접근을 하려면 ec2에서 보안그룹을 통해 22 port 액세스를 열어야한다.
인스턴스를 추가할 vpc (여기서는 default vpc) 의 보안그룹에 22 port, 혹은 전체 port를 내 IP에 대해 열어준다.
Terraform 작성
# provider.tf
provider "aws" {
access_key = var.AWS_ACCESS_KEY
secret_key = var.AWS_SECRET_KEY
region = var.AWS_REGION
}
# vars.tf
- key 파일 경로에 대한 변수를 추가했을 뿐, 위와 비슷하다.
variable "AWS_ACCESS_KEY" {}
variable "AWS_SECRET_KEY" {}
variable "AWS_REGION" {
default = "ap-northeast-2"
}
variable "AMIS" {
type = map(string)
default = {
ap-northeast-2 = "ami-0c9c942bd7bf113a2"
}
}
variable "PATH_TO_PRIVATE_KEY" {
default = "mykey"
}
variable "PATH_TO_PUBLIC_KEY" {
default = "mykey.pub"
}
variable "INSTANCE_USERNAME" {
default = "ubuntu"
}
# terraform.tfvars
AWS_ACCESS_KEY="..."
AWS_SECRET_KEY="..."
# instance.tf
- aws_key_pair를 통해 인스턴스에 public key를 등록한다.
- provisioner "file"을 통해 script.sh 파일을 업로드해준다.
- provisioner "remote-exec"을 통해 script.sh 파일을 실행시킨다.
- connection에 대한 정보를 정의한다.
- coalesce 함수는 여러 인자를 받아, null이 아닌 첫번째 값을 반환한다. 여기선 인스턴스의 public ip에 연결하게 될 것이다.
- 인스턴스에 ssh 연결하기 위한 user, private key를 전달한다.
resource "aws_key_pair" "mykey" {
key_name = "mykey"
public_key = file(var.PATH_TO_PUBLIC_KEY)
}
resource "aws_instance" "example" {
ami = var.AMIS[var.AWS_REGION]
instance_type = "t2.micro"
key_name = aws_key_pair.mykey.key_name
provisioner "file" {
source = "script.sh"
destination = "/tmp/script.sh"
}
provisioner "remote-exec" {
inline = [
"chmod +x /tmp/script.sh",
"sudo sed -i -e 's/\r$//' /tmp/script.sh", # Remove the spurious CR characters.
"sudo /tmp/script.sh",
]
}
connection {
host = coalesce(self.public_ip, self.private_ip)
type = "ssh"
user = var.INSTANCE_USERNAME
private_key = file(var.PATH_TO_PRIVATE_KEY)
}
}
여기서 script는 nginx를 설치하고 실행하는 간단한 내용이다. nginx를 바로 실행하기 때문에, 인스턴스 및 script가 정상적으로 실행되면 바로 80 port로 nginx 화면을 볼 수 있다.
#!/bin/bash
# sleep until instance is ready
until [[ -f /var/lib/cloud/instance/boot-finished ]]; do
sleep 1
done
# install nginx
apt-get update
apt-get -y install nginx
# make sure nginx is started
service nginx start
실행
$ terraform init
$ terraform apply
- 인스턴스 실행 로그 및 key pair 생성, remote-exec 실행에 대한 로그를 확인하자.
- 정상적으로 완료되었으면, 인스턴스의 public IP를 통해 접속해보자.
- 인스턴스에 key pair 또한, 지정한 key로 등록이 되었을 것이다.
속성 출력
https://developer.hashicorp.com/terraform/language/values/outputs
테라폼은 생성한 모든 리소스의 속성을 유지한다. 예를 들어, AWS 인스턴스 리소스에는 public_ip라는 속성이 있다. 이런 속성들은 쿼리되거나 출력될 수 있다. 이는 외부 소프트웨어에 정보를 제공할 때 전달되는 등 이후에 사용될 수가 있다.
이런 테라폼 리소스의 속성 정보를 활용해보자.
output을 통해 생성된 리소스의 속성을 출력할 수 있다.
resource "aws_instance" "example" {
ami = "ami-0c9c942bd7bf113a2"
instance_type = "t2.micro"
}
output "ip" {
value = aws_instance.example.public_ip
}
변수에 전체 요소를 지정해 모든 요소를 참조할 수 있다.이 예시에서는 아래와 같다.
- resource type : aws_instance
- resource name : example
- attribute name: public_ip
apply가 완료되면 다음과 같이 output이 출력될 것이다.
script에서도 attribute를 사용할 수 있다.
resource "aws_instance" "example" {
ami = "ami-0c9c942bd7bf113a2"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo ${aws_instance.example.private_ip} >> private_ips.txt"
}
}
- provisioner "local-exec" 은 aws 인스턴스가 아닌, 로컬에서 실행된다.
- instance의 private_ip를 txt 파일에 저장하게 된다.
- 만약 생성하는 aws_instance가 다수이면, 이런식으로 모든 생성 ip들을 수집할 수 있을 것이다.
- 이런 기능은 인프라 provisioning 후에 자동화 스크립트를 시작하는 경우에 유용하다.
Remote State
테라폼은 인프라의 remote state (원격 상태) 를 유지하며, 이를 terraform.tfstate 파일에 저장한다. 이전 상태에 대한 백업은 terraform.tfstate.backup 으로 저장된다.
apply를 실행하면 새로운 terraform.tfstate 와 terraform.tfstate.backup이 생성될 것이다.
만약 원격 상태가 변경되었을때 (ex. 수동으로) apply를 수행하면, 테라폼이 올바른 상태가 되도록 인프라를 변경할 것이다.
terraform tf.state 관리
git
git과 같은 version control에서, terraform.tfstate 을 유지할 수 있다. terraform.tfstate 파일은 결국 큰 json 파일이기 때문에, git으로 관리해도 history 추적이 가능하다. 다만 팀으로 관리하게 되면, 충돌이 생길 수도 있다. push와 pull을 통해 최신버전을 항상 확인해야한다.
terraform backend
테라폼은 테라폼 자체의 백엔드 기능을 활용해 terraform.tfstate 파일을 원격으로 저장할 수 있다.
기본은, local backend 이다. terraform.tfstate 파일이 local에 생성되는 것을 계속 보았을 것이다.
또다른 백엔드로는 s3, consul, terraform enterprise 등이 있다. 백엔드 기능을 사용하면, 팀으로 사용하는 데 더 용이하다. 같은 시간대에 한 사람만이 상태를 업데이트할 수 있다.
consul remote store
# backend.tf
terraform {
backend "consul"{
address = "demo.consul.io" # consul cluster의 hostname
path = "terraform/myproject" # 상태가 저장될 경로
}
}
s3 remote store
terraform {
backend "s3"{
bucket = "mybucket"
key = "terraform/myproject"
region = "ap-northeast-2"
}
}
s3 remote state를 사용할 때는, cli를 통해 aws credential을 구성하는 것이 좋다. 백엔드에서는 변수를 사용할 수 없다. 백엔드 코드는 다른 terraform 초기화가 수행되기 전에 이미 aws s3에 액세스해야하므로, 미리 configure을 해두어야한다.
$ aws configure
AWS Access KEY ID []: ...
위와 같이 구성하고 terraform init을 입력하면, 테라폼이 백엔드를 해당 정보로 초기화할 것이다.
테라폼 state를 위해 원격 저장소를 활용하면, 항상 최신의 상태를 보장할 수 있다. terraform.tfstate 파일을 적용하거나, push하는 과정은 필요가 없어진다.
테라폼 원격 저장소가 항상 잠금을 지원하지는 않는다. s3와 consul은 지원하며, .tf 파일에 read-only 원격 저장소를 지정할 수도 있다. 원격의 terraform.tfstate 파일에서, 읽기만 하려는 경우 사용한다.
참고: https://developer.hashicorp.com/terraform/language/state/remote-state-data
Datasources
https://registry.terraform.io/providers/hashicorp/aws/5.0.1/docs/data-sources/ip_ranges
aws와 같은 몇몇 provider에 대해 테라폼은 동적 정보를 제공하는 datasource를 지원한다.
예를 들어, AMI 목록이나 availability zones을 제공하는 datasource가 있는데, 이는 변경될 수 있는 동적 정보이다.
또 다른 예로, aws에서 사용중인 모든 IP 주소를 제공하는 datasource도 있다. aws region을 기반으로 트래픽을 필터링하는데 사용할 수 있다. 이 datasource를 활용해 security group을 설정하면, "서울의 aws instace에서 들어오는 traffic을 허용" 과 같은 설정이 가능해진다.
data "aws_ip_ranges" 를 통해 아일랜드와 프랑크프루트 내 모든 IP 대역을 가져와, "aws_security_group" 으로 security group을 설정하는 예시이다.
data "aws_ip_ranges" "european_ec2" {
regions = ["eu-west-1", "eu-central-1"]
services = ["ec2"]
}
resource "aws_security_group" "from_europe" {
name = "from_europe"
ingress {
from_port = "443"
to_port = "443"
protocol = "tcp"
cidr_blocks = data.aws_ip_ranges.european_ec2.cidr_blocks
}
tags = {
CreateDate = data.aws_ip_ranges.european_ec2.create_date
SyncToken = data.aws_ip_ranges.european_ec2.sync_token
}
}
유럽에서 사용하는 IP CIDR 에 대한 443 inbound가 허용된 security group이 생성된다.
확인
$ aws ec2 describe-security-groups --filters 'Name=group-name,Values=from_europe'
Temlate Provider
deprecated => https://developer.hashicorp.com/terraform/language/functions/templatefile
Modules
테라폼에서 리소스를 작성한 후 모듈에 저장하면, 다른 프로젝트에 해당 리소스를 다시 사용할 수 있다. 모듈을 사용해 타사 모듈을 사용하기도 한다.
git에 저장되어있는 외부 모듈 사용
# modules.tf
module "modul-example" {
source = "github.com/wardviaene/terraform-module-example"
key_name = aws_key_pair.mykey.key_name # 모듈이 요구하는 변수를 넘겨준다.
key_path = var.PATH_TO_PRIVATE_KEY
}
- 모듈이 요구하는 변수를 넘겨주어야한다.
로컬 폴더의 모듈을 사용
(module-example 폴더 내에는 여태 우리가 정의한 것과 같은 테라폼 파일들이 있을 것이다.)
module "module-example" {
source = "./module-example"
}
모듈을 사용할 때는, terraform get을 통해 모듈을 가져온다. 모듈은 ./terraform/modules 에 저장된다.
$ terraform get # 모듈을 가져온다.
테라폼 명령어
테라폼 사용시 몇가지 유용한 명령어
fmt
- 테라폼 파일에 표준 형식을 적용한다. (ex. 공백제거)
$ terraform fmt instance.tf
import
- 이미 실행 중인 인스턴스가 있지만 테라폼이 없는 경우, import를 통해 .tfstate 파일에 상태를 가져올 수 있다. (리소스 정의가 아닌 상태를 가져온다.)
- ADDRESS는 지정할 resource의 이름이라고 생각하면 된다. 예시에서는 resource "aws_instance" "example" {} 과 같다.
- ID는 가져올 aws instance ID 이다. 리소스마다 다른 ID 형태를 가지게된다.
- terraform.tfstate가 해당 리소스의 정보를 가지게된다.
$ terraform import [options] ADDRESS ID
$ terraform import aws_instance.example i-0bda...
taint
- 오염된 리소스를 수동으로 표시하는 방법으로, 다음 apply시 해당 리소스를 재생성한다.
- 해제시 untaint 사용
$ terraform taint aws_instance.example
graph
- 리소스 간 종속성을 그래프로 확인할 수 있다.
$ terraform graph
show
- 현재 state 정보를 좀 더 읽기 편한 형태로 확인한다.
$ terrafrom show
output
만약 output 을 정의하여 사용했으면, output을 통해 값을 다시 확인할 수 있다.
$ terraform output
'DevOps' 카테고리의 다른 글
[Istio] 로드밸런싱 (+ConsistentHashing) (0) | 2024.01.29 |
---|---|
[Istio] 트래픽 관리 (+카나리 배포) (0) | 2024.01.23 |
[Istio] Telemetry (kiali, jaeger) (0) | 2024.01.14 |
[Istio] Overview (0) | 2023.12.11 |
[terraform] 테라폼으로 aws 프로비저닝하기 (0) | 2023.05.28 |