Identificar o estado que cada execução controla
Antes de investigar um apply, identifica a execução e o estado selecionado. Regista repositório, revisão, backend, prefixo, workspace e identidade que executa a operação. No backend GCS, o estado fica num objeto cujo caminho depende do prefixo e do workspace dentro do bucket escolhido. Dois jobs podem usar variáveis diferentes e, ainda assim, apontar para o mesmo estado. Num ensaio fictício, desenvolvimento e produção receberam pipelines separados mas conservaram a mesma configuração de backend. O primeiro sinal foi um lock disputado; o problema de desenho era a ausência de separação intencional dos deployments. Confirma se a partilha é deliberada antes de alterar parâmetros. Para deployments independentes, define a organização dos estados e as respetivas permissões com os responsáveis. Mantém também uma estratégia de recuperação do estado; a documentação do backend recomenda versionamento de objetos. Um nome de branch diferente não constitui prova de isolamento. A evidência deve permitir que outra pessoa reconstrua exatamente qual conjunto de recursos a execução pretendia gerir.
Tratar locks como informação de coordenação
Um lock indica que o acesso ao estado precisa de coordenação. Se outro job está ativo, não uses force-unlock apenas porque a janela está a terminar. Identifica quem detém o lock e correlaciona-o com o estado da execução, logs e responsável. A ausência de novas linhas de log durante alguns segundos também não prova abandono. Quando uma execução termina anormalmente, segue o procedimento de recuperação e confirma que o writer já não está a atuar antes de tratar um lock órfão. O comando de desbloqueio não altera diretamente os recursos, mas pode permitir alterações concorrentes posteriores. Desativar locking ou copiar o estado para outro local também não resolve a coordenação sobre os mesmos recursos remotos. Num incidente, comunica o atraso, a operação em curso e a condição necessária para retomar. Esta informação é mais útil para o gestor da janela do que repetir applies até algum passar. Conserva a identificação da execução que fundamentou a decisão de esperar, cancelar ou recuperar.
Reconciliar estado, configuração e alterações de emergência
Distingue o código pretendido, o estado guardado e os objetos que existem no fornecedor. Uma intervenção de emergência pode alterar apenas o terceiro. Em Terraform a partir de 0.15.4, um plano refresh-only permite rever a atualização dos registos e outputs para refletir alterações remotas. Aplicar esse plano não repõe o recurso na configuração antiga nem modifica automaticamente o código versionado. Se um recurso passou de quatro para dez unidades durante um incidente, documenta a decisão de manter dez ou regressar a quatro antes de retomar o fluxo normal. Um plano normal posterior pode voltar a propor o que o código pede. A separação entre estados também exige atenção: CLI workspaces usam o mesmo backend e não fornecem por si só uma fronteira de credenciais adequada entre equipas. Para um requisito de acesso segregado, revê permissões e organização dos backends. O nome prod ajuda a identificar contexto, mas não é um controlo de autorização.
Gerir ambientes temporários com um inventário verificável
Um prazo num inventário ou label só é útil se existir um processo que o interpreta. Define quem pode criar, prolongar e retirar um ambiente, como se identifica trabalho ativo e que dados precisam de ser conservados. No exercício local desta aula, a regra é fictícia e explícita: só propor revisão quando environment é preview, o prazo foi atingido, existe owner, hold e active_lease são false e o snapshot tem entre zero e cinco minutos. Estas condições não são uma política automática Google Cloud. O programa não elimina recursos e um candidato ainda precisa do procedimento operacional aprovado. A data sem fuso horário é insuficiente para decidir um prazo; o texto "false" também não é tratado como o booleano false. Um snapshot futuro ou antigo fica retido para investigação. Ao explicar o resultado ao responsável financeiro, distingue recursos expirados, candidatos validados e eliminação realmente concluída. Isso evita prometer poupança com base numa lista que ainda inclui ambientes usados por ensaios ou incidentes. Os campos deste inventário não são uma proposta de labels Cloud.
Verificar defaults de fleet e a configuração efetiva
Um default define a base de configuração para novos clusters elegíveis, mas não demonstra que todos os membros existentes foram atualizados. Inventaria a versão efetiva de cada cluster, planeia a sincronização necessária e verifica os resultados. A própria presença de uma funcionalidade também não prova que a configuração desejada chegou à aplicação. No caso de Config Sync, a documentação atual permite configurar a ligação à fonte de verdade em defaults através de CLI ou Terraform, mas a consola não oferece essa ligação como default. Se a instalação foi feita pela consola e nenhum pacote foi depois configurado, não esperes que o componente descubra o Git da aplicação. Define fonte, revisão e autorização de leitura, e observa a sincronização. Para um repositório privado, o acesso do engenheiro não substitui o acesso da identidade usada pelo componente. Na passagem a RUN, regista tanto o estado da funcionalidade como a revisão realmente aplicada e os erros que impedem convergência.
Preparar ferramentas sem depender da sessão anterior
Cloud Shell e Cloud Workstations ajudam a disponibilizar ferramentas, mas têm ciclos de vida que precisam de ser conhecidos. No modo normal de Cloud Shell, o diretório pessoal tem persistência entre sessões enquanto a VM é temporária. Uma instalação manual em /usr/local pode desaparecer quando a VM muda. Usa uma preparação reprodutível e verifica versões antes de executar procedimentos; persistência de ficheiros não equivale a backup nem a retenção ilimitada. Em Cloud Workstations, um processo CPU-bound em background pode continuar ocupado sem produzir eventos que repõem o idle timeout. A documentação distingue pedidos de rede de entrada, chamadas Start e interação com o IDE. Se precisas de executar trabalho prolongado, escolhe um percurso aprovado que considere timeouts, recuperação e conservação dos resultados. Não aumentes memória ou privilégios sem evidência de que resolvem a causa. O runbook deve permitir começar numa sessão nova e chegar ao mesmo conjunto de ferramentas e contexto autorizado.
Controlar a resolução de dependências no cliente
A arquitetura de CI/CD inclui o percurso usado para obter dependências. Um virtual repository pode aplicar prioridades aos upstreams que agrega, mas não controla pedidos que o cliente envia diretamente a outro índice. Num exemplo fictício, o pip consulta o endpoint interno e PyPI como índice adicional. A prioridade privada configurada no endpoint interno não governa toda a seleção feita pelo cliente. Configura o cliente para usar o endpoint aprovado e fixa a versão pretendida; verifica também os upstreams que podem servir essa versão. Valores numéricos maiores representam maior prioridade e um empate não define uma origem única. Estas medidas reduzem uma lacuna de seleção, mas não demonstram que o pacote é seguro. A aprovação de dependências continua a precisar de evidência apropriada de origem, revisão e análise. Para diagnosticar diferenças entre builds, conserva a configuração efetiva do cliente, a versão resolvida e a origem observada, além do código da aplicação.
Executar o modelo e preparar a decisão operacional
O código abaixo usa apenas a biblioteca padrão Python e dados fictícios incorporados. Executa-o numa pasta de ensaio com python3 run.py e interpreta os resultados antes de alterar qualquer regra. Alpha, lambda e mu são candidatos segundo as condições fornecidas: lambda demonstra equivalência de fusos horários e mu fica exatamente no limite de cinco minutos. Os restantes registos mostram ausência de owner, produção, prazo futuro, lease ativa, hold, snapshot antigo, data sem fuso, booleano em texto e snapshot futuro. O programa testa 64 combinações das seis condições, seis limites temporais e cinco entradas de data inválidas. Não consulta inventário cloud nem confirma que os metadados correspondem à realidade. Acrescenta no teu raciocínio quem recolhe e valida essa evidência. Depois resolve o caso de um ambiente expirado com apply ativo. Explica ao gestor da janela por que motivo o prazo não demonstra abandono do lock e que prova falta antes de decidir a limpeza.
"""Original fictional inventory review. No provider calls or deletion operations."""
from datetime import datetime, timezone, timedelta
from itertools import product
from pathlib import Path
import hashlib
import json
import platform
NOW = datetime(2026, 10, 6, 12, tzinfo=timezone.utc)
def instant(value):
if not isinstance(value, str):
return None
try:
parsed = datetime.fromisoformat(value)
except ValueError:
return None
if parsed.tzinfo is None or parsed.utcoffset() is None:
return None
return parsed.astimezone(timezone.utc)
def classify(row, now=NOW):
"""Select a review candidate only under the six explicit exercise rules."""
expires = instant(row.get('expires_at'))
observed = instant(row.get('observed_at'))
checks = {
'preview_only': row.get('environment') == 'preview',
'expired': expires is not None and expires <= now,
'owner_known': isinstance(row.get('owner'), str) and bool(row['owner'].strip()),
'no_hold': row.get('hold') is False,
'no_active_lease': row.get('active_lease') is False,
'fresh_snapshot': observed is not None and timedelta(0) <= now - observed <= timedelta(minutes=5),
}
reasons = [key for key, passed in checks.items() if not passed]
return {'id': row['id'], 'review_candidate': not reasons, 'reasons': reasons}
def main():
base = dict(id='alpha', environment='preview', expires_at='2026-10-06T11:00:00Z',
owner='team-recon', hold=False, active_lease=False,
observed_at='2026-10-06T11:58:00Z')
fixtures = [base,
dict(base, id='beta', owner=''),
dict(base, id='gamma', environment='production'),
dict(base, id='delta', expires_at='2026-10-06T13:00:00Z'),
dict(base, id='epsilon', active_lease=True),
dict(base, id='zeta', hold=True),
dict(base, id='eta', observed_at='2026-10-06T11:54:59Z'),
dict(base, id='theta', expires_at='2026-10-06T11:00:00'),
dict(base, id='iota', hold='false'),
dict(base, id='kappa', observed_at='2026-10-06T12:00:01Z'),
dict(base, id='lambda', expires_at='2026-10-06T13:00:00+01:00'),
dict(base, id='mu', observed_at='2026-10-06T11:55:00Z')]
results = [classify(row) for row in fixtures]
assert [r['id'] for r in results if r['review_candidate']] == ['alpha', 'lambda', 'mu']
named_checks = ['fixture_candidates', 'production_excluded', 'owner_required',
'active_lease_excluded', 'hold_excluded', 'stale_snapshot_excluded',
'naive_timestamp_rejected', 'string_boolean_rejected',
'future_snapshot_rejected', 'timezone_equivalence', 'inclusive_expiry',
'inclusive_freshness']
assert results[2]['reasons'] == ['preview_only']
assert results[1]['reasons'] == ['owner_known']
assert results[4]['reasons'] == ['no_active_lease']
assert results[5]['reasons'] == ['no_hold']
assert results[6]['reasons'] == ['fresh_snapshot']
assert results[7]['reasons'] == ['expired']
assert results[8]['reasons'] == ['no_hold']
assert results[9]['reasons'] == ['fresh_snapshot']
assert instant('2026-10-06T13:00:00+01:00') == NOW
assert results[10]['review_candidate']
assert results[11]['review_candidate']
truth_cases = 0
for flags in product([False, True], repeat=6):
row = dict(base, environment='preview' if flags[0] else 'production',
expires_at='2026-10-06T11:00:00Z' if flags[1] else '2026-10-06T13:00:00Z',
owner='team' if flags[2] else '', hold=not flags[3],
active_lease=not flags[4],
observed_at='2026-10-06T11:59:00Z' if flags[5] else '2026-10-06T11:54:00Z')
result = classify(row)
assert result['review_candidate'] == all(flags)
assert len(result['reasons']) == flags.count(False)
truth_cases += 1
expiry_cases = 0
for seconds in [-3600, -1, 0, 1, 60, 3600]:
row = dict(base, expires_at=(NOW + timedelta(seconds=seconds)).isoformat())
assert classify(row)['review_candidate'] == (seconds <= 0)
expiry_cases += 1
for invalid in [None, 17, '', 'not-a-date', '2026-99-99T12:00:00Z']:
assert instant(invalid) is None
print(json.dumps({'scriptSha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
'runtime': platform.python_version(), 'checks': len(named_checks),
'checkNames': named_checks, 'fixtures': results, 'truthCases': truth_cases,
'expiryCases': expiry_cases, 'invalidTimestamps': 5, 'network': False,
'persistentWrites': False, 'deletions': 0, 'vendorExecution': False,
'independentVerification': False}, indent=2))
if __name__ == '__main__':
main()
Dois jobs apontam para o mesmo estado enquanto uma rotina de limpeza considera um ambiente cujo prazo expirou mas ainda tem trabalho ativo.
Armadilhas comuns
Forçar locks ativos, confundir refresh-only com reposição de recursos, usar nomes como controlo de acesso e assumir que instalar um componente configura a sua fonte.
Tópicos relacionados: Gestão de estado e drift · Plataformas de desenvolvimento e GitOps · Obsolescência, decommissioning e FinOps
Antes de alterar ou retirar um ambiente, identifica estado, identidade, configuração efetiva e trabalho em curso, e demonstra o resultado pretendido.
Referência: GCS backend · Current linked guide; edition date unconfirmed (2026-09-30 inspection)