4 분 소요

서론

학교에서 듣는 클라우드컴퓨팅 수업에서 IaC라는 것을 들었습니다. 간단히 말하면 “인프라를 코드로 관리하자!” 인데 처음 봤을 때는 굳이 코드로 관리해야 하나? 나는 UI가 편한데? 라는 생각을 하고 가볍게 흘렸습니다. 하지만, 아래의 여러가지 문제가 생겨 Terraform을 공부하게 되었습니다.

날이 갈수록 증가하는 KEEPER 서버비 문제

지금 서버비가 9만원대를 찍고 10만원 대로 달려가고 있어서 회장님과 회의를 했습니다. 나온 대안은 1년에 한번 계정을 새로 파서, 프리티어 돌려막자 였습니다. 프리티어로 돌려막으면 1만원 대로 줄지 않을까? 생각이 듭니다.

KEEPER, Doore 인프라 인수인계

곧 졸업을 해야 해서 인프라 담당자를 뽑아야 하는데, 다른 부서의 후계자는 정말 뽑기 쉬운데 인프라는 정말 힘든 것 같습니다. 또 키퍼 홈페이지가 어떤식으로 돌아가고 있는지, AWS 서비스는 뭘 쓰고, 어떤 방식으로 CICD가 되는지 모든 맥락을 설명할 생각을 하니 숨이 턱 막혔습니다. 또, 이걸 두레 프로젝트처럼 문서화 하려니 숨이 턱 막혔습니다.

졸업과제때 사용할 서버 구축

졸과할 때 무조건 쿠버네티스를 사용하라고 하셨습니다. 쿠버네티스를 사용하려면 3대의 서버를 구축해야 하는데, 동일한 환경 셋팅을 3번 해야 한다는 생각에 숨이 턱 막혔습니다.

위의 상황이 겹치니까 테라폼으로 코드를 작성해 놓으면 쉽게 동일한 환경의 AWS 인스턴스, IAM, S3 등등을 구축할 수 있을 것 같고, 인수인계나 동일한 서버 구축이 정말 쉬울 것 같았습니다.

Terraform 문법

<BLOCK TYPE> "<BLOCK LABEL>" "<BLOCK LABEL>" {
  # Block body
  <IDENTIFIER> = <EXPRESSIONS> # Argument
}

- BLOCK TYPE
terraform: Terraform 버전을 요구 -> 약간 Spring boot 버전 느낌.
provider: 클라우드 프로바이더(ex. AWS)  기타 API 상호작용   있게 해주는 플러그인 -> 약간 라이브러리 느낌
resource: 인스턴스를 예로 들면 물리적인 스펙 또는 논리적인 리소스를 기술하는 
data: Terraform 외부에서 정의된 정보를 사용할  있게 함.

module: 함께 사용되는 resource 담는 컨테이너? 리소스를 완전한 세트로 캡슐화하고 재사용 가능하다고 함.
local: 반복되는 값이나 여러 곳에서 사용되는 표현식을 단순화하기 위해 사용한다고 함.
variable: Terraform 구성에 값을 주입하는 방법을 제공한다고 함.

EC2 && IAM 만들어보기

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0" // 5.x 버전을 의미
    }
  }
}

provider "aws" { // invalid provider configuration 해결을 위해 필요
    region = "ap-northeast-2"
} 

data "aws_ami" "ubuntu" { // EC2 이미지 가져오기
  most_recent = true

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  owners = ["099720109477"] # Canonical
}

resource "aws_instance" "keeper-dev" { // 하드웨어 스펙 정의
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t3.micro"

  root_block_device {
    volume_size = "30"
    volume_type = "gp3"
  }

  tags = {
    Name = "Keeper Homepage"
  }
}

발생한 에러

Invalid provider configuration -> AWS provider를 정의해야 합니다. 위의 코드에서 provider “aws”를 넣어줌으로써 해결할 수 있습니다. No valid credential sources found -> aws configure 설정을 통해 해결할 수 있습니다.

위의 문제를 해결하고 terraform plan을 사용해 실제로 만들어질 리소스를 확인할 수 있습니다.

life

resource "aws_iam_user" "keeper-cicd" {
    name = "keeper-cicd"
    path = "/system/"
}

resource "aws_iam_user" "keeper-dev" {
    name = "keeper-dev"
    path = "/system/"
}

resource "aws_iam_group" "cicd-group" {
    name = "cicd-group"
    path = "/system/"
}

resource "aws_iam_group" "dev-group" {
    name = "dev-group"
    path = "/system/"
}

resource "aws_iam_group_membership" "cicd-membership" {
    name = "cicd-membership"

    users = [
        aws_iam_user.keeper-cicd.name,
    ]

    group = aws_iam_group.cicd-group.name
}

data "aws_iam_policy" "cloudFrontFullAccess" {
    arn = "arn:aws:iam::aws:policy/cloudFrontFullAccess"
}

resource "aws_iam_policy" "cloudFrontFullAccess_policy" {
    name = "cloudFrontFullAccess_policy"
    path = "/system/"
    description = "cloudFrontFullAccess_policy"

    policy = <<EOF
    {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "cfflistbuckets",
            "Action": [
                "s3:ListAllMyBuckets"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:s3:::*"
        },
        {
            "Sid": "cffullaccess",
            "Action": [
                "acm:ListCertificates",
                "cloudfront:*",
                "cloudfront-keyvaluestore:*",
                "iam:ListServerCertificates",
                "waf:ListWebACLs",
                "waf:GetWebACL",
                "wafv2:ListWebACLs",
                "wafv2:GetWebACL",
                "kinesis:ListStreams"
            ],
            "Effect": "Allow",
            "Resource": "*"
        },
        {
            "Sid": "cffdescribestream",
            "Action": [
                "kinesis:DescribeStream"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:kinesis:*:*:*"
        },
        {
            "Sid": "cfflistroles",
            "Action": [
                "iam:ListRoles"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:iam::*:*"
        }
    ]
    }   
    EOF
}

resource "aws_iam_policy" "amazonS3FullAccess_policy" {
    name = "amazonS3FullAccess_policy"
    path = "/system/"
    description = "amazonS3FullAccess_policy"

    policy = <<EOF
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "s3:*",
                    "s3-object-lambda:*"
                ],
                "Resource": "*"
            }
        ]
    }
    EOF
}

resource "aws_iam_policy" "amazonEC2ContainerRegistryFullAccess_policy" {
    name = "amazonEC2ContainerRegistryFullAccess_policy"
    path = "/system/"
    description = "amazonEC2ContainerRegistryFullAccess_policy"

    policy = <<EOF
    {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:*",
                "cloudtrail:LookupEvents"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "iam:CreateServiceLinkedRole"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "iam:AWSServiceName": [
                        "replication.ecr.amazonaws.com"
                    ]
                }
            }
        }
    ]
    }
    EOF
}

resource "aws_iam_user_policy_attachment" "cloudFrontFullAccess_attachment" {
    user = aws_iam_user.keeper-cicd.name
    policy_arn = data.aws_iam_policy.cloudFrontFullAccess.arn
}

resource "aws_iam_user_policy_attachment" "amazonS3FullAccess_attachment" {
    user = aws_iam_user.keeper-cicd.name
    policy_arn = aws_iam_policy.amazonS3FullAccess_policy.arn
}

resource "aws_iam_user_policy_attachment" "amazonEC2ContainerRegistryFullAccess_attachment" {
    user = aws_iam_user.keeper-cicd.name
    policy_arn = aws_iam_policy.amazonEC2ContainerRegistryFullAccess_policy.arn
}

life

위의 코드를 통해 IAM을 성공적으로 생성할 수 있었습니다. 주의할 점은 iam policy를 resource로 정의할 경우 AWS 관리형이 아닌 고객 관리형이 되어버리는데, 이렇게 될 경우 AWS의 케어를 못 받을 수 있습니다. 따라서 resource가 아닌 data로 정의해 AWS 관리형으로 바꾸는 것을 추천드립니다.

깃으로 관리하기

작성한 코드를 github에 올리기 위해서는 terraform init할 때 생긴 terraform-provider를 삭제 또는 깃 이그노어 처리를 하고 push를 해야 합니다. 그렇지 않을 경우에는 용량이 너무 커 push할 수 없다는 에러 메시지가 출력됩니다.

# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
crash.*.log

# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version 
# control as they are data points which are potentially sensitive and subject 
# to change depending on the environment.
*.tfvars
*.tfvars.json

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Include override files you do wish to add to version control using negated pattern
# !example_override.tf

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore CLI configuration files
.terraformrc
terraform.rc%  

결론

terraform을 통해 AWS 리소스를 쉽게 관리할 수 있었습니다.

카테고리:

업데이트: