프로그래밍/데이터 엔지니어링
도커 컨테이너에서 멀티 프로세싱을 하면?
자유로운 오랑우탄
2021. 8. 29. 14:10
데이터 엔지니어링을 하다 보면 실제로 컴퓨팅 리소스를 최대한으로 사용할 수 있도록 멀티스레딩 혹은 멀티프로세싱을 사용한다. 더불어 요새는 국 룰이 돼버린 컨테이너를 활용해서 프로세스를 격리하고 배포, 테스팅을 훨씬 쉽게 진행할 수 있게 되었다.
그러다 문득 궁금한 점이 하나 생겼다. 만약 멀티 프로세싱을 구현했을 때 컨테이너에서는 어떻게 동작할까. 사실 도커를 사용한 지는 오래 됐지만, 그냥 단순하게 하나의 격리된 프로세스라고만 생각했었다. 그래서 본 질문에 대해 대답이 제대로 떠오르지 않았다.
- 결과부터 이야기하면 컨테이너 내에서 Multi Process 동작은 한 컨테이너에서 호스트 머신의 CPU 자원을 최대한 활용하여 동작한다. 컨테이너의 네임스페이스 내부에서 멀티 프로세스들이 동작하며, 주어진 리소스 quota(cgroup)을 최대한 활용하며 동작하게 된다.
- 도커 컨테이너는 결국엔 호스트 머신(혹은 가상 머신) 위에 동작하는 프로세스들을 네임스페이스, cgroup 등을 기준으로 격리한 것이라고 한다.
참고 이미지 : https://dololak.tistory.com/351 - 네임스페이스는 프로세스가 실행될 때 시스템의 리소스를 분리할 수 있도록 도와주는 기능이다. cgroup(호스트 자원), pid(프로세스), mount(볼륨) 등의 분리를 지원한다.
- cgroup은 자원에 대한 제어를 가능하게 해주는 기능이다. 즉 한 cgroup에서 memory, cpu, I/O 등을 제어할 수 있다는 이야기이다.
- 컨테이너는 이를 통해 하나의 프로세스가 다른 프로세스에 영향을 끼치지 않도록 네임스페이스를 이용해 격리하고, 자원도 cgroup를 통해 할당할 수 있게 된다.
- 실제로 컨테이너에 접근해서 ps -all 을 돌려도 해당 컨테이너와 관련된 프로세스만 확인할 수 있다.
- 멀티 프로세스를 사용하는 애플리케이션을 컨테이너로 말게 되면, 보통 entrypoint에서 실행되는 프로세스가 pid가 1인 process가 되며, 거기서 포크되는 프로세스들은 ppid를 1로 가지며 실행된다. 그리고 해당 프로세스들은 같은 namespace, cgroup을 공유하게 된다.
- 보통 docker run을 할 때 실행되는 command가 pid가 1인 process가 된다.
- 실제 컨테이너 런타임에 sh로 접근한 후 top 를 해보면 아래와 같이 프로세스가 나온다. 이 경우 다른 프로세스의 PPID는 전부 1이며 부모 process로부터 fork 되어 만들어지게 된다.
- 호스트 머신의 관점에서 이는 다 같은 프로세스이며, 그저 격리된 네임스페이스 안에 존재하는 프로세스일 뿐이다.$ docker run -it --rm busybox /bin/sh $ sleep 10s & sleep 30s & top PID PPID USER STAT VSZ %VSZ CPU %CPU COMMAND 1 0 root S 1332 0.0 1 0.0 /bin/sh 21 1 root R 1324 0.0 1 0.0 top 19 1 root S 1312 0.0 0 0.0 sleep 10s 20 1 root S 1312 0.0 1 0.0 sleep 30s
- 파이썬 애플리케이션으로 멀티 프로세스를 구현한다면?
- 파이썬에서 multiprocessing.cpu_count()를 사용하면 사용가능한 CPU 코어 개수를 반환한다.
$ (container 내부) python -c 'import multiprocessing; print(multiprocessing.cpu_count())' 12 # 호스트 머신의 CPU 갯수를 가리킨다.
- 본 실험을 진행한 호스트 머신은 도커에서 cpu를 100% 활용하도록 설정하였다.
- 도커 클라이언트 엔진 설정을 통해서도 available cpu를 정해줄 수 있으며 도커 컨테이너를 실행할 때 --cpuset-cpus flag를 주면 해당 컨테이너에 CPU 코어 수를 할당해줄 수 있다.
- 멀티 프로세싱을 하는 코드를 구현한다
from concurrent.futures import ProcessPoolExecutor import multiprocessing as mp import logging import sys import psutil def get_logger(): logger = logging.getLogger() if not logger.hasHandlers(): handler = logging.StreamHandler(sys.stdout) formatter = logging.Formatter("[%(process)d/%(processName)s] %(message)s") handler.setFormatter(formatter) handler.setLevel(logging.DEBUG) logger.addHandler(handler) logger.setLevel(logging.DEBUG) return logger def func(n): get_logger().debug(f"number: {n}, cpu_num: {psutil.Process().cpu_num()}") if __name__ == "__main__": with ProcessPoolExecutor(max_workers=mp.cpu_count()) as executor: for n in range(1,100): executor.submit(func, n)
- docker build 후 run을 해보면 아래와 같은 로그가 찍힌다. 보통 하나의 프로세스는 하나의 cpu 코어에 할당되어 수행되는데, 결과를 보면 process 별로 cgroup에 할당된 cpu 자원을 활용하고 있는 걸 확인할 수 있다.
[16/ForkProcess-9] number: 89, cpu_num: 4 [14/ForkProcess-7] number: 88, cpu_num: 11 [11/ForkProcess-4] number: 87, cpu_num: 0 [18/ForkProcess-11] number: 90, cpu_num: 1 [19/ForkProcess-12] number: 91, cpu_num: 6 [10/ForkProcess-3] number: 92, cpu_num: 8 [13/ForkProcess-6] number: 93, cpu_num: 4 [12/ForkProcess-5] number: 94, cpu_num: 10 [17/ForkProcess-10] number: 95, cpu_num: 7 [9/ForkProcess-2] number: 97, cpu_num: 3 [15/ForkProcess-8] number: 96, cpu_num: 3 [8/ForkProcess-1] number: 98, cpu_num: 6 [16/ForkProcess-9] number: 99, cpu_num: 4
- 파이썬에서 multiprocessing.cpu_count()를 사용하면 사용가능한 CPU 코어 개수를 반환한다.
- docker 공식 문서에서는 한 컨테이너에서 여러 프로세스를 구현하기보단, 싱글 프로세스 - 멀티 컨테이너 전략을 사용하라고 한다.
- 하나의 컨테이너에서 멀티 프로세싱을 하게 되면, 컨테이너 특성상 디버깅과 테스트하기가 힘들다고 한다. 그도 그럴 것이 컨테이너에 문제가 생기면 돌고 있던 자식 프로세스들도 함께 영향을 받을 것 같음. 또한 E2E 테스트를 하게 됐을 때 사이드 이펙트도 무시할 수 없을 것 같다.
- 그래도 만약에 하게 된다면 process들을 managing해줄 수 있는 supervisord 같은 프로그램을 entrypoint에서 실행해 주는 게 좋다는 글도 있음
- 하지만 케바케라는 생각도 들음. 만약 1 process 1 container라면 격리된 컨테이너의 특성상 이들의 진행을 중재, 관리해주는 컨테이너(POD)가 별도로 존재해야 해서 복잡도가 올라갈 것 같다는 생각이 들었다. 또한 단순히 병렬적인 프로세스 운영이 아닌, 커플링이 강하게 된 두 프로세스를 띄워야 할 때는 불가피하지 않을까? 하는 생각도 든다.
[참고]
http://cloudrain21.com/examination-of-docker-process-binary
https://tech.ssut.me/what-even-is-a-container/
https://www.44bits.io/ko/keyword/linux-namespace
https://stackoverflow.com/questions/38519223/can-we-run-multi-process-program-in-docker