# Kubernetes Üzerinde Çalıştırma

> Helm chart'ının kurulumu — veritabanı ve SECRET_KEY yönetimi, ingress, kalıcılık, probe'lar, security context, kaynak boyutlandırma ve kaldırma.

- Güncelleme: 2026-09-25
- Kaynak: https://contextator.com/tr/docs/kubernetes/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
Contextator'ı Kubernetes üzerinde çalıştırmanın desteklenen yolu bir Helm chart'ıdır. Bu sayfadaki her
şeyin kaynağı: depodaki `charts/contextator/` altındaki chart'ın kendisi ve onun kendi, daha uzun
`charts/contextator/README.md`'si.

## Kurulum

```bash
helm repo add contextator https://contextator.github.io/Contextator
helm repo update
helm install contextator contextator/contextator \
  --set database.url="postgres://user:pass@host:5432/contextator" \
  --set-string image.tag="<version>-slim"
```

`database.url` (ya da zaten yönettiğiniz bir Secret için `database.existingSecret`) ve `image.tag`
ikisi de zorunludur ve varsayılanı yoktur — ikisinden biri eksikse chart hiçbir şey render etmeden önce
`helm install`/`helm template`'i anında başarısız kılar. `image.tag` bir `-slim` build'ine işaret etmelidir;
o son ek olmadan gelen tag'ler gömülü PostgreSQL'li tam imajdır, bu chart için yanlış topolojidir. Ya da
chart dizininin checkout edilmiş bir kopyasından doğrudan kurun:
`helm install contextator ./charts/contextator -f my-values.yaml` — aynı iki değer, değer dosyasında ya
da komut satırında olsun, yine zorunludur.

## Tasarım gereği tek replica

`replicaCount` desteklenen bir değer değildir — chart onu doğrudan reddeder
(`additional properties 'replicaCount' not allowed`) ve bu bilerek yapılmıştır, bir eksiklik değil:
Contextator kendi indeksleme kuyruğuna karşı tek bir yazıcı çalıştırır, ve onu kümelemek ürünün henüz
sahip olmadığı bir koordinasyon katmanı gerektirirdi (ADR-0078). Pod'un sayısını değil, kendi
kaynaklarını ölçekleyin; aşağıdaki [Kaynak boyutlandırma](#kaynak-boyutlandırma) bölümüne bakın.

## Veritabanı

Chart'a ya doğrudan bir bağlantı dizesi verin (`database.url`) ya da zaten yönettiğiniz bir Secret'a
işaret edin (`database.existingSecret`) — [Yapılandırma](/tr/docs/configuration/)'nın Kubernetes dışında
`DATABASE_URL` için anlattığı seçimin aynısı. PostgreSQL'in kendisi bu chart'ın parçası değildir; onu
ayrıca çalıştırın ya da yönetilen bir örneğe işaret edin.

## SECRET_KEY: bir kez üretilir, sabit kalır

`SECRET_KEY` kaynak kimlik bilgilerini durağan halde şifreler — git, Notion ve Confluence tokenları ve
webhook sırları — bu yüzden onu kaybetmek yalnızca herkesi oturumdan atmaz, aynı zamanda o saklanan
kimlik bilgilerini kurtarılamaz hale getirir. Ayarlanmadan bırakıldığında chart ilk kurulumda bir tane
üretir ve — yeni bir tane üretmeye karar vermeden önce çalışan kümenin Secret'ını Helm'in `lookup`
fonksiyonuyla okuduğu için — siz hiçbir şey yapmadan sonraki her `helm upgrade` boyunca aynı değeri
korur.

**`lookup` fonksiyonu yalnızca gerçek, erişilebilir bir kümeye karşı çalışır.** Chart'ı offline render
etmek — `helm template`, ya da render edilmiş bir manifestten canlı bir `lookup` olmadan reconcile eden
bir GitOps controller'ı — her seferinde var olan bir Secret görmez ve her render'da yeni bir anahtar
üretir, ki bu tam olarak çalışan bir örneğin altında değişmemesi gereken değerdir. Böyle bir kurulumda,
anahtarı bir kez kendiniz üretin ve chart'a onu üretmesine izin vermek yerine var olan bir Secret olarak
elden verin:

```bash
kubectl create secret generic contextator-secret-key \
  --from-literal=SECRET_KEY=$(openssl rand -hex 32)
```

```yaml
secretKey:
  existingSecret: contextator-secret-key
  existingSecretKey: SECRET_KEY
```

**Onu rotasyona sokmak** bir değer değişimi değil, iki adımlı bir harekettir. Önce mevcut değeri yeni bir
adla saklayın — `contextator-secret-key` yeni anahtarla üzerine yazılmak üzeredir, bu yüzden herhangi bir
şey üretmeden önce eskisini kopyalayıp çıkarın:

```bash
kubectl -n <namespace> create secret generic contextator-secret-key-previous \
  --from-literal=SECRET_KEY="$(kubectl -n <namespace> get secret contextator-secret-key -o jsonpath='{.data.SECRET_KEY}' | base64 -d)"
```

Sonra yeni anahtarı üretin ve onu `contextator-secret-key`'e yerinde yazın (`kubectl create` tek başına
zaten var olan bir Secret'ta başarısız olur):

```bash
kubectl -n <namespace> create secret generic contextator-secret-key \
  --from-literal=SECRET_KEY="$(openssl rand -hex 32)" \
  --dry-run=client -o yaml | kubectl -n <namespace> apply -f -
```

Yeni anahtar yukarıdaki gibi sabitlenmişken (`secretKey.existingSecret`/`secretKey.existingSecretKey`),
sürece eskisini `envSecret` üzerinden `SECRET_KEY_PREVIOUS` olarak verin; onun girdileri her zaman
`AD: {secretName, secretKey}` biçimindedir:

```yaml
envSecret:
  SECRET_KEY_PREVIOUS:
    secretName: contextator-secret-key-previous
    secretKey: SECRET_KEY
```

İkisi de mevcutken ve Pod yeniden başlatıldıktan sonra, Deployment adını bulun (`<release>-contextator`,
release adı zaten `contextator` içermiyorsa ve `fullnameOverride` ayarlanmamışsa) ve rotasyonu onun
içinde çalıştırın:

```sh
kubectl -n <namespace> get deploy -l app.kubernetes.io/instance=<release>
kubectl -n <namespace> exec deploy/<deployment-name> -- npm run rotate-secret
```

Eski anahtarın şifrelediğini çözer ve yenisinin altında yeniden şifreler. Dönüştürülecek bir şey
kalmadığını bildirdiğinde, `SECRET_KEY_PREVIOUS`'ı (`envSecret` girdisini) kaldırın ve Pod'un tekrar
yeniden başlamasına izin verin — eski anahtarı emekliye ayıran o kaldırmadır. Anahtar tamamen kaybolduysa
ve kurtaracak bir `SECRET_KEY_PREVIOUS` yoksa, onun altında saklanan kaynak kimlik bilgileri çözülemez —
geri dönüşün tek yolu o kaynakları yeniden eklemektir.

## Ingress ve reverse-proxy üçlüsü

Chart'ın önündeki bir Ingress, herhangi bir başka reverse proxy gibidir, ve
[Yapılandırma](/tr/docs/configuration/#bir-reverse-proxy-arkasında-çalışmak)'daki aynı üçlü geçerlidir:
`config.trustProxy`, `config.publicBaseUrl` ve `env.AUTH_COOKIE_SECURE` birlikte hareket eder, yoksa
IP başına oturum açma sınırı, MCP connector yetkilendirmesi ve oturum çerezi her biri kendi sessiz
biçiminde bozulur.

**Bir sıçrama sayısı (`2`, `3`, …) reddedilir; `1`/`true` kabul edilir ve *her sıçramaya güven* anlamına
gelir.** Bu, [Yapılandırma](/tr/docs/configuration/#bir-reverse-proxy-arkasında-çalışmak)'nın Kubernetes
dışında anlattığı aynı korumadır: chart `config.trustProxy`'yi doğrulamadan geçirir, bu yüzden `"1"` her
sıçramada `X-Forwarded-For` başlığını kim yazdıysa ona güvenir — yalnızca bu Pod'un portuna ingress
controller'dan başka bir şeyin ulaşmasına izin vermeyen bir NetworkPolicy arkasında güvenlidir. Bunun
yerine proxy'yi adlandırın, onu gerçekten kapsayan en dar aralığı:

```yaml
config:
  publicBaseUrl: "https://contextator.example.com"
  trustProxy: "<ingress-controller-pod-ip-or-cidr>"
env:
  AUTH_COOKIE_SECURE: "1"
```

Değer yalnızca socket'in eşine karşı değil her sıçramaya karşı eşleştirilir, bu yüzden tüm pod CIDR'ını
yapıştırmak bu Service'e ulaşabilen her diğer pod'a da güvenir — yalnızca ingress controller'dan başka
bir şeyin bu pod'un portuna ulaşmasına izin vermeyen bir NetworkPolicy arkasında güvenlidir.

## Kalıcılık

İki `ReadWriteOnce` PersistentVolumeClaim: `/data` (veritabanına bitişik çalışma dosyaları) ve
`/app/.cache/models` (indirilen gömme modeli, böylece bir pod yeniden başlatması onu yeniden indirmez).
`/data`'nın, canlı bir kümede, bir pod yeniden başlatmasından bütün çıktığı doğrulanmıştır.

## Probe'lar

Liveness, porttaki salt bir TCP kontrolüdür — veritabanına bağımlı olmadan tıkanmış bir süreci
yakalamaya yeter. Readiness ve startup ikisi de gerçek bir `GET /api/health` çağrısıdır, bu yüzden bir
pod yalnızca bir bağlantı kabul ettiğinde değil, uygulama gerçekten cevap verebildiğinde ready
işaretlenir. Bir veritabanı kesintisine karşı canlı bir kümede doğrulanmıştır: pod bu süre boyunca
`READY 0/1` raporlar, liveness hiçbir zaman veritabanına bağımlı olmadığı için **sıfır** restart alır,
ve veritabanı geri geldiği anda hiçbir manuel pod restart'ı gerekmeden otomatik olarak ready'e döner.

## Security context

Container **uid 0** olarak başlamak zorundadır — opsiyonel değildir, ve bunu yasaklayan sertleştirilmiş
bir `securityContext`, *"the container must start as root"* hatasıyla başlangıçta başarısız olur. Bu
kısa, yapısal bir gerekliliktir, kalıcı değil: entrypoint o ilk anı root olarak yukarıdaki iki bağlı
volume'un sahipliğini düzeltmek için kullanır (taze bir PVC varsayılan olarak `root` sahiplidir), sonra
herhangi bir uygulama kodu çalışmadan önce `gosu` ile ayrıcalıksız bir `node` kullanıcısına hemen
devreder. Trafiği gerçekten sunan süreç hiçbir zaman root olarak çalışmaz.

İmaj ayrıca `wget` ya da `curl` taşımaz — kendi healthcheck'i ve chart testleri, Dockerfile'dan
varsayılmak yerine gerçek bir `helm test` çalıştırmasıyla doğrulanmış şekilde, Node'un yerleşik
`fetch()`'iyle birlikte `node -e` kullanır.

## Kaynak boyutlandırma

Aşağıdaki sayılar tahmin edilmemiş, çalışan bir örneğe karşı ölçülmüştür:

| Durum | CPU | Bellek |
|-------|-----|--------|
| Boşta | düşük | ~770–820 MiB RSS |
| İndeksleme patlaması | 2–3 saniye boyunca ~10 çekirdek | — |
| Soğuk başlangıç | — | ~72,4 saniyede hazır |

`resources.requests`/`resources.limits`'i boşta değerinin üzerinde bir marjla ve indeksleme
patlamaları için yeterli burst kapasitesiyle ayarlayın; bir patlama sırasında sıkı bir CPU limiti diğer
pod'ları etkilemek yerine yalnızca tek bir indeksleme çalışmasını yavaşlatır, çünkü zaten hep tek bir
replica vardır.

## Kaldırma

`helm uninstall` varsayılan olarak iki PersistentVolumeClaim'i geride bırakır — bunlar bir
`helm.sh/resource-policy: keep` anotasyonu taşır, bu yüzden indekslenmiş veri ve önbelleğe alınmış
model kazara bir kaldırmadan sağ çıkar. `SECRET_KEY` Secret'ı böyle bir politika taşımaz ve release ile
birlikte **silinir**; aynı veriye karşı yeniden kurmayı planlıyorsanız önce anahtarı kaydedin (yukarıdaki
[SECRET_KEY: bir kez üretilir, sabit kalır](#secret_key-bir-kez-üretilir-sabit-kalır)'e bakın) yoksa
hayatta kalan PVC'lerin hâlâ referans verdiği kaynak kimlik bilgilerini çözemezsiniz.

## Katı values şeması

Chart, değerlerini katı bir JSON Schema'ya karşı doğrular (ADR-0092) — tanınmayan bir key, sessizce yok
sayılmak yerine `helm install`/`helm upgrade`'i anında başarısız kılar. Yanlış yolda aranması kolay
olduğu için adıyla bilinmeye değer iki key: `config.confluenceAllowedHosts` ve
`config.mcpStructuredOutput` ikisi de tipli, üst düzey `config.*` key'leridir, başka bir yerde iç içe
serbest değerler değil.

## Yayımlama

Chart release'leri deponun kendi release iş akışını izler ve `gh-pages` üzerinde barındırılan bir Helm
deposuna yayımlanır, uygulamanın kendi sürümünden bağımsız olarak sürümlenir — tam release ve
sürümleme kurallarını depodaki chart'ın kendi `charts/contextator/README.md`'sinde bulabilirsiniz.
