docker compose run 명령을 사용하여 일회성 작업 실행 방법

DockerBeginner
지금 연습하기

소개

이 랩에서는 docker compose run 명령을 효과적으로 사용하여 Docker Compose 서비스 내에서 일회성 작업을 실행하는 방법을 배우게 됩니다. 이는 전체 서비스 스택을 시작하지 않고도 관리 명령을 실행하거나, 디버깅하거나, 특정 작업을 수행하기 위한 강력한 기술입니다.

기본 서비스 명령 재정의, 상호 작용을 위한 서비스 포트 활성화, 수동 포트 매핑, 연결된 서비스를 시작하지 않고 명령 실행, 실행 후 컨테이너 자동 제거 등 다양한 시나리오를 살펴보겠습니다. 이 랩을 마치면 다양한 일회성 작업에 docker compose run을 능숙하게 사용할 수 있게 될 것입니다.

서비스 명령을 재정의하여 일회성 명령 실행

이 단계에서는 Docker 이미지 또는 Dockerfile 에 지정된 기본 명령을 재정의하여 Docker 컨테이너에서 일회성 명령을 실행하는 방법을 배우겠습니다. 이는 주 서비스를 시작하지 않고도 컨테이너 환경 내에서 관리 작업을 실행하거나, 디버깅하거나, 특정 스크립트를 실행하는 데 유용합니다.

먼저, 이 데모에 사용할 수 있는 간단한 Docker 이미지를 가져와 보겠습니다. ubuntu 이미지를 사용합니다.

docker pull ubuntu:latest

이미지가 가져와 다운로드되고 있음을 나타내는 출력을 볼 수 있습니다.

Using default tag: latest
latest: Pulling from library/ubuntu
...
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

이제 ubuntu 이미지를 기반으로 하는 컨테이너에서 일회성 명령을 실행해 보겠습니다. 이미지 이름과 실행하려는 명령과 함께 docker run 명령을 사용합니다. 예를 들어, 컨테이너의 루트 디렉토리 내용을 나열하기 위해 ls -l / 명령을 실행해 보겠습니다.

docker run ubuntu ls -l /

이 명령은 ubuntu 이미지에서 새 컨테이너를 생성하고, 내부에서 ls -l / 명령을 실행한 다음 종료합니다. 루트 디렉토리의 내용을 보여주는 다음과 유사한 출력을 볼 수 있습니다.

total 68
drwxr-xr-x   2 root root  4096 Oct 26 00:00 bin
drwxr-xr-x   2 root root  4096 Oct 26 00:00 boot
drwxr-xr-x   5 root root   360 Nov  1 00:00 dev
drwxr-xr-x  19 root root  4096 Nov  1 00:00 etc
drwxr-xr-x   2 root root  4096 Oct 26 00:00 home
drwxr-xr-x   7 root root  4096 Oct 26 00:00 lib
drwxr-xr-x   2 root root  4096 Oct 26 00:00 lib64
drwxr-xr-x   2 root root  4096 Oct 26 00:00 media
drwxr-xr-x   2 root root  4096 Oct 26 00:00 mnt
drwxr-xr-x   2 root root  4096 Oct 26 00:00 opt
drwxr-xr-x   2 root root  4096 Oct 04 14:00 proc
drwx------   2 root root  4096 Oct 26 00:00 root
drwxr-xr-x   2 root root  4096 Oct 26 00:00 run
drwxr-xr-x   2 root root  4096 Oct 26 00:00 sbin
drwxr-xr-x   2 root root  4096 Oct 26 00:00 srv
drwxr-xr-x   2 root root  4096 Oct 26 00:00 sys
drwxrwxrwt   2 root root  4096 Oct 26 00:00 tmp
drwxr-xr-x  11 root root  4096 Oct 26 00:00 usr
drwxr-xr-x  12 root root  4096 Oct 26 00:00 var

이 경우, ubuntu 이미지의 기본 명령은 일반적으로 /bin/bash와 같은 셸입니다. 이미지 이름 뒤에 ls -l /를 제공함으로써 Docker 에 기본 명령 대신 이 특정 명령을 실행하도록 지시하는 것입니다.

다른 명령을 시도해 보겠습니다. 예를 들어, 컨테이너 내부의 현재 작업 디렉토리를 출력하기 위해 pwd를 사용합니다.

docker run ubuntu pwd

출력은 /여야 하며, 이는 루트 디렉토리가 기본 작업 디렉토리임을 나타냅니다.

/

이것은 컨테이너에 대화식으로 들어가거나 이미지의 기본 명령을 수정할 필요 없이 컨테이너 내에서 임의의 명령을 쉽게 실행할 수 있는 방법을 보여줍니다.

서비스 포트 활성화 상태로 명령 실행

이 단계에서는 컨테이너 내부의 서비스가 노출하도록 구성된 포트를 활성화한 상태에서 Docker 컨테이너에서 명령을 실행하는 방법을 살펴보겠습니다. 이는 디버깅 또는 관리를 위해 임시 명령을 실행해야 하지만 컨테이너 외부에서 주 서비스에 계속 액세스하려는 경우에 유용합니다.

이 데모에서는 간단한 웹 서버 이미지를 사용합니다. 인기 있는 웹 서버인 nginx 이미지를 가져와 보겠습니다.

docker pull nginx:latest

이미지가 가져와지고 있음을 나타내는 출력을 볼 수 있습니다.

Using default tag: latest
latest: Pulling from library/nginx
...
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

기본 nginx 이미지는 컨테이너 내부의 포트 80 에서 수신 대기하도록 구성되어 있습니다. 이 포트를 호스트 머신에서 액세스할 수 있도록 하려면 docker run 명령과 함께 -p 플래그를 사용하여 호스트 포트를 컨테이너 포트에 매핑해야 합니다. 호스트 포트 8080 을 컨테이너 포트 80 에 매핑해 보겠습니다.

이제 기본 Nginx 서비스를 실행하는 대신, 포트 매핑을 활성화한 상태로 echo "Hello from the container"와 같은 간단한 명령을 실행해 보겠습니다.

docker run -p 8080:80 nginx echo "Hello from the container"

이것이 Nginx 서버를 시작한 다음 "Hello from the container"를 출력할 것으로 예상할 수 있습니다. 그러나 docker run에서 이미지 이름 뒤에 명령을 제공하면 해당 명령이 기본 명령을 대체합니다. 따라서 이 경우 컨테이너는 echo 명령을 실행한 다음 종료됩니다. 포트 매핑을 지정했음에도 불구하고 Nginx 서버는 시작되지 않습니다.

출력은 다음과 같습니다.

Hello from the container

웹 브라우저에서 또는 curl을 사용하여 http://localhost:8080에 액세스하려고 하면 Nginx 서버가 실행되고 있지 않기 때문에 연결이 거부됩니다.

curl http://localhost:8080

출력은 다음과 같습니다.

curl: (7) Failed to connect to localhost port 8080 after ... connection refused

이는 중요한 점을 보여줍니다. docker run <image> <command>로 기본 명령을 재정의하면 컨테이너는 제공된 명령만 실행하고 이미지가 실행하도록 설계된 서비스를 시작하지 않습니다. 따라서 포트 매핑이 구성되어 있더라도 해당 포트에서 수신 대기하는 서비스가 실행되고 있지 않으므로 활성화되지 않습니다.

서비스가 실행 중이고 해당 포트가 활성화된 상태에서 명령을 실행하려면 일반적으로 백그라운드에서 서비스를 시작한 다음 명령을 실행합니다. 그러나 docker run 명령은 단일 명령을 실행한 다음 종료하도록 설계되었습니다. 실행 중인 서비스와 함께 명령을 실행하는 효과를 얻으려면 일반적으로 이미 서비스를 실행 중인 컨테이너에서 docker exec를 사용합니다. docker exec는 다음 단계에서 살펴보겠습니다.

이 단계의 목적을 위해 핵심적인 내용은 docker run에 명령을 제공하면 기본 entrypoint/command가 재정의되므로, 포트 매핑이 지정되어 있더라도 기본적으로 실행하도록 구성된 서비스가 시작되지 않는다는 것을 이해하는 것입니다.

수동 포트 매핑으로 명령 실행

이 단계에서는 docker run을 사용하여 포트 매핑을 계속 탐색하고, 호스트 및 컨테이너 포트를 명시적으로 지정하는 방법에 중점을 둡니다. 이전 단계에서는 기본 명령을 재정의하면 서비스가 실행되지 않는다는 것을 보여주었지만, 서비스를 액세스 가능하게 하려는 경우 수동 포트 매핑을 이해하는 것이 중요합니다.

nginx 이미지를 계속 사용합니다. 다시 말씀드리지만, nginx 이미지는 컨테이너 내부의 포트 80 을 노출합니다. 호스트 머신에서 이 포트에 액세스하려면 -p 플래그를 사용하고 그 뒤에 host_port:container_port를 사용합니다.

nginx 컨테이너를 실행하고 호스트 포트 8081 을 컨테이너 포트 80 에 매핑해 보겠습니다. 이번에는 재정의 명령을 제공하지 않으므로 기본 Nginx 서비스가 시작됩니다. 또한 백그라운드에서 실행되도록 분리 모드 (-d) 로 실행합니다.

docker run -d -p 8081:80 nginx

컨테이너 ID 인 긴 문자열이 표시되어 컨테이너가 분리 모드로 시작되었음을 나타냅니다.

<container_id>

이제 컨테이너가 실행 중이고 포트가 매핑되었으므로 curl 또는 웹 브라우저를 사용하여 호스트 머신에서 Nginx 시작 페이지에 액세스할 수 있습니다.

curl http://localhost:8081

기본 Nginx 시작 페이지의 HTML 콘텐츠를 볼 수 있습니다. 이는 Nginx 서버가 컨테이너 내부에서 실행 중이며 포트 8081 을 통해 호스트 머신에서 액세스할 수 있음을 확인합니다.

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</body>
</html>

이는 수동 포트 매핑을 통해 컨테이너 내부의 특정 포트에서 실행되는 서비스에 액세스하는 데 사용되는 호스트 포트를 제어할 수 있음을 보여줍니다. 이는 호스트 머신에서 포트 충돌을 방지하고 컨테이너화된 서비스에 액세스할 수 있도록 하는 데 필수적입니다.

실행 중인 컨테이너를 중지하려면 컨테이너 ID 또는 이름 뒤에 docker stop 명령을 사용할 수 있습니다. docker ps를 실행하여 컨테이너 ID 를 찾을 수 있습니다.

docker ps

이렇게 하면 실행 중인 컨테이너 목록이 표시됩니다. 방금 시작한 nginx 컨테이너의 컨테이너 ID 를 찾습니다.

CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                  NAMES
<container_id>   nginx     "nginx -g 'daemon off"   About a minute ago   Up About a minute   0.0.0.0:8081->80/tcp   <container_name>

이제 ID 를 사용하여 컨테이너를 중지합니다. <container_id>를 출력의 실제 ID 로 바꿉니다.

docker stop <container_id>

컨테이너가 중지되었음을 확인하면서 컨테이너 ID 가 다시 인쇄됩니다.

<container_id>

컨테이너를 중지한 후 다시 http://localhost:8081에 액세스하려고 하면 연결이 거부됩니다.

연결된 서비스 시작 없이 명령 실행

이 단계에서는 다른 연결된 서비스를 시작하지 않고, 다중 컨테이너 애플리케이션의 일부인 Docker 컨테이너에서 명령을 실행하는 방법을 배우겠습니다. 이는 전체 애플리케이션 스택을 시작할 필요 없이 한 서비스에서 데이터베이스 마이그레이션, 설정 스크립트 또는 디버깅 명령을 실행하는 데 특히 유용합니다.

Docker Compose 는 다중 컨테이너 애플리케이션을 관리하기 위한 표준 도구이며 특정 서비스에서 일회성 명령을 실행하기 위한 기능이 있지만, 여기서는 기본 Docker 개념을 시연합니다. 이 환경에는 Docker Compose 가 미리 설치되어 있지 않으므로 네트워킹과 함께 docker run 명령을 사용하는 데 집중하겠습니다.

웹 애플리케이션과 데이터베이스의 두 컨테이너가 있는 간단한 시나리오를 시뮬레이션해 보겠습니다. 웹 애플리케이션을 나타내기 위해 일반적인 ubuntu 이미지를 사용하고 데이터베이스에 postgres 이미지를 사용합니다.

먼저, postgres 이미지를 가져옵니다.

docker pull postgres:latest

이미지가 가져와지고 있음을 나타내는 출력을 볼 수 있습니다.

Using default tag: latest
latest: Pulling from library/postgres
...
Status: Downloaded newer image for postgres:latest
docker.io/library/postgres:latest

이제 컨테이너가 이름으로 서로 통신할 수 있도록 Docker 네트워크를 생성해 보겠습니다.

docker network create my-app-network

네트워크 ID 가 출력되는 것을 볼 수 있습니다.

<network_id>

다음으로, postgres 컨테이너를 실행하고 네트워크에 연결해 보겠습니다. 또한 PostgreSQL 사용자의 비밀번호를 설정합니다.

docker run -d --network my-app-network --name my-database -e POSTGRES_PASSWORD=mypassword postgres

데이터베이스 컨테이너가 백그라운드에서 실행 중임을 나타내는 컨테이너 ID 가 출력되는 것을 볼 수 있습니다.

<container_id>

이제 "웹 애플리케이션" 컨테이너가 데이터베이스 마이그레이션 스크립트와 같이 데이터베이스와 상호 작용하는 명령을 실행해야 한다고 가정해 보겠습니다. 일반적으로 Docker Compose 를 사용하는 경우 웹 서비스에서 명령을 실행할 수 있으며 Docker Compose 가 네트워크 설정 및 연결을 처리합니다.

docker run만 사용하면 웹 애플리케이션 컨테이너를 실행하고 my-database에 연결하려고 하면 일반적으로 동일한 네트워크에 있어야 합니다.

동일한 네트워크에 연결된 ubuntu 컨테이너에서 데이터베이스와 상호 작용할 수 있는 명령을 시뮬레이션하여 명령을 실행해 보겠습니다. 데이터베이스 컨테이너를 이름 (my-database) 으로 ping 하려고 합니다.

docker run --network my-app-network ubuntu ping -c 4 my-database

이 명령은 다음을 수행합니다.

  1. ubuntu 이미지에서 새 컨테이너를 생성합니다.
  2. my-app-network에 연결합니다.
  3. 컨테이너 내에서 ping -c 4 my-database 명령을 실행합니다.

ubuntu 컨테이너가 my-database 컨테이너와 동일한 네트워크에 있으므로 my-database 이름을 데이터베이스 컨테이너의 IP 주소로 확인하고 ping 할 수 있습니다.

ping 요청 및 응답을 보여주는 출력을 볼 수 있습니다.

PING my-database (172.18.0.2) 56(84) bytes of data.
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=1 ttl=64 time=0.050 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=2 ttl=64 time=0.054 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=3 ttl=64 time=0.054 ms
64 bytes from my-database.my-app-network (172.18.0.2): icmp_seq=4 ttl=64 time=0.054 ms

--- my-database ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3060ms
rtt min/avg/max/mdev = 0.050/0.053/0.054/0.001 ms

이는 컨테이너에서 일회성 명령을 실행하고 명령을 실행하는 컨테이너의 기본 서비스 (이 경우 ubuntu 컨테이너에는 일반적인 "서비스"가 없음) 를 시작할 필요 없이 동일한 네트워크의 다른 컨테이너와 상호 작용할 수 있음을 보여줍니다. 핵심은 명령을 실행하는 컨테이너를 상호 작용해야 하는 서비스와 동일한 네트워크에 연결하는 것입니다.

마지막으로, 실행 중인 데이터베이스 컨테이너와 네트워크를 정리해 보겠습니다.

docker stop my-database
my-database
docker rm my-database
my-database
docker network rm my-app-network
my-app-network

명령 실행 후 컨테이너 자동 제거

이 단계에서는 Docker 컨테이너에서 명령을 실행하고 명령이 완료되면 컨테이너를 자동으로 제거하는 방법을 배우겠습니다. 이는 실행이 완료된 후 컨테이너를 유지할 필요가 없는 일회성 작업, 스크립트 또는 작업에 매우 유용합니다. 컨테이너를 자동으로 제거하면 시스템을 깨끗하게 유지하고 중지된 컨테이너가 누적되는 것을 방지할 수 있습니다.

이 데모를 위해 다시 ubuntu 이미지를 사용합니다. 메시지를 출력하고 종료하는 것과 같은 간단한 명령을 실행합니다. 명령이 완료된 후 컨테이너를 자동으로 제거하려면 docker run 명령과 함께 --rm 플래그를 사용합니다.

--rm 플래그를 사용하여 컨테이너를 실행하고 echo "This container will be removed automatically" 명령을 실행해 보겠습니다.

docker run --rm ubuntu echo "This container will be removed automatically"

이 명령은 다음을 수행합니다.

  1. ubuntu 이미지에서 새 컨테이너를 생성합니다.
  2. 컨테이너 내에서 echo "This container will be removed automatically" 명령을 실행합니다.
  3. echo 명령이 완료되면 컨테이너가 자동으로 제거됩니다.

echo 명령의 출력을 볼 수 있습니다.

This container will be removed automatically

명령이 완료되면 컨테이너가 중지되고 제거됩니다. 컨테이너가 제거되었는지 확인하려면 중지된 컨테이너를 포함한 모든 컨테이너를 나열하는 docker ps -a 명령을 사용할 수 있습니다.

docker ps -a

방금 실행된 컨테이너가 목록에 표시되지 않아야 합니다. --rm 플래그 없이 명령을 실행한 경우 컨테이너는 "Exited" 상태로 출력에 계속 나타납니다.

다른 예를 시도해 보겠습니다. sleep을 사용하여 몇 초 동안 일시 중지한 다음 --rm 플래그와 함께 종료하는 명령을 실행합니다.

docker run --rm ubuntu sh -c "echo 'Starting sleep...'; sleep 5; echo 'Sleep finished.'"

이 명령은 sh -c를 사용하여 5 초 동안 대기하기 전후에 메시지를 출력하는 간단한 스크립트를 실행합니다.

첫 번째 메시지가 즉시 표시됩니다.

Starting sleep...

그런 다음 약 5 초 후에 두 번째 메시지가 표시됩니다.

Sleep finished.

스크립트가 완료되면 컨테이너가 자동으로 제거됩니다. docker ps -a로 다시 확인할 수 있습니다.

docker ps -a

sleep 명령을 실행한 컨테이너는 컨테이너 목록에 없어야 합니다.

--rm 플래그를 사용하는 것은 특정 작업을 실행한 다음 종료하도록 설계된 컨테이너에 대한 좋은 방법입니다. 이는 디스크 공간을 관리하고 컨테이너 목록을 깨끗하게 유지하는 데 도움이 됩니다.

요약

이 Lab 에서는 docker compose run 명령을 사용하여 Docker Compose 서비스 내에서 일회성 작업을 실행하는 방법을 배웠습니다. 먼저 서비스 구성에 정의된 기본 명령을 재정의하는 방법을 이해하여 관리 또는 디버깅 목적으로 임의의 명령을 실행할 수 있도록 했습니다.

그런 다음 이러한 일회성 작업에 대한 네트워크 연결을 관리하는 방법을 탐구했습니다. 특히 서비스 포트를 활성화하고 수동으로 포트를 매핑하는 방법을 살펴보았습니다. 마지막으로, 연결된 서비스를 시작하지 않고 명령을 실행하여 종속성을 제어하는 방법과 명령이 완료된 후 컨테이너를 자동으로 제거하여 깨끗한 환경을 보장하는 방법을 배웠습니다.