T101-6주차 [Terraform 101 Study]

cloudNet@ 팀의 가시다 님이 진행하는 테라폼 102 스터디 6주차 정리입니다. (테라폼으로 시작하는 IaC” (한빛미디어) 도서로 진행!)

협업 이란?

  • 인프라 규모가 커지고 관리 팀원이 늘어날 수록 구성 코리 관리가 필요 → 서로 작성 코드 점검 및 협업 환경 구성
  • 구성 요소 : 코드를 다수의 작업자가 유지 보수 할 수 있도록 돕는 VCS Version Control System + 테라폼 State를 중앙화하는 중앙 저장소

https://kschoi728.tistory.com/139

유형 1 : VCS, 중앙 저장소 없음

– 동일한 대상을 관리하는 여러 작업자는 동일한 프로비저닝을 위해 각자 자신이 작성한 코드를 수동으로 공유가 필요

– 작업자의 수가 늘어날수록 코드 동기화는 어려워지고, 각 작업자가 작성한 코드를 병합하기도 어렵다. VCS 도입 시점이다.

유형 2 : VCS(SVN, Git), 중앙 저장소 도입

– 형성관리 도구를 통해 여러 작업자가 동일한 테라폼 코드를 공유해 구성 작업

– 변경 이력 관리 및 이전 버전으로 롤백 가능

– 공유파일은 테라폼 구성파일과 State ← 테라폼 프로비저닝의 결과물로 데이터 저장소와 같음

– 작업자가 서로 다른 프로비저닝한 State 결과를 공유를 위해서 백엔드(공유 저장소) 설정을 제공

유형 3 : VCS(Github), 중앙 저장소 도입

– 테라폼 코드 형상관리를 위한 중앙 저장소 + State 백엔드 → 작업자는 개별적으로 프로비저닝을 테스트하고 완 성된 코드를 공유

– State 는 중앙 관리되어, 작업자가 프로비저닝을 수행하면 원격 State의 상태를 확인하고 프로비저닝 수행

1. 형상 관리 도구

– SVN : 중앙 저장소에서 코드와 히스토리를 관리하는 방식

– Git : 분산형 관리 시스템으로 작업 환경에서도 별도로 코드 히스토리를 관리하고 중앙 저장소와 동기화

1.1 Git(깃)

깃을 사용하면 중앙 저장소와 코드를 동기화하지 않아도 같은 파일을 여러 명이 작업할 수 있다

  • 깃은 코드 형상관리를 작업 환경인 로컬 저장소 Local Repository와 리모트 저장소 Remote Repository에 저장할 수 있다
  • 기본적으로 작업자가 작업 환경에서 코드 커밋을 수행하면 로컬 저장소에 적용된다
  • 공동 작업을 위해 이 저장소의 내용을 다른 사람에게도 제공하려면 중앙 저장소가 필요하고, 이를 리모트 저장소가 담당한다
  • 로컬 저장소 : 작업자 로컬 환경에 저장되는 개인 전용 저장소
  • 리모트 저장소 : 코드 형상관리 이력과 코드가 원격지에서 관리되고 여러 사람이 공유하는 저장소

[git 용어 설명]

– 코드 추가와 변경 사항을 저장소에 기록하려면 커밋이라는 절차가 필요하다. 커밋을 수행하면 이전 커밋 시점부터 현재 상태까지의 변경 이력이 기록된다

1.2 GitHub

Git은 리모트 저장소가 있어야 코드 수준에서 다른 작업자와 협업 가능하다. 협업은 리모트 저장소를 관리하며 데이터를 올리고(Push) 받는 (Pull) 것이다. => 대표적인 리모트 저장소는 GitHub, GitLab, Bitbucket이 있다.

2. 코드 관리

깃허브 포크

  • 깃허브는 포크 fork 기능을 제공해 기존 리모트 저장소본인 소유의 저장소로 복사할 수 있다
  • 포크한 저장소는 원본 저장소와 연결이 되어 있으므로 이후 변경 사항을 원본 저장소에 적용하는 요청(Pull Request)이 가능하다.

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

  • 위 저장소 이동 후 우측 상단 [Fork] 클릭 후 새로운 깃허브 리포지터리 생성 [Create fork]

2.1 공유 제외 대상

테라폼 런타임 시 생성되는 파일, 실행 결과물, 개별 사용자 설정 파일, 시크릿 정보는 Git의 관리 대상에서 제외해야 한다.

  • .terraform 디렉터리 : init 실행 시 작성되므로 제외
  • .tfstate 파일 : 프로비저닝 결과 데이터 소스 정보, 민감 데이터가 포함, 다른 사용자가 같은 State 파일을 사용하는 경우 인프라 불합치 유발
  • tfvars 파일 : 프로비저닝 시 적용할 변수 값을 보관하는 파일로, 작업자 마다 별도 변수 사용
  • 시크릿 파일 : 인프라 구성에 필요한 시크릿 정보 파일
  • terraformrc 파일 : 작업자의 CLI 설정 파일

로컬 저장소에 복제

#
MyGit=<각자 자신의 깃허브 계정>
MyGit=gasida
git clone https://github.com/$MyGit/terraform-aws-collaboration

# 확인
tree terraform-aws-collaboration
cd terraform-aws-collaboration
git remote get-url origin

Push 테스트 : test.txt

#
echo "T101 Study" >> test.txt

#
git add test.txt
#주석 설명을 달아준다 첫번째 커밋이라고 
git commit -m "first commit"
git push

# Git 자격 증명 설정
git config --global user.name $MyGit
git config --global user.email <깃허브 가입 이메일주소>
git config --global user.email gasida.seo@gmail.com
cat ~/.gitconfig

# push 실행을 위해서 로그인 정보 입력
git push
Username for 'https://github.com': <깃허브 계정 정보>
Password for 'https://gasida@github.com': <토큰 정보>
...

# 로그인 정보 입력 방안2
export GITHUB_USER=<깃허브 계정 정보>
export GITHUB_TOKEN=<토큰 정보>

– 코드 협업을 위한 1인 2역을 준비 : 복제한 디렉터리 변경 및 추가 복사

  • terraform-aws-collaboration-tom : tom 작업 디렉터리
  • terraform-aws-collaboration-jerry : jerry 작업 디렉터리
#
cd ..
mv terraform-aws-collaboration terraform-aws-collaboration-tom
cp -r terraform-aws-collaboration-tom terraform-aws-collaboration-jerry

Push & Pull

  • 커밋된 코드는 로컬 저장소에 기록되며 푸시를 하기 전까지는 리모트 저장소에 공유되지 않음 → 또한 작업 중 풀을 통해 리모트 저장소의 변경 사항을 로컬 저장소에 반영
  • tom 디렉터리의 main.tf 파일 코드 내용 수정
...
provider "aws" {
  region = var.region
  default_tags {
    tags = {
      Project = "T101-Study-6week"
    }
  }
}
...

– 수정된 코드를 커밋하고 리모트 저장소에 푸시

#
cd terraform-aws-collaboration-tom
git remote get-url origin
git add main.tf
git commit -m "add default tags & project name"
git push

– 자신의 깃헙 사이트에서 main.tf 파일 코드 내용 수정 확인

* jerry 디렉터리에서 작업

#
cd ..
cd terraform-aws-collaboration-jerry
git remote get-url origin
#원격 저장소의 변경 사항을 로컬 저장소로 가져와 병합(merge)하는 작업을 수행합니다 = 원격 저장소와 로컬 저장소 동기화 합니다.
git pull
cat main.tf |grep Project

이번 상황은 tom과 jerry 양쪽에서 default_tags에 Owner 항목을 추가하고 저장 시도

– jerry 디렉터리의 main.tf 파일 코드 내용 수정

...
provider "aws" {
  region = var.region
  default_tags {
    tags = {
      Project = "T101-Study-6week"
      Owner = "jerry"
    }
  }
}
...

– 수정된 코드를 커밋하고 리모트 저장소에 푸시

#
cd terraform-aws-collaboration-jerry
git add main.tf
git commit -m "add default tags & Owner is jerry"
git push
  • 자신의 깃헙 사이트에서 main.tf 파일 코드 내용 수정 확인
  • 아래와 같이 추가 확인 가능 합니다.

– tom 디렉터리의 main.tf 파일 코드 내용 수정

...
provider "aws" {
  region = var.region
  default_tags {
    tags = {
      Project = "T101-Study-6week"
      Owner = "tom"
    }
  }
}
...

– 수정된 코드를 커밋하고 리모트 저장소에 푸시

#
cd ..
cd terraform-aws-collaboration-tom
git add main.tf
git commit -m "add default tags & Owner is tom"

# tom의 루트 모듈 변경 코드를 리모트 저장소에 보낼 때 실패
git push
===========
To https://github.com/sjmtom2/terraform-aws-collaboration
 ! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'https://github.com/sjmtom2/terraform-aws-collaboration'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
===========
  • 푸시 전 풀을 먼저 수행해 리모트의 변경 사항을 로컬과 비교한 후 필요할 때 수정해 푸시하는 습관이 필요하다
  • 변경 사항에 충돌이 없는 경우 자동으로 커밋 지점이 병합된다
  • 풀 동작에 충돌이 있을 것으로 가정해 옵션을 붙여 수행 : git rebase
git pull --no-rebase

* 로컬 저장소와 리모트 저장소의 충돌 내용 표기 , === 위는 로컬 저장소, 아래는 리모트 저장소 내용 ⇒ ‘현재 변경 사항 수락’

#
terraform fmt
git add main.tf
git commit -m "change default tags Owner"
git push
cat main.tf | grep Owner

Pull Request

  • Push와 Pull로도 코드 협업이 가능하지만 다른 사람의 커밋을 확인하기가 쉽지 않고, 리모트 저장소에 푸시할 때가 되어야 충돌 상황을 확인하게 된다는 단점이 있다.
  • 작업된 테라폼 코드에 대한 자연스러운 리뷰와 메인스트림 main branch의 병합을 관리하기 위해 Pull Request 방식을 사용한다.
  • Git으로 코드를 관리할 때 주로 사용되는 브랜치 branch를 이용하는 방안으로, 작업자가 코드 수정을 위해 메인과 별개의 브랜치를 생성하고 작업 후 본인의 브랜치를 Push하고 코드 관리자에게 검토 후 병합을 요청하는 방식이다.

코드 작성자 1단계

코드 충돌을 유발하기 위해 앞 실습의 결과로 tom이 메인 브랜치에 Push한 상황에서 jerry가 Owner 항목을 jerry & tom 으로 변경하고 싶어한다.

– 다른 브런치를 생성합니다.

#
cd ..
cd terraform-aws-collaboration-jerry

# 현재 브렌치 확인
git branch
==
* main
==
<q로 빠져나옴>

# 새로 만든 jerry-owner-task 브랜치로 전환
git checkout -b jerry-owner-task
git branch
==
* jerry-owner-task
  main
==

– jerry 디렉터리의 main.tf 파일 코드 내용 수정

...
provider "aws" {
  region = var.region
  default_tags {
    tags = {
      Project = "T101-Study-6week"
      Owner = "jerry & tom"
    }
  }
}
...

– jerry에서 작업중인 브랜치로 코드를 푸시

#
git add main.tf
git commit -m "add default tags & Owner is jerrh & tom"
git push origin jerry-owner-task
Enumerating objects: 38, done.
Counting objects: 100% (38/38), done.
Delta compression using up to 8 threads
Compressing objects: 100% (24/24), done.
Writing objects: 100% (38/38), 11.07 KiB | 11.07 MiB/s, done.
Total 38 (delta 15), reused 28 (delta 10), pack-reused 0
remote: Resolving deltas: 100% (15/15), done.
remote: 
remote: Create a pull request for 'jerry-owner-task' on GitHub by visiting:
remote:      https://github.com/gasida/terraform-aws-collaboration/pull/new/jerry-owner-task
remote: 
To https://github.com/gasida/terraform-aws-collaboration
 * [new branch]      jerry-owner-task -> jerry-owner-task

위 링크 방문 → 자신의 저장소 선택 : Change Owner Tag 입력 후 Create pull request

  • 풀 리퀘스트는 코드를 머지 Merge 하는 관리자에게 자신이 수정한 내용을 메인 코드병합해달라는 요청을 하는 것이다.
  • 푸시로 코드를 머지하는 것과는 다르게 풀 리퀘스트는 코드 리뷰를 강제하기 때문에 코드 변경 사항을 검토할 수 있다

코드 관리자

  • 리모트 저장소의 쓰기 권한이 있는 관리자는 풀 리퀘스트가 발생하면 코드를 기존 코드에 머지할 권한을 갖는다.
  • 풀 리퀘스트에는 요청자의 변경 사항이 표기된다. 풀 리퀘스트 수행 방식은 세 가지를 지원하나 여기서는 기본 설정대로 진행한다
  1. Create a merge commit : 브랜치의 모든 커밋이 병헙을 통해 기본 브랜치에 추가
  2. Squash and Merge : 브랜치의 변경 이력을 모두 합쳐 새로운 커밋을 생성하고 메인에 추가
  3. Rebase and Merge : 브랜치 변경 이력이 각각 메인에 추가


코드 작성자 2단계

– 머지가 완료되면 작업자는 메인 브랜치로 이동 후 풀을 수행해 코드를 동기화하고 기존 브랜치를 삭제한다

git checkout main
git pull
git branch -d jerry-owner-task
git branch

코드 협업자

– 머지 이후 코드를 작업하는 작업자는 개별 로컬 저장소에 새로운 코드를 가져올 수 있다

#
cd ..
cd terraform-aws-collaboration-tom
git checkout main
git pull

3. State 백엔드

  • 구성 목적
  • 관리 : 지속적인 State 백업을 위해서 local 이외의 저장소가 필요
  • 공유 : 다수의 작업자가 동일한 State로 접근해 프로비저닝하기 위한 공유 스토리지 필요
  • 격리 : 민감한 데이터가 State 파일에 저장될 가능성을 고려하여, 각각의 환경에 따라 접근 권한 제어 필요

3.1 Terraform Cloud (TFC) 백엔드

하시코프에서 프로비저닝 대상과 별개로 State를 관리할 수 있도록 SaaS 환경인 TFC를 제공하며 State 관리 기능은 무상을 제공

– 제공 기능 : 기본 기능 무료, State 히스토리 관리, State lock 기본 제공, State 변경에 대한 비교 기능

– Free Plan 업데이트 : 사용자 5명 → 리소스 500개, 보안 기능(SSO, Sentinel/OPA로 Policy 사용)

TFC 계정 생성

https://app.terraform.io/ 링크 접속 후 하단에 free account 클릭 → 계정 생성 후 이메일 확인 → 암호 입력 후 로그인

TFC 초기 설정 화면 → 사용자의 고유 조직 이름 생성 :<nickname>-org

CLI 환경에서 TFC 사용을 위한 자격증명

# 
terraform login
yes 입력

TFC 신규 창 → TFC 토큰 생성

# 
terraform login
yes 입력
...
Terraform will store the token in plain text in the following file
for use by subsequent commands:
    /Users/gasida/.terraform.d/credentials.tfrc.json

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

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

3.2 백엔드 구성

TFC State 실습

  • tom 루트 모듈과 jerry 루트 모듈을 사용해 공통 백엔드 구성과 동작을 확인 → 두 작업자가 동일한 AWS 인프라를 프로비저닝하기를 원하는 상황
  • tom 루트 모듈에서 실행
#
cd ..
cd terraform-aws-collaboration-tom

#
terraform init
terraform plan -var=prefix=dev
terraform apply -auto-approve -var=prefix=dev

# 확인
terraform workspace list
terraform state list
ls terraform.tfstate*
terraform output

– jerry 루트 모듈에서 실행 : plan 결과가 어떤가요? ⇒ 동일한 자원을 만들려고한다

#
cd ..
cd terraform-aws-collaboration-jerry

# plan 결과가 어떤가요?
terraform init && terraform plan -var=prefix=dev

해결방안: TFC를 통하여 작업 결과 공유 설정

  • TFC의 workspaces는 CLI에서의 workspace처럼 테라폼 구성과는 별개로 State를 관리하는 단위다
  • tom 루트 모듈에 main.tf 파일 수정
terraform {
  cloud {
    organization = "<MY_ORG_NAME>"         # 생성한 ORG 이름 지정
    hostname     = "app.terraform.io"      # default

    workspaces {
      name = "terraform-aws-collaboration"  # 없으면 생성됨
    }
  }
...
#
cd ..
cd terraform-aws-collaboration-tom

#
terraform init
yes

# 파일 내용이 어떤가요?
ls terraform.tfstate*
cat terraform.tfstate  => 내용이 없습니다.

용량이 0바이트 인것을 알수가 있습니다.

– TFC 전용 워크스페이스(State 백엔드 역할만 수행) 확인 → 선택 후 좌측에 Settings 클릭 → General

  • 실행 모드 변경 : Execution Mode 에서 Local 선택 → 하단의 Save settings 클릭하여 변경 적용
  • Remote : 테라폼 실행을 Terraform Cloud에서 수행
  • Local : 테라폼 실행은 작업자 환경 또는 연결된 외부 환경에서 실행해 State만을 동기화
  • Agent : 테라폼 실행을 사용자 지정 Agent(설치된 서버 환경)에서 수행 (Business 플랜에서 활성화)
  • 이후 [States] 탭에서 terraform init을 통해 마이그레이션된 State 업로드 정보 확인

백엔드 저장소 동작 확인

rm -rf terraform.tfstate*
terraform plan -var=prefix=dev

3.3 백엔드 활용

  • TFC State 잠금 기능 : 프로비저닝 수행 시 동일한 State 접근 못하게 잠금 → State 마지막 상태에 대한 무결성 확보
  • Jerry가 프로비저닝 실행 시 변경 사항이 발생하도록 코드를 수정한다고 가정하여, null_resource에 정의한 내용이 항상 수행되도록 trigger를 추가함
  • trigger에 timestamp() 함수를 지정하면 테라폼 프로비저닝 실행마다 다른 값이 저장되므로 항상 변경이 유발함
  • jerry 루트 모듈에서 main.tf 파일 코드 변경 및 실행
resource "null_resource" "configure-cat-app" {
  depends_on = [aws_eip_association.hashicat]

  triggers = {
    build_number = timestamp()
  }
# 
terraform apply -var=prefix=dev
...
Enter a value: <대기>

신규 터미널에서 tom 루트 모듈에서 실행

# 
terraform apply -var=prefix=dev
╷
│ Error: Error acquiring the state lock
│ 
│ Error message: workspace already locked (lock ID: "gasida-org/terraform-aws-collaboration")
│ Lock Info:
│   ID:        4869b6f7-8c57-6f50-857c-9730999b8b2a
│   Path:      
│   Operation: OperationTypeApply
│   Who:       gasida@seojonghoui-MacBookPro.local
│   Version:   1.5.1
│   Created:   2023-08-06 09:01:03.131157 +0000 UTC
│   Info:      
│ 
│ 
│ Terraform acquires a state lock to protect the state from being written
│ by multiple users at the same time. Please resolve the issue above and try
│ again. For most commands, you can disable locking with the "-lock=false"
│ flag, but this is not recommended.

– TFC Workspace 에 State 확인

– jerry 루트 모듈에서 실행

TFC Workspace 에 State → [Changes in this version] 에서 변경 사항 확인

실습 완료 하였으면 리소스 삭제 합니다.

#aws 리소스 삭제
terraform destroy -auto-approve -var=prefix=dev
  1. TFC에 워크스페이스 삭제 : Setting → Destruction and Deletion 클릭 후 삭제
  2. Github Repo 삭제 : terraform-aws-collaboration → Setting → Delete this repository
  3. 로컬 디렉터리 삭제 : terraform-aws-collaboration-tom , terraform-aws-collaboration-jerry
위로 스크롤