도커의 기본 사용 방법부터 최적화 방법까지 알아보자. (기본편)
대략 2022년 5월부터 지금까지 도커를 무척이나 많이 사용하고 있다.
그것도 그럴 것이 회사에서 Ops 업무를 수행하기 위해 Jupyterhub, Flyte 모두 기본적으로 ML 작업을 수행하는 환경을 보장해 주는 도커 이미지를 기반으로 인스턴스를 생성하고 있고, 작업자마다 필요한 모듈이나 환경이 제 각기 다르기 때문에 별도의 이미지를 생성해줘야하기 때문이다.
그 동안은 회사에서 사용해야만 하기 때문에 도커 명령어만 간단하게 익혀서 사용하는 단계였다면, 오늘은 도커의 기본 사용 방법부터 나름대로 알게 된 최적화 방법까지 글을 2편에 나누어 작성해보고자 한다.
1. 도커란?
도커라는 플랫폼에 대해 설명하기 위해 기본이 되어야하는 개념은 먼저 “컨테이너”라는 용어가 빠질 수 없을 것이다.
컨테이너란 소프트웨어 서비스를 실행하는 데 필요한 특정 버전의 프로그래밍 언어 런타임 및 라이브러리와 같은 종속 항목과 애플리케이션 코드를 함께 포함하는 경량화 된 패키지로, 보통 하나의 애플리케이션을 실행하기 위한 파일들이 포함되어 있다.
위의 정의 사항만으로 이해가 안된다면, 이해를 돕기 위해 아래 Dockerfile 예제 코드를 함께 보면서 이해하면 좋을 것 같다.
가장 상단에 컨테이너에 정의된 코드를 실행하기 위한 운영체제의 명칭과 버전을 명시해주고, 그 아래부터 해당 운영체제에서 사용하는 CLI를 사용해서 애플리케이션 실행을 위한 모듈들을 순차적으로 작성해주면 된다.
물론 이 때 컨테이너에 하나 이상의 애플리케이션 실행을 목적으로 여러 코드를 포함시킬 수 있겠지만, 생성된 도커 이미지의 용량이 무척 커지면 추후에 인스턴스를 띄울 때 시간이 많이 걸릴 수 있을 뿐더러 인스턴스에 부하가 오면 동시의 여러 애플리케이션에 영향을 주기 때문에 해당 방법은 추천하지 않는다.
따라서 도커라는 것은 기존에 존재했던 이 리눅스 Container를 활용함으로써
- 애플리케이션의 작업 환경의 보장
- 서비스를 개발하는 개발자 입장에서 동일한 도커 컨테이너를 사용하기 때문에 동일한 개발/운영 환경 보장
- 신속한 배포
- 적은 양의 하드웨어 용량 사용
을 등을 제공하는 오픈소스 프로젝트라고 볼 수 있다.
2. 도커 동작 원리
도커에서 컨테이너를 생성해주기 위해서는 먼저 도커를 사용해서 이미지(Image)라는 것을 생성해주는 작업을 거치게 된다.
앞서 얘기했던 컨테이너는 이미지(Image)를 실행한 상태로, 애플리케이션의 종속성이 포함된 모듈/코드 등을 함께 패키징
or 캡슐화
하여 격리된 공간에서 프로세스를 동작
시킨다.
도커에서 이 이미지를 사용하는 것이 장단점이 있는데
- 이미지의 용량은 일반적으로 수백 MB ~ 수 GB 정도하는데, 해당 용량은 VM을 사용하는 것보다 리소스 사용 측면에서 훨씬 저렴하다.
- 한 번 생성된 이미지는 불변(immutable)한 상태로 사용된다. (즉, 생성한 이미지에서 오류가 있으면 이미지를 다시 빌드해야하고, 새로운 작업 단위가 필요할 때마다 이미지를 다시 만듦으로써 버전 관리를 할 수 있다.)
- 2번의 불변적 특징을 이용해서 하나의 이미지는 여러 컨테이너를 생성할 수 있고, 컨테이너가 삭제되더라도 이미지는 변하지 않고 그대로 남아 있다. 이는 곧 필요에 따라 얼마든지 한 번 만들어진 이미지를 통해 인스턴스를 쉽게 생성할 수 있다는 뜻이다.
- 도커 이미지들은 DockerHub, ECR(AWS Elastic Container Registry) 등을 통해 버전 관리 및 배포(push&pull)를 할 수 있다.
개인적으로 2번 사항이 장점이자 단점으로 나에게 제일 많이 부각되는데, 그 이유는 Flyte라는 ML Pipeline 플랫폼에서는 작업이 실행되는 단위(task라는 것을 사용한다.) 별로 컨테이너가 필요로 하기 때문이다.
이미지의 개념을 따라가면 도커의 원리와 관련한 설명을 읽으면 가장 많이 보이는 아래 이미지를 이해하는 것이 한결 수월해진다.
따라서 도커는 개별 컨테이너가 Host 운영체제를 공유하면서 동작하는 프로세스로써 실행되며, 개별 컨테이너끼리는 서로에게 영향을 주지 않고 독립적인 상태로 실행되는 것이다.
(프로세스와 스레드의 차이는 이 글에서 확인을 할 수 있다.)
3. 도커 명령어 정리
Dockerfile을 사용해 도커 이미지를 생성해주기 위해서는 Dockerfile 내부에서 정의하는 명령어를 사용해 빌드를 진행해야하는데, 이 때 주로 사용되는 Dockerfile 명령어는 다음과 같다.
(Docker CLI와 명칭을 구분해주기 위해 Dockerfile이라는 표현을 지속적으로 사용했다.)
- FROM : 생성할 이미지의 base가 될 이미지를 정의한다. FROM 명령어는 Dockerfile을 작성할 때 반드시 한 번 이상 정의를 해야하고 (사용 목적에 따라 2번 이상 정의하는 것도 가능), 여기에는 개인적으로 생성한 base-image를 호출하거나 ubuntu, centOs, python 등 잘 알려져 있는 base-image를 사용하는 것도 가능하다.
- RUN : 사실상 제일 많이 사용하게 될 Dockerfile 명령어로 이미지를 만들기 위해 컨테이너 내부에서 명령어를 실행한다. 예를 들면, FROM ubuntu:20.04를 정의 후, RUN apt-get update를 작성했다면, 우분투 이미지를 사용해 컨테이너 내부에서 운영체제에서 사용 가능한 패키지들과 그 버전에 대한 정보를 업데이트하라는 뜻이 된다.
- WORKDIR : 명령어를 실행할 디렉터리를 정의한다. 애플리케이션이 실행될 때 모듈이나 파일을 실행하게 될 작업 폴더를 지정할 수 있으며, RUN 명령어를 사용해서 cd 명령어를 입력하는 것과 같은 기능을 한다. 마찬가지로 컨테이너 내부에서 동작한다.
- ADD : Dockerfile이 실행되는 위치를 기준으로, 해당 서버 내부 경로에 있는 파일을 이미지에 추가해준다.
- EXPOSE : Dockerfile의 빌드로 생성된 이미지에서 외부로 노출할 포트를 설정한다. 별도로 설정하지 않는다면, 80번 포트가 기본으로 설정되어 있다. EXPOSE로 설정한 포트가 다른 곳에서 사용 중이면 바인딩되지 않는다.
- CMD : 컨테이너가 시작될 때마다 실행할 명령어를 설정하며, Dockerfile에서 여러 번 정의할 수 있지만, 마지막에 정의한 한 개만 실제로 실행된다. (사실 상 하나의 Dockerfile에서 한 번만 사용 가능)
- COPY : Dockerfile에 경로에 있는 로컬 파일들을 옮길 때 사용하는데, 해당 읽어들인 파일을 복사해서 이미지에 넣는다. ADD를 사용하면 외부 URL이나 tar 파일 추가 등이 가능한데 보통 로컬 파일 복사할 때는 COPY 사용이 조금 더 보편적으로 쓰이는 것 같다.
- ENTRYPOINT : 사용 방법은 CMD와 비슷한데, 차이가 존재한다. CMD의 경우 컨테이너를 실행할때 CLI로 인자값을 넘겨 주면, Dockerfile 에 지정된 CMD 값을 대신 하여 지정한 인자값으로 변경하여 실행되는데, 이와 반대로 ENTRYPOINT의 경우, CLI로 념겨 받은 인자와 상관없이 값이 변경되지 않는 커맨드가 실행된다.
그 외 커맨드로 ARG, MAINTAINER 등이 있지만, 해당 사항들은 dockerfile로 인자를 넘겨받거나 작성자를 정의해주기 위한 목적으로 부수적으로 쓰여서 별도로 언급하지는 않았다.
처음에는 실전 사용까지 한 편의 글에 모두 담을 생각이었으나,,, 실전 사용 방법에 대한 내용을 정리하다 보니 양이 생각보다 더 많다.
실전에서 사용하는 방법은 CS 기본 지식에 대한 내용들이 더 많이 포함되다보니 분량이 많아지는데, 2편으로 도커 기본기를 마무리한다는 느낌으로 접근해야겠다.
(Docker CLI도 정리하려고 했는데, 이건 분량이 더 많아서 pass)
Ryan