Docker로 개발 환경 구축하기 (1)
현재 개발 동아리 팀에 다양한 OS로 인해 생기는 여러 문제들을 Docker로 힙하게 해결 하고자 Docker를 이용해 개발 환경을 구축 했다. 이후 동아리에서 Docker hands on 세션을 진행 하기 위해, 내용을 정리해 보려고 한다. 목표는 docker compose를 활용한 일반적인 백엔드 개발 환경을 구성해보는 것이고, 이번 편은 docker image까지 사용하는 방법을 확인해볼 예정이다.
Docker는 무엇일까?
도커는 2013년 솔로몬 하익스라는 사람이 최초로 오픈 소스로 공개했다고 한다. 도커의 역사나 기타 배경적 이야기는 제쳐두고, 도커의 기본 개념을 살펴보자.
도커는 컨테이너 기반 오픈 소스 가상화 플랫폼이다.
컨테이너 기반이라는 것은 컨테이너형 가상화 기술을 사용 한다는 뜻이다. 기존의 호스트 안에 게스트 OS를 가상화 하는 것은 운영 체제 가상화라고 하는데, 운영 체제 가상화를 위해서는 가상화 소프트웨어를 사용 해야만 하고 OS 전체를 가상화한다. 컨테이너 가상화 기술은 이와 비교했을 때 오버헤드가 작아 운영 환경에서도 사용 가능하고, 개발 환경에서도 사용 가능하다. 가벼움 뿐 아니라 도커는 실행 환경을 컨테이너로 추상화하고 동일한 인터페이스를 제공해 프로그램의 배포 및 관리를 단순하게 한다.
도커는 컨테이너형 가상화를 위한 상주 앱(도커 엔진), 이를 관리하기 위한 명령형 도구로 이루어져 있다.
아래 이미지에 나타난 것처럼 컨테이너를 올릴 도커 엔진과, 이들을 관리하기 위한 명령어들로 구성되어 있다는 것이다.
Docker는 왜 사용할까?
- 실행 환경이 변하지 않는다.
- 코드를 통해 실행 환경을 만들 수 있다.
- 실행 환경이 곧 앱이 되기 때문에 배포가 간단해진다. (컨테이너 자체를 바꿔서 버전을 업데이트 할 수 있다.)
도커를 왜 사용해야 하는가에 대한 글은 이 링크에서 확인하면 더 좋을 것 같다.
Docker는 어떻게 쓰는 걸까?
나는 이미 도커를 쓰겠다고 마음을 먹었으니 앞선 주제는 이 정도 선에서 마무리 짓고, 도커를 어떻게 쓸지를 정리하도록 하겠다. 도커는 기본적으로 이미지를 다루는 섹션과 컨테이너를 다루는 섹션으로 구분 지을 수 있다. 쉽게 말하자면 이미지는 컨테이너를 정의하는 것이라고 볼 수 있고, 이에 따라 컨테이너가 생성된다.
이제 본격적으로 이미지와 컨테이너를 다룰 건데, 이번 글에서는 먼저 이미지에 대한 내용을 공부하고, 다음 컨테이너를 다뤄 보도록 하겠다.
Docker Help와 Search
먼저 docker의 help 명령어와 search 명령어를 보고 넘어가자.
docker help
도커 명령어들은 하위 명령을 가지고 있는 박스처럼 구성되어 있어서, help 명령어로 내려가면서 명령들을 찾아볼 수 있다. 하위 명령을 찾는 방법은 다음처럼 어떤 명령어 뒤에 --help
를 붙여서 확인할 수 있다.
docker image --help
docker search
도커 허브에 등록된 이미지들을 찾아볼 수 있다. --limit
명령어로 검색 건수를 제한할 수도 있다.
docker search --limit 5 ubunut
(--limit
을 뒤에 붙여도 됨)
Docker 이미지
Docker 이미지는 컨테이너 실행에 필요한 파일과 설정 값 등을 포함하고 있는 것이다. 이미지는 Dockerfile을 통해서 빌드 할 수 있고, 빌드 된 이미지는 기본적으로 불변이다. 이미지는 Dockerfile이 변경되면 새롭게 빌드할 수 있고, 개인적으로는 이때 이미지는 변경된다기 보다는 대체된다는 느낌이다.
1 | Dockerfile -> Docker Image -> Docker Container |
Dockerfile은 우리가 필요한 이미지가 어떤 형태, 상태인지를 정의한 파일이다. 이 파일을 기반으로 이미지를 빌드할 수 있다. 여러 Instruction이 있는데 이후 정리해보도록 하고, 우선 가장 기본적인 Instruction을 정리해보자
Instructuon | 설명 |
---|---|
FROM | 도커 이미지의 바탕이 될 베이스 이미지를 지정한다. 이 이미지는 Docker hub라는 레지스트리에 공개된 것이어야 한다. |
RUN | 이미지를 실행할 때 컨테이너 안에서 실행할 명령어를 정의한다. |
COPY | 호스트의 파일 또는 디렉토리를 도커 컨테이너 안으로 복사한다. |
CMD | 도커를 실행할 때 컨테이너 안에서 실행할 명령을 정의한다. RUN과 다르게 RUN은 이미지를 빌드할 때 실행되고, CMD는 컨테이너를 실행하면 한 번만 실행된다. |
이제 지금 배운 걸로 docker image를 만들어보자. 아래와 같은 코드가 있고, Dependencies도 모두 설치 된 상태라고 가정하자.
1 | // app.js |
이런 자바스크립트 서버를 도커 위에서 동작하게 하려면 다음과 같은 Dockerfile이 필요하다.
1 | # Dockerfile |
위 Instruction을 확인해보면 위 Dockerfile은 node 최신 버전의 이미지를 베이스로, 현재 폴더를 도커 컨테이너의 현재 폴더에 복사 해 넣고, npm i를 수행한 상태의 컨테이너 이미지를 빌드하게 해준다. 이제 그럼 정말로 이미지를 만들어보자.
docker image build
docker image build
를 사용하면 도커 이미지가 빌드된다. 정확한 사용 방법은 아래와 같다.
docker image build <Dockerfile 위치>
docker image build .
명령어로 이미지를 빌드 한 다음 docker image ls
를 사용해서 빌드된 이미지를 확인할 수 있다. 이 명령어는 빌드된 이미지의 목록과 Dockerfile의 FROM
Instruction에 의해 pull 받아온 이미지들까지 보여주는 명령어이다.
1 | REPOSITORY TAG IMAGE ID CREATED SIZE |
node의 이미지와 다르게 방금 우리가 만든 이미지의 REPOSITORY와 TAG는 <none>
이다. 이미지의 이름과 태그라고 하는데 이를 지정해 주어야 나타난다. 빌드할 때 이미지에 이름과 태그를 붙이려면 -t
옵션을 붙여서 사용해야 한다.
docker image build -t 이름[:태그] <Dockerfile 위치>
태그는 없어도 되는데, 없는 경우 latest로 자동 설정 된다. 아래 명령어를 다시 실행한 다음 목록을 확인해 보면, none 이미지의 태그와 이름이 붙었다.
docker image build -t changhoi/hands-on:latest .
1 | REPOSITORY TAG IMAGE ID CREATED SIZE |
IMAGE ID를 통해서 <none>
과 changhoi/hands-on
이 같은 이미지라는 것을 알 수 있는데, Dockerfile의 변경점이 없어서 대체된 것으로 보인다. Dockerfile을 기반으로 빌드를 할 때 도커는 캐시를 사용한다. 예를 들어서 아무 변경 사항 없이 다시 docker image build .
명령어를 사용하면 아래처럼 콘솔에 찍히는 것을 볼 수 있다.
1 | Sending build context to Docker daemon 2.168MB |
캐시를 사용하는 것으로 인해서 원래는 <none>
이 생성되어야 하는데, 기존에 이름과 태그가 그대로 붙어 있는 것을 확인할 수 있다. 첫 번째 step은 FROM
Instruction을 실행하는데, 캐시를 사용한다는 말은 없지만, 지정한 이미지가 호스트에 남아있다면 그 이미지를 그대로 가져다 쓴다.
위 두 가지 상황(FROM
을 별도 pull없이 가져옴, 명령어를 캐시해서 사용함)을 캐시 없이 빌드 하도록 강제할 수 있는데 아래 옵션을 추가하면 된다.
docker image build --pull=true
:FROM
에 있는 베이스 이미지를 새로 pull 받도록 한다.docker image build --no-cache
: 다른 Instruction을 캐시 없이 진행한다.
아래 명령어를 다시 실행해보고 docker image ls
를 해 봤다.
docker image build -t changhoi/hands-on --no-cache --pull=true .
1 | REPOSITORY TAG IMAGE ID CREATED SIZE |
아까 이미지는 <none>
으로 바뀌고 새로운 latest
가 생긴 것을 확인할 수 있다. 이것은 이름:태그가 고유하기 때문이다. 이름은 중복 가능하지만, 같은 이름의 같은 태그는 존재하지 않는다.
docker image tag
<none>
이라고 붙은 이미지에 새롭게 태그를 붙여주자. 태그 명령어는 아래와 같이 사용할 수 있다.
docker image tag 원래이름:태그 새로운이름:태그
docker image tag 구분가능한IMAGEID 새로운이름:태그
구분 가능한 IMAGEID라는 것은 ID를 모두 치지 않더라도 유일해질 정도만 쳐도 괜찮다는 뜻이다. 아래 명령어로 태그를 붙인 다음, 목록을 확인해 보면 구분 가능한 ID에 새롭게 이름과 태그가 붙는 것을 확인할 수 있다.
docker image tag 14c changhoi/hands-on:old
1 | REPOSITORY TAG IMAGE ID CREATED SIZE |
docker image rm
만든 이미지는 당연히 지울 수도 있는데, 아래 명령어로 이미지를 삭제할 수 있다. 다만, 지우려는 이미지가 다른 이미지의 베이스 이미지면 안된다.
docker image rm changhoi/hands-on:old
위 명령어를 실행한 뒤 목록을 확인해 보면 삭제된 것을 알 수 있다.
1 | REPOSITORY TAG IMAGE ID CREATED SIZE |
ETC
docker image pull
: Docker hub에 있는 특정한 이미지를 Pull 받을 수 있다.
Appendix
Dockerfile Instruction
Instruction | 설명 |
---|---|
FROM | 도커 이미지의 바탕이 될 베이스 이미지를 지정한다. 이 이미지는 Docker hub라는 레지스트리에 공개된 것이어야 한다. |
RUN | 이미지를 실행할 때 컨테이너 안에서 실행할 명령어를 정의한다. |
COPY | 호스트의 파일 또는 디렉토리를 도커 컨테이너 안으로 복사한다. |
CMD | 도커를 실행할 때 컨테이너 안에서 실행할 명령을 정의한다. RUN과 다르게 RUN은 이미지를 빌드할 때 실행되고, CMD는 컨테이너를 실행하면 한 번만 실행된다. |
ADD | COPY + 압축 파일 해제, URL로부터 컨테이너 파일 및 디렉토리 추가 |
ARG | docker image build 실행할 때 사용하는 변수 |
ENTRYPOINT | 컨테이너를 실행 가능 파일로 사용할 때 정의하는 명령 |
ENV | 컨테이너 안에서 사용하는 환경 변수 |
EXPOSE | 컨테이너가 노출하는 포르 설정 |
HEALTHCHECK | 명령을 실행한 다음 결과를 헬스 체크에 사용한다 |
LABEL | 이미지에 추가되는 메타 데이터 |
ONBUILD | 컨테이너 안에서 실행되는 명령을 정의함. 이미지에서는 실행되지 않음 |
STOPSIGNAL | 컨테이너에 전달되면 컨테이너를 종료 |
USER | 컨테이너 사용자. USER Instruction 이후에 나오는 RUN Instruction도 해당 사용자 권한으로 실행된다. |
VOLUME | 호스트나 다른 컨테이너에서 볼륨을 마운트 |
WORKDIR | 컨테이너의 작업 디렉토리 |
Reference
- Book: 도커/쿠버네티스를 활용한 컨테이너 개발 실전 입문
- https://github.com/voyagerwoo/docker-hands-on
- https://www.44bits.io/ko/post/why-should-i-use-docker-container
- https://www.44bits.io/ko/post/almost-perfect-development-environment-with-docker-and-docker-compose?fbclid=IwAR3GPrNSySCj4k-4qu3pL-fRq29uBQ8RR6MSfviibHZnmYj-BQiV_G9e34U
Docker로 개발 환경 구축하기 (1)
https://changhoi.kim/posts/docker/docker-development-env-(1)/