This HW implementation includes advanced docker techniques like using docker-compose, docker deployment and file IO using socat. The HW specification can be found here. For all the three parts of this assignment, I have separate folders which contain individual code for that particular part.
I have three containers which are started by docker-compose
:
redis
: This container has a based Ubuntu image and simply installsredis
and starts off theredis
service at port6379
.webapp
: This holds the HW3 app with certain changes needed for spawning new containers instead of new servers. It depends on theredis
container. TheDockerfile
for this also has anARG
predicate which takes in an argument while building the docker image. This holds a default value of3000
, so thewebapp
starts off at this port. Later on when new containers will be spawned, they will be give new port numbers and that argument will be used to create an environment variable using theENV
predicate. The node application inwebapp
when started, will extract this environment variable usingprocess.env.start_port
and accordingly start theexpress
server in that port. As I refer to all my containers using the VM's IP, I need different port numbers to distinguished between the newly spawned containers.proxy
: This depends onredis
in thedocker-compose.yml
as well. It starts a simplehttp-proxy
at port3456
and on any request, does arpoplpush
on the servers list maintained inredis
and delegates the request to that server. This servers list is updated on request and periodically bywebapp
andinfrastructure.js
respectively.
The infrastructure.js
sits outside the containers as a simple node application, which has a setInterval
method and periodically checks for new server URLs being added to a temporary server list in redis
. This new server URL will be added by webapp
when /spawn
will be called by the user from a browser. webapp
will basically get all the current servers in the servers list in redis
, find the maximum of the port numbers and then allot a port number for the new server which is 1
more than the maximum and then push this value (as a complete formed URL) to the temporary servers list. infrastructure.js
will get this server URL and run spawn_docker.sh
giving the to-be-deployed container a new app name based on the port number and the port number itself. After spawning the new container, it removes the server URL from the temporary servers redis
list and pushes it to the servers list. Thus the servers list has a collection of all the stable and newly created containers. The shell script spawn_docker.sh
does the following things:
- Stops the app referred by the provided app name.
- Removes it.
- Removes the corresponding image.
- Builds a new image and passes the port number as a
--build-args
value. - Starts the container with the new app name based on the image just created.
Similarly, when /destroy
is called, a random server is chosen other than the first server that was spawned at port 3000
and is put in a delete servers temporary redis
list. The infrastructure.js
file also polls this list and sees if there is anything to destroy. If so, it stops the app, removes it and removes the image associated with it. Accordingly, it also removes the value from the main servers redis
list.
I have my app in the app
subdirectory. I have also created a deploy
subdirectory which holds the blue.git
and green.git
bare repositories and the blue-www
and green-www
deployed docker container contents. This folder is not present in the github repository as I don't want to commit irrelevant *.git
files to another git repository. So I have committed the hooks in the post-receive_blue
and post-receive_green
files. The complete implementation is present in these post-receive
hooks for both the git repositories. The app
holds both the repositories as remote
. A private docker registry has been started at port 5000
with:
docker run -d -p 5000:5000 --restart=always --name registry registry:2
When you push to either of the repositories, the post-receive
hook gets fired and it does the following things:
- It sets the Git work directory as the respective
www
directory, depending upon where it has been pushed (green.git
orblue.git
). - It checks out the files to that specific work directory.
- It stops the
green-app
or theblue-app
container. - It removes the specific container.
- It removes
green-image
or theblue-image
. - It builds the image again.
- It tags the image with the private registry and a specific name.
- It pushes the current image to the registry.
- It pulls the latest image from the registry.
- It runs the node application in a container called
green-app
orblue-app
at port50100
or50101
.
I have started this part using docker-compose as well. This has two docker containers - one for writing contents to a file and exposing it via socat
and another container for doing curl
from that port on a HTTP request and sending the contents back as response.
The writer
container has a setInterval
method which writes random stuff in a file every 2 minutes. Other than that, it has an exec
node command which starts the socat
listener. The socat
command used is:
socat TCP-LISTEN:7000,fork,reuseaddr,crlf OPEN:random,rdonly
where random
is the name of the file. 7000
is th port where it will open the TCP listener. Hence, in the Dockerfile, this container has to expose the port 7000
.
The webapp
container has a simple HTTP server which runs on port 8000
and listens for incoming requests. Whenever it gets a request, it does a curl
on the IP write
(this comes as a hostname as the write
container has been linked from the docker-compose.yml
file) and sends the response back to the browser.