4

Current Setup


We currently deploy our ASP.NET Core products to:

  • Windows Server 2016
  • Hosted on IIS Version 10.0.14393.0

We have a single "Site" with multiple applications under it. Each application uses its own application pool and is mapped to a different physical folder path location.

Each application is represented by its own source code ASP.NET Core Server project

IIS Structure

IIS Structure of child Apps in Site

The Problem


We use Jenkins for our CI/CD, and this cannot be changed. We have Powershell scripts to interact with IIS

There is no IIS command to stop an Application, only a Site, but we ideally do not want to bring down the site when publishing a new version of a single Application. We only want to bring down that specific Application.

We attempted this by stopping the WebAppPool associated with the application, and then waiting (2 minutes .. then 5 minutes). But even after that the application files are still locked.

We end up shutting down the WebAppPool and the Site to release the files so they can be replaced.

I know there has to be a better way to do this. How can we shut down an individual app, and prevent new requests from reaching it so we can replace the files and then restart it? All while the "SITE" and any other App under it not being updated is still running. I don't mind down time for the App being updated.

Update 6/8/2022

Found this: https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/app-offline?view=aspnetcore-6.0

Going to see if this can solve my problem.

Michael Thrift
  • 133
  • 1
  • 7
  • This isn't a trivial problem. From [the docs](https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/?view=aspnetcore-6.0#overlapped-recycle): *In general, we recommend using a pattern like [blue-green deployments](https://www.martinfowler.com/bliki/BlueGreenDeployment.html) for zero-downtime deployments. Features like Overlapped Recycle help, but don't guarantee that you can do a zero-downtime deployment.* This means deploying to a new site or machine and redirecting new requests to the new site. When the old requests are completed, kill the old site. – Panagiotis Kanavos Jun 08 '22 at 15:38
  • 1
    One way to do this is to use a load balancer to redirect traffic. Another way is to create a new web app and use ARP to redirect calls to it. It's not trivial. You can find articles that describe how to do blue-green deployments with Jenkins. Some will use load balancers, others will use ARP – Panagiotis Kanavos Jun 08 '22 at 15:42
  • This is an enterprise product and an on-premise server. A lot of ways to do this better, but I'm limited in what can be done from a platform perspective. I can affect is the scripts that interact with IIS. I don't mind the application having down time ... just not the whole site. And when i say "site" i mean the IIS "Site" node. So 5 applications currently running as children of the site. One goes down for an update, the other 4 should stay running. So i need a way to not stop the site, but still prevent requests being directed to the asp.net app that needs to be replaced. – Michael Thrift Jun 08 '22 at 16:10
  • i made an edit to the question that hopefully clarifies the ASK. – Michael Thrift Jun 08 '22 at 16:16

4 Answers4

6

Adding a file named app_offline.htm in the root folder of your application should force ASP.NET Core Module to shutdown the app and stop processing incoming requests. You should then be able to deploy new file with no impact on the the other applications.

Nicolas Boisvert
  • 1,141
  • 2
  • 10
  • 26
  • 1
    I found the msdn doc for this and tried it out. Worked perfectly. https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/app-offline?view=aspnetcore-6.0 – Michael Thrift Jun 09 '22 at 00:38
1

I highly recommend using web deploy with IIS. There are a couple of steps to set it up, but once configured it really works great.

If you publish this way, it will automatically do the following:

  • Continue to accept incoming requests, but pause them
  • Stop the old version of the application gracefully
  • Recycle the application pool
  • Start the new version of the application
  • Paused requests will be resumed to the new version of the application

This solution enables you to deploy new versions with no perceivable interruption to your service while other solutions may render your service unreachable during publish.

I have only used the publish menu from Visual Studio but it should be possible to make it work from a CI/CD script calling msbuild.exe as described here. (link descibes soultion for TeamCity but should be possible to adapt to Jenkins)

Oskar Sjöberg
  • 2,728
  • 27
  • 31
  • > Continue to accept incoming requests, but pause them Do you know how to set that up? I have a 'take offline' flag set to true, so perhaps that's giving me issues, but with a pretty much default config i keep seeing the under construction page instead of IIS pausing an retrying requests when deploying. – sommmen Mar 23 '23 at 15:03
  • It seems like the take app offline flag will give the result you just described. – Oskar Sjöberg Mar 23 '23 at 16:47
  • In that case it would return the html_offline file, i was hoping it would buffer the requests and resend them when my app would become online again. – sommmen Mar 25 '23 at 08:33
  • Sorry for the late reply. I use web deploy for a handful of projects and it works as described above. As far as I know this is the default behaviour. Maybe try to setup a new project and try to deploy it to see if this fixes the issue. – Oskar Sjöberg Jun 28 '23 at 22:06
1

Here's how I do it using a blue/green strategy:

First, I created this folder structure on the server:

D:\inetpub\wwwroot\MyAppPath\
  Staging
  Green
  Blue

Next, in IIS, I pointed my application's physical path to D:\inetpub\wwwroot\MyAppPath\Green

Then I created D:\inetpub\wwwroot\MyAppPath\SetAppPath.ps1

($curPath = Get-WebFilePath -PSPath "IIS:\Sites\www.example.com\MyApp")
if ($curPath -like "*Blue*") {
    Copy-Item -Path "D:\inetpub\wwwroot\MyAppPath\Staging\*" -Destination "D:\inetpub\wwwroot\MyAppPath\Green" -Recurse -Force
    Set-ItemProperty IIS:\Sites\www.example.com\MyApp -name physicalPath -value "D:\inetpub\wwwroot\MyAppPath\Green"
} else {
    Copy-Item -Path "D:\inetpub\wwwroot\MyAppPath\Staging\*" -Destination "D:\inetpub\wwwroot\MyAppPath\Blue" -Recurse -Force
    Set-ItemProperty IIS:\Sites\www.example.com\MyApp -name physicalPath -value "D:\inetpub\wwwroot\MyAppPath\Blue"
}

Then, I created a Task (SetMyAppPath) without triggers that runs SetAppPath.ps1

On my development workstation, I created deploy.ps1 in the project root:

param ($buildType)
net use r: /delete
dotnet publish --configuration $buildType
net use R: "\\111.222.333.444\d`$" /user:"MyAdminUser" "MyAdminPassword"
Copy-Item -Path "C:\Users\MyUser\Dir1\Dir2\Dir3\MyProject\bin\$buildType\netcoreapp3.1\publish\*"  -Exclude web.config,appsettings.json,appsettings.Development.json -Destination "R:\inetpub\wwwroot\MyAppPath\Staging" -Recurse -Force
schtasks /RUN /S \\111.222.333.444 /U MyAdminUser /P MyAdminPassword /tn SetMyAppPath
net use r: /delete

Then, to deploy, I run .\Deploy Debug or .\Deploy Release

So, when I have a new version I'm ready to deploy, the files are copied to the staging folder on the server. Then, the task on the server is triggered, which copies the staging files to the green or blue folder, which ever is not the current folder.

Finally, the app path is updated to the green or blue folder, which ever is not the current folder.

Works like a charm.

Chris
  • 650
  • 7
  • 20
0

I've previously had success with pointing the Site in IIS to a symlink. The symlink then in turn pointed to a folder that held the contents of the webpage.

Whenever I had to update the webpage, I would then create a new folder, lets call it webpage_date, then update the symlink and point it to the new folder. Lastly I recycled the AppPool that was responsible for the webpage.

So it would go:

symlink -> webpage_1

Put folder with updated webpage next to it, call it webpage_2. Point symlink to it:

symlink -> webpage_2

Then recycle AppPool.

This made it possible to have a pretty low downtime.

Powershell command to Restart-WebAppPool:

Restart-WebAppPool -Name "YourAppPool"

Restart-WebAppPool documentation

Symlinks can be created with

New-Item -ItemType SymbolicLink -Path .\link -Target .\Notice.txt

Powershell New-Item Symlink documentation