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á


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 pullgradle buildjava -jarsystemctl restart. Uma implantação levava 30 minutos, com 2-3 erros humanos por mês.

(2) Solução de CI/CD Totalmente Automatizada

TEXT 📖 Somente leitura
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

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

YAML
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

BASH
# 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

YAML
# .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

100%
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

KOTLIN
// 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

KOTLIN
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

KOTLIN
// 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

KOTLIN
// ============================================
// 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:

TEXT 📖 Somente leitura
=== 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

DOCKERFILE
# 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

YAML
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

YAML
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

KOTLIN
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

KOTLIN
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

KOTLIN
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

KOTLIN
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 -Xms e -Xmx com o mesmo valor (evita overhead de redimensionamento do heap). Use G1GC (-XX:+UseG1GC). Em containers, adicione -XX:+UseContainerSupport.


📖 Resumo


📝 Exercícios

  1. Iniciante (⭐): Escreva um Dockerfile para o OrderProcessor (single-stage está fine), baseado em eclipse-temurin:17-jre. Dica: COPY build/libs/*.jar app.jar
  2. Intermediário (⭐⭐): Escreva um docker-compose.yml contendo OrderProcessor + PostgreSQL com verificações de saúde configuradas. Dica: depends_on com condition: service_healthy
  3. 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]

← Anterior | Próximo →

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%