Seamless Replatforming of ASP.NET Apps to Cloud Foundry

Replatform ASP.NET apps with zero to minimal code changes to any Cloud Foundry based platform

Posted by Alfus Jaganathan on Friday, November 15, 2019

Also published at: www.initpals.com

Introduction

This article explains, how to move your ASP.NET workloads to Pivotal Platform (PAS)/CloudFoundry with very minimalistic efforts using few extension buildpacks and/or Nuget packages. Before getting deeper into it, let’s quickly refresh on Pivotal platform (PAS) and cloud native applications

Pivotal platform (PAS)

Pivotal platform (PAS) is a unified, multi-cloud product to run your enterprise apps where as PAS (Pivotal Application Service, earlier called as PCF or Pivotal Cloud Foundry) is a Cloud Foundry based, modern runtime for .NET, .NET Core, Java, Node and a lot other technologies.

Cloud Native Applications

An application can be called as cloud native, if the application adheres to cloud native 15 factors which originally used to be 12 factors. Here I will quickly summarize them (based on Beyond The Twelve Factor App book published by Kevin Hoffman), so as to use it as a quick reference. However, to get more in-depth understanding, please read through 12 Factors and e-book Beyond The Twelve Factor App.

15 Factors Summary

  1. One codebase, one application
    • Single codebase per deliverable
    • Any number of immutable releases
  2. API first
    • Contract first development
    • Use Server mocks (e.g. Apiary)
  3. Dependency management
    • Isolate dependencies as NuGet packages
    • Bundle dependencies together with build artifacts, meaning Self contained
  4. Design, build, release, and run
    • Must have CI/CD Pipeline
    • Design & build together will be an iterative process
  5. Configuration, credentials, and code
    • Code – Single artifact for multi environment, meaning the produced artifact should be never modified for environments
    • Treat configuration as environment variables
    • Externalize configuration and credentials
  6. Logs
    • Treat as event streams which are sequence of time-ordered emitted events
    • Emit them as STDOUT and STDERR only
  7. Disposability
    • Fast startup & graceful shutdown
    • Externalize states using backing services, for e.g externalize data caching
    • Maximize robustness of the application
  8. Backing Services
    • Treat as attached resources, which gives loose coupling and flexibility
    • E.g. data store, caching, side cars, etc.
  9. Environment Parity
    • Keep all environments including development as similar as possible
    • Every commit to be a deployment candidate which will work everywhere
  10. Administrative Processes
    • Run admin/management tasks as one-off, scheduled processes
  11. Port Binding
    • Avoid micromanaging port assignments
    • Allow externalized runtime port binding
  12. Stateless Processes
    • All long-lasting state to be externalized using backing services (e.g. session)
    • Share-Nothing (containers are highly disposable)
  13. Concurrency
    • Scale out horizontally rather than vertical scaling
    • Should be able to run multiple concurrent instances simultaneously
  14. Telemetry
    • Application Performance Monitoring(APM) – Wellness
    • Domain specific telemetry– Analytics & Reporting
    • Health & System logs – Stream of events
  15. Authentication & Authorization
    • Security should be never be an afterthought
    • Should be backed in from day one

Hope the above gives you just enough information on cloud native factors. At this point you may also wonder, how are we going to make our existing applications adhere to all these 15 factors. No worries! You can think of all these on green field or modernization effort. Here we are going to focus on getting our existing ASP.NET application to cloud, so we will go with a minimalistic approach where we will figure out the critical of these 15 factors which can help us move the application to cloud, in other words we will use the just enough of these and apply them to the application. This makes our application to be cloud ready rather than cloud native. Cloud readiness will give you most of the platform provided benefits such as zero downtime or high availability, horizontal scaling w/ auto scaling, backing services, environment parity, port binding, load balancing, logs drain and telemetry.

I am going to pick the below as critical/minimal factors (this is up to my point of view)

  1. Configuration, Credentials & Code
  2. Logs
  3. Stateless Processes
  4. Concurrency
  5. Disposability
  6. Dependency Management
  7. Telemetry (could be optional)
  8. Authentication & Authorization

Let’s look into these one by one and see how we can easily apply to our existing application. There are few Extension Buildpacks and NuGet packages which helps you in easing the process. So, before heading down, let’s see something about buildpacks.

Buildpacks

If you are not familiar with cloud foundry buildpacks, please refer to cf buildpacks. In a high level, buildpacks provide framework and runtime support for the apps, also does compile or prepare the application to run in the platform. Buildpack execution happens during the staging process during the cf push life cycle. We can also classify them into 2 broader categories, System Buildpack(provides framework and runtime support) and Supply or Extension Buildpacks(optional, which provides any custom feature to the application).

By now, you should have enough idea on what a buildpack is all about. Let’s continue on critical cloud native factors and implementations.

1. Configuration, Credentials & Code

Usually we store configurations for ASP.NET applications in web.config files. We use web config transformation (in most cases) or other methods to pick the right configuration for a particular environment. Here, we should note that, we are attaching those environmental configurations together with the publishing artifact which makes us fail in single artifact strategy. So, what the resolution? Let’s move all the environmental specific configurations from web.config file to environment variable and make our application to read from it. There are 2 ways you can encounter it, where first one is by using an extension buildpack and the second one, using a Nuget package.

(i) Using extension Buildpack

In this approach, we use the web config transformation buildpack, for more details, please refer to the article Externalize Configuration Using Extension Buildpack, which details you how to use this buildpack, based on the needs.

(ii) Using Nuget package

This as an alternative approach from buildpack where we use a Nuget package PivotalServices.AspNet.Bootstrap.Extensions.Cf.Configuration to externalize the configuration.

Info: To get into more details on the implementation, please refer here. This package also does placeholder replacement, which can be further used to pull user secrets from Credhub. For detailed instructions, please refer here

In a high level, below are the steps to follow

  • Upgrade your application framework 4.6.2 or above
  • Install the package from Nuget
  • Add code AppBuilder.Instance.AddDefaultConfigurations().AddConfigServer().Build().Start() in App_Start method of Global.ascx
  • Move your appSettings to environment variables or any other external sources, with variable names like AppSettings:key for AppSettings and ConnectionStrings:name for connectionStrings
  • Create the cf app manifest and push the application using cf push to see that the System.Configuration object is modified runtime during the application start, so there won’t be a need for code change where you are already using code like ConfigurationManager.AppSettings["bar"] or ConfigurationManager.ConnectionStrings["foo"].ConnectionString

2. Logs

In general we used to write log information in files and/or windows events. But, as per the cloud native factor guidelines, we should treat them as stream of ordered events which has be only STDOUT and STDERR. The simple way to achieve is to just write to console using System.Console.Writeline("log message") or System.Console.Error.Writeline("error message") or if you are using any logging frameworks like log4net, you can simply add a ConsoleAppender.

If you think about next level, of using structured logging and distributed tracing, you can very well use the package PivotalServices.AspNet.Bootstrap.Extensions.Cf.Logging. This package uses Serilog for structured logging, Steeltoe for distributed tracing.

Info: To get into more details on the implementation, please refer here. You can refer to my previous article on distributed logging for getting more understanding on it.

Here are some high level steps to follow

  • Upgrade your application framework 4.6.2 or above
  • Install the package from Nuget
  • Add code AppBuilder.Instance.AddConsoleSerilogLogging().Build().Start() in App_Start method of Global.ascx
  • You will now get an object extension method, so that you can simply access the logger anywhere in the application code as this.Logger().LogError("error message"). There are other options as well which you can refer here
  • Create the cf app manifest and push the application using cf push to see nice and cool logs which looks something like..
2019-11-14T01:44:37.603-05:00 [APP/PROC/WEB/0] [OUT] [Info] [aspnet_mvc_webapi_sample,3ca1344d1165f26a18214fbe0fef8135,26ef26cf0d1e3252,,true] => RequestPath:http://aspnetmvcwebapisample.apps.pcfone.io:8080/api/config => AspNet4WebApi2.Controllers.ValuesController => Performing a GET operation now

3. Stateless Processes

As we learn from above, containers should share nothing, they are volatile. So we should not maintain any state within the system. The most common use case here is maintaining Session State and we will try to tackle this here. There are couple of ways you can externalize the session state to Redis, one using an extension buildpack and the other to be a Nuget package

(i) Using extension Buildpack

In this approach, we use the redis session extension buildpack, for more details, please refer to the article Externalize Session to Redis using Extension Buildpack, which details you how to use this buildpack.

(ii) Using Nuget package

This as an alternative approach from buildpack where we use a Nuget package PivotalServices.AspNet.Bootstrap.Extensions.Cf.Redis.Session to externalize session state to Redis instance. This package uses Steeltoe Connectors for automatically connecting to the bounded Redis instance.

Info: To get into more details on the implementation, please refer here.

Here are some high level steps to follow.

  • Upgrade your application framework 4.6.2 or above
  • Install the package from Nuget
  • Add code AppBuilder.Instance.PersistSessionToRedis().Build().Start() in App_Start method of Global.ascx
  • You may have to add the keys to the machineKey section in web.config, if it does not exist, which is a one time activity.
  • Create a Redis instance from marketplace
  • Create the cf app manifest, bind the Redis instance and push the application using cf push to see application should be able to connect to the bounded Redis instance automatically and persist session state there.

4. Concurrency & 5. Disposability

I would think that, the factors, stateless processes, concurrency and disposability have some sort of relation between each other. For e.g, if you have externalized all state info from the application (adheres to stateless processes), externalized any of the data caching (adheres to disposability) and do not access local filesystem (adheres to disposability) with quick startup and graceful shutdown, your application should be very well adhere to concurrency, means your application is good for horizontal scaling.

Let’s quickly see few simple options, which can help us in externalizing data caching and filesystem handling.

(i) Externalizing data caching

There are several way we can achieve it, this is one of which I like. Here you can make use of the Nuget package PivotalServices.AspNet.Bootstrap.Extensions which will also offer you with various cool features like dependency injection, dynamic http handlers, etc., which is a separate topic altogether.

Info: To get into more details on the implementation of dependency injection, dynamic http handlers, etc. using the package, please refer here or specifically here for DI and here for Handlers.

Here are some high level steps to follow.

  • Upgrade your application framework 4.6.2 or above
  • Install the base package from Nuget
  • Install the Steeltoe Connector package from Nuget
  • Add code in the like in App_Start method of Global.ascx, as below
AppBuilder.Instance
	.ConfigureServices((hostBuilder, services) =>
	{
	    services.AddRedisDistributedCache(hostBuilder.Configuration);
	})
	.Build()
	.Start();
  • Now you should be able to consume the implementation of IDistributedCache anywhere in the application code using constructor injection or using code var redisCache = DependencyContainer.GetService<IDistributedCache>(isRequired: true);
  • You can make use of this to externalize the data caching mechanism, if it is already relying on application’s memory.

Info: You can also use Pivotal Cloud Cache which is naturally highly available and highly performant. Refer this Steeltoe sample to implement the same using PCC.

(ii) Handling file system access

If the application is already access the local file system, that is against cloud native principles. So we need to externalize it. There are several options available, using s3 storages, SMB shares, etc. to solve this problem. Here is an article I wrote earlier, which explains, how to use SMB shares, which is natively supported by Pivotal Platform(PASW), for you to make use of it.

6. Dependency Management

Traditionally, when we host ASP.NET applications in a server, we install the pre-requisites/dependencies (framework, IIS, etc.) in the server upfront to keep the server ready. This way, the application will run seamlessly as soon as you move the code and configure the web server as needed. In the container world, this is not possible. Imagine, container runs on light weight minimalistic operating system. So we need to bring in, all dependencies including application specific dependencies together, using DLLs or Nuget packages and produce a single artifact. As mentioned above, the runtime and framework dependencies will be taken care by System Buildpacks, in our case it is hwc_buildpack. There is also a buildpack called binary_buildpack which can be used in case you can compile your application as self contained (100% dependencies within the artifact, including framework dependencies)

7. Telemetry

Although telemetry is not critical for an application to run on the platform, I think there is more value in adding this factor for an application running in production, for e.g. trouble shooting a problem will be very hard in production environment. Also, in a longer run, you need proper metrics to understand the performance level of an application, which helps you in fine tuning your application for better efficiency and better performance. There are various number of tools like new relic, dynatrace, appdynamics, splunk, etc. available in the market. It’s up to you to choose the best, based on your needs.

But, Pivotal Platform also offers PCF Metrics off the shelf, AppsManager integrated actuators and actuator endpoints, which you can leverage for the need to an extent. This can be easily implemented in your application using a Nuget package PivotalServices.AspNet.Bootstrap.Extensions.Cf.Actuators. This package uses Steeltoe Management under the hood.

Info: To get into more details on the implementation, please refer here

Here are some high level steps to follow.

  • Upgrade your application framework 4.6.2 or above
  • Install the base package from Nuget
  • Add code in App_Start method of Global.ascx, as below
AppBuilder.Instance
        .AddCloudFoundryActuators()
        .AddCloudFoundryMetricsForwarder()
	.Build()
	.Start();
  • Add a line of code AppBuilder.Instance.Stop(); in App_End method of Global.asax
  • Now, compile and cf push the application, you should see the actuator endpoints (/actuator/health and /actuator/info), CloudFoundry actuators enabled. For more details you can refer to Steeltoe Management Endpoints
  • If you need to add a custom health contributor, you can create an implementation of Steeltoe.Common.HealthChecks.IHealthContributor and just inject into the service collection, which will be aggregated automatically into your health report.

8. Authentication & Authorization

Traditionally we use Forms and Windows authentication mechanisms for ASP.NET web applications. Windows authentication is not supported on cloud platforms as of now, because the containers are not domain joined.

Say, if you are already using Forms Authentication or any other SSO provider based authentication, it is pretty straight forward, if not you can make use of the Pivotal platform marketplace tile offering called SSO, which you can leverage following the recipe, which uses Steeltoe Security.

Info: You can enable windows authentication in another way which uses open source Kerberos .NET library, which uses a the Nuget package PivotalServices.AspNet.Auth.Extensions. I will write another article to flush more details on it. In case you want to try it, please refer to the readme of the source repository and follow the instructions.

I hope, I was able to help you in getting your ASP.NET workloads in Pivotal Application Service (PAS) platform with some simple and easy steps!

Quick References


comments powered by Disqus