28

I'm using Search Guard plugin to secure an elasticsearch cluster composed of multiple nodes. Here is my Dockerfile:

#!/bin/sh
FROM docker.elastic.co/elasticsearch/elasticsearch:5.6.3

USER root

# Install search guard
RUN bin/elasticsearch-plugin install --batch com.floragunn:search-guard-5:5.6.3-16 \
    && chmod +x \
        plugins/search-guard-5/tools/hash.sh \
        plugins/search-guard-5/tools/sgadmin.sh \
        bin/init_sg.sh \
    && chown -R elasticsearch:elasticsearch /usr/share/elasticsearch

USER elasticsearch

To initialize SearchGuard (create internal users and assign roles). I need to run the script init_sg.sh after the container startup. Here is the problem: Unless elasticsearch is running, the script will not initialize any security index.

The script's content is :

sleep 10
plugins/search-guard-5/tools/sgadmin.sh -cd config/ -ts config/truststore.jks -ks config/kirk-keystore.jks -nhnv -icl

Now, I just run the script manually after the container startup but since I'm running it on Kubernetes.. Pods may get killed or fail and get recreated automatically for some reason. In this case, the plugin have to be initialized automatically after the container startup!

So how to accomplish this? Any help or hint would be really appreciated.

PhiloJunkie
  • 1,111
  • 4
  • 13
  • 27
  • 1
    You will only need to initialize SG once per cluster. Re-initialize SG on every restart of a pod will overwrite any custom roles/permissions not saved. – nafooesi Sep 11 '19 at 22:51
  • @nafooesi Absolutely, you are right! The SG should be initialized only once per cluster. – PhiloJunkie Sep 15 '19 at 18:17
  • This question is also addressed [here](https://stackoverflow.com/questions/45371521/adding-startup-script-to-dockerfile/64935472#64935472) – ManishM Nov 20 '20 at 19:17

7 Answers7

26

The image itself has an entrypoint ENTRYPOINT ["/run/entrypoint.sh"] specified in the Dockerfile. You can replace it by your own script. So for example create a new script, mount it and first call /run/entrypoint.sh and then wait for start of elasticsearch before running your init_sg.sh.

caub
  • 2,709
  • 2
  • 28
  • 31
  • 3
    I'm trying to do the exact same thing, but the `/run/entrypoint.sh` is not present in the container. I can't find where it sits. Any help? – RedGiant May 25 '18 at 12:37
  • Found it at /entrypoint.sh for elasticsearch:1.5.2 – Marieke Jun 24 '21 at 16:50
  • Can you please specify how to `mount it`? – Gulzar Nov 27 '21 at 19:41
  • 1
    You can use [ADD](https://docs.docker.com/engine/reference/builder/#add) in Dockerfile command to add script to the container statically. If you change the script often and do not want to rebuild the image every time, you can make a script which is called from entrypoint.sh, and make all changes there. – Do-do-new Mar 12 '23 at 09:51
8

Not sure this will solves your problem, but its worth check my repo'sDockerfile

I have created a simple run.sh file copied to docker image and in the Dockerfile I wrote CMD ["run.sh"]. In the same way define whatever you want in run.sh and write CMD ["run.sh"]. You can find another example like below

Dockerfile

FROM java:8

RUN apt-get update && apt-get install stress-ng -y 
ADD target/restapp.jar /restapp.jar 
COPY dockerrun.sh /usr/local/bin/dockerrun.sh 
RUN chmod +x /usr/local/bin/dockerrun.sh 
CMD ["dockerrun.sh"]

dockerrun.sh

#!/bin/sh
java -Dserver.port=8095 -jar /restapp.jar &
hostname="hostname: `hostname`"
nohup stress-ng --vm 4 &
while true; do
  sleep 1000
done
Veerendra K
  • 2,145
  • 7
  • 32
  • 61
  • 2
    thanks for the reply, the elasticsearch image has already an entrypoint.sh file, using CMD like this will override it and elasticsearch search won't start at all :/ – PhiloJunkie Dec 06 '17 at 15:09
7

This is addressed in the documentation here: https://docs.docker.com/config/containers/multi-service_container/

If one of your processes depends on the main process, then start your helper process FIRST with a script like wait-for-it, then start the main process SECOND and remove the fg %1 line.

#!/bin/bash
  
# turn on bash's job control
set -m
  
# Start the primary process and put it in the background
./my_main_process &
  
# Start the helper process
./my_helper_process
  
# the my_helper_process might need to know how to wait on the
# primary process to start before it does its work and returns
  
  
# now we bring the primary process back into the foreground
# and leave it there
fg %1
ManishM
  • 127
  • 1
  • 3
  • duplicated here https://stackoverflow.com/questions/45371521/adding-startup-script-to-dockerfile/64935472#64935472 – ManishM Nov 20 '20 at 19:19
5

I was trying to solve the exact problem. Here's the approach that worked for me.

  1. Create a separate shell script that checks for ES status, and only start initialization of SG when ES is ready:

Shell Script

#!/bin/sh

echo ">>>>  Right before SG initialization <<<<"
# use while loop to check if elasticsearch is running 
while true
do
    netstat -uplnt | grep :9300 | grep LISTEN > /dev/null
    verifier=$?
    if [ 0 = $verifier ]
        then
            echo "Running search guard plugin initialization"
            /elasticsearch/plugins/search-guard-6/tools/sgadmin.sh -h 0.0.0.0 -cd plugins/search-guard-6/sgconfig -icl -key config/client.key -cert config/client.pem -cacert config/root-ca.pem -nhnv
            break
        else
            echo "ES is not running yet"
            sleep 5
    fi
done

Install script in Dockerfile

You will need to install the script in container so it's accessible after it starts.

COPY sginit.sh /
RUN chmod +x /sginit.sh

Update entrypoint script

You will need to edit the entrypoint script or run script of your ES image. So that it starts the sginit.sh in the background BEFORE starting ES process.

# Run sginit in background waiting for ES to start
/sginit.sh &

This way the sginit.sh will start in the background, and will only initialize SG after ES is started.

The reason to have this sginit.sh script starts before ES in the background is so that it's not blocking ES from starting. The same logic applies if you put it after starting of ES, it will never run unless you put the starting of ES in the background.

Community
  • 1
  • 1
nafooesi
  • 1,097
  • 12
  • 18
  • How to wait for the ES get started, then initialize the SG? – Akira Sep 10 '19 at 21:20
  • 1
    As commented below the original question, you do NOT want to initialize SG every single time. It will overwrite any modified config when a pod restarts. SG should only be initialized once per cluster at the beginning. If you need to run some other script after ES starts, my script can be used as an example to detect if ES is running in the background before kicking off the script. – nafooesi Dec 03 '19 at 01:23
  • The general approach described here is very useful. I had a similar problem (not with ES), where I wanted to run a script after the main process had a chance to start up. This pattern worked like a charm! – bischoje Feb 25 '20 at 20:07
3

You can also use wait-for-it script. It will wait on the availability of a host and TCP port. It is useful for synchronizing the spin-up of interdependent services and works like a charm with containers. It does not have any external dependencies so you can just run it as an RUN command without doing anything else.

A Dockerfile example based on this thread:

FROM elasticsearch

# Make elasticsearch write data to a folder that is not declared as a volume in elasticsearchs' official dockerfile.
RUN mkdir /data && chown -R elasticsearch:elasticsearch /data && echo 'es.path.data: /data' >> config/elasticsearch.yml && echo 'path.data: /data' >> config/elasticsearch.yml

# Download wait-for-it
ADD https://raw.githubusercontent.com/vishnubob/wait-for-it/e1f115e4ca285c3c24e847c4dd4be955e0ed51c2/wait-for-it.sh /utils/wait-for-it.sh

# Copy the files you may need and your insert script

# Insert data into elasticsearch
RUN /docker-entrypoint.sh elasticsearch -p /tmp/epid & /bin/bash /utils/wait-for-it.sh -t 0 localhost:9200 -- path/to/insert/script.sh; kill $(cat /tmp/epid) && wait $(cat /tmp/epid); exit 0;
2

I would suggest to put the CMD in you docker file to execute the script when the container start

FROM debian
RUN apt-get update && apt-get install -y nano && apt-get clean
EXPOSE 8484
CMD ["/bin/bash", "/opt/your_app/init.sh"]

There is other way , but before using this look at your requirement,

    ENTRYPOINT "put your code here" && /bin/bash
    #exemple ENTRYPOINT service nginx start && service ssh start &&/bin/bash "use && to separate your code"
Sohan
  • 6,252
  • 5
  • 35
  • 56
1

There is a dedicated tool for this - s6-overlay.
To quote from their description:

A simple init process which allows the end-user to execute tasks like initialization (...) Multiple processes in a single container (...) Able to operate in "The Docker Way"

The repo provides lengthy explanation how it works, how to install etc. which I won't repeat here.

Example

Imo their repo lacks a working, straightforward minimal example how to run a process + a script so I provide one. I modify the example they provide in their docs.
Say we want to run nginx (or any process that runs until end of container lifetime) plus some shell script myscript.sh.

Local directory structure:

./Dockerfile
./myscript.sh
./s6-overlay/s6-rc.d/myapp/type
./s6-overlay/s6-rc.d/myapp/up
./s6-overlay/s6-rc.d/user/contents.d/myapp

Dockerfile:

FROM ubuntu
ARG S6_OVERLAY_VERSION=3.1.4.1

RUN apt-get update && apt-get install -y nginx xz-utils
RUN echo "daemon off;" >> /etc/nginx/nginx.conf

# Minimal set of dependecies required for s6
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-noarch.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz
ADD https://github.com/just-containers/s6-overlay/releases/download/v${S6_OVERLAY_VERSION}/s6-overlay-x86_64.tar.xz /tmp
RUN tar -C / -Jxpf /tmp/s6-overlay-x86_64.tar.xz

# Overhead of files to manage processes via s6
COPY s6-overlay /etc/s6-overlay
# Copy the script we intend to run
COPY myscript.sh /home

# CMD is the main process - nothing special here
CMD ["/usr/sbin/nginx"]
# ENTRYPOINT must be /init for s6 to work
ENTRYPOINT ["/init"]

myscript.sh - make sure to make it executable:

#!/bin/bash
echo "foo" > /home/foo.txt
echo "bar" > /home/bar.txt

s6-overlay/s6-rc.d/myapp/type:

oneshot

"an up file contains a single command line" so as soon our script has >1 line, we have to outsource our script to a separate file. Therefore this is our s6-overlay/s6-rc.d/myapp/up:

/home/myscript.sh

s6-overlay/s6-rc.d/myapp/contents.d/myapp is an empty file.


Now we simply need to docker build (...) and docker run -p 80:80 (...). If you have done everything correctly, you should see a log message s6-rc: info: service myapp successfully started at container startup.
You can then visit localhost:80 and run docker exec CONTAINER bash -c "cat /home/foo.txt" to confirm it works as expected.

Note that utilizing s6-rc.d is the recommended way to do it. There's also a legacy way to accomplish this with less overhead by putting myscript.sh into folder /etc/cont-init.d/.

nichoio
  • 6,289
  • 4
  • 26
  • 33