Kuma Meshes Head-On - A beginners guide
Tags: kuma, kong, kumamesh, kongmesh
Published: 2024-04-12 11:10:00

---
title: Kuma Meshes Head-On - A beginners guide
published: true
description: 
series:  All about Kong
tags: kuma,kong,kumamesh,kongmesh
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hvyihh26udp0lyb1s8xd.jpg
# Use a ratio of 100:42 for best results.
published_at: 2024-04-12 11:10 +0000
---

To quickly start learning `Kuma`, on of the most important things we need is cluster. Then we also need a command to find out the status of our pods in kubernets (aka `k8s`), we also need to be able to install `Kuma` and finally we also need to be able to issue some `Kuma` commands.

This is a long way of saying that we need to install 4 essential commands in order to make everything ready for `Kuma`. These commands are:

* `kind` - This is also known as Kubernetes in Docker. This is a command that leverages the weight of creating stuff with only `kubectl`.
* `kubectl` - Probaly the most expect one of this list, if you are already used to working with `k8s`. This is how we can issue commands to our `k8s` cluster.
* `helm` - Helm allows us to execute some very handy scripts that allow amongs others, the installation of the `Kuma` control plane.
* `kumactl` - We will no be using this command very often in this guide, but it is important to be aware on how to use it.

This guide will let you know how to do this in `Ubuntu`. All of this has been tested in an `Ubuntu` system. If you are interested in a guide on how to install this in `Mac-OS` or `Windows` or any other operating system you may have please give me a shout out at my YouTube channel `JESPROTECH` [community](https://www.youtube.com%2F@www.youtube.com/@jesprotech/community).

---

## I. Installing the commands

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7o11yualkv5n3tltwmj2.png)

---

### Kind (`k8s`) in Docker

In order to install kind, we need to issue these commands:

```shell
[ $(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
```

It is important to note that the command `kind` will be installed in you `/usr/local/bin/kind`. This may vary per system, even withing Linux distributions.

---

### Installing certificates and GPG keys

Both `helm` and `kubectl` commands need to be installed with the presence of certain `GPG` keys. This is how we can add them to our local repository of our linux `apt` distribution:

```shell
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
```

---

### kubectl

The installation of `Kubectl` is very easy once the previous step is complete:

```shell
sudo apt-get install -y kubelet kubeadm kubectl
```

The commands `kubelet`, `kubeadm` and `kubectl` aren't mandatory, but it is a good idea to install them.

---

### helm

As you may have already guessed, `helm` is also now very easy to install:

```shell
sudo apt-get install -y helm
```

---

### kuma

`Kuma` installation can be a bit cumbersome because it involves one manual step, but first we need to download our dependencies:

```shell
cd ~ || exit;
curl -L https://kuma.io/installer.sh | VERSION=2.6.1 sh -
```

Be sure to be in your `HOME` folder before issuing this command. It is important to have `Kuma` installed in a place where it is easily accessible and easily spotted should we, for example, decide to remove it.

Once we are done with that, it is also very important to add the `bin` folder to our PATH:

```shell
export PATH=~/kuma-2.6.1/bin:$PATH;
```

Adding this line to the end or anywhere in between your star up script will make this process easy. Your startup script may be any of these `.bashrc`, `.zshrc`, `.profile` and possibly take another form.

---

### k9s

Installing `k9s` is also quite different from other applications. In this case we can either use `pacman` or `brew` for `Linux`. I have used `brew` mostly for `Mac-OS` and hardly ever needed it in Linux, but in this case it is very much needed and so to do that first we need to install brew like this:

```shell
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
```

Once the brew installation completes, all we have to do is to install `k9s`("`kanines`"):

```shell
brew install derailed/k9s/k9s
```

One thing that is important to take into account, and you'll probably notice this once you installed and start running `k9s` for the first time, is that `k9s` will crash if a cluster that it is monitoring gets removed and/or added.

---

## II. Creating the cluster

```shell
kind create cluster --name=wlsm-mesh-zone
kubectl cluster-info --context kind-wlsm-mesh-zone
```

The first command creates a cluster named `wlsm-mesh-zone`. This is just a cluster that we will use to install Kuma.
The second command is used to check the status of the cluster.

---

## III. Creating a local docker registry

As I mentioned before, we can create a docker registry quite easily. As easy it may sound to create it, the script to do this is a handful. So the best thing to do is to just copy past what kind already has available on their web-site. Here we can download this [script](https://kind.sigs.k8s.io/docs/user/local-registry/):

```shell
#!/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
```

This script can be found on the root [folder of the project](https://github.com/jesperancinha/wild-life-safety-monitor/blob/main/kind-with-registry.sh).
And to install the local docker registry, we only need to run this bash script.

---

## IV. How the code has been created

There may be a lot to be said about the code that I have provided for the example for this blog post. However, in this case, let's just focus on few key aspects. Let's start from the [listener service](https://github.com/jesperancinha/wild-life-safety-monitor/tree/main/wlsm-listener-service) to the [collector](https://github.com/jesperancinha/wild-life-safety-monitor/tree/main/wlsm-collector-service) and then to the [database](https://github.com/jesperancinha/wild-life-safety-monitor/tree/main/wlsm-database-service).
When we run the services locally, or even using a `docker-compose` configuration to get the containers going, usually we use the dns attributed names, which automatically get assigned to be the container name or the name that we configure with `hostname`. With `k8s` there is also a set of rules that make the host names available throughout the cluster. Let's have a look the listener and collector examples:

---

### Listener example

The listener is an application developed in `Java` using the `Spring framework`. Like all application created this way, there is also an `application.properties` file:

```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
```

In all of these properties, the most important one to focus on for the moment is the `wslm.url.collector` property. With the `default` configuration, we can run this service locally without the need to use any containerized environment. However, in the `k8s` cluster, we need to be able to access the `collector` and for that we have a `prod` profile with the definition file `application-prod.properties`:

```properties
wslm.url.collector=http://wlsm-collector-deployment.wlsm-namespace.svc.cluster.local:8081/api/v1/collector
```

This property tries to reach host `wlsm-collector-deployment.wlsm-namespace.svc.cluster.local`. This file follows this configuration:

`<Service Name>.<Namespace>.svc.cluster.local`

We've got 5 dot separated elements. The last three are static and the first two depend on the machine we are trying to reach. On the left we place the service name followed by the namespace. This is important to understand how the containers are connected to each other within the cluster.

The part of the code that is interesting to have a look at is of course the controller and the service. The controller looks like this:

```java
@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);
    }
}
```

And the service looks like this:

```java
@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);
    }
}
```

As you may have already noticed, this first application, like all of the applications implemented using the `Spring Framework` in this repository are all reactive and they all use `netty` instead of `tomcat`.
For the moment, we can ignore the `hazelcast` usage in this code. This will be used for later versions of this project.

---

### Collector example

The collector works in exactly in the same way as the listener at this point. Its only duty for now is to relay data from the listener to the database and to do that, the collector only needs to know exactly where the database is. Let's make the same analysis on the `application.properties file of this project`:

```properties
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
```
These properties are the minimum required to get the service going. However this is only to able to run it local. And for this service, we also have `prod` profile file and we can have a look at it in `application-prod.properties` over here:

```properties
spring.r2dbc.url=r2dbc:postgresql://wlsm-database-deployment.wlsm-namespace.svc.cluster.local:5432/wlsm
```
The database connection is in this case referring to the host of the database:

`wlsm-database-deployment.wlsm-namespace.svc.cluster.local`

Which again follows the same analysis as we have seen before. To the left we see the sevice name, followed by the namespace appending that at the end with `svc.cluster.local`.

And for this service we also use a controller and a service. The controller looks like this:

```kotlin
@RestController
@RequestMapping
class CollectorController(
    val collectorService: CollectorService
) {
    @PostMapping("animals")
    suspend fun listenAnimalLocation(@RequestBody animalLocationDto: AnimalLocationDto): AnimalLocationDto = run {
        collectorService.persist(animalLocationDto)
        animalLocationDto
    }
}
```

And the service looks like this:

```kotlin
@Service
class CollectorService(
    val applicationEventPublisher: ApplicationEventPublisher
) {
    fun persist(animalLocationDto: AnimalLocationDto) =
        applicationEventPublisher.publishEvent(AnimalLocationEvent(animalLocationDto))

}
```

The service uses and event publisher that is called `applicationEventPublisher`, that follows an event streaming architecture that gets handled later on in this event listener, which we can readily see that it uses `r2dbc` to keep in the reactive architecture implementations paradigms:

```kotlin
@Service
class EventHandlerService(
   val animalLocationDao: AnimalLocationDao
) {
    @EventListener
    fun processEvent(animalLocationEvent: AnimalLocationEvent){
        println(animalLocationEvent)
        runBlocking(Dispatchers.IO) {
            animalLocationDao.save(animalLocationEvent.animalLocationDto.toEntity())
        }
    }
}
```

---

## V. Deploy scripts

Deploying is normally a very straightforward task to do with `k8s`. However it is also important to have a look at the configuration needed for our services. For example let's have a look at the listener implementation:

```yaml
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
```
There are three blocks in this configuration. The first block is the namespace block. The namespace configuration is crucial to allow kuma to be able to inject the envoy sidecars that it needs to apply policies. Without a defined namespace, `kuma` will not be able to do this. The other thing that we need to pay attention when configuring kuma is that the namespace must containe the proper label that kuma will recognize:

`kuma.io/sidecar-injection: enabled`

The namespace definition with the correct label is vital to get kuma working. In the second block we find the definition of the deployment. This is how we define how the deployment of our pod is going to look like in our kubernetes cluster. What is important here to focus on is the `image`, the `imagePullPolicy` and the `containerPort`. The image is the complete tag of the Docker image we are using. The port that gets configure for our docker registry created with `kind` is 5001 and this is included in the tag for our image. It works as a tag but also as a connection to our Docker registry. That way we can pull the images and create our container to run in our kubernetes environment.

But, of course, to be able to use images, we need to create them and for that let's take a look at how is that done in the `listener` example and the `database` example. The docker image for the `listener` is defined like this:

```dockerfile
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"]
```

This is all starting from a base imaged called `eclipse-temurin:21-jdk-alpine`. After this we just copy the jar created by building the project and then making a copy of it into our image. Before of that we copy the `entrypoint.sh` to the container as well and define the `ENTRYPOINT` to use it. The `entrypoint` simply calls the jar like this:

```shell
#!/usr/bin/env sh
java -jar -Dspring.profiles.active=prod wlsm-listener-service.jar
```

The `database` service is quite different because it uses a few scripts that are opensource and available online:

```dockerfile
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
```

This script makes a copy of the following file and folder to the docker init directory: `create-multiple-postgresql-databases.sh` and `multiple`. Finally we simply define the variables used in those scripts to define our database and username/password combinations.

The database is created using the following schema:

```sql
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)
);
```
And as a data example, we will register one animal by the name of `piquinho`. Piquinho is simply the name of travelling albatross that is travelling around the world, which has a sensor attached to it and we are reding the data that the sensor is sending to use. There are two tables that define species. That is the species and the genus that define species. These are tables `families` and `genuses`. The table `species` defines the species that animal belongs to. Finally we define an `animal` in the table of the same name where the species and the name of the animal gets registered. The database looks like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wcxrgmiy5afcmas66xo7.jpg)

In order to build, create the images and start our project we can run the following commands which are available in the `Makefile`:

```shell
make
make create-and-push-images
make k8s-apply-deployment
```

The first make is just a `gradle build` command. The second command used the variable:

```yaml
MODULE_TAGS := aggregator \
			   collector \
			   listener \
			   management \
			   database
```

to run:

```shell
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
```

This simply goes through every module, and uses a standard generic command that changes per value given in the `MODULE_TAGS` to create the images and push them to the local registry on port 5001. Following the same strategy, we can then use the third command to deploy our pods. This third command uses a different loop that looks like this:

```shell
@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
```

In this case, it applies every deployment script to every single one of the services. If we run the command `kubectl get pods --all-namespaces`, we should be getting this output:

```shell
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
```

What we should observe here at this point is the presence of the `kuma-control-plane`, the `kube-controller-manager` and all the services running in our own custom `wlsm-namespace`. Our cluster is isolated from the outside, and in order to be able to access the different ports, we need to create `port-forwarding` for every pod we want to access. For that we can issue these commands in separate tabs:

We can also have a look at this by looking at `k9s`:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x4ujabq67j5lx0977meo.jpg)


```shell
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
```
## VI. Running the application

In order to run the application, we should open all the ports and when all of them are open we should see something like this in our screens

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ud2tqul15v2wipcqyok3.jpg)

We can connect to the database using `localhost` and port `5432`. The connection string is this one: `jdbc:postgresql://localhost:5432/wlsm`. And to access it we then use the username/password combination of `admin`/`admin`.

The first thing we need to do, before we perform any test is to know the id of `Piquinho` and we can do that by using Intellij database tools like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s9w6jii0ymq38xb1nftn.jpg)

In the root folder of the project, there is a file called `test-requests.http`. This is a scratch file to create REST requests against our open ports:

```text
###
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
}
```

In order to be able to use this file, we only need to replace the id in this example from `2ffc17b7-1956-4105-845f-b10a766789da` to `d5ad0824-71c0-4786-a04a-ac2b9a032da4`. In this case, we can make requests from the collector, or from the listener. Both requests should work and we should see afterward this kind of response per request:

```shell
{
  "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)
```

Because both ports are opened and they at this point share the same payload type, we can perform the same requests to the listener and to the collector. After making those two requests we should find results in the table `animal_locations`:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fo4q9e8uxrmwjo8jc4rw.jpg)

So this confirms only that the cluster is running correctly and now we are ready to test policies with our Kuma mesh.

## VII. MeshTrafficPermission - Part I

The `MeshTrafficPermission` is one of the features we can choose in Kuma, and it is probably the most used one.

But first let's take a moment to explore the kuma control plane. With all the porforwarding on, we can just go to [localhost:5681/gui](localhost:5681/gui) and visualize our kuma meshes. In the main page we should see something like this: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ziauosu8hus51dk5hkfn.jpg)

There is nothing much to see at the moment, but let's now apply the `MeshTrafficPermission`:

```shell
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 -
```

Once we apply this, we should be getting a response like this: `meshtrafficpermission.kuma.io/mtp created`.

## VIII. Mesh

Applying the mesh only doesn't change much when it comes to the setup of our cluster. What it does do is allow us to set up traffic routing policies. There are many things that we can choose from, but one of the most obvious thing we can choose from is `mTLS` otherwise referred to as mutual TLS, which in very short terms means that certificates are mutually accepted and validated in order to establish identity between parties and establish encrypted data traffic. This can be automatically done for us using this simple `Mesh` configuration:

```shell
echo "apiVersion: kuma.io/v1alpha1
kind: Mesh
metadata:
  name: default
spec:
  mtls:
    enabledBackend: ca-1
    backends:
    - name: ca-1
      type: builtin" | kubectl apply -f -
```

After applying this policy we may come across a warning like this one:

`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.`

For now, we can ignore this warning.

## IX MeshTrafficPermission - Part II

Now comes the fun part and the first thing we are going to do is to disable all traffic between all pods:

```shell
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 -
```

And after we get the confimation message `meshtrafficpermission.kuma.io/mtp configured`, if we try to make any request using any of the port-forwarding, we'll get:

```shell
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)
```

This means that all traffic between pods is being denied. What we now have is an internal system protected against possible bad actors within our organization, but we have also now blocked traffic between all pods. So `mTLS` is a great thing, but blocking all traffic not at all. The way to make this perfect is simply to make exceptions to that `DENY` all rule and to do that we need a policy that will allow traffic between, the listener and the collector and the collector and the database. Let's start with the traffic between the collector and the database:

```shell
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 -
```

In this case, what we are doing is allowing data traffic to flow from the `targetRef` collector to the `targetRef` database. If you don't know this, perhaps it is important to note how kuma interprets the `name`, which, just like the `hostname` creation, it is also used for functional purposes. The generic way to build these `name`s is like this:

`<service name>_<namespace>_svc_<service port>`

In this case, the separator is an underscore, and creating a name, this way, lets `Kuma` know exactly what is permitted. In this case if we apply this policy, we'll be able to send requests to the collector after getting this response: `meshtrafficpermission.kuma.io/wlsm-database created`. And when making them, the response should now be `200` confirming that the location record has been sent to the collector:

```shell
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)
```

However, we still didn't define exceptions to the traffic between the listener and the collector and so making a request that way will result in this:

```shell
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)
```

And this is of course expected. Let's now apply another policy for this data traffic:

```shell
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 -
```

Making it possible to now perform requests from the listener to the collector:

```shell
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)
```

## X - MeshFaultInjection

Finally and just to provide another feature as an example, we can also use another feature called `MeshFaultInjection`, which can be very useful when performing tests with `Kuma`. We can simulate potential problems within our mesh and check if the error handling is being done correctly for example. We can also check other things like how possible circuit breakers we may have configured may react to faulty connections or high rate requests.

So let's try it. One way to apply `MeshFaultInjection` is like this:

```shell
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 -
```

With this policy, we are saying that the traffic outbound from the listener and inboud to the collector will have a 50% chance of success. The request results are unpredictable and so after the applying this policy, we may expect errors or successful requests to the listener endpoint. 

```shell
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)
```

```shell
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)
```

Finally just out of interest, we can have a look at how our `animal_location` table looks like now:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dh6i80847pktpjdpamoc.jpg)


## XI - Conclusion

I hope you were able to follow this article so far and that you were able to have cluster running on your machine. Thanks anyway for reading this article and for giving up a bit of your time to understand an learn a bit more about Kuma. I personally se a great usage for this and a great future for `Kuma` as it makes it possible to configure and take a much more granular control of our network and our environment. It's enterprise version, `Kong-Mesh` seems quite complete. Kuma is opensource and its great for testing and also for enterprise it seems. I find the subject of meshes very interesting and I think `Kuma` provides a great way to learn about how meshes work and to get a feel on how can we better control the data flow withing our network.

If we want to see the status of our services, we can just go to our `Kuma` control plane in this `localhost` location: [http://localhost:5681/gui/meshes/default/services?page=1&size=50](http://localhost:5681/gui/meshes/default/services?page=1&size=50):

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2lq7zhhdmnqxpj6s1clf.jpg)

In Kuma control plane we can also have a look at the policies installed, checkout the status of our pods, monitor what is going on in the background and in generally just have an overview of what is happening in our mesh and how it is configured. I invite you to just go through the application and see if you can check the statues of the policies we have installed. The Kuma control plane a.k.a. the GUI, is made precisely to be easy to understand and follow up on our Mesh.

## XII - Resources

- [What is mutual TLS (mTLS)?](https://www.cloudflare.com/learning/access-management/what-is-mutual-tls/)
- [K9s](https://k9scli.io/)
- [North-South traffic](https://www.site24x7.com/it-glossary/north-south-and-east-west-traffic-security-.html)
- [East-West traffic](https://www.site24x7.com/it-glossary/north-south-and-east-west-traffic-security-.html)
- [Getting Started With Kuma Service Mesh](https://konghq.com/blog/engineering/getting-started-kuma-service-mesh)
- [Deploy Kuma on Kubernetes](https://kuma.io/docs/2.6.x/quickstart/kubernetes-demo/)
- [Local Registry with Kind](https://kind.sigs.k8s.io/docs/user/local-registry/)

I have also made a video about it on my [JESPROTECH](https://www.youtube.com/channel/UCzS_JK7QsZ7ZH-zTc5kBX_g) YouTube channel right over here:

{% embed https://www.youtube.com/watch?v=KE3VTYtLvnI %}