Deploying An ASP.NET Core Site, You're Still Doing It Wrong : Part 2 - TeamCity
This day and age, predictability and reliability are two of the most important words when it comes deployments. In this series, I am going to talk about how to add both predictability and reliability to your ASP.NET Core Continuous Integration pipeline using TeamCity and Octopus Deploy.
In the previous post to this series, we talked about our desires to become more predictable and reliable through automation. We also touched on some of the prerequisites needed to accomplish this using the methods defined in this series. In this post, we will dive into creating the base of a Continuous Integration pipeline by setting up TeamCity to serve our build server needs.
Wait, You Build Deployments Locally?
Building deployments out of your local source is for a lack of better words, a terrible idea. It is riddled with problems such as...
- When building from x-number of environments, you are introducing x-number of variables that may or may not have an effect on the final build that is outputted.
- By Isolating the environment to a single point of entry (repository) and single point of exit (build server), you are removing the "it works on my machine" from the equation.
- When a build fails, we need to be able to trace its origination to determine what went wrong.
- If we are building from x-amount of entry points, we don't have a clear path to that origin.
- If we do not have a clear path to the origin, we don't have a clear definition of the state of the code and by way of cannot reliably say what caused any given issue.
- If you are not using a single entry point for your build process, you are introducing the possibility of the state of code to not be aligned with the exceptions of multiple developers.
For example, black-box developer A is working on feature B. Black-box developer C is working on feature D and does not know about feature B.
- If developer C is responsible for producing a build for deployment, feature B might not end up in said deployment.
To be honest, this list can go one for a very long time. A simple Google search will pull up an exhaustive list of reasons why build servers building from a single source of truth is the desired practice. Point being, if you are not doing such then you are definitely still doing it wrong.
TeamCity these day is super easy to install. You have one of two option, install it using the TeamCity Installer or do it like a modern developer would and use Chocolatey. I went the Chocolatey route and ran the following command.
choco install teamcity
Once installed head over to http://localhost:8111 and follow the prompts.
Set the location where TeamCity will store its metadata on disk.
Make your connection to the database you want TeamCity to use.
It will take a minute or two to initialize the rest of TeamCity components but once it is done, you will be asked to accept a licensing agreement and create an administrator account. After you have finished creating an account, you will end up logged into the TeamCity portal.
Creating A Project
Now that we have our build server installed, it is time to create a project. A project is a group of one or more Build Configurations. A Build Configuration is a set of steps that will automate the process of building an asset that we can use to deploy. Let's start with a blank project and configure everything manually. On the "Create Project" screen select "Manually" and fill out the required fields.
Connecting Up With Our Source Code
Our build server needs to know or be told that our source code has changed in order to trigger a build. To accomplish this, we will have one of two options on how we want to communicate with Github.
Triggering From GitHub
If your install of TeamCity can be reached on the open Internet, you can set up GitHub to initiate a build every time code is pushed to your repository. To accomplish this you would create what is called a "Connection". This connection will allow for GitHub to talk directly to TeamCity.
You will be required to register your instance of TeamCity with GitHub. Once register, you will be supplied with a "Client ID" and "Client secret" to finalize the contract between the two apps.
If you don't want to open up access to your TeamCity instance from the Internet, you can instead tell TeamCity to poll your repository for changes. To accomplish this we will need to create two things.
- Define how the project source code is actually retrieved from your repoisotry.
I will talk more about both of these topics after we create a Build Configuration.
Now that our project is created and we have decided how we are going to tie in GitHub, we can start creating our Build Configuration. Build Configurations are made up of the step by step process we want to automate in order to build our project. For us, our goals will be as follows.
- Detect changes to the source and pull it to our build server.
- Restore all NuGet packages.
- Build our project.
- Publish the build and make it available to our automated deployment system, Octopus Deploy.
Head over to your projects "General Settings" page and select "Create Build Configuration". Let's again select the "Manually" option and fill out the required fields before hitting create.
Let's set up a versioning system for our build. I am partial to Semantic Versioning so I will be basing it off that. In your Build Configuration, navigate to the "General Settings" page. Here you will want to enter 1.0.0.%build.counter% in the "Build number format" field. This establishes a semantic version number with a hanging auto-incrementing counter off the end. It will be up to you to version forward the "semantic" portion of the version, but the "counter" will increment on ever build.
If you setup GitHub to trigger you builds, you will not need to do this step.
For our purposes, we are going to create a "VCS root" and "Trigger" to reach out to our repository and detect changes. In your Build Configuration, navigate to the "VCS Roots" page and hit "Create VCS root." Fill out all required information including any branch targeting you would like to do. I am leaving mine so it polls the "master" branch. I do this because I consider my "master" to be deployable at all time. Any important code only reaches my master branch when it has gone through a proper pull request.
Once the "VCS root" has been created, head over to your "Version Control Settings" and attach it to your Build Configuration.
In the "Triggers" section, "Add New Trigger" and select the "VCS Trigger" type.
The new "Trigger" will now add a build to the queue when a VCS check-in is detected.
If you have not noticed yet, three of our four steps in regards to setting up our Build Configuration directly map to commands we run against the dotnet cli: restore, build and publish. Thankfully the team at Jet Brains has created a plugin to facilitate execution of these commands.
Installing "The" Plugin & .NET Core SDK
- On the machine TeamCity is installed on, head over to the TeamCity plugin gallery and download the plugin.
- Copy the plugin zip package to your \<team city directory>\plugins directory.
- Restart your TeamCity service(s)
Install the latest .NET Core SDK. You have one of two options...
- Head to the official download site and get the installer from there.
Or, do it like a modern dev and run the following chocolatey command.
choco install dotnetcore-sdk -y
Build Configuration Steps
Now that we have the necessary plugin and SDK installed, we can create our individual build steps.
Head to your Build Configuration and under "Build Steps", select "Add Build Step". You will be given a choice of "Runner Type(s)". Select the ".NET Core (dotnet)" runner that we just installed with the plugin.
You will be presented with a list of configuration options. The values of which will be determined by your goals. For my setup I added a "Step Name", changed the "Command" to "restore" and targeted my solution file in the "Projects" section.
Technically this step is not needed if all your interested in doing is creating a build. The next step will actually accomplish that for you. That being said, this step is needed if you plan on introducing things like Unit Testing into your build pipeline. With that in mind, best we just set it up now. After all, Unit Tests are great!
Again, head to the "Build Steps" and select "Add Build Step". This time we will configure it with the same "Runner Type" but switch the command to "build". We will also set the "Configuration" which to build under to "Release".
The last step in the process is to tell the dotnet cli to "publish" our build to the file system. Just like we did previously, we again "Add Build Step" and configure it to the same "Runner Type". This time we switch the "command" to publish and set the "Configuration" which to build under to "Release".
Now that we have setup our build steps and the dotnet cli is in theory publishing assets to the file system, we need a way to tell TeamCity to take the published assets and make them available as an asset inside TeamCity. If we forgo this step, we will not have assets that we can access at the end of our build pipeline. That being said, lets head to the "General Settings" section of our Build Configuration and add publish => your.name.%build.number% to the "Artifact paths" value.
By doing this, we should now be able to access the assets from our build portal.
Off We Go
Setup of TeamCity and its necessary configuration should be complete at this point. Go ahead and check some code into your "master branch" on GitHub, watch your TeamCity portal to see the Build Agent recognize the change and then view your assets when your Build completes successfully. If all went well, you should end up with something like this.
And there you have it. We just went from a non-automated build that was not traceable, to a fully automated build that is properly versioned making it traceable and more important predictable. Next up we will start tackling the automation of configuration and deployment by installing and configuring Octopus Deploy. Until then, feel free to leave any comments below.Deploying An ASP.NET Core Site, You're Still Doing It Wrong : Part 3 - Octopus Deploy: Installation And Targets