You must have noticed how Docker and containers is playing a more and more important part in .NET development. Docker support is everywhere, so it should be easy to build solutions based on container technology, right? But, it takes a bit more to architect and create a .NET solution that use Docker at its core. Many questions arise: How do you design a solution architecture that fits well with containers? Would I use .NET or .NET Core? What is a proper way to migrate to such an architecture? What changes in the .NET implementation from pre-Docker solutions with micro-services? Where do container orchestrators fit in and how do I build and deploy my solutions on a Docker container cluster, such as Azure Kubernetes Service?
These and many other questions will be answered in this session. You will learn how to design and architect your .NET solutions and get a flying start to create, build and run Docker-based containerized applications.
3. 3
Agenda
A story on creating .NET applications
using container technology
Featuring Docker and Visual Studio
With highlights and some emphasis on .NET Core
17. 17
VS2019 container building
Multi-stage Dockerfile per project
Can be built without VS2019
FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base
WORKDIR /app
EXPOSE 13995
EXPOSE 44369
…
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "Leaderboard.WebAPI.dll"]
20. 20
Build pipeline in Azure DevOps
Contains steps to:
Docker images need to be
created on correct host OS
Produce artifacts to be used
during deployment
21. 21
Release pipelines in Azure DevOps
Create deployment file to apply on cluster
Elaborate scenarios involve multiple environments
22. Demo: Lifecycle
A new development lifecycle
for Docker based solutions
Visual Studio 2019
Azure DevOps
27. 27
Container cluster
Examples of external resources
Solution composed of
multiple containers
Different compositions
per environment
Web
Frontend
Web API
Composing
.NET solutions
Backend service
32. 32
Composing docker-compose files
Order of files matters:
last one wins
docker-compose
-f "docker-compose.yml"
-f "docker-compose.override.yml"
-p composition up -d
34. 34
Environment:
e.g. developer laptop,
staging or production cluster
Environments everywhere
Container images are immutable
Environments are not same
docker run –it –-env key=value alexthissen/gamingwebapp
Container image
{
"ConnectionString": "Server=tcp:…",
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
appsettings.json
35. 35
Working with environments in .NET Core
Bootstrapping environment variables
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
}
ENV variablesSettings files
36. 36
Docker-compose file
development.yml
production.yml
For .NET 4.7.1+ use similar strategy with new
ConfigurationBuilders
Environmental variables overrides
Non-container development
environment:
- ASPNETCORE_ENVIRONMENT=Development
- ASPNETCORE_URLS=http://0.0.0.0:1337
- ConnectionString=Server=sql.data;…
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://0.0.0.0:80
- ConnectionString=DOCKERSECRETS_KEY
{
"ConnectionString": "Server=tcp:…",
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
} appsettings.json
37. 37
Base compositions
• Services
• Images using registry names
• Dependencies between
services
• Networks
Composing for different environments
Environmental overrides:
• Port mappings
• Endpoint configurations
• Non-production services
SQL Server
on Linux
Redis
cache
Azure SQL
Database
Azure
Cache
38. 39
public class HomeController : Controller
{
private readonly IOptionsSnapshot<WebAppSettings> settings;
// Inject snapshot of settings
public HomeController(IOptionsSnapshot<WebAppSettings> settings) {
this.settings = settings;
}
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.Configure<WebAppSettings>(Configuration);
services.AddMvc();
} public class WebAppSettings {
public string BaseURL …
public int MaxRetryCount …
}
41. 42
Configuration
Dealing with secrets in configuration
Where would you leave sensitive data?
Production
appsettings.
development.json
Cluster secrets
docker-
compose.debug.yml
docker-
compose.production.yml
User Secrets Azure KeyVault
appsettings.
secrets.json
Connection
strings
Passwords
appsettings.
secrets.json
42. 43
Managed by and distributed across cluster
echo "azurekeysecret" | docker secret create azurekeyvault_secret –
docker secret ls
Specific to cluster orchestrator
Other orchestrators have similar mechanisms
e.g. kubectl create secret
docker-compose.production.yml
var builder = new ConfigurationBuilder()
// ...
.AddEnvironmentVariables()
.AddDockerSecrets();
services:
leaderboardwebapi:
secrets:
- azurekeyvault_secret
secrets:
azurekeyvault_secret:
external: true
43. 44
Reading secrets from Azure Key Vault
Available from IConfiguration in ASP.NET Core
Key and secret names
Connecting to KeyVault securely
if (env.IsProduction())
{
builder.AddAzureKeyVault(
$"https://{Configuration["azurekeyvault_vault"]}.vault.azure.net/",
Configuration["azurekeyvault_clientid"],
Configuration["azurekeyvault_clientsecret"]);
Configuration = builder.Build();
}
44. Demo: Secrets
Creating and reading secrets
in a Kubernetes cluster
Mounting secret settings in volume
Reading individual secrets
Read from Azure KeyVault
46. 47
Resiliency: design for failure
Determine your strategy for resiliency
Focus on dependencies outside of your own container
Cloud and containers:
Transient errors
are a fact,
not a possibility
47. 48
Dealing with transient faults
Containers will crash or be terminated
External dependencies might not be available
48. 49
Built-in Retry mechanisms
Most Azure services and client
SDKs include retry mechanism
Patterns and Practices
TFH Application Block (.NET FX)
Roll your own for container
connectivity
services.AddDbContext<LeaderboardContext>(options => {
string connectionString =
Configuration.GetConnectionString("LeaderboardContext");
options.UseSqlServer(connectionString, sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
});
49. 50
Fault handling policies
Use policy configurations
Centralize policies in registry
Policy.Handle<HttpRequestException>().WaitAndRetryAsync(
6, // Number of retries
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
(exception, timeSpan, retryCount, context) => { // On retry
logger.LogWarning(
"Retry {Count} at {ExecutionKey} : {Exception}.",
retryCount, context.ExecutionKey, exception);
})
53. 54
Challenges for cluster hosted apps
Keeping entire system running
Determine state of entire system and intervene
How to know health status of individual services?
Collecting/correlating performance and health data
Sentry.ioRaygun.io RunscopeNewRelic AlertSite DataDogAppMetrics Azure Monitor
54. 55
ASP.NET Core application
/api/v1/… Middle
ware
Integrating health checks
New in .NET Core 2.2
Bootstrap health checks in
ASP.NET Core app
services.AddHealthChecks();
endpoints.MapHealthChecks("/health);
/health DefaultHealthCheckService
Microsoft.Extensions.Diagnostics.HealthChecks
.Abstractions
.EntityFramework
Microsoft.AspNetCore.Diagnostics.HealthChecks
56. 57
Probing containers to check for availability and health
Readiness and liveness
Kubernetes node
Kubernetes node
Kubernetes nodes
Containers
Readiness
Liveliness
57. 58
Implementing readiness and liveliness
1. Add health checks with tags
2. Register multiple endpoints
with filter using
Options predicate
/api/v1/…
/health
/health/ready
/health/lively
app.UseHealthChecks("/health/ready",
new HealthCheckOptions() {
Predicate = reg => reg.Tags.Contains("ready")
});
services.AddHealthChecks()
.AddCheck<CircuitBreakerHealthCheck>(
"circuitbreakers",
tags: new string[] { "ready" });
app.UseHealthChecks("/health/lively",
new HealthCheckOptions() {
Predicate = _ => true
});
58. 59
Zero downtime deployments
Original pods only taken offline after new healthy one is up
Allows roll forward upgrades: Never roll back to previous version
61. 62
Docker containers in same network are connected
Drivers determine network type
Docker networks
Bridge network 172.17.0.0/16
Host IP 172.31.1.10
62. 63
Hosts may
span across
cloud
providers
Docker networks: overlay in Swarm
Overlay network 10.0.0.0/24
Host IP 172.31.1.10 Host IP 192.168.2.13
docker_gwbridge 172.18.0.0/16 docker_gwbridge
64. 65
Composing networks
By default single network is created
Create user-defined networks for
network isolation
Backend
network
Frontend network
Web API
Web App
SQL
Server