개발 트러블슈팅 완전 정복 | 웹개발·백엔드·데브옵스 실무 에러 해결법. Docker, GitHub Actions, TypeScript, MySQL, Redis 등 실전 경험 기반의 개발 블로그
수요일
Celery Worker 에러 해결 — 태스크가 처리되지 않을 때 완벽 가이드
증상: 태스크를 보냈는데 Worker가 반응이 없다
Celery 써본 사람이라면 한 번씩 겪어봤을 상황이다. .delay() 또는 .apply_async()로 태스크를 넣었는데 처리가 안 된다. 워커 로그를 보면 아예 조용하거나, 아래 같은 에러가 떠 있다.
또는 워커 프로세스는 살아 있는데 태스크 큐에 메시지가 쌓이기만 하고 소비가 안 된다. Celery + Django + Redis 조합에서 특히 자주 터진다.
원인 분류
Celery 워커가 태스크를 처리하지 못하는 원인은 크게 세 가지다.
1. 브로커(Redis/RabbitMQ)에 연결이 안 됨
가장 흔한 원인. Redis가 아예 안 떠 있거나, 포트나 URL이 틀렸다.
Django settings에서 CELERY_BROKER_URL 확인:
Docker Compose 쓸 때 실수가 잦다. 컨테이너 안에서 localhost는 자기 자신을 가리키기 때문에, Redis 컨테이너 이름(서비스명)을 써야 한다.
2. Celery 5.6.x 버전 버그 — Redis Reconnection 후 멈춤
이건 좀 억울한 케이스다. Celery 5.5.0에서 Kombu reconnection 버그를 고쳤는데, 5.6.x에서 같은 문제가 다시 들어왔다. 증상은 이렇다.
- 워커가 처음엔 잘 돌다가 Redis 재연결(failover, 재시작) 이후 멈춤
- 태스크가 큐에 들어오는 건 보이는데 워커가 pick-up 안 함
- 워커 프로세스는 살아 있음 (CPU 거의 0%)
5.6.x라면 다운그레이드하거나 패치 버전을 기다려야 한다.
3. Worker Concurrency 부족 — 큐는 차는데 처리 속도가 안 따라감
에러는 없는데 태스크가 밀리는 경우다. 기본 concurrency는 CPU 코어 수인데, I/O bound 작업이 많으면 이걸 올려야 한다.
디버깅 순서: 이 순서대로 확인해라
Step 1. 브로커 연결 직접 확인
에러 없이 통과하면 브로커 연결은 OK다.
Step 2. 간단한 태스크로 테스트
Step 3. 워커 로그 레벨 올려서 확인
[DEBUG/MainProcess] Received task: 라인이 안 뜨면 태스크가 워커까지 도달 자체를 못 하는 것. Received task:는 뜨는데 처리가 안 되면 concurrency 문제이거나 태스크 내부에서 죽는 것.
Step 4. Flower로 실시간 모니터링
http://localhost:5555에서 워커 상태, 태스크 큐, 처리 속도를 한눈에 볼 수 있다.
상황별 체크리스트
| 증상 | 체크 항목 | 해결 방법 |
|---|---|---|
| kombu connection refused | Redis 실행 여부 | redis-cli ping → Redis 시작 |
| Docker에서 Redis 연결 실패 | BROKER_URL 호스트명 | localhost → 서비스명 변경 |
| 워커 재시작 후 멈춤 | Celery 버전 | 5.5.0으로 다운그레이드 |
| 큐 적체, 에러 없음 | concurrency 설정 | --concurrency 값 증가 |
| 태스크 timeout | 작업 시간 초과 | soft_time_limit, time_limit 설정 |
| 특정 큐만 처리 안 됨 | 워커 큐 설정 | -Q 파라미터로 큐 명시 |
실무 설정 예시
Django settings.py — 권장 Celery 설정
Docker Compose 설정 예시
Docker Compose에서 depends_on: condition: service_healthy를 안 쓰면, Redis가 완전히 뜨기 전에 Celery worker가 연결 시도해서 connection refused가 뜬다.
Celery Beat (스케줄러) 별도로 떠야 한다
가끔 보면 periodic task가 실행이 안 된다는 이슈가 있는데, celery beat를 별도 프로세스로 안 띄워서 그런 경우가 있다.
beat와 worker를 같은 프로세스로 돌리는(-B 옵션) 건 개발환경에서만 써라. 프로덕션에서는 반드시 분리해야 한다.
마무리
Celery 문제는 대부분 브로커 연결 문제 아니면 버전 버그다. 위 순서대로 하나씩 확인하면 대부분 잡힌다. Docker 환경에서는 서비스명 vs localhost 혼동이 제일 많으니 그것부터 봐라.
실무에서 직접 겪은 내용 기반으로 작성했습니다. 틀린 부분 있으면 댓글로 알려주세요.
화요일
GitHub Actions secrets 환경변수 비어있음 해결 — secret not available 원인 분석
증상: 분명히 설정했는데 값이 없다고 한다
GitHub Actions 워크플로우는 돌리면 이런 상황이 생긴다.
Error: API_KEY is undefined
Error: Cannot read properties of undefined (reading 'length')
아니면 시크릿 값이 빈 문자열로 들어오거나, 더 황당하게는 배포가 그냥 조용히 실패한다. Secrets 탭에서 분명히 등록했는데 워크플로우가 못 읽는다.
CI 처음 세팅할 때, 또는 레포를 포크하거나 환경(Environment)을 새로 만들었을 때 이 문제를 자주 만난다.
원인 분류
1. 시크릿 이름 대소문자 불일치
가장 흔한 실수. Secrets UI에서 API_KEY로 등록했는데 워크플로우에서 ${{ secrets.api_key }}로 참조하면 빈 값이 온다. 시크릿은 대소문자 구분한다.
# 잘못된 예
env:
API_KEY: ${{ secrets.api_key }} # 실제 이름이 API_KEY면 못 읽음
# 올바른 예
env:
API_KEY: ${{ secrets.API_KEY }}
2. Environment 시크릿인데 job에 environment 지정 안 함
GitHub에서 환경(Environment)을 별도로 만들어서 거기에 시크릿을 등록했다면, job에서 그 environment를 명시해야 한다. 안 하면 해당 시크릿은 아예 조회 자체가 안 된다.
# 잘못된 예 — environment 지정 없이 env 시크릿 접근 시도
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo ${{ secrets.PROD_DB_URL }} # production environment의 시크릿이면 빈 값
# 올바른 예
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # ← 이게 있어야 environment 시크릿 접근 가능
steps:
- run: echo ${{ secrets.PROD_DB_URL }}
3. Fork PR에서는 시크릿 접근이 차단된다
외부 기여자가 fork해서 올린 PR의 워크플로우는 보안 정책상 시크릿에 접근할 수 없다. pull_request 이벤트 트리거를 쓰면 이 제한이 적용된다.
Warning: Context access might be invalid: secrets
의도적인 제한이다. 악의적인 코드가 PR로 들어와서 시크릿을 탈취하는 걸 막기 위한 것.
4. 시크릿 참조 문법 오류
# 틀린 문법들
${{ secret.API_KEY }} # secrets가 아니라 secret (오타)
${{ secrets[API_KEY] }} # 대괄호 안에 따옴표 필요
${{ env.secrets.API_KEY }} # env와 secrets 혼용
# 올바른 문법
${{ secrets.API_KEY }}
${{ secrets[env.SECRET_NAME] }} # 동적 키 참조는 이렇게
해결 방법
Step 1. 시크릿 이름 확인
# GitHub CLI로 현재 등록된 시크릿 목록 확인
gh secret list
# environment 시크릿 확인
gh secret list --env production
워크플로우 YAML의 이름과 정확히 일치하� 체크.
Step 2. 시크릿이 실제로 전달되는지 테스트
값을 직접 출력하면 안 된다 (마스킹됨). 대신�길이나 존재 여부를 확인:
steps:
- name: Check secrets
run: |
if [ -z "${{ secrets.API_KEY }}" ]; then
echo "API_KEY is EMPTY"
else
echo "API_KEY is set (length: ${#API_KEY})"
fi
env:
API_KEY: ${{ secrets.API_KEY }}
Step 3. Environment 시크릿 설정
레포 Settings → Environments → 환경 선택 → Environment secrets에서 등록했다면:
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # 반드시 명시
steps:
- name: Deploy
env:
DB_URL: ${{ secrets.DB_URL }}
API_KEY: ${{ secrets.API_KEY }}
run: ./deploy.sh
environment 이름은 Environments 탭에 있는 이름과 정확히 일치해야 한다.
Step 4. Fork PR 시크릿 접근 처리
내부 PR이라면 pull_request_target 이벤트 사용을 고려할 수 있다. 단, pull_request_target은 base 브랜치의 코드를 실행하므로 PR 코드를 checkout해서 실행하면 보안 취약점이 된다.
# 안전한 패턴 — PR 코드는 테스트만 하고, 배포는 main 병합 후에
on:
pull_request:
# 시크릿 없이 테스트만
push:
branches: [main]
# 여기서 시크릿 써서 배포
Step 5. Organization 시크릿 접근 권한
조직(organization) 레벨 시크릿은 레포별로 접근 권한이 따로 있다. 조직 Settings → Secrets → 해당 시크릿 → Repository access에서 해당 레포가 포함되어 있는지 확인.
상황별 체크리스트
| 증상 | 원인 | 조치 |
|---|---|---|
| 시크릿 값이 빈 문자열 | 이름 대소문자 불일치 | gh secret list로 정확한 이름 확인 |
| environment 시크릿 못 읽음 | job에 environment 미지정 | environment: 필드 추가 |
| Fork PR에서만 실패 | 보안 정책으로 차단 | 시크릿 없이 동작하도록 CI 설계 변경 |
| 조직 시크릿 못 읽음 | 레포 접근 권한 없음 | Organization 설정에서 레포 추가 |
| 로컬에서는 되는데 CI에서만 | 환경변수 주입 누락 | env: 블록에서 명시적 주입 확인 |
자주 하는 실수 — 디버깅 시 값 출력하려다 마스킹에 막히는 경우
# 이렇게 하면 *** 로 마스킹되어 아무 의미 없음
- run: echo ${{ secrets.API_KEY }}
# 디버깅 목적이면 이렇게
- run: |
echo "Length: ${#MY_SECRET}"
echo "First char: ${MY_SECRET:0:1}"
env:
MY_SECRET: ${{ secrets.API_KEY }}
시크릿 값 자체를 로그에 출력하는 건 GitHub이 자동 마스킹한다. 길이나 첫 글자 정도로 존재 여부 확인하는 게 현실적인 디버깅 방법이다.
실무 팁: 시크릿 관리 패턴
# 공통 시크릿은 레포 레벨에
# 환경별 시크릿(prod DB URL 등)은 environment 레벨에 분리
jobs:
test:
runs-on: ubuntu-latest
# environment 없음 — 레포 레벨 시크릿만 접근
steps:
- run: npm test
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # 레포 레벨 시크릿
deploy-prod:
runs-on: ubuntu-latest
environment: production # environment 레벨 시크릿 접근
needs: test
steps:
- run: ./deploy.sh
env:
DB_URL: ${{ secrets.DB_URL }} # production environment 시크릿
API_KEY: ${{ secrets.API_KEY }} # production environment 시크릿
시크릿을 환경별로 분리하면 실수로 개발용 키를 프로덕션에 쓰는 사고를 막을 수 있다.
정리
GitHub Actions 시크릿이 안 읽히는 케이스는 이름 대소문자 불일치, environment 미지정, fork PR 보안 정책 세 가지가 대부분을 차지한다. gh secret list로 정확한 이름 확인하고, environment 시크릿이면 job에 environment: 명시하는 것만 체크해도 80%는 해결된다.