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
- One codebase, one application
- Single codebase per deliverable
- Any number of immutable releases
- API first
- Contract first development
- Use Server mocks (e.g. Apiary)
- Dependency management
- Isolate dependencies as NuGet packages
- Bundle dependencies together with build artifacts, meaning Self contained
- Design, build, release, and run
- Must have CI/CD Pipeline
- Design & build together will be an iterative process
- 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
- Logs
- Treat as event streams which are sequence of time-ordered emitted events
- Emit them as STDOUT and STDERR only
- Disposability
- Fast startup & graceful shutdown
- Externalize states using backing services, for e.g externalize data caching
- Maximize robustness of the application
- Backing Services
- Treat as attached resources, which gives loose coupling and flexibility
- E.g. data store, caching, side cars, etc.
- Environment Parity
- Keep all environments including development as similar as possible
- Every commit to be a deployment candidate which will work everywhere
- Administrative Processes
- Run admin/management tasks as one-off, scheduled processes
- Port Binding
- Avoid micromanaging port assignments
- Allow externalized runtime port binding
- Stateless Processes
- All long-lasting state to be externalized using backing services (e.g. session)
- Share-Nothing (containers are highly disposable)
- Concurrency
- Scale out horizontally rather than vertical scaling
- Should be able to run multiple concurrent instances simultaneously
- Telemetry
- Application Performance Monitoring(APM) – Wellness
- Domain specific telemetry– Analytics & Reporting
- Health & System logs – Stream of events
- 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)
- Configuration, Credentials & Code
- Logs
- Stateless Processes
- Concurrency
- Disposability
- Dependency Management
- Telemetry (could be optional)
- 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 ofGlobal.ascx
- Move your
appSettings
to environment variables or any other external sources, with variable names likeAppSettings:key
for AppSettings andConnectionStrings:name
for connectionStrings - Create the
cf app manifest
and push the application usingcf push
to see that theSystem.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 likeConfigurationManager.AppSettings["bar"]
orConfigurationManager.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 ofGlobal.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 usingcf 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 ofGlobal.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 usingcf 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 usingconstructor injection
or using codevar 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
- PivotalServices.AspNet.Bootstrap.Extensions.Cf.* Package Samples
- PivotalServices.AspNet.Bootstrap.Extensions Package Sample
- PivotalServices.AspNet.Auth.Extensions Package Sample
- 12 Factors
- Beyond The Twelve Factor App
- Steeltoe
- Pivotal Platform
- Pivotal Application Service (PAS)
comments powered by Disqus