github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/resources/deployments/elasticsearch.go (about)

     1  // Copyright (C) 2020, 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package deployments
     5  
     6  import (
     7  	"fmt"
     8  
     9  	vmcontrollerv1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1"
    10  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/config"
    11  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/constants"
    12  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources"
    13  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources/nodes"
    14  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/util/memory"
    15  
    16  	"go.uber.org/zap"
    17  	appsv1 "k8s.io/api/apps/v1"
    18  	corev1 "k8s.io/api/core/v1"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  )
    21  
    22  // ElasticsearchBasic function type
    23  type ElasticsearchBasic struct {
    24  }
    25  
    26  // IsOpenSearchDataDeployment checks template label to see if a given deployment is an OpenSearch data deployment
    27  func IsOpenSearchDataDeployment(vmoName string, deployment *appsv1.Deployment) bool {
    28  	return deployment.Spec.Template.Labels[constants.ServiceAppLabel] == vmoName+"-"+config.ElasticsearchData.Name
    29  }
    30  
    31  // Returns a common base deployment structure for all Elasticsearch components
    32  func (es ElasticsearchBasic) createCommonDeployment(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, node vmcontrollerv1.ElasticsearchNode, componentDetails config.ComponentDetails, index int) *appsv1.Deployment {
    33  
    34  	deploymentElement := createDeploymentElementByPvcIndex(vmo, node.Storage, &node.Resources, componentDetails, index, node.Name)
    35  	esContainer := &deploymentElement.Spec.Template.Spec.Containers[0]
    36  	esContainer.Env = append(esContainer.Env,
    37  		corev1.EnvVar{
    38  			Name: "NAMESPACE",
    39  			ValueFrom: &corev1.EnvVarSource{
    40  				FieldRef: &corev1.ObjectFieldSelector{
    41  					FieldPath: "metadata.namespace",
    42  				},
    43  			},
    44  		},
    45  		corev1.EnvVar{
    46  			Name: "node.name",
    47  			ValueFrom: &corev1.EnvVarSource{
    48  				FieldRef: &corev1.ObjectFieldSelector{
    49  					FieldPath: "metadata.name",
    50  				},
    51  			},
    52  		},
    53  		corev1.EnvVar{Name: "cluster.name", Value: vmo.Name},
    54  		corev1.EnvVar{Name: "logger.org.opensearch", Value: "info"},
    55  	)
    56  
    57  	esContainer.Ports = []corev1.ContainerPort{
    58  		{Name: "http", ContainerPort: int32(constants.OSHTTPPort)},
    59  		{Name: "transport", ContainerPort: int32(constants.OSTransportPort)},
    60  	}
    61  
    62  	// Common Elasticsearch readiness and liveness settings
    63  	if esContainer.LivenessProbe != nil {
    64  		esContainer.LivenessProbe.InitialDelaySeconds = 60
    65  		esContainer.LivenessProbe.TimeoutSeconds = 3
    66  		esContainer.LivenessProbe.PeriodSeconds = 20
    67  		esContainer.LivenessProbe.FailureThreshold = 5
    68  	}
    69  	if esContainer.ReadinessProbe != nil {
    70  		esContainer.ReadinessProbe.InitialDelaySeconds = 60
    71  		esContainer.ReadinessProbe.TimeoutSeconds = 3
    72  		esContainer.ReadinessProbe.PeriodSeconds = 10
    73  		esContainer.ReadinessProbe.FailureThreshold = 10
    74  	}
    75  
    76  	// Add init containers
    77  	deploymentElement.Spec.Template.Spec.InitContainers = append(deploymentElement.Spec.Template.Spec.InitContainers, *resources.GetElasticsearchInitContainer())
    78  
    79  	// Add node labels
    80  	deploymentElement.Spec.Selector.MatchLabels[constants.NodeGroupLabel] = node.Name
    81  	deploymentElement.Spec.Template.Labels[constants.NodeGroupLabel] = node.Name
    82  	nodes.SetNodeRoleLabels(&node, deploymentElement.Labels)
    83  	nodes.SetNodeRoleLabels(&node, deploymentElement.Spec.Template.Labels)
    84  
    85  	var elasticsearchUID int64 = 1000
    86  	esContainer.SecurityContext.RunAsUser = &elasticsearchUID
    87  	return deploymentElement
    88  }
    89  
    90  // Creates all Elasticsearch Client deployment elements
    91  func (es ElasticsearchBasic) createElasticsearchIngestDeploymentElements(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) []*appsv1.Deployment {
    92  	var deployments []*appsv1.Deployment
    93  	nodeList := nodes.IngestNodes(vmo)
    94  	for i, node := range nodeList {
    95  		if node.Replicas < 1 {
    96  			continue
    97  		}
    98  		// Default JVM heap settings if none provided
    99  		javaOpts, err := memory.PodMemToJvmHeapArgs(node.Resources.RequestMemory, constants.DefaultESIngestMemArgs)
   100  		if err != nil {
   101  			javaOpts = constants.DefaultESIngestMemArgs
   102  			zap.S().Errorf("Failed to derive heap sizes from IngestNodes pod, using default %s: %v", javaOpts, err)
   103  		}
   104  		if node.JavaOpts != "" {
   105  			javaOpts = node.JavaOpts
   106  		}
   107  
   108  		ingestDeployment := es.createCommonDeployment(vmo, node, config.ElasticsearchIngest, -1)
   109  		ingestDeployment.Spec.Replicas = resources.NewVal(node.Replicas)
   110  
   111  		// Anti-affinity on other client zones
   112  		ingestDeployment.Spec.Template.Spec.Affinity = resources.CreateZoneAntiAffinityElement(vmo.Name, config.ElasticsearchIngest.Name)
   113  		ingestDeployment.Spec.Template.Spec.Containers[0].Env = append(ingestDeployment.Spec.Template.Spec.Containers[0].Env,
   114  			corev1.EnvVar{Name: "discovery.seed_hosts", Value: resources.GetMetaName(vmo.Name, config.ElasticsearchMaster.Name)},
   115  			corev1.EnvVar{Name: "NETWORK_HOST", Value: "0.0.0.0"},
   116  			corev1.EnvVar{Name: "node.roles", Value: nodes.GetRolesString(&nodeList[i])},
   117  			corev1.EnvVar{Name: "OPENSEARCH_JAVA_OPTS", Value: javaOpts},
   118  		)
   119  		// add the required istio annotations to allow inter-es component communication
   120  		if ingestDeployment.Spec.Template.Annotations == nil {
   121  			ingestDeployment.Spec.Template.Annotations = make(map[string]string)
   122  		}
   123  		// Adding command to install OS plugins at pod bootup
   124  		ingestDeployment.Spec.Template.Spec.Containers[0].Command = []string{
   125  			"sh",
   126  			"-c",
   127  			fmt.Sprintf(resources.OpenSearchIngestCmdTmpl, resources.GetOSPluginsInstallTmpl(resources.GetOpenSearchPluginList(vmo), resources.OSPluginsInstallCmd)),
   128  		}
   129  		ingestDeployment.Spec.Template.Annotations["traffic.sidecar.istio.io/excludeInboundPorts"] = fmt.Sprintf("%d", constants.OSTransportPort)
   130  		ingestDeployment.Spec.Template.Annotations["traffic.sidecar.istio.io/excludeOutboundPorts"] = fmt.Sprintf("%d", constants.OSTransportPort)
   131  		deployments = append(deployments, ingestDeployment)
   132  	}
   133  	return deployments
   134  }
   135  
   136  // Creates all Elasticsearch DataNodes deployment elements
   137  func (es ElasticsearchBasic) createElasticsearchDataDeploymentElements(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, pvcToAdMap map[string]string) []*appsv1.Deployment {
   138  	var deployments []*appsv1.Deployment
   139  	nodeList := nodes.DataNodes(vmo)
   140  	for idx, node := range nodeList {
   141  		if node.Replicas < 1 {
   142  			continue
   143  		}
   144  		// Default JVM heap settings if none provided
   145  		javaOpts, err := memory.PodMemToJvmHeapArgs(node.Resources.RequestMemory, constants.DefaultESDataMemArgs)
   146  		if err != nil {
   147  			javaOpts = constants.DefaultESDataMemArgs
   148  			zap.S().Errorf("Failed to derive heap sizes from DataNodes pod, using default %s: %v", javaOpts, err)
   149  		}
   150  		if node.JavaOpts != "" {
   151  			javaOpts = node.JavaOpts
   152  		}
   153  		for i := 0; i < int(node.Replicas); i++ {
   154  			dataDeployment := es.createCommonDeployment(vmo, node, config.ElasticsearchData, i)
   155  
   156  			dataDeployment.Spec.Replicas = resources.NewVal(1)
   157  			availabilityDomain := getAvailabilityDomainForPvcIndex(node.Storage, pvcToAdMap, i)
   158  			if availabilityDomain == "" {
   159  				// With shard allocation awareness, we must provide something for the AD, even in the case of the simple
   160  				// VMO with no persistence volumes
   161  				availabilityDomain = "None"
   162  			}
   163  
   164  			// Anti-affinity on other data pod *nodes* (try out best to spread across many nodes)
   165  			dataDeployment.Spec.Template.Spec.Affinity = &corev1.Affinity{
   166  				PodAntiAffinity: &corev1.PodAntiAffinity{
   167  					PreferredDuringSchedulingIgnoredDuringExecution: []corev1.WeightedPodAffinityTerm{
   168  						{
   169  							Weight: 100,
   170  							PodAffinityTerm: corev1.PodAffinityTerm{
   171  								LabelSelector: &metav1.LabelSelector{
   172  									MatchLabels: resources.GetSpecID(vmo.Name, config.ElasticsearchData.Name),
   173  								},
   174  								TopologyKey: "kubernetes.io/hostname",
   175  							},
   176  						},
   177  					},
   178  				},
   179  			}
   180  			// When the deployment does not have a pod security context with an FSGroup attribute, any mounted volumes are
   181  			// initially owned by root/root.  Previous versions of the ES image were run as "root", and chown'd the mounted
   182  			// directory to "elasticsearch", but we don't want to run as "root".  The current ES image creates a group
   183  			// "elasticsearch" (GID 1000), and a user "elasticsearch" (UID 1000) in that group.  When we provide FSGroup =
   184  			// 1000 below, the volume is owned by root/elasticsearch, with permissions "rwxrwsr-x".  This allows the ES
   185  			// image to run as UID 1000, and have sufficient permissions to write to the mounted volume.
   186  			elasticsearchGid := int64(1000)
   187  			dataDeployment.Spec.Template.Spec.SecurityContext = &corev1.PodSecurityContext{
   188  				FSGroup: &elasticsearchGid,
   189  			}
   190  
   191  			dataDeployment.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
   192  			dataDeployment.Spec.Strategy.RollingUpdate = nil
   193  			dataDeployment.Spec.Template.Spec.Containers[0].Env = append(dataDeployment.Spec.Template.Spec.Containers[0].Env,
   194  				corev1.EnvVar{Name: "discovery.seed_hosts", Value: resources.GetMetaName(vmo.Name, config.ElasticsearchMaster.Name)},
   195  				corev1.EnvVar{Name: "node.attr.availability_domain", Value: availabilityDomain},
   196  				corev1.EnvVar{Name: "node.roles", Value: nodes.GetRolesString(&nodeList[idx])},
   197  				corev1.EnvVar{Name: "OPENSEARCH_JAVA_OPTS", Value: javaOpts},
   198  				corev1.EnvVar{Name: constants.ObjectStoreAccessKeyVarName,
   199  					ValueFrom: &corev1.EnvVarSource{
   200  						SecretKeyRef: &corev1.SecretKeySelector{
   201  							LocalObjectReference: corev1.LocalObjectReference{
   202  								Name: constants.VerrazzanoBackupScrtName,
   203  							},
   204  							Key: constants.ObjectStoreAccessKey,
   205  							Optional: func(opt bool) *bool {
   206  								return &opt
   207  							}(true),
   208  						},
   209  					},
   210  				},
   211  				corev1.EnvVar{Name: constants.ObjectStoreCustomerKeyVarName,
   212  					ValueFrom: &corev1.EnvVarSource{
   213  						SecretKeyRef: &corev1.SecretKeySelector{
   214  							LocalObjectReference: corev1.LocalObjectReference{
   215  								Name: constants.VerrazzanoBackupScrtName,
   216  							},
   217  							Key: constants.ObjectStoreCustomerKey,
   218  							Optional: func(opt bool) *bool {
   219  								return &opt
   220  							}(true),
   221  						},
   222  					},
   223  				},
   224  			)
   225  
   226  			// Adding command for add keystore values and OS plugins installation at pod bootup
   227  			dataDeployment.Spec.Template.Spec.Containers[0].Command = []string{
   228  				"sh",
   229  				"-c",
   230  				resources.CreateOpenSearchContainerCMD(javaOpts, resources.GetOpenSearchPluginList(vmo)),
   231  			}
   232  
   233  			// add the required istio annotations to allow inter-es component communication
   234  			if dataDeployment.Spec.Template.Annotations == nil {
   235  				dataDeployment.Spec.Template.Annotations = make(map[string]string)
   236  			}
   237  			dataDeployment.Spec.Template.Annotations["traffic.sidecar.istio.io/excludeInboundPorts"] = fmt.Sprintf("%d", constants.OSTransportPort)
   238  			dataDeployment.Spec.Template.Annotations["traffic.sidecar.istio.io/excludeOutboundPorts"] = fmt.Sprintf("%d", constants.OSTransportPort)
   239  			deployments = append(deployments, dataDeployment)
   240  		}
   241  	}
   242  	return deployments
   243  }
   244  
   245  // Creates *all* Elasticsearch deployment elements
   246  func (es ElasticsearchBasic) createElasticsearchDeploymentElements(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, pvcToAdMap map[string]string) []*appsv1.Deployment {
   247  	var deployList []*appsv1.Deployment
   248  	deployList = append(deployList, es.createElasticsearchIngestDeploymentElements(vmo)...)
   249  	deployList = append(deployList, es.createElasticsearchDataDeploymentElements(vmo, pvcToAdMap)...)
   250  	return deployList
   251  }