Load balancing EPiServer CMS
Load balancing EPiServer can be quite tricky and there are a few key configuration items to be set.
All the information below has been gathered from several different articles, blog posts, etc. on the Internet. The purpose of this blog is to provide you with the tools to successfully setup a Load balanced EPiServer environment.
Introduction
For one of our customers we have the following EPiServer setup:
Functional:
A customer website with both a public and a private pages, using Forms bases authentication + (of course) EPiServer CMS pages.
The public part of website is available over http, the private and CMS pages over https.
Customers seamlessly roam back and forth between http and https pages.
Technical:
The architecture consists of 3 servers, all virtuals in the TerreMark eCloud: 2 EPiServer application servers and 1 database server.
Web servers:
Windows 2008 R2
4 VPU’s
6GB Memory
Both http and https traffic is load balanced over both application servers. Load balancing is configured with stickiness based on source IP address with a TTL of 5 minutes.
On both application servers the EPiServer application is served by a “Web Garden” of three Worker Processes.
So in effect EPiServer is served by 6 Worker Processes in total.
Database server:
Windows 2008
2 VPU’s
8GB Memory
Next to the standard EPiServer database, the application uses five more databases (i.e. store and product databases).
Challenges
With the setup described above, we are faced with a couple of challenges.
- Content & Cache Updates
- Shared VPP Data
- Unauthenticated Session States
- Authenticated Session States
The first two are actually easy because they are well documented in several EPiServer articles.
For the latter two some .net trickery is required.
Content & Cache Updates
Make sure the following section is uncommented in your web.config on all nodes:
<system.serviceModel>
<extensions>
<bindingElementExtensions>
<add name="udpTransport" type="Microsoft.ServiceModel.Samples.UdpTransportElement, EPiServer.Implementation" />
</bindingElementExtensions>
</extensions>
<services>
<!-- Before deployment, you should remove the returnFaults behavior configuration to avoid disclosing information in exception messages -->
<service name="EPiServer.Events.Remote.EventReplication" behaviorConfiguration="DebugServiceBehaviour">
<endpoint name="RemoteEventServiceEndPoint" contract="EPiServer.Events.ServiceModel.IEventReplication" binding="customBinding" bindingConfiguration="RemoteEventsBinding" address="soap.udp://239.255.255.19:5000/RemoteEventService" />
</service>
</services>
<client>
<endpoint name="RemoteEventServiceClientEndPoint" address="soap.udp://239.255.255.19:5001/RemoteEventService" binding="customBinding" bindingConfiguration="RemoteEventsBinding" contract="EPiServer.Events.ServiceModel.IEventReplication" />
<!--Client configuration for the ImageEditor, the client name has to be "ImageServiceClientEndPoint"-->
<!-- Only uncomment if the Image Service is hosted in an external application
<endpoint
name="ImageServiceClientEndPoint"
address="the address of the hosting application"
binding="the binding the hosting application is using"
bindingConfiguration="ImageServiceBinding"
contract="EPiServer.ImageLibrary.IImageService" />
-->
</client>
<behaviors>
<serviceBehaviors>
<behavior name="DebugServiceBehaviour">
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="RemoteEventsBinding">
<binaryMessageEncoding />
<udpTransport multicast="True" />
</binding>
</customBinding>
<!-- Only uncomment if the Image Service is hosted in an external application
<binding type used by the Image Service>
<binding name="ImageServiceBinding" maxReceivedMessageSize="20000000">
<readerQuotas maxArrayLength="20000000" />
<security mode="None" />
</binding>
</binding type used by the Image Service>
-->
</bindings>
</system.serviceModel>
This enables content updates, which are made e.g. on node CMS01 to be propagated to node CMS02.
Shared VPP Data
Use the following steps to enable use of shared VPP Folder:
1) Create a new user
2) Make this user member of the IIS IUSRS\ group (remove all other group memberships for extra security)
3) Make sure the IIS IUSRS\ group has appropriate rights on “non default” folders. (E.g. Logging folders)
4) Configure your Application Pool to run as this user
5) On the Server with the VPP folders, repeat steps 1) & 2)
6) Give the IIS IUSRS\ group appropriate rights on the VPP Folders (and sub folders)
Unauthenticated Session States
By default Microsoft Internet Information Server saves session states “In Process”. Meaning in the Worker Process of the web application.
In our scenario we will want to use a Session State Database. Using the ASP.NET State Service (part of default install) to save session states to a database.
Don’t worry about performance. This actually works very well and very fast.
Here is all the info you need to configure your website to use it.
After following the steps from the KB article to setup the DB. You add this piece of configuration to your web.config on all nodes:
<sessionState mode="SQLServer" sqlConnectionString="data source=10.1.1.1;user id=aspstate;password=******" cookieless="false" timeout="20" />
Make sure the user id is created on the DB server and has DBO rights (only) on the ASPstate DB.
Authenticated Session States
Picture this scenario:
- a user opens a public page and is routed by the load balancer over port 80 to CMS01
- he clicks a link to use a feature on the website only available for logged in users and is redirected to the login page. This is a SSL encrypted page and the users is routed by the load balancer over port 443 to CMS02
- the user fills in his credentials and is redirected back to the original public page to CMS01
Unless we perform the steps below, the user will not be logged in and the following error will appear in the Windows System Eventlog:
Event code: 4005
Event message: Forms authentication failed for the request. Reason: The ticket supplied was invalid.
Event time: 10/10/2011 2:29:32 PM
Event time (UTC): 10/10/2011 12:29:32 PM
Event ID: 2ddd863e47534f53a182f84a9a613070
Event sequence: 75
Event occurrence: 1
Event detail code: 50201
Application information:
Application domain: /LM/W3SVC/2/ROOT-1-129627223033590000
Trust level: Full
Application Virtual Path: /
Application Path: D:\Inetpub\wwwroot\
Machine name: CMS01
Process information:
Process ID: 2716
Process name: w3wp.exe
Account name: username
Request information:
Request URL: http://website/UI/javascript/system.aspx
Request path: /UI/javascript/system.aspx
User host address: ip address
User:
Is authenticated: False
Authentication Type:
Thread account name: username
This is what happened: after the post of login credentials on CMS02. The server encrypted the VIEWSTATE using the node’s machine key.
Now when the users arrived back on the node CMS01, this node was unable to decrypt the VIEWSTATE and therefore did not accept the user as validated user.
To fix this, we need to make sure all servers in the farm use the same machine key.
There are a ton of websites who provide online machine key generation. Like this one: http://aspnetresources.com/tools/machineKey
Just click generate and add it to your web.config. Make sure you add it between system.web tags:
<system.web>
<machineKey validationKey="7F252ED2FAF2BB70FDFB7AF2CC5864981D4933F795FDBC5E5B54DB2589148AF1BD10E277044C0426C6C47601AE2074F3B5E7012D6" decryptionKey="00C0C09D8E7D0DCB3535A29A00E3FBA39DF3300" validation="SHA1"/>
</system.web>
Good luck!
Nice post! Just one thing, why do you use Web Gardens?
I agree with evest, very nice article!
We see a significant performance boost when we implement a Web Garden.
IIS Request queue drops, CPU usage drops the web application just runs a lot smoother.
There is only one pitfall: the worker processes sometimes grow to 1.2GB memory usage or even more. Therefore we decided to configure 3 (and not more) WP instances and set the max. memory usage for the WP's @ 900MB. So if a WP goes over the 900MB threshold, it's recycled to a fresh WP.