Terraform Elasticsearch in Amazon Web Services
The elasticsearch cluster needs to know the seed ip addresses, which makes it a little bit tricker to do in terraform. We actually need to do have two separate ec2 declarations. The second batch will grab the first batches ip addresses. The userdata automatically will install and set up everything. This also allows us to dynamically change the size of the cluster if we need to scale up or down depending on the job. Any new ec2 instances that come up will automatically join the cluster. I specify the version of elasticsearch in order to make sure new virtual machines don’t automatically grab an incompatible version.
Files
ec2.tf
variable "ami_id_arm64" {
description = "Amazon Linux 2 ARM64 AMI"
default = "ami-07acebf185d439fa0"
}
#arm64 sizes
variable "instance_type_arm64" {
type = "map"
default = {
L3 = "a1.large"
L2 = "a1.4xlarge"
L1 = "a1.4xlarge"
}
}
variable "instance_type_arm64_metal" {
type = "map"
default = {
L3 = "a1.large"
L2 = "a1.metal"
L1 = "a1.metal"
}
}
variable "instance_count_reserved_arm64" {
type = "map"
default = {
L3 = "2"
L2 = "2"
L1 = "20"
}
}
variable "instance_count_reserved_2_arm64" {
type = "map"
default = {
L3 = "0"
L2 = "0"
L1 = "160"
}
}
variable "instance_count_reserved_failover_arm64" {
type = "map"
default = {
L3 = "0"
L2 = "0"
L1 = "0"
}
}
variable "instance_count_spot_arm64" {
type = "map"
default = {
L3 = "0"
L2 = "0"
L1 = "0"
}
}
data "template_cloudinit_config" "ondemand" {
gzip = true
base64_encode = true
part {
content = "${file("userdata_arm64.yml")}"
}
part {
merge_type = "list(append)+dict(recurse_array)+str()"
content_type = "text/cloud-config"
content = <<EOF
#cloud-config
---
write_files:
- path: /etc/elasticsearch/elasticsearch.yml
permissions: 0660
content: |
cluster.name: "BPI"
network.host: 0.0.0.0
xpack.ml.enabled: false
xpack.monitoring.enabled: false
bootstrap.memory_lock: true
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
discovery.zen.minimum_master_nodes: 1
http.cors.enabled: true
http.cors.allow-origin: /http?://localhost(:[0-9]+)?/
#cluster.routing.allocation.total_shards_per_node: 1
EOF
}
}
data "template_cloudinit_config" "spot" {
gzip = true
base64_encode = true
part {
content = "${file("userdata_arm64.yml")}"
}
part {
merge_type = "list(append)+dict(recurse_array)+str()"
content_type = "text/cloud-config"
content = <<EOF
#cloud-config
---
write_files:
- path: /etc/elasticsearch/elasticsearch.yml
permissions: 0660
content: |
cluster.name: "BPI"
network.host: 0.0.0.0
xpack.ml.enabled: false
xpack.monitoring.enabled: false
bootstrap.memory_lock: true
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
discovery.zen.minimum_master_nodes: 1
#cluster.routing.allocation.total_shards_per_node: 1
http.cors.enabled: true
http.cors.allow-origin: /http?://localhost(:[0-9]+)?/
discovery.zen.ping.unicast.hosts: ["${element(aws_instance.bgt-bpi-arm64.*.private_ip, 0)}:9300", "${element(aws_instance.bgt-bpi-arm64.*.private_ip, 1)}:9300", "${element(aws_instance.bgt-bpi-arm64.*.private_ip, 2)}:9300", "${element(aws_instance.bgt-bpi-arm64.*.private_ip, 3)}:9300"]
EOF
}
}
resource "aws_instance" "bgt-bpi-arm64" {
ami = "${var.ami_id_arm64}"
instance_type = "${var.instance_type_arm64["${data.terraform_remote_state.global.aws_environment}"]}"
iam_instance_profile = "${lower(var.stack_id)}"
subnet_id = "${data.terraform_remote_state.global.default_vpc_server_subnet_ids_list[1]}"
user_data = "${data.template_cloudinit_config.ondemand.rendered}"
vpc_security_group_ids = ["${data.terraform_remote_state.global.base_security_group_ids_default_vpc_list[0]}"]
count = "${var.instance_count_reserved_arm64["${data.terraform_remote_state.global.aws_environment}"]}"
placement_group = "${aws_placement_group.bgt_bpi_arm64_pg.name}"
ebs_optimized = "${var.ebs_optimized["${data.terraform_remote_state.global.aws_environment}"]}"
lifecycle {
ignore_changes = ["user_data", "ami", "ebs_optimized"]
}
root_block_device {
volume_type = "gp2"
volume_size = "165"
delete_on_termination = true
}
tags {
Name = "bgt-bpi-arm64-p1-z1"
Team = "Bigtree Services"
Tool = "Terraform"
StackId = "${var.stack_id}"
Deploy = "arm64"
}
}
resource "aws_elb" "elb" {
name = "bgt-bpi-elb"
subnets = ["${data.terraform_remote_state.global.default_vpc_server_subnet_ids_list}"]
security_groups = ["${data.terraform_remote_state.global.base_security_group_ids_default_vpc_list[0]}"]
internal = true
listener {
instance_port = 9200
instance_protocol = "http"
lb_port = 80
lb_protocol = "http"
}
health_check {
healthy_threshold = 2
interval = 10
target = "http:9200/"
timeout = 5
unhealthy_threshold = 3
}
tags {
Name = "bgt-bpi-elb"
Team = "Bigtree Services"
Tool = "Terraform"
StackId = "${var.stack_id}"
Deploy = "arm64"
}
}
resource "aws_elb_attachment" "elb" {
count = "${var.instance_count_reserved_arm64["${data.terraform_remote_state.global.aws_environment}"]}"
elb = "${aws_elb.elb.id}"
instance = "${element(aws_instance.bgt-bpi-arm64.*.id, count.index)}"
}
resource "aws_instance" "bgt-bpi-arm64-part2" {
ami = "${var.ami_id_arm64}"
instance_type = "${var.instance_type_arm64["${data.terraform_remote_state.global.aws_environment}"]}"
iam_instance_profile = "${lower(var.stack_id)}"
subnet_id = "${data.terraform_remote_state.global.default_vpc_server_subnet_ids_list[1]}"
user_data = "${data.template_cloudinit_config.spot.rendered}"
vpc_security_group_ids = ["${data.terraform_remote_state.global.base_security_group_ids_default_vpc_list[0]}"]
count = "${var.instance_count_reserved_2_arm64["${data.terraform_remote_state.global.aws_environment}"]}"
ebs_optimized = "${var.ebs_optimized["${data.terraform_remote_state.global.aws_environment}"]}"
lifecycle {
ignore_changes = ["user_data", "ami", "ebs_optimized", "placement_group"]
}
root_block_device {
volume_type = "gp2"
volume_size = "165"
delete_on_termination = true
}
tags {
Name = "bgt-bpi-arm64-a2-z1"
Team = "Bigtree Services"
Tool = "Terraform"
StackId = "${var.stack_id}"
Deploy = "arm64"
}
}
resource "aws_elb_attachment" "elb-part2" {
count = "${var.instance_count_reserved_2_arm64["${data.terraform_remote_state.global.aws_environment}"]}"
elb = "${aws_elb.elb.id}"
instance = "${element(aws_instance.bgt-bpi-arm64-part2.*.id, count.index)}"
}
In this example L3/L2/L1 are the same as having a dev/stage/prod environment.
userdata.yml
#cloud-config
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCTSRtWzW/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
yum_repos:
elasticsearch.repo:
name: Elasticsearch repository for 6.x packages
baseurl: https://artifacts.elastic.co/packages/6.x/yum
gpgcheck: 1
gpgkey: https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled: 1
autorefresh: 1
type: rpm-md
package_upgrade: true
packages:
- vim
- htop
- wget
- gcc
write_files:
- path: /etc/systemd/timesyncd.conf
permissions: 0644
owner: root
content: |
[Time]
NTP=0.amazon.pool.ntp.org 1.amazon.pool.ntp.org 2.amazon.pool.ntp.org 3.amazon.pool.ntp.org
- path: /etc/sysctl.d/net.ipv4.neigh.default.conf
content: |
net.ipv4.neigh.default.gc_thresh1=4096
net.ipv4.neigh.default.gc_thresh2=8192
net.ipv4.neigh.default.gc_thresh3=16384
- path: /etc/sysctl.d/fs.inotify.max_user_instances.conf
content: |
fs.inotify.max_user_instances=4096
- path: /etc/sysctl.d/net.conf
content: |
net.core.somaxconn = 1000
net.core.netdev_max_backlog = 5000
net.core.rmem_default = 524280
net.core.wmem_default = 524280
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.udp_rmem_min = 10240
net.nf_conntrack_max = 1048576
- path: /etc/sysctl.d/vm.conf
content: |
vm.max_map_count=262144
- path: /etc/security/limits.conf
content: |
* soft memlock unlimited
* hard memlock unlimited
* - nofile 65536
- path: /etc/systemd/system/elasticsearch.service.d/override.conf
permissions: 666
content: |
[Service]
LimitMEMLOCK=infinity
- path: /etc/elasticsearch/jvm.options
permissions: 0666
content: |
## JVM configuration
-Xms20g
-Xmx20g
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
-Des.networkaddress.cache.ttl=60
-Des.networkaddress.cache.negative.ttl=10
-XX:+AlwaysPreTouch
-Xss1m
-Djava.awt.headless=true
-Dfile.encoding=UTF-8
-Djna.nosys=true
-XX:-OmitStackTraceInFastThrow
-Dio.netty.noUnsafe=true
-Dio.netty.noKeySetOptimization=true
-Dio.netty.recycler.maxCapacityPerThread=0
-Dlog4j.shutdownHookEnabled=false
-Dlog4j2.disable.jmx=true
-Djava.io.tmpdir=${ES_TMPDIR}
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/var/lib/elasticsearch
-XX:ErrorFile=/var/log/elasticsearch/hs_err_pid%p.log
8:-XX:+PrintGCDetails
8:-XX:+PrintGCDateStamps
8:-XX:+PrintTenuringDistribution
8:-XX:+PrintGCApplicationStoppedTime
8:-Xloggc:/var/log/elasticsearch/gc.log
8:-XX:+UseGCLogFileRotation
8:-XX:NumberOfGCLogFiles=32
8:-XX:GCLogFileSize=64m
9-:-Xlog:gc*,gc+age=trace,safepoint:file=/var/log/elasticsearch/gc.log:utctime,pid,tags:filecount=32,filesize=64m
9-:-Djava.locale.providers=COMPAT
10-:-XX:UseAVX=2
runcmd:
- [ amazon-linux-extras, install, corretto8, -y ]
- [ yum, install, elasticsearch-6.4.3-1, -y ]
- [ systemctl, daemon-reload ]
- [ systemctl, enable, elasticsearch.service ]
- [ systemctl, start, elasticsearch.service ]