First Service

Ray Kahn
7 min readMay 11, 2018

--

Before we jump into the nitty gritty of creating our first container service let’s see what the end result should look like.

Our doctors-service, along with other service that we will develop later, will need to be deployed to all the containers that we have built, nodeMongo1..3.

Previously we covered the following:

How Does Building a Containerized Service Work?

A container image is a lightweight, stand-alone, executable package of a piece of software that includes everything needed to run it: code, runtime, system tools, system libraries, settings.

A container is launched by running an image, and an image is an executable package that includes everything needed to run an application. A container is a runtime instance of an image — what the image becomes in memory when executed.

Do you have Docker installed?

> docker --version
Docker version 18.03.1-ce, build 9ee9f40

If you wanted to further test your Docker (but we really don’t need to since we have already created mongoNode1..3).

> docker run hello-worldUnable to find image 'hello-world:latest' locallylatest: Pulling from library/hello-world9bb5a5d4561a: Pull completeDigest: sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77Status: Downloaded newer image for hello-world:latestHello from Docker!This message shows that your installation appears to be working correctly.

List Docker Images & Cleanup

> docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> c7fff56c65ea 9 days ago 734MBmongo latest 14c497d5c758 10 days ago 366MBnode latest 26cbfbc03e3f 13 days ago 675MBhello-world latest e38bc07ac18e 4 weeks ago 1.85kB

There are 2 images that I really don’t need. <none> (untagged) which is a zombie (dangling in Docker nomenclature), and hello-world, which was to show that Docker works.

Remove dangling images

The reason untagged images happen is because you built an image, then you changed the Dockerfileand built that image again and it reused some of the layers from the previous build. Now you have an untagged image which cannot be deleted because some of it’s layers are being used by a new version of that image.

> docker rmi $(docker images --filter "dangling=true" -q --no-trunc)

If you get an error message such as below, you must also remove the stopped containers which are being referenced by the dangling container.

Error response from daemon: conflict: unable to delete c7fff56c65ea (must be forced) - image is being used by stopped container b1b7eca5f8d0

Get a list of stopped (exited) containers.

> docker ps --filter "status=exited"
8a4b3d0bdb03 e38bc07ac18e "/hello"
0aba14ef1db2 162a72ecb480 "npm start" 4 days ago Exited (255) 4 days ago 0.0.0.0:3001->3001/tcp services-service3e64fc37bd8a 53feac689bf4 "npm start" 4 days ago Exited (255) 4 days ago 0.0.0.0:3000->3000/tcp doctors-serviceb1b7eca5f8d0 c7fff56c65ea "/bin/sh -c 'npm cac…" 34ad5095c1f0 26cbfbc03e3f "/bin/sh -c 'addgrou…"

I am going to remove the highlighted one

> docker rm — force b1b7eca5f8d0
b1b7eca5f8d0
> docker image ls
<none> <none> c7fff56c65ea
mongo latest 14c497d5c758 node latest 26cbfbc03e3f

Still there… so

> docker rmi $(docker images --filter "dangling=true" -q --no-trunc)
Deleted: sha256:c7fff56c65ea15a1b5dc56818d8a509f5314a85244712a5993ddf2031df96e62
Deleted: sha256:8eaa5e6e26bc7df61dbeb895c614a3efc86560ba77285d12d00e48178fb0f83bDeleted: sha256:14a7ea2ec100d8122f4c8472daa7c8838d48f407a7f4acba093794f79579aaabDeleted: sha256:1e19c0c00894a0e5239c5045950a2edd2e4c528c046bdb4fc2fc6f6be59feb36Deleted: sha256:0b580e044a94b6933c9eea5331be3782c7976ce96da45dec6ceee8dd6bdf80a0Deleted: sha256:75238e22891756f2af6d58c769abf550293a6c18dd6356b4da6f2c417cd1e51aDeleted: sha256:9c48d74ad793d12c1864080a76f09a8b3bbb5021a53af1b33bd4eafc723e8e17Deleted: sha256:843e458cfa6b0dc523f5381f26b6400731672497458eeaf512c62eab2bd194ecDeleted: sha256:37226dd2ac6f76694115aac637a4f7856068ae229a4951e30f473b451383fa9a

We have removed the dangling containers. To verify:

> docker image ls 
mongo latest 14c497d5c758
node latest 26cbfbc03e3f

Let’s create a containerized service

To create a service we will create an image for the service. From the top level of the doctors-service I will run the following commands (or just run create-image.sh file).

# will do some cleanup first to make sure the image doesn't exist   # already
> docker rm -f doctors-service
> docker rmi doctors-service> docker image prune> docker volume prune> docker build -t doctors-service .

The ‘docker build’ command will call your Dockerfile

# check whether node has been installed for this container, if not  # then it will install it.
FROM node:latest
# avoid the root user by creating a new one
RUN useradd --user-group --create-home --shell /bin/false nupp && \
apt-get cleanENV HOME=/home/nuppCOPY package.json npm-shrinkwrap.json $HOME/app/# copy the src to the image directory
COPY src/ $HOME/app/src
RUN chown -R nupp:nupp $HOME/* /usr/local/WORKDIR $HOME/app# install the dependencies from package.json
RUN npm install
RUN chown -R nupp:nupp $HOME/*USER nupp# expose port number 3000 (next service will have a different one)
EXPOSE 3000
# instantiate the service
CMD ["npm", "start"]

So to create the image

# Changed it to an executable one by 'chmod ugo+x create-image.sh'
# If not executable: 'bash < create-image.sh'
> ./create-image.sh
Error: No such container: doctors-service
Error: No such image: doctors-serviceWARNING! This will remove all dangling images.Are you sure you want to continue? [y/N] yTotal reclaimed space: 0BWARNING! This will remove all local volumes not used by at least one container.Are you sure you want to continue? [y/N] yTotal reclaimed space: 0BSending build context to Docker daemon 50.32MBStep 1/12 : FROM node:latest---> 26cbfbc03e3fStep 2/12 : RUN useradd --user-group --create-home --shell /bin/false nupp && apt-get clean---> Running in 23b2797b86f5Removing intermediate container 23b2797b86f5---> a8bbffd3de94Step 3/12 : ENV HOME=/home/nupp---> Running in f04fb87e1d93Removing intermediate container f04fb87e1d93---> 9caeec596b42Step 4/12 : COPY package.json npm-shrinkwrap.json $HOME/app/---> f7049426b187Step 5/12 : COPY src/ $HOME/app/src---> ef7af43eb500Step 6/12 : RUN chown -R nupp:nupp $HOME/* /usr/local/---> Running in 6a8a6088c353Removing intermediate container 6a8a6088c353---> 4b08a2201cddStep 7/12 : WORKDIR $HOME/appRemoving intermediate container 39f802ce5467---> 50ca20033467Step 8/12 : RUN npm install---> Running in d8d8a6aa4710added 415 packages in 8.978sRemoving intermediate container d8d8a6aa4710---> 79ad105e16e5Step 9/12 : RUN chown -R nupp:nupp $HOME/*---> Running in ef907691cc1cRemoving intermediate container ef907691cc1c---> e452c8133c83Step 10/12 : USER nupp---> Running in a48f80fb1bc1Removing intermediate container a48f80fb1bc1---> 1a62c5ad8196Step 11/12 : EXPOSE 3000---> Running in 5904ed111787Removing intermediate container 5904ed111787---> de97f891c2aaStep 12/12 : CMD ["npm", "start"]---> Running in 2851e0de3542Removing intermediate container 2851e0de3542---> 0a9afcc350a6Successfully built 0a9afcc350a6Successfully tagged doctors-service:latest

Verify that the image was created:

> docker image ls
doctors-service latest 0a9afcc350a6
mongo latest 14c497d5c758 node latest 26cbfbc03e3f

Run the Docker service

> ./start-service.sh562e6d63b76b656dddebef14037c3e5fc305396e95efcb3f1e4fd1813ff3b491

Verify it’s running.

Potential Error

If you get the following error, you must update the hosts file of each of your virtualBox vms.

(node:16) UnhandledPromiseRejectionWarning: MongoNetworkError: failed to connect to server [worker1:27017] on first connect [MongoNetworkError: connection 4 to worker1:27017 timed out]
at Pool.<anonymous> (/home/nupp/app/node_modules/mongoose/node_modules/mongodb-core/lib/topologies/server.js:505:11)
at Pool.emit (events.js:182:13)
at Connection.<anonymous> (/home/nupp/app/node_modules/mongoose/node_modules/mongodb-core/lib/connection/pool.js:329:12)
at Object.onceWrapper (events.js:273:13)
at Connection.emit (events.js:182:13)
at Socket.<anonymous> (/home/nupp/app/node_modules/mongoose/node_modules/mongodb-core/lib/connection/connection.js:256:10)
at Object.onceWrapper (events.js:273:13)
at Socket.emit (events.js:182:13)
at Socket._onTimeout (net.js:447:8)
at ontimeout (timers.js:427:11)
at tryOnTimeout (timers.js:289:5)
at listOnTimeout (timers.js:252:5)
at Timer.processTimers (timers.js:212:10)
(node:16) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:16) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

To solve this issue:

> docker-machine ssh manager1Boot2Docker version 18.05.0-ce, build HEAD : b5d6989 - Thu May 10 16:35:28 UTC 2018Docker version 18.05.0-ce, build f150324
docker@manager1:~$ sudo vi /etc/hosts
# add the following lines to hosts file (or whatever ip address # assigned to your vm box.
192.168.99.100 manager1
192.168.99.101 worker1192.168.99.102 worker2

Repeat the same for worker1 and worker2 vms.

> docker-machine ssh worker1Boot2Docker version 18.05.0-ce, build HEAD : b5d6989 - Thu May 10 16:35:28 UTC 2018Docker version 18.05.0-ce, build f150324
docker@worker1:~$ sudo vi /etc/hosts
# add the following lines to hosts file (or whatever ip address # assigned to your vm box.
192.168.99.100 manager1
192.168.99.101 worker1192.168.99.102 worker2

And for worker 2

> docker-machine ssh worker2Boot2Docker version 18.05.0-ce, build HEAD : b5d6989 - Thu May 10 16:35:28 UTC 2018Docker version 18.05.0-ce, build f150324
docker@worker2:~$ sudo vi /etc/hosts
# add the following lines to hosts file (or whatever ip address # assigned to your vm box.
192.168.99.100 manager1
192.168.99.101 worker1192.168.99.102 worker2

Another Potential Error

If you get the following error, remove any options to mongoose connection call.

Error: ERROR::providers-service::model::ProviderModel::getProviderById: TypeError: Cannot read property 'wireProtocolHandler' of nullat cb (/home/nupp/app/src/model/ProviderModel.js:182:13)at /home/nupp/app/node_modules/mongoose/lib/model.js:4161:16at Immediate.Query.base.findOne.call (/home/nupp/app/node_modules/mongoose/lib/query.js:1529:14)at Immediate.<anonymous> (/home/nupp/app/node_modules/mquery/lib/utils.js:119:16)at runCallback (timers.js:696:18)at tryOnImmediate (timers.js:667:5)at processImmediate (timers.js:649:5)

In essence mongooseObject.js should change to the following:

mediator.once('boot.ready', () => {
const options = {};
mongoose.connect(getURL(config), options);
mongoose.connection.on('error', (err) => {
mediator.emit('db.error', err);
});
mongoose.connection.on('connected', () => {
mediator.emit('db.ready', mongoose);
});
});

How does our service look like?

Well, we have managed to deploy our service to a single container. That’s because we are running our app in a non-swarm mode.

What’s Next?

We have successfully created our first container service. But we still have a very long way to go. In our next exercise we will create a service that can be called from our doctors-service. After all, in real life, services need to communicate with one another.

--

--