Multitenancy en Aplicaciones Windows Azure

César de la Torre Architect Advisor cesardl@microsoft.com Microsoft

Luis Panzano ISV Platform Strategy Advisor luispb@microsoft.com Microsoft

C

Empresas ofreciendo aplicaciones de negocio

C

C .

C .

Nivel I: Personalizado Nivel II: Personalizable Nivel III: Personalizable. Multi-Tenant Nivel IV: Escalable. Multi-Tenant C . Personalizable.

costes compartidos Igual para todos Idéntico para todos Posible pero más compleja Aprovisionamiento más complejo Nivel de Servicio Entorno Legal Personalización por tenant Autenticación y Autorización L .Single-tenant Estabilidad Escalabilidad Fallos afectan a 1 cliente Escalable pero más costoso Personalizable Adaptable por cliente Muy alta Estándar Multi-tenant Fallos afectan a todos Escalable.

Single-tenant Código fuente Actualizaciones Riesgo de fragmentación ‡ Costosas ‡ Soporte complejo de múltiples versiones Múltiples monitorizaciones Multi-tenant Único ‡ Sencillas ‡ Soporte simplificado de 1 versión Monitorización global Monitorización Aprovisionamiento de nuevos clientes y ¶trials· ‡ Necesario aprovisionar ‡ Basta con cambios de nuevos recursos configuración ‡ Más pasos manuales ‡ Más simplificado una por tenant. vez se tiene sistema multi-tenant L .

L .

L .

1 2 Resuelve conexión examinando URL y validando identidad L .

C .

(*) Bases de datos separadas Clientes empresa µpremium¶ ¼ BBDD SQL Azure Web 1GB 7 ¼/mes BBDD Compartida. Distintos Esquemas (*) BBDD Compartida. Esquema Compartido ¼ Clientes µde consumo¶ o micro-empresa Número masivo de clientes C .

C .

C .

ACS Custom STS Identity Management SQL Azure Databases Tenant databases Management and control database User management Sample µtask¶ application Provisioning Metering Auto scale Cleanup Browser Worker role instances Web role instances Service control application (Dashboard) IIS logs and PerfMon counters Azure Storage Web role instances AppFabric Data cache Tenant public and private blobs IIS Logs Job management queues Trace logs PerfMon counters C .

C .

C .

Provisioning Queue Create Tenant DB Web Role Provisioning Worker Tasks Create Tenant Containers Add Tenant Display Provision Status Primary DB Update Thumbprint Update Tenant Status Update Provisioning Status Create Certificate and Configure ACS C .

CreateTenantUser(tid.Models. userManagement. // Add a message to the provisioning queue ProvisioningQueue.Tenant.QueueProvisioningRequest(tid). // Add the initial user.MemberId. calling the UserManagement WebService // We should consider pushing this on to the provisioning queue and create the user during provisioning UserManagementClient userManagement = new UserManagementClient(). model.[HttpPost] public ActionResult Subscribe(RegistrationModel model) { //« //Add a record to the service DB Common.CreateNewSubscription(tid. model. model.MemberName. // Redirect to provisioning status page return Redirect("/Home/ProvisionStatus/" + model.MemberPassword. model. 1).CompanyName. } . "Administrator").TenantId). //« return View(model).

. stopWatch. public override bool Execute() { Stopwatch stopWatch = new Stopwatch()..Message). provisionIdentity.IsDelete = msg. .UpdateTenantStatus(tenant. provisionDb.IsDelete = msg. provisionDb.Stop().TenantId.. provisionStorage. « stopWatch.public class ProvisionTenantWork : TaskActivityBase { . ProvisionDatabaseActivity provisionDb = new ProvisionDatabaseActivity().TenantId = tenant. provisionStorage. .Remove.Remove.IsDelete = msg.Execute(). ProvisionIdentityActivity provisionIdentity = new ProvisionIdentityActivity(). ProvisioningQueue. if (!msg. ProvisionStorageActivity provisionStorage = new ProvisionStorageActivity(). provisionDb.Remove. "active").Start().Execute(). ProvisioningMessage msg = ProvisioningQueue.DeleteMessage(msg.. provisionIdentity.TenantId = tenant.GetMessage().Remove) Tenant.TenantId = tenant.Execute(). if (msg != null) { string tenant = msg. provisionIdentity. provisionStorage.

L .

L .

‡ Aplicación web ASP.Veamos un escenario real«.NET de gestión de almacenes en Windows Azure ‡ Utiliza SQL Azure para almacenar datos relacionales ‡ Utiliza el Storage de Azure para almacenar imágenes y ficheros ¼ .

Calculadora de precios online .

SQL SQL SQL SQL Coste unitario 0. ‡ Cada empresa tiene una media de 5 empleados ‡ A cada empresa le asigno: 1 Máquina virtual de Windows Azure (Web Role) 1 SQL Azure Web Edition de 1 Gb Concepto Horas proceso VM pequeña (50 VMs) Almacenamiento (GB) Transferencia datos (GB salida) SQL Azure Web Edition 1 Gb Total Mes VM VM VM VM «.25 ½ 3.20 ½ 0.37 ½ Coste Empresa/Mes Coste Usuario/Mes 68.1064 ½ 0.48 ½ 13.53 ½ 5..067.1064 ½ 7.0850 ½ Unidades 36000 5 50 50 Coste Total 3.32 ½ 354.70 ½ .Arquitectura single-tenant (despliegues dedicados) ‡ Tengo 50 empresas utilizando mi aplicación.0852 ½ 0.427.

64½ 708.0850 ½ Unidades 72000 10 100 100 Coste Total 6.854.38 ½ .50 ½ 6. SQL SQL SQL SQL Concepto Horas proceso VM pequeña (100 VMs) Almacenamiento (GB) Transferencia datos (GB salida) SQL Azure Web Edition 1 Gb Total Mes Coste unitario 0.75 ½ Coste Empresa/Mes Coste Usuario/Mes 68.41 ½ 13.Arquitectura single-tenant (despliegues dedicados) ‡ Tengo 100 empresas utilizando mi aplicación.0852 ½ 0.134.1064 ½ 7..1064 ½ 0.90 ½ 11.00 ½ Coste Empresa/Mes Coste Usuario/Mes 56.00 ½ 1.06 ½ 10. ‡ Cada empresa tiene una media de 5 empleados ‡ A cada empresa le asigno: 1 Máquina virtual de Windows Azure (Web Role) 1 SQL Azure Web Edition de 1 Gb VM VM VM VM «.68 ½ Y contratando 100 Windows Azure Core +10 SQL Azure Core: Concepto Total Mes Coste unitario Unidades Coste Total 5690.

69 ½ 0.0850 ½ Unidades 1440 5 1 50 50 Coste Total 122.07 ½ 5.1064 ½ 0.Arquitectura multi-tenant (despliegue único) ‡ ‡ ‡ ‡ Tengo 50 empresas utilizando mi aplicación.93 ½ .66 ½ 1. SQL Concepto Horas proceso VM pequeña (2 VMs) Almacenamiento (GB) Transferencia datos (GB entrada) Transferencia datos (GB salida) SQL Azure Web Edition 1Gb Total Mes Coste unitario 0.0710 ½ 0.25 ½ 482.0852 ½ 0.1064 ½ 7..86 ½ Coste Empresa/Mes Coste Usuario/Mes 9.32 ½ 354. Cada empresa tiene una media de 5 empleados Despliego 2 máquinas virtuales para todas las empresas A cada empresa le asigno: 1 SQL Azure Web Edition de 1 Gb dedicado SQL VM SQL SQL «.53 ½ 0.

36 ½ .65 ½ Y contratando 2 Windows Azure Core +10 SQL Azure Core : Concepto Total Mes Coste unitario Unidades Coste Total 681.0850 ½ Unidades 1440 10 100 100 Coste Total 122.81 ½ 1.69 ½ 1.0852 ½ 0.03 ½ SQL «.VM Arquitectura multi-empresa (despliegue único) ‡ ‡ ‡ ‡ Tengo 100 empresas utilizando mi aplicación.29 ½ 1.1064 ½ 7.50 ½ 843.64 ½ 708.1064 ½ 0. SQL SQL Cada empresa con una media de 5 empleados Despliego 2 máquinas virtuales para todas las empresas A cada empresa le asigno: 1 SQL Azure Web Edition de 1 Gb dedicado Concepto Horas proceso VM pequeña (2 VMs) Almacenamiento (Gb) Transferencia datos (GB salida) SQL Azure Web Edition 1Gb Total Mes Coste unitario 0.12 ½ Coste Empresa/Mes Coste Usuario/Mes 6. SQL Coste Empresa/Mes Coste Usuario/Mes 8.06 ½ 10..

00 ½20.00 ½40.00 ½E1 E1 > E2 E2 > Multi-tenant Coste empresa/mes Coste empresa/mes (suscripción) Single-Tenant .00 ½10.½70.00 ½30.00 ½60.00 ½50.

L .

NOTA: En una solución multi-tenant. lógicamente. las horas de computación no pueden medirse por Tenant pero sí pueden inferirse atendiendo a otros parámetros como el número de peticiones web y su frecuencia así como la transferencia de datos saliente por tenant. L .

C .

C .

wad-iis-logfiles C .

FromMinutes(1).FromMinutes(1).ScheduledTransferPeriod = TimeSpan.FromMinutes(1). « « CloudStorageAccount cloudStorageAccount = CloudStorageAccount. config). // Configuración para IIS-Logs config.ScheduledTransferPeriod = TimeSpan.Directories.ScheduledTransferPeriod = TimeSpan. « « « } } .Logs.cscfg WebRole. config.. // For windows azure logs config.FromMinutes(1).ConnectionString")).GetConfigurationSettingValue( "Microsoft.PerformanceCounters.Parse(RoleEnvironment.ScheduledTransferPeriod = TimeSpan. // Get the perf counters config.cs de aplicación final de Tenant public class WebRole : RoleEntryPoint { public override bool OnStart() { « « DiagnosticMonitorConfiguration config = DiagnosticMonitor.ServiceConfiguration.Plugins.GetDefaultInitialConfiguration().WindowsAzure.Diagnostics. DiagnosticMonitor diagMonitor = DiagnosticMonitor. // Cada minuto.*..Start(cloudStorageAccount.DiagnosticInfrastructureLogs.

Worker Roles Page blob Colas Ejecutan acciones: Cómo el auto-scaling (tareas que no pueden paralelizarse) Adquirir bloqueo JobScheduler Leer Jobs Añadir Jobs a las colas Procesadores de tareas Generador de tareas Tabla de Jobs Generador de tareas de logging 1 Cola de Tareas por cada par de Generador/procesador .

C .

C .

C .

Logs C .Lotes Batch a procesar Queues wad-iis-logfiles Info.

LastModifiedUtc.IISLogs_Container).BlobListingDetails = BlobListingDetails.ListBlobs(reqOptions)) { « // Codigo comprobacion si Log fue exportado « CloudBlob blob = blobStore.cs private List<MeteringIISLogBatchItem> GetIISLogs() { CloudStorageAccount storageAccount = Settings.GetStorageAccount().GetBlobReference(blobItem. CloudBlobContainer blobContainer = blobStore. } } return blobUris.Uri. BlobRequestOptions reqOptions = new BlobRequestOptions(). foreach (var blobItem in blobContainer. DateTime blobLastModified = blob. reqOptions.Credentials). } . reqOptions.AbsoluteUri). if (blobLastModified > timeProcesed || timeProcesed == DateTime.Properties.MaxValue) ? 0 : lastLineProcessed.FetchAttributes().AbsoluteUri. Uri baseUri = storageAccount. LastUpdatedTime = timeProcesed }).Add(new MeteringIISLogBatchItem() { LogUri = blobItem.Metadata. storageAccount. CloudBlobClient blobStore = new CloudBlobClient(baseUri.GenerateMeterWebAppBandwidthWork. List<MeteringIISLogBatchItem> blobUris = new List<MeteringIISLogBatchItem>().GetContainerReference(Settings.Uri.UseFlatBlobListing = true. LastLineProcessed = (timeProcesed == DateTime.MaxValue) { blobUris.BlobEndpoint. blob.

} queue.Format("Measure {0} logs in {1} batches.Stop(). string. numBatchesPrepared)).Add(IISLogBlobs[j + i]). result = true. "GenerateMeterWebAppBandwidthWork".Count.LogException(ex).Count. for (int j = 0. int numBatchesPrepared = 0. Helper.Enqueue(batchToProcess). } . } return result. List<MeteringIISLogBatchItem> IISLogBlobs = GetIISLogs().Elapsed.cs public override bool Execute() { « // access the work queue IISLogWorkQueue queue = (IISLogWorkQueue)this. "Execute".GenerateMeterWebAppBandwidthWork.".LogTaskCompletion(stopWatch. IISLogBlobs. } stopWatch. for (int i = 0. i = i + batchSize) { List<MeteringIISLogBatchItem> batchToProcess = new List<MeteringIISLogBatchItem>(). i < IISLogBlobs. numBatchesPrepared++.TotalSeconds.WorkQueue. j < batchSize && ((numBatchesPrepared * batchSize) + j) < IISLogBlobs. } catch (Exception ex) { Helper.Count. int batchSize = 5. j++) { batchToProcess.

requests += localAggregate[keyString].Keys) // Host header has the tenant ID.Dequeue().LogUri).requests.. ref lastLineProcessed). List<MeteringIISLogBatchItem> logFileBatch = queue. foreach (var key in localAggregate.cs_kbytes += localAggregate[keyString].cs_kbytes.cs public override bool Execute() { ..Today. ComputeBandwidthCounter> localAggregate = this. Timestamp.LastLineProcessed.sc_kbytes += localAggregate[keyString].Save(tenantIDString.WorkQueue. aggregate[keyString]. aggregate[tenantID].Keys) { « aggregate[keyString]. so the key { MeteringRecord. aggregate[keyString].sc_kbytes. Dictionary<string. « « foreach (MeteringIISLogBatchItem batchItem in logFileBatch) { lastLineProcessed = batchItem. isDatabaseBandwith: false)) « « .sc_kbytes.requests. « « « foreach (string tenantID in aggregate.ProcessMeteringWebAppBandwidth. // access the work queue IISLogWorkQueue queue = (IISLogWorkQueue)this.cs_kbytes. aggregate[tenantID]. aggregate[tenantID].ReadLogFileForBandwidth(new Uri(batchItem.

sys. direction " + "order by convert(varchar(8). direction. database_name. 1).bandwidth_usage.time.bandwidth_usage where time >= @time and class = 'External' and database_name <> 'master' " + "group by convert(varchar(8).bandwidth_usage. 1).sys.Consulta a la vista sys. sum(quantity) as kiloBytes " + "from sys. database_name. 1) as usage_day.bandwidth_usage.sys.time. database_name.time.bandwidth_usage en la BBDD master select convert(varchar(8). direction Microsoft Data-Center Transferencia ¼ C .

dm_db_partition_stats (*) Nota: * 8 El tamaño de página en SQL Server es 8 kb C .SELECT SUM(reserved_page_count)* 8 FROM sys.

CloudNinjaWeb Areas Operations C .

Column("WebAppBandwithUse_CS".Column(format: item => Html. "Web Requests").SnapshotTime)).Column("DatabaseSize". year=item. (DateTime)item.Column("DatabaseBandwidth_Ingress".Column("WebAppRequests". "Database size (kb)"). grid.ToString("N0")). for Blob (Mb)".Month.Column("TenantID". grid.ToString("N0")). (string)("Tenant/" + item.Column("WebAppBandwithUse_SC". grid.Column("DatabaseBandwidth_Egress".Column("TotalResponsePacketSize". <div id="grid"> @grid.SnapShotTime. "Database out .Column("BlobStoreUsage". "Blob size(kb)"). "Database in .TenantID). columns: grid. grid.TotalRequestPacketSize/1024/1024).TotalResponsePacketSize / 1024 / 1024). grid. for Blob (Mb)".GetHtml(tableStyle: "data-table".ActionLink("View". grid. grid. headerStyle: "head". "Tenant"). grid. alternatingRowStyle: "alt".Column("TotalStorageTransactions". grid.Int. "Snapshot time". selectionFieldName: "TenantID").SnapshotTime.Columns( grid. format: item => (item. (kb)").CloudNinjaWeb Index.Int. rowsPerPage: 50. (kb)"). format: item => (item. "Web out (kb)").cshtml Areas Operations Views @{ var grid = new WebGrid(Model. "Req. "Transactions for Blob"))) </div> } .Year})). "Web in (kb)"). new {month=item. "Resp. grid.Format("{0:MMM yyyy}".Column("SnapshotTime".Column("TotalRequestPacketSize". grid. format: (item) => String. grid.

} public long DatabaseSize { get.CloudNinjaWeb Areas MeteringController. string month) { if (string.Now.CurrentYear = year.Success && !match. set.TenantID).\d{1.Now.Year. ViewBag.Contains("www"). } )). return View(MeteringRecord. set. } public DateTime SnapshotTime { get. set. } public long WebAppRequests { get. } public long WebAppBandwithUse_CS { get. } public long TotalResponsePacketSize { get. } public long WebAppBandwithUse_SC { get.Month. } public long TotalRequestPacketSize { get. } public long DatabaseBandwidth_Egress { get. } « . } public long BlobStoreUsage { get.Parse(month)). set. set. Match regxMatch = regx. int. set. set. set.ToString().CurrentMonth = month.cs Operations Controllers public class MeteringController : Controller { public ActionResult Index(string year.Parse(year).IsNullOrEmpty(year)) { year = DateTime. return !regxMatch. set. } Obtiene una List<MeteringRecord> ViewBag. } « « MeteringRecord public string TenantID { get.\d{1.ToString().3}\.FindAll((match) => { Regex regx = new Regex(@"\b\d{1.3}\b").\d{1.Match(match. month = DateTime. set.3}\.GetAllTenantMonthly(int.TenantID.3}\. } public long DatabaseBandwidth_Ingress { get. } public int TotalStorageTransactions { get. set. set.

Cloud Ninja Portal Query storage metrics tables through Scheduler XRay Pump summary Worke log data to r role SQL

Query tables for storage metering

SQL Azure -Tenant databases -Control Database

XRay Lib

Read log blobs through Xray

Azure Storage -Tenant blobs -WAD Tables -Scheduling queues

Access company logo

Task App

C

http://cloudninja.codeplex.com/ http://www.thecloudninja.com

http://cloudsamurai.codeplex.com/

C

L

L .

L .

http://entlib.com/ L .codeplex.

L .

.

.

.

.

.

L .

Inc . C © 2010 Gartner./OS Infraestructura Infraestructura Infraestructura Infraestructura Infraestructura I Hosting Aislado II Hardware Compartido III IV Procesamiento BBDD Compartido Compartida IV Plataforma Compartida IV Aplicación Compartida* Elementos Multitenant *Existen modelos adicionales de aplicación compartida pero sin compartir datos. App. Tenant App. Tenant App. Tenant App. computación y/o infraestructura. SaaS Tenant Tenant Aplicación AP AP AP AP AP AP AP AP AP Datos Datos Datos Datos Datos Datos Datos Datos Datos Inf. Tenant App. Tenant App. PaaS Tenant Tenant App. Tenant Tenant App. App./OS Inf.IaaS Tenant App.

com C .msdn.msdn.panzano@microsoft.com Luis Panzano ISV Platform Strategy Advisor Microsoft División de Desarrolladores y Plataforma  Blog: http://blogs.com/cesardelatorre/ Email: cesardl@microsoft.César de la Torre Architect Evangelist Microsoft División de Desarrolladores y Plataforma  Blog: http://blogs.com/luispanzano/ Email: luis.

C .