1. Separar resultado do serviço e qualidade da observação
Uma equipa APS fictícia recebe três sinais: desaparecem métricas de uma região, falham probes e o dashboard mostra zero erros. Antes de concluir recuperação ou falha total, identifica o que cada observação realmente mede. Zero erros entre pedidos observados é diferente de zero pedidos observados. Uma região sem dados pode estar indisponível, isolada do sistema de monitorização ou fora do âmbito da consulta. Estas hipóteses conduzem a ações diferentes. Constrói um registo curto com percurso do utilizador, população esperada, período, origem, dependências do monitor, observação e limite da inferência. Por exemplo: consulta de posições funciona pelo caminho alternativo às 10h12; a reconciliação não tem resultado recente. Esse registo permite preservar sucesso parcial sem inventar o estado do batch. O exemplo não representa procedimentos internos do BNP Paribas. Escolhe o próximo teste pela capacidade de distinguir hipóteses. Se os probes partilham um proxy, um caminho autorizado com dependências diferentes pode ajudar a localizar o problema. Um teste deve ter âmbito e resultado esperado definidos antes de ser executado. Durante um incidente, alterar simultaneamente aplicação, DNS e monitorização pode destruir a comparação que permitiria perceber a causa. Regista a sequência das alterações e mantém explícito o que ainda não foi observado.
2. Confirmar o âmbito antes de interpretar ausência
O projeto central de monitorização só mostra os projetos incluídos no âmbito aplicável. Se as séries existem em funds-prod mas o projeto não está no metrics scope usado, a vista central incompleta não prova falha de recolha. Confirma projeto, recursos, labels e intervalo antes de reinstalar agentes. A comparação entre uma consulta na origem e a consulta central ajuda a localizar a diferença. Nos logs, uma view controla o subconjunto visível e o acesso é sujeito a IAM. Dois operadores podem obter resultados diferentes por usarem views ou permissões diferentes. Solicita o acesso necessário ao âmbito relevante pelo processo autorizado. Não alargues permissões indiscriminadamente nem transformes uma consulta vazia numa afirmação de que não houve eventos. Segue depois o percurso de encaminhamento. Um sink precisa de filtros adequados e permissão da sua identidade de escrita no destino. A conta pessoal conseguir publicar não valida a identidade do sink. Uma exclusão num sink independente não implica exclusão em todos os outros. Um sink novo não encaminha automaticamente entradas recebidas antes da sua criação. Se precisas de histórico, identifica onde continua armazenado e qual o mecanismo apropriado de cópia. Corrigir a exportação atual não demonstra que o período anterior ficou recuperado.
3. Distinguir zero, ausência e cobertura parcial em PromQL
Em Prometheus, up=0 indica que o scrape falhou. Não mede diretamente pedidos de negócio nem identifica a causa da falha. Um target pode responder a utilizadores e ser inacessível ao scraper. Existe ainda outra situação: um target sai da descoberta e as suas séries ficam stale. Quando deixam de aparecer na consulta instantânea, um painel que conta apenas zeros pode omitir completamente essa instância. Mantém um inventário esperado independente do conjunto que respondeu. Na situação original desta aula, são esperados 100 shards e apenas 80 reportam sucesso. Podes afirmar sucesso nos observados e cobertura de 80% do inventário. Não podes publicar 100% de sucesso para todos, nem chamar aos restantes 20 falhas confirmadas. O estado desses 20 continua por determinar. A função absent_over_time indica ausência da seleção durante a janela. Uma seleção ampla com amostras de A não enumera automaticamente a falta de B. Da mesma forma, acrescentar or vector(0) a uma expressão vazia pode produzir um zero sem observação real. Antes de usar preenchimento em dashboards, define o significado do zero e mantém visível a qualidade da recolha. As expressões desta aula são exemplos pedagógicos; o exercício local não executa um servidor Prometheus nem substitui validação da query no ambiente real.
4. Seguir os dados dentro do Collector
A configuração efetiva do OpenTelemetry Collector precisa de ligar os componentes nas pipelines da secção service. Declarar um receiver num ficheiro não demonstra que está ativo. Verifica que o componente está referenciado na pipeline correta e que a configuração em execução corresponde à revista. Não uses apenas o nome do ficheiro ou a existência de um dashboard para confirmar recolha. Observa depois as fronteiras do fluxo: itens aceites e recusados pelos receivers, entrada e saída dos processors, itens enviados ou rejeitados pelos exporters e ocupação das filas. Os nomes e disponibilidade das métricas dependem da versão e da configuração de telemetria interna; confirma-os no binário instalado. Compara o mesmo tipo de sinal e intervalo. Spans recebidos e lotes exportados não são unidades diretamente somáveis. No exemplo controlado, entram 1 000 spans e um filtro deliberado remove 200; exportar 800 é coerente. Noutro caso, o receiver recusa itens enquanto o exporter envia tudo o que recebe. O sucesso do exporter deixa por determinar o destino dos itens recusados. Investiga o comportamento de retry da origem e os limites do pipeline. Uma extensão de saúde acessível não comprova, por si, entrega de telemetria ao destino. Usa dados sintéticos limitados para seguir o percurso sem expor payloads reais de clientes.
5. Explicar o que os probes realmente testam
Um probe tem protocolo, destino, caminho de rede, identidade e critério de sucesso. Num uptime check HTTP público, os redirects são seguidos e os critérios são aplicados à resposta final. Se /portal redireciona para /maintenance e só se exige 200, o teste pode passar sem demonstrar a função do portal. Um uptime check também não executa automaticamente o JavaScript da página. Escolhe um teste funcional adequado ao requisito que pretendes observar. A ligação TCP à porta 443 não prova validação de TLS. Para esse requisito, é necessária uma verificação que exerça o protocolo e os critérios de certificado. A documentação consultada indica que os private uptime checks têm validação SSL desativada, independentemente da configuração. Não uses o seu resultado como prova de validade do certificado. Regista esse limite no desenho de cobertura. Considera ainda a identidade e as dependências do monitor. Uma conta sintética expirada pode falhar quando contas válidas continuam funcionais. Três localizações que usam o mesmo proxy não são três observações comprovadamente independentes dessa dependência. Um caminho alternativo ajuda a investigar, mas não apaga os utilizadores que continuam a depender do proxy. O relatório deve indicar quais os percursos observados e onde a evidência continua incompleta.
6. Correlacionar sinais sem inventar completude
Um trace retido mostra apenas o que foi instrumentado, propagado, recolhido e conservado. A ausência de um span de cliente não prova que a chamada externa não ocorreu. Confirma instrumentação, sampling e exportação antes de interpretar a lacuna. Procura evidência complementar com identificadores e intervalos coerentes. Um log da aplicação que regista uma tentativa e um trace sem o span podem apontar para cobertura incompleta, sem estabelecer ainda a causa técnica. VPC Flow Logs também não é uma captura de todos os pacotes. A amostragem secundária a 100% conserva os flow logs produzidos pela amostragem primária; não elimina a amostragem anterior. Uma pesquisa vazia não demonstra que uma tentativa de comunicação nunca existiu. Verifica interfaces e âmbito, período, filtros e limites da recolha antes de concluir algo sobre a rede. Num incidente fictício, o operador suspeita de DNS, mas só dispõe de um timeout e ausência de flow log. Formula hipóteses que possam ser distinguidas: resolução, ligação, negociação TLS e resposta funcional. Escolhe observações que localizem a fronteira da falha. Mantém o teste limitado à questão em investigação e regista resultados negativos úteis. Uma hipótese plausível deve permanecer hipótese até haver evidência que a sustente; multiplicar dashboards com a mesma origem não acrescenta independência.
7. Validar deteção, notificação e passagem de turno
Um alerta útil exige mais do que um canal criado. A condição deve selecionar os recursos certos, receber dados adequados, avaliar o comportamento pretendido e produzir uma notificação que chegue ao responsável. Um teste de ligação ao webhook prova parte do transporte. Não prova que a política real esteja associada ao canal ou que o limiar consiga disparar com os dados selecionados. Prepara um ensaio autorizado com condição controlada, destino acordado e identificação clara de teste. Observa o percurso completo até ao recetor e confirma quem assume a ação. Não executes esse ensaio sobre clientes reais sem o processo de mudança apropriado. Na documentação há opções de teste específicas de alguns canais; não suponhas que existe um botão universal que valide todas as políticas. Uma janela de snooze suprime alertas e notificações no âmbito definido. O silêncio durante essa janela não demonstra normalidade do serviço. Regista âmbito, duração, responsável e observação alternativa prevista. Se a manutenção terminar antes, revê o fim da supressão. Na passagem de turno, separa falhas observadas, cobertura em falta, hipóteses em investigação e ações pendentes. Um relatório que identifica a lacuna de reconciliação é mais útil do que um verde global que ninguém consegue relacionar com um teste funcional recente.
8. Exercício: cobertura por percurso e grupo declarado
O programa Python recebe um inventário de percursos esperados e um snapshot com a observação mais recente de cada probe. Cada registo contém ID único, percurso, grupo de dependência declarado, instante observado e resultado pass, fail ou unknown. O relógio é fictício e partilhado. A regra pedagógica exige dois grupos distintos com resultado conhecido por percurso e idade entre zero e 120 segundos, inclusive. Um grupo é uma declaração de inventário, não prova de independência física. Cinco probes com proxy-a contam como um grupo. None no instante ou no grupo preserva desconhecimento; um resultado unknown também não fornece cobertura conhecida. Registos antigos ou futuros ficam fora da cobertura recente. Uma falha recente é conservada mesmo quando o seu grupo é desconhecido. O programa não transforma lacunas em sucesso nem oculta falhas quando faltam dados de outro percurso. Executa os exemplos e modifica o snapshot: retira reconciliação, repete grupos, usa observedAt=480 com now=600 e compara com 479 e 620. Explica os resultados antes de olhar para a saída. Acrescenta depois um probe que contradiz outro do mesmo grupo. O algoritmo conserva ambas as observações e conta o grupo uma vez. CoverageComplete significa apenas cumprimento da regra declarada. O programa não faz pedidos, não executa PromQL e não prova saúde do serviço ou autorização de produção.
"""Original offline snapshot exercise, not a monitor or availability estimator.
One latest observation per probe ID. Groups are declared dependency boundaries,
not discovered or proven independence. Ages use one fictional shared clock.
"""
import copy
import hashlib
import itertools
import json
from pathlib import Path
def name(value):
if type(value) is not str or not value.strip():
raise ValueError('nonempty label required')
return value
def integer(value, minimum=0):
if type(value) is not int or value < minimum:
raise ValueError('integer outside permitted range')
return value
def evaluate(journeys, observations, now, max_age, minimum_groups):
integer(now); integer(max_age); integer(minimum_groups, 1)
if type(journeys) is not list or not journeys:
raise ValueError('nonempty journey inventory required')
for j in journeys: name(j)
if len(journeys) != len(set(journeys)):
raise ValueError('duplicate journey')
if type(observations) is not list:
raise ValueError('snapshot list required')
ids = set()
validated = []
for row in observations:
if type(row) is not dict or set(row) != {'id','journey','dependencyGroup','observedAt','result'}:
raise ValueError('observation fields')
name(row['id']); name(row['journey'])
if row['id'] in ids: raise ValueError('duplicate probe ID')
ids.add(row['id'])
if row['journey'] not in journeys: raise ValueError('unexpected journey')
if row['dependencyGroup'] is not None: name(row['dependencyGroup'])
if row['observedAt'] is not None: integer(row['observedAt'])
if row['result'] not in ('pass','fail','unknown'): raise ValueError('observation result')
validated.append(row)
output = []
for journey in sorted(journeys):
relevant = sorted((r for r in validated if r['journey'] == journey),key=lambda r:r['id'])
groups, issues, successes, failures = {}, [], [], []
for row in relevant:
reason = None
if row['observedAt'] is None: reason = 'unknown-time'
elif row['observedAt'] > now: reason = 'future-time'
elif now-row['observedAt'] > max_age: reason = 'stale'
if reason:
issues.append({'id':row['id'],'reason':reason})
continue
if row['result'] == 'unknown':
issues.append({'id':row['id'],'reason':'unknown-result'})
continue
(successes if row['result'] == 'pass' else failures).append(row['id'])
group = row['dependencyGroup']
if group is None:
issues.append({'id':row['id'],'reason':'unknown-group'})
else:
groups.setdefault(group,set()).add(row['result'])
output.append({'journey':journey,'freshGroups':[
{'group':g,'results':sorted(groups[g])} for g in sorted(groups)],
'requiredGroups':minimum_groups,'coverageMet':len(groups)>=minimum_groups,
'observedPasses':successes,'observedFailures':failures,'observationIssues':issues})
return {'journeys':output,'coverageComplete':all(j['coverageMet'] for j in output),
'failureObserved':any(j['observedFailures'] for j in output),
'hasObservationIssues':any(j['observationIssues'] for j in output),
'independenceProven':False,'serviceHealthProven':False,'productionAuthorized':False}
def baseline():
return ['query','reconciliation'], [
{'id':j+'-'+g,'journey':j,'dependencyGroup':g,'observedAt':590,'result':'pass'}
for j in ('query','reconciliation') for g in ('a','b')]
def exercise():
fixtures=[]
def record(label, mutate):
j,o=baseline();mutate(j,o);before=copy.deepcopy((j,o));r=evaluate(j,o,600,120,2)
assert (j,o)==before
fixtures.append({'id':label,**r});return r
assert record('complete-pass',lambda j,o:None)['coverageComplete']
assert not record('missing-journey',lambda j,o:o.__delitem__(slice(2,None)))['coverageComplete']
assert not record('shared-group',lambda j,o:[r.update(dependencyGroup='a') for r in o])['coverageComplete']
assert record('boundary-fresh',lambda j,o:o[0].update(observedAt=480))['coverageComplete']
assert not record('just-stale',lambda j,o:o[0].update(observedAt=479))['coverageComplete']
assert not record('future-time',lambda j,o:o[0].update(observedAt=620))['coverageComplete']
assert not record('unknown-time',lambda j,o:o[0].update(observedAt=None))['coverageComplete']
assert not record('unknown-group',lambda j,o:o[0].update(dependencyGroup=None))['coverageComplete']
assert not record('unknown-result',lambda j,o:o[0].update(result='unknown'))['coverageComplete']
f=record('failure-and-gap',lambda j,o:(o[0].update(result='fail'),o.__delitem__(slice(2,None))))
assert f['failureObserved'] and not f['coverageComplete']
f=record('mixed-group',lambda j,o:o.append({'id':'query-extra','journey':'query','dependencyGroup':'a','observedAt':590,'result':'fail'}))
assert f['failureObserved'] and f['coverageComplete']
f=record('unknown-group-failure',lambda j,o:o[0].update(result='fail',dependencyGroup=None))
assert f['failureObserved'] and not f['coverageComplete']
combinations=0
for states in itertools.product(('pass','fail','unknown','stale','future'),repeat=4):
j,o=baseline()
for row,state in zip(o,states):
row['result']=state if state in ('pass','fail','unknown') else 'pass'
row['observedAt']=479 if state=='stale' else 620 if state=='future' else 590
r=evaluate(j,o,600,120,2)
assert r['coverageComplete']==all(x in ('pass','fail') for x in states)
assert r['failureObserved']==('fail' in states)
assert r['hasObservationIssues']==any(x not in ('pass','fail') for x in states)
combinations+=1
j,o=baseline(); expected=evaluate(j,o,600,120,2); permutations=0
for order in itertools.permutations(o):
assert evaluate(list(reversed(j)),list(order),600,120,2)==expected
permutations+=1
invalid=[
lambda j,o:j.clear(), lambda j,o:j.append('query'),lambda j,o:j.append(''),
lambda j,o:o.append(copy.deepcopy(o[0])),lambda j,o:o[0].update(extra=1),
lambda j,o:o[0].pop('result'),lambda j,o:o[0].update(journey='payments'),
lambda j,o:o[0].update(id=' '),lambda j,o:o[0].update(dependencyGroup=''),
lambda j,o:o[0].update(observedAt=-1),lambda j,o:o[0].update(observedAt=True),
lambda j,o:o[0].update(result='healthy'),lambda j,o:o[0].update(observedAt=590.5),
]
for mutate in invalid:
j,o=baseline();mutate(j,o)
try:evaluate(j,o,600,120,2)
except ValueError:pass
else:raise AssertionError('invalid snapshot accepted')
settings=[(True,120,2),(600,-1,2),(600,120,0),(600,120,False)]
for values in settings:
j,o=baseline()
try:evaluate(j,o,*values)
except ValueError:pass
else:raise AssertionError('invalid settings accepted')
return {'scriptSha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
'fixtures':fixtures,'snapshotCombinations':combinations,'orderPermutations':permutations,
'invalidInputs':len(invalid)+len(settings),'inputPreserved':True,
'network':False,'persistentWrites':False,'cloudExecuted':False,'promqlExecuted':False}
if __name__=='__main__':
print(json.dumps(exercise(),ensure_ascii=False,sort_keys=True,indent=2))
Dois grupos observam consulta, mas nenhum observa reconciliação. O sucesso da consulta fica registado e a reconciliação mantém uma lacuna explícita, sem ser classificada como sucesso ou falha.
Armadilhas comuns
Substituir ausência por zero; contar probes como grupos independentes; equiparar sucesso de scrape a sucesso funcional; tratar silêncio de notificações como recuperação.
Tópicos relacionados: Monitorização sintética e SLI · Pipelines de telemetria e controlo de acesso · Diagnóstico e comunicação de incidentes
Relaciona cada conclusão com o âmbito, a idade e os limites da observação. Preserva falhas conhecidas e lacunas em simultâneo até existir evidência suficiente.
Referência: Professional Cloud DevOps Engineer exam guide · Current linked guide; edition date unconfirmed (2026-09-30 inspection)