<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[NoSQL Nest]]></title><description><![CDATA[NoSQL Nest]]></description><link>https://nosqlnest.net</link><image><url>https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/logos/697f8be5de00799cb34a04ca/260572f0-d3cb-4387-8a95-a5835dbfbeb5.png</url><title>NoSQL Nest</title><link>https://nosqlnest.net</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 09 Apr 2026 16:53:08 GMT</lastBuildDate><atom:link href="https://nosqlnest.net/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[From Zero to Green: Automating a Production-Ready, Secured 3-Node Elasticsearch Cluster]]></title><description><![CDATA[Chapter 1: Architecture & Design
Before writing a single line of code, we must understand the "Physical" and "Logical" layout of the cluster we are building, This project deploys a hyper-converged, 3-node Elasticsearch cluster using Vagrant and Virtu...]]></description><link>https://nosqlnest.net/from-zero-to-green-automating-a-production-ready-secured-3-node-elasticsearch-cluster</link><guid isPermaLink="true">https://nosqlnest.net/from-zero-to-green-automating-a-production-ready-secured-3-node-elasticsearch-cluster</guid><category><![CDATA[elasticsearch]]></category><category><![CDATA[production]]></category><category><![CDATA[deployment]]></category><category><![CDATA[NoSQL]]></category><category><![CDATA[#NoSQLDatabase]]></category><category><![CDATA[NoSQL databases]]></category><category><![CDATA[NoSQL Database]]></category><category><![CDATA[Search engine optimization]]></category><category><![CDATA[Search Engines]]></category><category><![CDATA[searchengineoptimization]]></category><category><![CDATA[search]]></category><dc:creator><![CDATA[AKEBLI Ouassim]]></dc:creator><pubDate>Tue, 17 Feb 2026 08:48:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770936147127/2a2f5aaa-04f0-4e05-87a8-9eb4a16b2b21.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-chapter-1-architecture-amp-design">Chapter 1: Architecture &amp; Design</h2>
<p>Before writing a single line of code, we must understand the "Physical" and "Logical" layout of the cluster we are building, This project deploys a hyper-converged, 3-node Elasticsearch cluster using Vagrant and VirtualBox. The infrastructure simulates a production environment with dedicated networks, storage partitioning, and full SSL security.</p>
<h3 id="heading-11-environment-specification">1.1 Environment Specification</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Component</td><td>Specification</td><td>Details</td></tr>
</thead>
<tbody>
<tr>
<td><strong>OS Base</strong></td><td>Ubuntu 22.04 LTS (Jammy64)</td><td>Standardized base box.</td></tr>
<tr>
<td><strong>Compute</strong></td><td>3 Nodes (<code>es1</code>, <code>es2</code>, <code>es3</code>)</td><td><strong>4 GB RAM</strong>, <strong>2 vCPUs</strong> per node.</td></tr>
<tr>
<td><strong>Storage</strong></td><td>15GB Dedicated LVM Disk</td><td>Partitioned for <code>/mnt</code> (System), <code>home</code>, <code>data</code>, <code>logs</code>, and <code>backup</code>.</td></tr>
<tr>
<td><strong>Network</strong></td><td>Dual Interface</td><td><code>NAT</code> (Mgmt/SSH) + <code>Host-Only</code> (Cluster Traffic <code>192.168.56.x</code>).</td></tr>
<tr>
<td><strong>Software</strong></td><td>Elasticsearch 9.3.0</td><td>Manual Tarball Installation (Archive).</td></tr>
<tr>
<td><strong>Security</strong></td><td>xPack Security Enabled</td><td>Full SSL/TLS for Inter-node (Transport) and Client (HTTP) traffic.</td></tr>
</tbody>
</table>
</div><hr />
<h3 id="heading-12-logical-architecture">1.2 Logical Architecture</h3>
<p>This diagram illustrates the physical layout of the cluster. It highlights the "Hub-and-Spoke" network topology where all nodes connect via a private Virtual Switch, and the Storage Architecture where <code>es1</code> acts as the central NFS repository for backups.</p>
<pre><code class="lang-mermaid">graph TD
    %% === HOST LAYER ===
    subgraph HostLayer ["&lt;b&gt;Host Machine&lt;/b&gt;&lt;br&gt;32GB RAM / 8 Cores&lt;br&gt;Provider: VirtualBox"]
        style HostLayer fill:#f9f9f9,stroke:#333,stroke-width:2px

        %% --- VIRTUAL SWITCHING ---
        subgraph Switches ["&lt;b&gt;Virtual Network Infrastructure&lt;/b&gt;"]
            style Switches fill:#ffffff,stroke:#999,stroke-dasharray: 5 5

            VS_NAT["&lt;b&gt;NAT Switch&lt;/b&gt;&lt;br&gt;DHCP / Internet Access&lt;br&gt;Port Forwarding: SSH (2222-&gt;22)"]
            style VS_NAT fill:#dbeeff,stroke:#0055bb,color:#0055bb

            VS_PROD["&lt;b&gt;Host-Only Switch&lt;/b&gt;&lt;br&gt;Subnet: 192.168.56.0/24&lt;br&gt;Gateway: 192.168.56.1"]
            style VS_PROD fill:#e0ffe0,stroke:#007700,color:#007700
        end

        %% ============================================================
        %% NODE 1 (MASTER / NFS SERVER)
        %% ============================================================
        subgraph ES1 ["&lt;b&gt;VM: es1&lt;/b&gt; (4GB RAM / 2 vCPU)&lt;br&gt;OS: Ubuntu 22.04 LTS"]
            style ES1 fill:#fff,stroke:#333

            subgraph ES1_OS ["&lt;b&gt;OS Configuration&lt;/b&gt;"]
                style ES1_OS fill:#f4f4f4,stroke:none
                K1["sysctl: vm.max_map_count=262144&lt;br&gt;ulimit: nofile=65535, memlock=inf&lt;br&gt;User: elasticsearch (1122)"]
            end

            subgraph ES1_App ["&lt;b&gt;JVM: Elasticsearch 9.3.0&lt;/b&gt;"]
                style ES1_App fill:#ffeae6,stroke:#cc5500
                JVM1["Heap: 2GB (Xms2g Xmx2g)&lt;br&gt;GC: G1GC&lt;br&gt;&lt;b&gt;bootstrap.memory_lock: true&lt;/b&gt;"]
                Roles1["Node Roles: Master, Data, Ingest&lt;br&gt;SSL: Enabled (Transport &amp; HTTP)"]
            end

            subgraph ES1_Store ["&lt;b&gt;LVM: vg01 (15GB XFS)&lt;/b&gt;"]
                style ES1_Store fill:#e1e1e1,stroke:none
                D1_Data["/mnt/../data (4GB)&lt;br&gt;(Indices &amp; Shards)"]
                D1_Logs["/mnt/../logs (1GB)"]
                D1_home["/mnt/../home (2GB)"]
                D1_NFS["&lt;b&gt;/mnt/../backup (4GB)&lt;/b&gt;&lt;br&gt;Type: Physical Dir&lt;br&gt;&lt;b&gt;Status: NFS EXPORT&lt;/b&gt;"]
                style D1_NFS fill:#fffec8,stroke:#dba900,stroke-width:2px
            end

            subgraph ES1_Net ["&lt;b&gt;Network Interfaces&lt;/b&gt;"]
                style ES1_Net fill:none,stroke:none
                N1_NAT["enp0s3 (DHCP)&lt;br&gt;Mgmt"]
                N1_PROD["prod (Static)&lt;br&gt;IP: 192.168.56.11&lt;br&gt;Ports: 9200, 9300, 2049"]
            end
        end

        %% ============================================================
        %% NODE 2 (DATA NODE)
        %% ============================================================
        subgraph ES2 ["&lt;b&gt;VM: es2&lt;/b&gt; (4GB RAM / 2 vCPU)"]
            style ES2 fill:#fff,stroke:#333

            subgraph ES2_App ["&lt;b&gt;JVM: Elasticsearch 9.3.0&lt;/b&gt;"]
                style ES2_App fill:#ffeae6,stroke:#cc5500
                JVM2["Heap: 2GB (Locked)"]
            end

            subgraph ES2_Store ["&lt;b&gt;LVM: vg01 (15GB XFS)&lt;/b&gt;"]
                style ES2_Store fill:#e1e1e1,stroke:none
                D2_Data["/mnt/../data (4GB)"]
                D2_Logs["/mnt/../logs (1GB)"]
                D2_home["/mnt/../home (2GB)"]
                D2_NFS["&lt;b&gt;/mnt/../backup&lt;/b&gt;&lt;br&gt;Type: NFS Mount&lt;br&gt;Source: 192.168.56.11"]
                style D2_NFS fill:#fffec8,stroke:#dba900,stroke-dasharray: 5 5
            end

            subgraph ES2_Net ["&lt;b&gt;Network Interfaces&lt;/b&gt;"]
                style ES2_Net fill:none,stroke:none
                N2_NAT["enp0s3 (DHCP)"]
                N2_PROD["prod (Static)&lt;br&gt;IP: 192.168.56.12&lt;br&gt;Ports: 9200, 9300"]
            end
        end

        %% ============================================================
        %% NODE 3 (DATA NODE)
        %% ============================================================
        subgraph ES3 ["&lt;b&gt;VM: es3&lt;/b&gt; (4GB RAM / 2 vCPU)"]
            style ES3 fill:#fff,stroke:#333

            subgraph ES3_App ["&lt;b&gt;JVM: Elasticsearch 9.3.0&lt;/b&gt;"]
                style ES3_App fill:#ffeae6,stroke:#cc5500
                JVM3["Heap: 2GB (Locked)"]
            end

            subgraph ES3_Store ["&lt;b&gt;LVM: vg01 (15GB XFS)&lt;/b&gt;"]
                style ES3_Store fill:#e1e1e1,stroke:none
                D3_Data["/mnt/../data (4GB)"]
                D3_Logs["/mnt/../logs (1GB)"]
                D3_home["/mnt/../home (2GB)"]
                D3_NFS["&lt;b&gt;/mnt/../backup&lt;/b&gt;&lt;br&gt;Type: NFS Mount&lt;br&gt;Source: 192.168.56.11"]
                style D3_NFS fill:#fffec8,stroke:#dba900,stroke-dasharray: 5 5
            end

            subgraph ES3_Net ["&lt;b&gt;Network Interfaces&lt;/b&gt;"]
                style ES3_Net fill:none,stroke:none
                N3_NAT["enp0s3 (DHCP)"]
                N3_PROD["prod (Static)&lt;br&gt;IP: 192.168.56.13&lt;br&gt;Ports: 9200, 9300"]
            end
        end
    end

    %% === CONNECTIONS ===

    %% 1. SSH / Management (Blue)
    VS_NAT -- "SSH / APT" --&gt; N1_NAT
    VS_NAT -- "SSH / APT" --&gt; N2_NAT
    VS_NAT -- "SSH / APT" --&gt; N3_NAT
    linkStyle 0,1,2 stroke:#0055bb,stroke-width:2px

    %% 2. Cluster Traffic (Green)
    N1_PROD == "TCP 9300 (Transport SSL)" ==&gt; VS_PROD
    N2_PROD == "TCP 9300 (Transport SSL)" ==&gt; VS_PROD
    N3_PROD == "TCP 9300 (Transport SSL)" ==&gt; VS_PROD
    linkStyle 3,4,5 stroke:#007700,stroke-width:3px

    %% 3. NFS Data Flow (Yellow / Dashed)
    D1_NFS -.-&gt; N1_PROD
    VS_PROD -. "TCP 2049 (NFSv4)" .-&gt; N2_PROD
    VS_PROD -. "TCP 2049 (NFSv4)" .-&gt; N3_PROD

    N2_PROD -.-&gt; D2_NFS
    N3_PROD -.-&gt; D3_NFS

    linkStyle 6,7,8,9,10 stroke:#dba900,stroke-width:2px,stroke-dasharray: 5 5
</code></pre>
<hr />
<h3 id="heading-13-provisioning-workflow">1.3 Provisioning Workflow</h3>
<p>The deployment is orchestrated via 12 modular shell scripts. This diagram details the execution flow, specifically the critical <strong>Phase 3 (Script 09)</strong> where nodes synchronize via the NFS share to securely generate and distribute SSL certificates without manual intervention.</p>
<pre><code class="lang-mermaid">sequenceDiagram
    autonumber
    participant V as Vagrant Host
    participant N1 as ES1 Master
    participant N23 as ES2 and ES3
    participant NFS as NFS Share

    Note over V, N23: === PHASE 1 - OS PREPARATION Parallel ===

    par Run Scripts 01-05
        V-&gt;&gt;N1: "Provision 01-05"
        V-&gt;&gt;N23: "Provision 01-05"
    and
        N1-&gt;&gt;N1: "Install Pkgs - Create User - LVM Setup"
        N23-&gt;&gt;N23: "Install Pkgs - Create User - LVM Setup"
    end

    Note over V, N23: === PHASE 2 - INFRASTRUCTURE Sequential Logic ===

    V-&gt;&gt;N1: "Run 06-nfs.sh"
    N1-&gt;&gt;N1: "Start NFS Server and Export backup dir"

    V-&gt;&gt;N23: "Run 06-nfs.sh"
    N23-&gt;&gt;N1: "Mount NFS Share from 192.168.56.11"

    par Run Scripts 07-08
        V-&gt;&gt;N1: "07-Network and 08-Install"
        V-&gt;&gt;N23: "07-Network and 08-Install"
    and
        N1-&gt;&gt;N1: "Rename NIC to prod - Unpack Tarball"
        N23-&gt;&gt;N23: "Rename NIC to prod - Unpack Tarball"
    end

    Note over V, N23: === PHASE 3 - SECURITY BARRIER Script 09 ===

    rect rgb(255, 240, 240)
        Note right of V: Critical Synchronization Point

        V-&gt;&gt;N1: "Run 09-certs.sh"
        N1-&gt;&gt;NFS: "Generate CA elastic-stack-ca.p12"
        N1-&gt;&gt;NFS: "Generate Node Certs"

        V-&gt;&gt;N23: "Run 09-certs.sh"

        loop Wait for CA
            N23-&gt;&gt;NFS: "Check if ca.p12 exists"
            NFS--&gt;&gt;N23: "Not yet... sleep 2s"
        end

        NFS--&gt;&gt;N23: "CA Found"
        N23-&gt;&gt;N23: "Generate Node Certs using CA"
        N23-&gt;&gt;NFS: "Create DONE marker file"

        N1-&gt;&gt;NFS: "Check if all 3 markers exist"
        N1-&gt;&gt;NFS: "Delete CA - Cleanup security risk"
    end

    Note over V, N23: === PHASE 4 - FINAL CONFIG Parallel ===

    par Run Scripts 10-13
        V-&gt;&gt;N1: "10-Keystore - 11-Service - 12-Config - 13-Sudoers"
        V-&gt;&gt;N23: "10-Keystore - 11-Service - 12-Config - 13-Sudoers"
    and
        N1-&gt;&gt;N1: "Set SSL Passwords - Enable Service - Heap - Sudo Rights"
        N23-&gt;&gt;N23: "Set SSL Passwords - Enable Service - Heap - Sudo Rights"
    end

    Note over N1, N23: === CLUSTER BOOT ===

    N1-&gt;&gt;N1: "Systemd Start PID 1"
    N23-&gt;&gt;N23: "Systemd Start"

    N1-&gt;&gt;N23: "Handshake over Port 9300 SSL"
    N23-&gt;&gt;N1: "Join Cluster es-cluster"

    Note over V, N23: SUCCESS - Cluster is Green
</code></pre>
<hr />
<h2 id="heading-chapter-2-lab-setup-amp-deployment">Chapter 2: Lab Setup &amp; Deployment</h2>
<p>Now that we understand the design, let's build it. We will use PowerShell to create the script files instantly.</p>
<h3 id="heading-21-prerequisites">2.1 Prerequisites</h3>
<p>Ensure you have the following installed on your host machine:</p>
<ul>
<li><strong>VirtualBox</strong> (Version 7.0 or higher recommended)</li>
<li><strong>Vagrant</strong> (Version 2.4 or higher)</li>
<li><strong>PowerShell</strong> (Standard on Windows, or pwsh on Mac/Linux)</li>
</ul>
<h3 id="heading-22-initialize-the-lab">2.2 Initialize the Lab</h3>
<p>Open your PowerShell terminal and run the following commands to create a clean workspace:</p>
<pre><code class="lang-powershell">mkdir lab_elasticsearch
<span class="hljs-built_in">cd</span> lab_elasticsearch
</code></pre>
<h3 id="heading-23-generate-provisioning-scripts-amp-technical-script-breakdown">2.3 Generate Provisioning Scripts &amp; Technical Script Breakdown</h3>
<h4 id="heading-the-vagrantfile-orchestrator">The Vagrantfile (Orchestrator)</h4>
<p>This file defines the 3 VMs, sets their IPs, RAM, and CPUs, and tells Vagrant to run the 13 shell scripts in order.</p>
<pre><code class="lang-powershell"><span class="hljs-string">@'
Vagrant.configure("2") do |config|
  # UPDATED: Changed to Noble (24.04) to match your requested package versions
  config.vm.box = "ubuntu/jammy64"

  (1..3).each do |i|
    config.vm.define "es#{i}" do |node|
      node.vm.hostname = "es#{i}"
      node.vm.network "private_network", ip: "192.168.56.#{10+i}"

      # Hardware: 15GB Disk + 2GB RAM
      node.vm.disk :disk, size: "15GB", name: "extra_storage"

      node.vm.provider "virtualbox" do |vb|
        vb.memory = "4096"
        vb.cpus = 2
      end      

      node.vm.provision "packages", type: "shell", path: "01-packages.sh"
      node.vm.provision "user", type: "shell", path: "02-user.sh"
      node.vm.provision "memlock", type: "shell", path: "03-config.sh"
      node.vm.provision "file_structure", type: "shell", path: "04-storage.sh"
      node.vm.provision "swap", type: "shell", path: "05-swap.sh"
      node.vm.provision "nfs", type: "shell", path: "06-nfs.sh"
      node.vm.provision "network", type: "shell", path: "07-network.sh"
      node.vm.provision "install", type: "shell", path: "08-install.sh"
      node.vm.provision "certs", type: "shell", path: "09-certs.sh"
      node.vm.provision "keystore", type: "shell", path: "10-keystore.sh"
      node.vm.provision "service", type: "shell", path: "11-service.sh"
      node.vm.provision "es_config", type: "shell", path: "12-configure-cluster.sh"
      node.vm.provision "sudoers", type: "shell", path: "13-sudoers.sh"

    end
  end
end
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"Vagrantfile"</span>
</code></pre>
<h4 id="heading-phase-1-foundation-scripts-01-05">Phase 1: Foundation (Scripts 01-05)</h4>
<p>This phase establishes a standardized OS environment tuned specifically for high-performance database workloads.</p>
<ul>
<li><strong><code>01-packages.sh</code> :</strong> Installs essential system utilities. <code>lvm2</code> and <code>xfsprogs</code> are required for the storage layer. <code>libuser</code> allows advanced user management. The <code>DEBIAN_FRONTEND=noninteractive</code> flag prevents the script from hanging on user prompts.</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e

export DEBIAN_FRONTEND=noninteractive
apt-get update -y

apt-get install -y libuser acl tar lvm2 xfsprogs wget net-tools
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"01-packages.sh"</span>
</code></pre>
<ul>
<li><strong><code>02-user.sh</code> (Identity):</strong> Creates the <code>elasticsearch</code> user with a fixed UID (<code>1122</code>) and GID (<code>1122</code>).<ul>
<li><em>Why Fixed IDs?</em> Since we are using NFS, the numeric User ID must match on all 3 servers. If <code>es1</code> writes a file as user 1001 and <code>es2</code> reads it as user 1002, permission errors will occur.</li>
</ul>
</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e

# Create Group
if ! getent group elasticsearch &gt;/dev/null; then
    groupadd -g 1122 -r elasticsearch
fi

# Create User
if ! id -u elasticsearch &gt;/dev/null 2&gt;&amp;1; then
    useradd -u 1122 -g 1122 -r -s /bin/bash -m -d /home/elasticsearch elasticsearch
fi
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"02-user.sh"</span>
</code></pre>
<ul>
<li><strong><code>03-config.sh</code> (Kernel Tuning):</strong> Applies critical sysctl settings.<ul>
<li><code>vm.max_map_count=262144</code>: Required for Lucene (the search engine core) to use <code>mmapfs</code> for efficient index access. Without this, Elasticsearch will not boot.</li>
<li><code>limits.conf</code>: Increases file descriptors to 65535, as databases hold thousands of files open simultaneously.</li>
</ul>
</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e

# set limits
cat &lt;&lt;EOF &gt; /etc/security/limits.d/elasticsearch.conf
elasticsearch   -       nofile      65535
elasticsearch   hard    memlock     unlimited
elasticsearch   soft    memlock     unlimited
elasticsearch   -       nproc       4096
EOF

# set sysctl
cat &lt;&lt;EOF &gt; /etc/sysctl.d/elasticsearch.conf
vm.max_map_count=262144
EOF

# Apply sysctl immediately
sysctl --system
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"03-config.sh"</span>
</code></pre>
<ul>
<li><strong><code>04-storage.sh</code> (LVM Partitioning):</strong> Formats the raw 15GB disk into a Logical Volume Manager (LVM) group <code>vg01</code>. It creates separate partitions for Home, Data, Logs, and Backups formatted with <strong>XFS</strong> (optimized for large file handling). This separation prevents a runaway log file from crashing the database by filling the data disk.</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e

# Create VG01
if ! vgs vg01 &gt;/dev/null 2&gt;&amp;1; then
    DISK=$(lsblk -dn -o NAME,SIZE | grep '15G' | awk '{print "/dev/"$1}')
    pvcreate $DISK
    vgcreate vg01 $DISK
fi

# Helper to create LV and Format XFS
create_lv() {
    local size=$1
    local name=$2
    if ! lvs vg01/$name &gt;/dev/null 2&gt;&amp;1; then
        lvcreate -L $size -n $name vg01
        mkfs.xfs /dev/vg01/$name
    fi
}

# Create LVs
create_lv 2G mnt
create_lv 4G data
create_lv 2G home
create_lv 1G logs
create_lv 4G backup

# Mount hierarchy
mount_and_fstab() {
    local lv=$1
    local path=$2

    mkdir -p $path
    if ! grep -q "$path " /proc/mounts; then
        mount /dev/vg01/$lv $path
        echo "/dev/vg01/$lv $path xfs defaults 0 0" &gt;&gt; /etc/fstab
    fi
}

# Mount /mnt
mount_and_fstab mnt "/mnt"

# Create subfolders
mkdir -p /mnt/elasticsearch/data
mkdir -p /mnt/elasticsearch/home
mkdir -p /mnt/elasticsearch/logs
mkdir -p /mnt/elasticsearch/backup

# Mount the sub-volumes
mount_and_fstab data   "/mnt/elasticsearch/data"
mount_and_fstab home   "/mnt/elasticsearch/home"
mount_and_fstab logs   "/mnt/elasticsearch/logs"
mount_and_fstab backup "/mnt/elasticsearch/backup"

# Fix Permissions
chown -R 1122:1122 /mnt/elasticsearch

lsblk
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"04-storage.sh"</span>
</code></pre>
<ul>
<li><strong><code>05-swap.sh</code> (Performance):</strong> Permanently disables swap memory. If the OS swaps Elasticsearch memory to disk, performance degrades instantly and can cause Garbage Collection pauses that disconnect the node from the cluster.</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e

# Turn off swap immediately
swapoff -a

# Remove swap entry from /etc/fstab so it stays off after reboot
sed -i '/swap/s/^/#/' /etc/fstab

# Verify
if [ $(swapon --show | wc -l) -eq 0 ]; then
    echo "Swap is disabled."
else
    echo "Swap might still be active."
    swapon --show
fi
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"05-swap.sh"</span>
</code></pre>
<h3 id="heading-phase-2-infrastructure-scripts-06-08">Phase 2: Infrastructure (Scripts 06-08)</h3>
<p>This phase builds the network and storage plumbing required for clustering.</p>
<ul>
<li><strong><code>06-nfs.sh</code> (Shared Storage):</strong><ul>
<li><strong>On ES1:</strong> Installs <code>nfs-kernel-server</code> and exports the <code>/mnt/elasticsearch/backup</code> partition.</li>
<li><strong>On ES2/3:</strong> Installs <code>nfs-common</code> and mounts that export. This allows all nodes to see the same "Snapshot Repository," enabling the entire cluster to back up data to a single location.</li>
</ul>
</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e

HOSTNAME=$(hostname)
NFS_SERVER_IP="192.168.56.11"
SHARE_PATH="/mnt/elasticsearch/backup"

# SERVER CONFIG (Only on es1)
if [ "$HOSTNAME" == "es1" ]; then

    apt-get update -y
    apt-get install -y nfs-kernel-server

    # Permission Check
    chown 1122:1122 "$SHARE_PATH"
    chmod 775 "$SHARE_PATH"

    # Configure Exports
    if ! grep -q "$SHARE_PATH" /etc/exports; then
        echo "$SHARE_PATH 192.168.56.0/24(rw,sync,no_subtree_check,no_root_squash)" &gt;&gt; /etc/exports
    fi

    exportfs -a
    systemctl restart nfs-kernel-server

    # Verify
    touch "$SHARE_PATH/verify_nfs.txt"

# CLIENT CONFIG (Only on es2 &amp; es3)
else

    apt-get update -y
    apt-get install -y nfs-common

    mkdir -p "$SHARE_PATH"
    chown 1122:1122 "$SHARE_PATH"

    # Check if NFS is already mounted
    if ! grep -q "$NFS_SERVER_IP:$SHARE_PATH" /proc/mounts; then
        mount "$NFS_SERVER_IP:$SHARE_PATH" "$SHARE_PATH"
        echo "$NFS_SERVER_IP:$SHARE_PATH $SHARE_PATH nfs defaults 0 0" &gt;&gt; /etc/fstab
    fi
fi
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"06-nfs.sh"</span>
</code></pre>
<ul>
<li><strong><code>07-network.sh</code> (Interface Naming):</strong> Identifies the secondary network card (<code>enp0s8</code> usually) and renames it to <code>prod</code> using systemd link rules. This guarantees that our configuration files (<code>network.host: _prod_</code>) will always find the correct interface, regardless of how VirtualBox assigns PCI slots.</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e
echo "--- Renaming Interface enp0s8 to prod ---"

CURRENT_NAME="enp0s8"
NEW_NAME="prod"

# Check if rename is already done
if ip link show "$NEW_NAME" &gt;/dev/null 2&gt;&amp;1; then
    exit 0
fi

# Get the MAC address of the current interface
if [ -d "/sys/class/net/$CURRENT_NAME" ]; then
    MAC=$(cat /sys/class/net/$CURRENT_NAME/address)
else
    exit 1
fi

# Create persistent Systemd Link Rule
cat &lt;&lt;EOF &gt; /etc/systemd/network/10-rename-prod.link
[Match]
MACAddress=$MAC

[Link]
Name=$NEW_NAME
EOF

# Update Netplan Configuration
sed -i "s/$CURRENT_NAME/$NEW_NAME/g" /etc/netplan/*.yaml

# Apply Changes Immediately
# We can safely down this interface because Vagrant uses enp0s3 (NAT) for SSH.
ip link set $CURRENT_NAME down
ip link set $CURRENT_NAME name $NEW_NAME
ip link set $NEW_NAME up

# Apply Netplan to bind the IP to the new name
netplan apply

#ip addr show $NEW_NAME
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"07-network.sh"</span>
</code></pre>
<ul>
<li><strong><code>08-install.sh</code> (Software):</strong> Downloads the official Elasticsearch 9.3.0 tarball and extracts it to <code>/mnt/elasticsearch/home</code>. We use the tarball method (instead of <code>apt</code>) for total control over the installation directory structure.</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e
echo "--- Downloading and Installing Elasticsearch ---"

URL="https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-9.3.0-linux-x86_64.tar.gz"
TMP_FILE="/tmp/elasticsearch.tar.gz"
DEST_DIR="/mnt/elasticsearch/home"

# Download
wget -q -O "$TMP_FILE" "$URL"

# Extract
tar -xzf "$TMP_FILE" -C "$DEST_DIR" --strip-components=1

# Cleanup
rm -f "$TMP_FILE"

# Permission Fix
chown -R 1122:1122 "$DEST_DIR"

'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"08-install.sh"</span>
</code></pre>
<h3 id="heading-phase-3-security-amp-identity-scripts-09-10">Phase 3: Security &amp; Identity (Scripts 09-10)</h3>
<p>This is the most complex phase, handling the "Chicken and Egg" problem of distributing security certificates in an automated environment.</p>
<ul>
<li><strong><code>09-certs.sh</code> (Certificate Authority):</strong><ul>
<li><strong>Synchronization Barrier:</strong> The script uses the NFS share as a "Dead Drop." <code>es1</code> generates a Certificate Authority (CA). <code>es2</code> and <code>es3</code> enter a <code>while</code> loop, pausing execution until they see the CA file appear on the shared drive.</li>
<li><strong>Generation:</strong> Once the CA is available, each node generates its own <code>node.p12</code> certificate signed by that CA.</li>
<li><strong>Cleanup:</strong> The last node to finish deletes the CA file from the share to ensure the master key is not left exposed.</li>
</ul>
</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e

ES_HOME="/mnt/elasticsearch/home"
CERT_TOOL="$ES_HOME/bin/elasticsearch-certutil"
NFS_PATH="/mnt/elasticsearch/backup" 
LOCAL_CERTS="$ES_HOME/config/certs"
HOSTNAME=$(hostname)

# IP Calc
NODE_NUM=$(echo $HOSTNAME | tr -dc '0-9')
NODE_IP="192.168.56.$((10 + NODE_NUM))"

# --- CA GENERATION (es1 only) ---
if [ "$HOSTNAME" == "es1" ]; then
    if [ ! -f "$NFS_PATH/elastic-stack-ca.p12" ]; then
        $CERT_TOOL ca --out "$NFS_PATH/elastic-stack-ca.p12" --pass ""
    fi
    chmod 777 "$NFS_PATH/elastic-stack-ca.p12"
fi

# --- CERT GENERATION ---
while [ ! -f "$NFS_PATH/elastic-stack-ca.p12" ]; do sleep 2; done

mkdir -p "$LOCAL_CERTS"

# UPDATED NAME: node.p12
if [ ! -f "$LOCAL_CERTS/node.p12" ]; then
    $CERT_TOOL cert \
        --ca "$NFS_PATH/elastic-stack-ca.p12" \
        --ca-pass "" \
        --out "$LOCAL_CERTS/node.p12" \
        --pass "" \
        --name "$HOSTNAME" \
        --dns "$HOSTNAME,localhost" \
        --ip "$NODE_IP,127.0.0.1"
fi

chown -R 1122:1122 "$LOCAL_CERTS"
chmod 600 "$LOCAL_CERTS/node.p12"
chmod 700 "$LOCAL_CERTS"

# --- CLEANUP ---
touch "$NFS_PATH/$HOSTNAME.cert_done"
DONE_COUNT=$(find "$NFS_PATH" -maxdepth 1 -name "*.cert_done" | wc -l)

if [ "$DONE_COUNT" -ge 3 ]; then
    rm -f "$NFS_PATH/elastic-stack-ca.p12"
    rm -f "$NFS_PATH"/*.cert_done
    rm -f "$NFS_PATH"/*.txt
fi
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"09-certs.sh"</span>
</code></pre>
<ul>
<li><strong><code>10-keystore.sh</code> (Secret Management):</strong> Elasticsearch stores sensitive passwords in a secure <code>elasticsearch.keystore</code> file. This script adds the SSL passwords (empty strings in this lab context) to the keystore. It uses a clever input redirection trick (<code>&lt; $PASS_FILE</code>) to feed passwords into the command, preventing Java from crashing in the headless Vagrant terminal.</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e
echo "--- Setup Keystore ---"

ES_HOME="/mnt/elasticsearch/home"
KEYSTORE_BIN="$ES_HOME/bin/elasticsearch-keystore"
PASS_FILE="/tmp/keystore_pass"

# Create a temp file with a NEWLINE
echo "" &gt; "$PASS_FILE"

# Create Keystore
if [ ! -f "$ES_HOME/config/elasticsearch.keystore" ]; then
    $KEYSTORE_BIN create
fi

# Add Keys function
add_key() {
    local key_name=$1
    if ! $KEYSTORE_BIN list | grep -q "$key_name"; then
        $KEYSTORE_BIN add --stdin --force "$key_name" &lt; "$PASS_FILE"
    fi
}

# Add Transport Layer Passwords
add_key "xpack.security.transport.ssl.keystore.secure_password"
add_key "xpack.security.transport.ssl.truststore.secure_password"

# Add HTTP Layer Passwords
add_key "xpack.security.http.ssl.keystore.secure_password"
add_key "xpack.security.http.ssl.truststore.secure_password"

# Cleanup &amp; Permissions
rm -f "$PASS_FILE"

chown 1122:1122 "$ES_HOME/config/elasticsearch.keystore"
chmod 600 "$ES_HOME/config/elasticsearch.keystore"

'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"10-keystore.sh"</span>
</code></pre>
<h3 id="heading-phase-4-service-amp-cluster-scripts-11-12">Phase 4: Service &amp; Cluster (Scripts 11-12)</h3>
<p>The final phase configures the application and boots the cluster.</p>
<ul>
<li><strong><code>11-service.sh</code> (Systemd Integration):</strong> Creates a <code>systemd</code> unit file to manage Elasticsearch as a background service. We use <code>Type=simple</code> because it is the most robust method for tarball installations, avoiding timeout issues often seen with <code>Type=notify</code>.</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e

# Variables
SERVICE_FILE="/etc/systemd/system/elasticsearch.service"
ES_HOME="/mnt/elasticsearch/home"
ES_CONF="$ES_HOME/config"
USER="elasticsearch"
GROUP="elasticsearch"

# Create the Systemd Unit File

cat &lt;&lt;EOF &gt; $SERVICE_FILE
[Unit]
Description=Elasticsearch
Documentation=https://www.elastic.co
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
RuntimeDirectory=elasticsearch
PrivateTmp=true

# Environment Variables
Environment=ES_HOME=$ES_HOME
Environment=ES_PATH_CONF=$ES_CONF
Environment=PID_DIR=/run/elasticsearch

# Execution
WorkingDirectory=$ES_HOME
User=$USER
Group=$GROUP
ExecStart=$ES_HOME/bin/elasticsearch -p /run/elasticsearch/elasticsearch.pid --quiet

# Logging
StandardOutput=journal
StandardError=inherit

# Resource Limits
LimitNOFILE=65535
LimitNPROC=4096
LimitAS=infinity
LimitFSIZE=infinity
LimitMEMLOCK=infinity

# Timeouts
TimeoutStartSec=75
TimeoutStopSec=0
KillSignal=SIGTERM
KillMode=process
SendSIGKILL=no
SuccessExitStatus=143

[Install]
WantedBy=multi-user.target
EOF

# Set Kernel Parameters
SYSCTL_FILE="/etc/sysctl.d/99-elasticsearch.conf"
if [ ! -f "$SYSCTL_FILE" ]; then
    echo "vm.max_map_count=262144" &gt; "$SYSCTL_FILE"
    sysctl -p "$SYSCTL_FILE"
fi

# Reload Systemd and Enable
systemctl daemon-reload
systemctl enable elasticsearch

# Check Status
systemctl status elasticsearch --no-pager | grep "Loaded:"
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"11-service.sh"</span>
</code></pre>
<ul>
<li><strong><code>12-configure-cluster.sh</code> (Bootstrap):</strong> Writes the final <code>elasticsearch.yml</code> and <code>jvm.options</code>.<ul>
<li><strong>Heap:</strong> Sets <code>-Xms2g -Xmx2g</code> (50% of RAM).</li>
<li><strong>Discovery:</strong> Lists all 3 IPs so nodes can find each other.</li>
<li><strong>Binding:</strong> <code>network.host: [_local_, "_prod_"]</code> forces the node to listen on the specific internal interface we configured, securing it from the public internet.</li>
<li><strong>Security:</strong> Enables <code>xpack.security</code> and points to the certificates generated in Script 09.</li>
</ul>
</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e
echo "--- Configuring Elasticsearch Cluster &amp; JVM ---"

# Variables
ES_HOME="/mnt/elasticsearch/home"
CONFIG_DIR="$ES_HOME/config"
YML_FILE="$CONFIG_DIR/elasticsearch.yml"
JVM_FILE="$CONFIG_DIR/jvm.options.d/heap.options"
HOSTNAME=$(hostname)

# JVM HEAP CONFIGURATION
cat &lt;&lt;EOF &gt; $JVM_FILE
-Xms2g
-Xmx2g
EOF
chown 1122:1122 $JVM_FILE

# ELASTICSEARCH.YML CONFIGURATION

cat &lt;&lt;EOF &gt; $YML_FILE
# --- Cluster &amp; Node ---
cluster.name: es-cluster
node.name: ${HOSTNAME}

# --- Paths ---
path.data: /mnt/elasticsearch/data
path.logs: /mnt/elasticsearch/logs
path.repo: ["/mnt/elasticsearch/backup"]

# --- Network ---
network.host: [_local_, "_prod_"]
http.port: 9200

# --- Discovery ---
discovery.seed_hosts: ["192.168.56.11", "192.168.56.12", "192.168.56.13"]
cluster.initial_master_nodes: ["es1", "es2", "es3"]

# --- Memory ---
bootstrap.memory_lock: true

# --- Safety ---
action.destructive_requires_name: true

# --- Security (xPack) ---
xpack.security.enabled: true

# Transport Layer
xpack.security.transport.ssl.enabled: true
xpack.security.transport.ssl.verification_mode: certificate
xpack.security.transport.ssl.keystore.path: certs/node.p12
xpack.security.transport.ssl.truststore.path: certs/node.p12

# HTTP Layer
xpack.security.http.ssl.enabled: true
xpack.security.http.ssl.keystore.path: certs/node.p12
xpack.security.http.ssl.truststore.path: certs/node.p12
EOF

# Secure the config file
chown 1122:1122 $YML_FILE
chmod 660 $YML_FILE

# RESTART SERVICE

# We perform a reload/restart to apply the changes
systemctl restart elasticsearch

# Wait and Check Health
sleep 15
if systemctl is-active --quiet elasticsearch; then
    PROD_IP=$(ip -4 addr show prod | grep -oP '(?&lt;=inet\s)\d+(\.\d+){3}')
    echo "SUCCESS: Elasticsearch is running on $HOSTNAME binding to prod ($PROD_IP)"
else
    exit 1
fi
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"12-configure-cluster.sh"</span>
</code></pre>
<ul>
<li><strong><code>13-sudoers.sh</code> (Service Management):</strong> Grants the <code>elasticsearch</code> user specific <code>sudo</code> privileges to start, stop, restart, and check the status of the <code>elasticsearch</code> service without requiring a password. This allows for automated maintenance scripts or easier manual intervention without needing root access.</li>
</ul>
<pre><code class="lang-powershell"><span class="hljs-string">@'
#!/bin/bash
set -e

SUDO_FILE="/etc/sudoers.d/elasticsearch"

# Allow systemctl commands without password
cat &lt;&lt;EOF &gt; $SUDO_FILE
elasticsearch ALL=(root) NOPASSWD: /usr/bin/systemctl start elasticsearch.service
elasticsearch ALL=(root) NOPASSWD: /usr/bin/systemctl stop elasticsearch.service
elasticsearch ALL=(root) NOPASSWD: /usr/bin/systemctl restart elasticsearch.service
elasticsearch ALL=(root) NOPASSWD: /usr/bin/systemctl status elasticsearch.service
elasticsearch ALL=(root) NOPASSWD: /usr/bin/systemctl status elasticsearch
EOF

# Strict permissions are required for sudoers files
chmod 0440 $SUDO_FILE
'@</span> | <span class="hljs-built_in">Set-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"13-sudoers.sh"</span>
</code></pre>
<h3 id="heading-24-deploy-the-cluster">2.4 Deploy the Cluster</h3>
<p>Start the deployment. Vagrant will bring up the VMs in parallel and execute the 12 scripts on each.</p>
<pre><code class="lang-powershell">vagrant up
</code></pre>
<p><em>Note: This process will take approximately 5-10 minutes depending on your internet connection speed (downloading the 600MB Elasticsearch tarball).</em></p>
<hr />
<h2 id="heading-chapter-3-verification-amp-expected-results">Chapter 3: Verification &amp; Expected Results</h2>
<p>Once <code>vagrant up</code> completes, you should verify that your cluster is healthy, secure, and functioning as designed.</p>
<h3 id="heading-31-terminal-output-check">3.1 Terminal Output Check</h3>
<p>At the very end of the deployment logs in your PowerShell window, you should see the success message from Script 12 for each node:</p>
<blockquote>
<p><code>es1: SUCCESS: Elasticsearch is running on es1 binding to prod (192.168.56.11)</code>
<code>es2: SUCCESS: Elasticsearch is running on es2 binding to prod (192.168.56.12)</code>
<code>es3: SUCCESS: Elasticsearch is running on es3 binding to prod (192.168.56.13)</code></p>
</blockquote>
<h3 id="heading-32-service-validation">3.2 Service Validation</h3>
<p>Login to the first node to perform internal checks.</p>
<pre><code class="lang-bash">vagrant ssh es1
</code></pre>
<p>Once inside, run the following to confirm the service status:</p>
<pre><code class="lang-bash">sudo -iu elasticsearch
sudo systemctl status elasticsearch
</code></pre>
<p><strong>Expected Result:</strong>
You should see <code>Active: active (running)</code> and the memory usage should be close to 2GB (due to <code>bootstrap.memory_lock</code>).</p>
<h3 id="heading-33-cluster-health-check-ssl-verification">3.3 Cluster Health Check (SSL Verification)</h3>
<p>Since we enabled security, we must use HTTPS and authenticate. Use the built-in <code>elastic</code> superuser. Since we haven't set a password yet, we will reset it first to something we know (e.g., <code>123456</code>), then check the health.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># 1. Reset password</span>
/mnt/elasticsearch/home/bin/elasticsearch-reset-password -u elastic -i 

<span class="hljs-comment"># (Type '123456' when prompted)</span>

<span class="hljs-comment"># 2. Check Cluster Health</span>
<span class="hljs-built_in">export</span> ELAS_PASS=<span class="hljs-string">'123456'</span>
curl -k -u elastic:<span class="hljs-variable">$ELAS_PASS</span> <span class="hljs-string">"https://192.168.56.11:9200/_cluster/health?pretty"</span>
</code></pre>
<p><strong>Expected Result:</strong></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"cluster_name"</span> : <span class="hljs-string">"es-cluster"</span>,
  <span class="hljs-attr">"status"</span> : <span class="hljs-string">"green"</span>,
  <span class="hljs-attr">"timed_out"</span> : <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"number_of_nodes"</span> : <span class="hljs-number">3</span>,
  <span class="hljs-attr">"number_of_data_nodes"</span> : <span class="hljs-number">3</span>,
  <span class="hljs-attr">"active_primary_shards"</span> : <span class="hljs-number">3</span>,
  <span class="hljs-attr">"active_shards"</span> : <span class="hljs-number">6</span>,
  <span class="hljs-attr">"relocating_shards"</span> : <span class="hljs-number">0</span>,
  <span class="hljs-attr">"initializing_shards"</span> : <span class="hljs-number">0</span>,
  <span class="hljs-attr">"unassigned_shards"</span> : <span class="hljs-number">0</span>,
  <span class="hljs-attr">"unassigned_primary_shards"</span> : <span class="hljs-number">0</span>,
  <span class="hljs-attr">"delayed_unassigned_shards"</span> : <span class="hljs-number">0</span>,
  <span class="hljs-attr">"number_of_pending_tasks"</span> : <span class="hljs-number">0</span>,
  <span class="hljs-attr">"number_of_in_flight_fetch"</span> : <span class="hljs-number">0</span>,
  <span class="hljs-attr">"task_max_waiting_in_queue_millis"</span> : <span class="hljs-number">0</span>,
  <span class="hljs-attr">"active_shards_percent_as_number"</span> : <span class="hljs-number">100.0</span>
}
</code></pre>
<ul>
<li><strong>Status: Green</strong> means all nodes are online and talking to each other.</li>
<li><strong>Number of nodes: 3</strong> confirms <code>es2</code> and <code>es3</code> joined successfully.</li>
</ul>
<h3 id="heading-34-nfs-storage-verification">3.4 NFS Storage Verification</h3>
<p>Verify that the NFS share is mounted correctly on the worker nodes.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Still inside es1, exit back to host</span>
<span class="hljs-built_in">exit</span>

<span class="hljs-comment"># Login to es2</span>
vagrant ssh es2

<span class="hljs-comment"># Check disk mounts</span>
sudo -iu elasticsearch
df -h | grep backup
</code></pre>
<p><strong>Expected Result:</strong></p>
<pre><code class="lang-bash">192.168.56.11:/mnt/elasticsearch/backup  4.0G   61M  4.0G   2% /mnt/elasticsearch/backup
</code></pre>
<p>This confirms that <code>es2</code> is effectively using the disk space of <code>es1</code> for its snapshot repository.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Congratulations! You have successfully engineered a platform, not just installed software.</p>
<p>By following this guide, you have moved beyond simple "Hello World" tutorials and built a <strong>Production-Grade Infrastructure</strong> that mimics real-world enterprise environments.</p>
<p><strong>What you have achieved:</strong></p>
<ul>
<li><strong>True High Availability:</strong> A 3-node cluster that can survive the loss of a server without losing data.</li>
<li><strong>Hardened Security:</strong> Full SSL encryption on the Transport layer (Node-to-Node) and HTTP layer (Client-to-Node), protecting your data from the start.</li>
<li><strong>Professional Storage:</strong> A tiered LVM storage strategy that protects your OS from log floods and enables snapshots via NFS.</li>
<li><strong>Zero-Touch Automation:</strong> A reusable Vagrant template that spins up identical, clean environments in minutes, perfect for testing upgrades or training new team members.</li>
</ul>
<p>Your cluster is now ready for data ingestion, Kibana integration, or experimenting with complex sharding strategies. You have built a solid foundation—now go build something amazing on top of it.</p>
]]></content:encoded></item><item><title><![CDATA[Elasticsearch en Production : Le Guide Ultime de l'Architecture et des Opérations]]></title><description><![CDATA[Définition
Elasticsearch est un moteur de recherche et d'analyse distribué et RESTful, capable de répondre à un nombre croissant de cas d'utilisation. À la base, il s'agit d'une base de données NoSQL, mais contrairement aux bases de données tradition...]]></description><link>https://nosqlnest.net/frelasticsearch-en-production-le-guide-ultime-de-larchitecture-et-des-operations</link><guid isPermaLink="true">https://nosqlnest.net/frelasticsearch-en-production-le-guide-ultime-de-larchitecture-et-des-operations</guid><category><![CDATA[elasticsearch]]></category><category><![CDATA[deployment]]></category><category><![CDATA[NoSQL]]></category><category><![CDATA[#NoSQLDatabase]]></category><category><![CDATA[NoSQL databases]]></category><category><![CDATA[Search engine optimization]]></category><category><![CDATA[Search Engines]]></category><category><![CDATA[no sql]]></category><category><![CDATA[search engine]]></category><category><![CDATA[search]]></category><dc:creator><![CDATA[AKEBLI Ouassim]]></dc:creator><pubDate>Wed, 04 Feb 2026 13:02:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770210233261/956a06d5-7643-4967-9a51-dcc80ca54599.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-definition">Définition</h1>
<p>Elasticsearch est un moteur de recherche et d'analyse distribué et RESTful, capable de répondre à un nombre croissant de cas d'utilisation. À la base, il s'agit d'une base de données NoSQL, mais contrairement aux bases de données traditionnelles conçues pour le stockage et la récupération simple, Elasticsearch est optimisé pour la <strong>vitesse</strong> et la <strong>pertinence</strong>.</p>
<hr />
<h1 id="heading-architecture-amp-capacity-planning">Architecture &amp; Capacity Planning</h1>
<p>Ce diagramme illustre une architecture typique de cluster de production, montrant la séparation des tâches entre les nœuds maîtres dédiés (dedicated master nodes), les nœuds de données étagés (tiered data nodes - hot/warm) et les nœuds de coordination (coordinating nodes).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770047462097/ea0805d0-f516-425b-9cc0-0375df47b29b.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-1-node-roles">1. Node Roles</h2>
<h3 id="heading-a-roles-de-base-obligatoires">A. Rôles de base obligatoires</h3>
<h4 id="heading-1-master-eligible-node-master">1. Master-eligible node (<code>master</code>)</h4>
<ul>
<li><p><strong>Fonction :</strong> Responsable des actions à l'échelle du cluster, telles que la création ou la suppression d'index, le suivi des nœuds faisant partie du cluster et l'allocation des shards aux nœuds.</p>
</li>
<li><p><strong>Pourquoi c'est obligatoire :</strong> Sans maître, le cluster ne peut pas être formé et aucun changement au niveau du cluster ne peut être suivi.</p>
</li>
<li><p><strong>Conseil de production :</strong> Vous avez généralement besoin de 3 nœuds maîtres dédiés pour la Haute Disponibilité (High Availability) afin d'éviter le "split-brain".</p>
</li>
</ul>
<h4 id="heading-2-data-node-data">2. Data node (<code>data</code>)</h4>
<ul>
<li><p><strong>Fonction :</strong> Contient les shards qui hébergent vos documents indexés. Ces nœuds effectuent des opérations liées aux données comme le CRUD, la recherche et les agrégations.</p>
</li>
<li><p><strong>Pourquoi c'est obligatoire :</strong> Sans nœuds de données, vous ne pouvez stocker aucune donnée.</p>
</li>
<li><p><strong>Sous-rôles (Tiered Architecture) :</strong></p>
<ul>
<li><p><code>data_content</code> : Pour les données à usage général qui ne correspondent pas à un cycle de vie de séries temporelles (time-series).</p>
</li>
<li><p><code>data_hot</code> : Pour les données time-series strictes (ex : logs) qui sont activement écrites et interrogées.</p>
</li>
<li><p><code>data_warm</code> : Pour les données plus anciennes qui sont en lecture seule (read-only) mais encore fréquemment interrogées.</p>
</li>
<li><p><code>data_cold</code> : Pour les données consultées peu fréquemment (optimisé pour le stockage).</p>
</li>
<li><p><code>data_frozen</code> : Pour les données stockées dans le stockage objet (S3) et rarement interrogées (Searchable Snapshots).</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-b-core-utility-roles-hautement-recommandes">B. Core Utility Roles (Hautement recommandés)</h3>
<p><em>Bien qu'ils ne soient pas strictement "obligatoires" pour le démarrage du cluster, ils sont standard dans presque tous les environnements de production.</em></p>
<h4 id="heading-1-ingest-node-ingest">1. Ingest node (<code>ingest</code>)</h4>
<ul>
<li><p><strong>Fonction :</strong> Exécute des "ingest pipelines" pour prétraiter les documents avant l'indexation. Cela agit comme un Logstash léger à l'intérieur d'Elasticsearch (ex : parsing JSON, suppression de champs, renommage de champs).</p>
</li>
<li><p><strong>Comportement par défaut :</strong> Chaque nœud est un ingest node par défaut, sauf configuration contraire.</p>
</li>
</ul>
<h4 id="heading-2-coordinating-only-node-aucun-role-specifique-defini">2. Coordinating-only node (Aucun rôle spécifique défini)</h4>
<ul>
<li><p><strong>Fonction :</strong> Ces nœuds ont une liste de rôles vide (<code>node.roles: []</code>). Ils agissent comme des "Smart Load Balancers". Ils acceptent les requêtes de recherche, les distribuent aux nœuds de données spécifiques détenant les données, rassemblent les résultats, effectuent la réduction finale (tri/agrégation) et envoient la réponse au client.</p>
</li>
<li><p><strong>Cas d'utilisation :</strong> Grands clusters avec un trafic de recherche important pour éviter que les data nodes ne soient submergés par des tâches d'agrégation gourmandes en CPU.</p>
</li>
</ul>
<h3 id="heading-c-specialized-roles-optionnels">C. Specialized Roles (Optionnels)</h3>
<p><em>Ceux-ci sont spécifiques à certaines fonctionnalités de la Suite Elastic.</em></p>
<h4 id="heading-1-machine-learning-node-ml">1. Machine Learning node (<code>ml</code>)</h4>
<ul>
<li><p><strong>Fonction :</strong> Exécute les tâches natives de Machine Learning (détection d'anomalies, prévisions).</p>
</li>
<li><p><strong>Exigence :</strong> Ces tâches sont gourmandes en CPU et en RAM. Si vous utilisez des fonctionnalités ML, vous devez avoir au moins un nœud ML.</p>
</li>
</ul>
<h4 id="heading-2-remote-cluster-client-remoteclusterclient">2. Remote Cluster Client (<code>remote_cluster_client</code>)</h4>
<ul>
<li><p><strong>Fonction :</strong> Permet au cluster de se connecter à d'autres clusters (Cross-Cluster Search ou Cross-Cluster Replication).</p>
</li>
<li><p><strong>Défaut :</strong> Activé par défaut sur tous les nœuds.</p>
</li>
</ul>
<h4 id="heading-3-transform-node-transform">3. Transform node (<code>transform</code>)</h4>
<ul>
<li><strong>Fonction :</strong> Exécute des tâches de transformation qui pivotent ou résument les données dans de nouveaux index (similaire aux "Materialized Views" en SQL).</li>
</ul>
<h4 id="heading-4-voting-only-node-votingonly">4. Voting-only node (<code>voting_only</code>)</h4>
<ul>
<li><p><strong>Fonction :</strong> Un nœud master-eligible qui peut participer aux élections du maître (voting) mais ne peut pas devenir le maître élu.</p>
</li>
<li><p><strong>Cas d'utilisation :</strong> Rarement utilisé ; principalement pour départager les votes (tie-breaking) dans les clusters à nombre pair.</p>
</li>
</ul>
<hr />
<h2 id="heading-2-hardware-requirements">2. Hardware Requirements</h2>
<p><em>Ces chiffres sont basés sur les contraintes de la JVM (Java Virtual Machine) et les meilleures pratiques opérationnelles pour la récupération et la stabilité.</em></p>
<h3 id="heading-a-ram-memoire">A. RAM (Mémoire)</h3>
<p><em>C'est la ressource la plus critique. Elle est divisée entre la JVM Heap (pour l'application) et l'OS Filesystem Cache (pour les fichiers de segments Lucene).</em></p>
<ul>
<li><p><strong>Minimum :</strong> <code>8 GB</code> - <code>16 GB</code></p>
<ul>
<li>Exécuter un nœud de production avec moins de 8GB est risqué. Vous avez besoin de suffisamment de marge (headroom) pour que l'OS puisse mettre en cache les segments d'index fréquemment consultés.</li>
</ul>
</li>
<li><p><strong>Le "Sweet Spot" :</strong> <code>64 GB</code></p>
<ul>
<li>C'est la spécification standard pour un nœud haute performance. Cela vous permet d'allouer environ 30GB à la Heap et de laisser environ 34GB pour le cache de l'OS.</li>
</ul>
</li>
<li><p><strong>Maximum (Effectif) :</strong> <code>64 GB</code> (RAM Physique)</p>
</li>
</ul>
<p><strong>⚠️ La limite "Compressed Oops"</strong> Vous devez strictement éviter d'allouer plus de 31GB-32GB à la JVM Heap. Si la Heap dépasse ~32GB, la JVM cesse d'utiliser les "Compressed Object Pointers" (les pointeurs gonflent de 32-bit à 64-bit). Cela réduit considérablement l'efficacité de la mémoire, réduisant effectivement votre mémoire disponible de moitié.</p>
<h3 id="heading-b-disk-stockage">B. Disk (Stockage)</h3>
<p><em>La vitesse du disque dicte le débit d'indexation, et la taille du disque dicte la durée de la récupération (rééquilibrage des shards).</em></p>
<ul>
<li><p><strong>Exigence de type :</strong> SSD / NVMe</p>
<ul>
<li>Les disques durs rotatifs (Spinning HDDs) ne sont acceptables que pour les tiers "Cold" ou "Frozen".</li>
</ul>
</li>
<li><p><strong>Capacité minimale :</strong> <code>200 GB</code></p>
<ul>
<li>Les petits clusters ont besoin d'assez d'espace pour les logs, l'OS et une marge pour le shard merging.</li>
</ul>
</li>
</ul>
<p><strong>Capacité maximale (Par nœud) :</strong></p>
<ol>
<li><p><strong>Hot Nodes (High IO) :</strong> Limite de <code>2 TB</code> - <code>4 TB</code>.</p>
<ul>
<li><em>Raisonnement :</em> Si un nœud contient 10TB de données "hot" et tombe en panne, répliquer ces 10TB sur un nouveau nœud via le réseau prend des heures ou des jours, laissant le cluster dans un état "Yellow" (à risque).</li>
</ul>
</li>
<li><p><strong>Warm/Cold Nodes :</strong> <code>10 TB</code> - <code>16 TB</code>.</p>
<ul>
<li><em>Raisonnement :</em> Puisque ces nœuds sont query-heavy (lecture) mais low-write (écriture), vous pouvez les remplir avec un stockage dense, à condition d'accepter des temps de récupération plus lents.</li>
</ul>
</li>
</ol>
<h3 id="heading-c-network">C. Network</h3>
<p><em>Elasticsearch est un système distribué ; le réseau en est le bus.</em></p>
<ul>
<li><p><strong>Minimum :</strong> <code>1 Gbps</code> (Gigabit Ethernet)</p>
<ul>
<li><p>Acceptable uniquement pour les petits clusters avec des taux d'indexation faibles.</p>
</li>
<li><p><em>Risque :</em> Lors d'une "peer recovery" (quand un nœud revient en ligne), un lien de 1 Gbps sera saturé à 100%, provoquant une augmentation de la latence de recherche.</p>
</li>
</ul>
</li>
<li><p><strong>Recommandé / Maximum :</strong> <code>10 Gbps</code> - <code>25 Gbps</code></p>
<ul>
<li>10 Gbps est la norme pour les clusters de production modernes afin de garantir que la récupération n'impacte pas le trafic live.</li>
</ul>
</li>
<li><p><strong>Latence :</strong> Doit être de quelques millisecondes (intra-datacenter).</p>
<ul>
<li><em>Avertissement :</em> Étendre un cluster unique sur des régions géographiques distinctes (ex : US-East vers EU-West) est généralement non supporté et causera une instabilité due aux erreurs de timeout.</li>
</ul>
</li>
</ul>
<hr />
<h2 id="heading-3-sizing-amp-sharding">3. Sizing &amp; Sharding</h2>
<p>Ce diagramme fournit une visualisation claire de la façon dont un index unique est divisé en shards primaires (P) et comment chaque shard primaire a un shard réplica correspondant (R) distribué sur différents nœuds pour la haute disponibilité.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770047597579/4820790c-7b9d-4887-bfe4-e3dcfbe8d186.jpeg" alt class="image--center mx-auto" /></p>
<h3 id="heading-a-shard-strategy">A. Shard Strategy</h3>
<p><em>Le sharding est le mécanisme qui permet à Elasticsearch de s'étendre au-delà des limites matérielles d'un seul serveur. Cependant, c'est la source la plus courante de problèmes de performance en production.</em></p>
<h4 id="heading-1-le-concept">1. Le Concept</h4>
<p>Un index Elasticsearch est en réalité un regroupement logique de <strong>Shards</strong>. Chaque shard est une instance autonome d'Apache Lucene, qui est un moteur de recherche entièrement fonctionnel à part entière. Lorsque vous exécutez une recherche sur un index, Elasticsearch interroge tous les shards pertinents en parallèle et fusionne les résultats.</p>
<h4 id="heading-2-le-piege-de-loversharding">2. Le piège de l'Oversharding"</h4>
<p>Les nouveaux utilisateurs pensent souvent : <em>"Si les shards offrent du parallélisme, plus de shards signifie plus de vitesse."</em> C'est une fausse idée connue sous le nom d'oversharding.</p>
<ul>
<li><p><strong>La surcharge des métadonnées :</strong> Chaque shard consomme des ressources. Le Cluster State (le "cerveau" du cluster) doit suivre l'emplacement, le statut et la taille de chaque shard. Si vous avez 100 000 petits shards, le Cluster State devient énorme et les mises à jour (comme la création d'un nouvel index) deviennent incroyablement lentes.</p>
</li>
<li><p><strong>La taxe "Map-Reduce" :</strong> Lorsque vous recherchez dans un index avec 50 shards, le nœud de coordination doit envoyer la requête à 50 endroits, attendre 50 réponses et fusionner 50 résultats. Si ces shards sont minuscules (ex : 50MB), la surcharge de gestion de la requête dépasse le bénéfice du traitement parallèle.</p>
</li>
<li><p><strong>Coût en mémoire :</strong> Chaque shard a une empreinte mémoire de base dans la JVM Heap pour conserver les informations de segment Lucene. Trop de petits shards épuiseront votre mémoire Heap même si le cluster est inactif.</p>
</li>
</ul>
<p><strong>✅ La Règle d'Or : 10GB – 50GB</strong> Pour la recherche à usage général (ex : produits, utilisateurs), visez une taille de shard comprise entre <strong>10GB et 50GB</strong>.</p>
<ul>
<li><p><strong>Pourquoi &gt; 10GB ?</strong> Pour minimiser la surcharge par shard et maximiser une compression efficace.</p>
</li>
<li><p><strong>Pourquoi &lt; 50GB ?</strong> Pour s'assurer que la récupération est rapide. Si un nœud tombe en panne, déplacer un shard de 50GB vers un nouveau nœud via le réseau est gérable. Déplacer un shard de 500GB prend tellement de temps que votre cluster reste dans un état vulnérable (santé "Yellow") pendant des heures.</p>
</li>
</ul>
<h3 id="heading-b-replicas">B. Replicas</h3>
<p><em>La réplication sert deux objectifs distincts dans un cluster : la High Availability (HA) et le Read Throughput (débit de lecture).</em></p>
<h4 id="heading-1-failover-haute-disponibilite">1. Failover (Haute Disponibilité)</h4>
<p>Un Replica Shard est une copie précise d'un Primary Shard.</p>
<ul>
<li><p><strong>Le Mécanisme :</strong> Si le nœud tenant un Primary Shard plante, le nœud maître "promeut" instantanément le Replica Shard (vivant sur un nœud différent) pour être le nouveau Primary.</p>
</li>
<li><p><strong>Standard de Production :</strong> Vous devez définir <code>number_of_replicas: 1</code> (au minimum). Cela garantit que si un seul nœud tombe en panne, aucune donnée n'est perdue et le cluster reste pleinement opérationnel.</p>
</li>
<li><p><strong>Le Compromis :</strong> Les réplicas doublent vos besoins de stockage. 100GB de données avec 1 replica nécessite 200GB d'espace disque physique.</p>
</li>
</ul>
<h4 id="heading-2-read-throughput-scaling-search">2. Read Throughput (Scaling Search)</h4>
<p>Contrairement aux Primary shards, qui gèrent à la fois les lectures et les écritures, les Replicas sont généralement utilisés pour les lectures.</p>
<ul>
<li><p><strong>Load Balancing :</strong> Lorsqu'une requête de recherche arrive, le nœud de coordination la route intelligemment. Elle peut aller vers le Primary ou l'un de ses Replicas.</p>
</li>
<li><p><strong>Scaling Up :</strong> Si votre application est "Read Heavy" (ex : un site e-commerce où les utilisateurs cherchent souvent mais les produits changent rarement), vous pouvez augmenter les performances en ajoutant plus de replicas.</p>
<ul>
<li><em>Exemple :</em> Un index avec 1 Primary et 5 Replicas permet à 6 nœuds de répondre aux requêtes de recherche simultanément pour ces données spécifiques.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>Distinction Clé :</strong></p>
<ul>
<li><p><strong>Primary Shards</strong> sont fixés à la création de l'index (les changer nécessite une réindexation).</p>
</li>
<li><p><strong>Replica Shards</strong> peuvent être changés dynamiquement. Vous pouvez passer de 1 replica à 5 replicas instantanément si vous prévoyez un pic de trafic (comme le Black Friday), et revenir en arrière ensuite.</p>
</li>
</ul>
</blockquote>
<h1 id="heading-environment-preparation-os-tuning">Environment Preparation (OS Tuning)</h1>
<p>Elasticsearch est sensible aux configurations du Système d'Exploitation. Ne pas les optimiser empêchera souvent le cluster de démarrer (Bootstrap Checks).</p>
<h2 id="heading-1-disable-swapping-le-tueur-de-performance">1. Disable Swapping (Le "Tueur de Performance")</h2>
<p><strong>Le Concept :</strong> Dans un serveur standard, si la RAM physique est pleine, l'OS déplace les pages mémoire inactives vers le disque dur (swap space). Pour Elasticsearch, c'est catastrophique. Le Java Garbage Collector (GC) a besoin de scanner la mémoire pour récupérer de l'espace. Si cette mémoire est sur le disque (qui est 100 000x plus lent que la RAM), un cycle GC qui prend habituellement des millisecondes prendra des secondes ou des minutes.</p>
<ul>
<li><strong>Le Résultat :</strong> Le nœud devient non réactif (pause "Stop-the-world"), le cluster pense que le nœud est mort, l'éjecte et déclenche un rééquilibrage massif des données.</li>
</ul>
<h3 id="heading-comment-le-configurer">Comment le configurer</h3>
<p>Vous avez deux méthodes principales. La meilleure pratique est de faire <strong>les deux</strong>.</p>
<ol>
<li><p><strong>OS Level (Permanent) :</strong> Disable swap completely.</p>
<pre><code class="lang-bash"> sudo swapoff -a
</code></pre>
</li>
<li><p><strong>Application Level (Memory Lock) :</strong> Force Elasticsearch à verrouiller son espace d'adressage mémoire dans la RAM pour que l'OS ne <em>puisse pas</em> le swapper.</p>
<ul>
<li><p>Dans <code>elasticsearch.yml</code> :</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">bootstrap.memory_lock:</span> <span class="hljs-literal">true</span>
</code></pre>
</li>
<li><p><em>Note :</em> Vous devrez peut-être éditer le fichier de service systemd (<code>systemctl edit elasticsearch</code>) pour autoriser cette limite :</p>
<pre><code class="lang-ini">  <span class="hljs-section">[Service]</span>
  <span class="hljs-attr">LimitMEMLOCK</span>=infinity
</code></pre>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-2-file-descriptors-la-limite-de-capacite">2. File Descriptors (La "Limite de Capacité")</h2>
<p><strong>Le Concept :</strong> Elasticsearch (via Lucene) décompose vos données en fichiers immuables fortement compressés appelés "segments". Un seul nœud peut facilement garder des milliers de ces petits fichiers ouverts simultanément. La limite par défaut de Linux pour les fichiers ouverts par utilisateur est souvent de <code>1024</code>. C'est beaucoup trop bas. Si Elasticsearch atteint cette limite, il peut perdre silencieusement des données ou planter car il ne peut pas écrire dans de nouveaux fichiers.</p>
<h3 id="heading-comment-le-configurer-1">Comment le configurer</h3>
<p>Vous devez augmenter la limite à au moins <strong>65 536</strong>.</p>
<ul>
<li><p><strong>Vérifier la limite actuelle :</strong> <code>ulimit -n</code></p>
</li>
<li><p><strong>Correctif permanent :</strong> Éditer <code>/etc/security/limits.conf</code> :</p>
<pre><code class="lang-text">  elasticsearch - nofile 65536
</code></pre>
<p>  <em>(Si vous installez via un paquet RPM/Deb, cela est souvent fait automatiquement, mais vous devez le vérifier).</em></p>
</li>
</ul>
<hr />
<h2 id="heading-3-virtual-memory-lexigence-mmap">3. Virtual Memory (L'exigence <code>mmap</code>)</h2>
<p><strong>Le Concept :</strong> C'est spécifique à la façon dont Lucene lit les données. Il utilise un appel système appelé <code>mmap</code> (memory map) pour mapper les fichiers sur le disque directement dans l'espace d'adressage de la mémoire virtuelle. C'est incroyablement rapide car cela laisse le noyau gérer la mise en cache des fichiers. Cependant, la limite par défaut du système d'exploitation sur le nombre de "maps mémoire" qu'un processus peut posséder est généralement de <code>65 530</code>. Elasticsearch en nécessite beaucoup plus.</p>
<h3 id="heading-comment-le-configurer-2">Comment le configurer</h3>
<p>C'est la raison la plus courante des échecs de démarrage.</p>
<ul>
<li><p><strong>Commande (Live) :</strong></p>
<pre><code class="lang-bash">  sysctl -w vm.max_map_count=262144
</code></pre>
</li>
<li><p><strong>Correctif permanent :</strong> Ajoutez cette ligne à <code>/etc/sysctl.conf</code> :</p>
<pre><code class="lang-text">  vm.max_map_count=262144
</code></pre>
</li>
</ul>
<hr />
<h2 id="heading-4-jvm-heap-size-le-jeu-dequilibre">4. JVM Heap Size (Le jeu d'équilibre)</h2>
<p>C'est le paramètre le plus mal compris. Vous configurez la mémoire de la Machine Virtuelle Java (JVM).</p>
<h3 id="heading-a-xms-et-xmx-min-vs-max">A. <code>Xms</code> et <code>Xmx</code> (Min vs. Max)</h3>
<ul>
<li><p><strong>Le Problème :</strong> Par défaut, Java démarre avec une petite heap (<code>Xms</code>) et l'agrandit selon les besoins jusqu'au max (<code>Xmx</code>). Ce processus de redimensionnement met en pause l'exécution.</p>
</li>
<li><p><strong>La Solution :</strong> Définissez-les à la <strong>même valeur</strong>. Cela alloue toute la mémoire immédiatement au démarrage, empêchant les pauses de redimensionnement.</p>
<pre><code class="lang-text">  # /etc/elasticsearch/jvm.options
  -Xms4g
  -Xmx4g
</code></pre>
</li>
</ul>
<h3 id="heading-b-la-regle-des-50-pourquoi-pas-100">B. La règle des 50% (Pourquoi pas 100% ?)</h3>
<p>Si vous avez une machine de 64GB, pourquoi donner seulement 30GB à Elasticsearch ? Pourquoi pas 60GB ?</p>
<ul>
<li><p><strong>La Raison :</strong> Elasticsearch repose sur deux types de mémoire :</p>
<ol>
<li><p><strong>JVM Heap :</strong> Pour les objets de requête, les agrégations et le Cluster State.</p>
</li>
<li><p><strong>OS Filesystem Cache :</strong> C'est là que vivent les données réelles (segments Lucene).</p>
</li>
</ol>
</li>
<li><p>Si vous donnez toute la RAM à la Heap, l'OS n'a plus de place pour mettre les fichiers en cache. Le disque sera sollicité à l'excès (thrashed), et les performances s'effondreront.</p>
</li>
<li><p><strong>Règle :</strong> <strong>50% à la Heap, 50% laissés libres pour l'OS.</strong></p>
</li>
</ul>
<h3 id="heading-c-la-limite-de-32gb-compressed-oops">C. La limite de 32GB (Compressed Oops)</h3>
<p>Vous ne devez <strong>jamais</strong> définir la Heap au-dessus de ~32GB (le seuil exact varie, généralement 30GB-31GB est sûr).</p>
<ul>
<li><p><strong>La Science :</strong> En dessous de 32GB, Java utilise des "Compressed Ordinary Object Pointers" (Compressed Oops). Il utilise des pointeurs de 32-bit pour référencer la mémoire.</p>
</li>
<li><p><strong>Le Piège :</strong> Une fois que vous franchissez le seuil (ex : 32.1GB), Java bascule vers des pointeurs 64-bit. Ils sont plus grands.</p>
</li>
<li><p><strong>Le Résultat :</strong> Une Heap de 35GB stocke en réalité <em>moins</em> de données qu'une Heap de 31GB car les pointeurs eux-mêmes prennent beaucoup plus de place. De plus, cela consomme plus de bande passante CPU.</p>
</li>
</ul>
<h1 id="heading-security-la-couche-must-have">Security (La couche "Must-Have")</h1>
<p>Cette section piège souvent les nouveaux administrateurs car elle implique des certificats et des mots de passe, ce qui peut être fastidieux. Cependant, dans les versions modernes d'Elasticsearch (version 8.x+), la sécurité est activée par défaut. Vous ne pouvez pas exécuter un cluster de production sans elle.</p>
<h2 id="heading-1-tlsssl-encryption-le-tunnel-chiffre">1. TLS/SSL Encryption (Le "Tunnel Chiffré")</h2>
<p>Le chiffrement empêche les attaques de type "Man-in-the-Middle". Dans Elasticsearch, nous implémentons cela en deux couches distinctes. Si vous manquez la première, votre cluster ne démarrera même pas.</p>
<h3 id="heading-a-transport-layer-node-to-node">A. Transport Layer (Node-to-Node)</h3>
<ul>
<li><p><strong>Ce que c'est :</strong> Le canal de communication interne sur le port <code>9300</code> où les nœuds se parlent (élection des maîtres, déplacement des shards, réplication des données).</p>
</li>
<li><p><strong>Pourquoi c'est obligatoire :</strong> Elasticsearch nécessite une confiance mutuelle. Le Nœud A doit prouver au Nœud B qu'il est une partie légitime du cluster, et non un serveur malveillant essayant de voler des données.</p>
</li>
<li><p><strong>Le Mécanisme :</strong></p>
<ol>
<li><p>Vous générez une Certificate Authority (CA).</p>
</li>
<li><p>Vous signez un certificat pour chaque nœud en utilisant cette CA.</p>
</li>
</ol>
</li>
<li><p><strong>Note Cruciale :</strong> Si vous n'activez pas le Transport SSL, Elasticsearch refusera de se lier à une adresse IP non-loopback (c'est-à-dire qu'il reste en "Development Mode").</p>
</li>
<li><p><strong>Outil clé :</strong> <code>bin/elasticsearch-certutil</code> (Cet outil intégré simplifie la création de ces certificats).</p>
</li>
</ul>
<h3 id="heading-b-http-layer-client-to-cluster">B. HTTP Layer (Client-to-Cluster)</h3>
<ul>
<li><p><strong>Ce que c'est :</strong> L'API externe sur le port <code>9200</code> où Kibana, votre application (Java/Python/Node.js) et les utilisateurs se connectent.</p>
</li>
<li><p><strong>Pourquoi c'est critique :</strong> Sans cela, les identifiants Basic Auth (username/password) sont envoyés en texte clair. N'importe qui sur le réseau peut intercepter le mot de passe admin.</p>
</li>
<li><p><strong>Configuration :</strong> Vous utilisez généralement la même CA pour signer ces certificats, ou vous pouvez utiliser une CA publique (Let's Encrypt, Verisign) si votre cluster est public-facing.</p>
</li>
</ul>
<hr />
<h2 id="heading-2-authentication-amp-rbac-le-gatekeeper">2. Authentication &amp; RBAC (Le "Gatekeeper")</h2>
<p>Une fois la connexion sécurisée, vous devez contrôler qui se connecte et ce qu'ils peuvent toucher. C'est le Role-Based Access Control (RBAC).</p>
<h3 id="heading-a-les-utilisateurs-integres">A. Les utilisateurs intégrés</h3>
<p>Lorsque vous démarrez le cluster pour la première fois, vous exécutez <code>bin/elasticsearch-reset-password</code>. Cela configure les comptes réservés qui sont vitaux pour la stack :</p>
<ul>
<li><p><code>elastic</code>: Le "Superuser" (Root). Il a le contrôle total.</p>
<blockquote>
<p><strong>Danger :</strong> N'utilisez pas ce compte dans le code de votre application ! Si ces identifiants fuitent, tout votre cluster est compromis.</p>
</blockquote>
</li>
<li><p><code>kibana_system</code>: Un service account utilisé uniquement par le serveur Kibana pour parler à Elasticsearch. Il ne peut pas être utilisé pour se connecter au tableau de bord.</p>
</li>
</ul>
<h3 id="heading-b-le-principe-du-moindre-privilege">B. Le Principe du Moindre Privilège</h3>
<p>Vous devriez créer des rôles personnalisés pour chaque cas d'usage spécifique.</p>
<ul>
<li><p><strong>Le Rôle "Developer" :</strong> Peut lire et surveiller les index mais ne peut pas supprimer de données ou changer les paramètres du cluster.</p>
</li>
<li><p><strong>Le Rôle "App" :</strong> Peut écrire dans l'index <code>logs-*</code> mais ne peut pas lire l'index <code>salary-data</code>.</p>
</li>
<li><p><strong>Document Level Security (DLS) :</strong> Vous pouvez même restreindre l'accès au sein d'un seul index.</p>
<ul>
<li><em>Exemple :</em> "L'utilisateur A peut chercher dans l'index <code>employees</code>, mais seulement les documents où <code>department: 'marketing'</code>."</li>
</ul>
</li>
</ul>
<hr />
<h2 id="heading-3-audit-logging-la-boite-noire">3. Audit Logging (La "Boîte Noire")</h2>
<p>Si des données disparaissent ou fuitent, comment savez-vous ce qui s'est passé ?</p>
<ul>
<li><p><strong>Ce qu'il suit :</strong> Vous pouvez le configurer pour journaliser des événements spécifiques : "Authentication Failed," "Index Deleted," ou même "L'utilisateur X a cherché la requête Y."</p>
</li>
<li><p><strong>Conformité :</strong> C'est obligatoire pour des normes comme le RGPD, HIPAA et PCI-DSS.</p>
</li>
<li><p><strong>Avertissement de performance :</strong> L'Audit logging est intensif en I/O.</p>
</li>
<li><p><strong>Mauvaise pratique :</strong> Journaliser chaque opération de "read". Cela remplira votre disque de logs et ralentira les performances de recherche.</p>
</li>
<li><p><strong>Meilleure pratique :</strong> Journaliser uniquement les opérations d'écriture/suppression" et les "authentication failures" pour détecter les attaques par force brute.</p>
</li>
</ul>
<h1 id="heading-deployment-methods">Deployment Methods</h1>
<h2 id="heading-lintroduction-une-taille-unique-ne-convient-pas-a-tous">L'Introduction : Une taille unique ne convient pas à tous</h2>
<p>Elasticsearch est agnostique en matière d'infrastructure. Il s'exécute partout où Java s'exécute. Le choix de la méthode de déploiement dépend généralement de trois facteurs :</p>
<ul>
<li><p><strong>Infrastructure existante :</strong> Êtes-vous déjà "all-in" sur Kubernetes ? Avez-vous des racks de serveurs physiques ?</p>
</li>
<li><p><strong>Expertise de l'équipe :</strong> Vos opérationnels sont-ils à l'aise avec le Linux kernel tuning, ou préfèrent-ils écrire des manifestes YAML ?</p>
</li>
<li><p><strong>Besoins d'évolutivité :</strong> Avez-vous besoin d'ajouter 10 nœuds en 5 minutes pendant le Black Friday, ou votre cluster est-il relativement statique ?</p>
</li>
</ul>
<hr />
<h2 id="heading-1-bare-metal-virtual-machines-lapproche-classique">1. Bare Metal / Virtual Machines (L'approche "Classique")</h2>
<p>C'est la manière traditionnelle de déployer des logiciels. Vous traitez Elasticsearch comme n'importe quelle autre base de données (PostgreSQL, MySQL).</p>
<h3 id="heading-comment-ca-marche">Comment ça marche :</h3>
<p>Vous provisionnez des serveurs Linux (matériel physique ou VMs comme EC2/Azure VMs), installez Java (si vous utilisez d'anciennes versions d'ES), configurez les prérequis de l'OS (comme discuté dans la section OS Tuning), ajoutez le dépôt Elastic et installez via les gestionnaires de paquets :</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Exemple Ubuntu/Debian</span>
wget -qO - [https://artifacts.elastic.co/GPG-KEY-elasticsearch](https://artifacts.elastic.co/GPG-KEY-elasticsearch) | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
sudo apt-get install elasticsearch
</code></pre>
<p><strong>Avantages :</strong></p>
<ul>
<li><p><strong>Performance Maximale :</strong> Vous avez un accès direct aux ressources matérielles sans aucune couche d'abstraction de conteneurisation. C'est idéal pour les cas d'utilisation ultra-performants.</p>
</li>
<li><p><strong>Dépannage plus simple :</strong> Si vous devez déboguer la latence réseau ou les I/O disque, vous utilisez les outils Linux standard (iostat, tcpdump) directement sur l'hôte.</p>
</li>
<li><p><strong>La persistance est facile :</strong> Les données sont écrites directement sur les disques attachés. Vous n'avez pas à vous soucier des Container Storage Interfaces (CSI) complexes.</p>
</li>
</ul>
<p><strong>Inconvénients :</strong></p>
<ul>
<li><p><strong>Surcharge de maintenance :</strong> Monter en charge signifie provisionner manuellement un nouveau serveur et le configurer. Les mises à jour nécessitent des "rolling restarts" manuels et prudents.</p>
</li>
<li><p><strong>Configuration Drift :</strong> Sans outils de gestion de configuration solides (Ansible, Chef, Puppet), les serveurs peuvent lentement diverger dans leurs configurations au fil du temps, menant à des problèmes du type "ça marche sur le nœud 1 mais pas sur le nœud 2".</p>
</li>
<li><p><strong>Idéal pour :</strong> Les environnements IT traditionnels, les clusters stables de longue durée et les équipes ayant de solides compétences en administration Linux.</p>
</li>
</ul>
<h2 id="heading-2-docker-containers-lapproche-prototypage-rapide">2. Docker / Containers (L'approche "Prototypage Rapide")</h2>
<p>Docker a tout changé en permettant aux développeurs de lancer des stacks complexes localement en quelques secondes.</p>
<p><strong>Comment ça marche :</strong> Elastic fournit des images Docker officielles et pré-renforcées. Vous exécutez rarement des commandes <code>docker run</code> simples. Au lieu de cela, vous utilisez Docker Compose pour définir un cluster multi-nœuds dans un seul fichier YAML.</p>
<p><strong>Avantages :</strong></p>
<ul>
<li><p><strong>Vitesse :</strong> Vous pouvez passer de zéro à un cluster fonctionnel de 3 nœuds sur votre ordinateur portable en moins de 60 secondes.</p>
</li>
<li><p><strong>Cohérence :</strong> L'environnement est identique entre le développement, les tests et la pré-production. "Ça marche sur ma machine" signifie réellement quelque chose.</p>
</li>
<li><p><strong>Isolation :</strong> Les dépendances sont empaquetées avec le conteneur.</p>
</li>
</ul>
<p><strong>Inconvénients :</strong></p>
<ul>
<li><p><strong>Pas un Orchestrateur de Production :</strong> Docker Compose n'est généralement pas recommandé pour les environnements de production multi-hôtes. Il manque de fonctionnalités avancées de failover, de scaling et de networking nécessaires pour la haute disponibilité.</p>
</li>
<li><p><strong>State Management :</strong> Vous devez être très prudent avec le volume mapping pour vous assurer que les données persistent si un conteneur redémarre.</p>
</li>
</ul>
<p><strong>Idéal pour :</strong> Le développement local, les pipelines de tests CI/CD et les très petits déploiements de production sur un seul hôte.</p>
<h2 id="heading-3-kubernetes-amp-eck-le-standard-cloud-native">3. Kubernetes &amp; ECK (Le Standard "Cloud-Native")</h2>
<p>Si votre organisation a adopté Kubernetes (K8s), c'est presque certainement ainsi que vous devriez déployer Elasticsearch. Mais il y a un avertissement massif.</p>
<p><strong>Le Piège du "Helm Chart"</strong> Les nouveaux utilisateurs de K8s essaient souvent de déployer Elasticsearch en utilisant des graphiques Helm génériques standard. Évitez cela. Elasticsearch est un système distribué complexe et stateful. Un déploiement K8s standard ne comprend pas que vous ne pouvez pas simplement tuer 3 nœuds maîtres simultanément pendant une mise à jour sans détruire le cluster.</p>
<p><strong>La Solution : Elastic Cloud sur Kubernetes (ECK)</strong> Elastic a développé son propre Kubernetes Operator appelé ECK.</p>
<p><strong>Qu'est-ce qu'un Operator ?</strong> Pensez-y comme un robot logiciel qui s'exécute à l'intérieur de votre cluster K8s et possède une connaissance opérationnelle humaine sur Elasticsearch. Il sait exactement dans quel ordre redémarrer les nœuds pour que le cluster ne tombe jamais en panne.</p>
<p><strong>Comment ça marche :</strong> Au lieu de gérer directement les pods et les statefulsets, vous installez l'opérateur ECK, puis vous soumettez un simple YAML de ressource personnalisée à K8s disant "Je veux un cluster Elasticsearch".</p>
<p>L'Opérateur voit le YAML et crée automatiquement les Services, StatefulSets, PersistentVolumeClaims et génère les certificats TLS.</p>
<p><strong>Avantages :</strong></p>
<ul>
<li><p><strong>Day 2 Operations Automatisées :</strong> L'Opérateur gère automatiquement le scaling, les rolling upgrades, la configuration sécurisée et les backups.</p>
</li>
<li><p><strong>Écosystème Elastic :</strong> Il rend le déploiement de Kibana, APM Server et Beats aux côtés d'Elasticsearch incroyablement facile.</p>
</li>
</ul>
<p><strong>Inconvénients :</strong></p>
<ul>
<li><strong>Haute Complexité :</strong> Vous avez besoin d'une expertise Kubernetes significative avant d'ajouter la complexité d'exécuter une base de données stateful par-dessus.</li>
</ul>
<p><strong>Idéal pour :</strong> Les organisations modernes, cloud-native, nécessitant une infrastructure hautement dynamique et évolutive.</p>
<h1 id="heading-configuration-best-practices">Configuration Best Practices</h1>
<h2 id="heading-le-guide-critique-de-configuration-elasticsearchyml">Le Guide Critique de Configuration <code>elasticsearch.yml</code></h2>
<p>Le fichier <code>elasticsearch.yml</code> est le centre de contrôle de votre nœud. Bien qu'il existe des centaines de paramètres, se tromper sur ces quelques-uns est la cause la plus fréquente de pannes de production ou de perte de données.</p>
<hr />
<h2 id="heading-1-identite-noms-de-cluster-amp-de-noeud">1. Identité : Noms de Cluster &amp; de Nœud</h2>
<p>Dans le vide, les noms ne semblent pas techniques. Dans un système distribué, ils sont vitaux pour l'observabilité et l'isolation.</p>
<h3 id="heading-a-clustername">A. <code>cluster.name</code></h3>
<ul>
<li><p><strong>Le Défaut :</strong> <code>elasticsearch</code></p>
</li>
<li><p><strong>Le Risque :</strong> Si vous laissez cela par défaut, un développeur malveillant ou imprudent démarrant une instance locale sur le même réseau (Wi-Fi ou VPN) pourrait accidentellement découvrir et rejoindre votre cluster de production.</p>
</li>
<li><p><strong>Meilleure Pratique :</strong> Soyez descriptif et spécifique à l'environnement.</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">cluster.name:</span> <span class="hljs-string">prod-search-cluster-v1</span>
</code></pre>
<h3 id="heading-b-nodename">B. <code>node.name</code></h3>
<ul>
<li><p><strong>Le Défaut :</strong> Le nom d'hôte du serveur.</p>
</li>
<li><p><strong>Le Risque :</strong> Des noms d'hôtes comme <code>ip-10-0-0-5</code> sont difficiles à lire dans les logs ou les tableaux de bord Kibana.</p>
</li>
<li><p><strong>Meilleure Pratique :</strong> Utilisez une convention de nommage qui indique le rôle et le numéro du nœud. Cela rend le débogage beaucoup plus rapide ("Oh, <code>prod-master-02</code> est en panne" est plus actionnable que "Le Serveur X est en panne").</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">node.name:</span> <span class="hljs-string">prod-data-hot-01</span>
</code></pre>
<hr />
<h2 id="heading-2-discovery-prevenir-le-split-brain">2. Discovery (Prévenir le "Split-Brain")</h2>
<p>"Discovery" est le processus par lequel les nœuds se trouvent et élisent un leader (Master). Si cela est mal configuré, les nœuds formeront des clusters séparés et concurrents, menant à une incohérence des données (Split-Brain).</p>
<h3 id="heading-a-discoveryseedhosts-lannuaire-telephonique">A. <code>discovery.seed_hosts</code> (L'annuaire téléphonique)</h3>
<p>Ce paramètre dit au nœud : <em>"Quand tu te réveilles, appelle ces gens pour demander où est le cluster."</em></p>
<ul>
<li><strong>Configuration :</strong> Vous n'avez pas besoin de lister chaque nœud. Listez simplement les adresses IP ou les noms d'hôtes de vos nœuds éligibles maîtres.</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">discovery.seed_hosts:</span> [<span class="hljs-string">"192.168.1.10"</span>, <span class="hljs-string">"192.168.1.11"</span>, <span class="hljs-string">"192.168.1.12"</span>]
</code></pre>
<blockquote>
<p><strong>Note :</strong> Si vous utilisez le Cloud/AWS, vous pourriez utiliser un plugin (comme <code>discovery-ec2</code>) pour les détecter automatiquement, mais coder les IP en dur est plus sûr pour le bare metal.</p>
</blockquote>
<h3 id="heading-b-clusterinitialmasternodes-le-bootstrapper">B. <code>cluster.initial_master_nodes</code> (Le Bootstrapper)</h3>
<p>C'est le paramètre le plus déroutant pour les débutants. Il n'est utilisé qu'<strong>une seule fois</strong> dans toute la vie du cluster : la toute première fois que vous l'allumez.</p>
<ul>
<li><p><strong>Le Problème :</strong> Quand vous démarrez 3 nœuds vides, ils pensent tous "Je devrais être le Roi". Sans ce paramètre, ils pourraient former 3 clusters séparés de 1 nœud chacun.</p>
</li>
<li><p><strong>La Solution :</strong> Ce paramètre les force à former un quorum. Il dit : "Ne démarre pas le cluster tant que tu ne vois pas un vote de ces nœuds spécifiques."</p>
</li>
<li><p><strong>Configuration :</strong> <strong>DOIT</strong> correspondre exactement au <code>node.name</code> de vos nœuds maîtres.</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">cluster.initial_master_nodes:</span> [<span class="hljs-string">"prod-master-01"</span>, <span class="hljs-string">"prod-master-02"</span>, <span class="hljs-string">"prod-master-03"</span>]
</code></pre>
<blockquote>
<p><strong>Avertissement Critique :</strong> Une fois que le cluster est formé pour la première fois, supprimez ce paramètre (ou commentez-le) de votre gestion de configuration. Si vous le laissez, et que plus tard vous essayez de redémarrer un nœud pour rejoindre un cluster existant, il pourrait essayer d'amorcer un nouveau cluster au lieu de rejoindre l'ancien.</p>
</blockquote>
<hr />
<h2 id="heading-3-path-settings-sauver-votre-os">3. Path Settings (Sauver votre OS)</h2>
<p>Par défaut, Elasticsearch écrit les données dans <code>/var/lib/elasticsearch</code> et les logs dans <code>/var/log/elasticsearch</code>. C'est dangereux.</p>
<h3 id="heading-a-le-risque-de-la-partition-racine">A. Le risque de la "Partition Racine"</h3>
<p>Sur Linux, <code>/var</code> fait généralement partie de la partition racine (<code>/</code>). Si vos utilisateurs inondent le cluster de données (remplissant <code>path.data</code>) ou si le cluster envoie des boucles d'erreurs massives (remplissant <code>path.logs</code>), vous remplirez le disque racine à 100%.</p>
<ul>
<li><strong>La Conséquence :</strong> Quand <code>/</code> est plein, Linux plante. Vous ne pouvez pas vous connecter en SSH pour réparer. Vous devez redémarrer physiquement en mode secours.</li>
</ul>
<h3 id="heading-b-pathdata">B. <code>path.data</code></h3>
<ul>
<li><strong>Meilleure Pratique :</strong> Montez un grand disque physique séparé (NVMe/SSD) sur un chemin comme <code>/mnt/data</code> et pointez Elasticsearch dessus. Si ce disque se remplit, Elasticsearch cesse de fonctionner, mais l'OS reste en vie, vous permettant de résoudre le problème.</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">path.data:</span> <span class="hljs-string">/mnt/data/elasticsearch</span>
</code></pre>
<p><strong>Astuce Pro (Striping) :</strong> Vous pouvez fournir plusieurs chemins. Elasticsearch agira comme un RAID 0 logiciel, répartissant (striping) les données entre eux.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">path.data:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">/mnt/disk1</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">/mnt/disk2</span>
</code></pre>
<h3 id="heading-c-pathlogs">C. <code>path.logs</code></h3>
<ul>
<li><strong>Meilleure Pratique :</strong> Idéalement, expédiez les logs vers un système distant (en utilisant Filebeat). Si vous les stockez localement, gardez-les sur une partition séparée de <code>path.data</code> pour qu'un pic de logs massif ne consomme pas votre espace de stockage de données.</li>
</ul>
<h1 id="heading-operations-amp-maintenance-from-hobby-to-production">Operations &amp; Maintenance: From Hobby to Production</h1>
<p>Cette section définit la différence entre un cluster "hobby" et un cluster de "production". Le déploiement est un événement unique ; les opérations sont éternelles.</p>
<hr />
<h2 id="heading-1-monitoring-vous-ne-pouvez-pas-gerer-ce-que-vous-ne-voyez-pas">1. Monitoring: Vous ne pouvez pas gérer ce que vous ne voyez pas</h2>
<p>Une erreur courante est d'attendre que les utilisateurs se plaignent que "La recherche est lente" avant de vérifier le cluster. Vous avez besoin d'une visibilité proactive.</p>
<h3 id="heading-a-les-outils">A. Les Outils</h3>
<p><strong>1. Elastic Stack Monitoring (La Voie Native)</strong></p>
<ul>
<li><p><strong>Comment ça marche :</strong> Vous activez <code>xpack.monitoring</code>. Le cluster envoie des métriques à lui-même (ou de préférence, à un "Monitoring Cluster" séparé pour éviter d'ajouter de la charge au système de production).</p>
</li>
<li><p><strong>Avantages :</strong> Profondément intégré ; l'interface Kibana est pré-construite et excellente.</p>
</li>
</ul>
<p><strong>2. Prometheus &amp; Grafana (La Voie Cloud-Native)</strong></p>
<ul>
<li><p><strong>Comment ça marche :</strong> Vous exécutez un conteneur sidecar <code>elasticsearch-exporter</code>. Prometheus le scrape, et Grafana le visualise.</p>
</li>
<li><p><strong>Avantages :</strong> Standard de l'industrie ; vous permet de corréler les métriques Elasticsearch avec les métriques Linux/Network sur le même tableau de bord.</p>
</li>
</ul>
<h3 id="heading-b-les-big-4-metriques-a-surveiller">B. Les "Big 4" Métriques à surveiller</h3>
<ol>
<li><p><strong>JVM Heap Usage</strong></p>
<ul>
<li><p><strong>Sain :</strong> Un motif en "dents de scie" (la mémoire se remplit, le Garbage Collection la nettoie, répéter).</p>
</li>
<li><p><strong>Danger :</strong> Une ligne plate près de 75-90%. Cela signifie que le nœud est affamé de mémoire et va bientôt planter avec une <code>OutOfMemoryError</code>.</p>
</li>
</ul>
</li>
<li><p><strong>Garbage Collection (GC) Count &amp; Time</strong></p>
<ul>
<li><strong>Danger :</strong> Si le temps de GC "Old Gen" grimpe, votre nœud met en pause l'exécution (Stop-the-World) pour nettoyer la mémoire. Les requêtes de recherche seront suspendues pendant ces pauses.</li>
</ul>
</li>
<li><p><strong>CPU Usage</strong></p>
<ul>
<li>Un CPU élevé est normal pendant l'indexation lourde, mais s'il reste à 100% en continu, vos nœuds sont sous-dimensionnés ou vos requêtes sont trop complexes (ex : wildcards commençant par <code>*</code>).</li>
</ul>
</li>
<li><p><strong>Thread Pool Rejections</strong></p>
<ul>
<li>C'est la métrique d'erreur la plus critique. Cela signifie que le nœud dit : <em>"Je suis trop occupé ; je ne peux pas accepter cette requête."</em> Si les rejets de recherche ou d'écriture sont &gt; 0, vous avez un problème de capacité.</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-2-backups-replicas-backups">2. Backups: Replicas ≠ Backups</h2>
<p>C'est la leçon la plus importante en matière de sécurité des données.</p>
<h3 id="heading-le-mythe">Le Mythe</h3>
<blockquote>
<p>"J'ai 2 réplicas, donc j'ai 3 copies de mes données. Je n'ai pas besoin de backups."</p>
</blockquote>
<h3 id="heading-la-realite">La Réalité</h3>
<p>Les réplicas protègent contre les <strong>Hardware Failure</strong> (crash de disque). Ils ne protègent pas contre l'<strong>Erreur Humaine</strong>.</p>
<ul>
<li><p><strong>Scénario :</strong> Vous exécutez accidentellement <code>DELETE /users</code>.</p>
</li>
<li><p><strong>Résultat :</strong> Elasticsearch supprime le Primary shard immédiatement, et propage instantanément cette instruction de suppression à tous les Replica shards. Vos données ont disparu des 3 copies en quelques millisecondes.</p>
</li>
</ul>
<h3 id="heading-la-solution-snapshots-amp-slm">La Solution : Snapshots &amp; SLM</h3>
<p>Vous devez prendre des <strong>Snapshots</strong> (instantanés), qui sont des backups incrémentiels envoyés vers un stockage de référentiel externe (S3, Google Cloud Storage, Azure Blob, ou un lecteur NFS partagé).</p>
<ul>
<li><p><strong>Incrémentiel :</strong> Le premier snapshot copie tout. Le second snapshot copie seulement les segments qui ont changé. C'est léger et rapide.</p>
</li>
<li><p><strong>SLM (Snapshot Lifecycle Management) :</strong> N'écrivez pas de scripts manuels. Utilisez la fonctionnalité intégrée SLM pour définir une politique.</p>
</li>
<li><p><strong>Exemple de Politique :</strong> "Prendre un snapshot chaque nuit à 2h du matin. Garder les 30 derniers snapshots. Supprimer les plus anciens automatiquement."</p>
</li>
</ul>
<hr />
<h2 id="heading-3-updates-la-strategie-de-rolling-restart">3. Updates: La stratégie de "Rolling Restart"</h2>
<p>Mettre à jour une base de données signifiait autrefois "Scheduled Downtime" (Temps d'arrêt planifié) un dimanche soir. Avec Elasticsearch, vous pouvez mettre à jour sans temps d'arrêt si vous suivez la procédure de "Rolling Restart".</p>
<h3 id="heading-la-logique">La Logique</h3>
<p>Vous n'éteignez jamais tout le cluster. Vous éteignez un nœud, le mettez à jour, le rallumez et passez au suivant.</p>
<h3 id="heading-letape-critique-desactiver-lallocation">L'Étape Critique : Désactiver l'Allocation</h3>
<p>Avant d'arrêter un nœud, vous devez dire au cluster : <em>"J'éteins ce nœud exprès. Ne panique pas et ne commence pas à reconstruire ses données ailleurs."</em></p>
<h3 id="heading-le-workflow">Le Workflow</h3>
<p><strong>1. Stop Allocation</strong> (Cela fige la disposition du cluster pour que les shards ne bougent pas).</p>
<pre><code class="lang-json">PUT _cluster/settings
{
  <span class="hljs-attr">"persistent"</span>: {
    <span class="hljs-attr">"cluster.routing.allocation.enable"</span>: <span class="hljs-string">"primaries"</span>
  }
}
</code></pre>
<p><strong>2. Stop the Node</strong> Exécutez <code>systemctl stop elasticsearch</code>.</p>
<p><strong>3. Upgrade</strong> Mettez à jour le paquet ou remplacez l'image Docker.</p>
<p><strong>4. Start the Node</strong> Exécutez <code>systemctl start elasticsearch</code>.</p>
<p><strong>5. Wait for Green</strong> Surveillez les logs ou <code>_cat/nodes</code> jusqu'à ce que le nœud rejoigne le cluster.</p>
<p><strong>6. Re-enable Allocation</strong></p>
<pre><code class="lang-json">PUT _cluster/settings
{
  <span class="hljs-attr">"persistent"</span>: {
    <span class="hljs-attr">"cluster.routing.allocation.enable"</span>: <span class="hljs-literal">null</span>
  }
}
</code></pre>
<p><strong>7. Repeat</strong> Passez au nœud suivant.</p>
<h1 id="heading-common-pitfalls-the-difference-between-novices-and-experts">Common Pitfalls: The Difference Between Novices and Experts</h1>
<p>Cette section sépare les novices des experts. Ce sont les problèmes qui n'apparaissent pas dans un tutoriel "Hello World" ; ils n'apparaissent que lorsque vous êtes en production, sous charge, et généralement à 3 heures du matin.</p>
<hr />
<h2 id="heading-1-split-brain-le-probleme-des-deux-capitaines">1. Split Brain (Le problème des "Deux Capitaines")</h2>
<p>Ce diagramme fournit une représentation visuelle du scénario de "split-brain", où une partition réseau conduit à la formation de deux clusters séparés, chacun avec son propre maître, risquant l'incohérence des données.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770047687950/c6394869-3499-4771-aa96-7f14ad27ef0a.jpeg" alt class="image--center mx-auto" /></p>
<p>C'est le scénario cauchemardesque pour les systèmes distribués.</p>
<h3 id="heading-le-scenario">Le Scénario</h3>
<p>Imaginez que vous avez un cluster de 3 nœuds (A, B, C) dans une seule pièce. Un switch réseau tombe en panne, coupant la pièce en deux. Les nœuds A et B peuvent se parler, mais le nœud C est isolé.</p>
<h3 id="heading-le-glitch">Le Glitch</h3>
<ol>
<li><p>Les nœuds A+B réalisent que C a disparu. Ils élisent le Nœud A comme Master.</p>
</li>
<li><p>Le nœud C pense que A et B sont morts. Il s'élit lui-même comme Master.</p>
</li>
</ol>
<h3 id="heading-le-resultat-split-brain">Le Résultat (Split Brain)</h3>
<p>Vous avez maintenant deux maîtres actifs dans le même cluster.</p>
<ul>
<li><p>L'Application 1 écrit des données sur le Nœud A.</p>
</li>
<li><p>L'Application 2 écrit des données sur le Nœud C.</p>
</li>
</ul>
<h3 id="heading-la-catastrophe">La Catastrophe</h3>
<p>Quand le réseau revient, vous avez deux versions différentes de l'historique. Elasticsearch ne peut pas "fusionner" ces chronologies. Vous perdrez probablement les données écrites du côté le plus petit de la partition.</p>
<h3 id="heading-le-fix-quorum">Le Fix (Quorum)</h3>
<ul>
<li><p><strong>Dans les anciennes versions (6.x et moins) :</strong> Vous deviez définir manuellement <code>discovery.zen.minimum_master_nodes</code> à <code>(N/2) + 1</code>.</p>
</li>
<li><p><strong>Dans les versions modernes (7.x+) :</strong> Elasticsearch utilise automatiquement un système de Voting Configuration. Cependant, vous devez vous assurer d'avoir <strong>3 nœuds éligibles maîtres</strong> (un nombre impair) pour qu'il y ait toujours un gagnant majoritaire lors d'un vote. <strong>Ne faites jamais tourner un cluster de production avec exactement 2 nœuds maîtres.</strong></p>
</li>
</ul>
<hr />
<h2 id="heading-2-mapping-explosion-la-mort-par-champs">2. Mapping Explosion (La "Mort par Champs")</h2>
<p>Elasticsearch est sans schéma par défaut (Dynamic Mapping), ce qui semble génial jusqu'à ce que cela plante votre cluster.</p>
<h3 id="heading-le-scenario-1">Le Scénario</h3>
<p>Vous journalisez les cookies utilisateurs ou les en-têtes HTTP. Un développeur décide d'envoyer un document JSON où les clés sont des UUID uniques ou des horodatages.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"2024-01-30_12:00"</span>: <span class="hljs-string">"error"</span>,
  <span class="hljs-attr">"2024-01-30_12:01"</span>: <span class="hljs-string">"info"</span>
}
</code></pre>
<h3 id="heading-le-bug">Le Bug</h3>
<p>Le dynamic mapping voit un nouveau champ (<code>"2024-01-30_12:00"</code>) et l'ajoute au <strong>Cluster State</strong> (le registre global de tous les paramètres).</p>
<h3 id="heading-le-resultat">Le Résultat</h3>
<p>Chaque clé unique devient un nouveau champ. Si vous envoyez 10 000 documents avec des clés uniques, vous créez 10 000 champs.</p>
<ul>
<li><p>Le Cluster State devient massif (des centaines de Mo).</p>
</li>
<li><p>Cet état doit être synchronisé avec chaque nœud. Le mettre à jour prend des secondes. Le cluster devient non réactif.</p>
</li>
</ul>
<h3 id="heading-le-correctif">Le Correctif</h3>
<ol>
<li><p><strong>Désactiver le Dynamic Mapping :</strong> Définissez <code>dynamic: false</code> ou <code>strict</code> dans vos modèles de production.</p>
</li>
<li><p><strong>Utiliser le Type de Données</strong> <code>flattened</code> : Si vous devez stocker du JSON non structuré avec des clés inconnues, mappez ce champ spécifique comme <code>type: "flattened"</code>. Elasticsearch traitera l'objet JSON entier comme un seul champ mot-clé, empêchant l'explosion.</p>
</li>
<li><p><strong>Limiter les Champs :</strong> La limite par défaut est de 1 000 champs par index (<code>index.mapping.total_fields.limit</code>). <strong>Ne l'augmentez pas.</strong> Si vous l'atteignez, votre modèle de données est mauvais.</p>
</li>
</ol>
<hr />
<h2 id="heading-3-deep-pagination-la-requete-tueuse">3. Deep Pagination (La "Requête Tueuse")</h2>
<p>Vos utilisateurs veulent sauter à la "Page 50 000" des résultats de recherche. Vous devez leur dire "Non".</p>
<h3 id="heading-le-scenario-2">Le Scénario</h3>
<p>Un utilisateur exécute une requête avec <code>from: 50000, size: 10</code>.</p>
<h3 id="heading-le-bug-distributed-sorting-cost">Le Bug (Distributed Sorting Cost)</h3>
<p>Pour trouver le "top 10" des résultats commençant à 50 000, Elasticsearch ne peut pas simplement sauter les 50 000 premiers enregistrements.</p>
<ol>
<li><p>Chaque shard impliqué dans la recherche doit récupérer ses propres 50 010 meilleurs résultats et les garder en mémoire.</p>
</li>
<li><p>Si vous avez 10 shards, le nœud de coordination reçoit <code>50 010 * 10 = 500 100</code> documents.</p>
</li>
<li><p>Il doit trier ce demi-million d'enregistrements en RAM, rejeter les 500 000 premiers et renvoyer les 10 derniers.</p>
</li>
</ol>
<h3 id="heading-le-resultat-1">Le Résultat</h3>
<p>Pics massifs de CPU et boucles de Garbage Collection (GC). Si plusieurs utilisateurs font cela simultanément, le nœud manque de mémoire (OOM) et plante.</p>
<h3 id="heading-le-correctif-1">Le Correctif</h3>
<ol>
<li><p><strong>Limite stricte :</strong> Elasticsearch définit par défaut <code>index.max_result_window</code> à 10 000. Ne l'augmentez pas à moins de savoir exactement ce que vous faites.</p>
</li>
<li><p><strong>Pour les Utilisateurs (</strong><code>search_after</code><strong>)</strong> : C'est la manière efficace de paginer. Cela dit à Elasticsearch : "Donne-moi les 10 résultats suivants après cette valeur de tri spécifique du dernier résultat." Cela ne nécessite pas de scan profond.</p>
</li>
<li><p><strong>Pour les Scripts (Scroll API / PIT) :</strong> Si vous devez exporter l'ensemble des données, utilisez le Point-in-Time (PIT) ou l'API Scroll, qui est conçue pour le traitement par lots.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Elasticsearch in Production: The Definitive Architecture & Operations Guide]]></title><description><![CDATA[Definition
Elasticsearch is a distributed, RESTful search and analytics engine capable of solving a growing number of use cases. At its core, it is a NoSQL database, but unlike traditional databases designed for storage and retrieval, Elasticsearch i...]]></description><link>https://nosqlnest.net/engelasticsearch-in-production-the-definitive-architecture-and-operations-guide</link><guid isPermaLink="true">https://nosqlnest.net/engelasticsearch-in-production-the-definitive-architecture-and-operations-guide</guid><category><![CDATA[elasticsearch]]></category><category><![CDATA[deployment]]></category><category><![CDATA[NoSQL]]></category><category><![CDATA[#NoSQLDatabase]]></category><category><![CDATA[Search engine optimization]]></category><category><![CDATA[NoSQL databases]]></category><category><![CDATA[no sql]]></category><category><![CDATA[NoSQL Database]]></category><category><![CDATA[elastic-search]]></category><dc:creator><![CDATA[AKEBLI Ouassim]]></dc:creator><pubDate>Mon, 02 Feb 2026 16:06:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1769968014359/7a846bba-9a15-419d-8c1a-dd060d7f9dbf.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-definition">Definition</h1>
<p>Elasticsearch is a distributed, RESTful search and analytics engine capable of solving a growing number of use cases. At its core, it is a NoSQL database, but unlike traditional databases designed for storage and retrieval, Elasticsearch is optimized for <strong>speed</strong> and <strong>relevance</strong>.</p>
<hr />
<h1 id="heading-architecture-amp-capacity-planning">Architecture &amp; Capacity Planning</h1>
<p>This diagram illustrates a typical producción cluster architecture, showing the separation of duties between dedicated master nodes, tiered data nodes (hot/warm), and coordinating nodes.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770047462097/ea0805d0-f516-425b-9cc0-0375df47b29b.jpeg" alt class="image--center mx-auto" /></p>
<h2 id="heading-1-node-roles">1. Node Roles</h2>
<h3 id="heading-a-mandatory-basic-roles">A. Mandatory Basic Roles</h3>
<h4 id="heading-1-master-eligible-node-master">1. Master-eligible node (<code>master</code>)</h4>
<ul>
<li><p><strong>Function:</strong> Responsible for cluster-wide actions such as creating or deleting indices, tracking which nodes are part of the cluster, and allocating shards to nodes.</p>
</li>
<li><p><strong>Why it's mandatory:</strong> Without a master, the cluster cannot be formed, and no cluster-level changes can be tracked.</p>
</li>
<li><p><strong>Production Tip:</strong> You typically need 3 dedicated master nodes for High Availability (to avoid "split-brain").</p>
</li>
</ul>
<h4 id="heading-2-data-node-data">2. Data node (<code>data</code>)</h4>
<ul>
<li><p><strong>Function:</strong> Holds the shards that contain your indexed documents. These nodes perform data-related operations like CRUD, search, and aggregations.</p>
</li>
<li><p><strong>Why it's mandatory:</strong> Without data nodes, you cannot store any data.</p>
</li>
<li><p><strong>Sub-roles (Tiered Architecture):</strong></p>
<ul>
<li><p><code>data_content</code>: For general-purpose data that doesn't fit a time-series lifecycle.</p>
</li>
<li><p><code>data_hot</code>: For strictly time-series data (e.g., logs) that is being actively written to and queried.</p>
</li>
<li><p><code>data_warm</code>: For older data that is read-only but still queried frequently.</p>
</li>
<li><p><code>data_cold</code>: For data accessed infrequently (optimized for storage).</p>
</li>
<li><p><code>data_frozen</code>: For data stored in object storage (S3) and rarely queried (Searchable Snapshots).</p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-b-core-utility-roles-highly-recommended">B. Core Utility Roles (Highly Recommended)</h3>
<p><em>While not strictly "mandatory" for the cluster to start, these are standard in almost all production environments.</em></p>
<h4 id="heading-1-ingest-node-ingest">1. Ingest node (<code>ingest</code>)</h4>
<ul>
<li><p><strong>Function:</strong> Runs "ingest pipelines" to pre-process documents before indexing. This acts like a lightweight Logstash inside Elasticsearch (e.g., parsing JSON, removing fields, renaming fields).</p>
</li>
<li><p><strong>Default Behavior:</strong> Every node is an ingest node by default unless configured otherwise.</p>
</li>
</ul>
<h4 id="heading-2-coordinating-only-node-no-specific-role-set">2. Coordinating-only node (No specific role set)</h4>
<ul>
<li><p><strong>Function:</strong> These nodes have an empty role list (<code>node.roles: []</code>). They act as "Smart Load Balancers." They accept search requests, distribute them to the specific data nodes holding the data, gather the results, perform the final reduction (sorting/aggregating), and send the response to the client.</p>
</li>
<li><p><strong>Use Case:</strong> Large clusters with heavy search traffic to prevent data nodes from being overwhelmed by CPU-intensive aggregation tasks.</p>
</li>
</ul>
<h3 id="heading-c-specialized-roles-optional">C. Specialized Roles (Optional)</h3>
<p><em>These are specific to certain features of the Elastic Stack.</em></p>
<h4 id="heading-1-machine-learning-node-ml">1. Machine Learning node (<code>ml</code>)</h4>
<ul>
<li><p><strong>Function:</strong> Runs the native Machine Learning jobs (anomaly detection, forecasting).</p>
</li>
<li><p><strong>Requirement:</strong> These jobs are CPU and RAM intensive. If you use ML features, you must have at least one ML node.</p>
</li>
</ul>
<h4 id="heading-2-remote-cluster-client-remoteclusterclient">2. Remote Cluster Client (<code>remote_cluster_client</code>)</h4>
<ul>
<li><p><strong>Function:</strong> Allows the cluster to connect to other clusters (Cross-Cluster Search or Cross-Cluster Replication).</p>
</li>
<li><p><strong>Default:</strong> Enabled by default on all nodes.</p>
</li>
</ul>
<h4 id="heading-3-transform-node-transform">3. Transform node (<code>transform</code>)</h4>
<ul>
<li><strong>Function:</strong> Runs transform jobs which pivot or summarize data into new indices (similar to "Materialized Views" in SQL).</li>
</ul>
<h4 id="heading-4-voting-only-node-votingonly">4. Voting-only node (<code>voting_only</code>)</h4>
<ul>
<li><p><strong>Function:</strong> A master-eligible node that can participate in master elections (voting) but cannot actually become the elected master.</p>
</li>
<li><p><strong>Use Case:</strong> Rarely used; mostly for tie-breaking in even-numbered clusters.</p>
</li>
</ul>
<hr />
<h2 id="heading-2-hardware-requirements">2. Hardware Requirements</h2>
<p><em>These figures are based on the constraints of the JVM (Java Virtual Machine) and operational best practices for recovery and stability.</em></p>
<h3 id="heading-a-ram-memory">A. RAM (Memory)</h3>
<p><em>This is the most critical resource. It is split between the JVM Heap (for the application) and the OS Filesystem Cache (for Lucene segment files).</em></p>
<ul>
<li><p><strong>Minimum:</strong> <code>8 GB</code> - <code>16 GB</code></p>
<ul>
<li>Running a production node with less than 8GB is risky. You need enough headroom for the OS to cache frequently accessed index segments.</li>
</ul>
</li>
<li><p><strong>The "Sweet Spot":</strong> <code>64 GB</code></p>
<ul>
<li>This is the standard specification for a high-performance node. It allows you to allocate ~30GB to the Heap and leave ~34GB for the OS cache.</li>
</ul>
</li>
<li><p><strong>Maximum (Effective):</strong> <code>64 GB</code> (Physical RAM)</p>
</li>
</ul>
<p><strong>⚠️ The "Compressed Oops" Limit</strong><br />You should strictly avoid allocating more than 31GB-32GB to the JVM Heap. If the Heap crosses ~32GB, the JVM stops using "Compressed Object Pointers" (pointers swell from 32-bit to 64-bit). This drastically reduces memory efficiency, effectively cutting your available memory by half.</p>
<h3 id="heading-b-disk-storage">B. Disk (Storage)</h3>
<p><em>Disk speed dictates indexing throughput, and disk size dictates how long recovery (rebalancing shards) takes.</em></p>
<ul>
<li><p><strong>Type Requirement:</strong> SSD / NVMe</p>
<ul>
<li>Spinning HDDs are only acceptable for "Cold" or "Frozen" tiers.</li>
</ul>
</li>
<li><p><strong>Minimum Capacity:</strong> <code>200 GB</code></p>
<ul>
<li>Small clusters need enough space for logs, the OS, and headroom for shard merging.</li>
</ul>
</li>
</ul>
<p><strong>Maximum Capacity (Per Node):</strong></p>
<ol>
<li><p><strong>Hot Nodes (High IO):</strong> <code>2 TB</code> - <code>4 TB</code> limit.</p>
<ul>
<li><em>Reasoning:</em> If a node holds 10TB of hot data and dies, replicating that 10TB to a new node over the network takes hours or days, leaving the cluster in a "Yellow" (at risk) state.</li>
</ul>
</li>
<li><p><strong>Warm/Cold Nodes:</strong> <code>10 TB</code> - <code>16 TB</code>.</p>
<ul>
<li><em>Reasoning:</em> Since these nodes are query-heavy but low-write, you can pack them with dense storage, provided you accept slower recovery times.</li>
</ul>
</li>
</ol>
<h3 id="heading-c-network">C. Network</h3>
<p><em>Elasticsearch is a distributed system; the network is the bus.</em></p>
<ul>
<li><p><strong>Minimum:</strong> <code>1 Gbps</code> (Gigabit Ethernet)</p>
<ul>
<li><p>Acceptable only for small clusters with low indexing rates.</p>
</li>
<li><p><em>Risk:</em> During a "peer recovery" (when a node comes back online), a 1 Gbps link will be 100% saturated, causing search latency to spike.</p>
</li>
</ul>
</li>
<li><p><strong>Recommended / Maximum:</strong> <code>10 Gbps</code> - <code>25 Gbps</code></p>
<ul>
<li>10 Gbps is the standard for modern production clusters to ensure recovery doesn't impact live traffic.</li>
</ul>
</li>
<li><p><strong>Latency:</strong> Must be low single-digit ms (intra-datacenter).</p>
<ul>
<li><em>Warning:</em> Spanning a single cluster across distinct geographical regions (e.g., US-East to EU-West) is generally unsupported and will cause instability due to timeout errors.</li>
</ul>
</li>
</ul>
<hr />
<h2 id="heading-3-sizing-amp-sharding">3. Sizing &amp; Sharding</h2>
<p>This diagram provides a clear visualization of how a single index is divided into primary shards (P) and how each primary shard has a corresponding replica shard (R) distributed across different nodes for high availability.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770047597579/4820790c-7b9d-4887-bfe4-e3dcfbe8d186.jpeg" alt class="image--center mx-auto" /></p>
<h3 id="heading-a-shard-strategy">A. Shard Strategy</h3>
<p><em>Sharding is the mechanism that allows Elasticsearch to scale beyond the hardware limits of a single server. However, it is the most common source of performance issues in production.</em></p>
<h4 id="heading-1-the-concept">1. The Concept</h4>
<p>An Elasticsearch index is actually a logical grouping of <strong>Shards</strong>. Each shard is a self-contained instance of Apache Lucene, which is a fully functional search engine in its own right. When you execute a search on an index, Elasticsearch queries all relevant shards in parallel and merges the results.</p>
<h4 id="heading-2-the-oversharding-trap">2. The "Oversharding" Trap</h4>
<p>New users often think, <em>"If shards provide parallelism, more shards must mean more speed."</em> This is a fallacy known as oversharding.</p>
<ul>
<li><p><strong>The Metadata Overhead:</strong> Every shard consumes resources. The Cluster State (the "brain" of the cluster) must track the location, status, and size of every shard. If you have 100,000 small shards, the Cluster State grows huge, and updates (like creating a new index) become incredibly slow.</p>
</li>
<li><p><strong>The "Map-Reduce" Tax:</strong> When you search an index with 50 shards, the coordinating node must send the request to 50 places, wait for 50 responses, and merge 50 results. If those shards are tiny (e.g., 50MB), the overhead of managing the request outweighs the benefit of parallel processing.</p>
</li>
<li><p><strong>Memory Cost:</strong> Each shard has a baseline memory footprint in the JVM Heap to hold Lucene segment info. Too many small shards will exhaust your Heap memory even if the cluster is idle.</p>
</li>
</ul>
<p><strong>✅ The Golden Rule: 10GB – 50GB</strong><br />For general-purpose search (e.g., products, users), aim for a shard size between <strong>10GB and 50GB</strong>.</p>
<ul>
<li><p><strong>Why &gt; 10GB?</strong> To minimize the per-shard overhead and maximize efficient compression.</p>
</li>
<li><p><strong>Why &lt; 50GB?</strong> To ensure recovery is fast. If a node fails, moving a 50GB shard to a new node over the network is manageable. Moving a 500GB shard takes so long that your cluster remains in a vulnerable state ("Yellow" health) for hours.</p>
</li>
</ul>
<h3 id="heading-b-replicas">B. Replicas</h3>
<p><em>Replication serves two distinct purposes in a cluster: High Availability (HA) and Read Throughput.</em></p>
<h4 id="heading-1-failover-high-availability">1. Failover (High Availability)</h4>
<p>A Replica Shard is a precise copy of a Primary Shard.</p>
<ul>
<li><p><strong>The Mechanism:</strong> If the node holding a Primary Shard crashes, the master node instantly "promotes" the Replica Shard (living on a different node) to be the new Primary.</p>
</li>
<li><p><strong>Production Standard:</strong> You must set <code>number_of_replicas: 1</code> (at minimum). This ensures that if any single node fails, no data is lost, and the cluster remains fully operational.</p>
</li>
<li><p><strong>The Trade-off:</strong> Replicas double your storage requirements. 100GB of data with 1 replica requires 200GB of physical disk space.</p>
</li>
</ul>
<h4 id="heading-2-read-throughput-scaling-search">2. Read Throughput (Scaling Search)</h4>
<p>Unlike Primary shards, which handle both reads and writes, Replicas are usually used for reads.</p>
<ul>
<li><p><strong>Load Balancing:</strong> When a search request comes in, the coordinating node intelligently routes it. It can go to the Primary or any of its Replicas.</p>
</li>
<li><p><strong>Scaling Up:</strong> If your application is "Read Heavy" (e.g., an e-commerce site where users search frequently but products change rarely), you can increase performance by adding more replicas.</p>
<ul>
<li><em>Example:</em> An index with 1 Primary and 5 Replicas allows 6 nodes to answer search queries simultaneously for that specific data.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>Key Distinction:</strong></p>
<ul>
<li><p><strong>Primary Shards</strong> are fixed at index creation (changing them requires reindexing).</p>
</li>
<li><p><strong>Replica Shards</strong> can be changed dynamically. You can scale from 1 replica to 5 replicas instantly if you expect a traffic spike (like Black Friday), and scale back down afterwards.</p>
</li>
</ul>
</blockquote>
<h1 id="heading-environment-preparation-os-tuning">Environment Preparation (OS Tuning)</h1>
<p>Elasticsearch is sensitive to Operating System configurations. Failing to tune these will often prevent the cluster from starting (Bootstrap Checks).</p>
<h2 id="heading-1-disable-swapping-the-performance-killer">1. Disable Swapping (The "Performance Killer")</h2>
<p><strong>The Concept:</strong> In a standard server, if physical RAM is full, the OS moves inactive memory pages to the hard disk (swap space). For Elasticsearch, this is catastrophic. The Java Garbage Collector (GC) needs to scan memory to reclaim space. If that memory is on the disk (which is 100,000x slower than RAM), a GC cycle that usually takes milliseconds will take seconds or minutes.</p>
<ul>
<li><strong>The Result:</strong> The node becomes unresponsive ("Stop-the-world" pause), the cluster thinks the node is dead, drops it, and triggers a massive data rebalance.</li>
</ul>
<h3 id="heading-how-to-configure-it">How to configure it</h3>
<p>You have two main methods. The best practice is to do <strong>both</strong>.</p>
<ol>
<li><p><strong>OS Level (Permanent):</strong> Disable swap completely.</p>
<pre><code class="lang-bash"> sudo swapoff -a
</code></pre>
</li>
<li><p><strong>Application Level (Memory Lock):</strong> Force Elasticsearch to lock its memory address space into RAM so the OS <em>cannot</em> swap it out.</p>
<ul>
<li><p>In <code>elasticsearch.yml</code>:</p>
<pre><code class="lang-yaml">  <span class="hljs-attr">bootstrap.memory_lock:</span> <span class="hljs-literal">true</span>
</code></pre>
</li>
<li><p><em>Note:</em> You may need to edit the systemd service file (<code>systemctl edit elasticsearch</code>) to allow this limit:</p>
<pre><code class="lang-ini">  <span class="hljs-section">[Service]</span>
  <span class="hljs-attr">LimitMEMLOCK</span>=infinity
</code></pre>
</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-2-file-descriptors-the-capacity-limit">2. File Descriptors (The "Capacity Limit")</h2>
<p><strong>The Concept:</strong> Elasticsearch (via Lucene) breaks your data into heavily compressed immutable files called "segments." A single node can easily hold thousands of these small files open simultaneously. The default Linux limit for open files per user is often <code>1024</code>. This is far too low. If Elasticsearch hits this limit, it can silently lose data or crash because it cannot write to new files.</p>
<h3 id="heading-how-to-configure-it-1">How to configure it</h3>
<p>You must increase the limit to at least <strong>65,536</strong>.</p>
<ul>
<li><p><strong>Check current limit:</strong> <code>ulimit -n</code></p>
</li>
<li><p><strong>Permanent fix:</strong> Edit <code>/etc/security/limits.conf</code>:</p>
<pre><code class="lang-text">  elasticsearch - nofile 65536
</code></pre>
<p>  <em>(If you install via RPM/Deb package, this is often done automatically, but you must verify it).</em></p>
</li>
</ul>
<hr />
<h2 id="heading-3-virtual-memory-the-mmap-requirement">3. Virtual Memory (The <code>mmap</code> Requirement)</h2>
<p><strong>The Concept:</strong> This is specific to how Lucene reads data. It uses a system call called <code>mmap</code> (memory map) to map the files on the disk directly into the virtual memory address space. This is incredibly fast because it lets the kernel manage file caching. However, the default operating system limit on how many "memory maps" a process can own is usually <code>65,530</code>. Elasticsearch requires significantly more.</p>
<h3 id="heading-how-to-configure-it-2">How to configure it</h3>
<p>This is the most common reason for startup failures.</p>
<ul>
<li><p><strong>Command (Live):</strong></p>
<pre><code class="lang-bash">  sysctl -w vm.max_map_count=262144
</code></pre>
</li>
<li><p><strong>Permanent fix:</strong> Add this line to <code>/etc/sysctl.conf</code>:</p>
<pre><code class="lang-text">  vm.max_map_count=262144
</code></pre>
</li>
</ul>
<hr />
<h2 id="heading-4-jvm-heap-size-the-balancing-act">4. JVM Heap Size (The Balancing Act)</h2>
<p>This is the most misunderstood setting. You are configuring the Java Virtual Machine (JVM) memory.</p>
<h3 id="heading-a-xms-and-xmx-min-vs-max">A. <code>Xms</code> and <code>Xmx</code> (Min vs. Max)</h3>
<ul>
<li><p><strong>The Problem:</strong> By default, Java starts with a small heap (<code>Xms</code>) and grows it as needed up to the max (<code>Xmx</code>). This resizing process pauses execution.</p>
</li>
<li><p><strong>The Fix:</strong> Set them to the <strong>same value</strong>. This allocates all the memory immediately at startup, preventing resizing pauses.</p>
<pre><code class="lang-text">  # /etc/elasticsearch/jvm.options
  -Xms4g
  -Xmx4g
</code></pre>
</li>
</ul>
<h3 id="heading-b-the-50-rule-why-not-100">B. The 50% Rule (Why not 100%?)</h3>
<p>If you have a 64GB machine, why give Elasticsearch only 30GB? Why not 60GB?</p>
<ul>
<li><p><strong>The Reason:</strong> Elasticsearch relies on two types of memory:</p>
<ol>
<li><p><strong>JVM Heap:</strong> For query objects, aggregations, and cluster state.</p>
</li>
<li><p><strong>OS Filesystem Cache:</strong> This is where the actual data (Lucene segments) lives.</p>
</li>
</ol>
</li>
<li><p>If you give all RAM to the Heap, the OS has no room to cache the files. The disk will be thrashed, and performance will tank.</p>
</li>
<li><p><strong>Rule:</strong> <strong>50% to Heap, 50% left free for the OS.</strong></p>
</li>
</ul>
<h3 id="heading-c-the-32gb-limit-compressed-oops">C. The 32GB Limit (Compressed Oops)</h3>
<p>You must <strong>never</strong> set the Heap above ~32GB (exact threshold varies, usually 30GB-31GB is safe).</p>
<ul>
<li><p><strong>The Science:</strong> Below 32GB, Java uses "Compressed Ordinary Object Pointers" (Compressed Oops). It uses 32-bit pointers to reference memory.</p>
</li>
<li><p><strong>The Trap:</strong> Once you cross the threshold (e.g., 32.1GB), Java switches to 64-bit pointers. These are larger.</p>
</li>
<li><p><strong>The Result:</strong> A 35GB Heap actually stores <em>less</em> data than a 31GB Heap because the pointers themselves take up so much more space. Plus, it consumes more CPU bandwidth.</p>
</li>
</ul>
<h1 id="heading-security-the-must-have-layer">Security (The "Must-Have" Layer)</h1>
<p>This section often trips up new administrators because it involves certificates and passwords, which can be tedious. However, in modern Elasticsearch (version 8.x+), security is enabled by default. You cannot run a production cluster without it.</p>
<h2 id="heading-1-tlsssl-encryption-the-encrypted-tunnel">1. TLS/SSL Encryption (The "Encrypted Tunnel")</h2>
<p>Encryption prevents "Man-in-the-Middle" attacks. In Elasticsearch, we implement this in two distinct layers. If you miss the first one, your cluster will not even start.</p>
<h3 id="heading-a-transport-layer-node-to-node">A. Transport Layer (Node-to-Node)</h3>
<ul>
<li><p><strong>What it is:</strong> The internal communication channel on port <code>9300</code> where nodes talk to each other (electing masters, moving shards, replicating data).</p>
</li>
<li><p><strong>Why it's mandatory:</strong> Elasticsearch requires mutual trust. Node A must prove to Node B that it is a legitimate part of the cluster, not a rogue server trying to steal data.</p>
</li>
<li><p><strong>The Mechanism:</strong></p>
<ol>
<li><p>You generate a Certificate Authority (CA).</p>
</li>
<li><p>You sign a certificate for each node using that CA.</p>
</li>
</ol>
</li>
<li><p><strong>Crucial Note:</strong> If you do not enable Transport SSL, Elasticsearch will refuse to bind to a non-loopback IP address (i.e., it stays in "Development Mode").</p>
</li>
<li><p><strong>Key Tool:</strong> <code>bin/elasticsearch-certutil</code> (This built-in tool simplifies creating these certificates).</p>
</li>
</ul>
<h3 id="heading-b-http-layer-client-to-cluster">B. HTTP Layer (Client-to-Cluster)</h3>
<ul>
<li><p><strong>What it is:</strong> The external API on port <code>9200</code> where Kibana, your application (Java/Python/Node.js), and users connect.</p>
</li>
<li><p><strong>Why it's critical:</strong> Without this, Basic Auth credentials (username/password) are sent in plain text. Anyone on the network can sniff the admin password.</p>
</li>
<li><p><strong>Configuration:</strong> You generally use the same CA to sign these certificates, or you can use a public CA (Let's Encrypt, Verisign) if your cluster is public-facing.</p>
</li>
</ul>
<hr />
<h2 id="heading-2-authentication-amp-rbac-the-gatekeeper">2. Authentication &amp; RBAC (The "Gatekeeper")</h2>
<p>Once the connection is secure, you need to control who is logging in and what they can touch. This is Role-Based Access Control (RBAC).</p>
<h3 id="heading-a-the-built-in-users">A. The Built-in Users</h3>
<p>When you first start the cluster, you run <code>bin/elasticsearch-reset-password</code>. This sets up reserved accounts that are vital for the stack:</p>
<ul>
<li><p><code>elastic</code>: The "Superuser" (Root). It has full control.</p>
<blockquote>
<p><strong>Danger:</strong> Do not use this account in your application code! If those credentials leak, your entire cluster is compromised.</p>
</blockquote>
</li>
<li><p><code>kibana_system</code>: A service account used only by the Kibana server to talk to Elasticsearch. It cannot be used to log in to the dashboard.</p>
</li>
</ul>
<h3 id="heading-b-the-principle-of-least-privilege">B. The Principle of Least Privilege</h3>
<p>You should create custom roles for every specific use case.</p>
<ul>
<li><p><strong>The "Developer" Role:</strong> Can read and monitor indices but cannot delete data or change cluster settings.</p>
</li>
<li><p><strong>The "App" Role:</strong> Can write to the <code>logs-*</code> index but cannot read from the <code>salary-data</code> index.</p>
</li>
<li><p><strong>Document Level Security (DLS):</strong> You can even restrict access within a single index.</p>
<ul>
<li><em>Example:</em> "User A can search the <code>employees</code> index, but only document where <code>department: 'marketing'</code>."</li>
</ul>
</li>
</ul>
<hr />
<h2 id="heading-3-audit-logging-the-black-box">3. Audit Logging (The "Black Box")</h2>
<p>If data disappears or leaks, how do you know what happened?</p>
<ul>
<li><p><strong>What it tracks:</strong> You can configure it to log specific events: "Authentication Failed," "Index Deleted," or even "User X searched for query Y."</p>
</li>
<li><p><strong>Compliance:</strong> This is mandatory for standards like GDPR, HIPAA, and PCI-DSS.</p>
</li>
<li><p><strong>Performance Warning:</strong> Audit logging is I/O intensive.</p>
</li>
<li><p><strong>Bad Practice:</strong> Logging every single "read" operation. This will fill your disk with logs and slow down search performance.</p>
</li>
<li><p><strong>Best Practice:</strong> Log only "write/delete" operations and "authentication failures" to catch brute-force attacks.</p>
</li>
</ul>
<h1 id="heading-deployment-methods">Deployment Methods</h1>
<h2 id="heading-the-introduction-one-size-does-not-fit-all">The Introduction: One Size Does Not Fit All</h2>
<p>Elasticsearch is infrastructure-agnostic. It runs wherever Java runs. The choice of deployment method usually depends on three factors:</p>
<ul>
<li><p><strong>Existing Infrastructure:</strong> Are you already all-in on Kubernetes? Do you have racks of physical servers?</p>
</li>
<li><p><strong>Team Expertise:</strong> Are your ops people comfortable with Linux kernel tuning, or do they prefer writing YAML manifests?</p>
</li>
<li><p><strong>Scalability Needs:</strong> Do you need to add 10 nodes in 5 minutes during Black Friday, or is your cluster relatively static?</p>
</li>
</ul>
<hr />
<h2 id="heading-1-bare-metal-virtual-machines-the-classic-approach">1. Bare Metal / Virtual Machines (The "Classic" Approach)</h2>
<p>This is the traditional way of deploying software. You treat Elasticsearch like any other database (PostgreSQL, MySQL).</p>
<h3 id="heading-how-it-works">How it works:</h3>
<p>You provision Linux servers (physical hardware or VMs like EC2/Azure VMs), install Java (if using older ES versions), configure the OS prerequisites (as discussed in the OS Tuning section), add the Elastic repository, and install via package managers:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Ubuntu/Debian example</span>
wget -qO - [https://artifacts.elastic.co/GPG-KEY-elasticsearch](https://artifacts.elastic.co/GPG-KEY-elasticsearch) | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
sudo apt-get install elasticsearch
</code></pre>
<p><strong>Pros:</strong></p>
<ul>
<li><p><strong>Maximum Performance:</strong> You have direct access to hardware resources without any containerization abstraction layer. This is ideal for ultra-high-performance use cases.</p>
</li>
<li><p><strong>Simpler Troubleshooting:</strong> If you need to debug network latency or disk I/O, you use standard Linux tools (iostat, tcpdump) directly on the host.</p>
</li>
<li><p><strong>Persistence is easy:</strong> Data is written directly to attached disks. You don't have to worry about complex container storage interfaces (CSI).</p>
</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li><p><strong>Maintenance Overhead:</strong> Scaling up means manually provisioning a new server and configuring it. Upgrades require careful, manual "rolling restarts."</p>
</li>
<li><p><strong>Configuration Drift:</strong> Without strong configuration management tools (Ansible, Chef, Puppet), servers can slowly diverge in their configurations over time, leading to "it works on node 1 but not node 2" issues.</p>
</li>
<li><p><strong>Best For:</strong> Traditional IT environments, long-running stable clusters, and teams with strong Linux administration skills.</p>
</li>
</ul>
<h2 id="heading-2-docker-containers-the-rapid-prototyping-approach">2. Docker / Containers (The "Rapid Prototyping" Approach)</h2>
<p>Docker changed everything by allowing developers to spin up complex stacks locally in seconds.</p>
<p><strong>How it works:</strong> Elastic provides official, pre-hardened Docker images. You rarely run plain docker run commands. Instead, you use Docker Compose to define a multi-node cluster in a single YAML file.</p>
<p><strong>Pros:</strong></p>
<ul>
<li><p><strong>Speed:</strong> You can go from zero to a working 3-node cluster on your laptop in under 60 seconds.</p>
</li>
<li><p><strong>Consistency:</strong> The environment is identical across development, testing, and staging. "It works on my machine" actually means something.</p>
</li>
<li><p><strong>Isolation:</strong> Dependencies are packaged with the container.</p>
</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li><p><strong>Not a Production Orchestrator:</strong> Docker Compose is generally not recommended for multi-host production environments. It lacks advanced failover, scaling, and networking features needed for high availability.</p>
</li>
<li><p><strong>State Management:</strong> You must be very careful with volume mapping to ensure data persists if a container restarts.</p>
</li>
</ul>
<p><strong>Best For:</strong> Local development, CI/CD testing pipelines, and very small, single-host production deployments.</p>
<h2 id="heading-3-kubernetes-amp-eck-the-cloud-native-standard">3. Kubernetes &amp; ECK (The "Cloud-Native" Standard)</h2>
<p>If your organization has adopted Kubernetes (K8s), this is almost certainly how you should deploy Elasticsearch. But there is a massive caveat.</p>
<p><strong>The "Helm Chart" Trap</strong> New K8s users often try to deploy Elasticsearch using standard generic Helm charts. Avoid this. Elasticsearch is a complex, stateful distributed system. A standard K8s deployment doesn't understand that you can't just kill 3 master nodes simultaneously during an update without destroying the cluster.</p>
<p><strong>The Solution: Elastic Cloud on Kubernetes (ECK)</strong> Elastic developed their own Kubernetes Operator called ECK.</p>
<p><strong>What is an Operator?</strong> Think of it as a software robot that runs inside your K8s cluster and possesses human operational knowledge about Elasticsearch. It knows exactly the order in which to restart nodes so the cluster never goes down.</p>
<p><strong>How it works:</strong> Instead of managing pods and statefulsets directly, you install the ECK operator, and then you submit a simple custom resource YAML to K8s saying "I want an Elasticsearch cluster".</p>
<p>The Operator sees The YAML and automatically creates the Services, StatefulSets, PersistentVolumeClaims, and generates the TLS certificates.</p>
<p><strong>Pros:</strong></p>
<ul>
<li><p><strong>Day 2 Operations Automated:</strong> The Operator handles scaling, rolling upgrades, secure configuration, and backups automatically.</p>
</li>
<li><p><strong>Elastic Ecosystem:</strong> It makes deploying Kibana, APM Server, and Beats alongside Elasticsearch incredibly easy.</p>
</li>
</ul>
<p><strong>Cons:</strong></p>
<ul>
<li><strong>High Complexity:</strong> You need significant Kubernetes expertise before you add the complexity of running a stateful database on top of it.</li>
</ul>
<p><strong>Best For:</strong> Modern, cloud-native organizations requiring highly dynamic, scalable infrastructure.</p>
<h1 id="heading-configuration-best-practices">Configuration Best Practices</h1>
<h2 id="heading-the-critical-elasticsearchyml-configuration-guide">The Critical <code>elasticsearch.yml</code> Configuration Guide</h2>
<p>The <code>elasticsearch.yml</code> file is the control center of your node. While there are hundreds of settings, getting these specific few wrong is the most common cause of production outages or data loss.</p>
<hr />
<h2 id="heading-1-identity-cluster-amp-node-names">1. Identity: Cluster &amp; Node Names</h2>
<p>In a vacuum, names don't seem technical. In a distributed system, they are vital for observability and isolation.</p>
<h3 id="heading-a-clustername">A. <code>cluster.name</code></h3>
<ul>
<li><p><strong>The Default:</strong> <code>elasticsearch</code></p>
</li>
<li><p><strong>The Risk:</strong> If you leave this as default, a rogue developer starting a local instance on the same network (Wi-Fi or VPN) could accidentally discover and join your production cluster.</p>
</li>
<li><p><strong>Best Practice:</strong> Be descriptive and specific to the environment.</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">cluster.name:</span> <span class="hljs-string">prod-search-cluster-v1</span>
</code></pre>
<h3 id="heading-b-nodename">B. <code>node.name</code></h3>
<ul>
<li><p><strong>The Default:</strong> The server’s hostname.</p>
</li>
<li><p><strong>The Risk:</strong> Hostnames like <code>ip-10-0-0-5</code> are hard to read in logs or Kibana dashboards.</p>
</li>
<li><p><strong>Best Practice:</strong> Use a naming convention that indicates the role and number of the node. This makes debugging much faster ("Oh, <code>prod-master-02</code> is down" is more actionable than "Server X is down").</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">node.name:</span> <span class="hljs-string">prod-data-hot-01</span>
</code></pre>
<hr />
<h2 id="heading-2-discovery-preventing-the-split-brain">2. Discovery (preventing the "Split-Brain")</h2>
<p>"Discovery" is the process where nodes find each other and elect a leader (Master). If this is misconfigured, nodes will form separate, competing clusters, leading to data inconsistency (Split-Brain).</p>
<h3 id="heading-a-discoveryseedhosts-the-phone-book">A. <code>discovery.seed_hosts</code> (The Phone Book)</h3>
<p>This setting tells the node: <em>"When you wake up, call these people to ask where the cluster is."</em></p>
<ul>
<li><strong>Configuration:</strong> You don't need to list every single node. Just list the IP addresses or hostnames of your Master-eligible nodes.</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">discovery.seed_hosts:</span> [<span class="hljs-string">"192.168.1.10"</span>, <span class="hljs-string">"192.168.1.11"</span>, <span class="hljs-string">"192.168.1.12"</span>]
</code></pre>
<blockquote>
<p><strong>Note:</strong> If using Cloud/AWS, you might use a plugin (like <code>discovery-ec2</code>) to auto-detect these, but hardcoding IPs is safest for bare metal.</p>
</blockquote>
<h3 id="heading-b-clusterinitialmasternodes-the-bootstrapper">B. <code>cluster.initial_master_nodes</code> (The Bootstrapper)</h3>
<p>This is the most confusing setting for beginners. It is only used <strong>once</strong> in the entire life of the cluster: the very first time you turn it on.</p>
<ul>
<li><p><strong>The Problem:</strong> When you start 3 empty nodes, they all think, "I should be the King." Without this setting, they might form 3 separate clusters of 1 node each.</p>
</li>
<li><p><strong>The Solution:</strong> This setting forces them to form a quorum. It says, "Do not start the cluster until you see a vote from these specific nodes."</p>
</li>
<li><p><strong>Configuration:</strong> <strong>MUST</strong> match the <code>node.name</code> of your master nodes exactly.</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">cluster.initial_master_nodes:</span> [<span class="hljs-string">"prod-master-01"</span>, <span class="hljs-string">"prod-master-02"</span>, <span class="hljs-string">"prod-master-03"</span>]
</code></pre>
<blockquote>
<p><strong>Critical Warning:</strong> Once the cluster has formed for the first time, remove this setting (or comment it out) from your config management. If you leave it, and later try to restart a node to join an existing cluster, it might try to bootstrap a new cluster instead of joining the old one.</p>
</blockquote>
<hr />
<h2 id="heading-3-path-settings-saving-your-os">3. Path Settings (Saving your OS)</h2>
<p>By default, Elasticsearch writes data to <code>/var/lib/elasticsearch</code> and logs to <code>/var/log/elasticsearch</code>. This is dangerous.</p>
<h3 id="heading-a-the-risk-of-the-root-partition">A. The Risk of the "Root Partition"</h3>
<p>On Linux, <code>/var</code> is usually part of the root partition (<code>/</code>). If your users spam the cluster with data (filling <code>path.data</code>) or the cluster throws massive error loops (filling <code>path.logs</code>), you will fill up the root disk to 100%.</p>
<ul>
<li><strong>The Consequence:</strong> When <code>/</code> is full, Linux crashes. You can't SSH in to fix it. You have to physically reboot into rescue mode.</li>
</ul>
<h3 id="heading-b-pathdata">B. <code>path.data</code></h3>
<ul>
<li><strong>Best Practice:</strong> Mount a separate, large physical disk (NVMe/SSD) to a path like <code>/mnt/data</code> and point Elasticsearch there. If this disk fills up, Elasticsearch stops working, but the OS stays alive, allowing you to fix the issue.</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-attr">path.data:</span> <span class="hljs-string">/mnt/data/elasticsearch</span>
</code></pre>
<p><strong>Pro Tip (Striping):</strong> You can provide multiple paths. Elasticsearch will act like a software RAID 0, striping data across them.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">path.data:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">/mnt/disk1</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">/mnt/disk2</span>
</code></pre>
<h3 id="heading-c-pathlogs">C. <code>path.logs</code></h3>
<ul>
<li><strong>Best Practice:</strong> Ideally, ship logs to a remote system (using Filebeat). If storing locally, keep them on a separate partition from <code>path.data</code> so that a massive log spike doesn't consume your data storage space.</li>
</ul>
<h1 id="heading-operations-amp-maintenance-from-hobby-to-production">Operations &amp; Maintenance: From Hobby to Production</h1>
<p>This section defines the difference between a "hobby" cluster and a "production" cluster. Deployment is a one-time event; operations are forever.</p>
<hr />
<h2 id="heading-1-monitoring-you-cannot-manage-what-you-cannot-see">1. Monitoring: You Cannot Manage What You Cannot See</h2>
<p>A common mistake is waiting for users to complain "Search is slow" before checking the cluster. You need proactive visibility.</p>
<h3 id="heading-a-the-tools">A. The Tools</h3>
<p><strong>1. Elastic Stack Monitoring (The Native Way)</strong></p>
<ul>
<li><p><strong>How it works:</strong> You enable <code>xpack.monitoring</code>. The cluster sends metrics to itself (or preferably, to a separate "Monitoring Cluster" to avoid adding load to the production system).</p>
</li>
<li><p><strong>Pros:</strong> Deeply integrated; the Kibana UI is pre-built and excellent.</p>
</li>
</ul>
<p><strong>2. Prometheus &amp; Grafana (The Cloud-Native Way)</strong></p>
<ul>
<li><p><strong>How it works:</strong> You run an <code>elasticsearch-exporter</code> sidecar container. Prometheus scrapes it, and Grafana visualizes it.</p>
</li>
<li><p><strong>Pros:</strong> Industry standard; allows you to correlate Elasticsearch metrics with Linux/Network metrics on the same dashboard.</p>
</li>
</ul>
<h3 id="heading-b-the-big-4-metrics-to-watch">B. The "Big 4" Metrics to Watch</h3>
<ol>
<li><p><strong>JVM Heap Usage</strong></p>
<ul>
<li><p><strong>Healthy:</strong> A "sawtooth" pattern (memory fills up, Garbage Collection clears it, repeats).</p>
</li>
<li><p><strong>Danger:</strong> A flat line near 75-90%. This means the node is starving for memory and will soon crash with <code>OutOfMemoryError</code>.</p>
</li>
</ul>
</li>
<li><p><strong>Garbage Collection (GC) Count &amp; Time</strong></p>
<ul>
<li><strong>Danger:</strong> If "Old Gen" GC time spikes, your node is pausing execution (Stop-the-World) to clean memory. Search requests will hang during these pauses.</li>
</ul>
</li>
<li><p><strong>CPU Usage</strong></p>
<ul>
<li>High CPU is normal during heavy indexing, but if it stays at 100% continuously, your nodes are under-provisioned or your queries are too complex (e.g., wildcards starting with <code>*</code>).</li>
</ul>
</li>
<li><p><strong>Thread Pool Rejections</strong></p>
<ul>
<li>This is the most critical error metric. It means the node is saying, <em>"I am too busy; I cannot accept this request."</em> If search or write rejections are &gt; 0, you have a capacity problem.</li>
</ul>
</li>
</ol>
<hr />
<h2 id="heading-2-backups-replicas-backups">2. Backups: Replicas ≠ Backups</h2>
<p>This is the single most important lesson in data safety.</p>
<h3 id="heading-the-myth">The Myth</h3>
<blockquote>
<p>"I have 2 replicas, so I have 3 copies of my data. I don't need backups."</p>
</blockquote>
<h3 id="heading-the-reality">The Reality</h3>
<p>Replicas protect against <strong>Hardware Failure</strong> (disk crash). They do not protect against <strong>Human Error</strong>.</p>
<ul>
<li><p><strong>Scenario:</strong> You accidentally run <code>DELETE /users</code>.</p>
</li>
<li><p><strong>Result:</strong> Elasticsearch deletes the Primary shard immediately, and instantly propagates that delete instruction to all Replica shards. Your data is gone from all 3 copies in milliseconds.</p>
</li>
</ul>
<h3 id="heading-the-solution-snapshots-amp-slm">The Solution: Snapshots &amp; SLM</h3>
<p>You must take <strong>Snapshots</strong>, which are incremental backups sent to external repository storage (S3, Google Cloud Storage, Azure Blob, or a shared NFS drive).</p>
<ul>
<li><p><strong>Incremental:</strong> The first snapshot copies everything. The second snapshot only copies the segments that changed. It is lightweight and fast.</p>
</li>
<li><p><strong>SLM (Snapshot Lifecycle Management):</strong> Do not write manual scripts. Use the built-in SLM feature to define a policy.</p>
</li>
<li><p><strong>Example Policy:</strong> "Take a snapshot every night at 2 AM. Keep the last 30 snapshots. Delete older ones automatically."</p>
</li>
</ul>
<hr />
<h2 id="heading-3-updates-the-rolling-restart-strategy">3. Updates: The "Rolling Restart" Strategy</h2>
<p>Upgrading a database used to mean "Scheduled Downtime" on a Sunday night. With Elasticsearch, you can upgrade with zero downtime if you follow the "Rolling Restart" procedure.</p>
<h3 id="heading-the-logic">The Logic</h3>
<p>You never turn off the whole cluster. You turn off one node, upgrade it, turn it back on, and move to the next.</p>
<h3 id="heading-the-critical-step-disabling-allocation">The Critical Step: Disabling Allocation</h3>
<p>Before you shut down a node, you must tell the cluster: <em>"I am turning this node off on purpose. Do not panic and do not start rebuilding its data elsewhere."</em></p>
<h3 id="heading-the-workflow">The Workflow</h3>
<p><strong>1. Stop Allocation</strong> (This freezes the cluster layout so shards don't move).</p>
<pre><code class="lang-json">PUT _cluster/settings
{
  <span class="hljs-attr">"persistent"</span>: {
    <span class="hljs-attr">"cluster.routing.allocation.enable"</span>: <span class="hljs-string">"primaries"</span>
  }
}
</code></pre>
<p><strong>2. Stop the Node</strong> Run <code>systemctl stop elasticsearch</code>.</p>
<p><strong>3. Upgrade</strong> Update the package or replace the Docker image.</p>
<p><strong>4. Start the Node</strong> Run <code>systemctl start elasticsearch</code>.</p>
<p><strong>5. Wait for Green</strong> Watch the logs or <code>_cat/nodes</code> until the node rejoins the cluster.</p>
<p><strong>6. Re-enable Allocation</strong></p>
<pre><code class="lang-json">PUT _cluster/settings
{
  <span class="hljs-attr">"persistent"</span>: {
    <span class="hljs-attr">"cluster.routing.allocation.enable"</span>: <span class="hljs-literal">null</span>
  }
}
</code></pre>
<p><strong>7. Repeat</strong> Move to the next node.</p>
<h1 id="heading-common-pitfalls-the-difference-between-novices-and-experts">Common Pitfalls: The Difference Between Novices and Experts</h1>
<p>This section separates the novices from the experts. These are the issues that don't show up in a "Hello World" tutorial; they only appear when you are in production, under load, and usually at 3 AM.</p>
<hr />
<h2 id="heading-1-split-brain-the-two-captains-problem">1. Split Brain (The "Two Captains" Problem)</h2>
<p>This diagram provides a visual representation of the "split-brain" scenario, where a network partition leads to the formation of two separate clusters, each with its own master, risking data inconsistency.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770047687950/c6394869-3499-4771-aa96-7f14ad27ef0a.jpeg" alt class="image--center mx-auto" /></p>
<p>This is the nightmare scenario for distributed systems.</p>
<h3 id="heading-the-scenario">The Scenario</h3>
<p>Imagine you have a cluster of 3 nodes (A, B, C) in a single room. A network switch fails, cutting the room in half. Node A and B can talk to each other, but Node C is isolated.</p>
<h3 id="heading-the-glitch">The Glitch</h3>
<ol>
<li><p>Nodes A+B realize C is gone. They elect Node A as Master.</p>
</li>
<li><p>Node C thinks A and B are dead. It elects itself as Master.</p>
</li>
</ol>
<h3 id="heading-the-result-split-brain">The Result (Split Brain)</h3>
<p>You now have two active masters in the same cluster.</p>
<ul>
<li><p>Application 1 writes data to Node A.</p>
</li>
<li><p>Application 2 writes data to Node C.</p>
</li>
</ul>
<h3 id="heading-the-catastrophe">The Catastrophe</h3>
<p>When the network comes back, you have two different versions of the history. Elasticsearch cannot "merge" these timelines. You will likely lose the data written to the smaller side of the partition.</p>
<h3 id="heading-the-fix-quorum">The Fix (Quorum)</h3>
<ul>
<li><p><strong>In older versions (6.x and below):</strong> You had to manually set <code>discovery.zen.minimum_master_nodes</code> to <code>(N/2) + 1</code>.</p>
</li>
<li><p><strong>In modern versions (7.x+):</strong> Elasticsearch uses a Voting Configuration system automatically. However, you must ensure you have <strong>3 Master-eligible nodes</strong> (an odd number) so there is always a majority winner in a vote. <strong>Never run a production cluster with exactly 2 master nodes.</strong></p>
</li>
</ul>
<hr />
<h2 id="heading-2-mapping-explosion-the-death-by-fields">2. Mapping Explosion (The "Death by Fields")</h2>
<p>Elasticsearch is schema-less by default (Dynamic Mapping), which sounds great until it crashes your cluster.</p>
<h3 id="heading-the-scenario-1">The Scenario</h3>
<p>You are logging user cookies or HTTP headers. A developer decides to send a JSON document where the keys are unique UUIDs or timestamps.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"2024-01-30_12:00"</span>: <span class="hljs-string">"error"</span>,
  <span class="hljs-attr">"2024-01-30_12:01"</span>: <span class="hljs-string">"info"</span>
}
</code></pre>
<h3 id="heading-the-glitch-1">The Glitch</h3>
<p>Dynamic mapping sees a new field (<code>"2024-01-30_12:00"</code>) and adds it to the <strong>Cluster State</strong> (the global registry of all settings).</p>
<h3 id="heading-the-result">The Result</h3>
<p>Every unique key becomes a new field. If you send 10,000 documents with unique keys, you create 10,000 fields.</p>
<ul>
<li><p>The Cluster State becomes massive (hundreds of MBs).</p>
</li>
<li><p>This state must be synced to every node. Updating it takes seconds. The cluster becomes unresponsive.</p>
</li>
</ul>
<h3 id="heading-the-fix">The Fix</h3>
<ol>
<li><p><strong>Disable Dynamic Mapping:</strong> Set <code>dynamic: false</code> or <code>strict</code> in your production templates.</p>
</li>
<li><p><strong>Use the</strong> <code>flattened</code> Data Type: If you need to store unstructured JSON with unknown keys, map that specific field as <code>type: "flattened"</code>. Elasticsearch will treat the entire JSON object as a single keyword field, preventing the explosion.</p>
</li>
<li><p><strong>Limit Fields:</strong> The default limit is 1,000 fields per index (<code>index.mapping.total_fields.limit</code>). <strong>Do not raise this.</strong> If you hit it, your data model is wrong.</p>
</li>
</ol>
<hr />
<h2 id="heading-3-deep-pagination-the-killer-query">3. Deep Pagination (The "Killer Query")</h2>
<p>Your users want to jump to "Page 50,000" of the search results. You must tell them "No."</p>
<h3 id="heading-the-scenario-2">The Scenario</h3>
<p>A user runs a query with <code>from: 50000, size: 10</code>.</p>
<h3 id="heading-the-glitch-distributed-sorting-cost">The Glitch (Distributed Sorting cost)</h3>
<p>To find the "top 10" results starting at 50,000, Elasticsearch cannot just skip the first 50,000 records.</p>
<ol>
<li><p>Every shard involved in the search must fetch its own top 50,010 results and hold them in memory.</p>
</li>
<li><p>If you have 10 shards, the coordinating node receives <code>50,010 * 10 = 500,100</code> documents.</p>
</li>
<li><p>It must sort all half-million records in RAM, discard the first 500,000, and return the last 10.</p>
</li>
</ol>
<h3 id="heading-the-result-1">The Result</h3>
<p>Massive CPU spikes and Garbage Collection (GC) loops. If multiple users do this simultaneously, the node runs out of memory (OOM) and crashes.</p>
<h3 id="heading-the-fix-1">The Fix</h3>
<ol>
<li><p><strong>Hard Limit:</strong> Elasticsearch defaults <code>index.max_result_window</code> to 10,000. Do not increase this unless you know exactly what you are doing.</p>
</li>
<li><p><strong>For Users (</strong><code>search_after</code>): This is the efficient way to paginate. It tells Elasticsearch, "Give me the next 10 results after this specific sort value from the last result." It requires no deep scanning.</p>
</li>
<li><p><strong>For Scripts (Scroll API / PIT):</strong> If you need to export the entire dataset, use the Point-in-Time (PIT) or Scroll API, which is designed for batch processing.</p>
</li>
</ol>
]]></content:encoded></item></channel></rss>