Adding helm chart & documentation for redis-cluster related study ticket 74/4874/1
authorAlok Bhatt <alok_bh@hcl.com>
Wed, 21 Oct 2020 14:37:35 +0000 (14:37 +0000)
committerAlok Bhatt <alok_bh@hcl.com>
Wed, 21 Oct 2020 14:37:35 +0000 (14:37 +0000)
Issue-ID: RIC-109
Signed-off-by: Alok Bhatt <alok_bh@hcl.com>
Change-Id: I57422f0126a73645333e1360f1faf010053d60d1

docs/installation-rediscluster.rst [new file with mode: 0644]
helm/redis-cluster/.helmignore [new file with mode: 0644]
helm/redis-cluster/Chart.yaml [new file with mode: 0644]
helm/redis-cluster/templates/NOTES.txt [new file with mode: 0644]
helm/redis-cluster/templates/configMap.yaml [new file with mode: 0644]
helm/redis-cluster/templates/deployment.yaml [new file with mode: 0644]
helm/redis-cluster/templates/service.yaml [new file with mode: 0644]
helm/redis-cluster/templates/serviceaccount.yaml [new file with mode: 0644]
helm/redis-cluster/templates/statefulset.yaml [new file with mode: 0644]
helm/redis-cluster/values.yaml [new file with mode: 0644]

diff --git a/docs/installation-rediscluster.rst b/docs/installation-rediscluster.rst
new file mode 100644 (file)
index 0000000..e66635e
--- /dev/null
@@ -0,0 +1,57 @@
+###################################################################################
+#  ============LICENSE_START=======================================================
+#
+#  ================================================================================
+#  Copyright (C) 2020 Hcl Technologies Limited.
+#  ================================================================================
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+# ============LICENSE_END=========================================================
+###################################################################################
+
+Important
+^^^^^^^^^^
+The redis-cluster currently is NOT part of RIC platform & hence is completely optional.
+This piece of document has been created as part of delivery item for below jira ticket 
+https://jira.o-ran-sc.org/browse/RIC-109 
+This ticket is about assessing the feasibility of redis-cluster (with data sharding) 
+supporting desired pod anti-affinity for high availability as per the ticket.
+
+Overview
+^^^^^^^^^^
+This document describes the environment/conditions used to test the feasibility of Redis 
+cluster set-up as detailed in the above ticket. Redis Cluster is a distributed implementation 
+of Redis with high performance goals. More details at https://redis.io/topics/cluster-spec
+
+Environment Set-Up
+^^^^^^^^^^
+The set up was tested with kubernetes v1.19 cluster with 
+   #. Pod topology spread constraint enabled
+      Reference: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints
+   #. CEPH as the Cluster Storage Solution.
+         Reference: https://github.com/rook/rook.github.io/blob/master/docs/rook/v1.4/ceph-filesystem.md
+   #. Three worker nodes in the kubernet cluster
+
+Execution
+^^^^^^^^^^
+Once environment is set-up,  a redis-cluster can be set up using the helm-chart (also provided with 
+this commit). Once cluster is running, any master/slave of the redis instance pods can be deleted which
+will be compensated automatically by new instances
+
+At this stage the perl utility program (included with helm-chart) can be run. The helm chart installation
+output generates the requirement commands to invoke.
+
+This utility program identifies the missing anti-affinity(as per above ticket) of redis instances required
+in a redis-cluster. When executed it communicates to redis nodes to switch roles (e.g. master/slave)
+such that the end-state meets the desired anti-affinity. 
+
+   
diff --git a/helm/redis-cluster/.helmignore b/helm/redis-cluster/.helmignore
new file mode 100644 (file)
index 0000000..50af031
--- /dev/null
@@ -0,0 +1,22 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/
diff --git a/helm/redis-cluster/Chart.yaml b/helm/redis-cluster/Chart.yaml
new file mode 100644 (file)
index 0000000..ccc6959
--- /dev/null
@@ -0,0 +1,25 @@
+###################################################################################
+#  ============LICENSE_START=======================================================
+#
+#  ================================================================================
+#  Copyright (C) 2020 Hcl Technologies Limited.
+#  ================================================================================
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+# ============LICENSE_END=========================================================
+###################################################################################
+
+apiVersion: v1
+appVersion: "1.0"
+description: A Helm chart for Redis Cluster Created as per study tickeet requirement of https://jira.o-ran-sc.org/browse/RIC-109
+name: redis-cluster
+version: 0.1.0
diff --git a/helm/redis-cluster/templates/NOTES.txt b/helm/redis-cluster/templates/NOTES.txt
new file mode 100644 (file)
index 0000000..1288614
--- /dev/null
@@ -0,0 +1,36 @@
+1.  Important: You must wait to ensure that all {{ .Values.rediscluster.replicaCount }} instances of redis are in "Running" state. You can use below command to watch if all the required instances are ready
+
+watch "kubectl -n {{ .Release.Namespace }} get po -l app.kubernetes.io/instance={{ .Release.Name }}"
+
+2. Run below command to create redis-cluster.
+
+kubectl -n {{ .Release.Namespace }} exec -it {{ .Values.rediscluster.name }}-0  -- sh -c "echo yes | redis-cli --cluster create --cluster-replicas 2 \
+$(kubectl -n {{ .Release.Namespace }} get po \
+{{ .Values.rediscluster.name }}-0 \
+{{ .Values.rediscluster.name }}-1 \
+{{ .Values.rediscluster.name }}-6 \ 
+{{ .Values.rediscluster.name }}-3 \
+{{ .Values.rediscluster.name }}-2 \
+{{ .Values.rediscluster.name }}-4 \ 
+{{ .Values.rediscluster.name }}-7 \ 
+{{ .Values.rediscluster.name }}-8 \ 
+{{ .Values.rediscluster.name }}-5 \
+-o=jsonpath='{range .items[*]}{.status.podIP}{":6379 "}{end}')"
+
+3. Once cluster is created, you can use below utility to see the related redis nodes (master and slaves) sets,
+   along with the k8 worknode details where each is placed.  
+
+   PLACENODE_POD=$(kubectl get --no-headers  po -l app={{ .Values.assigner.label }}  -o=jsonpath='{.items[0].metadata.name}')
+   kubectl exec -it ${PLACENODE_POD} -- sh /conf/relatenode.sh
+
+4. If previous step shows the undesired state for pod-antiaffinity use below perl program to make it as per desired state. 
+   It will NOT take any action when the set-up is with desired pod-antiaffinity
+   kubectl exec -it ${PLACENODE_POD} -- perl /conf/placenode.pl
+
+
+5. Run below commands to delete the helm release and the PVC
+   a. helm delete --purge {{ .Release.Name }}
+   b. kubectl delete pvc -l app.kubernetes.io/instance={{ .Release.Name }} -n {{ .Release.Namespace }}
+
+
+
diff --git a/helm/redis-cluster/templates/configMap.yaml b/helm/redis-cluster/templates/configMap.yaml
new file mode 100644 (file)
index 0000000..d7da426
--- /dev/null
@@ -0,0 +1,387 @@
+###################################################################################
+#  ============LICENSE_START=======================================================
+#
+#  ================================================================================
+#  Copyright (C) 2020 Hcl Technologies Limited.
+#  ================================================================================
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+# ============LICENSE_END=========================================================
+###################################################################################
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ .Values.rediscluster.name }}-cm
+data:
+  update-node.sh: |
+    #!/bin/sh
+    REDIS_NODES="/data/nodes.conf"
+    sed -i -e "/myself/ s/[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}/${POD_IP}/" ${REDIS_NODES}
+    exec "$@"
+
+  redis.conf: |+
+    cluster-enabled yes
+    cluster-require-full-coverage no
+    cluster-node-timeout {{ .Values.rediscluster.nodetimeout }}
+    cluster-config-file /data/nodes.conf
+    cluster-migration-barrier 1
+    appendonly yes
+    protected-mode no
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: {{ .Values.assigner.name }}-cm
+data:
+  placenode.pl: |
+    #!/usr/bin/env perl
+    =head
+    ============LICENSE_START=======================================================
+    
+    ================================================================================
+    Copyright (C) 2020 Hcl Technologies Limited.
+    ================================================================================
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+   
+         http://www.apache.org/licenses/LICENSE-2.0
+   
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+    ============LICENSE_END=========================================================
+  
+    
+    About:
+  
+    This script has been developed as part of https://jira.o-ran-sc.org/browse/RIC-360
+    This script identifies the missing anti-affinity(as per above ticket) of redis instances 
+    required in a redis-cluster. If there is an  undesired  anti-affinity this script can  be 
+    executed to communicate to redis nodes  to switch roles (e.g. master/slave) such that the 
+    end-state meets the desired anti-affinity.
+           
+  
+    Pre-requisites: 
+  
+      1) A redis cluster with 3 masters (2 replicas each) deployed on kubernetes 1.18 (or later) 
+      2) Three available worker nodes for serving redis workloads
+      3) kubectl (with access to the k8 cluster)
+   
+    =cut
+  
+  
+    my $podRow = { 
+    "podIP"      => "",
+    "podName"    => "",
+    "k8Node"     => "",
+    
+    "rdNodeRole" => "",
+    "rdNodeID"   => "",
+    
+    "rdMasterNodeID"   => "",
+    "slaveIPs"    => [] 
+    };
+    
+    # Pod label for redis nodes
+    my $podLabel = $ENV{'POD_LABEL'};
+    
+    my $podTable =   [];
+    my $k8NodeInfo = [];
+    
+    setk8NodesInfo();
+    validate();
+    
+    # Master
+    spreadMastersIfRequired();
+    # Slave
+    my $disparity = getSlaveDisparity();
+    spreadSlavesIfRequired();
+    
+    sub validate() {
+        my @masters = map { $_->{'rdNodeRole'} eq 'master' ? $_ : () } @{$podTable};
+       if ( @masters > @{$k8NodeInfo->{allk8Nodes}} ) {
+               print "Info: Skipping any action as num of master > number of k8 nodes..\n";
+           exit;
+       }
+    }
+    
+    
+    sub spreadSlavesIfRequired() {
+        
+    
+       # Get node with maximum disparity first
+        my @disparityMatrix = reverse sort { @{$a} <=> @{$b} } @${disparity}; 
+        #@disparityMatrix = grep defined, @disparityMatrix;
+        #@disparityMatrix = map { defined $_ ? $_ : () } @disparityMatrix;
+    
+        # Get list of slaves to be swapped roles.
+        my @slaveSwapList = ();
+        my $maxDisparityPerNode = @{$disparityMatrix[0]};
+    
+        for (my $disparityPass=0; $disparityPass < $maxDisparityPerNode; $disparityPass++) {
+            for (my $k8NodeIndex=0; $k8NodeIndex <= $#{disparityMatrix}; $k8NodeIndex++) {
+                  #print "$disparityMatrix[$disparityPass] && $disparityMatrix[$k8NodeIndex][$disparityPass]";
+               if ( $disparityMatrix[$disparityPass] && $disparityMatrix[$k8NodeIndex][$disparityPass] ) {
+                           push(@slaveSwapList,$disparityMatrix[$k8NodeIndex][$disparityPass]);
+                  }
+            }
+        }
+        if ( ! @slaveSwapList ) {
+               print "Info: No disparity found with slaves.\n" if ( @slaveSwapList < 2);
+               exit;
+       } elsif ( @slaveSwapList == 1 ) {
+               print "Info: single host scenario (with no swap candidate in other k8 nodes) found.\n";
+               exit;
+       } else {
+               print "Info: slave disparity found.\n";
+        }
+    
+       # Swap slaves 
+       for (my $swapIndex=0; $swapIndex < @slaveSwapList; $swapIndex++) {
+               $pod1 = $slaveSwapList[$swapIndex];
+               $pod2 = $slaveSwapList[++$swapIndex];
+               #print "Info: Swapping Slaves: " . join($pod1->{podName}, $pod2->{podName}) . "\n";
+               
+               my $cmd1 = qq[kubectl exec -it ].
+                                  qq[$pod1->{podName}  -- redis-cli -p 6379 cluster replicate $pod2->{rdMasterNodeID} ];
+               
+               my $cmd2 = qq[kubectl exec -it ].
+                                  qq[$pod2->{podName}  -- redis-cli -p 6379 cluster replicate $pod1->{rdMasterNodeID} ];
+    
+           runRediClusterCmd($cmd1);
+           runRediClusterCmd($cmd2);
+               #print "\n$cmd1";
+               #print "\n$cmd2\n";
+        }
+    
+    }
+    
+    
+    sub getSlaveDisparity() {
+    
+        # Get Slave Disparity Metrix
+        my $disparity = ();
+        my $nodeIndex = 0;
+        foreach my $k8NodeName ( @{$k8NodeInfo->{allk8Nodes}} ) {
+            my @redisNodesOnk8Node = map { $_->{'k8Node'} eq $k8NodeName ? $_ : () } @{$podTable};
+            @redisNodesOnk8Node    = sort { $a->{"rdNodeRole"} cmp $b->{"rdNodeRole"} } @redisNodesOnk8Node;
+    
+            my $master = shift @redisNodesOnk8Node;
+            
+            for (my $index=0; $index <= $#{redisNodesOnk8Node}; $index++ ) {
+                my $slave = $redisNodesOnk8Node[$index];
+                #print "chekcing for pod:  $slave->{podName}\n";
+                my $disparityFound = 0;
+                if ( $slave->{rdMasterNodeID} eq $master->{rdNodeID} ) {
+                   $disparityFound = 1;
+                } else {
+                   #check is other slaves are its sibling
+                   for (my $nextIndex=$index + 1; $nextIndex <= $#{redisNodesOnk8Node}; $nextIndex++ ) {
+                       if ( $slave->{rdMasterNodeID} eq $redisNodesOnk8Node[$nextIndex]->{rdMasterNodeID} ) {
+                              $disparityFound = 1;
+                           break;
+                       }
+                   }
+                }
+                       if ($disparityFound) {
+                       #$disparity[$nodeIndex][$index] = { 'podName' => $slave->{"podName"}, 'rdMasterNodeID' => $slave->{"rdMasterNodeID"} } ;
+                       push(@{$disparity[$nodeIndex]},{ 'podName' => $slave->{"podName"}, 'rdMasterNodeID' => $slave->{"rdMasterNodeID"} } ) ;
+                       }
+            }
+            $nodeIndex++;
+        }
+            return \@disparity;
+    }
+    
+    sub spreadMastersIfRequired() {
+    
+       NODE_WITH_NO_MASTER: foreach my $nodeWithoutMaster (@{$k8NodeInfo->{k8NodesWithoutMaster}}) {
+          # For each k8Node without any master 
+          #    Check for each extra master on its hostNode
+          #        Find its slave on the this hostNode (i.e. without any master) 
+          # Such slave must be Found for 3x3 set-up:
+          # Then Promote as master # Re-Evaluate
+    
+          # Get All Redis Slaves on This k8 node
+          print "Info: K8 node without any master : $nodeWithoutMaster\n";
+          my @rdSlaveNodes =  map { ($_->{'k8Node'} eq $nodeWithoutMaster ) && ($_->{'rdNodeRole'} eq 'slave') ? $_ : () } @{$podTable};
+    
+               foreach my $nodeWithExtraMaster (@{$k8NodeInfo->{k8NodesWithExtraMaster}} ) {
+                  print "Info: k8 Node with extra master : $nodeWithExtraMaster\n";
+                  #my @rdSlaveNodes =  map { ($_->{'k8Node'} eq $nodeWithoutMaster ) && ($_->{'rdNodeRole'} eq 'slave') ? $_ : () } @{$podTable};
+    
+                  my @masterInstances = map { ($_->{'k8Node'} eq $nodeWithExtraMaster ) && ($_->{'rdNodeRole'} eq 'master') ? $_ : () } @{$podTable};        
+                  foreach my $master (@masterInstances) {
+                      my @slave = map { $_->{"rdMasterNodeID"} eq $master->{rdNodeID} ? $_ : () } @rdSlaveNodes;
+                      if ( @slave ) {
+                          promoteSlaveAsMaster($slave[0]);
+                                         my $isPromoted = 0;
+                                     my $slaveNodeID= $slave[0]->{rdNodeID};
+                                         while( ! $isPromoted ) {
+                                                sleep(8);
+                                            setk8NodesInfo();
+                                                my ($promotedNode) = map { $slaveNodeID eq $_->{rdNodeID} ? $_ : () } @{$podTable};
+    
+                                                if ( $promotedNode->{'rdNodeRole'} ne 'master' ) {
+                                                       print ("Info: Waiting for node promotion confirmation..\n");
+                                                } else {
+                                                       $isPromoted = 1;
+                                                       print ("Info: Node promotion confirmed.\n");
+                                                }
+                                         }
+                          next NODE_WITH_NO_MASTER;
+                      }
+                  }
+               }
+       }
+       print "Info: All redis masters are on separate k8 Nodes. \n"    if ( ! @{$k8NodeInfo->{k8NodesWithoutMaster}}) ;
+    }
+    
+    sub promoteSlaveAsMaster() {
+        my $slavePod = shift;    
+        #print "Info: Promoting Slave $slavePod->{'podName'} On $slavePod->{'k8Node'} as master";
+        my $cmd = qq[kubectl exec -it $slavePod->{'podName'} -- redis-cli -p 6379 cluster failover takeover];
+        runRediClusterCmd($cmd);
+        
+    }
+    sub runRediClusterCmd() {
+      my $cmd = shift;    
+      print "Info: Running Cmd:$cmd \n";
+      `$cmd;`;
+      sleep(8);
+    }
+    
+    
+    #foreach my $item (@{$podTable}) {
+    #}
+    
+    # find_nodes_without-a-single_master
+    sub setk8NodesInfo() {
+    
+       $podTable   = [];
+       $k8NodeInfo = [];
+    
+       getCurrentStatus();
+       # All k8 nodes
+       my @k8NodeList = uniq(map { $_->{'k8Node'} } @$podTable);
+    
+       # Find Nodes with At least One master
+       my @k8NodesWithMaster;
+       foreach my $nodeName (@k8NodeList) {
+          push(@k8NodesWithMaster, map { ($_->{'k8Node'} eq $nodeName) && ($_->{'rdNodeRole'} eq 'master')   ? $nodeName : ()  } @{$podTable} );
+       }
+    
+       # Find Nodes without any master = All nodes - Nodes with at least one Master
+       my %k8NodesMap = ();
+       foreach (@k8NodesWithMaster) { 
+               if ( exists $k8NodesMap{$_} ) {
+                       $k8NodesMap{$_}++;
+               } else {
+                       $k8NodesMap{$_} = 1;
+               }
+       }
+       my @k8NodesWithoutMaster = map { exists $k8NodesMap{$_} ? () : $_ } @k8NodeList;
+       my @k8NodesWithExtraMaster = uniq(map { $k8NodesMap{$_} > 1 ? $_ : () } @k8NodesWithMaster);
+    
+       $k8NodeInfo = { 'allk8Nodes' => \@k8NodeList, 'k8NodesWithExtraMaster' => \@k8NodesWithExtraMaster, 'k8NodesWithoutMaster' => \@k8NodesWithoutMaster };
+    }
+    
+    
+    
+    
+    
+    # Validate if number of masters ,= number of rea
+    
+    #
+    #sub filter
+    
+    =head
+    get 
+    podName where k8Node eq "x"
+        get position of k8node eq x 
+    where 
+    =cut
+    
+    exit;
+    
+    sub uniq {
+        my %seen;
+        grep !$seen{$_}++, @_;
+    }
+    
+    sub getCurrentStatus() {
+    
+        # Run pod list command    
+        my @getPods = `kubectl get po --no-headers  -o wide -l $podLabel |grep Running`;    chomp @getPods;
+        #my @getPods = `kubectl get po --no-headers  -o wide -l managed-by=redis-cluster-operator|grep Running`;    chomp @getPods;
+    
+        foreach my $podLine (@getPods) {
+            my @podData = split(/\s+/,$podLine);
+            my ($podName,$status,$age,$podIP,$podNode) = ($podData[0], $podData[2], $podData[4], $podData[5],$podData[6]);
+    
+            #print "$podName,$status,$age,$podIP,$podNode" ."\n"; 
+            my $podRow = { 'podIP' => $podIP, 'podName' => $podName, 'k8Node' => $podNode, 'podAge' => $age, 'podStatus' => $status };    
+            push (@{$podTable},$podRow)
+        }
+    
+        my $podName = $podTable->[0]{'podName'};
+        #print "Info:kubectl exec $podName  -- cat nodes.conf|sort -k3\n";
+        my @rdNodeData = `kubectl exec $podName  -- cat nodes.conf|sort -k3`;    chomp @rdNodeData;
+        foreach my $rdNodeLine (@rdNodeData) {
+            next if ($rdNodeLine !~ /master|slave/);
+                my @rdNodeData = split(/\s+/,$rdNodeLine);
+                my ($rdNodeID,$rdRole,$rdMasterNodeID,$epoch) = ($rdNodeData[0], $rdNodeData[2], $rdNodeData[3],$rdNodeData[5]);
+                my ($podIP) = split(/:/,$rdNodeData[1]);
+                $rdRole =~ s/myself,//;
+    
+                #print "$rdNodeID,$rdRole,$rdMasterNodeID,$podIP" ."\n";
+                my $rdElem = { 'podIP'    => $podIP, 
+                               'rdNodeID' => $rdNodeID,
+                               'rdRole'   => $rdRole,
+                               'rdMasterNodeID' => $rdMasterNodeID,
+                               'epoch'          => $epoch
+                };
+    
+            for(my $index=0; $index <= $#{$podTable}; $index++) {
+                if ( $podTable->[$index]{'podIP'} eq $podIP ) {
+                    #print "Matched\n";
+                    $podTable->[$index]{'rdNodeID'}       = $rdNodeID;
+                    $podTable->[$index]{'rdNodeRole'}        = $rdRole;
+                    $podTable->[$index]{'rdMasterNodeID'} = $rdMasterNodeID;
+                    $podTable->[$index]{'epoch'}          = $epoch;
+                }
+            }
+            #exit;
+    
+        }
+    }
+
+  relatenode.sh: |
+    #!/bin/sh
+    podLabel=${POD_LABEL}
+    firstPod=$(kubectl  get   po -o wide -l app.kubernetes.io/name=redis-cluster --no-headers=true|head -1|cut -d" " -f1)
+    
+    kubectl get po -o wide -l $podLabel |tail +2|awk '{printf("%s:%s:%s:%s\n",$6,$1,$7,$10)}'|sort  > /tmp/1.txt
+    kubectl exec  $firstPod  -- cat nodes.conf|sed 's/myself,//'|awk '/master|slave/ {print $2,$1,$3,$4}'|sort > /tmp/2.txt
+    join -t ":"  /tmp/1.txt /tmp/2.txt |sort -k3,4 | sed 's/ /:/g'|awk -F":" '{print $2,$7,$3,$1,$4,$6,$8}' > /tmp/3.txt
+    
+    echo "\n   POD_NAME      ROLE      k8NODE        POD_IP                   REDIS_NODE_ID                       REDIS_MASTER_NODE_ID"
+    grep $(cut -d" " -f4 /tmp/2.txt|sort -u|grep -v "-"|sed -n '1p') /tmp/3.txt
+    echo ""
+    grep $(cut -d" " -f4 /tmp/2.txt|sort -u|grep -v "-"|sed -n '2p') /tmp/3.txt
+    echo ""
+    grep $(cut -d" " -f4 /tmp/2.txt|sort -u|grep -v "-"|sed -n '3p') /tmp/3.txt
+    
diff --git a/helm/redis-cluster/templates/deployment.yaml b/helm/redis-cluster/templates/deployment.yaml
new file mode 100644 (file)
index 0000000..b1526b7
--- /dev/null
@@ -0,0 +1,56 @@
+################################################################################
+#   Copyright (c) 2019 AT&T Intellectual Property.                             #
+#   Copyright (c) 2019 Nokia.                                                  #
+#                                                                              #
+#   Licensed under the Apache License, Version 2.0 (the "License");            #
+#   you may not use this file except in compliance with the License.           #
+#   You may obtain a copy of the License at                                    #
+#                                                                              #
+#       http://www.apache.org/licenses/LICENSE-2.0                             #
+#                                                                              #
+#   Unless required by applicable law or agreed to in writing, software        #
+#   distributed under the License is distributed on an "AS IS" BASIS,          #
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
+#   See the License for the specific language governing permissions and        #
+#   limitations under the License.                                             #
+################################################################################
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ .Values.assigner.name }}-dep
+  namespace: {{ .Release.Namespace }}
+  labels:
+    chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+    release: {{ .Release.Name }}
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: {{ .Values.assigner.label }}
+      release: {{ .Release.Name }}
+  template:
+    metadata:
+      labels:
+        app: {{ .Values.assigner.label }}
+        release: {{ .Release.Name }}
+    spec:
+      containers:
+      - name: kubectl
+        hostname: {{ .Values.assigner.label }}
+        image: "bitnami/kubectl:1.18"
+        command: ["/bin/sh"]
+        args: ["-c", "sleep 3000"]
+        env:
+        - name: "POD_LABEL"
+          value: app.kubernetes.io/instance={{ .Release.Name }}
+
+        volumeMounts:
+        - name: conf
+          mountPath: /conf
+          readOnly: false
+      volumes:
+      - name: conf
+        configMap:
+          name: {{ .Values.assigner.name }}-cm
+          defaultMode: 0755
+      serviceAccountName: {{ .Values.assigner.name }}-sa
diff --git a/helm/redis-cluster/templates/service.yaml b/helm/redis-cluster/templates/service.yaml
new file mode 100644 (file)
index 0000000..68c571e
--- /dev/null
@@ -0,0 +1,36 @@
+###################################################################################
+#  ============LICENSE_START=======================================================
+#
+#  ================================================================================
+#  Copyright (C) 2020 Hcl Technologies Limited.
+#  ================================================================================
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+# ============LICENSE_END=========================================================
+###################################################################################
+
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ .Values.rediscluster.name }}-svc
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+  - port: 6379
+    targetPort: 6379
+    name: client
+  - port: 16379
+    targetPort: 16379
+    name: gossip
+  selector:
+    app.kubernetes.io/name: {{ .Values.rediscluster.name }}
+    app.kubernetes.io/instance: {{ .Release.Name }}
diff --git a/helm/redis-cluster/templates/serviceaccount.yaml b/helm/redis-cluster/templates/serviceaccount.yaml
new file mode 100644 (file)
index 0000000..b799967
--- /dev/null
@@ -0,0 +1,55 @@
+###################################################################################
+#  ============LICENSE_START=======================================================
+#
+#  ================================================================================
+#  Copyright (C) 2020 Hcl Technologies Limited.
+#  ================================================================================
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+# ============LICENSE_END=========================================================
+###################################################################################
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ .Values.assigner.name }}-sa
+  
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: {{ .Values.assigner.name }}-role
+
+rules:
+  - apiGroups: [""]
+    resources:
+      - pods
+    verbs:
+      - get
+      - list
+  - apiGroups: [""]
+    resources: ["pods/exec"]
+    verbs: ["create"]  
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: {{ .Values.assigner.name }}-rb
+subjects:
+  - kind: ServiceAccount
+    name: {{ .Values.assigner.name }}-sa
+roleRef:
+  kind: Role
+  name: {{ .Values.assigner.name }}-role
+  apiGroup: rbac.authorization.k8s.io
+
diff --git a/helm/redis-cluster/templates/statefulset.yaml b/helm/redis-cluster/templates/statefulset.yaml
new file mode 100644 (file)
index 0000000..afebbaa
--- /dev/null
@@ -0,0 +1,85 @@
+###################################################################################
+#  ============LICENSE_START=======================================================
+#
+#  ================================================================================
+#  Copyright (C) 2020 Hcl Technologies Limited.
+#  ================================================================================
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+# ============LICENSE_END=========================================================
+###################################################################################
+
+apiVersion: apps/v1
+kind: StatefulSet
+metadata:
+  name: {{ .Values.rediscluster.name }}
+spec:
+  serviceName: {{ .Values.service.name }}
+  replicas: {{ .Values.rediscluster.replicaCount }}
+  selector:
+    matchLabels:
+      app.kubernetes.io/name: {{ .Values.rediscluster.name }}
+      app.kubernetes.io/instance: {{ .Release.Name }}
+  template:
+    metadata:
+      labels:
+        app.kubernetes.io/name: {{ .Values.rediscluster.name }}
+        app.kubernetes.io/instance: {{ .Release.Name }}
+    spec:
+      topologySpreadConstraints:
+       - maxSkew: 1
+         topologyKey: kubernetes.io/hostname
+         whenUnsatisfiable: {{ .Values.topology.policyUnstisfiable }}
+         #whenUnsatisfiable: ScheduleAnyway
+         #whenUnsatisfiable: DoNotSchedule
+         labelSelector:
+           matchLabels:
+             app.kubernetes.io/name: {{ .Values.rediscluster.name }}
+             app.kubernetes.io/instance: {{ .Release.Name }}
+      containers:
+      - name: {{ .Values.container.name }}
+        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
+        imagePullPolicy: {{ .Values.image.pullPolicy }}
+        ports:
+        - containerPort: 6379
+          name: client
+        - containerPort: 16379
+          name: gossip
+        command: ["/conf/update-node.sh", "redis-server", "/conf/redis.conf"]
+        env:
+        - name: POD_IP
+          valueFrom:
+            fieldRef:
+              fieldPath: status.podIP
+        volumeMounts:
+        - name: conf
+          mountPath: /conf
+          readOnly: false
+        - name: data
+          mountPath: /data
+          readOnly: false
+      volumes:
+      - name: conf
+        configMap:
+          name: {{ .Values.rediscluster.name }}-cm
+          defaultMode: 0755
+  volumeClaimTemplates:
+  - metadata:
+      name: data
+      labels:
+        app.kubernetes.io/name: {{ .Values.rediscluster.name }}
+        app.kubernetes.io/instance: {{ .Release.Name }}
+    spec:
+      accessModes: [ "ReadWriteOnce" ]
+      resources:
+        requests:
+          storage: {{ .Values.volume.storage }}
diff --git a/helm/redis-cluster/values.yaml b/helm/redis-cluster/values.yaml
new file mode 100644 (file)
index 0000000..39112d8
--- /dev/null
@@ -0,0 +1,53 @@
+###################################################################################
+#  ============LICENSE_START=======================================================
+#
+#  ================================================================================
+#  Copyright (C) 2020 Hcl Technologies Limited.
+#  ================================================================================
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+# ============LICENSE_END=========================================================
+###################################################################################
+
+#################################################################
+# Application configuration defaults.
+#################################################################
+
+rediscluster:
+  nodetimeout: 15000
+  replicaCount: 9
+  name: redis-cluster
+
+image:
+  repository: redis
+  tag: 5.0.1-alpine
+  pullPolicy: IfNotPresent
+
+container:
+  name: redis
+
+topology:
+  policyUnstisfiable: DoNotSchedule
+
+nameOverride: ""
+fullnameOverride: ""
+
+service:
+  type: ClusterIP
+
+volume:
+  storage: "1Gi"
+
+# Assigner is reponsible for assigning appropriate master/slave roles to redis nodes
+assigner:
+  name: assigner
+  label: assigner