Cada experimento segue os passos abaixo:
- Iniciar os pods do ycsb-benchmark, depois os pods do cluster Casssandra
- Carregar o workload Cassandra
- Executar as repetições
- Parar os pods Cassandra e remover os volumes de storage
Resumindos os passos fazendo apenas 1 repetição:
$ ssh pi@rpicloud
pi@rpicloud:~ $ kubectl delete -f cassandra/cassandra-statefulset.yaml
pi@rpicloud:~ $ kubectl delete persistentvolumeclaim -l app=cassandra
pi@rpicloud:~ $ kubectl delete pod ycsb-benchmark
pi@rpicloud:~ $ kubectl create -f ycsb/benchmark.yml
pi@rpicloud:~ $ kubectl create -f cassandra/cassandra-statefulset.yaml
pi@rpicloud:~ $ kubectl exec -it cassandra-0 -- nodetool status
pi@rpicloud:~/ycsb $ kubectl exec -i --tty ycsb-benchmark -- bash
root@ycsb-benchmark:/# /scripts/cassandra_load.sh cassandra-0.cassandra.default.svc.cluster.local a 100000 13 3
root@ycsb-benchmark:/# /scripts/cassandra_run.sh cassandra-0.cassandra.default.svc.cluster.local a 1000000 13 1 3
root@ycsb-benchmark:/# exit
pi@rpicloud:~ $
Os experimentos devem ter pelo menos 30 repetições em cada configuração. Por exemplo, rodar um experimentos com tamanho 1000000
, workload a
, 13
nós e 3
replicas:
root@ycsb-benchmark:/# /scripts/cassandra_run.sh cassandra-0.cassandra.default.svc.cluster.local a 1000000 13 1 3
root@ycsb-benchmark:/# /scripts/cassandra_run.sh cassandra-0.cassandra.default.svc.cluster.local a 1000000 13 2 3
root@ycsb-benchmark:/# /scripts/cassandra_run.sh cassandra-0.cassandra.default.svc.cluster.local a 1000000 13 3 3
root@ycsb-benchmark:/# /scripts/cassandra_run.sh cassandra-0.cassandra.default.svc.cluster.local a 1000000 13 4 3
root@ycsb-benchmark:/# /scripts/cassandra_run.sh cassandra-0.cassandra.default.svc.cluster.local a 1000000 13 5 3
........
root@ycsb-benchmark:/# /scripts/cassandra_run.sh cassandra-0.cassandra.default.svc.cluster.local a 1000000 13 30 3
Os resultados ficam disponíveis no diretório de saida:
pi@rpicloud:~ $ cat ycsb/out/cassandra-13-1000000-rf3-a-1.txt
pi@rpicloud:~ $ cat ycsb/out/cassandra-13-1000000-rf3-a-2.txt
pi@rpicloud:~ $ cat ycsb/out/cassandra-13-1000000-rf3-a-3.txt
....
pi@rpicloud:~ $ cat ycsb/out/cassandra-13-1000000-rf3-a-30.txt
A dissertação do Lucas Ferreira usou os parêmetros de execução:
- Tamanho: 1000000
- Workloads: a b c d
- Replicas: 1, 2 e 3
- Nós: 15
A métrica considerada será a média da vazão (ops/sec) que aparece como na linha abaixo:
[OVERALL], Throughput(ops/sec), 2.6705193358952517
O script abaixo foi usado para resumir os resultados dos experimentos. Ele gera um CSV com outras informações para gerar os gráficos de resultados. Note que ele não faz qualquer análise estatística:
#!/bin/bash
sizes="1000000 "
replicas="1 2 3"
runtime="cassandra hbase citus"
echo "DB,Replication,Index,Workload,Size,RunTime,Throughput,ReadLatency,WriteLatency"
for rt in $runtime
do
for w in a b c d
do
for s in $sizes
do
for rf in $replicas
do
for i in $(seq 1 30)
do
arquivo=
if [ "$rt" = "citus" ]; then
# citus-exec-14-1000000-d-6-rf_1-bs_7000-fs_3000.txt
arquivo="$rt-exec-14-${s}-${w}-${i}-rf_${rf}-bs_7000-fs_3000.txt"
else
# hbase-exec-15-1000000-b-22-rf_1.txt
arquivo="$rt-exec-15-${s}-${w}-${i}-rf_${rf}.txt"
fi
tempo=$(grep RunTime $arquivo | cut -d ',' -f3)
vazao=$(grep Throughput $arquivo | cut -d ',' -f3)
leitura=$(grep "\[READ\]" $arquivo |grep AverageLatency|cut -d ',' -f3)
escrita=$(grep -e "\[UPDATE\]" -e "\[INSERT\]" $arquivo |grep AverageLatency |cut -d ',' -f3)
#echo out-15-${s}-${w}-${i}.txt $w,$s,$tempo,$vazao,$leitura,$escrita | sed 's/ //g'
echo $rt,$rf,$i,$w,$s,$tempo,$vazao,$leitura,$escrita | sed 's/ //g'
done
done
done
done
done
Os detalhes de cada passo são descritos abaixo.
O cluster tem 15 nós no total:
- 1 nós de acesso rpicloud (16.0.0.10) que está no cluster. A pasta do usuário
pi
está em um disco SSD de 1TB. Este tem 4GB de RAM. - 14 nós de processamento do master rpi01 (16.0.0.11) até o rpi14 (16.0.0.24). Cada um tem um disco SSD 1TB na pasta
/mnt/storage
. Os nós tem 2GB de RAM.
O nó de acesso é o rpicloud
:
$ ssh pi@rpicloud
O nó rpicloud
acessa qualquer nó por SSH e tem acesso ao cluster pelo comando kubectl
:
pi@rpicloud:~ $ kubectl get nodes
NAME STATUS ROLES AGE VERSION
rpi14 NotReady <none> 4d14h v1.25.4+k3s1
rpi02 Ready <none> 4d14h v1.25.4+k3s1
rpi03 Ready <none> 4d14h v1.25.4+k3s1
rpi10 Ready <none> 4d14h v1.25.4+k3s1
rpi08 Ready <none> 4d14h v1.25.4+k3s1
rpicloud Ready <none> 20h v1.25.5+k3s2
rpi01 Ready control-plane,master 6d2h v1.25.4+k3s1
rpi07 Ready <none> 4d14h v1.25.4+k3s1
rpi05 Ready <none> 3d16h v1.25.4+k3s1
rpi06 Ready <none> 4d14h v1.25.4+k3s1
rpi12 Ready <none> 4d14h v1.25.4+k3s1
rpi11 Ready <none> 4d14h v1.25.4+k3s1
rpi13 Ready <none> 4d14h v1.25.4+k3s1
rpi09 Ready <none> 4d14h v1.25.4+k3s1
rpi04 Ready <none> 4d14h v1.25.4+k3s1
O dashboard está acessível localmente no cluster. Primeiro, é preciso criar um proxy para acessar no rpicloud
:
pi@rpicloud:~ $ kubectl proxy
Starting to serve on 127.0.0.1:8001
Depois precisamos criar uma ponte SSH para o host local com:
$ ssh -L 8001:127.0.0.1:8001 pi@rpicloud
O dashboard fica no endereço http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/ onde precisamos de um token de acesso criado com o comando:
pi@rpicloud:~ $ kubectl -n kubernetes-dashboard create token admin-user
O Cassandra é um banco de dados distribuído NoSQL com topologia de anel. É possível executar um cluster com Kubernetes criando um serviço ligado a múltiplos pods.
Primeiro é preciso criar um serviço headless cassandra-service.yaml para criar os pods de execução:
apiVersion: v1
kind: Service
metadata:
labels:
app: cassandra
name: cassandra
spec:
clusterIP: None
ports:
- port: 9042
selector:
app: cassandra
O comando para o serviço é:
pi@rpicloud:~ $ kubectl apply -f cassandra/cassandra-service.yaml
Em seguida, o arquivo cassandra-statefulset.yaml criar os pods do Cassandra:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cassandra
labels:
app: cassandra
spec:
serviceName: cassandra
replicas: 13
selector:
matchLabels:
app: cassandra
template:
metadata:
labels:
app: cassandra
spec:
terminationGracePeriodSeconds: 1800
containers:
- name: cassandra
image: cassandra:4
imagePullPolicy: IfNotPresent
ports:
- containerPort: 7000
name: intra-node
- containerPort: 7001
name: tls-intra-node
- containerPort: 7199
name: jmx
- containerPort: 9042
name: cql
securityContext:
capabilities:
add:
- IPC_LOCK
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- nodetool drain
env:
- name: MAX_HEAP_SIZE
value: 512M
- name: HEAP_NEWSIZE
value: 100M
- name: CASSANDRA_SEEDS
value: "cassandra-0.cassandra.default.svc.cluster.local"
- name: CASSANDRA_CLUSTER_NAME
value: "K8Demo"
- name: CASSANDRA_DC
value: "DC1-K8Demo"
- name: CASSANDRA_RACK
value: "Rack1-K8Demo"
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
readinessProbe:
exec:
command:
- /bin/bash
- -c
- 'if [[ $(nodetool status | grep $POD_IP) == *"UN"* ]]; then exit 0; else exit 1; fi'
initialDelaySeconds: 60
timeoutSeconds: 60
# These volume mounts are persistent. They are like inline claims,
# but not exactly because the names need to match exactly one of
# the stateful pod golumes.
volumeMounts:
- name: cassandra-data
mountPath: /var/lib/cassandra
# These are converted to volume claims by the controller
# and mounted at the paths mentioned above.
# do not use these in production until ssd GCEPersistentDisk or other ssd pd
volumeClaimTemplates:
- metadata:
name: cassandra-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: local-path
resources:
requests:
storage: 128Gi
O pod cassandra-0.cassandra.default.svc.cluster.local
é o seed do cluster Cassandra responsável por iniciar o protocolo de anel dentro do cluster.
O comando dos pods é:
pi@rpicloud:~ $ kubectl create -f cassandra/cassandra-statefulset.yaml
Verifique o estado dos pods:
pi@rpicloud:~ $ kubectl get pods -l="app=cassandra"
NAME READY STATUS RESTARTS AGE
cassandra-0 1/1 Running 0 70m
cassandra-1 1/1 Running 0 69m
cassandra-2 1/1 Running 0 67m
cassandra-3 1/1 Running 0 66m
cassandra-4 1/1 Running 0 64m
cassandra-5 1/1 Running 0 63m
cassandra-6 1/1 Running 0 61m
cassandra-7 1/1 Running 0 60m
cassandra-8 1/1 Running 0 58m
cassandra-9 1/1 Running 0 56m
cassandra-10 1/1 Running 0 55m
cassandra-11 1/1 Running 0 53m
cassandra-12 1/1 Running 0 51m
E quantos pods foram criados:
pi@rpicloud:~ $ kubectl get statefulset cassandra
NAME READY AGE
cassandra 13/13 14m
Também verifique o estado do cluster Cassandra com:
pi@rpicloud:~ $ kubectl exec -it cassandra-0 -- nodetool status
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
-- Address Load Tokens Owns (effective) Host ID Rack
UN 10.42.21.26 165.1 KiB 16 15.2% 0ab13a52-9b06-406b-b6b4-e40c741550af rack1
UN 10.42.15.23 132.07 KiB 16 15.8% 3f70234a-a0af-4fb0-89fe-98dd171f83df rack1
UN 10.42.23.20 70.24 KiB 16 16.0% 9f4a580f-c14d-4bb0-8a32-bf5c208444f4 rack1
UN 10.42.19.27 131.96 KiB 16 16.1% 99056488-4726-4fb7-ae17-b02442714673 rack1
UN 10.42.1.25 70.25 KiB 16 15.8% 9556c5c6-516b-41bf-b6b6-433cd576f0bc rack1
UN 10.42.24.23 70.23 KiB 16 16.0% 9411c879-b3ed-4ff8-bd32-cbeb9d6ff32a rack1
UN 10.42.22.33 131.97 KiB 16 15.9% 707f27d2-b106-4bea-be87-21bc4be13a9e rack1
UN 10.42.14.20 70.23 KiB 16 15.4% 79ffc760-6469-4e9c-ad3f-3ee7f2ff602f rack1
UN 10.42.17.23 70.23 KiB 16 14.3% 4c0cd6f7-1d99-4026-b49d-ca1285f5a6a4 rack1
UN 10.42.20.20 137.25 KiB 16 14.0% 4a9b72bf-39ac-4d60-b32b-a7e0701c9cc6 rack1
UN 10.42.0.36 70.25 KiB 16 15.2% bc3c133b-49fa-4b3e-9a77-6df97ed9695f rack1
UN 10.42.16.20 70.25 KiB 16 15.3% 318c4e08-a814-4a31-84ff-2312b22451a4 rack1
UN 10.42.18.26 137.5 KiB 16 14.9% 6ea118bc-d38d-4be5-8c75-35d501edda7f rack1
A remoção dos pods pode ser feita pelo comando:
pi@rpicloud:~ $ kubectl delete -f cassandra/cassandra-statefulset.yaml
Para remover os volumes de armazenamento criados pelo Cassandra:
pi@rpicloud:~ $ kubectl delete persistentvolumeclaim -l app=cassandra
Se precisar dos IPs no cluster:
pi@rpicloud:~ $ kubectl get pods --output=wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ycsb-benchmark 1/1 Running 0 116m 10.42.2.38 rpicloud <none> <none>
cassandra-0 1/1 Running 0 68m 10.42.21.26 rpi10 <none> <none>
cassandra-1 1/1 Running 0 67m 10.42.20.20 rpi09 <none> <none>
cassandra-2 1/1 Running 0 65m 10.42.22.33 rpi11 <none> <none>
cassandra-3 1/1 Running 0 64m 10.42.18.26 rpi07 <none> <none>
cassandra-4 1/1 Running 0 62m 10.42.15.23 rpi03 <none> <none>
cassandra-5 1/1 Running 0 61m 10.42.19.27 rpi08 <none> <none>
cassandra-6 1/1 Running 0 59m 10.42.14.20 rpi02 <none> <none>
cassandra-7 1/1 Running 0 57m 10.42.24.23 rpi13 <none> <none>
cassandra-8 1/1 Running 0 56m 10.42.17.23 rpi06 <none> <none>
cassandra-9 1/1 Running 0 54m 10.42.1.25 rpi05 <none> <none>
cassandra-10 1/1 Running 0 53m 10.42.23.20 rpi12 <none> <none>
cassandra-11 1/1 Running 0 51m 10.42.16.20 rpi04 <none> <none>
cassandra-12 1/1 Running 0 49m 10.42.0.36 rpi01 <none> <none>
Se ainda ficou na dúvida com relação o que acontece no pod use o comando para ver o log:
pi@rpicloud:~ $ kubectl get logs cassandra-12
Kubernetes é compatível com imagens Docker mas é preciso alguns passos para usar uma imagem criada localmente com Docker. Por exemplo, a imagem do benchmark usado foi criada do arquivo Dockerfile
pi@rpicloud:~ $ docker build -t ycsb-benchmark ./ycsb/Docker
O cluster tem um container Docker de imagens para servir as imagens ao Kubernetes descrito em k3s-docker_docker-compose-registry.yaml:
version: '3'
services:
registry.local:
image: "registry:2"
ports:
- 5000:5000
O comando executado foi:
pi@rpicloud:~ $ docker-compose -f ./registry/k3s-docker_docker-compose-registry.yaml up -d
O nó precisa de configurações do arquivo /etc/docker/daemon.json
:
{
"allow-nondistributable-artifacts": ["127.0.0.1:5000"],
"insecure-registries" : [ "127.0.0.1:5000" ]
}
Basta reiniciar o Docker:
pi@rpicloud:~ $ sudo systemctl restart docker
Por fim registramos a imagem local no container Docker de imagens:
pi@rpicloud:~ $ docker tag ycsb-benchmark 127.0.0.1:5000/ycsb-benchmark
pi@rpicloud:~ $ docker push 127.0.0.1:5000/ycsb-benchmark
O teste para verificar se a imagem existe localmente é rodar um pod:
pi@rpicloud:~ $ kubectl run bench -i --tty --rm --image 127.0.0.1:5000/ycsb-benchmark -- bash
If you don't see a command prompt, try pressing enter.
root@bench:/#
Não é possível montar diretórios com o comando kubectl
. Portanto, olhe a seção sobre rodar o benchmark YCSB para detalhes.
O trabalho tem um repositório original com as instruções em Docker. Para rodar o benchmark, vamos criar uma imagem Docker e rodar no cluster Kubernetes.
O arquivo benchmark.yml cria um pod para rodar os benchmark. A linha nodeName
restringe o pod no nó local rpicloud
e faz com que fique fora do cluster de teste.
apiVersion: v1
kind: Pod
metadata:
name: ycsb-benchmark
labels:
name: ycsb-benchmark
spec:
nodeName: rpicloud
containers:
- image: 127.0.0.1:5000/ycsb-benchmark
name: ycsb-benchmark
volumeMounts:
- mountPath: /ycsb_outputs
name: output
- mountPath: /scripts
name: scripts
imagePullPolicy: Never
command: [ "sleep" ]
args: [ "infinity" ]
resources:
requests:
memory: "3Gi"
restartPolicy: Never
volumes:
- name: scripts
hostPath:
path: /home/pi/ycsb/scripts
type: Directory
- name: output
hostPath:
path: /home/pi/ycsb/out
type: Directory
Esse pod irá rodar no nó rpicloud
:
pi@rpicloud:~ $ kubectl create -f ycsb/benchmark.yml
Abra um terminal dentro do pod:
pi@rpicloud:~/ycsb $ kubectl exec -i --tty ycsb-benchmark -- bash
root@ycsb-benchmark:/#
Basta carregar os dados para um workload específico. Por exemplo, conectar no seed cassandra-0.cassandra.default.svc.cluster.local
para carregar o workload a
, 100000
registros, 13
nós e 3
replicas:
root@ycsb-benchmark:/# /scripts/cassandra_load.sh cassandra-0.cassandra.default.svc.cluster.local a 100000 13 3
Verifique a execução olhando para os dados da tabela criada especialmente o campo Space used
:
pi@rpicloud:~ $ kubectl exec -it cassandra-0 -- nodetool cfstats -- ycsb.usertable
Total number of tables: 45
----------------
Keyspace : ycsb
Read Count: 0
Read Latency: NaN ms
Write Count: 22662
Write Latency: 0.0746256729326626 ms
Pending Flushes: 0
Table: usertable
SSTable count: 1
Old SSTable count: 0
Space used (live): 20419481
Space used (total): 20419481
Space used by snapshots (total): 0
Off heap memory used (total): 38238
SSTable Compression Ratio: 0.9938585064328254
Number of partitions (estimate): 22661
Memtable cell count: 3960
Memtable data size: 4708440
Memtable off heap memory used: 0
Memtable switch count: 1
Local read count: 0
Local read latency: NaN ms
Local write count: 22662
Local write latency: 0.070 ms
Pending flushes: 0
Percent repaired: 0.0
Bytes repaired: 0.000KiB
Bytes unrepaired: 19.012MiB
Bytes pending repair: 0.000KiB
Bloom filter false positives: 0
Bloom filter false ratio: 0.00000
Bloom filter space used: 23392
Bloom filter off heap memory used: 23384
Index summary off heap memory used: 5118
Compression metadata off heap memory used: 9736
Compacted partition minimum bytes: 925
Compacted partition maximum bytes: 1109
Compacted partition mean bytes: 1109
Average live cells per slice (last five minutes): NaN
Maximum live cells per slice (last five minutes): 0
Average tombstones per slice (last five minutes): NaN
Maximum tombstones per slice (last five minutes): 0
Dropped Mutations: 0
Droppable tombstone ratio: 0.00000
----------------
Ou os logs de execução do carregamento:
pi@rpicloud:~ $ cat ycsb/out/cassandra-load-13-100000-rf3-a.txt
A execução do benchmark para medidas pode ser por exemplo:
root@ycsb-benchmark:/# /scripts/cassandra_run.sh cassandra-0.cassandra.default.svc.cluster.local a 100000 13 1 3
root@ycsb-benchmark:/# /scripts/cassandra_run.sh cassandra-0.cassandra.default.svc.cluster.local a 100000 13 2 3
Onde o penúltimo argumento é o número da repetição salvo no arquivo:
pi@rpicloud:~ $ cat ycsb/out/cassandra-13-100000-rf3-a-1.txt
O cluster usa o Local Path Provisioner para criar pastas locais em cada nó do cluster. Caso contrário, seria preciso criar uma pasta para cada nó.
O arquivo local-path-storage.yaml cria o serviço de armazenamento no k3s.
O exemplo abaixo (teste-pod.yaml) cria um volume persistente:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-path-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 128Mi
Um exemplo de pod (teste-pod.yaml) que usa esse armazenamento:
apiVersion: v1
kind: Pod
metadata:
name: volume-test
spec:
containers:
- name: volume-test
image: nginx:stable-alpine
imagePullPolicy: IfNotPresent
volumeMounts:
- name: volv
mountPath: /data
ports:
- containerPort: 80
volumes:
- name: volv
persistentVolumeClaim:
claimName: local-path-pvc
O armazenamento será criado em cada nó físico que o pod executar.