소개
이 실습에서는 Docker 의 흔한 문제인, 시작 후 즉시 종료되는 컨테이너를 식별하고 해결하는 방법을 배우게 됩니다. 이 문제는 종종 초보자를 혼란스럽게 하며, 설정 오류부터 애플리케이션 문제까지 다양한 이유로 발생할 수 있습니다.
이 실습을 완료함으로써 Docker 컨테이너의 라이프사이클을 이해하고, 즉시 종료되는 컨테이너를 진단하는 방법을 배우며, 디버깅 기술을 숙달하고, 컨테이너가 안정적으로 실행되도록 보장하는 모범 사례를 구현할 수 있습니다. 이러한 기술은 개발 또는 프로덕션 환경에서 Docker 를 사용하는 모든 사람에게 필수적입니다.
Docker 컨테이너 기본 이해
Docker 컨테이너의 기본 사항을 살펴보고 컨테이너 관리에 사용되는 기본 명령에 익숙해지면서 시작해 보겠습니다.
Docker 컨테이너란 무엇인가?
Docker 컨테이너는 애플리케이션을 실행하는 데 필요한 모든 것을 포함하는 가볍고 독립적이며 실행 가능한 소프트웨어 패키지입니다.
- 코드
- 런타임
- 시스템 도구
- 라이브러리
- 설정
컨테이너는 호스트 시스템 및 다른 컨테이너와 격리되어 실행되므로 서로 다른 환경에서 일관성을 제공합니다.
시스템에서 Docker 탐색
먼저 Docker 가 시스템에 올바르게 설치되어 실행 중인지 확인해 보겠습니다.
docker --version
다음과 유사한 출력을 볼 수 있습니다.
Docker version 20.10.21, build baeda1f
다음으로 현재 실행 중인 컨테이너가 있는지 확인해 보겠습니다.
docker ps
이 명령은 실행 중인 컨테이너를 나열합니다. 아직 시작한 컨테이너가 없으므로 열 머리글만 표시됩니다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
중지된 컨테이너를 포함한 모든 컨테이너를 보려면 다음을 사용합니다.
docker ps -a
컨테이너 라이프사이클 이해
Docker 컨테이너 라이프사이클은 여러 상태로 구성됩니다.
- Created (생성됨): 컨테이너가 생성되었지만 아직 시작되지 않음
- Running (실행 중): 컨테이너가 정의된 프로세스를 실행 중
- Paused (일시 중지됨): 컨테이너 프로세스가 일시적으로 중단됨
- Stopped (중지됨): 컨테이너가 종료되었거나 중지됨
- Deleted (삭제됨): 컨테이너가 시스템에서 제거됨
간단한 컨테이너를 실행하고 라이프사이클을 관찰해 보겠습니다.
docker run hello-world
다음과 유사한 출력을 볼 수 있습니다.
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
...
이 컨테이너는 메시지를 표시한 후 즉시 실행되고 종료되었습니다. 이는 메시지를 표시하고 종료하도록 설계된 hello-world 컨테이너의 정상적인 동작입니다.
모든 컨테이너 목록에서 컨테이너를 확인합니다.
docker ps -a
"Exited (종료됨)" 상태로 hello-world 컨테이너가 목록에 표시됩니다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 hello-world "/hello" 30 seconds ago Exited (0) 30 seconds ago peaceful_hopper
종료 코드 (0)은 컨테이너가 오류 없이 성공적으로 종료되었음을 나타냅니다.
컨테이너 관리를 위한 주요 Docker 명령
다음은 이 실습에서 사용할 몇 가지 필수 Docker 명령입니다.
docker run [OPTIONS] IMAGE [COMMAND]: 컨테이너를 생성하고 시작합니다.docker ps: 실행 중인 컨테이너를 나열합니다.docker ps -a: 모든 컨테이너 (중지된 컨테이너 포함) 를 나열합니다.docker logs [CONTAINER_ID]: 컨테이너 로그를 봅니다.docker inspect [CONTAINER_ID]: 자세한 컨테이너 정보를 가져옵니다.docker exec -it [CONTAINER_ID] [COMMAND]: 실행 중인 컨테이너에서 명령을 실행합니다.docker stop [CONTAINER_ID]: 실행 중인 컨테이너를 중지합니다.docker rm [CONTAINER_ID]: 컨테이너를 제거합니다.
이제 Docker 컨테이너의 기본 사항과 라이프사이클을 이해했으므로 예기치 않게 종료되는 컨테이너 문제 해결로 넘어가겠습니다.
컨테이너 종료 문제 식별
이 단계에서는 즉시 종료되는 Docker 컨테이너를 생성하고 문제 진단 방법을 배우겠습니다.
즉시 종료되는 컨테이너 실행
먼저 Ubuntu 컨테이너를 실행해 보겠습니다.
docker run ubuntu
흥미로운 점을 발견할 것입니다. 명령이 즉시 완료되고 프롬프트로 돌아갑니다. Ubuntu 컨테이너는 어디에 있을까요? 확인해 보겠습니다.
docker ps
실행 중인 컨테이너가 없습니다. 이제 중지된 컨테이너를 포함한 모든 컨테이너를 확인합니다.
docker ps -a
다음과 유사한 출력을 볼 수 있습니다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f7d9e7f6543d ubuntu "/bin/bash" 10 seconds ago Exited (0) 10 seconds ago focused_galileo
a1b2c3d4e5f6 hello-world "/hello" 10 minutes ago Exited (0) 10 minutes ago peaceful_hopper
Ubuntu 컨테이너가 시작된 후 상태 코드 0 으로 즉시 종료되었습니다. 이는 오류 없이 종료되었음을 나타냅니다. 이는 예상되는 동작이지만 초보자에게는 혼란스러울 수 있습니다.
컨테이너가 종료되는 이유 이해
컨테이너는 특정 명령 또는 프로세스를 실행하도록 설계되었습니다. 해당 프로세스가 완료되거나 종료되면 컨테이너가 중지됩니다. 이는 Docker 설계의 기본 원칙입니다.
Ubuntu 컨테이너가 즉시 종료된 이유는 다음과 같습니다.
- Ubuntu 이미지의 기본 명령은
/bin/bash입니다. -it플래그 (대화형, 터미널) 없이 실행하면 bash 셸에 입력이 없습니다.- 입력이 없고 실행할 특정 명령이 없으면 bash 가 즉시 종료됩니다.
- 주 프로세스 (bash) 가 종료되면 컨테이너가 중지됩니다.
컨테이너 로그 및 정보 보기
종료된 Ubuntu 컨테이너의 로그를 검사하여 무슨 일이 있었는지 이해해 보겠습니다. 먼저 docker ps -a 출력에서 컨테이너 ID 를 찾은 다음 다음을 수행합니다.
docker logs CONTAINER_ID
CONTAINER_ID를 실제 컨테이너 ID 로 바꿉니다. 컨테이너가 종료되기 전에 로그를 생성하지 않았기 때문에 출력이 없을 것입니다.
컨테이너에 대한 자세한 정보는 다음과 같습니다.
docker inspect CONTAINER_ID
그러면 컨테이너 구성 및 상태 정보가 포함된 큰 JSON 객체가 표시됩니다.
종료 코드에 집중해 보겠습니다.
docker inspect CONTAINER_ID --format='{{.State.ExitCode}}'
다음과 같은 출력을 볼 수 있습니다.
0
이는 컨테이너가 오류로 인해 종료된 것이 아니라 정상적으로 종료되었음을 확인합니다.
컨테이너를 계속 실행하기
Ubuntu 컨테이너를 계속 실행하려면 다음 중 하나를 수행해야 합니다.
- 대화형 세션을 제공하거나
- 기본 명령을 장기 실행 프로세스로 재정의합니다.
대화형 방식을 시도해 보겠습니다.
docker run -it ubuntu
이제 bash 프롬프트로 컨테이너 내부에 있습니다.
root@3a4b5c6d7e8f:/#
이 bash 세션이 활성 상태인 동안 컨테이너는 계속 실행됩니다. exit를 입력하거나 Ctrl+D 를 눌러 컨테이너를 종료합니다.
exit
또는 즉시 완료되지 않는 명령을 제공하여 컨테이너를 계속 실행할 수 있습니다.
docker run -d ubuntu sleep 300
이렇게 하면 Ubuntu 컨테이너가 실행되고 sleep 300 명령이 실행되어 컨테이너가 300 초 (5 분) 동안 계속 실행됩니다.
컨테이너가 실행 중인지 확인합니다.
docker ps
실행 중인 상태로 컨테이너가 표시됩니다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9a8b7c6d5e4f ubuntu "sleep 300" 10 seconds ago Up 10 seconds hopeful_hopper
즉시 종료되는 컨테이너를 진단할 때 다음 주요 사항을 기억하십시오.
- 컨테이너는 주 프로세스가 완료되면 종료됩니다.
- 로그 및 종료 코드를 확인하여 중지된 이유를 이해합니다.
- 컨테이너가 계속 실행되어야 하는 경우 주 프로세스가 종료되지 않도록 합니다.
일반적인 컨테이너 종료 문제 해결
이제 컨테이너가 즉시 종료되는 이유를 이해했으므로 예기치 않은 컨테이너 종료의 일반적인 원인과 문제 해결 방법을 살펴보겠습니다.
문제가 있는 컨테이너 생성
컨테이너가 예기치 않게 종료되는 간단한 상황을 만들어 보겠습니다. 먼저 테스트 파일용 디렉토리를 만듭니다.
mkdir -p ~/project/docker-exit-test
cd ~/project/docker-exit-test
이제 오류가 있는 간단한 Python 스크립트를 만듭니다.
nano app.py
다음 코드를 추가합니다.
import os
## Attempt to read a required environment variable
database_url = os.environ['DATABASE_URL']
print(f"Connecting to database: {database_url}")
print("Application running...")
## Rest of the application code would go here
파일을 저장하고 종료합니다 (Ctrl+O, Enter, Ctrl+X).
이제 이 애플리케이션으로 이미지를 빌드하는 Dockerfile 을 만듭니다.
nano Dockerfile
다음 내용을 추가합니다.
FROM python:3.9-slim
WORKDIR /app
COPY app.py .
CMD ["python", "app.py"]
파일을 저장하고 종료합니다.
Docker 이미지를 빌드합니다.
docker build -t exit-test-app .
이미지가 성공적으로 빌드되었음을 나타내는 출력을 볼 수 있습니다.
Successfully built a1b2c3d4e5f6
Successfully tagged exit-test-app:latest
이제 컨테이너를 실행합니다.
docker run exit-test-app
컨테이너가 오류와 함께 즉시 종료되는 것을 볼 수 있습니다.
Traceback (most recent call last):
File "/app/app.py", line 4, in <module>
database_url = os.environ['DATABASE_URL']
File "/usr/local/lib/python3.9/os.py", line 679, in __getitem__
raise KeyError(key) from None
KeyError: 'DATABASE_URL'
Python 스크립트가 제공되지 않은 환경 변수를 예상했기 때문에 컨테이너가 종료되었습니다.
문제 진단
컨테이너가 예기치 않게 종료되면 다음 문제 해결 단계를 따르십시오.
- 오류인지 확인하기 위해 종료 코드를 확인합니다.
docker ps -a
컨테이너를 찾고 종료 코드를 기록합니다. 오류를 나타내는 0 이 아닌 값이어야 합니다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b1c2d3e4f5g6 exit-test-app "python app.py" 20 seconds ago Exited (1) 19 seconds ago vigilant_galileo
- 컨테이너 로그를 검사합니다.
docker logs $(docker ps -a -q --filter ancestor=exit-test-app --latest)
이렇게 하면 이미지에서 생성된 가장 최근 컨테이너의 로그가 검색됩니다.
오류 메시지는 문제를 명확하게 보여줍니다. Python 스크립트는 존재하지 않는 DATABASE_URL이라는 환경 변수에 액세스하려고 합니다.
문제 해결
이제 누락된 환경 변수를 제공하여 문제를 해결해 보겠습니다.
docker run -e DATABASE_URL=postgresql://user:password@db:5432/mydatabase exit-test-app
이제 컨테이너가 성공적으로 실행되는 것을 볼 수 있습니다.
Connecting to database: postgresql://user:password@db:5432/mydatabase
Application running...
컨테이너는 여전히 종료되지만 이번에는 스크립트가 끝에 도달하여 정상적으로 종료되기 때문입니다. 컨테이너가 계속 실행되기를 원한다면 무한 루프 또는 장기 실행 프로세스를 포함하도록 스크립트를 수정해야 합니다.
컨테이너 종료의 일반적인 원인
컨테이너가 예기치 않게 종료될 수 있는 몇 가지 일반적인 이유는 다음과 같습니다.
- 누락된 환경 변수: 방금 시연한 것처럼
- 누락된 종속성: 누락된 라이브러리 또는 시스템 패키지
- 연결 실패: 데이터베이스 또는 기타 서비스에 연결할 수 없음
- 권한 문제: 파일 또는 리소스에 액세스할 수 있는 권한 부족
- 리소스 제한: 컨테이너가 메모리 또는 CPU 를 다 사용함
- 애플리케이션 충돌: 애플리케이션 코드의 버그
문제 해결 기술
이러한 각 문제에 대해 효과적인 문제 해결 접근 방식은 다음과 같습니다.
- 로그 검토: 항상
docker logs로 먼저 컨테이너 로그를 확인합니다. - 진입점 재정의:
docker run --entrypoint /bin/sh -it my-image를 사용하여 컨테이너에 들어가 조사합니다. - 디버깅 문 추가: 더 많은 로깅을 추가하도록 애플리케이션을 수정합니다.
- 리소스 사용량 확인:
docker stats를 사용하여 컨테이너 리소스 사용량을 모니터링합니다. - 환경 검사:
docker exec -it CONTAINER_ID env를 실행하여 환경 변수를 확인합니다.
이미지로 진입점 재정의 기술을 시도해 보겠습니다.
docker run --entrypoint /bin/sh -it exit-test-app
이제 셸이 있는 컨테이너 내부에 있습니다. 환경을 탐색할 수 있습니다.
ls -la
cat app.py
echo $DATABASE_URL
DATABASE_URL이 설정되지 않은 것을 볼 수 있습니다. 완료되면 컨테이너를 종료합니다.
exit
컨테이너가 종료되는 이유를 이해하고 이러한 문제 해결 기술을 적용하면 대부분의 컨테이너 종료 문제를 신속하게 진단하고 해결할 수 있습니다.
컨테이너 안정성을 위한 모범 사례 구현
이제 컨테이너 종료 문제를 해결하는 방법을 이해했으므로 컨테이너를 보다 안정적이고 강력하게 만들기 위한 모범 사례를 구현해 보겠습니다. 적절한 오류 처리, 상태 확인 및 로깅을 통해 예제 애플리케이션을 개선할 것입니다.
1. 오류 처리 개선
Python 애플리케이션을 수정하여 누락된 환경 변수를 적절하게 처리해 보겠습니다.
cd ~/project/docker-exit-test
nano app.py
내용을 업데이트합니다.
import os
import time
import sys
## Get environment variables with defaults
database_url = os.environ.get('DATABASE_URL', 'sqlite:///default.db')
print(f"Connecting to database: {database_url}")
## Simulate a long-running process
try:
print("Application running... Press Ctrl+C to exit")
counter = 0
while True:
counter += 1
print(f"Application heartbeat: {counter}")
time.sleep(10)
except KeyboardInterrupt:
print("Application shutting down gracefully...")
sys.exit(0)
파일을 저장하고 종료합니다.
이 개선된 버전은 다음과 같습니다.
- 예외를 발생시키는 대신 기본값을 사용하여
os.environ.get()을 사용합니다. - 컨테이너를 활성 상태로 유지하기 위해 장기 실행 루프를 구현합니다.
- 종료 시 적절한 종료를 처리합니다.
이미지를 다시 빌드해 보겠습니다.
docker build -t exit-test-app:v2 .
그리고 개선된 컨테이너를 실행합니다.
docker run -d --name improved-app exit-test-app:v2
컨테이너가 실행 중인지 확인합니다.
docker ps
컨테이너가 실행 중인 것을 볼 수 있습니다.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c1d2e3f4g5h6 exit-test-app:v2 "python app.py" 10 seconds ago Up 10 seconds improved-app
로그를 보아 작동하는지 확인합니다.
docker logs improved-app
다음과 같은 출력을 볼 수 있습니다.
Connecting to database: sqlite:///default.db
Application running... Press Ctrl+C to exit
Application heartbeat: 1
Application heartbeat: 2
2. Dockerfile 에서 상태 확인 구현
상태 확인을 통해 Docker 는 컨테이너의 상태를 모니터링할 수 있습니다. 상태 확인을 포함하도록 Dockerfile 을 업데이트해 보겠습니다.
nano Dockerfile
다음으로 업데이트합니다.
FROM python:3.9-slim
WORKDIR /app
## Install curl for health checks
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY app.py .
## Add a health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
## Expose port for the health check endpoint
EXPOSE 8080
CMD ["python", "app.py"]
이제 애플리케이션에 상태 확인 엔드포인트를 추가해야 합니다.
nano app.py
내용을 다음으로 바꿉니다.
import os
import time
import sys
import threading
from http.server import HTTPServer, BaseHTTPRequestHandler
## Get environment variables with defaults
database_url = os.environ.get('DATABASE_URL', 'sqlite:///default.db')
## Simple HTTP server for health checks
class HealthRequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/health':
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(b'OK')
else:
self.send_response(404)
self.end_headers()
def run_health_server():
server = HTTPServer(('0.0.0.0', 8080), HealthRequestHandler)
print("Starting health check server on port 8080")
server.serve_forever()
## Start health check server in a separate thread
health_thread = threading.Thread(target=run_health_server, daemon=True)
health_thread.start()
print(f"Connecting to database: {database_url}")
## Main application loop
try:
print("Application running... Press Ctrl+C to exit")
counter = 0
while True:
counter += 1
print(f"Application heartbeat: {counter}")
time.sleep(10)
except KeyboardInterrupt:
print("Application shutting down gracefully...")
sys.exit(0)
파일을 저장하고 종료합니다.
상태 확인으로 이미지를 다시 빌드합니다.
docker build -t exit-test-app:v3 .
새 버전으로 컨테이너를 실행합니다.
docker run -d --name healthcheck-app -p 8080:8080 exit-test-app:v3
약 30 초 후에 상태를 확인합니다.
docker inspect --format='{{.State.Health.Status}}' healthcheck-app
다음과 같은 출력을 볼 수 있습니다.
healthy
상태 엔드포인트를 직접 테스트할 수도 있습니다.
curl http://localhost:8080/health
그러면 OK가 반환됩니다.
3. Docker 재시작 정책 사용
Docker 는 종료되거나 오류가 발생할 때 컨테이너를 자동으로 다시 시작하는 재시작 정책을 제공합니다.
docker run -d --restart=on-failure:5 --name restart-app exit-test-app:v3
이 정책은 0 이 아닌 코드로 종료되는 경우 컨테이너를 최대 5 번 다시 시작합니다.
사용 가능한 재시작 정책:
no: 절대 재시작 안 함 (기본값)always: 종료 상태에 관계없이 항상 재시작unless-stopped: 수동으로 중지하지 않는 한 항상 재시작on-failure[:max-retries]: 0 이 아닌 종료 시에만 재시작
4. 리소스 제한 설정
리소스 고갈로 인한 컨테이너 충돌을 방지하려면 적절한 리소스 제한을 설정합니다.
docker run -d --name resource-limited-app \
--memory=256m \
--cpus=0.5 \
exit-test-app:v3
이렇게 하면 컨테이너가 256MB 의 메모리와 CPU 코어의 절반으로 제한됩니다.
5. 적절한 로깅 구성
더 나은 디버깅을 위해 구조화된 로그를 출력하도록 애플리케이션을 구성합니다.
docker run -d --name logging-app \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
exit-test-app:v3
이렇게 하면 컨테이너가 로그 회전 (최대 3 개의 파일, 각 10MB) 을 사용하여 JSON 로그 드라이버를 사용하도록 구성됩니다.
모범 사례 요약
이러한 모범 사례를 구현함으로써 Docker 컨테이너의 안정성을 크게 개선했습니다.
- 기본값을 사용한 적절한 오류 처리
- 모니터링을 위한 컨테이너 상태 확인
- 자동 복구를 위한 적절한 재시작 정책
- 리소스 고갈을 방지하기 위한 리소스 제한
- 더 쉬운 문제 해결을 위한 적절한 로깅 구성
이러한 기술은 실패로부터 복구하고 문제가 발생할 때 더 나은 관찰 가능성을 제공할 수 있는 탄력적인 컨테이너를 만드는 데 도움이 됩니다.
요약
이 Lab 에서 Docker 컨테이너 종료 문제를 해결하고 해결하는 데 필요한 필수 기술을 배웠습니다.
- Docker 컨테이너 수명 주기와 컨테이너의 주 프로세스가 완료될 때 컨테이너가 종료되는 이유 이해
- 로그 및 종료 코드를 통해 즉시 컨테이너 종료의 일반적인 원인 식별
- 컨테이너화된 애플리케이션에서 강력한 오류 처리 구현
- 애플리케이션 상태를 모니터링하기 위한 컨테이너 상태 확인 추가
- 실패로부터 자동 복구를 위한 재시작 정책 구성
- 컨테이너 충돌을 방지하기 위한 적절한 리소스 제한 설정
- 더 쉬운 디버깅을 위한 적절한 로깅 전략 구현
이러한 기술은 안정적인 Docker 컨테이너 배포의 기반을 형성합니다. 실제 시나리오에서 Docker 를 계속 사용하면서 이러한 문제 해결 기술이 안정적이고 탄력적인 컨테이너화된 애플리케이션을 유지하는 데 매우 유용하다는 것을 알게 될 것입니다.
즉시 컨테이너 종료의 가장 일반적인 원인은 다음과 같습니다.
- 주 프로세스가 작업을 완료함 (설계상)
- 누락된 환경 변수 또는 구성
- 애플리케이션 오류 또는 예외
- 리소스 제약 또는 연결 문제
이 Lab 에서 다룬 진단 기술과 모범 사례를 적용하면 이러한 문제를 신속하게 식별하고 해결하여 Docker 컨테이너가 모든 환경에서 안정적으로 실행되도록 할 수 있습니다.



