Skip to main content

서버 인프라 구축 방법 비교 - 호스팅 플랫폼, Serverless, 클라우드 인프라

오늘은 작성된 코드를 서버에 올리는 다양한 서버 인프라 구축 방법 비교해보고 다양한 사례와 함께 Terraform으로 작성된 IaC 코드와 간단한 FastAPI 기반 Todo 앱 예제를 통해 서버를 구축 해보려고 합니다. 서버 인프라 구축 방법인 호스팅 플랫폼(Vercel, Netlify 등), 서버리스(AWS Lambda 등), 클라우드 (AWS) 방식을 비교 하고 세 가지 다른 서버 인프라 구축 사례를 소개하고 각 접근 방식의 구현을 설명하기 위해 간단한 Todo 앱 예제를 사용해 보겠습니다.

1. 호스팅 플랫폼 (Vercel, Netlify 등) 으로 서버 구축 하기

Vercel과 Netlify는 정적 웹사이트와 서버리스 함수를 배포하기 쉽게 해주는 인기있는 호스팅 플랫폼입니다. Vercel과 Netlify에 대한 설명은 생략 하고 바로 Vercel에 배포 하는 예제를 간단하게 설명하겠습니다.

예시: Vercel에서 FastAPI 앱 배포하기

먼저 필요한 패키지를 설치합니다:

pip install fastapi uvicorn

app.py 파일을 생성하고 다음 코드를 입력합니다:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
return {"Hello": "World"}

Vercel에 배포하려면, vercel.json 파일을 생성합니다:

{
"version": 2,
"builds": [{ "src": "app.py", "use": "@vercel/python" }],
"routes": [{ "src": "/(.*)", "dest": "app.py" }]
}

이제 코드를 Git 저장소에 푸시하고 Vercel과 연결합니다. 플랫폼은 FastAPI 앱을 자동으로 배포합니다.

2. AWS Lambda를 이용 하여 서버리스(Serverless)로 서버 구축 하기

서버리스 아키텍처를 사용하면 서버 관리에 대한 걱정 없이 애플리케이션을 구축하고 실행할 수 있습니다. AWS Lambda는 인기있는 서버리스 컴퓨팅 서비스이며, Terraform은 클라우드 리소스를 효율적으로 관리하는 데 도움이 되는 Infrastructure as Code(IaC) 도구입니다.

예시: AWS Lambda와 Terraform을 사용하여 FastAPI Todo 앱 배포하기

먼저 FastAPI Todo 앱(lambda_function.py)을 생성합니다:

import json
from fastapi import FastAPI
from mangum import Mangum

app = FastAPI()

todos = []

@app.get("/")
def read_root():
return {"Hello": "World"}

@app.get("/todos")
def get_todos():
return todos

@app.post("/todos")
def create_todo(todo: str):
todos.append(todo)
return {"result": "Todo added"}

handler = Mangum(app)

다음으로 Terraform을 설정합니다. AWS CLI를 설치하고 AWS 자격 증명을 구성합니다. 그런 다음 main.tf 파일을 생성합니다:

provider "aws" {
region = "us-west-2"
}

resource "aws_iam_role" "lambda_role" {
name = "lambda_role"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}

resource "aws_lambda_function" "todo_app" {
function_name = "todo_app"
handler = "main.handler"
runtime = "python3.8"
role = aws_iam_role.lambda_role.arn
filename = "function.zip"

environment {
variables = {
APP_MODULE = "lambda_function:handler"
}
}
}

resource "aws_api_gateway_rest_api" "api" {
name = "todo-api"
}

resource "aws_api_gateway_resource" "proxy" {
rest_api_id = aws_api_gateway_rest_api.api.id
parent_id = aws_api_gateway_rest_api.api.root_resource_id
path_part = "{proxy+}"
}

resource "aws_api_gateway_method" "proxy" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = "ANY"
authorization = "NONE"
}

resource "aws_api_gateway_integration" "proxy" {
rest_api_id = aws_api_gateway_rest_api.api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = aws_api_gateway_method.proxy.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.todo_app.invoke_arn
}

resource "aws_lambda_permission" "proxy" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.todo_app.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.api.execution_arn}/*/*"
}

이제 terraform init 및 terraform apply 명령을 실행하여 인프라를 생성하고 배포합니다.

3. AWS 클라우드 환경에 서버 직접 배포 하기

AWS 클라우드 환경에서 서버를 직접 배포하려면 다양한 AWS 서비스를 사용하여 인프라를 구성해야 합니다. 이를 위해 EC2, Auto Scaling, ALB(Application Load Balancer), Route 53, CI/CD(CodePipeline 및 CodeDeploy) 등의 서비스를 활용할 수 있습니다. 다음은 간단한 Terraform으로 이러한 서비스를 사용하여 인프라를 구성하는 방법을 설명합니다.

다음은 간단한 예시로서, 이러한 서비스를 사용하여 인프라를 구성하는 방법을 설명합니다.

  • Amazon EC2: 인스턴스를 실행할 Amazon Machine Image(AMI)를 선택하고, 인스턴스 유형 및 세부 정보를 지정하여 EC2 인스턴스를 생성합니다. 이 때, 보안 그룹 및 키 페어를 설정하여 인스턴스에 접속할 수 있도록 합니다.

  • Amazon RDS (Optional): 데이터베이스를 사용하는 경우, Amazon RDS를 사용하여 관리형 데이터베이스 인스턴스를 생성합니다. 이 때, 보안 그룹을 설정하여 EC2 인스턴스와 통신할 수 있도록 합니다.

  • Auto Scaling: 웹 서버를 자동으로 확장 및 축소하려면 EC2 Auto Scaling 그룹을 설정합니다. 이를 위해 Launch Configuration 또는 Launch Template를 생성하고, Auto Scaling 그룹을 정의합니다.

  • Application Load Balancer: 인스턴스 앞에 ALB를 생성하고, 리스너 및 대상 그룹을 설정하여 트래픽을 분산합니다. 이 때, Auto Scaling 그룹의 인스턴스를 대상 그룹에 등록합니다.

  • Route 53: 사용자 정의 도메인을 사용하려면 Route 53 호스팅 영역을 생성하고, 도메인 레코드를 ALB와 연결합니다.

  • CI/CD: 코드 변경 사항을 자동으로 배포하려면 AWS CodePipeline 및 AWS CodeDeploy를 사용하여 CI/CD 파이프라인을 설정합니다. 코드 저장소(GitHub, Bitbucket 등)를 연결하고, 빌드 및 배포 단계를 정의합니다.

다음은 위의 인프라를 구성하기 위한 Terraform 코드 예시 입니다. 이 예제에서는 EC2, Auto Scaling, ALB, Route 53 및 CI/CD (CodePipeline 및 CodeDeploy)를 사용하여 간단한 웹 서버 인프라를 설정합니다.

다음은 Terraform 설정 파일 main.tf의 예시입니다:

provider "aws" {
region = "us-west-2"
}

locals {
app_name = "my-web-app"
}

# VPC 생성
resource "aws_vpc" "this" {
cidr_block = "10.0.0.0/16"

tags = {
Name = local.app_name
}
}

# Subnet 생성
resource "aws_subnet" "this" {
vpc_id = aws_vpc.this.id
cidr_block = "10.0.1.0/24"

tags = {
Name = "${local.app_name}-subnet"
}
}

# Security Group 생성
resource "aws_security_group" "this" {
name = "${local.app_name}-sg"
description = "Allow inbound traffic"
vpc_id = aws_vpc.this.id
}

# ALB 생성
resource "aws_lb" "this" {
name = "${local.app_name}-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.this.id]
subnets = [aws_subnet.this.id]
}

# ALB Listener 생성
resource "aws_lb_listener" "this" {
load_balancer_arn = aws_lb.this.arn
port = 80
protocol = "HTTP"

default_action {
type = "forward"
target_group_arn = aws_lb_target_group.this.arn
}
}

# ALB Target Group 생성
resource "aws_lb_target_group" "this" {
name = "${local.app_name}-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.this.id
}

# EC2 Launch Template 생성
resource "aws_launch_template" "this" {
name_prefix = "${local.app_name}-lt"
image_id = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 AMI
instance_type = "t2.micro"

# 사용자 데이터 설정 (예: 웹 서버 설치 및 구성)
user_data = <<-EOF
#!/bin/bash
sudo yum update -y
sudo yum install -y httpd
sudo systemctl start httpd
sudo systemctl enable httpd
echo "Hello, World!" > /var/www/html/index.html
EOF

lifecycle {
create_before_destroy = true
}
}

# Auto Scaling Group 생성
resource "aws_autoscaling_group" "this" {
name_prefix = "${local.app_name}-asg"
launch_template {
id = aws_launch_template.this.id
version = "$Latest"
}
min_size = 1
max_size = 3

vpc_zone_identifier = [aws_subnet.this.id]

target_group_arns = [aws_lb_target_group.this.arn]

lifecycle {
create_before_destroy = true
}
}

# Route 53 생성 (예: 사용자 정의 도메인)
resource "aws_route53_zone" "this" {
name = "example.com"
}

# ALB에 연결된 Route 53 레코드 생성
resource "aws_route53_record" "this" {
zone_id = aws_route53_zone.this.id
name = "app.example.com"
type = "A"

alias {
name = aws_lb.this.dns_name
zone_id = aws_lb.this.zone_id
evaluate_target_health = false
}
}

# CodeDeploy용 IAM 롤 생성
resource "aws_iam_role" "codedeploy_role" {
name = "CodeDeployRole"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "codedeploy.amazonaws.com"
}
}
]
})
}

# CodeDeploy 애플리케이션 생성
resource "aws_codedeploy_app" "this" {
name = local.app_name
}

# CodeDeploy 배포 그룹 생성
resource "aws_codedeploy_deployment_group" "this" {
app_name = aws_codedeploy_app.this.name
deployment_group_name = "${local.app_name}-dg"
service_role_arn = aws_iam_role.codedeploy_role.arn

autoscaling_groups = [aws_autoscaling_group.this.name]

deployment_style {
deployment_option = "WITHOUT_TRAFFIC_CONTROL"
deployment_type = "IN_PLACE"
}
}

# CodePipeline용 IAM 롤 생성
resource "aws_iam_role" "codepipeline_role" {
name = "CodePipelineRole"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "codepipeline.amazonaws.com"
}
}
]
})
}

# CodePipeline 생성
resource "aws_codepipeline" "this" {
name = "${local.app_name}-pipeline"
role_arn = aws_iam_role.codepipeline_role.arn

artifact_store {
location = aws_s3_bucket.artifact_store.bucket
type = "S3"
}

stage {
name = "Source"

action {
name = "Source"
category = "Source"
owner = "ThirdParty"
provider = "GitHub"
version = "1"
output_artifacts = ["source_output"]

configuration = {
Owner = "your_github_username"
Repo = "your_repository_name"
Branch = "main"
OAuthToken = "your_github_oauth_token"
PollForSourceChanges = false
}
}
}

stage {
name = "Deploy"

action {
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "CodeDeploy"
input_artifacts = ["source_output"]
version = "1"

configuration = {
ApplicationName = aws_codedeploy_app.this.name
DeploymentGroupName = aws_codedeploy_deployment_group.this.deployment_group_name
}
}
}
}

# S3 버킷 생성 (CodePipeline 아티팩트 저장소)
resource "aws_s3_bucket" "artifact_store" {
bucket_prefix = "${local.app_name}-artifact-store-"
force_destroy = true
}

# CodePipeline용 S3 버킷 정책 생성
resource "aws_s3_bucket_policy" "artifact_store" {
bucket = aws_s3_bucket.artifact_store.id

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:GetBucketVersioning",
"s3:PutObject"
]
Effect = "Allow"
Resource = [
"${aws_s3_bucket.artifact_store.arn}/*",
aws_s3_bucket.artifact_store.arn
]
Principal = {
AWS = aws_iam_role.codepipeline_role.arn
}
}
]
})
}
# 필요한 경우 추가 리소스 및 설정을 작성합니다.

위 설정값들 외에도 프로젝트 요구 사항에 따라 보안 그룹 설정, 사용자 데이터 스크립트, Auto Scaling 그룹의 min_size 및 max_size 등을 적절히 수정해야 합니다.

3.3 Terraform 구성 파일 값 변경

main.tf Terraform 구성 파일에서 교체해야 할 항목 목록은 다음과 같습니다:

실제 프로젝트에 적용하기 전에 변경해야 할 설정값들을 확인해보겠습니다. 이 설정값들은 각자의 프로젝트와 환경에 맞게 적절히 수정해야 합니다.

  • AWS 리전: 프로젝트에 맞는 AWS 리전을 설정합니다.
provider "aws" {
region = "us-west-2" # 변경해야 할 리전 코드로 수정
}
  • VPC CIDR 블록, 서브넷 CIDR 블록: 프로젝트에 맞는 IP 범위를 설정합니다.
resource "aws_vpc" "this" {
cidr_block = "10.0.0.0/16" # 변경해야 할 CIDR 블록으로 수정
}

resource "aws_subnet" "this" {
cidr_block = "10.0.1.0/24" # 변경해야 할 CIDR 블록으로 수정
}
  • EC2 인스턴스 유형, AMI: 원하는 인스턴스 유형 및 AMI로 설정합니다.
resource "aws_launch_template" "this" {
image_id = "ami-0c55b159cbfafe1f0" # 변경해야 할 AMI ID로 수정
instance_type = "t2.micro" # 변경해야 할 인스턴스 유형으로 수정
}
  • 사용자 정의 도메인: 원하는 도메인으로 설정합니다.
resource "aws_route53_zone" "this" {
name = "example.com" # 변경해야 할 도메인으로 수정
}

resource "aws_route53_record" "this" {
name = "app.example.com" # 변경해야 할 서브 도메인으로 수정
}
  • GitHub 저장소 설정: 본인의 GitHub 계정 정보 및 저장소 이름, 브랜치 이름 등을 설정합니다.
action {
configuration = {
Owner = "your_github_username" # 변경해야 할 GitHub 사용자 이름으로 수정
Repo = "your_repository_name" # 변경해야 할 GitHub 저장소 이름으로 수정
Branch = "main" # 변경해야 할 브랜치 이름으로 수정
OAuthToken = "your_github_oauth_token" # 변경해야 할 GitHub OAuth 토큰으로 수정
}
}

Terraform 구성을 적용하기 전에 위의 항목을 반드시 자신의 값으로 교체해야 합니다.

3.4 보안이 필요한 값들은 환경변수로 지정 하기

OAuthToken을 Terraform 설정에 직접 넣는 대신 환경 변수를 설정하고 Terraform 코드에서 사용할 수 있습니다. 다음과 같이 진행 합니다.

  • GITHUB_OAUTH_TOKEN이라는 환경 변수를 설정하고 GitHub OAuth 토큰 값을 입력합니다.
export GITHUB_OAUTH_TOKEN=your_github_oauth_token
  • Terraform 코드를 수정하여 환경 변수를 사용하도록 설정합니다.
action {
configuration = {
Owner = "your_github_username"
Repo = "your_repository_name"
Branch = "main"
OAuthToken = var.github_oauth_token
PollForSourceChanges = false
}
}
  • 환경 변수를 사용할 수 있도록 Terraform 코드에 변수를 선언합니다.
variable "github_oauth_token" {
description = "GitHub OAuth 토큰"
default = ""
}

이제 Terraform 명령어를 실행할 때 GITHUB_OAUTH_TOKEN 환경 변수의 값이 github_oauth_token 변수로 사용됩니다.

OAuth 토큰이나 액세스 키와 같은 민감한 데이터를 버전 관리 시스템에 커밋하지 않도록 주의해야 합니다. 환경 변수를 사용하는 것이 IaC에서 민감한 데이터를 관리하는 더 안전한 방법입니다.

Terraform 구성을 적용하기 전에 위의 항목을 체크 하셔서 보안이 필요한 민감한 데이터는 반드시 코드에 들어가지 않도록 해야 합니다.

3.5 적용 하기

수정 후 terraform init, terraform plan, 그리고 terraform apply 명령어를 실행하여 변경사항을 적용할 수 있습니다.

4. 결론

지금 까지 서버 인프라 구축의 다양한 사례를 살펴보았습니다. 호스팅 플랫폼(Vercel 및 Netlify)은 프론트엔드 중심의 애플리케이션에 적합하며, 서버리스는 서버 관리에 대한 걱정 없이 확장 가능한 애플리케이션을 구축하는 데 이상적입니다. 마지막으로, AWS 클라우드에 서버를 직접 구축 하는 방식은 클라우드 리소스 관리를 자동화하고 대규모 애플리케이션을 구축하는 데 도움이 됩니다. 개발팀 리소스 현황이나 프로젝트 요구 사항을 고려 하여 적합한 서버 인프라 구성을 선택 하시는데 도움이 되셨으면 좋겠습니다.

본 포스팅은 ChatGPT의 도움을 받아 작성 되었으며 개인적인 개발 경험을 바탕으로 작성되었습니다.