Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 63 additions & 36 deletions .github/workflows/terraform-plan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,16 @@ permissions:
pull-requests: write
id-token: write

# 환경변수 설정
env:
GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
GCP_SERVICE_ACCOUNT: ${{ secrets.GCP_SERVICE_ACCOUNT }}
TERRAFORM_STATE_BUCKET: ${{ secrets.TERRAFORM_STATE_BUCKET }}
TERRAFORM_TFVARS_PROD: ${{ secrets.TERRAFORM_TFVARS_PROD }}

jobs:
# 변경된 Terraform 파일을 기준으로 영향받는 환경을 계산
detect-changes:
name: 변경 환경 감지
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
env:
GCP_WORKLOAD_IDENTITY_PROVIDER: ""
GCP_SERVICE_ACCOUNT: ""
TERRAFORM_STATE_BUCKET: ""
TERRAFORM_TFVARS_PROD: ""
outputs:
environments: ${{ steps.detect.outputs.environments || '[]' }}
steps:
Expand All @@ -45,41 +43,81 @@ jobs:
echo "변경된 파일 목록:"
echo "$CHANGED_FILES"

# 변경된 환경을 JSON 배열 문자열로 수집합니다.
ENVIRONMENTS=""

# prod 환경 파일 변경 여부를 확인합니다.
# 변경된 환경 목록을 배열로 수집합니다.
ENVIRONMENTS=()
if echo "$CHANGED_FILES" | grep -q "terraform/environments/prod/"; then
ENVIRONMENTS="$ENVIRONMENTS\"prod\","
ENVIRONMENTS+=("prod")
fi

# 공통 모듈이 바뀌면 모든 환경에서 Plan을 수행합니다.
if echo "$CHANGED_FILES" | grep -q "terraform/modules/"; then
ENVIRONMENTS="\"prod\""
ENVIRONMENTS+=("prod")
fi

# 중복을 제거한 뒤 JSON 배열 문자열로 변환합니다.
if [ ${#ENVIRONMENTS[@]} -eq 0 ]; then
ENVIRONMENTS_JSON='[]'
else
ENVIRONMENTS_JSON=$(printf '%s\n' "${ENVIRONMENTS[@]}" | sort -u | jq -R . | jq -cs .)
fi

# 마지막 쉼표를 제거하고 JSON 배열 형식으로 변환합니다.
ENVIRONMENTS=$(echo "$ENVIRONMENTS" | sed 's/,$//')
ENVIRONMENTS="[$ENVIRONMENTS]"
echo "감지된 환경: $ENVIRONMENTS_JSON"
echo "environments=$ENVIRONMENTS_JSON" >> "$GITHUB_OUTPUT"

# detect-changes 출력값을 바로 확인하기 위한 디버그 잡
debug-detect-changes:
name: 디버그 - 감지 결과
runs-on: ubuntu-22.04
needs: detect-changes
if: always()
env:
GCP_WORKLOAD_IDENTITY_PROVIDER: ""
GCP_SERVICE_ACCOUNT: ""
TERRAFORM_STATE_BUCKET: ""
TERRAFORM_TFVARS_PROD: ""

echo "감지된 환경: $ENVIRONMENTS"
echo "environments=$ENVIRONMENTS" >> $GITHUB_OUTPUT
steps:
- name: detect-changes 출력 확인
env:
ENVIRONMENTS: ${{ needs.detect-changes.outputs.environments }}
run: |
echo "detect-changes environments 원본: $ENVIRONMENTS"
printf 'detect-changes environments quoted: <%s>\n' "$ENVIRONMENTS"
echo "detect-changes environments 길이: ${#ENVIRONMENTS}"
echo "detect-changes environments 바이트:"
printf '%s' "$ENVIRONMENTS" | od -An -tx1

echo "detect-changes environments JSON 파싱 시도:"
if [ -n "$ENVIRONMENTS" ]; then
printf '%s\n' "$ENVIRONMENTS" | jq -c .
else
echo "값이 비어 있습니다."
fi

# 환경별 Terraform init/fmt/validate/plan을 수행하고 결과 파일을 아티팩트로 남깁니다.
terraform-plan-checks:
name: 체크 - ${{ matrix.environment }}
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs: detect-changes
if: (needs.detect-changes.outputs.environments || '[]') != '[]' # 변경된 환경이 있을 때만 실행합니다.
env:
GCP_WORKLOAD_IDENTITY_PROVIDER: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
GCP_SERVICE_ACCOUNT: ${{ secrets.GCP_SERVICE_ACCOUNT }}
TERRAFORM_STATE_BUCKET: ${{ secrets.TERRAFORM_STATE_BUCKET }}
TERRAFORM_TFVARS_PROD: ${{ secrets.TERRAFORM_TFVARS_PROD }}
strategy:
fail-fast: false # 한 환경 실패가 다른 환경 확인을 막지 않도록 유지합니다.
matrix:
environment: ${{ fromJson(needs.detect-changes.outputs.environments || '[]') }} # 감지된 환경별로 병렬 실행합니다.
environment: ${{ fromJson(needs.detect-changes.outputs.environments || '[]') }}

steps:
- name: 코드 체크아웃
uses: actions/checkout@v4

- name: Matrix 디버그
run: |
echo "matrix.environment=${{ matrix.environment }}"
echo "needs.detect-changes.outputs.environments=${{ needs.detect-changes.outputs.environments }}"

# 출력 파일 기반으로 결과를 다루기 위해 wrapper를 비활성화
- name: Terraform 설치
uses: hashicorp/setup-terraform@v3
Expand Down Expand Up @@ -120,16 +158,8 @@ jobs:
if: steps.init.outcome == 'success'
working-directory: terraform/environments/${{ matrix.environment }}
run: |
case "${{ matrix.environment }}" in
prod)
TFVARS_CONTENT="${TERRAFORM_TFVARS_PROD}"
SECRET_NAME="TERRAFORM_TFVARS_PROD"
;;
*)
echo "오류: 지원하지 않는 환경입니다: ${{ matrix.environment }}"
exit 1
;;
esac
TFVARS_CONTENT="${TERRAFORM_TFVARS_PROD}"
SECRET_NAME="TERRAFORM_TFVARS_PROD"

if [ -z "${TFVARS_CONTENT}" ]; then
echo "오류: GitHub Secret ${SECRET_NAME}를 설정해야 합니다."
Expand Down Expand Up @@ -194,16 +224,13 @@ jobs:
# 업로드된 결과 파일을 읽어 PR 댓글을 환경별로 갱신
comment-plan-results:
name: PR 댓글 갱신
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs:
- detect-changes
- terraform-plan-checks
if: always() && (needs.detect-changes.outputs.environments || '[]') != '[]' && github.event_name == 'pull_request'

# 특정 환경 댓글 실패가 다른 환경 댓글을 막지 않도록 유지
strategy:
fail-fast: false
# 감지된 환경별로 댓글을 분리
matrix:
environment: ${{ fromJson(needs.detect-changes.outputs.environments || '[]') }}

Expand Down
39 changes: 12 additions & 27 deletions terraform/environments/prod/compute.tf
Original file line number Diff line number Diff line change
Expand Up @@ -24,42 +24,27 @@ module "web_servers" {
# 관리형 인스턴스 그룹을 사용하지 않을 때만 개별 인스턴스를 정의합니다.
instances = !var.use_instance_group && var.create_web_instances ? tomap({
web1 = {
name = "${var.environment}-web-01"
zone = "${var.region}-a"
machine_type = var.web_machine_type
enable_external_ip = false # 프로덕션 환경은 로드 밸런서를 통한 접근만 허용합니다.
tags = ["web-server", var.environment]
labels = {
environment = var.environment
role = "web"
}
name = "${var.environment}-web-01"
zone = "${var.region}-a"
machine_type = var.web_machine_type
enable_external_ip = false # 프로덕션 환경은 로드 밸런서를 통한 접근만 허용합니다.
tags = ["web-server", var.environment]
deletion_protection = true # 실수로 삭제되지 않도록 보호합니다.
}
web2 = {
name = "${var.environment}-web-02"
zone = "${var.region}-b"
machine_type = var.web_machine_type
enable_external_ip = false
tags = ["web-server", var.environment]
labels = {
environment = var.environment
role = "web"
}
deletion_protection = true
}
}) : tomap({})

# 공통 인스턴스 설정
machine_type = var.web_machine_type
source_image = var.web_source_image
boot_disk_size_gb = 50
boot_disk_type = "pd-ssd" # 프로덕션 환경은 SSD 디스크를 사용합니다.
boot_disk_size_gb = var.web_machine_ssd
boot_disk_type = "pd-ssd"
enable_external_ip = false
tags = ["web-server", var.environment]
labels = {
environment = var.environment
role = "web"
}

# 태그
common_tags = merge(var.common_tags, {
Service = "Backend"
})

# 서비스 계정 설정
service_account_email = var.service_account_email
Expand Down
14 changes: 9 additions & 5 deletions terraform/environments/prod/load-balancer.tf
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ module "load_balancer" {
backend_timeout_sec = 30
session_affinity = "CLIENT_IP"
backend_groups = var.use_instance_group ? [
{
group = module.web_servers.instance_group_instance_group
balancing_mode = "UTILIZATION"
max_utilization = 0.8
}
merge(
{
group = module.web_servers.instance_group_instance_group
balancing_mode = var.lb_type == "NETWORK" ? "CONNECTION" : "UTILIZATION"
},
var.lb_type == "NETWORK" ? {} : {
max_utilization = 0.8
}
)
] : []
ssl_certificates = var.ssl_certificates

Expand Down
65 changes: 6 additions & 59 deletions terraform/environments/prod/storage.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ module "storage" {

project_id = var.project_id
default_location = var.storage_location
default_labels = {
environment = var.environment
managed_by = "terraform"
}

# 태그
common_tags = merge(var.common_tags, {
Service = "Storage"
})

# 버킷 정의
buckets = merge(
var.create_storage_buckets ? tomap({
static_assets = {
name = "${var.project_id}-${var.environment}-static-assets"
name = "${var.project_id}-${var.environment}"
storage_class = "STANDARD"
uniform_bucket_level_access = true
versioning_enabled = true
Expand Down Expand Up @@ -45,60 +46,6 @@ module "storage" {
}
] : []
}
}) : tomap({}),
var.create_storage_buckets ? tomap({
backups = {
name = "${var.project_id}-${var.environment}-backups"
storage_class = "NEARLINE"
uniform_bucket_level_access = true
versioning_enabled = true
force_destroy = false
public_access_prevention = "enforced"

# 오래된 백업은 저장 등급을 낮추고 최종적으로 삭제합니다.
lifecycle_rules = [
{
action = {
type = "SetStorageClass"
storage_class = "COLDLINE"
}
condition = {
age = 90
}
},
{
action = {
type = "Delete"
}
condition = {
age = 365
num_newer_versions = 10
}
}
]
}
}) : tomap({}),
var.create_storage_buckets ? tomap({
logs = {
name = "${var.project_id}-${var.environment}-logs"
storage_class = "STANDARD"
uniform_bucket_level_access = true
versioning_enabled = false
force_destroy = false
public_access_prevention = "enforced"

# 로그 버킷은 30일 이후 자동 삭제합니다.
lifecycle_rules = [
{
action = {
type = "Delete"
}
condition = {
age = 30
}
}
]
}
}) : tomap({})
)
}
2 changes: 1 addition & 1 deletion terraform/environments/prod/terraform.tfvars.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ autoscaling_max_replicas = 5

web_machine_type = "e2-standard-2"
web_source_image = "ubuntu-os-cloud/ubuntu-2204-lts"

web_machine_ssd = 30
# ========================================
# 스토리지 관련 값
# ========================================
Expand Down
26 changes: 23 additions & 3 deletions terraform/environments/prod/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ variable "create_web_instances" {
variable "instance_group_size" {
description = "관리형 인스턴스 그룹의 목표 인스턴스 수입니다."
type = number
default = 2
default = 1
}

variable "enable_autoscaling" {
Expand All @@ -76,7 +76,7 @@ variable "enable_autoscaling" {
variable "autoscaling_min_replicas" {
description = "오토스케일링 최소 인스턴스 수입니다."
type = number
default = 2
default = 1
}

variable "autoscaling_max_replicas" {
Expand All @@ -91,6 +91,12 @@ variable "web_machine_type" {
default = "e2-standard-2" # 운영 환경에 맞춘 고성능 인스턴스입니다.
}

variable "web_machine_ssd" {
description = "웹 서버 부팅 디스크 크기(GB)입니다."
type = number
default = 50
}

variable "web_source_image" {
description = "웹 서버 부팅 디스크에 사용할 이미지입니다."
type = string
Expand All @@ -115,7 +121,7 @@ variable "create_storage_buckets" {
variable "storage_location" {
description = "스토리지 버킷을 생성할 위치입니다."
type = string
default = "ASIA" # 운영 환경에서는 멀티 리전을 기본값으로 사용합니다.
default = "ASIA-NORTHEAST3" # 비용 절감을 위해 단일 리전을 기본값으로 사용합니다.
}

variable "allowed_cors_origins" {
Expand Down Expand Up @@ -144,3 +150,17 @@ variable "ssl_certificates" {
type = list(string)
default = []
}

# ========================================
# 공통 태그 변수
# ========================================
variable "common_tags" {
description = "공통 태그입니다."
type = map(string)
default = {
project = "pinhouse"
environment = "prod"
version = "v1"
managed_by = "terraform"
}
}
2 changes: 1 addition & 1 deletion terraform/environments/prod/vpc.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ module "vpc" {

vpc_name = "${var.project}-${var.environment}-vpc"
description = "프로덕션 환경용 VPC 네트워크"
routing_mode = "GLOBAL"
routing_mode = "REGIONAL"

# 서브넷 정의
subnets = {
Expand Down
Loading
Loading