Terraform Study #5
โํ ๋ผํผ์ผ๋ก ์์ํ๋ IaCโ ์ฑ ์ผ๋ก ์งํํ๋ Terraform ์คํฐ๋[T101] 5์ฃผ์ฐจ ์ ๋ฆฌ๋ด์ฉ์ ๋๋ค.
5์ฃผ์ฐจ
์ํฌํ๋ก
Terraform_remote_state
๋ฐ์ดํฐ ์์ค ์ค ํ๋๋ก, ๋ค๋ฅธ Terraform state ํ์ผ์ ๊ฐ์ ์ฐธ์กฐํ ์ ์๊ฒ ํด์ฃผ๋ ๊ธฐ๋ฅ์ด๋ค. ํ Terraform ํ๋ก์ ํธ์ ์ถ๋ ฅ ๋ณ์๋ฅผ ๋ค๋ฅธ Terraform ํ๋ก์ ํธ์์ ์ฝ์ด์ฌ ์ ์์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ๋ค์ํ ํ๋ก์ ํธ๋ ํ๊ฒฝ ๊ฐ์ ์ข ์์ฑ์ ๊ด๋ฆฌํ ์ ์๊ฒ ๋๋ค.
์ฌ๊ธฐ์ network ์ฝ๋์ ec2์ ์ฝ๋๋ฅผ ๋ถ๋ฆฌํ ๋ค, ๊ฐ๊ฐ terraform cloud์ ์ฌ๋ฆฌ๊ณ ์ด๋ฅผ terraform_remote_state์ ํตํด ์ฐ๊ฒฐํ๋ ๊ฒ์ ํ ์คํธํด๋ณธ๋ค.
- network
backend.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
terraform { cloud { organization = "kane-org" # ์์ฑํ ORG ์ด๋ฆ ์ง์ hostname = "app.terraform.io" # default workspaces { name = "network" # ์์ผ๋ฉด ์์ฑ๋จ } } required_providers { aws = { source = "hashicorp/aws" version = ">= 4.58" } } required_version = ">= 0.13" }
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
locals { additional_tags = { Terraform = "true" Environment = "Network" } } resource "aws_vpc" "kane_vpc" { cidr_block = "10.10.0.0/16" enable_dns_support = true enable_dns_hostnames = true tags = { Name = "t101-study" } } resource "aws_subnet" "kane_subnet" { vpc_id = aws_vpc.kane_vpc.id cidr_block = "10.10.1.0/24" availability_zone = "ap-northeast-2a" tags = { Name = "t101-subnet" } } resource "aws_internet_gateway" "kane_igw" { vpc_id = aws_vpc.kane_vpc.id tags = { Name = "t101-igw" } } resource "aws_route_table" "kane_rt" { vpc_id = aws_vpc.kane_vpc.id tags = { Name = "t101-rt" } } resource "aws_route_table_association" "kane_rtassociation1" { subnet_id = aws_subnet.kane_subnet.id route_table_id = aws_route_table.kane_rt.id } resource "aws_route" "kane_defaultroute" { route_table_id = aws_route_table.kane_rt.id destination_cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.kane_igw.id } resource "aws_security_group" "kane_sg" { vpc_id = aws_vpc.kane_vpc.id name = "T101 SG" description = "T101 Study SG" } resource "aws_security_group_rule" "kane_sginbound" { type = "ingress" from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] security_group_id = aws_security_group.kane_sg.id } resource "aws_security_group_rule" "kane_sgoutbound" { type = "egress" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] security_group_id = aws_security_group.kane_sg.id }
output.tf
1 2 3 4 5 6 7 8 9
output "aws_vpc_id" { value = aws_vpc.kane_vpc.id } output "aws_subnet_id" { value = aws_subnet.kane_subnet.id } output "aws_security_group_id" { value = aws_security_group.kane_sg.id }
- ec2
backend.tf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
terraform { cloud { organization = "kane-org" # ์์ฑํ ORG ์ด๋ฆ ์ง์ hostname = "app.terraform.io" # default workspaces { name = "ec2" # ์์ผ๋ฉด ์์ฑ๋จ } } required_providers { aws = { source = "hashicorp/aws" version = ">= 4.58" } } required_version = ">= 0.13" }
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
locals { additional_tags = { Terraform = "true" Environment = "EC2" } } data "aws_ami" "amazonlinux2" { most_recent = true filter { name = "owner-alias" values = ["amazon"] } filter { name = "name" values = ["amzn2-ami-hvm-*-x86_64-ebs"] } owners = ["amazon"] } data "tfe_outputs" "network" { organization = "kane-org" workspace = "network" } resource "aws_instance" "kane_ec2" { ami = data.aws_ami.amazonlinux2.id associate_public_ip_address = true instance_type = "t2.micro" vpc_security_group_ids = ["${data.tfe_outputs.network.values.aws_security_group_id}"] subnet_id = data.tfe_outputs.network.values.aws_subnet_id user_data_replace_on_change = true }
output.tf
1 2 3 4 5 6 7 8
output "instance_id" { value = aws_instance.kane_ec2.id description = "The ID of the App instance" } output "instance_public_ip" { value = aws_instance.kane_ec2.public_ip description = "The public IP address of the App instance" }
๋จผ์ network ๋ชจ๋์ ์คํํ๋ค. network ๋ชจ๋์ ์คํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด vpc, subnet, ๋ณด์๊ทธ๋ฃน, igw ๋ฑ์ด ์์ฑ๋๊ณ state ํ์ผ์ terraform cloud๋ก ์ ๋ก๋๋๋ค.
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
...
aws_vpc.kane_vpc: Creating...
aws_vpc.kane_vpc: Still creating... [10s elapsed]
aws_vpc.kane_vpc: Creation complete after 11s [id=vpc-0611bde7af568db76]
aws_internet_gateway.kane_igw: Creating...
aws_subnet.kane_subnet: Creating...
aws_security_group.kane_sg: Creating...
aws_route_table.kane_rt: Creating...
aws_internet_gateway.kane_igw: Creation complete after 0s [id=igw-0a40a4d39738b4bf1]
aws_route_table.kane_rt: Creation complete after 0s [id=rtb-07685b8b451a260ed]
aws_route.kane_defaultroute: Creating...
aws_subnet.kane_subnet: Creation complete after 0s [id=subnet-08e6ad517434f1842]
aws_route_table_association.kane_rtassociation1: Creating...
aws_route_table_association.kane_rtassociation1: Creation complete after 0s [id=rtbassoc-0994211199a244a68]
aws_route.kane_defaultroute: Creation complete after 0s [id=r-rtb-07685b8b451a260ed1080289494]
aws_security_group.kane_sg: Creation complete after 1s [id=sg-0002c365bfa9ad634]
aws_security_group_rule.kane_sgoutbound: Creating...
aws_security_group_rule.kane_sginbound: Creating...
aws_security_group_rule.kane_sgoutbound: Creation complete after 0s [id=sgrule-1882620294]
aws_security_group_rule.kane_sginbound: Creation complete after 1s [id=sgrule-1795632479]
Apply complete! Resources: 9 added, 0 changed, 0 destroyed.
Outputs:
aws_security_group_id = "sg-0002c365bfa9ad634"
aws_subnet_id = "subnet-08e6ad517434f1842"
aws_vpc_id = "vpc-0611bde7af568db76"
์ดํ ec2 ๋ชจ๋์ ์คํํ๋ค. ์๋์ ์ฝ๋๋ฅผ ํตํด ์์์ ์คํ๋ network ๋ชจ๋์ state ํ์ผ์ ์ฝ์ด ์ฐธ์กฐํ ์ ์๋ค.
1
2
3
4
data "tfe_outputs" "network" {
organization = "kane-org"
workspace = "network"
}
๋ฐ์ดํฐ์์ค๋ฅผ ์ด์ฉํ์ฌ EC2์ ๋คํธ์ํฌ ๊ด๋ จ ์ฌํญ์ด ์ค์ ๋๋ค. ์์ฑ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด ๋ฐ์ดํฐ์์ค๋ฅผ ํตํด ๊ฐ์ ธ์ค๋ ๊ฐ๋ค์ sensitive value๋ก ํ์๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
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
...
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ spot_instance_request_id = (known after apply)
+ subnet_id = (sensitive value)
+ tags_all = (known after apply)
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ user_data_replace_on_change = true
+ vpc_security_group_ids = (sensitive value)
...
Changes to Outputs:
+ instance_id = (known after apply)
+ instance_public_ip = (known after apply)
aws_instance.kane_ec2: Creating...
aws_instance.kane_ec2: Still creating... [10s elapsed]
aws_instance.kane_ec2: Still creating... [20s elapsed]
aws_instance.kane_ec2: Still creating... [30s elapsed]
aws_instance.kane_ec2: Still creating... [40s elapsed]
aws_instance.kane_ec2: Creation complete after 41s [id=i-052c74426b547ab75]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
instance_id = "i-052c74426b547ab75"
instance_public_ip = "3.34.94.79"
AWS ์ฝ์์์ ํ์ธํด๋ณด๋ฉด VPC,Subnet ๋ชจ๋ ์ ์์ ์ผ๋ก ๋คํธ์ํฌ ๋ชจ๋์์ ์์ฑ๋ ๊ฒ์ ๊ฐ์ ธ์๋ค.
๊ท๋ชจ์ ๋ฐ๋ฅธ ์ํฌํ๋ก
- ๊ฐ์ธ: ํผ์์ ํ
๋ผํผ์ผ๋ก ์์
ํ ๋๋ ๊ธฐ์กด๊ณผ ๊ฐ์ด 3๊ฐ์ง ๋ฐฉ์์ผ๋ก ์์
ํ๋ค.
- write: ํ ๋ผํผ ์ฝ๋ ์์ฑ
- plan: ๋ฆฌ๋ทฐ
- apply: ํ๋ก๋น์ ๋, ์ฑ๊ณตํ ๊ฒฝ์ฐ VCS์ ์ฝ๋๋ฅผ ๋ณํฉํ๋ค.
- ๋จ์ผ ํ:
- write: ๋ค๋ฅธ ๋ธ๋์น ํน์ ๋ค๋ฅธ ์์ ํ๊ฒฝ์์ ํผ์ ํ ๋ผํผ ์ฝ๋๋ฅผ ํ ์คํธํ๋ค.
- plan(review): ํ ์คํธ๊ฐ ์๋ฃ๋๋ฉด, plan์ ํตํด ๋ค๋ฅธ ํ์์๊ฒ ๋ฆฌ๋ทฐ๋ฐ๋๋ค.
- apply(merge): ๋ฆฌ๋ทฐ๊ฐ ์๋ฃ๋๋ฉด ์ฝ๋๋ฅผ ๋ณํฉํ๊ณ ์ธํ๋ผ๋ฅผ ํ๋ก๋น์ ๋ํ๋ค.
์ฌ๋ฌ ๊ฐ์ ํ
ํ ๋ณ๋ก (2)๋ฒ ๊ณผ์ ์ ์ํํ๋ค. ์ดํ
terraform_remote_state
๋ฅผ ํตํด ๋ค๋ฅธ ํ์ state ํ์ผ์ ์ฐธ์กฐํ์ฌ ์ธํ๋ผ๋ฅผ ๊ตฌ์ฑํ๋ค.
MSA
๋ฆฌ์์ค๊ฐ ์ ๋ค๋ฉด ๋ชจ๋๋ฆฌ์ ๋ฐฉ์์ผ๋ก ๊ตฌ์ฑํด๋ ์ข์ง๋ง ์ ์ง๋ณด์, ์ด์์ ์๊ฐํ๋ฉด ํ๋ก๋น์ ๋ ๋จ์๋ณ๋ก ๋ถ๋ฅํ๋ ๊ฒ์ด ์ข๋ค. ์ ๋ณด๋ ๊ณต์ ํ ์ ์์ง๋ง ๊ฐ ์งํฉ์ ๋ ๋ฆฝ์ ์ผ๋ก ์คํ๋๋ฉฐ ๋ค๋ฅธ ์งํฉ์ ์ํฅ์ ๋ฐ์ง ์๋ ๊ฒฉ๋ฆฌ๋ ๊ตฌ์กฐ๊ฐ ํ์ํ๋ค.
์ถ์ฒ: https://medium.com/@dudwls96/terraform-%ED%86%B5%ED%95%9C-iac-infrastructure-as-code-365%EC%9D%BC%EA%B0%84-%EC%9A%B4%EC%98%81-%ED%9B%84%EA%B8%B0-500737e6c1e6
CI/CD
์ ๊ณตํด์ฃผ์ ์๋ฃ๋ฅผ ํตํด GitHub Actions ์ค์ต์ ์งํํ๋ค.
https://github.com/terraform101/terraform-aws-github-action
actions.yaml ํ์ผ์ ๋ด์ฉ์ ์์ฝํด๋ณด๋ฉด
- TerraScan์ ํตํด ์ค์บ ๊ฒฐ๊ณผ๋ฅผ ์ป๊ณ , ์ ๋ก๋ํ๋ค.
- Terraform ์ํฌํ๋ก ์คํ: ์ฝ๋๋ฅผ ๋ณต์ฌํ๊ณ , fmt โ init โ validate โ plan โ apply ๋ฅผ ์งํํ๋ค.
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
name: Terraform DEV
on:
push:
branches:
- main
pull_request:
env:
MY_PREFIX: DEV
TF_VERSION: 1.2.5
jobs:
SCAN:
name: SCAN
runs-on: ubuntu-latest
# env:
# working-directory: terraform
# TF_WORKSPACE: my-workspace
steps:
# - name: Configure AWS credentials
# uses: aws-actions/configure-aws-credentials@v1
# with:
# aws-region: eu-west-1
- name: Check out code
uses: actions/checkout@v3
- name: Run Terrascan
id: terrascan
uses: tenable/terrascan-action@main
with:
iac_type: 'terraform'
iac_version: 'v14'
policy_type: 'aws'
only_warn: true
sarif_upload: true
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: terrascan.sarif
Terraform:
needs: SCAN
name: Terraform
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- uses: hashicorp/setup-terraform@v2
with:
terraform_version: $TF_VERSION
cli_config_credentials_token: $
- name: Terraform Fmt
id: fmt
run: terraform fmt -recursive -check
continue-on-error: true
- name: Terraform init
id: init
run: terraform init -upgrade
# working-directory: $
- name: Terraform validate
id: validate
run: terraform validate -no-color
- name: Terraform plan
id: plan
run: terraform plan -no-color -var=prefix="$MY_PREFIX"
# working-directory: $
env:
AWS_ACCESS_KEY_ID: $
AWS_SECRET_ACCESS_KEY: $
TF_LOG: info
- name: Plan output
id: output
uses: actions/github-script@v3
if: github.event_name == 'pull_request'
env:
PLAN: "terraform\n$"
with:
github-token: $
script: |
const output = `#### Terraform Format and Style ๐\`$\`
#### Terraform Initialization โ๏ธ\`$\`
#### Terraform Plan ๐\`$\`
<details><summary>Show Plan</summary>
\`\`\`hcl
${process.env.PLAN}
\`\`\`
</details>
**Pusher**: @$
**Action**: $
`;
github.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
- name: Terraform apply
id: apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve -var=prefix="$MY_PREFIX" -input=false
env:
AWS_ACCESS_KEY_ID: $
AWS_SECRET_ACCESS_KEY: $
๊ฒฐ๊ณผ
GitHub Actions, AWS, Terraform Cloud์ ๋ชจ๋ ์ ์์ ์ผ๋ก ๋ฐ์๋์๋ค. Terrascan์ด ์ํ๋๊ณ ์ฝ๋๊ฐ ์คํ๋ผ ํ๋ก๋น์ ๋๋์๊ณ , ๊ฒฐ๊ณผ๋ฅผ AWS ์ฝ์๊ณผ ํ ๋ผํผ ํด๋ผ์ฐ๋์์ ๋ชจ๋ ํ์ธํ ์ ์์๋ค.
- GitHub Actions
- AWS ์ฝ์
- Terraform Cloud
์ด์ ๋ก์ปฌ์์ terraform plan -destroy -out=destroy.tfplan
๋ช
๋ น์ด๋ฅผ ์คํํ์ฌ ๋ฐฑ์๋์ state๊ฐ์ ์ฝ์ด์ ์ธํ๋ผ๋ฅผ ์ ๊ฑฐํ๋ค.
๋์ ๊ณผ์ : Terrascan ์ค์น ํ ์ง์ ๊ฒ์ฆ ํ ์คํธ ํด๋ณด๊ธฐ
๊ณต์์ฌ์ดํธ์์ ์ง์ ์ค์นํ ํ ํ ์คํธ๋ฅผ ํด๋ณธ๋ค. ์๋๋ macOS, ๋ฆฌ๋ ์ค ์ ์ฉ ์ค์น๋ช ๋ น์ด๋ค.
1
2
3
4
5
$ curl -L "$(curl -s https://api.github.com/repos/tenable/terrascan/releases/latest | grep -o -E "https://.+?_Darwin_x86_64.tar.gz")" > terrascan.tar.gz
$ tar -xf terrascan.tar.gz terrascan && rm terrascan.tar.gz
$ install terrascan /usr/local/bin && rm terrascan
$ terrascan version
version: v1.18.3
docker image๋ ์ ๊ณตํ๊ณ ์์ด, gitlab ๋ฑ ๋ค๋ฅธ ํ๋ซํผ ํ์ดํ๋ผ์ธ์ ์ ์ฉํ ๋ ํธํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
1
docker run --rm tenable/terrascan version
์ด์ ํ ๋ผํผ ๋๋ ํฐ๋ฆฌ๋ก ์ด๋ํ์ฌ ๋ช ๋ น์ด๋ฅผ ์คํํ๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ๊ฒ์ฌ๋ฅผ ์คํํ ์ ์๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
$ terrascan init
$ terrascan scan
...
Scan Summary -
File/Folder : ...
IaC Type : terraform
Scanned At : 2023-09-30 02:36:12.552512 +0000 UTC
Policies Validated : 144
Violated Policies : 6
Low : 2
Medium : 1
High : 3
๋ณด์ ๊ทธ๋ฃน๊ณผ ๊ด๋ จํ์ฌ High์ด 3๊ฐ ์๋ค. 3๊ฐ์ ํฌํธ๋ฅผ ์ด์๋๋ฐ ๊ฐ๊ฐ ์ทจ์ฝ์ ์ผ๋ก ๊ฒ์ฌ๋์๋ค.
scan์ exit ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ด ์ด 5๊ฐ๋ก ๊ตฌ๋ถ๋๋ค. GitHub ์ฐธ๊ณ . Terraform Cloud Run task, CI/CD๋ฅผ ๊ตฌ์ฑํ ๋ ์ฐธ๊ณ ํ์ฌ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋ค.
Scenario | Exit Code |
---|---|
scan summary has errors and violations | 5 |
scan summary has errors but no violations | 4 |
scan summary has violations but no errors | 3 |
scan summary has no violations or errors | 0 |
scan command errors out due to invalid inputs | 1 |