Search

Docker: Image & Container

Created
2021/05/19
Tags
Docker
Container
Image

Subjects

Docker Engine에서 사용하는 기본 단위가 ImageContainer이다.

1. Docker Image

ImageContainer를 생성할 때 필요한 요소이며, 이는 VM으로 치면 iso 파일과 비슷하다고 생각하면 된다. Image는 여러 Layer로 이루어져 Container들이 동일한 Layer를 이용하려 할 때 재사용이 가능한 구조를 갖고 있다. Image를 이루고 있는 Layer는 바이너리 파일로써 유지되며, 오로지 Read-Only이다.
Image<hub-id> / <image-name> : <tag>의 형식을 가진다. 예를 들어 bigpel66이라는 사용자가 14.04 버전의 ubuntu라는 이미지를 사용한다면, bigpel66/ubuntu:14.04와 같이 표기한다.
이 때 <hub-id><tag>에 대해서는 생략이 가능하며, <hub-id> 생략 시에는 Docker Hub에서 OfficalImage를 의미하게 되고 <tag>를 생략하게 되면 latest 버전을 지칭하게 된다. 단, <image-name>은 생략할 수 없다.

2. Docker Container

1) Container 소개

위에서 설명된 Image를 이용하여 Container를 생성할 수 있다. Container를 생성하면, 현재 사용하고 있는 머신과는 독립적인 공간에 Image의 목적에 맞는 파일 시스템과 가상화된 시스템 자원 및 가상화된 네트워크를 이용할 수 있게 된다. 생성된 공간에서는 Image의 목적만을 수행하는 것이 일반적이다.
ContainerImageRead-Only로 이용하기만 하고, Image의 내용을 수정하거나 Image 내용 외에 추가적으로 수행된 부분에 대해서는 Container에 해당하는 Layer에 그 내용을 기록하게 된다. 따라서 ImageContainer가 동작하더라도 끝까지 Read-Only를 유지할 수 있게 된다. 즉, Container에서 특정 Layer에 변동 사항이 생기더라도 이를 Image에 바로 기록하는 것이 아니기 때문에, 해당 Layer를 참조하고 있는 여러 Container들은 변동 사항이 적용되지 않은 깨끗한 상태로 Layer를 사용할 수 있게 된다.
Container는 독립적인 공간을 제공 받아서 구동되기 때문에, Container에서 어떤 작업을 하더라도 머신에는 영향을 끼치지 않으며 심지어 Container 간에도 그 어떤 영향을 끼치지 않는다.

2) Container 자원 제한

이 때 Container를 생성하는 runcreate 같은 명령어에 자원 제한 옵션을 주어 Container를 운용하는 것이 가능하다. 아무런 옵션이 주어지지 않으면 Host의 자원을 위 그림과 같이 Docker Desktop에서 제한된 값 내에서 이용하게 되는데, Production 단계의 Container를 고려해보면 Container의 자원을 제한하는 것이 좋다. Container의 자원이 제한되지 않아 특정 ContainerHost에서 할당할 수 있는 자원을 모두 받음으로써 다른 Container가 이용할 수 있는 자원이 없어져 동작을 멈추면 곤란해질 수 있으므로, Host 내에서 동작하는 Container 간 동작을 서로 간섭하지 않게 만드는 것에 의의가 있다.
Container를 생성할 때 제한을 줄 수 있는 자원은 Memory, CPU, Block I/O가 있으며, Disk와 같은 StorageDocker 데몬이 어떤 Storage Driver를 이용하는지에 따라서 제한 여부가 달라진다. 즉, Storage에 대해서는 Storage Driver 자체에서 Storage 제한 옵션을 제공해야만 Container 단에서 제한을 가할 수 있는 것인데, 어차피 Container 내부에 큰 용량을 가진 정보들을 두는 식으로 Stateful 하게 두는 것은 바람직한 이용 방법은 아니므로 Container의 저장 공간을 제한하는 것이 꼭 필수적인 과정은 아니다.
여기서 소개하는 Container를 생성하고 실행하는 명령어들은 아래에서 하나하나 자세히 다루므로, 전반적으로 Container의 자원을 제한할 수 있다 정도로만 참고하면 된다.

Memory 제한

Container 생성 명령어에 —-memory 옵션을 주어 Memory를 제한할 수 있다. 옵션에 대한 값을 입력할 때는 단위를 함께 기재한다. b (Byte), k (Kilo Byte), m (Mega Byte), g (Giga Byte)라는 단위를 이용할 수 있다.
현재 시스템에서 제한할 수 있는 Memory의 최소 크기는 6MB이다. 이는 사용하고 있는 시스템에 따라서 최소 제한 크기가 모두 다르니 잘 확인해보고 제한해야 한다.
또한 위와 같이 Memory에 대해 잘못 제한하는 경우에 Container가 실행되지 않을 수 있다. 위 경우는 Container 실행에 필요한 Memory가 부족하여 실행 명령어가 무시되고 정지 상태에 있는 것을 확인할 수 있다.
단순히 Memory에 대한 제한 뿐만 아니라 —-memory-swap 이라는 옵션으로 Swap Memory에 대해서도 제한을 줄 수 있다. Swap Memory는 별도로 제한을 주지 않으면 현재 Memory 가용 공간의 2배만큼으로 설정된다. 예를 들어 container_memory—-memory로 준 값이 1GB였으니 Swap Memory2GB가 된다. —-memory-swap 옵션은 —-memory 옵션이 주어졌을 때만 그 의미를 갖는데, -—memory-swap 옵션으로 준 값은 Swap Memory + Memory의 값을 제한 공간으로 지칭하기 때문이다. 예를 들어, —-memory300MB을 주고 —-memory-swap으로 1GB를 주면, 300MBMemory700MBSwap Memory를 이용할 수 있는 것이다.
—-memory-swap의 값으로 0으로 주면 초반에 설명한, —-memory-swap 옵션을 주지 않아 그 공간을 Memory 공간의 2배만큼으로 두겠다는 의미와 동일하다.
—memory-swap의 값이 —-memory의 값과 같다면 Swap Memory를 이용하지 않겠다는 의미이다.
—-memory-swap-1로 사용하면 Swap MemoryDocker Desktop에서 제한한 크기만큼을 이용할 수 있게 된다.
Swap Memory 프로세스가 사용하는 Memory의 사용량이 늘어남에 따라 Disk와 같은 Storage의 일부를 RAM처럼 이용할 수 있게 만들어, 해당 공간을 확장된 RAM으로써 이용하는 기술이다. KernelRAM에 올라와 있는 Memory Block 들 중에서 당장 사용하지 않는 BlockDisk에 저장하여 가용 가능한 Memory의 영역을 확보한다. 이러한 전략은 Virtual Memory의 관리 기법으로도 종종 이용된다.

CPU 제한

CPU 제한에는 크게 2가지 방법이 있다. 첫 째는 가중치를 설정하여 Container가 얼만큼의 비율로 CPU를 이용할 것인지이고, 둘 째는 Container가 이용할 수 있는 특정 CPU를 지정하는 것이다.
전자의 경우는 —-cpu-shares, —-cpu-period—-cpu-quota, —-cpus 옵션들이 있다. 가중치를 이용하는 명령어들이 굉장히 다양한데, —-cpu-sharesCPU 점유율을 가중치로 이용한 것이고, —-cpu-period--cpu-quotaCPU 사용 주기를 가중치로 나타낸 것이고, —-cpus는 사용하려는 CPU의 수를 가중치로 나타낸 것이다. 이들은 옵션의 의미만 다를 뿐, 가중치를 이용한다는 점에서 사용의 차이가 크게 없다.
후자의 경우는 -—cpuset-cpu라는 옵션을 이용한다. 사용하려는 특정 CPU를 직접 지정한다는 점에서 가중치를 두어 자원을 제한하는 이전 옵션들과 큰 차이가 있는데, 병렬 처리를 위해 CPU를 다수 소모해야 하는 Container에 대해서는 가중치를 이용한 CPU 제한보다는 특정 CPU만 이용할 수 있도록 제한하는 -—cpuset-cpu 옵션을 이용하는 것이 좋다. 특정 CPU를 지정하여 점유한다는 의미는 CPU Affinity (친화성)을 보장할 수 있고, 따라서 CPU Cache Miss 혹은 Context Switching과 같이 성능 하락의 요인을 최소화 할 수 있다.
CPU 제한에 대한 확인을 위해 시작하세요! 도커 / 쿠버네티스라는 책의 도움을 받아, 해당 책에서 제시하는 alice106k/stress라는 Image를 이용하여 Container를 만들 것이다. 이 때 사용하는 명령어의 인자 중 stress —-cpu 1의 의미는 Stress는 1개의 프로세스로 진행한다는 것이다. 충분한 시간이 흘렀을 때 올바른 결과를 볼 수 있으므로, 궁금하다면 직접 Container를 생성하여 확인해보자. 직접 확인할 때는 Linux 환경에 들어가서 확인을 해야하고, 가장 중요한 점은 직접 확인을 하고 난 후에 반드시 Stress를 준 Container들을 삭제해줘야 한다는 것이다.
(1) —cpu-shares를 이용한 CPU 점유율 제한
1024 : 512의 비율로 점유하므로, 결과적으로 2 : 1 비율로 CPU를 점유한다. Stress를 준 단일 Container만 존재한다면 해당 Container100% 비율로 CPU를 점유한다.
(2) —cpu-period 및 —cpu-quota를 이용한 CPU 사용 주기 제한
ContainerCFS (Completely Fair Scheduler) 주기는 기본적으로 100ms로 설정되어 있지만, —-cpu-period—-cpu-quota 옵션을 이용하여 스케줄링 주기를 수정할 수 있다. 스케줄링 주기의 기본 값은 100ms였으므로 —-cpu-period의 기본 값은 100ms를 의미하는 100000이된다. —-cpu-quota의 의미는 —-cpu-period의 시간 중 얼만큼의 시간을 CPU에 할당할 것인지를 의미한다. 즉, —-cpu-period100000이고 —-cpu-quota25000이라면, 100ms 주기로 Container의 프로세스가 스케줄링을 점유하지만 CPU를 사용하는 시간은 25ms 밖에 되지 않는 것을 의미한다. 이 경우에는 기본 값으로 설정된 Container보다 14\frac{1}{4}만큼만 CPU를 이용하는 것이다. 즉, CPU를 이용하는 시간은 cpuquotacpuperiod\frac{—cpu-quota} { —cpu-period}만큼이 된다.
Container 모두 100ms 주기로 Container의 프로세스가 스케줄링을 점유하는데, 하나는 25ms만큼 CPU를 이용하고 나머지는 100ms 모두 CPU를 이용하므로 25ms : 100ms 비율인 1 : 4 비율로 CPU를 점유하게 된다.
(3) —cpus를 이용한 CPU 사용 개수 제한
—-cpusCPU 개수를 직접 설정한다는 점에서 굉장히 직관적으로 이용할 수 있다. 위처럼 —-cpus의 값으로 0.5를 지정하면 CPU50%로 점유하게 된다. 이는 —-cpu-period100000, —-cpu-quota50000과 동일한 설정이라고 볼 수 있다.
(4) —cpuset-cpu를 이용하여 특정 CPU만 이용
2indexCPU만을 이용하도록 설정했다. index0번부터 시작하는데, 위와 달리 여러 CPU를 이용하고 싶다면 다음과 같은 구문도 이용할 수 있다.
위 쪽 명령어는 0index3indexCPU를 이용하도록 설정한 것이고, 아래 쪽 명령어는 0index부터 2index까지 CPU를 이용하도록 설정한 것이다.

Block I/O 제한

Block I/O 제한이라고 함은 Container의 읽고 쓰는 입출력 작업에 대한 대역폭 설정을 말한다. 기본적으로 아무런 설정 없이 Container를 생성하게 되면 대역폭의 제한이 없다는 것을 의미한다. 만일 하나의 머신에서 다수의 Container를 실행해야 한다면, 특정 Container가 과도하게 Read/Write를 수행하여 입출력을 독점하는 현상을 방지하는 것이 좋다. —device-read-bps, --device-write-bps, --device-read-iops, —-device-write-iops 옵션이 존재하며, 이들은 Direct I/O에 대해서만 제한하고 Buffered I/O는 제한하지 않는다.
Direct I/O와 Buffered I/O Kernel에서의 입출력은 Direct I/O, Buffered I/O, Neither I/O와 같이 3가지 방법으로 나뉜다. 각 방법들은 KernelI/O 관리자Device와 데이터를 주고 받을 때 이용되는 모드들이며 각각 다른 특성을 갖고 있다. I/O 관리자는 기본적으로 IRP (I/O Request Packet)을 이용하여 Device에게 I/O를 요청한다. 이들 중 Direct I/OBuffered I/O를 간단히 알아보자. Direct I/O I/O를 수행하려는 User Application의 데이터가 존재하는 물리적 페이지들을 잠근다. 이는 별도의 Buffer를 둔 것이 아니므로 동시적인 접근을 제한하기 위함이다. 그리고 I/O 관리자가 해당 페이지의 데이터를 읽을 수 있도록 MDL (Memory Decriptor List)라는 구조체를 생성한 뒤, 구조체 내부에 데이터 주소들을 기록한다. 기록된 MDLDevice가 전달 받아 I/O를 수행하게 된다. Buffered I/O I/O를 수행하려는 User Application의 데이터를 Kernel단에서 Buffer를 만들어 복사한다. 이 때의 BufferKernel 단에서 유지 중이므로 페이징이 되는 Buffer는 아니다. 해당 BufferDevice에게 넘겨 I/O를 수행하게 된다. 특성 한번에 적은 양의 데이터를 주고 받는 Device의 경우엔 Buffered I/O를 주로 이용한다. 이 때는 Direct I/O 방식이 이용하는 것처럼 물리적 페이지에 대한 잠금이 불필요하므로 Memory 효율이 좋은 편이다. 대체적으로 Buffered I/O를 통해 성능면에서 득을 많이 보는 편이고, 이를 주로 이용하는 Device에는 키보드와 마우스 같은 것들이 있다. 반면에 Buffered I/O를 통해 득을 보지 못하는 특정 Application들이 있다. 예를 들면 DBMS같은 것들이 되겠다. 이들은 Kernel 단에서 제공하는 Buffer보다는 DBMS 내부에 존재하는 Buffer의 성능이 더 좋다고 보기 때문에 Direct I/O를 이용하려 한다. 이런 이유를 근거로 하여 내부적으로 둔 Buffer를 이용하기 때문에 Kernel 단의 Buffer를 다시 이용하면 두 번의 Copy로 인해 Overhead가 발생한다. 이러한 현상을 Double Copying이라고 하며, 이 때문에 DBMS 같은 ApplicationDirect I/O를 이용한다.
--device-read-bps--device-write-bpskb (Kilo Byte), mb (Mega Byte), gb (Giga Byte) 단위를 기재하여 값 설정이 가능하며, 기재된 값만큼의 초당 제한을 설정하게 된다. 이 때 어떤 Device에 대해서 제한하는지 명시해야 되므로 <device-path> : <number>의 형태로 입력한다.
--device-read-iops--device-write-iops는 정확히 값을 입력했던 옵션들과 달리 상대적인 가중치 값을 기재한다. --device-read-iops--device-write-iops 역시 <device-path> : <number>의 형태로 입력한다. 예를 들어 A, B라는 2개의 Container가 있다고 했을 때, A에서의 옵션 값보다 B의 옵션 값이 2배 큰 값을 갖는다면 B가 2배의 대역폭을 가지므로, B가 초당 2배 정도 더 많은 작업이 가능하다.

3. Image Commands

Image 명령어들에 대해서 <image-name> 뒤에는 <tag>를 붙일 수 있는데, 이를 생략하면 자동으로 latest 버전으로 명령어를 수행한다.

1) Image 찾기

docker search <image-name>
Docker Hub에서 Public으로 배포되어 있는 Image를 검색할 수 있다.

2) Image 다운로드

docker image pull <image-name>
Docker Hub에서 <image-name>에 해당하는 Image를 로컬에 다운로드 한다.

3) Image 생성

docker commit -m <msg> <container-name> <image-name>
버전 관리를 위한 Git과 동일한 과정이라고 생각하면 된다. -m 옵션을 통해 commit에 대한 기록이 가능하며, 기록하려는 메세지를 ""로 감싸서 작성하면 된다. 자주 사용하는 옵션으로는 -a 옵션이 있는데 이는 Image 생성에 대한 Author를 기록하는데 이용된다. inspect 명령어 수행 시 기록된 Author에 대한 정보를 확인할 수 있다.
위 그림의 경우 ubuntu:14.04에 해당하는 Image를 이용하여 testing이라는 Container를 생성한 후, 내부에 파일들을 생성해뒀다. 위 과정대로면 ubuntu:14.04라는 Image 위에 변경 사항에 대한 Layer가 붙은 형태로 새로운 Image를 생성하게 된다. Container를 생성하는 방법은 아래에서 다루게 된다.

4) Image 목록 확인

docker images
현재 로컬에 유지 중인 Image들의 목록을 확인할 수 있다.

5) Image 삭제

현재 Container들에 의해 참조되고 있지 않은 Image를 삭제할 수 있다.
docker image rm <image-name>
ImageLayer 구조로 그 형태를 유지하고 있으며, 이 덕분에 여러 Container가 특정 Layer를 이용해야 한다면 해당 Layer를 여럿 두지 않고도 재사용함으로써 Container를 생성할 수 있다. 위의 그림에서 출력된 각 줄은 Image를 구성하는 Layer들을 의미하고, Image 삭제는 Layer 단위로 이뤄지는 것을 확인할 수 있다. 이 때 삭제 결과는 UntaggedDeleted로 나뉜다. Untagged는 삭제하려는 Layer가 여전히 다른 Image에 의해 참조되고 있는 Layer이기 때문에 Layer 상에서 현재 Image의 이름만 지움으로써 참조만 없앤 것이다. 반면 Deleted는 삭제하려는 Layer가 다른 Image에 의해 참조되고 있지 않는 Layer이기 때문에 Layer 상의 현재 Image 이름을 지우면서 어떠한 참조도 일어나지 않아 완전히 삭제된 것을 의미한다. 위의 삭제 흐름을 자세히 살펴보면, Image 이름에 대한 Untagged가 먼저 일어나면서 이름을 먼저 삭제한 후, Image를 이루는 Layer들을 하나씩 삭제하는 것을 확인할 수 있다.
만일 Container에 의해 참조 중인 Image를 삭제하려 하면 오류 문구가 출력되며 삭제되지 않는다.

6) Image 강제 삭제

docker image rm -f <image-name>
옵션 없이 rm을 이용하면 ImageLayer 중 하나라도 참조 중인 Container가 존재하면 Image를 삭제할 수 없었다. 하지만 Unix 명령어 rm과 마찬가지로 -f 옵션으로 삭제를 강제할 수 있다.
위 그림을 자세히 살펴보면, -f 옵션을 이용하여 testing:first라는 Image가 잘 삭제된 것처럼 보이지만, 자세히 살펴보면 UntaggedImage의 이름만 삭제한 것을 확인할 수 있다. 이전 과정에서 Image를 정상적으로 삭제했다면, 이름에 대한 삭제 후에 Layer들의 삭제 내역을 확인할 수 있었는데 위 그림에서는 그렇지 않다. 위처럼 나타난 경우는 지우려는 Image가 어떤 Container에 의해 참조되고 있기 때문에 Layer는 삭제되지 않고 Image의 이름만 삭제된 것이다. 이렇게 Image 이름이 존재하지 않고 그 Layer들만 존재하는 ImageDangling Image라고 한다. 따라서 Image를 강제로 삭제하더라도 결과적으로는 참조 중인 Container들을 모두 정리 후에 Image를 삭제해야 깔끔하게 삭제되는 것을 알 수 있다.
Dangling Image는 이름이 존재하지 않는 Image므로 위 그림과 같이 <none>이라는 이름으로 나타난다.

7) Dangling Image 목록 확인

docker images -f dangling=true
로컬에 유지하고 있는 Image들 중에서도 Dangling Image만 확인할 수 있다.

8) Dangling Image 삭제

docker image prune
현재 로컬에 존재하는 모든 Dangling Image들을 삭제한다. 단, Dangling Image를 모두 지우려고 할 때 현재 Dangling ImageLayer를 참조하고 있는 Container가 하나라도 존재한다면, prune 명령어를 사용해도 Layer들은 삭제되지 않는다.
결과적으로 Image 삭제를 정확하게 수행하려면, 삭제하려는 ImageLayer를 어떤 Container도 참조하고 있지 않아야 한다.
Dangling ImageLayer를 참조하고 있는 Container가 있다면, 위 그림처럼 Dangling ImageLayer들이 하나도 정리되지 않는 것을 볼 수 있다. 따라서 이를 참조하고 있는 Container들을 모두 삭제한 뒤에야 prune 명령어가 제 기능을 할 수 있다.

9) Image 정보 확인

docker image inspect <image-name>
Image는 단순히 어떤 Layer들로 이뤄졌는지 뿐만 아니라 정말 많은 정보들을 갖고 있다. inspect를 통해 이 정보들을 확인할 수 있다.

10) Image 이름 추가

docker tag <image-name> <hub-id>/<image-name>
명령어를 수행하면 동일한 ImageDigest를 유지하지만, 각기 다른 <image-name>이 부여되는 것을 확인할 수 있다. 즉, Image에 추가적으로 이름을 부여하는 것과 동일한데 이는 Docker HubImage를 업로드할 때 주로 이용 된다. 현재 로컬에서 사용하고 있는 Docker HubID으로 <hub-id>를 설정함으로써 문제 없이 Image를 업로드 할 수 있다.

11) Image 업로드

docker push <hub-id>/<image-name>
<image-name>에 해당하는 ImageDocker Hub에 업로드 한다. Docker Hub의 어떤 계정에 업로드 하는지는 <hub-id>를 통해 결정하므로 <hub-id>Docker HubID와 동일해야 하며, 로컬에서도 로그인이 되어 있어야 한다. 문제 없이 push가 된다면, <image-name>이 곧 Repository의 이름이 된다.
Docker Hub에서 Public Repository의 개수는 제한이 없지만, 별도의 유료 서비스를 이용하지 않는다면 Private Repository는 오로지 1개만 둘 수 있다. 따라서 유료 서비스를 이용하고 싶지 않다면 Private Registry를 만들어서 Container로 운영할 수도 있다. Private RegistryDocker에서 공식적으로 배포하는 Image가 있으므로 이를 이용하면 된다.
별도로 tag 명령어를 통해 <hub-id>에 대한 설정이 되어 있지 않다면 위 그림처럼 오류를 뱉으며 요청이 거절되는 것을 확인할 수 있다.
<hub-id>에 대한 설정이 되어 있더라도 Docker HubID로 로컬에 로그인이 되어 있지 않다면 위 그림처럼 오류를 뱉으며 요청이 거절되는 것을 확인할 수 있다.

12 ) Image 내보내기

docker image save -o <file-name> <image-name>
Image를 별도로 저장하거나 옮길 때 바이너리 파일로써 이용해야 할 수도 있다. save 명령어를 이용하면 Container를 생성할 때 사용했던 명령어, Image의 이름 및 태그, 그리고 Image의 모든 메타 데이터를 포함하여 내보내는 것이 가능하다. -o 옵션을 이용하면 표준 출력으로 결과를 내보내지 않고 <file-name>의 이름으로 된 파일로 저장하겠다는 것이다. save를 수행하게 되면 tar로 아카이빙 된 파일을 만들며, 해당 명령어는 load 명령어와 쌍으로 이용된다.

13 ) Image 가져오기

docker image load -i <file-name>
바이너리 파일로 유지 중인 Image를 가져올 때 사용된다. load 명령어를 이용하면 Container를 생성할 때 사용했던 명령어, Image의 이름 및 태그, 그리고 Image의 모든 메타 데이터를 포함하여 가져오는 것이 가능하다. -i 옵션은 표준 입력을 이용하지 않고 문자열 인자로 <file-name>을 받겠다는 것이다. save 명령어와 쌍으로 이용되므로 load에 사용되는 파일은 tar로 아카이빙된 파일이어야 한다.

14) export

docker export -o <file-name> <container-name>
save와 비슷한 기능을 하지만 exportContainer를 바이너리 파일로 내보내는 명령어이다. Container를 바이너리 파일로 내보내는 과정에서 Image에 대한 모든 메타 데이터가 포함되지 않기 때문에, 이는 곧 Container파일 시스템을 내보내는 것과 동일하다. export를 수행하게 되면 tar로 아카이빙 된 파일을 만들며, 해당 명령어는 import 명령어와 쌍으로 이용된다.

15) import

docker import <file-name> <image-name>
load와 비슷한 기능을 하지만 export된 바이너리 파일에 대해서 Image로 저장하는 명령어이다. exportContainer파일 시스템을 저장하는 명령어였기 때문에 import를 수행하면 바이너리 파일로 유지 중인 파일 시스템Image로 만들게 된다. export와 쌍으로 이용되므로 import에 사용되는 파일은 tar로 아카이빙된 파일이어야 한다.

4. Container Commands

Container 생성의 —name 옵션을 제외하고, Container에 대한 모든 명령어에서 <container-name> 대신 <container-id>를 이용해도 무방하다. Container는 한 번 생성되면 임의로 16자리 이상의 HashingDigest 값을 <container-id>로 이용한다. Container에 대한 명령어를 이용할 때 <container-id>를 모두 입력할 필요 없이 일부만 입력해도 되며, <container-id>의 일부만으로 찾아낼 수 있는 Container가 여럿 있으면 명령어 수행이 이뤄지지 않는다.
Container 생성에는 Port, Volume, Network 에 대한 옵션 뿐만 아니라 Container가 정지 되었을 때 재시작을 할 수 있도록 이와 관련된 정책을 설정할 수 있는 많은 옵션들이 존재한다. 따라서 docker container create —help 혹은 docker container run —help를 통해 옵션들을 하나 하나 살펴보거나 Docker 공식 도큐멘트를 직접 찾아보는 것을 추천한다. VolumeNetwork에 대해선 차후에 다룬다.

1) Container 생성

docker container create —name <container-name> <image-name>
Image로 부터 Container를 생성할 수 있다. 경우에 따라 Container 생성으로 얻은 <container-id>로 조작하기 까다로울 수 있기 때문에 —name 옵션으로 Container의 이름을 부여하여 다른 명령어들을 편하게 이용할 수 있다.
DB를 돌리고 있는 서버와 API 요청을 받는 서버를 연동해야 할 때, Container로 각 서버를 운영하고 있다면 쉽게 연동할 수 있다. create 명령어는 —link라는 옵션을 제공한다. MySQLWordPress를 이용할 때 각 Container를 서로 연동하여 서비스를 두고 싶다면, 위 그림처럼 쉽게 처리할 수 있다. run 명령어는 create를 포함하기 때문에 —link 옵션을 이용할 수 있으며 -p 옵션은 Port에 대한 설정을 의미한다. run 명령어와 -p 옵션은 글 아래 부분에서 다루게 된다.

2) Container 실행

docker container start <container-name>
Container를 생성했다고 해서 자동으로 실행되는 것은 아니기 때문에 별도의 명령어로 실행시켜야 한다. 위 명령어를 이용하더라도 무조건 Container가 실행되는 것은 아니다. Container 생성 시 Container 내부에서 동작하는 프로세스에 대한 설정이 없다면 Container가 실행되지 않는다. 이는 Container가 프로세스로서 동작하기 때문이다. <container-name>은 1개 이상을 줄 수 있으며, 인자로 받은 <container-name>들에 대해 Container를 실행한다. 별도로 -a 옵션을 붙인 것이 아니면 Container는 포그라운드가 아닌 백그라운드로 실행된다.
이전 과정에서 생성된 container_no_running를 실행하면 Container는 정상적으로 실행되지 않는다. container_no_runningtesting:first라는 Image를 기반으로 하는데, 이는 ubuntu:14.04를 이용하여 만들어진 Image이다. 해당 Image는 단순히 Linux 환경만 구축할 뿐 내부적으로 실행되고 있는 프로세스가 없기 때문에 아무리 실행 명령어를 입력해도 Container는 실행되지 않는다. 이 같은 상황에서 Container를 문제 없이 실행하고 싶다면, 옵션을 통해 터미널을 생성하여 표준 입력을 받을 수 있는 상태로 Container를 생성해야 한다. 터미널 생성 시 자동으로 쉘이 실행되므로 Container가 문제 없이 실행 상태에 있게 된다.
옵션을 통해서 터미널을 생성하여 표준 입력을 받을 수 있는 상태로 Container를 생성하기 위해선, 위처럼 -i-t 옵션이 요구된다. -t 옵션이 터미널을 생성하는 옵션이며, -i 옵션을 통해 터미널과 상호작용을 할 수 있다.

3) 실행 중인 Container 목록 확인

docker ps
Unix 명령어의 ps처럼 현재 실행 중인 Container들의 목록을 확인할 수 있다.
이전 과정에서 container_no_running은 실행이 되지 않기 때문에 Container 생성에서 -i-t 옵션을 적용한 container_running만 실행 중인 Container인 것을 확인할 수 있다.

4) 모든 Container 목록 확인

docker ps -a
Unix 명령어의 ps처럼 모든 Container들의 목록을 확인할 수 있다. (물론 Unix 명령어의 ps에서 -a 옵션은 Docker 명령어의 ps에서 -a 옵션처럼 모든 프로세스를 보여주는 것이 아니라 그저 현재 시스템에서 다른 사용자가 실행시킨 모든 프로세스를 포함하여 보여주는 것이지만, 맥락 상 모든 프로세스를 보여준다는 점에서 동일하다.)

5) Container 포그라운드

docker container attach <container-name>
실행 중인 Container를 포그라운드로 확인할 수 있다.
별도의 데몬 프로세스가 돌아가도록 만들지 않고 Container 생성의 -i-t 옵션이 적용되어 있다면 터미널을 이용할 수 있는 상태로 포그라운드가 되므로 표준 입력을 받아 사용자와 상호작용 할 수 있는 상태가 된다. 이 상태에서 Container를 빠져나오기 위해선 exit이라는 명령어를 이용하거나 <C-p,q> 단축키를 이용하면 된다. exit 명령어는 현재 실행 중인 쉘과 터미널을 종료시키므로 Container는 정지 상태가 되고, <C-p,q> 단축키를 이용하면 단순히 터미널에서 일시적으로 나오는 것이므로 Container는 여전히 실행 상태로 유지된다.
-i와 -t 옵션을 Container를 생성할 때 기재를 하든 하지 않든, 특정 데몬 프로세스가 돌아가도록 만들어두면 Container의 실행 상태를 포그라운드로 만들었을 때 특정 데몬 프로세스가 동작하면서 표준 출력을 이용한 내역들을 그대로 확인할 수 있다. 다만 이 때 -i-t 옵션을 이용했더라도 특정 데몬 프로세스가 표준 출력을 지속하고 있는 상황이기 때문에 -i-t 옵션을 적용하지 않은 것처럼 명령어를 입력하는 상호작용은 이뤄질 수 없다. 따라서 해당 Container의 터미널을 이용하여 상호작용을 하고 싶다면 exec 명령어를 이용하여 새로운 쉘을 실행하면 된다. 위의 그림에서 -e 옵션은 환경 변수 설정을 위한 옵션으로, 해당 옵션이 없다면 MySQL 데몬 프로세스가 동작하지 않으므로 Container 실행이 불가능하다.

6) Image 다운로드 + Container 생성, 실행, 포그라운드

docker container run —name <container-name> <image-name>
<image-name>에 해당하는 Image가 로컬에 존재하지 않는다면 Docker Hub에서 자동으로 다운로드 받아 Container를 생성하고 실행한다. -i-t 옵션을 기재할지는 선택 사항이며, Linux 환경 구축처럼 별도의 데몬 프로세스가 돌아가도록 만든 것이 아니라면 -i-t 옵션을 기재하는 것이 좋다. -i-t 옵션은 create 명령어의 옵션을 이용한 것이다. run 명령어의 detach를 의미하는 옵션이 없기 때문에 start 명령어의 -a 옵션을 자동으로 적용하여 Container의 실행 상태를 포그라운드로 만든다.

7) Image 다운로드 + Container 생성, 실행, 백그라운드

docker container run -d —name <container-name> <image-name>
<image-name>에 해당하는 Image가 로컬에 존재하지 않는다면 Docker Hub에서 자동으로 다운로드 받아 Container를 생성하고 실행한다. -i-t 옵션을 기재할지는 선택 사항이며, Linux 환경 구축처럼 별도의 데몬 프로세스가 돌아가도록 만든 것이 아니라면 -i-t 옵션을 기재하는 것이 좋다. -i-t 옵션은 create 명령어의 옵션을 이용한 것이다. run 명령어의 detach를 의미하는 옵션이 적용되어 있으므로 start 명령어를 별도의 옵션 없이 사용하여 Container의 실행 상태를 백그라운드로 만든다.
Container 생성 시 -i-t 옵션을 적용했지만 Linux 환경 구축처럼 별도의 데몬 프로세스가 돌아가지 않는 상태에서 백그라운드로 만들어뒀더라도 attach 명령어를 이용하면 -i-t 옵션의 터미널을 이용할 수 있게 된다.

8) Container의 Port 확인

docker container port <container-name>
특정 ContainerPort 정보를 확인할 수 있다.
Container를 생성할 때 별도의 Port를 바인딩하지 않았다면 ps 명령어를 통해 Port를 확인해도 아무런 값이 적혀 있지 않다. ContainerPort와 머신의 Port를 바인딩 하기 위해선 -p 옵션을 이용하여 Container를 생성하면 되고 이것이 곧 포트 포워딩이 된다. <container-port> : <host-port>와 같은 형식으로 이용하며, <host-port>가 생략되면 머신의 무작위 Port로 바인딩 된다.

9) Container에서 명령어 수행

docker container exec <container-name> <command> <args>
실행 중인 Container에 대해 특정 명령어를 수행하도록 만든다. -i-t 옵션을 통해 새로운 터미널을 생성하여 상호작용이 가능하도록 만들 수 있다. 해당 옵션들이 없다면 단순히 명령어를 실행만 하게 되므로 별도의 상호작용은 불가능하게 된다.
특정 데몬 프로세스가 돌아가고 있을 때 attach 명령어를 이용하여 Container의 실행 상태를 포그라운드로 만들어도 Container 생성에 이용한 -i-t 옵션 여부에 상관 없이 상호작용이 불가능 했었다. 이 상황에서 exec 명령어의 -i, -t 옵션을 이용하여 쉘을 실행시키면 해당 Container와 상호작용을 할 수 있다.
쉘의 -c 옵션을 이용하면 쉘을 실행한 상태로 유지하는 것이 아니라 인자로 받은 명령어를 수행만 한 후 그 역할을 마치게 된다. 따라서 이 경우에는 -i-t 옵션을 주든 주지 않든 결과가 같으므로, exec 명령어의 -i-t는 사용하지 않는 것이 낫다.

10) Container 정보 확인

docker container inspect <container-name>
Image의 정보를 자세히 확인했던 것과 마찬가지로 Container의 정보를 자세히 확인할 수 있다.

11) Container 이름 변경

docker container rename <src-container-name> <target-container-name>
Container의 이름을 변경할 수 있다. Container 생성 시 이름을 잘못 설정했을 때 유용하게 사용할 수 있다.

12) Container 로그 확인

docker container logs <container-name>
DockerContainer가 실행 중에 이용하는 표준 출력과 표준 에러에 대해 별도의 파일을 두어 메타 데이터로 기록하도록 되어 있는데, Docker Engine은 이러한 파일을 확인할 수 있는 명령어를 기본적으로 제공한다. 메타 데이터들은 json 형태로 기록되기 때문에 위 명령어를 통해 출력된 로그들은 json 형태의 메타 데이터를 가공하여 출력된 것이다.
run 명령어를 이용할 때 —log-opt로 로그에 대한 다양한 옵션을 적용할 수 있으며, 대표적으로 max-size 혹은 max-file을 이용한다. max-sizejson 로그 파일의 최대 크기를 제한하고, max-filejson 로그 파일 개수의 제한을 둘 수 있다. Docker Engine이 기본적으로 이용하는 Log DriverDocker 데몬의 설정 값을 바꿈으로써 변경할 수 있다. Log Driver는 이처럼 json 로그 파일을 두는 것 이외에도 클라우드 플랫폼을 이용하는 경우에는 해당 플랫폼에서 제공하는 Log Driver를 이용할 수도 있고, 이를 통해 로그들을 파일로써 유지하는 것이 아니라 DB에 기록하는 것도 가능하다. Docker 데몬에 대한 내용은 다른 글에서 다룬다.
로그를 이용하는 것은 디버깅 및 운영 측면에서 굉장히 중요하다. Container에서 어플리케이션을 돌리는데 있어서 발생한 모든 기록들을 볼 수 있기 때문에 어떤 문제가 발생했는지 확인할 수 있다. json 형태의 메타 데이터들은 /var/lib/docker/containers에 <container-id>-json.log 파일로 유지되는 것을 확인할 수 있다.
docker run -i -t --rm --privileged --pid=host justincormack/nsenter1
이를 실제로 확인하려 했을 때 현재 사용자의 경로에는 /var/lib/docker/container가 존재하지 않는 것을 볼 수 있는데, 그 이유는 DockerLinux 상에서 동작하는 것이 아니라 Linuxkit이라는 가상화된 Kernel 위에서 동작하기 때문이다. (Docker Toolbox를 이용한다면 Linuxkit이 아니라 별도로 설치한 Linux 상에서 확인할 수 있다.) 따라서 위와 같은 경로를 확인하기 위해선 Linuxkit 내부로 들어가는 것이 우선이다. Linuxkit 내부로 들어가는 방법은 여럿 있지만 간단하게 위에 주어진 명령어로 Container를 만들어 구동함으로써 Linuxkit에 대한 쉘을 실행할 수 있다. 주어진 경로를 타고 들어가보면 json 로그 파일이 존재하는 것을 확인할 수 있다.

13) Container 정지

docker container stop <container-name>
실행 중인 Container를 정지 상태로 만든다.

14) Container 삭제

docker container rm <container-name>
정지 상태의 Container를 삭제할 수 있다.
실행 중인 Container를 삭제하려 하면 위 그림처럼 오류가 발생하는 것을 확인할 수 있다. 따라서 강제로 Container를 삭제하지 않는 한, 반드시 Container를 정지한 후에 삭제해야 한다.

15) Container 강제 삭제

docker container rm -f <container-name>
Container가 실행 중이더라도 강제로 Container를 삭제한다.

16) 정지 상태의 Container 삭제

docker container prune
Imageprune 명령어가 이름이 없는 ImageDangling Image를 모두 삭제하는 명령어였다면, Containerprune은 단지 정지 상태로 존재하는 모든 Container를 삭제하는 명령어이다.

17) 모든 Container들의 ID 확인

docker ps -a -q
모든 Container들의 <container-id>를 알아낼 수 있다. 이를 이용하여 다양한 명령어를 활용할 수 있다.
예상하다시피 -a 옵션이 없다면 현재 실행 중인 Container들의 <container-id>만을 알아낼 수 있다.
<container-id>를 통해 한 번에 Container들을 정지시키는 것이 가능하다.
Container의 삭제는 정지되어 있는 Container들을 대상으로 가능하므로 위처럼 Container를 한 번에 정지시킨 후 Container들을 삭제하는 것이 가능하다.
Container들을 정지시키는 과정 없이 강제로 모든 Container들을 삭제하는 것도 가능하다.

18) Container 자원 제한 갱신

docker container update <resource-control-option> <container-name>
Container 생성 시에 설정했던 자원 제한 옵션을 갱신할 수 있다. 글 윗 쪽의 Container 자원 제한 부분에서 설명된 옵션들에 대해서 갱신이 가능하다.

5. Reference