Mutisite와 Cloudflare R2 객체

Discourse 멀티사이트 설치에서 Cloudflare R2를 단일 사이트에만 사용하는 방법

멀티사이트 Discourse 설치를 운영 중이며, 하나의 사이트만 Cloudflare R2에(업로드, 백업, 그리고 선택적으로 정적 자산) 두고 싶다면, 표준 업로드용 S3 호환 객체 저장소 프로바이더 설정 가이드가 멀티사이트 환경의 특수성을 완전히 다루지 않습니다. 이 게시글에서는 실제로 일어나는 과정, 사이트별 설정과 클러스터 전체 설정의 차이, 그리고 해결하는 데 상당한 시간이 걸린 몇 가지 함정들을 다루고 있습니다.

핵심 이해 포인트: GlobalSetting vs SiteSetting

이 설정의 모든 것은 하나의 구분에 달려 있습니다:

  • app.yml 환경 변수(DISCOURSE_*)는 **GlobalSetting**이 됩니다. 컨테이너 시작 시 프로세스 환경에서 한 번만 읽히며, 클러스터 내 모든 사이트에 공유됩니다. RAILS_DB는 이 변수들에 영향을 미치지 않습니다.
  • 관리자 UI 필드는 일반적인 **SiteSetting**입니다. 각 사이트의 자체 데이터베이스에 사이트별로 저장되며, 해당 사이트에만 실제로 적용됩니다.

어떤 항목에 대해 GlobalSetting이 존재한다면, 이는 Admin UI에서 해당 SiteSetting 필드를 조용히 덮어쓰고 숨깁니다. 즉: app.yml에 넣는 것은 예외 없이, RAILS_DB 우회책 없이 모든 사이트에 적용됩니다.

1부 — 업로드 및 백업 (진정으로 사이트별, 간단함)

이 부분은 기대하듯 정확히 작동합니다. enable_s3_uploads, s3_upload_bucket, backup_location, s3_backup_bucket 및 자격 증명 필드는 모두 일반적인 사이트 설정입니다. R2에 두려는 특정 사이트에 로그인한 상태에서 Admin → Settings → “S3” 검색을 통해 단독으로 설정하고, app.yml은 건드리지 마세요. 클러스터의 다른 사이트들은 로컬에 계속 저장됩니다.

R2용 샘플 값:

Enable S3 uploads = true
Enable direct S3 uploads = true
S3 access key ID / secret access key = <your R2 token>
S3 region = auto
S3 upload bucket = <bucket name>
S3 endpoint = https://<account-id>.r2.cloudflarestorage.com
S3 CDN URL = https://uploads.yourdomain.com
S3 use ACLs = false   (R2는 객체 ACL이 아닌 버킷 수준 권한을 사용합니다)
S3 backup bucket = <backup bucket name>
Backup location = S3

버킷의 CORS 정책을 Cloudflare 대시보드에서 직접 설정하세요 (R2는 Discourse의 CORS rake 작업이 필요하지 않습니다):

[
  {
    "AllowedOrigins": ["https://your-site.tld"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
    "AllowedHeaders": ["*"],
    "ExposeHeaders": ["ETag"],
    "MaxAgeSeconds": 3000
  }
]

2부 — 기존 로컬 업로드 마이그레이션

rake uploads:migrate_to_s3오직 환경 변수에서만 S3 설정을 읽습니다. Admin UI에 무엇이 구성되어 있든 관계없이 사이트 설정으로의 폴백이 전혀 없습니다. 이는 구성 실수가 아닌 작업의 실제 결함입니다. app.yml을 건드리지 않고 일회성 실행을 위해 자격 증명을 인라인으로 전달하세요:

./launcher enter app

RAILS_DB=default \
DISCOURSE_S3_REGION=auto \
DISCOURSE_S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com \
DISCOURSE_S3_BUCKET=<bucket name> \
DISCOURSE_S3_ACCESS_KEY_ID=<key> \
DISCOURSE_S3_SECRET_ACCESS_KEY=<secret> \
rake uploads:migrate_to_s3

이 환경 변수들은 해당 셸 프로세스에만 존재합니다 — 종료 시 아무것도 영구적으로 저장되지 않습니다.

최신 AWS SDK 버전에서 체크섬 오류

다음과 같은 오류가 발생하면:

Aws::S3::Errors::InvalidRequest: You can only specify one non-default checksum at a time.

이는 최근 aws-sdk-core 버전(기본적으로 CRC32 체크섬을 전송하도록 설정됨)과 R2 간의 알려진 호환성 문제입니다. 동일한 명령에 다음 두 환경 변수를 추가하여 수정하세요:

export AWS_REQUEST_CHECKSUM_CALCULATION=when_required
export AWS_RESPONSE_CHECKSUM_VALIDATION=when_required

대부분 성공한 실행 후 남은 “미마이그레이션” 기록

작업이 1 of 1291 uploads are not migrated와 같은 메시지로 끝나면 당황하지 마세요 — 나머지는 이미 마이그레이션되었고 DB URL은 이미 재작성되었습니다. rails c에서 남은 항목을 찾으세요:

base_url = File.join(SiteSetting.Upload.s3_base_url, "original/")
Upload.by_users.where("url NOT LIKE '#{base_url}%'").pluck(:id, :url, :original_filename)

제 경우, 이는 R2의 원시 엔드포인트 스타일 URL을 사용하는 방치된 Upload 기록(백업 로그 zip)이었으며, 체크가 기대하는 CDN URL 형식이 아니었습니다 — 이는 실제 실패가 아닌 오탐이었습니다. URL을 수정하거나, 의미 있는 콘텐츠가 아니라면 기록을 삭제하세요.

3부 — 정적 자산 (JS/CSS) — 진정으로 클러스터 전체인 부분

여기가 "단일 사이트에만"이라는 목표가 벽에 부딪히는 곳입니다. 컴파일된 자산은 멀티사이트 클러스터 전체에 공유됩니다 — 사이트별로 하나씩이 아니라 하나의 컴파일된 JS/CSS 번들만 존재합니다. 자산이 R2에서 제공되느냐 로컬에서 제공되느냐는 GlobalSetting.use_s3?를 통해 Rails 시작 시 한 번만 결정되며, 이 부분에 대한 사이트별 오버라이드는 없습니다.

자산을 R2로 오프로드하려면, 연결 정보(업로드 활성화 플래그가 아닌)를 app.yml에 넣어야 합니다:

env:
  DISCOURSE_USE_S3: true
  DISCOURSE_S3_REGION: auto
  DISCOURSE_S3_ENDPOINT: https://<account-id>.r2.cloudflarestorage.com
  DISCOURSE_S3_ACCESS_KEY_ID: "xxx"
  DISCOURSE_S3_SECRET_ACCESS_KEY: "xxx"
  DISCOURSE_S3_BUCKET: <bucket name>
  DISCOURSE_S3_CDN_URL: https://uploads.yourdomain.com
  AWS_REQUEST_CHECKSUM_CALCULATION: when_required
  AWS_RESPONSE_CHECKSUM_VALIDATION: when_required

hooks:
  after_assets_precompile:
    - exec:
        cd: $home
        cmd:
          - sudo -E -H -u discourse bundle exec rake s3:upload_assets
          - sudo -E -H -u discourse bundle exec rake s3:expire_missing_assets

참고:

  • DISCOURSE_CDN_URL을 설정하지 마세요. DISCOURSE_S3_CDN_URL만 설정하세요. 둘 다 설정하고 메인 도메인이 Cloudflare를 통해 프록시되는 경우, 메인 S3 가이드의 경고에 따라 리다이렉트 루프가 발생합니다.
  • bundle exec rake를 사용하고, bundle rake는 사용하지 마세요 (쉬운 오타) — 그리고 sudo -E -H -u discourse를 사용하세요 (-Hdiscourse 사용자를 위해 HOME을 올바르게 설정합니다; 없으면 Bundler가 실행될 때마다 임시 디렉터리로 폴백합니다).
  • 자산 오프로드는 두 사이트 모두에 영향을 미칩니다. 동일한 컴파일된 번들을 사용하므로 두 번째 사이트의 <script>/<link> 태그도 R2 CDN URL로 해석되기 시작합니다. 버킷의 CORS AllowedOrigins에 모든 사이트의 도메인이 포함되어 있는지 확인하세요.
  • 이는 두 번째 사이트의 실제 업로드를 S3로 강제하지 않습니다enable_s3_uploads는 여전히 자산 서빙 GlobalSetting과 독립적인 진정으로 사이트별 설정입니다. 재빌드 후 해당 사이트의 DB에서 rails cSiteSetting.Upload.enable_s3_uploads를 확인하세요.

3부 — USE_DB_S3_CONFIG — 실제로 하는 일 (과 하지 않는 일)

일부 커뮤니티 설정(예: Bitnami 차트)에서 s3:upload_assets가 환경 변수 대신 사이트 설정에서 자격 증명을 읽도록 하는 방법으로 USE_DB_S3_CONFIG=true를 참조하는 것을 볼 수 있습니다. 업로드 작업 자체에는 작동합니다 — 하지만 렌더링 시 자산 URL이 CDN으로 재작성되는지를 실제로 제어하는 플래그인 GlobalSetting.use_s3?를 전환하지 않습니다. 따라서 USE_DB_S3_CONFIG를 사용하여 파일 전송에 성공하더라도, 페이지 렌더링 체크가 "S3가 활성화됨"을 보지 못하므로 사이트가 여전히 로컬에서 자산을 서빙하는 것을 볼 수 있습니다. 자산을 실제로 R2에서 서빙하려면, DB-설정 우회책이 아닌 app.yml에 실제 DISCOURSE_USE_S3: true + 연결 환경 변수가 필요합니다.

4부 — R2에 오지 않는 것과 그 이유

훅이 작동하더라도, s3:upload_assetsRails.application.assets.load_path — Rails의 Sprockets 매니페스트에 있는 것만 업로드합니다. 세 가지 범주는 해당 파이프라인 외부에서 생성되며 이 목록에 절대 나타나지 않으므로, 어떤 경우에도 로컬 디스크에 남습니다:

  • 테마 CSS — Sprockets를 통해 Discourse의 Stylesheet::Manager가 테마/색상 스키마별로 동적으로 컴파일합니다.
  • theme-javascriptsThemeJavascriptCompiler의 테마별 컴파일된 JS.
  • extra-locale / 로케일 JS 파일JsLocaleHelper가 생성합니다.

이는 구성 문제가 아닙니다 — 이들은 처음부터 Sprockets 자산이 아니었으므로, 이를 가져오는 환경 변수는 없습니다. 실제로 이는 다음과 같은 것을 의미합니다: 코어 Ember/벤더 JS 번들 → R2에 성공적으로 오프로드; 테마 CSS/JS 및 로케일 → 로컬에 유지, 앱에 의해 직접 서빙. 이는 깨진 상태가 아닌 정상적이고 작동하는 상태입니다.

요약: 실제로 무엇을 어디에 넣을 것인가

항목 위치 범위
enable_s3_uploads, s3_upload_bucket, backup_location, s3_backup_bucket Admin UI, 사이트별 사이트별
자격 증명 + DISCOURSE_S3_REGION/ENDPOINT/BUCKET/CDN_URL + DISCOURSE_USE_S3 app.yml, 자산 CDN 오프로드를 원하는 경우에만 클러스터 전체 (피할 수 없음)
after_assets_precompile app.yml 클러스터 전체
AWS_REQUEST_CHECKSUM_CALCULATION / AWS_RESPONSE_CHECKSUM_VALIDATION app.yml 클러스터 전체 (해armless SDK 동작 플래그)
버킷의 CORS Cloudflare 대시보드 자산이 공유되면 모든 사이트의 도메인을 포함해야 함

자산 CDN 오프로드가 필요하지 않다면 3부를 완전히 건너뛰세요 — app.yml을 전혀 건드리지 않고도 완전히 작동하는, 진정으로 사이트별 R2 설정(업로드 + 백업만)을 실행할 수 있습니다.

1개의 좋아요