도커 이미지를 빌드하고 컨테이너를 생성하는 데에는 시간과 자원이 소모된다. 따라서 소스 코드를 변경하고 이를 도커에서 확인할 때마다 이미지를 다시 빌드하고 컨테이너를 생성하는 일은 소모적이다. 따라서 이런 자원 낭비를 줄이면서 도커 환경에서 개발하는 방법을 알아보자.
도커는 레이어를 이용해서 이미지를 생성한다. 즉, 이미지는 레이어들이 쌓여있는 형태라고 볼 수 있다. 이미지의 베이스 이미지는 가장 기초 레이어가 된다. 도커파일의 각각의 명령어는 하나의 새로운 레이어를 만들고 쌓는 행위라고 볼 수 있다. 가장 상단에 FROM <베이스 이미지>
가 있는 것도 베이스 이미자가 가장 기초 레이어가 되기 때문이다. 각 레이어는 도커파일의 명령어가 실행 전의 파일 시스템 변경사항과 실행 후의 파일 시스템 변경사항을 포함하고 있다. 즉, 도커파일이 실행되면 레이어는 파일 시스템에서 무엇이 변경되었는지, 변경되지 않았는지 안다. 그래서 변경사항이 없다면 캐싱된 레이어를 사용한다. 새로 설치하거나 다운로드 받지않아서 자원과 시간소모가 줄어든다.
예를 들어서 소스코드를 수정했다고 가정해보자. 작업이 이루어 지는 디렉토리의 구조와 도커파일은 다음과 같다.
기존에 생성된 도커 이미지에서 소스코드에 해당하는 server.js
를 변경하고 다시 docker build
를 했을 때를 도커는 어떻게 동작하는지 알아보자. (즉, 해당 도커 파일과 server.js
파일로 생성된 도커 이미지가 있는 상태에서 server.js
만 수정하고 다시 빌드했을 때를 말한다.)
우선 [1/4] 가장 첫번째 레이어인 베이스 레이어는 캐싱되지 않고 이미지에 깔린다. 그리고 두번째 레이어를 명시하는 명령어인 [2/4]WORKDIR /usr/src/app
명령어는 캐싱이 된다. 그 이유는 [2/4] 명령어의 바로 직전 명령어인 [1/4] 명령어를 수행하고 난 모습이 이전 이미지의 [1/4] 명령어를 수행하고 난 모습과 똑같기 때문이다. 즉, 레이어 캐싱을 사용하기 위해서는 각 명령어마다 조건이 충족되어야 한다.
RUN
1. 각 레이어를 명시하는 명령어가 동일하다.
2. 명령어를 수행하기 직전 레이어의 모습이 이전 이미지와 현재 이미지가 동일하다.
COPY
1. 각 레이어를 명시하는 명령어가 동일하다.
2. 외부 파일과 도커 이미지 내부 파일이 일치한다.
이 과정은 아래와 같이 나타낼 수 있다.
이같은 관점에서 볼때 [3/4] 에서부터 캐싱이 일어나지 않은 이유는 COPY
의 대상이 되는 <외부 파일> 과 <이미지 내부 파일>이 다르다고 도커가 판단했기 때문이다. /.
안에 속한 server.js
파일이 변경되었기 때문이다.
위 같은 이점을 이용하기 위해서는 도커 파일의 명령어를 잘 배치해야한다. 위의 상황에서 변경된 것은 service.js
파일 뿐이다. 종속성을 나타내는 package.json
파일은 변경된 것이 없다. 따라서 RUN npm install
도 레이어 캐싱을 이용할 수 있다. 도커 파일을 다음과 같이 구성해보자.
package.json
파일은 먼저 COPY
한다. 이렇게 함으로써 갖는 이점은 RUN npm install
에서도 레이어 캐싱을 활용할 수 있다. 우선 변경된 도커 파일로 이미지를 빌드해보자.
변경된 도커 파일의 최초 빌드이기 때문에 [2/5]번 아래의 명령어들은 직전 도커 이미지를 생성한 명령어들과 달라서 레이어 캐싱을 사용하지 못한다. 이제 생성된 도커 이미지는 현재의 도커 파일을 기억하고 있다. server.js
를 수정해서 빌드를 했을 때 어떤 변화가 있는지 살펴보자.
[1/5] 과 [5/5] 를 제외한 레이어가 모두 CACHED
되었다. [5/5]
에서 레이어 캐싱이 일어나지 않은 이유는 ./
에 변경된 server.js
파일이 포함되어 있기 때문이다.