1. Docker 사용 이유
- 도커를 사용하는 이유는 특정한 프로세스를 실행 시키는 환경을 이미지화 하여 일정하게 관리하면서 컨테이너 외부와는 격리 시키는 것에 있다. Docker는 이 과정을 VM과 달리 매우 가볍고 효율적으로 달성할 수 있다.
-
환경을 일정하게 유지: 한번 생성된 image는 다른 시점, 환경에서 실행해도 같은 개발, 배포 환경을 구축할 수 있게 해준다.
-
외부와 격리: 컨테이너는 docker-engine이 구동시키고 이러한 컨테이너들은 서로 다른 컨테이너, host와 격리된다. 각각의 컨테이너가 개별적인 프로세스 실행능력과 , 메모리 공간을 가지게 된다.
-
효율성 : 공유하는 부분은 하나로 나누어 쓰고 변경지점이 필요할 경우 각자 상위 레이어에 추가할 수 있다.
💡 효율성 부분은 중요한 부분이므로 밑에서 자세하게 설명합니다.
2. Docker 와 Virtual machine 차이점
- 가장 핵심적인 차이는 Docker는 컨테이너 실행 시 공유하는 부분을 서로 다른 컨테이너가 공유한다는 점에 있다.
- 또한 컨테이너는 Host os 의 커널을 공유하여 사용하기 때문에 Host os에 종속 적이다.(가상머신은 GuestOS를 따로 설치할 수 있음.)
- 예를들면, Window os에서 Linux 컨테이너를 구동시키는 것은 불가능하다.(중간에 virtual machine을 두어 해결할 수 있음.)
- 만약, 같은 서버 인스턴스 1000개를 1000개의 컨테이너에 각각 넣어 실행시킨다고 도커 파일로 부터 생성된 이미지 부분은 모두 공유하여 사용하기 때문에 컨테이너 실행시 로딩시간과 메모리관점에서 매우 효율적이다.
- 하지만 이를 가상머신에서 구동시킨다면 각각의 격리 공간마다 Guest os가 모두 필요하고 각각의 서버 인스턴스를 따로 실행시키고 메모리를 할당해주어야 한다.
3. Docker 내부 매커니즘
3-1. Dockerfile 을 통한 레이어 이미지 생성
FROM debian:buster
RUN apt-get update && apt-get install nginx -y && \
apt-get install openssl -y
RUN openssl req -x509 -nodes -days 365 -subj "/C=KR/ST=Seoul/L=Songpa-gu/O=42/OU=42Seoul/CN=takim.42.fr" \
-newkey rsa:2048 -keyout /etc/ssl/nginx.key -out /etc/ssl/nginx.crt;
COPY ./default etc/nginx/sites-enabled/default
CMD ["nginx", "-g", "daemon off;"]
다음은 nginx 이미지를 생성하기 위한 dockerfile 이다.
- 이미지 생성(docker build)시 FROM, RUN, COPY, ADD는 순차적으로 실행될때마다 image layer에 변경사항을 추가한다.
- RUN, FROM 줄의 형태가 같게 작성되어 있다면 서로 다른 도커 파일에서 이미지를 생성할때 캐시화 시켜놓은 데이터를 불러와 바로 이미지로 추가한다.
- COPY, ADD의 경우에도 같은 host의 파일을 옮기는 경우에는 캐싱을 사용한다.
- 따라서 위 4 커맨드의 경우 캐싱이 효율적으로 잘 이루어질 수 있도록 순서를 잘 생각하여 도커 파일들을 작성하는 것이 중요하다.
- CMD, ENTRY POINT 는 컨테이너 실행시 최초의 프로세스(PID 1)를 의미한다.
- 사실 정확하게 말하면 ENTRY POINT가 최초의 프로세스에 가까우며 CMD는 ENTRY POINT의 default parameter로 이해하는 것이 맞다.
- ENTRY POINT가 명시 되지 않는 경우 디폴트가
ENTRYPOINT ["/bin/sh", "-c"]
이기 때문에 CMD가 그대로 실행되는 것처럼 보인다.
- docker run , exec시 파라미터가 주어지면 CMD는 덮어씌워진다.
3-2.컨테이너 실행
- 같은 이미지를 기반으로한 컨테이너를 실행할 경우 이미지 데이터를 read-only로 취급하여 공유하고 변경 사항이 발생할 경우 thin write-layer를 컨테이너마다 개별적으로 생성함.
- 기본적으로 Copy-On-Write전략을 사용한다.
- 자바의 String 객체, fork 시스템콜등에서 사용되는 전략이다.
- 변경이 발생할때 동시성 문제를 해결하기 위해서 변경 이전 까지는 공유해서 사용하고 변경이 발생할 경우 변경을 한 주체를 위하여 복사 변경본을 새로 생성해 주는 것이다.
- 복사 과정에서 최소 페이지 단위의 복사가 일어나기때문에 write가 빈번할 경우 성능이 저하된다는 문제가 있다.
- 컨테이너가 삭제되면 컨테이너 실행 이후에 생성된 thin write layer는 삭제되기 때문에 영속성을 제공하지 못한다.
- 따라서 영구적으로 ssd에 저장해야하는 데이터(DB)등에 대해서는 docker Volume이라는 기능을 사용해야한다.
- 자세한 내용이 궁금하면 https://docs.docker.com/storage/storagedriver/
3-3 Container의 PID 1 과 Zombie Reaping Problem
- CMD, ENTRY POINT 는 컨테이너 실행시 최초로 실행되는 프로세스(PID 1)를 의미한다.
- 원래 linux 시스템등에서는 PID 1이 init 프로세스로 되어있는데 컨테이너 내부에서는 PID1이 지정된 프로세스이기 때문에 기존에 init 프로세스가 해주는 역할을 수행하지 못함.
- Zombie Reaping Problem
- 원래 모든 프로세스는 init 프로세스를 제외하면 부모프로세스로 부터 생성되어 존재하고 소멸시, 혹은 이전에 wait, waitpid등의 시스템콜을 부모로 부터 호출 받아 exit status 값을 반환해야함.
- 그렇지 못한경우 exit status 값 반환을 위해 확보되어있는 최소한의 자원은 커널로 반환 되지 못하고 이러한 프로세스를 Zombie Process라고 함.
- 자식 프로세스가 먼저 죽은 경우에는 부모가 뒤늦게라도 wait시스템 콜을 호출 (이를 Reaping 이라고함)하면 되지만, 부모 프로세스가 먼저 죽는 경우에는 Reaping해줄 부모가 사라지게 됨.
- 부모가 먼저죽은 자식 프로세스를 Orphan process라고 하고 이러한 Orphan process는 init 프로세스가 입양하여 대신 Reaping을 해줌.
- 도커 컨테이너의 경우
- PID1이 init 프로세스가 아니므로 이를 대체할 프로세스를 PID1에 인위적으로 만들어줄 필요가 있음.
- dumb-init, tini등의 프로그램을 PID1에 등록할 수 있다.
- 가장 간단한 방법은 docker-compose.yml 파일에 각 서비스에 “init: true”라는 옵션을 붙여 주어 tini가 PID 1 로 실행될 수 있도록 하는 것이다.
출처:
도커 공식문서: https://docs.docker.com
Zombie Reaping Process PID1: https://blog.phusion.nl/2015/01/20/docker-and-the-pid-1-zombie-reaping-problem/