← Professional Cloud DevOps Engineer: entrega e fiabilidade
22 / 25 · 135 MIN

Continuidade de pipelines e compatibilidade de recuperação

Preparar opções de recuperação que incluam artefactos, dados, configuração, mensagens e inferência, com evidência explícita para cada contrato.

1. Definir a unidade de recuperação

Uma release recuperável precisa de uma combinação identificável de código, configuração, dados e dependências. Num exercício fictício de reconciliação bancária, a versão A funcionava com o esquema s1, o segredo 7 e mensagens m1. A versão B introduziu s2 e m2. Guardar a imagem A permite identificar código; falta demonstrar que o serviço atual ainda satisfaz os contratos de A. Este exemplo não descreve procedimentos internos do BNP Paribas. Constrói uma ficha por opção de recuperação: digest da imagem, configuração versionada, referência do segredo sem o seu valor, esquemas aceites, formatos emitidos e lidos, endpoints necessários, testes e responsável pela decisão. Regista também o que pode continuar a executar durante a mudança. O batch mensal e um consumidor atrasado podem manter dependências que não aparecem no dashboard web. Usa três perguntas na reunião de preparação: conseguimos obter a combinação, consegue interpretar o estado atual e consegue servir a procura esperada? Uma resposta desconhecida deve produzir uma ação de recolha de evidência com responsável. Não a transformes num sim por pressão da janela. O resultado é uma opção demonstrável, com restrições e critérios de interrupção, que a equipa de turno seguinte consegue compreender sem reconstruir conversas anteriores.

2. Conservar e obter artefactos concretos

Uma tag como stable pode apontar hoje para conteúdo diferente do usado no ensaio. Regista o digest observado e a localização completa. A identidade do conteúdo e a sua disponibilidade são verificações distintas: uma referência correta pode apontar para um artefacto que o executor não consegue obter. Testa o caminho autorizado a partir das condições reais de execução, incluindo identidade, rede e destino. Não uses credenciais do portátil como prova da capacidade do runner. A limpeza do Artifact Registry deve respeitar as opções de recuperação que a equipa decidiu conservar. Se uma versão corresponder a políticas de eliminação e conservação, a conservação prevalece. O dry run permite observar a seleção antes de ativar eliminações; usa os logs Data Access de escrita necessários e aguarda a execução periódica. Uma consulta vazia imediatamente após configurar a política não demonstra que a retenção está correta. No exercício, o inventário indica imagem A disponível e configuração 7 desconhecida. A conclusão é uma lacuna de evidência. Não se deve substituir a configuração por latest para obter um resultado verde. Se for necessário reconstruir um artefacto desaparecido, trata o resultado como candidato a validar: o mesmo commit não garante que todas as dependências e ferramentas da construção sejam idênticas às anteriores.

3. Migrar dados mantendo contratos explícitos

Considera A a ler account_ref e B a ler account_key. Renomear a coluna enquanto A serve pedidos quebra o seu contrato. Uma estratégia possível consiste em acrescentar a nova representação, definir como as escritas se mantêm coerentes, preencher dados históricos, reconciliar e mudar leitores de forma controlada. A remoção da representação antiga é uma decisão posterior, condicionada pelo fim das dependências e do caminho de retorno aprovado. Este desenho precisa de testes próprios; acrescentar uma coluna não resolve automaticamente coerência ou impacto operacional. Num ensaio, um backfill termina às 22h00 e as escritas antigas continuam sete minutos. A igualdade do número de linhas não prova igualdade dos valores. Define como identificar alterações concorrentes, repetir lotes sem criar efeitos indevidos e verificar a população completa. Uma amostra útil não deve ser apresentada como reconciliação integral. Inclui consumidores pouco frequentes. Um batch mensal que ainda consulta account_ref impede retirar a coluna apenas porque o frontend já usa B. Em PostgreSQL 18, consulta o subcomando concreto de ALTER TABLE para conhecer o lock necessário. Compatibilidade lógica das consultas não elimina espera por locks. Planeia duração, limite de espera e observação de contenção antes de escolher a janela de execução.

4. Avaliar rollback com o estado que existe agora

O rollback de um Deployment Kubernetes repõe o Pod template de uma revisão anterior. A migração de uma base externa e os efeitos consumados noutros serviços não são automaticamente invertidos por essa operação. De modo semelhante, Cloud Deploy cria um novo rollout baseado numa release anterior; é necessário escolher uma opção funcional no contexto atual. A designação sucesso de ontem não é um teste de compatibilidade com os dados de hoje. Imagina que B removeu um campo e confirmou novas operações. Repor A pode causar mais falhas se A depender desse campo. Recriar uma coluna vazia pode fazer o arranque passar e deixar os resultados financeiros incorretos. A decisão deve considerar contenção de novas escritas, restabelecimento de um contrato compatível, correção da versão em serviço e reconciliação dos efeitos. São opções de engenharia a avaliar com evidência, não instruções automáticas para executar durante qualquer incidente. A matriz de testes deve representar os estados transitórios: A com o esquema novo, B com os dados históricos, coexistência de escritores e recuperação depois de B produzir novos formatos. Regista resultados por combinação. B conseguir ler v1 não demonstra que A leia v2. Esta assimetria explica por que um upgrade aparentemente compatível pode deixar de permitir um rollback simples.

5. Incluir configuração e validade dos segredos

Reproduzir o código não reproduz necessariamente o contexto de execução. Se a configuração usar latest para obter um segredo, duas execuções da mesma imagem podem pedir versões diferentes. Referenciar uma versão numérica permite relacionar configuração, ensaio e alteração. Guarda a referência nos registos de recuperação; evita incluir o valor do segredo em tickets, logs ou exemplos de ensino. Há várias condições distintas. A versão precisa de existir, estar num estado que permita acesso e ser acessível à identidade certa. Uma versão desativada não é substituída automaticamente pela versão ativa seguinte quando o pedido especifica o seu número. Além disso, a credencial armazenada precisa de continuar válida no sistema que a aceita. Se um parceiro a revogou, conseguir lê-la no Secret Manager não restabelece a sua validade. Num caso APS, a imagem A usa o segredo 7 e a imagem B usa o 8. Antes de propor A como recuperação, pergunta se a rotação manteve um período autorizado de coexistência e como foi testada a autenticação no parceiro. Se o segredo antigo foi comprometido, o plano deve procurar uma combinação segura com credenciais válidas. A pressão para voltar rapidamente ao código antigo não justifica reintroduzir a causa conhecida de uma exposição.

6. Tratar mensagens que sobrevivem ao deployment

Uma fila contém história da aplicação. Voltar ao produtor anterior muda futuras emissões, mas não altera automaticamente as mensagens já publicadas. Se B introduziu um estado que A não interpreta, o backlog pode continuar incompatível depois da recuperação do produtor. Inventaria formatos armazenados e formatos que escritores ainda ativos podem emitir. Inclui retries e fluxos periódicos que podem reaparecer após a janela. Para mensagens Avro em Pub/Sub, a revisão do esquema do escritor é relevante para a leitura. O atributo googclient_schemarevisionid ajuda a identificar essa revisão; a estratégia do parser deve considerar a resolução entre esquemas. Uma revisão desconhecida exige diagnóstico, não a suposição de que todos os bytes seguem o contrato mais recente. Os tokens v1 e v2 do exercício são rótulos de contratos da aplicação, não emulação dos mecanismos de compatibilidade do Pub/Sub. A validade estrutural também não prova significado correto. Um inteiro amount pode continuar válido quando muda de cêntimos para euros. Usa testes com resultados esperados para unidades, enumerações, valores omissos e regras de negócio. Define o tratamento dos itens incompatíveis preservando rastreabilidade e evitando efeitos duplicados. Um consumidor que deixa de falhar porque confirma mensagens sem as processar pode reduzir o backlog enquanto aumenta o impacto funcional.

7. Recuperar o fluxo completo de inferência

Um modelo de recuperação inclui mais do que pesos guardados. Identifica a versão explícita, o pré-processamento, a ordem e unidades dos atributos, o código de serving, a capacidade disponível e os consumidores das decisões. Um alias pode mudar de versão; regista os identificadores concretos usados no ensaio. Nas referências consultadas, algumas páginas do percurso Vertex AI redirecionam para a documentação Gemini Enterprise Agent Platform; a data de consulta não representa uma nova edição confirmada do exame. No exemplo original, M1 recebe [idade, saldo] e M2 recebe [saldo, idade]. Trocar apenas o modelo pode produzir respostas HTTP bem-sucedidas com significado errado. Valida a combinação com entradas conhecidas e critérios do caso de uso. Um modelo presente no registo não demonstra que exista serving pronto com capacidade de contingência. O plano deve incluir os recursos e o tempo necessários para tornar essa opção utilizável. Se o modelo encaminhou 180 incidentes críticos para prioridade baixa, a reposição melhora decisões futuras, mas não corrige automaticamente as anteriores. Define a população afetada, reavaliação, responsável e aceitação operacional. Pode ser necessário um modo manual autorizado durante a contenção. Distingue qualidade das decisões, disponibilidade do endpoint e reconciliação de efeitos; são resultados diferentes que devem aparecer no relatório da recuperação.

8. Exercício: confrontar contratos com evidência

O programa Python local compara três famílias de contratos: schema, message e feature. O candidato declara os rótulos que aceita. A evidência declara os rótulos armazenados e os que podem ser emitidos durante a recuperação. Os nomes são convenções pedagógicas: um rótulo schema não é uma análise de SQL, e feature não executa um modelo. As listas têm de resultar de inventário e testes reais fora deste programa. Em cada família, o algoritmo procura rótulos observados que o candidato não aceita. Conserva a origem stored ou emitters para orientar a ação. None significa desconhecido; uma lista vazia significa que a ausência foi estabelecida. O programa também recebe disponibilidade explícita dos artefactos necessários. False produz impedimento, None produz lacuna, e True é apenas evidência fornecida pelo utilizador. Um conflito conhecido permanece visível mesmo quando outro campo é desconhecido. Executa os exemplos fornecidos. Depois acrescenta v2 ao backlog, retira v2 das capacidades do candidato e observa o conflito. Repete com emitters desconhecido e explica por que o resultado deixa de poder ser concluído. Por fim, declara todos os contratos compatíveis: eligible-for-review continua sem autorizar produção. O programa não testa payloads, capacidade, locks, credenciais nem comportamento cloud. Entrega uma ficha de decisão com os limites da evidência e as verificações operacionais ainda necessárias.

"""Original offline exercise: declared recovery contracts, not cloud validation.

Tokens are explicit application-contract labels, not provider schema IDs.
None means unknown; [] means a known empty population. No inferred compatibility.
"""
import copy
import hashlib
import itertools
import json
from pathlib import Path

KINDS = ('schema', 'message', 'feature')

def labels(value, nullable=False):
    if value is None and nullable:
        return None
    if not isinstance(value, list) or any(type(x) is not str or not x.strip() for x in value):
        raise ValueError('expected a list of nonempty contract labels')
    if len(value) != len(set(value)):
        raise ValueError('duplicate contract label')
    return set(value)

def evaluate(candidate, evidence):
    """Check reader compatibility with stored and possible future contracts.

    Artifact state is supplied, not discovered. False and unknown stay distinct.
    Every candidate required artifact needs an explicit state; extra states fail.
    Unknown reader capability preserves inventory gaps rather than inventing them.
    """
    if type(candidate) is not dict or set(candidate) != {'accepts', 'artifacts'}:
        raise ValueError('candidate fields')
    if type(evidence) is not dict or set(evidence) != {'stored', 'emitters', 'artifacts'}:
        raise ValueError('evidence fields')
    for mapping in (candidate['accepts'], evidence['stored'], evidence['emitters']):
        if type(mapping) is not dict or set(mapping) != set(KINDS):
            raise ValueError('all three contract kinds are required')
    artifacts = labels(candidate['artifacts'])
    if not artifacts:
        raise ValueError('at least one required artifact')
    states = evidence['artifacts']
    if type(states) is not dict or set(states) != artifacts:
        raise ValueError('artifact evidence must match requirements exactly')
    if any(v is not None and type(v) is not bool for v in states.values()):
        raise ValueError('artifact state must be boolean or None')
    conflicts, unknown = [], []
    for kind in KINDS:
        accepted = labels(candidate['accepts'][kind], True)
        if accepted is None:
            unknown.append({'kind': kind, 'origin': 'candidate'})
        for origin in ('stored', 'emitters'):
            actual = labels(evidence[origin][kind], True)
            if actual is None:
                unknown.append({'kind': kind, 'origin': origin})
            elif accepted is not None:
                for token in sorted(actual - accepted):
                    conflicts.append({'kind': kind, 'origin': origin, 'contract': token})
    unavailable = sorted(k for k, v in states.items() if v is False)
    unknown_artifacts = sorted(k for k, v in states.items() if v is None)
    blocked = bool(conflicts or unavailable)
    incomplete = bool(unknown or unknown_artifacts)
    return {
        'status': 'blocked' if blocked else 'incomplete' if incomplete else 'eligible-for-review',
        'conflicts': conflicts, 'unknownContracts': unknown,
        'unavailableArtifacts': unavailable, 'unknownArtifacts': unknown_artifacts,
        'evidenceComplete': not incomplete,
        'eligibleForReview': not blocked and not incomplete,
        'productionAuthorized': False,
    }

def baseline():
    return (
        {'accepts': {k: ['v1'] for k in KINDS}, 'artifacts': ['image:a', 'config:7']},
        {'stored': {k: ['v1'] for k in KINDS},
         'emitters': {k: ['v1'] for k in KINDS},
         'artifacts': {'image:a': True, 'config:7': True}},
    )

def exercise():
    fixtures = []
    def record(name, edit, expected):
        c, e = baseline(); edit(c, e)
        before = copy.deepcopy((c, e)); out = evaluate(c, e)
        assert (c, e) == before
        assert out['status'] == expected, name
        fixtures.append({'id': name, **out})
    record('compatible', lambda c, e: None, 'eligible-for-review')
    record('stored-incompatible', lambda c, e: e['stored'].update(message=['v2']), 'blocked')
    record('future-incompatible', lambda c, e: e['emitters'].update(message=['v2']), 'blocked')
    record('unknown-emitter', lambda c, e: e['emitters'].update(message=None), 'incomplete')
    record('known-empty', lambda c, e: (e['stored'].update(message=[]), e['emitters'].update(message=[])), 'eligible-for-review')
    record('unknown-reader', lambda c, e: c['accepts'].update(feature=None), 'incomplete')
    record('missing-artifact', lambda c, e: e['artifacts'].update({'image:a': False}), 'blocked')
    record('unknown-artifact', lambda c, e: e['artifacts'].update({'image:a': None}), 'incomplete')
    record('conflict-and-gap', lambda c, e: (e['stored'].update(schema=['s3']), e['artifacts'].update({'config:7': None})), 'blocked')
    record('new-reader-accepts-old', lambda c, e: c['accepts'].update(message=['v1', 'v2']), 'eligible-for-review')
    record('both-origins-conflict', lambda c, e: (e['stored'].update(message=['v2']), e['emitters'].update(message=['v2'])), 'blocked')
    contract_combinations = 0
    # Three observations per kind: supported, unsupported, unknown.
    # Independent oracle computes known conflicts and evidence gaps over six cells.
    for values in itertools.product((['v1'], ['v2'], None), repeat=6):
        c, e = baseline()
        for (origin, kind), value in zip(itertools.product(('stored', 'emitters'), KINDS), values):
            e[origin][kind] = value
        out = evaluate(c, e)
        assert len(out['conflicts']) == sum(v == ['v2'] for v in values)
        assert len(out['unknownContracts']) == sum(v is None for v in values)
        assert out['eligibleForReview'] == all(v == ['v1'] for v in values)
        contract_combinations += 1
    artifact_combinations = 0
    for values in itertools.product((True, False, None), repeat=2):
        c, e = baseline(); e['artifacts'] = dict(zip(c['artifacts'], values))
        out = evaluate(c, e)
        assert len(out['unavailableArtifacts']) == sum(v is False for v in values)
        assert len(out['unknownArtifacts']) == sum(v is None for v in values)
        assert out['eligibleForReview'] == all(v is True for v in values)
        artifact_combinations += 1
    # Input label order and irrelevant dictionary order do not affect decisions.
    c, e = baseline(); c['accepts']['message'] = ['v1', 'v2']
    e['stored']['message'] = ['v1', 'v3', 'v4']; expected = evaluate(c, e)
    order_permutations = 0
    for order in itertools.permutations(e['stored']['message']):
        e['stored']['message'] = list(order); assert evaluate(c, e) == expected
        order_permutations += 1
    invalid = [
        lambda c, e: c.update(extra=True),
        lambda c, e: e.update(extra=True),
        lambda c, e: c.pop('accepts'),
        lambda c, e: e['stored'].pop('schema'),
        lambda c, e: e['emitters'].update(extra=[]),
        lambda c, e: c['accepts'].update(message='v1'),
        lambda c, e: e['stored'].update(message=['v1', 'v1']),
        lambda c, e: e['emitters'].update(message=['']),
        lambda c, e: e['stored'].update(message=[1]),
        lambda c, e: e['artifacts'].update({'image:a': 1}),
        lambda c, e: e['artifacts'].update({'image:a': 'true'}),
        lambda c, e: e['artifacts'].pop('image:a'),
        lambda c, e: e['artifacts'].update(extra=True),
        lambda c, e: c.update(artifacts=[]),
        lambda c, e: c.update(artifacts=['image:a', 'image:a']),
        lambda c, e: c['accepts'].update(schema=[' ']),
    ]
    for mutate in invalid:
        c, e = baseline(); mutate(c, e)
        try:
            evaluate(c, e)
        except ValueError:
            pass
        else:
            raise AssertionError('invalid input accepted')
    return {'scriptSha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
            'fixtures': fixtures, 'contractCombinations': contract_combinations,
            'artifactCombinations': artifact_combinations, 'orderPermutations': order_permutations,
            'invalidInputs': len(invalid), 'inputPreserved': True,
            'cloudExecuted': False, 'databaseExecuted': False,
            'network': False, 'persistentWrites': False}

if __name__ == '__main__':
    print(json.dumps(exercise(), ensure_ascii=False, sort_keys=True, indent=2))
NA PRÁTICA

A imagem A está disponível, mas só lê v1 e há mensagens v2 por processar. A recuperação exige tratar esse contrato, mesmo com rollout tecnicamente executável.

Armadilhas comuns

Tratar sucesso antigo como compatibilidade atual; esquecer batches e mensagens históricas; usar latest como identidade; confundir um segredo legível com credencial válida.

Tópicos relacionados: Migrações e reconciliação de dados · Gestão de segredos e artefactos · Recuperação de serviços e modelos ML

Leva esta ideia contigo

Uma opção de recuperação é uma combinação demonstrável de contratos e dependências. Regista conflitos e lacunas sem converter um ensaio local em autorização operacional.

Criar conta

Referência: Professional Cloud DevOps Engineer exam guide · Current linked guide; edition date unconfirmed (2026-09-30 inspection)

Google Cloud é uma marca comercial de Google LLC. A dr.pt é uma plataforma de preparação independente e não está afiliada, associada, patrocinada, autorizada nem aprovada por Google. Os conteúdos e as perguntas são originais, não são perguntas oficiais de exame, e concluir os nossos testes não atribui nem garante qualquer certificação. Os nomes são usados apenas para identificar o tema. Todas as outras marcas pertencem aos respetivos titulares.