Napoleon 'Ike' Jones
Posted on February 2, 2020
Deploying a .NET Core App to AWS Elastic Beanstalk via Azure DevOps
Introduction
In another life and my two previous roles I evangalized Azure DevOps relentlessly. In one position we had nothing in terms of a tooling outside of a very outdated implementation of SubVersion. In the other postion, add outdated versions of Jira and Jenkins to the mix. At least in this instance, Jenkins and SubVersion were somewhat connected, however, Jira was left out in the cold.
Anytime I would demo Azure DevOps I faced the same questions and criticisms.
- “But it won’t work with [insert non-Microsoft language], will it?”
- “It’s Microsoft, it will never work outside of their ecosystem!”
- “We want to host our applications with Amazon, this won’t work here.”
Those were the comments on the friendlier side of things. At times, they were just plain unprofessional (to put it nicely). But as @DonovanBrown, “This ain’t your daddy’s Microsoft.”
In this post I will show you how easy it is to deploy a .NET Core application to AWS Elastic Beanstalk.
Setting Up the AWS Environment
To begin, you will need to set up an AWS Elastic Beanstalk environment. Before writing this post, I had never touched AWS. The process is fairly straight forward.
Login to the AWS Management Console and then search for “Elastic Beanstalk”
Click the Create New Application button towards the upper right-hand corner. The below modal will pop up. Enter a name for your application in the Application name field and click the Create button.
This takes us to the Base configuration section. I selected .NET (Windows/IIS) for the Platform, however you could run .NET Core most of the Linux offerings. For Application code, choose Sample application as we will deploy our actual code with Azure DevOps. Once you have set the base configuration click the Create application button.
This will kickoff the creation of our Elastic Beanstalk environment.
Once the creation is complete it will redirect you to the Elastic Beanstalk dashboard shown below. You can view the application by clicking the URL at the top of the page.
Congratulations, you have just created a deault ASP.NET Web App.
### Creating an AWS IAM Account
Next, you will create an account that you can use to deploy your application from Azure DevOps. Click the dropdown menu associated with your account name and then select My Security Credentials.
A warning modal will popup, select Get Started with IAM Users.
Next, you will create a security group. Enter “AzureDevOps” for the Group Name. Then click Next Step.
This brings you to the Attach Policy page. Enter “AWSElasticBeanstalk” into the search box to filter through the policies. Then select AWSElasticBeanstalkFullAccess. Click Attach Policy to continue.
Review the settings and click Create Group.
This brings you to the Add user screen. Enter a User name. I used the name “AzureDevOps” for this example. For Acces type select Programmatic access. Then click Next: Permissions.
Next we need to set the permissions on the account. We do this by adding the users to groups with policies attached to them. We will place this account in the AzureDevOps group we previously created. Click Next: Tags.
This will bring us to the Tags page. In this example I did not add any tags. Click Next: Review.
Review your account settings and if they are correct click Create user.
Congratulations, you have successfully create a new user account. Next copy the Access key ID and Secret access key as we will be storing them in Azure Key Vault.
### Creating Azure DevOps Service Connection for AWS
The next step is to create a new Service Connection within Azure DevOps for AWS. This connection will use the account we created in the previous step.
First, go to the Projects Settings by clicking the Settings button towards the bottom of the left hand menu.
Then scroll down to Pipelines and select Service connections.
Next, click Create service connection
Fill in the Access Key ID and Secret Access Key from the IAM User you created in AWS.
Enter a Service connection name and check Grant access permission to all pipelines. Then click Save.
You have now created a new service connection.
In this post we will deploy a fairly simple .NET Core Application. Create a new .NET Core MVC Application and make the following changes.
In the Index.chtml file replace the existing code with the following.
@{
ViewData["Title"] = "Home Page";
}
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>This demo is running .NET Core on @ViewData["CloudEnv"].</p>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
</div>
Then in the HomeController add an IConfiguration property and add it to the constructor.
private readonly ILogger<HomeController> _logger;
private readonly IConfiguration _configuration;
public HomeController(ILogger<HomeController> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}
Replace the Index method of the HomeController with the following code.
```
public IActionResult Index()
{
ViewData["CloudEnv"] = _configuration["CloudEnv"];
return View();
}Add the setting **CloudEnv** to appsettings.json. Not the prefix and postfix “\_\_” in the CloudEnv variable. We are tokenizing this variable so that the value can be replaced with a pipeline variable later on. ```<pre class="wp-block-preformatted">{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "CloudEnv": "__CloudEnv__" }
Afterwards, add the CloudEnv setting to appsettings.Development.json
```
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"CloudEnv": "my local machine"
}That’s it for the application. Now on to the pipeline. ### Creating the Multi-Stage Pipeline In this example we will create a multi-stage pipeline using YAML. The first stage will build the pipeline and the second will deploy it to AWS. ### Marketplace Extensions The multi-stage pipeline we are creating requires two [Marketplace Extensions](https://docs.microsoft.com/en-us/azure/devops/marketplace-extensibility/?view=azure-devops). Install the following extensions into your project. Consult [this reference guide](https://docs.microsoft.com/en-us/azure/devops/marketplace/install-extension?view=azure-devops&tabs=browser) if you are having issues installing the extensions. - [Replace Tokens](https://marketplace.visualstudio.com/items?itemName=qetza.replacetokens) - [AWS Tools for Microsoft Visual Studio Team Services](https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.aws-vsts-tools) ### Creating the Initial Pipeline First, we will create the basis for the pipeline. In Azure DevOps select the **“Rocket Ship”** from the lefthand menu, then select **Pipelines**. ![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/01-Go-to-Pipelines.png?w=843&ssl=1)Next, select **New pipeline** ![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/02-Select-New-Pipeline.png?fit=843%2C81&ssl=1)Then select where your code is located. For this example my code is stored in Azure Repos Git. However, your code could be in GitHub or any other source control tool. ![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/03-Select-Code-Location.png?w=843&ssl=1)Afterwards, select your repository. ![](https://i2.wp.com/ikethe.dev/wp-content/uploads/2020/01/04-Select-Repository.png?w=843&ssl=1)Then select **Starter pipeline**. This will create a basic pipeline with a couple of tasks. ![](https://i1.wp.com/ikethe.dev/wp-content/uploads/2020/01/05-Select-Starter-Pipeline.png?w=843&ssl=1)### Modifying the YAML Before we begin, I would like to warn you that YAML can be very fickle and whitespaces matter. One misaligned space can cause the entire pipeline to fail. To learn more about YAML pipelines [this reference guide](https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema#pipeline-structure) is an excellent resource. ### Initial Pipeline The initial portion of our pipeline defines the trigger, which is defined as the master branch of our repository. Anytime master changes, our pipeline will automatically run. ```<pre class="wp-block-preformatted">trigger: - master
Stage 1 – Build
The first stage of our pipeline downloads any dependencies and then builds the solution. It then creates then publishes the artifact that will be used in the second stage.
```
stages:
-
stage: 'Build'
variables:
solution: '*/.sln'
buildPlatform: 'Any CPU'
buildConfiguration: 'Release'
displayName: 'Build'
jobs:- job: 'Build' displayName: 'Build' pool: vmImage: 'windows-latest' steps:
- task: NuGetToolInstaller@1
- task: NuGetCommand@2 inputs: restoreSolution: '$(solution)'
- task: VSBuild@1 inputs: solution: '$(solution)' msbuildArgs: '/p:SkipInvalidConfigurations=true /p:DeployOnBuild=true /p:WebPublishMethod=FileSystem /p:publishUrl="$(build.artifactstagingdirectory)\" /p:DeployDefaultTarget=WebPublish' platform: '$(buildPlatform)' configuration: '$(buildConfiguration)'
- task: VSTest@2 inputs: platform: '$(buildPlatform)' configuration: '$(buildConfiguration)'
- task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'drop' publishLocation: 'Container' ```
Important: When deploying a .NET Core App to AWS Elastic Beanstalk you cannot use the default msbuildArgs. For some reason Elastic Beanstalk cannot find the web.config file when the artifact is published as a zip file. Please note the WebPublishMethod=FileSystem argument.
Stage 2 – Deploying to AWS
The second stage of the pipeline deploys the artifact from the first stage to AWS.
```
- stage: 'Deploy'
displayName: 'Deploy'
jobs:
- deployment: DeployToAws
displayName: 'Deploy To AWS Elastic Beanstalk'
pool:
vmImage: 'windows-latest'
variables:
CloudEnv: 'AWS Elastic Beanstalk'
environment: 'Production'
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: drop
- task: replacetokens@3
displayName: 'Replace Tokens'
inputs:
rootDirectory: '$(Pipeline.Workspace)/drop'
targetFiles: '*/.json'
tokenPrefix: ''
tokenSuffix: ''- task: BeanstalkDeployApplication@1
displayName: 'Deploy to Elastic Beanstalk'
inputs:
awsCredentials: 'AWS Deployment Service Connection'
regionName: 'us-east-2'
applicationName: DotNetCoreDemo
environmentName: 'Dotnetcoredemo-env'
applicationType: aspnetCoreWindows
dotnetPublishPath: '$(Pipeline.Workspace)/drop'Notice in the deployment job we are setting a job level variable **CloudEnv**. We will use variable in a later task to replace our tokenized appsetting from earlier. ```<pre class="wp-block-preformatted"> - deployment: DeployToAws displayName: 'Deploy To AWS Elastic Beanstalk' pool: vmImage: 'windows-latest' variables: CloudEnv: 'AWS Elastic Beanstalk'
Let’s break down the tasks in this stage of the pipeline.
The first task, download, downloads the artifact we created in the previous build stage.
```
- download: current
artifact: dropThe next task, **replacetokens@3**, is the first Marketplace Extension we installed earlier. This tasks looks for any string with a prefix and suffix of “\_\_” and attempts to replace them with a variable defined in the pipeline. In this example, “AWS Elastic Beanstalk” replaces the value for CloudEnv in appsettings.json ```<pre class="wp-block-preformatted"> - task: replacetokens@3 displayName: 'Replace Tokens' inputs: rootDirectory: '$(Pipeline.Workspace)/drop' targetFiles: '**/*.json' tokenPrefix: '__' tokenSuffix: '__'
Important: The variable name must match the string without the prefix and suffix.
The final task, BeanstalkDeployApplication@1, is the second Marketplace Extension we installed earlier. This task deploys the artifact to AWS Elastic Beanstalk. Pay close attention to the inputs. The awsCredentials argument is the name of the service connection we created earlier. regionName, applicationName, and environmentName should match with the Elastic Beanstalk environment and application we originally created. The last setting, dotnetPublishPath, is the location of the artifact we downloaded.
```
- task: BeanstalkDeployApplication@1
displayName: 'Deploy to Elastic Beanstalk'
inputs:
awsCredentials: 'AWS Deployment Service Connection'
regionName: 'us-east-2'
applicationName: DotNetCoreDemo
environmentName: 'Dotnetcoredemo-env'
applicationType: aspnetCoreWindows
dotnetPublishPath: '$(Pipeline.Workspace)/drop'### The Entire Pipeline Below is the complete multi-stage pipeline. ```<pre class="wp-block-preformatted"> trigger: - master stages: - stage: 'Build' variables: solution: '**/*.sln' buildPlatform: 'Any CPU' buildConfiguration: 'Release' displayName: 'Build' jobs: - job: 'Build' displayName: 'Build' pool: vmImage: 'windows-latest' steps: - task: NuGetToolInstaller@1 - task: NuGetCommand@2 inputs: restoreSolution: '$(solution)' - task: VSBuild@1 inputs: solution: '$(solution)' msbuildArgs: '/p:SkipInvalidConfigurations=true /p:DeployOnBuild=true /p:WebPublishMethod=FileSystem /p:publishUrl="$(build.artifactstagingdirectory)\\" /p:DeployDefaultTarget=WebPublish' platform: '$(buildPlatform)' configuration: '$(buildConfiguration)' - task: VSTest@2 inputs: platform: '$(buildPlatform)' configuration: '$(buildConfiguration)' - task: PublishBuildArtifacts@1 inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'drop' publishLocation: 'Container' - stage: 'Deploy' displayName: 'Deploy' jobs: - deployment: DeployToAws displayName: 'Deploy To AWS Elastic Beanstalk' pool: vmImage: 'windows-latest' variables: CloudEnv: 'AWS Elastic Beanstalk' environment: 'Production' strategy: runOnce: deploy: steps: - download: current artifact: drop - task: replacetokens@3 displayName: 'Replace Tokens' inputs: rootDirectory: '$(Pipeline.Workspace)/drop' targetFiles: '**/*.json' tokenPrefix: '__' tokenSuffix: '__' - task: BeanstalkDeployApplication@1 displayName: 'Deploy to Elastic Beanstalk' inputs: awsCredentials: 'AWS Deployment Service Connection' regionName: 'us-east-2' applicationName: DotNetCoreDemo environmentName: 'Dotnetcoredemo-env' applicationType: aspnetCoreWindows dotnetPublishPath: '$(Pipeline.Workspace)/drop'
At this point we can run the pipeline and it will deploy our application to our AWS Elastic Beanstalk environment.
Successful Deployment
As you can tell by the below screenshot the deployment was successful and the CloudEnv appsetting was replaced with the pipeline variable.
Posted on February 2, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.