Slides from the talk, "From Zero to Cloud in 12 Easy Factors", presented by Jatin Naik and Ed King at CF Summit 2018.
https://www.youtube.com/watch?v=3ziP2wIbNXo
2. Fire Exit Announcement
Please note the locations of the surrounding emergency exits & located the nearest lit EXIT sign to you
In the event of a fire alarm or other emergency, please calmly exit to the public concourse area
Emergency exit stairwells leading to the outside of this facility are located along the public concourse
For your safety in an emergency, please follow the directions of the Public Safety Staff
3. Ed King - Pivotal
@edking2
Jatin Naik - Pivotal
@tinygrasshopper
7. Current status
> Deployed on a shared IIS instance
> Uses the local filesystem for storage
> Risky, error-prone deployments
> Strict networking requirements
> Unable to scale effectively
> Not ready for the cloud
8. The 12 Factors
> A methodology for building Software as a Service applications
> Originated from engineers working at Heroku
> A focus on stateless applications suitable for modern cloud platforms
10. Codebase
What
> Each codebase must be stored in version control
> One-to-one relationship between application and codebase
> One-to-many relationship between codebase and deploys
Why
> To support collaboration between developers and teams
> To enable proper versioning
How
> Git, Subversion, Mercurial, etc.
12. Build, Release, Run
What
> Strictly separate the build, release and run stages
Why
> To prevent changes to code and/or configuration at runtime …
> … which in turn lowers the number of unexpected failures
How
> cf push
13.
14. Dependencies
What
> Explicitly declare and isolate application dependencies
> Use a declaration manifest that’s stored with the codebase
> Use a dependency isolation tool during application execution
Why
> Provides consistency between dev/prod environments
> Simplifies setup for developers new to the application
> Supports portability between cloud platforms
How
> Nuget
20. # Program.cs
namespace slogram
{
public class Program
{
public static void Main(string[] args)
{
var hostBuilder = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseUrls("http://0.0.0.0:9000")
.Build();
hostBuilder.Run();
}
}
}
hardcoded config
21. Config
What
> Ensure strict separation between code and configuration
> Store configuration in environment variables
Why
> Modify application behaviour without making code changes
> Simplifies deployment to multiple environments
> Reduce risk of leaking credentials and passwords
How
> Extract all application config to environment variables
> cf set-env
22. # Startup.cs
namespace slogram
{
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{ env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
Configuration = configuration;
}
}
}
config from env
28. Processes
What
> Execute the app as one or more stateless processes
> Ensure all stateful data is stored in a backing service
Why
> Reduce deployment complexity
> Reduce operational complexity
> Allow for much simpler scaling
How
> Store any stateful data in backing services
> Do not use sticky sessions or the local filesystem!
29. Backing Services
What
> Treat backing services (E.g. MySQL) as attached resources
> Make no distinction between local and third party services
> Attach resources via URLs stored in the app’s config
Why
> Provides loose coupling between service and deployment
> No code changes required to update backing services
> Allows operators to swap services out as they see fit
How
> cf marketplace, cf create/bind-service
30. Scaling concerns
1. Local database -> injected database service (MySQL)
2. Local storage -> remote storage (azure blobstore)
3. Data protection keys -> store in azure blobstore
31. # Startup.cs
namespace slogram
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<SlogramContext>(options =>
options.UseSqlite("Data Source=slogram.db”));
}
// …
}
using local filesystem
hardcoded config
Local database -> injected database service (MySQL)
32. public static void ProcessImage(int photoId, string rootPath)
{
using (var db = new MvcPhotoContext())
{
var photo = db.Find<Photo>(photoId);
var savedImageFullPath = Path.Combine(Path.Combine(rootPath, photo.RawUrl));
var processedFullPath = Path.Combine(Path.Combine(rootPath, photo.ProcessedUrl));
using (Image<Rgba32> image = Image.Load(savedImageFullPath))
{
image.Mutate(ctx => ctx.Polaroid());
image.Save(processedFullPath);
}
// …
}
}
using local filesystem
hardcoded config
Local storage -> remote storage (azure blobstore)
33. Data protection keys -> store in azure blobstore
%LOCALAPPDATA%ASP.NETDataProtection-Keys
37. public class MvcPhotoContext : DbContext
{
public DbSet<slogram.Models.Photo> Photo { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var configuration = new ConfigurationBuilder()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddEnvironmentVariables()
.AddCloudFoundry()
.Build();
optionsBuilder.UseMySql(configuration);
}
}
38. namespace slogram.Adapters
{
public static class Azure
{
public static CloudBlockBlob Blob(string blobpath)
{
var cloudStorageAccount =
CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("storageconnectionstring"));
var cloudBlobClient = cloudStorageAccount.CreateCloudBlobClient();
var container = cloudBlobClient.GetContainerReference("slogramblobcontainer");
return container.GetBlockBlobReference(blobpath);
}
}
}
39. # Startup.cs
namespace slogram
{
public class Startup
{
public Startup(IConfiguration configuration)
{
// …
services.AddDataProtection()
.PersistKeysToAzureBlobStorage(Azure.Blob("encrypt_token"));
// …
}
}
}
40.
41. Disposability
What
> Maximise robustness via fast startup and graceful shutdown
> Treat app instances as cattle, not pets
> Be resilient to unexpected and sudden shutdowns (SIGKILL)
Why
> To facilite fast, elastic scaling
> To allow for rapid deployment of new code/config changes
> Provide robustness in production deployments
How
> Minimise work on startup, don’t depend on shutdown hooks
42. Concurrency
What
> Scale out via the process model
> Treat processes as first class citizens
> Do not daemonize processes or attempt to write PID files
Why
> Allows for easy, horizontal scaling
How
> Split app into separate, runnable processes
> Scale independently
46. using System;
using System.Threading;
using Hangfire;
namespace worker
{
class Program
{
static void Main(string[] args)
{
GlobalConfiguration.Configuration.UseSqlServerStorage(“....”);
using (var server = new BackgroundJobServer())
{
Thread.Sleep(Timeout.Infinite);
}
}
}
}