Monolith to Microservices 2

Dockerising the Monolith

Docker as a technology seems to divide the community, some say its a good solution to the problem of packaging up your software that collects all of your dependencies and then stamps them into a repeatable deployable package….others say its been done before with other technologies or that its a security problem waiting to happen since all of the dependencies you’ve placed in the container are locked at a particular version and not updatable.

If you’ve got a pipeline around it thet can regenerate your deployable package in a repeatable and testable way (which is what we’re aiming to do with this series), then my opinion tends to be with the former. We can use these repeatable blocks to create something complex yet easy to support. BUT we do have to make sure its automated and repeatable…..

Lets make our first immutable building block.

Here are the basics of Docker (and yes, there are books and books more on this topic alone, however I’m purely going for the minimum here…)

Docker Images are like Templates. We can use any templates stored in a Docker Registry and use that as a basis to create a new Image. (If you want to have a look at what images you can build on top of, search google for docker hub). To create a new Image, stored locally, we’ll use the “docker build” command in combination with a “DockerFile”

Docker Containers are the running version of an Image. They are an “instance” of that image that can run. To execute a container we’ll use “docker container run”

We’ll get to registries, and docker pulls in another article.

We’re going to use my example hosted at https://github.com/r3adm3/monolithsvc (commit: 4d9481801d9ed7b45f380ecb6b3ab6187fcd3fed) to save you (for now) from doing some coding.

If you look at the docker file in the monolithsvc/monolithsvc folder you’ll find a docker file which we can use to build a new image.

For now the aim is to build a docker image using a base image, and add our code to it. Lets go through my docker file one line at a time. Each of these lines, becomes a layer in our docker “onion”, this leads to the ability to reuse layers and speed up successive builds.

1FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS builder

This instructs docker to use the mcr.microsoft.com/dotnet/core/sdk:3.1 image (pull it from docker hub or mcr.microsoft.com if necessary, or if its already in the local library just use that).

1WORKDIR /source

Docker build working inside the container, then sets the working directory to “source”

1COPY *.csproj .
2RUN dotnet restore

…this copies our project file from our Docker host into our image, and pulls down all of our Nuget dependencies.

1COPY . .
2RUN dotnet publish --output /app/ --configuration Release

copies the rest of our source into place, from Docker host into the SDK image, and creates a binary in the app folder.

1FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
2WORKDIR /app
3COPY --from=builder /app .
4ENTRYPOINT ["dotnet", "monolithsvc.dll"]

and now the magic. Create another new image from the “mcr.microsoft.com/dotnet/core/aspnet:3.1 ” image, set the working directory to “app” and copy the contents of the app folder on the builder image to this one. “Entrypoint” defines the command that will run on invocation.

Here’s the whole file:

 1# Sample contents of Dockerfile (another test)
 2# Stage 1
 3FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS builder
 4WORKDIR /source
 5
 6# caches restore result by copying csproj file separately
 7COPY *.csproj .
 8RUN dotnet restore
 9
10# copies the rest of your code
11COPY . .
12RUN find -type d -name bin -prune -exec rm -rf {} \; && find -type d -name obj -prune -exec rm -rf {} \;
13RUN dotnet publish --output /app/ --configuration Release
14
15# Stage 2
16FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
17WORKDIR /app
18COPY --from=builder /app .
19ENTRYPOINT ["dotnet", "monolithsvc.dll"]

So how do we now build the image? Do this by:

 1$ docker build -t monolithsvc .
 2Sending build context to Docker daemon  123.9MB
 3
 4Step 1/11 : FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS builder
 5 ---> 9ab567a29502
 6Step 2/11 : WORKDIR /source
 7 ---> Using cache
 8 ---> a3865660feec
 9Step 3/11 : COPY *.csproj .
10 ---> f288604a05eb
11Step 4/11 : RUN dotnet restore
12 ---> Running in 470e94685a37
13  Determining projects to restore...
14  Restored /source/monolithsvc.csproj (in 29.18 sec).
15
16Removing intermediate container 470e94685a37
17 ---> cb8af44c9f96
18Step 5/11 : COPY . .
19 ---> 68b936eaea5d
20Step 6/11 : RUN find -type d -name bin -prune -exec rm -rf {} \; && find -type d -name obj -prune -exec rm -rf {} \;
21 ---> Running in 27b126eb6c2b
22Removing intermediate container 27b126eb6c2b
23 ---> d7ac0ef9c649
24Step 7/11 : RUN dotnet publish --output /app/ --configuration Release
25 ---> Running in 8bdc5eb89d55
26
27Microsoft (R) Build Engine version 16.7.0-preview-20360-03+188921e2f for .NET 
28Copyright (C) Microsoft Corporation. All rights reserved.
29
30  Determining projects to restore...
31  Restored /source/monolithsvc.csproj (in 866 ms).
32
33  monolithsvc -> /source/bin/Release/netcoreapp3.1/monolithsvc.dll
34  monolithsvc -> /source/bin/Release/netcoreapp3.1/monolithsvc.Views.dll
35  monolithsvc -> /app/
36
37Removing intermediate container 8bdc5eb89d55
38 ---> f728dea1188c
39Step 8/11 : FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
40 ---> bdca989bc8d3
41Step 9/11 : WORKDIR /app
42 ---> Using cache
43 ---> bf0c00033f92
44Step 10/11 : COPY --from=builder /app .
45 ---> a471287b9c9d
46Step 11/11 : ENTRYPOINT ["dotnet", "monolithsvc.dll"]
47 ---> Running in 94044c2fd3ef
48
49Removing intermediate container 94044c2fd3ef
50 ---> 218bd7169dbb
51Successfully built 218bd7169dbb
52Successfully tagged monolithsvc:latest

We should have a successful image in the local docker repo now, do a

1$ docker image ls
2
3REPOSITORY                                  TAG                      IMAGE ID            CREATED             SIZE
4monolithsvc                                 latest                   ecc207b7e994        5 minutes ago       256MB

as you can see the image id matches with the hash at the final step of the docker build. Cool.

Last but not least we should try and make it run:

1$ docker container run --name testdoodad -p 8080:80 monolithsvc
2
3warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
4      No XML encryptor configured. Key {20f00e1c-b3c9-4b3a-b1e8-b36e7bbc73eb} may be persisted to storage in unencrypted form.
5Hosting environment: Production
6Content root path: /app
7Now listening on: http://[::]:80
8Application started. Press Ctrl+C to shut down.

…and test using a browser http://localhost:8080

Hopefully should all be nice and happy!

Source code at this point is here:

https://github.com/r3adm3/monolithsvc/commit/4d9481801d9ed7b45f380ecb6b3ab6187fcd3fed

Links to other articles in the series:

  1. First a Monolith

…and a quick thx to SamM for going through this today 9/3/2021.

Posts in this Series