Discord slash command 기반 음악 봇입니다. 현재 저장소는 gateway-app + audio-node-app + common-core 구조로 분리되어 있고, 사용자 응답은 기본적으로 Discord ephemeral reply로 처리되며, 명령 전달은 RabbitMQ 비동기 이벤트 흐름으로 구성되어 있습니다.
단일 앱 안에서 모든 것을 처리하는 형태가 아니라, Discord 진입점과 실제 재생 워커를 분리해서 운영하는 구조입니다. 덕분에 slash command 응답, 재생 처리, 상태 복구, 관측성을 각 책임에 맞게 나눠서 관리할 수 있습니다.
이 프로젝트는 Discord 서버에서 음악 명령을 처리하기 위한 봇입니다.
gateway-app- Discord slash command 진입점
deferReply(true)기반 deferred ephemeral 응답 시작- RabbitMQ command publish
- command result event를 받아 original ephemeral reply 수정
audio-node-app- RabbitMQ command consumer
- 실제 음성 채널 연결, 로드, 재생, 복구, 유휴 퇴장 처리
- command 처리 결과를 RabbitMQ result event로 발행
modules/common-core- 공용 command / event 계약
- playback 코어 로직
- Redis / RabbitMQ / JDA 연동
- 공용 Spring bootstrap / observability 설정
- Discord slash command 기반 음악 제어
- deferred ephemeral reply 기반 사용자별 응답
- RabbitMQ 비동기 command publish / result event consume
- Redis 기반 재생 상태 공유 및 복구
- audio-node 재기동 이후 playback recovery
- 음성 채널 유휴 상태 감지 및 자동 퇴장
- Prometheus, Grafana, Loki, Alloy 기반 관측성 구성
- GitHub Actions + remote deploy 스크립트 기반 배포 자동화
flowchart LR
U[Discord User]
D[Discord API]
subgraph APPS[Docker Compose Stack]
G[gateway-app]
A[audio-node-app]
R[(Redis)]
MQ[(RabbitMQ)]
end
U --> D
D --> G
G -->|MusicCommandEnvelope publish| MQ
MQ -->|command consume| A
A -->|MusicCommandResultEvent publish| MQ
MQ -->|result consume| G
A -->|state read/write| R
A -->|voice/playback| D
현재 명령 처리 경로는 다음과 같습니다.
- 사용자가 slash command를 실행합니다.
gateway-app이deferReply(true)로 응답을 선점합니다.- gateway가
MusicCommandEnvelope를 RabbitMQ로 발행합니다. audio-node-app이 command를 소비하고 실제 재생 로직을 수행합니다.- 결과를
MusicCommandResultEvent로 다시 RabbitMQ에 발행합니다. - gateway가 result event를 받아 original ephemeral reply를 수정합니다.
즉, 현재 구조는 RPC 응답을 기다리는 방식이 아니라 비동기 command / result 이벤트 흐름으로 Discord 응답을 마무리합니다.
- 애플리케이션 진입점, 설정, 메시징, 관측성 구성을 일관되게 관리하기 좋습니다.
- actuator, configuration, bootstrap 구성을 통해 운영 편의성을 확보했습니다.
- Discord slash command와 voice gateway를 다루기 위한 핵심 라이브러리입니다.
- interaction 응답과 음성 연결 책임을 명확하게 분리하기 좋았습니다.
- gateway와 audio-node 사이 command / result transport 역할을 맡습니다.
- request/reply보다 비동기 publish / consume 구조가 현재 봇 아키텍처와 더 잘 맞습니다.
- shared source of truth 역할을 합니다.
- guild state, queue state, player state, processed command 등을 보관하고 복구 흐름의 기반이 됩니다.
- gateway, audio-node, Redis, RabbitMQ, observability stack을 한 번에 띄우기 쉽습니다.
- 로컬 개발과 원격 서버 운영의 차이를 줄이는 데 유리합니다.
- 메트릭, 로그, 알림을 한 스택으로 묶어서 운영할 수 있습니다.
- 현재 구조에서는 ELK보다 가볍고, compose 환경에서 바로 붙이기 좋습니다.
apps/
gateway-app/
src/main/java/discordgateway/gateway/
application/
config/
interaction/
messaging/
presentation/discord/
audio-node-app/
src/main/java/discordgateway/audionode/
config/
lifecycle/
recovery/
modules/
common-core/
src/main/java/discordgateway/common/
bootstrap/
command/
event/
src/main/java/discordgateway/playback/
application/
audio/
domain/
src/main/java/discordgateway/infra/
audio/
discord/
messaging/rabbit/
redis/
docs/
ops/
docker-compose.yml
deploy.sh
apps/gateway-app- Discord interaction 처리
- command envelope 생성
- pending interaction 관리
- result event 소비 후 사용자 응답 수정
apps/audio-node-app- command 소비
- 실제 playback 실행
- recovery / idle disconnect 수행
- result event 발행
modules/common-core- 공용 계약과 playback 코어 로직 제공
- Redis, RabbitMQ, Discord/JDA 연동 구현 포함
Copy-Item .env.example .env최소 필요 값:
DISCORD_TOKENRABBITMQ_USERNAMERABBITMQ_PASSWORD
선택 값:
DISCORD_DEV_GUILD_IDYOUTUBE_REFRESH_TOKENYOUTUBE_PO_TOKENYOUTUBE_VISITOR_DATAYOUTUBE_REMOTE_CIPHER_*GRAFANA_*
.\gradlew.bat bootJarAll산출물:
apps/gateway-app/build/libs/gateway-app.jarapps/audio-node-app/build/libs/audio-node-app.jar
기본 스택:
docker compose up -d --build관측성 포함:
docker compose --profile observability up -d --buildGateway:
.\gradlew.bat :apps:gateway-app:bootJar
java -jar apps/gateway-app/build/libs/gateway-app.jarAudio Node:
.\gradlew.bat :apps:audio-node-app:bootJar
java -jar apps/audio-node-app/build/libs/audio-node-app.jar- Gateway actuator:
8081 - Audio Node actuator:
8082 - Redis:
6379 - RabbitMQ AMQP:
5672 - RabbitMQ UI:
15672 - Grafana:
3000 - Prometheus:
9090 - Loki:
3100 - Alloy UI:
12345
현재 관측성 스택은 아래 구성으로 정리되어 있습니다.
- Prometheus metrics
- ECS JSON structured logging
- Loki + Alloy 로그 수집
- Grafana datasource / dashboard provisioning
- Prometheus alert rule
- Grafana managed alert rule
- Discord webhook 알림 경로
기본 수신처는 observability-noop입니다. 실제 Discord 알림을 켜려면 아래 값을 설정하면 됩니다.
GRAFANA_ALERT_DEFAULT_RECEIVER=observability-discordGRAFANA_ALERT_DISCORD_WEBHOOK_URL=<실제 webhook>
OBSERVABILITY_ENABLED=true면 원격 서버에서 prometheus, loki, alloy, redis-exporter, grafana도 함께 기동합니다.
GitHub Actions는 main push 기준으로 동작하며, 현재 배포는 아래를 포함합니다.
gateway-app이미지audio-node-app이미지docker-compose.ymlops/ops/observability/.env.cicd
원격 서버에서는 deploy.sh가 release 디렉터리를 만들고 compose를 다시 올리는 방식으로 동작합니다.
운영 중 자주 쓰는 명령 예시는 다음과 같습니다.
전체 상태 확인:
docker compose --env-file .env ps관측성 포함 상태 확인:
docker compose --profile observability --env-file .env ps앱 로그 확인:
docker compose --project-name discord-bot --env-file .env logs gateway --tail=200
docker compose --project-name discord-bot --env-file .env logs audio-node --tail=200배포 직후 smoke check:
bash /home/ubuntu/dis-bot/current/ops/smoke-check.sh- Discord 응답을 기본적으로 ephemeral로 유지해 서버 채널에 공개 메시지를 남기지 않도록 구성했습니다.
- gateway와 audio-node를 분리해 slash command 응답과 실제 playback 워크로드를 나눴습니다.
- Redis를 source of truth로 사용해 재기동 이후 recovery 흐름을 설계했습니다.
- observability stack을 compose 기반으로 붙여 운영 상태 확인과 알림 구성을 빠르게 할 수 있게 했습니다.
- GitHub Actions와 remote deploy 스크립트를 묶어 배포 절차를 반복 가능하게 만들었습니다.
- 원격 서버에서 YouTube 재생 결과가 로컬과 다르게 실패하는 경우가 있습니다.
- 현재까지는 코드 자체보다 서버 IP/ASN, YouTube anti-bot 응답 차이 영향 가능성이 더 크게 보입니다.
- Grafana 관리자 계정은 최초 기동 시점의 env 값이 우선 적용됩니다.
- Loki 기반 로그 알림 추가
- 비즈니스 메트릭 추가
music_commands_totalmusic_command_duration_secondsmusic_track_load_failures_totalmusic_recovery_attempts_total
- ngrok 또는 reverse proxy 환경에서 관측성 접근 경로 정리
- 배포 중 무중단성 개선