이미지 레이어 이해하기
이미지 레이어 이해하기
컨테이너 이미지는 여러 레이어로 이루어져 있고, 각 레이어는 한 번 만들어지면 불변이에요. 이 가이드에서는 레이어가 무엇인지, 컨테이너가 사용할 파일시스템이 어떻게 만들어지는지, 그리고 직접 레이어를 만들어 보면서 원리를 이해해요.
출처: 문서
본문
설명
이미지란 무엇인가요?에서 배웠듯이, 컨테이너 이미지는 레이어로 구성돼요. 그리고 각 레이어는 한 번 생성되면 불변(immutable)이에요. 그런데 이게 정확히 무슨 뜻일까요? 또 그 레이어들은 컨테이너가 사용할 수 있는 파일시스템을 만드는 데 어떻게 쓰일까요?
이미지 레이어
이미지의 각 레이어는 파일시스템 변경 세트(추가, 삭제, 수정)를 담고 있어요. 이론적인 이미지를 하나 살펴볼까요.
- 첫 번째 레이어는
apt같은 패키지 매니저와 기본 명령을 추가해요. - 두 번째 레이어는 의존성 관리를 위해 Python 런타임과 pip를 설치해요.
- 세 번째 레이어는 애플리케이션의 특정
requirements.txt파일을 복사해 넣어요. - 네 번째 레이어는 애플리케이션의 특정 의존성들을 설치해요.
- 다섯 번째 레이어는 애플리케이션의 실제 소스 코드를 복사해 넣어요.
이 예시는 레이어를 이미지 간에 재사용할 수 있게 해 주기 때문에 유용해요. 예를 들어 또 다른 Python 애플리케이션을 만들고 싶다고 해 볼게요. 레이어링 덕분에 동일한 Python 베이스를 활용할 수 있어요. 이렇게 하면 빌드가 빨라지고, 이미지를 배포하는 데 필요한 저장 공간과 대역폭도 줄어들어요. 이미지 레이어링은 다음과 비슷하게 생겼어요.
레이어를 사용하면 다른 사람의 베이스 레이어를 재사용해 이미지를 확장할 수 있고, 여러분의 애플리케이션이 필요한 데이터만 추가하면 돼요.
레이어 쌓기
레이어링은 콘텐츠 주소 지정 저장소(content-addressable storage)와 유니온 파일시스템(union filesystem) 덕분에 가능해요. 다소 기술적이지만, 어떻게 동작하는지 설명할게요.
- 각 레이어는 다운로드된 후 호스트 파일시스템의 자신만의 디렉터리로 추출돼요.
- 이미지에서 컨테이너를 실행하면 레이어들이 서로 위에 쌓여 새로운 통합된 뷰를 만드는 유니온 파일시스템이 생성돼요.
- 컨테이너가 시작되면
chroot를 사용해 루트 디렉터리가 이 통합 디렉터리의 위치로 설정돼요.
유니온 파일시스템이 생성될 때, 이미지 레이어 외에도 실행 중인 컨테이너 전용 디렉터리가 하나 만들어져요. 이 덕분에 컨테이너가 파일시스템을 변경하더라도 원래 이미지 레이어는 그대로 유지될 수 있어요. 그래서 동일한 베이스 이미지에서 여러 컨테이너를 실행할 수 있는 거예요.
직접 해보기
이 실습 가이드에서는 docker container commit 명령을 사용해 새 이미지 레이어를 수동으로 만들어 봐요. 보통은 Dockerfile을 사용하기 때문에 실제로 이 방식으로 이미지를 만드는 경우는 드물지만, 어떻게 작동하는지 이해하는 데는 훨씬 좋아요.
베이스 이미지 만들기
첫 번째 단계에서는 이후 단계에서 사용할 나만의 베이스 이미지를 만들어 봐요.
- Docker Desktop을 다운로드하고 설치해요.
- 터미널에서 다음 명령을 실행해 새 컨테이너를 시작해요.
이미지가 다운로드되고 컨테이너가 시작되면 새 셸 프롬프트가 보여요. 이것은 컨테이너 안에서 실행되는 거예요. (컨테이너 ID는 달라질 수 있어요.)$ docker run --name=base-container -ti ubunturoot@d8c5ca119fcd:/# - 컨테이너 안에서 다음 명령을 실행해 Node.js를 설치해요.
이 명령이 실행되면 컨테이너 안에 Node가 다운로드되어 설치돼요. 유니온 파일시스템의 관점에서 보면, 이런 파일시스템 변경은 이 컨테이너 고유의 디렉터리 안에서 일어나요.$ apt update && apt install -y nodejs - 다음 명령을 실행해 Node가 설치됐는지 확인해요.
콘솔에 "Hello world!"가 나타나면 돼요.$ node -e 'console.log("Hello world!")' - Node가 설치됐으니 이제 변경 사항을 새 이미지 레이어로 저장할 준비가 됐어요. 이 레이어에서 새 컨테이너를 시작하거나 새 이미지를 만들 수 있어요. 이를 위해
docker container commit명령을 사용해요. 새 터미널에서 다음 명령을 실행해요.$ docker container commit -m "Add node" base-container node-base docker image history명령으로 이미지의 레이어를 확인해요.
다음과 비슷한 출력이 보여요.$ docker image history node-base
맨 위 줄의 "Add node" 코멘트를 주목하세요. 이 레이어에는 방금 만든 Node.js 설치가 포함돼 있어요.IMAGE CREATED CREATED BY SIZE COMMENT 9e274734bb25 10 seconds ago /bin/bash 157MB Add node cd1dba651b30 7 days ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B 7 days ago /bin/sh -c #(nop) ADD file:6089c6bede9eca8ec… 110MB 7 days ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B 7 days ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B 7 days ago /bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH 0B 7 days ago /bin/sh -c #(nop) ARG RELEASE 0B- 이미지에 Node가 설치됐음을 확인하려면 이 새 이미지로 새 컨테이너를 시작해 보세요.
터미널에 "Hello again" 출력이 나오면 Node가 설치되어 정상 작동한다는 뜻이에요.$ docker run node-base node -e "console.log('Hello again')" - 이제 베이스 이미지를 다 만들었으니, 그 컨테이너를 제거할 수 있어요.
$ docker rm -f base-container -
베이스 이미지 정의
베이스 이미지는 다른 이미지를 빌드하기 위한 토대예요. 어떤 이미지든 베이스 이미지로 사용할 수 있어요. 다만 일부 이미지는 의도적으로 빌딩 블록으로 만들어져, 애플리케이션의 토대나 시작점을 제공해요.
이 예시에서는
node-base이미지가 실제로는 아무것도 하지 않기 때문에 배포하지 않겠지만, 이후 다른 빌드에 사용할 수 있는 베이스가 돼요.
앱 이미지 빌드하기
베이스 이미지가 있으니 이제 그 이미지를 확장해 추가 이미지를 빌드할 수 있어요.
- 방금 만든 node-base 이미지로 새 컨테이너를 시작해요.
$ docker run --name=app-container -ti node-base - 이 컨테이너 안에서 다음 명령을 실행해 Node 프로그램을 만들어요.
이 Node 프로그램을 실행하려면 다음 명령을 사용해 화면에 메시지가 출력되는 걸 볼 수 있어요.$ echo 'console.log("Hello from an app")' > app.js$ node app.js - 다른 터미널에서 다음 명령을 실행해 이 컨테이너의 변경 사항을 새 이미지로 저장해요.
이 명령은$ docker container commit -c "CMD node app.js" -m "Add app" app-container sample-appsample-app이라는 새 이미지를 만들 뿐 아니라, 컨테이너 시작 시 기본 명령을 설정하는 추가 구성을 이미지에 더해요. 여기서는 컨테이너가 시작될 때 자동으로node app.js를 실행하도록 설정하는 거예요. - 컨테이너 밖의 터미널에서 다음 명령을 실행해 업데이트된 레이어를 확인해요.
그러면 다음과 같은 출력이 보여요. 맨 위 레이어의 코멘트가 "Add app"이고, 그다음 레이어는 "Add node"인 걸 확인할 수 있어요.$ docker image history sample-appIMAGE CREATED CREATED BY SIZE COMMENT c1502e2ec875 About a minute ago /bin/bash 33B Add app 5310da79c50a 4 minutes ago /bin/bash 126MB Add node 2b7cc08dcdbb 5 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B 5 weeks ago /bin/sh -c #(nop) ADD file:07cdbabf782942af0… 69.2MB 5 weeks ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B 5 weeks ago /bin/sh -c #(nop) LABEL org.opencontainers.… 0B 5 weeks ago /bin/sh -c #(nop) ARG LAUNCHPAD_BUILD_ARCH 0B 5 weeks ago /bin/sh -c #(nop) ARG RELEASE 0B - 마지막으로, 새로운 이미지로 새 컨테이너를 시작해요. 기본 명령을 지정했으니 다음 명령을 사용할 수 있어요.
여러분의 Node 프로그램에서 만든 인사말이 터미널에 나타나는 걸 볼 수 있어요.$ docker run sample-app - 이제 컨테이너를 다 사용했으니 다음 명령으로 제거해요.
$ docker rm -f app-container
추가 자료
배운 내용을 더 깊이 파고들고 싶다면 다음 자료를 확인해 보세요.
docker image historydocker container commit
다음 단계
앞서 힌트를 드렸듯이, 대부분의 이미지 빌드는 docker container commit을 사용하지 않아요. 대신 이러한 단계를 자동화해 주는 Dockerfile을 사용해요. Dockerfile 작성하기