도커

이상훈
상훈 Devlog
Published in
10 min readApr 28, 2024

개요

컨테이너

컨테이너는 리눅스 커널에서 제공하는 chroot, cgroup, namespace 등을 활용하여 애플리케이션 코드, 라이브러리, 환경변수 등 실행에 필요한 파일을 패키징하고 프로세스, 네트워크, 마운트 등을 호스트와 격리되어 실행됩니다. 또한 CPU, 메모리, 디스크 I/O 등 컨테이너 단위로 자원을 별도로 할당 받아 실행할 수도 있습니다.

도커

이러한 컨테이너를 생성하고 관리할 수 있는 컨테이너 런타임이 바로 도커입니다. 느슨하게 격리된 환경에서 애플리케이션을 패키징하고 실행할 수 있게하는 오픈 플랫폼입니다.

VirtualBox나 VMware에서 사용하던 가상머신은 Hypervisor 위에 게스트 운영체제를 별도로 가지는 무겁고 의존적인 애플리케이션을 구동하게 됩니다. 그에 비해 도커는 리눅스 커널 자체 기능을 사용하여 격리 환경을 만들어 성능 손실도 매우 적고 가볍습니다. 호스트 운영체제에 의존도 하지 않기 때문에 빠르게 배포하고 스케일 아웃이 가능합니다.

가상머신 VS 도커 엔진

Getting Started

애플리케이션 컨테이너화

먼저 아래 링크에서 각 환경에 맞는 최신 버전의 도커를 설치합니다. 이 글에서는 macOS 기준으로 작성되었습니다.

깃을 통해 소스코드를 로컬 환경에 복제합니다.

git clone https://github.com/docker/getting-started-app.git

이 애플리케이션은 프론트엔드는 리액트 백엔드는 노드로 구성되어 있으며 mysql 또는 sqlite를 데이터베이스로 사용하고 있는 TO-DO 서비스입니다.

소스코드를 복제하였으면 이 애플리케이션을 이미지로 빌드해야 합니다. 프로젝트 최상단 경로에 Dockerfile을 작성합니다.

cd /path/to/getting-started-app
touch Dockerfile

#Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
EXPOSE 3000

Dockerfile에서 사용되는 키워드는 다음과 같습니다.

  • FROM: 컨테이너의 베이스 이미지(운영환경)을 지정합니다.
  • RUN: 컨테이너 빌드를 위해 베이스 이미지에서 실행할 커맨드를 작성합니다.
  • COPY: 컨테이너 빌드 시 호스트 파일을 컨테이너에 복사합니다.
  • ADD: COPY와 동일하게 컨테이너에 파일을 복사하는 기능이지만 tar 파일을 풀어서 복사하거나 ftp url을 통해 파일을 다운받아 복사하는 등추가적인 기능이 가능하다.
  • WORKDIR: 컨테이너 빌드 시 명령이 실행될 작업 디렉토리를 설정합니다.
  • ENV: 환경 변수를 지정합니다.
  • USER: 명령 및 컨테이너 실행 시 적용할 유저 설정
  • VOLUME: 파일 또는 디렉토리를 컨테이너 디렉토리로 마운트합니다.
  • EXPOSE: 컨테이너 동작 시 외부에서 사용할 포트를 지정합니다.
  • CMD: 컨테이너 동작 시 자동으로 실행할 서비스나 스크립트를 지정합니다.
  • ENTRYPOINT: CMD와 함께 사용하면서 command 지정 시 사용합니다.

아래 명령어로 도커 이미지를 빌드합니다.

docker build -t digoro/todo .

도커 이미지를 빌드 명령을 수행하면 Dockerfile에서 작성한대로 레이어를 다운로드 받습니다. 먼저 로컬 환경에 해당 레이어를 조회 후 있으면 그대로 사용하고 없으면 Docker Hub에서 검색 후 다운로드 받습니다. -t 옵션으로 이미지에 digoro/todo라는 태그를 지정합니다. (digoro는 작성자 Docker Hub의 계정이름 입니다.)

이제 생성한 이미지를 실행합니다.

docker run -d -p 3000:3000 getting-started

-d 옵션은 detach 약어로 백그라운드에서 컨테이너를 실행하게 합니다. -p 옵션은 호스트와 컨테이너 간의 포트포워딩을 하게 합니다. localhost:3000으로 접속 하면 다음과 같은 TO-DO 애플리케이션을 확인할 수 있습니다.

이제 실행 중인 컨테이너를 조회해봅니다.

docker ps

컨테이너 아이디, 이미지, 커맨드, 상태, 포트 등 컨테이너의 정보를 확인할 수 있습니다.

애플리케이션 업데이트

TO-DO 애플리케이션에 수정이 있다고 가정해보겠습니다. 먼저 소스 코드를 업데이트합니다. 프로젝트 src/static/js/app.js 경로에서 메인 화면에 뜨는 문구를 수정해보겠습니다.

소스 코드가 수정되었으니 다시 이미지를 빌드하고 실행합니다.

docker build -t digoro/todo .
docker run -d -p 3000:3000 digoro/todo

docker: Error response from daemon: driver failed programming external connectivity on endpoint nifty_knuth (f11ba139404354de4a735fcdf876f58b030f5b1b99c35e2687609bc09f9de418): Bind for 0.0.0.0:3000 failed: port is already allocated.

이전에 실행했던 컨테이너가 3000 포트를 사용하고 있기 때문에 오류가 발생하게 됩니다. 기존 컨테이너를 제거해야 합니다.

docker ps #컨테이너 아이디 확인
docker stop [컨테이너 아이디] #컨테이너 중단
docker rm [컨테이너 아이디] #컨테이너 삭제

docker rm -f [컨테이너 아이디] #-f(force)옵션으로 컨테이너 강제로 삭제

컨테이너가 삭제 되었다면 다시 실행합니다.

docker run -d -p 3000:3000 digoro/todo

localhost:3000에 접속하면 새롭게 바뀐 문구를 확인할 수 있습니다.

애플리케이션 공유

도커 이미지를 만들었으므로 컨테이너 레지스트리에 공유할 수 있습니다. 대표적인 public 레지스트리는 다음과 같습니다.

물론 private 하게 컨테이너 레지스트리도 구축할 수 있습니다. 이 레지스트리 또한 컨테이너로 실행이 가능합니다. 이 글에서는 Docker Hub 기준으로 작성하였습니다.

먼저 Docker Hub에 가입과 로그인을 합니다. 그 후 push 명령으로 공유합니다.

docker push digoro/todo

Docker Hub의 계정 Repository 메뉴를 보면 다음과 같이 todo 컨테이너 이미지가 업로드된 것을 확인할 수 있다. 이 이미지는 누구나 조회, 다운로드 할 수 있게된다.

데이터베이스 유지

사실 todo 컨테이너를 종료한 후 다시 실행하면 저장했던 할일 목록이 모두 초기화 되어 있는 것을 확인할 수 있다. 데이터베이스에 저장한 데이터의 영속성이 제공되지 않는다. 한가지 예시로 컨테이너 공간 내에 파일을 생성하고 종료한 후 해당 파일이 존재하는지 확인해볼 수 있습니다.

alpine 컨테이너를 실행한 후 greeting.txt 파일을 생성하고 종료합니다.

docker run -it --name mytest alpine
/ # echo "hello" > greeting.txt
/ # exit

다시 alpine 컨테이너를 실행하고 ls 명령으로 greeting.txt 파일을 확인해보면 존재하지 않는 것을 확인하실 수 있습니다.

docker run alpine ls greeting.txt
ls: greeting.txt: No such file or directory

이를 해결하기 위해 컨테이너에 데이터가 저장될 볼륨을 생성하고 마운트해야합니다.

docker volume create todo-db
docker run -d -p 3000:3000 --mount type=volume,src=todo-db,target=/etc/todos digoro/todo

localhost:3000에 접속하여 todo 애플리케이션에서 2가지 할일을 작성 한 후 컨테이너를 종료합니다. 그 후 다시 컨테이너를 실행하면 할일 데이터가 초기화되지 않고 유지되고 있는 것을 확인하실 수 있습니다.

실제로 데이터가 어떤 경로에 저장되는지 확인할 수도 있습니다.

docker volume inspect todo-db
[
{
"CreatedAt": "2024-04-18T15:22:46Z",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
"Name": "todo-db",
"Options": null,
"Scope": "local"
}
]

컨테이너 리소스 관리

앞서 서두에 작성한대로 컨테이너별로 메모리, CPU, 디스크 I/O 자원을 할당할 수 있습니다. 기본적으로 컨테이너는 호스트 하드웨어 리소스의 사용에 제한을 받지는 않지만 어떤 컨테이너는 리소스를 별도로 제한해야하는 경우도 있습니다.

먼저 컨테이너의 메모리를 제한해보겠습니다. 도커에서는 메모리 제한을 할 수 있는 다양한 방법을 제공합니다.

  • --memory, -m: 컨테이너가 사용할 최대 메모리 양을 지정합니다.
  • --memory-swap: 컨테이너가 사용할 스왑 메모리 영역에 대해 설정합니다. 생략 시 메모리의 2배가 자동으로 설정됩니다.
  • --memory-reservation: --memory 값보다 적은 값으로 구성하는 소프트 제한 값을 설정합니다.
  • --oom-kill-disable: OOM Killer가 프로세스 kill을 하지 못하도록 보호합니다. (OOM Killer: Out Of Memory Killer, 메모리가 부족할 경우 해당 프로세스를 강제로 종료시킵니다.)

예를 들어 다음과 같이 명령을 하면 nginx 이미지를 실행할 때 메모리는 200MB를 제한하고 스왑 메모리는 300MB로 지정하게 됩니다. 이렇게 설정하면 200MB까지는 메모리 자원을 사용하고 그 이상이 되면 100MB까지 디스크 자원으로 사용하게 됩니다.

docker run -d -m 200m --memory-swap 300m nginx

--

--

이상훈
상훈 Devlog

Frontend Developer 😁😁 #angular #javascript #typescript #scala #node