Yayına almak
Yayına alma
Binary'yi derleyin, bir container'da ya da systemd altında çalıştırın, production'ın gerektirdiklerini ayarlayın ve TLS'in arkasına koyun.
Bir collage sitesi bir Go programıdır; dolayısıyla yayına aldığınız şey derlenmiş bir
binary'dir. Şablonlar ve statik dosyalar onun içine gömülüdür, bu yüzden yanına hiçbir
şey kopyalanmadan her dizinden çalışır — plugin yapılandırıyorsanız
plugins-config.json hariç; bkz. aşağısı. Bu sayfa o
binary'yi bir sunucuda çalıştırmakla ilgilidir; hiç sunucusu olmayan bir site için
bkz. Statik dışa aktarma.
Derleme: collage build
collage build
./bin/mysite
collage build, aksi hâlde aklınızda tutmanız gereken go build komutunu sizin
yerinize çalıştırır:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o bin/mysite .
- CGO kapalı, çünkü collage'ın ve standart kütüphanenin C'ye ihtiyacı yoktur ve statik bir binary, başka hiçbir şey içermeyen bir imaja konabilir.
-trimpath, böylece binary kendisini derleyen makinenin yollarını taşımaz.-s -wdebug tablolarını atar; boyutun çoğu onlardır.- Varsayılan olarak bu makine değil, linux/amd64. Mac için derlenmiş bir binary
bir Linux container'ında çalışmaz ve bunu öğrenmek için yanlış yer, sunucuda
karşınıza çıkan bir
exec format error'dır.
| Flag | Varsayılan | Anlamı |
|---|---|---|
-o path |
bin/<module name> |
Binary'nin yazılacağı yer. |
-os name |
linux |
Hedef işletim sistemi. |
-arch name |
amd64 |
Hedef mimari — Graviton ya da Ampere makineler için arm64. |
-i |
kapalı | Binary'nin yanına bir Dockerfile ve bir systemd unit'i yazmayı önerir. |
Binary dist/'e değil, bin/'e gider: dist/, collage export'a aittir ve onun
-clean'i içinde duran bir binary'yi silerdi.
collage start diye bir şey yoktur. Derlenmiş bir binary'yi çalıştırmak için hiçbir
şeyi aklınızda tutmanız gerekmez; bir start komutu da ancak go run . çalıştırabilirdi
— bu da Go araç zincirini production imajınıza koyar ve her açılışta derleme yapar.
collage build -i: bir Dockerfile ve bir systemd unit'i
-i ile collage build, binary'nin yanına bir Dockerfile ve bir systemd unit'i
yazıp yazmayacağını sorar:
$ collage build -i
Building mysite for linux/amd64.
Write a Dockerfile? [y/N]: y
Write a systemd unit? [y/N]: y
✓ bin/mysite
linux/amd64 · 9.3 MB
wrote bin/Dockerfile docker build -f bin/Dockerfile .
wrote bin/mysite.service
9.3 MB · linux/amd64 · 4.1s
Üretildikleri için bin/'e giderler; proje kökü sizin yazdıklarınız içindir. Var olan
bir dosyanın üzerine asla yazılmaz — bunlar düzenlemeniz beklenen dosyalardır — ve
iskelet olarak üretilen bir projenin .gitignore'u yalnızca binary'yi yok sayar;
bu yüzden onları diğer her dosya gibi commit'lersiniz.
Dockerfile iki aşamalıdır: Go imajında derler ve distroless bir taban üzerinde yalnızca binary'yi taşır:
FROM golang:1.26 AS build
WORKDIR /src
COPY go.mod go.sum* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /mysite .
FROM gcr.io/distroless/static-debian12
WORKDIR /srv
COPY --from=build /mysite /usr/local/bin/mysite
ENV HOST=0.0.0.0 PORT=8080
# ENV COLLAGE_CSRF_KEY=
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/mysite"]
Onu proje kökünden docker build -f bin/Dockerfile . ile derleyin. Şablonlar ya da
statik dosyalar için bir COPY yoktur, çünkü onlar binary'nin içindedir. Son aşama
binary'yi kopyalar — ve v0.11.1'den itibaren, Dockerfile yazıldığı sırada projede
plugins-config.json varsa onu da; bkz. Plugin yapılandırması.
WORKDIR, sayfa önbelleği ve o dosya için önemlidir; aşağıya bakın.
COLLAGE_CSRF_KEY'i dosyada değil, platformunuzun gizli değerlerinden (secret)
ayarlayın.
systemd unit'i binary'yi /usr/local/bin'den, /srv/<name> içinde, aynı adı taşıyan
bir kullanıcı olarak çalıştırır ve önündeki bir reverse proxy için 127.0.0.1:8080'i
dinler. /etc/systemd/system/<name>.service konumuna kurmadan önce kullanıcıyı,
dizini ve yolu düzenleyin:
systemctl daemon-reload && systemctl enable --now mysite
Ortam
İskelet olarak üretilen main.go dört değişken okur. Production'da hiçbir şey .env
dosyalarını okumaz — onlar collage dev içindir — bu yüzden bunları binary nerede
çalışıyorsa orada ayarlayın.
| Değişken | Varsayılan | Ne olmalı |
|---|---|---|
HOST |
localhost |
Bir container'da 0.0.0.0. localhost makinenin dışından hiçbir şey kabul etmez; bu, yerel bir reverse proxy'nin arkasında doğru, diğer her yerde yanlıştır. |
PORT |
3000 |
Platformunuz ne atıyorsa o. Binary'nin -port flag'i bunu geçersiz kılar. |
COLLAGE_CSRF_KEY |
üretilir | En az 32 rastgele bayt. Bunu ayarlayın. |
COLLAGE_DEV |
ayarlanmamış | Hiçbir şey — ayarlamayın. collage dev onu 1 yapar ve bu geliştirme modunu açar: şablonlar ve statik dosyalar diskten okunur, hiç okunmayan bellek içi bir önbellek kullanılır, hata sayfalarında hata zincirlerinin tamamı gösterilir. Bununla çalışan bir sunucu bir geliştirme sunucusudur. |
Bir anahtarı şöyle üretin:
openssl rand -hex 32
Anahtar yoksa collage her süreç için bir anahtar üretir ve başlangıçta uyarır. Bu kendi makinenizde sorun değildir, bir sunucuda ise iki nedenle yanlıştır:
- Formlar yeniden başlatmalar ve instance'lar arasında bozulur. Bir yayına almadan önce render edilmiş bir form, sonrasında gönderildiğinde reddedilir; bir instance da diğerinin verdiğini reddeder. Anahtarı her instance'ta ve yeniden başlatmalar boyunca aynı tutun.
- Form içeren önbellekteki sayfalar her başlatmadan sonra yeniden render edilir. Önbellekteki bir sayfadaki sahtecilik token'ı yer tutucusu anahtardan türetilir; bu yüzden önceki sürecin anahtarıyla saklanmış bir sayfa ıskalama (miss) sayılır ve baştan render edilir. Form içermeyen sayfalar etkilenmez; disk önbelleğinin geri kalanı her durumda yeniden başlatmadan sağ çıkar. Bkz. Önbellekleme.
Anahtarı değiştirmek güvenlidir ama bedelsiz değildir: değişiklikten önce render edilmiş formlar reddedilir ve form içeren önbellekteki sayfalar birer kez yeniden render edilir.
Gömülü şablonlar ve statik dosyalar
İskelet olarak üretilen bir proje ikisini de gömer:
//go:embed all:templates
var templatesFS embed.FS
//go:embed all:static
var staticFS embed.FS
Şablonlar collage'a Template.FS olarak verilir, statik dosyalar ise
fs.Sub(staticFS, "static")'tan mount edilir. Geliştirme modunda diskteki dizinler
önceliklidir; böylece bir şablonu düzenlediğinizde değişiklik bir sonraki istekte
görünür. Production'da yalnızca gömülü kopya okunur; böylece süreç nerede başlarsa
başlasın, çalışan şey test ettiğiniz şeydir.
Sayfalarınızın çalışma zamanında okuduğu içeriği — Markdown dosyaları, bir JSON
kataloğu — aynı şekilde gömmek size kalmıştır. collage-docs, tam da bu nedenle kendi
content/ dizinini gömer ve geliştirmede onu diskten okur. Böyle bir dizini
Config.DevWatch içinde adlandırın (v0.10.0'dan
itibaren); içindeki bir dosyayı düzenlemek de tarayıcıyı yeniler.
Plugin yapılandırması
İskelet olarak üretilen main.go, plugins-config.json'ı
collage.LoadPluginConfig("plugins-config.json") ile okur — binary'ye değil, çalışma
dizinine göre bir yol; gömülü de değil. Eksik bir dosya hata değildir: her plugin
sessizce varsayılanlarıyla çalışır. Yani dosyanın bulunmadığı bir yerde başlatılan
bir sunucu, plugin ayarlarınızı tek kelime etmeden yok sayar.
collage build -i'nin yazdığı Dockerfile, Dockerfile yazıldığı sırada projede bu
dosya varsa onu kopyalar (v0.11.1'den itibaren; öncekiler yalnızca binary'yi
kopyalıyordu). Dosyayı sonradan oluşturursanız satırı kendiniz ekleyin —
collage build -i bir Dockerfile'ın üzerine asla yazmaz — sürecin başladığı
WORKDIR'in yanına:
WORKDIR /srv
COPY --from=build /mysite /usr/local/bin/mysite
COPY --from=build /src/plugins-config.json /srv/plugins-config.json
ya da onu gömün; böylece şablonlar gibi binary'nin içinde taşınır:
//go:embed plugins-config.json
var pluginConfigJSON []byte
var pluginConfig map[string]json.RawMessage
if err := json.Unmarshal(pluginConfigJSON, &pluginConfig); err != nil {
return nil, fmt.Errorf("plugin configuration: %w", err)
}
systemd ile dosyayı unit'in WorkingDirectory'sinde tutun.
Düzgün kapanış
app.ListenAndServe, SIGINT ve SIGTERM'ü yakalar. İkisinden birinde yeni
bağlantı kabul etmeyi bırakır, süren isteklerin bitmesi için Server.ShutdownTimeout
kadar (varsayılan 10 saniye) bekler, her plugin'in Shutdown'ını çalıştırır ve nil
döner. SIGTERM gönderip bekleyen bir container çalışma ortamı, hiçbir şey eklemeden
temiz bir boşaltma elde eder. Üretilen systemd unit'i TimeoutStopSec=30 ayarlar; bu,
kapanış zaman aşımından rahatça uzundur, böylece systemd hâlâ boşaltmakta olan bir
süreci öldürmez.
Boşaltmanın süresi dolarsa — zaman aşımı geçtiğinde hâlâ açık bir istek varsa —
plugin'ler yine de kapatılır ve ListenAndServe bunu söyleyen bir hata döndürür
(collage: server shutdown: context deadline exceeded); Shutdown'ı başarısız olan
bir plugin için de aynısını yapar. İskeletin main.go'su bunu log.Fatalf'e verir;
bu yüzden süreç 0 yerine 1 durum koduyla çıkar. Durdurma sırasında sıfırdan farklı bir
çıkışı çökme olarak değerlendiren bir platform bunu böyle bildirir.
ShutdownTimeout'u yükseltirseniz, platformunuzun bekleme süresini de onunla birlikte
yükseltin.
Sağlık denetimleri
collage new'in iskelet olarak ürettiği proje, /healthz'ye status'ü ok olan
küçük bir JSON gövdesiyle yanıt verir. Bu bir sayfa değil, bir
document'tır; dolayısıyla hiçbir şablon içermez ve bir şablon
bozuldu diye başarısız olmaya başlayamaz. Dynamic()'tir; böylece her denetim
gerçekten sürece ulaşır.
Platformunuzun liveness denetimini ona yönlendirin. Size sürecin ayakta olduğunu ve
sunum yaptığını söyler. Veritabanınıza ulaşılamadığında da başarısız olması gereken
bir readiness denetimi, aynı şekilde yazılmış kendi document'ınızdır; bir hata
döndürün, 500 ile yanıt verir. collage new --template minimal ile oluşturulan bir
projede /healthz yoktur; bir tane istiyorsanız demo iskeletinin
documents/health.go'sunu kopyalayın.
Production'da sayfa önbelleği
İskelet bir disk önbelleği yapılandırır:
Cache: collage.CacheConfig{
Enabled: true,
Type: "disk",
Dir: cacheDir, // ".cache"
DefaultTTL: 5 * time.Minute,
},
Render edilmiş sayfalar yeniden başlatmadan sağ çıkar; böylece aynı build'in yeniden yayına alınması, siteyi soğuk bir önbelleğe baştan render etmez. Yeni bir build'in ne bulacağı, neyin değiştiğine bağlıdır:
- Önbellek, binary'nin bir hash'iyle ad alanına ayrılır. Yeni bir build farklı bir
dizini,
.cache/<hash>'i okur; bu yüzden önceki build'in render ettiği sayfaları asla sunmaz. Doğruluk için hiçbir şeyin temizlenmesi gerekmez — ama yer açmak için de hiçbir şey temizlemez: önceki build'lerin dizinleri asla silinmez; bu yüzden yeni bir container olarak değil de yerinde yeniden yayına alınan bir sunucu, her build için bir tane biriktirir. Eskilerini yayına alma betiğinizden silin. Sayfaların nasıl göründüğüne binary dışındaki bir şey karar veriyorsaCache.Version'ı — bir commit, bir sürüm etiketi — ayarlayın. - Dizin, çalışma dizinine göredir. İskelet olarak üretilen bir projede, nerede
başlatıldığına bağlı olan tek şey budur. Container'da
WORKDIR'i ya da unit'teWorkingDirectory'yi ayarlayın veyaDir'e mutlak bir yol verin ve sürecin oraya yazabildiğinden emin olun. - Yazamadığı bir önbellek hata değildir. Başlangıçta oluşturulamayan bir dizin — salt okunur bir dosya sistemi, sürecin yazamayacağı bir çalışma dizini — collage'ın başlamayı reddetmek yerine bir uyarıyla bellek içi bir önbelleğe geri dönmesine yol açar (v0.11.0'dan itibaren). Çalışırken başarısız olan bir yazma loglanır, sayfa önbelleğe alınmadan sunulur ve bir sonraki istek onu yeniden render eder: yavaş, ama bozuk değil. Uyarıya bakın ve bir yayına almadan sonra dizinin dolduğunu kontrol edin.
- Her instance'ın kendi önbelleği vardır. Bir container'da dizin container'ın içindedir; bu yüzden her instance kendi önbelleğini, instance başına sayfa başına bir render ile doldurur.
Birden çok instance ile geçersiz kılma
app.InvalidateTags, içinde çalıştığı sürecin önbelleğinden girdileri düşürür. Tek
bir instance varken, bir yazının etiketini geçersiz kılan bir CMS webhook'u siteyi
günceller. Birkaç instance varken ise yalnızca webhook'un ulaştığı instance'ı
günceller; diğerleri eski sayfayı TTL'i dolana kadar sunmaya devam eder.
Üç yanıttan birini seçin: değişen sayfalara Incremental(ttl) verin ki bayat bir
kopya kendiliğinden sona ersin, webhook'u her instance'a gönderin ya da
Cache.Store üzerinden collage.TaggedCache'i uygulayan paylaşımlı bir depolama
yapılandırın; bu, tek bir geçersiz kılmanın herhangi bir instance'ın yazdığı
girdilere ulaşmasını sağlar. Bkz. Önbellekleme.
Paylaşımlı bir depolama veriyi değil, sayfaları tutar.
collage.Cached ile tutulan değerler,
Cache.Store ne olursa olsun her sürecin belleğinde yaşar ve bir geçersiz kılma
yalnızca içinde çalıştığı instance'a ulaşır. Bir webhook A instance'ına ulaştıktan
sonra B instance'ı, hâlâ tuttuğu eski değerden — paylaşımlı depolama sayfayı
düşürdüğü için — taze bir sayfa render eder. Bu değerlere katlanabileceğiniz kadar
kısa bir TTL verin ya da geçersiz kılmayı her instance'a gönderin.
TLS, bir proxy'nin arkasında
collage düz HTTP sunar ve ListenAndServeTLS'i yoktur. TLS'i iyi sonlandırmak;
sertifikalar, yenileme, HTTP/2, HSTS ve 80 numaralı porttan bir yönlendirme demektir
ve bunun üzerinde çalıştığı her platform — bir load balancer, bir reverse proxy,
Cloudflare, Fly, Render — bunu bir framework flag'inin yapabileceğinden zaten daha iyi
yapar.
Binary'yi bunlardan birinin arkasına koyun ve proxy'nin X-Forwarded-Proto: https
gönderdiğinden emin olun. collage bunu sahtecilik token'ı cookie'sini Secure olarak
işaretlemek için kullanır; böylece HTTPS üzerinden sunulan bir site o cookie'yi asla
düz HTTP üzerinden göndermez. Sertifikayı da alan minimal bir Caddy yapılandırması:
example.com {
reverse_proxy 127.0.0.1:8080
}
nginx ile location bloğundaki proxy_set_header X-Forwarded-Proto $scheme; aynı
işi görür.
TLS'i binary'nin kendisinin sonlandırmasını istiyorsanız, app.Handler() sıradan bir
http.Handler'dır:
srv := &http.Server{
Addr: ":443",
Handler: app.Handler(),
ReadHeaderTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServeTLS(certFile, keyFile))
Bunu yapmak, ListenAndServe'ün sizin için yaptığı zaman aşımlarını ve sinyal
işlemeyi artık sizin üstlendiğiniz anlamına gelir; plugin'lerin kapanması için
app.Shutdown(ctx)'i de kendiniz çağırırsınız.
Zaman aşımları
ListenAndServe, Config.Server içindeki zaman aşımlarını uygular:
| Alan | Varsayılan | Sınırladığı |
|---|---|---|
ReadTimeout |
15s | Header'lar dahil bir isteğin okunması — header'ları yavaş gönderen bir istemci bir bağlantıyı açık tutamaz. |
WriteTimeout |
30s | Yanıtın yazılması. Verisi bundan uzun süren bir sayfa yarıda kesilir. |
IdleTimeout |
60s | Bir sonraki isteğini bekleyen bir keep-alive bağlantısı. |
ShutdownTimeout |
10s | SIGTERM üzerine yapılan düzgün boşaltma. |
MaxBodyBytes |
4 MiB | Action kendi sınırını belirlemedikçe, bir action'ın istek gövdesi. Negatif değer sınırsızdır. |
Server: collage.ServerConfig{
Host: envString("HOST", "localhost"),
Port: port,
WriteTimeout: time.Minute,
},
Bir data handler'ın ne kadar sürebileceği ayrı bir ayardır: her fragment'in
WithTimeout'u ya da hiçbir şey ayarlamayan fragment'ler ve document'lar için
Template.Timeout (5 saniye). Onu WriteTimeout'un epey altında tutun; böylece
yavaş bir dış servis, sayfanın ortasında kapanan bir bağlantıya değil, bir fragment'in
yedeğine dönüşür. Bkz. Yapılandırma.
Loglar
Config.Logger yoksa collage, slog'un varsayılan handler'ına — ya da bir
terminaldeyse insanlar için biçimlendirilmiş bir handler'a — log yazar. Logların bir
makine tarafından okunduğu bir sunucuda bir JSON handler'ı verin:
Logger: slog.New(slog.NewJSONHandler(os.Stdout, nil)),
Kontrol listesi
COLLAGE_CSRF_KEYayarlı ve her instance'ta aynı.- Bir container'da
HOST=0.0.0.0;PORTplatformdan. .cacheiçin yazılabilir bir çalışma dizini ve yayına almada silinen eski.cache/<hash>dizinleri.- Plugin yapılandırıyorsanız, çalışma dizininde ya da gömülü bir
plugins-config.json. COLLAGE_DEVayarlanmamış.- Proxy'de TLS ve iletilen
X-Forwarded-Proto. - Platformun durdurma bekleme süresi
Server.ShutdownTimeout'tan uzun. /healthzüzerinde liveness denetimi.- Build'den önce CI'da
go test ./...— bkz. Test.