제목과 같은 글을 적게 된 건 온전히 효율성이 떨어지는 빌드와 도커 컨테이너를 계속적으로 생성했기 때문에 이 글을 작성한다.
레이어의 구조
Docker 이미지는 컨테이너를 생성하기 위한 템플릿 역할을 한다. 이 이미지는 여러 개의 읽기 전용 레이어로 구성되어 있다.
위의 사진을 보면, ubuntu, nginx, web app을 이루어 docker image가 생성된다. 각 레이어는 이미지의 변경 사항을 포함하며, 이는 새로운 파일을 추가하거나 기존 파일을 수정하거나 삭제한다. Docker는 이러한 레이어들을 효율적으로 관리하여 이미지 빌드 속도를 향상시키고, 저장 공간을 절약한다.
이 이미지들이 모여 컨테이너를 만들고, 하나의 이미지로 여러개의 컨테이너를 만든다.
도커 이미지를 만들기 위해서는 dockerfile을 만들어야 한다.
Dockerfile
베이스 이미지(Base Image)
Docker 이미지의 첫 번째 레이어는 베이스 이미지(Base Image)이다. 이는 운영 체제의 기본 파일 시스템을 포함하며, 다른 모든 레이어의 기반이 된다.
중간 레이어(Intermediate Layer)
베이스 이미지 위에 추가되는 레이어들은 중간 레이어(Intermediate Layer)이다. 각 중간 레이어는 Dockerfile의 명령어에 의해 생성된다. 예를 들어, RUN, COPY, ADD 명령어는 새로운 중간 레이어를 생성한다.
최종 레이어(Final Layer)
최종 레이어는 컨테이너를 실행하기 위해 필요한 모든 파일과 설정을 포함한다. 이 레이어는 읽기 전용이며, 컨테이너가 생성될 때 읽기-쓰기 레이어가 이 위에 추가된다.
FROM node
WORKDIR /app
COPY . /app
RUN npm install
EXPOSE 80
CMD ["node", "server.js"]
레이어의 특성
- 불변성(Immutability): 한 번 생성된 레이어는 변경될 수 없다. 변경이 필요하면 새로운 레이어가 생성된다.
- 캐싱(Caching): Docker는 레이어를 캐싱하여 이미 빌드된 레이어를 재사용한다. 이는 이미지 빌드 속도를 크게 향상시킨다.
- 공유성(Shareability): 동일한 레이어는 여러 이미지 간에 공유될 수 있다. 이는 저장 공간을 절약하는 데 도움이 된다.
이미 빌드된 이미지를 수정 사항없이 재빌드 한다 했을 때 시간이 엄청 단축되는 것을 볼수 있다. 이는 캐시 때문이다. 도커는 기본적으로 모든 명령어에 대해 명령어를 다시 실행했을 때의 결과가 이전과 동일하다는 것을 인식한다. 이미지를 빌드할 때마다 도커는 모든 명령 결과를 캐시하고 이미지를 다시 빌드할 때 캐시를 사용하여 명령을 다시 실행할 필요가 없다면 캐시된 결과를 사용한다.
이를 “레이어 기반 아키텍처” 라고 한다.
만약 프로젝트 내의 html 에서 작은 변화가 일어 났다고 가정했을 때, 이를 수정해서 이미지를 기반으로 컨테이너를 실행해도 변경이 되지 않는다. COPY . . 을 진행 할 때에 기본적으로 소스 코드를 이미지에 복사하고, 복사한 시점에서 소스 코드의 스냅샷을 만든다.
Html 의 작은 수정이 일어난 이후의 스냅샷은 현재 수정된 이후의 이미지와 달라야 한다. 즉 어떠한 작은 변화가 일어났더라도 새롭게 이미지를 만들어야 한다.
이러한 레이어 개념을 사용한 도커를 빌드하고 도커 컨테이너를 생성하는데 2분에서 3분정도의 시간을 기다리는 것이 싫었다. 앱의 용량이 더 커지면 더 시간이 소모될 것이다.
Volume 활용하기
위의 개념으로 인해서 새로 빌드하는 것이 싫었던 구글링을 진짜 많이 했다. 그런데 이미지를 수정하라는 말이 있어서 이를 해보니 수정은 되는데 반영이 되지 않았다. 이를 참고하실 분은 참고 사이트에 있으니 참고 바란다.
나의 경우 이미지 수정이 되지 않았기에 volume을 가지고 코드를 수정해 실시간 반영을 하고자 하였다. 우선 내 경우 express 서버를 가지고 도커에 올렸다. 그래서 express가지고 설명을 할 것이다.
express 프로젝트 셋팅하기
npm init -y
npm을 설치하여 기본 셋팅을 해준다.
npm install express
기초 셋팅을 마쳤다면, express를 설치해준다.
// index.js or app.js
const express = require("express");
const app = express();
const port = 3000;
app.get("/", (req, res) => res.send("Hello Express!"));
app.listen(port, () =>
console.log(`Example app listening at <http://localhost>:${port}`)
);
index.js 혹은 app.js이 생성되었다면 위와 같이 작성해준다.
"scripts": {
"start": "node ."
}
package.json에 들어가서 scripts부분에 start에 node .와 같이 적혀 있는지 확인해준다.
npm i supervisor
그리고 supervisor나 nodemon을 설치해준다. 이를 설치해주는 이유는 실시간으로 코드 변경을 감지하기 위하여 설치해주는 것이다. 둘 중 하나만 설치해준다.
"scripts": {
"start": "supervisor app.js"
}
💡그리고 나서 start부분에 supervisor app.js으로 변경해준다.
이는 실시간으로 app.js을 구동해준다는 의미이다.
위의 작업이 끝났다면 express 서버를 구동할 작업이 다 끝난 것이다. docker가 설치되었다는 가정하에 dockerfile을 작성해보자.
dockerfile 작성하기
# 노드:버전-alpine로 베이스 이미지를 생성해준다.
FROM node:18-alpine
# 도커 컨테이너 안의 작업 디렉토리를 설정해준다.
WORKDIR /app
# package.json와 package-lock.json를 작업 디렉토리에 복사해준다.
COPY package*.json ./
# dependencies를 설치해준다.
RUN npm install
# 나머지 코드들도 복사해준다.
COPY . .
# 본인이 사용하는 port를 설정해준다.
EXPOSE 3000
# 앱에서 구동되어지는 command를 설정해준다.
CMD ["npm", "start"]
Dockerfile에서 작성된 순서는 매우 중요하며, 임의로 바꾸지 않는 것을 매우 추천한다.
위와 같이 작성이 끝났다면, dockerfile에서 작성된 순서대로 명령어가 실행된다. image 빌드가 이대로 이루어진다.
.dockerignore 파일을 생성하여 아래와 같이 작성해준다.
node_modules
위의 파일을 생성하는 이유는 node_modules와 같은 파일을 빌드하면 image가 너무 커지기 때문에 이를 제외하기 위하여 작성해준다.
도커 이미지 생성 및 도커 컨테이너 생성
docker build --no-cache -t my-express-app .
위의 명령어는 도커를 빌드해주는 명령어이다. 캐시를 삭제하고 새로 my-express-app을 생성해준다는 의미이다. 여기서 my-express-app를 본인의 도커 이미지 이름으로, .를 express 파일이 있는 위치로 설정해주면 된다.
docker run -d --restart always -p 3001:3001 --name my-container-express -v /mqtt_back:/app my-express-app
위의 명령어는
- -d : detach 옵션으로 백그라운드 프로세스로 실행함을 의미한다. 이를 설정해주지 않으면 docker가 돌아가는 것을 command에서 확인할 수 있다.
- restart always : 도커가 죽어도 계속 돌아가게끔 설정해주는 것이다.
- -p : 포트를 설정해주는 것이다. 앱에서 돌아가는 포트: 도커 컨테이너에서 돌아가는 포트번호
- —name : 도커 컨테이너의 이름을 설정해준다.
- -v : volume을 의미하며, local에서 있는 코드 위치:도커 컨테이너에 위치하는 코드 위치, 이를 연결해줌을 의미한다.
- my-express-app : 마지막에 써져 있는 이 것은 도커 이미지 이름을 의미한다.
로컬 작업 디렉토리와 컨테이너 상의 작업 디렉토리 간 sync를 맞춰주면 바인드 마운트가 된다. 그래서 로컬에서 수정하든, 도커 컨테이너 상에서 코드를 수정해도 둘 다 반영된다.
만약 반영되는 것이 궁금하다면
docker exec -it <컨테이너id> /bin/sh
로 들어가서 반영되었는지 확인해준다.
+추가)
정적인 파일을 올려놓고 도커 컨테이너를 실행했었다. 그러나 위와 같은 작업을 할려면 계속적으로 앱이 돌아간다는 바탕에 돌려줘야 한다. 그래서 앱이 계속적으로 돌아가서 인스턴스가 계속 멈춘다. 효율측면으로 돌릴려고 했다가 비효율적으로 된 것 같다. 계속적으로 코드를 수정해야 하는 작업이 생긴다면 추천하겠지만, 그게 아니라면 굳이 하지 않는 것을 추천한다.
👇🏻 참고 사이트
- 이미지 레이어 관련 참고 사이트
https://hstory0208.tistory.com/entry/Docker-도커의-레이어Layer에-대해-알아보자
https://velog.io/@khjgmdwns/Docker-도커의-레이어Layer-란
https://velog.io/@wjddn3711/도커-이미지와-컨테이너-명령어
https://seosh817.tistory.com/377
- volume 활용 및 도커 이미지 수정 관련 참고 사이트
https://rondeveloper.tistory.com/91
https://kyeongseo.tistory.com/entry/docker-이미지-수정하는-방법