Ver3.0 ๐ ํด๋ผ์ฐ๋ ์ธํ๋ผ ๋ณ๊ฒฝ ์๋ํ: Terraform๊ณผ CI/CD๋ก ์์ฑํ๋ ์ค๋งํธํ ๋ฐฐํฌ ์ ๋ต

๐ ํด๋ผ์ฐ๋ ์ธํ๋ผ ๋ณ๊ฒฝ ์๋ํ: Terraform๊ณผ CI/CD๋ก ์์ฑํ๋ ์ค๋งํธํ ๋ฐฐํฌ ์ ๋ต
์ฝ๋ ํ ์ค๋ก ์ธํ๋ผ๋ฅผ ๊ด๋ฆฌํ๋ ๋ง๋ฒ ๊ฐ์ ์ธ๊ณ๋ก ์ด๋ํฉ๋๋ค! โจ
์๋
, ์น๊ตฌ! ๐ ํน์ ํด๋ผ์ฐ๋ ์ธํ๋ผ๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํ๋ฉด์ "์ด๊ฑฐ ์ค์ํ๋ฉด ์ด์ฉ์ง?" ํ๊ณ ์์๋ ํ๋ฆฐ ์ ์์ด? ์๋๋ฉด ์๋ฒ ์ค์ ์ ๋ฐ๊พธ๋ ค๊ณ AWS ์ฝ์์ ํด๋ฆญํด๋ฆญํ๋ค๊ฐ "์, ์ด๊ฑฐ ์ด๋๊น์ง ํ๋๋ผ?" ํ๊ณ ํท๊ฐ๋ฆฐ ์ ์? ๐ค
๋๋ ๊ทธ๋ฌ์ด. ๊ทธ๋ฐ๋ฐ Terraform๊ณผ CI/CD๋ฅผ ๋ง๋๊ณ ๋์ ์์ ํ ์๋ก์ด ์ธ์์ด ์ด๋ ธ์ง! ์ด์ ๋ ์ฝ๋ ๋ช ์ค๋ก ์๋ฐฑ ๊ฐ์ ์๋ฒ๋ฅผ ๊ด๋ฆฌํ๊ณ , ์ค์ ์์ด ์๋์ผ๋ก ๋ฐฐํฌํ ์ ์๊ฒ ๋๊ฑฐ๋ . ๋ง์น ๋ง๋ฒ ๊ฐ์ ์ผ์ด์ผ! โจ
์ค๋์ ์ด ๋๋ผ์ด ๊ธฐ์ ๋ค์ ์น๊ตฌ์๊ฒ ์ค๋ช
ํ๋ฏ์ด ์ฝ๊ณ ์ฌ๋ฏธ์๊ฒ ํ์ด๋ณผ๊ฒ. ์ฌ๋ฅ๋ท์์ DevOps ๊ด๋ จ ์ฌ๋ฅ์ ๊ณต์ ํ๊ฑฐ๋ ๋ฐฐ์ฐ๊ณ ์ถ์ ์ฌ๋๋ค์๊ฒ๋ ๋ฑ ๋ง๋ ๋ด์ฉ์ด๋๊น, ๋๊น์ง ํจ๊ป ๊ฐ๋ณด์! ๐ฏ
๐ฏ ์ ์ธํ๋ผ ์๋ํ๊ฐ ํ์ํ ๊น?
๋จผ์ ์ด์ผ๊ธฐ๋ฅผ ํ๋ ํด๋ณผ๊ฒ. ๋ด ์น๊ตฌ ๋ฏผ์(๊ฐ๋ช
)๋ ์คํํธ์
์์ ๋ฐฑ์๋ ๊ฐ๋ฐ์๋ก ์ผํ๊ณ ์์ด. ์ด๋ ๋ ์๋น์ค๊ฐ ๊ฐ์๊ธฐ ํฐ์ก๋๋ฐ, ์๊ณ ๋ณด๋ ๋๊ตฐ๊ฐ AWS ์ฝ์์์ ๋ณด์ ๊ทธ๋ฃน ์ค์ ์ ์๋ชป ๊ฑด๋๋ฆฐ ๊ฑฐ์์ด. ๐ฑ
๋ฌธ์ ๋ ๋๊ฐ, ์ธ์ , ๋ฌด์์ ๋ฐ๊ฟจ๋์ง ์ถ์ ์ด ์ ๋๋ค๋ ๊ฑฐ์ผ. ํ์๋ค๋ผ๋ฆฌ "๋ด๊ฐ ์๋๋ฐ?" ํ๋ฉด์ ์๋ก ๋์น๋ง ๋ณด๋ค๊ฐ ๊ฒฐ๊ตญ ๋ช ์๊ฐ ๋์ ์๋น์ค๊ฐ ๋ค์ด๋์ง. ์ด๋ฐ ์ํฉ, ์ ๋ง ๋์ฐํ์ง ์์?
๐ฅ ์๋ ์ธํ๋ผ ๊ด๋ฆฌ์ ๋ฌธ์ ์ ๋ค
1. ํด๋จผ ์๋ฌ์ ์ํ ๐
์ฌ๋์ด ์ง์ ํด๋ฆญํ๊ณ ํ์ดํํ๋ค ๋ณด๋ฉด ์ค์๋ ํ์ฐ์ ์ด์ผ. ํนํ ์๋ฒฝ 3์์ ๊ธด๊ธ ๋ฐฐํฌํ ๋๋ ๋๋์ฑ!
2. ์ผ๊ด์ฑ ๋ถ์กฑ ๐ญ
๊ฐ๋ฐ ํ๊ฒฝ, ์คํ
์ด์ง ํ๊ฒฝ, ํ๋ก๋์
ํ๊ฒฝ... ๊ฐ๊ฐ ์ค์ ์ด ์กฐ๊ธ์ฉ ๋ค๋ฅด๋ฉด "๋ด ์ปดํจํฐ์์ ๋๋๋ฐ?"๋ผ๋ ์
๋ชฝ์ด ์์๋ผ.
3. ์ถ์ ๋ถ๊ฐ๋ฅ ๐ต๏ธ
๋๊ฐ ์ธ์ ๋ฌด์์ ๋ฐ๊ฟจ๋์ง ๊ธฐ๋ก์ด ์์ผ๋ฉด, ๋ฌธ์ ๊ฐ ์๊ฒผ์ ๋ ์์ธ์ ์ฐพ๊ธฐ๊ฐ ์ ๋ง ์ด๋ ค์.
4. ํ์ฅ์ฑ ํ๊ณ ๐
์๋ฒ 10๋๋ ์๋์ผ๋ก ๊ด๋ฆฌํ ์ ์์ด๋, 100๋, 1000๋๊ฐ ๋๋ฉด? ์์๋ง ํด๋ ๋์ฐํ์ง.
5. ์ง์์ ์ฌ์ผ๋กํ ๐๏ธ
"์ด ์ค์ ์ ์ฒ ์๋ง ์์"๋ผ๋ ์ํฉ์ด ๋๋ฉด, ์ฒ ์๊ฐ ํด๊ฐ ๊ฐ๋ฉด ํ ์ ์ฒด๊ฐ ๋ฉ์ถฐ๋ฒ๋ ค.
์ด๋ฐ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๊ธฐ ์ํด ๋ฑ์ฅํ ๊ฒ ๋ฐ๋ก Infrastructure as Code (IaC)์ผ! ์ธํ๋ผ๋ฅผ ์ฝ๋๋ก ๊ด๋ฆฌํ๋ ๊ฑฐ์ง. ๊ทธ๋ฆฌ๊ณ ๊ทธ ์ค์ฌ์ Terraform์ด ์์ด. ๐ช
๐๏ธ Terraform์ด ๋ญ๊ธธ๋?
Terraform์ HashiCorp๋ผ๋ ํ์ฌ์์ ๋ง๋ ์คํ์์ค IaC ๋๊ตฌ์ผ. ๊ฐ๋จํ ๋งํ๋ฉด, ์ธํ๋ผ๋ฅผ ์ฝ๋๋ก ์ ์ํ๊ณ ๊ด๋ฆฌํ ์ ์๊ฒ ํด์ฃผ๋ ๋ง๋ฒ ๋๊ตฌ์ง! ๐ช
์๋ฅผ ๋ค์ด๋ณผ๊ฒ. AWS์์ EC2 ์ธ์คํด์ค๋ฅผ ๋ง๋ค๋ ค๋ฉด ๋ณดํต ์ด๋ ๊ฒ ํ์์?
1. AWS ์ฝ์์ ๋ก๊ทธ์ธ ๐ฑ๏ธ
2. EC2 ์๋น์ค๋ก ์ด๋
3. "์ธ์คํด์ค ์์" ๋ฒํผ ํด๋ฆญ
4. AMI ์ ํ
5. ์ธ์คํด์ค ํ์ ์ ํ
6. ๋คํธ์ํฌ ์ค์
7. ์คํ ๋ฆฌ์ง ์ค์
8. ํ๊ทธ ์ถ๊ฐ
9. ๋ณด์ ๊ทธ๋ฃน ์ค์
10. ํค ํ์ด ์ ํ
11. ์ต์ข ํ์ธ ๋ฐ ์์
์ด ์์ ์๊ฐ: ์ฝ 10-15๋ถ (ํด๋ฆญ ํฌํจ) ๐
ํ์ง๋ง Terraform์ ์ฌ์ฉํ๋ฉด?
resource "aws_instance" "web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
tags = {
Name = "MyWebServer"
}
}
๊ทธ๋ฆฌ๊ณ ํฐ๋ฏธ๋์์:
terraform apply์ด ์์ ์๊ฐ: ์ฝ 2-3๋ถ (์ฝ๋ ์์ฑ + ์คํ) ๐
"์ด๋ป๊ฒ" ๋ง๋ค์ง๊ฐ ์๋๋ผ "๋ฌด์์" ๋ง๋ค์ง๋ง ์ ์ํ๋ฉด ๋ผ. Terraform์ด ์์์ ์ต์ ์ ๋ฐฉ๋ฒ์ผ๋ก ๋ง๋ค์ด์ค!
2. ๋ฉํฐ ํด๋ผ์ฐ๋ ์ง์
AWS, Azure, GCP, ์ฌ์ง์ด ์จํ๋ ๋ฏธ์ค๊น์ง! ํ๋์ ๋๊ตฌ๋ก ๋ชจ๋ ํด๋ผ์ฐ๋๋ฅผ ๊ด๋ฆฌํ ์ ์์ด. ๐
3. ์ํ ๊ด๋ฆฌ (State Management)
ํ์ฌ ์ธํ๋ผ์ ์ํ๋ฅผ ์ถ์ ํด์, ๋ณ๊ฒฝ์ฌํญ๋ง ์ ํํ๊ฒ ์ ์ฉํด. ๋๋ํ์ง?
4. ์คํ ๊ณํ (Plan)
์ค์ ๋ก ์ ์ฉํ๊ธฐ ์ ์ "์ด๋ ๊ฒ ๋ฐ๋ ๊ฑฐ์์"๋ผ๊ณ ๋ฏธ๋ฆฌ ๋ณด์ฌ์ค. ์ค์๋ฅผ ๋ฐฉ์งํ ์ ์์ง!
5. ๋ชจ๋ํ
์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ฝ๋ ๋ธ๋ก์ ๋ง๋ค์ด์, ๋ง์น ๋ ๊ณ ๋ธ๋ก์ฒ๋ผ ์กฐ๋ฆฝํ ์ ์์ด. ๐งฑ
๐ Terraform์ ํต์ฌ ๊ฐ๋ ๋ค
1. Provider (ํ๋ก๋ฐ์ด๋)
Provider๋ Terraform์ด ์ด๋ค ํด๋ผ์ฐ๋๋ ์๋น์ค์ ํต์ ํ ์ง ์ ์ํ๋ ๊ฑฐ์ผ. AWS, Azure, GCP ๋ฑ๋ฑ ๋ง์ด์ผ.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
provider "aws" {
region = "ap-northeast-2" # ์์ธ ๋ฆฌ์
}
2. Resource (๋ฆฌ์์ค)
์ค์ ๋ก ์์ฑํ๊ณ ๊ด๋ฆฌํ ์ธํ๋ผ ๊ตฌ์ฑ ์์์ผ. EC2, RDS, S3 ๋ฒํท ๋ฑ๋ฑ!
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-awesome-bucket-2024"
tags = {
Environment = "Production"
Team = "DevOps"
}
}
3. Variable (๋ณ์)
์ฝ๋๋ฅผ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค์ด์ฃผ๋ ๋ง๋ฒ! ํ๊ฒฝ๋ณ๋ก ๋ค๋ฅธ ๊ฐ์ ์ฝ๊ฒ ์ ์ฉํ ์ ์์ด.
variable "instance_type" {
description = "EC2 ์ธ์คํด์ค ํ์
"
type = string
default = "t2.micro"
}
resource "aws_instance" "app_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = var.instance_type
}
4. Output (์ถ๋ ฅ)
์์ฑ๋ ๋ฆฌ์์ค์ ์ ๋ณด๋ฅผ ์ถ๋ ฅํด์ ๋ค๋ฅธ ๊ณณ์์ ์ฌ์ฉํ ์ ์๊ฒ ํด์ค.
output "instance_public_ip" {
description = "EC2 ์ธ์คํด์ค์ ๊ณต๊ฐ IP"
value = aws_instance.app_server.public_ip
}
5. Module (๋ชจ๋)
์ฌ์ฌ์ฉ ๊ฐ๋ฅํ Terraform ์ฝ๋ ๋ฌถ์! ๋ง์น ํจ์์ฒ๋ผ ์ฌ์ฉํ ์ ์์ด.
module "vpc" {
source = "./modules/vpc"
vpc_cidr = "10.0.0.0/16"
environment = "production"
}
โ๏ธ Terraform ์ํฌํ๋ก์ฐ: ์ค์ ์ฌ์ฉ๋ฒ
์, ์ด์ Terraform์ ์ค์ ๋ก ์ด๋ป๊ฒ ์ฌ์ฉํ๋์ง ์์๋ณผ๊น? Terraform์ ์ํฌํ๋ก์ฐ๋ ํฌ๊ฒ 4๋จ๊ณ๋ก ๋๋์ด. ์ด๊ฑธ ์ดํดํ๋ฉด Terraform ๋ง์คํฐ์ ์ ๋ฐ์ ์จ ๊ฑฐ์ผ! ๐ช
๐ Terraform 4๋จ๊ณ ์ํฌํ๋ก์ฐ
๋จผ์ .tf ํ์ผ์ ์ํ๋ ์ธํ๋ผ๋ฅผ ์ฝ๋๋ก ์์ฑํด. HCL(HashiCorp Configuration Language)์ด๋ผ๋ ์ธ์ด๋ฅผ ์ฌ์ฉํ๋๋ฐ, ์ ๋ง ์ฝ๊ธฐ ์ฌ์!
# main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# VPC ์์ฑ
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "main-vpc"
Environment = var.environment
}
}
# ์๋ธ๋ท ์์ฑ
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-2a"
map_public_ip_on_launch = true
tags = {
Name = "public-subnet"
}
}
terraform init ๋ช ๋ น์ด๋ก Terraform์ ์ด๊ธฐํํด. ์ด ๊ณผ์ ์์ ํ์ํ Provider ํ๋ฌ๊ทธ์ธ๋ค์ ๋ค์ด๋ก๋๋ฐ์.
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 4.0"...
- Installing hashicorp/aws v4.67.0...
- Installed hashicorp/aws v4.67.0
Terraform has been successfully initialized! ๐
terraform plan์ผ๋ก ์คํ ๊ณํ์ ํ์ธํด. ์ค์ ๋ก ์ ์ฉํ๊ธฐ ์ ์ "์ด๋ ๊ฒ ๋ฐ๋ ๊ฑฐ์์"๋ผ๊ณ ๋ฏธ๋ฆฌ ๋ณด์ฌ์ฃผ๋ ๊ฑฐ์ผ. ์ด๊ฒ ์ง์ง ์ค์ํด! ๐
$ terraform plan
Terraform will perform the following actions:
# aws_vpc.main will be created
+ resource "aws_vpc" "main" {
+ cidr_block = "10.0.0.0/16"
+ enable_dns_hostnames = true
+ enable_dns_support = true
+ id = (known after apply)
+ tags = {
+ "Environment" = "production"
+ "Name" = "main-vpc"
}
}
# aws_subnet.public will be created
+ resource "aws_subnet" "public" {
+ cidr_block = "10.0.1.0/24"
+ vpc_id = (known after apply)
+ availability_zone = "ap-northeast-2a"
+ map_public_ip_on_launch = true
+ tags = {
+ "Name" = "public-subnet"
}
}
Plan: 2 to add, 0 to change, 0 to destroy.
์ฌ๊ธฐ์ ์ฃผ๋ชฉํ ์ ! โจ
- + (์ด๋ก์): ์๋ก ์์ฑ๋ ๋ฆฌ์์ค
- ~ (๋ ธ๋์): ์์ ๋ ๋ฆฌ์์ค
- - (๋นจ๊ฐ์): ์ญ์ ๋ ๋ฆฌ์์ค
์ด๋ ๊ฒ ๋ฏธ๋ฆฌ ํ์ธํ ์ ์์ผ๋๊น ์ค์๋ฅผ ๋ฐฉ์งํ ์ ์์ด!
๋ชจ๋ ๊ฒ ๊ด์ฐฎ์ ๋ณด์ด๋ฉด terraform apply๋ก ์ค์ ๋ก ์ ์ฉํด!
$ terraform apply
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_vpc.main: Creating...
aws_vpc.main: Creation complete after 3s [id=vpc-0123456789abcdef]
aws_subnet.public: Creating...
aws_subnet.public: Creation complete after 2s [id=subnet-0123456789abcdef]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
์ง์! ๐ ์ด์ AWS์ ์ค์ ๋ก VPC์ ์๋ธ๋ท์ด ์์ฑ๋์ด!
๐๏ธ ๋ณด๋์ค: Destroy (์ญ์ )
ํ
์คํธ๊ฐ ๋๋๊ณ ๋ฆฌ์์ค๋ฅผ ์ ๋ฆฌํ๊ณ ์ถ์ ๋๋ terraform destroy๋ฅผ ์ฌ์ฉํด. ์์ฑํ ๋ชจ๋ ๋ฆฌ์์ค๋ฅผ ๊น๋ํ๊ฒ ์ญ์ ํด์ค!
$ terraform destroy
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_subnet.public: Destroying... [id=subnet-0123456789abcdef]
aws_subnet.public: Destruction complete after 1s
aws_vpc.main: Destroying... [id=vpc-0123456789abcdef]
aws_vpc.main: Destruction complete after 2s
Destroy complete! Resources: 2 destroyed.
๐ State ๊ด๋ฆฌ: Terraform์ ๋๋
Terraform์ ๊ฐ์ฅ ์ค์ํ ๊ฐ๋ ์ค ํ๋๊ฐ ๋ฐ๋ก State(์ํ)์ผ. ์ด๊ฒ ๋ญ๋๋ฉด, Terraform์ด ๊ด๋ฆฌํ๋ ์ธํ๋ผ์ ํ์ฌ ์ํ๋ฅผ ๊ธฐ๋กํ๋ ํ์ผ์ด์ผ. ๋ง์น ์ธํ๋ผ์ "์ฌ์ง"์ ์ฐ์ด๋๋ ๊ฑฐ์ง! ๐ธ
๐ง State ํ์ผ์ด ํ๋ ์ผ
1. ํ์ฌ ์ํ ์ถ์
์ด๋ค ๋ฆฌ์์ค๊ฐ ์์ฑ๋์ด ์๋์ง, ๊ฐ ๋ฆฌ์์ค์ ID๋ ๋ฌด์์ธ์ง ๋ฑ์ ๊ธฐ๋กํด.
2. ๋ณ๊ฒฝ์ฌํญ ๊ณ์ฐ
์ฝ๋์ ์ค์ ์ธํ๋ผ๋ฅผ ๋น๊ตํด์ ๋ฌด์์ ์ถ๊ฐ/์์ /์ญ์ ํด์ผ ํ๋์ง ๊ณ์ฐํด.
3. ์ฑ๋ฅ ์ต์ ํ
๋งค๋ฒ ํด๋ผ์ฐ๋ API๋ฅผ ํธ์ถํ์ง ์๊ณ , State ํ์ผ์ ์ฐธ์กฐํด์ ๋น ๋ฅด๊ฒ ๋์ํด.
4. ๋ฉํ๋ฐ์ดํฐ ์ ์ฅ
๋ฆฌ์์ค ๊ฐ์ ์์กด์ฑ ์ ๋ณด ๋ฑ์ ์ ์ฅํด.
๐ Local State vs Remote State
State ํ์ผ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ก์ปฌ์ terraform.tfstate๋ผ๋ ์ด๋ฆ์ผ๋ก ์ ์ฅ๋ผ. ํ์ง๋ง ํ์ผ๋ก ์ผํ ๋๋ ๋ฌธ์ ๊ฐ ์๊ฒจ!
2. ๋์ ์์ ์ํ: ๋ ๋ช ์ด ๋์์ applyํ๋ฉด ์ถฉ๋ ๋ฐ์!
3. ๋ฐฑ์ ๋ถ์ฌ: ์ปดํจํฐ๊ฐ ๊ณ ์ฅ๋๋ฉด? State ํ์ผ๋ ๋ ์๊ฐ!
4. ๋ณด์ ์ํ: State ํ์ผ์๋ ๋ฏผ๊ฐํ ์ ๋ณด(๋น๋ฐ๋ฒํธ, API ํค ๋ฑ)๊ฐ ๋ค์ด์์ด.
๊ทธ๋์ ์ค๋ฌด์์๋ Remote State๋ฅผ ์ฌ์ฉํด! ์ฃผ๋ก S3๋ Terraform Cloud๋ฅผ ์ฌ์ฉํ์ง.
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state-bucket"
key = "production/terraform.tfstate"
region = "ap-northeast-2"
encrypt = true
dynamodb_table = "terraform-lock" # State Lock์ฉ
}
}
# S3 ๋ฒํท ์์ฑ (๋ณ๋๋ก ๋จผ์ ์์ฑ ํ์)
resource "aws_s3_bucket" "terraform_state" {
bucket = "my-terraform-state-bucket"
lifecycle {
prevent_destroy = true # ์ค์๋ก ์ญ์ ๋ฐฉ์ง!
}
}
# ๋ฒํท ์ํธํ ์ค์
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
# ๋ฒ์ ๊ด๋ฆฌ ํ์ฑํ
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
versioning_configuration {
status = "Enabled"
}
}
# DynamoDB ํ
์ด๋ธ ์์ฑ (State Lock์ฉ)
resource "aws_dynamodb_table" "terraform_lock" {
name = "terraform-lock"
billing_mode = "PAY_PER_REQUEST"
hash_key = "LockID"
attribute {
name = "LockID"
type = "S"
}
}
๐ ๏ธ State ๊ด๋ฆฌ ๋ช ๋ น์ด๋ค
State๋ฅผ ์ง์ ์กฐ์ํด์ผ ํ ๋๊ฐ ์์ด. ์กฐ์ฌํด์ ์ฌ์ฉํด์ผ ํ์ง๋ง, ์์๋๋ฉด ์ ๋ง ์ ์ฉํด!
# State ํ์ผ ๋ด์ฉ ํ์ธ
$ terraform state list
# ํน์ ๋ฆฌ์์ค์ ์์ธ ์ ๋ณด ํ์ธ
$ terraform state show aws_instance.web_server
# ๋ฆฌ์์ค๋ฅผ State์์ ์ ๊ฑฐ (์ค์ ๋ฆฌ์์ค๋ ์ญ์ ์ ๋จ)
$ terraform state rm aws_instance.old_server
# ๋ฆฌ์์ค๋ฅผ ๋ค๋ฅธ ์ด๋ฆ์ผ๋ก ์ด๋
$ terraform state mv aws_instance.old aws_instance.new
# State ํ์ผ์ ์๊ฒฉ์์ ๋ก์ปฌ๋ก ๊ฐ์ ธ์ค๊ธฐ
$ terraform state pull > terraform.tfstate
# ๋ก์ปฌ State๋ฅผ ์๊ฒฉ์ผ๋ก ํธ์
$ terraform state push terraform.tfstate
๐ CI/CD์ Terraform์ ๋ง๋จ
์, ์ด์ ์ง์ง ์ฌ๋ฏธ์๋ ๋ถ๋ถ์ด์ผ! Terraform์ CI/CD ํ์ดํ๋ผ์ธ์ ํตํฉํ๋ฉด, ์ธํ๋ผ ๋ณ๊ฒฝ๋ ์ฝ๋ ๋ฐฐํฌ์ฒ๋ผ ์๋ํํ ์ ์์ด. ์ด๊ฒ ๋ฐ๋ก GitOps์ ํต์ฌ์ด์ง! ๐ฏ
๐ญ ์ ํต์ ์ธ ๋ฐฉ์ vs CI/CD ๋ฐฉ์
| ๊ตฌ๋ถ | ์ ํต์ ์ธ ๋ฐฉ์ | CI/CD ๋ฐฉ์ |
|---|---|---|
| ๋ฐฐํฌ ๋ฐฉ๋ฒ | ๊ฐ๋ฐ์๊ฐ ์๋์ผ๋ก ์คํ | Git push๋ง ํ๋ฉด ์๋ ๋ฐฐํฌ |
| ๊ฒ์ฆ | ๊ฐ๋ฐ์์ ๋์ผ๋ก๋ง ํ์ธ | ์๋ํ๋ ํ ์คํธ์ ๊ฒ์ฆ |
| ์น์ธ ํ๋ก์ธ์ค | ๊ตฌ๋ ๋๋ ์ด๋ฉ์ผ | Pull Request ๋ฆฌ๋ทฐ |
| ๋กค๋ฐฑ | ์๋์ผ๋ก ์ด์ ์ํ ๋ณต๊ตฌ | Git revert๋ก ์๋ ๋กค๋ฐฑ |
| ๊ฐ์ฌ ์ถ์ | ๋ณ๋ ๋ฌธ์ ์์ฑ | Git ํ์คํ ๋ฆฌ์ ์๋ ๊ธฐ๋ก |
๐๏ธ CI/CD ํ์ดํ๋ผ์ธ ๊ตฌ์กฐ
Terraform์ CI/CD์ ํตํฉํ๋ ์ ํ์ ์ธ ์ํฌํ๋ก์ฐ๋ฅผ ์ดํด๋ณผ๊น?
๊ฐ๋ฐ์๊ฐ Terraform ์ฝ๋๋ฅผ ์์ฑํ๊ณ Git์ ์ปค๋ฐํด.
$ git checkout -b feature/add-rds-database
$ vim main.tf # RDS ๋ฆฌ์์ค ์ถ๊ฐ
$ git add .
$ git commit -m "Add RDS database for production"
$ git push origin feature/add-rds-database
GitHub/GitLab์์ PR์ ์์ฑํ๋ฉด, CI ํ์ดํ๋ผ์ธ์ด ์๋์ผ๋ก ์คํ๋ผ.
์๋ ์คํ๋๋ ์์ ๋ค:
- ์ฝ๋ ํฌ๋งท ๊ฒ์ฌ (terraform fmt -check)
- ๋ฌธ๋ฒ ๊ฒ์ฆ (terraform validate)
- ๋ณด์ ์ค์บ (tfsec, Checkov ๋ฑ)
- ๋น์ฉ ์์ธก (Infracost)
- Plan ์คํ ๋ฐ ๊ฒฐ๊ณผ๋ฅผ PR ์ฝ๋ฉํธ๋ก ์๋ ๊ฒ์
ํ์๋ค์ด PR์ ๋ฆฌ๋ทฐํ๊ณ , Plan ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ ํ ์น์ธํด.
โ Plan ๊ฒฐ๊ณผ๊ฐ ์์๊ณผ ์ผ์นํ๋๊ฐ?
โ ๋ณด์ ์ด์๋ ์๋๊ฐ?
โ ๋น์ฉ ์ฆ๊ฐ๊ฐ ํฉ๋ฆฌ์ ์ธ๊ฐ?
โ ๋ค์ด๋ฐ ์ปจ๋ฒค์ ์ ๋ฐ๋ฅด๋๊ฐ?
โ ๋ฌธ์ํ๊ฐ ์ถฉ๋ถํ๊ฐ?
PR์ด ๋จธ์ง๋๋ฉด, CD ํ์ดํ๋ผ์ธ์ด ์๋์ผ๋ก terraform apply๋ฅผ ์คํํด!
๋ฐฐํฌ ํ๋ก์ธ์ค:
1. main ๋ธ๋์น์ ๋จธ์ง
2. CD ํ์ดํ๋ผ์ธ ํธ๋ฆฌ๊ฑฐ
3. ํ๊ฒฝ๋ณ๋ก ์์ฐจ ๋ฐฐํฌ (dev โ staging โ production)
4. ๊ฐ ๋จ๊ณ๋ง๋ค ์น์ธ ๊ฒ์ดํธ ์ค์ ๊ฐ๋ฅ
5. ๋ฐฐํฌ ์๋ฃ ํ Slack/์ด๋ฉ์ผ ์๋ฆผ
๋ฐฐํฌ ํ ๋ชจ๋ํฐ๋งํ๊ณ , ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ์ฆ์ ๋กค๋ฐฑ!
# ๋ฌธ์ ๋ฐ๊ฒฌ ์ Git revert
$ git revert HEAD
$ git push origin main
# CI/CD๊ฐ ์๋์ผ๋ก ์ด์ ์ํ๋ก ๋กค๋ฐฑ!
๐ง GitHub Actions๋ก ๊ตฌํํ๊ธฐ
์ค์ ๋ก GitHub Actions๋ฅผ ์ฌ์ฉํด์ Terraform CI/CD๋ฅผ ๊ตฌํํด๋ณผ๊น? ์์ ์ค์ ์์ ์ผ! ๐ช
my-terraform-project/
โโโ .github/
โ โโโ workflows/
โ โโโ terraform-plan.yml # PR ์์ฑ ์ ์คํ
โ โโโ terraform-apply.yml # ๋จธ์ง ์ ์คํ
โโโ environments/
โ โโโ dev/
โ โ โโโ main.tf
โ โ โโโ variables.tf
โ โโโ staging/
โ โ โโโ main.tf
โ โ โโโ variables.tf
โ โโโ production/
โ โโโ main.tf
โ โโโ variables.tf
โโโ modules/
โโโ vpc/
โโโ ec2/
โโโ rds/
name: 'Terraform Plan'
on:
pull_request:
branches:
- main
paths:
- 'environments/**'
- 'modules/**'
env:
TF_VERSION: '1.5.0'
AWS_REGION: 'ap-northeast-2'
jobs:
terraform-plan:
name: 'Terraform Plan'
runs-on: ubuntu-latest
strategy:
matrix:
environment: [dev, staging, production]
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Format Check
id: fmt
run: terraform fmt -check -recursive
continue-on-error: true
- name: Terraform Init
id: init
run: |
cd environments/${{ matrix.environment }}
terraform init
- name: Terraform Validate
id: validate
run: |
cd environments/${{ matrix.environment }}
terraform validate -no-color
- name: Terraform Plan
id: plan
run: |
cd environments/${{ matrix.environment }}
terraform plan -no-color -out=tfplan
continue-on-error: true
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.0
with:
working_directory: environments/${{ matrix.environment }}
- name: Comment PR
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Terraform Format and Style ๐\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization โ๏ธ\`${{ steps.init.outcome }}\`
#### Terraform Validation ๐ค\`${{ steps.validate.outcome }}\`
#### Terraform Plan ๐\`${{ steps.plan.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`\n
${process.env.PLAN}
\`\`\`
</details>
*Environment: ${{ matrix.environment }}*
*Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
- name: Terraform Plan Status
if: steps.plan.outcome == 'failure'
run: exit 1
name: 'Terraform Apply'
on:
push:
branches:
- main
paths:
- 'environments/**'
- 'modules/**'
env:
TF_VERSION: '1.5.0'
AWS_REGION: 'ap-northeast-2'
jobs:
terraform-apply-dev:
name: 'Apply to Dev'
runs-on: ubuntu-latest
environment: dev
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init
run: |
cd environments/dev
terraform init
- name: Terraform Apply
run: |
cd environments/dev
terraform apply -auto-approve
- name: Notify Slack
uses: 8398a7/action-slack@v3
if: always()
with:
status: ${{ job.status }}
text: 'Terraform Apply to Dev: ${{ job.status }}'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
terraform-apply-staging:
name: 'Apply to Staging'
runs-on: ubuntu-latest
needs: terraform-apply-dev
environment: staging
steps:
# Dev์ ๋์ผํ ์คํ
๋ค...
- name: Checkout
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init
run: |
cd environments/staging
terraform init
- name: Terraform Apply
run: |
cd environments/staging
terraform apply -auto-approve
terraform-apply-production:
name: 'Apply to Production'
runs-on: ubuntu-latest
needs: terraform-apply-staging
environment:
name: production
url: https://myapp.example.com
steps:
# Staging๊ณผ ๋์ผํ ์คํ
๋ค...
- name: Checkout
uses: actions/checkout@v3
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: ${{ env.TF_VERSION }}
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Terraform Init
run: |
cd environments/production
terraform init
- name: Terraform Apply
run: |
cd environments/production
terraform apply -auto-approve
- name: Notify Slack - Success
uses: 8398a7/action-slack@v3
if: success()
with:
status: custom
custom_payload: |
{
text: '๐ Production ๋ฐฐํฌ ์๋ฃ!',
attachments: [{
color: 'good',
text: `Commit: ${{ github.event.head_commit.message }}\nAuthor: ${{ github.actor }}`
}]
}
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
- Production์๋ ์น์ธ์(Reviewer) ์ค์
- ๊ฐ ํ๊ฒฝ๋ณ๋ก Secret ๋ณ์ ์ค์
- ๋ฐฐํฌ ๋ธ๋์น ์ ํ ์ค์
์ด๋ ๊ฒ ํ๋ฉด ํ๋ก๋์ ๋ฐฐํฌ ์ ์ ํ์ฅ์ ์น์ธ์ ๋ฐ์ ์ ์์ด! ๐
๐จ ์ค์ ์์ : 3-Tier ์น ์ ํ๋ฆฌ์ผ์ด์ ์ธํ๋ผ
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ด์ ์ค์ ์์ ๋ฅผ ๋ง๋ค์ด๋ณผ๊น? ์ ํ์ ์ธ 3-Tier ์น ์ ํ๋ฆฌ์ผ์ด์ ์ธํ๋ผ๋ฅผ Terraform์ผ๋ก ๊ตฌ์ถํด๋ณด์! ๐๏ธ
๐๏ธ ์ํคํ ์ฒ ๊ตฌ์ฑ
Presentation Tier (ํ๋ก ํธ์๋)
- CloudFront (CDN)
- S3 (์ ์ ํ์ผ ํธ์คํ
)
- Route53 (DNS)
Application Tier (๋ฐฑ์๋)
- Application Load Balancer
- Auto Scaling Group
- EC2 ์ธ์คํด์ค๋ค
Data Tier (๋ฐ์ดํฐ๋ฒ ์ด์ค)
- RDS (PostgreSQL)
- ElastiCache (Redis)
- S3 (๋ฐฑ์
๋ฐ ๋ก๊ทธ)
๐ ํ๋ก์ ํธ ๊ตฌ์กฐ
3tier-webapp/
โโโ main.tf # ๋ฉ์ธ ์ค์
โโโ variables.tf # ๋ณ์ ์ ์
โโโ outputs.tf # ์ถ๋ ฅ ๊ฐ
โโโ backend.tf # State ๋ฐฑ์๋ ์ค์
โโโ versions.tf # Provider ๋ฒ์
โ
โโโ modules/
โ โโโ networking/ # VPC, ์๋ธ๋ท, ๋ผ์ฐํ
โ โ โโโ main.tf
โ โ โโโ variables.tf
โ โ โโโ outputs.tf
โ โ
โ โโโ compute/ # EC2, ASG, ALB
โ โ โโโ main.tf
โ โ โโโ variables.tf
โ โ โโโ outputs.tf
โ โ
โ โโโ database/ # RDS, ElastiCache
โ โ โโโ main.tf
โ โ โโโ variables.tf
โ โ โโโ outputs.tf
โ โ
โ โโโ cdn/ # CloudFront, S3
โ โโโ main.tf
โ โโโ variables.tf
โ โโโ outputs.tf
โ
โโโ environments/
โโโ dev/
โ โโโ terraform.tfvars
โ โโโ backend.hcl
โโโ staging/
โ โโโ terraform.tfvars
โ โโโ backend.hcl
โโโ production/
โโโ terraform.tfvars
โโโ backend.hcl
๐ 1. Networking ๋ชจ๋
# VPC ์์ฑ
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.project_name}-vpc"
Environment = var.environment
}
}
# ์ธํฐ๋ท ๊ฒ์ดํธ์จ์ด
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.project_name}-igw"
}
}
# Public ์๋ธ๋ท๋ค (ALB์ฉ)
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.project_name}-public-${var.availability_zones[count.index]}"
Type = "Public"
}
}
# Private ์๋ธ๋ท๋ค (EC2์ฉ)
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 10)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.project_name}-private-${var.availability_zones[count.index]}"
Type = "Private"
}
}
# Database ์๋ธ๋ท๋ค (RDS์ฉ)
resource "aws_subnet" "database" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 20)
availability_zone = var.availability_zones[count.index]
tags = {
Name = "${var.project_name}-database-${var.availability_zones[count.index]}"
Type = "Database"
}
}
# NAT ๊ฒ์ดํธ์จ์ด์ฉ EIP
resource "aws_eip" "nat" {
count = length(var.availability_zones)
domain = "vpc"
tags = {
Name = "${var.project_name}-nat-eip-${count.index + 1}"
}
depends_on = [aws_internet_gateway.main]
}
# NAT ๊ฒ์ดํธ์จ์ด
resource "aws_nat_gateway" "main" {
count = length(var.availability_zones)
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${var.project_name}-nat-${count.index + 1}"
}
}
# Public ๋ผ์ฐํ
ํ
์ด๋ธ
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.project_name}-public-rt"
}
}
# Private ๋ผ์ฐํ
ํ
์ด๋ธ
resource "aws_route_table" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = {
Name = "${var.project_name}-private-rt-${count.index + 1}"
}
}
# ๋ผ์ฐํ
ํ
์ด๋ธ ์ฐ๊ฒฐ
resource "aws_route_table_association" "public" {
count = length(var.availability_zones)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private" {
count = length(var.availability_zones)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
๐ป 2. Compute ๋ชจ๋
# Application Load Balancer
resource "aws_lb" "main" {
name = "${var.project_name}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnet_ids
enable_deletion_protection = var.environment == "production" ? true : false
tags = {
Name = "${var.project_name}-alb"
Environment = var.environment
}
}
# ALB ํ๊ฒ ๊ทธ๋ฃน
resource "aws_lb_target_group" "app" {
name = "${var.project_name}-tg"
port = 80
protocol = "HTTP"
vpc_id = var.vpc_id
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/health"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 2
}
tags = {
Name = "${var.project_name}-tg"
}
}
# ALB ๋ฆฌ์ค๋
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.main.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
# HTTPS ๋ฆฌ์ค๋ (์ธ์ฆ์ ์์ ๊ฒฝ์ฐ)
resource "aws_lb_listener" "https" {
count = var.certificate_arn != "" ? 1 : 0
load_balancer_arn = aws_lb.main.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01"
certificate_arn = var.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.app.arn
}
}
# Launch Template
resource "aws_launch_template" "app" {
name_prefix = "${var.project_name}-lt-"
image_id = var.ami_id
instance_type = var.instance_type
vpc_security_group_ids = [aws_security_group.app.id]
iam_instance_profile {
name = aws_iam_instance_profile.app.name
}
user_data = base64encode(templatefile("${path.module}/user_data.sh", {
environment = var.environment
app_name = var.project_name
}))
block_device_mappings {
device_name = "/dev/xvda"
ebs {
volume_size = 20
volume_type = "gp3"
delete_on_termination = true
encrypted = true
}
}
monitoring {
enabled = true
}
tag_specifications {
resource_type = "instance"
tags = {
Name = "${var.project_name}-instance"
Environment = var.environment
}
}
}
# Auto Scaling Group
resource "aws_autoscaling_group" "app" {
name = "${var.project_name}-asg"
vpc_zone_identifier = var.private_subnet_ids
target_group_arns = [aws_lb_target_group.app.arn]
health_check_type = "ELB"
health_check_grace_period = 300
min_size = var.asg_min_size
max_size = var.asg_max_size
desired_capacity = var.asg_desired_capacity
launch_template {
id = aws_launch_template.app.id
version = "$Latest"
}
tag {
key = "Name"
value = "${var.project_name}-asg-instance"
propagate_at_launch = true
}
tag {
key = "Environment"
value = var.environment
propagate_at_launch = true
}
}
# Auto Scaling ์ ์ฑ
- CPU ๊ธฐ๋ฐ
resource "aws_autoscaling_policy" "cpu_scale_up" {
name = "${var.project_name}-cpu-scale-up"
scaling_adjustment = 1
adjustment_type = "ChangeInCapacity"
cooldown = 300
autoscaling_group_name = aws_autoscaling_group.app.name
}
resource "aws_autoscaling_policy" "cpu_scale_down" {
name = "${var.project_name}-cpu-scale-down"
scaling_adjustment = -1
adjustment_type = "ChangeInCapacity"
cooldown = 300
autoscaling_group_name = aws_autoscaling_group.app.name
}
# CloudWatch ์๋ - CPU ๋์
resource "aws_cloudwatch_metric_alarm" "cpu_high" {
alarm_name = "${var.project_name}-cpu-high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = "120"
statistic = "Average"
threshold = "80"
dimensions = {
AutoScalingGroupName = aws_autoscaling_group.app.name
}
alarm_actions = [aws_autoscaling_policy.cpu_scale_up.arn]
}
# CloudWatch ์๋ - CPU ๋ฎ์
resource "aws_cloudwatch_metric_alarm" "cpu_low" {
alarm_name = "${var.project_name}-cpu-low"
comparison_operator = "LessThanThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = "120"
statistic = "Average"
threshold = "20"
dimensions = {
AutoScalingGroupName = aws_autoscaling_group.app.name
}
alarm_actions = [aws_autoscaling_policy.cpu_scale_down.arn]
}
# Security Group - ALB
resource "aws_security_group" "alb" {
name = "${var.project_name}-alb-sg"
description = "Security group for ALB"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
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"]
}
tags = {
Name = "${var.project_name}-alb-sg"
}
}
# Security Group - Application
resource "aws_security_group" "app" {
name = "${var.project_name}-app-sg"
description = "Security group for application instances"
vpc_id = var.vpc_id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.project_name}-app-sg"
}
}
๐๏ธ 3. Database ๋ชจ๋
# RDS ์๋ธ๋ท ๊ทธ๋ฃน
resource "aws_db_subnet_group" "main" {
name = "${var.project_name}-db-subnet-group"
subnet_ids = var.database_subnet_ids
tags = {
Name = "${var.project_name}-db-subnet-group"
}
}
# RDS ์ธ์คํด์ค
resource "aws_db_instance" "main" {
identifier = "${var.project_name}-db"
engine = "postgres"
engine_version = "14.7"
instance_class = var.db_instance_class
allocated_storage = var.db_allocated_storage
max_allocated_storage = var.db_max_allocated_storage
storage_type = "gp3"
storage_encrypted = true
db_name = var.db_name
username = var.db_username
password = var.db_password # ์ค์ ๋ก๋ AWS Secrets Manager ์ฌ์ฉ ๊ถ์ฅ!
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.rds.id]
multi_az = var.environment == "production" ? true : false
backup_retention_period = var.environment == "production" ? 7 : 1
backup_window = "03:00-04:00"
maintenance_window = "mon:04:00-mon:05:00"
enabled_cloudwatch_logs_exports = ["postgresql", "upgrade"]
skip_final_snapshot = var.environment != "production"
final_snapshot_identifier = var.environment == "production" ? "${var.project_name}-final-snapshot-${formatdate("YYYY-MM-DD-hhmm", timestamp())}" : null
tags = {
Name = "${var.project_name}-db"
Environment = var.environment
}
}
# RDS ์ฝ๊ธฐ ์ ์ฉ ๋ณต์ ๋ณธ (ํ๋ก๋์
๋ง)
resource "aws_db_instance" "read_replica" {
count = var.environment == "production" ? 1 : 0
identifier = "${var.project_name}-db-replica"
replicate_source_db = aws_db_instance.main.identifier
instance_class = var.db_instance_class
skip_final_snapshot = true
tags = {
Name = "${var.project_name}-db-replica"
Environment = var.environment
}
}
# ElastiCache ์๋ธ๋ท ๊ทธ๋ฃน
resource "aws_elasticache_subnet_group" "main" {
name = "${var.project_name}-cache-subnet-group"
subnet_ids = var.database_subnet_ids
}
# ElastiCache Redis ํด๋ฌ์คํฐ
resource "aws_elasticache_replication_group" "main" {
replication_group_id = "${var.project_name}-redis"
replication_group_description = "Redis cluster for ${var.project_name}"
engine = "redis"
engine_version = "7.0"
node_type = var.redis_node_type
num_cache_clusters = var.environment == "production" ? 2 : 1
port = 6379
parameter_group_name = "default.redis7"
subnet_group_name = aws_elasticache_subnet_group.main.name
security_group_ids = [aws_security_group.redis.id]
at_rest_encryption_enabled = true
transit_encryption_enabled = true
auth_token = var.redis_auth_token
automatic_failover_enabled = var.environment == "production" ? true : false
multi_az_enabled = var.environment == "production" ? true : false
snapshot_retention_limit = var.environment == "production" ? 5 : 0
snapshot_window = "03:00-05:00"
tags = {
Name = "${var.project_name}-redis"
Environment = var.environment
}
}
# Security Group - RDS
resource "aws_security_group" "rds" {
name = "${var.project_name}-rds-sg"
description = "Security group for RDS"
vpc_id = var.vpc_id
ingress {
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
tags = {
Name = "${var.project_name}-rds-sg"
}
}
# Security Group - Redis
resource "aws_security_group" "redis" {
name = "${var.project_name}-redis-sg"
description = "Security group for Redis"
vpc_id = var.vpc_id
ingress {
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [var.app_security_group_id]
}
tags = {
Name = "${var.project_name}-redis-sg"
}
}
๐ 4. ๋ฉ์ธ ์ค์ ํ์ผ
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
default_tags {
tags = {
Project = var.project_name
Environment = var.environment
ManagedBy = "Terraform"
}
}
}
# Networking ๋ชจ๋
module "networking" {
source = "./modules/networking"
project_name = var.project_name
environment = var.environment
vpc_cidr = var.vpc_cidr
availability_zones = var.availability_zones
}
# Compute ๋ชจ๋
module "compute" {
source = "./modules/compute"
project_name = var.project_name
environment = var.environment
vpc_id = module.networking.vpc_id
public_subnet_ids = module.networking.public_subnet_ids
private_subnet_ids = module.networking.private_subnet_ids
ami_id = var.ami_id
instance_type = var.instance_type
asg_min_size = var.asg_min_size
asg_max_size = var.asg_max_size
asg_desired_capacity = var.asg_desired_capacity
certificate_arn = var.certificate_arn
}
# Database ๋ชจ๋
module "database" {
source = "./modules/database"
project_name = var.project_name
environment = var.environment
vpc_id = module.networking.vpc_id
database_subnet_ids = module.networking.database_subnet_ids
app_security_group_id = module.compute.app_security_group_id
db_instance_class = var.db_instance_class
db_allocated_storage = var.db_allocated_storage
db_max_allocated_storage = var.db_max_allocated_storage
db_name = var.db_name
db_username = var.db_username
db_password = var.db_password
redis_node_type = var.redis_node_type
redis_auth_token = var.redis_auth_token
}
๐ 5. ํ๊ฒฝ๋ณ ๋ณ์ ์ค์
# ํ๋ก์ ํธ ์ค์
project_name = "myapp"
environment = "production"
aws_region = "ap-northeast-2"
# ๋คํธ์ํฌ ์ค์
vpc_cidr = "10.0.0.0/16"
availability_zones = ["ap-northeast-2a", "ap-northeast-2c"]
# Compute ์ค์
ami_id = "ami-0c55b159cbfafe1f0" # Amazon Linux 2
instance_type = "t3.medium"
asg_min_size = 2
asg_max_size = 10
asg_desired_capacity = 4
# Database ์ค์
db_instance_class = "db.t3.medium"
db_allocated_storage = 100
db_max_allocated_storage = 500
db_name = "myapp_prod"
db_username = "admin"
# db_password๋ ํ๊ฒฝ๋ณ์๋ Secrets Manager์์ ๊ฐ์ ธ์ค๊ธฐ!
# Redis ์ค์
redis_node_type = "cache.t3.medium"
# SSL ์ธ์ฆ์
certificate_arn = "arn:aws:acm:ap-northeast-2:123456789012:certificate/xxxxx"
# ํ๋ก์ ํธ ์ค์
project_name = "myapp"
environment = "dev"
aws_region = "ap-northeast-2"
# ๋คํธ์ํฌ ์ค์
vpc_cidr = "10.1.0.0/16"
availability_zones = ["ap-northeast-2a"] # ๊ฐ๋ฐ์ ๋จ์ผ AZ
# Compute ์ค์
ami_id = "ami-0c55b159cbfafe1f0"
instance_type = "t3.small" # ๊ฐ๋ฐ์ ์์ ์ธ์คํด์ค
asg_min_size = 1
asg_max_size = 2
asg_desired_capacity = 1
# Database ์ค์
db_instance_class = "db.t3.small"
db_allocated_storage = 20
db_max_allocated_storage = 50
db_name = "myapp_dev"
db_username = "admin"
# Redis ์ค์
redis_node_type = "cache.t3.micro"
# SSL ์ธ์ฆ์ (๊ฐ๋ฐ์ ์์ ์๋)
certificate_arn = ""
- Multi-AZ๋ ํ๋ก๋์ ์๋ง ์ ์ฉ
- ๊ฐ๋ฐ ํ๊ฒฝ์ ์ ๋ฌด ์๊ฐ ์ธ์๋ ์๋์ผ๋ก ์ค์ง
- Reserved Instance๋ Savings Plan ํ์ฉ
- ๋ถํ์ํ ๋ฆฌ์์ค๋ ์ฆ์ ์ญ์ (terraform destroy)
์ด๋ ๊ฒ ํ๋ฉด ํด๋ผ์ฐ๋ ๋น์ฉ์ 50% ์ด์ ์ ๊ฐํ ์ ์์ด! ๐ฐ
๐ก๏ธ ๋ณด์๊ณผ ๋ฒ ์คํธ ํ๋ํฐ์ค
Terraform๊ณผ CI/CD๋ฅผ ์ฌ์ฉํ ๋ ๋ณด์์ ์ ๋ง ์ค์ํด! ํ ๋ฒ์ ์ค์๋ก ํ์ฌ์ ๋ชจ๋ ์ธํ๋ผ๊ฐ ๋ ธ์ถ๋ ์ ์๊ฑฐ๋ . ๐ฑ ์ค์ ์์ ๊ผญ ์ง์ผ์ผ ํ ๋ณด์ ์์น๋ค์ ์์๋ณด์!
๐ 1. ๋ฏผ๊ฐํ ์ ๋ณด ๊ด๋ฆฌ
- State ํ์ผ์ Git์ ์ปค๋ฐ
- AWS Access Key๋ฅผ ์ฝ๋์ ํ๋์ฝ๋ฉ
- ํ๋ก๋์ ์๊ฒฉ์ฆ๋ช ์ ๊ฐ๋ฐ์ ๋ก์ปฌ์ ์ ์ฅ
AWS Secrets Manager ์ฌ์ฉ:
# Secrets Manager์์ ๋น๋ฐ๋ฒํธ ๊ฐ์ ธ์ค๊ธฐ
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/database/password"
}
resource "aws_db_instance" "main" {
# ... ๋ค๋ฅธ ์ค์ ๋ค ...
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
ํ๊ฒฝ ๋ณ์ ์ฌ์ฉ:
# GitHub Actions์์
env:
TF_VAR_db_password: ${{ secrets.DB_PASSWORD }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
OIDC๋ฅผ ํตํ ์ธ์ฆ (์ถ์ฒ!):
# GitHub Actions์์ OIDC ์ฌ์ฉ
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole
aws-region: ap-northeast-2
# Access Key ์์ด ์์ ํ๊ฒ ์ธ์ฆ!
๐ 2. ๋ณด์ ์ค์บ ์๋ํ
์ฝ๋๋ฅผ ๋ฐฐํฌํ๊ธฐ ์ ์ ์๋์ผ๋ก ๋ณด์ ์ทจ์ฝ์ ์ ๊ฒ์ฌํด์ผ ํด!
tfsec - Terraform ๋ณด์ ์ค์บ๋:# GitHub Actions์ ์ถ๊ฐ
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.0
with:
soft_fail: false # ๋ณด์ ์ด์ ๋ฐ๊ฒฌ ์ ๋น๋ ์คํจ
Checkov - ์ ์ฑ ๊ธฐ๋ฐ ์ค์บ๋:
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
์ฃผ์ ๊ฒ์ฌ ํญ๋ชฉ๋ค:
โ ์ํธํ๋์ง ์์ ์คํ ๋ฆฌ์ง
โ ๊ณต๊ฐ๋ ๋ณด์ ๊ทธ๋ฃน
โ ๋ก๊น ์ด ๋นํ์ฑํ๋ ๋ฆฌ์์ค
โ ์ทจ์ฝํ ์ํธํ ์๊ณ ๋ฆฌ์ฆ
โ ๊ณผ๋ํ IAM ๊ถํ
๐ฏ 3. ์ต์ ๊ถํ ์์น (Least Privilege)
IAM ์ญํ ๊ณผ ์ ์ฑ ์ ํ์ํ ์ต์ํ์ ๊ถํ๋ง ๋ถ์ฌํด์ผ ํด!
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
# ๋ชจ๋ ๊ถํ ๋ถ์ฌ - ์ ๋ ๊ธ์ง!
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeImages",
"ec2:DescribeKeyPairs",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeVpcs"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ec2:RunInstances",
"ec2:TerminateInstances"
],
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "ap-northeast-2"
}
}
}
]
}
# ํ์ํ ๊ถํ๋ง ๋ช
์์ ์ผ๋ก ๋ถ์ฌ
๐ 4. ์ฝ๋ ๋ฆฌ๋ทฐ ํ๋ก์ธ์ค
๋ชจ๋ ์ธํ๋ผ ๋ณ๊ฒฝ์ ๋ฐ๋์ ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ๊ฑฐ์ณ์ผ ํด!
๋ฆฌ๋ทฐ ์ฒดํฌ๋ฆฌ์คํธ:| ํญ๋ชฉ | ํ์ธ ์ฌํญ |
|---|---|
| ๋ณด์ | ๋ฏผ๊ฐํ ์ ๋ณด ๋ ธ์ถ ์ฌ๋ถ, ๋ณด์ ๊ทธ๋ฃน ์ค์ , ์ํธํ ์ ์ฉ |
| ๋น์ฉ | ์์ ๋น์ฉ ์ฆ๊ฐ, ๋ถํ์ํ ๋ฆฌ์์ค, ์ธ์คํด์ค ํ์ ์ ์ ์ฑ |
| ๊ฐ์ฉ์ฑ | Multi-AZ ์ค์ , ๋ฐฑ์ ์ ์ฑ , ์ฅ์ ๋ณต๊ตฌ ๊ณํ |
| ์ฑ๋ฅ | ๋ฆฌ์์ค ํฌ๊ธฐ ์ ์ ์ฑ, Auto Scaling ์ค์ , ์บ์ฑ ์ ๋ต |
| ๊ท์น ์ค์ | ๋ค์ด๋ฐ ์ปจ๋ฒค์ , ํ๊ทธ ์ ์ฑ , ๋ฌธ์ํ |
# CODEOWNERS
/environments/production/ @senior-devops @team-lead
/modules/database/ @database-admin @senior-devops
*.tf @devops-team
๐ 5. State ํ์ผ ๋ณด์
State ํ์ผ์๋ ๋ชจ๋ ์ธํ๋ผ ์ ๋ณด๊ฐ ๋ค์ด์์ด์ ์ ๋ง ์ค์ํด!
ํ์ ๋ณด์ ์ค์ :# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "production/terraform.tfstate"
region = "ap-northeast-2"
# ์ํธํ ํ์!
encrypt = true
kms_key_id = "arn:aws:kms:ap-northeast-2:123456789012:key/xxxxx"
# State Lock์ผ๋ก ๋์ ์์ ๋ฐฉ์ง
dynamodb_table = "terraform-lock"
# ๋ฒ์ ๊ด๋ฆฌ๋ก ๋กค๋ฐฑ ๊ฐ๋ฅ
versioning = true
}
}
# S3 ๋ฒํท ๋ณด์ ์ค์
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
# ๋ฒํท ์ ์ฑ
- ํน์ ์ญํ ๋ง ์ ๊ทผ ๊ฐ๋ฅ
resource "aws_s3_bucket_policy" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = [
aws_s3_bucket.terraform_state.arn,
"${aws_s3_bucket.terraform_state.arn}/*"
]
Condition = {
Bool = {
"aws:SecureTransport" = "false"
}
}
}
]
})
}
๐ 6. ๋ชจ๋ํฐ๋ง๊ณผ ์๋ฆผ
๋ฐฐํฌ ํ์๋ ๊ณ์ ๋ชจ๋ํฐ๋งํด์ผ ํด!
CloudWatch ์๋ ์ค์ :resource "aws_cloudwatch_metric_alarm" "unauthorized_api_calls" {
alarm_name = "UnauthorizedAPICalls"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "UnauthorizedAPICalls"
namespace = "AWS/CloudTrail"
period = "300"
statistic = "Sum"
threshold = "5"
alarm_description = "๋ฌด๋จ API ํธ์ถ ๊ฐ์ง"
alarm_actions = [aws_sns_topic.security_alerts.arn]
}
resource "aws_cloudwatch_metric_alarm" "root_account_usage" {
alarm_name = "RootAccountUsage"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "1"
metric_name = "RootAccountUsage"
namespace = "AWS/CloudTrail"
period = "60"
statistic = "Sum"
threshold = "0"
alarm_description = "๋ฃจํธ ๊ณ์ ์ฌ์ฉ ๊ฐ์ง"
alarm_actions = [aws_sns_topic.security_alerts.arn]
}
SNS ํ ํฝ์ผ๋ก ์๋ฆผ:
resource "aws_sns_topic" "security_alerts" {
name = "security-alerts"
}
resource "aws_sns_topic_subscription" "security_email" {
topic_arn = aws_sns_topic.security_alerts.arn
protocol = "email"
endpoint = "security-team@company.com"
}
resource "aws_sns_topic_subscription" "security_slack" {
topic_arn = aws_sns_topic.security_alerts.arn
protocol = "https"
endpoint = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
}
๐ 7. ํ ๊ต์ก๊ณผ ๋ฌธ์ํ
์๋ฌด๋ฆฌ ์ข์ ๋๊ตฌ์ ํ๋ก์ธ์ค๊ฐ ์์ด๋, ํ์๋ค์ด ์ ๋๋ก ์ดํดํ์ง ๋ชปํ๋ฉด ์์ฉ์์ด!
ํ์ ๋ฌธ์๋ค:โ ์ํคํ ์ฒ ๋ค์ด์ด๊ทธ๋จ
โ Terraform ๋ชจ๋ ์ฌ์ฉ ๊ฐ์ด๋
โ CI/CD ํ์ดํ๋ผ์ธ ์ค๋ช
โ ํธ๋ฌ๋ธ์ํ ๊ฐ์ด๋
โ ๊ธด๊ธ ์ํฉ ๋์ ๋งค๋ด์ผ
โ ๋ณด์ ์ ์ฑ ๋ฐ ์ฒดํฌ๋ฆฌ์คํธ
๐จ ํธ๋ฌ๋ธ์ํ : ์์ฃผ ๋ง๋๋ ๋ฌธ์ ๋ค
Terraform๊ณผ CI/CD๋ฅผ ์ฌ์ฉํ๋ค ๋ณด๋ฉด ์ฌ๋ฌ ๊ฐ์ง ๋ฌธ์ ๋ฅผ ๋ง๋๊ฒ ๋ผ. ๋ด๊ฐ ์ค์ ๋ก ๊ฒช์๋ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๊ณต์ ํ ๊ฒ! ๐
โ ๋ฌธ์ 1: State Lock ๊ฑธ๋ฆผ
์ฆ์:Error: Error acquiring the state lock
Error message: ConditionalCheckFailedException: The conditional request failed
Lock Info:
ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Path: my-bucket/terraform.tfstate
Operation: OperationTypeApply
Who: user@hostname
Version: 1.5.0
Created: 2024-01-15 10:30:00 UTC
์์ธ:
- ์ด์ Terraform ์คํ์ด ๋น์ ์ ์ข ๋ฃ๋จ
- ๋ค๋ฅธ ์ฌ๋์ด ๋์์ ์คํ ์ค
- CI/CD ํ์ดํ๋ผ์ธ์ด ์ค๋ณต ์คํ๋จ
ํด๊ฒฐ ๋ฐฉ๋ฒ:
# 1. ๋จผ์ ์ ๋ง๋ก ๋ค๋ฅธ ์ฌ๋์ด ์คํ ์ค์ธ์ง ํ์ธ!
# ํ์๋ค์๊ฒ ๋ฌผ์ด๋ณด๊ธฐ
# 2. ํ์คํ ์๋ฌด๋ ์คํ ์ค์ด ์๋๋ผ๋ฉด Lock ๊ฐ์ ํด์
$ terraform force-unlock a1b2c3d4-e5f6-7890-abcd-ef1234567890
# 3. DynamoDB์์ ์ง์ ํ์ธ
$ aws dynamodb get-item \
--table-name terraform-lock \
--key '{"LockID": {"S": "my-bucket/terraform.tfstate"}}'
โ ๋ฌธ์ 2: State Drift (์ํ ๋ถ์ผ์น)
์ฆ์:๋๊ตฐ๊ฐ AWS ์ฝ์์์ ์ง์ ๋ฆฌ์์ค๋ฅผ ์์ ํด์, Terraform State์ ์ค์ ์ธํ๋ผ๊ฐ ๋ฌ๋ผ์ง.
๊ฐ์ง ๋ฐฉ๋ฒ:
$ terraform plan
# ์ถ๋ ฅ:
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the
last "terraform apply":
# aws_instance.web_server has changed
~ resource "aws_instance" "web_server" {
id = "i-0123456789abcdef"
~ instance_type = "t2.micro" -> "t2.small"
# (10 unchanged attributes hidden)
}
ํด๊ฒฐ ๋ฐฉ๋ฒ:
# ๋ฐฉ๋ฒ 1: Terraform ์ฝ๋๋ฅผ ์ค์ ์ํ์ ๋ง์ถค
# main.tf๋ฅผ ์์ ํด์ instance_type = "t2.small"๋ก ๋ณ๊ฒฝ
# ๋ฐฉ๋ฒ 2: ์ค์ ์ธํ๋ผ๋ฅผ Terraform ์ฝ๋์ ๋ง์ถค
$ terraform apply
# ๋ค์ t2.micro๋ก ๋ณ๊ฒฝ๋จ
# ๋ฐฉ๋ฒ 3: State๋ฅผ ์ค์ ์ํ๋ก ์
๋ฐ์ดํธ (๊ถ์ฅํ์ง ์์)
$ terraform refresh
- CloudTrail๋ก ๋ชจ๋ ๋ณ๊ฒฝ์ฌํญ ๋ก๊น
- ํ ๊ท์น: ๋ชจ๋ ๋ณ๊ฒฝ์ Terraform์ ํตํด์๋ง!
- ํ๋ก๋์ ํ๊ฒฝ์ ์ฝ์ ์ ๊ทผ ์ ํ
โ ๋ฌธ์ 3: ์ํ ์์กด์ฑ (Circular Dependency)
์ฆ์:Error: Cycle: aws_security_group.app, aws_security_group.db
์์ธ:
๋ ๋ฆฌ์์ค๊ฐ ์๋ก๋ฅผ ์ฐธ์กฐํ๊ณ ์์.
# ์๋ชป๋ ์
resource "aws_security_group" "app" {
# ...
egress {
security_groups = [aws_security_group.db.id]
}
}
resource "aws_security_group" "db" {
# ...
ingress {
security_groups = [aws_security_group.app.id]
}
}
ํด๊ฒฐ ๋ฐฉ๋ฒ:
# ์ฌ๋ฐ๋ฅธ ๋ฐฉ๋ฒ: Security Group Rule์ ๋ณ๋๋ก ๋ถ๋ฆฌ
resource "aws_security_group" "app" {
name = "app-sg"
# egress ๊ท์น ์ ๊ฑฐ
}
resource "aws_security_group" "db" {
name = "db-sg"
# ingress ๊ท์น ์ ๊ฑฐ
}
# ๋ณ๋์ ๊ท์น์ผ๋ก ์ ์
resource "aws_security_group_rule" "app_to_db" {
type = "egress"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_group_id = aws_security_group.app.id
source_security_group_id = aws_security_group.db.id
}
resource "aws_security_group_rule" "db_from_app" {
type = "ingress"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_group_id = aws_security_group.db.id
source_security_group_id = aws_security_group.app.id
}
โ ๋ฌธ์ 4: Provider ๋ฒ์ ์ถฉ๋
์ฆ์:Error: Failed to query available provider packages
Could not retrieve the list of available versions for provider
hashicorp/aws: locked provider registry.terraform.io/hashicorp/aws 4.0.0
does not match configured version constraint ~> 5.0
ํด๊ฒฐ ๋ฐฉ๋ฒ:
# 1. .terraform.lock.hcl ํ์ผ ์ญ์
$ rm .terraform.lock.hcl
# 2. .terraform ๋๋ ํ ๋ฆฌ ์ญ์
$ rm -rf .terraform
# 3. ๋ค์ ์ด๊ธฐํ
$ terraform init -upgrade
# 4. ์๋ก์ด lock ํ์ผ ์์ฑ๋จ
- .terraform.lock.hcl์ Git์ ์ปค๋ฐ
- ํ ์ ์ฒด๊ฐ ๊ฐ์ Terraform ๋ฒ์ ์ฌ์ฉ
- ๋ฉ์ด์ ๋ฒ์ ์ ๊ทธ๋ ์ด๋๋ ์ ์คํ๊ฒ!
โ ๋ฌธ์ 5: ๋ฆฌ์์ค ์ญ์ ์คํจ
์ฆ์:Error: Error deleting VPC: DependencyViolation: The vpc 'vpc-xxxxx' has
dependencies and cannot be deleted.
์์ธ:
VPC ์์ ๋ค๋ฅธ ๋ฆฌ์์ค๋ค์ด ๋จ์์์ (ENI, NAT Gateway ๋ฑ)
ํด๊ฒฐ ๋ฐฉ๋ฒ:
# 1. ์์กด์ฑ ํ์ธ
$ terraform state list
# 2. ์์กด์ฑ ๊ทธ๋ํ ํ์ธ
$ terraform graph | dot -Tpng > graph.png
# 3. ์๋์ผ๋ก ๋ฆฌ์์ค ์ ๋ฆฌ
$ aws ec2 describe-network-interfaces --filters "Name=vpc-id,Values=vpc-xxxxx"
# 4. Terraform์ผ๋ก ๋ค์ ์๋
$ terraform destroy -target=aws_vpc.main
# 5. ๊ทธ๋๋ ์ ๋๋ฉด State์์ ์ ๊ฑฐํ๊ณ ์๋ ์ญ์
$ terraform state rm aws_vpc.main
$ aws ec2 delete-vpc --vpc-id vpc-xxxxx
โ ๋ฌธ์ 6: CI/CD ํ์ดํ๋ผ์ธ ์คํจ
์ฆ์:GitHub Actions์์ Terraform apply๊ฐ ์คํจํจ.
์ฃผ์ ์์ธ๋ค:
1. AWS ์๊ฒฉ์ฆ๋ช ๋ฌธ์
# ํด๊ฒฐ: GitHub Secrets ํ์ธ
Settings โ Secrets and variables โ Actions
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_REGION
2. ๊ถํ ๋ถ์กฑ
# IAM ์ ์ฑ
ํ์ธ
$ aws iam get-role-policy \
--role-name GitHubActionsRole \
--policy-name TerraformPolicy
3. ํ์์์
# GitHub Actions์์ ํ์์์ ๋๋ฆฌ๊ธฐ
jobs:
terraform:
timeout-minutes: 60 # ๊ธฐ๋ณธ๊ฐ์ 360๋ถ
4. ๋์ ์คํ
# ๋์ ์คํ ๋ฐฉ์ง
concurrency:
group: terraform-${{ github.ref }}
cancel-in-progress: false # ์งํ ์ค์ธ ์์
์ทจ์ํ์ง ์์
๐ฐ ๋น์ฉ ์ต์ ํ ์ ๋ต
ํด๋ผ์ฐ๋ ์ธํ๋ผ๋ฅผ ์๋ํํ๋ฉด ํธํ์ง๋ง, ๋ฐฉ์ฌํ๋ฉด ๋น์ฉ์ด ํญํ์ฒ๋ผ ๋์ฌ ์ ์์ด! ๐ฃ ์ค์ ์์ ์ฌ์ฉํ๋ ๋น์ฉ ์ต์ ํ ์ ๋ต๋ค์ ๊ณต์ ํ ๊ฒ.
๐ 1. Infracost๋ก ๋น์ฉ ์์ธก
Terraform ์ฝ๋๋ฅผ ์คํํ๊ธฐ ์ ์ ์์ ๋น์ฉ์ ๋ฏธ๋ฆฌ ํ์ธํ ์ ์์ด!
GitHub Actions์ ํตํฉ:- name: Setup Infracost
uses: infracost/actions/setup@v2
with:
api-key: ${{ secrets.INFRACOST_API_KEY }}
- name: Generate Infracost JSON
run: |
infracost breakdown --path . \
--format json \
--out-file /tmp/infracost.json
- name: Post Infracost comment
uses: infracost/actions/comment@v1
with:
path: /tmp/infracost.json
behavior: update
PR์ ์๋์ผ๋ก ๋น์ฉ ์ ๋ณด ํ์:
๐ฐ Monthly cost estimate
Name Monthly Qty Unit Monthly Cost
aws_instance.web_server
โโ Instance usage 730 hours $8.47
โโ root_block_device
โโ Storage 20 GB $2.00
aws_rds_instance.main
โโ Database instance 730 hours $73.00
โโ Storage 100 GB $11.50
OVERALL TOTAL $94.97
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ธ Change from current: +$94.97 (+100%)
โฐ 2. ์ค์ผ์ค๋ง์ผ๋ก ๋น์ฉ ์ ๊ฐ
๊ฐ๋ฐ/์คํ ์ด์ง ํ๊ฒฝ์ ์ ๋ฌด ์๊ฐ์๋ง ์ผ๋๊ณ , ๋ฐค์๋ ์๋์ผ๋ก ๊บผ๋ฒ๋ฆฌ์!
Lambda + EventBridge๋ก ์๋ํ:# lambda_scheduler.tf
resource "aws_lambda_function" "instance_scheduler" {
filename = "scheduler.zip"
function_name = "instance-scheduler"
role = aws_iam_role.lambda_scheduler.arn
handler = "index.handler"
runtime = "python3.9"
environment {
variables = {
ENVIRONMENT = var.environment
}
}
}
# ํ์ผ ์ค์ 9์์ ์์
resource "aws_cloudwatch_event_rule" "start_instances" {
name = "start-instances"
schedule_expression = "cron(0 0 * * MON-FRI *)" # UTC ๊ธฐ์ค
}
resource "aws_cloudwatch_event_target" "start_instances" {
rule = aws_cloudwatch_event_rule.start_instances.name
target_id = "StartInstances"
arn = aws_lambda_function.instance_scheduler.arn
input = jsonencode({
action = "start"
})
}
# ํ์ผ ์คํ 7์์ ์ค์ง
resource "aws_cloudwatch_event_rule" "stop_instances" {
name = "stop-instances"
schedule_expression = "cron(0 10 * * MON-FRI *)" # UTC ๊ธฐ์ค
}
resource "aws_cloudwatch_event_target" "stop_instances" {
rule = aws_cloudwatch_event_rule.stop_instances.name
target_id = "StopInstances"
arn = aws_lambda_function.instance_scheduler.arn
input = jsonencode({
action = "stop"
})
}
์์ ์ ๊ฐ์ก:
- ํ๋ฃจ 10์๊ฐ๋ง ์ด์ (์ ๋ฌด ์๊ฐ)
- ์ฃผ๋ง ์์ ์ค์ง
- ์ 730์๊ฐ โ ์ฝ 220์๊ฐ (70% ์ ๊ฐ!)
- EC2 t3.medium ๊ธฐ์ค: $30/์ โ $9/์ ๐ฐ
๐ฏ 3. ์ ์ ํ ์ธ์คํด์ค ํ์ ์ ํ
| ํ๊ฒฝ | ์ถ์ฒ ์ธ์คํด์ค | ์ด์ |
|---|---|---|
| ๊ฐ๋ฐ | t3.micro ~ t3.small | ๋ฒ์คํธ ๊ฐ๋ฅ, ์ ๋ ด, ๊ฐ๋ฐ์ ์ถฉ๋ถ |
| ์คํ ์ด์ง | t3.medium | ํ๋ก๋์ ๊ณผ ์ ์ฌํ ํ๊ฒฝ ํ ์คํธ |
| ํ๋ก๋์ | c5.large ์ด์ | ์์ ์ ์ธ ์ฑ๋ฅ, ์์ธก ๊ฐ๋ฅ |
| ๋ฐฐ์น ์์ | Spot Instance | ์ต๋ 90% ํ ์ธ! |
Spot Instance ํ์ฉ:
resource "aws_spot_instance_request" "batch_worker" {
ami = var.ami_id
instance_type = "c5.xlarge"
spot_price = "0.05" # ์ต๋ ์ง๋ถ ๊ฐ๊ฒฉ
wait_for_fulfillment = true
spot_type = "one-time"
tags = {
Name = "batch-worker"
}
}
# Spot Instance๋ ์ค๋จ๋ ์ ์์ผ๋ ์ค์ํ ์์
์ ํผํ๊ธฐ!
๐พ 4. ์คํ ๋ฆฌ์ง ์ต์ ํ
EBS ๋ณผ๋ฅจ ํ์ ์ ํ:resource "aws_ebs_volume" "data" {
availability_zone = "ap-northeast-2a"
# gp3๊ฐ gp2๋ณด๋ค 20% ์ ๋ ดํ๊ณ ์ฑ๋ฅ๋ ์ข์!
type = "gp3"
size = 100
# IOPS์ ์ฒ๋ฆฌ๋์ ํ์ํ ๋งํผ๋ง ์ค์
iops = 3000 # ๊ธฐ๋ณธ๊ฐ
throughput = 125 # ๊ธฐ๋ณธ๊ฐ
tags = {
Name = "data-volume"
}
}
S3 Lifecycle ์ ์ฑ :
resource "aws_s3_bucket_lifecycle_configuration" "logs" {
bucket = aws_s3_bucket.logs.id
rule {
id = "archive-old-logs"
status = "Enabled"
# 30์ผ ํ Glacier๋ก ์ด๋
transition {
days = 30
storage_class = "GLACIER"
}
# 90์ผ ํ ์ญ์
expiration {
days = 90
}
}
rule {
id = "delete-incomplete-uploads"
status = "Enabled"
abort_incomplete_multipart_upload {
days_after_initiation = 7
}
}
}
์์ ์ ๊ฐ์ก:
- S3 Standard: $0.025/GB
- S3 Glacier: $0.004/GB (84% ์ ๊ฐ!)
- 1TB ๋ก๊ทธ ๋ฐ์ดํฐ ๊ธฐ์ค: $25/์ โ $4/์ ๐ฐ
๐ 5. ์ฌ์ฉํ์ง ์๋ ๋ฆฌ์์ค ์ฐพ๊ธฐ
AWS Cost Explorer๋ก ๋ถ์:# Terraform์ผ๋ก Cost Explorer ํ์ฑํ
resource "aws_ce_cost_category" "environment" {
name = "Environment"
rule_version = "CostCategoryExpression.v1"
rule {
value = "Production"
rule {
dimension {
key = "TAG"
values = ["production"]
match_options = ["EQUALS"]
}
}
}
rule {
value = "Development"
rule {
dimension {
key = "TAG"
values = ["dev", "development"]
match_options = ["EQUALS"]
}
}
}
}
์ ๊ธฐ์ ์ผ๋ก ํ์ธํ ๊ฒ๋ค:
โ ์ฐ๊ฒฐ๋์ง ์์ EBS ๋ณผ๋ฅจ
โ ์ฌ์ฉํ์ง ์๋ Elastic IP
โ ์ค๋๋ ์ค๋ ์ท
โ ํธ๋ํฝ ์๋ Load Balancer
โ ๋น S3 ๋ฒํท
โ ์ฌ์ฉํ์ง ์๋ NAT Gateway
๐ ํ์ต ๋ก๋๋งต๊ณผ ๋ค์ ๋จ๊ณ
์ฌ๊ธฐ๊น์ง ์ฝ์๋ค๋ฉด ์ ๋ง ๋๋จํด! ๐ ์ด์ Terraform๊ณผ CI/CD์ ๊ธฐ๋ณธ์ ๋ง์คํฐํ์ด. ํ์ง๋ง DevOps์ ์ธ๊ณ๋ ์ ๋ง ๋๊ณ ๊น์ด. ๋ค์ ๋จ๊ณ๋ก ๋์๊ฐ๊ธฐ ์ํ ๋ก๋๋งต์ ์๋ ค์ค๊ฒ!
๐ ์ด๊ธ โ ์ค๊ธ (์ง๊ธ ๋น์ ์ ์์น!)
โ ์ด๋ฏธ ๋ฐฐ์ด ๊ฒ๋ค:- Terraform ๊ธฐ๋ณธ ๋ฌธ๋ฒ๊ณผ ์ํฌํ๋ก์ฐ
- ์ฃผ์ ๋ฆฌ์์ค ํ์ (VPC, EC2, RDS ๋ฑ)
- State ๊ด๋ฆฌ์ Remote Backend
- GitHub Actions๋ฅผ ํตํ CI/CD
- ๊ธฐ๋ณธ์ ์ธ ๋ณด์ ์ค์
๐ฏ ๋ค์ ํ์ต ๋ชฉํ:
1. Terraform ๋ชจ๋ ๊ณ ๊ธ ํ์ฉ
- ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ชจ๋ ์ค๊ณ
- ๋ชจ๋ ๋ฒ์ ๊ด๋ฆฌ
- Terraform Registry ํ์ฉ
2. ๋ค์ํ Provider ๊ฒฝํ
- Kubernetes Provider
- Helm Provider
- Datadog, PagerDuty ๋ฑ ๋ชจ๋ํฐ๋ง ๋๊ตฌ
3. ๊ณ ๊ธ State ๊ด๋ฆฌ
- Workspace ํ์ฉ
- State ๋ง์ด๊ทธ๋ ์ด์
- State ๋ฐฑ์ ๋ฐ ๋ณต๊ตฌ
4. ํ ์คํธ ์๋ํ
- Terratest๋ก ์ธํ๋ผ ํ ์คํธ
- Kitchen-Terraform
- Compliance ํ ์คํธ
๐ ์ค๊ธ โ ๊ณ ๊ธ
1. GitOps ์์ ์ ๋ณต# ArgoCD + Terraform
resource "kubernetes_namespace" "argocd" {
metadata {
name = "argocd"
}
}
resource "helm_release" "argocd" {
name = "argocd"
repository = "https://argoproj.github.io/argo-helm"
chart = "argo-cd"
namespace = kubernetes_namespace.argocd.metadata[0].name
set {
name = "server.service.type"
value = "LoadBalancer"
}
}
2. Multi-Cloud ์ ๋ต
- AWS + GCP ๋์ ๊ด๋ฆฌ
- Cloud-agnostic ์ํคํ ์ฒ ์ค๊ณ
- ํด๋ผ์ฐ๋ ๊ฐ ๋ฐ์ดํฐ ๋๊ธฐํ
3. ๋๊ท๋ชจ ์ธํ๋ผ ๊ด๋ฆฌ
- Terraform Cloud/Enterprise
- Sentinel ์ ์ฑ ๊ด๋ฆฌ
- Cost Estimation & Governance
- Private Module Registry
4. ๊ณ ๊ธ ๋ณด์
- Vault๋ฅผ ํตํ ์ํฌ๋ฆฟ ๊ด๋ฆฌ
- OPA (Open Policy Agent) ํตํฉ
- Compliance as Code
- Zero Trust ์ํคํ ์ฒ
๐ ๏ธ ์ค์ ํ๋ก์ ํธ ์์ด๋์ด
ํ๋ก์ ํธ 1: ๊ฐ์ธ ๋ธ๋ก๊ทธ ์ธํ๋ผ- CloudFront + S3๋ก ์ ์ ์ฌ์ดํธ
- Route53์ผ๋ก ์ปค์คํ ๋๋ฉ์ธ
- ACM์ผ๋ก SSL ์ธ์ฆ์
- GitHub Actions๋ก ์๋ ๋ฐฐํฌ
- ์์ ๋น์ฉ: ์ $5 ์ดํ
ํ๋ก์ ํธ 2: ๋ง์ดํฌ๋ก์๋น์ค ํ๋ซํผ
- EKS ํด๋ฌ์คํฐ ๊ตฌ์ถ
- Service Mesh (Istio)
- ์ค์ํ๋ ๋ก๊น (ELK Stack)
- ๋ถ์ฐ ์ถ์ (Jaeger)
- ์์ ๋น์ฉ: ์ $200-500
ํ๋ก์ ํธ 3: ๋ฐ์ดํฐ ํ์ดํ๋ผ์ธ
- Airflow on ECS
- Redshift ๋ฐ์ดํฐ ์จ์ดํ์ฐ์ค
- Glue ETL ์์
- QuickSight ๋์๋ณด๋
- ์์ ๋น์ฉ: ์ $300-800
๐ ์ถ์ฒ ํ์ต ์๋ฃ
๊ณต์ ๋ฌธ์:- Terraform Documentation (terraform.io/docs)
- AWS Well-Architected Framework
- Kubernetes Documentation
์จ๋ผ์ธ ๊ฐ์:
- HashiCorp Learn (learn.hashicorp.com)
- A Cloud Guru
- Udemy์ Terraform ๊ฐ์๋ค
์ปค๋ฎค๋ํฐ:
- Terraform Community Forum
- Reddit r/Terraform
- DevOps Korea ์ปค๋ฎค๋ํฐ
- ์ฌ๋ฅ๋ท์ DevOps ์ ๋ฌธ๊ฐ๋ค
์ฑ :
- "Terraform: Up & Running" by Yevgeniy Brikman
- "Infrastructure as Code" by Kief Morris
- "The DevOps Handbook"
- AWS Certified DevOps Engineer
- Certified Kubernetes Administrator (CKA)
์ด๋ฐ ์๊ฒฉ์ฆ๋ค์ ์ทจ์ ์ด๋ ์ด์งํ ๋ ์ ๋ง ๋์์ด ๋ผ!
๐ค ์ปค๋ฎค๋ํฐ ์ฐธ์ฌํ๊ธฐ
ํผ์ ๊ณต๋ถํ๋ ๊ฒ๋ ์ข์ง๋ง, ์ปค๋ฎค๋ํฐ์ ์ฐธ์ฌํ๋ฉด ํจ์ฌ ๋น ๋ฅด๊ฒ ์ฑ์ฅํ ์ ์์ด!
์คํ์์ค ๊ธฐ์ฌ:- Terraform Provider ๋ฒ๊ทธ ๋ฆฌํฌํธ
- ๋ฌธ์ ๊ฐ์ PR
- ์ปค๋ฎค๋ํฐ ๋ชจ๋ ๊ฐ๋ฐ
์ง์ ๊ณต์ :
- ๊ธฐ์ ๋ธ๋ก๊ทธ ์์ฑ
- ์ปจํผ๋ฐ์ค ๋ฐํ
- ์ฌ๋ฅ๋ท์์ ๋ฉํ ๋ง ์ ๊ณต
- YouTube ํํ ๋ฆฌ์ผ ์ ์
๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด. ๐ Terraform๊ณผ CI/CD๋ฅผ ํ์ฉํ ํด๋ผ์ฐ๋ ์ธํ๋ผ ์๋ํ์ ๋ํด ์ ๋ง ๋ง์ ๊ฑธ ๋ฐฐ์ ์ง?
โจ ํต์ฌ ์์ฝ
1. Infrastructure as Code์ ํ- ์ฝ๋๋ก ์ธํ๋ผ๋ฅผ ๊ด๋ฆฌํ๋ฉด ์ผ๊ด์ฑ, ์ฌํ์ฑ, ๋ฒ์ ๊ด๋ฆฌ๊ฐ ๊ฐ๋ฅํด
- Terraform์ ์ ์ธ์ ๋ฌธ๋ฒ์ผ๋ก ์ฝ๊ฒ ๋ฐฐ์ธ ์ ์์ด
- ๋ฉํฐ ํด๋ผ์ฐ๋ ์ง์์ผ๋ก ๋ฒค๋ ์ข ์์ฑ ์์
2. CI/CD ํตํฉ์ ์ค์์ฑ
- ์๋ํ๋ ํ ์คํธ์ ๋ฐฐํฌ๋ก ํด๋จผ ์๋ฌ ๋ฐฉ์ง
- Pull Request ๊ธฐ๋ฐ ์ํฌํ๋ก์ฐ๋ก ํ์ ๊ฐํ
- GitOps๋ก ๋ชจ๋ ๋ณ๊ฒฝ์ฌํญ ์ถ์ ๊ฐ๋ฅ
3. ๋ณด์์ ์ ํ์ด ์๋ ํ์
- ๋ฏผ๊ฐํ ์ ๋ณด๋ ์ ๋ ์ฝ๋์ ๋ฃ์ง ๋ง๊ธฐ
- State ํ์ผ ์ํธํ์ ์ ๊ทผ ์ ์ด
- ์๋ํ๋ ๋ณด์ ์ค์บ ๋๊ตฌ ํ์ฉ
4. ๋น์ฉ ์ต์ ํ
- ํ๊ฒฝ๋ณ๋ก ์ ์ ํ ๋ฆฌ์์ค ํฌ๊ธฐ ์ ํ
- ์ค์ผ์ค๋ง์ผ๋ก ๋ถํ์ํ ๋น์ฉ ์ ๊ฐ
- Infracost๋ก ์ฌ์ ์ ๋น์ฉ ์์ธก
5. ์ง์์ ์ธ ํ์ต
- DevOps๋ ๋์์์ด ์งํํ๋ ๋ถ์ผ
- ์ปค๋ฎค๋ํฐ ์ฐธ์ฌ์ ์ง์ ๊ณต์
- ์ค์ ํ๋ก์ ํธ๋ก ๊ฒฝํ ์๊ธฐ
๐ ๋น์ ์ ๋ค์ ์ก์ ํ๋
์ค๋ ๋น์ฅ ์์ํ ์ ์๋ ๊ฒ๋ค:1๋จ๊ณ: ํ๊ฒฝ ์ค์ (30๋ถ)
- Terraform ์ค์น
- AWS CLI ์ค์
- GitHub ์ ์ฅ์ ์์ฑ
2๋จ๊ณ: ์ฒซ ํ๋ก์ ํธ (2-3์๊ฐ)
- S3 ๋ฒํท ํ๋ ๋ง๋ค์ด๋ณด๊ธฐ
- Terraform ์ํฌํ๋ก์ฐ ์ตํ๊ธฐ
- GitHub Actions ์ฐ๋
3๋จ๊ณ: ์ค์ ์ ์ฉ (1์ฃผ์ผ)
- ํ์ฌ/๊ฐ์ธ ํ๋ก์ ํธ์ ์ ์ฉ
- ํ์๋ค๊ณผ ์ง์ ๊ณต์
- ๋ฌธ์ ํด๊ฒฐ ๊ฒฝํ ์๊ธฐ
4๋จ๊ณ: ์ฌํ ํ์ต (์ง์์ )
- ๊ณ ๊ธ ๊ธฐ๋ฅ ํ์ต
- ์ปค๋ฎค๋ํฐ ์ฐธ์ฌ
- ์๊ฒฉ์ฆ ์ทจ๋ ๊ณ ๋ ค
๐ฌ ๋ง์ง๋ง ํ๋ง๋
DevOps์ ์ธํ๋ผ ์๋ํ๋ ์ฒ์์๋ ์ด๋ ต๊ฒ ๋๊ปด์ง ์ ์์ด. ํ์ง๋ง ํ ๋ฒ ์ต์ํด์ง๋ฉด, ์ด์ ์ผ๋ก๋ ์ ๋ ๋์๊ฐ ์ ์์ ๊ฑฐ์ผ! ๐
์๋์ผ๋ก ์๋ฒ๋ฅผ ์ค์ ํ๋ ์์ ์ด ์ผ๋ง๋ ๋นํจ์จ์ ์ด์๋์ง ๊นจ๋ซ๊ฒ ๋ ๊ฑฐ๊ณ , ์ฝ๋ ํ ์ค๋ก ์๋ฐฑ ๊ฐ์ ์๋ฒ๋ฅผ ๊ด๋ฆฌํ๋ ์์ ์ ๋ฐ๊ฒฌํ๊ฒ ๋ ๊ฑฐ์ผ.
์ค์๋ฅผ ๋๋ ค์ํ์ง ๋ง! ๊ฐ๋ฐ ํ๊ฒฝ์์ ๋ง์๊ป ์คํํ๊ณ , ์คํจํ๊ณ , ๋ฐฐ์. ๊ทธ๊ฒ ์ฑ์ฅํ๋ ๊ฐ์ฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ์ด์ผ. ๐ช
๊ทธ๋ฆฌ๊ณ ํผ์ ๊ณ ๋ฏผํ์ง ๋ง๊ณ , ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ ๋ฌธ๊ฐ๋ค์ ๋์์ ๋ฐ๊ฑฐ๋, ๋ฐ๋๋ก ๋น์ ์ ์ง์์ ๊ณต์ ํ๋ฉด์ ํจ๊ป ์ฑ์ฅํ์!
๋น์ ์ DevOps ์ฌ์ ์ ์์ํด! ํ์ดํ
! ๐โจ
- GitHub์์ ์คํ์์ค ํ๋ก์ ํธ์ ์ฐธ์ฌํด๋ณด์ธ์
- ๊ธฐ์ ๋ธ๋ก๊ทธ๋ฅผ ์์ํด์ ๋ฐฐ์ด ๋ด์ฉ์ ์ ๋ฆฌํด๋ณด์ธ์
- ์ปจํผ๋ฐ์ค๋ ๋ฐ์ ์ ์ฐธ์ํด์ ๋คํธ์ํนํ์ธ์
ํจ๊ป ๋ฐฐ์ฐ๊ณ ์ฑ์ฅํ๋ DevOps ์ปค๋ฎค๋ํฐ๋ฅผ ๋ง๋ค์ด๊ฐ์! ๐ค
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.