Continuing on from part 1 where we introduced the app’s architecture and implemented the solution using Docker and Docker Compose, in this article we will address the issue of distributing the images. Once the Docker image is created it becomes an artifact, two of the biggest advantages that containers add to the software development lifecycle (SDLC) is the fact that they are self-sufficient and immutable.
Being immutable doesn’t lend them to be used with software source control management (SCM) systems like Git or Mercurial, so we need a different type of solution to distribute these Docker images - a container registry.
As the diagram above illustrates, there is a build stage that happens in a build server, this is where the docker build ..
command is executed. The resulting image is an artifact that is immutable, and can be signed with a security certificate for an added layer of security. This image can then be uploaded to a container registry in a process called push, using a command like docker push ...
. Even though these repositories are called container registries, what is actually “pushed” is a Docker image not a container.
Once the image is on the container registiries it can then be downloaded into multiple targets where containers of the image need to be created, this processes is called pull, executed through a command like docker pull ...
. There are several solutions for container registries ranging from hosted solutions to on premise products.
On top of providing a centralised distribution centre for Docker images, some of these products provide quite useful features, one of them being Role Based Access Control (RBAC). RBAC adds a layer of security which allows teams to have granular control of who has access to do what with the images. For example if the developers need to be able to both push and pull images from the registry while Quality Assurance (QA) team mebers only need to be able to pull images but not push, RBAC allows teams to define this.
Hosted providers of container registries as a service include all the major cloud prviders;
- AWS’s Elastic Container Registry
- GCP’s Container Registry
- Azure’s Container Registry
- IBM’s Cloud Container Registry
- Gitlab’s Container Registry
- Quay
- And of course Docker’s very own Docker Hub
- ….
There are also self-hosted options for teams that require that level of control, examples include;
- Docker Distribution
- Harbor
- JFrog’s Artifactory
In this article we are going to use Gitlab’s hosted service which is an excellent product that is available at no charge. So we want to be in a position where all our images (the ones we built and their dependencies) are fetched from repositories, separating the build stage from the run stage. Our docker-compose.yml
file (that we put together in the previous article) should not have any build steps, it should look like this instead;
version: '3'
services:
reverse-proxy:
image: "jwilder/nginx-proxy"
ports:
- "80:80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
depends_on:
- nilipie-web
- nilipie-api
nilipie-web:
# build: "./nilipie-web/"
image: "registry.gitlab.com/haddad/nilipie:1.0.0"
container_name: "nilipie-web"
environment:
- VIRTUAL_HOST=www.nilipie.com
depends_on:
- nilipie-api
nilipie-api:
# build: "./nilipie-api/"
image: "registry.gitlab.com/haddad/nilipie-api:1.0.0"
container_name: "nilipie-api"
links:
- mongodb:mongodb
environment:
- MONGODB_URL=mongodb://mongodb:27017/nilipie
- VIRTUAL_HOST=api.nilipie.com
depends_on:
- mongodb
mongodb:
image: "mongo:latest"
container_name: "mongodb"
The Build Stage
This is the stage that the source code is retrieved from the SCM server, most likely tagged as a release, and the release is packaged into a self sufficient immutable build in the form of a Docker image. This image is then pushed to the Container Registry ready to be ran in relevant upstream environments.
So first we are going to make sure we don’t have the two images we built earlier in our local images repository;
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
git_nilipie-web latest 62c69deb61d6 4 days ago 186MB
git_nilipie-api latest 715b4163393c 4 days ago 109MB
mongo latest 052ca8f03af8 12 days ago 381MB
...
We remove both of them
λ docker rmi --force git_nilipie-web git_nilipie-api
Untagged: git_nilipie-web:latest
Deleted: sha256:62c69deb61d66f7b1101634f4f2c9ded36bde6276cc7c41239df37b08a8cb750
Deleted: sha256:d9ca6919ef22bdac106561fcb8d5a51ecb03f1790f31b34645c2057d4ddc291f
Deleted: sha256:ce54529321735aef880ee4538efdaed6b753cbe1451dd4e06b2803d7a6d4c1fb
Untagged: git_nilipie-api:latest
Deleted: sha256:715b4163393cff4da3f415a08f6531a381036a69ad4406c4be22c77667b7b10b
Deleted: sha256:69fbda258f68427f42282add1441d2975dc091840b4f2fcbb590fe8ea71d8130
Deleted: sha256:301f06f88fc0658f8ccda7c5d8fd1b05372a951140ec3bcb5e6953399afead31
Deleted: sha256:6887de9181acf3ad32d6e3a7a26b55de32837fb5f2e18301079fb703b60d3429
Deleted: sha256:a0ea9ddfb9704119dbb3390237f3af3d6ff82a3f6ac80db9baf01ac75531320d
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mongo latest 052ca8f03af8 12 days ago 381MB
...
Next we are going to rebuild our images but push them to a remote container registry in Gitlab instead. First step is to log in to the repository
docker login registry.gitlab.com
Username: [email protected]
Password:
Login Succeeded
Then build the images with the proper tags
docker build -t registry.gitlab.com/haddad/nilipie-api:1.0.0 .
Sending build context to Docker daemon 13.81MB
Step 1/6 : FROM node:alpine
---> 5ffbcf1d9932
Step 2/6 : WORKDIR /app
---> Running in a1037f7b952f
Removing intermediate container a1037f7b952f
---> d4b528406854
Step 3/6 : ADD . /app
---> 4ada6cf9bd97
Step 4/6 : RUN yarn install
---> Running in f1198ac8e2ea
yarn install v1.9.4
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
Done in 5.66s.
Removing intermediate container f1198ac8e2ea
---> bff952bb93fe
Step 5/6 : EXPOSE 80
---> Running in f1134c7b04d9
Removing intermediate container f1134c7b04d9
---> a830e7f75e8b
Step 6/6 : CMD ["yarn", "start"]
---> Running in d941050c9a61
Removing intermediate container d941050c9a61
---> 548bc9cc30f6
Successfully built 548bc9cc30f6
Successfully tagged registry.gitlab.com/haddad/nilipie-api:1.0.0
docker build -t registry.gitlab.com/haddad/nilipie:1.0.0 .
Sending build context to Docker daemon 3.736MB
Step 1/4 : FROM kyma/docker-nginx
---> eb883be3763d
Step 2/4 : ADD . /var/www
---> e03493620f07
Step 3/4 : EXPOSE 80
---> Running in 06516b329d2c
Removing intermediate container 06516b329d2c
---> 5201b172622f
Step 4/4 : CMD 'nginx'
---> Running in 6f6cd113b5c1
Removing intermediate container 6f6cd113b5c1
---> 5db32e960cd8
Successfully built 5db32e960cd8
Successfully tagged registry.gitlab.com/haddad/nilipie:1.0.0
Now we will have these two images in our local image repository
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.gitlab.com/haddad/nilipie 1.0.0 5db32e960cd8 About a minute ago 186MB
registry.gitlab.com/haddad/nilipie-api 1.0.0 548bc9cc30f6 2 minutes ago 109MB
mongo latest 052ca8f03af8 12 days ago 381MB
...
We now push them both to the remote container registry at Gitlab
docker push registry.gitlab.com/haddad/nilipie
The push refers to repository [registry.gitlab.com/haddad/nilipie]
327c4e3f6689: Pushed
368a7dffbe7b: Pushed
62ebc37d2e70: Pushed
b773e9979a9b: Pushed
4204ee0217af: Pushed
746fbc761b74: Pushed
f30c2bee94eb: Pushed
e71f17e9dd2b: Pushed
1d64af5e5b60: Pushed
42ed799f0d4c: Pushed
5f70bf18a086: Pushed
b91b7f6cd891: Pushed
7ec8eb54c64e: Pushed
6eb35183d3b8: Pushed
1.0.0: digest: sha256:e5f0ab9ed6f9f25720e1bdb05284f8980939db440441602e67ea6b4eb2724ee6 size: 4056
λ docker push registry.gitlab.com/haddad/nilipie-api
The push refers to repository [registry.gitlab.com/haddad/nilipie-api]
227d8a9131c0: Pushed
6bfbbc25b203: Pushed
4cd17b4c6c40: Pushed
6fbd66047f80: Pushed
d29e6a09f93f: Pushed
df64d3292fd6: Pushed
1.0.0: digest: sha256:308a8b94b9f9bfccf01dff881685e318d7054a1690fca22748c00bf0d7fda2e4 size: 1579
We can also verify that these images are now in the container registry from Gitlab’s wed front end;
We now have completed the build stage and the images are available in the remote container registry.
The Run Stage
In the run stage the previously created images which are now available in a container registry are pulled down and ran. This can be done in many environments, developer’s laptops or desktop machines, QA environment (whether it may be their individual machines or a virtualized server cluster) or ops running the containers in a production environment. All these will be retrieved from the same image in the container registry - build once run many times.
To elaborate the point of fetching the images remotely we will first remove again the two images from our local image repository so that Docker pulls all images from remote registries;
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.gitlab.com/haddad/nilipie 1.0.0 5db32e960cd8 16 minutes ago 186MB
registry.gitlab.com/haddad/nilipie-api 1.0.0 548bc9cc30f6 17 minutes ago 109MB
mongo latest 052ca8f03af8 12 days ago 381MB
...
docker rmi 5db32e960cd8 548bc9cc30f6
Untagged: registry.gitlab.com/haddad/nilipie:1.0.0
Untagged: registry.gitlab.com/haddad/nilipie@sha256:e5f0ab9ed6f9f25720e1bdb05284f8980939db440441602e67ea6b4eb2724ee6
Deleted: sha256:5db32e960cd86746b8fecc82b0d3bbb476cb6c802b8d46b553c4a1fafdccb59d
Deleted: sha256:5201b172622f0cd831b518e387ce3ec8b104282e7f79e014ad861ea8f4f65d40
Deleted: sha256:e03493620f070c0dc3f9ddacdd5cfa2b481559ad1bc6a703d7f38055d9d07db7
Deleted: sha256:c618c66bf0ea0d339e010b4349804aac9d09987a4f76a95aa3783552787dbbcb
Untagged: registry.gitlab.com/haddad/nilipie-api:1.0.0
Untagged: registry.gitlab.com/haddad/nilipie-api@sha256:308a8b94b9f9bfccf01dff881685e318d7054a1690fca22748c00bf0d7fda2e4
Deleted: sha256:548bc9cc30f6368205e257121cd8f81d4394bb92bebb2ad7934eafc8508889f4
Deleted: sha256:a830e7f75e8b1d114e0eda324eb1c6eaf0841a97b399a403f56164136ead5e87
Deleted: sha256:bff952bb93fededd15379e3ba3b4f8413719ef89073ad58d02468150d59305c7
Deleted: sha256:4bdecc3e4e9e776229a895e54d3454ecc31d5426f3b676a3486a8110912d967c
Deleted: sha256:4ada6cf9bd97b32ba5be9c9962f39022af921faa9ac196d2e67976a68da5ec46
Deleted: sha256:44949d138112716dcb0b8aee9f265fafce6be617eed058ea96c637061937de95
Deleted: sha256:d4b528406854aefda26b2cf0dc77bae2b482ed96db878d6c5393054485a0a8cb
Deleted: sha256:0326eeae1a6f21c73fdce720a744a0dd77b5fe43459e5ea40bc1892c7d646a04
λ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mongo latest 052ca8f03af8 12 days ago 381MB
...
Now we use Docker Compose to launch our service
λ docker-compose up
Pulling mongodb (mongo:latest)...
latest: Pulling from library/mongo
3b37166ec614: Pull complete
504facff238f: Pull complete
ebbcacd28e10: Pull complete
c7fb3351ecad: Pull complete
2e3debadcbf7: Pull complete
004c7a04feb1: Pull complete
897284d7f640: Pull complete
af4d2dae1422: Pull complete
5e988d91970a: Pull complete
aebe46e3fb07: Pull complete
6e52ad506433: Pull complete
47d2bdbad490: Pull complete
0b15ac2388a7: Pull complete
7b8821d8bba9: Pull complete
Digest: sha256:1a3554b0a631c17738d581afca4715588d6d3e332a24ca23a54ce9fd6b8a1ef6
Status: Downloaded newer image for mongo:latest
Pulling nilipie-api (registry.gitlab.com/haddad/nilipie-api:1.0.0)...
1.0.0: Pulling from haddad/nilipie-api
4fe2ade4980c: Already exists
36e3ebf48735: Already exists
2542dc4892a7: Already exists
57805c670d0d: Pull complete
a1b088131946: Pull complete
7e53d4eeba10: Pull complete
Digest: sha256:308a8b94b9f9bfccf01dff881685e318d7054a1690fca22748c00bf0d7fda2e4
Status: Downloaded newer image for registry.gitlab.com/haddad/nilipie-api:1.0.0
Pulling nilipie-web (registry.gitlab.com/haddad/nilipie:1.0.0)...
1.0.0: Pulling from haddad/nilipie
fffd53603a30: Already exists
4f4fb700ef54: Already exists
43a58ea360c3: Already exists
9720f09c28f1: Already exists
39e3e11b2204: Already exists
a01c25a9e5d0: Already exists
90b1830dd253: Already exists
b548d0ebc7c6: Already exists
5b92a71edf40: Already exists
d1ac08c9491d: Already exists
688ec4ec52d9: Already exists
fea29cbd54a2: Already exists
a3723af21029: Already exists
0c696cae0931: Pull complete
Digest: sha256:e5f0ab9ed6f9f25720e1bdb05284f8980939db440441602e67ea6b4eb2724ee6
Status: Downloaded newer image for registry.gitlab.com/haddad/nilipie:1.0.0
Pulling reverse-proxy (jwilder/nginx-proxy:)...
latest: Pulling from jwilder/nginx-proxy
be8881be8156: Pull complete
b4babd36efe5: Pull complete
f4eba7658e18: Pull complete
fc141716ac64: Pull complete
87b964c68304: Pull complete
d07092114f4c: Pull complete
5092b1e0c1da: Pull complete
d90a3596290d: Pull complete
5ca9f664a671: Pull complete
eb9b93208683: Pull complete
Digest: sha256:e869d7aea7c5d4bae95c42267d22c913c46afd2dd8113ebe2a24423926ba1fff
Status: Downloaded newer image for jwilder/nginx-proxy:latest
Creating mongodb ... done
Creating nilipie-api ... done
Creating nilipie-web ... done
Creating git_reverse-proxy_1 ... done
Attaching to mongodb, nilipie-api, nilipie-web, git_reverse-proxy_1
mongodb | 2018-10-12T13:56:25.584+0000 I CONTROL [main] Automatically disabling TLS 1.0, to force-enable TLS 1.0 specify --sslDisabledProtocols 'none'
mongodb | 2018-10-12T13:56:25.593+0000 I CONTROL [initandlisten] MongoDB starting : pid=1 port=27017 dbpath=/data/db 64-bit host=0652126ade6a
mongodb | 2018-10-12T13:56:25.593+0000 I CONTROL [initandlisten] db version v4.0.3
mongodb | 2018-10-12T13:56:25.593+0000 I CONTROL [initandlisten] git version: 7ea530946fa7880364d88c8d8b6026bbc9ffa48c
mongodb | 2018-10-12T13:56:25.593+0000 I CONTROL [initandlisten] OpenSSL version: OpenSSL 1.0.2g 1 Mar 2016
mongodb | 2018-10-12T13:56:25.593+0000 I CONTROL [initandlisten] allocator: tcmalloc
mongodb | 2018-10-12T13:56:25.593+0000 I CONTROL [initandlisten] modules: none
mongodb | 2018-10-12T13:56:25.593+0000 I CONTROL [initandlisten] build environment:
mongodb | 2018-10-12T13:56:25.593+0000 I CONTROL [initandlisten] distmod: ubuntu1604
mongodb | 2018-10-12T13:56:25.593+0000 I CONTROL [initandlisten] distarch: x86_64
mongodb | 2018-10-12T13:56:25.593+0000 I CONTROL [initandlisten] target_arch: x86_64
mongodb | 2018-10-12T13:56:25.594+0000 I CONTROL [initandlisten] options: { net: { bindIpAll: true } }
mongodb | 2018-10-12T13:56:25.594+0000 I STORAGE [initandlisten]
mongodb | 2018-10-12T13:56:25.594+0000 I STORAGE [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
mongodb | 2018-10-12T13:56:25.594+0000 I STORAGE [initandlisten] ** See http://dochub.mongodb.org/core/prodnotes-filesystem
mongodb | 2018-10-12T13:56:25.594+0000 I STORAGE [initandlisten] wiredtiger_open config: create,cache_size=4467M,session_max=20000,eviction=(threads_min=4,threads_max=4),config_base=false,statistics=(fast),log=(enabled=true,archive=true,path=journal,compressor=snappy),file_manager=(close_idle_time=100000),statistics_log=(wait=0),verbose=(recovery_progress),
mongodb | 2018-10-12T13:56:26.408+0000 I STORAGE [initandlisten] WiredTiger message [1539352586:408380][1:0x7f55577e2a00], txn-recover: Set global recovery timestamp: 0
mongodb | 2018-10-12T13:56:26.421+0000 I RECOVERY [initandlisten] WiredTiger recoveryTimestamp. Ts: Timestamp(0, 0)
mongodb | 2018-10-12T13:56:26.438+0000 I CONTROL [initandlisten]
mongodb | 2018-10-12T13:56:26.438+0000 I CONTROL [initandlisten] ** WARNING: Access control is not enabled for the database.
mongodb | 2018-10-12T13:56:26.438+0000 I CONTROL [initandlisten] ** Read and write access to data and configuration is unrestricted.
mongodb | 2018-10-12T13:56:26.438+0000 I CONTROL [initandlisten]
mongodb | 2018-10-12T13:56:26.438+0000 W CONTROL [initandlisten]
mongodb | 2018-10-12T13:56:26.438+0000 W CONTROL [initandlisten]
mongodb | 2018-10-12T13:56:26.438+0000 I CONTROL [initandlisten]
mongodb | 2018-10-12T13:56:26.438+0000 I STORAGE [initandlisten] createCollection: admin.system.version with provided UUID: b017f0f6-f3f9-4f0d-9815-4bf7f2b2fac6
mongodb | 2018-10-12T13:56:26.599+0000 I COMMAND [initandlisten] setting featureCompatibilityVersion to 4.0
mongodb | 2018-10-12T13:56:26.606+0000 I STORAGE [initandlisten] createCollection: local.startup_log with generated UUID: af2233c7-6313-4c2c-a3c6-eb7faf323a61
mongodb | 2018-10-12T13:56:26.645+0000 I FTDC [initandlisten] Initializing full-time diagnostic data capture with directory '/data/db/diagnostic.data'
nilipie-api | yarn run v1.9.4
mongodb | 2018-10-12T13:56:26.649+0000 I NETWORK [initandlisten] waiting for connections on port 27017
mongodb | 2018-10-12T13:56:26.649+0000 I STORAGE [LogicalSessionCacheRefresh] createCollection: config.system.sessions with generated UUID: bda8656e-4ea1-4df6-ba39-13a30467210d
nilipie-api | $ node app.js
mongodb | 2018-10-12T13:56:26.692+0000 I INDEX [LogicalSessionCacheRefresh] build index on: config.system.sessions properties: { v: 2, key: { lastUse: 1 }, name: "lsidTTLIndex", ns: "config.system.sessions", expireAfterSeconds: 1800 }
mongodb | 2018-10-12T13:56:26.692+0000 I INDEX [LogicalSessionCacheRefresh] building index using bulk method; build may temporarily use up to 500 megabytes of RAM
nilipie-api | DB connected succesfully
nilipie-api | Nilipie API listening at http://:::80
reverse-proxy_1 | WARNING: /etc/nginx/dhparam/dhparam.pem was not found. A pre-generated dhparam.pem will be used for now while a new one
reverse-proxy_1 | is being generated in the background. Once the new dhparam.pem is in place, nginx will be reloaded.
mongodb | 2018-10-12T13:56:26.693+0000 I INDEX [LogicalSessionCacheRefresh] build index done. scanned 0 total records. 0 secs
reverse-proxy_1 | forego | starting dockergen.1 on port 5000
mongodb | 2018-10-12T13:56:27.908+0000 I NETWORK [listener] connection accepted from 172.18.0.3:56082 #1 (1 connection now open)
reverse-proxy_1 | forego | starting nginx.1 on port 5100
mongodb | 2018-10-12T13:56:27.926+0000 I NETWORK [conn1] received client metadata from 172.18.0.3:56082 conn1: { driver: { name: "nodejs", version: "2.2.27" }, os: { type: "Linux", name: "linux", architecture: "x64", version: "4.9.93-linuxkit-aufs" }, platform: "Node.js v10.10.0, LE, mongodb-core: 2.1.11" }
reverse-proxy_1 | dockergen.1 | 2018/10/12 13:56:28 Generated '/etc/nginx/conf.d/default.conf' from 26 containers
reverse-proxy_1 | dockergen.1 | 2018/10/12 13:56:28 Running 'nginx -s reload'
reverse-proxy_1 | dockergen.1 | 2018/10/12 13:56:28 Watching docker events
reverse-proxy_1 | dockergen.1 | 2018/10/12 13:56:28 Contents of /etc/nginx/conf.d/default.conf did not change. Skipping notification 'nginx -s reload'
reverse-proxy_1 | 2018/10/12 13:56:29 [notice] 52#52: signal process started
reverse-proxy_1 | Generating DH parameters, 2048 bit long safe prime, generator 2
reverse-proxy_1 | This is going to take a long time
reverse-proxy_1 | dhparam generation complete, reloading nginx
We now have clearly separated the build and the run stages of our SDLC, we have a declarative definition of our cloud native solution in the form of the docker-compse.yml
file that can be maintained separately from the source code of the individual components of the solution. This file could belong to a different project that maintains the solution (or project) as a whole, as in the file we point to a particular version of the web and api Docker images (we can do that for all images as well, like MongoDB etc if we want to tie down our project to particular versions of dependencies) when we want to move to different versions of these components we have a new version of this project released that will pull the right versions of components’ Docker images.
This is still a novel set up as we haven’t addressed issues like security, networking, session management or storage for the cloud native architecture but we will cover these in coming articles. Next thing I would like to cover is scaling, how do we scale the application in a cloud infrastructure. We might want to, for example, scale the back end application more than the front end should we deem it necessary which is quite interesting. The tools that allow this are called container orchestration tools, like Docker Swarm or the famous Kubernetes.
In the next article we look at using Kubernetes to orchestrate our app.
comments powered by Disqus