[Docker] Docker 입문
현업에서 개발을 하거나 프로젝트를 진행할 때 Docker 는 매우 자주 사용되고 있다. Docker 에 대해 간단하게 정리해보자.
가상화
- 일반적으로 개발할 때 서비스 운영에 사용하는 서버에 직접 들어가서 개발하지 않는다.
- 대부분 local 환경에서 개발하고, 완료되면 staging 서버, 잘 동작하면 production 서버에 순차적으로 배포한다.
- 즉 서비스에 사용하는 코드를 local → staging → production 서버로 옮기는 작업을 한다.
- 개발을 진행한 local 환경과 production 서버 환경이 다른 경우가 있을 수 있다. 즉 local 환경은 윈도우인데 서버는 리눅스거나, 혹은 local 은 Mac OS 인데 서버는 리눅스 우분투인 경우가 있을 수 있다.
-
이러한 경우 OS 가 다르기 때문에 라이브러리, Python 등을 설치할 때 다르게 진행해야 한다.
- 또한 로컬 환경과 서버 환경이 같은 OS 를 사용하더라도, 환경변수가 꼬이는 등 서버에서 올바르게 작동하지 않을 수 있다.
-
예를 들어 local 의 환경 변수와 production 서버의 환경 변수(Env)가 다르게 설정되어 있거나, production 서버의 사용자 그룹 등 Permission 이 문제될 수 있다.
- 따라서 다양한 설정을 README.md 등에 기록하고, 항상 실행하도록 하는 방법을 많이 썼다. 그러나 사람이 진행하는 일이었기 때문에 Human Error 가 발생할 수 있었고, 매번 이런 작업을 해야 하는 과정이 소요가 컸다.
- 예를 들어 jupyter notebook 서버를 만들기 위해 클라우드에서 인스턴스로 접속해서 필요한 패키지 설치하는 과정을 계속 거쳐야 하는 것이다.
- 이 때 운영하고 있는 서버가 100 대 라면, 특정 서버의 업데이트가 진행 되었다면(윈도우, 스마트폰 OS 자동 업데이트 실행 등) 나머지 서버에도 모두 접속해서 업데이트 해주는 것이 필요하다.
- 이러한 과정은 매우 번거로운데, “서버 환경까지도 모두 한번에 소프트웨어화 할 수 없을까?” 라는 고민을 해결하기 위해 나온 개념이 “가상화” 다.
- 엄밀한 의미의 가상화는 하드웨어 가상화(ex. 가상메모리) 등 더 넓은 개념이 있지만, 여기서는 소프트웨어 가상화로 한정한다.
- 가상화는 Research / Production 환경에서 공통적으로 사용하는 일종의 템플릿으로, 특정 소프트웨어 환경을 만들고, local / production 서버에서 그대로 활용할 수 있다.
- 개발(local)과 운영(production) 서버의 환경 불일치가 해소된다.
- 어느 환경에서나 동일한 환경으로 프로그램을 실행할 수 있다.
- 개발 외에 다른 용도로 사용하고 싶을 때도 동일한 환경을 쓸 수 있다.
VM(Virtual Machine)
- 아래에서 볼 Docker 의 등장 전에는 가상화 기술로 주로 VM(Virtual Machine)을 사용했다.
-
VM 은 호스트 머신이라고 하는 실제 물리적인 컴퓨터 위에, OS 를 포함한 가상화 소프트웨어를 두는 방식이다.
- 위 그림과 같이 VM 은 호스트 OS 가 있음에도 추가적인 OS 를 설치하기 때문에 무거운 단점이 있다.
- GCP 의 Compute Engine 또는 AWS EC2 가 이러한 VM 개념을 활용한다. 클라우드 회사에서 미리 만든 이미지를 바탕으로, 컴퓨팅 서비스를 통해 사용자에게 동일한 컴퓨팅 환경을 제공하는 것이다.
Container
- OS 위에 또 다른 OS 를 하나 더 설치하는 점에서 VM 은 굉장히 리소스를 많이 사용한다. 이에 대한 해결책으로 Container 개념이 등장했다.
- Container 는 VM 의 무거움을 크게 덜어주면서, 가상화를 좀 더 경량화된 프로세스의 개념으로 만든 기술이다. 이 기술의 등장으로 이전보다 빠르고 가볍게 가상화를 구현할 수 있다.
-
Container 는 아래 그림과 같이 OS 가 하나만 있으면 된다.
Docker
- Container 기술을 쉽게 사용할 수 있도록 나온 도구가 바로 Docker 다. Container 에 기반한 개발과 운영을 매우 빠르게 확장할 수 있다.
- Docker image 를 만들어 두고, 재부팅하면 Docker image 의 상태로 실행해서 container 를 띄우게 된다.
- PC 방에서 특정 게임이나 특정 프로그램을 깔아도, 재부팅하면 항상 PC 방에서 저장해둔 형태로 복구되는 개념과 비슷하다.
-
아래 그림과 같이 하나의 이미지(Image)를 만들어서 여러 Container 로 띄울 수 있다.
- Docker Image 는 Container 를 실행할 때 사용할 수 있는 “템플릿” 이다. Read Only 로, 하나의 이미지를 만든 후에는 새로운 이미지를 만들어야 한다.
- Docker Container 는 Docker Image 를 활용해 실행된 인스턴스다. Write 가 가능하고, 하나의 Image 로 여러 개의 컨테이너를 띄울 수 있다.
- 또한 Docker 로 다른 사람이 만든 소프트웨어를 가져와서 바로 사용할 수 있다. 즉 다른 사람이 만든 소프트웨어가 Docker Image 이고, OS 와 설정을 포함한 실행환경이 된다.
- 만들어진 이미지를 가져와서 실행할 때, Container 를 띄운다라고 표현한다.
- Docker 로 할 수 있는 일은 아래와 같다.
- 자신만의 이미지를 만들고 공유할 수 있다. 원격 저장소에 저장하면 Window, Linux, Mac OS 어디서나 사용할 수 있다.
- 회사에서 서비스를 배포할 때는 원격 저장소(Container Registry)에 이미지를 업로드하고, 서버에서 그 이미지를 받아서 실행하는 식으로 진행한다.
-
대표적인 컨테이너 원격 저장소는 아래와 같다. 클라우드 서비스 마다 원격 저장소가 있다.
Docker 실행
- 먼저 Docker 공식 홈페이지에서 자신의 운영체제에 맞는 Docker Desktop 을 설치한다. 설치 후 터미널 상에서
docker
커맨드가 동작하는지를 확인한다. docker pull “이미지 이름:태그”
는 필요한 이미지를 다운로드 하는 명령어다.docker pull mysql:8
을 통해 이미지를 다운받는다. 이 이미지를 활용해서 컨테이너를 띄운다.
docker images
는 다운 받은 Image 의 List 를 보여준다.docker run “이미지 이름:태그”
와 같이 실행하여 이미지를 기반으로 컨테이너를 생성한다. 구체적인 사례를 보자.docker run —name mysql-tutorial -e MYSQL_ROOT_PASSWORD=1234 -d -p 3306:3306 mysql:8
와 같이 실행하게 되는데, 이 명령어에 사용된 옵션들은 아래와 같다.—name mysql-tutorial
: 컨테이너 이름. 지정하지 않으면 랜덤으로 생성한다.-e MYSQL_ROOT_PASSWARD=1234
: 환경변수 설정. 사용하는 이미지에 따라 이 설정이 다르다. 보통 mysql 에 접속할 때 패스워드가 필요한데 이를 1234 로 하겠다는 것을 의미한다.- mysql 은 환경변수를 통해 root 계정의 비밀번호를 설정한다.
-d
: 데몬 (백그라운드) 모드. 컨테이너를 백그라운드 형태로 실행한다.- 이 설정을 하지 않으면, 현재 실행하는 셀 위에서 컨테이너가 실행된다.
- 이 설정을 하게 되면, 컨테이너가 띄워졌다고만 보여주고, 다른 명령어를 실행 가능하다.
- 컨테이너의 로그를 바로 볼 수 있으나, 컨테이너를 나가면 실행이 종료된다. 따라서 컨테이너의 활동을 계속 살리고 싶을 때
-d
옵션을 줄 수 있다.
-p 3306:3306
: 포트 지정.로컬 호스트 포트:컨테이너 포트
의 형태다. 해당 예시는 로컬 포트 3306 으로 접근 시 컨테이터 포트 3306 으로 연결되도록 설정된다.- mysql 은 기본적으로 3306 포트를 통해서 통신한다.
- 로컬 호스트는 내가 현재 사용하고 있는 컴퓨터이고, 컨테이너는 컨테이너 이미지 내부라고 생각할 수 있다.
- 로컬 호스트의 3000 번 포트와 컨테이너의 3000 번 포트를 연결하면, 로컬의 3000 번 포트가 컨테이너의 3000 번 포트와 동일하다고 지정해주는 것이다. 즉 누군가 로컬 3000 번 포트로 들어가면 컨테이너 안으로 접근하게 된다.
- 일반적으로 같은 포트 번호를 주는 것이 좋다.
docker ps
로 실행한 컨테이너를 확인할 수 있다.- 만약
-d
(백그라운드 모드) 로 실행하면 우리가 실행하는 쉘에서는 별도로 메시지가 나오지 않는다. 따라서 컨테이너에 진입, 즉 컨테이너 내부의 쉘에서 접속을 하기 위해서docker exec -it “컨테이너 이름(혹은 ID)” /bin/bash
명령어를 사용한다.- 즉 MySQL 이 실행되고 있는지 확인하기 위해 컨테이너에 진입하는 명령어다. GCP 의 Compute Engine 에서 SSH 로 접속하는 것과 유사하다.
-it
옵션과/bin/bash
를 명시해줘야 한다.-i
는 컨테이너에서 stdin 을 열어두는, 즉 터미널(키보드)을 연결하는 interactive 를 의미하고,-t
는 OS 의 CLI 에 접속할 수 있게 해주는 가상터미널(tty) 할당을 의미한다.- 즉
-it /bin/bash
는 해당 컨테이너 안으로 bash shell 을 통해 들어가서 명령어를 직접 입력할 수 있는 상태가 되는 것이다.
-
이후
mysql -u root -p
을 입력하여 MySQL 프로세스로 들어가면 MySQL 쉘 화면이 보이게 된다. 아래의 그림이 바로 컨테이너 안으로 들어가서 mysql 을 실행하고 있는 모습이다. docker stop “컨테이너 이름(ID)”
은 실행중인 컨테이너를 중지한다.docker ps -a
docker ps
는 실행 중인 컨테이너 목록만 보여주기 때문에, 작동을 멈춘 컨테이너는docker ps -a
(all) 명령어로만 확인할 수 있다.
docker rm “컨테이너 이름(ID)”
- 멈춘 컨테이너를 삭제한다. 멈춘 컨테이너만 삭제할 수 있지만
docker rm “컨테이너 이름(ID)” -f
로 실행 중인 컨테이너도 삭제 가능하다.-f
옵션은 강제로 실행한다는 의미다.
- 멈춘 컨테이너를 삭제한다. 멈춘 컨테이너만 삭제할 수 있지만
Volume Mount
docker run
명령어를 실행할 때 파일을 공유하는 방법으로 Volume Mount 가 있다.- Docker Container 내부는 특별한 설정이 없으면 컨테이너를 삭제할 때 파일이 사라지게 된다. 즉 Host 와 Container 간의 파일 공유가 되지 않는다. 컨테이너에서 호스트로 연결이 안되는 것이다.
- 만약 파일을 유지하고 싶다면 Host(자신의 컴퓨터)와 Container 의 저장소를 공유해야 한다. 이를 통해 컨테이너에서 작업하고 삭제되더라도 호스트에 저장되도록 할 수 있다.
- Volume Mount 를 진행하면 Host 와 Container 의 폴더가 공유되고, 싱크된다.
- 이를 위해서
-v
옵션을 사용하며,-p
(Port) 처럼 사용한다. ex.-v Host_Folder:Container_Folder
- 예를 들어
docker run -it -p 8888:8888 -v /some/host/folder/for/work:/home/jovyan/workspace jupyter/minimal-notebook
와 같이:
기준 좌우의 두 폴더를 싱크할 수 있다. 일반적으로 오른쪽이 컨테이너 내 폴더다.
- Dockerhub 에서 공개된 모든 이미지를 다운 받을 수 있다. 웬만한 오픈소스들이 공개되어 있고, 우리는 필요한 이미지를 찾아 실행하기만 하면 된다.
Docker Image 생성
- pytorch example 코드를 실행하는 Docker Image 를 생성해보자.
- Docker Image 를 만들기 위해서
Dockerfile
이라는 파일을 만들어 작성한다.Dockerfile
은 Docker Image 를 빌드하기 위한 정보가 담긴다. -
Dockerfile
내 한 줄을 layer 라고 한다.\
는 줄바꿈으로, 하나의 layer 로 볼 수 있다. 따라서 아래의RUN
은 하나의 layer 다.FROM pytorch/pytorch:1.13.1-cuda11.6-cudnn8-runtime COPY . /app WORKDIR /app ENV PYTHONPATH=/app ENV PYTHONUNBUFFERED=1 RUN pip install pip==23.0.1 && \ pip install poetry==1.2.1 && \ poetry export -o reuqirements.txt && \ pip install -r requirements.txt CMD ["python", "main.py"]
- layer 는 한 줄마다 용량을 차지하고, layer 가 변할 때마다 빌드를 다시 해서 용량 이슈가 있다.
FROM “이미지 이름:태그”
FROM pytorch/pytorch:1.13.1-cuda11.6-cudnn8-runtime
로 이미지 빌드에 사용할 베이스 이미지를 지정한다.- 이 이미지(템플릿)을 써서 만들 것임을 명시하게 되고, 베이스 이미지는 일반적으로 이미 만들어진 이미지다.
- 보통 처음부터 만들지 않고, 이미 공개된 이미지를 기반으로 새로운 설정을 추가한다.
COPY “로컬 디렉토리(파일)” “컨테이너 내 디렉토리(파일)”
- 컨테이너는 자체적인 파일 시스템과 디렉토리를 가진다.
COPY
명령어는 Dockerfile 이 존재하는 경로 기준, 로컬 디렉토리를 컨테이너 내부의(자체 파일 시스템을 가진) 디렉토리로 복사하는 명령어다. COPY . /app
코드는 로컬의 프로젝트 최상위(.
)에 존재하는 모든 파일을 컨테이너 내부의/app
디렉토리로 복사하는 것을 의미한다.- 파일을 컨테이너에서 사용하려면 COPY 명령어로 반드시 복사해야 한다.
- 컨테이너는 자체적인 파일 시스템과 디렉토리를 가진다.
WORKDIR “컨테이너 내 디렉토리”
- Dockerfile 의
RUN
,CMD
,ENTRYPOINT
등의 명렁어를 실행할 컨테이너 경로를 지정한다. - 즉
WORKDIR /app
라인 뒤에 등장하는RUN
,CMD
는 컨테이너 내부의/app
에서 실행하게 된다. 따라서 한 번 지정해주고 넘어가야 한다.
- Dockerfile 의
ENV “환경변수 이름=값”
ENV PYTHONPATH=/app
와ENV PYTHONUNBUFFERED=1
는 컨테이너 내 환경변수를 지정하는 명령어다. Python 애플리케이션의 경우 통상 이 두 환경변수 값을 지정한다.
RUN “실행할 리눅스 명령어”
- 컨테이너 내에서 리눅스 명령어를 실행하는 명령어다.
- 위 Dockerfile 예시인
RUN pip install pip==23.0.1 && \ ...
의 경우pip install pip==23.0.1 ...
부터-r requirements.txt
까지의 명령어를 실행한다. - 한번에 실행할 명령어가 여러 개인 경우
&& \
로 이어준다. - 이전 라인에서 COPY 와 WORKDIR 이 실행되었기 때문에, 컨테이너 내에서
requirements.txt
이 존재하고(복사했기 때문), 이를pip install -r
명령어로 실행하여 필요한 library 를 설치할 수 있다.
CMD [”실행할 명령어”, “인자”, …]
- command 의 약자로,
docker run
명령어를 통해 이 이미지를 기반으로 컨테이너를 만들게 되는 그 시점에 실행할 명령어다. CMD ["python", "main.py"]
를 통해서 이 이미지는 실행되는 즉시python main.py
를 실행하게 된다.- CMD 는 CMD 는 띄어쓰기를 사용하지 않고
[ ]
로 쓰는 것이 특징이다. - 이처럼 CMD 는 이미지에서 컨테이너를 시작할 때 실행될 기본 명령어를 지정하는 데 사용된다. 이 명령은 Dockerfile 내에서 한 번만 사용되며, 만약 여러 개가 지정된다면 마지막 CMD 만 유효하게 된다.
- command 의 약자로,
- Docker File 을 완성한 후에는 이미지를 빌드해야 한다.
docker build -t <빌드할 이미지 이름:태그 이름> “Dokcerfile이 위치한 경로”
- ex.
docker build -t 02-docker:latest .
- 이미지를 생성하는 것을 빌드라고 표현한다.
- 해당 이미지 빌드 명령어에서
.
은 현재 폴더에 Dockerfile 이 있음을 의미한다. -t
옵션은“이미지 이름:태그”
옵션으로, 이미지 이름과 태그를 함께 지정할 수 있다. 태그는 미 지정시 latest 로 채워지게 된다.
- 빌드를 마치면 layer 가 push 되면서 진행과정이 있고, 이를 마치게 되면
docker images
로 방금 빌드한 이미지를 확인할 수 있다. - 이제
docker run “이미지 이름:태그”
로 방금 만든 이미지를 실행하여 컨테이너를 생성한다. 태그가 “latest” 인 경우 생략 가능하다. - 위 예제에서는 CMD 가 있었기 때문에
python main.py
를 실행하게 된다. - 그 외 Dockerfile 에서 사용하는 layer 명령어는 아래와 같다.
EXPOSE
는 컨테이너 외부에 노출할 포트를 지정한다. 로컬 호스트와 컨테이너가 통신하기 위해3000:3000
이런 방식으로 포트를 연결해주었는데, 이것이EXPOSE
명령어에서 하는 것이다.ENTRYPOINT
는 이미지를 컨테이너로 띄울 때 항상 실행하는 커맨드다.CMD
와ENTRYPOINT
는 컨테이너가 만들어질 때 실행되는 명령어를 지정할 때 활용하지만,CMD
는 실행 시점에 오버라이딩이 가능하여 인자를 바꿀 수 있는 등 자유도가 높다.- 반면에
ENTRYPOINT
는 오버라이딩이 어렵지만 CMD 와 같이 쓰여 꼭 실행할 명령어를 지정하는데 쓰이고, 보안적인 면에서 더 안전하다고 한다.
- 이렇게 만든 이미지를 인터넷에 업로드할 수 있다. 대표적으로 이미지 저장소인 Container Registry 에
Docker Image Push
를 통해 업로드하게 된다.- 그러면 Container Registry 에서 Docker Image 를 사용할 수 있다.
- 대표적인 Container Registry 에는 Dockerhub, GCP GCR, AWS ECR 등이 있다.
- Registry 에 Docker Image 를 push 하기 위해서 Tag 를 설정한다. 즉 올릴 이미지에 태그를 붙이는 것이다.
docker tag “기존 이미지:태그” “새 이미지 이름:태그”
- dockerhub 에 올릴 이미지 이름은
내 계정 ID/이미지 이름
형태여야 한다. - ex.
docker tag 02-docker:latest bkkhyunn/02-docker:latest
docker push “이미지 이름:태그”
를 통해 우리가 지정한 layer 가 push 된다. 이 작업이 완료되면 dockerhub 에서 푸시된 이미지를 확인할 수 있다.- Registry 에 push 로 올린 이미지는,
docker pull
명령어로 어디서든 다운로드 받을 수 있다. - GCP 의 GCR 에 올렸다면, 제공해주는 URL 이 있다.
- Registry 에 push 로 올린 이미지는,
- 즉 흐름은 Build $\rightarrow$ Tag $\rightarrow$ Push 다.
댓글 남기기