Redis 기반 태스크 디스패처 시스템으로, FastAPI Producer와 다수의 Worker로 구성됩니다.
모든 서비스(API, Worker, Locust)가 공유하는 단일 컨테이너 이미지를 정의합니다.
| 항목 | 내용 |
|---|---|
| 베이스 이미지 | python:3.13.0-slim |
| 추가 런타임 | OpenJDK 17 (headless) |
| 비루트 사용자 | appuser (보안 강화) |
| 의존성 설치 | requirements.txt 레이어 캐싱 활용 |
| 소스 복사 | app/, tests/ |
| 기본 CMD | uvicorn app.api:app (포트 8000) |
Worker와 Locust는 docker-compose.yml에서 command를 오버라이드하여 동일 이미지로 다른 역할을 수행합니다.
전체 서비스 스택을 정의하며, podman compose 또는 docker compose로 실행할 수 있습니다.
| 서비스 | 이미지/빌드 | 포트 | 역할 |
|---|---|---|---|
redis |
redis:7-alpine |
6379 | 메시지 브로커 (태스크 큐) |
api |
Containerfile 빌드 | 8000 | FastAPI Producer – 태스크 생성 API |
worker-1 |
Containerfile 빌드 | – | Consumer – 태스크 처리 워커 |
worker-2 |
Containerfile 빌드 | – | Consumer – 태스크 처리 워커 |
locust |
Containerfile 빌드 | 8089 | 부하 테스트 (Locust Web UI) |
주요 특징:
- 헬스체크: Redis(
redis-cli ping)와 API(/health엔드포인트`) 헬스체크 설정 - 의존성 순서: Redis → API → Locust, Redis → Worker 순으로 기동
- 자동 재시작: 모든 서비스에
restart: unless-stopped적용 - 볼륨:
redis-dataNamed Volume으로 Redis 데이터 영속화
# 전체 스택 빌드 및 실행
podman compose up -d --build
# 중지
podman compose downWindows 환경의 Podman Machine에서 systemd(Quadlet)를 이용한 컨테이너 자동 재시작 설정 가이드입니다.
Podman은 Docker와 달리 데몬이 없어 restart: unless-stopped 같은 정책이 자동으로 동작하지 않습니다. Quadlet은 systemd를 통해 컨테이너를 관리하며, 비정상 종료 시 자동 재시작을 안정적으로 지원합니다.
| systemd 설정 | 동작 | Docker 정책 대응 |
|---|---|---|
Restart=on-failure |
비정상 종료 시만 재시작 | ≈ unless-stopped |
Restart=always |
어떤 종료든 재시작 | ≈ always |
Restart=no |
재시작 안 함 | ≈ no |
# Podman Machine에 SSH 접속
podman machine ssh이하 모든 명령은 podman machine ssh 내부에서 실행합니다.
mkdir -p ~/.config/containers/systemd/Compose와 달리 Quadlet은 공유 네트워크를 자동 생성하지 않으므로 직접 설정해야 합니다.
cat > ~/.config/containers/systemd/dispatcher.network << 'EOF'
[Network]
NetworkName=dispatcher-net
[Install]
WantedBy=default.target
EOFcat > ~/.config/containers/systemd/dispatcher-redis.container << 'EOF'
[Unit]
Description=Task Dispatcher Redis
After=local-fs.target
[Container]
ContainerName=dispatcher-redis
Image=docker.io/library/redis:7-alpine
PublishPort=6379:6379
Volume=redis-data:/data
Network=dispatcher.network
[Service]
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=default.target
EOFcat > ~/.config/containers/systemd/dispatcher-api.container << 'EOF'
[Unit]
Description=Task Dispatcher API
After=dispatcher-redis.service
[Container]
ContainerName=dispatcher-api
Image=docker.io/library/task-dispatcher-api:latest
PublishPort=8000:8000
Network=dispatcher.network
Environment=REDIS_HOST=dispatcher-redis
Environment=REDIS_PORT=6379
[Service]
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=default.target
EOFcat > ~/.config/containers/systemd/dispatcher-worker-1.container << 'EOF'
[Unit]
Description=Task Dispatcher Worker 1
After=dispatcher-redis.service
[Container]
ContainerName=dispatcher-worker-1
Image=docker.io/library/task-dispatcher-worker-1:latest
Network=dispatcher.network
Environment=REDIS_HOST=dispatcher-redis
Environment=REDIS_PORT=6379
Exec=python -m app.worker_demo
[Service]
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=default.target
EOFcat > ~/.config/containers/systemd/dispatcher-worker-2.container << 'EOF'
[Unit]
Description=Task Dispatcher Worker 2
After=dispatcher-redis.service
[Container]
ContainerName=dispatcher-worker-2
Image=docker.io/library/task-dispatcher-worker-2:latest
Network=dispatcher.network
Environment=REDIS_HOST=dispatcher-redis
Environment=REDIS_PORT=6379
Exec=python -m app.worker_demo
[Service]
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=default.target
EOFcat > ~/.config/containers/systemd/dispatcher-locust.container << 'EOF'
[Unit]
Description=Task Dispatcher Locust
After=dispatcher-api.service
[Container]
ContainerName=dispatcher-locust
Image=docker.io/library/task-dispatcher-locust:latest
PublishPort=8089:8089
Network=dispatcher.network
Environment=LOCUST_HOST=http://dispatcher-api:8000
Exec=locust -f /app/tests/locustfile.py --host http://dispatcher-api:8000 --web-host 0.0.0.0
[Service]
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=default.target
EOFsystemctl --user daemon-reload
systemctl --user start dispatcher-redis.service
systemctl --user start dispatcher-api.service
systemctl --user start dispatcher-worker-1.service
systemctl --user start dispatcher-worker-2.service
systemctl --user start dispatcher-locust.serviceloginctl enable-linger $(whoami)cat > ~/dispatcher-start.sh << 'EOF'
#!/bin/bash
systemctl --user start dispatcher-redis.service
systemctl --user start dispatcher-api.service
systemctl --user start dispatcher-worker-1.service
systemctl --user start dispatcher-worker-2.service
echo "All services started."
podman ps
EOF
chmod +x ~/dispatcher-start.shcat > ~/dispatcher-stop.sh << 'EOF'
#!/bin/bash
systemctl --user stop dispatcher-worker-2.service
systemctl --user stop dispatcher-worker-1.service
systemctl --user stop dispatcher-api.service
systemctl --user stop dispatcher-redis.service
echo "All services stopped."
podman ps
EOF
chmod +x ~/dispatcher-stop.shcat > ~/dispatcher-restart.sh << 'EOF'
#!/bin/bash
systemctl --user restart dispatcher-redis.service
systemctl --user restart dispatcher-api.service
systemctl --user restart dispatcher-worker-1.service
systemctl --user restart dispatcher-worker-2.service
echo "All services restarted."
podman ps
EOF
chmod +x ~/dispatcher-restart.shcat > ~/dispatcher-locust-start.sh << 'EOF'
#!/bin/bash
systemctl --user start dispatcher-locust.service
echo "Locust started."
podman ps
EOF
chmod +x ~/dispatcher-locust-start.shcat > ~/dispatcher-locust-stop.sh << 'EOF'
#!/bin/bash
systemctl --user stop dispatcher-locust.service
echo "Locust stopped."
podman ps
EOF
chmod +x ~/dispatcher-locust-stop.sh~/dispatcher-start.sh
~/dispatcher-stop.sh
~/dispatcher-restart.sh
~/dispatcher-locust-start.sh
~/dispatcher-locust-stop.shpodman machine ssh "~/dispatcher-start.sh"
podman machine ssh "~/dispatcher-stop.sh"
podman machine ssh "~/dispatcher-restart.sh"
podman machine ssh "~/dispatcher-locust-start.sh"
podman machine ssh "~/dispatcher-locust-stop.sh"# 1. Windows 터미널에서 이미지 재빌드
podman compose build
# 2. 서비스 재시작
podman machine ssh "~/dispatcher-restart.sh"# 서비스 상태 확인
systemctl --user status dispatcher-api.service
# 로그 확인
journalctl --user -u dispatcher-api.service --no-pager -n 30
# Quadlet 파일 확인
cat ~/.config/containers/systemd/dispatcher-api.container
# Quadlet 생성 결과 확인
/usr/libexec/podman/quadlet -dryrun -user
# 실행 중인 컨테이너 확인
podman pspodman kill dispatcher-api
sleep 5
podman ps # dispatcher-api가 다시 Running 상태로 복구되어야 함systemctl --user stop dispatcher-locust.service dispatcher-worker-2.service dispatcher-worker-1.service dispatcher-api.service dispatcher-redis.service
rm -f ~/.config/containers/systemd/dispatcher-*.container
rm -f ~/.config/containers/systemd/dispatcher.network
systemctl --user daemon-reload
rm -f ~/dispatcher-start.sh ~/dispatcher-stop.sh ~/dispatcher-restart.sh
rm -f ~/dispatcher-locust-start.sh ~/dispatcher-locust-stop.sh- Compose와 Quadlet을 동시에 사용하지 마세요. 같은 컨테이너 이름/포트로 충돌합니다.
docker-compose.yml의restart: unless-stopped는 Docker 이식성을 위해 그대로 유지하되, Podman에서는 Quadlet으로 관리합니다.- Quadlet 파일 수정 후 반드시
systemctl --user daemon-reload를 실행하세요.