서버가 재부팅되었는데 중요한 컨테이너가 올라오지 않아 당황한 적 있으신가요? 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
서비스 시작 순서:
- Docker → PostgreSQL → Redis
- API Gateway
- Auth, Payment, Notification 서비스 (병렬)
- 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 서비스 등록이 답입니다. 이제 여러분은:
- 컨테이너 시작 순서를 완벽하게 제어할 수 있습니다
- 시스템 로그와 통합된 모니터링을 구축했습니다
- 장애 시 자동 복구와 알림을 설정했습니다
- 기업 환경의 요구사항을 충족시켰습니다
처음에는 복잡해 보이지만, 한 번 설정해두면 수년간 안정적으로 작동합니다. 이 투자는 충분한 가치가 있습니다!
궁금한 점이나 특수한 상황이 있다면 댓글로 공유해주세요. 함께 해결해나가요!
참고 자료: