교육, 커뮤니티 후기/T101(Terraform 101 Study) 스터디

T101(Terraform 101 Study) 5주차

Somaz 2023. 9. 26. 00:29
728x90
반응형

Overview

이번주는 CloudNet@에서 진행하시는 T101(Terraform 101 Study) 스터디 5주차이다.
 
블로그 내용은 `테라폼으로 시작하는 IaC` 책을 기준하여 정리하였다.

 

출처 : https://developer.hashicorp.com/terraform/tutorials/aws-get-started/infrastructure-as-code

 

모든 실습내용은 아래의 github에 정리하였다.
https://github.com/somaz94/t101-study

 

GitHub - somaz94/t101-study: t101-study

t101-study. Contribute to somaz94/t101-study development by creating an account on GitHub.

github.com

 


1. 형상관리 도구

형상 관리 도구는 버전 관리 시스템, VCS

  • SVN : 중앙 저장소에서 코드와 히스토리를 관리하는 방식 - [참고: 아파치 서브버전]
  • Git : 분산형 관리 시스템으로 작업 환경에서도 별도로 코드 히스토리를 관리하고 중앙 저장소와 동기화 - [참고: Git(깃), 리누스 토발즈 TED 영상]

 

출처 : https://medium.com/@urna.hybesis/pull-request-workflow-with-git-6-steps-guide-3858e30b5fa4

 

차트는 Mermaid Live Editor를 활용해서 그렸다.

flowchart LR
    A[Write] --> |Commits|B{Push your Changes}
    B[Open A pull Request] --> |Discuss and review your code|C{Rebase and tests}
    C --> D(Merge)

 

TFC 실습때 활용하기 위해, 해당 Github 링크를 `fork`한다.

https://github.com/somaz94/terraform-aws-collaboration

 

GitHub - somaz94/terraform-aws-collaboration: [Chapter 7] Collaboration Code Example

[Chapter 7] Collaboration Code Example. Contribute to somaz94/terraform-aws-collaboration development by creating an account on GitHub.

github.com

 


2. Terraform Cloud(TFC)

하시코프에서 프로비저닝 대상과 별개로 State를 관리할 수 있도록 SaaS 환경인 TFC를 제공하며 State 관리 기능은 무상을 제공 → 기존 Terraform CE 사용자가 가장 좋아하는 기능이다.


 

TFC 가격정책

Free : 리소스 500개 까지 무료 → 커뮤니티 버전

Standard : Free + 워크플로우 기능 추가 + 동시실행(Concurrency 개수 3개)

Plus : 정책, 보안, 신뢰성, 확장성 등 기업형 고객에게 적합(대규모 사용자를 위한 비용모델)

Enterprise : Plus와 대부분 유사하며 설치형 모델

출처 : CloudNet@ 스터디

 


TFC 계정생성

  1. https://app.terraform.io/ 링크 접속 후 하단에 free account 클릭 → 계정 생성 후 이메일 확인 → 암호 입력 후 로그인
  2. TFC 초기 설정 화면 → 사용자의 고유 조직 이름 생성 : `<nickname>-org`
    • 조직명은 유일한 값이여야 한다!
  3. CLI 환경에서 TFC 사용을 위한 자격증명
    • 명령 후 TFC 신규 창 → TFC 토큰 생성 클릭
# 
terraform login
yes 입력
...
Terraform will store the token in plain text in the following file
for use by subsequent commands:
    /Users/somaz/.terraform.d/credentials.tfrc.json

Token for app.terraform.io:
  Enter a value: <자신의 토큰 입력>
...

# 토큰 확인
cat ~/.terraform.d/credentials.tfrc.json | jq

참고 : terraform login 명령 후 웹 브라우저가 연동되어 뜨지 않는 경우에는 

[User Settings] - [Tokens] - [Create an API Token] 클릭

계정 생성 완료!

 


 

TFC를 State로 활용한 실습 

목표 : 아래 깃허브 저장소를 복제해 아래 조건에 만족하는 코드를 작성

조건

  1. Terraform Cloud를 State 백엔드로 구성
    • Workspace 이름 : terraform-edu-part1-assessment
    • 실행 모드는 local
  2. AWS 공통 Tag : Project = “workshop”
  3. aws_instance는 반복문을 사용해 3개 구성
  4. EIP를 제거하고 EC2에서 public ip를 자체 사용하도록 구성
  5. placeholder 변수는 아래 3가지가 각각의 aws_instance에 적용되도록 구성
    • placekitten.com / placebear.com / placedog.net
  6. placekitten.com placebear.com placedog.net
  • 풀이 예제는 ‘example’ 브랜치에 있다. 조건에 맞게 작성한 코드와 비교해보고 더 나은 방안을 찾아보자!
    • 반복문에 `count`와 `for_each`를 활용할 수 있다
    • `module`을 활용하는 방안도 있다

 

1. Terraform Cloud를 State 백엔드로 구성

  • Workspace 이름 : `terraform-edu-part1-assessment`
  • 실행 모드는 local
terraform {
  cloud {
    organization = "somaz-org"             # 생성한 ORG 이름 지정
    hostname     = "app.terraform.io"      # default

    workspaces {
      name = "terraform-edu-part1-assessment" # 없으면 생성된다.
    }
  }
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

local로 변경해준다.

 

 

2. AWS 공통 Tag : Project = “workshop”

provider "aws" {
  region = var.region
  default_tags {
    tags = {
      Project = "workshop"
    }
  }
}

 

 

3. aws_instance는 반복문을 사용해 3개 구성

`count = 3`을 사용하면 Terraform은 이 리소스를 3회 반복하여 생성한다.

각 인스턴스에 대한 특정 정보나 태그를 변경하려면 `count.index`를 사용하여 현재 반복 인덱스에 접근할 수 있다. 

코드에서 `count.index`는 `null_resource`의 provisioner 부분에서 각 인스턴스의 공개 IP에 연결하기 위해 사용한다.

resource "aws_instance" "hashicat" {
  count = 3
  ami                         = data.aws_ami.ubuntu.id
  instance_type               = var.instance_type
  key_name                    = aws_key_pair.hashicat.key_name
  associate_public_ip_address = true
  subnet_id                   = aws_subnet.hashicat.id
  vpc_security_group_ids      = [aws_security_group.hashicat.id]

  tags = {
    Name = "${var.prefix}-hashicat-instance"
  }

 

 

4. EIP를 제거하고 EC2에서 public ip를 자체 사용하도록 구성

# 해당부분 제거
- resource "aws_eip" "hashicat" {
-  instance = aws_instance.hashicat.id
-  vpc      = true
- }

- resource "aws_eip_association" "hashicat" {
-  instance_id   = aws_instance.hashicat.id
-  allocation_id = aws_eip.hashicat.id
-}

resource "aws_instance" "hashicat" {
  count = 3
  ami                         = data.aws_ami.ubuntu.id
  instance_type               = var.instance_type
  key_name                    = aws_key_pair.hashicat.key_name
  associate_public_ip_address = true			# public ip true 설정
  subnet_id                   = aws_subnet.hashicat.id
  vpc_security_group_ids      = [aws_security_group.hashicat.id]

  tags = {
    Name = "${var.prefix}-hashicat-instance"
  }
}

 

 

5. placeholder 변수는 아래 3가지가 각각의 aws_instance에 적용되도록 구성

variable "placeholder" {
  default     = [
    "placekitten.com",
    "placebear.com",
    "placedog.net"
  ]
  description = "Image-as-a-service URL. Some other fun ones to try are fillmurray.com, placecage.com, placebeard.it, loremflickr.com, baconmockup.com, placeimg.com, placebear.com, placeskull.com, stevensegallery.com, placedog.net"
}

 

 

6. Terraform Cloud State 구성을 위해 login → init → apply

terraform login

 

Generate a token using your browser, and copy-paste it into this prompt.

Terraform will store the token in plain text in the following file
for use by subsequent commands:
    /home/somaz/.terraform.d/credentials.tfrc.json

Token for app.terraform.io:
  Enter a value:


Retrieved token for user genius5711


---------------------------------------------------------------------------------

                                          -
                                          -----                           -
                                          ---------                      --
                                          ---------  -                -----
                                           ---------  ------        -------
                                             -------  ---------  ----------
                                                ----  ---------- ----------
                                                  --  ---------- ----------
   Welcome to Terraform Cloud!                     -  ---------- -------
                                                      ---  ----- ---
   Documentation: terraform.io/docs/cloud             --------   -
                                                      ----------
                                                      ----------
                                                       ---------
                                                           -----
                                                               -

Terraform Cloud 연동확인

terraform apply -auto-approve
catapp_ip = {
  "i-038a5ddd82b24f9a3" = "http://43.200.180.96"
  "i-0912e0c2cc8ab85d0" = "http://43.201.8.220"
  "i-0ebf7bf83405b4989" = "http://3.35.10.64"
}
catapp_url = {
  "i-038a5ddd82b24f9a3" = "http://ec2-43-200-180-96.ap-northeast-2.compute.amazonaws.com"
  "i-0912e0c2cc8ab85d0" = "http://ec2-43-201-8-220.ap-northeast-2.compute.amazonaws.com"
  "i-0ebf7bf83405b4989" = "http://ec2-3-35-10-64.ap-northeast-2.compute.amazonaws.com"
}

잘나온다!
강아지도 귀엽다?!
Cloud에도 리소스들이 잘보인다~

 

 

7. 실습 완료 후 리소스 삭제

 

1. AWS 리소스 삭제

terraform destroy -auto-approve

 

2. TFC에 워크스페이스 삭제 : `Setting → Destruction and Deletion` 클릭 후 삭제

3. Github Repo 삭제 : `terraform-aws-collaboration → Setting → Delete this repository`

4. 로컬 디렉터리 삭제 : `terraform-aws-collaboration-tom , terraform-aws-collaboration-jerry`

AWS 리소스 삭제 확인
AWS console에서 종료 확인
TFC 워크스페이스 삭제 확
Github Repo 삭제

 


3. 워크플로(Workflow)

 


 

1. 규모에 따른 워크플로

테라폼 워크플로 : Write → Plan → Apply, 워크스페이스 별로 접근 권한을 관리하고 중앙에서 관리되는 실행 환경을 설계하여 규모에 맞는 워크플로 설계가 필요하다.

테라폼 워크플로는 종류는 간단하게 개인 워크플로, 다중 작업자 워크플로, 다수 팀의 워크플로로 나눌수 있다.

 

개인 워크플로

개인이 테라폼으로 일하는 방식의 예이다. 차트는 Mermaid Live Editor를 활용해서 그렸다.

flowchart LR
    A[Write] --> B(Plan)
    B --> C{apply}
    C --> A

출처 : 테라폼으로 시작하는 IaC 책의 그림을 재구성

 

 

다중 작업자 워크플로

팀이 테라폼으로 일하는 방식의 예이다. 차트는 Mermaid Live Editor를 활용해서 그렸다.

flowchart LR
    subgraph Write
        A[Write] --> B[Plan]
        B --> C[Apply]
    end

    subgraph Plan
        D[Review]
    end

    subgraph Apply
        E[Merge]
    end

    C --> D
    D --> E

출처 : 테라폼으로 시작하는 IaC 책의 그림을 재구성

 

 

다수 팀의 워크플로 

R&R이 분리 된 다수 팀 또는 조직의 경우이다.

-R&R이 분리된 다수 팀 또는 조직의 경우 테라폼의 프로비저닝 대상은 하나이지만 관리하는 리소스가 분리된다.

단일 팀의 워크플로가 유지되고 그 결과에 대해 공유해야 하는 핵심 워크플로가 필요하다.

R&R이 분리된 팀이 테라폼으로 일하는 방식의 예이다. 차트는 Mermaid Live Editor를 활용해서 그렸다.

flowchart LR

    subgraph NetworkTeam["Network Team"]
        A[Write] --> B[Plan]
        B --> C[Apply]
    end

    subgraph ServerTeam["Server Team"]
        E[Write] --> F[Plan]
        F --> G[Apply]
    end

    D[(State)]
    C -.-> D
    D -.-> E

    subgraph Network["Network"]
        H[A Server]
        I[B Server]
    end

    NetworkTeam --> Network
    ServerTeam -.-> H
    ServerTeam -.-> I

출처 : 테라폼으로 시작하는 IaC 책의 그림을 재구성

 


 

2. 격리구조

테라폼 수준의 격리 목표는 State 분리이다.

  • 테라폼은 파일이나 하위 모듈로 구분하더라고 동작 기준은 실행하는 루트 모듈에서 코드를 통합하고 하나의 State로 관리한다.
  • 애플리케이션 구조가 모놀리식(+아키텍처)에서 MSA로 변화하는 과정은 테라폼의 IaC 특성과도 결부된다.

 

MSA와 모놀리식의 차이를 나타내 보았다. 차트는 Mermaid Live Editor를 활용해서 그렸다.

flowchart TB
    %% Monolithic section
    subgraph "Monolithic Application"
        A[User Interface] --> B[Business Logic]
        B --> C[Data Access Layer]
        C --> D[Database]
    end

    %% Spacing
    Space1[ ] --> Space2[ ]
    
    %% Microservices section
    subgraph "MSA Application"
        UI[User Interface Service]
        S1[Service 1]
        S2[Service 2]
        S3[Service 3]
        DB1[Database 1]
        DB2[Database 2]
        DB3[Database 3]
    end
    
    UI --> S1
    UI --> S2
    UI --> S3
    S1 --> DB1
    S2 --> DB2
    S3 --> DB3

MSA vs Monolithic

 

루트 모듈 격리(파일/디렉터리)

 

차트는 Mermaid Live Editor를 활용해서 그렸다.

flowchart LR

    subgraph RootModule["Root Module"]
        a["main.tf(VM)"]
        b[network.tf]
        c[etc.tf]
        d[terraform.tfstate]
        f[vpc.tf]
        b -.-> a
    end

    subgraph RootModuleNet["Root Module(Net)"]
        g[main.tf]
        h[terraform.tfstate]
    end

    subgraph RootModuleVM["Root Module (VM)"]
        i[main.tf]
        j[terraform.tfstate]
        i --> h
    end

    subgraph RootModuleEtc["Root Module (etc)"]
        k[main.tf]
        l[terraform.tfstate]
        k --> h
    end

    RootModule --> RootModuleNet

Root Module 격리

 

 

환경 격리 - 깃 브랜치

서비스의 테스트, 검증, 운영 배포를 위해 테라폼으로 관리하는 리소스가 환경별로 격리되어야 한다면 디렉터리 구조로 분리하는 방안을 고려할 수 있다.

디렉터리별로 각 환경을 나누는 것은 개인의 관리 편의성은 높지만, 환경의 아키텍처를 고정시키고 코드 수준의 승인 체계를 만들기 위해서는 최종 형상에 대한 환경별 브랜치를 구성하기를 권장한다.

디렉터리 구조 만으로는 환경에 따라 사용자를 격리할 수 없다. 이때 깃의 브랜치 기능을 활용하면 환경 별로 구별된 작업과 협업이 가능하다.

출처 :&nbsp;https://www.campingcoder.com/2018/04/how-to-use-git-flow/

 

 


 

3. 프로비저닝 파이프라인 설계 - 깃허브

2023.05.19 - [IaC/CI CD Tool] - 1. Github Action이란?

 

1. Github Action이란?

Overview 오늘은 Github Action에 대해서 공부해보려고 한다. Github Action이란? GitHub Actions는 빌드, 테스트 및 배포 파이프라인을 자동화할 수 있는 지속적 통합 및 지속적 배포(CI/CD) 플랫폼이다. 리포지

somaz.tistory.com

 

 

프로비저닝 파이프라인 + Github Action 준비

저장소 포크 : https://github.com/terraform101/terraform-aws-github-action

Github Action은 별도의 State 저장소를 제공하지 않기 때문에 테라폼 실행으로 생성되는 State가 항상 초기화되어 프로비저닝 결과를 유지할 수 없다.

 


 

연습문제1

동작 확인을 위해 다음의 조건으로 브랜치를 생성하고 새로운 입력 변수를 적용한다.

출처 : CloudNet@ 스터디

 

조건

  • 브랜치 이름 : `add-env-variable`
  • 테라폼 입력 변수 추가
    • 이름 : environment
    • 설명 : Define infrastructure’s environment
    • 타입 : string
    • 기본값 : dev
    • 변수 확인
      • 조건 : dev, qa, prod 인 경우 허용
      • 에러 메시지 : The environment value must be dev, qa, or prod.
  • aws_vpc의 `tags.environment`를 입력 변수 `environment` 값으로 선언

새로운 브랜치의 변경 내용을 커밋, 푸시하고 깃허브 웹페이지에서 풀 리퀘스트를 생성한다 ← 주의: 자신의 저장소로 요청하는지 꼭 확인

 

Github Action의 동작 조건은 메인 브랜치에 푸시가 발생하거나 풀 리퀘스트가 발생하는 경우로 정의되어 있다.

# Github Action의 실행 조건 : ./github/workflows/action.yml
on:
  push:
    branches:
      - main
  pull_request:
..

 

브랜치를 생성한다.

# 확인 (빠져나오기 q)
git branch
git branch -M add-env-variable

# 확인 (빠져나오기 q)
git branch 
* add-env-variable

 

 

`main.tf` 내용을 수정한다.

resource "aws_vpc" "hashicat" {
  cidr_block           = var.address_space
  enable_dns_hostnames = true

  tags = {
    name        = "${var.prefix}-vpc-${var.region}"
    environment = var.environment  # 원래 값 Production
  }
}

 

`variable.tf` 내용 추가한다.

  • 조건 : `dev, qa, prod` 인 경우 허용
  • 에러 메시지 : The environment value must be dev, qa, or prod.
variable "environment" {
  type        = string
  description = "Define infrastructure’s environment"
  default     = "dev"

  validation {
    condition     = contains(["dev", "qa", "prod"], var.environment)
    error_message = "The environment value must be dev, qa, or prod."
  }
}

 

push 후 pull request merge 전에 github action enable 설정 해줘야한다!

그리고 action.yml에 token도 정의해줘야 한다.

github actions의 Secrets에 정의해준다. AWS Secret Access Key도 Secret에 정의해준다.

 

main.tf에서 <MY-ORG>도 자신의 ORG로 변경해준다.

 

 

enable actions

 

terraform {
  cloud {
    organization = "somaz-org"
    hostname     = "app.terraform.io" # default

    workspaces {
      name = "terraform-aws-github-action"
    }
  }
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

 

 

push한다. 

terraform fmt
git add .
git commit -m "add env variable"
git push origin HEAD
...
To github.com:somaz94/terraform-aws-github-action.git
 * [new branch]      HEAD -> add-env-variable

pull request

 

fork한 나의 github main 저장소로 변경!

 

merge 한다!

 

돈다? 잘돌아

 

ORG를 빼먹엇다. 다시 돌려준다!

 

잘 생성된 것을 볼 수 있다.

 

테라폼 코드가 돌아간 `actions.yml` 마지막에 보면 `ip`와 `url`도 잘 나와 있다.

...
catapp_ip = "http://3.38.69.44"
catapp_url = "http://ec2-3-38-69-44.ap-northeast-2.compute.amazonaws.com"

 

여전히 냥이는 귀엽다.

고양이는 귀엽다?!

 


 

 

연습문제2

Destroy에 대한 Github Actions을 작성 - 수동으로 실행(on.workflow_dispatch)

 

`actions.yml` 내용 전체 수정해준다.

name: Terraform DEV Destroy

on:
  workflow_dispatch:

env:
  MY_PREFIX: DEV
  TF_VERSION: 1.5.6

jobs:
  Terraform:
    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: ${{ secrets.TF_API_TOKEN }}

      - name: Terraform init
        id: init
        run: terraform init -upgrade
        # working-directory: ${{ env.working-directory }}

      - name: Terraform validate
        id: validate
        run: terraform validate -no-color

      - name: Terraform destroy
        id: Destory
        run: terraform destroy -auto-approve -var=prefix="$MY_PREFIX"
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

 

push해준다.

terraform fmt
git add .
git commit -m "destroy"
git push origin HEAD

 

Create PR → Merge 한다.

 

destroy

 

Actions &rarr; Terraform DEV Destroy &rarr; Run workflow 로 수동 실행

 

잘 뿌시는 중이다?
잘 삭제되었다.
AWS 리소스 삭제도 확인해준다.

 

 

실습 완료 후 리소스 삭제

  1. Github Repo 삭제
  2. TFC Workspace 삭제
  3. 로컬에 코드 폴더 삭제

 


 

Pull request 활용해보기

 

  • Github Token 생성 → CICD_PAT Secret 설정

시나리오

워크플로는 test2 분기에 변경 사항을 푸시할 때 트리거된다.

test 디렉토리를 만들고 변경 사항을 커밋하고 test1 브랜치로 푸시한 다음 마지막으로 test1의 변경 사항을 main 브랜치로 병합하는 풀 요청을 생성한다.

 

풀 요청을 생성하려면 충분한 권한(예: repo 범위)이 있는 개인 액세스 토큰으로 CICD_PAT 리포지토리 암호를 설정해야 한다.


 

 

test2 branch workflow를 생성

name: Create Pull Request

on:
  push:
    branches:
      - test2

jobs:
  create_pull_request:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
          persist-credentials: false
          token: ${{ secrets.CICD_PAT }}

      - name: Create test.txt file
        run: echo "This is a test file" > test.txt

      - name: Commit and push test.txt file
        run: |
          git config --global user.email "cicd@somaz.link"
          git config --global user.name "cicd"
          git checkout -b "test1"
          git add test.txt
          git commit -m "Add test.txt file"
          git push https://${{ secrets.CICD_PAT }}@github.com/${{ github.repository }} test1

      - name: Create pull request
        env:
          GITHUB_TOKEN: ${{ secrets.CICD_PAT }}
        run: |
          PR_JSON=$(curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github+json" https://api.github.com/repos/${{ github.repository }}/pulls -d '{"title": "Add test.txt file", "head": "test1", "base": "main"}')
          echo "$PR_JSON" | jq '.number'

 

 

그럼 아래와 같이 test1에서 main으로 자동으로 pull request가 생기는 것을 볼 수 있다.

 

 

해당 코드를 활용하면 prod branch로 trigger를 통해서 push 자동화를 설정했을때!

아래와 같이 github bracnh protection rules를 설정해 prod 브랜치를 보호하고 pull request를 통해서만 main branch로 merge 하도록 설정할 수 있다.

 

protection rules 설정

 

      - name: Add & Commit & Push files & Pull request
        if: ${{ steps.gen.outputs.isGenfileModified == 'true' && github.event.client_payload.ref_name == 'prod' && github.event.client_payload.ref_name != 'gcpprod' }}
        env:
          TRIGGER_USER: ${{ github.event.sender.login }}
          BRANCH: ${{ env.BRANCH }}
          RUN_NUMBER: ${{ env.RUN_NUMBER }}
          CICD_PAT: ${{ secrets.CICD_PAT }} # Add CICD_PAT from organization secrets
        run: |
          git config --global user.email "cicd@somaz.link"
          git config --global user.name "cicd"
  
          # Create a new branch for the changes
          git checkout -b "update-$TRIGGER_USER-$RUN_NUMBER"
  
          git add libs
          git commit -m "Update files by $TRIGGER_USER. Run number: ${{ env.RUN_NUMBER }}"
          git push -u origin "update-$TRIGGER_USER-$RUN_NUMBER"
  
          # Create a pull request using the GitHub API
          PR_JSON=$(curl -s -X POST -H "Authorization: token $CICD_PAT" -H "Accept: application/vnd.github+json" https://api.github.com/repos/$GITHUB_REPOSITORY/pulls -d '{"title":"Update files by '"$TRIGGER_USER"'. Run number: '"${RUN_NUMBER}"'", "head":"update-files-'"$TRIGGER_USER"'-'"$RUN_NUMBER"'", "base":"'"${{ github.event.client_payload.ref }}"'"}')
          echo "$PR_JSON" | jq '.number'

 


Reference

 

728x90
반응형