Utilizzo di Cloudflare R2 su un solo sito in un’installazione multisite di Discourse
Se gestisci un’installazione multisite di Discourse e desideri che solo uno dei tuoi siti utilizzi Cloudflare R2 (per caricamenti, backup e, facoltativamente, asset statici), la guida standard Configurazione di un provider di object storage compatibile con S3 per i caricamenti non copre del tutto le peculiarità dell’ambiente multisite. Questo post illustra cosa accade effettivamente, cosa rimane a livello di singolo sito rispetto a ciò che è a livello di cluster, e alcuni punti critici che ho incontrato lungo il percorso e che hanno richiesto tempo reale per essere risolti.
Il concetto fondamentale da comprendere: GlobalSetting vs SiteSetting
Tutto in questa configurazione si riduce a una distinzione:
- Le variabili di ambiente di
app.yml(DISCOURSE_*) diventanoGlobalSetting— lette una sola volta all’avvio del contenitore, dall’ambiente di processo, e condivise da ogni sito nel cluster.RAILS_DBnon ha alcun effetto su di esse. - I campi dell’interfaccia di amministrazione sono normali
SiteSetting— memorizzati per singolo sito nel rispettivo database, effettivamente limitati a quel singolo sito.
Se esiste una GlobalSetting per una determinata voce, essa sovrascrive silenziosamente e nasconde il campo corrispondente SiteSetting nell’interfaccia di amministrazione. Questo significa: qualsiasi cosa inserisci in app.yml si applica a ogni sito, senza eccezioni e senza workaround tramite RAILS_DB.
Parte 1 — Caricamenti e backup (effettivamente per singolo sito, semplice)
Questa parte funziona esattamente come ci si aspetterebbe. enable_s3_uploads, s3_upload_bucket, backup_location, s3_backup_bucket e i campi delle credenziali sono tutti normali impostazioni di sito. Configurali solo tramite Amministrazione → Impostazioni → cerca “S3”, accedendo al sito specifico che desideri su R2, e lascia app.yml intatto. Gli altri siti nel cluster continueranno a memorizzare localmente.
Valori di esempio per R2:
Enable S3 uploads = true
Enable direct S3 uploads = true
S3 access key ID / secret access key = <il tuo token R2>
S3 region = auto
S3 upload bucket = <nome del bucket>
S3 endpoint = https://<account-id>.r2.cloudflarestorage.com
S3 CDN URL = https://uploads.yourdomain.com
S3 use ACLs = false (R2 utilizza autorizzazioni a livello di bucket, non ACL di oggetto)
S3 backup bucket = <nome del bucket di backup>
Backup location = S3
Imposta la policy CORS del tuo bucket direttamente nella dashboard di Cloudflare (R2 non ha bisogno del task rake CORS di Discourse):
[
{
"AllowedOrigins": ["https://your-site.tld"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag"],
"MaxAgeSeconds": 3000
}
]
Parte 2 — Migrazione dei caricamenti locali esistenti
rake uploads:migrate_to_s3 legge la configurazione S3 solo dalle variabili di ambiente — non ha alcun fallback alle impostazioni di sito, indipendentemente da ciò che è configurato nell’interfaccia di amministrazione. Questa è una lacuna reale nel task, non un errore di configurazione. Passa le credenziali inline per un’esecuzione una tantum invece di modificare app.yml:
./launcher enter app
RAILS_DB=default \
DISCOURSE_S3_REGION=auto \
DISCOURSE_S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com \
DISCOURSE_S3_BUCKET=<nome del bucket> \
DISCOURSE_S3_ACCESS_KEY_ID=<chiave> \
DISCOURSE_S3_SECRET_ACCESS_KEY=<segreto> \
rake uploads:migrate_to_s3
Queste variabili di ambiente esistono solo per quel processo shell — nulla viene persistito una volta che esci.
Errore di checksum con versioni più recenti di AWS SDK
Se riscontri:
Aws::S3::Errors::InvalidRequest: You can only specify one non-default checksum at a time.
questa è un’incapacità nota tra le versioni recenti di aws-sdk-core (che impostano per impostazione predefinita l’invio di un checksum CRC32) e R2. Correggi aggiungendo due ulteriori variabili di ambiente allo stesso comando:
export AWS_REQUEST_CHECKSUM_CALCULATION=when_required
export AWS_RESPONSE_CHECKSUM_VALIDATION=when_required
Record “non migrati” residui dopo un’esecuzione per lo più riuscita
Se il task termina con un messaggio come 1 of 1291 uploads are not migrated, non allarmarti — tutto il resto è già stato migrato e gli URL del DB sono già stati riscritti. Trova il ritardatario in 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)
Nel mio caso si trattava di un record Upload errato (un zip di log di backup) che utilizzava l’URL in stile endpoint grezzo di R2 invece del formato URL CDN che il controllo si aspetta — un falso positivo, non un fallimento reale. Correggi l’URL o elimina il record se non si tratta di contenuto significativo.
Parte 3 — Asset statici (JS/CSS) — la parte che è effettivamente a livello di cluster
È qui che l’obiettivo di “solo per un sito” incontra un muro invalicabile. Gli asset compilati sono condivisi su tutto il cluster multisite — esiste un unico bundle JS/CSS compilato, non uno per sito. Se gli asset vengono serviti da R2 o localmente è deciso una sola volta, all’avvio di Rails, tramite GlobalSetting.use_s3? — non esiste un override per singolo sito per questo.
Se desideri che gli asset siano scaricati su R2, devi inserire i dettagli di connessione (non la flag di abilitazione del caricamento) in 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: <nome del bucket>
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
Note:
- Non impostare
DISCOURSE_CDN_URL. SoloDISCOURSE_S3_CDN_URL. Impostare entrambi, con il tuo dominio principale proxyato tramite Cloudflare, causa loop di reindirizzamento secondo l’avvertimento stesso della guida S3 principale. - Usa
bundle exec rake, nonbundle rake(errore di battitura facile) — e usasudo -E -H -u discourse(il-Himposta correttamenteHOMEper l’utentediscourse; senza di esso Bundler ricade in una directory temporanea a ogni esecuzione). - Lo scaricamento degli asset interessa entrambi i siti. I tag
<script>/<link>del tuo secondo sito inizieranno anche a risolvere verso l’URL CDN di R2, poiché è lo stesso bundle compilato. Assicurati cheAllowedOriginsdella CORS del tuo bucket includa il dominio di ogni sito. - Questo non forza i caricamenti effettivi del tuo secondo sito su S3 —
enable_s3_uploadsrimane un’impostazione genuinamente per singolo sito, indipendente dallaGlobalSettingdi servizio degli asset. Verifica conSiteSetting.Upload.enable_s3_uploadsinrails cper il DB di quel sito dopo la ricompilazione.
USE_DB_S3_CONFIG — cosa fa effettivamente (e cosa no)
Vedrai USE_DB_S3_CONFIG=true menzionato in alcune configurazioni della community (ad es. il chart di Bitnami) come modo per far sì che s3:upload_assets legga le credenziali dalle impostazioni di sito invece che dalle variabili di ambiente. Funziona per il task di caricamento stesso — ma non inverte GlobalSetting.use_s3?, che è la flag che controlla effettivamente se gli URL degli asset vengono riscritti verso il CDN al momento del rendering. Quindi puoi spingere con successo i file su R2 con USE_DB_S3_CONFIG e vedere comunque il tuo sito servire gli asset localmente, perché il controllo del rendering della pagina non vede mai “S3 è abilitato”. Se desideri che gli asset vengano effettivamente serviti da R2, hai bisogno del vero DISCOURSE_USE_S3: true + variabili di ambiente di connessione in app.yml, non solo del workaround di configurazione DB.
Parte 4 — Cosa non sarà comunque su R2, e perché
Anche con l’hook funzionante, s3:upload_assets carica solo ciò che si trova in Rails.application.assets.load_path — il manifesto Sprockets di Rails. Tre categorie vengono generate fuori da quella pipeline e non appaiono mai in questo elenco, quindi rimangono su disco locale indipendentemente da tutto:
- CSS dei temi — compilati dinamicamente per tema/scheme di colori da
Stylesheet::Managerdi Discourse, non tramite Sprockets. theme-javascripts— JS compilato per tema daThemeJavascriptCompiler.extra-locale/ file JS di localizzazione — generati daJsLocaleHelper.
Questo non è un problema di configurazione — questi non sono mai stati asset Sprockets in primo luogo, quindi non esiste una variabile di ambiente che li includa. Nella pratica questo significa: bundle JS core Ember/vendor → scaricato con successo su R2; CSS/JS dei temi e localizzazioni → rimangono locali, serviti direttamente dall’app. Questo è uno stato normale e funzionante, non uno rotto.
Riepilogo: cosa mettere effettivamente dove
| Cosa | Dove | Ambito |
|---|---|---|
enable_s3_uploads, s3_upload_bucket, backup_location, s3_backup_bucket |
Interfaccia di amministrazione, per sito | Per singolo sito |
Credenziali + DISCOURSE_S3_REGION/ENDPOINT/BUCKET/CDN_URL + DISCOURSE_USE_S3 |
app.yml, solo se desideri lo scaricamento CDN degli asset |
A livello di cluster (inevitabile) |
Hook after_assets_precompile |
app.yml |
A livello di cluster |
AWS_REQUEST_CHECKSUM_CALCULATION / AWS_RESPONSE_CHECKSUM_VALIDATION |
app.yml |
A livello di cluster (flag di comportamento SDK innocuo) |
| CORS sul bucket | Dashboard Cloudflare | Deve includere il dominio di ogni sito se gli asset sono condivisi |
Se non hai bisogno dello scaricamento CDN degli asset, salta completamente la Parte 3 — puoi eseguire una configurazione R2 completamente funzionante, genuinamente per singolo sito (solo caricamenti + backup) senza mai toccare app.yml.