DockerNodeTest
OK, so you've heard about Node, Redis, microservices and Docker. Apparently it's like a perfect storm sweeping across the interwebs.
So let's get all futurized with this Docker containerization. Why? So we can spin up multiple instances of our stateless Node apps. Why? So we can web scale out, be resilient to failure, do continuous deployment, A/B testing, and what have you. Stateless microservices are enabled by putting our shared application state in Redis, for example.
Let's create a bash script that creates a sample Dockerfile
which pulls the basic Ubuntu 14.04 Docker image (about 70 megs), then installs Node and npm
on top of that (40 megs).
It has been tested on an Ubuntu 14.04 droplet (Docker 1.0), AWS CentOS 6 and a CoreOS droplet. It failed on an Ubuntu 14.10 laptop (Docker 1.2), and on another Ubuntu 14.04 cloud server.
Before you sudo
this script, sudo apt-get install docker.io
on your Ubuntu host, or yum install docker
and service docker start
on your CentOS. But only try this at home on your laptop or some disposal test cloud server, and certainly not on any production servers - which could get us both in trouble. Perhaps spin up a temporary CoreOS droplet.
#!/bin/bash
which docker || exit 1
netstat -ntlp 2>/dev/null | grep -qv 8888 || exit 1
mkdir -p ~/tmp/dockernodetest
cd ~/tmp/dockernodetest || exit 1
pwd
echo "
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello, Dockerized Node World!');
}).listen(8888);
console.info('test web app started');
" > index.js
echo "
mkdir -p testapp
cd testapp
cp /tmp/index.js .
nodejs index.js
" > start.sh
echo "
FROM ubuntu:14.04
RUN apt-get update
RUN apt-get install -y nodejs npm
RUN npm install http -g
ADD start.sh /tmp/
ADD index.js /tmp/
RUN chmod +x /tmp/start.sh
EXPOSE 8888
CMD /tmp/start.sh
" > Dockerfile
ls -l
c0_build_nodetest() {
docker build -t nodetest .
docker ps -a
}
c0_run_detached_nodetest1() {
docker run -d --name nodetest1 -p 8888:8888 nodetest
docker ps -a
sleep 1
echo
if curl -s 'http://localhost:8888'
then
echo; echo
echo "curl succeed! If on your localhost, "
echo "try in your browser: http://localhost:8888"
echo "or similarly with your server's IP number or name"
else
echo; echo
echo "curl failed!"
fi
echo "If you interrupt this script, check: docker ps -a"
echo "and if necessary, to cleanup: docker rm -f nodetest1"
}
c0_attach_nodetest1() {
echo "attach - press ctrl-pq to detach"
docker attach nodetest1
echo
docker ps -a
}
c0_exec_nodetest1_bash() {
echo "exec bash - press enter to see shell, ctrl-pq to detach,"
echo "or type exit to stop the container"
docker exec nodetest1 /bin/bash
echo
docker ps -a
}
c0_rm_nodetest1() {
if docker ps | grep -q nodetest1
then
echo "removing container"
docker rm -f nodetest1
fi
}
c0_default() {
c0_rm_nodetest1
c0_build_nodetest
c0_run_detached_nodetest1
#c0_attach_nodetest1
#c0_exec_nodetest1_bash
echo "Press any key to continue, to remove container"
read any
c0_rm_nodetest1
}
if [ $# -gt 0 ]
then
command=$1
shift
c$#_$command $@
else
c0_default
fi
You can copy and paste the following commands to fetch the script.
mkdir -p ~/tmp
cd ~/tmp
curl -s -O https://raw.githubusercontent.com/evanx/docker-node-test/master/dockertest.sh
cat dockertest.sh
chmod +x dockertest.sh
sudo ./dockertest.sh
It fetches and runs the main script:
https://raw.githubusercontent.com/evanx/docker-node-test/master/dockertest.sh
Yikes, what does that all do? For starters, it creates a minimal test Node web app index.js
, and a start.sh
script for the container to run this app.
The test app runs on port 8888, which we publish to our host, when we run the app container. (Hopefully port 8888 is not used on your host.)
So you run the above script, and then you're like, "What all just happened?"
If you see "Hello, Dockerized Node World" then it worked! We curl'ed localhost
on port 8888, which is bound to docker.io
according to netstat
. That there HTTP request was forwarded to our test app inside its container. Yes that's right, some kinda wonderful has just happened to your Linux computer.
Besides creating the sample index.js
, start.sh
and Dockerfile
for you, the important commands in the above script are actually just the following, albeit as root
e.g. sudo -i
.
docker build -t nodetest .
docker run -d --name nodetest1 -p 8888:8888 nodetest
docker ps -a
sleep 1
curl -s 'http://localhost:8888'; echo
docker rm -f nodetest1
We run the container in "detached mode" via -d
, i.e. in the background, so that the script continues to the curl
. If we don't detach, the script blocks there, i.e. in the console of our test Node app, which is cool, but then nothing else seems to happen.
The script waits for you to "press enter," so that you can try the URL in your browser. If you are on the host e.g. your laptop, then try http://localhost:8888
in your browser. If the host is a remote server on the cloud, then use its hostname. In this case, you might have to open port 8888 if your server is firewalled.
Finally we clean up by removing the container we created. Afterwards, you should check using docker ps
if the container is still running, stopped or removed.
Incidently, I found that the attach
and exec
commands (and --add-host
option on run
) didn't work for me on an Ubuntu 14.04 host (with docker -v
showing 1.0), but did work on a CoreOS droplet, which has an up to date version of Docker (1.3).
As alluded to earlier, the script failed altogether on an Ubuntu 14.10 laptop (Docker 1.2), and on another Ubuntu 14.04 cloud server. However it worked on my Ubuntu 14.04 laptop, and my 14.04 cloud server.
To take this further, you'll want to deploy a Node app using git clone
. In this case, include the following in your Dockerfile
, to install git
.
RUN apt-get install -y git
In your start.sh
, after cloning the repo and changing into its directory, you would npm install
your dependencies as usual.
See: http://seanmcgary.com/posts/deploying-a-nodejs-application-using-docker
If you are using a private repo, include the following in your Dockerfile
, to add the private key to the container.
RUN mkdir /root/.ssh
ADD id_rsa /root/.ssh/
where you have copied id_rsa
i.e. the private component of your deploy key, into the directory of your Dockerfile
e.g. ~/tmp/dockernodetest
in our example.
Also add the following in your start.sh
script to make ssh
happy.
touch /root/.ssh/known_hosts
chmod 700 /root/.ssh
chmod 600 /root/.ssh/*
ssh-keyscan github.com >> /root/.ssh/known_hosts
Then you should be able to git clone
your private repo.
The following method is in the "danger zone" so beware. It removes all containers from your host. This is only safe if the only container is your test one! So it's not included in the above script. But you might want it, so here it is.
c0_rm_all() {
for name in `docker ps -a | grep -v ^CONTAINER | cut -f1 -d' '`
do
docker rm -f $name
done
}
Our microservices could use Redis' pubsub and message queues to collaborate.
We might use Nginx and/or build a custom router in Node, which can handle our authentication and authorisation concerns.
We should enable centralized configuration and logging.
Enjoy and good luck!