Danilo Cutrim

Desenvolvimento Local Simplificado com Kubernetes e Tilt.dev

Desenvolvimento Local Simplificado com Kubernetes e Tilt.dev

Neste tutorial, vamos aprender como configurar e implantar uma aplicação Kotlin com Spring Boot em um cluster Kubernetes local, utilizando a ferramenta Tilt.

O código completo pode ser encontrado em: dev-with-tilt

1. Overview


Tilt é uma ferramenta projetada para simplificar e acelerar o desenvolvimento local de aplicações e microsserviços que utilizam contêineres ou Kubernetes. Com suporte a clusters K8s como MicroK8s, Kind, Minikube e também Docker Compose, o Tilt permite que desenvolvedores executem projetos localmente de forma eficiente.

A ferramenta automatiza a implantação de aplicações durante o desenvolvimento, oferecendo atualizações em tempo real aos arquivos do projeto através do recurso live_update, o que garante um ciclo de feedback contínuo e ágil. Além disso, o Tilt facilita o gerenciamento e compartilhamento de ambientes de desenvolvimento, automatizando tarefas repetitivas e integrando-se perfeitamente aos fluxos de trabalho existentes.

Recursos oferecidos pela ferramenta:

  • Automação de workflows:
    • Tilt automatiza a construção, implantação e monitoramento de aplicações em Kubernetes, incluindo a reconstrução automática de contêineres e o redeployment de serviços sempre que há mudanças no código.
  • Feedback rápido:
    • A ferramenta proporciona feedback quase em tempo real, com logs e eventos centralizados, facilitando a análise e resolução de problemas.
  • Configuração simplificada:
    • Utiliza um arquivo de configuração chamado Tiltfile, escrito em Starlark (baseado em Python), que descreve como o Tilt deve construir e implantar a aplicação.
  • Integração com ferramentas de desenvolvimento:
    • Integra-se com diversas ferramentas como Docker, Helm e Kubernetes, oferecendo um ambiente coeso e eficiente para desenvolvimento local.
  • Suporte a microsserviços:
    • Tilt é ideal para ambientes de microsserviços, permitindo que desenvolvedores trabalhem em múltiplos serviços simultaneamente e observem como eles interagem em tempo real.
  • UI interativa:
    • Possui uma interface web que mostra o estado atual dos serviços, logs e permite interações manuais como reiniciar serviços ou construir imagens.

2. Instalando e configurando o Tilt com MicroK8s

A instalação do Tilt é simples, porém precisamos nos atentar a alguns requisitos, como:

  • Ter um cluster Kubernetes configurado e em execução.
  • Docker instalado e em execução na máquina local.

Neste tutorial vamos utilizar o MicroK8s como cluster Kubernetes local, porém o Tilt suporta outros clusters, que estão listados em: Tilt - Supported Clusters

Nota: O Tilt também suporta Docker Compose, caso não tenha um cluster Kubernetes configurado.

Preparando o MicroK8s

Caso não tenha o MicroK8s instalado, siga o tutorial de instalação em:

Instalando MicroK8s

Com o MicroK8s instalado precisamos habilitar 2 addons:

  • DNS
    • Implanta o CoreDNS, que é responsável pela resolução de endereços dentro do nosso cluster Kubernetes
  • Registry
    • Habilita um local registry, que será utilizado pelo Tilt para armazenar as imagens Docker.

Para habilitar os addons, execute o comando:

1
2
sudo microk8s.enable dns && \
sudo microk8s.enable registry

Por fim habilitaremos o MicroK8s como nosso cluster padrão, com o comando:

1
2
3
4
sudo microk8s.kubectl config view --flatten > ~/.kube/microk8s-config && \
KUBECONFIG=~/.kube/microk8s-config:~/.kube/config kubectl config view --flatten > ~/.kube/temp-config && \
mv ~/.kube/temp-config ~/.kube/config && \
kubectl config use-context microk8s
Instalando o Tilt

A instalação do Tilt é feita com o comando:

1
curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash

3. Preparando nossa aplicação Kotlin com Spring Boot de exemplo


Para exemplificar o uso do Tilt, vamos criar um projeto em Kotlin com Spring Boot e automatizar o workflow de desenvolvimento.

Nosso exemplo será composto por 1 serviço:

  • sample-tilt
    • Aplicação gradle + Kotlin + Spring Boot que expõe uma API REST simples.

Para a execução da aplicação com Tilt, precisamos de 3 arquivos de configuração:

  1. Dockerfile
  2. Manifesto Kubernetes
  3. Tiltfile
Estrutura do projeto:

A estrutura do projeto será a seguinte:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
├── build.gradle
├── Dockerfile
├── gradle
├── gradle.properties
├── gradlew
├── gradlew.bat
├── kub-tilt.yaml
├── settings.gradle
├── src
│   ├── main
│   │   ├── kotlin
│   │   │   └── br
│   │   │       └── com
│   │   │           └── sampletilt
│   │   │               ├── SampleController.kt
│   │   │               └── SampleTiltApplication.kt
│   │   └── resources
│   │       ├── application.yml
│   │       └── logbback.xml
│   └── test
│       └── kotlin
│           └── br
│               └── com
│                   └── sampletilt
│                       └── SampleTilt2ApplicationTests.kt
└── Tiltfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package br.com.sampletilt

import mu.KotlinLogging
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class SampleController {

    val logger = KotlinLogging.logger { }

    @GetMapping("/hello")
    fun hello() = Sample("world").also {
        logger.info { "hello: received call to sample" }
    }
}

data class Sample(val response: String)
1
2
3
4
5
6
7
8
9
10
11
package br.com.sampletilt

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class SampleTiltApplication

fun main(args: Array<String>) {
    runApplication<SampleTiltApplication>(*args)
}

Dockerfile

1
2
3
4
5
6
7
8
FROM openjdk:11.0.11-slim

WORKDIR /app
ADD BOOT-INF/lib /app/lib
ADD META-INF /app/META-INF
ADD BOOT-INF/classes /app

ENTRYPOINT java -cp .:./lib/* br.com.sampletilt.SampleTiltApplicationKt

Manifesto Kubernetes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-tilt
  labels:
    app: sample-tilt
spec:
  selector:
    matchLabels:
      app: sample-tilt
  template:
    metadata:
      labels:
        app: sample-tilt
    spec:
      hostNetwork: false
      containers:
        - name: sample-tilt
          image: sample-tilt-image
          env:
            - name: ENV
              value: dev
            - name: JAVA_OPTS
              value: >-
                -Duser.timezone=America/Sao_Paulo -Dfile.encoding=UTF8 -Xms512m -Xmx512m
            - name: ENV_SBA_ACTIVE
              value: 'true'
O Tiltfile

O Tiltfile é o arquivo de configuração que descreve como o Tilt deve construir, implantar e orquestrar a aplicação em nosso cluster Kubernetes.

Para configurar nosso Tiltfile, vamos seguir algumas etapas

Etapa 1 - Pré Configuração

Esta etapa não é obrigatória, mas é útil para definir variáveis que serão utilizadas posteriormente.

  • application_name : Nome da aplicação, que será utilizado para identificar os recursos no Tilt.
  • port : A porta que nossa aplicação spring boot irá escutar e que será utilizada no foward.
  • application_class : A classe principal da aplicação.
  • project_name : Nome do projeto, que utilizaremos como label para identificar os recursos no Tilt.
  • java_opts : Parâmetros para a JVM.
  • entrypoint_var : Entrypoint para a execução do nosso container
1
2
3
4
5
6
7
# -*- mode: Python -*-
application_name = "sample-tilt"
port = 9082
application_class = 'br.com.sampletilt.SampleTiltApplicationKt'
project_name = 'til-dev-samples'
java_opts = '-Duser.timezone=America/Sao_Paulo -Dfile.encoding=UTF8 -Xms512m -Xmx1024m'
entrypoint_var = ['java', '-noverify', java_opts, '-cp', '.:./lib/*', application_class]
Etapa 2 - Configuração da etapa de compilação

Nesta etapa utilizaremos duas funções do Tilt:

  • load() : Função utilizada para carregar recursos externos e extensões do Tilt.
  • local_resource()
    • Configura um ou mais comandos shell que serão executados na máquina host (não no cluster k8s).
    • Por padrão, o Tilt executa uma atualização do local_resources sempre que um arquivo de dependência é alterado.
1
2
3
4
5
6
7
8
9
load('ext://restart_process', 'docker_build_with_restart')

local_resource(
    application_name + '-compile',
    "./gradlew" + ' bootJar && ' +
    'unzip -o build/libs/*.jar -d build/jar-staging && ' +
    'rsync --inplace --checksum -r build/jar-staging/ build/jar',
    deps=['src', 'build.gradle'],
    labels=project_name + "-compile")

Neste exemplo load('ext://restart_process', 'docker_build_with_restart') carrega a extensão restart_process, que contém a função docker_build_with_restart, responsável por reconstruir a imagem Docker e reiniciar o container, nos auxiliando no live update.

Por fim, nosso local_resource descompacta o jar para build/jar-stagingcom o comando:

1
unzip -o build/libs/*.jar -d build/jar-staging`

e em seguida utiliza o rsync --checksum para copiá-lo para build/jar com o comando:

1
rsync --inplace --checksum -r build/jar-staging/ build/jar

O live_update do Tilt copiará todos os arquivos que foram modificados, já que rsync --checksum copia o diretório, mas não altera nenhum arquivo que não tenha sido modificado.

Etapa 3 - Construção da imagem Docker com live_update

Nesta etapa, utilizaremos a função docker_build_with_restart que importamos anteriormente, para construir a imagem com o live_update.

1
2
3
4
5
6
7
8
9
10
11
12
docker_build_with_restart(
    application_name + '-image',
    './build/jar',
    entrypoint=entrypoint_var,
    dockerfile='./Dockerfile',
    live_update=[
        sync('./build/jar/BOOT-INF/lib', '/app/lib'),
        sync('./build/jar/META-INF', '/app/META-INF'),
        sync('./build/jar/BOOT-INF/classes', '/app'),
    ],
)

No docker_build_with_restart, o primeiro argumento é o nome da imagem, que deve ser o mesmo nome que utilizamos no nosso manifesto Kubernetes. O segundo é o contexto, que neste caso será o diretório onde descompactamos nosso jar. O parâmetro entrypoint é o comando que será executado quando o container for iniciado. O parâmetro dockerfile é o caminho para o Dockerfile que utilizaremos para construir a imagem, e o parâmetro live_update é uma lista de comandos sync que copiam as bibliotecas, .class e outros arquivos compilados do diretório build/jar para dentro do container.

Etapa 4 - Configurando recursos do Kubernetes

Por fim, configuramos o Kubernetes para implantar nossa aplicação.

1
2
3
k8s_yaml('./kub-tilt.yaml')
k8s_resource(application_name,
             resource_deps=[application_name + '-compile'], labels=project_name + "-pods", port_forwards=port)

k8s_yaml('./kub-tilt.yaml'), carrega o arquivo de manifesto Kubernetes que criamos anteriormente que esta em ./kub-tilt.yaml

k8s_resource(application_name, resource_deps=[application_name + '-compile'], labels=project_name + "-pods", port_forwards=port), cria um recurso k8s com o nome da aplicação, que depende do recurso de compilação, adicionamos um label para identificar os pods no Tilt e configuramos o port_forward para a nossa variável port.

Após todas as etapas configuradas, nosso Tiltfile deve ficar assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# -*- mode: Python -*-
application_name = "sample-tilt"
port = 9082
application_class = 'br.com.sampletilt.SampleTiltApplicationKt'
project_name = 'til-dev-samples'
java_opts = '-Duser.timezone=America/Sao_Paulo -Dfile.encoding=UTF8 -Xms512m -Xmx1024m'
entrypoint_var = ['java', '-noverify', java_opts, '-cp', '.:./lib/*', application_class]

# Extensão de live update para sincronizar arquivos
# Para mais exemplos de extensões, acesse: https://docs.tilt.dev/extensions.html
load('ext://restart_process', 'docker_build_with_restart')

local_resource(
    application_name + '-compile',
    "./gradlew" + ' bootJar && ' +
    'unzip -o build/libs/*.jar -d build/jar-staging && ' +
    'rsync --inplace --checksum -r build/jar-staging/ build/jar',
    deps=['src', 'build.gradle'],
    labels=project_name + "-compile")

docker_build_with_restart(
    application_name + '-image',
    './build/jar',
    entrypoint=entrypoint_var,
    dockerfile='./Dockerfile',
    live_update=[
        sync('./build/jar/BOOT-INF/lib', '/app/lib'),
        sync('./build/jar/META-INF', '/app/META-INF'),
        sync('./build/jar/BOOT-INF/classes', '/app'),
    ],
)

k8s_yaml('./kub-tilt.yaml')
k8s_resource(application_name,
             resource_deps=[application_name + '-compile'], labels=project_name + "-pods", port_forwards=port)
Executando o Tilt

Para executar o Tilt, basta executar o comando tilt up na raiz do projeto.

1
tilt up

Dica: Caso receba um erro parecido com: tls: failed to verify client certificate: x509: certificate signed by unknown authority, execute os comandos abaixo:

1
2
sudo microk8s refresh-certs -e ca.crt
microk8s config > ~/.kube/config

A saída do comando será algo parecido com:

1
2
3
4
5
6
7
Tilt started on http://localhost:10350/
v0.33.17, built 2024-06-12

(space) to open the browser
(s) to stream logs (--stream=true)
(t) to open legacy terminal mode (--legacy=true)
(ctrl-c) to exit

Pronto! Agora temos nossa aplicação em execução no Tilt, com live_update e pronto para ser desenvolvida.

Acesse o endereço http://localhost:10350/ no navegador para visualizar o dashboard do Tilt.

login

Para ter acesso ao overview do projeto, clique no botão Resource Name e selecione um dos recursos, automaticamente seremos redirecionados para a página de Overview, onde podemos visualizar os logs, status e interagir com o serviço.

login

Para interromper o Tilt, basta pressionar ctrl-c no terminal e em seguida executar o comando:

1
tilt down

Para mais detalhes sobre a UI do tilt, acesse a documentação oficial em: Tilt - UI

4. Leitura Adicional

Exemplos em outras linguagens
Escolhendo um local dev cluster

Documentação sobre clusters suportados pelo Tilt: Tilt - Choosing Clusters

Referências

comments powered by Disqus