티스토리 뷰
대부분의 사람들이 프로젝트를 진행하며 환경 설정, 아키텍처, 인프라와 관련된 문제와 부딪힐 때쯤
도커와 마주하지 않을까 싶다.
필자도 친구들과 프로젝트를 진행하기 위한 준비과정에서
모두가 각자 다른 운영체제를 사용했기에(맥, 윈도우, 리눅스)
운영체제와 관련 없이 어느 곳에서든 동일하게 동작할 수 있는 환경을 만들어야겠다는 생각에서 시작하여
결국 도커와 마주하게 됐다.
도커란 뭘까?
도커 공식사이트에서는 도커에 대해 이렇게 설명하고 있다.
간단하게 요약하면 도커는 인프라에서 분리하여 쉽게 개발, 배포, 실행할 수 있다고 한다.
사실 공식설명만 봐서는 처음 접하는 사람에겐 공식문서의 설명이 와닿을 수 없다고 생각한다.
위 설명으로 일단 도커의 장점은 알겠는데
도대체 어떠한 방식으로 인프라를 분리하는 걸까?
컨테이너
컨테이너라는 단어를 상상해 보면 필자 머릿속에서 가장 먼저 생각나는 이미지는
많은 컨테이너를 적재한 거대한 컨테이너선이 바다에서 이동하는 모습이다.
위 사진에 있는 컨테이너선에 적재된 컨테이너에는 무엇이 담겨있을까??
정확히는 모르겠지만 바다 건너 배송하기 위한 어떠한 것들이 담겨 패키지 된 상태이다.
도커 또한 컨테이너 기술을 사용하며 위 컨테이너선에 적재된 컨테이너와 개념적으론 같다.
'컨테이너'말 그대로 무언가가 담겨서 패키징 된 공간이다.
해당 공간에 우리는 이제 애플리케이션을 담을 것이다.
애플리케이션을 담는 것은 물론이고 실행시켜야 한다.
그렇기 위해서는 하나의 컴퓨터가 필요한데(IP, Disk...)
컨테이너는 애플리케이션을 실행시키기 위한 환경을 갖춘 하나의 독립적인 가상환경이라고 생각하면 된다.
말 그대로 내가 마음대로 환경설정한 또 다른 컴퓨터가 컨테이너가 되고
해당 컨테이너 안에는 애플리케이션과 애플리케이션을 실행하기 위한 환경이 갖춰져 있는 상태다.
그리고 그 컨테이너는 호스트의 자원(CPU, 메모리)을 받아 동작하게 된다.
※ 여담으로 실제 도커뜻은
"항만에서 일하는 부두 노동자"라는 뜻이다.
결론은 컨테이너선이 당신의 PC 호스트이고 당신이 적재할 컨테이너는 여러 가상환경이 된다.
그리고 그 컨테이너를 적재하는 건 항만에서 일하는 부두 노동자 "도커"가 도와주는 것이다.
컨테이너 VS 가상머신
아마 VirtualBox같은 가상머신을 사용하여 리눅스 환경을 구축해 본 사람들에겐 위 설명이 익숙했을 것이다.
하지만 또 하나의 의문이 든다.
위 설명만 봤을 때는 가상머신과 컨테이너는 동일한 게 아닐까 싶다.
독립적인 환경을 구성한다는 점에서 똑같지 않은가?
사실 독립적인 환경을 구성한다는 점에서는 동일하지만
큰 차이점이 존재한다.
위 사진을 보면 알 수 있지만 가상머신은 각 가상환경마다 운영체제가 필요하다.
하지만 컨테이너 쪽 사진을 보면 운영체제 위에 Container Engine이 존재한다.
실제로 도커 컨테이너 같은 경우는 컨테이너 생성 시 해당 컨테이너의 운영체제로 경량화된 linux를 사용한다.
그리고 그 위에 도커 엔진이 자리 잡게 된다.(위 그림처럼)
실제로 컨테이너가 생성될 때 호스트의 운영체제와 비교하여 다른 부분만 컨테이너에서 생성되고
나머지 부분들은 호스트의 운영체제를 공유한다.(읽기 전용으로)
그렇기 때문에 가상환경에 필요한 커널을 호스트 운영체제의 커널로 사용하게 됨으로써
자체적인 사이즈도 작아지고 속도면에서도 호스트의 커널을 사용함으로써
CPU 사용이나 메모리 접근면에서 훨씬 우수하다.
(★ 호스트의 운영체제를 공유하는 것 ★)
간단하게 말하면 컨테이너가 훨씬 가볍고 다루기가 용이하다고 할 수 있다.
그 뜻은 당신이 가진 컨테이너선(호스트)에 더 많은 가상환경(컨테이너)을 실행시키고
더 빠른 속도로 적재하며 즐길 수 있다는 걸 의미한다.
컨테이너 생성하고 실행해 보기
필자의 예제코드 저장소에서 guide-antoliny 저장소를 clone한 뒤
이번에 테스트할 폴더로 이동한다.
~ cd guide-antoliny/docker/ch01/
일단 가장 먼저 해당 폴더의 구조를 살펴보면
.
├── Dockerfile
├── main.py
└── requirements.txt
1 directory, 3 files
세 개의 파일이 존재하는 걸 확인할 수 있다.
그리고 Dockerfile이라는 누가 봐도 Docker와 관련된 파일이 보인다.
FROM python:3.9
WORKDIR /usr/app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
ENV FLASK_APP main
ENV FLASK_DEBUG 1
EXPOSE 5000
CMD ["python3", "-m" , "flask", "run", "--host=0.0.0.0"]
Dockerfile은 생성할 가상환경(컨테이너)이 어떠한 설정을 가질 것인지 정의하는 스크립트 파일인데
간단하게 자신이 실행할 애플리케이션의 환경을 갖추게 하는 명령어들의 집합이라고 생각하면 된다.
( ※ 도커 이미지와 관련된 설명은 다음 포스팅에서 자세히 다루겠다. )
이번에 테스트할 예제에 쓰인 위 Dockerfile은 Flask앱을 실행할 수 있도록 환경설정했다.
이제 Dockerfile을 빌드해서 이미지를 생성한 뒤 해당 이미지를 통해 컨테이너 가상환경을 생성해 보자.
~ docker image build -t docker-test .
참고로 맨 마지막에 있는 . 은 현재 위치한 경로에서 Dockerfile을 찾는다.
(★ 즉 위 명령어를 성공적으로 실행하기 위해 자신이 현재 위치한 PATH를 확인하기 바란다. ★)
(★ docker명령어를 사용하기 위해선 당연히 docker가 설치되어 있는 상태여야 하고 도커엔진 또한 켜져있어야 한다. ★)
(※ -t 옵션은 이미지에 이름을 부여한다.)
~ docker image ls
위 명령어를 통해 현재 다운로드된 이미지들을 확인해 보자
docker-test가 성공적으로 빌드된 걸 확인할 수 있다.
이제 docker-test이미지를 통해 컨테이너를 생성하고 실행해 볼 것이다.
~ docker container run --name flask-app -d -p 4000:5000 docker-test
~ docker ps
이번 예제에서는 -d 옵션을 사용하여 백그라운드로 컨테이너를 실행하였고
-p 옵션을 통해 컨테이너 포트를 외부로 공개했다.
docker container run 명령어가 잘 실행되어 생성된 컨테이너의 ID를 출력했고
(※ -d 옵션을 사용했기에 컨테이너의 ID를 출력했다.)
docker ps(현재 동작중인 컨테이너 목록) 명령어를 통해 생성한 컨테이너가 잘 동작하는 걸 확인할 수 있다.
이제 http://localhost:4000으로 이동해 보면
필자가 미리 만들어놓은 플라스크앱이 잘 동작하고 있다.
(main.py를 확인해 보자)
지금까지 도커파일을 빌드해서 도커 이미지로 만든 뒤
도커 이미지를 바탕으로 성공적으로 컨테이너를 생성해 보았다.
실제로 확인해 보자
이번에는 exec 명령어를 통해 이전에 실행한 컨테이너 flask-app 컨테이너 내부로 접근해 해당 컨테이너 환경을 살펴보겠다.
~ docker exec -it flask-app /bin/bash
exec명령어는 실행 중인 컨테이너 내부에 접근함으로써 내부에서 명령어를 사용할 수 있고 보통 디버깅 하는 용도로 쓰인다.
실제로 위 명령어를 입력하게 되면
[root@{컨테이너 ID}:{PATH}#] --> 컨테이너 내부 터미널에 접근했다.
이제 pip list를 통해 필자가 도커파일에서 설정한 requirements.txt파일에 적어진 모듈들이
컨테이너 환경에 잘 설치되어 있는지 확인해 보겠다.
~ pip list
필자가 생성한 컨테이너는 플라스크 앱을 실행하는 컨테이너이기 때문에
당연히 해당 컨테이너에는 플라스크가 설치되어 있어야 한다.
pip list를 통해 확인해 보면 컨테이너에 Flask가 잘 설치되어 있는 걸 확인할 수 있다.
(※ requirements.txt파일을 확인해 보자)
다음으로 필자가 위에서 가상머신과 컨테이너의 차이점에 대해 설명하면서
컨테이너는 호스트의 운영체제를 공유한다고 했는데
이와 관련해서 테스트를 진행해 보겠다.
테스트는 동일한 이미지를 사용하여(위에서 사용한 docker-test[flask app])
우분투, 윈도우, 맥 환경에서 각각 컨테이너를 실행해 보고 컨테이너 내부의 커널을 확인해 보겠다.
일단 가장 먼저 우분투부터 진행하면
필자의 우분투 환경에서 커널은 5.19.0-46-generic인걸 확인할 수 있다. 이제 우분투 환경에서 컨테이너를 생성해 보고
해당 컨테이너에서 커널을 출력해 보겠다.
컨테이너에서 동일한 명령어를 입력할 시 컨테이너도 5.19.0-46-generic으로 호스트와 동일한 커널을 사용하는 걸 확인할 수 있다.
이번에는 맥, 윈도로 동일한 테스트를 진행해 보았다.
(★ 맥 호스트 커널 --> Darwin Kernel ★)
(★ 맥에서 실행한 컨테이너 커널 --> 5.15.49-linuxkit-pr ★)
(윈도우 호스트 커널 --> NT Kernel)
(윈도우에서 실행한 컨테이너 커널 --> 5.15.90.1-microsoft-standrad-WSL2)
맥과 윈도우에서의 테스트는 이전 우분투테스트 결과와 차이점이 존재한다.
우분투는 호스트와 컨테이너의 커널이 같았지만
맥과 윈도우는 다른 걸 확인할 수 있다.
Docker는 리눅스 커널을 사용하여 컨테이너 간의 리소스를 관리하기 때문에
Docker 엔진은 리눅스 운영체제에서 실행된다.
그러나 Mac은 리눅스 커널이 없다.
(윈도우는 윈도우에서 리눅스를 사용할 수 있게 해주는 WSL2가 존재한다.)
그렇기 때문에 Mac환경에서는 위에서도 한 번 설명했듯이
경량화된 리눅스 가상 머신이 실행되고 그 위에 도커 엔진이 자리 잡게 된다.
결국 리눅스 커널이 없는 Mac같은 경우는 추가 추상화 계층이 있어야지 도커를 실행할 수 있다.
※ 그렇다면 맥에서 실행하는 도커는 한 차례 추가 가상화가 있기 때문에
맥에서의 도커 사용은 리눅스 기반 운영체제에 비해 성능이 떨어지나?
실제로 Mac같은 경우는 추가 가상화를 거치기 때문에 Linux보다 컨테이너 작업이 느린 건 맞지만
유의미한 차이를 나타내진 않는다고 한다.
(이러한 부분들에 대한 해결방법도 어느 정도 존재한다.)
※ 위 테스트에서 확인할 수 있듯이 맥에서 실행한 컨테이너의 커널은 Darwin이 아닌 linuxkit이다.
uname 명령어를 통해 출력된 커널 정보로 컨테이너가 호스트의 운영체제를 공유한다는 걸 확인했다.
도커 시작하기
위 테스트에서 각기 다른 운영체제에서 똑같은 도커파일을 빌드하고 생성된 이미지를 통해 컨테이너를 실행해 보았다.
놀랍게도 운영체제에 구애받지 않고 모든 환경에서 잘 동작한다는 것 또한 확인했다.
필자가 처음 도커를 사용했을 때 신비한 무언가와 마주한 듯한 느낌이 들었다.
그 신비로움이 이 글을 읽음으로써 도커를 처음 접하는 사람에게 도커의 첫인상으로 남았으면 좋겠다.