DevOps
  • Introduction
  • Setting
    • Terminal
      • Tmux
    • WSL+Ubuntu
    • [NeoVIM]
      • install & 활용방법
      • error 처리
      • LazyVIM
        • install & 활용방법
    • ssh
    • mysql
    • package관리
  • Fundamental(basic)
    • Network
      • https
  • [GitOps]
    • [SCM]
      • [Github]
        • center-managed
      • bitbucket
      • AWS-codeCommit
  • roadmap
    • devops
    • kubernetes
    • AWS
    • MLOPS
  • Cloud
    • [AWS]
      • aws sso script
      • tagging 자동화
      • 동일cidr에서 VPC 연결
      • 무중단서비스를 위한 고려사항
    • [GCP]
      • [GCP] GCP의 VPC
      • [GCP] GCP의 ALB
      • [GCP] OIDC와 OAUTH를 활용한 github action
      • [GCP] Composer 설명
      • [GCP] gmail-api
      • [GCP] DataLake
      • [GCP] Cloud 관리형 계정&role
      • [[GCP] private환경
        • DNS 설정으로 google api 및 colab-notebook 사용 하기
        • intelligence 설정으로 google api 및 colab-notebook 사용 하기
  • [kubernetes]
    • [cloud 기반]
      • csr
  • InfraAsCode
    • terraform
  • 코드로 그리는 다이어그램
    • CodeAsDiagram
      • example
    • Mermaid
    • PDFtoImage
  • AutoMation
  • [ETC]
    • Magic_Trackpad Window설치
Powered by GitBook
On this page
  • terraform 서비스 구축
  • 두 신뢰관계 정책 파일을 병합하는 jq 명령어
  1. InfraAsCode

terraform

terraform 서비스 구축

Intro

  • 본 문서는 terraform 으로 실습한 소스코드를 설명하는 데 중점을 두었습니다.

  • 본 문서는 markdown 언어로 제작 되었습니다.

  • 김태형(kimutae@naver.com) 의 저작물이며 무단 배포 / 재배포를 금합니다.

IAC(infra as code) 이란

  • 인프라를 코드로 프로비저닝 / 관리한다는 의미입니다.

  • 장점

  • 비용 절감

  • 배포 속도 향상

  • 오류 감소

  • 인프라 일관성 향상

  • 구성 변동 제거

terraform 이란

  • 테라폼(Terraform)은 하시코프(Hashicorp)에서 오픈소스로 개발중인 클라우드 인프라스트럭처 입니다.

  • 대표적인 public cloud 인 aws / gcp / azure 등을 모두 지원합니다.

  • 하시코프 설정 언어HCL(Hashicorp Configuration Language)을 사용해 클라우드 리소스를 선언합니다

  • 파일 확장자는 .tf입니다.

terraform 명령어

  • init : . terrform 초기화 이후 각 vendor사에 맞는 provier 파일을 설치 합니다.

  • plan :

    1. 테라폼 프로젝트 디렉터리 아래의 모든 .tf 파일의 내용을 실제로 적용 가능한지 확인하는 작업을 계획

    2. 테라폼은 이를 terraform plan 명령어로 제공하며, 이 명령어를 실행하면 어떤 리소스가 생성되고, 수정되고, 삭제

  • apply :

    1. 테라폼 프로젝트 디렉터리 아래의 모든 .tf 파일의 내용대로 리소스를 생성, 수정, 삭제

    2. 테라폼은 이를 terraform apply 명령어로 제공합니다. 이 명령어를 실행하기 전에 변경 예정 사항은 plan 명령어

terraform 으로 구성하기 앞서 생각해야 할 점

  • 상시 변경 인프라 / 기반인프라 구분

  • 테라폼을 사용하게 되면 한 디렉토리 내에 있는 모든 tf 파일들을 동시에 실행을 합니다.

  • 그렇기에 한번 구축하면 거의 변경 할 일이 없는 인프라와 상시로 변경이나 구축이 자주 일어나는 인프라를 구분해야 합니다.

  • 초기 구성 이후 거의 변하지 않는 항목은 vpc등 네트워크 인프라이며 - resource(신규생성)를 지정 해서 인프라를 생성 합니다.

  • 서비스 구성 시 항상 자주 구성해야 하는 항목 - data(불러오기) / resource를 혼용 하여 사용 합니다.

  • EX

    resource
    data

    vpc

    security gruop

    subnet

    Load Balancer

    igw

    target group

    NAT

    EC2

    ECS-Cluster

    ecs

    task difinition

    ecr

    RDS

  • code 재 사용이 가능하도록 설계

  • IAC 를 사용 한다는 것은 결국 인프라를 프로그래밍한다는 의미이며 프로그래밍 언어의 특징도 그대로 가져옵니다.

  • 한번 작성한 코드는 재 사용이 가능 하도록 설계 되어야 합니다.

  • 공통으로 사용하는 부분은 변수로 처리 해야 합니다.

  • 자주 사용 하는 구문은 모듈화 해야 합니다.

variables를 활용한 code 사용 예시

  • 변수지정

variable "cidr_numeral" {
description = "The VPC CIDR numeral (10.x.0.0/16)"
type  = number
default = "30"
}

variable "cidr_numeral_public" {
default = {
    "0" = "0"
    "1" = "16"
    "2" = "32"
}
}

############# 가용 영역 지정 - 가용영역의 갯수만큼 서브넷이 만들어진다
variable "availability_zones" {
    type    = list(string)
    default = [
    "ap-northeast-2a" ,
    "ap-northeast-2b"
    ]
}
  • 사용

    resource "aws_subnet" "public" {
    count  = length(var.availability_zones)  # 변수로 지정된 가용 영역의 갯수 현재 2 즉 변수는 0,1 
    vpc_id = aws_vpc.vpc.id #위에서 생성한 vpc 별칭 입력
    #################
    # count.index = for 문 역할 0 = 0 / 1=16
    cidr_block  = "10.${var.cidr_numeral}.${var.cidr_numeral_public[count.index]}.0/20"    
    # resource참조는 element 구문을 활용 하여 for 문을 작성합니다
    availability_zone = element(var.availability_zones, count.index)
    
    #################
    map_public_ip_on_launch = true
    tags = {
        Name = "public${count.index}-${var.vpc_name}"
    }
    }
    

디렉토리 구성

main
├── global           >> global 설정 - vpc를 추가 할때 사용
│   └── mk-network-var   >>  vpc / subnet /igw 등 네트워크 설정
│       ├── main.tf
│       ├── network.tf
│       ├── terraform.tfstate
│       ├── terraform.tfstate.backup
│       └── variables.tf
└── service
└── ecs-web
├── falgate   >> ecs-cluster 내 다중 서비스를 포함하고 있어 별도로 생성 할수 있도록 구성
│   ├── ecr.tf
│   ├── ecs-cluster.tf
│   ├── td-service
│   │   ├── ecs.tf
│   │   ├── serevice.conf.json
│   │   ├── serevice.conf.json.tpl
│   │   ├── td.tf
│   │   ├── terraform.tfstate
│   │   ├── terraform.tfstate.backup
│   │   └── variables.tf -> ../../variables.tf   >> 공용 변수를 사용 하기 위해 심볼릭 링크로 구성
│   ├── terraform.tfstate
│   ├── terraform.tfstate.backup
│   └── variables.tf -> ../variables.tf
├── sg-lb     >> security group 과 로드벨런서를 구성
│   ├── lb.tf
│   ├── output.tf
│   ├── sg.tf -> ../sg.tf
│   ├── terraform.tfstate
│   ├── terraform.tfstate.backup
│   ├── variables.tf -> ../variables.tf
├── sg.tf
└── variables.tf

VPC 구성

코드 펼치기
  • 공용 변수 : variables.tf


variable "access_key" {
type = string
}

variable "secret_key" {
type = string
}

variable "region" {
type    = string
default = "ap-northeast-2"
}

variable "vpc_name" {
description = "The name of the VPC"
default = "terra"
}

########### CIDR 변수 지정 
variable "cidr_numeral" {
description = "The VPC CIDR numeral (10.x.0.0/16)"
type  = number
default = "30"
}

############# 가용 영역 지정 - 가용영역의 갯수만큼 서브넷이 만들어진다
variable "availability_zones" {
type    = list(string)
default = [
"ap-northeast-2a" ,
"ap-northeast-2b"
]
}

variable "cidr_numeral_public" {
default = {
    "0" = "0"
    "1" = "16"
    "2" = "32"
}
}

variable "cidr_numeral_private" {
default = {
    "0" = "80"
    "1" = "96"
    "2" = "112"
}
}

variable "cidr_numeral_private_db" {
default = {
    "0" = "160"
    "1" = "176"
    "2" = "192"
}
}

variable "site_name" {
type = string
default = "terra"
}
  • 프로바이더 지정 - main.tf

provider "aws" {
access_key = var.access_key
secret_key = var.secret_key
region     = var.region
}
  • subnet / igw / nat 구성

resource "aws_vpc" "vpc" {
# cidr_block = "10.30.0.0/16" #IPv4 CIDR Block
cidr_block  = "10.${var.cidr_numeral}.0.0/16" # Please set this according to your company size
enable_dns_hostnames = true #DNS Hostname 사용 옵션, 기본은 false
tags =  { Name = "${var.site_name}_vpc" } #tag 입력
}


resource "aws_internet_gateway" "default" {
vpc_id = aws_vpc.vpc.id

tags = {
Name = "igw-${var.vpc_name}"
}
}


## NAT Gateway 
resource "aws_nat_gateway" "nat" {
count = length(var.availability_zones)

allocation_id = element(aws_eip.nat.*.id, count.index)

subnet_id = element(aws_subnet.public.*.id, count.index)

lifecycle {
create_before_destroy = true
}

tags = {
Name = "NAT-GW${count.index}-${var.site_name}"
}

}

# Elastic IP for NAT Gateway 
resource "aws_eip" "nat" {
count = length(var.availability_zones)
vpc   = true

lifecycle {
create_before_destroy = true
}
}


#### PUBLIC SUBNETS
resource "aws_subnet" "public" {
count  = length(var.availability_zones)
vpc_id = aws_vpc.vpc.id #위에서 생성한 vpc 별칭 입력
cidr_block  = "10.${var.cidr_numeral}.${var.cidr_numeral_public[count.index]}.0/20"
availability_zone = element(var.availability_zones, count.index)
map_public_ip_on_launch = true
tags = {
Name = "public${count.index}-${var.vpc_name}"
}
}

# Route Table for public subnets
resource "aws_route_table" "public" {
vpc_id = aws_vpc.vpc.id #위에서 생성한 vpc 별칭 입력

tags = {
Name = "publicrt-${var.vpc_name}"
}
}


# Route Table Association for public subnets
resource "aws_route_table_association" "public" {
count          = length(var.availability_zones)
subnet_id      = element(aws_subnet.public.*.id, count.index)
route_table_id = aws_route_table.public.id
}


#### PRIVATE SUBNETS
# Subnet will use cidr with /20 -> The number of available IP is 4,096  (Including reserved ip from AWS)
resource "aws_subnet" "private" {
count  = length(var.availability_zones)
vpc_id = aws_vpc.vpc.id

cidr_block        = "10.${var.cidr_numeral}.${var.cidr_numeral_private[count.index]}.0/20"
availability_zone = element(var.availability_zones, count.index)

tags = {
Name    = "private${count.index}-${var.vpc_name}"
Network = "Private"
}
}

# Route Table for private subnets
resource "aws_route_table" "private" {
count  = length(var.availability_zones)
vpc_id = aws_vpc.vpc.id

tags = {
Name    = "private${count.index}rt-${var.vpc_name}"
Network = "Private"
}
}

# Route Table Association for private subnets
resource "aws_route_table_association" "private" {
count          = length(var.availability_zones)
subnet_id      = element(aws_subnet.private.*.id, count.index)
route_table_id = element(aws_route_table.private.*.id, count.index)
}


# DB PRIVATE SUBNETS
# This subnet is only for the database. 
# For security, it is better to assign ip range for database only. This subnet will not use NAT Gateway
# This is also going to use /20 cidr, which might be too many IPs... Please count it carefully and change the cidr.
resource "aws_subnet" "private_db" {
count  = length(var.availability_zones)
vpc_id = aws_vpc.vpc.id

cidr_block   = "10.${var.cidr_numeral}.${var.cidr_numeral_private_db[count.index]}.0/20"
availability_zone = element(var.availability_zones, count.index)

tags = {
Name    = "db-private${count.index}-${var.vpc_name}"
Network = "Private"
}
}

# Route Table for DB subnets
resource "aws_route_table" "private_db" {
count  = length(var.availability_zones)
vpc_id = aws_vpc.vpc.id

tags = {
Name    = "privatedb${count.index}rt-${var.vpc_name}"
Network = "Private"
}
}

# Route Table Association for DB subnets
resource "aws_route_table_association" "private_db" {
count          = length(var.availability_zones)
subnet_id      = element(aws_subnet.private_db.*.id, count.index)
route_table_id = element(aws_route_table.private_db.*.id, count.index)
}

Load Balancer 구성

코드 펼치기
data "aws_subnet" "public" {
count  = length(var.availability_zones)
tags = {
Name = "public${count.index}-${var.vpc_name}"
}
}

#resource "aws_lb" "terra-lb" {
resource "aws_lb" "terra-lb" {
count = length(var.product) 
name               = "${var.site_name}-${var.service_name}-${var.product[count.index]}"
subnets            = data.aws_subnet.public[*].id
load_balancer_type = "application"
security_groups    = [aws_security_group.lb.id]

#  access_logs {
#    bucket  = aws_s3_bucket.log_storage.id
#    prefix  = "frontend-alb"
#    enabled = true
#  }

tags = {
Environment = "terra-lb"
Application = var.app_name
}
}

#resource "aws_lb_listener" "https_forward" {
#  load_balancer_arn = aws_lb.terra-lb.arn
#  port              = 443
#  protocol          = "HTTPS"
#  certificate_arn   = aws_acm_certificate.cert.arn
#  ssl_policy        = "ELBSecurityPolicy-2016-08"
#
#  default_action {
#    type             = "forward"
#    target_group_arn = aws_lb_target_group.terra-lb.arn
#  }
#}


resource "aws_lb_listener" "http_forward" {

count = length(var.product) 

load_balancer_arn = element(aws_lb.terra-lb.*.arn,count.index)
#load_balancer_arn = aws_lb.terra-lb.arn
port              = 80
protocol          = "HTTP"

default_action {
#    type = "redirect"
type             = "forward"
target_group_arn = element(aws_lb_target_group.terra-lb.*.arn,count.index)


#    redirect {
#      port        = "443"
#      protocol    = "HTTPS"
#      status_code = "HTTP_301"
#    }
}
}

resource "aws_lb_target_group" "terra-lb" {

count = length(var.product) 
name = "${var.site_name}-${var.service_name}-${var.product[count.index]}" 
#  name        = "service-alb-tg"
port        = 80
protocol    = "HTTP"
vpc_id      = data.aws_vpc.vpc.id
target_type = "ip"

health_check {
enabled             = true
interval            = 300
path                = "/"
timeout             = 60
matcher             = "200"
healthy_threshold   = 5
unhealthy_threshold = 5
}

lifecycle {
create_before_destroy = true
}
}

task definition 구성

코드 펼치기
  • templete 를 활용 하여 사전에 작성한 td.json파일을 import 합니다

    data "aws_iam_role" "ecs_task_execution_role" {
    name     = "ecsTaskExecutionRole"
    }
    data "aws_ecr_repository" "repo" {
    
    count = length(var.product) 
    name = "${var.site_name}-${var.service_name}-${var.product[count.index]}"
    #  name = "ecr-repository"
    }
    
    data "template_file" "service" {
     template = file(var.tpl_path)
    count = length(var.product) 
     vars = {
       region             = var.region
       aws_ecr_repository = element(data.aws_ecr_repository.repo.*.repository_url,count.index)
       tag                = "latest"
       container_port    = var.container_port
       host_port          = var.host_port
       app_name         = var.app_name
     }
    }
    
    
    
    #output "template_file"  {
    #    value = data.template_file.service.*.vars
    #}
    
    
    #output "aws_ecs_task_definition" {
    #  value    = data.template_file.service
    #}
    
    resource "aws_ecs_task_definition" "service" {
     count = length(var.product) 
     family                   = "${var.site_name}-${var.service_name}-${var.product[count.index]}"
     network_mode             = "awsvpc"
     execution_role_arn       = data.aws_iam_role.ecs_task_execution_role.arn
     cpu                      =2048 
     memory                   =4096 
     requires_compatibilities = ["FARGATE"]
     container_definitions    = element(data.template_file.service.*.rendered,count.index)
     tags = {
       Environment = "${var.site_name}-${var.service_name}-${var.product[count.index]}"
       Application = "${var.site_name}-${var.service_name}-${var.product[count.index]}"
     }
    }
    
    

ECS - service 구성

코드 펼치기
  data "aws_ecs_cluster" cluster {
    cluster_name = "${var.site_name}-${var.service_name}"
  }
  
  data "aws_security_group" sg  {
   name = "${var.app_name}"
  }
  
  data "aws_lb_target_group" tg  {
   count = length(var.product) 
   name = "${var.site_name}-${var.service_name}-${var.product[count.index]}"
  }
  
  data "aws_subnet" "public_sub" {
  
   count = length(var.availability_zones) 
    tags = {
      Name = "public${count.index}-${var.vpc_name}"
    }
  }
  
   
  
  resource "aws_ecs_service" "ecs_service" {
    count = length(var.product) 
    name = "${var.site_name}-${var.service_name}-${var.product[count.index]}"
    cluster         = data.aws_ecs_cluster.cluster.id
    
    task_definition = element(aws_ecs_task_definition.service.*.arn,count.index)
    desired_count   = 3
    launch_type     = "FARGATE"
  
    network_configuration {
      security_groups  = [data.aws_security_group.sg.id]
     # count = length(var.availability_zones) 
      subnets          = data.aws_subnet.public_sub[*].id
      assign_public_ip = true
    }
  
    load_balancer {
     # count = length(var.product) 
      target_group_arn = element(data.aws_lb_target_group.tg.*.arn,count.index)
      container_name   = var.app_name
      container_port   = var.container_port
    }
  
  #  depends_on = [aws_lb_listener.https_forward, aws_lb_listener.http_forward, aws_iam_role_policy_attachment.ecs_task_execution_role]
  
    tags = {
  
      count = length(var.product) 
      Environment = "ecs_service"
      Application = "${var.site_name}-${var.service_name}-${var.product[count.index]}"
    }
  }

  • 결과

두 신뢰관계 정책 파일을 병합하는 jq 명령어

TrustShip

aws iam update-assume-role-policy --role-name MyRole --policy-document "$(
  aws iam get-role --role-name MyRole --query "Role.AssumeRolePolicyDocument" --output json | \
  jq -c '.Statement |= map(
    if .Principal.AWS then
      .Principal.AWS |= (if type == "string" then [.] else . end + ["arn:aws:iam::xxxx:root"] | unique)
    else
      .
    end
  )'
)"
PreviousInfraAsCodeNext코드로 그리는 다이어그램

Last updated 3 months ago

subnet

결과

결과

image