사용된 코드는 다음과 같다
import express from 'express';
import connectToDatabase from './helpers.mjs'
const app = express();
app.get('/', (req, res) => {
res.send('<h2>Hi there!</h2>');
});
await connectToDatabase();
app.listen(3000);
만약 로컬 환경에서 해당 애플리케이션을 구동하고자 한다면 npm install
을 통하여 종속성을 다운로드 받게 될것이다
그렇다면 도커 컨테이너에서 nodeJS 앱을 실행시키려면?
궁극적으로 도커 컨테이너 위에서 nodeJS 앱을 실행 하기 위해서는 환경에 대한 설정이 있어야 한다
반드시 도커가 실행되고 있고 Dockerfile 에서 대소문자 구분을 정확히 하여 디렉토리내에 파일을 생성하여야 동작한다 ! 😊 덕분에 삽질 20분
이제 이미지가 생성 되었으니 컨테이너에 올려 시작해보자
docker run -p 3000:3000 {imageID}
Docker 컨테이너를 실행시킨뒤 3000 포트에 접근하면 “/” 에 잘 매핑되어 나오는 것을 확인 해볼 수 있다
실행중인 도커 리스트를 갖고와 중지시키고 싶은 이름을 통하여 docker stop epic_snyder 명령어를 통해 컨테이너를 중지한다
당연히 컨테이너가 종료되고 난 이후에는 3000포트에 접근하여도 애플리케이션이 실행되어있지 않음을 확인 해볼 수 있다
컨테이너는 애플리케이션, 웹사이트 , 노드서버, 애플리케이션을 실행하는 전체 환경 등 무엇이든 포함하는 작은 패키지 → 컨테이너에 소프트웨어 실행 유닛이 존재
즉, 이미지는 템플릿으로 볼 수 있으며 컨테이너의 블루프린트가 됨
이미지는 실제로 코드와 코드를 실행하는데 필요한 도구를 포함한다
그리고 이 이미지를 기반으로 컨테이너가 실행되어 코드를 실행하는 것이다
이미지의 장점은 하나의 이미지 기반으로 여러 컨테이너를 만들 수 있다. 이말은 한번만 이미지를 정의하면 다른 시스템과 다른 서버에서 여러 번 실행할 수 있다.
(이미지는 모든 설정 명령과 모든 코드가 포함된 공유 가능한 패키지이기 때문)
👀 컨테이너는 이미지의 구체적인 실행 인스턴스일 뿐, 이미지를 기반으로 하는 컨테이너를 실행하는 것 그것이 도커가 하는일
컨테이너는 이미지를 실행 시키는 실행 유닛이라 하였을 때 실제로 컨테이너를 실행할 수 있도록 이미지를 생성하거나 가져오거나 두가지 선택지가 있다
이렇게 다른 이들이 만든 공식 이미지를 가져오고 싶다면 Docker Hub 를 주로 사용하여 쉽게 찾을 수 있다
가령 Spring 과 관련된 이미지를 찾고 싶다면
공식이미지/커뮤니티로 나뉘어져 찾고자 하는 이미지를 찾아 pull 하여 사용 가능하다
예제로 node Js 를 사용하고자 한다면 CMD 에서 docker run node 라고 입력하였을 때 도커는 공식 nde 이미지를 기반으로 하는 컨테이너를 생성할 것이다
다음과 같이 컨테이너에 node 라는 이미지를 찾고 없다면 docker hub로부터 pull 하여 다운받게 된다
확인하고자 한다면 docker ps -a
를 통해 현재 도커가 생성한 모든 컨테이너, 모든 프로세스가 표시된다.
물론 stop {이미지이름}
을 한다면 중지시킬 수 있다
하지만 Status 를 확인해보면 실행되지 않는 다는 것을 알수 있는데 이는 컨테이너가 격리되어 실행되기 때문이다. 노드 이미지를 기반으로 하는 컨테이너로 실행했지만 이것만으로는 의미가 없다.
✔ : 노드에 의해 노출된 인터렉티브 쉘은 컨테이너에 의해 자동으로 노출되진 않기 때문
그렇기에 -it 플래그를 통하여 도커 컨테이너에게 내부 호스팅 머신으로 node 를 노출 시키겠다고 명시하여 줘야 한다
→ 인터렉티브 노드 터미널에 들어감
-it 플래그를 추가해주는 것만으로 컨테이너 내부에서 노드를 실행하여 상호작용이 가능하게 해준다
실제로 node가 로컬 환경에서 설치되지 않은 환경에서도 컨테이너에서 실행 되는 것을 확인.
이미지는 컨테이너에 필요한 모든 로직과 코드를 보관하는데 사용되며 docker run 을 통하여 이미지의 인스턴스를 만들게 된다. 이것은 이미지를 기반으로 하는 구체적인 컨테이너를 실행하게 된다
다음과 같이 같은 노드 이미지를 사용하더라도 여러 개의 컨테이너에서 동작할 수 있으며 -it 플래그를 통하여 node 를 노출시켰기 때문에 같은 노드 이미지로 두개의 컨테이너가 생겼음을 알 수 있다
일반적으로는 공식 이미지를 사용하기 보다는 자신의 애플리케이션 환경에 맞는 이미지를 커스텀하여 생성하는 것이 일반적이다.
즉, docker hub 로부터 베이스가 되는 공식 이미지 위에 코드를 추가하여 이미지를 생성하는 것이다
간단한 노드 앱을 통해 실습을 거친다
FROM
을 통하여 다른 베이스 이미지에 커스텀 이미지를 구축할 수 있다
이론적으로 도커 이미지를 처음부터 빌드할 수 있겠지만 언제나 코드에 필요한 기타 도구와 같은 운영체제 레이어가 필요할 수 있기 때문에 BASE
가 되는 이미지를 통하여 커스텀하는 것이 일반적이다
FROM 을 통하여 컨테이너에 이미 생성한 이미지나 없다면 docker hub 로부터 자동으로 pull 되어 참조된다
이전에 이미지가 로컬로 다운로드 되어 캐시로 남아있기 때문에 현재는 로컬 머신에도 존재하게 된다
다음으로는 어떤 파일이 이미지에 들어가야 하는지 명시해주어야 한다
가장 쉬운 방법은 COPY . .
이다
( 첫번째 . 은 컨테이너의 외부, 이미지의 외부경로이며 이미지로 복사 되어야할 파일들이 있는 곳 ; 만약 . 만 사용된다면 도커에게 기본적으로 Dockerfile 이 포함된 동일한 파일임을 말하는것임. 즉, 프로젝트 내의 모든 폴더 하위폴더 / 두번째 . 은 이미지 내부의 경로. 모든 이미지와 이미지를 기반으로 생성된 모든 컨테이너에는 로컬 머신의 파일 시스템에서 완전히 분리된 내부 파일 시스템이 있는데 루트 엔트리를 사용하지 않고 사용자 지정 서브 폴더를 사용하는 것이 좋다 ;. 을 사용하게 되면 루트 엔트리임)
🤔 만약 사용자 지정 파일이 없다면? FileNotExist Error ? 이 아니라 지정한 디렉토리를 컨테이너 내부 파일 시스템에 새롭게 생성해준다
RUN 을 통하여 디펜던시들을 받아준다. 디폴트로 이러한 모든 명령은 도커 컨테이너 및 이미지의 작업 디렉토리에서 실행된다. 그리고 이 디폴트 작업 디렉토리는 컨테이너의 루트 폴더인데 만약 사용자 지정 디렉토리에서 복사를 진행하였다면 npm install 과 같은 작업은 해당 디렉토리에서 실행되어야 할 것이다.
이것을 위해 RUN 이 실행되는 작업 디렉토리를 WORKDIR을 통하여 지정해줄 수 있다. 이는 모든 실행 커맨드들이 지정한 폴더내에서 실행됨을 명시한다
👀 만약 WORKDIR 을 명시해줬다면 작업 디렉토리는 default 로 사용자 지정 폴더로 바뀌게 되는데 COPY 에서 따로 파일을 지정하지 않고 . 을 통해서 바로 작업 디렉토리에 복사를 진행 할 수 있다
물론 절대 경로로 지정해도 문제는 안된다
이제 남은 것은 노드 앱을 시작하는 것이다. 이곳에서 흔히 할 수 있는 실수는 RUN application 이 될것이다. 만약 이렇게 된다면 이미지를 불러올 때 마다 해당 애플리케이션이 실행될것이다.
앞서 보았듯 이미지는 템플릿의 역할을 수행하여야 하며 블루프린트인 이미지에게 실행을 위임하게 되면 여럿 문제가 발생할 수 있다. 즉, 실행은 전적으로 컨테이너에게 위임해주어야 한다
즉 앞서 했던 작업은 하나의 템플릿 (이미지) 를 만들기 위한 커맨드로서 환경에 대한 작업, 컨테이너에 디펜던시를 다운받을 수 있기 위해, 그리고 코드들을 디렉토리로 복사하기 위한 일련의 블루프린트를 만드는 과정이라고 할 수 있다
그렇기에 CMD 커맨드를 통하여 이미지를 기반으로 컨테이너가 시작될 때 애플리케이션이 시작될 수 있도록 하는 것이 맞다
✔ : CMD 의 경우 array 로 파라미터를 넘겨줘야 하는데 run 에서의 node server.js 라면 ["node", "server.js"] 를 통하여 노드 명령어로 애플리케이션을 시작할 수 있도록 해준다
가장 중요하게 컨테이너는 격리되어 있기에 로컬 환경에서 선언한 포트 8080 에서 실행하겠다는 것을 알수가 없다
FROM node
WORKDIR /app
COPY . /app
RUN npm install
EXPOSE 80
CMD ["node", "server.js"]
도커 파일을 통하여 이미지파일에 대한 설정등을 등록하였다면 빌드를 최우선적으로 해주어야한다
만약 현재 프로젝트 내에 도커파일이 있다면 docker build . 을 통하여 해당 프로젝트 디렉토리에서 docker file 을 찾아 빌드까지 해주게 된다
그리고 docker run 을 통하여 해당 이미지를 실행하게 되면
다음과 같이 계속 실행됨을 알수 있음. 이는 CMD 에서 node 서버를 시작했기 때문에 노드 서버는 완료되지 않고 진행 중인 프로세스가 됨. 즉, 컨테이너 또한 실행을 계속 유지
하지만 컨테이너를 실행하였음에도 해당 포트에 접근하였을 때 앱이 동작 하지 않는 모습을 볼 수 있다
이는 EXPOSE 80 을 통하여 포트를 명시하긴 했지만 EXPOSE 는 문서화의 목적으로만 추가되었을 뿐이기 때문이다. 즉, EXPOSE 명령어는 명시만 할뿐 컨테이너에게 포트를 지정해주진 않는다.
컨테이너가 지정한 포트를 listening 하게 하고 싶다면 -p 를 사용하여 publish 과정을 거쳐야 한다
-p : 로컬포트가 내부 도커의 특정 포트에 액세스 할 수 있는지 명시함
Ex ) -p {로컬환경에서 열고자하는 포트}:{내부 도커 컨테이너 노트}