도커 컨테이너 systemd 서비스 등록 – 완벽 가이드

서버가 재부팅되었는데 중요한 컨테이너가 올라오지 않아 당황한 적 있으신가요? restart 정책만으로는 부족한 복잡한 환경에서 systemd 서비스 등록이 답입니다. 이 방법을 사용하면 컨테이너 시작 순서를 세밀하게 제어하고, 시스템 로그와 통합하며, 의존성 관리까지 완벽하게 할 수 있습니다. 기업 환경에서 검증된 실전 노하우를 공개합니다.

systemd 서비스 등록이 필요한 5가지 이유

많은 개발자가 docker run --restart=always로 충분하다고 생각합니다. 하지만 실무에서는 이것만으로 해결되지 않는 상황이 많습니다.

restart 정책의 한계

1. 시작 순서 제어 불가

  • 데이터베이스보다 애플리케이션이 먼저 시작되는 문제
  • 의존성 체인이 복잡한 마이크로서비스 환경

2. 세밀한 타이밍 조절 어려움

  • Docker 데몬이 완전히 준비되기 전 컨테이너가 시작 시도
  • 네트워크나 볼륨이 준비되지 않은 상태에서 실행

3. 시스템 통합 부족

  • journalctl로 통합 로그 관리 불가
  • systemctl 명령어로 일관된 관리 어려움

4. 복잡한 사전/사후 작업

  • 컨테이너 시작 전 환경 검증 필요
  • 시작 후 헬스체크나 알림 발송

5. 기업 표준 준수

  • IT 부서의 시스템 관리 정책
  • 보안 감사 요구사항

이런 상황에서 systemd 서비스 등록이 최선의 해법입니다.


기본 systemd 서비스 파일 작성법

단계 1: 컨테이너 확인

먼저 서비스로 등록할 컨테이너를 확인합니다.

# 모든 컨테이너 목록 보기
docker ps -a

# 출력 예시
CONTAINER ID   IMAGE          STATUS    NAMES
a1b2c3d4e5f6   sonatype/nexus Up        nexus
b2c3d4e5f6g7   postgres:14    Up        postgres-db

단계 2: 서비스 파일 생성

systemd 서비스 파일을 생성합니다.

# 서비스 파일 생성 (nexus 컨테이너 예시)
sudo vi /etc/systemd/system/docker-nexus.service

단계 3: 기본 서비스 설정 작성

[Unit]
Description=Nexus Repository Manager Container
# Docker가 완전히 시작된 후 실행
Requires=docker.service
After=docker.service

[Service]
# 서비스가 종료되어도 systemd가 활성 상태로 유지
RemainAfterExit=yes

# 컨테이너 시작 명령
ExecStart=/usr/bin/docker start nexus

# 컨테이너 중지 명령
ExecStop=/usr/bin/docker stop nexus

# 재시작 정책 (선택사항)
Restart=on-failure
RestartSec=10s

[Install]
# 멀티유저 모드에서 자동 시작
WantedBy=multi-user.target

단계 4: 서비스 등록 및 활성화

# systemd 설정 다시 읽기
sudo systemctl daemon-reload

# Docker 서비스 활성화 (부팅 시 자동 시작)
sudo systemctl enable docker

# Nexus 서비스 활성화
sudo systemctl enable docker-nexus.service

# 서비스 즉시 시작
sudo systemctl start docker-nexus.service

# 상태 확인
sudo systemctl status docker-nexus.service

단계 5: 등록 확인

# 서비스 목록에서 확인
systemctl list-unit-files | grep docker-nexus

# 출력 예시
docker-nexus.service    enabled

# 활성화된 모든 Docker 관련 서비스 보기
systemctl list-units | grep docker


실전 시나리오별 고급 설정

시나리오 1: 데이터베이스 컨테이너 (PostgreSQL)

데이터베이스는 안정적인 시작과 정상 종료가 매우 중요합니다.

sudo vi /etc/systemd/system/docker-postgres.service

[Unit]
Description=PostgreSQL Database Container
Documentation=https://www.postgresql.org/docs/
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes

# 시작 전 환경 검증
ExecStartPre=/bin/bash -c 'until docker ps | grep -q postgres-db; do echo "Waiting for Docker..."; sleep 2; done || true'

# 컨테이너 시작
ExecStart=/usr/bin/docker start postgres-db

# 시작 후 헬스체크 (30초 대기)
ExecStartPost=/bin/sleep 30
ExecStartPost=/bin/bash -c 'docker exec postgres-db pg_isready -U postgres'

# 정상 종료 (30초 그레이스 기간)
TimeoutStopSec=30
ExecStop=/usr/bin/docker stop -t 30 postgres-db

# 실패 시 자동 재시작 (3번까지)
Restart=on-failure
RestartSec=15s
StartLimitBurst=3

[Install]
WantedBy=multi-user.target

주요 설정 설명:

  • ExecStartPre: 시작 전 Docker 준비 상태 확인
  • ExecStartPost: 시작 후 데이터베이스 준비 검증
  • TimeoutStopSec=30: 정상 종료를 위한 충분한 시간 부여
  • StartLimitBurst=3: 3번 실패 시 재시작 중단


시나리오 2: 웹 애플리케이션 (의존성 있음)

애플리케이션은 데이터베이스가 준비된 후 시작되어야 합니다.

sudo vi /etc/systemd/system/docker-webapp.service

[Unit]
Description=Web Application Container
Requires=docker.service docker-postgres.service
After=docker.service docker-postgres.service

[Service]
Type=oneshot
RemainAfterExit=yes

# 데이터베이스 연결 대기 (최대 60초)
ExecStartPre=/bin/bash -c '\\
    for i in {1..30}; do \\
        docker exec postgres-db pg_isready -U postgres && break || sleep 2; \\
    done'

# 애플리케이션 시작
ExecStart=/usr/bin/docker start webapp

# 헬스체크 엔드포인트 확인
ExecStartPost=/bin/sleep 10
ExecStartPost=/usr/bin/curl -f <http://localhost:8080/health> || exit 1

# 정상 종료
ExecStop=/usr/bin/docker stop -t 20 webapp

# 실패 시 재시작
Restart=on-failure
RestartSec=10s

[Install]
WantedBy=multi-user.target

핵심 포인트:

  • After=docker-postgres.service: 데이터베이스 서비스 후 시작
  • ExecStartPre: 데이터베이스 연결 가능 여부 확인
  • curl 헬스체크: 애플리케이션이 정상 작동하는지 검증


시나리오 3: 마이크로서비스 체인

여러 서비스가 순차적으로 시작되어야 하는 경우입니다.

1) API Gateway

sudo vi /etc/systemd/system/docker-gateway.service

[Unit]
Description=API Gateway Container
Requires=docker.service docker-postgres.service docker-redis.service
After=docker.service docker-postgres.service docker-redis.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/docker start api-gateway
ExecStop=/usr/bin/docker stop api-gateway
Restart=on-failure

[Install]
WantedBy=multi-user.target

2) 백엔드 서비스들

# Auth 서비스
sudo vi /etc/systemd/system/docker-auth.service

[Unit]
Description=Authentication Service Container
Requires=docker-gateway.service
After=docker-gateway.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/docker start auth-service
ExecStop=/usr/bin/docker stop auth-service

[Install]
WantedBy=multi-user.target

서비스 시작 순서:

  1. Docker → PostgreSQL → Redis
  2. API Gateway
  3. Auth, Payment, Notification 서비스 (병렬)
  4. Frontend


시나리오 4: Nginx 리버스 프록시 (가장 마지막 시작)

sudo vi /etc/systemd/system/docker-nginx.service

[Unit]
Description=Nginx Reverse Proxy Container
# 모든 백엔드 서비스가 준비된 후 시작
Requires=docker.service
After=docker.service docker-gateway.service docker-auth.service

[Service]
Type=oneshot
RemainAfterExit=yes

# 백엔드 서비스들이 준비될 때까지 대기
ExecStartPre=/bin/sleep 15
ExecStartPre=/bin/bash -c '\\
    curl -f <http://localhost:3000/health> && \\
    curl -f <http://localhost:3001/health>'

ExecStart=/usr/bin/docker start nginx-proxy
ExecStop=/usr/bin/docker stop nginx-proxy

# Nginx는 중요하므로 적극적으로 재시작
Restart=always
RestartSec=5s

[Install]
WantedBy=multi-user.target


고급 기능: 환경별 설정 파일

개발 환경용 서비스

sudo vi /etc/systemd/system/docker-app-dev.service

[Unit]
Description=Development Application Container
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes

# 개발 환경 변수 설정
Environment="NODE_ENV=development"
Environment="DEBUG=true"

# 컨테이너 시작 (볼륨 마운트 포함)
ExecStart=/usr/bin/docker start app-dev

# 개발 중에는 자동 재시작 비활성화
Restart=no

[Install]
WantedBy=multi-user.target

프로덕션 환경용 서비스

sudo vi /etc/systemd/system/docker-app-prod.service

[Unit]
Description=Production Application Container
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target

[Service]
Type=oneshot
RemainAfterExit=yes

# 프로덕션 환경 변수
Environment="NODE_ENV=production"
Environment="LOG_LEVEL=error"

# 시작 전 디스크 공간 확인
ExecStartPre=/bin/bash -c '\\
    AVAILABLE=$(df -h / | awk "NR==2 {print \\$4}" | sed "s/G//"); \\
    if [ "$AVAILABLE" -lt 10 ]; then \\
        echo "Insufficient disk space"; exit 1; \\
    fi'

ExecStart=/usr/bin/docker start app-prod

# 프로덕션은 적극적으로 재시작
Restart=always
RestartSec=10s
StartLimitBurst=5

# Slack 알림 (실패 시)
ExecStopPost=/usr/bin/curl -X POST -H "Content-type: application/json" \\
    --data "{\\"text\\":\\"Production app failed!\\"}" \\
    <https://hooks.slack.com/services/YOUR/WEBHOOK/URL>

[Install]
WantedBy=multi-user.target

통합 관리 스크립트

여러 서비스를 한 번에 관리하는 스크립트를 만들어보겠습니다.

서비스 일괄 등록 스크립트

sudo vi /usr/local/bin/register-docker-services.sh

#!/bin/bash

# 색상 정의
RED='\\033[0;31m'
GREEN='\\033[0;32m'
YELLOW='\\033[1;33m'
NC='\\033[0m' # No Color

echo -e "${GREEN}Docker 컨테이너 서비스 등록 스크립트${NC}"
echo "=========================================="

# 서비스 목록 정의
SERVICES=(
    "postgres:docker-postgres"
    "redis:docker-redis"
    "api-gateway:docker-gateway"
    "auth-service:docker-auth"
    "nginx-proxy:docker-nginx"
)

# systemd 템플릿 함수
create_service_file() {
    local CONTAINER_NAME=$1
    local SERVICE_NAME=$2
    local SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"

    echo -e "${YELLOW}Creating service: ${SERVICE_NAME}${NC}"

    cat > "$SERVICE_FILE" << EOF
[Unit]
Description=${CONTAINER_NAME} Container Service
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/docker start ${CONTAINER_NAME}
ExecStop=/usr/bin/docker stop ${CONTAINER_NAME}
Restart=on-failure
RestartSec=10s

[Install]
WantedBy=multi-user.target
EOF

    if [ $? -eq 0 ]; then
        echo -e "${GREEN}✓ Service file created${NC}"
    else
        echo -e "${RED}✗ Failed to create service file${NC}"
        return 1
    fi
}

# Docker 서비스 활성화
echo -e "\\n${YELLOW}Enabling Docker service...${NC}"
systemctl enable docker
systemctl start docker

# systemd 리로드
systemctl daemon-reload

# 각 컨테이너를 서비스로 등록
for service in "${SERVICES[@]}"; do
    IFS=':' read -r CONTAINER_NAME SERVICE_NAME <<< "$service"

    echo -e "\\n${YELLOW}Processing: ${CONTAINER_NAME}${NC}"

    # 컨테이너 존재 확인
    if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
        echo -e "${RED}✗ Container '${CONTAINER_NAME}' not found${NC}"
        continue
    fi

    # 서비스 파일 생성
    create_service_file "$CONTAINER_NAME" "$SERVICE_NAME"

    # 서비스 활성화
    systemctl enable "${SERVICE_NAME}.service"
    systemctl start "${SERVICE_NAME}.service"

    # 상태 확인
    sleep 2
    if systemctl is-active --quiet "${SERVICE_NAME}.service"; then
        echo -e "${GREEN}✓ Service started successfully${NC}"
    else
        echo -e "${RED}✗ Service failed to start${NC}"
    fi
done

echo -e "\\n${GREEN}=========================================="
echo "등록 완료!${NC}"
echo ""
echo "확인 명령어:"
echo "  systemctl list-unit-files | grep docker"
echo "  systemctl status docker-postgres.service"

실행 및 사용

# 실행 권한 부여
sudo chmod +x /usr/local/bin/register-docker-services.sh

# 스크립트 실행
sudo /usr/local/bin/register-docker-services.sh

# 출력 예시
Docker 컨테이너 서비스 등록 스크립트
==========================================
Enabling Docker service...
✓ Docker enabled

Processing: postgres
Creating service: docker-postgres
✓ Service file created
✓ Service started successfully

Processing: redis
Creating service: docker-redis
✓ Service file created
✓ Service started successfully

서비스 관리 명령어 완전 정리

기본 관리 명령어

# 서비스 시작
sudo systemctl start docker-nexus.service

# 서비스 중지
sudo systemctl stop docker-nexus.service

# 서비스 재시작
sudo systemctl restart docker-nexus.service

# 서비스 상태 확인
sudo systemctl status docker-nexus.service

# 서비스 활성화 (부팅 시 자동 시작)
sudo systemctl enable docker-nexus.service

# 서비스 비활성화
sudo systemctl disable docker-nexus.service

# 설정 다시 읽기
sudo systemctl daemon-reload

로그 확인

# 서비스 로그 보기
sudo journalctl -u docker-nexus.service

# 실시간 로그
sudo journalctl -u docker-nexus.service -f

# 최근 50줄
sudo journalctl -u docker-nexus.service -n 50

# 오늘 로그만
sudo journalctl -u docker-nexus.service --since today

# 특정 시간대 로그
sudo journalctl -u docker-nexus.service --since "2025-01-01 00:00:00" --until "2025-01-01 23:59:59"

# 에러 로그만 필터링
sudo journalctl -u docker-nexus.service -p err

서비스 목록 조회

# 모든 Docker 관련 서비스
systemctl list-unit-files | grep docker

# 활성화된 서비스만
systemctl list-unit-files --state=enabled | grep docker

# 실행 중인 서비스
systemctl list-units --type=service --state=running | grep docker

# 실패한 서비스
systemctl list-units --type=service --state=failed

의존성 확인

# 서비스 의존성 트리
systemctl list-dependencies docker-webapp.service

# 역 의존성 (이 서비스에 의존하는 다른 서비스)
systemctl list-dependencies --reverse docker-postgres.service

# 출력 예시
docker-postgres.service
● ├─docker-webapp.service
● ├─docker-api.service
● └─docker-gateway.service

문제 해결 가이드

서비스가 시작되지 않는 경우

1단계: 상세 상태 확인

sudo systemctl status docker-nexus.service -l

# 출력에서 에러 메시지 확인
● docker-nexus.service - Nexus Repository Manager Container
   Loaded: loaded (/etc/systemd/system/docker-nexus.service; enabled)
   Active: failed (Result: exit-code)

2단계: 로그 분석

# 가장 최근 에러 확인
sudo journalctl -u docker-nexus.service -n 100 --no-pager

# 흔한 에러: "Container not found"
# 원인: 컨테이너 이름 오타 또는 컨테이너 미존재
docker ps -a | grep nexus

3단계: 서비스 파일 검증

# 구문 오류 확인
sudo systemd-analyze verify /etc/systemd/system/docker-nexus.service

# 문제 없다면 출력 없음, 오류 있으면 메시지 표시

4단계: Docker 상태 확인

sudo systemctl status docker
docker version
docker ps

의존성 문제

순환 의존성 탐지:

# 의존성 오류 확인
sudo systemctl list-dependencies --all docker-webapp.service

# 순환 참조가 있다면 에러 메시지 표시

해결 방법:

# 잘못된 설정
[Unit]
Requires=docker-postgres.service docker-webapp.service  # 순환 참조!

# 올바른 설정
[Unit]
Requires=docker-postgres.service
After=docker-postgres.service

타이밍 문제

컨테이너가 준비되기 전에 서비스가 완료된 것으로 인식:

[Service]
Type=oneshot
RemainAfterExit=yes

# 시작 후 충분한 대기 시간
ExecStartPost=/bin/sleep 10

# 또는 헬스체크 추가
ExecStartPost=/bin/bash -c 'until curl -f <http://localhost:8080/health>; do sleep 2; done'

재시작이 너무 빠른 경우

[Service]
# 재시작 간격 늘리기
RestartSec=30s

# 재시작 횟수 제한
StartLimitBurst=3
StartLimitIntervalSec=300


모니터링과 알림 통합

Prometheus Exporter 추가

sudo vi /etc/systemd/system/docker-app-monitored.service

[Unit]
Description=Monitored Application Container
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/docker start app
ExecStop=/usr/bin/docker stop app

# 시작 성공 알림
ExecStartPost=/usr/bin/curl -X POST <http://prometheus-pushgateway:9091/metrics/job/docker-startup> \\
    --data "docker_container_start{container=\\"app\\",status=\\"success\\"} 1"

# 실패 알림
ExecStopPost=/usr/bin/curl -X POST <http://prometheus-pushgateway:9091/metrics/job/docker-startup> \\
    --data "docker_container_start{container=\\"app\\",status=\\"failed\\"} 0"

Restart=on-failure

[Install]
WantedBy=multi-user.target


Slack 알림 통합

sudo vi /usr/local/bin/notify-slack.sh

#!/bin/bash

SERVICE_NAME=$1
STATUS=$2
WEBHOOK_URL="<https://hooks.slack.com/services/YOUR/WEBHOOK/URL>"

if [ "$STATUS" == "failed" ]; then
    COLOR="danger"
    EMOJI=":x:"
else
    COLOR="good"
    EMOJI=":white_check_mark:"
fi

curl -X POST "$WEBHOOK_URL" \\
    -H 'Content-Type: application/json' \\
    -d "{
        \\"username\\": \\"Docker Monitor\\",
        \\"icon_emoji\\": \\"$EMOJI\\",
        \\"attachments\\": [{
            \\"color\\": \\"$COLOR\\",
            \\"title\\": \\"Docker Service Alert\\",
            \\"text\\": \\"Service: $SERVICE_NAME\\\\nStatus: $STATUS\\\\nTime: $(date)\\"
        }]
    }"

서비스 파일에 적용:

ExecStopPost=/usr/local/bin/notify-slack.sh docker-app failed


재부팅 테스트 완벽 가이드

테스트 준비

# 1. 현재 실행 중인 컨테이너 목록 저장
docker ps --format "{{.Names}}" > /tmp/containers-before-reboot.txt

# 2. 모든 서비스 상태 확인
systemctl list-unit-files | grep docker > /tmp/services-before-reboot.txt

# 3. 테스트 스크립트 작성
cat > /usr/local/bin/post-reboot-check.sh << 'EOF'
#!/bin/bash
echo "Waiting 60 seconds for all services to start..."
sleep 60

echo "Checking Docker containers..."
docker ps --format "table {{.Names}}\\t{{.Status}}"

echo -e "\\nChecking systemd services..."
systemctl list-units --type=service --state=running | grep docker

echo -e "\\nService statuses:"
for service in $(systemctl list-unit-files | grep docker | awk '{print $1}'); do
    echo -n "$service: "
    systemctl is-active $service
done
EOF

chmod +x /usr/local/bin/post-reboot-check.sh

재부팅 실행

# 재부팅 수행
sudo reboot

재부팅 후 검증

# 로그인 후 자동 체크 실행
/usr/local/bin/post-reboot-check.sh

# 수동 검증
docker ps
systemctl list-units | grep docker
journalctl -b -u docker*.service

프로덕션 체크리스트

서비스 등록 전 반드시 확인하세요.

필수 점검 항목

  • ✅ Docker 서비스가 enabled 상태인가?
  • ✅ 서비스 파일의 컨테이너 이름이 정확한가?
  • ✅ 의존성 순서가 올바른가? (DB → App → Proxy)
  • ✅ 타임아웃 설정이 충분한가?
  • ✅ 재시작 정책이 적절한가?
  • ✅ 로그 로테이션이 설정되어 있나?
  • ✅ 헬스체크가 구현되어 있나?
  • ✅ 알림이 설정되어 있나?


보안 점검

  • ✅ 서비스 파일 권한이 644인가?
  • ✅ 민감한 정보가 환경 변수에 하드코딩되지 않았나?
  • ✅ 불필요한 권한이 부여되지 않았나?

성능 점검

  • ✅ 컨테이너 리소스 제한이 설정되었나?
  • ✅ 로그 크기 제한이 있나?
  • ✅ 동시 시작 컨테이너 수가 적절한가?


마무리: systemd의 힘

restart 정책만으로는 해결할 수 없는 복잡한 시나리오에서 systemd 서비스 등록이 답입니다. 이제 여러분은:

  • 컨테이너 시작 순서를 완벽하게 제어할 수 있습니다
  • 시스템 로그와 통합된 모니터링을 구축했습니다
  • 장애 시 자동 복구와 알림을 설정했습니다
  • 기업 환경의 요구사항을 충족시켰습니다

처음에는 복잡해 보이지만, 한 번 설정해두면 수년간 안정적으로 작동합니다. 이 투자는 충분한 가치가 있습니다!

궁금한 점이나 특수한 상황이 있다면 댓글로 공유해주세요. 함께 해결해나가요!


참고 자료:

댓글 남기기