sábado, 24 de agosto de 2024

Kafka - Rodando no WSL 2 (Ubuntu) e usando (Producer e Consumer) a partir do Windows

        Lá estava eu estudando Kafka e me deparei com a seguinte situação: O Kafka instalado no Ubuntu através do WSL 2, rodando legal e eu criando produtores e consumidores a partir do Java usando o Intellij no Windows. Daí o que ocorre é: o Kafka sobe normal, mas o projeto Java não consegue encontrar o serviço, mesmo colocando no projeto o IP do WSL.

      Então descobri que para fazer o projeto Java se comunicar com o Kafka no WSL 2 era preciso configurar a propriedade advertised.listeners no arquivo server.properties no diretório config do Kafka: 


advertised.listeners=PLAINTEXT://ip_do_wsl:9092


        Dessa forma a aplicação mesmo em outra rede consegue encontrar o Kafka e criar e ler os tópicos, produzindo e consumindo suas menagens. A propriedade advertised.listeners no Apache Kafka desempenha um papel crucial na comunicação entre os clientes (produtores e consumidores) e os brokers do Kafka, especialmente em cenários de redes distribuídas ou ambientes com várias interfaces de rede.

        Ela especifica os endereços IP ou nomes de host e portas que os brokers Kafka devem anunciar para os clientes (produtores, consumidores, etc.). Esses são os endereços que os clientes usarão para se conectar ao broker após receberem a metadata inicial do Kafka.

        Uma questão é: o IP do WSL pode mudar toda vez que for iniciado. Ficar mudando isso tanto no arquivo de configuração do Kafka quanto na aplicação (ou aplicações como é o caso microsseviços) é chato. Então pra mudar na aplicação (no meu caso Java) muda muito de caso pra caso, mas a ideia inicial seria usar variável de ambiente ou arquivo de propriedades tirando do código a dependência. 

     Para o Kafka recorri ao GPT que gerou o script bash a seguir que troca o IP no arquivo config/server.properties:


#!/bin/bash

# Passo 1: Obter o IP do WSL 2

WSL_IP=$(hostname -I | awk '{print $1}')


# Passo 2: Caminho para o arquivo server.properties do Kafka

KAFKA_CONFIG_PATH="/caminho/para/seu/kafka/config/server.properties"


# Passo 3: Atualizar o advertised.listeners com o IP atual do WSL 2

# Verifique se o arquivo existe

if [ -f "$KAFKA_CONFIG_PATH" ]; then

    # Usar sed para substituir o advertised.listeners existente ou adicionar se não existir

    if grep -q "^advertised.listeners=" "$KAFKA_CONFIG_PATH"; then

        # Substitui a linha existente

        sed -i "s/^advertised.listeners=.*/advertised.listeners=PLAINTEXT:\/\/$WSL_IP:9092/" "$KAFKA_CONFIG_PATH"

    else

        # Adiciona a linha se não existir

        echo "advertised.listeners=PLAINTEXT://$WSL_IP:9092" >> "$KAFKA_CONFIG_PATH"

    fi

    echo "advertised.listeners atualizado com o IP: $WSL_IP"

else

    echo "Erro: Arquivo server.properties não encontrado em $KAFKA_CONFIG_PATH"

    exit 1

fi


# Passo 4: Iniciar ou reiniciar o Kafka para aplicar as mudanças

# Ajuste o comando abaixo para iniciar ou reiniciar seu Kafka

# Exemplo para iniciar Kafka:

# /caminho/para/seu/kafka/bin/kafka-server-start.sh -daemon $KAFKA_CONFIG_PATH

echo "Script concluído. Certifique-se de reiniciar o Kafka para aplicar as mudanças."


       Basta salvar o script como atualiza_kafka.sh na pasta do Kafka, dar permissão de execução com chmod +x atualiza_kafka.sh e executar antes de subir o Kafka.

        Ainda não executei o Kafka pelo Docker subindo também os produtores e consumidores em uma mesma rede com Docker Compose então não sei se muda muita coisa, mas como seria a mesma rede, acredito que essa configuração de advertised.listeners não seja necessária, de qualquer forma esse artigo pode ser útil pra mim mesmo no futuro e outros que estão aprendendo a ferramenta.







domingo, 4 de agosto de 2024

Metodologia Twelve-Factor

         A metodologia Twelve-Factor App é um conjunto de 12 regras para manter uma aplicação de forma eficaz, sendo elas:


  1. Codebase (Código-fonte único): Uma aplicação deve ser armazenada em um único repositório de código, com múltiplas implantações derivadas do mesmo código-base.

  2. Dependencies (Dependências explícitas): Todas as dependências da aplicação, incluindo bibliotecas e ferramentas de sistema, devem ser declaradas explicitamente e gerenciadas de forma isolada.

  3. Config (Configurações): As configurações da aplicação devem ser armazenadas em variáveis de ambiente, não no código-fonte, para permitir a configuração flexível em diferentes ambientes.

  4. Backing Services (Serviços de Back-end): Os serviços de back-end, como bancos de dados e filas, devem ser tratados como recursos externos, acessíveis por meio de interfaces padrão.

  5. Build, Release, Run (Construir, entregar, executar): O processo de build, entrega e execução da aplicação deve ser separado em etapas distintas, com cada etapa tendo suas próprias responsabilidades e garantindo a consistência entre ambientes.

  6. Processes (Processos): As aplicações devem ser executadas como processos independentes, leves e sem estado (Stateless), para facilitar a escalabilidade e a resiliência.

  7. Port Binding (Ligação de porta): As aplicações devem ser autocontidas e expor serviços por meio de portas, para que possam ser facilmente conectados a outras aplicações e serviços.

  8. Concurrency (Concorrência): As aplicações devem escalar horizontalmente, adicionando instâncias concorrentes para lidar com cargas de trabalho aumentadas.

  9. Disposability (Descartabilidade): As aplicações devem ser fáceis de iniciar e parar rapidamente, sem impacto para outras partes do sistema, para facilitar o deploy e a atualização contínua.

  10. Dev/Prod Parity (Paridade dev/prod): Os ambientes de desenvolvimento, testes e produção devem ser o mais semelhantes possível, para minimizar diferenças e evitar problemas de compatibilidade.

  11. Logs (Registros): As aplicações devem produzir logs estruturados e acessíveis por meio de interfaces padronizadas, para facilitar a depuração e o monitoramento.

  12. Admin Processes (Processos de administração): As tarefas administrativas, como migrações de banco de dados e limpeza de caches, devem ser executadas como processos únicos e rastreáveis.

Balanceamento com NGINX

         Além de expor conteúdo estático e proxy reverso, os servidores web também fazem balanceamento entre nós da aplicação. Nesse artigo vou mostrar o NGINX fazendo o balanceamento entre 2 nós.

         Para fazer o balanceamento, no arquivo nginx.conf que fica dentro de /etc/nginx/conf.d declaramos os servidores (com ip e porta) na seção upstream e depois usamos no proxy pass. Cada upstream deve ter um nome para poder ser referenciado no server, nesse caso o nome foi meu_upstream:


upstream meu_upstream{

        # ip_hash habilita sticky sessions com base no endereço IP do cliente, para garantir a seção        

        ip_hash;

server ip_server_01:8080;

server ip_server_02:8080;

}


server {

    listen 80;

    

    location / {

        proxy_pass http://meu_upstream;

        proxy_set_header Host $host;

        proxy_set_header X-Real-IP $remote_addr;

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_set_header X-Forwarded-Proto $scheme;

    }

}


        Pronto, dessa forma temos um balanceamento configurado.

Redis com Spring Boot

         O Redis é um banco de dados em memória para criar caches na aplicação, tornando o acesso a dado mais rápidos.

      Claro que não é simplesmente fazer cache de toda a aplicação, por isso é preciso entender como funciona o sistema. O cache é indicado para tabelas que tem bastante acesso e pouca alteração, diminuindo dessa forma o acesso desnecessário ao banco e ajudando na saúde da aplicação como um todo, pois desonerando o acesso ao banco com coisas que mudam pouco deixamos ele livre para responder a requisições mais importantes.

        Pra começarmos, precisamos adicionar a dependência no projeto:


<dependency>

<groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-cache</artifactId>

</dependency>


    Uma vez adicionada a dependência, é preciso adicionar o endereço e a porta no arquivo application.properties:


spring.data.redis.host=127.0.0.1

spring.data.redis.port=6379


        É preciso também habilitar o Cache na aplicação, assim como é habilitado as requisições assíncronas, com o uso de anotação na classe principal:


@EnableCaching


        Agora o Redis está configurado para ser usado, o que falta é usar o Cache no projeto. Os métodos que desejamos que tenham seu resultado guardado no cache (geralmente estão nas classes de negócio - também chamadas de services - ou no DAO). Basta anotá-los com @Cacheable e dar um nome para esses dados na tabela de cache:


@Cacheable(value = "nome_da_tb_de_cache")


        E para atualizar os dados, fazendo o sistema ir buscar no banco? Podemos anotar os métodos de inserção, update e exclusão com @CacheEvict passando o nome da tabela de cache e se queremos limpar todos os registros:


@CacheEvict(value = "nome_da_tb_de_cache", allEntries = true)


     Isso vai limpar os dados. Também podemos definir um tempo de vida (TTL) para o cache no application.properties, o que dá a ele um tempo padrão de vida (o default é ficar pra sempre).


# TTL em milissegundos (10 minutos)

spring.cache.redis.time-to-live=600000  


        Existem outras formas que podem ser pesquisadas caso haja necessidade, como o tamanho do cache.