Hey there! If you haven't checked out the previous post from this series, I highly recommend it to get a bigger picture.
Let's say that you and your team are developing the most awesome app from all times... it's Friday 5 PM, and one of your coworkers is going to miss a very important heavy metal show if he stays one more minute at the office, so he commits his work and pushes to master.
Then... next Monday at 9 AM your boss enters the office jelling, WHO BROKE IT! everyone starts looking at the commit history, one by one, reverting, resetting, compiling different versions from the branches, after 4 exhausting hours they found out... it was the heavy metal guy, who pushed to master without even compiling his code that Friday.
This is wrong in so many ways, just to point a few, first, why the heck your boss is an angry panda? now... seriously, you should never commit something anywhere without even testing locally on your machine. If it doesn't work on your machine don't expect to magically work somewhere else.
Nobody should be able to push directly to master without passing some kind of test and, reviews and final approvement, you should have a branching strategy that is clear and known by all team members.
But we can do this better and in fact, be able to deliver our code faster and safer.
In Sam Guckenheimer words
Continuous Integration (CI) is the process of automating the build and testing of code every time a team member commits changes to version control. CI encourages developers to share their code and unit tests by merging their changes into a shared version control repository after every small task completion. Committing code triggers an automated build system to grab the latest code from the shared repository and to build, test, and validate the full master branch (also known as the trunk or main).
So the whole idea of having Continuous Integration is that you're always compiling your app as your Code Repository changes, so everyone knows if something fails, you can figure out which change broke the code, who pushed the changes, find the error and solve it faster.
Adopting DevOps practices won't avoid failure, you got it wrong if you thought that by adopting DevOps practices nothing is going to go wrong, instead... you're more common to be failing more than before, but you'll fail FASTER and you learn faster too.
As we have discussed before, there are several colors and flavors of tools and products made to help you adopting DevOps practices such as Continuous Integration, but we will be using Azure DevOps, which is what I like the most.
Azure DevOps is Microsoft's collection of Services to take your ideas into production. It has everything you need to plan and organize your work with Azure Boards, a single source for all your repositories in Azure Repos, a great CI/CD service Azure Pipelines,
You don't have to use all of this, you can pick whatever service you may need, if you already have your source code somewhere else like Gitlab, you don't have to use Azure Repos, or maybe you or your company already invested in a CI/CD automation technology, again you don't have to use Azure Pipelines. Just pick what you need, they work great by themselves, but when you use altogether, you can have the best traceability of your code, you can find a bug and solve it in record time.
We are going to create a CI pipeline for an application built upon ASP.NET and React, every time we make a change to our code repo, we're going to build a Docker image.
- Don't have one yet? create an Azure subscription for free. Get credits that can be used to try paid Azure services. Once you consume all your credits you can keep the account and use all free Azure services and features.
- If you are Visual Studio subscriber, claim your benefits. Visual Studio subscription gives access to monthly credits that you can use on Azure services.
- Are you student? Create an Azure Student Account and get free credits.
First, we need some code and a repository to work with, I'll be using GitHub as my code repository to show you that you don't have to use Azure Repos if you already have an existing repository.
Make sure to fork the repo to follow along
Tailwind Traders Website
You can take a look at our live running website following this address: https://tailwindtraders.com
For this demo reference, we built several consumer and line-of-business applications and a set of backend services. You can find all repositories in the following locations:
- Tailwind Traders
- Backend (AKS)
- Website (ASP.NET & React)
- Desktop (WinForms & WPF -.NET Core)
- Rewards (ASP.NET Framework)
- Mobile (Xamarin Forms 4.0)
Deploy to Azure
With the following ARM template you can automate the creation of the resources for this website.
When you deploy this website to Azure you can define the Backend you want to use in case you have deploy your own backend. By defaults it is configured the public Backend environment provided by Microsoft.
Note: you can change the InstrumentationKey of the Application Insight that is configured by default.
If you want to update the application to use your own backend, set
You could just go to Azure Pipelines and create a Build Pipeline in dev.azure.com but today we will start from GitHub to show this great experience.
Navigate to GitHub's Marketplace and search for Azure Pipelines (not Actions, we will talk about it in next posts)
Then install the extension and grant it access to your existing repository.
Now we need to assign an Organization and project if you don't have any you can totally create them at this step. Keep in mind that Organizations are just groupings of Projects, you can have many projects inside an organization.
Note that Azure Pipelines already knows that we want to integrate our repository from GitHub, this is because we used the extension from GitHub's Marketplace, so select your repo.
It is time to create our build definition! Azure DevOps by default offers a lot of different Templates ready to use so we can have the option to not start from scratch, and be more productive.
Select or search the Docker Template
It will ask you to specify your app's Dockerfile path, but what is a Dockerfile, to put it simply, a Dockerfile is a like a cookbook recipe instruction, where it specifies each step in order to compile your application. Our project already has a Dockerfile, that specifies tasks like copying directories, and running commands such as npm and dotnet, it also specifies which Docker image to use, in this case, Docker Alpine.
FROM node:10-alpine as build-node WORKDIR /ClientApp COPY ClientApp/package.json . COPY ClientApp/package-lock.json . RUN npm install COPY ClientApp/ . RUN npm run build FROM microsoft/dotnet:2.2.100-preview3-sdk-stretch as build-net ENV BuildingDocker true ENV ASPNETCORE_ENVIRONMENT=Development WORKDIR /app COPY *.csproj . RUN dotnet restore COPY . . RUN dotnet build RUN dotnet publish -o /ttweb FROM microsoft/dotnet:2.1-aspnetcore-runtime as runtime WORKDIR /web COPY --from=build-net /ttweb . COPY --from=build-node /ClientApp/build ./ClientApp/build ENTRYPOINT [ "dotnet","Tailwind.Traders.Web.dll" ]
for our project the path is Source/Tailwind.Traders.Web/Dockerfile
Click Validate and configure and it will generate the following YAML definition for your Build Pipeline, it contains every step that your CI is going to execute in order to compile your application, in our case we just need one task, Docker build, specifying our Dockerfile path.
Your YAML code should look like this
# Docker # Build a Docker image # https://docs.microsoft.com/azure/devops/pipelines/languages/docker trigger: - master resources: - repo: self variables: tag: '$(Build.BuildId)' stages: - stage: Build displayName: Build image jobs: - job: Build displayName: Build pool: vmImage: 'ubuntu-latest' steps: - task: Docker@2 displayName: Build an image inputs: command: build dockerfile: 'Source/Tailwind.Traders.Web/Dockerfile' tags: | $(tag)
Once we click save, it will ask if you want to directly commit to master branch or create a Pull Request, I don't mind pushing code to master in my own demo, but if you're working on a real project, go should for the Pull Request 😁
It will keep your Build Pipeline together with your application code, meaning, this has some cool advantages, for example, you can make Code Review over the Build Pipeline if someone modifies it, and ask questions about it. Another reason is that the code and how to compile the code will live together. If someone new is onboarding your project he won't have any problems trying to get it working.
Once you hit Save it will start working on your pipeline tasks, such as preparing the job, downloading the source code and compiling with our Yaml definition. You can now go and prepare some coffee, or enjoy the view looking at the output window.
If everything goes as expected, when you come back you'll get a nice overview of every task marked with a green ticket ✔, and a pretty email in your inbox saying that the [Build succeeded] yay! 🙌
Congratulations! 🎉 you just Setup Continuous Integration for your project! So what would happen now? For every committed change coming to our master branch, there will be a new Build triggered, ensuring that what we're sending to our repo is not breaking our app.
Go to your Build Pipeline and click on the three dots button, you'll see an option called Status badge, it's an indicator that you can show off on your project readme file to let everyone know about the Build status.
Copy the Sample Markdown and paste it on your README.md file, commit that change to your repository and see what happens on your Build Pipeline.
Voilà! Now you have Continuous Integration for your GitHub project!
We started talking about the problems that come with not having Continuous Integration for our projects, we covered some concepts behind CI, and why it's important on DevOps.
I introduced to you Azure DevOps, we just moved around in one specific service, Azure Pipelines, and to be more specific, Build Pipelines which is our CI.
We took an existing application written in ASP.NET and React, and built a simple Pipeline using the Docker Template in just minutes.
Finally, after setting up our CI, we tested that seconds after committing new code on GitHub it automatically triggered a new Build on Azure DevOps, completing our CI cycle.
This is just a simple demo, this is not a production-ready CI by no means, but I hope it helps you to get started.
In the next episode, we will cover how to take what we just compiled, and deliver it to Azure through a Release Pipeline 🚀☁ as we deep dive into Continuous Delivery, stay tuned!