diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..7d0fa80d --- /dev/null +++ b/.dockerignore @@ -0,0 +1,20 @@ +# Dependencies +node_modules + +# Environment +.env +.env.test + +/generated/prisma +uploads/ +.dest +coverage + +# sprint sub +infra +http + +# Docker +Dockerfile +.dockerignore +docker-compose.yaml \ No newline at end of file diff --git a/.env.sample b/.env.sample index 40e23ad7..96988c66 100644 --- a/.env.sample +++ b/.env.sample @@ -14,7 +14,7 @@ AWS_S3_ACCESS_KEY_ID='access key' AWS_S3_SECRET_KEY_ID='secret access key' #ENV -ENV='production | develop' +NODE_ENV='production | develop' #Using .env list # .env diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 00000000..745725b2 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,28 @@ +name: cd + +on: + push: + branches: + - main + +jobs: + AWS_deploy: + runs-on: ubuntu-latest + environment: AWS_Deploy + steps: + # EC2 배포 + - name: Deploy to EC2 + uses: appleboy/ssh-action@v1.2.2 + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USER }} + key: ${{ secrets.EC2_PRIVATE_KEY }} + port: 22 + script: | + cd ~/3-sprint-mission # EC2 인스턴스 내 프로젝트 경로로 진입 + git pull origin main # 코드 최신화 + npm install + npm run build # ts -> js + npm run prisma:migrate # Prisma 마이그레이션 + pm2 restart codeit-sprint10 # 이전 스프린트10때 프로세스 이름을 codeit-sprint10으로 설정했음 + echo "✅ Deployment successful!" \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..438365df --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: ci + +on: + pull_request: + branches: [main, 천수] + +jobs: + test: + runs-on: ubuntu-latest + env: + DATABASE_URL: postgresql://postgres:test_password@localhost:5432/test_db?schema=public + JWT_SECRET: test_secret + PORT: 3000 + NODE_ENV: develop #production만 아니면 로직은 동일 + + services: + postgres: + image: postgres + env: + POSTGRES_PASSWORD: test_password + POSTGRES_DB: test_db + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: 22 + + - name: Install dependencies + run: npm ci + + - name: Type Check + run: npx tsc --noEmit + + - name: Prisma migrate + run: npm run prisma:migrate + + - name: Run Tests + run: npm run jest:test \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..e789cc56 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# node js 환경 준비 +FROM node:22.16.0 + +# 소스코드 다운로드 +COPY . /panda-market + +# 소스코드 디렉토리 이동 +WORKDIR /panda-market + +# 의존성 패키지 설치 (npm ci) +RUN npm ci + +# 소스코드 빌드 +RUN npm run build + +# 환경 변수 정의 +ENV PORT=3000 +ENV NODE_ENV='develop' + +# 서버 실행 +CMD sh -c "npm run prisma:migrate && npm run start" \ No newline at end of file diff --git a/README.md b/README.md index 21fd51f6..5127497c 100644 --- a/README.md +++ b/README.md @@ -308,4 +308,37 @@ - 환경변수를 통해, 운영 환경이라면 multer-s3를 이용하여 운영 환경시에는 aws를 사용하도록 수정하였습니다. - AWS 프리티어 사용이 불가하여 제출을 최대한 늦게 하였습니다. - 요구사항 자체는 프리티어에서 사용 가능한 인스턴스 유형을 사용하였습니다 -- pm2의 ecosystem.config.js는 강의 시간에 다루지 않았던 내용이라 간단하게 찾아보고 적용을 해보았습니다. 적절하지 않은 설정이 있거나 보완할 수 있는 내용이 있는지 궁금합니다. \ No newline at end of file +- pm2의 ecosystem.config.js는 강의 시간에 다루지 않았던 내용이라 간단하게 찾아보고 적용을 해보았습니다. 적절하지 않은 설정이 있거나 보완할 수 있는 내용이 있는지 궁금합니다. + +--- +# 스프린트 미션 11 + +## 미션 목표 + +- Github Actions로 테스트, 배포 자동화 +- Docker 이미지 만들기 + +## ✅ 요구사항 + +### Github Actions 활용 + +- [x] 브랜치에 pull request가 발생하면 테스트를 실행하는 액션을 구현해 주세요. +- [x] main 브랜치에 push가 발생하면 AWS 배포를 진행하는 액션을 구현해 주세요. +- [x] 개인 Github 리포지터리에서 Actions 동작을 확인해 보세요. + +### Docker 이미지 만들기 + +다음을 만족하는 Dockerfile과 docker-compose.yaml을 작성해 주세요. +- [x] Express 서버를 실행하는 Dockerfile을 작성해 주세요. +- [x] Express 서버가 파일 업로드를 처리하는 폴더는 Docker의 Volume을 활용하도록 구현해 주세요. +- [x] 데이터베이스는 Postgres 이미지를 사용해 연결하도록 구현해 주세요. +- [x] 실행된 Express 서버 컨테이너는 호스트 머신에서 3000번 포트로 접근 가능하도록 구현해 주세요. + +--- + +## 멘토에게 +- CI/CD에서 사용하는 환경 변수는 github의 enviroment secret기능을 활용하여 원하는 환경에 맞게 변수를 구분하였습니다. +- CI에서 push시 테스트를 실행하는 액션에 대해 브랜치 조건이 없었기에 제 이름이 들어간 브랜치는 모두 수행되도록 작성하였습니다. +- Docker-compose.yaml에서 depend_on과 service_healthy를 추가하여 postgres가 정상적으로 동작이 확인 되었을때 백엔드 서버가 실행되도록 구현하였습니다. +- 학습을 위해 Docker-compose.yaml에서 env_file과 environment를 사용하는 방법을 구성해보았습니다. 보안을 위해서라면 env_file에 전부 넣는게 맞다는 생각이 드는데 이부분은 어떻게 생각하시는지 궁금합니다. +- CD시 docker를 활용해서 배포하는 방법이 궁금합니다. (ec2에서 docker를 설정하는 등) \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..cd06930f --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,50 @@ +name: panda-market +services: + panda-market-app: + image: seracine/panda-market-app:1.0.0 + restart: always # 에러 발생시 재시작 + build: + tags: + - seracine/panda-market-app:1.0.0 + - seracine/panda-market-app:latest + dockerfile: Dockerfile + pull: true + context: . + container_name: panda-market-app + ports: + - "3000:3000" + networks: + - panda-network + volumes: + - panda-app-volume:/panda-market/uploads + env_file: # env 파일로 환경변수 주입 + - .env + depends_on: + panda-market-postgres-db: + condition: service_healthy + panda-market-postgres-db: + image: postgres + restart: always # 에러 발생시 재시작 + container_name: panda-market-postgres + volumes: + - panda-db-volume:/var/lib/postgresql/data + networks: + - panda-network + environment: # 환경변수 파라미터 직접 전달 + - POSTGRES_PASSWORD=1234 + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 1m30s + timeout: 30s + retries: 5 + start_period: 30s + +networks: + panda-network: + name: panda-network + +volumes: + panda-app-volume: + name: panda-app-volume + panda-db-volume: + name: panda-db-volume diff --git a/src/services/imageUpload.ts b/src/services/imageUpload.ts index c30b6539..5ddbf566 100644 --- a/src/services/imageUpload.ts +++ b/src/services/imageUpload.ts @@ -8,7 +8,7 @@ import multerS3 from 'multer-s3' let storage -if (process.env.ENV === 'production') { //배포 환경일 때는 AWS로 +if (process.env.NODE_ENV === 'production') { //배포 환경일 때는 AWS로 const s3ClientParams = { region: process.env.AWS_S3_REGION ?? '', credentials: { @@ -84,7 +84,7 @@ function uploadImage(req: Request, res: Response) { // 유효성 검사 실패 } console.log(file) - if (process.env.ENV === 'production' && 'location' in file) { // Express.MulterS3.File 타입에 있는 location이 있는지 확인 + if (process.env.NODE_ENV === 'production' && 'location' in file) { // Express.MulterS3.File 타입에 있는 location이 있는지 확인 res.json({ message: 'Finish Upload', path: file.location }) } else { res.json({ message: 'Finish Upload', path: `/images/${file.filename}` })