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
73 changes: 27 additions & 46 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,9 @@ jobs:
id: vars
run: |
if [ "${{ github.ref_name }}" == "main" ]; then
echo "APP_SECRET=APPLICATION" >> $GITHUB_OUTPUT
echo "DOCKER_TAG=latest" >> $GITHUB_OUTPUT
echo "COMPOSE_FILE=docker-compose.prod.yml" >> $GITHUB_OUTPUT
else
echo "APP_SECRET=APPLICATION_STAGING" >> $GITHUB_OUTPUT
echo "DOCKER_TAG=staging" >> $GITHUB_OUTPUT
echo "COMPOSE_FILE=docker-compose.staging.yml" >> $GITHUB_OUTPUT
fi

- name: Remove existing application.yml
run: rm -f src/main/resources/application.yml

- name: Make application.yml
run: |
mkdir -p src/main/resources
if [ "${{ github.ref_name }}" == "main" ]; then
echo "${{ secrets.APPLICATION }}" > src/main/resources/application.yml
else
echo "${{ secrets.APPLICATION_STAGING }}" > src/main/resources/application.yml
fi

- name: Build with Gradle
Expand All @@ -55,39 +39,36 @@ jobs:
docker build -f Dockerfile -t ${{ secrets.DOCKER_REPO }}:${{ steps.vars.outputs.DOCKER_TAG }} .
docker push ${{ secrets.DOCKER_REPO }}:${{ steps.vars.outputs.DOCKER_TAG }}

- name: Deploy_EC2
- name: Copy files to server
uses: appleboy/scp-action@master
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
source: "docker-compose.yml,init-db.sql,nginx/,scripts/"
target: /home/ubuntu/cockple

- name: Deploy
uses: appleboy/ssh-action@master
id: deploy
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
envs: >-
DB_PASSWORD,GCS_BUCKET,
KAKAO_CLIENT_ID,KAKAO_CLIENT_SECRET,KAKAO_REDIRECT_URI_PROD,KAKAO_REDIRECT_URI_STAGING,KAKAO_ADMIN_KEY,
JWT_SECRET_KEY
script: |
cd /home/ubuntu/home/monitor
echo "=== 배포 전 상태 ==="
sudo docker ps
sudo docker image prune -f
sudo docker pull ${{ secrets.DOCKER_REPO }}:${{ steps.vars.outputs.DOCKER_TAG }}

if [ "${{ github.ref_name }}" == "main" ]; then
sudo docker stop cockple-app || true
sudo docker rm -f cockple-app || true
if ! sudo docker ps | grep -q cockple-redis; then
echo "Redis(prod)가 죽었음, 재시작 중..."
sudo docker compose -f docker-compose.prod.yml up -d redis
sleep 10
fi
sudo docker compose -f docker-compose.prod.yml up -d cockple-app
else
sudo docker stop cockple-app-staging || true
sudo docker rm -f cockple-app-staging || true
if ! sudo docker ps | grep -q cockple-redis-staging; then
echo "Redis(staging)가 죽었음, 재시작 중..."
sudo docker compose -f docker-compose.staging.yml up -d redis-staging
sleep 10
fi
sudo docker compose -f docker-compose.staging.yml up -d cockple-app-staging
fi

echo "=== 배포 후 상태 ==="
sudo docker ps
chmod +x /home/ubuntu/cockple/scripts/deploy.sh
bash /home/ubuntu/cockple/scripts/deploy.sh \
${{ secrets.DOCKER_REPO }} \
${{ github.ref_name }}
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
GCS_BUCKET: ${{ secrets.GCS_BUCKET }}
KAKAO_CLIENT_ID: ${{ secrets.KAKAO_CLIENT_ID }}
KAKAO_CLIENT_SECRET: ${{ secrets.KAKAO_CLIENT_SECRET }}
KAKAO_REDIRECT_URI_PROD: ${{ secrets.KAKAO_REDIRECT_URI_PROD }}
KAKAO_REDIRECT_URI_STAGING: ${{ secrets.KAKAO_REDIRECT_URI_STAGING }}
KAKAO_ADMIN_KEY: ${{ secrets.KAKAO_ADMIN_KEY }}
JWT_SECRET_KEY: ${{ secrets.JWT_SECRET_KEY }}
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ bin/

### IntelliJ IDEA ###
.idea
src/main/generated/
*.iws
*.iml
*.ipr
Expand All @@ -40,4 +41,10 @@ src/main/resources/application-dev.yml

.env

application-dev.yml
application-dev.yml

### Terraform ###
terraform/.terraform/
terraform/terraform.tfstate
terraform/terraform.tfstate.backup
terraform/terraform.tfvars
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
FROM eclipse-temurin:17-jdk-jammy
FROM eclipse-temurin:17-jre-jammy

COPY build/libs/cockple.demo-0.0.1-SNAPSHOT.jar app.jar

CMD ["java", "-Dspring.profiles.active=dev", "-jar", "app.jar"]
ENTRYPOINT ["java", "-jar", "app.jar"]
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ dependencies {
// swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.7.0'

// s3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
// gcs
implementation 'com.google.cloud:google-cloud-storage:2.40.1'

// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
Expand Down
114 changes: 114 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: cockple

services:
mysql:
image: mysql:8.0
container_name: cockple-mysql
restart: always
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
TZ: Asia/Seoul
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --innodb-buffer-pool-size=256M
volumes:
- mysql-data:/var/lib/mysql
- ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql:ro
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
mem_limit: 512m
memswap_limit: 768m

redis:
image: redis:7-alpine
container_name: cockple-redis
restart: always
ports:
- "6379:6379"
command:
- redis-server
- --appendonly
- "yes"
- --maxmemory
- 200mb
- --maxmemory-policy
- allkeys-lru
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
mem_limit: 256m
memswap_limit: 384m

cockple-app:
container_name: cockple-app
image: kanghana1/cockple:latest
restart: always
environment:
JAVA_TOOL_OPTIONS: "-Xms768m -Xmx768m"
SPRING_PROFILES_ACTIVE: prod
DB_PASSWORD: ${DB_PASSWORD}
GCS_BUCKET: ${GCS_BUCKET}
KAKAO_CLIENT_ID: ${KAKAO_CLIENT_ID}
KAKAO_CLIENT_SECRET: ${KAKAO_CLIENT_SECRET}
KAKAO_REDIRECT_URI: ${KAKAO_REDIRECT_URI_PROD}
KAKAO_ADMIN_KEY: ${KAKAO_ADMIN_KEY}
JWT_SECRET_KEY: ${JWT_SECRET_KEY}
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
mem_limit: 1200m
memswap_limit: 1536m

cockple-app-staging:
container_name: cockple-app-staging
image: kanghana1/cockple:staging
restart: always
environment:
JAVA_TOOL_OPTIONS: "-Xms128m -Xmx512m"
SPRING_PROFILES_ACTIVE: staging
DB_PASSWORD: ${DB_PASSWORD}
GCS_BUCKET: ${GCS_BUCKET}
KAKAO_CLIENT_ID: ${KAKAO_CLIENT_ID}
KAKAO_CLIENT_SECRET: ${KAKAO_CLIENT_SECRET}
KAKAO_REDIRECT_URI: ${KAKAO_REDIRECT_URI_STAGING}
KAKAO_ADMIN_KEY: ${KAKAO_ADMIN_KEY}
JWT_SECRET_KEY: ${JWT_SECRET_KEY}
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
mem_limit: 1024m
memswap_limit: 1280m

nginx:
image: nginx:stable-alpine
container_name: cockple-nginx
restart: always
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
mem_limit: 64m
memswap_limit: 128m

volumes:
mysql-data:
redis-data:

networks:
default:
name: cockple_network
2 changes: 2 additions & 0 deletions init-db.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
CREATE DATABASE IF NOT EXISTS cockple CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS cockple_staging CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
12 changes: 12 additions & 0 deletions nginx/conf.d/prod.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
server {
listen 80;
server_name cockple.store;

location / {
proxy_pass http://cockple-app:8080;
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 $http_x_forwarded_proto;
}
}
12 changes: 12 additions & 0 deletions nginx/conf.d/staging.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
server {
listen 80;
server_name staging.cockple.store;

location / {
proxy_pass http://cockple-app-staging:8080;
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 $http_x_forwarded_proto;
}
}
25 changes: 25 additions & 0 deletions nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';

access_log /var/log/nginx/access.log main;

sendfile on;
keepalive_timeout 65;
client_max_body_size 30M;

include /etc/nginx/conf.d/*.conf;
}
70 changes: 70 additions & 0 deletions scripts/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/bin/bash

DOCKER_REPO=$1
BRANCH=$2

cd /home/ubuntu/cockple

if [ "$BRANCH" == "main" ]; then
SERVICE="cockple-app"
TAG="latest"
else
SERVICE="cockple-app-staging"
TAG="staging"
fi

cat > .env << EOF
DB_PASSWORD=${DB_PASSWORD}
GCS_BUCKET=${GCS_BUCKET}
KAKAO_CLIENT_ID=${KAKAO_CLIENT_ID}
KAKAO_CLIENT_SECRET=${KAKAO_CLIENT_SECRET}
KAKAO_REDIRECT_URI_PROD=${KAKAO_REDIRECT_URI_PROD}
KAKAO_REDIRECT_URI_STAGING=${KAKAO_REDIRECT_URI_STAGING}
KAKAO_ADMIN_KEY=${KAKAO_ADMIN_KEY}
JWT_SECRET_KEY=${JWT_SECRET_KEY}
EOF

echo "=== 배포 전 상태 ==="
sudo docker ps

sudo docker compose up -d mysql redis nginx
sudo docker image prune -f
sudo docker pull $DOCKER_REPO:$TAG

sudo docker stop $SERVICE || true
sudo docker rm -f $SERVICE || true

sudo docker compose up -d $SERVICE

echo "=== 배포 후 상태 ==="
sudo docker ps

echo "=== 헬스체크 ==="
for container in cockple-mysql cockple-redis $SERVICE; do
for i in $(seq 1 12); do
STATUS=$(sudo docker inspect --format='{{.State.Status}}' $container 2>/dev/null)
HEALTH=$(sudo docker inspect --format='{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' $container 2>/dev/null)

if [ "$STATUS" != "running" ]; then
echo "FAIL: $container 상태 이상 (status=$STATUS)"
sudo docker logs --tail 20 $container
exit 1
fi

if [ "$HEALTH" == "healthy" ] || [ "$HEALTH" == "none" ]; then
echo "OK: $container (status=$STATUS, health=$HEALTH)"
break
fi

if [ $i -eq 12 ]; then
echo "FAIL: $container 헬스체크 타임아웃 (health=$HEALTH)"
sudo docker logs --tail 20 $container
exit 1
fi

echo "대기 중: $container ($i/12, health=$HEALTH)..."
sleep 5
done
done

echo "=== 배포 성공 ==="
20 changes: 20 additions & 0 deletions scripts/tunnel.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
# 사용법: bash scripts/tunnel.sh [GCP_IP]
# 예시: bash scripts/tunnel.sh 34.64.xxx.xxx

GCP_IP=${1}

if [ -z "$GCP_IP" ]; then
read -p "GCP IP 입력: " GCP_IP
fi

echo "터널링 시작: $GCP_IP"
echo " MySQL -> localhost:3306 -> cockple-mysql:3306"
echo " Redis -> localhost:6379 -> cockple-redis:6379"
echo "종료: Ctrl+C"

ssh -N \
-L 3307:localhost:3306 \
-L 6380:localhost:6379 \
-i ~/.ssh/cockple_gcp \
ubuntu@$GCP_IP
Loading