Redis Cluster auf Triton

Der Eintrag hatte ich im Juli 2020 geschrieben


Wenn man so durch die Weiten des Netzes braust und in fremden github-Repositories stöbert, stolpert man manchmal über durchaus schöne kleine Projekte. Wie es der Zufall wollte bin ich in Joyents Github über ein Projekt von Brie Bennet gestolpert: Den Redis Cluster Creator. Das Repository enthält zwei bash-Skripte - kein Terraform, kein Ansible - und demonstriert nebenbei ganz schön, was man mit der triton CLISmartOS Zonen und pkgsrc erreichen kann. 

Mit Hilfe der beiden Skripte wird in einer Triton-Umgebung - innerhalb eines privaten Netzes - ein Redis Cluster aufgesetzt, der nur über einen Bastion-Host erreichbar ist. Dazu muß vorher ein entsprechendes Netz vorhanden sein (oder angelegt werden). Genauso muss ein Bastion-Host erzeugt werden, der ein Netzwerkinterface in dem privaten Netz und ein Netzwerinterface im Internet hat:

root@e3b75fc5-3621-cdd8-f9dd-c9acbf4a37e9:~/triton-redis-cluster# triton network ls -l
ID                                    NAME               SUBNET            GATEWAY        FABRIC  VLAN  PUBLIC
2fa4d4ed-fd46-4469-9b95-f7a61d4f9089  My-Fabric-Network  192.168.128.0/22  192.168.128.1  true    2     false
aa3f924b-05b8-41ce-b638-cdd7246f3d7a  extpool            -                 -              -       -     true
933ef2e0-a467-407b-b6de-99bb77906398  jitsi-test         192.168.15.0/24   192.168.15.1   true    3     false
bc7d2db4-6fc1-46f2-bc65-f38b4025ed5c  sdc_nat            -                 -              -       -     true
879ae5a2-daec-4aab-ab45-b7fd39e67b33  trudesk_default    192.168.5.0/24    192.168.5.1    true    3     false
root@e3b75fc5-3621-cdd8-f9dd-c9acbf4a37e9:~/triton-redis-cluster# triton inst create --name bast minimal-64-lts@19.4.0 sample-128M --network=extpool,trudesk_default

Ist das erledigt. Kann man schon damit beginnen, das Redis Cluster aufzubauen. Dazu wird das Skript spawn_redis_instance.sh benutzt (z. B. so: ./spawn_redis_instance.sh -n trudesk_default -b bast -P rtest -t geheim)

Am interessantesten ist der letzte Teil des Skripts, wo der Aufruf des triton Clients für den Aufbau der Redis-Instanz zusammengebaut wird. Triton-Profil und -Account werden - so vorhanden - aus den Umgebungsvariablen ermittelt. Damit sich die Instanznamen voneinander unterscheiden, wird mit shortId der erste Teil der UUID der Instanz an den Namen angehängt. Mit Metadaten-Keys werden verschiedene Informationen in die zu erstellende Instanz übergeben. Diese Metadaten sind dann innerhalb der Instanz mit den mdata-Kommandos auslesbar:

[root@rtest-redis-1a0fbf07 ~]# mdata-listredis_token 
network_name
svc_name
user-script
root_authorized_keys
[root@rtest-redis-1a0fbf07 ~]# mdata-get redis_token
geheim
[root@rtest-redis-1a0fbf07 ~]# mdata-get svc_name
rtest-redis

Die Instanzen sollten während der Initialisierung im DNS nicht auflösbar sein (jedenfalls nicht in der "cns.tgos.de" Domain). Weiterhin wird ihre Erreichbarkeit über den Bastion-Host konfiguriert. Damit funktioniert dann sowas (sofern man mindestens einen ssh-Client ab Version 7.3 installiert hat, der die Option "proxyjump" kennt):

root@32f38d72-bb85-45ca-9d43-27aa109eb404:~# triton ssh rtest-redis-1a0fbf07
   __        .
 _|  |_      | .-. .  . .-. :--. |-
|_    _|     ;|   ||  |(.-' |  | |
  |__|   `--'  `-' `;-| `-' '  ' `-'
                   /  ; Instance (base-64-lts 19.4.0)
                   `-'  https://docs.joyent.com/images/smartos/base

[root@rtest-redis-1a0fbf07 ~]#

Als letzter Parameter wird dann noch das Startskript übergeben, welches beim Start der Instanz ablaufen soll (s. u.).

Ich habe einerseits ein in meiner Umgebung verfügbares Package (sample-1G) eingetragen und andererseits einen Affinity-Parameter (-a) ergänzt, um die Redis Instanzen auf verschiedenen Compute Nodes auszurollen. 

Das Startskript (redis_user-script.sh) richtet dann die Instanz ein.

Gleich zu Beginn erfolgt der Check, ob die Instanz schon eingerichtet ist - wenn redis-sentinel bereits als Dienst eingerichtet ist, wird das Skript sofort beendet. Das ist wichtig, weil das Start-Skript der Instanz tatsächlich bei jedem Start ausgeführt wird.

Die Metadaten-API wird zur Übernahme von Informationen für das Skript in der VM verwendet. Je nach dem, ob es "peers" gibt, wird die redis-Konfigurationsdatei mit dem "init_patch" oder mit dem "join_patch" versehen.  Dynamisch werden per pkgin redis und tmux installiert. Dann noch die Konfigurationsdatei und das smf-Manifest für redis-sentinel erzeugt. Danach werden die neuen Dienste per svccfg und svcadm hochgefahren. Als letztes werden sie auch im CNS sichtbar gemacht. 

Auf der ersten Instanz sieht man schön, wie nacheinander die beiden Replikas dazukommen:

91710:C 08 Jul 2020 08:34:30.922 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
91710:C 08 Jul 2020 08:34:30.922 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=91710, just started
91710:C 08 Jul 2020 08:34:30.922 # Configuration loaded
                _._                                                 
           _.-``__ ''-._                                            
      _.-``    `.  `_.  ''-._           Redis 5.0.7 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                  
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6379
 |    `-._   `._    /     _.-'    |     PID: 91711
  `-._    `-._  `-./  _.-'    _.-'                                  
 |`-._`-._    `-.__.-'    _.-'_.-'|                                 
 |    `-._`-._        _.-'_.-'    |           http://redis.io       
  `-._    `-._`-.__.-'_.-'    _.-'                                  
 |`-._`-._    `-.__.-'    _.-'_.-'|                                 
 |    `-._`-._        _.-'_.-'    |                                 
  `-._    `-._`-.__.-'_.-'    _.-'                                  
      `-._    `-.__.-'    _.-'                                      
          `-._        _.-'                                          
              `-.__.-'                                              

91711:M 08 Jul 2020 08:34:30.936 # Server initialized
91711:M 08 Jul 2020 08:34:30.937 * Ready to accept connections
91711:M 08 Jul 2020 09:16:03.985 * Replica 192.168.5.14:6379 asks for synchronization
91711:M 08 Jul 2020 09:16:03.985 * Full resync requested by replica 192.168.5.14:6379
91711:M 08 Jul 2020 09:16:03.985 * Starting BGSAVE for SYNC with target: disk
91711:M 08 Jul 2020 09:16:03.990 * Background saving started by pid 6706
6706:C 08 Jul 2020 09:16:04.000 * DB saved on disk
91711:M 08 Jul 2020 09:16:04.069 * Background saving terminated with success
91711:M 08 Jul 2020 09:16:04.069 * Synchronization with replica 192.168.5.14:6379 succeeded
91711:M 08 Jul 2020 09:21:46.733 * Replica 192.168.5.15:6379 asks for synchronization
91711:M 08 Jul 2020 09:21:46.733 * Full resync requested by replica 192.168.5.15:6379
91711:M 08 Jul 2020 09:21:46.733 * Starting BGSAVE for SYNC with target: disk
91711:M 08 Jul 2020 09:21:46.738 * Background saving started by pid 8581
8581:C 08 Jul 2020 09:21:46.749 * DB saved on disk
91711:M 08 Jul 2020 09:21:46.832 * Background saving terminated with success
91711:M 08 Jul 2020 09:21:46.832 * Synchronization with replica 192.168.5.15:6379 succeeded

Und genauso im sentinel.log:

91657:X 08 Jul 2020 08:34:28.844 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
91657:X 08 Jul 2020 08:34:28.845 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=91657, just started
91657:X 08 Jul 2020 08:34:28.845 # Configuration loaded
91663:X 08 Jul 2020 08:34:28.855 * Running mode=sentinel, port=26379.
91663:X 08 Jul 2020 08:34:28.862 # Sentinel ID is eac9d01bcd45afd1ab2f811f2a2d7a10beb261e5
91663:X 08 Jul 2020 08:34:28.862 # +monitor master rtest-redis 192.168.5.13 6379 quorum 2
91663:X 08 Jul 2020 09:16:03.925 * +sentinel sentinel d9311c01207b665fb75fe65c487af670ee228875 192.168.5.14 26379 @ rtest-redis 192.168.5.13 6379
91663:X 08 Jul 2020 09:16:10.004 * +slave slave 192.168.5.14:6379 192.168.5.14 6379 @ rtest-redis 192.168.5.13 6379
91663:X 08 Jul 2020 09:21:46.656 * +sentinel sentinel b00129df3a4d56ae8d816989daf2bbfbba719550 192.168.5.15 26379 @ rtest-redis 192.168.5.13 6379
91663:X 08 Jul 2020 09:21:51.184 * +slave slave 192.168.5.15:6379 192.168.5.15 6379 @ rtest-redis 192.168.5.13 6379

Wenn man zum Schluß die Verteilung im Datacenter überprüft, sieht man auch, dass die Affinity-Regel gegriffen hat, und Triton die drei Instanzen tatsächlich auf verschiedenen Compute-Nodes ausgerollt hat:

[root@headnode (de-gt-2) ~]# sdc-vmadm list -o uuid,server_uuid,brand,ram,alias -s server_uuid |grep -i redis |grep -v amonredis
1a0fbf07-6c8f-4d5c-c37e-9e0f586f47ec  3a3100f7-01b9-417f-ae1f-fc98a6773b2b  joyent          1024  rtest-redis-1a0fbf07
b954a40c-a860-403a-f970-80582076b305  6426b66b-11e8-49bd-8185-73171054bae7  joyent          1024  rtest-redis-b954a40c
aa4c784c-7c57-4b8f-8c19-f68bec68b2f6  eff10f9a-8e60-4a73-811c-5078cde28c7c  joyent          1024  rtest-redis-aa4c784c

Dabei kommt so eine SmartOS-Zone relativ schlank daher (27 MB) - beim Bastion-Host sind es nur 19 MB:

[root@hh24-gts2-de29 (de-gt-2) ~]# zonememstat -a |grep b954a40c-a860-403a-f970-80582076b305
 b954a40c-a860-403a-f970-80582076b305                     rtest-redis-b954a40c       27   1024   0         0 0,814533

Um zu sehen, wie sich die Instanzen so machen, könnte man z. B. den redis_exporter verwenden. Dazu könnte man sich z. B. die Quellen auschecken und in einer SmartOS-Zone mit go übersetzen. Netterweise stellt der Autor aber nach kurzer Anfrage inzwischen auch ein fertiges Illumos-Binary in seinen Releases zur Verfügung. Ich habe die "Docker-Variante" gewählt und wie folgt einen Docker-Container mit dem redis_exporter in der Triton-Umgebung ausgerollt:

root@e3b75fc5-3621-cdd8-f9dd-c9acbf4a37e9:~# docker run -d --name redis_exporter -p 9121 --network=879ae5a2 -e REDIS_ADDR="redis://geheim@192.168.5.13:6379" -e REDIS_ADDR="redis://geheim@192.168.5.14:6379" -e REDIS_ADDR="redis://geheim@192.168.5.15:6379" -e REDIS_PASSWORD="geheim" oliver006/redis_exporter
944c31aca575ee58d64f80755042b7c417f1d90cc26663a9b8e0dd9ea84f16c0
ERRO[0024] error getting events from daemon: Error response from daemon: (NotImplemented) events is not implemented (0ff6f9ad-04b8-4fc9-8c75-6cdb310d5e7d)
root@e3b75fc5-3621-cdd8-f9dd-c9acbf4a37e9:~# triton ip redis_exporter
10.65.69.109

Wichtig ist dabei, dass auch dieser Container ein Netzwerkinterface in dem privaten Netz bekommt, in dem sich die Redis-Instanzen befinden. Schnell die einfache scrape Konfiguration der bereits laufenden Prometheus-Instanz hinzugefügt:

scrape_configs:
  - job_name: redis_exporter
    static_configs:
    - targets: ['10.65.69.109:9121']

Das entsprechende Grafana-Dashboard wird gleich mitgeliefert. In Aktion sieht das dann so aus:

Links


Redis Cluster mit Autopilot: Auch schon drei Jahre alt - aber noch gut.

Redis Benchmarks: 

Prometheus Exporter für redis: redis_exporter