Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 77 additions & 43 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,54 +1,88 @@
FROM debian:jessie

RUN apt-get update && \
apt-get install -y \
openjdk-7-jre-headless \
curl \
jq \
&& rm -rf /var/lib/apt/lists/*

ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64

# If we wanted the development version we could pull that instead but we want to
# run a production environment here.
RUN export ES_PKG=elasticsearch-2.2.0.deb && \
curl -LO https://download.elasticsearch.org/elasticsearch/release/org/elasticsearch/distribution/deb/elasticsearch/2.2.0/${ES_PKG} && \
dpkg -i ${ES_PKG} && \
rm ${ES_PKG} && \
rm /etc/elasticsearch/elasticsearch.yml

# Add Containerpilot and set its configuration
ENV CONTAINERPILOT_VER 2.1.0
ENV CONTAINERPILOT file:///etc/containerpilot.json

RUN export CONTAINERPILOT_CHECKSUM=e7973bf036690b520b450c3a3e121fc7cd26f1a2 \
FROM docker.elastic.co/elasticsearch/elasticsearch-alpine-base:latest

# LICENSE - Apache License 2.0
# https://github.com/elastic/elasticsearch-alpine-base/blob/master/LICENSE

ENV ELASTIC_VERSION=5.3.0 \
PATH=/usr/share/elasticsearch/bin:$PATH \
JAVA_HOME=/usr/lib/jvm/java-1.8-openjdk \
CONSUL_VERSION=0.7.5 \
CONSUL_CLI_VER=0.3.1 \
CONTAINERPILOT_VER=2.7.2 \
CONTAINERPILOT=file:///etc/containerpilot.json

WORKDIR /usr/share/elasticsearch

# Required dependencies
RUN apk update && \
apk add jq curl unzip tar && \
rm -rf /var/cache/apk/*

# Download/extract defined ES version. busybox tar can't strip leading dir.
RUN export ELASTIC_CHECKSUM=9273fdecb2251755887f1234d6cfcc91e44a384d && \
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-${ELASTIC_VERSION}.tar.gz && \
test $ELASTIC_CHECKSUM == $(sha1sum elasticsearch-${ELASTIC_VERSION}.tar.gz | awk '{print $1}') && \
tar zxf elasticsearch-${ELASTIC_VERSION}.tar.gz && \
chown -R elasticsearch:elasticsearch elasticsearch-${ELASTIC_VERSION} && \
mv elasticsearch-${ELASTIC_VERSION}/* . && \
rmdir elasticsearch-${ELASTIC_VERSION} && \
rm elasticsearch-${ELASTIC_VERSION}.tar.gz

RUN set -ex && for esdirs in config data logs; do \
mkdir -p "$esdirs"; \
chown -R elasticsearch:elasticsearch "$esdirs"; \
done

# Add consul agent
RUN export CONSUL_CHECKSUM=40ce7175535551882ecdff21fdd276cef6eaab96be8a8260e0599fadb6f1f5b8 \
&& curl --retry 7 --fail -vo /tmp/consul.zip "https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip" \
&& echo "${CONSUL_CHECKSUM} /tmp/consul.zip" | sha256sum -c \
&& unzip /tmp/consul -d /usr/local/bin \
&& rm /tmp/consul.zip

# Consul client
RUN export CONSUL_CLIENT_CHECKSUM=037150d3d689a0babf4ba64c898b4497546e2fffeb16354e25cef19867e763f1 \
&& curl -Lso /tmp/consul-cli.tgz "https://github.com/CiscoCloud/consul-cli/releases/download/v${CONSUL_CLI_VER}/consul-cli_${CONSUL_CLI_VER}_linux_amd64.tar.gz" \
&& echo "${CONSUL_CLIENT_CHECKSUM} /tmp/consul-cli.tgz" | sha256sum -c \
&& tar zxf /tmp/consul-cli.tgz -C /usr/local/bin --strip-components 1 \
&& rm /tmp/consul-cli.tgz

# Add ContainerPilot and set its configuration file path
RUN export CONTAINERPILOT_CHECKSUM=e886899467ced6d7c76027d58c7f7554c2fb2bcc \
&& curl -Lso /tmp/containerpilot.tar.gz \
"https://github.com/joyent/containerpilot/releases/download/${CONTAINERPILOT_VER}/containerpilot-${CONTAINERPILOT_VER}.tar.gz" \
"https://github.com/joyent/containerpilot/releases/download/${CONTAINERPILOT_VER}/containerpilot-${CONTAINERPILOT_VER}.tar.gz" \
&& echo "${CONTAINERPILOT_CHECKSUM} /tmp/containerpilot.tar.gz" | sha1sum -c \
&& tar zxf /tmp/containerpilot.tar.gz -C /usr/local/bin \
&& rm /tmp/containerpilot.tar.gz

# Create and take ownership over required directories
RUN mkdir -p /var/lib/elasticsearch/data && \
chown -R elasticsearch:elasticsearch /var/lib/elasticsearch/data && \
chown -R root:elasticsearch /etc/elasticsearch && \
chmod g+w /etc/elasticsearch

USER elasticsearch
COPY /etc/containerpilot.json /etc/
COPY /etc/elasticsearch.yml config/
COPY /etc/log4j2.properties config/
COPY /bin/es-docker bin/es-docker
COPY /bin/* /usr/local/bin/

# Add our configuration files and scripts
COPY /etc/containerpilot.json /etc
COPY /etc/elasticsearch.yml /etc/elasticsearch/elasticsearch.yml
COPY /bin/manage.sh /usr/local/bin
USER root
RUN chown elasticsearch:elasticsearch config/elasticsearch.yml config/log4j2.properties bin/es-docker && \
chmod 0750 bin/es-docker && \
mkdir -p /opt/consul/config && \
mkdir -p /opt/consul/data && \
chmod 770 /opt/consul/data && \
chown -R elasticsearch:elasticsearch /opt/consul && \
mkdir -p /etc/containerpilot && \
chmod -R g+w /etc/containerpilot && \
chmod +x /usr/local/bin/elastic-server.sh && \
chown -R elasticsearch:elasticsearch /etc/containerpilot

USER elasticsearch

# Expose the data directory as a volume in case we want to mount these
# as a --volumes-from target; it's important that this VOLUME comes
# after the creation of the directory so that we preserve ownership.
VOLUME /var/lib/elasticsearch/data

# We don't need to expose these ports in order for other containers on Triton
# to reach this container in the default networking environment, but if we
# leave this here then we get the ports as well-known environment variables
# for purposes of linking.
EXPOSE 9200
EXPOSE 9300
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were these removed just because the official image has them already? (Again, not sure because I can't find the Dockerfile.)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VOLUME ["/usr/share/elasticsearch/data"]

# Start with containerpilot then to our wrapper
CMD ["containerpilot", "/usr/local/bin/elastic-server.sh"]


EXPOSE 9200 9300
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,14 @@ $ curl "http://${MASTER_IP}:9200/_cluster/state?pretty=true"
}

```

### Dockerfile

Elastic has depreciated the official repository in favor of their own registry.

> This image will receive no further updates after 2017-06-20 (June 20, 2017). Please adjust your usage accordingly. - [Docker Hub](https://hub.docker.com/_/elasticsearch/)

Unfortunately they don't include the Dockerfile as part of the elasticsearch repository, links are included below for easy reference.

* [Dockerfile](https://github.com/elastic/elasticsearch-docker/blob/master/build/elasticsearch/Dockerfile)
* [Base Dockerfile](https://github.com/elastic/elasticsearch-alpine-base/blob/master/build/elasticsearch-alpine-base/Dockerfile)
6 changes: 6 additions & 0 deletions bin/elastic-server.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh -xe
manage.sh onStart #|| exit $?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything like this should be in a preStart, not wrapping the main app. That would also eliminate the need for this shell script.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this from the redis autopilot example.
https://github.com/autopilotpattern/redis/blob/master/bin/redis-server-sentinel.sh Where it obviously does a bit more.

Does preStart fire BEFORE you start the co-processes? I'm asking because we need the co-process consul agent to start before we start the onStart function.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did some research and unfortunately this has to be a separate wrapper as the coprocess doesn't start until immediately after preStart. We need the coprocess (consul agent) to start prior to onStart. onStart's job is there to check whether it has joined the consul cluster at which point we can verify if other services exist. If we did this in preStart, the consul agent won't be running and will assume every container should become the master.

Coprocesses are started immediately following the exit of the preStart hook, before polling for health or backend hooks, and before the main application starts. - Containerpilot Docs

I think this could be confusing for developers like me who are just getting started with containerpilot and consul agent running as a coprocess. Is there a reason you guys use CONSUL_AGENT=1 in most of the examples. What is the purpose of making that optional? Meaning, why wouldn't you always use consul agent on each container?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bah, no you're right. This will be fixed in CPv3 because we're forcing people to admit they need a real init system. But in the meantime we're stuck with it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would a "real" init system change this? I'm not sure what you mean by real init though.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In CPv3 we're supporting multiple processes as first-class, rather than having a "main" process and coprocesses. The end user will create the chain of dependencies explicitly, so in this case we'll be able to have everything wait on the consul agent. This is pretty much how every modern init system works, but was something we were avoiding in ContainerPilot originally because of community... obstinance(?) around the notion of there being more than one process in a container. Turns out that's exactly what you want a lot of the time. 😀

if [[ $? != 0 ]]; then
exit $?
fi
exec /usr/share/elasticsearch/bin/es-docker $*
39 changes: 39 additions & 0 deletions bin/es-docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash

# Run Elasticsearch and allow setting default settings via env vars
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we control the command we're using to call ES along with its environment, we don't really need this shell script. We can include its effects entirely in the docker-compose.yml's command section.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can move it to elastic-server.sh to reduce the number of files but it doesn't really simplify it that much does it? I don't think it is smart to remove the code, anyone who has used the official docker repo for Elasticsearch will expect that functionality to continue. I would expect switching to containerpilot from the official image to work without any changes. I would want all ENV variables I used to configure elastic to continue working as is with containerpilot.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would want all ENV variables I used to configure elastic to continue working as is with containerpilot.

Sure but there's literally only one line that doesn't work as-is: https://github.com/autopilotpattern/elasticsearch/pull/8/files/2f807599ea62bf41d3fc811586ffd0648ac40496#diff-35c179adb2b0c25802542935645540a7R37

#
# e.g. Setting the env var cluster.name=testcluster
#
# will cause Elasticsearch to be invoked with -Ecluster.name=testcluster
#
# see https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html#_setting_default_settings

es_opts=''

while IFS='=' read -r envvar_key envvar_value
do
# Elasticsearch env vars need to have at least two dot separated lowercase words, e.g. `cluster.name`
if [[ "$envvar_key" =~ ^[a-z]+\.[a-z]+ ]]
then
if [[ ! -z $envvar_value ]]; then
es_opt="-E${envvar_key}=${envvar_value}"
es_opts+=" ${es_opt}"
fi
fi
done < <(env)

# The virtual file /proc/self/cgroup should list the current cgroup
# membership. For each hierarchy, you can follow the cgroup path from
# this file to the cgroup filesystem (usually /sys/fs/cgroup/) and
# introspect the statistics for the cgroup for the given
# hierarchy. Alas, Docker breaks this by mounting the container
# statistics at the root while leaving the cgroup paths as the actual
# paths. Therefore, Elasticsearch provides a mechanism to override
# reading the cgroup path from /proc/self/cgroup and instead uses the
# cgroup path defined the JVM system property
# es.cgroups.hierarchy.override. Therefore, we set this value here so
# that cgroup statistics are available for the container this process
# will run in.
export ES_JAVA_OPTS="-Des.cgroups.hierarchy.override=/ $ES_JAVA_OPTS"

exec bin/elasticsearch ${es_opts}
149 changes: 102 additions & 47 deletions bin/manage.sh
Original file line number Diff line number Diff line change
@@ -1,67 +1,122 @@
#!/bin/bash

MASTER=null
CONSUL_HOST=${CONSUL}
CONSUL_AGENT=${CONSUL_AGENT:=false}

if [[ -z ${CONSUL} ]]; then
if [ $CONSUL_AGENT != false ]; then
CONSUL_HOST='localhost'
fi

if [[ -z $CONSUL_HOST ]]; then
echo "Missing CONSUL environment variable"
exit 1
fi

preStart() {
# happy path is that there's a master available and we can cluster
configureMaster

# data-only nodes can only loop until there's a master available
if [ ${ES_NODE_MASTER} == false ]; then
while true
do
sleep 1.7
configureMaster
done
exit 0
fi
consulCommand() {
consul-cli --quiet --consul="${CONSUL_HOST}:8500" $*
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's already a consul binary in the container. We don't need this wrapper.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you want me to pull this out and make the following changes?

Service addresses

Use curl and jq to get service addresses
https://github.com/sberryman/elasticsearch/blob/wip-v5/bin/manage.sh#L28

waitForLeader

waitForLeader using consul members -status alive | grep server

Replace: https://github.com/sberryman/elasticsearch/blob/wip-v5/bin/manage.sh#L48-L64
With: https://github.com/sberryman/autopilotpattern-nats/blob/master/bin/manage.sh#L28-L44

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that should do it.

}

onStart() {
logDebug "onStart"

waitForLeader

# wait for a healthy master
local i
for (( i = 0; i < ${MASTER_WAIT_TIMEOUT-60}; i++ )); do
getServiceAddresses "elasticsearch-master"
if [[ ${serviceAddresses} ]]; then
MASTER=$serviceAddresses
break
fi
sleep 1
done

# replace zen hosts
replaceZenHosts

# disable seccomp (only supported on newer Linux kernels)
replaceSeccomp
}

health() {
local privateIp=$(ip addr show eth0 | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}')
/usr/bin/curl --fail -s -o /dev/null http://${privateIp}:9200
}

# for a master+data node, we'll retry to see if there's another
# master in the cluster in the process of starting up. But we
# bail out if we exceed the retries and just bootstrap the cluster
if [ ${ES_NODE_DATA} == true ]; then
local n=0
until [ $n -ge 2 ]
do
sleep 1.7
configureMaster
n=$((n+1))
done
waitForLeader() {
# no need to wait for a leader unless we are using consul in agent mode
if [ $CONSUL_HOST != 'localhost' ]; then
return
fi

# for a master-only node or master+data node that's exceeded the
# retry attempts, we'll assume this is the first master and bootstrap
# the cluster
MASTER=127.0.0.1
replace
logDebug "Waiting for consul server"
local tries=0
while true
do
logDebug "Waiting for consul server"
tries=$((tries + 1))
local server=$(consul members -status alive | grep server)
if [[ -n "$server" ]]; then
break
elif [[ $tries -eq 60 ]]; then
echo "No consul server"
exit 1
fi
sleep 1
done
}

getServiceAddresses() {
local serviceInfo=$(consulCommand health service --passing "$1")
serviceAddresses=($(echo $serviceInfo | jq -r '.[].Service.Address'))
logDebug "serviceAddresses $1 ${serviceAddresses[*]}"
}

getRegisteredServiceName() {
registeredServiceName=$(jq -r '.services[0].name' /etc/containerpilot.json)
}

getNodeAddress() {
nodeAddress=$(ifconfig eth0 | awk '/inet addr/ {gsub("addr:", "", $2); print $2}')
}

# get the list of ES master nodes from Consul
configureMaster() {
MASTER=$(curl -Ls --fail http://${CONSUL}:8500/v1/catalog/service/elasticsearch-master | jq -r '.[0].ServiceAddress')
if [[ $MASTER != "null" ]] && [[ -n $MASTER ]]; then
replace
exit 0
replaceZenHosts() {
REPLACEMENT=$(printf 's/^# discovery\.zen\.ping\.unicast\.hosts.*$/discovery.zen.ping.unicast.hosts: ["%s"]/' ${MASTER})
sed -i "${REPLACEMENT}" /usr/share/elasticsearch/config/elasticsearch.yml
}

replaceSeccomp() {
SECCOMP_ENABLED=$(zcat /proc/config.gz | grep CONFIG_SECCOMP=y)
if [[ "${SECCOMP_ENABLED}" != "CONFIG_SECCOMP=y" ]]; then
echo "WARNING: seccomp unavailable, disabling system_call_filter..."
REPLACEMENT=$(printf 's/^# bootstrap\.system_call_filter.*$/bootstrap.system_call_filter: false/')
sed -i "${REPLACEMENT}" /usr/share/elasticsearch/config/elasticsearch.yml
fi
# if there's no master we fall thru and let the caller figure
# out what to do next
}

# update discovery.zen.ping.unicast.hosts
replace() {
REPLACEMENT=$(printf 's/^discovery\.zen\.ping\.unicast\.hosts.*$/discovery.zen.ping.unicast.hosts: ["%s"]/' ${MASTER})
sed -i "${REPLACEMENT}" /etc/elasticsearch/elasticsearch.yml
logDebug() {
if [[ "${LOG_LEVEL}" == "DEBUG" ]]; then
echo "manage: $*"
fi
}

health() {
local privateIp=$(ip addr show eth0 | grep -o '[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}')
/usr/bin/curl --fail -s -o /dev/null http://${privateIp}:9200
help() {
echo "Usage: ./manage.sh onStart => first-run configuration"
echo " ./manage.sh health => health check Elastic"
echo " ./manage.sh preStop => prepare for stop"
}

# do whatever the arg is
$1
until
cmd=$1
if [[ -z "$cmd" ]]; then
help
fi
shift 1
$cmd "$@"
[ "$?" -ne 127 ]
do
help
exit
done
Loading