* Dockerfile이란?

도커 명령어로 컨테이너를 구성하기 위해서는 컨테이너 생성, 컨테이너 안에서 OS설정, 미들웨어 설치, 파라미터 설정 등은 모두 직접 수행해야 한다. 이러한 일련의 작업을 자동으로 수행하도록 스크립트로 만든것이 dockerfile이다. 이러한 Dockerfile은 필요한 환경설정이 모두 세팅된 image를 만드는 데 목적이 있으며, 또한 자동화로써 사용할 수 있고 스크립트이므로 변경해서 재사용할수도 있다.이러한 도커파일의 내용은 아래와 같은 정보들을 기술한다.

 

1. 베이스가 될 도커 이미지

2. 도커 컨테이너 안에서 수행할 명령, 조작 등

3. 환경변수 등의 설정

4. 도커 컨테이너 안에서 작동시킬 데몬 실행

 

* Dockerfile 구문 기본

Dockerfile은 텍스트 형식이며 확장자는 없다. 만들기 위해서는 어떤 에디터도 상관없다. 메모장도 좋고, vi editor, notepad++, ultraedit 등등 어떤 프로그램도 상관없다. visual studio code는 dockerfile 작성을 위한 확장파일을 제공하므로 좀 더 편하게 사용할 수 있다. 이러한 텍스트 파일의 이름은 기본적으로 Dockerfile 이라는 이름을 사용한다. 만약 이름을 변경하는 경우, 이미지 build 시 따로 옵션을 줘서 해당 파일명을 명시해줘야 한다. 

 

기본 서식은 "명령 인수" 형식이다. FROM centos:centos7 이런 식. 또한 명령 부분은 대소문자를 가리지는 않지만, 가독성을 위해 관례적으로 대문자를 사용한다. 또한 주석도 가능하며 주석은 맨 앞에 #을 붙이면 된다. 명령의 종류는 아래와 같으며, 각 상세 명령의 사용과 설명은 맨 아래 Docker 명령 상세 설명 부분을 참고한다.

명령

설명

상세

FROM

베이스 이미지 지정

docker 컨테이너를 어떤 docker 이미지로부터 생성할지 기술

RUN

명령 실행

from에서 지정한 베이스 이미지에 대해 수행하는 명령

CMD

컨테이너 실행 명령

from에서 지정한 베이스 이미지에 대해 데몬을 실행하는데 자주 사용

LABEL

라벨 설정

도커파일에 기술할 기타정보들을 기록한다. 만든사람, 버전 등

EXPOSE

포트 익스포트

포트를 오픈한다.

ENV

환경변수

도커파일 안에서 환경변수를 설정한다.

ADD

파일/디렉토리 추가

 

COPY

파일 복사

 

ENTRYPOINT

컨테이너 실행 명령

cmd와 동일하나 컨테이너 생성명령 수행시 무조건 우선적이다.

VOLUME

볼륨 마운트

 

USER

사용자 지정

특정 명령을 수행할 때 사용할 사용자를 명시한다.

WORKDIR

작업 디렉토리

명령어가 실행될 경로를 지정

ARG

dockerfile 안의 변수

도커파일에서 변수를 만들 때 사용

ONBUILD

빌드 완료 후 실행되는 명령

빌드한 후 나온 이미지를 다시 빌드할 때 실행되는 명령

STOPSIGNAL

시스템 콜 시그널 설정

컨테이너 종료시 사용할 시그널 설정

HEALTHCHECK

컨테이너의 헬스체크

일정 주기로 컨테이너의 헬스를 체크하여 결과를 inspect로 볼수있게 한다.

SHELL

기본 쉘 설정

도커파일에서 사용할 쉘 지정

2020/06/15 - [Docker Basic] - [Docker Basic] 17. Dockerfile 상세 명령

 

* Dockerfile 만들기

1. docker build 명령

docker build 명령은 dockerfile에 기술된 구성정보를 바탕으로 도커 이미지를 생성한다. 즉 dockerfile을 사용하는 프로세스는 다음과 같은 절차로 작업이 진행되게 된다. 

 

dockerfile 생성 → docker build 로 이미지 생성 생성된 이미지로 docker container run 수행

 

dockerfile에 명시된 이미지가 없는 경우 자동으로 다운로드를 받으며, 그것이 베이스 이미지가 된다. 그리고 이후 명령어들을 모두 수행한 다음, 그 상태를 이미지로 만든다. 만들어진 이미지의 이름을 build 명령 수행 시 명시해야 한다. 또한 Dockerfile 그대로 이름을 쓰지 않고 따로 임의의 파일명을 사용하는 경우, -f 옵션을 사용해야 한다. 참고로, 임의의 파일명을 쓰는 경우 Docker hub에서 이미지 자동 생성 기능을 사용할 수 없다.

 

형식

docker build -t [생성할이미지명]:[태그명] [dockerfile위치]

 

예시

docker build -t sample -f Dockerfile-base .

- 해당 예시 맨 뒤의 . 은 현재 경로를 의미한다. 현재 경로에 Dockerfile-base 파일이 있는 것.

 

# 참고 : 주의사항 1

Dockerfile을 빌드할 때, 내용을 검증하지 않는다. 있는 그대로 만들 뿐이므로 에러가 있지 않도록 직접 잘 검증해야 한다.

 

# 참고 : 주의사항 2

dockerfile 빌드 시, 해당 dockerfile이 있는 디렉토리 전체의 내용을 모두 docker daemon으로 전송한다. 예를들어 루트디렉토리에 dockerfile을 놓고 build를 실행해버리면 영 좋지 않을 것이다. 따라서 dockerfile은 따로 빈 디렉토리 안에 만들어 놓고 build를 수행해야 한다.

 

# 참고 : 빌드할 때 불필요한 파일 제외하기

도커에서 빌드하면 빌드를 실행한 디렉토리 아래에 있는 모든 파일이 docker 데몬으로 전송된다. 빌드에서 제외하고 싶은 파일이 있는 경우, ".dockerignore" 라는 이름의 파일안에 해당 파일명을 기술하면, 해당 파일은 제외된다. 여러개의 파일을 지정할 때는 줄바꿈을 해서 파일명을 나열한다. 경로 /test에 Dockerfile hello.txt 가 있는데 hello.txt는 제외하고 싶다면, 해당 경로에 .dockerignore를 만들고 내용에 hello.txt를 입력하면 된다. 만약 제외하도록 명시한 파일을 Dockerfile에 ADD 등으로 들어가게 하면, 도커파일 빌드할 때 파일이 없다고 에러가 발생하므로 참고할 것.

 

2. docker history 명령

dockerfile을 빌드한 후 생성된 이미지에 대해 어떤 이미지가 순차적으로 만들어져 있고, 용량은 얼마이고, 이미지ID는 무엇인지 등 상세한 정보를 확인할 수 있다.

docker history 이미지명

이러한 docker history의 결과는 아래서부터 시작하여 맨 위가 끝인 순서로 기록된다. missing 부분은 base image의 history이다. 중요하게 봐야 할 것은 missing이 아닌 부분이며 이것들이 실제로 dockerfile에 기록된 각 step 이다.

 

또한 CREATED BY 아래에 #(nop)라는 부분이 있는데, 그것의 의미는 명령어가 아닌 것들을 의미한다. 명령어는 "실행" 된다. 하지만 #(nop)가 있는것은 "실행"하는 게 아니다. 예를들어 ENV foo=bar 이런것은 "실행"하는 것이 아님.

 

 

 

* Dockerfile과 Docker image

Dockerfile 명령은 해당 스크립트를 build 할 때 각 항목들을 Image Layer 로서 중간 이미지들을 생성한다. 이렇게 하면 다른 이미지를 빌드할 때 이런 중간 이미지들을 내부적으로 재이용하여 빌드의 속도를 높이고 용량 낭비도 줄일 수 있다. 아래 예시를 통해 이미지가 어떻게 사용되는지 확인할 수 있다. 

 

 

예시 : Dockerfile과 Docker image의 관계

 

- 사전정보 1 : Dockerfile 내용

FROM ubuntu:16.04
RUN apt-get -y update && apt-get -y upgrade
RUN apt-get -y install nginx
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

- 사전정보 2 : docker images 명령

사전정보 1에서는 ubuntu:16.04 이미지를 사용한다. 그리고 사전정보 2를 보면 이미 ubuntu 16.04 이미지를 이미 보유한 상태로 확인할 수 있다. 이미지 아이디는 005d2078bdfa 이다. 기억해두자. 이제 다음과 같이 빌드를 수행하면, 아래와 같은 Step을 진행한다.

docker build -t dockerfile_ubuntu-nginx .

 

Step 1/5 : FROM ubuntu:16.04

 ---> 005d2078bdfa

 

Step 2/5 : RUN apt-get -y update && apt-get -y upgrade

 ---> Running in 23ec84a0a536

Get:1 http://archive.ubuntu.com/ubuntu xenial InRelease [247 kB]

Processing triggers for libc-bin (2.23-0ubuntu11) ...

(중략)

Removing intermediate container 23ec84a0a536

 ---> 3d241e1c26f6

 

Step 3/5 : RUN apt-get install -y -q nginx

 ---> Running in a1a6eaaa9313

Reading package lists...

(중략)

Processing triggers for sgml-base (1.26+nmu4ubuntu1) ...

Processing triggers for systemd (229-4ubuntu21.28) ...

Removing intermediate container a1a6eaaa9313

 ---> 90f723b65664

 

Step 4/5 : EXPOSE 80

 ---> Running in 38f032d07f6a

Removing intermediate container 38f032d07f6a

 ---> d62272ebb369

 

Step 5/5 : CMD ["nginx", "-g", "daemon off;"]

 ---> Running in 88d82f578ea5

Removing intermediate container 88d82f578ea5

 ---> c6dca2667105

 

Dockerfile의 각 라인이 하나의 Step이 되며, 각 Step마다 여러 색깔로 표기한 IMAGE ID가 생성되는 것을 확인할 수 있다. Step 1/5 를 보면, ubuntu:16.04를 받아야 하는데, 이미 보유하고 있으므로 다운받지 않는다. 또한 출력되는 IMAGE ID는 005d2078bdfa인데, 이 코드는 docker images의 결과로 나왔던 ubuntu 16.04 버전과 동일한 ID이다. 따라서 Dockerfile을 build할 때는 원래 있는 이미지는 재사용하는 것을 알 수 있다.

 

도커 이미지뿐만 아니라 위 1~5 Step의 모든 항목들은 이미지화 되며, 이러한 이미지들을 저장해 두었다가 다른 Dockerfile에서 동일한 내용을 사용할 때 이미지를 재사용 하는 것이다. 이렇게 모아져서 만들어진 최종 이미지인 dockerfile_ubuntu-nginx 는 005d2078bdfa, 3d241e1c26f6, 90f723b65664, d62272ebb369, c6dca2667105 이미지들이 layer로 겹져쳐서 생성되는 것이다.

dockerfile을 build할 때는, 각 항목을 실행하고 컨테이너를 만들고 컨테이너를 이미지화하고 컨테이너를 삭제하는 일련의 작업을 반복한다. 그래서 위처럼 이미지가 생성되는 것이다. 그런데 dockerfile을 build 할 때 실패하거나 중단하는 경우, docker images나 docker container ls에 이상한 것들이 있을 수 있다. 이것들은 위의 컨테이너 생성, 이미지화, 삭제 등의 일련의 진행 중 취소나 중단이 되었기 때문에 생기는 것이다. 이런것들은 따로 삭제해주면 된다.

 

# 참고 : 도커파일의 레이어 이미지 (중간이미지) 개수를 줄이기

- 여러가지 방법이 있는데, 대표적으로 RUN 명령이 여러개라면 하나로 합칠 수 있다.

- 예를들어, yum -y install httpd , yum -y install php 이렇게 2개라면, yum -y install httpd php  이렇게 합친다.

- 이런식으로 명령어들을 제작자가 입맛에 맞춰 만들 수 있다. && 같은 다중 명령어 등도 사용할 수 있다.

 

도커의 컨테이너안에 데이터를 저장할 수 있지만 여기에는 몇 가지 문제점이 존재한다.

1. 컨테이너가 삭제되면 데이터도 삭제된다. 
2. 다른 프로세스에서 특정 컨테이너에 저장된 데이터를 사용하기 어렵다.
3. 컨테이너에 데이터를 저장하기 위해서는 파일시스템을 관리하는 스토리지 드라이버가 필요하다.

   그러나 이 기능은 호스트 파일시스템에 직접 쓴는 것보다 성능이 떨어진다.

따라서 이러한 문제를 해결하기 위해 여러가지 방법이 있는데, 그 중 2가지 방법을 알아본다. 첫번째는 호스트 서버의 경로와 컨테이너의 경로를 연결하는 방식, 두번째는 docker volume을 생성해서 연결하는 방식이다.

 

 

* 호스트 서버의 경로와 컨테이너의 경로를 연결

- docker container run -v 옵션을 사용해서 호스트의 디렉토리와 마운트될 컨테이너의 디렉토리를 명시할 수 있다.

 

 

1. 호스트 디스크를 명시하지 않고 생성

컨테이너에서 -v 명령에서 명시될 디렉토리들은 없으면 알아서 만들어진다. (따라서 미리 만들 필요가 없다) 

- 아래 예시는 한 컨테이너 안에 2개의 마운트 포인트를 만드는 것이다.

- 이 마운트 포인트는 호스트 서버의 /var/lib/docker/volumes에 저장된다. 컨테이너가 삭제되어도 해당 데이터는 남아있다.

- 호스트 디스크는 도커 디렉토리 (/var/lib/docker/volumes) 에 저장되므로, 해당 디렉토리 파티션의 용량이 한도가 된다.

docker run -it --name data_share -v /data_dir -v /test_dir ubuntu /bin/bash

- 참고로, 컨테이너 안에서 df -h 쳤을 때 마운트 포인트가 2개가 나와야 하는데, 하나만 나온다. 2개가 실제로는 있다.

- mount 명령으로 확인하면, 잘 마운트 되어있는 것을 확인할 수 있다.

 

- 호스트에서 해당 컨테이너의 마운트 정보를 보려면 다음과 같이 입력한다.

docker inspect --format="{{ .HostConfig.Binds }}" data_share

 

2. 호스트 디스크를 명시하고 생성

- 호스트 디스크를 따로 명시하므로, 해당 디스크가 외장 스토리지가 될 수도 있고, 다른 내부 저장공간이 될 수도 있다.

- -v 옵션이후 호스트와 컨테이너 디렉토리는 없으면 알아서 만들어진다. 따라서 둘 다 일부러 따로 만들 필요가 없다.

- 이렇게 하면 호스트에 /test1 /test2를 만들어서 각각 컨테이너의 /data_dir, /test_dir 에 마운트된다.

docker run -it --name data_share -v /test1:/data_dir -v /test2:/test_dir ubuntu /bin/bash

- 호스트의 /test1, /test2는 외장 스토리지나 NFS 등 여러방식으로 외장 스토리지가 될 수 있다.

 

# 참고 : 디스크 마운트 설정 시ro/rw 옵션 외 기타 옵션 넣기

docker run -it --name data_share -v /test1:/data_dir:ro -v /test2:/test_dir:rw ubuntu /bin/bash

또한, --volume = host:container1:container2 이런식으로 다중컨테이너에 연결 가능

 

다른 옵션들은 더 이상 자세한 설명은 생략한다.

 

 

 

* docker volume을 구성해서 컨테이너에 연결

docker volume은 따로 docker에서 관리하는 형태로 호스트에 volume을 생성하며, 해당 볼륨은 docker volume ls 등으로 리스트를 확인할 수 있다.  따라서 관리하는 측면에서 훨씬 유용하다. 또한 volume이 저장되는 공간만 외장 스토리지로 마운트하여 대용량으로 사용할수도 있다.

 

도커 volume은 /var/lib/docker/volumes 경로에 저장된다.  또한 각 데이터는 /var/lib/docker/volumes/볼륨명/_data 밑에 저장된다. 이러한 volume 들은 컨테이너가 삭제되어도 그대로 유지된다. 또한 다른 컨테이너에서 다시 마운트하면 해당 데이터를 볼 수 있다.

 

 

1. docker volume 명령 리스트

< 도커 기본 명령어 차례 >

 

● 시스템 명령

docker version

docker system info

docker system df

docker system prune

docker login

locker logout

 

● 이미지 관리 명령

docker pull

docker image ls

docker images

docker image inspect

docker image tag

docker push

docker search

docker image rm

docker image prune

docker container commit

 

● 컨테이너 실행 및 조작 관련 명령

docker container create

docker container run

docker container attach

docker container exec

 

● 컨테이너 정보 확인 명령

docker container ps

docker container stats

docker container inspect

docker container top

docker container port

docker container diff

docker container logs

docker container ls

 

● 컨테이너 관리 명령

docker container stop

docker container start

docker container kill

docker container restart

docker container prune

docker container pause

docker container unpause

docker container rename

docker container cp

 

● 컨테이너/이미지 백업 명령

docker container export

docker container import

docker image save

docker image load

 

● 네트워크 명령

docker network ls

docker network create

docker network connect

docker network disconnect

docker networkinspect

docker network rm

brctl show

 

● 볼륨 명령 
docker volume create 
docker volume ls 
docker volume rm 
docker volume prune 
docker volume inspect


 

docker volume create  볼륨명

- docker에서 사용할 volume을 생성한다. 볼륨명을 명시하지 않으면, random한 이름으로 생성된다.

 

 

docker volume ls 

- 현재 생성되어 있는 모든 볼륨을 보여준다.

- 긴 이름으로 된 볼륨들은 위에서 수행한 "호스트 디스클를 명시하지 않고 생성하거나, 볼륨 생성시 따로 이름을 주지 않은 것들이다.

 

 

docker volume rm  볼륨명

- 현재 존재하는 볼륨을 삭제한다.

 

 

 

docker volume prune

- 사용하지 않는 모든 볼륨을 지울 수 있다. 

 

 

docker volume inspect 볼륨명

- volume의 상세 정보를 확인할 수 있다.

 

# 참고 : 컨테이너가 가지고 있는 볼륨을 확인하기

- docker inspect data_share | grep -i volumes

 

 

2. docker volume을 이용하여 컨테이너 생성하기

run -it --name disk-test -v test-volume:/test ubuntu /bin/bash 

 

 

 

* 동일 디스크 정보를 그대로 공유받기

- 생성된 볼륨이나 파일시스템들은 동시에 볼륨을 여러 컨테이너에 마운트할  수 있다. 아래와 같이 --volumes-from 옵션을 사용할 수 있다.

- 아래 명령은 맨 처음에 만든 data_share 라는 이름의 컨테이너가 가진 볼륨 정보를 그대로 공유받아서 마운트한다. 

docker run -d -it --name disk-test-share --volumes-from data_share  ubuntu /bin/bash

또 새롭게 컨테이너를 만들어서, 예를들어 이름을 disk-test-share2 하고 똑같은 data_share의 볼륨을 "공유" 받을 수 있다. 이렇게 하면 아래와 같은 형식이 될 것이다. 

 

 

 

* 오버레이 파일시스템 드라이버

overlay는 도커에서 사용하는 파일시스템 드라이버이다. 더 이상 자세한 설명은 생략한다. 컨테이너를 만들고 호스트에서 df -h를 쳤을 때 overlay 라고 마운트되어있는 것은 각 컨테이너의 루트디렉토리이다. 즉 /var/lib/docker/overlay2 에는 각 컨테이너의 루트 디렉토리, /var/lib/docker/volumes 에는 생성한 볼륨이 들어간다. 

 

호스트에서 df -h 를 쳤을 때. 아래 overlay라고 써있는 것이 도커 컨테이너들의 루트이다. 

또한 해당 데이터는 마운트 포인트인 /var/lib/docker/overlay2/이름/merged 디렉토리에 저장된다. 들어가보면 루트 파일시스템 같은 모양을 확인할 수 있을 것이다. 컨테이너가 삭제되면, 이 오버레이 마운트 경로의 데이터는 모두 삭제된다.

컨테이너에서 df -h를 쳤을 때. overlay 부분이 / 인 것을 확인할 수 있다.

 

# 참고 : 컨테이너 내에서 lsblk, blkid 명령은 제대로 나오지 않는다.

 

 

* -v 대신 사용하는 --mount 옵션

위에서 언급한대로 독립형 컨테이너에서는 -v / --volume이 사용되며, 도커 스웜 모드에서는 --mount가 사용된다.  이후 도커 17.06 부터 --mount를 독립형 컨테이너에서 사용할 수 있게 되었다. 따라서 -v 옵션 외에 --mount 옵션도 쓸 수 있다. 특히 특정 볼륨 드라이버를 명시해서 사용할 때는 --mount를 사용해야 한다. 


--mount 옵션은 쉼표(,)로 구분되고 각각 여러 key-value 쌍으로 구성된다. --mount 구문은 -v 또는 --volume보다 길지만, key의 순서는 중요치 않으며 Flag의 값을 이해하기 쉽게 되어있다.

 

예시

docker run -d -it --name=nginxtest --mount source=nginx-vol,destination=/usr/share/nginx/html,readonly nginx:latest

- type : mount의 유형을 지정한다. (bind, volume,tmpfs)

- source(src) : volume의 이름. 익명 볼륨을 사용한다면 생략해도 된다.

- destination(dst,target) : 컨테이너에 마운트 될 경로

- readonly : 컨테이너에 읽기 전용으로 마운트됨

opt 등 여러 추가 옵션이 있으나 더 이상 자세한 설명은 생략한다.

 

 

 

 

* 추가 예시 : dd로 디스크 img 파일을 만들어 할당하기

 

1. disk 파일 생성

dd if=/dev/zero of=파일명 count=512 bs=1M

 

2. 해당 이미지 파일로 파일시스템 생성

mkfs.ext4 harddrive.img

 

3. 호스트 서버에서 테스트 디렉토리 생성 후 마운트

mkdir /test
mount /harddrive.img /test

 

4. 도커 컨테이너를 만들고 해당 파일시스템을 참조시킨다.

docker run -it -v /test:/container_dr ubuntu /bin/bash

 

5. 컨테이너 상에서 확인. 파일시스템의 사이즈와 안에 있는 데이터를 확인할 수 있다.

df -h

 

도커 컨테이너에서는 여러가지 자원을 할당할 수 있는데, 할당하지 않으면 해당 컨테이너는 마음대로 호스트의 자원을 사용한다. (CPU, MEMORY, swap 등) 따라서 어플리케이션에 따라 사용하는 자원의 양을 제한하는 것은 매우 중요하다.  이러한 자원 제한은 cgroup의 기능을 도커 명령어로 손쉽게 사용할 수 있다.

 

docker container run 명령을 실행할 때, 아래 옵션들 (외에 추가로 많음) 을 사용해서 컨테이너의 자원을 어떻게 쓸지 정의할 수 있다. 이러한 제한이 없으면, 컨테이너는 Host OS의  자원을 제한없이 사용한다. 옵션 설정을 통해 다른 컨테이너의 동작 간섭을 방해하지 않게 할 수 있다.  생성 후 변경 시에는 docker update 명령으로 변경을 할 수 있다. 이러한 리소스 제한 기능은 리눅스의 cgroups 기능을 활용하는 것이다.

 

이러한 성능 제한 작업을 수행하면서 htop 등으로 모니터링 하여 변화를 테스트 할 수 있다. htop 사용을 위해서는 다음 링크를 참고한다.

2020/06/10 - [Performance] - Linux 시스템 모니터링 Tool - htop

 

Linux 시스템 모니터링 Tool - htop

htop은 기존의 top보다 더 향상된 시스템 모니터링 툴로서, 프로세스 뷰어 및 프로세스 관리자이다. top 과 두드러지게 다른점은 htop은 색을 사용하여 좀 더 효과적인 시각화를 꾀했고, 또한 CPU 각��

watch-n-learn.tistory.com

 

* 메모리 제한

 

메모리 관련 옵션

--memory, -m, --memory-swap 등

 

1. nginx의 메모리 사이즈를 1G로 제한하기

docker run -d --memory=1g --name=nginx_mem_1g nginx

 

# 참고 : swap 관련 메시지 발생하는 경우

WARNING: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.

grub을 수정해야 함. 호스트에서,  vi /etc/default/grub 에 GRUB_CMDLINE_LINUX_DEFAULT="cgroup_enable=memory swapaccount=1" 을 추가해야 함. 이후 grub rebuild 수행 후 리스타트

 

2. 메모리 확인

- Memory 부분이 1073741824 값이 나온다. 이것은 Byte 단위이다.

- 참고로, 이렇게 제한하지 않고 그냥 컨테이너를 생성하는 경우 다음과 같이 Memory 부분이 0으로 나온다.

 

# 참고 : 컨테이너 내부에서 시스템 정보를 확인할 때

- 컨테이너에서 /proc/meminfo에서는 해당 컨테이너가 아닌 해당 호스트의 정보가 나온다.

- 커널 버전이라던지, cpu 개수라던지 등도 마찬가지이다.

 

 

* swap 메모리 제한

1. ubuntu OS의 swap 메모리 사용을 300MB 로 제한하기

docker run --memory=200m --memory-swap=300m -it ubuntu /bin/bash

 

2. swap 메모리 확인

 

# 참고 : 만약, 사용하는 컨테이너에 실제 사이즈와 맞지않게 메모리를 제한하면 FAIL 발생한다.

docker run -it --memory=4m --name=mysql mysql:5.7

ERROR : mysqld faile whild attempting to check config

 

 

* CPU 제한

CPU를 제한하는 데는 여러가지 옵션이 있다. 그 중 대표적인 옵션만 설명한다.

 

# 참고

CPU 부하를 주기 위해 alicek106이 만든 stress라는 컨테이너를 사용한다. 또한 맨 뒤에 --cpu 1은 사용할 cpu의 개수를 의미한다.

 

1. 특정 코어로 CPU 제한 (--cpuset-cpus)

호스트에 CPU가 여러개 있을 때 --cpuset-cpus를 지정해 컨테이너가 특정 CPU만 쓰도록 할 수 있다. CPU 집중적인 작업이 필요하다면 여러개 CPU를 사용하여 작업을 잘 분배하도록 해야 한다. 아래 예시에서 확인할 수 있다.

 

* 특정 CPU 코어 1개 사용하도록 허용 
docker run -d --cpuset-cpus=3 alicek106/stress stress --cpu 1

* 특정 CPU 코어 2개 사용하도록 허용
docker run -d --cpuset-cpus=0,3 alicek106/stress stress --cpu 2 

* 특정 CPU 코어 3개 사용하도록 허용
docker run -d --cpuset-cpus=0-2 alicek106/stress stress --cpu 3

 

 

2. CPU 사용 비율 제한하기 (--cpus)

--cpus는 각 컨테이너에게 CPU를 최대 몇 % 할당할 수 있는지를 명시한다. CPU가 1개 기준으로, 값은 0~1이다. 1은 비율을 의미하며 100%를 의미한다. 즉 0.5 로 하면 50%까지 최대 사용할 수 있다는 뜻. 근데 만약 CPU를 3개로 했다면 값은 0~3이 되며 최대값 3이 100%를 의미하게 된다.  정리하면, 컨테이너가 사용하도록 허용한 CPU의 개수에 따라 사용량 최대값이 변한다는 것.

* CPU 3개를 100% 사용
docker run -d --cpuset-cpus=0-2 --cpus=3 alicek106/stress stress --cpu 3

* CPU 3개를 각각 약 33%씩 사용 
docker run -d --cpuset-cpus=0-2 --cpus=1 alicek106/stress stress --cpu 3 

 


* CPU 1개를 50% 사용
docker run -d --cpuset-cpus=1 --cpus=0.5 alicek106/stress stress --cpu 1

 

3. 상대적 가중치 제한 (--cpu-shares)

--cpu-shares는 한 코어에 여러 도커 컨테이너가 작동할 때, 컨테이너간 상대적 가중치이다. 실제 프로세서 클럭 속도와는 관련이 없다. 모든 컨테이너는 기본적으로 1024 값을 가지고 있고, 혼자 한 코어를 사용할 때는 아무 의미가 없다. 한 코어에 두 컨테이너가 시작하는 경우, cpu-share 값을 설정하지 않으면 기본값인 1024를 가지기 때문에 CPU 시간이 둘 다 균등하게 분할된다. (해당 코어를 쓰는 다른 프로세스가 없다고 가정)

 

만약 두 컨테이너 중 한 컨테이너에 cpu-share 512 값을 설정한다면, 다른 1024의 컨테이너에 비해 CPU 시간이 절반이 사용된다. 그러나 이것이 CPU의 절반만 사용한다는 의미는 아님. 다른 컨테이너가 유휴 상태인 경우, 512로 설정한 컨테이너도 CPU의 100%를 쓸 수 있다. 또한 이러한 가중치 제한은 필요할 때만 수행된다. 사전에 CPU를 제한하는게 아니고, 많은 프로세스가 동시에 CPU를 많이 사용하는 경우 같이 이런 경우에 수행된다. 아래는 여러가지 예시이다.

 

* 같은 코어를 쓰는 두 컨테이너에 둘 다 1024 값 사용

docker run -d --cpuset-cpus=3 --cpu-shares 1024 alicek106/stress stress --cpu 1

docker run -d --cpuset-cpus=3 --cpu-shares 1024 alicek106/stress stress --cpu 1

둘 다 비슷하게 50%를 사용한다.

 

* 같은 코어를 쓰는 두 컨테이너에 하나는 1024, 하나는 512 값 사용

docker run -d --cpuset-cpus=3 --cpu-shares 1024 alicek106/stress stress --cpu 1
docker run -d --cpuset-cpus=3 --cpu-shares 512 alicek106/stress stress --cpu 1

한쪽은 70%, 다른 한쪽은 30%정도 사용한다.

 

* 같은 코어를 쓰는 두 컨테이너에 하나는 1024, 하나는 256 값 사용

docker run -d --cpuset-cpus=3 --cpu-shares 1024 alicek106/stress stress --cpu 1
docker run -d --cpuset-cpus=3 --cpu-shares 256 alicek106/stress stress --cpu 1

한쪽은 80%, 다른 한쪽은 20%정도 사용한다.

 

* 한 컨테이너만 512값으로 줄 때

docker run -d --cpuset-cpus=3 --cpu-shares 512 alicek106/stress stress --cpu 1

해당 코어를 쓰는 컨테이너가 하나뿐이므로 100% 모두 사용한다.

 

 

 

* 디스크 I/O 제한

도커 컨테이너는 block I/O 관련하여 옵션을 지정하지 않으면 무제한으로 I/O를 사용한다. 이러한 무분별한 I/O 사용을 막기 위해 여러가지 옵션이 있다. - --device-write-bps, --device-read-bps, --device-write-iops, --device-read-iops 등이 있다. 단 direct I/O만 제한하며, bufered I/O는 제한하지 않는다.

 

또한 이렇게 값을 설정한다 하더라도, 실제 물리 디스크의 성능에 따라 달라질 수 있다. 따라서 제한 없는 상태에서의 디스크 성능을 미리 알고 있어야 하며, 그 상태를 기준으로 제한을 건 후 값이 어떻게 나오는지 테스트 및 검증을 수행해야 한다. 디스크의 block I/O 성능은 초당 얼마 사이즈를 I/O 할 수 있느냐를 알 수 있으면 된다.

 

여기서는 4가지 옵션 중 --device-write-bps 옵션을 사용하여 초당 쓰기속도를 설정해본다. dd 명령을 사용하여 10메가 파일을 생성할 때 걸리는 시간을 측정한다. 

 

 

* 초당 1MB로 속도 제한

docker run -it --device-write-bps /dev/sda:1mb ubuntu:14.04 bash

실제로 약 10초가 걸렸다.

 

초당 10MB로 속도 제한

docker run -it --device-write-bps /dev/sda:10mb ubuntu:14.04 bash 

실제로 약 0.9초가 걸렸다.

 

 

* 운영중에 자원 변경하기

컨테이너를 내렸다 올리지 않고, 값을 업데이트하여 사용량 제한 값을 변경할 수 있다. 다음 예시를 참고할 것.

 

docker update --cpu-shares 512 컨테이너명

docker update --cpus=0.2 컨테이너명

docker container run 명령 개요

docker container run 구문은 도커에서 가장 중요하고 기능이 많아서, 따로 한 페이지로 떼어 두었다. 사실상 가장 핵심적인 내용이다. 이 run 명령어는 pull, create, start 3가지를 모두 수행한다. 그래서 이미지가 없이 run을 수행하면 이미지를 받고, 컨테이너를 생성하고, 시작도 수행한다. 시작하면서 명시한 명령도 수행한다. 

docker container(안써도됨) run [옵션] 도커이미지명[:태그] [명령어]

이 명령의 옵션은 크게 4가지로 나뉜다. 그 설정은 다음과 같다.

 

  • 컨테이너 설정 옵션

  • 네트워크 설정 옵션

  • 환경변수 설정 옵션

  • 자원제한 설정 옵션

또한 도커 서버가 아닌 도커 서버-클라이언트로 구성된 경우(즉, 서버는 따로 있고 내가 명령어 치는 곳이 클라이언트인 경, 클라이언트에서 docker run 을 수행하면, 도커 서버의 docker daemon에 의해 실행되며, 결과를 클라이언트가 받아서 볼 수 있다.

 

컨테이너 설정 옵션

attach, -a

표준입력, 표준출력, 표준오류출력에 어태치한다.

--cidfile

컨테이너 아이디를 파일로 출력한다.

--detach, -d

컨테이너를 생성하고 백그라운드에서 작동시킨다. 보통 많이 쓴다. 

--interactive, -i

컨테이너의 표준 입력을 연다.

--tty, -t

단말기 디바이스를 사용한다.

--name

컨테이너의 이름을 만든다. 안쓰면, 랜덤으로 생성된다.

--user, -u

사용자명을 지정

--restart=[옵션]

명령의 실행 결과에 따라 재시작을 하는 옵션

no : 재시작하지 않음

on-failure : 종료 스테이터스가 0이 아닐 때 재시작한다.

on-failure:횟수n : 종료 스테이터스가 0이 아닐 때 n번 재시작한다.

always : 항상 재시작한다. (exit로 나가도, 재시작한다)

unless-stopped : 최근 컨테이너가 정지 상태가 아니라면 항상 재시작한다.

--rm

명령 실행 완료 후에 컨테이너를 자동 삭제 (--restart 옵션과 함께 사용할 수 없음)

 

# 참고 : 컨테이너의 "종료"
대부분의 컨테이너는 명령어에 /bin/bash를 넣으면, 해당 컨테이너에 진입할 수 있다. 컨테이너 진입한 후, exit를 입력하면 쉘에서 나가는게 아니고, 컨테이너를 "종료" 한다. 컨테이너에 인수를 넣으면, 그 인수를 "가진" 컨테이너로써 생성된다. 원래 컨테이너는 인수 없이 컨테이너에 구축된 내용 그대로 사용한다. 하지만 필요한 경우와 여러가지 사용을 위해 인수(명령어)를 추가하여 컨테이너를 실행한다. 컨테이너를 종료하지 않으려면, ctrl+p+q를 입력하여 빠져나올 수 있다.

 

docker container run centos

아무 일도 없이 컨테이너가 종료된다. 받은 인수가 없어 할 일이 없기 때문이다.

 

docker container run -itd centos

컨테이너가 백그라운드로 작동하고, 표준 입력 터미널이 열린 상태가 된다. 컨테이너가 계속 유지된다.

 

docker container run centos /bin/cal

아래와 같이 결과가 나오며 컨테이너가 종료된다. 받은 인수의 결과를 리턴하고 더이상 할 일이 없기 때문이다.

 

docker container run -itd centos /bin/cal

아무 결과도 없이 컨테이너가 종료된다. 백그라운드에서 /bin/cal을 실행하고 종료되었기 때문이다.

 

docker container run -it centos /bin/ping localhost

컨테이너로 진입 후 계속 핑을 날리게 된다. 끝없이.

ctrl+p+q로 빠져 나가면 이것은 백그라운드로 계속 ping을 날리는 중이다. 컨테이너는 살아있다.

만약 ctrl+p+q로 빠져 나가지 않고 exit로 나갔다면, 해당 명령이 끝나므로 컨테이너는 종료된다. 

 

 

네트워크 설정 옵션

포트 번호같은 네트워크 설정을 통해 외부에서 컨테이너의 프로세스에 액세스하도록 설정할 수 있고, 컨테이너끼리 통신도 할 수 있는 여러가지 네트워크 통신을 설정할 수 있다.

--add-host=[호스트명:ip주소]

컨테이너의 /etc/hosts 에 호스트명과 ip 정의

--dns=[ip주소]

컨테이너용 dns서버의 ip주소 지정

--expose

지정된 범위의 포트 번호를 할당
호스트OS까지만 해당 컨테이너의 포트를 노출한다. (외부에는 노출하지 않음)
expose한 포트는 호스트에서만 연결이 된다.
호스트에서만 쓸 목적으로 사용하는것이며 서비스로는 사용하지 않는다.

--mac-address=[맥주소]

컨테이너의 맥주소 지정

--net=[bridge | none | container:<이름 | id> | host | NETWORK]

컨테이너의 네트워크를 지정. 도커에서는 기본적으로 호스트os와 브릿지 연결을 하며, --net옵션을 통해 네트워크 설정 가능

- bridge : 브리지연결 사용 (default)

- none : 네트워크 연결하지 않음

- container:[이름 | id] 다른 컨테이너의 네트워크를 사용 (해당 컨테이너의 네트워크 기법을 그대로 사용하겠다는 의미.)

- host : 컨테이너가 호스트 os의 네트워크를 사용

- NETWORK : 사용자 정의 네트워크를 사용

--hostname, -h

컨테이너 자신의 호스트명을 지정

--publish, -p [호스트의포트번호]:[컨테이너포트번호]

호스트포트와 컨테이너포트를 매핑. 명시한 포트에 대한 액세스를 허가하기 위해 사용된다.
(-p 80:80 이라고 하면, 80번 포트에 대한 액세스를 허가)
호스트os를 넘어 외부에 포트를 오픈한다.

둘이 되도록이면 같은 포트는 피해야 한다. -p 1521:1523 이런식으로.
-p  80 이렇게 하나만 있으면 컨테이너의 포트이다. 호스트포트는 알아서 지정하라는뜻.

docker ps에서 의미 예시
 -p 8080:80 옵션으로 하면 다음과 같이 docker ps에서 확인할 수 있다.
0.0.0.0:8080->80/tcp
 : 컨테이너 80/tcp가 호스트 0.0.0.0:8080에 연결된 것. 
즉, 외부에서 도커 호스트의 8080 포트로 요청이 들어오면, 컨테이너의 80번 포트로 해당 요청을 forword.
--publish-all, -P 호스트의 임의의 포트를 컨테이너에 할당. 호스트os를 넘어 외부에 포트를 오픈한다.
알아서 32767번부터 포트를 임의로 넣어준다. 아무옵션없이 대문자P 로 옵션을 넣기만 하면 됨

 

예시 : 포트 설정

docker container run -d -p 8080:80 nginx

- nginx라는 이미지를 바탕으로 컨테이너를 생성하고, 백그라운드에서 실행한다. 그리고 호스트 포트 8080과 컨테이너포트 80을 매핑한다. (0.0.0.0:8080->80/tcp)

- 즉 이 명령을 실행하여 컨테이너를 만든 후, 브라우저 등에서 호스트의 8080에 엑세스하면 컨테이너에서 작동하고 있는 nginx(80번포트) 서비스에 엑세스할수 있다.

 

예시 : dns 서버 설정

docker container run -d --dns 192.168.1.1 nginx

 

예시 : 맥주소 설정

docker container run -d --mac-address="92:d0:c6:0a:29:33" centos

- docker container inspect --format="{{ .Config.MacAddress }} 로 실제 설정값 확인 가능

 

 

예시 : /etc/hosts 호스트명과 ip주소 정의하기

docker container run -it --add-host test.com:192.168.1.1 centos

 

예시 : 컨테이너 호스트명 설정

docker container run -it --hostname www.test.com

- 이 값은 자신의 /etc/hosts에도 자동으로 적용된다.

 

 

예시 : 도커 네트워크 명시하여 연결

docker container run -itd --name=webap --net=web-network nginx

- nginx 이미지이고 이름이 webap인 컨테이너를 web-network라는 도커 네트워크에 연결한다.

 

 

 

서버 환경설정 옵션

해당 컨테이너 OS의 환경설정을 컨테이너 생성시 사전에 설정할 수 있다. DB나 특정 어플리케이션들은 환경변수가 필수이며, 해당 환경변수 관련해서는 Docker hub의 description을 참고해야 한다.

--env=[환경변수], -e

환경변수 설정. -e -e -e 이런식으로 여러개 나열해서 명시할 수있음.

--env-file=[파일명]

환경변수를 파일로부터 설정(양이 많을 때)

--read-only=[true | false]

컨테이너의 파일 시스템을 읽기 전용으로 만듬

--workdir=[경로], -w

컨테이너의 작업 디렉토리 (시작디렉토리) 지정. 디렉토리는 자동으로 만들어진다.

--user=[사용자명], -u

사용자명 또는 UID를 지정한다.

 

예시 : 환경변수 설정

docker container run -it -e foo=bar centos /bin/bash

컨테이너 들어간 후, set 명령을 치면 설정된 환경변수들을 확인할 수 있다.

 

예시 : 환경변수 파일로 여러개 일괄 등록하기

# cat env_list
hoge=fuga
foo=bar

docker container run -it --env-file=/test/env_list centos /bin/bash

 

예시 : 컨테이너의 작업 디렉토리를 지정하기

docker container run -it -w=/tensorflow centos /bin/bash

 

 

 

자원설정 옵션

다음 포스팅에서 상세하게 확인할 수 있다.

2020/05/15 - [Docker Basic] - [Docker Basic] 15. Docker Container 자원 제한

 

[Docker Basic] 15. Docker Container 자원 제한

도커 컨테이너에서는 여러가지 자원을 할당할 수 있는데, 할당하지 않으면 해당 컨테이너는 마음대로 호스트의 자원을 사용한다. (CPU, MEMORY, swap 등) 따라서 어플리케이션에 따라 사용하는 자원�

watch-n-learn.tistory.com

 

 

 

# 추가 개념 이해 : 도커 컨테이너가 꺼질 때도 있고 안꺼질 때도 있는데..??

도커 컨테이너를 단순히 나갈려고 했는데 컨테이너가 내려가는 경우가 많다. Production 장비에서는 매우 위험한 일이므로 docker run의 개념과 컨테이너 내부에서 exit 명령과 ctrl+p+q 를 정확히 이해해야 한다. 테스트에는 "운영체제" 컨테이너와 "프로그램" 컨테이너 2가지에 따라 실행 및 종료 옵션을 여러가지 변경하여 테스트를 해 보았다. 그냥 아무생각 없이 도커에서 아래 내용을 쭉 쳐보면서 설명을 읽어보면 컨테이너가 어떻게 작동하는지 어느정도 감을 잡을 수 있을 것이다.

 

* 운영체제 컨테이너

 

docker run ubuntu 

프롬프트 떨어짐. 컨테이너 종료됨
docker ps -a로 확인하면, COMMAND 부분이 "/bin/bash"라고 되어있음.
포그라운드에서 bash 쉘을 실행한 후, 추가로 할 것이 없으므로 종료된다.

 

docker run ubuntu /bin/bash 

 

프롬프트 떨어짐.  컨테이너 종료됨. 위와 동일하다.
docker ps -a로 확인하면, COMMAND 부분이 "/bin/bash"라고 되어있음.

포그라운드에서 bash 쉘을 실행한 후, 추가로 할 것이 없으므로 종료된다.


docker run -it ubuntu

컨테이너 프롬프트로 들어가짐.  exit 로 나가면 컨테이너 종료됨. ctrl+p+q 로 나가면 계속 살아있음
docker attach 컨테이너명 으로 들어간 후 exit 쳐서 나가면, 컨테이너는 종료된다.

attach는 단순히 컨테이너에 붙은 것이고, 현재 컨테이너상태에 대하여 exit를 했기 때문.

컨테이너의 표준 입력 옵션이 들어있으므로 컨테이너에 진입된 상태가 유지된다.


docker run -it ubuntu /bin/bash
컨테이너 프롬프트로 들어가짐.  exit 로 나가면 컨테이너 종료됨.  ctrl+p+q 로 나가면 계속 살아있음. 위와 동일하다.
docker attach 컨테이너명 으로 들어간 후 exit 쳐서 나가면, 컨테이너는 종료된다.

attach는 단순히 컨테이너에 붙은거고, 현재 컨테이너상태에 대하여 exit를 했기 때문.

컨테이너의 표준 입력 옵션이 들어있으므로 컨테이너에 진입된 상태가 유지된다.

 

docker run -d ubuntu
프롬프트 떨어짐. 종료됨
docker ps -a로 확인하면, COMMAND 부분이 "/bin/bash"라고 되어있음.

백그라운드에서 bash 쉘을 실행한 후, 추가로 할 것이 없으므로 종료된다.


docker run -d ubuntu /bin/bash
프롬프트 떨어짐. 종료됨
docker ps -a로 확인하면, COMMAND 부분이 "/bin/bash"라고 되어있음.
백그라운드에서 bash 쉘을 실행한 후, 추가로 할 것이 없으므로 종료된다


docker run -it -d ubuntu
프롬프트 떨어짐. 종료되지 않음. 백그라운드에서 bash 쉘이 실행되고 표준 입력이 실행된 상태이기 때문.
docker ps -a로 확인하면, COMMAND 부분이 "/bin/bash"라고 되어있음.
docker exec -it ubuntu /bin/bash로 들어간 후 exit 쳐서 나가면, 컨테이너는 종료되지 않는다. exec로 실행한 /bin/bash가 종료되었을뿐.
docker attach 컨테이너명 으로 들어간 후 exit 쳐서 나가면, 컨테이너는 종료된다. attach는 단순히 컨테이너에 붙은거고, 현재 컨테이너상태에 대하여 exit를 했기 때문.

 

docker run -it -d ubuntu /bin/bash
프롬프트 떨어짐. 종료되지 않음. 백그라운드에서 bash 쉘이 실행되고 표준 입력이 실행된 상태이기 때문.
docker ps -a로 확인하면, COMMAND 부분이 "/bin/bash"라고 되어있음.
docker exec -it ubuntu /bin/bash로 들어간 후 exit 쳐서 나가면, 컨테이너는 종료되지 않는다. exec로 실행한 /bin/bash가 종료되었을뿐.
docker attach 컨테이너명 으로 들어간 후 exit 쳐서 나가면, 컨테이너는 종료된다. attach는 단순히 컨테이너에 붙은거고, 현재 컨테이너상태에 대하여 exit를 했기 때문.

 


* 소프트웨어 컨테이너


docker run httpd:latest
이렇게 하면, 로그만 뜨고 대기함. 프롬프트도 안뜸. ctrl+c 로 나가면, 컨테이너 종료됨, docker ps -a로 확인하면, COMMAND 부분이 "http-foreground"라고 되어있음.
crtl+c 나가기 전에, 창을 하나 더 띄워서 docker ps 로 보면 살아있다.

즉 OS에 httpd를 실행시켜놓은 상태로 run 되었고, 계속 실행중이므로 프롬프트 없이 대기하는 것임.


docker run httpd:latest /bin/bash
이렇게 하면, 그냥 끝남. 프롬프트 떨어짐. /bin/bash라는 배쉬 쉘을 실행한 후, 할게 없으니 종료된다. docker ps -a로 확인하면, COMMAND 부분이 "/bin/bash"라고 되어있음.

실제로 이것은 httpd가 실행되어있는 컨테이너의 OS에서 /bin/bash를 실행한 것이다. /bin/bash를 실행하고 할 게 없으므로 종료되는 것.

 

docker run -it httpd:latest
이렇게 하면, 로그 뜨고 컨테이너 프롬프트가 뜬다. httpd가 올라가 있는 컨테이너 OS를 포그라운드로 실행했으므로.

ctrl+c로 나가면 컨테이너는 종료된다. 즉, httpd가 종료되며 컨테이너도 종료되는 것.
docker ps -a로 확인하면, COMMAND 부분이 "http-foreground"라고 되어있음.

docker run -it httpd:latest /bin/bash
이렇게 하면, 컨테이너 프롬프트가 뜬다. httpd가 올라가 있는 컨테이너 OS에 /bin/bash를 포그라운드로 실행했으므로.
exit로 나가면 컨테이너가 종료된다. 즉 컨테이너 실행시 명시한 /bin/bash가 끝났기 때문에 컨테이너 자체가 종료되는 것.
crtl+c 나가기 전에, 창을 하나 더 띄워서 docker ps 로 보면 살아있다.
이상태에서, docker exec -it 이름 /bin/bash 라고 하면 들어가짐. 들어간 후 exit 하면? 컨테이너는 살아있다. 이유는? exec로 실행한 /bin/bash 만 종료했기 때문. 최초 실행한 /bin/bash는 실행된 상태에서 아직 유지중.
docker attach 컨테이너명 으로 들어간 후 exit 쳐서 나가면, 컨테이너는 종료된다. attach는 단순히 컨테이너에 붙은거고, 현재 컨테이너상태에 대하여 exit를 했기 때문.

docker run -d httpd:latest
이렇게 하면, 그냥 프롬프트 떨어지고, 컨테이너는 계속 살아있다.
이상태에서, docker exec -it 이름 /bin/bash 라고 하면 들어가짐. 들어간 후 exit 하면? 컨테이너는 살아있다. 이유는? exec로 실행한 /bin/bash 만 종료했기 때문.
docker attach 컨테이너명 으로 들어간 후 exit 쳐서 나가면, 컨테이너는 종료된다. attach는 단순히 컨테이너에 붙은거고, 현재 컨테이너상태, 즉 httpd을 실행한 것에 대하여 exit를 했기 때문.

docker run -d httpd:latest /bin/bash
이렇게 하면 그냥 프롬프트 떨어지고, 컨테이너는 종료된다.
/bin/bash를 백그라운드로 실행시키고 할 게 없으니 종료된 것임.

docker run -it -d httpd:latest
이렇게 하면, 프롬프트 떨어지고 컨테이너는 계속 살아있다.
이상태에서, docker exec -it 이름 /bin/bash 라고 하면 들어가짐.

들어간 후 exit 하면? 컨테이너는 살아있다. 이유는? exec로 실행한 /bin/bash 만 종료했기 때문. 그냥 -d로 한 것과 동일하다.
docker attach 컨테이너명 으로 들어가면 바로 종료된다. -it 옵션이 인터렉티브 모드이고, 인터렉티브 상태를 attach 했는데 그 후 할 게 없으므로 종료됨. (조심해야 할 듯)

attach는 단순히 컨테이너에 붙은거고, 현재 컨테이너상태에 대하여 exit를 했기 때문.

docker run -it -d httpd:latest /bin/bash
이렇게 하면, 프롬프트 떨어지고 컨테이너는 계속 살아있다. 
이상태에서, docker exec -it 이름 /bin/bash 라고 하면 들어가짐. 위와 다른점은

들어간 후 exit 하면? 컨테이너는 살아있다. 이유는? exec로 실행한 /bin/bash 만 종료했기 때문.  그냥 -d로 한 것과 동일하다.
docker attach 컨테이너명 으로 들어간 후 exit 쳐서 나가면, 컨테이너는 종료된다. attach는 단순히 컨테이너에 붙은거고, 현재 컨테이너상태에 대하여 exit를 했기 때문.

 


결론
- 도커 컨테이너 실행시 어떤 명령어로, 어떤 옵션으로  실행시켰느냐에 따라 도커 생성의 정체성이 달라지므로, 그 정체성에 따라 exit를 쳤을때 컨테이너가 종료될수도, 종료되지 않을수도 있음.
- 도커 컨테이너가 종료되지 않을려면, 도커 컨테이너 실행시 시작한 프로그램을 종료하지 않도록 해야 한다.
- attach 명령어는 stdout, stderr을 가져오는 것이다. ssh처럼 연결되는 것이 아니다. 
-  모르면 exit로 나가지 말고, ctrl+p+q 하는게 가장 좋다.

< 도커 기본 명령어 차례 >

● 시스템 명령

docker version

docker system info

docker system df

docker system prune

docker login

locker logout

 

● 이미지 관리 명령

docker pull

docker image ls

docker images

docker image inspect

docker image tag

docker push

docker search

docker image rm

docker image prune

docker container commit

 

● 컨테이너 실행 및 조작 관련 명령

docker container create

docker container run

docker container attach

docker container exec

 

● 컨테이너 정보 확인 명령

docker container ps

docker container stats

docker container inspect

docker container top

docker container port

docker container diff

docker container logs

docker container ls

 

● 컨테이너 관리 명령

docker container stop

docker container start

docker container kill

docker container restart

docker container prune

docker container pause

docker container unpause

docker container rename

docker container cp

 

● 컨테이너/이미지 백업 명령

docker container export

docker container import

docker image save

docker image load

 

● 네트워크 명령

docker network ls

docker network create

docker network connect

docker network disconnect

docker networkinspect

docker network rm

brctl show

 

● 볼륨 명령
docker volume create
docker volume ls
docker volume rm
docker volume prune
docker volume inspect


docker network ls

설명
- 도커의 네트워크 목록 표시

옵션
-f, --filter=[] : 출력을 필터링한다.
        driver : 드라이버 지정
        id : 네트워크 아이디
        label : 네트워크에 설정된 라벨 (label= 또는 label==로 지정한다
        name : 네트워크명
        scope : 네트워크의 스코프 (swarm, global, local)
        type : 네트워크의 타입 (사용자 정의 네트워크 custom/정의완료 네트워크 builtin)
--no-trunc : 상세 정보를 출력한다
--quiet, -q : 네트워크 id만 표시

예시

- 아래 내용은 다른 아무 네트워크도 생성하지 않은 기본 도커 설치 후의 내용이다.

9e2939a3e54e : bridge : docker0 가상 브릿지에 연결할 때 사용한다. (가상 브릿지를 추가하면 이러한 bridge Driver로 생성된다.

cb84e2a80ad9 : host - 호스트OS의 IP 대역과 동일하게 할 때 사용한다.

ebc9705adcd6 : none - 사용하지 않음.

 

 


docker network create

설명
- docker netework create 옵션 이름
- 사용자 정의의 도커 네트워크를 생성한다.
- 네트워크 생성 시 도커 네트워크 driver 또는 외부 네트워크 driver 플러그인을 사용해야 한다.
- 이러한 생성된 네트워크에 여러 도커 컨테이너가 연결되어 통신을 하게 된다.
- 이러한 네트워크에 컨테이너가 연결되면, 컨테이너는 ip뿐만 아니라 컨테이너명 또는 컨테이너ID로도 통신(ping 등)이 가능하다.
- 생성된 네트워크는 컨테이너 생성시 --net 옵션으로 해당 네트워크를 컨테이너에 추가할 수 있다. (docker container run 부분에서 상세히 설명)
- driver 중에서, 오버레이 네트워크나 커스텀 플러그인을 사용하면, 멀티호스트에 대한 연결을 할 수 있다. (즉, 도커 서버가 여러대라는 의미)

- 컨테이너가 동일한 멀티호스트 네트워크에 연결되어 있으면 이 네트워크를 통해서 서로 통신이 가능하다.

옵션
--driver, -d : 네트워크 브리지 또는 오버레이(기본값은 bridge)     - overlay, bridge 두가지임. 또한 커스텀 네트워크 드라이버도 사용 가능.
--ip-range : 컨테이너에 할당하는 ip 주소의 범위를 지정
--subnet : 서브넷을 CIDR형식으로 지정
--ipv6 ipv6 : 네트워크를 유효화할지 말지(true/false)
-label : 네트워크에 설정하는 라벨.

예시

- 브릿지 네트워크를 새로 생성하기

 

docker network connect

설명
- 도커 컨테이너를 도커 네트워크에 연결할 때 사용
- docker network connect 옵션 네트워크 컨테이너
- 생성할 때가 아닌, 이미 생성된 컨테이너에 특정 네트워크를 추가하는 것
- 연결 후에는 해당 컨테이너 내부에 새로운 네트워크 장치가 생기며, 동일한 네트워크상에 있는 다른 컨테이너와 통신이 가능해진다.
- docker container inspect 컨테이너명   에서 web-network 항목에서 정보 확인 가능.

옵션
--ip : ipv4주소
--ip6 : ipv6주소
--alias : 알리아스명
--link : 다른 컨테이너에 대한 링크

예시

docker network connect web-network webfront (webfront라는 컨테이너를 web-network라는 네트워크에 연결)

 

docker network disconnect

설명
- 도커 컨테이너안에 연결된 특정 네트워크를 해제한다.
- docker network disconnect 옵션 네트워크 컨테이너

옵션
- 미확인


예시
- 없음

docker network inspect

설명
- 네트워크의 상세정보 보기
- docker network inspect 옵션 네트워크명
- 네트워크 드라이버, 서브넷, 게이트웨이 등등의 정보가 나오며, Container 부분 아래에는 해당 네트워크에 연결된 컨테이너들의 정보가 나온다.

옵션
- 미확인


예시


docker network rm

설명
- 생성한 도커 네트워크를 삭제한다.
- docker network rm 옵션 네트워크명
- 네트워크를 삭제하려면, 이 네트워크에 연결된 모든 컨테이너와의 연결을 해제해야 한다. (disconnect 명령 사용)

옵션
- 미확인


예시
- 없음

 

brctl show

설명
- 브릿지 확인하는 명령어. 따로 설치해야 한다.
- 설치 : 우분투 apt-get -y install bridge-utils
         레드햇 yum install bridge-utils

 

옵션
- 미확인


예시

 


도커 네트워크 연결 예제

 

* 새로운 브릿지 네트워크를 생성하고 생성한 네트워크를 사용하여 컨테이너 생성

1. 브릿지 네트워크 새로 생성

# 설정없이 기본값으로 만들기
docker network create -d bridge noset-network

# 상세 설정하여 만들기
docker network create --driver bridge --subnet 172.100.1.0/24 --ip-range 172.100.1.0/24 --gateway=172.100.1.1 set-network

 

2. 생성 후 NETWORK ID를 잘 확인하자.

3. ip a 명령으로 생성된 정보를 확인할 때 위의 NETWORK ID로 해당 도커 네트워크 장치를 확인할 수 있다.

4. 설정된 상세 정보를 확인한다.

5. 컨테이너 생성

docker container run --net=set-network -it centos

생성된 컨테이너에서 네트워크 정보를 확인해보면, 해당 네트워크 대역으로 생성된 것을 확인할 수 있다.

 

* 컨테이너에 네트워크 장치 추가하기

컨테이너를 생성하면 네트워크가 eth0 하나가 추가되는데, 거기에 더 추가할 수 있다. connect 명령을 사용한다. 

 

1. 컨테이너 생성 및 네트워크 확인

docker run -it --name=add_network centos bash

- eth0/if148 한개가 있는 것을 확인할 수 있다.

 

2. 새로운 도커 브릿지 네트워크 생성

docker network create --driver=bridge second-network

호스트에서 docker network ls 명령으로 NETWORK ID를 확인하고, ip a 명령으로 해당 네트워크의 ip 대역대를 확인할 수 있다.

3. connect 명령으로 새로 만든 브릿지 네트워크를 추가한다.

docker network connect second-network add_network

4. attach로 들어간 후 확인해본다.

docker attach add_network

- 새로운 네트워크인 eth1@if153이 추가된 것을 확인할 수 있다.

 

5. 추가했으면 삭제도 해 보자.

- 컨테이너에 특정 네트워크가 추가된 상태에서, 해당 네트워크를 삭제하려고 하면 에러가 발생한다.

- 따라서 컨테이너를 중지하던지 연결을 해제해 주어야 한다.

- 이 작업은 컨테이너 온라인 중 가능하다. 명령을 치면 바로 작동된다.

docker network disconnect second-network add_network

- 작업 후 컨테이너로 들어가서 다시 확인해보면 해당 네트워크 장치가 사라진 것을 확인할 수 있다.

 

 

 

 

 

도커 네트워크 개요

도커는 호스트 서버의 물리적 네트워크와 도커의 가상 네트워크가 혼합되어 네트워크가 이루어진다. 아래 그림의 예시와 같은 네트워크가 구성된다.

이 그림에서 eth-0 은 eth0 이며, vth-0은 vethXXXXXXX 이다.

그림 맨 아래의 eth0 은 서버의 물리적 인터넷 포트이며, 이 포트가 인터넷에 연결되어 있다고 가정한다. 이 인터넷 포트가 iptables를 통해 docker0 또는 다른 사용자가 직접 만든 가상 브릿지(위 그림에서 Mybridge)와 통신이 된다.  또한 이 가상 브릿지는 각각의 컨테이너와 통신하게 된다. 가상브릿지와 컨테이너간 통신은 맥주소로 통신한다.

 

컨테이너를 생성하면, 자동으로 컨테이너에 eth0 interface가 생기며 (물론 옵션을 통해 더 추가할 수도 있다) 그 컨테이너의 eth0 과 1:1 매치(pair)되는 vethXXXXXX interface가 생성된다. 이 가상 interface OSI 참조 모델의 레이어2 가상 네트워크 인터페이스로, pair eth0(컨테이너)과 터널링 통신을 한다

 

이렇게 pair로 구성된 두개의 interface를 통해 격리된 네트워크 환경(namespace) 제공되며, MAC주소와 Private ip 부여된다마치 직접 다이렉트로 케이블을 연결한 두 대의 PC가 서로 패킷을 주고받는 형태와 같다.

 

또한 따로 옵션을 주지 않았다면 컨테이너의 eth0와 한 쌍인 vethXXXXXX은 docker0 가상 브릿지 네트워크에 연결된다. 이 가상 네트워크는 컨테이너끼리 통신할 수 있도록 한다. 도커는 1서비스당 1컨테이너를 권고하므로, 한 컨테이너에 여러 서비스를 포함하지 않고 각각 컨테이너로 하나씩 만들고 이 가상 브리지 네트워크로 연결되는 것이 권고된다.

 

예를들어 데이터베이스 컨테이너와 웹서버 컨테이너는 각각 다른 컨테이너야 한다는 것. 결론적으로  컨테이너(eth0) <-> vethXXXXXX(호스트) <-> docker0(호스트) <-> 외부네트워크 이런식으로 네트워크가 연결된다.

 

 

 

도커의 기본 가상 브리지 네트워크 : docker0

도커 설치 시, default 로 docker0라는 가상 브리지 네트워크가 생성된다. docker0 가상 브리지 네트워크는 는 소프트웨어적인 스위치방식으로, 일반적인 스위치 개념과는 다르게 DHCP로 연결된 container에게 사전에 정의된 IP pool을 할당한다. docker0 는 default로 172.17.0.0/16 서브넷을 가진 IP 대역을 가진다.

 

컨테이너가 특별한 옵션 없이 생성된다면 이 docker0 에 기본적으로 연결된다. docker0 는 default 브리지 네트워크일 뿐이고, 사용자가 얼마든지 추가로 가상 브리지 네트워크를 생성할 수 있다.

 

 

docker0와 사용자 정의 네트워크

docker0는 도커 설치시 자동으로 만들어지며, 컨테이너 생성시 명시적으로 지정하지 않고 run 하는 경우 이 네트워크로 컨테이너가 시작된다. docker0 의 문제점은, DNS를 사용해 자동으로 서비스를 찾아주는 기능이 비활성화 되어있다. 즉, 이름식별이 안되므로 통신이 되지 않는다. IP로만 통신이 가능한 상태이다. 컨테이너는 일종의 프로세스이므로, 내렸다 올리면 아이피가 바뀌는 등의 유동성이 있다. 따라서 문제가 된다.

 

컨테이너끼리 연동을 하려면 IP 기반의 설정은 좋지 않으며, link 옵션을 사용하여 연동해야 한다. 즉 docker container run 할 때 --link 옵션을 지정하야 한다. --link 옵션을 지정한다는 것은 컨테이너 안에 있는 /etc/hosts 파일에 컨테이너명과 컨테이너에 할당된 IP가 등록되고 이를 통해 통신이 가능하게 된다. link를 하지 않으면 당연히 그 정보는 없다.  link가 걸린상태에서 컨테이너가 재기동하면 IP가 바뀔 수 있는데, 바뀐 IP값이 자동으로 /etc/hosts에 등록되어서 container의 ip정보가 바뀌어도 서로간 문제없이 통신이 가능하다. 

 

사용자 정의 네트워크는  도커 데몬에 내장된 내부 DNS 서버에 의해 이름 해결이 이루어진다. 내부 DNS 서버를 사용하면, link 기능과 같이 /etc/hosts 파일에 의존하지 않고 이름 해결을 할 수 있다. 따라서 docker0 에 연결된 컨테이너들은 ping을 할 때 IP만 가능하지만 사용자 정의 네트워크에 연결된 컨테이너들은 컨테이너명(docker container run --name 으로 생성되는 컨테이너명)뿐만 아니라 컨테이너 시작 시 지정한 --net-alias 옵션을 사용한 알리아스명으로도 통신이 가능하다. 사용자 정의 네트워크를 쓰는 편이 보다 유연하고 쉽게 네트워크 구성관리를 할 수 있어서 이쪽이 권고된다. 

 

# 참고 : link 명령

위에서 설명한 link 명령은 이해를 위해 설명한 것이며, 실제로는 아래와 같이 legacy 기능으로써 조만간 제거될 있다.

 

각 네트워크 상태 예시 

도커를 설치한 직후 네트워크 상태

lo : 루프백 네트워크. 서버 자기 자신을 의미하며 물리적인 네트워크가 아닌 가상으로 생성된 네트워크이다. 신경쓰지 않아도 됨.

enp0s3 : 해당 서버의 물리적인 네트워크 포트. CentOS7 이전에는 eth0 이었으나, CentOS7 부터는 이러한 형식을 가진다.

docker0 : 도커를 설치하고 생성된 가상 브릿지. 도커에서 생성된 컨테이너와 통신하며, 이 서버의 물리적 네트워크와 브릿지 되어 있다.

 

아래처럼 2개의 컨테이너를 띄운 후 네트워크 상태

2개의 가상 네트워크 장치가 생긴것을 볼 수 있다. 또한, 각 컨테이너는 eth0 을 가지고 있다. 바로 아래 "#참고" 에서 각각 컨테이너의 ip a 명령 결과를 확인할 수 있다. 

 

가상 브릿지 docker0 와 컨테이너의 연결상태

그렇다면 각 컨테이너의 eth와 매핑되는 vethXXXXX은 어디에 연결되어 있을까? 호스트 서버에서 brctl show 명령을 사용하면, 가상 네트워크 interface가 어디에 연결되어 있는지 확인할 수 있다.

호스트 서버에서 ip a를 쳤을 때 나온 vethXXXXX 2개가 docker0 와 연결되어 있는 것을 확인할 수 있다. docker0 외에 다른 가상 네트워크를 만들 수 있고, 다른 가상 네트워크에 다른 컨테이너들을 또한 연결할 수 있다. 물론 ip도 각 가상 네트워크마다 다른 대역으로 생성된다.

 

# 참고 : 각 컨테이너와 가상 네트워크 장치 파악

어떤 가상 네트워크가 어떤 컨테이너의 네트워크인지 확인하려면 다음과 같이 확인할 수 있다.  아래 그림에서 새로 생긴 2개의 가상 네트워크 장치는 13번 항목의 if12로 끝나는 장치, 15번 항목의 if14로 끝나는 장치가 생성되어 있다. 

또한 각 컨테이너의 네트워크 정보도 다음과 같다.

8835bd89la60 : nginx 컨테이너, 12번 항목의 eth0@if13 을 가진다.

28485a9293db : CentOS 컨테이너, 14번 항목의 eth0@if15 를 가진다.

즉, 컨테이너와 호스트 사이 서로 매핑되는 장치는 항목과 if값이 거꾸로 되어있다. nginx 컨테이너의 12번 항목의 if13과 호스트 13번 항목의 if12가 일치하며, CentOS 컨테이너의 14번 항목의 if15와 호스트 1번 항목의 if14가 일치한다.

 

외부에서 컨테이너로 연결

컨테이너는 기본적으로 docker0 브릿지 네트워크에 있으므로 호스트에서만 접근이 가능하다. 기본적으로 외부접근은 불가하다. 외부에 컨테이너 에플리케이션을 노출하기 위해서는 컨테이너의eth0의 ip와 포트를 host의 ip+포트에 바인딩을 수행해야 한다.

 

컨테이너 실행 시 포트옵션으로 바인딩할 수 있다. (docker conatiner run 포스팅에서 자세히 설명한다) 이러한 바인딩은 기술적으로 가상 브릿지 docker0와 호스트OS의 물리 NIC에서 패킷을 전송하는 장치가 필요하다. 도커에서는 NAPT 기능을 사용하여 연결한다.

 

# 참고 : NAPT(Network Address Port Translation) 이란?

Private IP 를 Public IP로 변환하여 Pirvate IP가 외부에 연결할 수 있도록 하는 기술은 NAT, NAPT 두가지가 있다. NAT는 단순히 Private IP를 변경하는 것이지만, NAPT는 Private IP와 함께 포트번호까지 함께 변환한다. 따라서 하나의 Public 주소를 여러 개의 Private IP 주소로 변환할 수 있다. NAPT는 이러한 기술의 이름으로써, LINUX에서 NAPT를 사용하는 것을 IP Masquerade 라고 부른다. docker에서는 NAPT 기술을 사용하기 위해 LINUX의 iptables 프로그램을 사용한다.

ip 마스커레이드는 패킷의 소스 주소를 자체 공용 ip주소로 변경하면서 패킷을 전달하는 기능이다. 브릿지에 연결된 컨테이너들은 private ip가 할당된다. Private ip로는 인터넷 통신이 불가능 하기 때문에, 출발지 IP를 Public ip로 바꾸는 방법으로 인터넷 통신이 가능하게 한다.  해당 패킷에 대한 응답을 받을 때는, 받을 대상으로써 자체 공용 ip주소가 응답을 받고, 그걸 다시 원래 호스트 주소로 수정하고 패킷을 전송한다. NAT의 한 형태이다.

 

NAPT NAT 두가지의 예시를 아래와 같이 정리할 있다.

 

외부에서 컨테이너로 들어올 때 :  웹서버 컨테이너를 시작할 때 컨테이너 안의 웹서버가 사용하는 80번 포트를 호스트OS의 8080포트로 전송하도록 설정한다. 그러면 외부 네트워크에서 호스트OS의 8080포트로 접속하면, 컨테이너안의 80번 포트로 연결하게 된다.

 

컨테이너에서 외부로 나갈 때 : 192.168.0.1, 192.168.0.2 각각 컨테이너가 있고, NAT 54.10.10.1이 있다고 가정하자. 각 컨테이너가 외부로 나가려면 192.168.0.1 이 54.10.10.1 변환되어 할일을 다 한 후, 그 후에야 192.168.0.2가 54.10.10.1로 변환되어 할일을 할 수 있다. 하지만 NAT가 아니라 NAPT라면, 192.168.0.1:1500 포트로 NAPT 54.10.10.1 로 변환해서 나가고, 192.168.0.2:1501 포트가 NAPT 54.10.10.1로 변환해서 나갈 수 있다. 

 

# 참고 : docker-proxy

컨테이너 포트를 외부로 노출하도록 설정하면, 도커 호스트에는 docker-proxy라는 프로세스가 자동으로 생성된다. docker-proxy는 이름처럼 docker host로 들어온 요청을 container로 넘기는 것 뿐이다. docker-proxy는 커널이 아닌 userland에서 수행되므로 kernel과 상관없이 host가 받은 패킷을 그대로 containt의 포트로 넘긴다. 이렇게 외부로 binding 된 컨테이너가 있다면, 아래처럼 docker-proxy라는 프로세스가 생기며, 포트 LISTEN도 확인할 수 있다.

예를들어, -p 8080:80 옵션을 사용해서 컨테이너 포트를 오픈하면, docker-proxy가 생성되며, 8080포트를 리스닝하는것을 확인할 수 있다.

root@~~# netstat -nlp | grep 8080
tcp6       0      0 :::8080                 :::*                    LISTEN      12581/docker-proxy

+ Recent posts