Loading...
Loading...
Marco Franssen

In a previous post, I showed you how to set up Elasticsearch in docker-compose.yml. I've also covered setting up Traefik 1.7 in docker-compose.yml. Today, let's use Traefik to expose a load-balanced endpoint for a two-node Elasticsearch cluster.
Simplify networking complexity while designing, deploying, and running applications.
We'll set up our cluster using Docker Compose so we can easily run it and clean it up on a laptop.
Let's first create a two-node Elasticsearch cluster with the following Docker Compose configuration.
version: "3.7"
services:
es01:
image: "docker.elastic.co/elasticsearch/elasticsearch-oss:7.7.1"
ports:
- "9200:9200"
- "9300:9300"
environment:
node.name: es01
discovery.seed_hosts: es02
cluster.initial_master_nodes: es01,es02
cluster.name: traefik-tutorial-cluster
bootstrap.memory_lock: "true"
ES_JAVA_OPTS: -Xms256m -Xmx256m
volumes:
- "es-data-es01:/usr/share/elasticsearch/data"
ulimits:
memlock:
soft: -1
hard: -1
es02:
image: "docker.elastic.co/elasticsearch/elasticsearch-oss:7.7.1"
ports:
- "9201:9200"
- "9301:9300"
environment:
node.name: es02
discovery.seed_hosts: es01
cluster.initial_master_nodes: es01,es02
cluster.name: traefik-tutorial-cluster
bootstrap.memory_lock: "true"
ES_JAVA_OPTS: -Xms256m -Xmx256m
volumes:
- "es-data-es02:/usr/share/elasticsearch/data"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
es-data-es01:
es-data-es02:When we run this Docker Compose configuration, you can reach the first node at http://localhost:9200 and the second at http://localhost:9201. For every node we add to the cluster, we'd need to expose another port from our Docker environment to connect to it directly.
A cleaner solution is to expose a single port to the host and load-balance requests across the nodes in our cluster.
Traefik has several configuration providers. One is Docker, which lets us configure Traefik through Docker labels. Let's add the Traefik container first.
version: "3.7"
services:
gateway:
image: traefik:v2.2
command:
- --api.insecure=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:roFor Traefik to read Docker labels, we need to mount docker.sock as a volume. We'll enable the Traefik Docker provider and configure it to include only containers explicitly enabled with a Docker label. Finally, we'll enable the API so we can look at the Traefik dashboard.
Run docker-compose up -d again, then open the Traefik dashboard. It shows an overview of routers, services, and middleware for HTTP, TCP, and UDP. We haven't configured any for Elasticsearch yet because we haven't added labels to those containers.
Let's define the labels on the Elasticsearch containers. For brevity, I've left out their other properties in the example below.
es01:
labels:
- "traefik.enable=true"
- "traefik.http.routers.elasticsearch.entrypoints=http"
- "traefik.http.routers.elasticsearch.rule=Host(`localhost`) && PathPrefix(`/es`) || Host(`elasticsearch`)"
- "traefik.http.routers.elasticsearch.middlewares=es-stripprefix"
- "traefik.http.middlewares.es-stripprefix.stripprefix.prefixes=/es"
- "traefik.http.services.elasticsearch.loadbalancer.server.port=9200"
es02:
labels:
- "traefik.enable=true"
- "traefik.http.routers.elasticsearch.entrypoints=http"
- "traefik.http.routers.elasticsearch.rule=Host(`localhost`) && PathPrefix(`/es`) || Host(`elasticsearch`)"
- "traefik.http.routers.elasticsearch.middlewares=es-stripprefix"
- "traefik.http.middlewares.es-stripprefix.stripprefix.prefixes=/es"
- "traefik.http.services.elasticsearch.loadbalancer.server.port=9200"The labels on these two containers do the following:
/es prefix before forwarding the request.9200 and 9300.Run docker-compose up -d again to reload the Elasticsearch containers. In the Traefik dashboard, you'll now see a configured router, service, and middleware. You can access Elasticsearch at http://localhost/es. Refresh your browser a few times and notice how requests are load-balanced across the two nodes.
If you add the following entry to your hosts file, you can also access the Elasticsearch cluster at http://elasticsearch. This matches the other host we defined in the Traefik routing rule.
127.0.0.1 localhost elasticsearchYou can now remove the Elasticsearch port mappings from docker-compose.yml. Remove the following mappings from both containers.
es01:
ports:
- 9200:9200
- 9300:9300
es02:
ports:
- 9201:9200
- 9301:9300Finally, I want to show you Cerebro, a nice little admin tool for working with your Elasticsearch cluster. The following Docker Compose configuration exposes Cerebro at http://localhost/admin.
cerebro:
image: lmenezes/cerebro:0.8.5
volumes:
- "./conf/cerebro/application.conf:/opt/cerebro/conf/application.conf"
depends_on:
- gateway
links:
- "gateway:elasticsearch"
labels:
- "traefik.enable=true"
- "traefik.http.routers.admin.entrypoints=http"
- "traefik.http.routers.admin.rule=Host(`localhost`) && PathPrefix(`/admin`)"
- "traefik.http.services.cerebro.loadbalancer.server.port=9000"Here, we enable the container in Traefik and add a rule for http://localhost/admin. We've also included a link that gives our gateway container the network alias elasticsearch. Remember the rule we defined for http://elasticsearch? We'll use it in the Cerebro configuration mounted into our container.
Loading https://raw.githubusercontent.com/marcofranssen/es-cluster-traefik/f3f8d79d9784c5c38f492fdfda089523ba6b2a9e/conf/cerebro/application.conf…
Two settings are important for Cerebro to work with our Traefik setup. First, basePath is set to /admin/ because we're running Cerebro at http://localhost/admin. Second, we use the elasticsearch route, which we defined in Traefik and added as an alias for the gateway container.
Now you can run docker-compose up -d again. Give Cerebro a try at http://localhost/admin.
Here's the complete docker-compose.yml covered in this post.
Loading https://raw.githubusercontent.com/marcofranssen/es-cluster-traefik/f3f8d79d9784c5c38f492fdfda089523ba6b2a9e/docker-compose.yml…
Now try adding Kibana yourself. Expose it at http://localhost by defining a Traefik rule for it. The Docker Elastic Stack guide can help you get started with Kibana.
I hope you enjoyed this post. As always, share it with your friends and colleagues, and let me know what you think in the comments below.
# Secret will be used to sign session cookies, CSRF tokens and for other encryption utilities.
# It is highly recommended to change this value before running cerebro in production.
secret = "ki:s:[[@=Ag?QI`W2jMwkY:eqvrJ]JqoJyi2axj3ZvOv^/KavOT4ViJSv?6YY4[N"
# Application base path
basePath = "/admin/"
# Defaults to RUNNING_PID at the root directory of the app.
# To avoid creating a PID file set this value to /dev/null
#pidfile.path = "/var/run/cerebro.pid"
pidfile.path=/dev/null
# Rest request history max size per user
rest.history.size = 50 // defaults to 50 if not specified
# Path of local database file
#data.path: "/var/lib/cerebro/cerebro.db"
data.path = "./cerebro.db"
play {
# Cerebro port, by default it's 9000 (play's default)
server.http.port = ${?CEREBRO_PORT}
}
es = {
gzip = true
}
# Authentication
auth = {
# either basic or ldap
type: ${?AUTH_TYPE}
settings {
# LDAP
url = ${?LDAP_URL}
# OpenLDAP might be something like "ou=People,dc=domain,dc=com"
base-dn = ${?LDAP_BASE_DN}
# Usually method should be "simple" otherwise, set it to the SASL mechanisms to try
method = ${?LDAP_METHOD}
# user-template executes a string.format() operation where
# username is passed in first, followed by base-dn. Some examples
# - %s => leave user untouched
# - %s@domain.com => append "@domain.com" to username
# - uid=%s,%s => usual case of OpenLDAP
user-template = ${?LDAP_USER_TEMPLATE}
// User identifier that can perform searches
bind-dn = ${?LDAP_BIND_DN}
bind-pw = ${?LDAP_BIND_PWD}
group-search {
// If left unset parent's base-dn will be used
base-dn = ${?LDAP_GROUP_BASE_DN}
// Attribute that represent the user, for example uid or mail
user-attr = ${?LDAP_USER_ATTR}
// Define a separate template for user-attr
// If left unset parent's user-template will be used
user-attr-template = ${?LDAP_USER_ATTR_TEMPLATE}
// Filter that tests membership of the group. If this property is empty then there is no group membership check
// AD example => memberOf=CN=mygroup,ou=ouofthegroup,DC=domain,DC=com
// OpenLDAP example => CN=mygroup
group = ${?LDAP_GROUP}
}
# Basic auth
username = ${?BASIC_AUTH_USER}
password = ${?BASIC_AUTH_PWD}
}
}
# A list of known hosts
hosts = [
{
host = "http://elasticsearch"
name = "traefik-tutorial-cluster"
headers-whitelist = [ "x-proxy-user", "x-proxy-roles", "X-Forwarded-For" ]
}
# Example of host with authentication
# {
# host = "http://some-authenticated-host:9200"
# name = "Secured Cluster"
# auth = {
# username = "username"
# password = "secret-password"
# }
# }
]version: '3.7'
services:
gateway:
image: traefik:v2.2
command:
- --api.insecure=true
- --providers.docker=true
- --providers.docker.exposedByDefault=false
ports:
- "80:80"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
es01:
image: "docker.elastic.co/elasticsearch/elasticsearch-oss:7.7.1"
environment:
node.name: es01
discovery.seed_hosts: es02
cluster.initial_master_nodes: es01,es02
cluster.name: traefik-tutorial-cluster
bootstrap.memory_lock: "true"
ES_JAVA_OPTS: -Xms256m -Xmx256m
volumes:
- "es-data-es01:/usr/share/elasticsearch/data"
ulimits:
memlock:
soft: -1
hard: -1
labels:
- "traefik.enable=true"
- "traefik.http.routers.elasticsearch.entrypoints=http"
- "traefik.http.routers.elasticsearch.rule=Host(`localhost`) && PathPrefix(`/es`) || Host(`elasticsearch`)"
- "traefik.http.routers.elasticsearch.middlewares=es-stripprefix"
- "traefik.http.middlewares.es-stripprefix.stripprefix.prefixes=/es"
- "traefik.http.services.elasticsearch.loadbalancer.server.port=9200"
es02:
image: "docker.elastic.co/elasticsearch/elasticsearch-oss:7.7.1"
environment:
node.name: es02
discovery.seed_hosts: es01
cluster.initial_master_nodes: es01,es02
cluster.name: traefik-tutorial-cluster
bootstrap.memory_lock: "true"
ES_JAVA_OPTS: -Xms256m -Xmx256m
volumes:
- "es-data-es02:/usr/share/elasticsearch/data"
ulimits:
memlock:
soft: -1
hard: -1
labels:
- "traefik.enable=true"
- "traefik.http.routers.elasticsearch.entrypoints=http"
- "traefik.http.routers.elasticsearch.rule=Host(`localhost`) && PathPrefix(`/es`) || Host(`elasticsearch`)"
- "traefik.http.routers.elasticsearch.middlewares=es-stripprefix"
- "traefik.http.middlewares.es-stripprefix.stripprefix.prefixes=/es"
- "traefik.http.services.elasticsearch.loadbalancer.server.port=9200"
cerebro:
image: lmenezes/cerebro:0.8.5
volumes:
- "./conf/cerebro/application.conf:/opt/cerebro/conf/application.conf"
depends_on:
- gateway
links:
- "gateway:elasticsearch"
labels:
- "traefik.enable=true"
- "traefik.http.routers.admin.entrypoints=http"
- "traefik.http.routers.admin.rule=Host(`localhost`) && PathPrefix(`/admin`)"
- "traefik.http.services.cerebro.loadbalancer.server.port=9000"
volumes:
es-data-es01:
es-data-es02:Marco Franssen
Remove unwanted files from Git history with git-filter-repo, find previously deleted files, and push rewritten history using this practical walkthrough.
Marco Franssen
Configure a React app in the Nginx 1.19 Docker image with environment variables and templates, including API proxy settings for different deployments.
Marco Franssen
Use Lego and the ACME DNS-01 challenge to request a Let's Encrypt TLS certificate for local development without exposing your web server to the internet.
Marco Franssen
Build a Go HTTP/2 web server with TLS, self-signed certificates for offline development, and Let's Encrypt certificates managed by the autocert package.