Para começar a aprender Kuma
rapidamente, uma das coisas mais importantes que precisamos é cluster. Então, também precisamos de um comando para descobrir o status de nossos pods no Kubernetes (também conhecido como k8s
), também precisamos ser capazes de instalar Kuma
e, finalmente, também precisamos ser capazes de emitir alguns comandos Kuma
.
Isso é um longo caminho para dizer que precisamos instalar 4 comandos essenciais para deixar tudo pronto para Kuma
. Esses comandos são:
kind
- Também é conhecido como Kubernetes no Docker. Este é um comando que aproveita o peso da criação de coisas apenas com kubectl
.
kubectl
– Provavelmente o mais esperado desta lista, se você já está acostumado a trabalhar com k8s
. É assim que podemos emitir comandos para nosso cluster k8s
.
helm
- Helm permite-nos executar alguns scripts muito úteis que permitem, entre outros, a instalação do plano de controle Kuma
.
kumactl
– Não usaremos esse comando com muita frequência neste guia, mas é importante saber como usá-lo.
Este guia permitirá que você saiba como fazer isso no Ubuntu
. Tudo isso foi testado em um sistema Ubuntu
. Se você estiver interessado em um guia sobre como instalar isso no Mac-OS
ou Windows
ou qualquer outro sistema operacional que você possa ter, por favor, avise-me no meu canal do YouTube, comunidade JESPROTECH
.
k8s
) no DockerPara instalar o kind, precisamos emitir estes comandos:
[ $(uname -m) = x86_64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.22.0/kind-linux-amd64 chmod +x ./kind sudo mv ./kind /usr/local/bin/kind
É importante observar que o comando kind
será instalado em você /usr/local/bin/kind
. Isso pode variar de acordo com o sistema, mesmo nas distribuições Linux.
Os comandos helm
e kubectl
precisam ser instalados com a presença de certas chaves GPG
. É assim que podemos adicioná-los ao nosso repositório local da nossa distribuição Linux apt
:
sudo apt-get install -y apt-transport-https ca-certificates curl gpg curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list sudo apt-get update
A instalação do Kubectl
é muito fácil assim que a etapa anterior for concluída:
sudo apt-get install -y kubelet kubeadm kubectl
Os comandos kubelet
, kubeadm
e kubectl
não são obrigatórios, mas é uma boa ideia instalá-los.
Como você já deve ter adivinhado, helm
agora também é muito fácil de instalar:
sudo apt-get install -y helm
A instalação Kuma
pode ser um pouco complicada porque envolve uma etapa manual, mas primeiro precisamos baixar nossas dependências:
cd ~ || exit; curl -L https://kuma.io/installer.sh | VERSION=2.6.1 sh -
Certifique-se de estar na sua pasta HOME
antes de emitir este comando. É importante ter Kuma
instalado em um local de fácil acesso e localização caso decidamos, por exemplo, removê-lo.
Assim que terminarmos isso, também é muito importante adicionar a pasta bin
ao nosso PATH:
export PATH=~/kuma-2.6.1/bin:$PATH;
Adicionar esta linha ao final ou em qualquer lugar entre o script inicial tornará esse processo mais fácil. Seu script de inicialização pode ser qualquer um destes .bashrc
, .zshrc
, .profile
e possivelmente assumir outro formato.
A instalação k9s
também é bastante diferente de outros aplicativos. Neste caso, podemos usar pacman
ou brew
para Linux
. Eu usei brew
principalmente para Mac-OS
e quase nunca precisei dele no Linux, mas, neste caso, é muito necessário e, para fazer isso primeiro, precisamos instalar o brew assim:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Assim que a instalação do brew for concluída, tudo o que precisamos fazer é instalar k9s
(“ kanines
”):
brew install derailed/k9s/k9s
Uma coisa que é importante levar em consideração, e você provavelmente notará isso quando instalar e começar a executar k9s
pela primeira vez, é que k9s
travará se um cluster que está monitorando for removido e/ou adicionado.
kind create cluster --name=wlsm-mesh-zone kubectl cluster-info --context kind-wlsm-mesh-zone
O primeiro comando cria um cluster denominado wlsm-mesh-zone
. Este é apenas um cluster que usaremos para instalar o Kuma. O segundo comando é usado para verificar o status do cluster.
Como mencionei antes, podemos criar um registro docker com bastante facilidade. Por mais fácil que pareça criá-lo, o script para fazer isso é difícil. Então, o melhor a fazer é apenas copiar e colar o tipo que já está disponível no site deles. Aqui, podemos baixar este script :
#!/bin/sh # Original Source # https://creativecommons.org/licenses/by/4.0/ # https://kind.sigs.k8s.io/docs/user/local-registry/ set -o errexit # 1. Create registry container unless it already exists reg_name='kind-registry' reg_port='5001' if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then docker run \ -d --restart=always -p "127.0.0.1:${reg_port}:5000" --network bridge --name "${reg_name}" \ registry:2 fi # 2. Create kind cluster with containerd registry config dir enabled # TODO: kind will eventually enable this by default and this patch will # be unnecessary. # # See: # https://github.com/kubernetes-sigs/kind/issues/2875 # https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration # See: https://github.com/containerd/containerd/blob/main/docs/hosts.md cat <<EOF | kind create cluster --config=- kind: Cluster apiVersion: kind.x-k8s.io/v1alpha4 containerdConfigPatches: - |- [plugins."io.containerd.grpc.v1.cri".registry] config_path = "/etc/containerd/certs.d" EOF # 3. Add the registry config to the nodes # # This is necessary because localhost resolves to loopback addresses that are # network-namespace local. # In other words: localhost in the container is not localhost on the host. # # We want a consistent name that works from both ends, so we tell containerd to # alias localhost:${reg_port} to the registry container when pulling images REGISTRY_DIR="/etc/containerd/certs.d/localhost:${reg_port}" for node in $(kind get nodes); do docker exec "${node}" mkdir -p "${REGISTRY_DIR}" cat <<EOF | docker exec -i "${node}" cp /dev/stdin "${REGISTRY_DIR}/hosts.toml" [host."http://${reg_name}:5000"] EOF done # 4. Connect the registry to the cluster network if not already connected # This allows kind to bootstrap the network but ensures they're on the same network if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${reg_name}")" = 'null' ]; then docker network connect "kind" "${reg_name}" fi # 5. Document the local registry # https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/generic/1755-communicating-a-local-registry cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ConfigMap metadata: name: local-registry-hosting namespace: kube-public data: localRegistryHosting.v1: | host: "localhost:${reg_port}" help: "https://kind.sigs.k8s.io/docs/user/local-registry/" EOF
Este script pode ser encontrado napasta raiz do projeto . E para instalar o registro docker local, só precisamos executar este script bash.
Pode haver muito a ser dito sobre o código que forneci como exemplo para esta postagem do blog. No entanto, neste caso, vamos nos concentrar apenas em alguns aspectos principais. Vamos começar doserviço listener para o coletor e depois para obanco de dados . Quando executamos os serviços localmente ou até mesmo usamos uma configuração docker-compose
para ativar os contêineres, geralmente usamos os nomes atribuídos ao DNS que são automaticamente atribuídos como o nome do contêiner ou o nome que configuramos com hostname
.
Com k8s
, também existe um conjunto de regras que disponibilizam os nomes de host em todo o cluster. Vamos dar uma olhada nos exemplos de ouvinte e coletor:
O listener é uma aplicação desenvolvida em Java
utilizando o Spring framework
. Como todos os aplicativos criados desta forma, também existe um arquivo application.properties
:
spring.application.name=wlsm-listener-service server.port=8080 spring.main.web-application-type=reactive spring.webflux.base-path=/app/v1/listener wslm.url.collector=http://localhost:8081/api/v1/collector
Em todas essas propriedades, a mais importante a ser focada no momento é a propriedade wslm.url.collector
. Com a configuração default
, podemos executar este serviço localmente sem a necessidade de utilizar qualquer ambiente conteinerizado. Porém, no cluster k8s
, precisamos conseguir acessar o collector
e, para isso, temos um perfil prod
com o arquivo de definição application-prod.properties
:
wslm.url.collector=http://wlsm-collector-deployment.wlsm-namespace.svc.cluster.local:8081/api/v1/collector
Esta propriedade tenta alcançar o host wlsm-collector-deployment.wlsm-namespace.svc.cluster.local
. Este arquivo segue esta configuração:
<Service Name>.<Namespace>.svc.cluster.local
Temos 5 elementos separados por pontos. Os três últimos são estáticos e os dois primeiros dependem da máquina que estamos tentando alcançar. À esquerda, colocamos o nome do serviço seguido do namespace. Isso é importante para entender como os contêineres estão conectados entre si no cluster.
A parte do código que é interessante dar uma olhada é, obviamente, o controlador e o serviço. O controlador fica assim:
@RestController @RequestMapping public class ListenerController { private final ListenerService listenerService; ListenerController(ListenerService listenerService) { this.listenerService = listenerService; } @GetMapping("info") public String info() { return "Listener Service V1"; } @PostMapping("create") public Mono<AnimalLocationDto> sendAnimalLocation( @RequestBody AnimalLocationDto animalLocationDto) { return listenerService.persist(animalLocationDto); } }
E o serviço fica assim:
@Service public class ListenerService { @Value("${wslm.url.collector:http://localhost:8080}") private String collectorUrl; private final WebClient client = WebClient.create(collectorUrl); HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(); List<AnimalLocationDto> cache = hazelcastInstance.getList("data"); public Mono<AnimalLocationDto> persist(AnimalLocationDto animalLocationDto) { cache.add(animalLocationDto); return client.post() .uri(collectorUrl.concat("/animals")) .contentType(MediaType.APPLICATION_JSON) .bodyValue(animalLocationDto) .retrieve() .bodyToMono(AnimalLocationDto.class); } }
Como você já deve ter notado, este primeiro aplicativo, como todos os aplicativos implementados usando o Spring Framework
neste repositório, é reativo e todos usam netty
em vez de tomcat
. Por enquanto, podemos ignorar o uso hazelcast
neste código . Isso será usado para versões posteriores deste projeto.
O coletor funciona exatamente da mesma maneira que o ouvinte neste ponto. Sua única função por enquanto é retransmitir os dados do ouvinte para o banco de dados e para isso, o coletor só precisa saber exatamente onde está o banco de dados. Vamos fazer a mesma análise no application.properties file of this project
:
spring.application.name=wlsm-collector-service server.port=8081 spring.main.web-application-type=reactive spring.webflux.base-path=/api/v1/collector spring.r2dbc.url=r2dbc:postgresql://localhost:5432/wlsm spring.r2dbc.username=admin spring.r2dbc.password=admin spring.data.r2dbc.repositories.naming-strategy=org.springframework.data.relational.core.mapping.BasicRelationalPersistentEntityNamingStrategy spring.data.r2dbc.repositories.naming-strategy.table=org.springframework.data.relational.core.mapping.SnakeCaseNamingStrategy spring.data.r2dbc.repositories.naming-strategy.column=org.springframework.data.relational.core.mapping.SnakeCaseNamingStrategy
Essas propriedades são o mínimo necessário para iniciar o serviço. No entanto, isso serve apenas para poder executá-lo localmente. E para este serviço, também temos o arquivo de perfil prod
, e podemos dar uma olhada nele em application-prod.properties
aqui:
spring.r2dbc.url=r2dbc:postgresql://wlsm-database-deployment.wlsm-namespace.svc.cluster.local:5432/wlsm
A conexão com o banco de dados, neste caso, refere-se ao host do banco de dados:
wlsm-database-deployment.wlsm-namespace.svc.cluster.local
O que novamente segue a mesma análise que vimos antes. À esquerda, vemos o nome do serviço, seguido pelo namespace acrescentando-o no final com svc.cluster.local
.
E para esse serviço também utilizamos um controlador e um serviço. O controlador fica assim:
@RestController @RequestMapping class CollectorController( val collectorService: CollectorService ) { @PostMapping("animals") suspend fun listenAnimalLocation(@RequestBody animalLocationDto: AnimalLocationDto): AnimalLocationDto = run { collectorService.persist(animalLocationDto) animalLocationDto } }
E o serviço fica assim:
@Service class CollectorService( val applicationEventPublisher: ApplicationEventPublisher ) { fun persist(animalLocationDto: AnimalLocationDto) = applicationEventPublisher.publishEvent(AnimalLocationEvent(animalLocationDto)) }
O serviço usa um editor de eventos chamado applicationEventPublisher
, que segue uma arquitetura de streaming de eventos que é tratada posteriormente neste ouvinte de eventos, que podemos ver prontamente que ele usa r2dbc
para manter os paradigmas de implementações de arquitetura reativa:
@Service class EventHandlerService( val animalLocationDao: AnimalLocationDao ) { @EventListener fun processEvent(animalLocationEvent: AnimalLocationEvent){ println(animalLocationEvent) runBlocking(Dispatchers.IO) { animalLocationDao.save(animalLocationEvent.animalLocationDto.toEntity()) } } }
A implantação normalmente é uma tarefa muito simples de fazer com k8s
. Porém, também é importante dar uma olhada na configuração necessária para nossos serviços. Por exemplo, vamos dar uma olhada na implementação do ouvinte:
apiVersion: v1 kind: Namespace metadata: name: wlsm-namespace labels: kuma.io/sidecar-injection: enabled --- apiVersion: apps/v1 kind: Deployment metadata: name: wlsm-listener namespace: wlsm-namespace spec: replicas: 1 selector: matchLabels: app: wlsm-listener template: metadata: labels: app: wlsm-listener spec: containers: - name: wlsm-listener-service image: localhost:5001/wlsm-listener-service:latest imagePullPolicy: Always ports: - containerPort: 8080 --- apiVersion: v1 kind: Service metadata: name: wlsm-listener-deployment namespace: wlsm-namespace spec: selector: app: wlsm-listener ports: - protocol: TCP appProtocol: http port: 8080
Existem três blocos nesta configuração. O primeiro bloco é o bloco de namespace. A configuração do namespace é crucial para permitir que Kuma seja capaz de injetar os sidecars enviados necessários para aplicar políticas. Sem um namespace definido, kuma
não será capaz de fazer isso. A outra coisa que precisamos prestar atenção ao configurar o kuma é que o namespace deve conter o rótulo adequado que o kuma reconhecerá:
kuma.io/sidecar-injection: enabled.
A definição do namespace com o rótulo correto é vital para que o Kuma funcione. No segundo bloco, encontramos a definição da implantação. É assim que definimos como será a implantação do nosso pod em nosso cluster Kubernetes. O que é importante focar aqui é a image
, a imagePullPolicy
e a containerPort
. A imagem é a tag completa da imagem Docker que estamos usando.
A porta configurada para nosso registro docker criado com kind
é 5001 e está incluída na tag de nossa imagem. Funciona como uma tag, mas também como uma conexão ao nosso registro Docker. Dessa forma, podemos extrair as imagens e criar nosso contêiner para rodar em nosso ambiente Kubernetes.
Mas, claro, para podermos utilizar imagens, precisamos criá-las, e para isso, vamos dar uma olhada em como isso é feito no exemplo listener
e no exemplo database
. A imagem docker para o listener
é definida assim:
FROM eclipse-temurin:21-jdk-alpine WORKDIR /root ENV LANG=C.UTF-8 COPY entrypoint.sh /root COPY build/libs/wlsm-listener-service.jar /root/wlsm-listener-service.jar ENTRYPOINT ["/root/entrypoint.sh"]
Tudo isso começa a partir de uma imagem base chamada eclipse-temurin:21-jdk-alpine
. Depois disso, basta copiar o jar criado na construção do projeto e depois fazer uma cópia dele em nossa imagem. Antes disso, copiamos também o entrypoint.sh
para o container e definimos o ENTRYPOINT
para utilizá-lo. O entrypoint
simplesmente chama o jar assim:
#!/usr/bin/env sh java -jar -Dspring.profiles.active=prod wlsm-listener-service.jar
O serviço database
é bem diferente porque usa alguns scripts de código aberto e disponíveis online:
FROM postgres:15 COPY . /docker-entrypoint-initdb.d COPY ./multiple /docker-entrypoint-initdb.d/multiple ENV POSTGRES_USER=admin ENV POSTGRES_PASSWORD=admin ENV POSTGRES_MULTIPLE_DATABASES=wlsm EXPOSE 5432
Este script faz uma cópia do seguinte arquivo e pasta para o diretório init do docker: create-multiple-postgresql-databases.sh
e multiple
. Finalmente, simplesmente definimos as variáveis usadas nesses scripts para definir nosso banco de dados e combinações de nome de usuário/senha.
O banco de dados é criado usando o seguinte esquema:
CREATE TABLE families( id uuid DEFAULT gen_random_uuid(), name VARCHAR(100), PRIMARY KEY(id) ); CREATE TABLE genuses( id uuid DEFAULT gen_random_uuid(), name VARCHAR(100), PRIMARY KEY(id) ); CREATE TABLE species( id uuid DEFAULT gen_random_uuid(), common_name VARCHAR(100), family uuid, genus uuid, PRIMARY KEY(id), CONSTRAINT fk_species FOREIGN KEY(family) REFERENCES families(id), CONSTRAINT fk_genus FOREIGN KEY(genus) REFERENCES genuses(id) ); CREATE TABLE animal ( id uuid DEFAULT gen_random_uuid(), name VARCHAR(100), species_id uuid, PRIMARY KEY(id), CONSTRAINT fk_species FOREIGN KEY(species_id) REFERENCES species(id) ); CREATE TABLE animal_location ( id uuid DEFAULT gen_random_uuid(), animal_id uuid, latitude BIGINT, longitude BIGINT, PRIMARY KEY(id), CONSTRAINT fk_animal FOREIGN KEY(animal_id) REFERENCES animal(id) );
E, como exemplo de dados, registraremos um animal com o nome de piquinho
. Piquinho é simplesmente o nome de um albatroz viajante que está viajando pelo mundo, que tem um sensor acoplado, e estamos lendo os dados que o sensor está nos enviando. Existem duas tabelas que definem as espécies. Essa é a espécie e o gênero que definem as espécies. Estas são tabelas families
e genuses
.
A tabela species
define a espécie à qual o animal pertence. Por fim, definimos um animal
na tabela de mesmo nome onde são registrados a espécie e o nome do animal. O banco de dados fica assim:
Para construir, criar as imagens e iniciar nosso projeto, podemos executar os seguintes comandos que estão disponíveis no Makefile
:
make make create-and-push-images make k8s-apply-deployment
O primeiro make é apenas um comando gradle build
. O segundo comando usou a variável:
MODULE_TAGS := aggregator \ collector \ listener \ management \ database
para correr:
docker images "*/*wlsm*" --format '{{.Repository}}' | xargs -I {} docker rmi {} @for tag in $(MODULE_TAGS); do \ export CURRENT=$(shell pwd); \ echo "Building Image $$image..."; \ cd "wlsm-"$$tag"-service"; \ docker build . --tag localhost:5001/"wlsm-"$$tag"-service"; \ docker push localhost:5001/"wlsm-"$$tag"-service"; \ cd $$CURRENT; \ done
Isso simplesmente passa por cada módulo e usa um comando genérico padrão que muda de acordo com o valor fornecido em MODULE_TAGS
para criar as imagens e enviá-las para o registro local na porta 5001. Seguindo a mesma estratégia, podemos então usar o terceiro comando para implantar nosso vagens. Este terceiro comando usa um loop diferente parecido com este:
@for tag in $(MODULE_TAGS); do \ export CURRENT=$(shell pwd); \ echo "Applying File $$tag..."; \ cd "wlsm-"$$tag"-service"; \ kubectl apply -f $$tag-deployment.yaml --force; \ cd $$CURRENT; \ done
Nesse caso, ele aplica todos os scripts de implantação a cada um dos serviços. Se executarmos o comando kubectl get pods --all-namespaces
, devemos obter esta saída:
NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-76f75df574-dmt5m 1/1 Running 0 5m21s kube-system coredns-76f75df574-jtrfr 1/1 Running 0 5m21s kube-system etcd-kind-control-plane 1/1 Running 0 5m38s kube-system kindnet-7frts 1/1 Running 0 5m21s kube-system kube-apiserver-kind-control-plane 1/1 Running 0 5m36s kube-system kube-controller-manager-kind-control-plane 1/1 Running 0 5m36s kube-system kube-proxy-njzvl 1/1 Running 0 5m21s kube-system kube-scheduler-kind-control-plane 1/1 Running 0 5m36s kuma-system kuma-control-plane-5f47fdb4c6-7sqmp 1/1 Running 0 17s local-path-storage local-path-provisioner-7577fdbbfb-5qnxr 1/1 Running 0 5m21s wlsm-namespace wlsm-aggregator-64fc4599b-hg9qw 1/1 Running 0 4m23s wlsm-namespace wlsm-collector-5d44b54dbc-swf84 1/1 Running 0 4m23s wlsm-namespace wlsm-database-666d794c87-pslzp 1/1 Running 0 4m22s wlsm-namespace wlsm-listener-7bfbcf799-f44f5 1/1 Running 0 4m23s wlsm-namespace wlsm-management-748cf7b48f-8cjh9 1/1 Running 0 4m23s
O que devemos observar aqui neste ponto é a presença do kuma-control-plane
, do kube-controller-manager
e de todos os serviços em execução em nosso próprio wlsm-namespace
customizado. Nosso cluster é isolado do exterior e para poder acessar as diferentes portas, precisamos criar port-forwarding
para cada pod que queremos acessar. Para isso, podemos emitir estes comandos em abas separadas:
Também podemos dar uma olhada nisso olhando para k9s
:
kubectl port-forward svc/wlsm-collector-deployment -n wlsm-namespace 8081:8081 kubectl port-forward svc/wlsm-listener-deployment -n wlsm-namespace 8080:8080 kubectl port-forward svc/wlsm-database-deployment -n wlsm-namespace 5432:5432 kubectl port-forward svc/kuma-control-plane -n kuma-system 5681:5681
Para rodar a aplicação devemos abrir todas as portas, e quando todas elas estiverem abertas, devemos ver algo assim em nossas telas:
Podemos nos conectar ao banco de dados usando localhost
e porta 5432
. A string de conexão é esta: jdbc:postgresql://localhost:5432/wlsm
. E para acessá-lo usamos a combinação nome de usuário/senha admin
/ admin
.
A primeira coisa que precisamos fazer antes de realizar qualquer teste é saber o id do Piquinho
, e podemos fazer isso usando ferramentas de banco de dados Intellij como esta:
Na pasta raiz do projeto, existe um arquivo chamado test-requests.http
. Este é um arquivo temporário para criar solicitações REST em nossas portas abertas:
### GET http://localhost:8080/app/v1/listener/info ### POST http://localhost:8080/app/v1/listener/create Content-Type: application/json { "animalId": "2ffc17b7-1956-4105-845f-b10a766789da", "latitude": 52505252, "longitude": 2869152 } ### POST http://localhost:8081/api/v1/collector/animals Content-Type: application/json { "animalId": "2ffc17b7-1956-4105-845f-b10a766789da", "latitude": 52505252, "longitude": 2869152 }
Para poder utilizar este arquivo, precisamos apenas substituir o ID, neste exemplo, de 2ffc17b7-1956-4105-845f-b10a766789da
para d5ad0824-71c0-4786-a04a-ac2b9a032da4
. Neste caso, podemos fazer solicitações do coletor ou do ouvinte. Ambas as solicitações devem funcionar e devemos ver posteriormente este tipo de resposta por solicitação:
{ "animalId": "d5ad0824-71c0-4786-a04a-ac2b9a032da4", "latitude": 52505252, "longitude": 2869152 } Response file saved. > 2024-04-12T001024.200.json Response code: 200 (OK); Time: 7460ms (7 s 460 ms); Content length: 91 bytes (91 B)
Como ambas as portas estão abertas e, neste ponto, compartilham o mesmo tipo de carga útil, podemos realizar as mesmas solicitações ao ouvinte e ao coletor. Depois de fazer essas duas solicitações, devemos encontrar os resultados na tabela animal_locations
:
Portanto, isso confirma apenas que o cluster está funcionando corretamente e agora estamos prontos para testar políticas com nossa malha Kuma.
O MeshTrafficPermission
é um dos recursos que podemos escolher no Kuma, e é provavelmente o mais utilizado.
Mas primeiro, vamos explorar o plano de controle Kuma. Com todo o encaminhamento ativado, podemos simplesmente acessar localhost:5681/gui e visualizar nossas malhas Kuma. Na página principal, devemos ver algo assim:
Não há muito o que ver no momento, mas vamos agora aplicar o MeshTrafficPermission
:
echo "apiVersion: kuma.io/v1alpha1 kind: MeshTrafficPermission metadata: namespace: kuma-system name: mtp spec: targetRef: kind: Mesh from: - targetRef: kind: Mesh default: action: Allow" | kubectl apply -f -
Depois de aplicarmos isso, devemos receber uma resposta como esta: meshtrafficpermission.kuma.io/mtp created
.
A aplicação da malha não muda muito no que diz respeito à configuração do nosso cluster. O que ele faz é nos permitir configurar políticas de roteamento de tráfego.
Há muitas coisas que podemos escolher, mas uma das coisas mais óbvias que podemos escolher é mTLS.
Também denominado TLS mútuo, o que, em termos muito curtos, significa que os certificados são mutuamente aceitos e validados para estabelecer a identidade entre as partes e estabelecer o tráfego de dados criptografados.
Isso pode ser feito automaticamente para nós usando esta configuração simples Mesh
:
echo "apiVersion: kuma.io/v1alpha1 kind: Mesh metadata: name: default spec: mtls: enabledBackend: ca-1 backends: - name: ca-1 type: builtin" | kubectl apply -f -
Após aplicar esta política poderemos nos deparar com um aviso como este:
Warning: resource meshes/default is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
Por enquanto, podemos ignorar este aviso.
Agora vem a parte divertida, e a primeira coisa que faremos é desabilitar todo o tráfego entre todos os pods:
echo " apiVersion: kuma.io/v1alpha1 kind: MeshTrafficPermission metadata: namespace: wlsm-namespace name: mtp spec: targetRef: kind: Mesh from: - targetRef: kind: Mesh default: action: Deny" | kubectl apply -f -
E depois de recebermos a mensagem de confirmação meshtrafficpermission.kuma.io/mtp configured
, se tentarmos fazer qualquer solicitação usando qualquer encaminhamento de porta, obteremos:
HTTP/1.1 500 Internal Server Error Content-Type: application/json Content-Length: 133 { "timestamp": "2024-04-12T07:09:26.718+00:00", "path": "/create", "status": 500, "error": "Internal Server Error", "requestId": "720749ce-56" } Response file saved. > 2024-04-12T090926.500.json Response code: 500 (Internal Server Error); Time: 10ms (10 ms); Content length: 133 bytes (133 B)
Isso significa que todo o tráfego entre os pods está sendo negado. O que temos agora é um sistema interno protegido contra possíveis malfeitores dentro de nossa organização, mas também bloqueamos o tráfego entre todos os pods. Portanto, mTLS
é ótimo, mas bloquear todo o tráfego não é nada.
A maneira de tornar isso perfeito é simplesmente abrir exceções à regra DENY
all e, para fazer isso, precisamos de uma política que permita o tráfego entre o ouvinte e o coletor e entre o coletor e o banco de dados. Vamos começar com o tráfego entre o coletor e o banco de dados:
echo " apiVersion: kuma.io/v1alpha1 kind: MeshTrafficPermission metadata: namespace: kuma-system name: wlsm-database spec: targetRef: kind: MeshService name: wlsm-database-deployment_wlsm-namespace_svc_5432 from: - targetRef: kind: MeshService name: wlsm-collector-deployment_wlsm-namespace_svc_8081 default: action: Allow" | kubectl apply -f -
Neste caso, o que estamos fazendo é permitir que o tráfego de dados flua do coletor targetRef
para o banco de dados targetRef
. Se você não sabe disso, talvez seja importante observar como Kuma interpreta o name
, que, assim como a criação hostname
, também é usado para fins funcionais.
A maneira genérica de construir esses name
é assim:
<service name>_<namespace>_svc_<service port>
Neste caso, o separador é um sublinhado, e criar um nome desta forma permite que Kuma
saiba exatamente o que é permitido. Neste caso, se aplicarmos esta política, poderemos enviar solicitações ao coletor após obter esta resposta: meshtrafficpermission.kuma.io/wlsm-database created
.
E ao realizá-los, a resposta agora deverá ser 200
confirmando que o registro de localização foi enviado ao coletor:
POST http://localhost:8081/api/v1/collector/animals HTTP/1.1 200 OK Content-Type: application/json Content-Length: 91 { "animalId": "a3a1bc1c-f284-4876-a84f-f75184b6998f", "latitude": 52505252, "longitude": 2869152 } Response file saved. > 2024-04-12T091754.200.json Response code: 200 (OK); Time: 1732ms (1 s 732 ms); Content length: 91 bytes (91 B)
Porém, ainda não definimos exceções para o tráfego entre o listener e o coletor, então fazer uma requisição dessa forma resultará no seguinte:
HTTP/1.1 500 Internal Server Error Content-Type: application/json Content-Length: 133 { "timestamp": "2024-04-12T07:18:54.149+00:00", "path": "/create", "status": 500, "error": "Internal Server Error", "requestId": "e8973d33-62" } Response file saved. > 2024-04-12T091854-1.500.json Response code: 500 (Internal Server Error); Time: 10ms (10 ms); Content length: 133 bytes (133 B)
E isso é obviamente esperado. Vamos agora aplicar outra política para esse tráfego de dados:
echo " apiVersion: kuma.io/v1alpha1 kind: MeshTrafficPermission metadata: namespace: kuma-system name: wlsm-collector spec: targetRef: kind: MeshService name: wlsm-collector-deployment_wlsm-namespace_svc_8081 from: - targetRef: kind: MeshService name: wlsm-listener-deployment_wlsm-namespace_svc_8080 default: action: Allow" | kubectl apply -f -
Possibilitando agora realizar requisições do listener para o coletor:
POST http://localhost:8080/app/v1/listener/create HTTP/1.1 200 OK Content-Type: application/json Content-Length: 91 { "animalId": "a3a1bc1c-f284-4876-a84f-f75184b6998f", "latitude": 52505252, "longitude": 2869152 } Response file saved. > 2024-04-12T092039-2.200.json Response code: 200 (OK); Time: 14ms (14 ms); Content length: 91 bytes (91 B)
Por fim e apenas para fornecer outro recurso como exemplo, também podemos usar outro recurso chamado MeshFaultInjection
, que pode ser muito útil ao realizar testes com Kuma
. Podemos simular possíveis problemas dentro da nossa malha e verificar se o tratamento de erros está sendo feito corretamente, por exemplo.
Também podemos verificar outras coisas, como como os disjuntores que configuramos podem reagir a conexões defeituosas ou solicitações de alta taxa.
Então, vamos tentar. Uma maneira de aplicar MeshFaultInjection
é assim:
echo " apiVersion: kuma.io/v1alpha1 kind: MeshFaultInjection metadata: name: default namespace: kuma-system labels: kuma.io/mesh: default spec: targetRef: kind: MeshService name: wlsm-collector-deployment_wlsm-namespace_svc_8081 from: - targetRef: kind: MeshService name: wlsm-listener-deployment_wlsm-namespace_svc_8080 default: http: - abort: httpStatus: 500 percentage: 50" | kubectl apply -f -
Com esta política, estamos dizendo que o tráfego de saída do ouvinte e de entrada para o coletor terá 50% de chance de sucesso. Os resultados da solicitação são imprevisíveis, portanto, após aplicar esta política, podemos esperar erros ou solicitações bem-sucedidas ao endpoint do ouvinte.
POST http://localhost:8080/app/v1/listener/create HTTP/1.1 500 Internal Server Error Content-Type: application/json Content-Length: 133 { "timestamp": "2024-04-12T07:28:00.008+00:00", "path": "/create", "status": 500, "error": "Internal Server Error", "requestId": "2206f29e-78" } Response file saved. > 2024-04-12T092800.500.json Response code: 500 (Internal Server Error); Time: 8ms (8 ms); Content length: 133 bytes (133 B)
POST http://localhost:8080/app/v1/listener/create HTTP/1.1 200 OK Content-Type: application/json Content-Length: 91 { "animalId": "a3a1bc1c-f284-4876-a84f-f75184b6998f", "latitude": 52505252, "longitude": 2869152 } Response file saved. > 2024-04-12T092819.200.json Response code: 200 (OK); Time: 13ms (13 ms); Content length: 91 bytes (91 B)
Finalmente, só por curiosidade, podemos dar uma olhada em como nossa tabela animal_location
está agora:
Espero que você tenha conseguido acompanhar este artigo até aqui e que tenha conseguido ter um cluster rodando em sua máquina. De qualquer forma, obrigado por ler este artigo e por dedicar um pouco do seu tempo para entender e aprender um pouco mais sobre Kuma. Pessoalmente, vejo um grande uso disso e um grande futuro para Kuma
pois possibilita configurar e ter um controle muito mais granular de nossa rede e de nosso ambiente.
Sua versão empresarial, Kong-Mesh
, parece bastante completa. Kuma é de código aberto. e parece ótimo para testes e também para empresas. Acho o assunto malhas muito interessante e acho que Kuma
oferece uma ótima maneira de aprender como as malhas funcionam e de ter uma ideia de como podemos controlar melhor o fluxo de dados em nossa rede.
Se quisermos ver o status de nossos serviços, podemos simplesmente ir ao nosso plano de controle Kuma
neste localhost
local: http://localhost:5681/gui/meshes/default/services?page=1&size=50 :
No plano de controle Kuma, também podemos dar uma olhada nas políticas instaladas, verificar o status de nossos pods, monitorar o que está acontecendo em segundo plano e, geralmente, apenas ter uma visão geral do que está acontecendo em nossa malha e como isso acontece. está configurado. Convido você a simplesmente acessar o aplicativo e ver se consegue verificar o status das políticas que instalamos. O plano de controle Kuma, também conhecido como GUI, é feito justamente para ser fácil de entender e acompanhar em nosso Mesh.
Também fiz um vídeo sobre isso no meu canal JESPROTECH no YouTube bem aqui: