본문 바로가기

서버운영 (TA, ADMIN)/인프라

[인프라] 도커 컨테이너

 컨테이너란? 개발, 배포하는 소프트웨어 패키지들의 표준. OS 커널 하나에 여러개의 컨테이너가 격리된 상태로 병리되어 실행될 수 있는 개념. 컨테이너는 VM 보다 가볍고 빠르다.


컨테이너는 리눅스 커널에서 제공하는 기능으로, 도커는 컨테이너라는 기술을 이용한 오픈소스이다. 도커는 컨테이너를 이미지 파일로 빌드/배포하도록 한다. 도커 이미지 규격에 따라 git 저장소 처럼 build/push/pull하는 것도 가능하다.


비슷하게 리눅스 컨테이너를 활용한 rkt, lxc, lxd 오픈소스도 존재하지만 도커만큼 많이 사용되고 있지는 않다.


기존 Virtual Machine의 경우는 모든 VM이 자신의 OS를 별도로 가지고 있다면, 컨테이너의 개념은 단일 HOST OS 위에 애플리케이션 + 바이너리/라이브러리가 컨테이너 각각으로 띄워져 있는 형태라고 보면 됨. (패키징된 형태)


VM의 시스템 영역에서 격리가 되지만, Container는 어플리케이션과 바이너리 영역에서 격리가 된다.


도커는 컨테이너를 실행하기 위한 시스템 라이브러리. 도커는 리눅스에서 컨테이너라는 기술을 지원한다. 사용자가 컨테이너를 실행하기 위해 호출을 해주면 도커라는 바이너리에 내가 원하는 스펙의 컨테이너를 실행할 수 있도록 Host OS에 요청(System Call)을 보내는 형태로 이해하면 된다.


하이퍼바이저 기반으로 부팅되어 리소스를 점유하고 실행되는 VM(빌드하는 것도 무겁고 띄우는 것도 무겁다.)과는 달리 하나의 프로세스가 격리되어 실행되는 형태라 가볍다(빌드/로드 단위가 매우 가볍다.)


배포단위에 맞춰 이미지를 맞춰서 이미지를 만들어서 바로 배포해서 띄우는 작업에 용이하다. 컨테이너는 뜨는데 부팅시간(수초내 실행)이 없다.



도커 컨테이너 주요 기술 



chroot (Change Root Directory) - confied environment

유닉스 운영체제에선 chroot는 현재 실행 중인 프로세스와 차일드 프로세스 그룹에서 루트 디렉터리를 변경하는 작업이다. 이렇게 수정된 환경에서 실행되는 프로그램은 지정된 디렉터리 트리 밖의 파일들의 이름을 지정할 수 없으므로 chroot 감옥이라고 부른다. chroot는 chroot(2) 시스템 호출이나 chroot(8) 래퍼 프로그램을 가리킬 수 있다.


주로 host 한대를 여러사람이 같이 쓸때 사용하는 기능이다.


Namespaces

Linux Namespaces는 프로세스에게 격리된 OS View를 제공하는 커널 기술. chroot 처럼 마운트 경로만 다르게 지정되는 것이 아니라 network 경로도 다르게 보여주고, host name / 사용자 계정 / process id 영역도 다르게 보여준다. (pid - processors, net - network, ipc - inter process call, mnt - system mount table, uts - host name, user - user account 등)


프로세스 생성 시점에 어떤 네임스페이스를 사용할지 선택할 수 있고 프로세스는 선택한 네임스페이스 별로 격리된다.


Namespace 없이는 모든 프로세스가 동일한 Network Resources를 사용하게 되지만, Namespace로 격리된 프로세스들은 같은 eth0 이더라도 서로 다른 Network Resource를 사용하게 된다.


Cgroup

cgroup(Control Group)은 애플리케이션(프로레스)에게 하드웨어 리소스 (ex. CPU, MEM, Disk, Network, ...)를 그룹으로 묶어서 할당하는 기술. (namespace 기술을 통해 서로 안보이는것에만 그치지 않고, 서로간 리소스에 제한을 걸어둬야할 필요가 있음.)


cgroup으로 묶은 프로세스들은 리소스 별로 정해진 cpu, mem, disk io량만 사용할 수 있게 된다. cgroup은 리소스 타입별로 독립적인 트리 구조를 가진다. cgroup 노드는 하위에 서브 노드를 가질 수 있으며 Leaf-Node는 프로세스가 된다. 리소스 종류별로 그루핑하여서 그룹별로 사용량 지정 세팅이 가능하다.


Share : 리소스 사용의 우선순위(Priority)를 줄 수있다.

Set : 특정 리소스만 사용하도록 할 수 있다. (ex. binding cgroup0 to CPU0)

Quota: 시간당 정해진 리소스만 사용하도록 한다. (ex. limit CPU 100% - cpu 전체의 사용률 : 사용텀 단위 알고리즘)


Union Filesystem

도커 컨테이너가 Namespace와 cgroup에 의해 격리될때 컨테이너가 사용할 파일 시스템도 격리가 되어야 한다.

UnionFS은 서로 다른 파일 시스템이나 디렉토리를 합쳐서 하나의 논리적인 파일 시스템으로 컨테이너에게 제공한다.


Image layer(RO, lowerdir) - Container layer(RW, upperdir) - Container mount(merged)


기본적으로 이미지는 컨테이너들이 공유하게 되고, RW레이어만 따로가지고 있는 것으로 이해하면 더 쉽다. 따라서 파일을 수정할때 이미지를 수정해버리면 다른곳에서 보는 이미지도 수정되기때문에 COPY UP이라고 해서 이미지를 수정하면 수정한 순간에 파일을 복사하여 Container layer 층에 파일이 신규 생성되고 overwrite된 파일이 Container mount된다. WHITE OUT이라고 특정 이미지 파일을 지우면 해당 파일이 지워졌다고 마킹을 한다. 이미지 하나를 컨테이너 여러개가 share해서 쓰면서 전체 파일시스템 전체복사의 오버헤드를 줄이게 된다. 트레이드오프로 작은 파일이 많을수록 성능이 느려지는 특징을 가지고 있다.


컨테이너는 RW레이어만 따로 가지고 있다. Union FileSystem을 사용하면 여러 컨테이너가 하나의 이미지를 사용할 경우 각각의 컨테이너는 자신이 overwrite 할 영역만 유지할 수 있다. (GraphDriver?)




각각의 단계마다 layer에 대한 hash 값을 뽑아둔다. 로컬에 같은 hash 값의 layer가 있다면 그것을 사용한다.


> docker build -f Dockerfile -t demo:tag

Sending build context to Docker daemon 4.608kB

Step 1/6 : FROM golang:1.8.3

Step 2/6 : WORKDIR /go/src/hello-docker

Step 3/6 : COPY main.go /go/src/hello-docker

Step 4/6 : RUN go get ./..

Step 5/6 : RUN go install

Step 6/6 : CMD hello-docker

Successfully built ...

Successfullty tagged demo:tag


도커이미지는 layer로 구성이 되어있고, 각각의 레이어는 .tar.zip 파일 형식이다. docker pull을 하게되면 해당 이미지를 가져오고, 이후 컨테이너를 띄우고 run, copy . 명령어를 수행한다. 결과물이 파일에 남게되면 그걸 다시 tar.zip로 묶어서 변경된 부분만 그것을 layer로 저장하는 방식이다.


> docker image ls (도커 이미지 리스트 보기)

> docker ps | grep "도커명" (컨테이너 리스팅)

> docker ps -f (with filter) (filtering된 컨테이너를 리스팅 - shell script에서 쓰는 경우 있음)

> docker inspect $DOCKER_NAME (도커 상세정보 보기)

> docker exec (컨테이너 내부에서 bash 등의 명령을 실행)

> docker rm -f $DOCKER_NAME (컨테이너 제거 (프로세스, 컨테이너 메타정보 삭제))


> docker run --help


Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]


Options:

-d, --detach            컨테이너를 background로 실행한다.

--name string           컨테이너의 이름을 지정한다.

-p, --publish 외부:내부   컨테이너의 포트를 host의 포트와 연결한다(NAT 방식)


네트워크 바인딩 방식은 docker inspect 명령어 응답을 보면 자세히 살펴볼 수 있다. (Networks bridge 방식)

"NetworkSettings": {
	"Bridge": "",
	"SandboxID": "",
	"HairpinMode": false,
	"LinkLocalIPv6Address": "",
	"LinkLocalIPv6PrefixLen": 0,
	"Ports": {
		// 도커 내부 바인딩 port
		"80/tcp": [
			{
				"HostIp": "0.0.0.0",
				"HoutPort": "32769" // 도커 외부 바인딩 port
			}
		]
	}
	...
	"Gateway": "172.17.0.1",
	"IPAddress": "172.17.0.4",
}

도커 네트워크에 대한 상세한 설명은 다음 포스팅에서 확인.



이미지 배포와 실행


지금까지는 도커 저장소 한대에서 이미지 빌드와 실행만 해본것이다. 하지만, 서비스 배포시에는 리모트에 있는 저장소에 이미지를 보내고 실 애플리케이션 서버에서는 이를 받아서 실행하는 과정이 추가적으로 필요하다. 이 과정이 바로 이미지 배포와 실행이다.


> docker tag (기존 이미지를 참조하는 새이미지를 생성)

> docker push (로컬의 이미지를 원격의 저장소로 올린다)

> docker pull (원격의 이미지를 로컬의 저장소로 내려받는다)


도커는 이미지와 태그의 이름이 url 주소형식이면, private registry라고 인식하여 그쪽으로 이미지를 operation하려고 시도한다. url 주소가 아니면 public registry라고 인식한다.