Configuração
Cada ajuste é uma opção, e cada opção tem uma variável de ambiente com o mesmo significado. As poucas estruturadas demais para uma linha de comando ficam em um único arquivo JSON. Esta página traz todas.
Duas formas de dizer a mesma coisa
Rode o mcpd sem argumento nenhum e ele funciona: escuta em
0.0.0.0:4317, guarda os dados em ./mcpd.db, oculta
segredos e armazena todo trace para sempre. Tudo o que vem abaixo muda uma
dessas decisões.
Uma opção sempre vence a variável de ambiente correspondente. As duas são globais, então são aceitas antes ou depois de um subcomando.
Referência
| Opção | Variável | Padrão |
|---|---|---|
--listen | MCPD_LISTEN | 0.0.0.0:4317 |
--data | MCPD_DATA | ./mcpd.db |
--project | MCPD_PROJECT | default |
--retention-days | MCPD_RETENTION_DAYS | não definido: guarda tudo |
--ingest-key | MCPD_INGEST_KEYS | nenhuma: a ingestão é aberta |
--read-key | MCPD_READ_KEYS | nenhuma: a leitura é aberta |
--write-key | MCPD_WRITE_KEYS | nenhuma: as chaves de ingest são aceitas |
--admin-key | MCPD_ADMIN_KEYS | nenhuma: as chaves de leitura são aceitas |
--execute-key | MCPD_EXECUTE_KEYS | nenhuma: as chaves de escrita são aceitas |
--worker-key | MCPD_WORKER_KEYS | nenhuma: as chaves de escrita são aceitas |
--runs-data | MCPD_RUNS_DATA | o caminho de --data com .runs antes da extensão |
--trusted-identity-header | MCPD_TRUSTED_IDENTITY_HEADER | nenhum: nenhum sujeito é registrado |
--trusted-proxy-cidr | MCPD_TRUSTED_PROXY_CIDRS | nenhum: nenhum endereço pode definir esse cabeçalho |
--sample | MCPD_SAMPLE | não definido: guarda todo trace |
--quota-per-minute | MCPD_QUOTA_PER_MINUTE | não definido: sem teto |
--no-redact | MCPD_NO_REDACT | desligado: a ocultação está ligada |
--redact-key | MCPD_REDACT_KEY | uma chave embutida, que é pública |
--redact-key-contains | MCPD_REDACT_KEY_CONTAINS | nenhuma: apenas as regras embutidas |
--redact-max-string | MCPD_REDACT_MAX_STRING | 128 KiB |
--cors-origin | MCPD_CORS_ORIGINS | nenhuma: apenas a mesma origem |
--request-timeout-seconds | MCPD_REQUEST_TIMEOUT_SECONDS | 30 |
--max-body-bytes | MCPD_MAX_BODY_BYTES | 33554432, que são 32 MiB |
--max-batch-signals | MCPD_MAX_BATCH_SIGNALS | 10000 |
--log-format | MCPD_LOG_FORMAT | text |
--clickhouse-url | MCPD_CLICKHOUSE_URL | desligado: o arquivo embutido é usado |
--clickhouse-database | MCPD_CLICKHOUSE_DATABASE | mcpd |
--clickhouse-user | MCPD_CLICKHOUSE_USER | nenhum |
--clickhouse-password | MCPD_CLICKHOUSE_PASSWORD | nenhuma |
--postgres-url | MCPD_POSTGRES_URL | desligado: o arquivo embutido é usado |
--config-file | MCPD_CONFIG_FILE | nenhum |
--forward-endpoint | MCPD_FORWARD_ENDPOINT | desligado: nada é encaminhado |
--forward-header | MCPD_FORWARD_HEADERS | nenhum |
| nenhuma | MCPD_LOG | mcpd=info,mcpd_server=info,tower_http=warn |
--listen
O endereço em que escutar. Uma única porta serve os endpoints OTLP, o endpoint de ingestão nativo, a API de consulta, as sondas, as métricas e a interface, então este é o único endereço que o mcpd tem.
Escutar em um endereço de loopback é a escolha discreta: o mcpd só avisa
sobre ingestão aberta quando o endereço em que escuta não é loopback. Em
0.0.0.0 e sem chave nenhuma definida, ele avisa duas vezes na
partida, uma pela ingestão e outra pela leitura.
--data
Caminho do arquivo SQLite. Os diretórios pai são criados se não existirem.
Passe :memory: para um armazenamento que não deixa nada para
trás, o que é certo para uma demonstração ou um teste e errado para qualquer
coisa que você queira ver amanhã.
--project
O projeto em que um sinal cai quando o remetente não nomeou nenhum. Um
projeto é a fronteira de isolamento: toda consulta é limitada a um. A passagem
de retenção roda sobre ele e sobre todo projeto do bloco
projects; um projeto que não aparece em nenhum dos dois lugares
nunca é aparado, o que importa se seus remetentes nomeiam o projeto deles.
--retention-days
Apaga sinais mais velhos que esta quantidade de dias. Não definido, guarda tudo, que é o padrão correto para um notebook e uma decisão que vale tomar de propósito em qualquer outro lugar.
A janela vai de 1 a 36500 dias, aqui e no retention_days de um
projeto. Zero é recusado na partida em vez de ser lido como ausência de
retenção: ele poria o corte em agora, e a primeira passada roda na partida,
então apagaria todos os sinais do projeto. Para guardar tudo, deixe o ajuste
de fora.
A passagem roda em um temporizador de hora em hora, e não no momento da consulta, então um apagamento grande nunca cai dentro da requisição de um usuário. Spans, traces, logs e métricas são cortados pela hora em que o mcpd os recebeu; notas, feedback e notas de aspecto, pelo carimbo de tempo deles. Apagar linhas não encolhe o arquivo. Veja Como executar.
--ingest-key, --read-key, --write-key, --admin-key, --execute-key e --worker-key
Seis escopos, porque eles carregam riscos genuinamente diferentes. Cada
endpoint declara o escopo de que precisa em
spec/mcpd.spec.json, o handler correspondente é marcado com o
mesmo escopo, e make check compara os dois, então um escopo
documentado aqui é o escopo que o servidor em execução aplica.
| Escopo | Definido com | O que abre |
|---|---|---|
ingest | --ingest-key | Os quatro endpoints de ingestão. Não lê nada. |
read | --read-key | A API de consulta: traces, spans, logs, métricas, notas, análises. |
write | --write-key | Notas, feedback, datasets, experimentos, reconhecimentos e a captura de uma execução para um dataset. |
admin | --admin-key | Apagar um trace, ler o log de auditoria e tudo o que os dois escopos abaixo abrem. |
execute | --execute-key | Iniciar, cancelar e sinalizar uma execução, e publicar um evento nela. |
worker | --worker-key | Registrar definições, reivindicar etapas e relatar o resultado delas. Não lê trace nenhum. |
A separação que mais importa é ingest contra todo o resto. Uma
chave de ingest vive em todo processo que emite um span, inclusive nos que
rodam código que você não escreveu. Dar a essa população uma credencial que
também consegue ler de volta todo prompt armazenado é um erro que o mcpd não
torna fácil: uma chave de ingest apresentada à API de consulta é recusada.
write é separado de ingest pela mesma razão, em
escala menor. Capturar uma execução para um dataset lê a entrada e
a saída dessa execução, ou seja, é uma leitura vestida de escrita e não
pertence à chave que a sua frota carrega.
admin é separado de read porque apagar uma execução
não é lê-la. Até você definir --admin-key, uma chave de leitura
apaga além de ler, e o mcpd diz isso na partida.
execute e worker são os dois escopos da superfície
de execução. Iniciar uma execução faz o worker de alguém trabalhar e gastar
dinheiro, o que anotar um trace não faz, então não é write. Uma
chave de worker vive no processo que roda os corpos das etapas, que
normalmente é o processo que guarda a credencial do provedor, então ela abre
o protocolo de lease e nada que leia um prompt armazenado. Até você
definir as duas opções, as chaves de escrita são aceitas para ambos, e uma
chave de admin sempre é.
Atualizar sem parada
Um --write-key não definido aceita as chaves de ingest, e um
--admin-key não definido aceita as chaves de leitura. Uma
implantação configurada antes de esses dois escopos existirem mantém, então,
exatamente o alcance que tinha, e nada quebra na atualização. Defina as duas
opções para assumir as concessões mais estreitas; mcpd config
diz em qual estado você está: imprime key required quando o
escopo tem chaves próprias e key required (ingest keys) ou
key required (read keys) quando ele as está herdando.
Repita qualquer uma das seis opções para ter várias chaves, que é como se
gira uma chave sem parada. As variáveis de ambiente aceitam uma lista
separada por vírgulas. Quem chama apresenta a chave como
Authorization: Bearer KEY ou como X-Mcpd-Key: KEY,
e a comparação é de tempo constante, então nem o comprimento nem a posição
da primeira diferença são observáveis.
Uma chave que o mcpd não conhece é um 401. Uma chave que o mcpd
conhece, usada em um endpoint que o escopo dela não cobre, é um
403. Vale manter os dois separados: um 401 manda você caçar um
erro de digitação em uma chave que está perfeitamente correta.
Sem chave nenhuma configurada, todo escopo fica aberto. Isso é de propósito, porque a primeira execução não pode exigir uma credencial, e é por isso que o aviso de partida existe.
Note o que as chaves não cobrem: /healthz,
/readyz, /metrics e a própria interface são
servidos sem chave nesta versão. Ponha-os atrás do seu próprio ingress se
isso importa para você.
Uma entrada vazia é descartada antes que qualquer coisa leia a lista. Uma lista definida que não contém nenhuma chave, como MCPD_EXECUTE_KEYS= ou um valor vazio em um Secret do Kubernetes, impede o daemon de iniciar, e a recusa nomeia a opção e a variável; omita a variável para deixar o escopo não definido, e uma requisição que envia um cabeçalho de chave vazio não apresenta chave nenhuma. Uma chave que ainda é um marcador dos arquivos de exemplo em deploy/, qualquer valor que comece com REPLACE_WITH ou replace-me em maiúsculas ou minúsculas, impede o daemon de iniciar; a recusa nomeia a opção ou a lista do projeto e nunca o valor. O mesmo vale para --redact-key. Gere cada chave com openssl rand -hex 32. Sem --write-key, --execute-key nem --worker-key, as chaves de ingest também iniciam execuções e reivindicam passos, e o mcpd avisa disso ao iniciar e no mcpd doctor.
O log de auditoria
Os handlers que devolvem payloads registram quem leu o quê:
GET /v1/api/traces/{id}, as buscas de span e de log, o
feedback, os itens de dataset, a captura e o apagamento. Cada
linha carrega a hora, um resumo curto da chave que agiu, a ação, o projeto,
o recurso e se a autorização permitiu. A chave em si nunca é anotada.
Leia o log com GET /v1/api/audit, que exige o escopo de
admin. Ele não é aparado por --retention-days: um log que
se apagasse no mesmo ritmo dos dados que descreve não poderia dizer quem leu
os dados que já se foram. Escrever uma linha nunca faz falhar a leitura que
ela descreve; uma linha que não pode ser escrita é logada e contada como
mcpd_audit_write_failures_total em /metrics, o que
merece um alerta.
--sample
Uma fração entre 0.0 e 1.0. Um valor fora dessa faixa é um erro de partida,
e não um corte silencioso: --sample must be between 0.0 and 1.0, got
2.
A decisão é tomada aplicando um hash ao id do trace, então uma frota de instâncias do mcpd atrás de um balanceador de carga guarda e descarta os mesmos traces sem conversar entre si. Ela é tomada uma vez por trace e herdada por todo span daquele trace no lote, porque um trace gravado pela metade parece um defeito no sistema que você está observando.
O hash lê os bytes finais do id do trace. Alguns produtores derivam os bytes iniciais de um carimbo de tempo, e um amostrador que olhasse para esses bytes se correlacionaria com a hora do dia e descartaria horas inteiras em silêncio.
O tipo de política também carrega "sempre guardar erros" e "sempre guardar mais lento que", mas isso vale para uma decisão de cauda, tomada depois que um trace termina. Esta versão amostra só na cabeça, e nenhuma opção liga a amostragem de cauda. Amostragem trata da troca.
--no-redact, --redact-key, --redact-key-contains e --redact-max-string
A ocultação vem ligada e roda antes de qualquer coisa ser escrita, então um valor que nunca deveria ter sido persistido nunca é. Apagá-lo depois não é a mesma garantia. Três conjuntos de regras vêm embutidos, casados por substrings em minúsculas do nome de uma chave:
| Regra | Aplicada quando a chave contém | O que é armazenado |
|---|---|---|
| Máscara | api_key, apikey, api-key,
secret, password, passwd,
access_token, refresh_token,
id_token, bearer,
authorization, auth_token,
private_key, client_secret,
session_token, credential,
signature, x-api-key | [redacted] |
| Parcial | email, phone, ssn,
credit_card, card_number,
iban |
Os dois primeiros e os dois últimos caracteres,
ab…yz. Um valor de seis caracteres ou menos é
mascarado por inteiro, em vez de vazar quase todo ele
|
| Hash | user_id, userid | sha256: e 12 bytes de um resumo com chave, para você
ainda poder agrupar por usuário sem armazenar nenhum
|
Uma chave casada que guarda um objeto ou um array tem tudo abaixo dela
ocultado também, então {"credentials": {"token": "..."}}
não escapa só pelo nome aninhado. Qualquer string única maior que 128 KiB é
truncada com um marcador que nomeia a contagem de bytes, o que limita o que
um prompt desgovernado faz com o seu disco.
--redact-key é a chave da regra de hash. Entradas iguais geram
o mesmo hash sob uma chave, que é o que faz o agrupamento funcionar, e girar
a chave quebra esse vínculo de propósito. Defina-a em qualquer implantação
que use hash: o padrão embutido está no código-fonte, então resumos sem
chave são adivinháveis.
--redact-key-contains acrescenta uma regra de máscara sua às
três embutidas. As regras embutidas conhecem os nomes sob os quais
credenciais costumam ser guardadas; elas não conhecem o campo em que as suas
próprias ferramentas põem o número de conta de um cliente. O valor é casado
do mesmo jeito que as regras embutidas, como uma substring em minúsculas da
chave, então --redact-key-contains ticket mascara
ticket_id e supportTicket igualmente, e tudo o que
estiver aninhado sob uma chave casada por elas. Repita a opção para várias,
ou defina MCPD_REDACT_KEY_CONTAINS com uma lista separada por
vírgulas.
--redact-max-string baixa o limite de comprimento a partir dos
128 KiB embutidos. Uma string maior que o limite é cortada ali, com um
marcador que nomeia quantos bytes foram descartados, antes de ser
armazenada.
--no-redact desliga todo o pipeline, inclusive o limite de
comprimento. Como variável de ambiente, é
MCPD_NO_REDACT=true. Só faz sentido quando algo antes dele já
oculta, e o daemon se recusa a iniciar se qualquer um dos dois acréscimos
acima estiver definido ao lado dele, porque não há nada a que eles se
apliquem.
--runs-data
Onde mora o armazenamento das execuções: as definições, o diário das
tentativas de cada etapa, cada prazo e cada espera. É um arquivo embutido,
seja qual for o armazenamento de sinais, então em Postgres ou ClickHouse as
execuções continuam em um arquivo ao lado do daemon, e a superfície de
execução é servida por uma réplica. Por padrão, é o caminho de
--data com .runs antes da extensão,
./mcpd.runs.db no caso do padrão, e :memory:
quando --data é. --runs-data off não serve execução
nenhuma: todo endpoint de execução responde 503 nomeando esta
opção, e o resto do daemon fica igual.
Os tempos do armazenamento ficam no arquivo de configuração, sob
runs, em milissegundos inteiros: lease_ttl_ms,
reclaim_window_ms, stall_window_ms,
idempotency_window_ms, park_ttl_ms e
max_depth. Os padrões, um lease de 30 segundos e uma
janela de recuperação de 30 segundos, são certos para um daemon em uma
máquina e são o que um worker lê de volta quando se registra.
--cors-origin
Quais origens de navegador podem chamar a API de consulta a partir de outro
site. Sem nenhuma definida, uma requisição de outra origem não recebe
cabeçalho access-control-allow-origin e o navegador se recusa a
entregar a resposta à página. Esse é o padrão, e é o que impede que um
daemon no seu notebook, com a API de leitura aberta, seja legível por
qualquer página aberta no mesmo navegador. A interface incluída é servida
pela própria origem do daemon e nunca precisa desta opção.
Servir a interface de outro lugar, uma CDN ou um portal interno, é o caso
para o qual esta opção existe: nomeie essa origem, como esquema e host, por
exemplo --cors-origin https://console.example. Repita a opção
para várias, ou defina MCPD_CORS_ORIGINS com uma lista separada
por vírgulas. Um valor que não é uma origem, console.example ou
um com barra no fim, recusa a partida nomeando a opção, em vez de deixar
você com uma interface que não consegue conectar e nenhuma razão para isso.
--cors-origin '*' permite toda origem, que é o que versões
anteriores faziam sem perguntar.
/healthz, /readyz e /metrics não são
governados por esta opção. Eles não carregam dado armazenado, e a interface
sonda /healthz antes de fazer qualquer outra coisa, então
continuam legíveis a partir de qualquer origem.
--request-timeout-seconds
O máximo que uma requisição pode levar, leitura do corpo incluída, antes de
o daemon responder por ela. Um handler que passa do prazo recebe como
resposta um 503 cujo corpo carrega timeout e o nome
desta opção. O padrão é 30 segundos, o que é longo o bastante para um lote
grande ser lido e armazenado, e curto o bastante para que um armazenamento
que aceita conexões e nunca responde seja relatado em vez de esperado. Não
dá para desligar; aumente se uma operação realmente precisar de mais tempo.
--max-body-bytes e --max-batch-signals
Os dois limites de uma requisição. --max-body-bytes é o maior
corpo que o daemon lê, 32 MiB por padrão, e é o que decide quanta memória
uma requisição pode tomar antes de qualquer parte dela ser decodificada. Um
corpo maior é recusado com 413 nomeando os dois números, e um
corpo comprimido é medido pelo mesmo número depois de inflado, então um corpo
que se expande mil vezes é recusado em vez de expandido.
--max-batch-signals é quantos sinais uma requisição pode
carregar depois de decodificada, 10.000 por padrão. É contado por toda a
requisição, então um array de lotes e um fluxo NDJSON são submetidos ao
mesmo número que um único lote. Nada é armazenado quando uma requisição
passa do limite: a recusa vem antes da primeira escrita, e não depois de
metade delas.
Os dois também são chaves do arquivo de configuração,
max_body_bytes e max_batch_signals, e a opção vence
onde os dois estiverem definidos. Nenhum dos dois pode ser desligado, e zero
é recusado na partida em vez de ser lido como "sem limite": um limite de
corpo igual a zero recusa toda requisição, então a leitura que parece
generosa é a que derruba a ingestão.
--log-format e MCPD_LOG
text para um terminal, json para um pipeline de
logs. MCPD_LOG aceita uma string de filtro do
tracing e não tem opção, porque é um controle de depuração e
não uma decisão de implantação. Defina-a como
mcpd_server=debug quando quiser ver detalhe por requisição.
Onde valem os dois limites de requisição
O limite de corpo vale para toda rota que lê um corpo, API de consulta incluída. O limite de sinais vale para as portas de ingestão, e é isto que ele conta em cada uma delas.
| Porta | O que o limite de sinais conta |
|---|---|
POST /v1/ingest | Cada span, log, métrica, nota, pedaço de feedback e nota de aspecto da requisição, somados por tudo o que houver nela, tenha chegado como um lote, um array de lotes ou um fluxo de linhas NDJSON |
POST /v1/traces | Os spans que o remetente enviou, contados antes de serem mapeados, que é o número sobre o qual o remetente pode agir |
POST /v1/logs | Os registros de log da exportação |
POST /v1/metrics | Os pontos de dados da exportação, por toda métrica que houver nela |
Subcomandos
| Comando | O que faz |
|---|---|
mcpd serve | Roda o daemon. É o que acontece sem subcomando nenhum |
mcpd demo | Escreve uma execução de exemplo realista no armazenamento e depois serve, para a interface ter algo dentro dela na primeira vez que você a abrir |
mcpd config | Imprime a configuração que esta invocação usaria e sai |
mcpd mcp | Serve o Model Context Protocol por stdio, para um agente poder ler os próprios traces de um daemon em execução |
mcpd doctor | Dá uma nota a esta implantação e relata o que está mal configurado. Sai com 2 diante de uma falha, então pode servir de portão para uma implantação. Veja Como executar |
mcpd config existe por causa da discussão que você está prestes
a ter com um contêiner. Opções, variáveis de ambiente e padrões acabam todos
em um lugar só, e isto imprime esse lugar sem iniciar um servidor nem tocar
no arquivo de dados:
$ MCPD_INGEST_KEYS=k1,k2 mcpd --retention-days 30 --sample 0.1 configlisten 0.0.0.0:4317storage sqlite at ./mcpd.dbconfig file nonelog format textproject defaultprojects noneretention 30 daysingest auth key requiredread auth openwrite auth key required (ingest keys)admin auth openexecute auth key required (ingest keys)worker auth key required (ingest keys)runs sqlite at ./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 offsampling deterministic 0.1quota unlimitedredaction onredact key built in, which is public; set --redact-keycors same-origin onlytimeout 30s per requestlimits 32 MiB per request, 10000 signals per batchderived duration and token metricsaspects offmonitors noneobjectives nonejudge offforwarding off
Leia as seis linhas de autenticação antes de implantar qualquer coisa.
open quer dizer aberto a quem alcançar a porta. Uma linha entre
parênteses nomeia a lista mais ampla de que aquele escopo está herdando, que
é o estado em que começa uma implantação atualizada a partir de uma versão
mais antiga.
Uma implantação configurada
Nada aqui é específico do mcpd: as variáveis são ambiente comum, então funcionam do mesmo jeito no Compose, em um manifesto do Kubernetes e em uma unidade do systemd.
compose
services:
mcpd:
image: ghcr.io/mcpd/mcpd
ports: ['4317:4317']
volumes: ['mcpd-data:/data']
environment:
MCPD_LISTEN: 0.0.0.0:4317
MCPD_DATA: /data/mcpd.db
MCPD_PROJECT: checkout
MCPD_RETENTION_DAYS: '30'
MCPD_INGEST_KEYS: write-key-one,write-key-two
MCPD_READ_KEYS: read-key
MCPD_REDACT_KEY: a-long-random-string
MCPD_LOG_FORMAT: json
MCPD_LOG: mcpd=info,mcpd_server=warn
volumes:
mcpd-data: Os mesmos ajustes como opções, para um shell ou um arquivo de unidade:
mcpd --listen 0.0.0.0:4317 --data /data/mcpd.db --retention-days 30 --ingest-key $KEY --log-format json O que os SDKs leem
Estas variáveis são lidas pelo processo do seu agente, não pelo daemon. Elas estão listadas aqui porque os dois conjuntos compartilham nomes, e um contêiner que roda um agente e o mcpd é configurado por um ambiente só.
| Variável | Padrão |
|---|---|
MCPD_ENDPOINT | http://localhost:4317 |
MCPD_API_KEY | não definido |
MCPD_PROJECT | default |
MCPD_ENVIRONMENT | não definido |
MCPD_SERVICE_NAME | não definido |
MCPD_SERVICE_VERSION | não definido |
MCPD_SAMPLE | 1.0 |
MCPD_ENABLED | true |
MCPD_CAPTURE_PAYLOADS | true |
MCPD_LOG_LEVEL | debug |
MCPD_PROJECT e MCPD_SAMPLE significam a mesma
coisa dos dois lados, que normalmente é o que você quer: defina uma vez e o
agente e o daemon concordam sobre o projeto. A amostragem é a que exige
pensar. Um SDK descarta um trace antes de ele ser enviado, o que poupa rede.
O daemon o descarta antes de ser armazenado, que é o único lugar onde a taxa
pode ser imposta a remetentes que você não controla. Defina a taxa em um
lugar, não nos dois, ou você estará filtrando um fluxo já filtrado.
MCPD_ENABLED=false faz de toda chamada de SDK uma operação sem
efeito que ainda devolve objetos válidos, então o mcpd pode ser desligado em
testes sem mudar código nenhum. MCPD_CAPTURE_PAYLOADS=false
mantém prompts e respostas dentro do processo: os spans ainda chegam com o
tempo, o uso, o modelo e os erros deles, mas input e
output são descartados antes de entrarem na fila, então nunca
atravessam a rede. A ocultação no daemon é a outra ponta do mesmo fio, e a
ponta errada para um time que não pode enviar os bytes de jeito nenhum. O
detalhe por linguagem está em cada
página de SDK.
--config-file
Um arquivo JSON com os ajustes que não cabem em uma linha de comando: monitores, objetivos de nível de serviço, o intervalo em que são avaliados, o encaminhamento, o juiz, os tempos do armazenamento de execuções, os dois limites de requisição e o bloco por projeto que vem abaixo. JSON, e não YAML ou TOML, porque o mcpd já interpreta JSON para todo o resto, então o arquivo não acrescenta dependência nem uma segunda sintaxe a aprender. Ele monta como um ConfigMap do Kubernetes sem mudança nenhuma.
max_body_bytes e max_batch_signals estão aqui além
da linha de comando, para uma frota poder mantê-los ao lado dos monitores com
que são implantados. Uma opção vence o arquivo, que é como uma instância
assume um limite diferente sem que o arquivo compartilhado seja editado para
todo mundo.
Uma chave desconhecida faz a partida falhar em vez de ser ignorada, porque um erro de digitação que silenciosamente não observa nada é o pior desfecho disponível.
O mesmo vale para um valor com o qual um temporizador em segundo plano não
consegue rodar: um objective que não seja pelo menos 0 e menor
que 1, um window_days de objetivo fora de 1 a 365, um
monitor_interval_seconds acima de 86400, um
aspects.window_seconds fora de 1 a 31536000, um
aspects.interval_seconds acima de 604800 e um
judge.timeout_seconds fora de 1 a 300. Cada recusa diz a chave
e o intervalo.
{
"max_body_bytes": 33554432,
"max_batch_signals": 10000,
"monitor_interval_seconds": 60,
"monitors": [
{
"id": "error-rate",
"name": "Error rate above 5%",
"project": "checkout",
"watch": "error_rate",
"compare": "above",
"threshold": 0.05,
"window_seconds": 300,
"min_samples": 20,
"webhook": "https://hooks.example.com/mcpd"
}
],
"forward": {
"endpoint": "https://otlp.example.com",
"headers": [["Authorization", "Bearer sk-..."]],
"batch_size": 256,
"enabled": true
}
} Projetos, e as chaves que os alcançam
Um projeto é a chave de isolamento. Listar chaves sob um projeto é o que torna isso verdadeiro em vez de decorativo: uma chave no bloco de um projeto alcança aquele projeto e nenhum outro, então um daemon serve vários times sem que nenhum deles consiga ler os prompts dos outros.
{
"projects": {
"checkout": {
"ingest_keys": ["ck-write-..."],
"read_keys": ["ck-read-..."],
"admin_keys": ["ck-admin-..."],
"retention_days": 30,
"sample": 0.25
},
"support": {
"ingest_keys": ["sp-write-..."],
"read_keys": ["sp-read-..."],
"retention_days": 90
}
}
}
As seis listas existem por projeto, como ingest_keys,
read_keys, write_keys, admin_keys,
execute_keys e worker_keys, e
recuam do mesmo jeito: um projeto sem write_keys aceita as
próprias chaves de ingest, e um sem admin_keys aceita as
próprias chaves de leitura. O bloco support acima não define
nenhuma das duas, então se comporta exatamente como se comportava antes de
os dois escopos existirem.
Uma chave definida com qualquer uma das seis opções de nível superior alcança todo projeto. Esse é o caminho de inquilino único, e ele continua sendo o caminho simples: se você nunca abrir este bloco, nunca encontra nada disto.
retention_days e sample recuam para as opções
globais quando um projeto não os define. Os dois são por projeto pela mesma
razão que as chaves são: a exigência de noventa dias de um time não deveria
obrigar todo mundo a guardar os dados, e o tráfego de um time não deveria
decidir como outro time é amostrado.
Um lote pode nomear vários projetos. Uma chave de ingest com escopo escreve os sinais que ela alcança e recusa o resto, relatando a recusa na resposta em vez de descartá-los em silêncio, então uma frota apontada para o projeto errado fica visível na resposta que ela já está lendo.
Uma requisição de leitura para um projeto que a sua chave não alcança é recusada sem dizer se aquele projeto existe. Em um daemon compartilhado, os nomes dos projetos costumam ser os nomes de times ou de clientes.
Monitores e objetivos de nível de serviço são tratados em
Monitores e alertas; os
objetivos vão em um array slos ao lado de
monitors e são avaliados no mesmo temporizador. O bloco
judge liga a avaliação por juiz e é tratado em
Testes de regressão. Ele fica
desligado até você defini-lo, e um avaliador por juiz nunca roda a menos que uma
requisição o nomeie, porque julgar gasta dinheiro a cada execução que lê.
{
"judge": {
"endpoint": "http://localhost:11434/v1",
"model": "qwen2.5:14b",
"api_key": "",
"criteria": {
"answer_relevance": "Does the answer address the question that was asked?"
}
}
} monitor_interval_seconds é de quanto em quanto tempo os monitores e os objetivos são avaliados. O padrão é 60, e um valor abaixo de 5 é elevado a 5.
derived_metrics
Quais métricas o daemon deriva dos spans que guarda. As duas metades ficam ligadas quando o bloco não existe. durations grava um ponto de duração para cada span terminado de agente, ferramenta, workflow, geração de modelo ou processador. tokens grava até treze contadores de tokens para cada span de geração de modelo que informou o uso. Defina qualquer uma das duas como false para parar de gravá-la; um campo omitido continua ligado. Não há opção de linha de comando para isso. Os pontos derivados entram depois do teto por projeto e não contam no número accepted de uma resposta de ingestão.
{
"derived_metrics": { "durations": true, "tokens": false }
} aspects
Se o daemon agrupa as notas de aspecto em temas, e sob quais limites. Desligado quando o bloco não existe. enabled liga a passada e não precisa de modelo nem de credencial, porque o próprio daemon grava o aspecto outcome. interval_seconds é de quanto em quanto tempo uma passada roda, 300 por padrão, e um valor abaixo de 30 é elevado a 30. window_seconds é até onde uma passada lê notas, 86400 por padrão. disabled nomeia aspectos que nenhuma passada agrupa; as notas já gravadas sob um deles continuam legíveis.
{
"aspects": {
"enabled": true,
"interval_seconds": 300,
"window_seconds": 86400,
"disabled": ["sentiment"]
}
} --forward-endpoint
Copia todo span armazenado para outro endpoint OTLP/HTTP. Dê a URL base;
/v1/traces é acrescentado. A escrita local sempre acontece
primeiro, então um destino fora do ar não custa nada a você.
Esta opção vence o bloco forward do arquivo de configuração,
então uma instância pode ser apontada para outro lugar sem editar um
ConfigMap compartilhado. Veja
Encaminhamento.
--forward-header
Um cabeçalho enviado com todo lote encaminhado, escrito como
Name: value. Repita a opção para mais de um. A variável de
ambiente aceita uma lista separada por vírgulas.
Um valor sem dois-pontos é recusado na partida, com a forma que funciona, em
vez de ser descartado. Um cabeçalho no estilo do OpenTelemetry,
key=value, aqui se escreve key: value.
--clickhouse-url
O endpoint HTTP de um servidor ClickHouse, por exemplo
http://clickhouse:8123. Defini-lo troca o armazenamento do
arquivo embutido para o ClickHouse, e --data deixa de ser lido.
O esquema é criado na partida.
Este é o ajuste que remove os limites de um escritor e de uma réplica do armazenamento embutido. Recorra a ele quando a retenção for medida em meses de tráfego de produção, e não antes: Como executar trata da troca.
--clickhouse-database, --clickhouse-user, --clickhouse-password
O banco em que criar as tabelas, e as credenciais com que alcançá-lo. Um usuário sem senha é enviado sem uma, o que é certo para um contêiner em uma rede privada e errado em qualquer outro lugar.
--postgres-url
Uma string de conexão do Postgres, por exemplo
postgres://mcpd:secret@db:5432/mcpd. Defini-la troca o
armazenamento do arquivo embutido para o Postgres, e --data
deixa de ser lido. O esquema é criado na partida.
Esta é a opção do meio. Ela remove os mesmos limites de um escritor e de uma
réplica que o ClickHouse remove, sem pedir que você rode um banco que ainda
não tem. Nomear esta e --clickhouse-url faz a partida falhar em
vez de escolher uma das duas.
A migração toma um advisory lock, então várias réplicas partindo juntas é seguro, e uma partida sem nada a aplicar não toca em esquema nenhum.
Para TLS, acrescente sslmode=require à string de conexão. O
mcpd então cifra a conexão e sempre verifica o certificado do servidor. Ele o
confere com as autoridades certificadoras públicas, ou com a de
sslrootcert=/caminho/para/ca.pem, que é o que um serviço
gerenciado com autoridade própria exige. verify-ca e
verify-full são lidos como require. Com
prefer, o padrão, ou com disable, a conexão não é
cifrada.
Registrar quem, e não só qual chave
O log de auditoria anota um resumo da chave que agiu. Isso responde "uma chave de admin apagou esta execução" e não "qual pessoa apagou". O mcpd não tem provedor de identidade, então a identidade tem de vir do ingress que já está na frente dele.
Dê a ele o cabeçalho que o seu proxy define, e os endereços autorizados a defini-lo:
tome a identidade de um ingress em que você confia
mcpd serve \
--trusted-identity-header x-forwarded-user \
--trusted-proxy-cidr 10.42.0.0/16 As duas metades são obrigatórias, e a lista de endereços é a razão de isto ser seguro. Um cabeçalho é uma string que qualquer um capaz de abrir um socket para a porta pode enviar, então o mcpd só o lê de um endereço que você nomeou. Uma requisição de qualquer outro lugar é servida normalmente e a linha de auditoria dela não carrega sujeito, que é a resposta honesta em vez de um palpite. O valor também tem o comprimento limitado e é despido de caracteres de controle, porque um cabeçalho sofre influência de quem ataca mesmo quando o proxy não sofre.
Sem nenhuma das duas opções definidas, nada muda e nenhum sujeito é jamais registrado. Esse é o padrão.
Um projeto não pode gastar o daemon inteiro
Um projeto que inunda o mcpd custa caro a todo outro projeto nele: o disco
enche, a retenção não dá conta, e o time que não fez nada de errado é o que
perde execuções. --quota-per-minute é o teto que impede isso.
um teto para todo projeto, e um maior para um deles
mcpd serve --quota-per-minute 10000
Não definido quer dizer sem teto, então uma implantação que nunca o define se
comporta exatamente como se comportava antes de as cotas existirem. Definido,
ele cobre três coisas: todo projeto do bloco projects que não
nomeia o próprio quota_per_minute, o projeto nomeado por
--project e todo projeto que a configuração não lista de
jeito nenhum.
Este último é a parte que faz dele um teto e não uma sugestão. O nome do projeto chega dentro do corpo da requisição, então um remetente que escolhe um nome que arquivo de configuração nenhum lista era cobrado de ninguém e podia armazenar sem limite. Um limite do qual o remetente escapa inventando uma string não é um limite.
Todo projeto não listado divide um contador, em vez de cada um ganhar o seu.
Um contador por nome seria uma alocação que qualquer remetente poderia mover
inventando nomes, que é o mesmo buraco em outra forma. Dividir quer dizer que
uma inundação espalhada por mil nomes inventados é limitada no total por este
número, e quer dizer que um projeto que merece a própria cota deve ser
nomeado em projects, onde ele ganha uma.
O que o teto realmente limita
O contador é do tipo ponderado de duas janelas: a contagem deste minuto, mais a do minuto anterior ponderada por quanto dele a janela que termina agora ainda cobre. Ele segura a taxa sustentada no teto e permite um pico de até um teto inteiro por cima disso, porque um SDK esvaziando uma fila cheia chega como um pico e um contador que o recusasse recusaria tráfego comum.
Esses dois juntos têm uma consequência aritmética que vale enunciar em vez de esconder. Um projeto pode armazenar um pouco menos que o dobro do teto dentro de um intervalo de 60 segundos: um teto inteiro bem no fim de uma janela, e um teto inteiro de novo bem no fim da seguinte. Isso não é um artefato da aproximação. Qualquer limitador que deixe um projeto gastar o teto de uma vez e o teto de novo por minuto depois disso permite isso na virada, e o único jeito de tirar é recusar o pico.
A janela é de um minuto e não é configurável. A opção nomeia a unidade, então um segundo ajuste que mudasse o que "por minuto" quer dizer tornaria o primeiro número ilegível.
O que acontece com o excesso
Sinais acima do teto são recusados e relatados, nunca descartados em silêncio. O lote não é recusado inteiro: o mcpd armazena o que cabe e diz a você o que não coube.
- Spans vão por trace inteiro. Meia execução tem spans cujo pai ninguém armazenou, o que se lê como uma execução quebrada e não como uma execução faltante. Um trace grande demais para o que sobrou é pulado e o próximo é tentado, então uma execução grande não gasta o resto da cota à toa.
- Logs, métricas, notas e feedback são independentes, então preenchem o que sobrou na ordem de chegada.
- Só o que é armazenado é cobrado. O corte guarda traces inteiros e por isso pode armazenar menos sinais do que lhe foi permitido, e a diferença é devolvida. Sem isso, um projeto cujas execuções são maiores que a cota restante pagaria até zerar sem armazenar nada.
- Outros projetos da mesma requisição ficam intocados. Um projeto acima do teto dele não custa a outro os sinais que este enviou junto.
A resposta carrega a contagem em rejected e uma linha em
errors nomeando o projeto e o teto dele. Um projeto que a
configuração nunca listou recebe outra frase, porque ouvir que o projeto
analytics-v2 está acima do teto manda você procurar um ajuste
que não existe, quando a resposta é acrescentar o projeto:
$ curl -sX POST "$MCPD/v1/ingest" -H content-type:application/json -d @batch.json | jq{ "accepted": 5, "rejected": 3, "duplicate": 0, "errors": [ { "signal": "span", "index": 0, "message": "3 signal(s) named project analytics-v2, which no configuration lists. Every unlisted project shares one ceiling of 5 per minute and it was full, so they were not stored. Add analytics-v2 to the `projects` block to give it an allowance of its own, or raise --quota-per-minute." } ]}
Uma recusa é contada em mcpd_signals_over_quota_total além de em
mcpd_signals_rejected_total, então uma frota sendo limitada se
distingue de uma frota enviando dados malformados. Veja
Como executar.
Duas coisas para conferir quando você o definir
O teto cobre toda porta que armazena um sinal: as de lote,
/v1/ingest, /v1/traces, /v1/logs e
/v1/metrics, e as de registro único,
POST /v1/api/scores e POST /v1/api/feedback. As
duas respondem de propósito de formas diferentes a um projeto acima do teto.
Uma porta de lote armazena o que cabe e relata o resto como
rejected com um motivo, porque um lote pode nomear vários
projetos e recusar a requisição inteira puniria os que estão dentro do teto.
Uma porta de registro único não tem resposta parcial a dar, então recusa com
429 e um cabeçalho Retry-After com os segundos que
faltam até o contador daquele projeto admitir outro sinal. O número vem do
próprio contador, então um cliente que o respeita volta quando há espaço, e
não quando um atraso fixo terminou.
Uma porta de escrita não é cobrada nele: POST /v1/api/score, que
roda avaliadores sobre execuções armazenadas. Ela entrega as notas que produziu
direto ao armazenamento, então uma passagem cai no disco para um projeto cujo
teto já está gasto, e essas linhas não são contadas nem por
mcpd_signals_over_quota_total nem por
mcpd_signals_unbounded_total. É uma porta que você mesmo abre, e
não uma para a qual uma frota aponta, então trate o teto como um limite do
que os remetentes conseguem empurrar para dentro, e não de tudo o que pode
chegar ao disco.
Um teto abaixo de --max-batch-signals, 10.000 por padrão, quer
dizer que um lote legítimo pode passar da cota de um minuto inteiro e é
cortado por mais bem comportado que o remetente seja. mcpd doctor
dá nota a isso sob limits e nomeia os projetos a que se aplica.
Ele também avisa quando um teto é definido por projeto e nenhum teto de nível
superior é definido ao lado, que é a configuração que deixa todo projeto não
listado sem limite.
mcpd config imprime os projetos limitados pelo nome, para você
poder ler de volta o que o ajuste cobre em vez de deduzi-lo. Ele imprime só
os tetos nomeados, então confirme o compartilhado a partir da própria opção.