Windows Azure Scale UP – Scale OUT

Laatst moest ik aan een collega bij een klant iets vertellen over Windows Azure. Dan komt natuurlijk ook altijd het item schaalbaarheid om de hoek kijken. Ik was allerlei plaatjes aan het tekenen, daar heb ik later maar een mooie presentatie van gemaakt. Hier een paar kleine stukjes.

Als eerste de terminologie.

scaleup-out4

Op het Windows Azure kennen we 5 verschillende grote van Virtual Machines. Dat is SCALE UP.

scaleup-out1

Als je in de ServiceConfiguration.csdfg het aantal instanties verhoogd van de default 1 naar meer, dan spreken we van SCALE OUT.

scaleup-out2

Uiteraard is er ook nog een tussen weg. Meerdere processen draaien binnen 1 goed gedimensioneerde Virtual Machine (Scale out) en meerdere van deze Virtual Machine naast elkaar.

scaleup-out3

Dit alles valt en staat wel of het proces ook daadwerkelijk schaalbaar is. Code moet geschreven vanuit het oogpunt dat hij niet de enige is.

SQL Azure Custom Logging naar Windows Azure Table storage

Bij een Windows Azure klant van ons worden wijzigingen op de tabellen bewaard in een SQL Azure tabel. Deze wijzigingen worden via een trigger geinsert in de tabel. Ik zou ook niet zo een twee drie een andere manier weten om dit netjes zonder verlies te doen.

De keerzijde van deze medaille is wel, dat deze logging ruimte in SQL Azure kost in verhouding veel voor deze data. Tenslotte is deze logging data alleen bedoeld voor analyse bij problemen en is de lees frequentie erg laag. Daarbij is deze data ook nog eens niet relationeel.

1 Gb SQL Azure Database $ 9.99
1 Gb Windows Azure storage $ 0,15 (exclusief transactions)

(Meer informatie over Windows Azure kosten)

Daarom is het heel interessant om deze data zo snel mogelijk te verplaatsen van SQL Azure naar Windows Azure Table storage. Om dit voor elkaar te krijgen, heb ik een WorkerRole gemaakt die dat doet.

new LoggingUtils().MoveLoggingToStorage(
       RoleEnvironment.GetConfigurationSettingValue("SQLAZURE"),
       RoleEnvironment.GetConfigurationSettingValue("DATASETSIZE")
)

Deze WorkerRole haalt in een loop een set van dataregels uit de SQL Azure table:

using (SqlDataAdapter sqlDataAdapter =
    new SqlDataAdapter(
     " Delete TOP (" + _datasetSize + ") " +
" from [Logging].[ChangeLogging] " +
     " WITH (readpast, rowlock, xlock) OUTPUT DELETED.* ",
     sqlc)   ) {
    sqlDataAdapter.Fill(_dataSet); }

Vervolgens gaan we deze regels toevoegen aan een Windows Azure storage table. De logging tabel bevat naast meerdere velden ook een TableName veld. Deze TableName gebruiken we als PartitionKey, de UseCase beschrijft dat controles op basis van Tabellen plaatsvinden. Als RowKey heb ik het verschil in Ticks en een Guid gebruikt. Tenslotte moet deze combinatie uniek blijven en met de overige data uit de Logging tabel wist ik dat niet.

loggingEntry.PartitionKey = dr["TableName"].ToString();
loggingEntry.RowKey = string.Format(
"{0:10}_{1}",
DateTime.MaxValue.Ticks - DateTime.Now.Ticks,
Guid.NewGuid()
);

Om nu zo vlug mogelijk de Logging tabel uit te lezen, kunnen we Scale OUT in twee vormen doen. Meerdere instanties van de WorkerRole of meerdere threads maken die de MoveLoggingToStorage methode uitvoeren.

Nu moeten we nog iets hebben om de Windows Azure Table Storage op te schonen na verloop van tijd. Naar een zip of zo en die op tape opslaan. Of zou het weggooien van de records voldoende zijn? Laatste is denk ik afhankelijk van de organisatie.

Update Record in Windows Azure Table storage

Zoals verteld in mijn vorige Blogpost hield ik een lijstje bij met de Blob naam en de LastModifiedUtc van de Blob. Om de oplossing geschikt te maken dat meerdere instancies van de WorkerRole daar aan werkte, sloeg ik dit lijstje op in Windows Azure Tablestorage.

Als ik een Blob heb met een gewijzigde LastModifiedUtc, dan kopieer ik de blob maar moet ik natuurlijk ook het record in mijn lijstje bijwerken.

Het toevoegen van een nieuwe record aan de Table storage is eenvoudig.

this.context.AddObject(
BlobCheckListContext.BlobCheckListTable,
newItem
); this.context.SaveChanges();

Voor het wijzigen moet je een klein dingetje extra doen. NB geldt ook voor Delete.

this.context.AttachTo(
BlobCheckListContext.BlobCheckListTable,
updatedItem,
"*"
); this.context.UpdateObject(updatedItem); this.context.SaveChangesWithRetries(
SaveChangesOptions.ReplaceOnUpdate
);

Je moet dus eerst Attachen aan het record en dan UpdateObject uitvoeren.

Windows Azure Blobstorage Modified date

Ik was bezig met een oplossing voor klant, waarbij ik Blobs uit Windows Azure Blobstorage haalde en kopieerde naar een andere locatie. Om dit een beetje efficient te laten gebeuren hield ik een lijst met de Blob naam en de last modified date bij.

Dat leek op zichzelf erg simpel. Een Blob heeft properties en een van die properties is LastModifiedUtc.

blockBlob.Properties.LastModifiedUtc

Bij een blob keek ik in mijn lijstje. Ik vergeleek de LastModifiedUtc van de Blob met de LMU waarde behorend van de Blob in mijn lijstje. Als deze gelijk waren deed ik niets en als ze verschilde kopieerde ik de blob van de ene naar de andere locatie.

Tijdens het testen bleek ik bij een tweede upload de kopieerslag toch over te slaan. Al debuggend zag ik dat de waarde voor LastModifiedUtc altijd 0 is. Vreemd omdat mijn Cerebrata Storage tool wel zag, dat de blob gewijzigd was.

De oplossing was eigenlijk heeft simpel. Gebruik FetchAttributes op de Blob en voila de property is wel gevuld.

CloudBlockBlob blockBlob = 
_sourceContainer.GetBlockBlobReference(uri.ToString()); blockBlob.FetchAttributes();

Om deze oplossing ook te laten werken als ik meerdere instantie van de WorkerRole had, hield ik het lijstje bij in Windows Azure Tablestorage. Maar daarover in een later post meer.

Windows Azure Configurations

Na aanleiding van mijn vorige post nog iets over de verschillende configuratie mogelijkheden op het Windows Azure platform.

Voor de WebRole heb je de web.config en voor de WorkerRole heb je de app.config. Deze config files zijn we allemaal gewoon om te gebruiken. Voordeel van deze configuratie is, dat we aan de onze huidige applicaties weinig hoeven aan te passen om het op het Windows Azure platform te laten draaien. Nadeel is dat settings alleen bij een nieuwe deployment te wijzigen zijn. In princope kun je wel RDP-en naar een Role en dan de web of app.config aan te passen, maar deze wijzigingen zijn bij een reimage verdwenen en gelden niet voor alle instanties van de rollen.

config1config2

Ook heb je de settings in de ServiceConfiguration.cscfg staan. Voordeel hiervan zijn, dat je ze on the fly kunt aanpassen en dat ze voor alle instanties gelden. Ook na een reimage zijn ze nog steeds aanwezig. Nadeel is dat je je huidige WebSites / Windows Services moet aanpassen om gebruik te maken van deze settings.

config3config4

Het aanpassen van de code is relatief simpel:

   1: RoleEnvironment.GetConfigurationSettingValue("setting")

Ander nadeel, deze settings worden niet automatische doorgevoerd door een draaiende Role, daarvoor zul je de role moeten herstarten. Maar als je in de OnStart methode deze code opneemt, dan gaat het wel goed.

   1: // For information on handling configuration changes

   2: // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.

   3: RoleEnvironment.Changing += RoleEnvironmentChanging;

En dit event toevoegt.

   1: private void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)

   2: {

   3:     Trace.WriteLine("Role Environment Changing");

   4:     // If a configuration setting is changing

   5:     if (e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange))

   6:     {

   7:         // Set e.Cancel to true to restart this role instance

   8:         e.Cancel = true;

   9:     }

  10: }

Het was al belangrijk om te bepalen welke settings je opneemt in de config bestanden, nu wordt ook de locatie belangrijk.

Ook hiervan hoop ik dat er nog een inhaalslag gemaakt wordt.

Op dit moment staan de settings in de ServiceConfiguration.cscfg per Role. Mocht je voor alle Rollen in je Windows Azure applicatie een gemeenschappelijke setting hebben, dan zijn er nu geen mogelijkheden om deze maar een keer op te voeren. Je zult ze per Role moeten opgeven.

Ook een nesting van settings is niet mogelijk. Er is maar een level.

Maar ik heb er wel vertrouwen in. De meeste zijn al opgevoerd op http://www.mygreatwindowsazureidea.com. De product teams zullen deze ideeën oppakken.

Windows Azure & Configuratie transformaties

Standaard zit er in Visual Studio de mogelijkheid om configuratie bestanden automatisch te transformeren op basis van een geselecteerde configuration. Deze werkwijze is perfect voor web.configs, maar helaas is dit niet zomaar bruikbaar voor App.configs of andersoortige configuraties bestanden.

cfgtrans1cfgtrans4

Op deze site staat hoe het voor andere configuratie bestanden werkt. In deze blogpost haal ik alleen het Windows Azure gedeelte eruit.

Voor Windows Azure applicaties geldt dat de configuratie in ServiceConfiguration.cscfg na deployment aan te passen is. Deze aanpassingen zijn dan ook persistent. Zoals bekend kunnen wijzigingen in de web.config wel, maar na een reimage of Windows Azure fabric actie zijn ze niet meer aanwezig. Tenslotte zijn deze configuraties onderdeel van de deployment en die zijn zonder een nieuwe deploy niet meer wijzigbaar.

Maar ook voor de items in ServiceConfiguration.cscfg geldt, dat je deze graag per omgeving zou willen instellen. Zoals bijvoorbeeld de connectionstring naar Windows Azure storage (development fabric of productie), connectionstrings naar je database (Lokaal zul je vaak naar je on premise database connecten en in productie SQL Azure) of andere instellingen zoals verwijzingen naar URL’s van de applicatie etc.

In de Configuration manager kun je een nieuwe configuration maken.

cfgtrans2

Daarbij geef je aan wat de basis van de settings is, dit is niet van belang voor de Service configuration maar wel voor de web configuraties.

cfgtrans3

Uiteindelijk zie je dan deze omgevingen. Dan ben je er nog niet helaas. Je kunt voor de Service configuration niet doen zoals het tweede plaatje uit deze blogpost (rechtermuisklik op de web.config en kiezen voor Add Config transformations).

cfgtrans8cfgtrans5

Voor de Service configuration zullen we dat anders moeten doen. We zullen kopieën moeten maken van de ServiceConfiguration.cscfg bestanden. Deze bestanden moeten dan gerenamed worden naar de configurations (AzureProductionAccount en AzureTestAccount). Zie plaatje.

cfgtrans6cfgtrans7

De volgende stappen moeten in de .ccproj file aangepast worden. Dit kun je uiteraard doen met behulp van Notepad, maar dat is niet verstandig. Visual Studio biedt de mogelijkheid om de .ccproj file te unloaden en daarna te editen. Doe daarvoor Rechtermuis klik op de .ccproj en kies Unload. Daarna nogmaals Rechtemuis klik en dan edit.

In dit bestand kom je het volgende gedeelte tegen, dat lijkt verdacht veel op de kopieer actie die we eerder hebben uitgevoerd.

   1: <!-- Items for the project -->

   2: <ItemGroup>

   3:   <ServiceDefinition Include="ServiceDefinition.csdef" />

   4:   <ServiceConfiguration Include="ServiceConfiguration.cscfg" />

   5:   <None Include="ServiceConfiguration.AzureTestAccount.cscfg"/>

   6:   <None Include="ServiceConfiguration.AzureProductionAccount.cscfg"/>

   7: </ItemGroup>

   1: <PropertyGroup Condition=" '$(Configuration)' == 'AzureProductionAccount' ">

   2:   <OutputPath>bin\AzureProductionAccount\</OutputPath>

   3: </PropertyGroup>

   4: <PropertyGroup Condition=" '$(Configuration)' == 'AzureTestAccount' ">

   5:   <OutputPath>bin\AzureTestAccount\</OutputPath>

   6: </PropertyGroup>

Onderaan het bestand moeten we deze regels toevoegen.

   1: <!-- SN Add at the end -->

   2: <UsingTask TaskName="TransformXml" 

   3:            AssemblyFile="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\Web\Microsoft.Web.Publishing.Tasks.dll" />

   4: <PropertyGroup>

   5:   <ServiceConfigurationTransform>ServiceConfiguration.$(Configuration).cscfg</ServiceConfigurationTransform>

   6: </PropertyGroup>

   7: <Target Name="TransformServiceConfiguration" BeforeTargets="CopyServiceDefinitionAndConfiguration" Condition="exists(‘$(ServiceConfigurationTransform)’)">

   8:   <!-- Generate transformed service config in the intermediate directory -->

   9:   <TransformXml Source="@(ServiceConfiguration)" Destination="$(IntermediateOutputPath)%(Filename)%(Extension)" Transform="$(ServiceConfigurationTransform)" />

  10:   <!-- Force build process to use the transformed configuration file from now on. -->

  11:   <ItemGroup>

  12:     <ServiceConfiguration Remove="ServiceConfiguration.cscfg" />

  13:     <ServiceConfiguration Include="$(IntermediateOutputPath)ServiceConfiguration.cscfg" />

  14:   </ItemGroup>

  15: </Target>

  16: <!-- EN Add at the end –>

Vervolgens moeten we in de cscfg files de transformaities toevoegen, daarvoor moet je de xdt:Transform en xdt:Locator regels gebruiken.

cfgtrans9

Als je dit allemaal gedaan hebt, je selecteert de juiste configuration en doet daarna een Rebuild, dan zullen de transformaties uitgevoerd worden. Dat kun je dan controleren op de locatie waar de bin folder staat.

cfgtrans10

In dit cscfg bestand kun je dan het resultaat van de transformatie zien.

cfgtrans11

En voila! Het werkt.

Uiteraard kun je ook als alternatief twee Solution files maken, waarbij de settings voor de verschillende omgevingen in de solutions anders zijn. Maar dit is suboptimaal en in mijn optiek fout gevoelig.

Wel heb ik gemerkt, dat bovenstaande op een andere computer onder een ander account niet zomaar altijd goed werkt. De reden hiervoor ben ik nog aan het uitzoeken. Maar de laatste keer werkte het na wat gepriegel eerst niet en daarna wel, terwijl ik geen wijzigingen had doorgevoerd.

Ik hoop wel, dat er nog een mooiere oplossing komt waarbij je niet zoveel handmatige handelingen hoeft uit te voeren.

Nieuwe Windows Azure Management Portal

Ja hoor het is eindelijk zover. Er is een update gedaan op de Windows Azure Management portal. Niet dat er extra functionaliteit is bijgevoegd, ik heb nog wel een paar wensen hoor, maar de UX is een beetje aangepast. Voorheen kreeg je als je ‘per ongeluk’ of ‘intuitief’ een rechtermuis klikt deed een vage melding, dat het een Silverlight site was. Geweldig natuurlijk tenslotte is Silverlight nog lang niet dood (dat is een andere discussie), maar niet erg functioneel.

Maar nu is er dan een heuse context gevoelig action menu als je Rechtermuis klikt op een level in de Hosted Services tree.

azureportal1

(Helaas mist in dit context menu “Connect” oftwel het opzetten van een RDP sessie nog.)

azureportal2

azureportal3

En is nog iets! Je kunt nu ook voor een ander taal kiezen voor de scherm teksten! Oke, Nederlands mist nog, maar dat zal toch ook nog wel komen.

azureportal4

In het Duits bijvoorbeeld.

azureportal5

Oke, deze context gevoelige actionmenu’s zijn alleen nog maar beschikbaar bij de keuze “Hosted Services, Storage Account & CDN”. Maar ik denk dat de andere teams niet te lang op zich laten wachten met het doorvoeren van deze nieuwe functionaliteit.

Oh, mocht je problemen hebben met het connecten naar de nieuwe portal, flush dan je Internet explorer cache even dat zal helpen.

Window Azure Co-admin en SQL Azure

Bij verschillende klanten ben ik uiteraard niet de Administrator van de Windows Azure subscription. Maar daar ben ik (een van) de co-admin(‘s). Mijn strategie is dan om een live id te maken op het e-mail adres van die klant. Bij het verlaten van mijn opdracht kan ik het account en wachtwoord terug geven. De klant weet dan, dat ik niet ongeoorloofd aan hun instellingen kan zitten. Ze vertrouwen mij natuurlijk wel, maar dit is wel zo prettig.

Enige nadeel van deze situatie was, dat je geen SQL Azure database kon beheren. Je kon geen SQL Azure databases aanmaken, firewall rules etc doen. Althans niet via de Windows Azure Management portal, uiteraard wel met de SQL Server Management Studio (SSMS) met het overall account.

Vandaag zag ik dat het inmiddels opgelost is, nu kun je als co-admin wel de database beheren etc. Handig!

coadmin1

Oke, nu zou ik eigenlijk nog verschillen in co-admins willen onderscheiden. Dat je co-admins niet alle rechten geeft, maar beperkte rechten. Ach, wensen blijven er altijd Winking smile

Upgrade Windows Azure Services

De reden waarom je twee instanties van een Windows Azure Service nodig hebt, is om een optimale beschikbaarheid van je website te garanderen. Ten eerste omdat de Windows Azure fabric zomaar kan bepalen, dat jouw instantie van een service moete verhuizen binnen het DataCenter. De reden kan zijn kapotte hardware of instabiliteit van je service. Door twee instanties is je site netjes beschikbaar tijdens deze verhuizing.

Ook zelf kun je daar mooi voordeel van hebben. Als je een site of Hosted Service upgrade, dan wil je niet dat je site niet meer beschikbaar is. Helaas kent Windows Azure niet iets als een ‘Site is temporary out of order’- sign, maar krijg je als klant gewoon een vette 404 voor je kiezen. Daarom is het wel mooi, dat Windows Azure zorgt voor de juiste beschikbaarheid.

clip_image002

Het uploaden van de package.

clip_image004

De eerste role wordt geupdate.

clip_image006

Daarna de tweede role.

Mooi he. Deden onze on premises systeembeheerders ook maar zo hun werk 😉

LET OP: Dit gaat niet altijd goed. Als je Hosted Service gebruik maakt van een Database en bij de update van de Hosted Service horen database wijzigingen (met name Model wijzigingen), dan moet je daar een goede strategie voor bepalen. Bijvoorbeeld de wijzigingen in de Database pas uitvoeren als de Hosted Service volledig is geupgrade, maar dan moet de nieuwe versie om kunnen gaan met het niet aanwezig zijn van deze wijzigingen. Hoe dan ook food for Thought!

Windows Azure AppFabric ACS met meerdere instanties

Met Windows Azure AppFabric ACS had ik een mooie demo in elkaar gezet. Op deze simpele MVC2 site is een pagina Secret. Om deze pagina te kunnen bekijken moet je inloggen. Dat inloggen kon via alle mogelijke social networks, kortom alle Social networks die worden ondersteund door de Windows Azure AppFabric ACS. Dat had ik vrij snel in elkaar, via Bing zijn diverse stappenplannen te vinden.

Het enige dat toen nog niet werkte was het uit te loggen. De standaard Logout link op de Masterpage had niet het gewenste effect. Je werd als user niet uitgelogd. Na wat zoeken en hulp van oa Patriek van Dorp bleek dat ik een ander control moest gebruiken.

wifmulti2

(het uitgecommentarieerde stuk is de standaard code)

Daarna werkte mijn demo zoals ik mij had voorgesteld. Maar ik had van de webrole maar een instantie draaien. Nadat ik opmerkingen had gelezen over ACS met meerdere instanties, besloot ik mijn webrole ook op 2 instanties te laten draaien. En inderdaad, de demo deed het soms wel en soms niet. Niet zo vreemd bleek na het lezen van het book van Captain Identity Vittorio Bertocci. Je krijgt een cookie en deze is versleuteld met machine-sleutel van de instantie waar je zat. Maar op de andere instantie is de machine-sleutel natuurlijk anders en kan het cookie niet gelezen worden.

De oplossing is dan ook vrij simpel, zorg ervoor dat de versleuteling plaats vind met voor alle machines een gelijke sleutel. Ik heb voor mijn domein een SSL certificaat gekocht en deze kan ik daar mooi voor gebruiken.

In de code behind van de Global.asax zul je de Application_Start methode moeten aanpassen.

void Application_Start(object sender, EventArgs e)
{
     // Code that runs on application startup
     FederatedAuthentication.ServiceConfigurationCreated +=
OnServiceConfigurationCreated;
}

En je moet deze methode toevoegen. Deze methode zorgt uiteindelijk voor de versleuteling.

void OnServiceConfigurationCreated(
object sender,
ServiceConfigurationCreatedEventArgs e)
{
// Use the <serviceCertificate> to protect the cookies that are
// sent to the client.
So multiple roles do the same.
List<CookieTransform> sessionTransforms =
new List<CookieTransform>(
new CookieTransform[] {
new DeflateCookieTransform(),
new RsaEncryptionCookieTransform(
e.ServiceConfiguration.ServiceCertificate),
new RsaSignatureCookieTransform(
e.ServiceConfiguration.ServiceCertificate)
}
);

SessionSecurityTokenHandler sessionHandler =
new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly());
e.ServiceConfiguration.SecurityTokenHandlers.AddOrReplace(
sessionHandler);
}

Oke, zoals je ziet, wordt er in de ServiceConfiguration gekeken naar een Certificaat. Deze zul je in de Web.config dan nog wel even moeten toevoegen.

wifmulti1

Na al deze aanpassingen werkte de demo met meerdere instanties en alle Social networks. Hoe leuk is dat!

De demo is in zijn geheel te bekijken op https://cloudtest.marcelmeijer.net. Als je naar de Secret page gaat, dan wordt je Social Network ID opgeslagen in een database om te tellen hoevaak je naar deze Secret page gaat. Ik doe verder niets met deze informatie.