학습일지/K-Digital Traing

[KDT] AIaaS 마스터클래스 6주차 - 테라폼, 깃허브 액션 실습

tierr 2025. 4. 28. 12:06

26일차에는 과제가 있었다. 로드밸런서를 생성하는 부분은 시간이 많이 필요해서 아직 실습이 진행중이긴 하지만 요약하자면, Terraform 을 이용해서 GitHub에 앱을 배포할때, GitHub 상에서 자동으로 스크립트를 실행할 수 있는 Action에 대해 다루는 실습이 대부분이다. 이를 이용해 카카오클라우드에 자동으로 인스턴스와 보안그룹을 생성할 수도 있다.

기본 세팅이 연동된 코드를 베이스로 진행했기 때문에, 스크립트에 대한 이해도는 아직 부족하지만 돌아가는 원리는 이해가 된 것 같다.


1. 리눅스 실습 - 테라폼 모듈

Terraform과 Github Action을 활용 기본 배포를 구현해봅시다. Kakaocloud와 Terraform 연결은 가이드가 충분하지 않아 기본 세팅을 연동해두었습니다. (VM 1대 띄우는 예시 코드)
kakaocloud-terraform.tar.gz

1-1. 개요

Terraform 모듈(Module)은 반복 가능한 리소스 묶음을 재사용할 수 있게 해 주는 기능입니다. 이를 통해 코드 중복을 줄이고 관리·유지 보수가 용이해집니다.


1-2. 환경 설정

lab-terraform-modules/
├── modules/
│   └── compute/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── main.tf

1-3. VM 1개 띄워보기

  • 설정파일 채워넣기
  • terraform.tfvars
username     = "ooo@gmail.com"
password     = "****"
tenant_name  = "kc-sfacspace"
region       = "kr-central-2"
project_name = "kc-sfacspace"
dev_name = "ooo"

# 네트워크 설정
network_name = "sfacspace-default"

# 인스턴스 설정
create_instance   = true
image_name        = "Ubuntu 24.04"
image_id          = "6f8f7e1c-b801-46c6-940c-603ffc05247a"
flavor_name       = "t1i.small"
key_name          = "ooooo"
root_volume_size  = 12

# 추가 볼륨 설정
create_data_volume = false
data_volume_size   = 50

# 오브젝트 스토리지 설정
create_s3_bucket   = false
s3_bucket_suffix   = "unique-suffix-12345"
  • 초기화 및 실행
    terraform init
    terraform plan
    
    terraform apply

VM 인스턴스가 1개 생성됨


1-4. compute 모듈 제작

더보기
  • modules/compute/main.tf
resource "openstack_compute_instance_v2" "web" {
  count           = var.create_instance ? 1 : 0
  name            = var.instance_name
  image_id        = var.image_id
  flavor_name     = var.flavor_name
  key_pair        = var.key_name
  security_groups = var.security_groups

  network {
    name = var.network_name
  }
  
  block_device {
    uuid                  = var.image_id
    source_type           = "image"
    volume_size           = var.root_volume_size
    boot_index            = 0
    destination_type      = "volume"
    delete_on_termination = true
  }
}

 

  • modules/compute/variables.tf
variable "instance_name" {
  description = "Name tag for the instance"
  type        = string
  default     = "module-vm"
}

variable "create_instance" {
  description = "인스턴스 생성 여부"
  type        = bool
  default     = false
}

variable "image_id" {
  description = "인스턴스의 이미지 ID"
  type        = string
  default     = ""
}

variable "flavor_name" {
  description = "인스턴스 타입(flavor)"
  type        = string
}

variable "key_name" {
  description = "SSH 접속을 위한 키 페어 이름"
  type        = string
  default     = ""
}

variable "security_groups" {
  description = "보안 그룹 목록"
  type        = list(string)
}

variable "network_name" {
  description = "네트워크 이름"
  type        = string
}

variable "root_volume_size" {
  description = "루트 볼륨 크기(GB)"
  type        = number
  default     = 20
}

 

  • modules/compute/outputs.tf
output "instance_id" {
  description = "인스턴스 ID"
  value       = openstack_compute_instance_v2.web[0].id
}

output "instance_name" {
  description = "인스턴스 이름"
  value       = openstack_compute_instance_v2.web[0].name
}

output "public_ip" {
  description = "인스턴스의 공인 IP 주소"
  value       = openstack_compute_instance_v2.web[0].access_ip_v4
}

 

  • modules/compute/provider.tf
terraform {
  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "~> 3.0.0"
    }
  }
}

1-5. 루트에서 모듈 사용

  • main.tf
... (아래에 추가)

module "web_server" {
  source = "./modules/compute"

  create_instance   = var.create_instance
  instance_name     = "${var.dev_name}-web-server"
  image_id          = var.image_id != "" ? var.image_id : data.openstack_images_image_v2.ubuntu.id
  flavor_name       = var.flavor_name
  key_name          = var.key_name
  security_groups   = [openstack_networking_secgroup_v2.web.name]
  network_name      = "75ec8f1b-f756-45ec-b84d-6124b2bd2f2b_7c90b71b-e11a-48dc-83a0-e2bf7394bfb4"
  root_volume_size  = var.root_volume_size
}
  • 초기화 및 비교, 실행 (모듈 사용시 초기화 다시 필요)
terraform init
terraform plan
terraform apply

루트에와 모듈에서 각 1개씩, 총 2개의 VM 인스턴스가 생성됨


1-6. 모듈을 반복문 돌리기(=여러 개 띄우기)

루트에서 모듈 사용

  • main.tf
...

module "app_servers" {
  source = "./modules/compute"
  for_each = toset(["app-1","app-2","app-3"])

  create_instance   = var.create_instance
  instance_name     = "${var.dev_name}-${each.value}"
  image_id          = var.image_id != "" ? var.image_id : data.openstack_images_image_v2.ubuntu.id
  flavor_name       = var.flavor_name
  key_name          = var.key_name
  security_groups   = [openstack_networking_secgroup_v2.web.name]
  network_name      = "75ec8f1b-f756-45ec-b84d-6124b2bd2f2b_7c90b71b-e11a-48dc-83a0-e2bf7394bfb4"
  root_volume_size  = var.root_volume_size
}
  • 초기화, 비교 및 실행
    terraform init
    terraform plan
    
    terraform apply

모듈을 반복문으로 사용해서 VM 인스턴스가 여러개 생성됨

*참고 자료

 

Modules Overview - Configuration Language | Terraform | HashiCorp Developer

Modules are containers for multiple resources that are used together in a configuration. Find resources for using, developing, and publishing modules.

developer.hashicorp.com


2. 리눅스 실습 - 깃헙액션 기본

2-1. 기본 CI 워크플로우 작성

워크플로우 예제

  • Github에 PR 또는 Push 시 자동으로 “Hello, Actions!” 메시지 출력하는 워크플로우 파일 만들기
  • .github/workflows/hello.yml
name: Hello Actions

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  greet:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repo
        uses: actions/checkout@v3
      - name: Say hello
        run: echo "Hello, Actions!"
  • Github 저장소에 Push하기
git status
git add .
git commit -m "Add hello actions workflow"
git push origin main

실행결과 - Hello Action


2-2. 매트릭스 빌드 및 캐싱

워크플로우 예제

  • 여러 Node.js 버전·OS 환경에서 병렬 빌드
  • 의존성 캐싱으로 속도 개선
  • 25/04/28) GitHub의 macOS ARM64 머신에서 Node.js 14 설치가 안 되는 오류가 발생해 코드를 약간 수정했음
name: Matrix CI

on: [push]

jobs:
  matrix-build:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false   # 실패해도 다른 조합은 계속 실행
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node: [14, 16, 18]
        exclude:
          - os: macos-latest
            node: 14     # macOS + Node.js 14 조합만 제외!

    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node }}

      - name: Cache node_modules
        uses: actions/cache@v3
        with:
          path: node_modules
          key: ${{ runner.os }}-node-${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }}

      - name: Install deps & test
        run: |
          npm install
          npm test

 

  • Github 저장소에 Push하기
git status
git add .
git commit -m "Add hello actions workflow"
git push origin main

실행결과 - Matrix 빌드 및 캐싱


2-3. Secret 관리 및 배포

워크플로우 예제

  • GitHub Secret을 이용해 AWS S3에 빌드 파일 업로드
name: Deploy to S3

on:
  push:
    branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Install AWS CLI
        run: |
          curl "<https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip>" -o awscliv2.zip
          unzip awscliv2.zip
          sudo ./aws/install

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2

      - name: Sync to S3
        run: aws s3 sync ./dist s3://your-bucket-name --delete

3. 리눅스 실습 - Docker 볼륨

  • Docker 볼륨과 바인드 마운트를 활용한 데이터 공유
    1. Named Volume, Bind Mount, Data-only 컨테이너, tmpfs 마운트를 직접 만들어 보고
    2. 각 방식의 특징·장단점을 체험하고 비교
    3. 간단한 백업·복원, 퍼미션 문제 해결까지 실습

3-1. 실습 환경 설정

  • 워크디렉토리 생성
mkdir ~/docker-data-share && cd ~/docker-data-share
  • 확인: Docker가 정상 동작하는지 확인
docker run --rm hello-world

3-2. Named Volume 으로 컨테이너 간 데이터 공유

  • 볼륨 생성
docker volume create shared_vol
  • 데이터 쓰기 컨테이너 실행
    • 3초마다 타임스탬프를 /data/timestamp.txt에 기록
docker run -d --name writer \\
  -v shared_vol:/data \\
  alpine sh -c "while true; do date +%T >> /data/timestamp.txt; sleep 3; done"
  • 읽기 컨테이너 실행
docker run --rm -it --name reader \\
  -v shared_vol:/data \\
  alpine sh -c "tail -n 10 /data/timestamp.txt"
  • 검증: reader에 출력된 시간이 3초 간격으로 누적되어 있으면 성공

3-3. Host Bind-Mount 로 호스트 ↔ 컨테이너 데이터 공유

  • 호스트 디렉토리 준비
mkdir host_data
echo "Host says hi" > host_data/greeting.txt
  • 컨테이너에서 읽기
docker run --rm -it \\
  -v "$(pwd)/host_data:/mnt/data" \\
  alpine sh -c "cat /mnt/data/greeting.txt"
  • 컨테이너에서 쓰기 후 호스트 확인
docker run --rm \\
  -v "$(pwd)/host_data:/mnt/data" \\
  alpine sh -c "echo 'at $(date +"%p %I:%M")' >> /mnt/data/greeting.txt"
  • 호스트 터미널에서 cat host_data/greeting.txt 실행해 결과 확인

3-4. 실습 정리 및 질문

  • Named Volume과 Bind Mount의 주요 차이점은?
  • 실제 운영 환경에서 볼륨 백업·복원 전략을 어떻게 설계할 것인가?

(심화) 응용 실습

👉혹시나 도커라이징된 DB를 VM 위에 직접 띄우게 된다면 아래와 같은 작업을 하게 됩니다

  1. 블록스토리지를 추가하여 VM이 꺼져도 스토리지는 안 꺼지게 하고.
  2. Docker 컨테이너 볼륨과 연결해보기.
  3. VM이 종료되어도 데이터가 남아있는지 확인해보기.

4. 리눅스 실습 - 과제: 블루그린 배포전략 구현하기 (테라폼/깃헙액션)

👉 Terraform과 Github Action을 활용하여 블루그린 배포전략을 구현하는 과제입니다.
Kakaocloud와 Terraform 연결은 가이드가 충분하지 않아 기본 세팅을 연동해두었습니다.
(VM 1대 띄우는 예시 코드)
kakaocloud-terraform.tar.gz

 

블루그린(Blue-Green) 배포전략 구현하기

  1. Terraform을 이용해 KakaoCloud 상에 Blue/Green 환경(VM 그룹, 로드밸런서 등)을 코드로 프로비저닝
  2. GitHub Actions 워크플로우로 Green (혹은 Blue) 환경에 새 버전 애플리케이션을 빌드·배포하고, 성공 시 트래픽을 전환
  3. 헬스체크 실패 시 자동 롤백 또는 알림 기능 구현

테라폼 설치

https://developer.hashicorp.com/terraform

 

Terraform | HashiCorp Developer

Explore Terraform product documentation, tutorials, and examples.

developer.hashicorp.com

 

로드맵

1. 웹서버 준비 Python FastAPI로 아주 간단한 /hello 서버 만들기

  • /hello를 호출하면 배포된 시각을 보여주는 간단한 Python FastAPI 앱 제작

1-1. 기본 패키지 설치

sudo apt update
sudo apt install -y python3-pip python3-venv

 

1-2. 앱 디렉토리 만들기

mkdir -p ~/app
cd ~/app
nano main.py   # 아래 코드 복붙
# app/main.py

from fastapi import FastAPI
import datetime
import os

app = FastAPI()

# 서버 시작 시, 배포된 시간 저장
DEPLOYED_AT = None

def load_deploy_time():
    global DEPLOYED_AT
    if os.path.exists("deploy_time.txt"):
        with open("deploy_time.txt", "r") as f:
            DEPLOYED_AT = f.read()
    else:
        now = datetime.datetime.utcnow().isoformat()
        DEPLOYED_AT = now
        with open("deploy_time.txt", "w") as f:
            f.write(now)

load_deploy_time()

@app.get("/hello")
def hello():
    return {"deployed_at": DEPLOYED_AT}

 

1-3. 가상환경 활성화

# 가상환경 (venv) 생성
python3 -m venv venv

# 가상환경 활성화
source venv/bin/activate

# FastAPI + Uvicorn 설치
pip install fastapi uvicorn

 

1-4. FasAPI 서버 실행

uvicorn main:app --host 0.0.0.0 --port 8000 --reload

 

1-5. 브라우저에서 접속하여 블루/그린 환경 테스트

http://<퍼블릭 IP>:8000/hello


2. FastAPI 앱을 GitHub에 올리기 (Green 환경)

2-1. requirements.txt 생성

# (venv) ubuntu@서버:~/app$ 가상환경에서 진행
pip freeze > requirements.txt

현재 venv 안에 설치된 패키지 목록을 requirements.txt로 뽑아냄.

 

2-2. FastAPI 앱을 GitHub에 올리기

외부환경에서 GitHub에 접근시 비밀번호는 Token을 사용한다.

Fine-grained 토큰보다는 Classic Token을 사용하길 바란다.

# (venv) ubuntu@서버:~/app$ 가상환경에서 진행
# <초기 실행시>
# git config --global user.email "you@example.com"
# git config --global user.name "Your Name"

git init
git remote add origin https://github.com/<Github 계정>/fastapi-bluegreen-demo.git
git add .
git commit -m "Initial commit"
git branch -M main
git push -u origin main

 

2. GitHub Actions 워크플로우 만들기

your-project/
├── app/
├── main.py
├── requirements.txt
└── .github/
    └── workflows/
        └── deploy.yml  ← GitHub Actions 워크플로우 정의용

 

2-1. deploy.yml 파일 작성 및 push

더보기
name: Deploy to Green Server

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Repository
        uses: actions/checkout@v3

      - name: Set up SSH Key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.GREEN_SERVER_SSH_KEY }}" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa

      - name: SSH into Green Server and deploy
        run: |
          ssh -o StrictHostKeyChecking=no ubuntu@${{ secrets.GREEN_SERVER_IP }} << 'EOF'
            cd ~/app
            git pull origin main
            source venv/bin/activate
            nohup uvicorn main:app --host 0.0.0.0 --port 8000 > server.log 2>&1 &
          EOF
git add .github/
git commit -m "Github Action Workflow 1st Commit"
git push origin main

 

2-2. Github에 Green서버 SSH Private Key와 서버IP 등록하기

  • 저장 위치: GitHub → Settings → Secrets and variables → Actions → New repository secret
  • 1. Secret Name: GREEN_SERVER_SSH_KEY
  • 로컬 PC에 있는 ~/.ssh/your-key.pem 파일 내용 (개인키)을 붙여넣기
  • 2. Secret Name: GREEN_SERVER_IP
  • 서버 퍼블릭 IP 등록

 

3. GitHub Actions 워크플로우 배포 & 실행 테스트

3-1. 배포

이제 해당 GitHub에 배포를 하면 Action이 자동으로 실행될 것이다.

 

Green 서버 웹 브라우저 접속 확인
URL 예시: http://210.109.82.188:8000/hello
이전에 만든 FastAPI 앱이 /hello에서 배포 시각을 출력하도록 되어 있다면, 방금 워크플로우 실행 시각과 비교해서 최신으로 바뀌었는지 확인하세요.

  • 예시 응답:
{"deployed_at":"2025-04-28T10:50:32.892195"}

 

추가) Green 서버 내부 상태 확인 (선택)
직접 SSH 접속해서도 확인할 수 있습니다.

ssh ubuntu@210.109.82.188
ps aux | grep uvicorn
cat ~/app/server.log

# 서버를 강제종료할 필요가 있을 경우
# pkill -f "uvicorn main:app" || true

3. 웹 서버 블루/그린 모듈화 ~ 로드밸런서 구현

  • 블루/그린 환경 모듈화하는 코드를 추가 작성
  • 로드밸런서를 생성하는 코드를 추가 작성

4. 최종 테스트: 인스턴스 및 보안그룹 생성 ~ 로드밸런서 구성

1. Terraform Init-Plan-Apply를 통해 인스턴스와 보안그룹을 생성한다.

2. GitHub Action을 통해 언제 배포되었는지 알려주는 앱을 기동한다.

3. 로드 밸런싱을 통해 블루/그린 배포를 진행할 수 있게 된다.


본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 (B-log) 리뷰로 작성 되었습니다.