Blue/Green 배포 (3): 클라우드 & CI/CD 통합
AWS, GCP, Azure에서의 Blue/Green 구현과 GitHub Actions, GitLab CI 자동화 파이프라인
2026년 1월 27일·11 min read·
infra
deployment
blue-green
aws
gcp
azure
github-actions
gitlab-ci
cicd
이 글은 Blue/Green 배포 시리즈의 세 번째 글입니다.
- 기초와 전략
- Kubernetes 구현
- 클라우드 & CI/CD 통합 (현재 글)
- 운영 및 최적화
1. AWS에서의 Blue/Green 배포
AWS는 여러 서비스를 통해 Blue/Green 배포를 지원합니다.
1.1 ALB + Target Group 아키텍처
1.2 Terraform으로 인프라 구성
# alb.tf
resource "aws_lb" "main" {
name = "my-app-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = var.public_subnets
tags = {
Name = "my-app-alb"
}
}
# Blue Target Group
resource "aws_lb_target_group" "blue" {
name = "my-app-blue"
port = 80
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/health"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 3
}
tags = {
Name = "my-app-blue"
Color = "blue"
}
}
# Green Target Group
resource "aws_lb_target_group" "green" {
name = "my-app-green"
port = 80
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
health_check {
enabled = true
healthy_threshold = 2
interval = 30
matcher = "200"
path = "/health"
port = "traffic-port"
protocol = "HTTP"
timeout = 5
unhealthy_threshold = 3
}
tags = {
Name = "my-app-green"
Color = "green"
}
}
# Listener with weighted routing
resource "aws_lb_listener" "main" {
load_balancer_arn = aws_lb.main.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = var.certificate_arn
default_action {
type = "forward"
forward {
target_group {
arn = aws_lb_target_group.blue.arn
weight = 100
}
target_group {
arn = aws_lb_target_group.green.arn
weight = 0
}
}
}
}
1.3 AWS CLI로 트래픽 전환
#!/bin/bash
# aws-switch-traffic.sh
ALB_LISTENER_ARN="arn:aws:elasticloadbalancing:..."
BLUE_TG_ARN="arn:aws:elasticloadbalancing:...blue"
GREEN_TG_ARN="arn:aws:elasticloadbalancing:...green"
TARGET_COLOR=${1:-green}
if [ "$TARGET_COLOR" == "green" ]; then
BLUE_WEIGHT=0
GREEN_WEIGHT=100
else
BLUE_WEIGHT=100
GREEN_WEIGHT=0
fi
echo "Switching traffic: Blue=$BLUE_WEIGHT%, Green=$GREEN_WEIGHT%"
aws elbv2 modify-listener --listener-arn $ALB_LISTENER_ARN \
--default-actions '[
{
"Type": "forward",
"ForwardConfig": {
"TargetGroups": [
{"TargetGroupArn": "'$BLUE_TG_ARN'", "Weight": '$BLUE_WEIGHT'},
{"TargetGroupArn": "'$GREEN_TG_ARN'", "Weight": '$GREEN_WEIGHT'}
]
}
}
]'
echo "Traffic switch completed!"
1.4 AWS CodeDeploy (ECS Blue/Green)
# appspec.yml (ECS)
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "arn:aws:ecs:region:account:task-definition/my-app:2"
LoadBalancerInfo:
ContainerName: "my-app"
ContainerPort: 8080
PlatformVersion: "LATEST"
Hooks:
- BeforeInstall: "LambdaFunctionToValidateBeforeInstall"
- AfterInstall: "LambdaFunctionToValidateAfterInstall"
- AfterAllowTestTraffic: "LambdaFunctionToValidateAfterTestTraffic"
- BeforeAllowTraffic: "LambdaFunctionToValidateBeforeTraffic"
- AfterAllowTraffic: "LambdaFunctionToValidateAfterTraffic"
2. GCP에서의 Blue/Green 배포
2.1 Cloud Load Balancing 아키텍처
2.2 Cloud Run (Serverless Blue/Green)
# Cloud Run에서 트래픽 분할
# 새 버전 배포 (트래픽 없이)
gcloud run deploy my-app \
--image gcr.io/project/my-app:v1.1.0 \
--region us-central1 \
--no-traffic \
--tag green
# 트래픽 전환 (100% Green)
gcloud run services update-traffic my-app \
--region us-central1 \
--to-tags green=100
# 롤백 (100% Blue)
gcloud run services update-traffic my-app \
--region us-central1 \
--to-latest
2.3 GKE + Istio
# gcp-traffic-split.yaml
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: my-app-cert
spec:
domains:
- my-app.example.com
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
annotations:
kubernetes.io/ingress.class: "gce"
networking.gke.io/managed-certificates: "my-app-cert"
spec:
rules:
- host: my-app.example.com
http:
paths:
- path: /*
pathType: ImplementationSpecific
backend:
service:
name: my-app-active
port:
number: 80
3. Azure에서의 Blue/Green 배포
3.1 Application Gateway + Deployment Slots
3.2 App Service Deployment Slots
# Staging 슬롯에 배포
az webapp deployment source config-zip \
--resource-group my-rg \
--name my-app \
--slot staging \
--src app.zip
# 슬롯 스왑 (Blue/Green 전환)
az webapp deployment slot swap \
--resource-group my-rg \
--name my-app \
--slot staging \
--target-slot production
# 롤백 (다시 스왑)
az webapp deployment slot swap \
--resource-group my-rg \
--name my-app \
--slot staging \
--target-slot production
3.3 Azure Traffic Manager
// traffic-manager.json (ARM Template)
{
"type": "Microsoft.Network/trafficManagerProfiles",
"apiVersion": "2018-08-01",
"name": "my-app-tm",
"location": "global",
"properties": {
"trafficRoutingMethod": "Weighted",
"endpoints": [
{
"name": "blue-endpoint",
"type": "Microsoft.Network/trafficManagerProfiles/externalEndpoints",
"properties": {
"target": "my-app-blue.azurewebsites.net",
"endpointStatus": "Enabled",
"weight": 100
}
},
{
"name": "green-endpoint",
"type": "Microsoft.Network/trafficManagerProfiles/externalEndpoints",
"properties": {
"target": "my-app-green.azurewebsites.net",
"endpointStatus": "Enabled",
"weight": 0
}
}
]
}
}
4. GitHub Actions 자동화
4.1 완전 자동화 파이프라인
# .github/workflows/blue-green-deploy.yml
name: Blue/Green Deployment
on:
push:
branches: [main]
workflow_dispatch:
inputs:
action:
description: 'Action to perform'
required: true
default: 'deploy'
type: choice
options:
- deploy
- promote
- rollback
env:
AWS_REGION: us-east-1
ECR_REPOSITORY: my-app
ECS_CLUSTER: my-cluster
ECS_SERVICE: my-app-service
jobs:
build:
runs-on: ubuntu-latest
outputs:
image: ${{ steps.build-image.outputs.image }}
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push image
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
deploy-green:
needs: build
runs-on: ubuntu-latest
if: github.event.inputs.action != 'rollback'
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Deploy to Green environment
run: |
# Update task definition with new image
NEW_TASK_DEF=$(aws ecs describe-task-definition \
--task-definition my-app-green \
--query 'taskDefinition' | \
jq --arg IMAGE "${{ needs.build.outputs.image }}" \
'.containerDefinitions[0].image = $IMAGE | del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)')
# Register new task definition
NEW_TASK_ARN=$(aws ecs register-task-definition \
--cli-input-json "$NEW_TASK_DEF" \
--query 'taskDefinition.taskDefinitionArn' \
--output text)
# Update Green service
aws ecs update-service \
--cluster $ECS_CLUSTER \
--service my-app-green \
--task-definition $NEW_TASK_ARN
- name: Wait for Green deployment
run: |
aws ecs wait services-stable \
--cluster $ECS_CLUSTER \
--services my-app-green
test-green:
needs: deploy-green
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run smoke tests on Green
run: |
GREEN_URL="${{ secrets.GREEN_INTERNAL_URL }}"
# Health check
for i in {1..10}; do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" $GREEN_URL/health)
if [ "$STATUS" == "200" ]; then
echo "Health check passed"
break
fi
echo "Attempt $i: Status $STATUS, retrying..."
sleep 5
done
# API tests
./scripts/smoke-tests.sh $GREEN_URL
promote:
needs: test-green
runs-on: ubuntu-latest
if: github.event.inputs.action != 'rollback'
environment: production
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Switch traffic to Green
run: |
aws elbv2 modify-listener --listener-arn ${{ secrets.ALB_LISTENER_ARN }} \
--default-actions '[
{
"Type": "forward",
"ForwardConfig": {
"TargetGroups": [
{"TargetGroupArn": "${{ secrets.BLUE_TG_ARN }}", "Weight": 0},
{"TargetGroupArn": "${{ secrets.GREEN_TG_ARN }}", "Weight": 100}
]
}
}
]'
- name: Notify Slack
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": "Deployed ${{ github.sha }} to production",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Blue/Green Deployment Complete*\nCommit: `${{ github.sha }}`\nStatus: :white_check_mark: Success"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
rollback:
runs-on: ubuntu-latest
if: github.event.inputs.action == 'rollback'
environment: production
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Rollback to Blue
run: |
aws elbv2 modify-listener --listener-arn ${{ secrets.ALB_LISTENER_ARN }} \
--default-actions '[
{
"Type": "forward",
"ForwardConfig": {
"TargetGroups": [
{"TargetGroupArn": "${{ secrets.BLUE_TG_ARN }}", "Weight": 100},
{"TargetGroupArn": "${{ secrets.GREEN_TG_ARN }}", "Weight": 0}
]
}
}
]'
- name: Notify Slack (Rollback)
uses: slackapi/slack-github-action@v1
with:
payload: |
{
"text": ":warning: Rollback executed",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Rollback Executed*\nTraffic switched back to Blue environment"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
4.2 Kubernetes + Argo Rollouts 파이프라인
# .github/workflows/k8s-blue-green.yml
name: K8s Blue/Green Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push image
uses: docker/build-push-action@v5
with:
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:latest
- name: Setup kubectl
uses: azure/setup-kubectl@v3
- name: Configure kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
- name: Update Rollout image
run: |
kubectl argo rollouts set image my-app \
app=ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Wait for rollout
run: |
kubectl argo rollouts status my-app --timeout=10m
- name: Auto-promote (if tests pass)
run: |
# Wait for analysis to complete
sleep 60
# Check rollout status
STATUS=$(kubectl argo rollouts status my-app -o json | jq -r '.status')
if [ "$STATUS" == "Paused" ]; then
kubectl argo rollouts promote my-app
fi
5. GitLab CI/CD 통합
5.1 GitLab CI 파이프라인
# .gitlab-ci.yml
stages:
- build
- deploy-green
- test
- promote
- cleanup
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
KUBE_CONTEXT: production
build:
stage: build
image: docker:24
services:
- docker:24-dind
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
deploy-green:
stage: deploy-green
image: bitnami/kubectl:latest
script:
- kubectl config use-context $KUBE_CONTEXT
- |
kubectl set image deployment/my-app-green \
app=$DOCKER_IMAGE
- kubectl rollout status deployment/my-app-green --timeout=300s
environment:
name: green
url: https://green.my-app.example.com
test-green:
stage: test
image: curlimages/curl:latest
script:
- |
for i in $(seq 1 10); do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://green.my-app.example.com/health)
if [ "$STATUS" = "200" ]; then
echo "Health check passed"
exit 0
fi
echo "Attempt $i failed with status $STATUS"
sleep 5
done
exit 1
needs:
- deploy-green
promote:
stage: promote
image: bitnami/kubectl:latest
script:
- kubectl config use-context $KUBE_CONTEXT
- |
kubectl patch service my-app -p '{"spec":{"selector":{"color":"green"}}}'
- echo "Traffic switched to Green"
environment:
name: production
url: https://my-app.example.com
when: manual
needs:
- test-green
rollback:
stage: promote
image: bitnami/kubectl:latest
script:
- kubectl config use-context $KUBE_CONTEXT
- |
kubectl patch service my-app -p '{"spec":{"selector":{"color":"blue"}}}'
- echo "Rolled back to Blue"
when: manual
needs:
- test-green
cleanup-old-blue:
stage: cleanup
image: bitnami/kubectl:latest
script:
- kubectl config use-context $KUBE_CONTEXT
# 이전 Blue를 새로운 Green 대기 환경으로 전환
- |
kubectl set image deployment/my-app-blue \
app=$DOCKER_IMAGE
when: manual
needs:
- promote
5.2 GitLab 환경 변수 설정
# GitLab CI/CD Variables 설정
KUBECONFIG # Kubernetes 설정 (base64 encoded)
CI_REGISTRY # Container Registry URL
AWS_ACCESS_KEY_ID # AWS 인증 (선택)
AWS_SECRET_ACCESS_KEY
SLACK_WEBHOOK_URL # 알림용
6. 배포 파이프라인 비교
| 기능 | GitHub Actions | GitLab CI | Jenkins |
|---|---|---|---|
| 설정 복잡도 | 낮음 | 낮음 | 높음 |
| 병렬 실행 | 기본 지원 | 기본 지원 | 플러그인 |
| 환경 승인 | Environments | Environments | 플러그인 |
| 비밀 관리 | Secrets | Variables | Credentials |
| Self-hosted | 지원 | 지원 | 기본 |
| 비용 | 무료 티어 있음 | 무료 티어 있음 | 무료 (인프라 비용) |
7. 다음 단계
이 글에서는 주요 클라우드 환경과 CI/CD 도구를 통한 Blue/Green 배포 자동화를 다루었습니다.
다음 글에서는 운영 및 최적화를 다룹니다:
- 모니터링 및 알림 설정
- 비용 최적화 전략
- 네트워킹 고려사항
- 트러블슈팅 가이드
- 실제 사례 연구
이전 글: Blue/Green 배포 (2) - Kubernetes 구현 ← 다음 글: Blue/Green 배포 (4) - 운영 및 최적화 →