Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 51 additions & 38 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,64 +11,77 @@ jobs:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Configure SSH
run: |
mkdir -p ~/.ssh
echo "$EC2_SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan -H $EC2_HOST >> ~/.ssh/known_hosts

cat >>~/.ssh/config <<END
cat >> ~/.ssh/config <<EOF
Host ec2
HostName $EC2_HOST
User ubuntu
IdentityFile ~/.ssh/id_rsa
StrictHostKeyChecking no
END
EOF
env:
EC2_HOST: ${{ secrets.EC2_HOST }}
EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }}

- name: Copy code to EC2 (using rsync)
run: |
ssh ec2 "sudo mkdir -p /opt/app && sudo chown ubuntu:ubuntu /opt/app"
rsync -avz --delete -e "ssh" ./ ec2:/opt/app

- name: Create .env file
run: |
ssh ec2 "echo '$ENV_FILE' > /opt/app/.env"
- name: Create .env on EC2
run: ssh ec2 "echo '$ENV_FILE' > /opt/app/.env"
env:
ENV_FILE: ${{ secrets.ENV_FILE }}
- name: Restore Firebase service account key

- name: Deploy with Blue-Green
run: |
ssh ec2 "mkdir -p /opt/app/config && echo $FIREBASE_SERVICE_ACCOUNT_BASE64 | base64 -d > /opt/app/config/service-account-key.json"
env:
FIREBASE_SERVICE_ACCOUNT_BASE64: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_BASE64 }}
ssh ec2 <<'EOF'
echo "📦 Pulling latest image..."
docker pull ${{ secrets.DOCKER_HUB_USERNAME }}/commit-api:latest

- name: Install dependencies
run: ssh ec2 "cd /opt/app && npm install"
echo "🔍 Checking current active environment..."
CURRENT=$(readlink -f /opt/app/nginx/default.conf)
if echo "$CURRENT" | grep -q "default-blue.conf"; then
TARGET_COLOR=green
TARGET_PORT=3001
TARGET_CONF=/opt/app/nginx/default-green.conf
else
TARGET_COLOR=blue
TARGET_PORT=3000
TARGET_CONF=/opt/app/nginx/default-blue.conf
fi

- name: Create systemd service
run: |
ssh ec2 "echo '[Unit]
Description=Node.js App
After=network.target
echo "🚀 Deploying to $TARGET_COLOR container on port $TARGET_PORT..."
docker run -d \
--name node-app-$TARGET_COLOR \
--env-file /opt/app/.env \
-p $TARGET_PORT:3000 \
--network=commit-networks \
${{ secrets.DOCKER_HUB_USERNAME }}/commit-api:latest

[Service]
Environment=NODE_ENV=production
User=ubuntu
ExecStart=/usr/bin/npm run start --prefix /opt/app/
Restart=always
echo "⏳ Health check for $TARGET_COLOR..."
for i in {1..10}; do
sleep 2
if curl -s http://localhost:$TARGET_PORT/health | grep "ok" > /dev/null; then
echo "✅ Health check passed. Switching traffic..."

[Install]
WantedBy=multi-user.target' | sudo tee /etc/systemd/system/app.service"
# Nginx 심볼릭 링크 교체
ln -sf $TARGET_CONF /opt/app/nginx/default.conf
docker exec nginx-proxy nginx -s reload

- name: Enable & Restart systemd service
run: |
ssh ec2 "sudo systemctl daemon-reload"
ssh ec2 "sudo systemctl enable app"
ssh ec2 "sudo systemctl restart app"
# 이전 컨테이너 제거
if [ "$TARGET_COLOR" = "blue" ]; then
docker rm -f node-app-green || true
else
docker rm -f node-app-blue || true
fi

docker rename node-app-$TARGET_COLOR node-app
exit 0
fi
done

echo "❌ Health check failed. Rolling back..."
docker rm -f node-app-$TARGET_COLOR || true
exit 1
EOF
28 changes: 16 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,26 @@ on:
branches: [develop]

jobs:
test:
build-and-push:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: npm install
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Run Linter
run: npm run lint
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}

- name: Run Tests
run: npm test
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/commit-api:latest
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM node:20-alpine3.22
WORKDIR /app
RUN apk update && apk upgrade && rm -rf /var/cache/apk/*
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "start"]
19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
services:
nginx:
image: nginx:latest
container_name: nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- /opt/app/nginx/default.conf:/etc/nginx/conf.d/default.conf
- /opt/app/nginx/default-blue.conf:/etc/nginx/conf.d/default-blue.conf
- /opt/app/nginx/default-green.conf:/etc/nginx/conf.d/default-green.conf
restart: always
networks:
- commit-networks

networks:
commit-networks:
driver: bridge
17 changes: 17 additions & 0 deletions nginx/default-blue.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
server {
listen 443 ssl;
server_name commit.n-e.kr www.commit.n-e.kr;

ssl_certificate /etc/letsencrypt/live/commit.n-e.kr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/commit.n-e.kr/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
17 changes: 17 additions & 0 deletions nginx/default-green.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
server {
listen 443 ssl;
server_name commit.n-e.kr www.commit.n-e.kr;

ssl_certificate /etc/letsencrypt/live/commit.n-e.kr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/commit.n-e.kr/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

location / {
proxy_pass http://127.0.0.1:3001;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Empty file added nginx/default.conf
Empty file.
4 changes: 4 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ app.get('/', (req, res) => {
res.send('Hello World!')
})

app.get('/health', (_, res) => {
res.send('ok');
});

/**
* 전역 오류를 처리하기 위한 미들웨어
*/
Expand Down