← Professional Cloud Security Engineer: controlos e evidência
20 / 23 · 135 MIN

Failover com capacidade e confiança preservadas

Avalia dependências comuns, certificados, transições NAT e reposição de fronteiras antes de aceitar um percurso de recuperação.

Definir o que tem de sobreviver à falha

Uma equipa APS de um banco fictício prepara um ensaio de recuperação do serviço de fundos. O percurso principal falha durante o envio de posições para um parceiro. O objetivo é manter o processamento autorizado dentro da capacidade acordada, conservando os controlos do transporte. Este caso não descreve procedimentos internos BNP Paribas. Antes de contar túneis, lista os componentes de que cada percurso depende: interfaces cloud, routers do parceiro, ligações físicas, resolução DNS, terminação TLS e regras de segurança. A topologia HA VPN tem requisitos próprios de interfaces. Dois túneis na mesma interface não substituem um túnel em cada interface. Mesmo quando essa distribuição existe, dois IPs num único equipamento do parceiro sem redundância interna continuam dependentes desse equipamento. Regista separadamente a condição da topologia cloud e a hipótese de falha do sistema completo. Um compromisso de disponibilidade do produto não prova a disponibilidade da aplicação. Como exercício guiado, desenha dois percursos e marca um router comum. Remove esse router do desenho e identifica os consumidores sem caminho. Depois acrescenta um percurso independente e verifica se preserva os controlos exigidos. Evita propor funcionalidades fora do âmbito suportado: BFD no Cloud Router é documentado para sessões de VLAN attachments de Interconnect, não para as sessões HA VPN deste caso. O gestor deve converter cada lacuna numa decisão com responsável, evidência necessária e impacto no prazo do ensaio.

Interpretar o sinal de saúde que realmente existe

O dashboard mostra DNS saudável e a equipa quer encerrar a recuperação. Pergunta primeiro o que esse sinal mede. Num internal passthrough Network Load Balancer sem instâncias backend, Cloud DNS pode considerar o endpoint saudável. No health checking de endpoints externos, quando todos estão unhealthy, as respostas podem continuar a incluir esses destinos. Um endereço devolvido não prova serviço funcional e uma falha de health check não é um mecanismo universal para impedir tráfego. A resolução híbrida também precisa de dados consistentes. Cloud DNS ordena os forwarding targets automaticamente, considerando respostas e latência. NXDOMAIN conta como resposta bem-sucedida nesse contexto. Se um servidor contém o registo e outro responde que o nome não existe, não deves tratar o conjunto como uma pesquisa sequencial garantida até encontrar um endereço. Corrige a divergência dos dados e verifica o percurso efetivamente usado pelo workload. No ensaio, recolhe três observações distintas: resposta DNS, resultado do probe aplicável e operação funcional do consumidor. Especifica o nome consultado, a origem e o momento, para evitar comparar testes diferentes como se fossem o mesmo. Dá ao observador um caso em que o VIP existe sem backends e outro em que o forwarder devolve NXDOMAIN. Pede uma conclusão sustentada e uma verificação seguinte para cada um. O relatório deve preservar a diferença entre nome resolvido, endpoint avaliado e negócio recuperado.

Confirmar o certificado apresentado ao cliente

A equipa renovou um certificado wildcard, mas o cliente continua a receber material anterior. No certificate map existe uma entrada exata para o SNI do cliente. A correspondência exata tem precedência sobre o wildcard; atualizar outro recurso não muda necessariamente o certificado selecionado. O teste de aceitação deve usar o hostname real de recuperação e verificar o handshake, a cadeia, a validade e a correspondência do nome. A cobertura do wildcard também tem limites. Um certificado para *.funds.example não cobre api.eu.funds.example. Separa este problema de resolução DNS: apontar ambos os nomes para o mesmo VIP não altera os nomes cobertos. Identifica ainda quem renova o material. Certificados self-managed exigem renovação e substituição sob responsabilidade da equipa; a presença no Certificate Manager não os converte em Google-managed. Para DNS authorization, conserva a dependência do CNAME necessário à renovação, além da emissão inicial. Prepara uma tabela original com hostname, SNI do teste, entrada selecionada, certificado observado e resultado. Inclui uma linha cujo health check HTTP passa mas cujo handshake do cliente falha. O objetivo é demonstrar por que uma resposta de saúde não substitui a validação TLS. Se o novo destino ainda não estiver correto, apresenta a opção de manter temporariamente um destino anterior autorizado, com prazo e condição de saída. Não desligues a validação do cliente para obter um resultado verde que deixaria por cumprir o critério aprovado.

Conservar controlo durante a transição de tráfego

Uma regra Cloud Armor em preview corresponde ao pedido, mas a avaliação continua até à regra aplicada que permite o tráfego. O ensaio mostrou o efeito potencial do deny; não mostrou um bloqueio efetivo. No relatório, associa cada decisão ao modo da regra e à versão da política. Se um import falhar por fingerprint desatualizado, volta a obter o estado e reconcilia a alteração concorrente. Colar o fingerprint atual num export antigo pode apagar uma correção legítima. Na saída para um parceiro, a equipa pretende substituir um IP Public NAT configurado manualmente. Drenar permite que sessões existentes continuem até terminarem naturalmente, enquanto novas ligações usam endereços ativos. Deve permanecer pelo menos um endereço ativo. O parceiro precisa de aceitar a nova origem antes de se considerar a transição funcional. Um ficheiro transferido pela sessão antiga não demonstra essa aceitação. Ao usar gcloud para drenar, a passagem do active pool para o drain pool deve fazer parte da mesma alteração. Remover primeiro e drenar depois pode terminar sessões em curso. Constrói uma sequência de mudança com autorização da nova origem, teste de uma ligação nova, drenagem e observação das sessões restantes. Declara o que faria a equipa recuar e o que não é recuperável apenas voltando a adicionar um endereço. No handover, indica quais sessões testaram o destino ativo e quais apenas demonstram continuidade do estado anterior.

Recuperar fronteiras com âmbito e versões explícitos

Foi aprovada a promoção de um único perímetro, mas o script usa dry-run enforce-all na access policy. Outra equipa tem uma experiência preparada nesse âmbito. O comando abrangente pode promover também essa configuração. A seleção da operação deve corresponder ao âmbito aprovado. Um etag identifica uma revisão; não transforma uma operação sobre todos os perímetros numa operação individual. Quando existe configuração dry-run explícita, compara spec com status antes de a promover. A mudança do modo enforced não prova que o ensaio ficou igual. Uma proposta antiga pode voltar a introduzir uma regra retirada em produção. O registo de mudança deve mostrar quais configurações foram revistas em conjunto e quem confirmou os fluxos que precisam de continuar permitidos e negados. Num cluster GKE Standard com dataplane legado, ativar network policy enforcement exige recriação de nós e respeita políticas de manutenção. Aceitação da configuração não significa transição concluída. Numa migração para GKE Dataplane V2, revê também manifests que identificam Pods por ipBlock.cidr e valida labels e seletores apropriados. Os probes do Application Load Balancer precisam de atravessar a política aplicável. Um teste interno bem-sucedido não prova esse percurso. Como entrega, constrói uma matriz curta com recurso, estado pretendido, estado observado e teste pendente. Inclui a dependência temporal da atualização dos nós no plano, em vez de anunciar cobertura antes de existir evidência.

Distinguir convergência, capacidade e tamanho de pacote

Dois percursos independentes suportam seis unidades abstratas cada e a carga é nove. Em operação repartida existe capacidade agregada suficiente. Quando um falha, restam seis unidades e um défice de três. Os números são premissas didáticas, não limites de throughput do produto. Definir redundância sem testar capacidade sobrevivente pode esconder degradação precisamente no momento do failover. A convergência do control plane é outra dimensão. Durante manutenção de software Cloud Router, graceful restart no peer participa na preservação de rotas. Se estiver desativado e houver CEASE, aumentar apenas o hold timer não impede o encerramento explícito da sessão. Não confundas ausência de keepalives com uma notificação de encerramento. Regista o comportamento esperado de cada cenário e mede-o num ensaio autorizado, sem inferir tempo de recuperação a partir do estado final da consola. Depois da mudança de percurso, testa tráfego representativo. Pacotes TCP pequenos podem passar enquanto UDP grande com DF falha. MSS clamping é um mecanismo TCP; para o outro tráfego, analisa payload MTU, encapsulação e entrega das mensagens ICMP necessárias. O MTU externo não é automaticamente o espaço disponível ao payload. Prepara três evidências separadas para o comité: existência de percurso, capacidade sob carga e compatibilidade dos protocolos e tamanhos usados. Uma evidência positiva numa coluna não preenche uma lacuna nas outras.

Modelar dependências comuns sem duplicar capacidade

O exercício local representa a rede como um grafo dirigido com capacidades inteiras abstratas. Cada ligação tem domínios de falha e estados declarados para os controlos exigidos. Uma falha retira todas as ligações que dependem do domínio afetado. Uma ligação com controlo ausente, desconhecido ou falhado fica excluída da capacidade elegível. Isto é uma regra conservadora do exercício, não uma leitura automática de configuração cloud. Prevê primeiro o caso common-router-fails. Embora existam dois percursos desenhados, ambos perdem uma ligação essencial quando falha o router comum. Depois compara shared-capacity-bottleneck: dois ramos de seis unidades passam por uma ligação comum de sete. Somar os ramos daria doze, mas o limite partilhado mantém a capacidade total em sete. O algoritmo procura fluxo máximo no grafo elegível e os testes comparam o resultado com cortes enumerados num pequeno grafo. O modelo aceita repartição de unidades de carga numa direção. Não representa sessões indivisíveis, ECMP, latência, perda, protocolos de encaminhamento ou o tempo de convergência. Uma ligação desenhada no sentido inverso não cria automaticamente o percurso de ida. Marca o inventário como incompleto e observa que a capacidade calculada continua visível, mas já não sustenta aceitação do cenário. Justifica por escrito as premissas que precisariam de medição real antes de usar o resultado numa decisão operacional. O cálculo ajuda a encontrar contradições; não substitui um ensaio da aplicação.

Entregar uma decisão de recuperação verificável

Executa o programa sem credenciais e com os dados fictícios fornecidos. Antes de olhar para o resultado, escreve a capacidade que esperas nos casos de falha de um percurso, falha do router comum e controlo desconhecido no backup. Compara a previsão com capacityUnderDeclaredModel, shortfall e excludedControlEdges. Se a previsão divergir, explica qual premissa ou ligação foi ignorada. Os testes verificam combinações de capacidade, controlos e falhas, além de permutações da entrada e rejeição de modelos inválidos. IDs repetidos, domínios de falha desconhecidos e capacidades negativas não são convertidos silenciosamente em dados úteis. O programa preserva a entrada, não contacta serviços e não escreve configuração. O hash regista que versão do código produziu a evidência; não autentica as capacidades ou os estados declarados. No handover, entrega uma nota curta com cenário de falha, carga exigida, dependências removidas, capacidade elegível, controlos em falta e próxima validação. Se o modelo tiver capacidade suficiente mas o inventário estiver incompleto, mantém as duas afirmações explícitas. Distingue quem observou o ensaio, quem aceita o risco residual e quem executaria a mudança. A decisão final precisa ainda dos testes de aplicação, dos critérios temporais e das autorizações reais. Completa a aula respondendo aos casos de certificado e dependência comum, explicando por que as alternativas não satisfazem as restrições dadas.

"""Synthetic directed capacity worksheet, not a cloud routing simulator.

Capacity units are divisible abstract units for one direction and one demand.
Every selected edge must pass every declared control. Failure domains remove
all affected edges. This does not model BGP, ECMP, latency or real throughput.
"""
from collections import deque
from copy import deepcopy
from hashlib import sha256
from itertools import combinations, permutations, product
from pathlib import Path
import json


def label(x):
    return isinstance(x, str) and bool(x.strip()) and x == x.strip()


def unique_labels(values, nonempty=True):
    return isinstance(values, list) and (bool(values) or not nonempty) and all(label(x) for x in values) and len(values) == len(set(values))


def validate(model, failed):
    if not isinstance(model, dict) or set(model) != {'nodes','source','sink','demand','controls','edges','inventoryComplete'}:
        raise ValueError('Expected exact worksheet model fields')
    if not unique_labels(model['nodes']) or not unique_labels(model['controls']):
        raise ValueError('Unique nonempty node and control lists required')
    if not all(label(model[k]) and model[k] in model['nodes'] for k in ['source','sink']) or model['source'] == model['sink']:
        raise ValueError('Source and sink must be distinct declared nodes')
    if type(model['demand']) is not int or model['demand'] <= 0:
        raise ValueError('Demand must be a positive integer')
    if type(model['inventoryComplete']) is not bool or not isinstance(model['edges'], list):
        raise ValueError('Explicit inventory flag and edge list required')
    ids, pairs, domains = set(), set(), set()
    for edge in model['edges']:
        if not isinstance(edge, dict) or set(edge) != {'id','src','dst','capacity','domains','controls'}:
            raise ValueError('Expected exact edge fields')
        if not label(edge['id']) or edge['id'] in ids:
            raise ValueError('Unique nonblank edge ID required')
        ids.add(edge['id'])
        if not all(label(edge[k]) and edge[k] in model['nodes'] for k in ['src','dst']) or edge['src'] == edge['dst']:
            raise ValueError('Edge endpoints must be distinct declared nodes')
        pair = (edge['src'], edge['dst'])
        if pair in pairs:
            raise ValueError('Duplicate directed pair; use explicit intermediate nodes')
        pairs.add(pair)
        if type(edge['capacity']) is not int or edge['capacity'] < 0:
            raise ValueError('Capacity must be a nonnegative integer')
        if not unique_labels(edge['domains']):
            raise ValueError('Each edge needs unique declared failure domains')
        domains.update(edge['domains'])
        states = edge['controls']
        if not isinstance(states, dict) or any(k not in model['controls'] or v not in ['pass','fail','unknown'] for k,v in states.items()):
            raise ValueError('Only declared controls and recognized states are accepted')
    if not unique_labels(failed, nonempty=False) or not set(failed) <= domains:
        raise ValueError('Failure scenarios must name declared domains without duplicates')


def capacity(model, edges):
    """Augmenting-path maximum for this synthetic directed graph only."""
    nodes = model['nodes']
    residual = {u:{v:0 for v in nodes} for u in nodes}
    for e in edges:
        residual[e['src']][e['dst']] += e['capacity']
    source, sink, value = model['source'], model['sink'], 0
    while True:
        previous, queue = {source:None}, deque([source])
        while queue and sink not in previous:
            u = queue.popleft()
            for v in sorted(nodes):
                if v not in previous and residual[u][v] > 0:
                    previous[v] = u
                    queue.append(v)
        if sink not in previous:
            return value
        increment, v = None, sink
        while previous[v] is not None:
            u = previous[v]
            increment = residual[u][v] if increment is None else min(increment,residual[u][v])
            v = u
        v = sink
        while previous[v] is not None:
            u = previous[v]
            residual[u][v] -= increment
            residual[v][u] += increment
            v = u
        value += increment


def analyze(model, failed):
    validate(model, failed)
    disabled, unproven, eligible = [], [], []
    for e in model['edges']:
        if set(e['domains']) & set(failed):
            disabled.append(e['id'])
        elif any(e['controls'].get(c,'unknown') != 'pass' for c in model['controls']):
            unproven.append(e['id'])
        else:
            eligible.append(e)
    maximum = capacity(model, eligible)
    return {'failedDomains':sorted(failed),'disabledEdges':sorted(disabled),
            'excludedControlEdges':sorted(unproven),'eligibleEdges':sorted(e['id']for e in eligible),
            'capacityUnderDeclaredModel':maximum,'demand':model['demand'],
            'shortfall':max(0,model['demand']-maximum),
            'meetsDeclaredCapacity':maximum >= model['demand'],
            'inventoryCoverageUnproven':not model['inventoryComplete'],
            'scenarioSupportedUnderAssumptions':maximum >= model['demand'] and model['inventoryComplete'],
            'realThroughputMeasured':False,'routingConvergenceVerified':False,
            'controlAuthenticityVerified':False,'productionFailoverAuthorized':False}


def edge(id, src, dst, cap=6, domains=None):
    return {'id':id,'src':src,'dst':dst,'capacity':cap,'domains':[id] if domains is None else domains,
            'controls':{'transport':'pass','boundary':'pass'}}


def model():
    return {'nodes':['source','a','b','sink'],'source':'source','sink':'sink','demand':6,
            'controls':['transport','boundary'],'inventoryComplete':True,
            'edges':[edge('sa','source','a'),edge('at','a','sink'),edge('sb','source','b'),edge('bt','b','sink')]}


def evidence():
    fixtures=[]
    def record(id,m,failed):
        old=deepcopy((m,failed));r=analyze(m,failed);assert(m,failed)==old
        fixtures.append({'id':id,**r});return r
    m=model();assert record('two-independent-paths',m,[])['capacityUnderDeclaredModel']==12
    assert record('one-path-fails',m,['sa'])['capacityUnderDeclaredModel']==6
    assert record('two-paths-fail',m,['sa','sb'])['capacityUnderDeclaredModel']==0
    m=model();m['demand']=9
    assert record('survivor-under-capacity',m,['sa'])['shortfall']==3
    m=model();m['edges'][0]['domains'].append('shared-router');m['edges'][2]['domains'].append('shared-router')
    assert record('common-router-fails',m,['shared-router'])['capacityUnderDeclaredModel']==0
    m=model();m['nodes'].append('shared');m['edges']=[edge('common','source','shared',7),edge('sa','shared','a'),edge('at','a','sink'),edge('sb','shared','b'),edge('bt','b','sink')];m['demand']=9
    assert record('shared-capacity-bottleneck',m,[])['capacityUnderDeclaredModel']==7
    m=model();m['edges'][2]['controls']['transport']='unknown'
    assert record('unknown-backup-control',m,['sa'])['capacityUnderDeclaredModel']==0
    m=model();del m['edges'][2]['controls']['transport']
    assert record('missing-backup-control',m,['sa'])['excludedControlEdges']==['sb']
    m=model();m['edges'][2]['controls']['boundary']='fail'
    assert record('failed-backup-boundary',m,['sa'])['scenarioSupportedUnderAssumptions'] is False
    m=model();m['inventoryComplete']=False
    r=record('incomplete-inventory',m,[]);assert r['meetsDeclaredCapacity'] and not r['scenarioSupportedUnderAssumptions']
    m=model();m['edges'][2]['src'],m['edges'][2]['dst']='b','source'
    assert record('reverse-edge-not-forward-path',m,['sa'])['capacityUnderDeclaredModel']==0
    m=model();m['edges'][2]['capacity']=0
    assert record('zero-capacity-backup',m,['sa'])['shortfall']==6
    m=model();m['edges'].append(edge('cycle','a','source',3));m['edges'].append(edge('cross','a','b',2))
    assert record('cycles-do-not-create-capacity',m,[])['capacityUnderDeclaredModel']==12
    m=model();m['edges']=[]
    assert record('empty-declared-topology',m,[])['capacityUnderDeclaredModel']==0
    # Independent minimum-cut oracle for all 0/1/2 capacities on a five-edge graph.
    checked=0
    m=model();m['edges'].append(edge('cross','a','b'))
    for caps in product(range(3),repeat=5):
        for e,c in zip(m['edges'],caps):e['capacity']=c
        cut_values=[]
        for count in range(3):
            for middle in combinations(['a','b'],count):
                side={'source',*middle}
                cut_values.append(sum(e['capacity']for e in m['edges']if e['src']in side and e['dst']not in side))
        assert capacity(m,m['edges'])==min(cut_values)
        checked+=1
    states=0
    for state,failed in product(['pass','fail','unknown'],[[],['sa'],['sb'],['sa','sb']]):
        m=model();m['edges'][2]['controls']['boundary']=state
        expected=(0 if 'sa'in failed else 6)+(6 if state=='pass'and'sb'not in failed else 0)
        assert analyze(m,failed)['capacityUnderDeclaredModel']==expected
        states+=1
    m=model();expected=analyze(m,['sa']);orders=0
    for order in permutations(m['edges']):
        candidate=deepcopy(m);candidate['edges']=list(order)
        assert analyze(candidate,['sa'])==expected;orders+=1
    invalid=[]
    def changed(fn):
        v=model();fn(v);invalid.append((v,[]))
    changed(lambda m:m.update(demand=0))
    changed(lambda m:m.update(demand=True))
    changed(lambda m:m.update(nodes=['source','source','sink']))
    changed(lambda m:m.update(source='absent'))
    changed(lambda m:m.update(sink='source'))
    changed(lambda m:m.update(controls=[]))
    changed(lambda m:m.update(inventoryComplete='yes'))
    changed(lambda m:m.update(edges={}))
    changed(lambda m:m['edges'][0].update(capacity=-1))
    changed(lambda m:m['edges'][0].update(capacity=1.5))
    changed(lambda m:m['edges'][0].update(capacity=True))
    changed(lambda m:m['edges'][0].update(src='absent'))
    changed(lambda m:m['edges'][0].update(dst='source'))
    changed(lambda m:m['edges'][0].update(domains=[]))
    changed(lambda m:m['edges'][0].update(domains=['same','same']))
    changed(lambda m:m['edges'][0].update(controls={'other':'pass'}))
    changed(lambda m:m['edges'][0].update(controls={'transport':'yes'}))
    changed(lambda m:m['edges'][1].update(id='sa'))
    changed(lambda m:m['edges'].append(edge('duplicate','source','a')))
    changed(lambda m:m.update(extra='unexpected'))
    invalid.extend([(model(),['unknown-domain']),(model(),['sa','sa']),(model(),None),(None,[])])
    for bad,failures in invalid:
        try:analyze(bad,failures)
        except ValueError:pass
        else:raise AssertionError('Invalid input accepted')
    return {'scriptSha256':sha256(Path(__file__).read_bytes()).hexdigest(),'fixtures':fixtures,
            'capacityCombinations':checked,'controlFailureCombinations':states,'inputPermutations':orders,
            'invalidInputs':len(invalid),'inputPreserved':True,'orderIndependent':True,
            'network':False,'cloudExecuted':False,'persistentWrites':False}


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

Dois ramos de seis unidades partilham uma ligação de sete: a capacidade elegível é sete, não doze. Se o router comum falhar, ambos os percursos podem desaparecer.

Armadilhas comuns

Contar túneis como independência; somar capacidade partilhada; tratar preview como enforcement; validar só sessões antigas; confundir certificado atualizado com certificado servido.

Tópicos relacionados: Redes privadas, nomes e fronteiras de confiança · Diagnóstico de conectividade e evidência de falha · Recuperação de confiança e reversão de acesso

Leva esta ideia contigo

Um percurso de recuperação precisa de capacidade, independência e controlos demonstrados para o cenário que se pretende aceitar.

Criar conta

Referência: DNS routing policies and health checks · 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.