이번 포스트에서는 도커 이미지
에 대한 개념정리와 명령어, 그리고 관리하는 방법에 대해 정리하겠습니다.
도커 이미지를 빌드하는 방법은 크게 Dockerfile 없이 도커 이미지를 생성하는 방법과 Dockerfile을 이용하여 도커 이미지를 생성하는 방법 두 가지로 나뉩니다. 그래서 이번 포스트에서는 Dockerfile 없이 도커 이미지를 빌드하는 방법만 정리하고 다음 포스트에서 Dockerfile과 Dockerfile로 도커이미지를 생성하는 방법을 정리하겠습니다.
도커 이미지
모든 컨테이너는 이미지를 기반으로 생성되므로 이미지를 다루는 방법은 도커 관리에서 빼놓을 수 없는 부분입니다. 이미지의 이름을 구성하는 저장소, 이미지 이름, 태그를 잘 관리하는 것 뿐만 아니라 이미지가 어떻게 생성되고 삭제되는지, 이미지의 구조는 어떻게 되어 있는지 등을 아는 것 또한 중요합니다. 이번 포스트에서는 도커 이미지
를 관리하는 방법을 정리하겠습니다.
데비안 운영체제에서 apt-get install을 실행하면 apt 레포지토리에서 패키지를 내려받듯이 도커는 기본적으로 도커 허브(Docker hub)라는 중앙 이미지 저장소에서 이미지를 내려받습니다. 도커 허브(Docker hub)는 도커가 공식적으로 제공하고 있는 이미지 저장소로서, 도커 계정을 가지고 있다면 누구든지 이미지를 올리고 내려받을 수 있기 때문에 다른 사람들에게 이미지를 쉽게 공유할 수 있습니다.
docker create, docker run, docker pull 명령어로 이미지를 내려받을 때 도커는 도커 허브에서 해당 이미지를 검색한 후에 내려받습니다. 필요한 대부분의 이미지는 도커 허브에서 공식적으로 제공하거나 다른사람들이 도커 허브에 이미 올려놓은 경우가 대부분이라서 애플리케이션 이미지를 직접 만들지 않아도 손쉽게 사용할수 있다는 장점이 있습니다.
단, 도커 허브는 누구나 이미지를 올릴 수 있기 때문에 공식(Official) 라벨이 없는 이미지는 사용법을 찾을 수 없거나 제대로 동작하지 않을 수 있습니다. 또한, 이미지 저장소를 다른 사람들에게 공개하지 않기 하기 위해 비공개(Private) 저장소를 사용하려면 비공개 저장소의 수에 따라 요금을 지불해야 합니다. 이를 해결하기 위해 도커 사설 레지스트리를 직접 구축하여 도커 이미지 저장소를 직접 구축해 비공개로 사용할 수 있습니다.
도커 이미지의 구조
도커 이미지
는 컨테이너를 실행하기 위한 모든 정보들을 가지고 있기 때문에 보통 용량이 수백 MB에 이릅니다. 이미지를 수정없이 계속 사용한다면 괜찮겠지만 기존 이미지에 파일 하나를 수정한다고 했을 때 기존이미지 + 수정한 파일을 전부 다시 받아야 한다면 매우 비효율적일 수 밖에 없습니다. 그래서, 이러한 문제를 해결하기 위해 도커 이미지는 Layer 구조
로 이루어져 있습니다.
이미지는 여러개의 읽기 전용(Read only) 레이어로 구성이 되고 파일이 추가되거나 수정되면 새로운 레이어가 생성됩니다. 즉, 특정 이미지를 사용하다가 이미지에 변경사항을 가한다고 했을 경우(ex. source코드의 변경)에 Layer 구조를 사용하므로 기존 base layer는 변경할 필요없이 새로운 Layer를 추가만 하면 됩니다.
위 그림을 예를들면, ubuntu 이미지는 Layer A + Layer B + Layer C로 이루어져 있습니다. 만약, ubuntu 이미지를 베이스로 nginx 이미지를 만든다면 레이어 구조는 Layer A + Layer B + Layer C + nginx 레이어로 구성됩니다. 그리고 web app 이미지를 nginx 이미지를 베이스로 만든다면 Layer A + Layer B + Layer C + nginx + web app source 레이어로 구성됩니다. 만약, 나중에 web app이미지의 소스를 수정한다고 하면 Layer A, Layer B, Layer C + nginx를 제외하고 web app source 레이어만 다운받으면 되기 때문에 효율적으로 이미지를 관리할 수 있습니다.
그래서 도커 이미지는 총 세 가지의 이미지가 생성이 되었는데 오른쪽에 Docker Container 레이어를 보면 web app 이미지를 기반으로 컨테이너를 만드는 것을 확인할 수 있습니다. 이렇게 컨테이너를 생성하면 web app 이미지가 이미지 레이어 형태로 컨테이너가 실행될 때 복사가 되는데 Docker Container에 있는 레이어는 Read Only 레이어로 읽기 전용으로 생성이 됩니다.
그래서 이미지 레이어에 있는 파일들은 변경이 불가능한 상태고 그와 함께 컨테이너가 생성될 때 마다 해당 컨테이너에 Container Layer가 생성이 됩니다. Container Layer는 Read/Write 레이어로써 읽기와 쓰기가 모두 가능한 레이어이지만, 컨테이너가 삭제될 때 Container Layer도 같이 삭제된다는 점 잊지 않아야 합니다.
도커 이미지 생성하기(Dockerfile 없이)
도커 이미지는 docker search
를 통해 검색한 이미지를 pull 사용할 수 있습니다
$ docker search [image name]
하지만, 도커로 개발하는 대부분의 경우는 컨테이너에 특정 개발 환경을 직접 구축한 뒤 사용자만의 이미지를 직접 생성해야만 합니다.
그래서, 컨테이너에서 작업한 내용을 이미지로 만드는 방법을 정리하겠습니다.
먼저, 아래의 docker run 명령어를 입력하여 우분투 focal 컨테이너를 만들고 실행하겠습니다.
$ docker run -it --name seunghwan_ubuntu ubuntu:focal
그리고 컨테이너 내부에 my_name이라는 파일을 생성하고 저장해서 기존의 이미지에서 변경사항을 만들어줍니다.
$ echo seunghwan >> my_name
그리고 ubuntu:focal 이미지에 변경사항을 만들었으면 컨테이너에서 도커 호스트로 나와서 docker commit 명령어를 입력해서 컨테이너를 이미지로 만듭니다.
도커 이미지를 만드는 방법은 docker commit
명령어를 통해 도커 컨테이너를 이미지로 만들 수 있습니다.
$ docker commit [options] [container] [repository:tag]
아래의 명령어는 seunghwan_ubuntu 컨테이너를 seunghwan_ubuntu:first라는 이름의 이미지로 생성합니다.
$ docker commit \
-a "seosh817" -m "my first commit" \
seunghwan_ubuntu \
seunghwan_ubuntu:first
-a 옵션
-> author를 의미하며 이미지의 작성자를 나타내는 메타데이터를 이미지에 포함시킵니다. 예를들면, seunghwan_ubuntu:first 이미지의 작성자 데이터는 "seosh817"로 설정됩니다.
-m 옵션
-> 이미지 커밋에 포함될 커밋 메시지를 입력합니다.
저장소 이름을 입력하지 않아도 상관없지만 이미지의 태그를 입력하지 않으면 자동으로 latest로 설정됩니다. 위 명령어는 이미지의 이름을 seunghwan_ubuntu로, 태그를 first로 설정합니다.
docker images 명령어로 seunghwan_ubuntu 이미지가 생성되었는지 확인합니다.
seunghwan_ubuntu라는 이름에 first 태그로 이미지가 생성되었습니다. 사이즈를 비교했을 때 ubnutu 이미지와 차이가 없는 것은 컨테이너의 변경사항이 너무 작기 때문입니다.
docker images insepct 명령어로 두 개의 이미지의 레이어들을 비교해보겠습니다.
먼저, unbutu:focal의 레이어들은 아래와 같습니다.
0002c9로 시작하는 16진수 해시값을 가진 이미지 레이어가 하나 존재하는 것을 확인할 수 있습니다.
다음은, ubuntu:focal 이미지를 베이스로 만든 seunghwan_ubuntu:first 이미지의 레이어는 아래와 같습니다.
seunghwan_ubuntu:first의 이미지 레이어는 ubuntu:focal에 존재하던 0002c9 해시값을 가진 이미지 레이어에 f87f89 해시값을 가진 이미지 레이어가 하나 추가된 것을 확인할 수 있습니다.
위와 똑같은 방법으로 seunghwan_ubuntu:first 이미지를 기반으로 seunghwan_ubuntu:second 이미지를 만들어 줍니다.
그리고 seunghwan_ubuntu:second 이미지의 이미지 레이어를 확인해보겠습니다.
seunghwan_ubuntu:second의 이미지 레이어는 seunghwan_ubuntu:first의 이미지를 베이스로 만들었으므로 0002c93와 f87f89 해시값 위에 새로운 이미지 레이어가 쌓인 것을 확인할 수 있습니다.
즉, 도커 이미지의 구조가 Layer 구조라는 것을 위의 내용을 통해 실제로 확인할 수 있습니다.
도커 이미지 삭제하기
docker rmi
명령어를 입력하여 도커 이미지를 삭제할 수 있습니다.
$ docker rmi [repository name:tag]
만약, 이미지를 사용중인 컨테이너가 존재한다면 이미지를 삭제할 수 없습니다.
컨테이너를 삭제할 때의 docker rm -f [container name] 명령어 처럼 -f 옵션으로 이미지를 강제로 삭제할 수도 있지만 docker rmi -f 명령어는 이미지 레이어 파일을 실제로 삭제하지 않고 이미지 이름만 삭제하기 때문에 의미가 없습니다.
따라서 아래의 명령어와 같이 컨테이너를 삭제한 뒤 이미지를 삭제해야 합니다.
$ docker stop seunghwan_ubuntu $$ docker rm seunghwan_ubuntu
$ docker rmi seunghwan_ubuntu:first
하지만, seunghwan_ubuntu:first 이미지를 삭제했다고 해서 실제로 해당 이미지의 레이어 파일이 삭제되지는 않습니다. seunghwan_ubuntu:first 이미지를 기반으로 하는 하위 이미지인 seunghwan_ubuntu:second가 존재하기 때문입니다. 그러므로, 실제 이미지 파일을 삭제하지는 않고 레이어에 부여된 이름만 삭제하게 됩니다.
아래의 화면은 rmi 명령어의 출력 결과인 Untagged: ...는 이미지에 부여된 이름만 삭제한다는 것을 의미합니다.
이번에는 seunghwan_ubuntu:second 이미지를 삭제해 보겠습니다. seunghwan_ubuntu:second 이미지는 사용하고 있는 컨테이너가 없으므로 바로 삭제가 가능합니다.
"Deleted:"라는 출력 결과는 이미지 레이어가 실제로 삭제됐음을 의미합니다. 이미지의 이름인 seunghwan_ubuntu:second를 가리키는 ID와 실제 레이어를 가리키는 ID가 삭제되었는데 실제 레이어 파일은 sha256:05e3c0.... 라는 ID에 의해 참조됩니다. 즉, 삭제되는 이미지의 부모 이미지가 존재하지 않아야만 해당 이미지의 파일이 실제로 삭제됩니다.
seunghwan_ubuntu:first 이미지는 태그만 삭제, seunghwan_ubuntu:second 이미지는 이미지 레이어까지 전부 삭제되어서 ubuntu:focal 이미지만 존재하는 것을 확인할 수 있습니다.
이미지 추출하기
도커 이미지를 별도로 저장하거나 옮기는 등 필요에 따라 이미지를 단일 바이너리 파일로 저장해야 할 때가 있습니다.
docker save
명령어를 사용하면 컨테이너의 커맨드, 이미지 이름 및 태그 등 이미지의 모든 메타데이터를 포함해 하나의 파일로 추출할 수 있습니다.
$ docker save -o ubuntu:focal.tar ubuntu:focal
-o 옵션
-> 추출될 파일명을 입력합니다.
추출된 이미지는 load 명령어로 도커에 다시 로드할 수 있습니다. save 명령어로 추출된 이미지는 이미지의 모든 메타데이터를 포함하기 때문에 load 명령어로 이미지를 로드하면 이전의 이미지와 완전히 동일한 이미지가 도커 엔진에 생성됩니다.
$ docker load -i ubuntu:focal.tar
save, load 명령어와 유사하게 사용할 수 있는 명령어로 export
, import
가 있습니다.
docker commit 명령어로 컨테이너를 이미지로 만들면 컨테이너에서 변경된 사항뿐만이 아니라 컨테이너가 생성될 때 설정된 detached 모드, 컨테이너 커맨드와 같은 컨테이너의 설정 등도 이미지에 함께 저장됩니다.
반면에, export
명령어는 컨테이너의 파일시스템을 tar 파일로 추출하며 컨테이너 및 이미지에 대한 설정 정보를 저장하지 않습니다.
export
와 import
는 다음 예제처럼 사용할 수 있습니다. export 명령어는 mycontaier라는 컨테이너의 파일시스템을 rootFS.tar 파일로 추출하고, 이 파일을 import 명령어로 myimage:0.0이라는 이미지로 다시 저장합니다.
$ docker export -o rootFS.tar mycontainer
$ docker import rootFS.tar myimage:0.0
그러나 이미지를 단일 파일로 저장하는 것은 효율적인 방법이 아닙니다. 추출된 이미지는 레이어 구조의 파일이 아닌 단일 파일이기 때문에 여러 버전의 이미지를 추출하면 이미지 용량을 각기 차지하게 됩니다. 예를 들면, ubuntu:focal 이미지와 seunghwan_ubuntu:first라는 두 개의 이미지를 추출하면 각각 72MB의 파일이 생성되어 총 144MB를 차지하게 될 것입니다.