Introduction
MLOps를 하기 위해서는 DevOps의 필수품이었던 Docker와 Kuberentes, Prometheus, Grafana 등이 거의 기본 소양이 되어가는 것 같습니다.
저는 아직은 AI 모델러의 역할에 익숙해서 하나하나 기초적인 책을 보면서 배워가고 있지만 이전에 포스팅 했던 Jenkins 를 통한 CI/CD 자동화도 하나씩 해보면서 배워가니 MLOps 쪽이 훨씬 더 재미있네요 ㅎㅎ
앞으로 Jenkins, Argo를 통한 CI/CD와 Docker의 기초부터 시작해서 Kubernetes 그리고 MLOps (MLflow, AWS model serving ..) 까지 쭉 공부했던 내용과 지속적으로 몰랐던 것들을 채우면서 공부하는 지식들을 정리를 해보려고 합니다. 1일 ~ 2일에 한번은 포스팅을 해야 저도 열심히 공부하는 사람이 되는 것 같아서 정리가 부족해도 같이 공부해나가실 분들은 도움이 되지 않을까 합니다.
우선은 이 모든 MLOps의 기본이 되는 Docker부터 시작을 하려고 합니다. 워낙 많은 자료들이 있지만 저는 개인적으로 "쿠버네티스 입문 90가지 예제로 배우는 컨테이너 관리 자동화 표준" 라는 책이 제일 좋았습니다. 인터넷에서 정보를 찾다보면 단편적으로만 정보가 있어서 흐름을 알고 공부하기가 어려운데 개념을 잡는데 매우 좋았습니다.
자 그럼 컨테이너 이해의 시작인 chroot, cgroup 그리고 namespace부터 살펴보도록 하겠습니다.
Chroot
쿠버네티스와 도커라고 하면 가장 많이 듣게되는 말 중 하나가 컨테이너 입니다. 그런데 컨테이너란 원래 리눅스에 있던 용어 입니다. 그래서 이 컨테이너가 무엇인지를 간단하게 정의하자면 '격리된 프로세스' 입니다. 즉, 격리된 프로세스를 리눅스에서 생성해내는 방법들인 '네임스페이스', cgroup, chroot 등의 방법은 격리된 프로세스를 생성할 수 있는 방법이기 때문에 컨테이너를 생성하는 방법 중 하나입니다.
그 중 리눅스, 유닉스 환경에는 원래 chroot 라는 명령어가 있었습니다. 'ch'(ange) + 'root' 라는 명령어로써 말 그대로 리눅스의 '/'인 root를 아래 명령어와 같이 다른 directory로 바꾸는 명령어입니다.
# chroot '새로 root가 될 directory' '새 root를 기준으로 실행 할 프로세스'
mkdir -p /home/something/bin/
chroot /home/something /bin/bash
그런데 root를 바꾼다는 것의 의미가 무엇일까요? 리눅스에서 각각의 프로세스는 자신이 접근할 수 있는 파일 시스템의 root를 알고 있습니다. 여기서 중요한 리눅스 시스템의 파일 구조를 하나 알고가야 합니다.
root 디렉토리는 최상위를 의미하는 특별한 위치이며, 모든 디렉터리와 파일은 이 루트 디렉터리 아래에 존재합니다. CS 전공자 분들에게 익숙한 트리구조가 되는 것입니다.
그런데, 이때 위의 그림과 같이 /home을 새로운 root로 chroot를 통해서 변경시킨 경우를 생각해 봐야합니다. 새로운 root를 자신의 root로 인식하고 있는 프로세스는 기존에 root에 접근할 수 있을까요?
새로운 프로세스는 회색 박스 내의 영역만을 인식하고 접근할 수 있게 됩니다. root라는 디렉토리는 최상위 폴더라고 인식하고 있으며 모든 디렉토리는 root 아래에 있다고 생각하고 작동하기 때문입니다.
즉, chroot는 프로세스가 자신이 알고 있는 root에서 이를 통해 프로세스가 액세스 할 수있는 디렉토리를 제한하거나 시스템 라이브러리와 관련 라이브러리를 로드 할 수 있게 하기 때문에, 컨테이너인 격리 프로세스를 만들 수 있는 하나의 방법인 것입니다.
하지만 저희가 알고 있는 최신 컨테이너처럼 네트워크를 격리시키거나 프로세스를 제어하는 방법은 가지고 있지 않기 때문에 지금의 컨테이너와 완벽하게 같다고 할 수는 없습니다. 이러한 제약 사항을 조금 해결하기 위한 방법이 FreeBSD jail 입니다.
chroot를 통해서 파일 시스템을 나누고 이 나누어진 파일 시스템 (위 사진의 회색 박스 영역)을 jail이라고 칭합니다. 각각의 jail은 서로 독립적이고 영향을 미치지 않습니다. 그리고 한개의 서버에 여러 jail 을 만들고 각각 ip 주소를 할당할 수 있으며, jail 내부는 프로그램 입장에서 실제 시스템 (/ 을 root로 하는 시스템)과 구별 할 수 없어 여러 장점을 가집니다.
여기까지 리눅스의 컨테이너의 역사를 검색하면 나오는 가장 기본이 되는 chroot에 대해서 알아보았습니다.
Cgroup
Cgroup은 'control' + 'group'의 약자로 하드웨어 자원 (ex., cpu, ram, newtork, device, io)들을 group 별로 묶어 컨트롤하는 리눅스 커널의 기능입니다. 이때 group에 속하는 대상들은 프로세스입니다. 즉, 시스템에서 생성된 프로세스들은 모두 자신에게 정해진 cgroup에 속하며 프로세스가 사용하는 하드웨어 자원은 자신이 속한 cgroup의 통제를 받게 됩니다.
이러한 기능은 격리된 프로세스인 컨테이너가 OS의 모든 하드웨어 자원을 사용하는 것을 막을 수 있는 중요한 기능을 제공합니다.
Namespace
Namespace는 하나의 시스템에서 프로세스 별로 리소스 사용을 분리시키고자 하는 기능입니다. 그러나 cgroup처럼 하드웨어적으로 리소스를 분리 시키는 것이 아니라 OS 와 커널은 동일한 상태에서 그 위의 프로세스들 간의 격리가 이루어 집니다.
*하드웨어 가상화가 아닌 Linux 내의 자원을 가상화. 하드웨어 자원의 가상화는 cgroup*
조금 더 쉽게 표현하자면, 하나의 OS와 커널 위에서 프로세스들이 동작하는 이름이 다른 공간을 만들어, 프로세스를 분리하는 방법입니다. 아파트에서 각자 다른 호실에 사는 것과 유사하겠죠. 그렇기 때문에 다른 namespace에 존재하는 프로세스들은 서로 리소스 관리가 따로 이루어 집니다. 101 호가 주소를 100호로 바꾼다고 해도 102호, 103호 ... 는 아무런 영향이 없는 것처럼 말이죠.
네임스페이스에는 종류가 많이 있습니다.
- PID : 프로세스 ID를 격리시킬 수 있는 네임스페이스. 항상 1은 init 프로세스이지만 격리된 네임스페이스에서는 다시 1부터 프로세스 ID가 할당된다. (메인 프로세스 입장에서는 아니지만 .. 가상화 가상화!)
- Network : 프로세스의 네트워크 환경을 분리하는 네임스페이스. 분리된 네트워크 네임스페이스 내부의 프로세스들은 새로운 IP를 부여받거나 네트워크 인터페이스를 추가할 수 있습니다.
- UTS : 호스트 네임과 NIS 도메인 네임을 격리하는 네임스페이스. 컨테이너 네트워크의 기초가 됩니다.
- MNT : 파일 시스템의 마운트를 격리하는 네임스페이스. 호스트 파일시스템에 구애받지 않고 자유롭게 파일 시스템을 마운트 혹은 언마운트가 가능하게 해줍니다.
- USR ......
- IPC .....
위와 같은 기능들 하나하나가 컨테이너에서 매우 중요한 기능을 수행하고 있습니다. 하지만 설명드렸다싶이 네임스페이스는 하드웨어의 격리를 수행할 수 없으므로 cgroup, 네임스페이스 그리고 chroot 의 기능이 합쳐져야 저희가 docker를 통해서 실행시키고자 하는 컨테이너의 OS 위에서의 프로세스 별 격리가 잘 이루어질 수 있게 됩니다. (실제로 도커에서 chroot를 사용해 루트 디렉터리를 격리하지 않는다고 합니다. 그래도 원리를 이해하기 위해서 chroot를 볼 필요가 있다고 44bits.io 에서 ...)
* 도커의 컨테이너는 기본값으로 브릿지(L2) 네트워크를 사용합니다. 물론 이외에도 다양한 네트워크 인터페이스를 사용할 수 있습니다 *
* 도커의 컨테이너가 네트워크 통신을 하기 위해서는 namespace로 격리된 네트워크 설정에서 호스트 (서버 머신)의 eth와 격리된 컨테이너의 eth를 연결짓는 veth 쌍을 연결시켜줘야 합니다. 아래의 링크에서 이러한 방법에 대해 자세히 다루고 있으니 꼭 한번 읽어보세요 *
https://www.44bits.io/ko/post/container-network-2-ip-command-and-network-namespace
컨테이너와 가상머신
도커 컨테이너와 가상머신의 차이에 대해서 우선 정의를 명확히 하는 것은 필수적입니다.
어떠한 하나의 프로세스를 실행시키고자 할 때 가상 머신은 "운영체제 위에 하드웨어를 에뮬레이션하고 그 위에 가상 운영체제를 올리고 프로세스를 실행하는 방법"인 반면, 컨테이너는 "하드웨어 에뮬레이션 없이 OS와 리눅스 커널을 공유해서 바로 프로세스를 실행하는 방법" 이라고 설명할 수 있습니다.
리눅스의 컨테이너는 lxc(Linux Container)라고도 불리며, 앞서 배운 네임스페이스와 cgroup을 사용하여 각각의 프로세스를 서로 격리 시키고 샌드박싱하여 독립된 런타임 환경을 제공하는 기술을 의미합니다.
샌드박싱이란 컨테이너 내부에서 실행되는 프로세스를 호스트로부터 격리시키는 것을 말합니다. 즉, 호스트 시스템에 의존하지 않고 프로세스가 돌아가게 하는 것을 의미합니다. 이를 통해 컨테이너는 도커와 결합되어 어플리케이션이 서로 다른 머신 환경에서도 동일하게 실행될 수 있음을 보장합니다.
*책이나 블로그 들에서 이런식으로 표현을 하기도 하는 것을 기억해주세요.
"호스트 OS의 리소스를 논리적으로 분리시키는 방법"*
정리
여기까지가 Container의 기본 개념입니다. 물론 나중에는 더 깊이 있는 지식이 k8s를 운용하기 위해서 필요하지만 차근차근 포스팅으로 살펴보도록 하고 다음 포스팅에서는 kubernetes 의 기본 개념부터 살펴볼 예정입니다.
References
https://iancoding.tistory.com/184
https://mangkyu.tistory.com/86
https://dololak.tistory.com/351
https://subin-0320.tistory.com/28
https://admion.net/what-is-jail/
https://www.44bits.io/ko/post/change-root-directory-by-using-chroot
https://www.44bits.io/ko/keyword/linux-namespace
https://www.44bits.io/ko/post/is-docker-container-a-virtual-machine-or-a-process
'MLOps' 카테고리의 다른 글
[Tensorboard] Multi Process 에서 하나의 Tensorboard 에 접근할 때 문제점 (0) | 2021.10.29 |
---|---|
[유용한 블로그 포스팅 모음] (0) | 2021.10.29 |
[Github, MLOps] ML 모델 CI/CD 를 위한 Jenkins 테스트 자동화 [3] (0) | 2021.10.22 |
[Github, MLOps] ML 모델 CI/CD 를 위한 Jenkins 테스트 자동화 [2] (0) | 2021.10.15 |
[Github, MLOps] ML 모델 CI/CD 를 위한 Jenkins 테스트 자동화 [1] (0) | 2021.10.14 |