# Deploy Elasticsearch Custom Resource with Terraform

**URL:** https://discuss.elastic.co/t/deploy-elasticsearch-custom-resource-with-terraform/314699
**Category:** Elastic Cloud on Kubernetes (ECK)
**Created:** [September 19, 2022, 1:24pm UTC](https://discuss.elastic.co/t/deploy-elasticsearch-custom-resource-with-terraform/314699 "2022-09-19T13:24:54Z")
**Posts on this page:** 2
**Page:** 1

<div class="post-metadata">

### Author: ![BenB196](https://sea2.discourse-cdn.com/elastic/user_avatar/discuss.elastic.co/benb196/32/83401_2.png) [@BenB196](https://discuss.elastic.co/u/BenB196)
#### Post date: [September 19, 2022, 1:24pm UTC](https://discuss.elastic.co/t/deploy-elasticsearch-custom-resource-with-terraform/314699/1 "2022-09-19T13:24:54Z")

</div>

(Preface - I recognize this question leans more towards Terraform, but wanted to ask it here as it mainly involves ECK/Elasticsearch).

Hi All,

I was wondering if anyone has a working example of using the [Terraform Kubernetes `kubernetes_manifest`](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/manifest), for deploying/updating an Elasticsearch cluster custom resource (CR).

I'm trying to deploy Elasticsearch clusters via Terraform, but have run into a bit of a snag and I'm unsure where/why the issue is occurring.

**Issue:**  
I am able to successfully deploy the Elasticsearch CR successfully the first time, but then if I go to change any value within the CR and have Terraform update it, rather than Terraform performing an update on the CR it instead tries to do a `replace`, which deletes the old Elasticsearch cluster and builds a new one.

**Setup:**  
Terraform `locals`:

```auto
locals {
  ELASTICSEARCH_API_VERSION = "elasticsearch.k8s.elastic.co/v1"
  ELASTICSEARCH_KIND = "Elasticsearch"

  ELASTICSEARCH_FILE = "${path.module}/environments/${var.ENVIRONMENT_NAME}/elasticsearch.yml.tftpl"
  ELASTICSEARCH_CONTENT = fileexists(local.ELASTICSEARCH_FILE) ? templatefile(local.ELASTICSEARCH_FILE, var.ELASTICSEARCH_TEMPLATE_ARGS) : "NoSettingsFileFound: true"
  ELASTICSEARCH_CONFIG = yamldecode(local.ELASTICSEARCH_CONTENT)
}

```

Terraform Elasticsearch `kubernetes_manifest`:

```auto
resource "kubernetes_manifest" "elasticsearch" {
  depends_on = [
    ...
  ]

  computed_fields = []

  manifest = {
    apiVersion = local.ELASTICSEARCH_API_VERSION
    kind = local.ELASTICSEARCH_KIND

    metadata = {
      name = var.ELASTICSEARCH_CLUSTER_NAME
      namespace = var.ELASTICSEARCH_NAMESPACE
    }

    spec = {
      version = var.ELASTICSEARCH_VERSION
      updateStrategy = local.ELASTICSEARCH_CONFIG[var.ELASTICSEARCH_CLUSTER_NAME].updateStrategy
      volumeClaimDeletePolicy = local.ELASTICSEARCH_CONFIG[var.ELASTICSEARCH_CLUSTER_NAME].volumeClaimDeletePolicy
      secureSettings = local.ELASTICSEARCH_SECURE_SETTINGS

      http = {
        service = {
          spec = {
            selector = local.ELASTICSEARCH_CONFIG[var.ELASTICSEARCH_CLUSTER_NAME].http.service.spec.selector
          }
        }
        tls = {
          certificate = {
            secretName = format("%s%s", "elasticsearch-cert", var.ENVIRONMENT_SUB_NAME != null ? "-${var.ENVIRONMENT_SUB_NAME}" : "")
          }
        }
      }

      podDisruptionBudget = {}

      nodeSets = [
        for node in local.ELASTICSEARCH_CONFIG[var.ELASTICSEARCH_CLUSTER_NAME].nodeSets : {
          name = node.name
          count = node.count

          config = try(merge(node.config, local.ELASTICSEARCH_CONFIG[var.ELASTICSEARCH_CLUSTER_NAME].baseNodeConfig), local.ELASTICSEARCH_CONFIG[var.ELASTICSEARCH_CLUSTER_NAME].baseNodeConfig)

          podTemplate = node.podTemplate

          volumeClaimTemplates = try(node.volumeClaimTemplates, null)
        }
      ]
    }
  }

  field_manager {
    name = "elastic-operator"
    force_conflicts = true
  }
}

```

`ELASTICSEARCH_CONTENT`

```auto
${CLUSTER_NAME}:
  updateStrategy:
    changeBudget:
      maxSurge: 3
      maxUnavailable: 1
  volumeClaimDeletePolicy: DeleteOnScaledownOnly
  http:
    service:
      spec:
        selector:
          elasticsearch.k8s.elastic.co/cluster-name: ${CLUSTER_NAME}
  baseNodeConfig:
    cluster:
      routing:
        allocation:
          awareness:
            attributes: k8s_node_name,zone
    xpack:
      security:
        audit:
          enabled: true
  nodeSets:
    - name: rack1
      count: 1
      podTemplate:
        spec:
          automountServiceAccountToken: true
          containers:
            - name: elasticsearch
              resources:
                limits:
                  cpu: "1"
                  memory: 1Gi
                requests:
                  cpu: "1"
                  memory: 1Gi
              volumeMounts:
                - mountPath: /usr/share/elasticsearch/config/certs
                  name: tls-ca
          initContainers:
            - command:
                - sh
                - -c
                - sysctl -w vm.max_map_count=262144
              name: sysctl
              securityContext:
                privileged: true
          volumes:
            - name: tls-ca
              secret:
                secretName: %{ if ENVIRONMENT_SUB_NAME != null }tls-ca-${ENVIRONMENT_SUB_NAME}%{ else }tls-ca%{ endif }
            - name: elasticsearch-data
              emptyDir:
                medium: ""

```

As mentioned this works fine for initial deployment, but when I try to make any changes (example: increase `requests.cpu` from `1` to `2`), Terraform thinks it needs to `replace` rather than update.

Example:

```auto
-/+ destroy and then create replacement
Terraform will perform the following actions:
  # kubernetes_manifest.elasticsearch must be replaced
-/+ resource "kubernetes_manifest" "elasticsearch" {
      ~ manifest = {
          ~ spec = {
              ~ nodeSets = [
                  ~ {
                        name = "rack1"
                      ~ podTemplate = {
                          ~ spec = {
                              ~ containers = [
                                  ~ {
                                        name = "elasticsearch"
                                      ~ resources = {
                                          ~ requests = {
                                              ~ cpu = "1" -> "2"
                                                # (1 unchanged element hidden)
                                            }
                                            # (1 unchanged element hidden)
                                        }
                                        # (1 unchanged element hidden)
                                    },
                                ]
                                # (4 unchanged elements hidden)
                            }
                        }
                        # (3 unchanged elements hidden)
                    },
...
      ~ object = {
          ~ metadata = {
              ~ annotations = {
                  - "eck.k8s.elastic.co/orchestration-hints" = jsonencode(
                        {
                          - no_transient_settings = true
                          - service_accounts = true
                        }
                    )
                  - "elasticsearch.k8s.elastic.co/cluster-uuid" = "kq-OmZ83Qj259Q6sTjgA6A"
                } -> (known after apply)
              ~ clusterName = null -> (known after apply)
              ~ creationTimestamp = null -> (known after apply)
              ~ deletionGracePeriodSeconds = null -> (known after apply)
              ~ deletionTimestamp = null -> (known after apply)
              ~ finalizers = null -> (known after apply)
              ~ generateName = null -> (known after apply)
              ~ generation = null -> (known after apply)
              ~ labels = null -> (known after apply)
              ~ managedFields = null -> (known after apply)
                name = "elasticsearch"
              ~ ownerReferences = null -> (known after apply)
              ~ resourceVersion = null -> (known after apply)
              ~ selfLink = null -> (known after apply)
              ~ uid = null -> (known after apply)
                # (1 unchanged element hidden)
            }
          ~ spec = {
              ~ auth = {
                  ~ fileRealm = null -> (known after apply)
                  ~ roles = null -> (known after apply)
                }
              ~ http = {
                  ~ service = {
                      ~ metadata = {
                          ~ annotations = null -> (known after apply)
                          ~ finalizers = null -> (known after apply)
                          ~ labels = null -> (known after apply)
                          ~ name = null -> (known after apply)
                          ~ namespace = null -> (known after apply)
                        }
                      ~ spec = {
                          ~ allocateLoadBalancerNodePorts = null -> (known after apply)
                          ~ clusterIP = null -> (known after apply)
                          ~ clusterIPs = null -> (known after apply)
                          ~ externalIPs = null -> (known after apply)
                          ~ externalName = null -> (known after apply)
                          ~ externalTrafficPolicy = null -> (known after apply)
                          ~ healthCheckNodePort = null -> (known after apply)
                          ~ internalTrafficPolicy = null -> (known after apply)
                          ~ ipFamilies = null -> (known after apply)
                          ~ ipFamilyPolicy = null -> (known after apply)
                          ~ loadBalancerClass = null -> (known after apply)
                          ~ loadBalancerIP = null -> (known after apply)
                          ~ loadBalancerSourceRanges = null -> (known after apply)
                          ~ ports = null -> (known after apply)
                          ~ publishNotReadyAddresses = null -> (known after apply)
                          ~ sessionAffinity = null -> (known after apply)
                          ~ sessionAffinityConfig = {
                              ~ clientIP = {
                                  ~ timeoutSeconds = null -> (known after apply)
                                }
                            }
                          ~ type = null -> (known after apply)
                            # (1 unchanged element hidden)
                        }
                    }
                  ~ tls = {
                      ~ selfSignedCertificate = {
                          ~ disabled = null -> (known after apply)
                          ~ subjectAltNames = null -> (known after apply)
                        }
                        # (1 unchanged element hidden)
                    }
                }
              ~ image = null -> (known after apply)
              ~ monitoring = {
                  ~ logs = {
                      ~ elasticsearchRefs = null -> (known after apply)
                    }
                  ~ metrics = {
                      ~ elasticsearchRefs = null -> (known after apply)
                    }
                }
              ~ nodeSets = [
                  ~ {
                        name = "rack1"
                      ~ podTemplate = {
                          ~ spec = {
                              ~ containers = [
                                  ~ {
                                        name = "elasticsearch"
                                      ~ resources = {
                                          ~ requests = {
                                              ~ cpu = "1" -> "2"
                                                # (1 unchanged element hidden)
                                            }
                                            # (1 unchanged element hidden)
                                        }
                                        # (1 unchanged element hidden)
                                    },
                                ]
                                # (4 unchanged elements hidden)
                            }
                        }
                      ~ volumeClaimTemplates = [
                          ~ {
                              ~ apiVersion = null -> (known after apply)
                              ~ kind = null -> (known after apply)
                              ~ metadata = {
                                  ~ annotations = null -> (known after apply)
                                  ~ finalizers = null -> (known after apply)
                                  ~ labels = null -> (known after apply)
                                    name = "elasticsearch-data"
                                  ~ namespace = null -> (known after apply)
                                }
                              ~ spec = {
                                  ~ dataSource = {
                                      ~ apiGroup = null -> (known after apply)
                                      ~ kind = null -> (known after apply)
                                      ~ name = null -> (known after apply)
                                    }
                                  ~ dataSourceRef = {
                                      ~ apiGroup = null -> (known after apply)
                                      ~ kind = null -> (known after apply)
                                      ~ name = null -> (known after apply)
                                    }
                                  ~ resources = {
                                      ~ limits = null -> (known after apply)
                                        # (1 unchanged element hidden)
                                    }
                                  ~ selector = {
                                      ~ matchExpressions = null -> (known after apply)
                                      ~ matchLabels = null -> (known after apply)
                                    }
                                  ~ volumeMode = null -> (known after apply)
                                  ~ volumeName = null -> (known after apply)
                                    # (2 unchanged elements hidden)
                                }
                              ~ status = {
                                  ~ accessModes = null -> (known after apply)
                                  ~ allocatedResources = null -> (known after apply)
                                  ~ capacity = null -> (known after apply)
                                  ~ conditions = null -> (known after apply)
                                  ~ phase = null -> (known after apply)
                                  ~ resizeStatus = null -> (known after apply)
                                }
                            },
                        ]
                        # (2 unchanged elements hidden)
                    },
                  ~ {
                        name = "rack2"
                      ~ volumeClaimTemplates = [
                          ~ {
                              ~ apiVersion = null -> (known after apply)
                              ~ kind = null -> (known after apply)
                              ~ metadata = {
                                  ~ annotations = null -> (known after apply)
                                  ~ finalizers = null -> (known after apply)
                                  ~ labels = null -> (known after apply)
                                    name = "elasticsearch-data"
                                  ~ namespace = null -> (known after apply)
                                }
                              ~ spec = {
                                  ~ dataSource = {
                                      ~ apiGroup = null -> (known after apply)
                                      ~ kind = null -> (known after apply)
                                      ~ name = null -> (known after apply)
                                    }
                                  ~ dataSourceRef = {
                                      ~ apiGroup = null -> (known after apply)
                                      ~ kind = null -> (known after apply)
                                      ~ name = null -> (known after apply)
                                    }
                                  ~ resources = {
                                      ~ limits = null -> (known after apply)
                                        # (1 unchanged element hidden)
                                    }
                                  ~ selector = {
                                      ~ matchExpressions = null -> (known after apply)
                                      ~ matchLabels = null -> (known after apply)
                                    }
                                  ~ volumeMode = null -> (known after apply)
                                  ~ volumeName = null -> (known after apply)
                                    # (2 unchanged elements hidden)
                                }
                              ~ status = {
                                  ~ accessModes = null -> (known after apply)
                                  ~ allocatedResources = null -> (known after apply)
                                  ~ capacity = null -> (known after apply)
                                  ~ conditions = null -> (known after apply)
                                  ~ phase = null -> (known after apply)
                                  ~ resizeStatus = null -> (known after apply)
                                }
                            },
                        ]
                        # (3 unchanged elements hidden)
                    },
                  ~ {
                        name = "rack5"
                      ~ volumeClaimTemplates = [
                          ~ {
                              ~ apiVersion = null -> (known after apply)
                              ~ kind = null -> (known after apply)
                              ~ metadata = {
                                  ~ annotations = null -> (known after apply)
                                  ~ finalizers = null -> (known after apply)
                                  ~ labels = null -> (known after apply)
                                    name = "elasticsearch-data"
                                  ~ namespace = null -> (known after apply)
                                }
                              ~ spec = {
                                  ~ dataSource = {
                                      ~ apiGroup = null -> (known after apply)
                                      ~ kind = null -> (known after apply)
                                      ~ name = null -> (known after apply)
                                    }
                                  ~ dataSourceRef = {
                                      ~ apiGroup = null -> (known after apply)
                                      ~ kind = null -> (known after apply)
                                      ~ name = null -> (known after apply)
                                    }
                                  ~ resources = {
                                      ~ limits = null -> (known after apply)
                                        # (1 unchanged element hidden)
                                    }
                                  ~ selector = {
                                      ~ matchExpressions = null -> (known after apply)
                                      ~ matchLabels = null -> (known after apply)
                                    }
                                  ~ volumeMode = null -> (known after apply)
                                  ~ volumeName = null -> (known after apply)
                                    # (2 unchanged elements hidden)
                                }
                              ~ status = {
                                  ~ accessModes = null -> (known after apply)
                                  ~ allocatedResources = null -> (known after apply)
                                  ~ capacity = null -> (known after apply)
                                  ~ conditions = null -> (known after apply)
                                  ~ phase = null -> (known after apply)
                                  ~ resizeStatus = null -> (known after apply)
                                }
                            },
                        ]
                        # (3 unchanged elements hidden)
                    },
                ]
              ~ podDisruptionBudget = {
                  ~ metadata = {
                      ~ annotations = null -> (known after apply)
                      ~ finalizers = null -> (known after apply)
                      ~ labels = null -> (known after apply)
                      ~ name = null -> (known after apply)
                      ~ namespace = null -> (known after apply)
                    }
                  ~ spec = {
                      ~ maxUnavailable = null -> (known after apply)
                      ~ minAvailable = null -> (known after apply)
                      ~ selector = {
                          ~ matchExpressions = null -> (known after apply)
                          ~ matchLabels = null -> (known after apply)
                        }
                    }
                }
              ~ remoteClusters = null -> (known after apply)
              ~ revisionHistoryLimit = null -> (known after apply)
              ~ secureSettings = [
                  ~ {
                      ~ entries = null -> (known after apply)
                        # (1 unchanged element hidden)
                    },
                  ~ {
                      ~ entries = null -> (known after apply)
                        # (1 unchanged element hidden)
                    },
                  ~ {
                      ~ entries = null -> (known after apply)
                        # (1 unchanged element hidden)
                    },
                ]
              ~ serviceAccountName = null -> (known after apply)
              ~ transport = {
                  ~ service = {
                      ~ metadata = {
                          ~ annotations = null -> (known after apply)
                          ~ finalizers = null -> (known after apply)
                          ~ labels = null -> (known after apply)
                          ~ name = null -> (known after apply)
                          ~ namespace = null -> (known after apply)
                        }
                      ~ spec = {
                          ~ allocateLoadBalancerNodePorts = null -> (known after apply)
                          ~ clusterIP = null -> (known after apply)
                          ~ clusterIPs = null -> (known after apply)
                          ~ externalIPs = null -> (known after apply)
                          ~ externalName = null -> (known after apply)
                          ~ externalTrafficPolicy = null -> (known after apply)
                          ~ healthCheckNodePort = null -> (known after apply)
                          ~ internalTrafficPolicy = null -> (known after apply)
                          ~ ipFamilies = null -> (known after apply)
                          ~ ipFamilyPolicy = null -> (known after apply)
                          ~ loadBalancerClass = null -> (known after apply)
                          ~ loadBalancerIP = null -> (known after apply)
                          ~ loadBalancerSourceRanges = null -> (known after apply)
                          ~ ports = null -> (known after apply)
                          ~ publishNotReadyAddresses = null -> (known after apply)
                          ~ selector = null -> (known after apply)
                          ~ sessionAffinity = null -> (known after apply)
                          ~ sessionAffinityConfig = {
                              ~ clientIP = {
                                  ~ timeoutSeconds = null -> (known after apply)
                                }
                            }
                          ~ type = null -> (known after apply)
                        }
                    }
                  ~ tls = {
                      ~ certificate = {
                          ~ secretName = null -> (known after apply)
                        }
                      ~ otherNameSuffix = null -> (known after apply)
                      ~ subjectAltNames = null -> (known after apply)
                    }
                }
                # (3 unchanged elements hidden)
            }
            # (2 unchanged elements hidden)
        }
        # (1 unchanged attribute hidden)
        # (1 unchanged block hidden)
    }
Plan: 1 to add, 0 to change, 1 to destroy.

```

---

<div class="post-metadata">

### Author: ![system](https://us1.discourse-cdn.com/elastic/original/3X/1/a/1ac57faf039f6b580b3f104ef42a2a89e41014de.png) [@system](https://discuss.elastic.co/u/system)
#### Post date: [October 17, 2022, 1:25pm UTC](https://discuss.elastic.co/t/deploy-elasticsearch-custom-resource-with-terraform/314699/2 "2022-10-17T13:25:06Z")

</div>

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.
