The Akka.CQRS sample used in this workshop uses a build system built on top of Petabridge’s standard dotnet new template.

One thing you’ll need to do multiple times during the course of this workshop is build new versions of your Docker images - this document outlines how the Akka.CQRS build system works and how to do this.

Update RELEASE_NOTES.md

The first step to publishing a new Docker image for all of the Akka.CQRS projects is to update the top entry of the RELEASE_NOTES.md file:

#### 0.1.0 May 26 2019 ####
* Fixed `NullReferenceException` when recovering `MatchAggregatorSnapshot` records with no price and volume updates.
* Fixed issue with BSON serialization for `MatchAggregatorSnapshot` records.

To bump the version number on your Docker images, add a new entry using this format to the top of the _RELEASE_NOTES.md markdown file along with a description of your changes:

#### 0.1.1 May 27 2019 ####
* Reduced verbosity of the logging system.

#### 0.1.0 May 26 2019 ####
* Fixed `NullReferenceException` when recovering `MatchAggregatorSnapshot` records with no price and volume updates.
* Fixed issue with BSON serialization for `MatchAggregatorSnapshot` records.

The version number at the top of the RELEASE_NOTES.md file dictates the version number added to all of the Docker image tags we’re going to create, so choose it accordingly!

Run build.cmd on Windows or build.sh on OS X / Linux

Once RELEASE_NOTES.md is updated, we then need to execute the following command:

./build.cmd Docker [phobos] [remoteRegistry={YOUR_REMOTE_DOCKER_URL}]` 

Advanced Usage: Pass in the phobos flag on the commandline if you want to add Phobos Akka.NET actor monitoring and tracing support to this Docker image - otherwise it’ll be left off by default.

You can also pass in a URL to a remote registry, such as myazurecontainerregistery.acr.io, to have Docker images tagged for that remote Docker image registry via the ./build.cmd Docker remoteRegistry=myazurecontainerregistery.acr.io parameter.

However, you will need to make sure your build server or your local development machine has completed the necessary docker login steps before you run this command.

Under the covers, this command executes the following workflow:

Akka.CQRS Docker image build process

Call dotnet publish on Every .Service or .Web .csproj

We only want to publish our .NET Core applications as Docker images, not our unit tests and and libraries, thus that’s why we have the following filtering code-in place in our build.fsx file:

Target "PublishCode" (fun _ ->    
    let projects = !! "src/**/*.Service.csproj" // publish services  and web only
                      ++ "src/**/*.Web.csproj"

    let runSingleProject project =
        DotNetCli.Publish
            (fun p -> 
                { p with
                    Project = project
                    Configuration = configuration
                    VersionSuffix = overrideVersionSuffix project
                    AdditionalArgs = ["--no-restore --output bin/Release/netcoreapp2.1/publish"] // would be ideal to change publish dir via MSBuild
                    })

    projects |> Seq.iter (runSingleProject)
)

The dotnet publish command packs all of the binaries and other resources our application needs to run into a single set of folders: bin/Release/netcoreapp2.1/publish typically. This is what is typically used for all kinds of .NET Core binary deployments and Docker is no exception.

Call docker build on every .Service or .Web .csproj

Once all of the content is published into the bin/Release/netcoreapp2.1/publish folder for each project, our Dockerfiles can be written to copy those binaries into the working directory our application will use inside each Docker container:

FROM microsoft/dotnet:2.1-sdk AS base
WORKDIR /app

# should be a comma-delimited list
ENV CLUSTER_SEEDS "[]"
ENV CLUSTER_IP ""
ENV CLUSTER_PORT "6055"
ENV MONGO_CONNECTION_STR "" #MongoDb connection string for Akka.Persistence

COPY ./bin/Release/netcoreapp2.1/publish/ /app

# 9110 - Petabridge.Cmd
# 6055 - Akka.Cluster
EXPOSE 9110 6055

# Install Petabridge.Cmd client
RUN dotnet tool install --global pbm 

# Needed because https://stackoverflow.com/questions/51977474/install-dotnet-core-tool-dockerfile
ENV PATH="${PATH}:/root/.dotnet/tools"

# RUN pbm help

CMD ["dotnet", "Akka.CQRS.Pricing.Service.dll"]

This Dockerfile is from the Akka.CQRS.Pricing.Service. As you can see, the COPY instruction is what copies the output from the dotnet publish command we executed a step earlier.

Mapping .csproj Names to Docker Images

Inside build.fsx we use a simple matching function to translate the name of every Akka.CQRS service into an acceptable Docker image name:

let mapDockerImageName (projectName:string) =
    match projectName with
    | "Akka.CQRS.TradeProcessor.Service" -> Some("akka.cqrs.tradeprocessor")
    | "Akka.CQRS.TradePlacers.Service" -> Some("akka.cqrs.traders")
    | "Akka.CQRS.Pricing.Service" -> Some("akka.cqrs.pricing")
    | "Akka.CQRS.Pricing.Web" -> Some("akka.cqrs.pricing.web")
    | _ -> None

The reason why we do this is because Docker has a specific convention it enforces for image names:

An image name is made up of slash-separated name components, optionally prefixed by a registry hostname. The hostname must comply with standard DNS rules, but may not contain underscores. If a hostname is present, it may optionally be followed by a port number in the format :8080. If not present, the command uses Docker’s public registry located at registry-1.docker.io by default. Name components may contain lowercase letters, digits and separators. A separator is defined as a period, one or two underscores, or one or more dashes. A name component may not start or end with a separator.

Since all image names must be lowercase, we have to convert the normal Pascal case used by .NET namespaces into something the Docker runtime will accept, hence this mapping function.

Tagging Docker Images

Now for the final stage, tagging the Docker images. This is done using the version number from the ‘RELEASE_NOTES.md’ file, the mapped Docker image name, and the remoteRegistry value if you provided one.

let buildDockerImage imageName projectPath =
    
    let args = 
        if(hasBuildParam "remoteRegistry") then
            StringBuilder()
                |> append "build"
                |> append "-t"
                |> append (imageName + ":" + releaseNotes.AssemblyVersion) 
                |> append "-t"
                |> append (imageName + ":latest") 
                |> append "-t"
                |> append (remoteRegistryUrl + "/" + imageName + ":" + releaseNotes.AssemblyVersion) 
                |> append "-t"
                |> append (remoteRegistryUrl + "/" + imageName + ":latest") 
                |> append "."
                |> toText
        else
            StringBuilder()
                |> append "build"
                |> append "-t"
                |> append (imageName + ":" + releaseNotes.AssemblyVersion) 
                |> append "-t"
                |> append (imageName + ":latest") 
                |> append "."
                |> toText


    ExecProcess(fun info -> 
            info.FileName <- "docker"
            info.WorkingDirectory <- Path.GetDirectoryName projectPath
            info.Arguments <- args) (System.TimeSpan.FromMinutes 5.0) (* Reasonably long-running task. *)

All Docker images will automatically be tagged with the version number you provided AND the latest tag.

Upon successfully building your Docker images, you should see output similar to the following (using 0.1.0 in our RELEASE_NOTES.md, in this case):

Akka.CQRS Docker Build output

If you liked this post, you can share it with your followers or follow us on Twitter!
Written on

 

 

Observe and Monitor Your Akka.NET Applications with Phobos

Did you know that Phobos can automatically instrument your Akka.NET applications with OpenTelemetry?

Click here to learn more.