Kotlin: Implantação de Projetos Kotlin Explicada
Última atualização: 2026-08-26
O código está escrito — agora a última milha. Charlie containeriza o OrderProcessor, orquestra o pipeline de CI/CD, configura monitoramento e alertas, tornando a implantação do git push até a produção totalmente automatizada.
1. O que Você Aprenderá
- Builds multi-stage do Docker:
gradle:jdk→ imagem JRE leve - Docker Compose: OrderProcessor + PostgreSQL + Redis
- CI/CD: GitHub Actions compila, testa, envia a imagem
- Monitoramento: painel de métricas Micrometer + Prometheus + Grafana
- Prática de Charlie: implantação com um clique + verificações de saúde + checklist de prontidão para produção
2. A História Real de um Arquiteto
(1) Dor: O Pipeline de Implantação Manual
A equipe de Charlie fazia implantações manuais: SSH no servidor → git pull → gradle build → java -jar → systemctl restart. Uma implantação levava 30 minutos, com 2-3 erros humanos por mês.
(2) Solução de CI/CD Totalmente Automatizada
Antes: git push → SSH → build → deploy (30 min, propenso a erros)
Depois: git push → GitHub Actions → Docker build → deploy (5 min, zero toque)
Containerização + CI/CD = a implantação passa de 30 minutos de esforço manual para um pipeline totalmente automatizado de 5 minutos.
3. Build Multi-Stage do Docker
(1) Dockerfile
# Estágio 1: Build
FROM gradle:8.5-jdk17 AS builder
WORKDIR /app
COPY build.gradle.kts settings.gradle.kts ./
COPY gradle ./gradle
COPY src ./src
RUN gradle bootJar --no-daemon -x test
# Estágio 2: Runtime (JRE leve)
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/build/libs/*.jar app.jar
# Usuário não-root para segurança
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["java", "-jar", "app.jar"]
(2) Comparação Multi-Stage vs Single-Stage
| Dimensão | Single-Stage | Multi-Stage |
|---|---|---|
| Tamanho da imagem | ~800MB (JDK + fonte) | ~150MB (apenas JRE) |
| Segurança | Código-fonte na imagem | Código-fonte não está na imagem de runtime |
| Cache de build | Sem camadas | Cache de camadas independente por estágio |
| Tempo de build | Rebuild completa toda vez | Reutilização de cache da camada de dependências |
4. Orquestração com Docker Compose
(1) docker-compose.yml
version: '3.8'
services:
order-processor:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
- SPRING_DATASOURCE_URL=r2dbc:postgresql://postgres:5432/orderdb
- SPRING_DATASOURCE_USERNAME=order_user
- SPRING_DATASOURCE_PASSWORD=order_pass
- SPRING_REDIS_HOST=redis
- MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE=health,info,prometheus
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- order-net
postgres:
image: postgres:16-alpine
environment:
- POSTGRES_DB=orderdb
- POSTGRES_USER=order_user
- POSTGRES_PASSWORD=order_pass
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U order_user -d orderdb"]
interval: 5s
timeout: 5s
retries: 5
networks:
- order-net
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
networks:
- order-net
prometheus:
image: prom/prometheus:latest
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
networks:
- order-net
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
depends_on:
- prometheus
networks:
- order-net
volumes:
pgdata:
networks:
order-net:
driver: bridge
(2) Implantação com Um Clique
# Iniciar todos os serviços
docker-compose up -d
# Verificar status
docker-compose ps
# Ver logs
docker-compose logs -f order-processor
# Parar todos
docker-compose down
5. Pipeline de CI/CD
(1) GitHub Actions
# .github/workflows/deploy.yml
name: Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Cache Gradle
uses: actions/cache@v3
with:
path: ~/.gradle/caches
key: gradle-${{ hashFiles('**/*.gradle.kts') }}
- name: Run tests
run: ./gradlew test
- name: Build JAR
run: ./gradlew bootJar
- name: Build Docker image
run: docker build -t order-processor:${{ github.sha }} .
- name: Push to registry
if: github.ref == 'refs/heads/main'
run: |
docker tag order-processor:${{ github.sha }} registry.example.com/order-processor:latest
docker push registry.example.com/order-processor:latest
- name: Deploy
if: github.ref == 'refs/heads/main'
run: |
ssh deploy@prod-server "docker pull registry.example.com/order-processor:latest && docker-compose up -d"
(2) Diagrama do Pipeline de CI/CD
flowchart TD
A[git push] --> B[GitHub Actions]
B --> C[Checkout Code]
C --> D[Setup JDK 17]
D --> E[Cache Gradle]
E --> F[Run Tests]
F --> G{Tests Pass?}
G -->|Yes| H[Build JAR]
G -->|No| I[Notify Team]
H --> J[Build Docker Image]
J --> K{Main Branch?}
K -->|Yes| L[Push to Registry]
K -->|No| M[Stop]
L --> N[Deploy to Production]
N --> O[Health Check]
O --> P{Healthy?}
P -->|Yes| Q[Live]
P -->|No| R[Rollback]
6. Monitoramento
(1) Configuração do Spring Boot Actuator
// application.yml
// management:
// endpoints:
// web:
// exposure:
// include: health,info,prometheus,metrics
// metrics:
// export:
// prometheus:
// enabled: true
// endpoint:
// health:
// show-details: always
(2) Métricas Personalizadas com Micrometer
import io.micrometer.core.instrument.Counter
import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.Timer
class OrderMetrics(registry: MeterRegistry) {
private val ordersCreated = Counter.builder("orders.created.total")
.description("Total de pedidos criados")
.register(registry)
private val orderProcessingTime = Timer.builder("orders.processing.time")
.description("Tempo de processamento do pedido")
.register(registry)
fun recordOrderCreated() { ordersCreated.increment() }
fun <T> recordProcessingTime(block: () -> T): T {
return orderProcessingTime.recordCallable { block() } ?: block()
}
}
(3) Métricas de Monitoramento
| Métrica | Tipo | Limite de Alerta |
|---|---|---|
orders_created_total |
Counter | — |
orders_processing_time |
Timer | P99 > 2s |
jvm_memory_used_bytes |
Gauge | > 80% |
db_connection_pool_active |
Gauge | > 90% |
http_server_requests_seconds |
Timer | P99 > 5s |
disk_free_bytes / disk_total_bytes |
Gauge | Livre < 10% |
7. Verificações de Saúde e Prontidão para Produção
(1) Endpoint de Verificação de Saúde
// Endpoint de saúde do Spring Boot Actuator
// GET /actuator/health
// {
// "status": "UP",
// "components": {
// "db": { "status": "UP" },
// "redis": { "status": "UP" },
// "diskSpace": { "status": "UP" }
// }
// }
(2) Checklist de Prontidão para Produção
| Categoria | Item de Verificação | Status |
|---|---|---|
| Segurança | Usuário não-root em execução | ☐ |
| Segurança | Sem segredos codificados | ☐ |
| Segurança | HTTPS configurado | ☐ |
| Confiabilidade | Endpoint de verificação de saúde | ☐ |
| Confiabilidade | Desligamento gracoso (SIGTERM) | ☐ |
| Confiabilidade | Pool de conexões com banco configurado | ☐ |
| Observabilidade | Saída de logs estruturados | ☐ |
| Observabilidade | Métricas Prometheus expostas | ☐ |
| Observabilidade | Regras de alerta configuradas | ☐ |
| Desempenho | Tamanho do heap JVM configurado | ☐ |
| Desempenho | Estratégia de GC selecionada | ☐ |
| Implantação | Imagem Docker < 200MB | ☐ |
| Implantação | Pipeline de CI/CD | ☐ |
| Implantação | Estratégia de rollback | ☐ |
8. Exemplo Completo: Demo de Implantação com Um Clique
// ============================================
// OrderProcessor - Simulação de Implantação
// Funcionalidade: Demo de Docker + CI/CD + Verificação de saúde
// ============================================
import kotlin.system.measureTimeMillis
data class DeployResult(val service: String, val status: String, val time: Long)
class DeploySimulator {
private val services = mutableListOf<DeployResult>()
private var deployed = false
fun build(): DeploySimulator {
print(" Building Docker image...")
val time = measureTimeMillis { Thread.sleep(800) }
println(" Done (${time}ms)")
return this
}
fun test(): DeploySimulator {
print(" Running tests...")
val time = measureTimeMillis { Thread.sleep(300) }
println(" Passed (${time}ms)")
return this
}
fun push(): DeploySimulator {
print(" Pushing to registry...")
val time = measureTimeMillis { Thread.sleep(500) }
println(" Done (${time}ms)")
return this
}
fun deploy(service: String, port: Int): DeploySimulator {
print(" Deploying $service on port $port...")
val time = measureTimeMillis { Thread.sleep(400) }
services.add(DeployResult(service, "RUNNING", time))
println(" Running (${time}ms)")
return this
}
fun healthCheck(): DeploySimulator {
print(" Health check...")
val time = measureTimeMillis { Thread.sleep(200) }
val allHealthy = services.all { it.status == "RUNNING" }
println(if (allHealthy) " ALL HEALTHY" else " UNHEALTHY DETECTED")
return this
}
fun summary() {
println("\n=== Deployment Summary ===")
services.forEach { s ->
println(" ${s.service}: ${s.status} (${s.time}ms)")
}
println("\n Total services: ${services.size}")
println(" Health: ${if (services.all { it.status == "RUNNING" }) "ALL GREEN" else "ISSUES DETECTED"}")
deployed = true
}
fun isDeployed() = deployed
}
fun main() {
println("=== OrderProcessor CI/CD Pipeline ===\n")
println("[1/6] Build Stage:")
DeploySimulator()
.build()
.test()
println("\n[2/6] Push Stage:")
DeploySimulator().push()
println("\n[3/6] Deploy Stage:")
val deployer = DeploySimulator()
.deploy("postgres", 5432)
.deploy("redis", 6379)
.deploy("order-processor", 8080)
.deploy("prometheus", 9090)
.deploy("grafana", 3000)
println("\n[4/6] Health Check:")
deployer.healthCheck()
println("\n[5/6] Smoke Test:")
println(" GET /actuator/health -> 200 OK")
println(" GET /api/v1/orders -> 200 OK")
println("\n[6/6] Production Ready Checklist:")
val checks = listOf(
"Non-root user" to true,
"No hardcoded secrets" to true,
"Health endpoint exposed" to true,
"Prometheus metrics enabled" to true,
"Graceful shutdown configured" to true,
"Docker image < 200MB" to true,
"CI/CD pipeline active" to true,
"Rollback strategy defined" to true
)
checks.forEach { (item, passed) ->
println(" ${if (passed) "✅" else "❌"} $item")
}
val passCount = checks.count { it.second }
println("\n Result: $passCount/${checks.size} checks passed")
if (passCount == checks.size) {
println("\n 🚀 OrderProcessor is LIVE!")
}
}
Saída:
=== OrderProcessor CI/CD Pipeline ===
[1/6] Build Stage:
Building Docker image... Done (804ms)
Running tests... Passed (301ms)
[2/6] Push Stage:
Pushing to registry... Done (502ms)
[3/6] Deploy Stage:
Deploying postgres on port 5432... Running (401ms)
Deploying redis on port 6379... Running (401ms)
Deploying order-processor on port 8080... Running (401ms)
Deploying prometheus on port 9090... Running (401ms)
Deploying grafana on port 3000... Running (401ms)
[4/6] Health Check:
Health check... ALL HEALTHY
[5/6] Smoke Test:
GET /actuator/health -> 200 OK
GET /api/v1/orders -> 200 OK
[6/6] Production Ready Checklist:
✅ Non-root user
✅ No hardcoded secrets
✅ Health endpoint exposed
✅ Prometheus metrics enabled
✅ Graceful shutdown configured
✅ Docker image < 200MB
✅ CI/CD pipeline active
✅ Rollback strategy defined
Result: 8/8 checks passed
🚀 OrderProcessor is LIVE!
9. Exemplos práticos rápidos
▶ Exemplo: Dockerfile multi-stage
# Etapa 1: build
FROM eclipse-temurin:17-jdk-jammy AS builder
WORKDIR /build
COPY . .
RUN ./gradlew bootJar --no-daemon
# Etapa 2: runtime
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY --from=builder /build/build/libs/*.jar app.jar
EXPOSE 8080
ENV JAVA_OPTS="-Xms256m -Xmx512m"
ENTRYPOINT ["sh", "-c", "exec java $JAVA_OPTS -jar /app/app.jar"]
▶ Exemplo: docker-compose para desenvolvimento
version: '3.9'
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: orders
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
app:
build: .
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: dev
DB_URL: jdbc:postgresql://postgres:5432/orders
REDIS_URL: redis://redis:6379
depends_on:
- postgres
- redis
volumes:
postgres_data:
▶ Exemplo: Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: order-app
template:
metadata:
labels:
app: order-app
spec:
containers:
- name: app
image: myregistry/order-app:1.0.0
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: DB_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
▶ Exemplo: Health checks em Spring Boot Actuator
import org.springframework.boot.actuate.health.*
import org.springframework.stereotype.Component
@Component
class PaymentServiceHealth(private val paymentGateway: PaymentGatewayClient) : HealthIndicator {
override fun health(): Health {
return try {
val response = paymentGateway.ping()
if (response.isHealthy) {
Health.up()
.withDetail("gateway", "responsive")
.withDetail("latency", "${response.latencyMs}ms")
.build()
} else {
Health.down()
.withDetail("gateway", "unhealthy")
.withDetail("reason", response.errorMessage)
.build()
}
} catch (e: Exception) {
Health.down(e).build()
}
}
}
// Acesso: GET /actuator/health
// {"status":"UP","components":{...}}
▶ Exemplo: Logging estruturado
import org.slf4j.LoggerFactory
class OrderService(private val repo: OrderRepository) {
private val log = LoggerFactory.getLogger(OrderService::class.java)
suspend fun confirm(orderId: String): Result<Order> {
log.info("Iniciando confirmação orderId={}", orderId)
val order = repo.findById(orderId) ?: run {
log.warn("Pedido não encontrado orderId={}", orderId)
return Result.failure(NoSuchElementException())
}
log.debug("Pedido encontrado status={}", order.status)
val confirmed = order.copy(status = OrderStatus.Confirmed(System.currentTimeMillis()))
val saved = repo.save(confirmed)
log.info("Pedido confirmado orderId={} durationMs={}", orderId, confirmed.status.toString())
return Result.success(saved)
}
}
// Saída (JSON):
// {"timestamp":"2026-07-28T10:00:00Z","level":"INFO","logger":"OrderService","message":"Pedido confirmado","orderId":"ORD-001"}
▶ Exemplo: Feature flags e rollout gradual
import java.util.concurrent.atomic.AtomicBoolean
class FeatureFlags {
private val newCheckoutEnabled = AtomicBoolean(false)
private val rolloutPercent = AtomicInteger(0)
fun isEnabled(userId: String): Boolean {
// Rollout baseado em percentual
val bucket = userId.hashCode() % 100
return rolloutPercent.get() > 0 && bucket < rolloutPercent.get()
}
fun enableForAll() {
rolloutPercent.set(100)
}
fun disable() {
rolloutPercent.set(0)
}
fun setRolloutPercent(percent: Int) {
require(percent in 0..100)
rolloutPercent.set(percent)
}
}
// Uso em service
class OrderService(private val flags: FeatureFlags, private val newCheckout: NewCheckoutFlow) {
suspend fun processOrder(order: Order): Receipt {
return if (flags.isEnabled(order.customerId)) {
log.info("Using new checkout flow")
newCheckout.process(order)
} else {
log.info("Using legacy checkout flow")
legacyProcess(order)
}
}
}
▶ Exemplo: Monitoramento com Micrometer/Prometheus
import io.micrometer.core.instrument.*
import io.micrometer.prometheus.PrometheusConfig
import io.micrometer.prometheus.PrometheusMeterRegistry
class OrderMetrics {
private val registry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT)
private val orderCounter = registry.counter("orders_processed_total", "status", "success")
private val orderLatency = registry.timer("order_processing_duration_seconds")
fun recordSuccess(durationMs: Long) {
orderCounter.increment()
orderLatency.record(java.time.Duration.ofMillis(durationMs))
}
fun renderMetrics(): String = registry.scrape()
class RecordException(val status: String) : Exception(status)
}
// Endpoint: GET /actuator/prometheus
// Saída:
// # HELP orders_processed_total Total de pedidos processados
// # TYPE orders_processed_total counter
// orders_processed_total{status="success"} 1523.0
// order_processing_duration_seconds_sum 45.234
// order_processing_duration_seconds_count 1523.0
❓ Perguntas Frequentes
P: Quais são os benefícios dos builds multi-stage do Docker? R: A imagem final contém apenas dependências de runtime (JRE) — sem código-fonte ou ferramentas de build. A imagem reduz de 800MB para 150MB, com superfície de ataque menor e inicialização mais rápida.
P: Como obter implantação sem tempo de inatividade? R: Implantação blue-green ou atualizações graduais (rolling updates). Blue-green mantém dois ambientes e alterna entre eles. Rolling updates substituem instâncias uma a uma. Kubernetes suporta rolling updates nativamente.
P: Como reverter uma implantação com falha? R: Marque cada build de imagem Docker com o SHA do Git, então reverta simplesmente com
docker run registry.example.com/order-processor:<sha-anterior>. Rollback automatizado no CI/CD é ainda melhor.
P: Como gerenciar segredos? R: Nunca armazene segredos em variáveis de ambiente ou arquivos de configuração. Use Vault, AWS Secrets Manager ou Kubernetes Secrets. O CI/CD os injeta a partir do gerenciador de segredos.
P: Como monitorar uma aplicação JVM? R: Spring Boot Actuator + Micrometer + Prometheus + Grafana é a combinação padrão. Métricas específicas da JVM: memória heap, contagem/tempo de GC, contagem de threads.
P: Como configurar parâmetros da JVM para produção? R: Defina
-Xmse-Xmxcom o mesmo valor (evita overhead de redimensionamento do heap). Use G1GC (-XX:+UseG1GC). Em containers, adicione-XX:+UseContainerSupport.
📖 Resumo
- Builds multi-stage do Docker: estágio de build com JDK, estágio de runtime com JRE — a imagem reduz de 800MB para 150MB
- Orquestração com Docker Compose em um clique: aplicação + banco de dados + cache + monitoramento
- Pipeline de CI/CD: git push → teste → build → push → deploy, totalmente automatizado em 5 minutos
- Micrometer + Prometheus + Grafana: a trindade de coleta, armazenamento e visualização de métricas
- Checklist de prontidão para produção: segurança, confiabilidade, observabilidade, desempenho, implantação — 8 verificações essenciais
- Verificações de saúde são a linha de base da produção: endpoint
/actuator/health+ Docker HEALTHCHECK
📝 Exercícios
- Iniciante (⭐): Escreva um Dockerfile para o OrderProcessor (single-stage está fine), baseado em
eclipse-temurin:17-jre. Dica:COPY build/libs/*.jar app.jar - Intermediário (⭐⭐): Escreva um docker-compose.yml contendo OrderProcessor + PostgreSQL com verificações de saúde configuradas. Dica:
depends_oncomcondition: service_healthy - Avançado (⭐⭐⭐): Escreva um pipeline completo de CI/CD com GitHub Actions incluindo etapas de build, teste, push do Docker e deploy. Dica:
on: push: branches: [main]