From f8ecb04998770182b1d5b9fa26794532b180e2c8 Mon Sep 17 00:00:00 2001 From: bkw535 Date: Tue, 5 Aug 2025 01:08:21 +0900 Subject: [PATCH] =?UTF-8?q?[REFACTOR]=20=20=EB=B0=B0=ED=8F=AC=20=EB=B0=A9?= =?UTF-8?q?=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd.yml | 89 +++++++++++++++++++++++----------------- .github/workflows/ci.yml | 28 +++++++------ Dockerfile | 7 ++++ docker-compose.yml | 19 +++++++++ nginx/default-blue.conf | 17 ++++++++ nginx/default-green.conf | 17 ++++++++ nginx/default.conf | 0 src/index.js | 4 ++ 8 files changed, 131 insertions(+), 50 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 nginx/default-blue.conf create mode 100644 nginx/default-green.conf create mode 100644 nginx/default.conf diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index cca8469..b358897 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -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 <> ~/.ssh/config < /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" \ No newline at end of file + # 이전 컨테이너 제거 + 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 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b0e560..092b72c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 \ No newline at end of file + - 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 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3c68185 --- /dev/null +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9c9468d --- /dev/null +++ b/docker-compose.yml @@ -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 \ No newline at end of file diff --git a/nginx/default-blue.conf b/nginx/default-blue.conf new file mode 100644 index 0000000..c450f3c --- /dev/null +++ b/nginx/default-blue.conf @@ -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; + } +} \ No newline at end of file diff --git a/nginx/default-green.conf b/nginx/default-green.conf new file mode 100644 index 0000000..03a9324 --- /dev/null +++ b/nginx/default-green.conf @@ -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; + } +} \ No newline at end of file diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..e69de29 diff --git a/src/index.js b/src/index.js index b84e38f..b8adc79 100644 --- a/src/index.js +++ b/src/index.js @@ -113,6 +113,10 @@ app.get('/', (req, res) => { res.send('Hello World!') }) +app.get('/health', (_, res) => { + res.send('ok'); +}); + /** * 전역 오류를 처리하기 위한 미들웨어 */