Troubleshooting 2026.08.10 4 min read

rclone Google Drive RATE_LIMIT_EXCEEDED 해결기

백업 Job에 반복된 RATE_LIMIT_EXCEEDED를 조사했다. 전용 OAuth client로 바꾸고 수동·정기 백업과 원격 snapshot 복원까지 확인했다.

이슈 발생: 2026-08-09 · 발행 기준: 2026-08-10 08:26 KST
환경: Kubernetes CronJob, restic, rclone, Google Drive

Ghost 데이터를 restic으로 암호화하고 rclone을 통해 Google Drive에 보내도록 구성했다. 백업 Job이 재시도 끝에 성공한 경우에도 로그에는 RATE_LIMIT_EXCEEDED가 반복됐다. Job의 성공 표시와 별개로 전송 상태를 확인해야 했다.

확인한 원인은 OAuth client와 API quota였다. rclone 공용 OAuth client는 여러 사용자가 quota를 공유하므로 영향을 받을 수 있었다. 전용 Google OAuth client로 바꾼 뒤 수동 백업과 정기 백업에서 오류가 사라졌다. Drive의 저장 용량 문제는 아니었다.

백업 경로

Ghost content PVC ─┐
                   ├→ backup Job → restic 암호화 snapshot
MySQL dump ────────┘                 → rclone → Google Drive

어느 도구에서 실패했는지 확인하려고 역할을 나눠 봤다.

  • restic: 파일 묶음, 암호화, 중복 제거, snapshot, 무결성 검사
  • rclone: Google Drive 같은 원격 저장소와의 전송 및 OAuth 인증

RATE_LIMIT_EXCEEDED는 대개 rclone 전송 계층에서 보인다. restic repository 손상과 같은 의미는 아니다.

먼저 확인한 것

kubectl -n backup get cronjob,job,pod
kubectl -n backup logs job/<job-name> --all-containers
kubectl -n backup logs job/<job-name> --all-containers | \
  grep -E 'RATE_LIMIT|quota|403|429|error'

로그와 실제 생성 결과를 아래 순서로 확인했다.

  1. HTTP 403인가 429인가?
  2. 오류 reason이 rateLimitExceeded, userRateLimitExceeded, dailyLimitExceeded 중 무엇인가?
  3. 재시도 후 snapshot은 실제로 생성됐는가?
  4. 같은 시간에 여러 Job 또는 서버가 같은 OAuth client로 대량 전송했는가?
  5. rclone이 자체 client ID가 아니라 기본 공용 client를 쓰고 있는가?

JSON 응답의 reason과 message를 함께 확인했다. 같은 오류 코드라도 원인에 따라 재시도나 지수 백오프, 요청량 축소, quota 확인, 전용 OAuth client 전환 중 필요한 조치가 달라진다.

전용 OAuth client로 분리

Google Cloud Console에서 별도의 Desktop OAuth client를 만들고 Google Drive API를 활성화했다. 그다음 rclone remote를 다시 승인해 새 refresh token을 받았다.

rclone config

rclone 설정에 들어가는 항목은 아래와 같다. 실제 자격 증명은 문서나 Git에 저장하지 않았다.

[gdrive]
type = drive
client_id = <dedicated-client-id>
client_secret = <dedicated-client-secret>
scope = drive
token = <oauth-token-json>

Kubernetes에는 이 파일을 Secret으로 주입했다. 평문 출력, shell history와 CI 로그에 비밀값이 남지 않도록 확인했다.

# 값 자체를 출력하지 않고 Secret 존재와 갱신 시각만 확인
kubectl -n backup get secret <rclone-secret-name>
kubectl -n backup describe secret <rclone-secret-name>

kubectl get secret -o yaml은 base64일 뿐 암호화된 비밀 표시가 아니다. 블로그, 메신저, CI 로그에 그대로 복사하면 안 된다.

재시도만으로 끝내면 안 되는 이유

일시적인 rate limit에는 지수 백오프가 필요하다. 다만 공용 client quota의 영향을 계속 받는다면 재시도만으로 원인이 없어지지는 않는다. 백업이 길어져 다음 CronJob과 겹치면 요청량도 늘 수 있어 중복 실행을 막았다.

spec:
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3

concurrencyPolicy: Forbid는 이전 백업이 끝나기 전에 새 Job이 겹치는 상황을 막는다. 다만 이것도 전용 OAuth client를 대신하지는 않는다.

수정 후 검증

전용 OAuth client와 새 token을 Kubernetes Secret에 반영하고 먼저 수동 Job을 실행했다. 이어 실제 CronJob 시각까지 기다려 정기 백업도 확인했다.

# 기존 CronJob을 기준으로 일회성 검증 Job 생성
kubectl -n backup create job --from=cronjob/<cronjob-name> <manual-job-name>
kubectl -n backup wait --for=condition=complete \
  job/<manual-job-name> --timeout=30m
kubectl -n backup logs job/<manual-job-name> --all-containers

Job의 Complete 표시와 함께 아래 결과를 확인했다.

  • 로그의 quota/auth 오류 0건
  • 새 restic snapshot 생성
  • restic snapshots에서 시간과 경로 확인
  • restic check 성공
  • 별도 복구 위치에서 실제 restore 성공
  • 다음 정기 CronJob도 같은 결과

업로드 뒤에는 별도 위치로 실제 복원까지 확인했다.

재발 방지

  • rclone용 OAuth client를 다른 서비스와 분리한다.
  • refresh token, client secret, restic password를 각각 Secret으로 관리한다.
  • CronJob 중복 실행을 막고 실패 알림을 둔다.
  • 403/429와 Google API reason을 모니터링한다.
  • 정기적으로 restic check와 격리 복구 시험을 수행한다.
  • 비밀값이 로그에 노출되면 즉시 폐기하고 재발급한다.

참고 자료

SEARCH JOURNAL

기록 검색

글 제목, 요약, 태그에서 찾습니다.

검색을 준비하고 있어요.

검색어를 입력해 주세요.

    Ctrl/⌘ K 검색 Esc 닫기 ↑ ↓ 결과 이동