Terraform Study #4
โํ ๋ผํผ์ผ๋ก ์์ํ๋ IaCโ ์ฑ ์ผ๋ก ์งํํ๋ Terraform ์คํฐ๋[T101] 4์ฃผ์ฐจ ์ ๋ฆฌ๋ด์ฉ์ ๋๋ค.
4์ฃผ์ฐจ
์ด๋ฒ ์ฃผ์ฐจ์์๋ ๊ธฐ๋ณธ ๋ฌธ๋ฒ์ ๋์ด, ์ฝ๋๋ฅผ ๊ตฌ์กฐํํ๊ณ ํ์ ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ๋ฐฐ์ด๋ค. ๊ตฌ์ฒด์ ์ผ๋ก๋ module๊ณผ state์ ๋ํด ํ์ตํ๋ฉฐ, ํ์ ๊ณผ ๊ด๋ จ๋ ๋ด์ฉ์ 5์ฃผ์ฐจ์์ ๋ ์์ธํ๊ฒ ๋ค๋ฃฌ๋ค.
State
์๋๋ 1์ฃผ์ฐจ ์ ๋ฆฌ๋ด์ฉ์ด๋ค. ํ ๋ผํผ์์๋ State ํ์ผ์ Serial์ ๊ธฐ์ค์ผ๋ก backup ๊ด๋ฆฌํ๋ค.
Terraform์
.tfstate
ํ์ผ ๋ด์serial
๊ฐ์ ์ํ ํ์ผ์ ๋ฒ์ ์ ๋ํ๋ด๋ฉฐ, ๋์์ฑ ์ ์ด์ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ํ์ธ์๋ ์ค์ํ ์ญํ ์ ํ๋ค. ์ด ๊ฐ์ Terraform ๋ช ๋ น์ด ์คํ๋ ๋๋ง๋ค ์๋์ผ๋ก ์ฆ๊ฐํ์ฌ, ์ํ ํ์ผ์ ์ต์ ์ฑ๊ณผ ์ผ๊ด์ฑ์ ์ ์งํ๋ค. ๊ทธ๋ ๊ธฐ์ backup ํ์ผ์ด ํ์ฌ state ํ์ผ๋ณด๋ค serial ๋ฒํธ๊ฐ ๋ฎ๋ค.
์ด๋ก ์ ์ธ ๋ด์ฉ
์ํ ํ์ผ์ ๋ฐฐํฌํ ๋๋ง๋ค ๋ณ๊ฒฝ๋๋ ํ๋ผ์ด๋น API์ด๋ฉฐ, ์ค์ง ํ ๋ผํผ ๋ด๋ถ์์ ์ฌ์ฉ์ฉ๋์ด๋ ์ง์ ํธ์งํ๊ฑฐ๋ ์์ฑํด์๋ ์๋๋ค. (ํ์ผ ๋ด๋ถ ๋ฐ์ดํฐ๋ฅผ ํตํด, API๋ฅผ ์์ฒญํ๋ ๊ฒ ๊ฐ๋ค.)
๋ง์ฝ, ํ ๋ผํผ์ ํตํด ํ์ ์ ์งํํด์ผ ํ๋ค๋ฉด state ํ์ผ์ ๊ด๋ฆฌํด์ผ ํ๋ค. ์ด๋๋ 1์ฃผ์ฐจ๋ ์งํํ๋ ์๊ฒฉ ๋ฐฑ์๋๋ฅผ ์ฌ์ฉํ๋ค.
ํ ๋จ์ ์ด์์ ํ์ํ ์ ์ ๋ค์๊ณผ ๊ฐ๋ค.
- state ํ์ผ์ ๊ณต์ ์คํ ๋ฆฌ์ง
- Locking(ํ๋ช ์ ํ๋ช ์ฉ)
- ํ์ผ ๊ฒฉ๋ฆฌ(dev, stage ๋ฑ ํ๊ฒฝ ๋ณ ๊ฒฉ๋ฆฌ๊ฐ ํ์)
์๋๋ VCS๋ฅผ ์ฌ์ฉํ ๋, ๋ฐ์ํ๋ ๋ฌธ์ ์ ์ด๋ค.
- VCS: ์๋์ผ๋ก ์ํํ์ผ์ push, pull ํด์ผ ํ๋ ํด๋จผ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์๋ค.++ Lock ๊ธฐ๋ฅ์ด ์๋ค.
๊ฒฐ๊ตญ, ํ ๋ผํผ์ ์ง์ํ๋ ์๊ฒฉ ๋ฐฑ์๋๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค. S3, Terraform Cloud ๋ฑ์ด ์๋ค.
๊ด๋ จ ์ค์ต
State ํ์ผ์๋ ๋ฆฌ์์ค์ ๋ํ ๋ชจ๋ ๊ฒ์ด ๋ด๊ฒจ์๋ค. ์๋์ ์ค์ต์ ํตํด, ์ํ ํ์ผ ๊ด๋ฆฌ์ ์ค์์ฑ์ ํ์ธํด๋ณธ๋ค.
- ํจ์ค์๋ ๋ฆฌ์์ค ์ฝ๋
1
2
3
4
5
resource "random_password" "mypw" {
length = 16
special = true
override_special = "!#$%"
}
์๋์ ๋ช ๋ น์ด๋ฅผ ํตํด ๋ฆฌ์์ค ์์ฑ
1
terraform init && terraform plan && terraform apply -auto-approve
ํ ๋ผํผ ๋ช ๋ น์ด๋ฅผ ํตํด ๋ฆฌ์์ค๋ฅผ ํ์ธํ๋ฉด, ์ํฌ๋ฆฟ ์ ๋ณด๋ ์๋ ค์ฃผ์ง ์๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
โฏ terraform state show random_password.mypw
# random_password.mypw:
resource "random_password" "mypw" {
bcrypt_hash = (sensitive value)
id = "none"
length = 16
lower = true
min_lower = 0
min_numeric = 0
min_special = 0
min_upper = 0
number = true
numeric = true
override_special = "!#$%"
result = (sensitive value)
special = true
upper = true
}
ํ์ง๋ง, state ํ์ผ์ ํ์ธํด๋ณด๋ฉด ๋ฆฌ์์ค์ ๋ํ ์ ๋ณด๊ฐ ์ ๋ถ ์กด์ฌํ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
...
"resources": [
{
"mode": "managed",
"type": "random_password",
...
"instances": [
{
"schema_version": 3,
"attributes": {
"bcrypt_hash": "$2a$10$pLMnmRKSY52ageoVumPlpuP5dyo2GZpomOxo6MsQetO/F28dR2ge2",
"id": "none",
...
"result": "CLTOsYB9zWifY9WT",
"special": true,
"upper": true
},
"sensitive_attributes": []
}
]
}
],
"check_results": null
}
ํ ๋ผํผ ์ฝ์์์๋ sensitive value๋ ๋ณด์ด์ง ์๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
echo "random_password.mypw" | terraform console
{
"bcrypt_hash" = (sensitive value)
"id" = "none"
"keepers" = tomap(null) /* of string */
"length" = 16
...
"override_special" = "!#$%"
"result" = (sensitive value)
"special" = true
"upper" = true
}
.tfstate
Terraform์ .tfstate
ํ์ผ ๋ด์ serial
๊ฐ์ ์ํ ํ์ผ์ ๋ฒ์ ์ ๋ํ๋ด๋ฉฐ, ๋์์ฑ ์ ์ด์ ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ํ์ธ์๋ ์ค์ํ ์ญํ ์ ํ๋ค. ์ด ๊ฐ์ Terraform ๋ช
๋ น์ด ์คํ๋ ๋๋ง๋ค ์๋์ผ๋ก ์ฆ๊ฐํ์ฌ, ์ํ ํ์ผ์ ์ต์ ์ฑ๊ณผ ์ผ๊ด์ฑ์ ์ ์งํ๋ค. ๊ทธ๋ ๊ธฐ์ backup ํ์ผ์ด ํ์ฌ state ํ์ผ๋ณด๋ค serial ๋ฒํธ๊ฐ ๋ฎ๋ค.
์ ํ | ๊ตฌ์ฑ ๋ฆฌ์์ค ์ ์ | State ๊ตฌ์ฑ ๋ฐ์ดํฐ | ์ค์ ๋ฆฌ์์ค | ๊ธฐ๋ณธ ์์ ๋์ |
---|---|---|---|---|
1 | ์์ | ย | ย | ๋ฆฌ์์ค ์์ฑ |
2 | ์์ | ์์ | ย | ๋ฆฌ์์ค ์์ฑ |
3 | ์์ | ์์ | ์์ | ๋์ ์์ |
4 | ย | ์์ | ์์ | ๋ฆฌ์์ค ์ญ์ |
5 | ย | ย | ์์ | ๋์ ์์ |
6 | ์์ | ย | ์์ | ย |
-refresh=false
์ต์ ํ์ฌ์ stateํ์ผ๊ณผ ํ ๋ผํผ์ฝ๋๋ฅผ ๋น๊ตํ์ฌ ๊ทธ๋๋ก ์ ์ฉํ๋ค. ์๊ฒฉ ๋ฆฌ์์ค์ ์ค์ ์ํ๋ ํ์ธํ์ง ์์ผ๋ฉฐ ๋ง์ฝ ์๊ฒฉ๋ฆฌ์์ค๊ฐ ์ ๊ฑฐ๋์์ด๋ ๋ค์ ์์ฑํ์ง ์๋๋ค.
์ ํ6๋ฒ ์ค์ต์งํ
- IAM user๋ฅผ ์ถ๊ฐํ๋ ๋ฆฌ์์ค ํ์ธ
1
2
3
4
5
6
7
8
9
10
11
locals {
name = "mytest"
}
resource "aws_iam_user" "myiamuser1" {
name = "${local.name}1"
}
resource "aws_iam_user" "myiamuser2" {
name = "${local.name}2"
}
- ๋ฐฐํฌ์งํ
1
terraform apply -auto-approve
- ๋ฐฐํฌ ์ํ ํ์ธ
1
2
3
4
aws iam list-users | jq '.Users[] | .UserName'
"admin"
"mytest1"
"mytest2"
- tfstate ํ์ผ ์ญ์
1
2
3
rm -rf terraform.tfstate*
โฏ ls terraform.tfstate*
zsh: no matches found: terraform.tfstate*
- Plan ๋ช ๋ น์ ์คํํ๋ฉด, ์๋์ ๊ฐ์ด ์ด๋ฏธ ์กด์ฌํ๋ ๋ฆฌ์์ค๋ฅผ ํ์ ํ์ง ๋ชปํ๊ณ ์๋กญ๊ฒ ์์ฑํ๋ ค๊ณ ํจ.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ 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_iam_user.myiamuser1 will be created
+ resource "aws_iam_user" "myiamuser1" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "mytest1"
+ path = "/"
+ tags_all = (known after apply)
+ unique_id = (known after apply)
}
- apply ๋ช ๋ น์ด๋ฅผ ์คํํ๋ฉด, EntityAlreadyExists ์๋ฌ ๋ฐ์
1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ terraform apply
...
Plan: 2 to add, 0 to change, 0 to destroy.
aws_iam_user.myiamuser2: Creating...
aws_iam_user.myiamuser1: Creating...
โท
โ Error: creating IAM User (mytest1): **EntityAlreadyExists: User with name mytest1 already exists.**
โ status code: 409, request id: e32ae858-e9eb-4c3a-a6ab-d7dba9f8bbd8
โ
โ with aws_iam_user.myiamuser1,
โ on main.tf line 5, in resource "aws_iam_user" "myiamuser1":
โ 5: resource "aws_iam_user" "myiamuser1" {
โ
โต
- ์ด๋ด๋๋ import ๋ช
๋ น์ด๋ฅผ ํตํด ํด๊ฒฐํ ์ ์๋ค, IAM user์ ID๋ ์ ์ ์ ์ด๋ฆ์ด๋ฏ๋ก ์๋์ ๋ช
๋ น์ด๋ฅผ ์คํํ๋ค.
terraform import [options] ADDRESS ID
1
2
3
4
5
6
7
8
9
10
terraform import aws_iam_user.myiamuser1 mytest1
aws_iam_user.myiamuser1: Importing from ID "mytest1"...
aws_iam_user.myiamuser1: Import prepared!
Prepared aws_iam_user for import
aws_iam_user.myiamuser1: Refreshing state... [id=mytest1]
Import successful!
The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
- tfstate ํ์ธ, myiamuser1์ ์ํํ์ผ์ด ์ถ๊ฐ๋์๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"version": 4,
"terraform_version": "1.5.6",
"serial": 4,
"lineage": "0e52f56e-fe1e-d6e7-acb3-f521a3c2f365",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_iam_user",
"name": "myiamuser1",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
...
"id": "mytest1",
"name": "mytest1",
},
"sensitive_attributes": [],
...
}
์ํฌ์คํ์ด์ค
State๋ฅผ ๊ด๋ฆฌํ๋ ๋ ผ๋ฆฌ์ ์ธ ๊ฐ์ ๊ณต๊ฐ์ ์ํฌ์คํ์ด์ค๋ผ๊ณ ํ๋ค.
๊ฐ๋ฐ์ฉ ํ๊ฒฝ, ์คํ
์ด์ง ํ๊ฒฝ, ์ด์ํ๊ฒฝ์ ๋๋ถ๋ถ ๊ฐ์ง๊ณ ์๋ค. ๊ฑฐ์ ์ ์ฌํ ํ๊ฒฝ์ ๊ตฌ์ถํ๋ค๊ณ ํ๋ฉด ์ฌ๋ฌ ๊ฐ์ ํ๋ก์ ํธ๋ฅผ ํตํด ์ด์ํ ์ ์์ง๋ง ๋์ผํ ํ๊ฒฝ์ ๊ตฌ์ฑํ๋ค๋ฉด ์ด๋ ์ผ๊ด์ฑ ์ ์ง์ ์ข์ง ์๋ค. ํ
๋ผํผ์์๋ ์ด๋ฅผ ์ํด workspace๋ฅผ ์ง์ํ๋ค. ํ๋์ ์ฝ๋๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ๋ฌ ๊ฐ์ ํ๊ฒฝ์ ๊ตฌ์ฑํ ์ ์์ผ๋ฉฐ terraform.workspace
๋ณ์๋ฅผ ํตํด ํ๊ฒฝ๋ง๋ค ๋ฆฌ์์ค๋ฅผ ์กฐ์ ํ ์ ์๋ค.
vs ์๋๋ ์ํฌ์คํ์ด์ค๊ฐ ์๋ ์ฌ๋ฌ ๊ฐ์ ํ๋ก์ ํธ๋ก ๊ตฌ์ฑํ ๋ชจ์ต์ด๋ค.
[์ธ๊ธํด์ฃผ์ ์ํฌ์คํ์ด์ค์ ์ฅ๋จ์ ]
[์ฅ์ ]
- ํ๋์ ๋ฃจํธ ๋ชจ๋์์ ๋ค๋ฅธ ํ๊ฒฝ์ ์ํ ๋ฆฌ์์ค๋ฅผ ๋์ผํ ํ ๋ผํผ ๊ตฌ์ฑ์ผ๋ก ํ๋ก๋น์ ๋ํ๊ณ ๊ด๋ฆฌ
- ๊ธฐ์กด ํ๋ก๋น์ ๋๋ ํ๊ฒฝ์ ์ํฅ์ ์ฃผ์ง ์๊ณ ๋ณ๊ฒฝ ์ฌํญ ์คํ ๊ฐ๋ฅ
- ๊น์ ๋ธ๋์น ์ ๋ต์ฒ๋ผ ๋์ผํ ๊ตฌ์ฑ์์ ์๋ก ๋ค๋ฅธ ๋ฆฌ์์ค ๊ฒฐ๊ณผ ๊ด๋ฆฌ - [์ฐธ๊ณ : ํํด - Git ๋ธ๋์น ์ ๋ต ์๋ฆฝ์ ์ํ ์ ๋ฌธ๊ฐ์ ์กฐ์ธ๋ค]
[๋จ์ ]
- State๊ฐ ๋์ผํ ์ ์ฅ์(๋ก์ปฌ ๋๋ ๋ฐฑ์๋)์ ์ ์ฅ๋์ด State ์ ๊ทผ ๊ถํ ๊ด๋ฆฌ๊ฐ ๋ถ๊ฐ๋ฅ(์ด๋ ค์)
- ๋ชจ๋ ํ๊ฒฝ์ด ๋์ผํ ๋ฆฌ์์ค๋ฅผ ์๊ตฌํ์ง ์์ ์ ์์ผ๋ฏ๋ก ํ ๋ผํผ ๊ตฌ์ฑ์ ๋ถ๊ธฐ ์ฒ๋ฆฌ๊ฐ ๋ค์ ๋ฐ์ ๊ฐ๋ฅ
- ํ๋ก๋น์ ๋ ๋์์ ๋ํ ์ธ์ฆ ์์๋ฅผ ์๋ฒฝํ ๋ถ๋ฆฌํ๊ธฐ ์ด๋ ค์
- ๊ฐ์ฅ ํฐ ๋จ์ ์ ์๋ฒฝํ ๊ฒฉ๋ฆฌ๊ฐ ๋ถ๊ฐ๋ฅ
- โ ํด๊ฒฐ๋ฐฉ์ 1. ํด๊ฒฐํ๊ธฐ ์ํด ๋ฃจํธ ๋ชจ๋์ ๋ณ๋๋ก ๊ตฌ์ฑํ๋ ๋๋ ํฐ๋ฆฌ ๊ธฐ๋ฐ์ ๋ ์ด์์์ ์ฌ์ฉํ ์ ์๋ค.
- โ ํด๊ฒฐ๋ฐฉ์ 2. Terraform Cloud ํ๊ฒฝ์ ์ํฌ์คํ์ด์ค๋ฅผ ํ์ฉ
Module
๋ชจ๋์ ๋๋ถ๋ถ์ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์ ์ฐ์ด๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํจํค์ง์ ์ญํ ์ด ๋น์ทํ๋ค.
์ค๋ณต๋๊ฑฐ๋, ์์ฃผ์ฐ๋ ์ฝ๋๋ฅผ ๋ชจ๋ํํด์ ํธํ๊ฒ ์ฌ์ฌ์ฉํ ์ ์๋ค.
๋ชจ๋ ๋๋ ํฐ๋ฆฌ ํ์์ terraform-<ํ๋ก๋ฐ์ด๋ ์ด๋ฆ>-<๋ชจ๋ ์ด๋ฆ>
ํ์์ ์ ์ํ๋ค.
์ด ํ์์ Terraform Cloud, Terraform Enterprise์์๋ ์ฌ์ฉ๋๋ ๋ฐฉ์์ผ๋ก 1) ๋๋ ํฐ๋ฆฌ ๋๋ ๋ ์ง์คํธ๋ฆฌ ์ด๋ฆ์ด ํ ๋ผํผ์ ์ํ ๊ฒ์ด๊ณ , 2) ์ด๋ค ํ๋ก๋ฐ์ด๋์ ๋ฆฌ์์ค๋ฅผ ํฌํจํ๊ณ ์์ผ๋ฉฐ, 3) ๋ถ์ฌ๋ ์ด๋ฆ์ด ๋ฌด์์ธ์ง ํ๋ณํ ์ ์๋๋ก ํ๋ค.
๊ตฌ์กฐ
์๋์ ๊ฐ์ด ๋ฃจํธ ๋ชจ๋์์ ์์ ๋ชจ๋์ ์ฐธ์กฐํ๋ค. ์์๋ชจ๋์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ญํ ์ ์ํํ๊ณ , ๋ฃจํธ ๋ชจ๋์ด main ํจ์์ด๋ค. ์์ ๋ชจ๋์ ํธ์ถํ ๋, ๋ณ์๋ ๋ง๊ฒ ๋์ ํ๋ค.
์ถ์ฒ: https://jloudon.com/cloud/Azure-Policy-as-Code-with-Terraform-Part-1/
๊ฐ๋จํ ์์๋ฅผ ํ์ธํด๋ณด๋ฉด, ์๋์ ์์ ๋ชจ๋์ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ํด๋ณด์. isDB๋ผ๋ ๋ณ์์ ๊ฐ์ ๋์ ํด์ค์ผํ๊ณ , id์ pw๋ฅผ ์ถ๋ ฅํ ์ ์๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# main.tf
resource "random_pet" "name" {
keepers = {
ami_id = timestamp()
}
}
# DB์ผ ๊ฒฝ์ฐ Password ์์ฑ ๊ท์น์ ๋ค๋ฅด๊ฒ ๋ฐ์
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)
}
๋ฃจํธ ๋ชจ๋
โmypw1โ์ ๊ฒฝ์ฐ๋ ๋ณ์๋ฅผ ๋์
ํ์ง ์์์ผ๋, isDB
์ ๊ธฐ๋ณธ๊ฐ์ด ๋ค์ด๊ฐ๋ค. ๋ค์๊ณผ ๊ฐ์ด ๋ชจ๋์ ํตํด ์ฝ๋๋ฅผ ๊ตฌ์กฐํํ๊ณ ์ฌ์ฌ์ฉํ ์ ์๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
}
ํ๋ก๋ฐ์ด๋ ์ ์
๋ฃจํธ๋ชจ๋์์ ํ๋ก๋ฐ์ด๋๋ฅผ ์ ์ํ๋ ๊ฒ์ด ์ข๋ค. ๋ง์ฝ, ์์๋ชจ๋์์ ํ๋ก๋ฐ์ด๋๋ฅผ ์ ์ํ๋ฉด ๋ฃจํธ ๋ชจ๋์ ๋ฒ์ ์ด ๋ค๋ฅด๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ๊ณ ๋ชจ๋์ ๋ฐ๋ชฉ๋ฌธ์ ์ธ ์ ์๋ค.
์๋์ module โexampleโ๊ณผ ๊ฐ์ด ๋ฃจํธ ๋ชจ๋์ ํ๋ก๋ฐ์ด๋๋ฅผ ์ ์ธํ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# The default "aws" configuration is used for AWS resources in the root
# module where no explicit provider instance is selected.
provider "aws" {
region = "us-west-1"
}
# An alternate configuration is also defined for a different
# region, using the alias "usw2".
provider "aws" {
alias = "usw2"
region = "us-west-2"
}
# An example child module is instantiated with the alternate configuration,
# so any AWS resources it defines will use the us-west-2 region.
module "example" {
source = "./example"
providers = {
aws = aws.usw2
}
}
๋ชจ๋์ ์์์ ์งํํ๋ฏ์ด ๋ก์ปฌํ์ผ๋ก ๊ฐ๋ฅํ๋ฉฐ ํ ๋ผํผ ๋ ์ง์คํธ๋ฆฌ, ๊นํ๋ธ ๋ฑ์์ ๊ฐ์ ธ์์ ์ธ ์ ์๋ค.
ex) Terraform registry์์ ๊ฐ์ ธ์ค๊ธฐ
1
2
3
4
module "consul" {
source = "hashicorp/consul/aws"
version = "0.1.0"
}
ํ์
S3๋ฅผ ํตํด, ๋ฐฑ์๋๋ฅผ ๊ตฌ์ฑํ๋ ๊ฒ์ 1์ฃผ์ฐจ๋ ์งํํ๋ค. ์ฌ๊ธฐ์๋ Terraform Cloud๋ฅผ ์ด์ฉํ์ฌ TFC ๋ฐฑ์๋๋ฅผ ๊ตฌ์ฑํด๋ณธ๋ค. ๋น์ฐํ Lock ๊ธฐ๋ฅ๊ณผ ๋ฒ์ ๊ด๋ฆฌ๋ ์ง์ํ๋ค.
Terraform Cloud
state ๊ด๋ฆฌ๋ฅผ ์งํํ๋ TFC๋ ๋ฌด์์ด๋ผ๊ณ ํ๋ค.
[TFC]
- ์ ๊ณต ๊ธฐ๋ฅ : ๊ธฐ๋ณธ ๊ธฐ๋ฅ ๋ฌด๋ฃ, State ํ์คํ ๋ฆฌ ๊ด๋ฆฌ, State lock ๊ธฐ๋ณธ ์ ๊ณต, State ๋ณ๊ฒฝ์ ๋ํ ๋น๊ต ๊ธฐ๋ฅ
- Free Plan ์ ๋ฐ์ดํธ : ์ฌ์ฉ์ 5๋ช โ ๋ฆฌ์์ค 500๊ฐ, ๋ณด์ ๊ธฐ๋ฅ(SSO, Sentinel/OPA๋ก Policy ์ฌ์ฉ) - ๋งํฌ
์ํฌ์คํ์ด์ค ์์ฑ
- https://app.terraform.io/ ๋งํฌ ์ ์ ํ ๊ณ์ ์์ฑ
- workflow ์ ํ ํ๋ฉด์์ Create a new organization ์ ํ
- Connect
- GitHub์ ๊ฐ์ ๋ฒ์ ๊ด๋ฆฌ ์์คํ ์ ์ฐ๊ฒฐํ ๊ฑฐ๋ฉด VCS๋ฅผ ์ ํํ๋ค.
- CLI-driven ์ ํ
terraform login์ ์งํํ๋ค.
1
2
$ terraform login
[ํ ํฐ ์
๋ ฅ]
ํ ํฐ ํ์ธ
1
2
3
4
5
6
7
8
cat ~/.terraform.d/credentials.tfrc.json | jq
{
"credentials": {
"app.terraform.io": {
"token": "YMgr4VM...EWGuw"
}
}
}
provider.tf์์ ํ ๋ผํผ ํด๋ผ์ฐ๋๋ฅผ ์ ์ํ๋ค.
1
2
3
4
5
6
7
8
9
10
terraform {
cloud {
organization = "kane-org" # ์์ฑํ ORG ์ด๋ฆ ์ง์
hostname = "app.terraform.io" # default
workspaces {
name = "terraform-stduy" # ์์ผ๋ฉด ์์ฑ๋จ
}
}
}
์ดํ, terraform init ๋ช
๋ น์ด๋ฅผ ์คํํ๋ฉด .terraform
๋๋ ํฐ๋ฆฌ๊ฐ ์์ฑ๋๊ณ ์์ ์ํํ์ผ์ด ์์ฑ๋๋ค. ์๋๋ ์ํํ์ผ ์ธ๋ถ๋ด์ฉ์ด๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"version": 3,
"serial": 1,
...
"backend": {
"type": "cloud",
"config": {
"hostname": "app.terraform.io",
"organization": "kane-org",
"token": null,
"workspaces": {
"name": "terraform-stduy",
"tags": null
}
},
์ด์ init && plan ๋ช ๋ น์ด๋ฅผ ์คํํ๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ํด๋ผ์ฐ๋์์ ๋์ํ๋ค. [terraform cloud local ์ค์ x]
์ค์ ์ ํตํด, terraform ์์ ์ ๋ชจ๋ ๋ก์ปฌ์์ ๋๋ฆฌ๊ณ state ํ์ผ๋ง ์ ๋ก๋ํ ์ ์๋ค.
Plan์ด ๋ชจ๋ ๋์ํ๋ฉด, ์๋์ ๊ฐ์ด UI๋ก ๋ฐฐํฌ๋ ๋ฆฌ์์ค๋ฅผ ์๋ ค์ค๋ค.
ํ์คํ GUI๋ก ๋ณด๋ ๊น๋ํ ๊ฒ ๊ฐ๋ค. ํนํ ๋ฆฌ์์ค๊ฐ ๋ง์์ก์ ๋ ๋ณด๊ธฐํธํ ๊ฒ ๊ฐ๋ค.
๋์ ๊ณผ์ 3
๊ฐ์ ์ฌ์ฉํ๊ธฐ ํธ๋ฆฌํ ๋ฆฌ์์ค๋ฅผ ๋ชจ๋ํ ํด๋ณด๊ณ , ํด๋น ๋ชจ๋์ ํ์ฉํด์ ๋ฐ๋ณต ๋ฆฌ์์ค๋ค ๋ฐฐํฌํด๋ณด์ธ์!
VPC, Subnet ๋ฑ EC2์ ํ์ํ ๋ฆฌ์์ค๋ฅผ ๋ชจ๋ํํด๋ดค๋ค. ๋ณด์๊ทธ๋ฃน์ ํฌํธ๋ SSH, HTTP๋ฅผ ์ด์ด๋จ๋ค.
module/main.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
locals { additional_tags = { Name = var.namespace } } resource "aws_vpc" "vpc" { cidr_block = "192.169.0.0/16" tags = local.additional_tags } data "aws_availability_zones" "available" { state = "available" } resource "aws_subnet" "public_subnet" { vpc_id = aws_vpc.vpc.id cidr_block = "192.169.1.0/24" availability_zone = data.aws_availability_zones.available.names[0] map_public_ip_on_launch = true tags = local.additional_tags } resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.vpc.id tags = local.additional_tags } resource "aws_route_table" "public_route_table" { vpc_id = aws_vpc.vpc.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.igw.id } tags = local.additional_tags } resource "aws_route_table_association" "public_rtb_assoc" { subnet_id = aws_subnet.public_subnet.id route_table_id = aws_route_table.public_route_table.id } resource "aws_security_group" "web_sg" { name = var.namespace vpc_id = aws_vpc.vpc.id ingress { from_port = var.ssh_port to_port = var.ssh_port protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { from_port = var.http_port to_port = var.http_port protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } 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" "app" { ami = data.aws_ami.default.id instance_type = var.ec2_instance_type key_name = var.key_name vpc_security_group_ids = [aws_security_group.web_sg.id] subnet_id = aws_subnet.public_subnet.id tags = local.additional_tags }
module/output.tf
1 2 3 4
output "instance_public_ip" { value = aws_instance.app.public_ip description = "The public IP address of the App instance" }
module/variable.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
variable "ssh_port" { default = 22 type = number description = "SSH port" } variable "http_port" { default = 80 type = number description = "HTTP port" } variable "ec2_instance_type" { type = string description = "The type of EC2 instance to launch" } variable "key_name" { type = string description = "The key name to use for an EC2 instance" } variable "namespace" { type = string description = "env namespace" }
root/main.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
locals { env = { dev = { instance_type = "t3.micro" key_name = "m1" namespace = "dev" } prod = { instance_type = "t3.medium" key_name = "m1" namespace = "prod" } } } provider "aws" { region = "ap-northeast-2" } module "ec2_aws_amazone" { for_each = local.env source = "../../module/ec2" key_name = each.value.key_name ec2_instance_type = each.value.instance_type namespace = each.value.namespace } # output.tf output "module_output_instance_public_ip" { value = [ for k in module.ec2_aws_amazone : k.instance_public_ip ] }
์ด์ , ํ ๋ผํผ ๋ช ๋ น์ด๋ฅผ ์คํํ์ฌ ๋ฆฌ์์ค๋ฅผ ๋ฐฐํฌํ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ tf apply -auto-approve
module.ec2_aws_amazone["dev"].data.aws_availability_zones.available: Reading...
module.ec2_aws_amazone["prod"].data.aws_availability_zones.available: Reading...
module.ec2_aws_amazone["prod"].data.aws_ami.default: Reading...
module.ec2_aws_amazone["dev"].data.aws_ami.default: Reading...
module.ec2_aws_amazone["dev"].data.aws_availability_zones.available: Read complete after 1s [id=ap-northeast-2]
module.ec2_aws_amazone["prod"].data.aws_availability_zones.available: Read complete after 1s [id=ap-northeast-2]
module.ec2_aws_amazone["dev"].data.aws_ami.default: Read complete after 1s [id=ami-0ec77cfb1037681eb]
module.ec2_aws_amazone["prod"].data.aws_ami.default: Read complete after 1s [id=ami-0ec77cfb1037681eb]
Apply complete! Resources: 14 added, 0 changed, 0 destroyed.
Outputs:
module_output_instance_public_ip = [
"3.35.235.54",
"13.124.205.208",
]
AWS ์ฝ์์์ ๋ฐฐํฌ๋ ๋ชฉ๋ก์ ํ์ธํ๋ค.
ํ๊ฒฝ ๋ณ๋ก, ๋ฐฐํฌ๊ฐ ์ ๋ ๋ชจ์ต์ ํ์ธํ ์ ์๋ค.