본문 바로가기

[IT/Programming]

Linux (AWS Amazon Linux) 에 git 설치 및 nginx 설치 (초기 세팅들), Docker (Dockerfile and .dockerignore) and CI/CD GitHub Action (main.yml)

반응형
# Linux (AWS Amazon Linux) 에 git 설치 및 nginx 설치 (초기 세팅들), Docker (Dockerfile and .dockerignore) and GitHub Action (main.yml) 코드잇 풀스택 2기 - Part 4 - Project-3 FitMate 개인 개발 리포트 (이강수 (kipid)) ## PH
  • 2024-12-31 : First posting.
## TOC ## 팀원별 구현 기능 상세 및 담당 업무 - 로그인 페이지 - 회원가입 페이지 - 프로필 등록 페이지 - 마이페이지 - 프로필 수정 페이지 - 알림 페이지 - AI 챗봇 페이지 --- - UserProvider/NotificationProvider/ViewportProvider - ErrorBoundary - GNB (Global Navigation Bar) - Input/InputPassword 컴포넌트 - Textarea/Button 컴포넌트 - ReviewCard/RatingAvgCard/RatingStatCard 컴포넌트 - Chip 컴포넌트 - 기본 정보 카드 템플릿 (강사님) - 리뷰 리스트 카드 템플릿 (강사님) - 좋아요 기능 (카드 템플릿 안에 들어감) --- - 프론트 배포 (Docker and GitHub Action (CI/CD)) - 시연 영상 발표 ## 문제해결 문제상황: 서버와 데이터를 주고받는 일이 많다보니까 tanstack-query 를 사용할 일이 많았는데, 데이터가 필요한 페이지마다 useQuery 를 쓰다보니 queryKey 도 다들 제각각이고 그러다보니 invalidateQueries 쓰는것도 복잡해짐. 원인분석: 파편화 된 것이 문제니까 한곳에 모아놓고 export 해서 써보자는 생각을 함. 해결방법: useQuery 나 useMutation 쓰는것을 파일로 분리해서 export 해서 써보기로 함. ```[.linenums.lang-js] import { useQuery } from "@tanstack/react-query"; import { ProfileData } from "@/types/types"; import { getUserInfo } from "../userService"; // 유저 프로필 조회 export const useGetUser = (userId: string) => { return useQuery<ProfileData>(["user-info", userId], () => getUserInfo(userId), { enabled: !!userId, cacheTime: 5 * 60 * 1000, staleTime: 5 * 60 * 1000, retry: 3, }); }; ```/ 배운점 및 개선사항: 파일로 분리해서 쓰다보니 queryKey 관리도 쉬워지고 여러모로 좋은점이 많았음. useMutateUser 같은 useMutation 도 이런식으로 사용하면 더 좋을거 같음. ## EC2 접속 EC2 로 인스턴스 생성 (AWS Amazon Linux) free tier 로. Amazon Linux 에 접속 .ssh 이용. (public IP 는 재부팅시 항상 바뀌기 때문에 돈을 내고 Elastic IP 를 이용하는게 좋긴 함.) 아래와 같이 터미널/cmd 창에서 EC2 로 접속. 43.202.97.246 ```[.linenums.lang-sh] # FE ssh -i "codeit-fs-key.pem" ec2-user@ec2-43-202-97-246.ap-northeast-2.compute.amazonaws.com # BE ssh -i "fitmate-be.pem" ec2-user@ec2-15-164-195-136.ap-northeast-2.compute.amazonaws.com ```/ ## Git 및 Nginx 설치 ```[.linenums.lang-sh] sudo yum update -y sudo yum install git sudo yum install nginx # Amazon Linux sudo apt install nginx -y # Ubuntu (WSL) ```/ Git clone 하기. (Open Source 라서 따로 사용자 및 비번을 넣지 않아도 됨.) ```[.linenums.lang-sh] git clone https://github.com/FS-part4-1team-FitMate/FitMate-FE.git # 특정 branch 로 pull 하기. git branch git checkout Feat-kipid git pull origin Feat-kipid ```/ ## Windows 11 의 Local WSL (Windows Subsystem for Linux) 에서 Back-End 실행하기 ```[.linenums.lang-sh] wsl # Ubuntu 접속 cd ~ # Home directory 로 이동 git clone https://github.com/FS-part4-1team-FitMate/FitMate-BE.git # Clone Back-End repo git pull npx prisma migrate reset npx prisma migrate dev npm run seed npm run build npm run start ```/ ```[.linenums.lang-sh] ip addr show eth0 # ip 확인 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:15:5d:84:de:a6 brd ff:ff:ff:ff:ff:ff inet 172.21.104.225/20 brd 172.21.111.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::215:5dff:fe84:dea6/64 scope link valid_lft forever preferred_lft forever # 위와 같이 뜨면 localhost:3000 이 아니라 172.21.104.225:3000 으로 연결해야 함. # Front-End 의 .env 에서 NEXT_PUBLIC_API_URL="http://172.21.104.225:3000" 으로 설정해줘야 함. # wsl --shutdown 후 # wsl 로 접속해야 ip 들이 재설정 되는듯? # PostgreSQL 이 Windows 에서 돌아가고 있을거기 때문에. # BE 의 .env 의 DATABASE_URL="postgresql://..." 도 바꿔줘야 함. # cmd 창에서 ipconfig 쳤을때 나오는 Windows ip 로. DATABASE_URL="postgresql://postgres:[Password]@[Windows local ip address]:5432/fitmate_dev?schema=public" # 방화벽 확인 sudo ufw status Status: active To Action From -- ------ ---- 80/tcp ALLOW Anywhere 443/tcp ALLOW Anywhere 22/tcp ALLOW Anywhere 3000 ALLOW Anywhere 3001/tcp ALLOW Anywhere 80/tcp (v6) ALLOW Anywhere (v6) 443/tcp (v6) ALLOW Anywhere (v6) 22/tcp (v6) ALLOW Anywhere (v6) 3000 (v6) ALLOW Anywhere (v6) 3001/tcp (v6) ALLOW Anywhere (v6) # 와 같이 떠야 함. sudo ufw allow 3000/tcp # 와 같이 Back-End port 를 허용해줘야 할 수 있음. ```/ ## Windows 11 의 Local WSL (Windows Subsystem for Linux) 에서 Front-End 실행하기 ```[.linenums.lang-sh] wsl # Ubuntu 접속 cd ~ # Home directory 로 이동 git clone https://github.com/FS-part4-1team-FitMate/FitMate-BE.git # Clone Back-End repo git pull git checkout Feat-kipid # 자신의 branch 로 들어가야 자기가 작업한 것들을 불러올 수 있음. git pull npm run build npm run start ```/ ```[.linenums.lang-sh] ip addr show eth0 # ip 확인 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:15:5d:84:de:a6 brd ff:ff:ff:ff:ff:ff inet 172.21.104.225/20 brd 172.21.111.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::215:5dff:fe84:dea6/64 scope link valid_lft forever preferred_lft forever # 위와 같이 뜨면 localhost:3001 이 아니라 172.21.104.225:3001 으로 연결해야 함. (어쩔땐 그냥 localhost:3001 로도 접속이 되긴 했는데, 컴퓨터를 껐다 키니까 안됨 ㅡ,.ㅡ;;) ```/ ## Docker 설치 방법 (Amazon Linux 2) ```[.linenums.lang-sh] sudo yum update -y # Amazon Linux 2 sudo yum install -y docker sudo systemctl enable docker sudo systemctl start docker sudo systemctl status docker docker ps docker images ```/ Docker 만 깔면 Node.js 및 pm2 설치는 넘어가도 됨. Dockerfile 을 다음과 같이 작성. ```[.linenums.lang-sh] # Use the official Node.js LTS image for building FROM node:lts AS builder # Set working directory WORKDIR /app ARG NEXT_PUBLIC_KAKAO_API_KEY ENV NEXT_PUBLIC_API_URL="https://fitmate-be.asia" ENV NODE_ENV="production" ENV NEXT_PUBLIC_KAKAO_API_KEY=${NEXT_PUBLIC_KAKAO_API_KEY} # Install dependencies (only production deps for runtime) COPY package.json package-lock.json ./ RUN npm ci # Copy the rest of the application files COPY . . # Build the Next.js app RUN npm run build # Use a minimal runtime image FROM node:lts AS runtime # Create a non-root user # RUN addgroup -S appgroup && adduser -S appuser -G appgroup # Set working directory WORKDIR /app # pm2 글로벌 설치 RUN npm install -g pm2 # Copy necessary files from the builder stage COPY --from=builder /app/package.json /app/package-lock.json ./ COPY --from=builder /app/.next ./.next COPY --from=builder /app/public ./public COPY --from=builder /app/node_modules ./node_modules # Ensure correct permissions # RUN chown -R appuser:appgroup /app # Switch to non-root user # USER appuser # 환경 변수 설정 ENV NEXT_PUBLIC_API_URL="https://fitmate-be.asia" ENV NODE_ENV="production" ENV PORT=3001 # Expose application port EXPOSE 3001 # Define runtime command CMD ["pm2-runtime", "start", "npm", "--", "start"] ```/ .dockerignore 도 다음과 같이 작성. ```[.linenums.lang-sh] # 운영체제 파일 .DS_Store Thumbs.db # Node.js 관련 파일 (예: 로컬 개발 환경에서 설치된 모듈) node_modules/ .next/ .github/ .git/ # 빌드 아티팩트 dist/ build/ # 로그 파일 *.log # 기타 불필요한 파일 *.md *.txt Dockerfile ```/ 다음과 같은 명령어들로 Docker 실행. HTTPS 설정이랑 Nginx 설정은 따로 해줘야 함. ```[.linenums.lang-sh] # 1. Docker 이미지 빌드 (빌드는 Local 컴퓨터에서 하는게 좋아보임. 서버 성능이 좋지 않다면 특히나...) docker login # https://hub.docker.com/ 의 ID/Password 를 입력해야 함. # .env 를 production 에 맞춰서 바꿔준 뒤, build 해야 함. docker build -t kipid/fitmate-fe:v0.2 . docker push kipid/fitmate-fe:v0.2 # 2. 컨테이너 실행 (EC2 에 터미널로 연결해서) docker login # https://hub.docker.com/ 의 ID/Password 를 입력해야 함. docker images # 안쓰는 이미지 삭제 docker stop $(docker ps -aq) docker system prune -a docker rmi <이미지 ID 또는 이미지 이름> docker pull kipid/fitmate-fe:v0.2 docker stop fitmate-fe-container docker rm fitmate-fe-container docker run -d -p 3001:3001 --name fitmate-fe-container kipid/fitmate-fe:v0.2 docker run -d -p 3001:3001 --name fitmate-fe-container \ -e "NEXT_PUBLIC_API_URL='https://fitmate-be.asia'" \ -e "NODE_ENV='production'" \ -e "NEXT_PUBLIC_KAKAO_API_KEY='83c6b0fde64b363bd52e26fd41f2fbd6'" \ kipid/fitmate-fe:v0.2 # Nginx 시작 sudo systemctl start nginx # Docker 중단 docker stop <컨테이너_ID 또는 컨테이너_이름> ```/ ## CI/CD (Continuous Integration, Continuous Delivery/Deployment) .github/workflows/main.yml 파일에 다음과 같이 입력하고 저장 and Git push Settings - Secrets and variables - Actions - Repository secrets 에 New repository secret 에다 비번같은걸 등록해야 함! Environment secrets 이 아님! 이걸로 한참 고생함. Organization secrets 도 아님. ```[.linenums.lang-sh] name: CI/CD Pipeline # 워크플로우의 이름을 'CI/CD Pipeline'으로 설정 on: # 워크플로우 트리거 조건 설정 push: # Git push 이벤트 발생 시 실행 branches: ["Feat-kipid"] # Feat-kipid 브랜치에 push가 발생했을 때만 실행 jobs: # 실행할 작업들을 정의하는 섹션 build-and-deploy: # 작업 이름을 'build-and-deploy'로 설정 runs-on: ubuntu-latest # Ubuntu 최신 버전의 러너에서 작업 실행 steps: # 작업의 세부 단계들을 정의 - name: Checkout code # 코드 체크아웃 단계 uses: actions/checkout@v3 # GitHub 저장소 코드를 가져오는 공식 액션 사용 - name: Build Docker image # Docker 이미지 빌드 단계 env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} run: | echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin # Docker Hub 로그인 (보안을 위해 secrets 사용) docker --version sleep 3 docker build \ --build-arg "NEXT_PUBLIC_API_URL=https://fitmate-be.asia" \ --build-arg "NODE_ENV=production" \ --build-arg "NEXT_PUBLIC_KAKAO_API_KEY=${{ secrets.NEXT_PUBLIC_KAKAO_API_KEY }}" \ -t kipid/fitmate-fe:v0.2 . || exit 1 # Dockerfile을 사용하여 이미지 빌드, 태그는 v0.2로 지정 sleep 3 docker push kipid/fitmate-fe:v0.2 # 빌드된 이미지를 Docker Hub에 업로드 sleep 3 docker logout continue-on-error: false - name: Deploy to server # 서버 배포 단계 uses: appleboy/ssh-action@master env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} with: host: ${{ secrets.SERVER_IP }} username: ${{ secrets.SERVER_USERNAME }} key: ${{ secrets.SERVER_PRIVATE_KEY }} port: 22 script: | echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin docker pull kipid/fitmate-fe:v0.2 # 최신 이미지 다운로드 docker stop $(docker ps -a -q) || true docker rm -f $(docker ps -a -q) || true docker stop fitmate-fe-container || true # 기존 컨테이너 중지 (실패해도 계속 진행) docker rm fitmate-fe-container || true # 기존 컨테이너 제거 (실패해도 계속 진행) docker run -d -p 3001:3001 --name fitmate-fe-container \ -e "NEXT_PUBLIC_API_URL=https://fitmate-be.asia" \ -e "NODE_ENV=production" \ -e "NEXT_PUBLIC_KAKAO_API_KEY=${{ secrets.NEXT_PUBLIC_KAKAO_API_KEY }}" \ kipid/fitmate-fe:v0.2 sleep 5 # 컨테이너 실행 안정화 대기 docker system prune -f # 사용하지 않는 Docker 리소스 정리 sleep 3 ps -ef | grep docker # Check running processes docker logout ```/ Settings - Secrets and variables - Actions 들어가서 Environment secrets 저장해주면 끝. ## Node.js 설치 ```[.linenums.lang-sh] curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash nvm install --lts ```/ ## Redis server 띄우기 Redis server 까는 법은 ChatGPT 에게 문의. ```[.linenums.lang-sh] redis-server --daemonize yes redis-cli ping # Expected PONG ```/ ## pm2 start package.json 은 다음과 같이.. port 3001 로 start. ```[.linenums.lang-sh] "scripts": { "dev": "next dev -p 3001", "build": "next build", "start": "next start -p 3001", "lint": "next lint" }, ```/ ```[.linenums.lang-sh] npm i pm2 -g npm run build pm2 start "npm run start" pm2 start npm -- name 'my-app' -- start "pm2:start": "pm2 start npm -- name 'my-app' -- start" // npm run pm2:start pm2 list pm2 stop 0 pm2 restart 0 pm2 delete 0 and so on. ```/ ## HTTPS 연결로 만들기 및 Nginx 설정 ```[.linenums.lang-sh] sudo yum install -y certbot sudo certbot certonly --standalone -d fitmate.asia sudo certbot certonly --standalone -d fitmate-be.asia ```/ 성공적으로 인증서를 발급받으면 /etc/letsencrypt/live/fitmate.asia/ 에 인증서 파일이 저장됨. ```[.linenums.lang-sh] # Nginx 설정 파일 편집 sudo vi /etc/nginx/nginx.conf sudo vi /etc/nginx/conf.d/fitmate.asia.conf sudo vi /etc/nginx/conf.d/fitmate-be.asia.conf ```/ ```[.linenums.lang-sh] server { listen 80; server_name fitmate.asia www.fitmate.asia; # Redirect HTTP to HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl; server_name fitmate.asia www.fitmate.asia; ssl_certificate /etc/letsencrypt/live/fitmate.asia/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/fitmate.asia/privkey.pem; location / { proxy_pass http://localhost:3000; # 애플리케이션 포트 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } ```/ ```[.linenums.lang-sh] server { listen 80; server_name fitmate-be.asia www.fitmate-be.asia; # Redirect HTTP to HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl; server_name fitmate-be.asia www.fitmate-be.asia; ssl_certificate /etc/letsencrypt/live/fitmate-be.asia/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/fitmate-be.asia/privkey.pem; location / { proxy_pass http://localhost:3001; # 애플리케이션 포트 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } ```/ 전체 파일을 들여다 보면 다음과 같음. ```[.linenums.lang-sh] # For more information on configuration, see: # * Official English Documentation: http://nginx.org/en/docs/ # * Official Russian Documentation: http://nginx.org/ru/docs/ user nginx; worker_processes auto; error_log /var/log/nginx/error.log notice; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; keepalive_timeout 65; types_hash_max_size 4096; include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; server { listen 80; listen [::]:80; server_name fitmate.asia www.fitmate.asia; root /usr/share/nginx/html; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; # Redirect HTTP to HTTPS return 301 https://$host$request_uri; error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } # Settings for a TLS enabled server. server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name fitmate.asia www.fitmate.asia; root /usr/share/nginx/html; ssl_certificate "/etc/letsencrypt/live/fitmate.asia/fullchain.pem"; ssl_certificate_key "/etc/letsencrypt/live/fitmate.asia/privkey.pem"; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers PROFILE=SYSTEM; ssl_prefer_server_ciphers on; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { proxy_pass http://localhost:3001; # 애플리케이션 포트 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } error_page 404 /404.html; location = /404.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } } ```/ ```[.linenums.lang-sh] sudo systemctl start nginx sudo vi /etc/nginx/nginx.conf ============================ server { listen 80 ...... 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; } } ============================ sudo nginx -t sudo systemctl restart nginx ```/ ## Typescript of APIspec ```[.linenums.lang-sh] export type APISpec<BU = any, P = any, B = any, T = any> = { method: 'GET' | 'POST' | 'PATCH' | 'DELETE' baseURL: BU path: P body?: B response: T } export type APISpecs< T extends { [K in string]: APISpec } > = T export type ProductAPI = APISpecs<{getProducts: { method: 'GET', baseURL: 'http://localhost:3100', path: '/products' response: Product[] }}> ```/ ## ESLint 무효화 ```[.linenums.lang-sh] // eslint-disable-next-line @typescript-eslint/no-explicit-any ```/ ## RRA
  1. https://www.docker.com/
반응형