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의 도움을 받아 작성 되었으며 개인적인 개발 경험을 바탕으로 작성되었습니다.