Kubernetes se consolidou como o padrao para orquestracao de containers, e aplicacoes Kotlin rodam excepcionalmente bem nesse ambiente. Com a JVM otimizada para containers e frameworks como Spring Boot e Ktor prontos para cloud-native, Kotlin e Kubernetes formam uma dupla poderosa. Vamos explorar como fazer isso na pratica.
Preparando a Aplicacao Kotlin para Kubernetes
Dockerfile Otimizado
O primeiro passo e criar uma imagem Docker eficiente:
// build.gradle.kts - Configuracao para gerar JAR otimizado
plugins {
kotlin("jvm") version "2.1.0"
kotlin("plugin.spring") version "2.1.0"
id("org.springframework.boot") version "3.3.0"
id("io.spring.dependency-management") version "1.1.4"
}
tasks.bootJar {
archiveFileName.set("app.jar")
layerTools {
enabled = true // Habilita layer tools para build Docker eficiente
}
}
// Configuracoes JVM para containers
tasks.bootRun {
jvmArgs = listOf(
"-XX:+UseContainerSupport",
"-XX:MaxRAMPercentage=75.0",
"-XX:InitialRAMPercentage=50.0"
)
}
O Dockerfile multi-stage ideal para Spring Boot com Kotlin usa layer extraction para maximizar cache de camadas Docker. A primeira fase extrai as camadas do JAR, e a segunda fase copia cada camada separadamente, permitindo que o Docker reutilize cache para camadas que nao mudaram.
Health Checks para Kubernetes
Kubernetes usa probes para gerenciar o ciclo de vida dos pods:
// Health checks detalhados para Kubernetes
@RestController
class KubernetesHealthController(
private val dataSource: DataSource
) {
// Liveness probe - "A aplicacao esta viva?"
// Se falhar, Kubernetes reinicia o pod
@GetMapping("/health/live")
fun liveness(): ResponseEntity<Map<String, String>> {
return ResponseEntity.ok(mapOf("status" to "alive"))
}
// Readiness probe - "A aplicacao esta pronta para receber trafego?"
// Se falhar, Kubernetes remove o pod do service
@GetMapping("/health/ready")
fun readiness(): ResponseEntity<Map<String, Any>> {
val dbReady = try {
dataSource.connection.use { it.isValid(2) }
} catch (e: Exception) {
false
}
return if (dbReady) {
ResponseEntity.ok(mapOf(
"status" to "ready",
"database" to "connected"
))
} else {
ResponseEntity.status(HttpStatusCode.valueOf(503)).body(mapOf(
"status" to "not_ready",
"database" to "disconnected"
))
}
}
// Startup probe - "A aplicacao terminou de iniciar?"
// Util para aplicacoes JVM que demoram para iniciar
@GetMapping("/health/started")
fun startup(): ResponseEntity<Map<String, String>> {
return ResponseEntity.ok(mapOf("status" to "started"))
}
}
Graceful Shutdown
Kubernetes envia SIGTERM antes de matar um pod. Sua aplicacao precisa tratar isso:
// Configuracao de graceful shutdown
// application.yml:
// server:
// shutdown: graceful
// spring:
// lifecycle:
// timeout-per-shutdown-phase: 30s
@Component
class GracefulShutdownHandler(
private val processamentoService: ProcessamentoService
) {
@PreDestroy
fun onShutdown() {
println("Recebido sinal de shutdown, finalizando processamentos...")
runBlocking {
processamentoService.finalizarPendentes()
}
println("Shutdown completado com sucesso")
}
}
// Service que respeita shutdown
@Service
class ProcessamentoService {
private val processando = AtomicBoolean(true)
suspend fun finalizarPendentes() {
processando.set(false)
// Aguardar processamentos em andamento
delay(5000)
}
suspend fun processar(item: Item) {
if (!processando.get()) {
throw ShutdownInProgressException()
}
// Processamento normal
}
}
Configuracao do Kubernetes
Deployment
O Deployment define como sua aplicacao roda no Kubernetes. A configuracao inclui:
// Configuracao da aplicacao para ler do environment
@Configuration
@ConfigurationProperties(prefix = "app")
data class AppConfig(
var databaseUrl: String = "",
var databaseUsername: String = "",
var databasePassword: String = "",
var redisHost: String = "localhost",
var redisPort: Int = 6379,
var featureFlags: FeatureFlags = FeatureFlags()
) {
data class FeatureFlags(
var novaUI: Boolean = false,
var cacheHabilitado: Boolean = true
)
}
O manifesto Kubernetes correspondente define replicas, recursos (requests e limits de CPU e memoria), probes de saude, variaveis de ambiente via ConfigMap e Secrets, e estrategia de rolling update.
Configuracao de Recursos JVM
A JVM dentro de containers precisa de atencao especial:
// Configuracao JVM otimizada para Kubernetes
// Variavel de ambiente JAVA_OPTS no Deployment:
// -XX:+UseContainerSupport -> Reconhece limits do container
// -XX:MaxRAMPercentage=75.0 -> Usa ate 75% da RAM disponivel
// -XX:InitialRAMPercentage=50.0 -> Inicia com 50% da RAM
// -XX:+UseG1GC -> Garbage Collector eficiente
// -XX:+UseStringDeduplication -> Economia de memoria
// -Xss512k -> Stack size reduzido por thread
// Verificar configuracao em runtime
@PostConstruct
fun logJvmConfig() {
val runtime = Runtime.getRuntime()
logger.info("JVM Config - Max Memory: ${runtime.maxMemory() / 1024 / 1024}MB")
logger.info("JVM Config - Available Processors: ${runtime.availableProcessors()}")
logger.info("JVM Config - Total Memory: ${runtime.totalMemory() / 1024 / 1024}MB")
}
Service e Ingress
Para expor sua aplicacao, voce precisa de um Service e um Ingress. O Service abstrai o acesso aos pods, e o Ingress gerencia o roteamento HTTP externo com TLS.
Horizontal Pod Autoscaler (HPA)
Escale sua aplicacao automaticamente baseado em metricas:
// Exponha metricas customizadas para o HPA
@Component
class AutoscalingMetrics(
private val meterRegistry: MeterRegistry,
private val filaProcessamento: BlockingQueue<Tarefa>
) {
init {
// Metrica customizada para autoscaling
meterRegistry.gauge("app.fila.tamanho", filaProcessamento) {
it.size.toDouble()
}
meterRegistry.gauge("app.fila.utilizacao", filaProcessamento) {
it.size.toDouble() / 1000.0 // Capacidade maxima = 1000
}
}
}
O HPA pode escalar baseado em CPU, memoria ou metricas customizadas via Prometheus Adapter.
Ktor no Kubernetes
Se voce usa Ktor em vez de Spring Boot, a configuracao e mais leve:
// Ktor otimizado para Kubernetes
fun main() {
embeddedServer(
Netty,
port = System.getenv("PORT")?.toIntOrNull() ?: 8080,
host = "0.0.0.0"
) {
install(ContentNegotiation) {
json()
}
routing {
// Health checks
get("/health/live") {
call.respond(mapOf("status" to "alive"))
}
get("/health/ready") {
val healthy = checkDependencies()
if (healthy) {
call.respond(mapOf("status" to "ready"))
} else {
call.respond(
HttpStatusCode.ServiceUnavailable,
mapOf("status" to "not_ready")
)
}
}
// API routes
route("/api/v1") {
produtoRoutes()
pedidoRoutes()
}
}
}.start(wait = true)
}
// Shutdown hook para Ktor
Runtime.getRuntime().addShutdownHook(Thread {
println("Shutdown hook executado")
// Cleanup resources
})
Logging para Kubernetes
Em Kubernetes, logs devem ir para stdout/stderr em formato JSON:
// Configuracao de logging para Kubernetes
// Use JSON format para que ferramentas como Fluentd ou Loki
// possam processar os logs automaticamente
// logback-spring.xml para producao deve usar:
// - ConsoleAppender com LogstashEncoder
// - Nao usar file appenders (Kubernetes gerencia logs)
// - Incluir campos: timestamp, level, logger, message, stack_trace
// Exemplo de log que fica bem em Kubernetes
logger.info("Pedido processado",
StructuredArguments.kv("pedido_id", pedido.id),
StructuredArguments.kv("duracao_ms", duracao),
StructuredArguments.kv("status", "sucesso")
)
// Resultado em JSON:
// {"timestamp":"2025-08-24T10:00:00","level":"INFO",
// "message":"Pedido processado","pedido_id":"abc-123",
// "duracao_ms":42,"status":"sucesso"}
Secrets e ConfigMaps
Gerenciar configuracao sensivel em Kubernetes:
// Lendo secrets do Kubernetes como variaveis de ambiente
@Configuration
class SecretsConfig {
@Value("\${DB_PASSWORD}")
private lateinit var dbPassword: String
@Value("\${API_KEY}")
private lateinit var apiKey: String
// Ou usando ConfigurationProperties
}
Para segredos mais sensiveis, considere usar External Secrets Operator ou HashiCorp Vault com integracao Kubernetes.
Boas Praticas
- Imagens pequenas: Use JRE alpine, nao JDK completo
- Limits realistas: Defina CPU e memoria baseados em testes de carga
- Health checks: Sempre configure liveness, readiness e startup probes
- Graceful shutdown: Trate SIGTERM adequadamente
- 12-Factor App: Configuracao via environment variables
- Observabilidade: Logs JSON, metricas Prometheus, tracing OpenTelemetry
- Security context: Rode containers como non-root
Conclusao
Kotlin e Kubernetes formam uma combinacao poderosa para aplicacoes cloud-native. A JVM moderna e otimizada para containers, frameworks como Spring Boot e Ktor tem suporte nativo para Kubernetes, e as ferramentas de observabilidade da JVM sao as mais maduras do mercado.
Invista em entender bem o ecossistema Kubernetes e como configurar sua aplicacao Kotlin para aproveitar ao maximo a plataforma. O resultado sera aplicacoes resilientes, escalaveis e faceis de operar em producao.