 ## Terraform 이란? 클라우드가 대세가 되면서 개발환경도 많은 변화를 가져왔다. 단순히 사용하는 도구, 환경의 변화가 아니라 문화 자체를 다시 정의하게 되면서 다양한 방식이 거론 되었고 그 중 가장 널리 퍼지고 있는 문화로 DevOps 가 있다. 짧게 요약하면 기존에 분리되어 운영 되었던 개발과 운영 환경을 통합하여 관리하고자 하는 문화로 DevOps를 성공적으로 하기 위해선 IaC(Infrastructure as code) 환경이 제대로 구성 되어야 한다. IaC란 인프라에 사용되는 모든 설정 들을 Code로서 관리하기 위한 것으로 기존 에서 쉽게 하지 못하던 것들이 클라우드가 자리잡으면서 가능하게 되었다. IaC를 위한 도구로는 다양한 것들이 있는데 Terraform은 그 중 **인프라 전체를 안전하게 배포, 변경, 버전관리등을 해주는** 대표적인 도구로 자리잡았다. 테라폼이랑 비슷한 도구로는 AWS 서비스 중 하나인 CloudFormation, Heat 등이 있다. 테라폼은 클라우드에 종속적이지 않아 AWS에서 GCP로 배포하거나 그 반대로 하는 등 Resource에 대한 정의만 잘 되어있다면 어떤 환경에서든 사용가능한 것이 큰 장점이다. 또한 Terraform 개발사인 Hasicorp에서 만든 HCL언어 를 통해 작성하는데 문법이 비교적 쉬워 러닝커브 또한 높지 않다. (물론 인프라를 잘 구성할 수 있다는 가정하에) 단점으로는 현재 활발하게 개발 중이므로 버전이 빠르게 올라가며 버전간 문법차이가 조금 존재한다. 따라서 현재 실행중인 테라폼의 버전을 항상 확인하며 작성이 필요하다. 테라폼의 가장 중요한 개념은 다른 IaC 관련 툴들처럼 `멱등성(Idempotency)` 이다 . 코드로 정의된 리소스들이 클라우드 환경에 명시된 대로 적용하도록 하므로써 항상 같은 결과값을 낼 수 있다. 예를 들어 EC2 2개가 설치 하도록 선언한 경우 항상 EC2 2개가 설치 되도록 명시하는 식으로 작성되어야 한다. 그러므로 만약 테라폼 환경에서 설치되지 않은 리소스들에 대한 접근과 핸들링은 피하는것이 좋다. 그런식으로 복잡하게 구성된 경우 외부에서 참조한 리소스가 없는 환경에서는 같은 결과값이 아닌 에러를 낼 수 있으니 말이다. 테라폼에서 가장 중요한 명령어는 3가지로 `terraform init`, `terraform plan`, `terraform apply`가 있다. **init** 명령어는 terraform 을 초기화 해주고 상태 파일 성성등 현재 상태관리를 폴더 단위로 해준다. 테라폼의 전체 환경 폴더 구조가 있고 하위에 global 한 폴더 또는 module 이나 각 개발 환경에 맞춰 폴더를 위치시키고 각 폴더별로 초기화를 해줌으로써 각 환경별로 테라폼이 상태를 관리한다. **plan**은 테라폼이 클라우드 환경에 배포하기 전 미리 한번 작동시켜서 정상적으로 배포가 가능한지 체크하는 명령어 이며 **apply**는 실제 배포를 수행한다. ## Terraform 설치 및 실행 *아래 실습은 모두 AWS EC2에서 실행되었다.* 먼저 테라폼을 설치해주며 현재 여기선 `0.12.17` 버전으로 설치되었다. 그 외 다른 버전이나 최신버전은 [이곳](https://releases.hashicorp.com/terraform/)에서 다운받을 수 있다. ``` shell $ wget https://releases.hashicorp.com/terraform/0.12.17/terraform_0.12.17_linux_amd64.zip $ unzip terraform_0.12.17_linux_amd64.zip $ mv terraform /usr/bin $ terraform version Terraform v0.12.17 ``` 위에서 설명한대로 테라폼은 폴더 단위로 환경을 구성 하므로 예제를 실행할 폴더를 만들어 준다. ``` shell $ mkdir terraform-test && cd terraform-test ``` ### Provider Provider는 리소스들이 배포될 환경을 정의한 것으로 terraform은 다양한 [Provider들을 지원한다.](https://www.terraform.io/docs/providers/index.html) 여기서 우리는 AWS를 활용하여 배포하도록 하겠다. ``` shell $ vi provider.tf ``` ``` terraform provider "aws" { access_key = YOUR_ACCESS_KEY secret_key = YOUR_SECRET_KEY region = "ap-northeast-2" } ``` Provider에서는 배포하기 위한 액세스 정보들이 필요한데, Github public repository등을 통해 소스를 관리할때 공개되지 않도록 주의해야 한다. 테라폼에서 변수 값들의 경우 별도로 관리할 수 있으며 테라폼을 배포할때 입력하거나, 변수로 참조하거나 파일을 직접 읽어 해당 값을 입력할 수도 있다. Region은 서울(ap-northeast-2)로 설정한다. 해당 파일을 저장한 후 초기화를 시켜준다. ``` shell $ terraform init Initializing the backend... Initializing provider plugins... The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, it is recommended to add version = "..." constraints to the corresponding provider blocks in configuration, with the constraint strings suggested below. * provider.aws: version = "~> 2.48" 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 폴더가 생긴것을 확인할 수 있다. ``` shell $ ls -al drwxr-xr-x 3 root root 60 3월 6 06:23 . dr-xr-x--- 29 root root 4096 3월 6 06:22 .. drwxr-xr-x 3 root root 21 3월 6 06:23 .terraform -rw-r--r-- 1 root root 112 3월 6 06:22 provider.tf ``` ### Backends Terraform은 상태 정보를 tfstate 파일을 통해 관리를 하는데 배포하고 실행된 상태를 저장하는 곳을 `backends`라고 부른다. 기본적으로는 local backends를 활용하고 있다. 혼자서 배포하는게 아니라 여럿이서 다른 환경에서 배포하다 보면 같은 상태를 공유해야 하는 경우가 나올 수 있으므로 terraform 에서는 local에서 backends를 지정하는 것 보단 AWS S3등 외부 저장공간에서 버전관리와 상태정보를 관리하기를 권장한다. 또한 terraform은 코드로서 인프라를 배포, 운영을 하다보니 코드를 하나만 잘못 수정 하더라도 인프라 전체가 중단되는 결과가 나올 수 있다. 이것을 방지하기 위해 lock 기능을 추가로 제공한다. S3와 Dynamo DB를 활용해서 Backends를 설정하는 방법은 아래와 같다. (S3 와 Dynamo DB가 미리 설치되었다고 가정한다.) ``` yaml terraform { backend "s3" { bucket = YOUR_S3_BUCKET_NAME dynamodb_table = YOUR_DYNAMODB_NAME region = "ap-northeast-2" key = "test-terraform/terraform.tfstate" encrypt = true } } ``` Buckets에 관한 더 자세한 사항은 [공식문서](https://www.terraform.io/docs/backends/index.html)를 참고하도록 한다. ### Resources 테라폼에서 인프라의 리소스는 전부 resource를 통해 설정한다. Provider 와 데이터를 다루는 variable이나 data를 제외하고는 사실상 테라폼에서 이루어지는 설정들은 전부 resources를 통해 설정한다. 그렇기때문에 resources로 설정할 수 있는 부분은 매우 방대하여 모든걸 외우고 사용하기 보다는 인프라 설계를 마친 후 필요한 리소스를 설정하는 법을 찾아서 적용하는 편이 빠르다. 대표적으로 아래는 VPC 를 생성하는 파일이다. ``` yaml resource "aws_vpc" "demo" { cidr_block = "10.0.0.0/16" tags = map( "Name", "terraform-eks-demo-node", "kubernetes.io/cluster/${var.cluster-name}", "shared" ) } ``` ### Input Variables 변수들을 설정해서 담을 수 있다. 해당 변수들은 사전에 정의해 둘수도 있고, apply를 하면서 파일 단위로 참조할 수도 있다. ``` yaml variable "cluster-name" { default = "terraform-eks-demo" type = string } ``` ### Data Sources Data 파일들을 정의하여 terraform 설정에서 참조할 수 있다. 블록을 비어두면 해당 데이터 내의 모든 값들을 참조할 수 있다. ``` yaml # Using these data sources allows the configuration to be # generic for any region. data "aws_region" "current" {} ``` ### Output Values Output values를 선언하면 apply 후 output으로 알려준다. ``` shell output "instance_ip_addr" { value = aws_instance.server.private_ip description = "The private IP address of the main server instance." } ``` 이 외에도 모듈을 설정하여 파일 구조를 정의할 수도 있으며 각 설정 안에 반복을 하거나 필터를 하는 등 추가적인 문법들이 존재한다. 그러나 위의 설정가지고도 충분히 인프라를 배포하고 관리할 수 있으며 바로 적용할 수 있다. 해당 설정 파일들은 전부 설정 한 후 `plan`을 통해 설치과정을 미리 테스트 해 볼수 있다. ``` shell $ terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. data.http.workstation-external-ip: Refreshing state... data.aws_region.current: Refreshing state... data.aws_availability_zones.available: Refreshing state... ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: . . . . # aws_vpc.demo will be created + resource "aws_vpc" "demo" { + arn = (known after apply) + assign_generated_ipv6_cidr_block = false + cidr_block = "10.0.0.0/16" + default_network_acl_id = (known after apply) + default_route_table_id = (known after apply) + default_security_group_id = (known after apply) + dhcp_options_id = (known after apply) + enable_classiclink = (known after apply) + enable_classiclink_dns_support = (known after apply) + enable_dns_hostnames = (known after apply) + enable_dns_support = true + id = (known after apply) + instance_tenancy = "default" + ipv6_association_id = (known after apply) + ipv6_cidr_block = (known after apply) + main_route_table_id = (known after apply) + owner_id = (known after apply) + tags = { + "Name" = "terraform-eks-demo-node" + "kubernetes.io/cluster/terraform-eks-demo" = "shared" } } Plan: 18 to add, 0 to change, 0 to destroy. ``` Terraform plan을 할 경우 위와같이 설치, 삭제될 resource 들의 정보가 나오며 마지막에 줄에 표시된 정보를 통해 바뀌게 될 상태 정보를 나타낸다. 위에서 말했듯이 테라폼은 멱등성을 지원하므로 상태를 선언만 하게될 것이다. 만약 테라폼을 설치하고 다시 plan 명령어를 실행하면 0 to add, 0 to change, 0 to destroy가 나올 것이다. 존재하도록 선언한 경우 존재하면 변경될 것이 없기 때문이다. plan에서 별다른 문제 없이 성공적으로 나왔다면 apply를 실행 시켜준다. ``` shell $ terraform apply ``` 인프라를 삭제할 경우 `destroy`를 실행시켜주면 관련된 모든 리소스들이 삭제된다. ``` shell $ terraform destroy ``` 테라폼을 삭제할 경우 삭제될 리소스들의 관계를 정확히 확인하고 삭제하도록 한다. 테라폼으로 인해 인프라를 배포, 관리하는 것이 소스로 쉽고 간편하게 할 수 있게 되었다. 덕분에 버전 관리, 소스 공유등으로 언제 어디서든 동일한 환경을 구성할 수 있게 되었으며 다양한 클라우드 환경이 있는 지금, 동일한 문법으로 인프라를 설정할 수 있게 되었다. AWS의 cloudformation을 사용 해보았다면 terraform으로 구성하는 것이 얼마나 쉽고 간편한 것인지를 알게 될 것이다. 또한 별다른 러닝커브 없이 쉽게 문법들을 찾아 적용 할 수 있으며 남은 문제는 단 하나, 어떻게 하면 인프라를 잘 구성할 것인가 이다. 나머지는 모두 terraform에게 맡겨두면 된다.