도커의 장점은 각 프로젝트에 맞는 이미지를 설치하고, 이 환경을 docker-compose
로 관리함으로써 팀원 모두가 같은 환경에서 개발할 수 있고 또한 배포할 때 따로 환경 세팅을 하지 않아도 된다.
그러나 도커 파일을 대충 작성하면 도커를 빌드하고 올리는 과정에서 시간이 많이 소요되어서 오히려 효율적이지 못할 수 있다. 처음 대충 쓸데는 컨테이너 수도 적고, 시간이 많이 필요하지 않았었는데 점점 컨테이너가 많이 추가되면서 윈도우 빌드는 정말 오래 걸리는 사태가 발생했다. 따라서 현재 도커파일을 수정하면서 최적화를 조금 시켜보려고 한다.
Dockerfile의 CMD, RUN 등 실행 명령어를 전달할 때 shell type과 exec type이 있다.
RUN env
CMD ["env"]
shell type은 항상 쉘을 통해 실행되므로 불필요한 프로세스가 실행될 수 있다. 따라서 쉘 기능이 필요한게 아니라면 exec type으로 작성하는 것이 좋다고 한다. 또한 쉘(/bin/sh
)은 docker stop 등으로 인한 종료 요청을 무시하기 때문에 강제종료가 이루어질 수 있다. 따라서 gracefully shut down 될 수 있도록 하려면 exec type을 써야 한다.
개발할 때는 npm start
를 통해 프론트 서버를 띄우고 있는데, 이게 실행되는 과정을 볼 필요가 있다.
우선 npm start
를 통해 컨테이너를 띄운 상황을 보자.
컨테이너 접속
docker exec -it frontend /bin/sh
컨테이너에 접속해 top을 통해 실행 중인 프로세스를 조회해보면,
현재 npm start를 위해 총 3개의 프로세스가 쓰이는 것을 볼 수 있다.
npm start
가 node
를 실행하고, node
가 start 스크립트를 실행하는 것을 볼 수 있다. 따라서 CMD 자체를 [ "node", "node_modules/react-scripts/scripts/start.js"]
로 바꿔주었다.
CMD [ "node", "node_modules/react-scripts/scripts/start.js" ]
확인해보니 프로세스 수가 하나로 줄은 것을 알 수 있다.
기본적으로 Dockerfile의 명령어 단위로 레이어가 생성되고 캐싱이 이루어진다. 변경사항이 없고 캐싱된 레이어가 있다면, 새로 만들지 않고 전에 쌓았던 레이어를 재활용하는 것이다. 그러나 한번 변경사항이 생기면 그 이후부터는 모조리 새로 생성하게 된다. 따라서 Dockerfile의 작성 순서 또한 빌드 시간에 영향을 미칠 것이다.
따라서 패키지 등의 설치에서 의존성을 고려하면서도, 자주 바뀌는 것을 가능한 뒤쪽으로 배치하고 자주 변하지 않는 부분을 앞쪽에 배치하는 것이 좋다.
예를 들어, 리액트의 경우
FROM node:16-alpine3.15
WORKDIR /client
COPY [".", "."]
RUN ["npm", "install", "react-scripts", "-g", "--slient"]
RUN ["npm", "install", "--silent"]
CMD ["node", "node_modules/react-scripts/scripts/start.js"]
이렇게 되면 3번째 명령어인 COPY
에 의해 소스코드가 바뀔 때마다 npm install이 새로 진행될 것이다. 그러나 npm install은 패키지가 변경할 때만 실행하면 된다. 따라서
FROM node:16-alpine3.15
WORKDIR /client
# install package
COPY ["package*.json", "./"]
RUN ["npm", "install", "react-scripts", "-g", "--slient"]
RUN ["npm", "install", "--silent"]
# run npm server
COPY [".", "."]
CMD ["node", "node_modules/react-scripts/scripts/start.js"]
package.json
만 미리 복사하여 패키지가 변하지 않았을 경우 COPY
이전 단계까지는 캐싱된 레이어를 사용하도록 작성할 수 있다.
추가) 변경사항을 판단하는 기준
ADD
와 COPY
의 경우, 해당 파일의 변경사항까지 감지한다.Dockerfile에서 빌드에 필요한 소스코드 등을 COPY [".", "."]
를 통해 전부 복사하는 경우가 있다. 이 경우 빌드에 필요하지 않은 파일이 로컬에 저장되어 있다면 dockerignore
파일을 작성해서 제외해주는 것이 좋다. 용량이 적은 파일 한 두개 정도는 큰 문제가 되지 않지만, node_modules
와 같이 용량이 조금 있는 폴더의 경우 제외하면 빌드 시간 단축에 큰 도움이 된다. 나의 경우, 도커 이미지에서 npm install
을 하기 때문에 로컬의 node_modules
를 복사해갈 필요가 없었다. 따라서 .dockerignore
파일을 생성해서 제외시켜주었다.