Kubernetes para GCP: Guia Completo de Deploy
Se você já trabalha com containers e está avaliando onde rodar seus workloads em produção, o Google Kubernetes Engine (GKE) é, sem dúvida, uma das opções mais maduras do mercado. Neste guia, vou compartilhar o que aprendi em anos operando clusters GKE em produção: desde a criação do cluster até boas práticas de infraestrutura que evitam dor de cabeça às 3 da manhã.
Este artigo assume que você já tem familiaridade com Docker e conceitos básicos de Kubernetes. Se precisar revisar esses fundamentos, recomendo antes a leitura do post DevOps para Devs: Docker, CI/CD, Kubernetes e AWS em Produção, que cobre a base necessária.
Por que usar Kubernetes na GCP
O Kubernetes nasceu dentro do Google. Isso não é apenas um fato histórico: significa que a integração entre o GKE e os serviços da GCP é profunda e nativa. Enquanto outras clouds adaptaram o Kubernetes às suas plataformas, o Google construiu sua plataforma ao redor do Kubernetes.
Vantagens concretas do GKE
O Google gerencia o plano de controle nos modos Standard e Autopilot. No Standard, você configura os node pools; no Autopilot, o serviço também gerencia os nodes. Existe uma taxa de gerenciamento por cluster, com créditos de nível gratuito sujeitos ao tipo de cluster e à conta de faturamento. Vale conferir essas condições ao estimar o custo do ambiente.
A integração com outros serviços da GCP acontece de forma transparente: Cloud SQL, Pub/Sub, Cloud Storage, Secret Manager e IAM funcionam como extensões naturais do cluster. O Workload Identity, por exemplo, permite que pods assumam identidades de service accounts da GCP sem precisar de chaves JSON, o que é um ganho enorme de segurança.
Além disso, o GKE oferece auto-upgrade de nodes, auto-repair de nodes com problemas, VPC-native networking com alias IPs e integração nativa com o Cloud Logging e Cloud Monitoring.
Casos de uso onde essa combinação brilha
Na prática, vi o GKE se destacar em cenários como: microsserviços que precisam escalar independentemente (plataformas de e-commerce com picos sazonais), pipelines de processamento de dados que consomem mensagens do Pub/Sub e escrevem no BigQuery, APIs de machine learning que servem modelos treinados no Vertex AI, e aplicações multi-região que exigem failover automático com o Multi Cluster Ingress.
Setup inicial do GKE na GCP
Pré-requisitos e ferramentas
Antes de tudo, você precisa instalar e configurar o Google Cloud SDK:
# Instalar o gcloud CLI (macOS com Homebrew)
brew install google-cloud-sdk
# Autenticar
gcloud auth login
# Configurar o projeto
gcloud config set project meu-projeto-producao
# Instalar o plugin do kubectl
gcloud components install kubectl
# Instalar o plugin de autenticação do GKE
gcloud components install gke-gcloud-auth-pluginCriando o cluster GKE
Existem dois modos principais: Standard (você gerencia os nodes) e Autopilot (o Google gerencia tudo). Para produção, recomendo começar com Autopilot a menos que você tenha necessidades muito específicas de customização de nodes:
# Criar cluster Autopilot (recomendado para a maioria dos casos)
gcloud container clusters create-auto meu-cluster-prod \
--region=southamerica-east1 \
--release-channel=regular \
--network=minha-vpc \
--subnetwork=minha-subnet \
--enable-private-nodes \
--enable-master-authorized-networks \
--master-authorized-networks=10.0.0.0/8
# Ou criar cluster Standard com mais controle
gcloud container clusters create meu-cluster-prod \
--region=southamerica-east1 \
--num-nodes=2 \
--machine-type=e2-standard-4 \
--enable-autoscaling \
--min-nodes=1 \
--max-nodes=5 \
--enable-autorepair \
--enable-autoupgrade \
--workload-pool=meu-projeto-producao.svc.id.goog \
--enable-ip-alias \
--release-channel=regular
# Conectar o kubectl ao cluster
gcloud container clusters get-credentials meu-cluster-prod \
--region=southamerica-east1Note que estou usando a região southamerica-east1 (São Paulo). Para aplicações que atendem usuários brasileiros, isso reduz a latência significativamente.
Configurando o Workload Identity
O Workload Identity é a forma recomendada pelo Google para que seus pods acessem serviços da GCP. Ele elimina a necessidade de montar service account keys como secrets:
# Criar service account na GCP
gcloud iam service-accounts create minha-app-sa \
--display-name="Service Account da Minha App"
# Dar permissões necessárias (exemplo: acesso ao Cloud SQL)
gcloud projects add-iam-policy-binding meu-projeto-producao \
--member="serviceAccount:[email protected]" \
--role="roles/cloudsql.client"
# Vincular a SA da GCP com a SA do Kubernetes
gcloud iam service-accounts add-iam-policy-binding \
[email protected] \
--role="roles/iam.workloadIdentityUser" \
--member="serviceAccount:meu-projeto-producao.svc.id.goog[default/minha-app-ksa]"Exemplo prático: deploy de uma API Node.js com TypeScript
Vamos ao que interessa. Vou mostrar um exemplo completo: uma API em TypeScript que roda no GKE, usa o Secret Manager da GCP para gerenciar segredos e se conecta ao Cloud SQL.
O código da aplicação
// src/server.ts
import express, { Request, Response } from "express";
import { SecretManagerServiceClient } from "@google-cloud/secret-manager";
import { Pool } from "pg";
const app = express();
app.use(express.json());
const secretClient = new SecretManagerServiceClient();
interface AppConfig {
dbHost: string;
dbPassword: string;
dbName: string;
}
async function loadSecrets(): Promise<AppConfig> {
const projectId = process.env.GCP_PROJECT_ID;
const [dbPasswordVersion] = await secretClient.accessSecretVersion({
name: `projects/${projectId}/secrets/db-password/versions/latest`,
});
const dbPassword = dbPasswordVersion.payload?.data?.toString() ?? "";
return {
dbHost: process.env.DB_HOST ?? "localhost",
dbPassword,
dbName: process.env.DB_NAME ?? "minha_app",
};
}
async function startServer(): Promise<void> {
const config = await loadSecrets();
const pool = new Pool({
host: config.dbHost,
user: "app_user",
password: config.dbPassword,
database: config.dbName,
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
app.get("/healthz", (_req: Request, res: Response) => {
res.status(200).json({ status: "ok" });
});
app.get("/readyz", async (_req: Request, res: Response) => {
try {
await pool.query("SELECT 1");
res.status(200).json({ status: "ready" });
} catch (error) {
res.status(503).json({ status: "not ready", error: String(error) });
}
});
app.get("/api/users", async (_req: Request, res: Response) => {
try {
const result = await pool.query(
"SELECT id, name, email FROM users LIMIT 100"
);
res.json(result.rows);
} catch (error) {
console.error("Erro ao buscar usuários:", error);
res.status(500).json({ error: "Erro interno" });
}
});
const port = parseInt(process.env.PORT ?? "8080", 10);
app.listen(port, () => {
console.log(`Servidor rodando na porta ${port}`);
});
}
startServer().catch((err) => {
console.error("Falha ao iniciar servidor:", err);
process.exit(1);
});Manifesto Kubernetes para o deploy
Crie o arquivo k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: minha-api
labels:
app: minha-api
spec:
replicas: 3
selector:
matchLabels:
app: minha-api
template:
metadata:
labels:
app: minha-api
spec:
serviceAccountName: minha-app-ksa
containers:
- name: api
image: southamerica-east1-docker.pkg.dev/meu-projeto-producao/meu-repo/minha-api:v1.0.0
ports:
- containerPort: 8080
env:
- name: GCP_PROJECT_ID
value: "meu-projeto-producao"
- name: DB_HOST
value: "10.0.1.3"
- name: DB_NAME
value: "minha_app"
- name: PORT
value: "8080"
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: minha-api-service
spec:
selector:
app: minha-api
ports:
- port: 80
targetPort: 8080
type: ClusterIP
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: minha-app-ksa
annotations:
iam.gke.io/gcp-service-account: [email protected]Aplique com:
kubectl apply -f k8s/deployment.yamlScript de deploy automatizado com TypeScript
Para equipes que preferem ter mais controle programático sobre o deploy, aqui vai um script que utiliza as bibliotecas do Google Cloud para validar e executar o deploy:
// scripts/deploy.ts
import { ContainerClient } from "@google-cloud/container";
import { exec } from "child_process";
import { promisify } from "util";
const execAsync = promisify(exec);
interface DeployConfig {
projectId: string;
region: string;
clusterName: string;
imageName: string;
imageTag: string;
}
async function verifyClusterHealth(config: DeployConfig): Promise<boolean> {
const client = new ContainerClient();
const clusterPath = `projects/${config.projectId}/locations/${config.region}/clusters/${config.clusterName}`;
try {
const [cluster] = await client.getCluster({ name: clusterPath });
console.log(`Cluster: ${cluster.name}`);
console.log(`Status: ${cluster.status}`);
console.log(`Versão do Kubernetes: ${cluster.currentMasterVersion}`);
console.log(`Total de nodes: ${cluster.currentNodeCount}`);
if (cluster.status !== "RUNNING") {
console.error("Cluster não está em estado RUNNING. Abortando deploy.");
return false;
}
return true;
} catch (error) {
console.error("Erro ao verificar cluster:", error);
return false;
}
}
async function buildAndPushImage(config: DeployConfig): Promise<boolean> {
const fullImageName = `southamerica-east1-docker.pkg.dev/${config.projectId}/meu-repo/${config.imageName}:${config.imageTag}`;
try {
console.log("Construindo imagem Docker...");
await execAsync(`docker build -t ${fullImageName} .`);
console.log("Enviando imagem para o Artifact Registry...");
await execAsync(`docker push ${fullImageName}`);
console.log(`Imagem ${fullImageName} enviada com sucesso.`);
return true;
} catch (error) {
console.error("Erro no build ou push da imagem:", error);
return false;
}
}
async function deployToGKE(config: DeployConfig): Promise<void> {
const fullImageName = `southamerica-east1-docker.pkg.dev/${config.projectId}/meu-repo/${config.imageName}:${config.imageTag}`;
try {
console.log("Atualizando imagem do deployment...");
await execAsync(
`kubectl set image deployment/minha-api api=${fullImageName} --record`
);
console.log("Aguardando rollout completar...");
const { stdout } = await execAsync(
"kubectl rollout status deployment/minha-api --timeout=300s"
);
console.log(stdout);
console.log("Deploy concluído com sucesso.");
} catch (error) {
console.error("Erro no deploy. Iniciando rollback...");
await execAsync("kubectl rollout undo deployment/minha-api");
throw error;
}
}
async function main(): Promise<void> {
const config: DeployConfig = {
projectId: process.env.GCP_PROJECT_ID ?? "meu-projeto-producao",
region: "southamerica-east1",
clusterName: "meu-cluster-prod",
imageName: "minha-api",
imageTag: process.env.IMAGE_TAG ?? `build-${Date.now()}`,
};
console.log(`Iniciando deploy da versão ${config.imageTag}`);
const clusterHealthy = await verifyClusterHealth(config);
if (!clusterHealthy) {
process.exit(1);
}
const imageReady = await buildAndPushImage(config);
if (!imageReady) {
process.exit(1);
}
await deployToGKE(config);
}
main().catch((err) => {
console.error("Deploy falhou:", err);
process.exit(1);
});Boas práticas para GKE em produção
Segurança
Use clusters privados sempre. Nodes não precisam de IPs públicos na grande maioria dos cenários. Habilite o Master Authorized Networks para restringir quem pode acessar a API do Kubernetes. Utilize o Workload Identity em vez de montar chaves de service account. Ative o Binary Authorization para garantir que apenas imagens aprovadas rodem no cluster. Mantenha o release channel "regular" para receber patches de segurança com um equilíbrio entre estabilidade e atualização.
Custos e eficiência
Defina requests de CPU e memória de acordo com a carga da aplicação e revise os valores com métricas reais. O Cluster Autoscaler usa os recursos solicitados pelos pods e as condições de agendamento para dimensionar os nodes. Já um HPA, quando configurado para utilização percentual de CPU ou memória, usa os respectivos requests como referência para ajustar as réplicas. Configure os limits considerando o comportamento da aplicação; eles não substituem o dimensionamento dos requests.
No exemplo Standard regional deste guia, --min-nodes e --max-nodes são limites por zona do node pool. Considere isso no orçamento: cinco nodes por zona não significam cinco nodes no total.
Inclua também a taxa de gerenciamento. Na tabela consultada em outubro de 2026, o GKE cobra US$ 0,10 por cluster por hora. O nível gratuito oferece US$ 74,40 mensais por conta de faturamento, aplicáveis à taxa de clusters Autopilot e Standard zonais. Esse crédito não cobre a taxa do cluster Standard regional usado no exemplo, nem os recursos de computação. Confira a tabela oficial de preços do GKE e os demais serviços consumidos antes de estimar o total.
Guias Relacionados
Explore mais guias técnicos
Mais de 100 guias práticos de integração entre frameworks, ferramentas e plataformas.
Ver todos os guias