İçeriğe geç
mcpd
daemon yok
Kaynak

Docker ve Kubernetes

Tek süreç, tek birim, yedeklenecek tek dosya. Bu sayfanın en uzun bölümü, sizden başkasının ona erişebilmesinden önce nelerin ayarlanacağını anlatandır.

Tek konteyner

Henüz bir sürüm yayımlanmadı.

Bu sitedeki komutların adını verdiği imaj, chart ve paketler planlanan dağıtımdır; hiçbiri yayımlanmadı. Bugün çalışan yol, kaynaktan derlemektir ve Rust ile Node gerekir:

Derleyip çalıştırma

git clone https://github.com/mcpd/mcpd && cd mcpd && make build && ./target/release/mcpd demo

mcpd, deposu içine derlenmiş tek bir süreçtir. Yanında çalıştırılacak bir veritabanı, bir yan araç ve bir statik dosya sunucusu yoktur: alım, sorgu API'si ve arayüz hep tek bir porttan sunulur.

docker run -p 4317:4317 -v mcpd-data:/data -e MCPD_DATA=/data/mcpd.db ghcr.io/mcpd/mcpd:0.1.0

İki kez okumaya değer kısım birimdir. O olmadan SQLite dosyası konteynerin yazılabilir katmanında durur ve trace'ler konteynerle birlikte gider. MCPD_DATA aynı nedenle açıkça ayarlanır: daemon'un yazdığı yol ile bağladığınız yol birbirinden ayrılırsa mcpd çalışmayı sürdürür ve birim boş kalır; bu da veriye ihtiyaç duyduğunuz gün öğrendiğiniz türden bir hatadır.

Etiketi sabitleyin. latest, yeniden başlatmayı kimsenin seçmediği bir anda bir yükseltmeye çevirir. Her etiket linux/amd64 ve linux/arm64 için derlenir ve imajda /data dizininin sahibi olan uid 10001 ile çalışır.

Port ve üzerinde yanıt veren şeyler

Her şey 4317 üzerindedir. Kurulumu tek konteyner yapan budur; bir giriş kuralı ya da bir ağ politikası yazarken bilmeniz gereken de budur, çünkü yolların hepsi aynı riski taşımaz.

Yol Ne olduğu Anahtar gerekir mi
/v1/traces, /v1/logs, /v1/metrics Herhangi bir OpenTelemetry SDK'sı için OTLP alımı Alım
/v1/ingest Yerel alım, tek bir istekte modelin tamamı Alım
/v1/api/… Sorgu API'si: trace'ler, span'ler, loglar, puanlar, analiz Okuma
/ İkili dosyaya gömülü operatör arayüzü Yok
/healthz, /readyz, /metrics Canlılık, hazırlık ve mcpd'nin kendi Prometheus sayaçları Yok

Bir alım isteği 32 MiB ile sınırlıdır. Prompt'lar büyüktür, o yüzden bu sınır bilerek cömerttir; ancak mcpd'nin önündeki bir proxy'nin sınırı genellikle daha küçüktür: 3 MiB'lik bir yığını reddeden bir ters proxy, bir trace'in hiç ulaşmamasının en yaygın nedenidir.

Compose

Bu dosyadaki iki karar açıklanmaya değer, çünkü ikisi de eksiklik gibi görünüyor. Port 127.0.0.1 üzerinde yayımlanır, böylece siz hâlâ anahtarları ayarlarken makinenin dışından hiçbir şey ona erişemez. Ve dosyada bir sağlık denetimi yoktur, çünkü imajda bir tane vardır: imajdaki busybox wget ile MCPD_LISTEN içindeki porta her 30 saniyede bir /healthz isteği gönderir ve sonucu docker compose ps gösterir. Docker, sağlıksız diye bir konteyneri yeniden başlatmaz; yeniden başlatma politikası süreç çıktığında devreye girer.

deploy/docker

# mcpd: one service, one volume.
#
# The volume is most of the reason this file exists. Without it the SQLite file
# lives in the container's writable layer, and `docker compose down` takes every
# trace with it.
#
# Copy .env.example to .env and fill it in before running this. The keys are not
# in this file on purpose: a compose file is committed, a .env file is not.

services:
  mcpd:
    image: ghcr.io/mcpd/mcpd:0.1.0
    restart: unless-stopped

    # Published on the loopback interface of the host, so nothing outside this
    # machine can reach the port even while you are still setting up keys.
    ports:
      - "127.0.0.1:4317:4317"

    environment:
      MCPD_LISTEN: 0.0.0.0:4317
      # Set the path explicitly. If this and the mount below ever disagree, the
      # daemon writes to the container layer and the volume stays empty.
      MCPD_DATA: /data/mcpd.db
      MCPD_PROJECT: default
      MCPD_RETENTION_DAYS: "30"
      MCPD_LOG_FORMAT: json
      MCPD_INGEST_KEYS: ${MCPD_INGEST_KEYS:?set MCPD_INGEST_KEYS in .env}
      MCPD_READ_KEYS: ${MCPD_READ_KEYS:?set MCPD_READ_KEYS in .env}
      MCPD_REDACT_KEY: ${MCPD_REDACT_KEY:?set MCPD_REDACT_KEY in .env}
      # The run store: a second file on the same volume.
      MCPD_RUNS_DATA: /data/mcpd.runs.db
      # The two scopes of the run surface. Unset accepts the write keys, which
      # here are the ingest keys; set them in .env once a worker runs outside
      # this host. No value here on purpose: Compose passes the variable only
      # when .env sets it, and `${VAR:-}` would pass an empty string, which the
      # daemon reads as one empty key rather than none.
      MCPD_EXECUTE_KEYS:
      MCPD_WORKER_KEYS:
      MCPD_REQUEST_TIMEOUT_SECONDS: "30"
      # The two bounds on one request: bytes of body, and signals in it once
      # decoded. Lower the first on a small host, since it is what decides how
      # much memory one request can take before anything is parsed.
      MCPD_MAX_BODY_BYTES: "33554432"
      MCPD_MAX_BATCH_SIGNALS: "10000"
      # Set MCPD_CORS_ORIGINS when the interface is served from another origin,
      # MCPD_REDACT_KEY_CONTAINS to mask fields the built-in rules do not know,
      # and MCPD_REDACT_MAX_STRING to lower the 128 KiB cap on a stored string.
      # MCPD_SIGNUP=true opens account sign-up; the accounts go to
      # MCPD_ACCOUNTS_DATA, beside the data file by default. Behind a proxy, set
      # MCPD_CLIENT_IP_HEADER and MCPD_TRUSTED_PROXY_CIDRS so the sign-up and
      # log-in limits count clients rather than the proxy.

    volumes:
      - mcpd-data:/data

    # No healthcheck here, because the image has one: it requests /healthz
    # every 30 seconds with the busybox wget in the image, on the port in
    # MCPD_LISTEN, and `docker compose ps` shows the result. It is the liveness
    # endpoint, never /readyz, so a slow query does not mark the daemon sick.
    # Docker does not restart a container for being unhealthy; the restart
    # policy above acts when the process exits.

    # Longer than the 30-second request timeout above, so a request still
    # running at `docker compose down` can finish before Docker kills it.
    stop_grace_period: 45s

volumes:
  mcpd-data:
docker compose -f deploy/docker/docker-compose.yml up -d

Her anahtar satırındaki :? süs değildir. Değişken ayarsızken Compose hizmeti başlatmayı reddeder; bu da unutulmuş bir anahtarı sessizce açık bir porttan bir hata iletisine çevirir. İki çalıştırma anahtarının dosyada hiç değeri yoktur, bu yüzden Compose onları yalnızca .env ayarladığında geçirir: boş bir değer daemon'a hiç anahtar olarak değil, boş bir anahtar olarak ulaşırdı.

Kubernetes

Dört nesne: bir Deployment, bir Service, bir PersistentVolumeClaim ve sizin oluşturduğunuz bir Secret. Deployment, Recreate stratejisiyle tek bir kopya çalıştırır ve bu manifest'lerde değiştirmemeniz gereken tek satır odur. mcpd her şeyi tek bir SQLite dosyasında saklar, onu tek bir süreç yazar ve aşamalı bir güncelleme, ilki dosyayı hâlâ tutarken aynı birim üzerinde ikinci bir pod başlatırdı. Bu bir ölçeklenme hikâyesi değil, bir bozulma hikâyesidir.

Secret, kubectl apply komutunun okuduğu dosyalardan biri değildir. secret.yaml.example onun biçimini gösterir ve uzantısı onu apply dışında tutar, çünkü yazıldığı gibi uygulanan bir Secret yer tutucu metinlerini anahtarlarınız yapardı. Bunun yerine onu komut satırından, o an üretilen anahtarlarla oluşturun. Pod; uid 10001 ile, salt okunur bir kök dosya sistemiyle, hiçbir yetenek (capability) olmadan ve çalışma ortamının varsayılan seccomp profiliyle çalışır. Pod Security restricted düzeyinin istediği de budur.

deploy/k8s

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mcpd
  labels:
    app.kubernetes.io/name: mcpd
spec:
  # One SQLite file has one writer. Two pods mounting the same volume is not a
  # scaling story, it is a corruption story.
  replicas: 1
  strategy:
    # RollingUpdate would start the new pod while the old one still holds the
    # database. Recreate stops the old pod first and costs a few seconds of
    # downtime, which the SDK buffers cover.
    type: Recreate
  selector:
    matchLabels:
      app.kubernetes.io/name: mcpd
  template:
    metadata:
      labels:
        app.kubernetes.io/name: mcpd
    spec:
      securityContext:
        runAsNonRoot: true
        # The uid and gid the image creates its user with, so this pod and a
        # plain `docker run` own files the same way.
        runAsUser: 10001
        runAsGroup: 10001
        # The volume is created owned by root. Without fsGroup the daemon
        # cannot create its database and exits on the first start.
        fsGroup: 10001
        # The Pod Security `restricted` level refuses a pod without it.
        seccompProfile:
          type: RuntimeDefault
      # Longer than the 30-second request timeout below, so a request still
      # running at SIGTERM can finish before the kubelet kills the process.
      terminationGracePeriodSeconds: 45
      containers:
        - name: mcpd
          image: ghcr.io/mcpd/mcpd:0.1.0
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 4317
          env:
            - name: MCPD_LISTEN
              value: 0.0.0.0:4317
            - name: MCPD_DATA
              value: /data/mcpd.db
            - name: MCPD_PROJECT
              value: default
            - name: MCPD_RETENTION_DAYS
              value: "30"
            - name: MCPD_LOG_FORMAT
              value: json
            - name: MCPD_INGEST_KEYS
              valueFrom:
                secretKeyRef:
                  name: mcpd-keys
                  key: ingest-keys
            - name: MCPD_READ_KEYS
              valueFrom:
                secretKeyRef:
                  name: mcpd-keys
                  key: read-keys
            - name: MCPD_ADMIN_KEYS
              valueFrom:
                secretKeyRef:
                  name: mcpd-keys
                  key: admin-keys
            # The two scopes of the run surface. A worker key lives in the
            # process that runs step bodies and opens nothing that reads a
            # stored prompt. Optional: with no entry in the Secret the
            # variable is not set at all, and the ingest keys are accepted.
            - name: MCPD_EXECUTE_KEYS
              valueFrom:
                secretKeyRef:
                  name: mcpd-keys
                  key: execute-keys
                  optional: true
            - name: MCPD_WORKER_KEYS
              valueFrom:
                secretKeyRef:
                  name: mcpd-keys
                  key: worker-keys
                  optional: true
            # The run store is a second file on the same volume.
            - name: MCPD_RUNS_DATA
              value: /data/mcpd.runs.db
            - name: MCPD_REQUEST_TIMEOUT_SECONDS
              value: "30"
            # The two bounds on one request: bytes of body, and signals in it
            # once decoded. Lower the first on a small pod, since it is what
            # decides how much memory one request can take.
            - name: MCPD_MAX_BODY_BYTES
              value: "33554432"
            - name: MCPD_MAX_BATCH_SIGNALS
              value: "10000"
            # Set when the interface is served from another origin, as a
            # comma-separated list of origins; and to mask fields the built-in
            # rules do not know, or to lower the 128 KiB cap on a stored string:
            #   MCPD_CORS_ORIGINS, MCPD_REDACT_KEY_CONTAINS, MCPD_REDACT_MAX_STRING
            - name: MCPD_REDACT_KEY
              valueFrom:
                secretKeyRef:
                  name: mcpd-keys
                  key: redact-key
          # Liveness never touches storage, so a slow query cannot restart a
          # process that is working.
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3
          # Readiness pings the store, so traffic only reaches an instance that
          # can actually write to its database.
          readinessProbe:
            httpGet:
              path: /readyz
              port: http
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 2
          # Migrations run at start. On a large database that takes longer than
          # the liveness probe would allow, so the startup probe holds it off.
          startupProbe:
            httpGet:
              path: /healthz
              port: http
            periodSeconds: 2
            failureThreshold: 45
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              # No CPU limit: ingest arrives in bursts, and throttling the
              # writer is how a queue turns into dropped spans.
              memory: 512Mi
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsNonRoot: true
            capabilities:
              drop:
                - ALL
            seccompProfile:
              type: RuntimeDefault
          volumeMounts:
            - name: data
              mountPath: /data
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: mcpd-data
export INGEST_KEY=$(openssl rand -hex 32) READ_KEY=$(openssl rand -hex 32) ADMIN_KEY=$(openssl rand -hex 32)
kubectl create namespace mcpd
kubectl -n mcpd create secret generic mcpd-keys \
  --from-literal=ingest-keys=$INGEST_KEY \
  --from-literal=read-keys=$READ_KEY \
  --from-literal=admin-keys=$ADMIN_KEY \
  --from-literal=redact-key=$(openssl rand -hex 32)
kubectl -n mcpd apply -f deploy/k8s/

Üç anahtarı ekibinizin sırları sakladığı yerde tutun: alım anahtarı her ajana, okuma anahtarı arayüzü okuyan kişiye, yönetici anahtarı da veri silebilecek kişiye gider. Bir worker kümenin dışında çalışmaya başladığında execute-keys ve worker-keys anahtarlarını da aynı yolla ekleyin; o zamana kadar ikisi için de alım anahtarları kabul edilir.

export EXECUTE_KEY=$(openssl rand -hex 32) WORKER_KEY=$(openssl rand -hex 32)
kubectl -n mcpd patch secret mcpd-keys --type merge \
  -p '{"stringData":{"execute-keys":"'$EXECUTE_KEY'","worker-keys":"'$WORKER_KEY'"}}'
kubectl -n mcpd rollout restart deploy/mcpd
kubectl -n mcpd port-forward svc/mcpd 4317:4317

Küme içindeki ajanlar Service'i gösterir. SDK'lar bu ikisini de ortamdan okur, yani ölçümlenmiş kodun değişmesi gerekmez:

MCPD_ENDPOINT=http://mcpd.mcpd.svc:4317 MCPD_API_KEY=$INGEST_KEY

Yoklamalar

İki uç bilerek farklı işler yapar ve yerlerini değiştirmek belirli ve çirkin bir hataya yol açar.

  • /healthz depoya hiç dokunmaz. Yalnızca süreç durumundan yanıt verir. Veritabanını sorgulayan bir canlılık yoklaması, büyük bir sorgu daemon'u her yavaşlattığında sağlıklı bir daemon'u yeniden başlatırdı; oysa yeniden başlatmak, bir ileri yazma günlüğü boşaltılırken tam da istemediğiniz şeydir.
  • /readyz depoya ping atar ve yapamadığında "status": "degraded" ile neyin yanlış olduğunu söyleyen bir satırı 503 olarak döndürür. Bu, hizmet veremeyen bir örneği öldürmeden trafikten uzak tutar.

Başlangıç yoklaması oradadır, çünkü göçler açılışta çalışır. Tekrar çalıştırılmaları güvenlidir ve genellikle anlıktır, ama büyük bir veritabanında canlılık yoklamasının hoş göreceğinden uzun sürebilirler; her denemede göçün ortasında öldürülen bir pod ise hiç başlamaz.

Ona ne vermeli

Bu sayılar bir kıyaslamadan değil, sürecin gerçekte yaptığı işten gelir ve hiçbir kıyaslama iddia edilmiyor.

Kaynak Değer Neden
CPU isteği 100m Boştayken neredeyse hiçbir şeye mal olmaz. İstek, zamanlayıcı onu gürültülü bir şeyin altına yığmasın diye vardır.
CPU sınırı yok Alım dalgalar hâlinde gelir. Dalga sırasında yazıcıyı kısmak, bir istemci kuyruğunun dolmasının ve span'lerin düşürülmesinin yoludur.
Bellek isteği 128Mi Statik bir ikili dosya, beş SQLite bağlantısı ve onların sayfa önbellekleri.
Bellek sınırı 512Mi Eşzamanlı alım için pay. Tek bir istek 32 MiB prompt metni tutabilir, yani bellek aynı anda kaç büyük yığının geldiğini izler.
Birim 20Gi, ReadWriteOnce Kendi yüklerinize göre boyutlandırın. Metin baskındır ve arama dizini dış içerikli değildir, yani dizine alınan prompt'lar ve tamamlamalar bir kez daha saklanır.

Birimi yerel bir blok aygıtında tutun. SQLite'ın kilitlemesi yalnızca orada doğrudur: NFS veya başka bir paylaşımlı dosya sisteminde kilitler en iyi ihtimalle danışma niteliğindedir ve hata bir hata iletisi olarak değil, haftalar sonra bozuk bir veritabanı olarak ortaya çıkar.

Helm chart'ı

Chart aynı dört nesneyi, önlerinde bir değerler dosyasıyla oluşturur; ayrıca bir değer açana kadar kapalı duran altı nesne daha: bir Ingress, bir NetworkPolicy, bir PodDisruptionBudget, bir ServiceAccount, bir ServiceMonitor ve yapılandırma dosyası için bir ConfigMap ya da Secret. Her sürüm chart'ı imajla aynı registry'ye gönderir. Anahtarları önce üretin ve ekibinizin sırları sakladığı yerde tutun, çünkü kurulum onları size geri yazdırmayacak:

helm install mcpd oci://ghcr.io/mcpd/charts/mcpd -n mcpd --create-namespace --set keys.ingest=$INGEST_KEY --set keys.read=$READ_KEY

keys.ingest veya keys.read ayarsız olduğunda kurulum başarısız olur ve nedenini söyler. Açık bir alım ucunu sonradan keşfetmenize izin veren bir chart işini yapmıyor olurdu. Secret'ı kendiniz yönetiyorsanız bunun yerine keys.existingSecret ayarlayın; chart hiçbiri oluşturmaz.

Pod şablonu Secret'ın bir sağlama toplamını taşır, yani bir anahtarı döndürmek ve helm upgrade çalıştırmak pod'u yeniden başlatır. Bu olmadan yeni anahtar kümede durur ve daemon eskisini uygulamayı sürdürür.

Monitörler, SLO'lar, hakem, proje başına anahtarlar ve çalıştırma deposunun zamanlamaları bayraklardan değil, daemon'un yapılandırma dosyasından okunur. Bu JSON nesnesini configFile.content altına yazın; chart onu salt okunur bağlar ve MCPD_CONFIG_FILE değişkenini ona yöneltir. Proje başına anahtarlar, hakemin api_key değeri ve yönlendirme başlıkları kimlik bilgisidir, bu yüzden chart bunlardan birini taşıyan içeriği bir ConfigMap'e koymayı reddeder: configFile.kind değerini Secret yapın ya da dosyayı kendi yönettiğiniz bir Secret'ta tutup configFile.existingSecret ile adlandırın. extraEnv, extraVolumes ve extraVolumeMounts özel bir CA paketi gibi geri kalan her şeyi karşılar.

Chart'ın sunamayacağı her değer bileşimi, kurulum anında değiştirilecek değeri adlandıran bir iletiyle reddedilir; hiçbiri sonradan çökme döngüsüne bırakılmaz. Chart'ın README dosyası her reddi listeler.

deploy/helm/mcpd

# mcpd chart values.
#
# The chart refuses to install without keys. That is deliberate: a daemon on a
# cluster network with open ingest accepts writes from anything that can reach
# the Service, and a chart that lets you find that out later is not doing its
# job. Set keys.ingest and keys.read, or point keys.existingSecret at a Secret
# you manage yourself.

image:
  repository: ghcr.io/mcpd/mcpd
  # Empty means the chart's appVersion. Pin it in production.
  tag: ""
  pullPolicy: IfNotPresent

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

# Daemon configuration. Each of these maps to one environment variable.
config:
  # The container port and the NetworkPolicy port are read from the number
  # after the last colon, so moving the listener moves both.
  listen: 0.0.0.0:4317
  dataPath: /data/mcpd.db
  project: default
  # Days to keep signals. Empty or 0 keeps everything for ever, and the chart
  # then sets no MCPD_RETENTION_DAYS at all.
  retentionDays: 30
  # Fraction of traces to keep, 0.0 to 1.0. Empty keeps all of them.
  sample: ""
  logFormat: json
  # Turn off built-in redaction. Only sensible when something upstream already
  # redacts.
  noRedact: false
  # Mask any value whose key contains one of these, on top of the built-in
  # rules. Matched on lowercase substrings of the key.
  redactKeyContains: []
  # Longest single string stored, in bytes. Empty keeps the built-in 128 KiB.
  redactMaxString: ""
  # Where the run store lives, when runs.enabled is true. It must be inside the
  # directory of dataPath, because that directory is the only writable mount.
  # Empty derives it from dataPath: /data/mcpd.runs.db for the default. "off"
  # is the same as runs.enabled false.
  runsPath: ""
  # Browser origins allowed to call the query API from another site, such as
  # an interface served from a portal. Empty answers no cross-origin request.
  corsOrigins: []
  # Longest a request may take before it is answered with a 503. Empty keeps
  # the built-in 30 seconds. Raise terminationGracePeriodSeconds with it.
  requestTimeoutSeconds: ""

# An external database for signals, instead of the embedded file.
#
# An external store is what lets mcpd run more than one pod: the single writer
# and the single replica are limits of the SQLite file, not of mcpd. It is half
# of what it takes, though. The run store is always an embedded file (see
# `runs` below), so more than one replica also needs runs.enabled false.
#
# `type` decides the mode, never the URLs. That is what lets a URL live only in
# a Secret you manage: set the type here, point keys.existingSecret at the
# Secret, and leave the URL values empty. A URL value with type none, or with
# the other type, refuses to render rather than being ignored.
externalStore:
  # none, postgres or clickhouse.
  type: none
  # A Postgres connection string. Reach for this first: it lifts both limits on
  # a database most teams already run, back up and monitor.
  postgresUrl: ""
  # A ClickHouse HTTP endpoint. Reach for this when retention is measured in
  # billions of spans.
  clickhouseUrl: ""
  clickhouseDatabase: mcpd
  clickhouseUser: ""
  # Both URLs and the ClickHouse password carry credentials, so they are held in
  # the chart's Secret rather than in the pod spec. With keys.existingSecret,
  # the chart creates no Secret: put them in yours under the keys postgres-url,
  # clickhouse-url and clickhouse-password, and leave these values empty.
  clickhousePassword: ""

# The durable run surface: definitions, the journal of step attempts, deadlines
# and waits.
#
# Its store is an embedded SQLite file whatever the signal store is, so while
# it is on, the pod keeps a file on a volume and runs as one replica. With an
# external store that volume holds the run store alone. Turn it off to run
# several pods against an external store; the daemon then answers the run
# endpoints with a 503 that says so, and nothing else changes.
runs:
  enabled: true

# Pods to run. More than one needs both an external store and runs.enabled
# false, because each embedded SQLite file has one writer. The chart refuses to
# render a larger count otherwise, rather than pinning it to 1 behind your back.
replicaCount: 1

keys:
  # Comma-separated keys permitted to write.
  ingest: ""
  # Comma-separated keys permitted to read stored prompts and completions.
  read: ""
  # Comma-separated keys permitted to change stored data: scores, feedback,
  # datasets, experiments and acknowledgements. Unset means the ingest keys are
  # accepted, which is what a deployment written before this scope existed did.
  # The ingest key is in every container that emits a span, so separating them
  # is worth doing once you have more than one team.
  write: ""
  # Comma-separated keys permitted to destroy data and read the audit log.
  # Unset means the read keys are accepted, so a read key can delete a trace.
  # `mcpd doctor` reports that as a warning and names this setting.
  admin: ""
  # Comma-separated keys permitted to start, cancel and signal runs. Unset
  # means the write keys are accepted, and an admin key always is.
  execute: ""
  # Comma-separated keys permitted to claim steps and report their outcome.
  # This is the key that lives in the process running step bodies. Unset means
  # the write keys are accepted, and an admin key always is.
  worker: ""
  # Salt for the hashes redaction leaves behind. Optional, but set it anywhere
  # that hashes: without it, digests of short identifiers can be guessed.
  redact: ""
  # Use a Secret you already manage instead of the values above. It must carry
  # ingest-keys and read-keys, and optionally write-keys, admin-keys,
  # execute-keys, worker-keys and redact-key.
  existingSecret: ""

# The two bounds on one request. Empty means the built-in defaults, 33554432
# bytes (32 MiB) of body and 10000 signals per request.
#
# The byte cap is what bounds how much memory one request can take before
# anything is decoded, so lower it on a small pod. The signal cap is counted
# across the whole request on every ingest door, whether the signals arrive as
# one batch, an array of batches or an NDJSON stream.
maxBodyBytes: ""
maxBatchSignals: ""

# A ceiling on signals stored per project per minute. Empty means no ceiling,
# which is what a deployment that never sets it had before quotas existed.
#
# It stops one project spending the whole daemon. Signals over the ceiling are
# refused and reported with the count and the project, never dropped in silence,
# and the rest of the batch is still stored.
quotaPerMinute: ""

# Record WHICH PERSON acted on an audit row, not only which key.
#
# mcpd has no identity provider and is not getting one, so the identity comes
# from the ingress already in front of it. Both halves are required: the header
# your ingress sets, and the ranges allowed to set it. A header is a string
# anyone who can reach the port may send, so mcpd reads it only from an address
# you have named here. A request from anywhere else is served normally and its
# audit row carries no subject.
#
# Set trustedProxyCidrs to the range your ingress controller runs in, not to
# 0.0.0.0/0, which would trust every client to name itself.
identity:
  # For example x-forwarded-user, x-forwarded-email or x-auth-request-user.
  header: ""
  # A list of CIDR ranges, for example ["10.42.0.0/16"].
  trustedProxyCidrs: []
  # The header your proxy puts the client's address in, for example
  # cf-connecting-ip. Read only from trustedProxyCidrs. The sign-up and log-in
  # limits count this address; without it they count the proxy's.
  clientIpHeader: ""

# Open sign-up: anyone may create an account, which owns one new project and
# its own API keys. Off by default. On, a request with no key is refused
# everywhere except the health probes, sign-up and log-in. Set
# identity.clientIpHeader and identity.trustedProxyCidrs as well when an
# ingress is in front, so the per-address limits count clients.
signup:
  enabled: false
  # Where the accounts file goes. Empty puts it beside the data file, as
  # mcpd.accounts.db, which is on the volume.
  accountsData: ""

# Copy every stored span on to another OTLP endpoint. The local write always
# happens first, so a slow or absent destination never costs you the data.
forward:
  # Base URL, not the /v1/traces path; mcpd appends it.
  endpoint: ""
  # Headers as "Name: value" strings. A credential here lands in the Secret.
  headers: []

# The volume for whatever the pod keeps on disk: the signal store and the run
# store on the embedded store, or the run store alone with an external one.
# Not used with an external store and runs.enabled false, since nothing local
# is left to keep.
#
# With enabled false the chart mounts an emptyDir instead, so the daemon can
# still write, and everything it wrote is gone when the pod is.
persistence:
  enabled: true
  # Sized for the embedded signal store. A volume that holds the run store
  # alone needs far less.
  size: 20Gi
  # Leave empty for the cluster default. SQLite locking is only correct on a
  # local block device, so do not point this at NFS.
  storageClass: ""
  existingClaim: ""

service:
  type: ClusterIP
  port: 4317
  annotations: {}

# Everything from here to `resources` is off by default, so an install that sets
# nothing renders the same four objects it always did. Each block is here
# because a chart review asks a question this chart could not otherwise answer.

# "What identity does this pod run as, and what can that identity do?"
serviceAccount:
  create: false
  # With create true, empty means the release's fullname. With create false,
  # naming an account you manage yourself puts the pod on it, and leaving it
  # empty keeps the namespace's `default` account, which is today's behaviour.
  name: ""
  annotations: {}
  # mcpd never calls the Kubernetes API, so it has no use for a token.
  # Turn this on only if something you add to the pod needs one.
  automountServiceAccountToken: false

# "How does traffic reach mcpd from outside the cluster, and who terminates
# TLS?"
#
# Not mcpd: it speaks plain HTTP and has no TLS configuration, so the ingress
# controller has to terminate. Two things to know before you publish it.
#
# One path root. mcpd serves OTLP ingest, the query API and the operator
# interface from the same root, so a rule for "/" publishes all three, and your
# ingest keys are what stands between the open internet and a write. There is no
# path prefix that separates them.
#
# Body size. mcpd accepts a 32 MiB batch, and several ingress controllers cap a
# request body at 1 MiB by default, so a batch the daemon would have taken is
# refused before it arrives. On ingress-nginx the annotation to raise is
# `nginx.ingress.kubernetes.io/proxy-body-size`.
ingress:
  enabled: false
  className: ""
  annotations: {}
  hosts:
    - host: mcpd.example.com
      paths:
        - path: /
          pathType: Prefix
  tls: []
  #  - secretName: mcpd-tls
  #    hosts:
  #      - mcpd.example.com

# "Only these namespaces may reach ingest."
#
# The limit belongs in the answer, so here it is. mcpd serves ingest, the query
# API, the operator interface and /metrics on ONE port, and a NetworkPolicy
# matches pods and ports, never paths. This gates the whole daemon or nothing:
# every source you allow in for ingest can also reach /v1/api and read stored
# prompts and completions. The read key is what holds those apart, not this
# policy. A split at the network layer would need a second listener, and mcpd
# does not have one.
#
# What it does do is still worth having. With no policy in the namespace, every
# pod in the cluster can open a connection to mcpd. With one, only these can.
#
# A trap worth knowing first: some CNI plugins enforce policy on traffic
# arriving from the node, and the kubelet's liveness and readiness probes
# arrive that way. On those clusters, add the node CIDR to `ipBlocks`, or the
# pod never becomes ready and the rollout stalls.
networkPolicy:
  enabled: false
  ingress:
    # Whole namespaces, by label. Kubernetes sets
    # `kubernetes.io/metadata.name` on every namespace itself, so this works
    # without labelling anything first.
    namespaceSelectors: []
    #  - matchLabels:
    #      kubernetes.io/metadata.name: agents
    # Pods in mcpd's OWN namespace. A podSelector with no namespaceSelector
    # beside it never reaches across a namespace boundary.
    podSelectors: []
    #  - matchLabels:
    #      app.kubernetes.io/part-of: checkout-agents
    ipBlocks: []
    #  - cidr: 10.42.0.0/16
    #    except:
    #      - 10.42.7.0/24
    # Whole `from` entries, written out and appended as they are. Use this for
    # the one thing the three lists above cannot say: a namespaceSelector and a
    # podSelector inside a SINGLE entry, which means "those pods, in those
    # namespaces" rather than "either of the two".
    extraFrom: []
    #  - namespaceSelector:
    #      matchLabels:
    #        kubernetes.io/metadata.name: agents
    #    podSelector:
    #      matchLabels:
    #        app.kubernetes.io/name: checkout-agent
  egress:
    # Leave this off unless you have a reason. Restricting egress breaks four
    # things and breaks them quietly, because mcpd logs a warning and carries
    # on: alert webhooks, forwarding to another OTLP endpoint, the judge's model
    # endpoint, and an external Postgres or ClickHouse.
    enabled: false
    allowDns: true
    dnsNamespace: kube-system
    # Complete egress rules, appended as written, because what mcpd is allowed
    # to dial depends entirely on what you configured it to dial.
    rules: []

# "What happens to ingest while a node is drained?"
#
# Only answerable with more than one replica, which takes an external store and
# runs.enabled false. With one replica, a budget over that pod either blocks
# every drain or permits every one. The chart refuses to render it there rather
# than let you discover which during a cluster upgrade.
podDisruptionBudget:
  enabled: false
  # Set one of these, never both. With replicaCount 2, minAvailable 1 lets one
  # pod go at a time and keeps ingest up through a drain.
  minAvailable: 1
  maxUnavailable: ""

# "Is the collector itself monitored?"
#
# Needs the Prometheus Operator's CRDs in the cluster. Without them the install
# fails on a kind the API server does not know, which is why this is off rather
# than on with a capability check: a chart that silently drops the object you
# asked for is worse than one that fails.
#
# There is no credential below because mcpd serves /metrics with no key. It
# exposes counters and uptime, not stored prompts or completions.
serviceMonitor:
  enabled: false
  # Empty means the release's namespace.
  namespace: ""
  interval: 30s
  scrapeTimeout: 10s
  # The labels your Prometheus selects ServiceMonitors by. Without the ones it
  # expects, this object is created and never scraped, and nothing reports that.
  labels: {}
  relabelings: []
  metricRelabelings: []

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    memory: 512Mi

# The uid and gid are the ones the image creates its user with, so the pod and
# a plain `docker run` own files the same way. fsGroup is what makes a new
# volume, created owned by root, writable to that user. The seccomp profile is
# required by the Pod Security `restricted` level, which refuses the pod
# without it.
podSecurityContext:
  runAsNonRoot: true
  runAsUser: 10001
  runAsGroup: 10001
  fsGroup: 10001
  seccompProfile:
    type: RuntimeDefault

securityContext:
  allowPrivilegeEscalation: false
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  capabilities:
    drop:
      - ALL
  seccompProfile:
    type: RuntimeDefault

# Longer than the longest request. A request may run for the request timeout,
# 30 seconds by default, and the pod has to outlive it after SIGTERM or the
# kubelet kills it mid-write. Raise it with config.requestTimeoutSeconds.
terminationGracePeriodSeconds: 45

# The settings too structured for an environment variable: monitors, SLOs, the
# judge, per-project keys, retention and sampling under `projects`, and the run
# store's timings under `runs`. Written here as the JSON object the daemon's
# --config-file reads (see https://mcpd.sh/docs/configuration), mounted
# read-only at /etc/mcpd/mcpd.json, and named in MCPD_CONFIG_FILE. A change
# restarts the pod, as a key rotation does.
#
# Per-project keys, the judge's api_key and forward headers are credentials. A
# ConfigMap is readable by anything allowed to list ConfigMaps in the
# namespace, so the chart refuses to put content carrying any of them in one.
# Set kind to Secret, or keep the file in a Secret you manage and name it in
# existingSecret.
configFile:
  content: {}
  #  monitors:
  #    - name: error-rate
  #      ...
  #  projects:
  #    checkout:
  #      retention_days: 90
  # ConfigMap or Secret: the object `content` is rendered into.
  kind: ConfigMap
  # A Secret you manage that holds the file, instead of `content`.
  existingSecret: ""
  # The entry in existingSecret that holds the file.
  existingSecretKey: mcpd.json

# Extra environment variables, as Kubernetes writes them, appended after the
# ones the chart sets. Use it for a setting this chart has no value for, or to
# read one from a Secret of your own with valueFrom.
extraEnv: []
#  - name: MCPD_FORWARD_HEADERS
#    valueFrom:
#      secretKeyRef:
#        name: otlp-credentials
#        key: headers

# Extra volumes and where to mount them, for example a CA bundle for a
# ClickHouse or a judge endpoint behind a private certificate authority.
extraVolumes: []
extraVolumeMounts: []

podAnnotations: {}
nodeSelector: {}
tolerations: []
affinity: {}

Başka bir şey ona erişebilmeden önce

Alım varsayılan olarak açıktır. Bu, bir dizüstündeki ilk beş dakika için bilinçli bir seçimdir; orada ilk trace'ten önce bir kimlik bilgisi istemek yararsız bir sürtünme olurdu. Port başka bir şey tarafından erişilebilir olduğu anda ise yanlış varsayılandır. Bu yüzden daemon, anahtarsız olarak geri döngü olmayan bir adreste her başladığında bunu söyler:

korunmasız bir başlangıç nasıl görünür
$ docker run -p 4317:4317 -v mcpd-data:/data ghcr.io/mcpd/mcpd:0.1.0mcpd 0.1.0 listening on http://0.0.0.0:4317WARN listening on 0.0.0.0:4317 with open ingest. Anyone who can     reach this port can write to it. Set --ingest-key (or     MCPD_INGEST_KEYS) before exposing it.WARN listening on 0.0.0.0:4317 with an open read API. Stored     prompts and completions are readable by anyone who can reach     this port. Set --read-key to require a key.

mcpd, başlamayı reddetmek yerine uyarır, çünkü reddetmek onu çalıştırmaya değer kılan yolu koparırdı. Uyarı sözleşmedir: bunu bir kurulum kaydında görüyorsanız o kurulum açıktır.

Altı anahtar, altı kapsam

--ingest-key sinyal yazmaya izin verir. --read-key onları okumaya izin verir. --write-key saklanmış veriyi değiştirmeye izin verir: puanlar, geri bildirimler, veri kümeleri, deneyler ve onaylar. --execute-key çalıştırmaları başlatmaya, iptal etmeye ve onlara sinyal göndermeye izin verir. --worker-key adımları üstlenmeye ve onlara ne olduğunu bildirmeye izin verir; adım gövdelerinizi çalıştıran süreçte duran anahtar budur. --admin-key bir trace'i silmeye ve denetim günlüğünü okumaya izin verir.

Patlama yarıçapınıza karar veren ilk ayrımdır, çünkü kalabalıklar farklı büyüklüktedir: filonuzdaki her ajan sürecinin alım anahtarına ihtiyacı vardır, diğerlerinden herhangi birine ise çok daha az sayıda insanın. Filonuza aynı zamanda okuyan tek bir anahtar vermek, saklanmış her prompt'un kimlik bilgisini span üreten her konteynere koyardı; üçüncü taraf kod çalıştıranlar dâhil.

Bir çalıştırmayı veri kümesine almanın yazma kapsamına geçmesinin nedeni de budur. Geçerken çalıştırmanın girdisini ve çıktısını okur, yani onu filonuzun taşıdığı anahtarla yetkilendirmek o konteynerlerin her birine saklanmış herhangi bir prompt'u dışarı kopyalamanın bir yolunu vermiş olurdu.

Her seçenek virgülle ayrılmış bir liste alır ve her birinin bir ortam değişkeni vardır: MCPD_INGEST_KEYS, MCPD_READ_KEYS, MCPD_WRITE_KEYS ve MCPD_ADMIN_KEYS. Birden fazla kullanın: ekip başına veya filo başına bir anahtar, sızan bir anahtarın span üreten her şeyi yeniden dağıtarak değil listeden çıkarılarak iptal edilmesi demektir. Anahtar Authorization: Bearer … olarak veya X-Mcpd-Key başlığında sunulur ve sabit zamanda karşılaştırılır.

Bilinmesi gereken asimetri, iki yeni seçeneği ayarsız bıraktığınızda ne olduğudur. Ayarsız bir yazma listesi alım anahtarlarını, ayarsız bir admin listesi okuma anahtarlarını kabul eder; yani bir yükseltme çalışmakta olan bir anahtarı hiçbir zaman iptal etmez. --admin-key ayarlanana kadar okuma anahtarı, salt okunur bir anahtar değil, okuma ve silme anahtarıdır ve daemon bunu açılışta söyler. Ya buna göre dağıtın ya da seçeneği ayarlayın.

Kim neyi okudu

Yük geri veren işleyiciler erişimi kaydeder: trace ayrıntısı, span ve log aramaları, geri bildirim, veri kümesi öğeleri, alma ve silme. Bir satır zamanı, işlemi yapan anahtarın kısa bir özetini, eylemi, projeyi, kaynağı ve yetkilendirmenin izin verip vermediğini taşır. Anahtarın kendisi hiçbir zaman yazılmaz, yani günlük kimlik bilgilerinizin durduğu ikinci bir yer değildir.

Onu GET /v1/api/audit ile okuyun; admin kapsamı ister. Ona güvenmeden önce bilinmeye değer iki özelliği vardır. --retention-days ile budanmaz, çünkü kendini anlattığı verinin takvimiyle silen bir günlük, artık ortada olmayan veriyi kimin okuduğunu söyleyemezdi. Ve bir satırın yazılması, anlattığı okumayı hiçbir zaman başarısız kılmaz: yazılamayan bir satır /metrics üzerinde mcpd_audit_write_failures_total olarak sayılır, uyarı kurulacak sayaç odur.

Bir anahtarın kapsamadıkları

  • Arayüz. / üzerindeki işaretleme, porta erişebilen herkese sunulur. Arkasındaki veri sunulmaz: yaptığı her istek, okuma anahtarını uygulayan sorgu API'sinden geçer. Daemon'da bir oturum açma ekranı yoktur; yani hizmetlerin değil kişilerin erişmesi gerekiyorsa onu zaten çalıştırdığınız kimlik proxy'sinin arkasına koyun.
  • Yoklamalar ve /metrics. Bilerek kimlik doğrulaması yoktur, çünkü bir kubelet ve bir Prometheus collector'ı anahtar taşıyamaz. Sayaçları ve çalışma süresini gösterirler, yükleri hiç.
  • Taşıma. mcpd düz HTTP konuşur ve TLS sonlandırmaz. Onu girişinizde veya ağ örgünüzde sonlandırın. Prompt'lar ve tamamlamalar o bağlantıdan olduğu gibi geçer.
  • Maskelenmiş yüklerdeki özetler. --no-redact vermediğiniz sürece maskeleme açıktır ve bir tanımlayıcıyı özetlediği yerde özet ancak tuzu kadar özeldir. --redact-key ayarlayın, yoksa kısa tanımlayıcılar tahminle geri elde edilebilir. Bakınız Maskeleme ve saklama.

Açmadan önce denetleyin

mcpd config, bir çağrının kullanacağı yapılandırmayı yazdırır ve çıkar. Yanlış yere ayarladığınız ortam değişkeninin hiçbir işe yaramadığını öğrenmenin en ucuz yoludur:

uçuş öncesi
$ docker compose -f deploy/docker/docker-compose.yml run --rm mcpd configlisten          0.0.0.0:4317storage         sqlite at /data/mcpd.dbconfig file     nonelog format      jsonproject         defaultprojects        noneretention       30 daysingest auth     key requiredread auth       key requiredwrite auth      key required (ingest keys)admin auth      key required (read keys)execute auth    key required (ingest keys)worker auth     key required (ingest keys)signup          offruns            sqlite at /data/mcpd.runs.dbrun settings    lease_ttl_ms 30000, reclaim_window_ms 30000, stall_window_ms 300000, idempotency_window_ms 86400000, park_ttl_ms 86400000, max_depth 8, max_step_repeats 16identity        offclient ip       peer addresssampling        keep everythingquota           unlimitedredaction       onredact key      setcors            same-origin onlytimeout         30s per requestlimits          32 MiB per request, 10000 signals per batchderived         duration and token metricsaspects         offmonitors        noneobjectives      nonejudge           offforwarding      off

Burada altı satır önemlidir. Herhangi bir yetki satırı open diyorsa o kapsam, porta erişebilen herkese açıktır. Köşeli parantez içindeki bir satır, o kapsamın devraldığı daha geniş listeyi adlandırır: yukarıdaki çağrı hiç --admin-key ayarlamıyor, yani okuma anahtarları okuduğu gibi siliyor. --execute-key ya da --worker-key da ayarlamıyor, yani alım anahtarları çalıştırma da başlatır ve adım da üstlenir; mcpd bunu başlarken uyarır. Bunun yazdırdığı her seçenek Yapılandırma sayfasında belgelenmiştir.

Yedekler

Yedek tek bir dosyadır. mcpd'nin sakladığı her şey, arama dizini dâhil, MCPD_DATA konumundaki dosyanın içindedir.

Daemon çalışırken o dosyayı kopyalamak, yapılmaması gereken tek şeydir. mcpd SQLite'ı WAL kipinde çalıştırır, yani son yazmalar yanındaki bir -wal dosyasında durur ve yalnızca veritabanının bir kopyası, onları içermeyen bir kopyadır. Bunun yerine, sqlite3 aracı olan ve aynı birimi bağlayan herhangi bir imajdan tutarlı bir kopya alın:

sqlite3 /data/mcpd.db ".backup '/backup/mcpd-$(date -u +%Y%m%dT%H%M).db'"

İkisi de yazmalar sürerken tutarlı bir anlık görüntü okur. CSI sürücünüzün aldığı bir birim anlık görüntüsü de işe yarar, sürücünün anlık görüntüsü atomikse; canlı bir dizinin tar arşivi yaramaz. Geri yükleme, daemon'u durdurmak, dosyayı MCPD_DATA konumuna geri koymak, yanındaki -wal ve -shm dosyalarını silmek ve daemon'u yeniden başlatmaktır; İşletme bunu adım adım anlatır.

Silinen satırlar dosyanın içinde sayfa boşaltır, dosyayı küçültmez; yani çok veri eskitmiş bir veritabanı olduğu boyutta kalır. Diski geri istediğinizde VACUUM çalıştırın.

Yükseltmeler

Etiketi değiştirin ve yeniden başlatın. Göçler açılışta çalışır ve her açılışta çalıştırılmaları güvenlidir, yani sürecin dışında hiçbir adım olmaz. Yine de önce yedeği alın: göç, bir şemanın verinizin altında değiştiği tek andır.

Yeniden başlatma, tek kopya olmasının aynı nedeniyle, aşamalı değil kısa bir kesintidir. Ajanlar bunu atlatır: SDK'lar bellekte kuyruğa alır ve yeniden dener, kuyruk da sınırlıdır; yani saniyeler süren bir yeniden başlatma hiçbir şeye mal olmaz, bir saat sürense en eski span'leri düşürür ve bunu istemcinin kaydında söyler.

mcpd, Kubernetes'in gönderdiği SIGTERM sinyalini karşılar ve düzgün kapanır. terminationGracePeriodSeconds en uzun istekten uzun olmalıdır, yoksa kubelet süreci bir yazmanın ortasında öldürür. Bir istek 30 saniyelik istek zaman aşımı kadar sürebilir; bu yüzden manifestler ve chart pod'a 45 saniye verir, compose dosyası da konteynere aynısını verir. İkisini birlikte yükseltin.

Tek bir SQLite dosyası nerede yetmez ve bu konuda ne yapmalı

Gömülü dosya doğru varsayılandır ve iki sert sınırı vardır. İkisi de mcpd'nin değil SQLite'ın sınırıdır.

  • Tek yazıcı. Okumalar kendi bağlantılarında çalışır ve yazıcıyı hiç engellemez, WAL kipi bunun içindir; ama dosyanın sahibi tek bir süreçtir. O örneği yatay değil dikey ölçeklersiniz.
  • Tek birim, tek erişilebilirlik bölgesi. ReadWriteOnce bir talep pod'u sabitler. Veri sağlamken başka bir düğüme devretme yoktur.

İki ayar genellikle yeni bir arka ucun kazandıracağından fazlasını geri kazandırır. Örnekleme trace'lerin bir bölümünü tutar ve tuttuğu her trace'i bütün tutar; kararı trace kimliğinin özetini alarak verir, böylece bir filodaki her örnek anlaşır. Saklama, dosyanın ne kadar geriye gittiğini sınırlar. Her biri kendi birimiyle ekip başına bir örnek çalıştırmak da işe yarar ve gürültülü bir ekibin herkesin diskini doldurmasını engeller.

Bunlar tükendiğinde mcpd'yi bir veritabanına yönlendirin. Chart'ta externalStore.type değerini postgres ya da clickhouse yapın, bağlantı dizesini de externalStore.postgresUrl veya externalStore.clickhouseUrl içine yazın. Dize pod tanımında değil chart'ın Secret'ında tutulur. Onu değerlerinizin tamamen dışında tutmak için kendi yönettiğiniz bir Secret'a postgres-url ya da clickhouse-url altında koyun, o Secret'ı keys.existingSecret ile adlandırın ve türü yine de ayarlayın: arka ucu URL değil tür seçer.

Bu, sinyalleri taşır ve diskte tek bir dosya bırakır: sinyal deposu ne olursa olsun gömülü SQLite olan çalıştırma deposu. Varsayılan olan runs.enabled true iken chart onu birimde tutar ve tek kopya çalıştırır. Birkaç pod çalıştırmak için çalıştırma yüzeyini kapatın. Chart o zaman replicaCount kadar pod çalıştırır, Recreate yerine aşamalı güncellemeye geçer ve PersistentVolumeClaim'i bırakır, çünkü çekişilecek yerel bir şey kalmaz; daemon da çalıştırma uçlarını bunu söyleyen bir 503 ile yanıtlar. Chart, çalıştırmalar açıkken 1'den büyük bir replicaCount değerini, her biri kendi çalıştırma deposunu tutan pod'lar başlatmak yerine reddeder.

helm install mcpd ./deploy/helm/mcpd \
  --set keys.ingest=$INGEST_KEY \
  --set keys.read=$READ_KEY \
  --set externalStore.type=postgres \
  --set externalStore.postgresUrl=postgres://mcpd:$PGPASSWORD@db:5432/mcpd \
  --set runs.enabled=false \
  --set replicaCount=3

Önce Postgres'e uzanın: iki sınırı da, çoğu ekibin zaten çalıştırdığı, yedeklediği ve izlediği bir veritabanı üzerinde kaldırır. Göç bir danışma kilidi alır ve şema güncelse atlanır, yani birkaç kopyanın birlikte başlaması güvenlidir. Saklama milyarlarca span ile ölçüldüğünde ClickHouse'a uzanın. Türün adlandırmadığı bir arka uç için verilen URL yok sayılmaz, reddedilir.

Arka uçla birlikte değişmeyen bir şey var. Saklama, yazdığınız projeleri kapsar. Saatlik tarama, --project ile adlandırılan projede ve projects bloğundaki her projede, her biri kendi retention_days değerini ayarlamışsa ona göre, --retention-days değerinden eski sinyalleri siler. İkisinde de görünmeyen bir proje adıyla gönderilen trace'lerin penceresi yoktur ve hiç budanmazlar; yani ajanlarınız kendi projelerini adlandırıyorsa hacmi izleyin.

Sonraki