Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions deployments/k8s/cluster/traefik-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# k3s settings for the cluster itself, not the application: applied once to
# a new cluster (B), kept out of the overlays. kubectl apply -f this file.
#
# HelmChartConfig is k3s's way to override the values of a chart it ships
# (here Traefik, in kube-system). k3s re-renders the chart when it changes.
apiVersion: helm.cattle.io/v1
kind: HelmChartConfig
metadata:
name: traefik
namespace: kube-system
spec:
valuesContent: |-
service:
spec:
# With the default (Cluster) ServiceLB SNATs every connection before
# it reaches Traefik: the gateway saw each client as 10.42.0.1, so
# all users shared one rate-limit bucket (finding #52). Local keeps
# the client's source address; on a single node it costs nothing,
# since every Traefik pod is local.
externalTrafficPolicy: Local
53 changes: 51 additions & 2 deletions docs/deployment-phase1-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ Her adım şu yapıda:

### A9. Secret'ları repodan çıkar

- [ ] **Ne:** `01-secrets.yaml`'ı base'den kaldırmak.
- [x] **Ne:** `01-secrets.yaml`'ı base'den kaldırmak.
- **local overlay:** dev değerleri `secretGenerator` ile, gitignore'daki bir
`.env.local`'dan okunuyor.
- **prod:** Secret repoda **hiç yer almıyor**. CI, GitHub Environment secret'larından
Expand Down Expand Up @@ -1067,6 +1067,15 @@ Her adım şu yapıda:
- Not: eski dev değerleri git geçmişinde duruyor; hiçbir zaman local dışında kullanılmadı.
- **Bitti sayılır:** `git grep -i jwt_secret deployments/` sadece anahtarın adını veren bir
yorum satırı buluyor; `deployments/` altında hiçbir dosya bir secret'a değer atamıyor.
- **Sonuç (2026-10-19), PR #63 → `main` `4348c89`:** İlk CI'da gitleaks kırmızıydı ve bulgunun
yerini göstermiyordu (`--verbose` yoktu). Yerelde `--verbose --redact`: `generic-api-key`,
`pkg/config/config_test.go:103`, test sabiti `strongSecret`; gerçek bir secret değil. Geçmiş
yeniden yazılamadığı için test değeri çalışma anında üretiliyor ve o tek bulgu
`.gitleaksignore`'da parmak iziyle (commit SHA'sına bağlı; branch merge commit'le
güncellendi, SHA `main`'de korundu) kabul edildi; CI'daki gitleaks artık `--verbose`. Sonra
24 check yeşil, gitleaks 161 commit "no leaks found". Ölçümler: Secret API'de base64 (88
karakter), `base64 -d` değeri anahtarsız geri veriyor; k3s'te diskte şifreleme kapalı (#74);
uygulama ServiceAccount'u Secret okuyamıyor (`can-i` → no).

### A10. Prova: yerel k3s (k3d) üzerinde prod'a en yakın deploy

Expand Down Expand Up @@ -1115,6 +1124,42 @@ Her adım şu yapıda:
Traefik herkesi aynı iç IP olarak görür. Çözüm adayı, `HelmChartConfig` ile
`externalTrafficPolicy: Local`. k3d'de node'un önünde k3d'nin kendi proxy'si de
olduğu için kesin sonuç B4'te, VPS üzerinde gerçek bir dış istemciyle alınmalı.
- **Kararlar (2026-10-19):** prod overlay'i ayrı bir k3d cluster'ında (`gamemesh-prod`, API
`127.0.0.1:6551`, Traefik `127.0.0.1:18081`, `--secrets-encryption` ile) provası; Traefik
`HelmChartConfig` (`externalTrafficPolicy: Local`) `deployments/k8s/cluster/` altında;
kalan ölçümler bu cluster'da Traefik üzerinden.
- **Prod provası (2026-10-19/20):** Secret elle (`kubectl create secret`, `openssl rand -hex
32`, değerler hiç basılmadan), overlay'in kopyasında `kustomize edit set image` ile
`main`'in SHA'sı (`4348c89`). İlk denemede 10/10 `1/1`, 0 restart; 7 uygulama imajı GHCR'dan,
üçüncü taraf digest'ler sabitlenenlerle aynı; fail-fast'a takılan servis yok; Traefik +
`Host: api.gamemesh.invalid` ile `/health` 200 ve `version` = commit SHA'sı. Not: k3d
cluster oluştururken aktif kubectl context'ini sessizce yeni cluster'a geçirdi; geri
alındı, prova komutları açık `--context` ile verildi.

| # | Kontrol | Sonuç |
|---|---|---|
| 1 | WS fan-out | ✅ A7-3 (iki pod, port-forward ile 3/3). Prod'da websocket 1 replica |
| 2 | Rollout'ta tek matchmaking | ✅ A7-1 (36/88 → 0) |
| 3 | Rolling update'te hata yok; WS 1001 | ✅ gateway rollout'u (1 replica), `/health` 50 ms'de bir: 1050/1050 200; leaderboard rollout'u, proxy'lenen rota: 1032/1032 200; websocket rollout'unda Traefik üzerinden bağlı istemci `1001 "server shutting down"` |
| 4 | Redis kapalıyken | ✅ gateway `1/1`, `/health` 200; Redis'e bağlı 5 servis `0/1`, 0 restart; outbox-relay `1/1`; Redis'e bağlı rotalar 502; Redis dönünce 10/10 |
| 5 | ConfigMap değişikliği | ✅ `RATE_LIMIT_RPS` 50 → 60: yeni hash'li ConfigMap, 7 Deployment kendiliğinden yeniden başladı; `rollout undo` gateway'i eski ConfigMap'e (50) döndürdü |
| 6 | Hatalı deploy | ✅ A9 (zayıf JWT, yanlış Postgres şifresi): yeni pod takılıyor, eski pod'lar trafik alıyor |
| 7 | Drain | ✅ 37 sn'de tamamlandı, PDB kilitlemedi; tek node'da pod'lar `Pending`, uncordon sonrası 10/10 |
| 8 | Logout sonrası WS | ✅ Traefik üzerinden: önce 101, logout 200, sonra 401 |
| 9 | Gerçek istemci IP'si | Kısmen: `Cluster` politikasıyla herkes `10.42.0.1` (SNAT); `Local` ile bağlantının gerçek kaynağı (`172.21.0.4`, k3d'nin önündeki proxy). Kesin sonuç B4 |
| + | WS boşta kalma (Traefik) | ✅ 130 sn boşta açık kaldı, sunucudan 2 ping (54 sn) |

- `rollout undo` uyarısı: "Rolling back will not update the last-applied-configuration
annotation": bir sonraki `apply` geri alınanı yeniden uygular. CI'lı bir sistemde asıl
geri alma, önceki commit'i yeniden deploy etmek (D).
- Gateway, hazır endpoint'i olmayan bir arka servis için 502 dönüyor; geçici bir durum
için 503 + `Retry-After` daha açık olurdu (Faz 1 sonrası).
- Gözlem: A10 sırasında `commit.sh`'teki `go test -race` bir kez tek bir testte düştü
(iki k3d cluster'ı ve compose aynı anda çalışırken); adı kaydedilmemişti, ardından üç tam
tekrarda yeniden üretilemedi. `checks.sh` artık test çıktısının tamamını saklıyor.
- **Bitti sayılır (2026-10-20):** `/health` Traefik üzerinden 200 (local ve prod provası),
WebSocket Traefik üzerinden bağlanıyor (101), Redis pod'u silinip yeniden oluşturulunca
veri duruyor (A7-2). Yukarıdaki 9 kontrolün 8'i tamam, 9. madde B4'te.

---

Expand Down Expand Up @@ -1442,6 +1487,8 @@ Bunlar unutulmadı; Faz 1'i sade tutmak için ertelendi:
- **Monitoring ve tracing imajlarının major yükseltmesi (#68):** Prometheus 3, Grafana 12/13,
Jaeger v2 (config biçimi değişiyor), collector 0.16x. Yapıldıktan sonra #66 taramasındaki
`REPORT_ONLY` listesinden çıkarılmalı.
- **Gateway'de upstream yokken 503 + `Retry-After` (A10):** Bugün 502; geçici bir durum için
503 istemciye yeniden denemeyi açıkça söyler.
- **Kalan iki taşınabilir referans (A5a):** Dockerfile'daki `# syntax=docker/dockerfile:1`
satırı bir tag (digest ile sabitlenebilir; Dependabot'un bu satırı güncelleyip
güncellemediği test edilmedi). Trivy sürümü action'ın varsayılanına bağlı; `version:`
Expand Down Expand Up @@ -1514,7 +1561,7 @@ manifest okunarak tespit edildi, henüz çalışan bir sistemde test edilmedi.
| 55 | Presence (8086) ve websocket (8084) compose'da `0.0.0.0`'a yayınlı; presence kimliksiz bir iç API (LAN'dan herhangi bir oyuncunun durumu değiştirilebilir) | M2 | Ölçüldü (token'sız `POST /presence/connect` → 200) | A2a (çözüldü) |
| 56 | k8s'teki Redis şifresiz; NetworkPolicy olmadan cluster'daki her pod erişebiliyor | M5, M8 | Kod | A9 (çözüldü: `requirepass`, şifresiz erişim `NOAUTH`) |
| 57 | Postgres DSN'inde `connect_timeout`/`statement_timeout` yok; ağ bölünmesinde sorgular ve açılış asılı kalabilir | M4 | Kod | A3a (çözüldü: 75 sn → 5 sn, açılışta retry) |
| 58 | Docker Desktop kapanırken container'ları "elle durduruldu" işaretliyor; `unless-stopped` stack'i geri getirmiyor (A2a iddiasının düzeltmesi) | M2 | Ölçüldü (`HasBeenManuallyStopped: true`, 14/14) | Karar: `restart: always` (Docker Desktop yeniden açılışında doğrulanacak) |
| 58 | Docker Desktop kapanırken container'ları "elle durduruldu" işaretliyor; `unless-stopped` stack'i geri getirmiyor (A2a iddiasının düzeltmesi) | M2 | Ölçüldü (`HasBeenManuallyStopped: true`, 14/14) | Karar: `restart: always` (çözüldü, 2026-10-19: Docker Desktop yeniden açılınca 14/14 container kendiliğinden ayağa kalktı) |
| 59 | Açık WS bağlantısı JWT'nin süresi dolduktan sonra da açık kalıyor (token sadece el sıkışmada kontrol ediliyordu) | M8 | Kod, test ile doğrulandı | A3b (çözüldü: tarama `1008 "token expired"`) |
| 60 | Trivy'ye `scanners` verilmemiş; step adı "misconfig" dese de manifest'ler hiç taranmıyordu (33 HIGH görünmüyordu) | M7 | Ölçüldü (yerel Trivy 0.70.0) | A5a (misconfig sadece rapor; bloklama A7) |
| 61 | `main` ruleset'inde zorunlu check'ler sadece `lint`, `test`, `integration`; taramalar, gitleaks ve imaj build/taramaları kırmızıyken de merge mümkün (A5a'daki "bloklayan" iddiasının düzeltmesi) | M7 | Ölçüldü (rulesets API) | A5a takibi: `ci-ok` toplayıcı job'u; ruleset değişikliği kullanıcı tarafından |
Expand All @@ -1530,3 +1577,5 @@ manifest okunarak tespit edildi, henüz çalışan bir sistemde test edilmedi.
| 71 | Dependabot sonek taşıyan tek mimarili bir tag'i (`otel-collector:0.162.0-386`, sadece `linux/386`) yeni sürüm sandı; CI yeşil, çünkü collector sadece taranıyor | M7 | Ölçüldü (`imagetools inspect`: tek manifest, `linux 386`) | PR #58 (collector Dependabot'ta ignore; #68 ile elle) |
| 72 | Ingress `/health`'i yönlendirmiyordu (sadece `/api`, `/ws`); Faz 1'in hedef komutu sınıf düzeltilse de 404 olurdu | M5 | Ölçüldü (k3d: Traefik 404) | A8 (çözüldü: `/health` Exact) |
| 73 | k8s DSN'lerinde `connect_timeout` yok (A3a düzeltmesi k8s'e ulaşmamış); tek deneme 39 sn asılı kalıp player'ı restart ettirdi | M4 | Ölçüldü (k3d) | A8 (çözüldü: `connect_timeout=5`, 0 restart) |
| 74 | k3s'te Secret'lar diskte şifrelenmiyor (`Encryption Status: Disabled`); datastore dosyası ya da yedeği bütün Secret'ları base64 olarak içeriyor | M8 | Ölçüldü (k3d: `k3s secrets-encrypt status`) | B (k3s `--secrets-encryption` ile kurulacak; prova cluster'ında `Enabled` doğrulandı) |
| 75 | Local overlay'de her secret değişikliği yeni hash'li bir Secret üretiyor, eskileri eski değerlerle cluster'da kalıyor | M8 | Ölçüldü (k3d: 3 `gamemesh-secrets-*`) | Sadece local (prod'da generator yok); `kubectl apply --prune` ya da elle temizlik |
14 changes: 14 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ kubectl -n gamemesh exec redis-0 -- redis-cli zcard matchmaking:queue
make k8s-delete # remove the local overlay
```

### Cluster settings (once per new cluster)

Settings for the k3s cluster itself, outside the overlays:

```bash
# k3s installed with encryption at rest for Secrets (off by default):
# curl -sfL https://get.k3s.io | sh -s - --secrets-encryption
k3s secrets-encrypt status # Encryption Status: Enabled

# Traefik keeps the client's address (externalTrafficPolicy: Local); without
# it every client reaches the gateway as 10.42.0.1 and shares one rate limit.
kubectl apply -f deployments/k8s/cluster/traefik-config.yaml
```

### Production secrets

The local overlay generates `gamemesh-secrets` from
Expand Down
Loading