React + express(node.js) + Docker 프로젝트 AWS EC2 배포 후기 | Web IDE 구축하기 - 5

    반응형

     

    요 근래 제가 백수기간에 만들었던 프로젝트들을 하나씩 배포하고 있습니다

    myCafe Map service 같은 경우에는 vercel로 배포하면서 어려움이 크게 없었습니다
    (오히려 너무 쉬워서 당황했을 정도)

    근데 이번에 web-ide 플젝을 배포하려면서 엄청 고난과 역경을 겪었습니다

    이번 포스팅에 배포법을 설명하지 않습니다

    배포법이 복잡하고, 다른 블로그 글에 설명이 잘 되어있어서..

    건너뛰고 마주쳤던 이슈들만 다뤄보려고 합니다

     

     

    프로젝트의 핵심 기능


    제 프로젝트는 단순 CRUD가 아닌, 조금은 특이한 경우라고 생각합니다
    (난이도가 마냥 높은건 아니지만 주니어가 하기엔 어려울 수 있다고 생각합니다)

    클라이언트 - React (CRA)
    서버 - express.js (Node.js)
    그 외 핵심 - Docker, Socket.io

    • 클라이언트에서 서버로 작성한 코드를 보낸다
    • 서버는 클라이언트에게 받은 코드를 로컬에 파일로 컴파일합니다
    • 파일로 된 코드를 실행 중인 docker 컨테이너 내부에 복사합니다
    • docker 컨테이너 내부에 복사된 파일(코드)을 실행시킵니다
    • 실행시킨 결과물은 서버로, 서버는 클라이언트에게 반환합니다

    이 과정은 모두 Socket.io를 통해서 처리되고 응답을 받습니다

    Socket.io을 사용한 이유

    소켓을 사용한 이유는 보통 코딩테스트를 풀 때 내 코드가 처리되는 과정을 실시간으로 보잖아요?
    (처리중.. -> 실패, 통과)

    그래서 저도 소켓을 사용하게 되었습니다

    Docker를 사용한 이유

    다양한 언어로 쓰일 클라이언트의 코드를 처리하는 방법은 2가지가 있습니다

    1. 모든 언어의 컴파일러가 깔린 가상머신
    2. 모든 언어의 컴파일러가 깔린 Docker 컨테이너

    당장 제 로컬 컴퓨터로 개발을 할 때 첫 번째 방법은 어떻게 해야 하지? 싶더라고요

    그래서 어쩌다 보니 저는 두 번째 방법으로 작업하게 되었는데

    지금 생각해 보면 굳이 docker가 아니어도 AWS EC2 인스턴스 안에 모든 언어의 컴파일러를 설치하고 해당 주소로 콜 하면 될 것 같긴 하더라고요

    방법이야 다양하게 있습니다만... 당장 작업한다고 했을 때 그나마 접근이 쉬운 건 docker일 것 같네요...허허

     

     

     

    Docker를 이용한 프로젝트 배포


    AWS EC2에 배포하는 방법은 여러 가지가 있습니다

    저는 이미 Docker 찍먹 해봤으니 해당 프로젝트를 Docker 이미지로 만들어서 배포하자! 였습니다

    FROM node:20-alpine as frontend
    WORKDIR /app/front
    COPY front/package.json front/package-lock.json ./
    RUN npm install
    COPY front/. .
    RUN npm run build
    
    FROM node:20-alpine as backend
    WORKDIR /app/back
    COPY back/package.json back/package-lock.json ./
    RUN npm install
    COPY back/. .
    RUN npm install -g ts-node typescript
    
    FROM node:20-alpine
    WORKDIR /app
    COPY --from=frontend /app/front/build ./front/build
    COPY --from=backend /app/back ./back
    WORKDIR /app/back
    RUN mkdir -p /app/back/compile
    EXPOSE 80
    CMD ["npm", "start"]

    위 코드는 프로젝트 root 경로에 Dockerfile로 작성해 줬습니다

    front 코드 안에 react 코드가 있고, 이걸 빌드합니다

    그리고 back 코드 안에는 node.js로 구성된 서버 코드들이 있으며,
    (여기에 react 빌드 파일들을 랜더링 해주는 코드를 작성해놔야 합니다)

    최종적으로 서버를 실행시켜서 화면을 보는거라고 생각하면 되겠습니다

    그리고 이 이미지를 미리 만들어놓은 docker hub에 push 해서 어디서든 받아서 사용할 수 있게 해 놓습니다

    최종적으로 이런 식으로 배포했을 때 마주한 에러는 3가지입니다

    • ts-node가 없음
    • env 에러
    • compile이 없음

    ts-node가 없음

    이 부분은 대체 왜...? 에러가 뜨는 건지 의문이더라고요

    npm i -g ts-node로 전역 설치를 해서 해결해 주라는 의견이 제일 많았으나 저는 이 부분으로 해결되지 못했고

    서버 쪽 package.json을 수정해 주니 해결되었습니다

    // 전
    "scripts": {
    	"dev": "nodemon src/app.ts"
    },
    
    // 후
    "scripts": {
    	"start": "nodemon --exec npx ts-node src/app.ts"
    },

    (웃긴 건 경고문 뜨는 거 보면 이 명령어는 무시되는 것 같던데..?)

    env 에러

    이 부분은 빌드하는 과정에서 API 주소가 로컬호스트 주소로 되어있어서 문제였습니다

    은근 놓치기 쉬운 부분입니다..

    npm i env-cmd를 설치해 주고 프런트 쪽 package.json을 수정해 줬습니다

      "scripts": {
        "start": "env-cmd -f .env react-scripts start",
        "build": "env-cmd -f .env.production react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject"
      },

    build 할 때는. env.production을 이용하게끔 설정해 주고

    로컬에 .env.production 파일을 만들어서 제 EC2 주소를 적어줬습니다

    compile이 없음

    이거는 제가 끝내 해결하지 못한 이슈 입니다 이거 때문에 docker 배포를 포기했고요 (...)

    이 당시 EC2에 배포가 잘 되었고 화면도 잘 뜨며 API를 통해 문제도 잘 받아오고 있던 상태였습니다

    근데..! 클라이언트에 코드를 작성해서 보낼때 socket.io에서 무한 polling이 뜨면서

    net::ERR_CONNECTION_REFUSED 라는 에러가 발생하더라고요

    저는 별도의 다른 서비스? 같은거 없이 EC2에 docker 이미지를 pull 받고 컨테이너 띄어서 실행시킨 상태라 제대로 된 에러를 발견하기도 어려운 상태였습니다

    그리고 검색했을때 socket.io의 net::ERR_CONNECTION_REFUSED 라는 에러에 대한 솔루션도 딱히 발견하지 못 했습니다...

    나중에 제가 다른 방식으로 배포했을때 알게된 점이 EC2 인스턴스 내부에 compile이란 폴더가 없던게 문제였습니다

    클라이언트에게 받은 코드를 compile이란 폴더 안에 컴파일 해야하는데 그걸 못 찾았던거였습니다

    그래서 일단 가장 먼저 docker 컨테이너 내부에 compile 폴더를 생성하기도 해봤는데요

    FROM node:20-alpine
    WORKDIR /app
    COPY --from=frontend /app/front/build ./front/build
    COPY --from=backend /app/back ./back
    WORKDIR /app/back
    RUN mkdir -p /app/back/compile
    EXPOSE 80
    CMD ["npm", "start"]

    Dockerfile 코드중 RUN 명령어가 이를 위해 작성된거였습니다

    근데 역시나 해결되지 못했고...

    EC2 로컬에 compile 폴더를 생성해보기도 했는데 역시 안되더군요...
    (지금 생각해보면 로직을 수정해서 로컬 어디든 compile 폴더를 찾을 수 있게끔 하면 될 것 같..기도....?)

    결국 이유를 몰라서 저는.... Docker를 통핸 플젝 배포를 포기하게 되었습니다

     

     

    git clone을 이용한 프로젝트 배포


    결국 프로젝트 자체를 EC2 내부에서 clone 받아 실행시키는 형태로 진행했습니다

    이때 마주친 에러는 4가지 입니다

    • node 버전 이슈
    • compile 폴더 없음
    • env 없음
    • npm run build 안됨

    node 버전 이슈

    clone 받고 (해당 플젝)/back 으로 이동해서 npm i를 실행했을때 이슈였던 것 같습니다

    node: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by node) 이란 에러가 터미널에 뜨더라고요

    스택오버플로우 답변을 보고 해결했습니다

    compile 폴더 없음

    이건 제가 바보같이 놓쳤던...

    클라이언트 코드를 git에 push 하면 안되니까 안했더니 github 레포에서는 compile 폴더가 없던게 문제였습니다

    이건 EC2 내부에서 플젝 폴더 내에 직접 알맞은 경로에 생성해주면 됩니다

    env 없음

    이것 또한 당연히 github 내에 env 파일을 올리지 않으니까 그렇습니다

    직접 EC2 내부에서 vim 명령어로 파일을 만들어주면 됩니다

    npm run build 안됨

    이게 결정적인 큰 이슈였는데요...

    정석은 EC2 내부에서 플젝을 clone하고 프론트 코드를 빌드해서 실행시키는게 정석입니다

    근데 왜... 왜.... npm run build가 영원히 끝나지 않는 현상이 발생됩니다(?)

    로컬에서는 3분이면 끝날 빌드가 EC2 내부에서는 끝나질 않습니다

    에러가 별도로 뜨는 것도 아니고 그냥 계속 빌드중에 멈춰있습니다

    검색을 해보니 그닥... 해결법도 없고 메모리 부족 문제가 발생하는 경우도 있더라고요? (관련글)

    혹시 몰라 저도 해당 솔루션을 진행하고 다시 빌드해보았으나 전혀 효과가 없었습니다

    한 30분 정도 기다려봤는데도 변화가 없더군요... 1시간 기다리면 또 어떨지 모르겠네요...ㅎ...ㅋ....

    그래서 저는 결국 로컬에서 build하고 그 build 폴더 자체를 github 레포에 push 해버렸습니다

    좋은 방법이 아닌데.... 어쩔수 없었습니다 😭

     

     

    그 외 이슈


    11시간을 매달려 정신없이 해결하다 보니 사실상 기억이 좋지 못합니다....😵

    마주했던 이슈중 하나는

    제 코드상 문제점은 클라이언트가 코드상 실수를 하거나, 제 로직상 문제가 발생했을때

    return이 되지 않아 서버가 재실행되지 못하는? 문제가 있었습니다

    로컬에서는 서버쪽 코드를 저장하면 다시 실행되니까 그냥 넘겼다가

    실제 배포하고 실행할때 문제더군요... return문을 꼭 넣어주는거... 잊지말아야겠습니다

     

    그리고 CORS였나? git clone으로 배포했을때 문제가 있었는데

    서버쪽 포트를 80으로 맞춰놨더니 CORS 에러가 뜨더군요 (CORS 아닐 수 있음 쨌뜬 무슨 에러 뜸)

    그래서 8080으로 바꾸고 80으로 들어왔을때 8080으로 리다이렉트 해주는 명령어를 쳐줬던 것 같아요

    클라이언트에서는 80으로 보내는게 맞고, 서버 포트는 80이 아닌 다른 포트여야하는...? 그런게 있더군요?????

     

    또한 저는 코드를 실행시킬 담당을 하는 docker 컨테이너를 유동적으로 실행시키고 싶었습니다

    docker image가 없으면 그 image를 pull 해오고, 컨테이너를 띄우는 쪽으로 하고 싶었는데요

    이유는 그냥... 같잖은 보안 걱정이었습니다 (ㅎㅎ...)

    그래서 소켓 연결이 끊기면 컨테이너도 삭제되게 하고 싶었고요

    // 문제에 최초 접근했을때
    const imageExists = execSync(`docker images -q kyunga/all-lang:latest`)
      .toString()
      .trim();
    
    // 이미지가 없으면 생성
    if (!imageExists) {
      dockerBuild();
      execSync(`docker run --rm -d -it --name test-app kyunga/all-lang:latest`);
    } else {
      exec(`docker run --rm -d -it --name test-app kyunga/all-lang:latest`);
    }
    
    // 연결에 끊겼을때
    socket.on("disconnect", () => {
      exec("docker stop test-app");
      exec("docker rm test-app");
    });

    이런식의 코드를 작성해놨던 상태였습니다

    로컬에서는 문제가 없었는데...

    배포하면 안되더라고요? 계속 docker 에러를 반환하는데 에러문은 정확히 기억나지 않습니다

    그래서 해결 방법은 결국 컨테이너를 늘 가동 상태로 두는 것이었습니다......

     

     

     

    마무리


    정말..... 힘든 여정이었습니다...... 포기 해야하나 싶었어요........................

    내가 괜히 내 실력에 과분한(?) 플젝을 해서... 해결도 못하고 결국 배포도 못하는구나 싶었네요...

    하지만 결국 이 삽질과 시간 버리기에 모든 원인은 compile 폴더의 부재가 가장 컸고요^^.....

    근데 확실히 vercel 배포를 처음으로 경험해보고 EC2 배포 하려니 확실히 어렵네요

    이렇게....! 4년전 헤로쿠 배포를 마지막으로 정말 오랜만에 해본 플젝 배포!!! 

    정말 많은 경험을 얻어가는 삽질이었습니다

    반응형

    댓글