[docker] 도커 컨테이너 실행 제대로 이해하기

현서황·2024년 11월 3일

Docker

목록 보기
9/9

Node.js 앱 만들기


먼저,

Node.js 앱이란?

Node.js 앱은 Node.js 런타임 환경에서 실행되는 애플리케이션이다. Node.js는 자바스크립트(JavaScript)코드를 서버 측에서도 실행할 수 있게 만들어주며, 주로 웹 서버, API서버, 또는 백엔드 애플리케이션을 개발하는 데 사용된다.

일반적으로 Node.js앱은 자바스크립트코드, 패키지관리(package.json), 주요 프레임워크(Express.js)로 구성된다.
1. JavaScript 코드
Node.js는 자바스크립트를 사용하여 서버 기능을 구현할 수 있다. 기본적인 웹 서버나 데이터베이스 연결, 파일 시스템 제어 등을 모두 자바스크립트로 작성할 수 있다.
2. 패키지 관리
Node.js앱에서는 npm(Node Package Manager)을 통해 다양한 라이브러리와 모듈을 설치하고 관리한다. package.json파일에 의존성 정보가 저장되어 있어 필요한 라이브러리들을 손쉽게 설치하고 관리할 수 있다.
3. 주요 프레임워크
Node.js 앱에서 가장 널리 사용되는 웹 프레임워크는 Express.js이다. Express는 웹 서비스나 RESTful API를 쉽게 구축할 수 있도록 해준다. 예를 들어, Express를 사용하면 클라이언트의 요청을 처리하고 응답을 반환하는 웹 서버를 간단하게 만들 수 있다.


Node.js 앱을 만드는데 필요한 두 가지 파일

  1. package.json
    프로젝트의 정보와 프로젝트에서 사용 중인 패키지의 의존성을 관리하는 곳이다.
    npm init 명령어를 사용해 만들 수 있다.
    (Node.js와 WSL설치가 되어있어야한다!)

    dependencies의 express부분을 추가한 이유는 Node.js를 더욱 쉽고 유용하게 사용할 수 있도록 하기 위함이다.
    Javascript와 jQuery의 관계처럼 Node.js의 API를 단순화하고 새로운 기능을 추가해준다.

  2. server.js
    Node.js에서 진입점이 되는 파일이다.

    const express = require('express'); : express 모듈 불러온다.
    const PORT = 8080; : express 서버를 위한 포트 설정
    const app = express(); : 새로운 express어플 생성
    app.get('/',(req,res)=>{ res.send("Hello World") });
    : "/" 이 경로로 요청이 오면 Hello World를 결과값으로 전달
    app.listen(PORT);: 해당 포트에서 HTTP서버를 시작

이렇게 두 파일을 이용해 기본적인 Node.js애플리케이션을 완성했다.
이제 이 Node.js 앱을 도커 환경에서 실행하기 위해서 도커와 관련된 부분을 만들어야한다.


Dockerfile 작성하기

Node.js앱을 도커 환경에서 실행하려면 먼저 이미지를 생성하고 그 이미지를 이용해서 컨테이너를 실행한 후 그 컨테이너 안에서 Nodejs앱을 실행해야한다. 그래서 그 이미지를 먼저 생성하려면 Dockerfile을 먼저 작성해야한다.
Dockerfile만들기 내용 복습 - FROM,RUN,CMD 개념
도커파일,도커이미지,도커 컨테이너 간의 관계 도식화 그림
FROM,RUN,CMD에 명시한 것 각각이 무엇인지는 밑에서 차차 설명할 예정이다.

Dockerfile 변경코드

alpine 베이스 이미지 대신 node이미지 사용하는 이유?

Dockerfile에서 FROM을 apline으로 변경한 버전 - 오류발생!
alpine으로 하면 npm not found 오류가 나는 것을 확인할 수 있다.
그 이유는 alpine이미지는 가장 최소한의 경량화된 파일들이 들어있기에 npm을 위한 파일이 들어있지 않아서 RUN부분에 npm install을 할 수가 없다.
알파인 이미지의 사이즈는 5MB정도밖에 안된다.
그래서 이미지 사이즈가 더 큰 node를 사용해야 오류가 발생하지 않는다.

npm install?

  • npm은 Node.js로 만들어진 모듈을 웹에서 받아서 설치하고 관리해주는 프로그램이다.
  • npm install은 package.json에 적혀있는 종속성들을 웹에서 자동으로 다운받아 설치해주는 명령어이다.
    • 그러니까, package.json내부의 dependencies를 설치해주는 역할!
  • 결론적으로는 현재 Node.js앱을 만들 때 필요한 모듈들을 다운받아 설치하는 역할을 한다.

CMD 안의 "node","sever. js"?

  • 노드 웹 서버를 작동시키려면 "node"와 "엔트리 파일 이름"을 입력해야 한다.

이제, 만들어둔 Dockerfile을 docker build .으로 실행시켜보면 오류가 나는 것을 확인할 수 있다.

오류 화면을 보면, RUN npm install 부분에서 오류가 발생한다.
원인이 무엇일까?


이미지를 빌드할 때 package.json 파일이 없다고 나오는 이유

지금 우리는 docker의 이미지를 만들고 있다. 이미지를 만들 때에는 Node 베이스 이미지를 통해 임시 컨테이너를 만든 다음에 임시 컨테이너로 실제 사용할 이미지를 만드는 것이다. 베이스 이미지에는 파일 스냅샷이 존재한다. 그래서 그것을 하드디스크에 넣어주고, RUN에 있는 npm install을 하게 된다.
RUN은 추가적으로 필요한 파일들을 다운로드 받는 역할을 한다.
RUN에 쓴 npm install은 package.json파일에 명시된 모든 패키지를 설치하는 역할을 수행한다.
Node.js 프로젝트에서는 package.json 파일에 의존성 정보가 포함되어 있으며, npm install을 실행하면 해당 의존성들이 node_modules 폴더에 설치됩니다.

그러나, package.json이 컨테이너의 밖에 존재하여 파일을 보고 설치하는 것이 불가능하다.

그 이유는 Node 베이스 이미지의 스냅샷이 임시 컨테이너의 하드디스크로 들어오게 되는데, Node 베이스 이미지의 스냅샷에는 두 파일이 없기 때문이다.

아래의 사진을 보면 이 말을 더욱 잘 이해할 수 있다.
package.json이 임시컨테이너 밖에 존재하는!


그렇다면, 이 문제를 어떻게 해결해야할까?

COPY를 이용해 Package.json을 컨테이너 안으로 넣어주기


Dockerfile내부에 COPY를 적은 뒤,
docker build -t <docker account>/<앱 이름>을 통해 실행시켜보았다.

빌드가 잘 되는 것을 확인할 수 있다.

이제 run을 시켜볼건데, 잘 되는지 확인해보기 위해

이렇게 콘솔 로그를 server.js에 넣어주었다.
그리고 다시 빌드시킨 후 run해보면,

이렇게 잘 실행되는 것을 확인할 수 있다.

하지만, localhost:8080주소로 가보면, 실행이 되지 않은 것을 확인할 수 있다.


생성한 이미지로 애플리케이션 실행 시 접근이 안 되는 이유(포트 매핑)

위에서 컨테이너를 실행하기 위해 사용한 명령어는 다음과 같다.

여기에서 추가해야할 부분이 있다.

이미지를 만들 때 로컬에 있던 파일(package.json)등을 컨테이너에 복사해주어야했다.
그것과 유사하게 네트워크도 로컬 네트워크에 있던 것을 컨테이너 내부에 있는 네트워크에 연결을 시켜주어야한다.

run -p 명령어 실행화면
브라우저에서 5001번 포트로 접속하고, 컨테이너 내부에서 컨테이너 속 8080포트로 접근하는 방식이다.
포트 접근방식 도식화 그림

localhost:5001 실행화면
이제 hello world가 잘 보이는 것을 확인할 수 있다!


WORKING DIRECTORY 명시해주기

도커 파일에 WORKDIR이라는 부분을 추가해주어야 한다.

이미지 안에서 애플리케이션 소스 코드를 가지고 있을 디렉터리를 생성하는 것이다. 그리고 이 디렉터리가 애플리케이션에 working directory가 된다.

그런데, 왜 따로 working directory가 있어야할까?

현재 만들어놓은 이미지의 Root 디렉토리를 살펴보기 위해
docker run -it <이미지이름> sh
명령어를 사용했고, 그 터미널 안에서 ls 명령어를 입력했다.

여기 파일 중에서 COPY를 해서 컨테이너 안으로 들어온 것들은 다음과 같다.

  • Dockerfile
  • package.json
  • package-lock.json
  • node_module
    이렇게 workdir을 지정하지 않고 그냥 COPY할 때 생기는 문제점
  1. 원래 이미지에 있던 파일과 이름이 같다면?
    예를 들어, 베이스 이미지에 이미 home이라는 폴더가 있고 COPY를 함으로써 새로 추가되는 폴더 중에 home이라는 폴더가 있다면 중복이 되므로 원래 있던 폴더가 덮어씌워져버린다.
  2. 모든 파일이 한 디렉토리 내부에 들어가게 되어 정돈이 되어있지 않아 식별이 어렵다.

그래서 모든 어플리케이션을 위한 소스들은 WORK 디렉토리를 따로 만들어서 보관한다.

WORKDIR 추가된 Dockerfile 코드
Dockerfile에 WORKDIR을 추가시켰다.

build화면
디렉토리 내부 확인
처음 ls를 실행한 결과에 COPY된 것들만 존재한다. 그 이유는 WORKDIR을 지정하면 root가 아니라 WORKDIR로 가게 되기 때문이다. 따라서 처음 ls실행환경이 WORKDIR로 지정했던 /usr/src/app인 것이다.
그 후, cd /를 통해 root로 가서 ls를 치면, 원래 존재하는 다른 디렉토리들이 있는 것을 확인할 수 있다.


애플리케이션 소스 변경 시 재빌드 문제점

어플리케이션을 만들다보면 소스 코드를 계속 변경시켜주어야하며 그에따라 변경된 부분을 확인하면서 개발을 해나가야한다. 그래서 도커를 이용해 어떻게 실시간으로 소스가 반영되게 하는지 알아본다.

현재까지 만든 어플을 소스 변경 시 변경된 소스를 반영시켜주기 위해서는
현재까지 도커 컨테이너로 어플을 실행한 순서를 보면

그러면, 이러한 비효율적인 부분을 어떻게 해결할까?


애플리케이션 소스 변경으로 재 빌드 시 효율적으로 하는 법


위의 도표를 보면, 완성본 Dockerfile에는 RUN 위에 COPY package.json ./이 하나가 추가되고 원래의 COPY가 RUN 아래로 내려갔다.

그 이유는 무엇일까?
npm install할 때 불필요한 다운로드를 피하기 위해서이다.
코드 변경이 있을 때 문제점) COPY ./ ./ 명령어로 모든 파일을 복사하게 되면, 작은 소스 코드 변경이 있어도 Docker는 npm install 단계 이전의 모든 캐시를 무효화하고, 모듈을 다시 다운로드하게 된다.

그래서 npm install 단계 전에는 의존성파일(package.json)만 COPY해서 필요한 모듈만 설치하고, 그 이후에 다른 파일들을 다시 COPY해서 Docker이미지가 효율적으로 빌드되도록 한다.

이렇게 함으로써 코드 수정 시 npm install 이전 단계를 다시 실행하지 않아도 되며, 불필요한 다운로드를 방지할 수 있다.


Docker Volume

이제는 npm install 전에 package.json만 따로 COPY 해주어서 모든 캐시를 무효화하고 모듈을 다시 다운로드 하는 일을 없앴다.

하지만 아직도 소스를 변경할 때마다 변경된 소스 부분은 COPY한 후 이미지를 다시 빌드를 해주고 컨테이너를 다시 실행해주어야지 변경된 소스가 화면에 반영이 된다.

이러한 작업은 너무나 시간 소요가 크며 이미지도 너무나 많이 빌드하게 된다.

이러한 문제점을 해결하기 위해!!! Volume을 사용한다.

💻 Docker Volume?
지금까지는 컨테이너에 소스 코드를 COPY로 넣어주었다.

COPY는 호스트 디렉터리에 있는 파일을 도커 컨테이너네 복사하는 것이고, 볼륨은 도커 컨테이너에서 호스트 디렉터리에 있는 파일들을 매핑해서 사용한다.

Volume을 사용하여 어플리케이션을 실행해보려면,
docker run -p 5000:8080 -v /usr/src/app/node_modules -v $(pwd):/usr/src/app <이미지 아이디>

  • -p 5000:8080: 호스트의 5000번 포트를 컨테이너의 8080포트에 매핑한다.
  • -v /usr/src/app/node_modules: node_modules 폴더를 호스트에 공유하지 않고, 컨테이너 내부에만 유지시킨다.
    • 호스트 시스템에는 node_modules폴더를 포함하지 않는 이유는 컨테이너 내부에서만 필요한 의존성 파일을 관리하려는 것이다. 이는 호환성 문제를 줄이고, Docker의 독립적 환경 내에서 의존성을 유지할 수 있도록 도와준다.
  • -v $(pwd):/usr/src/app: 현재 작업 디렉토리($(pwd))를 /usr/src/app경로에 매핑하여 소스 코드 파일이 컨테이너와 동기화되도록 한다.


일단, "Hello World" 출력하는 것에서 "안녕하세요."로 소스코드를 변경했다.

재빌드 하지 않고 볼륨도 사용하지 않고 서버 실행시키면,

이렇게 기존대로 실행이 된다.


재빌드 없이, 도커 볼륨을 이용해서 실행해보았다.

변경한 코드가 반영된 것을 확인할 수 있다!!

profile
노는 게 제일 좋은 뽀로로

0개의 댓글