Skip to content

Latest commit

 

History

History
447 lines (353 loc) · 12.5 KB

6. Production Readiness and Deployment.md

File metadata and controls

447 lines (353 loc) · 12.5 KB

Production Readiness and Deployment

Adding EF Healthchecks to the BackEnd

  1. Add a package reference to Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore version 2.2.0.
  2. Add a DbContext health check in Startup.cs by adding the following code to ConfigureServices:
    services.AddHealthChecks()
            .AddDbContextCheck<ApplicationDbContext>();
  3. Add the health checks middleware by modifying Configure to call app.UseHealthChecks("/health");. This will configure the health checks on the /health end point.
  4. Test the end point by navigating to /health, it should return the text Healthy.

Adding EF Healthchecks to the FrontEnd

  1. Add a package reference to Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore version 2.2.0.
  2. Add a DbContext health check in Startup.cs by adding the following code to ConfigureServices:
    services.AddHealthChecks()
            .AddDbContextCheck<IdentityDbContext>();
  3. Add the health checks middleware by modifying Configure to call app.UseHealthChecks("/health");. This will configure the health checks on the /health end point.
  4. Test the end point by navigating to /health, it should return the text Healthy.

Adding a custom health check to test for BackEnd availability

  1. Add a Task<bool> CheckHealthAsync() method to IApiClient.
  2. Implement the CheckHealthAsync method in ApiClient by adding the following code:
    public async Task<bool> CheckHealthAsync()
    {
        try
        {
            var response = await _httpClient.GetStringAsync("/health");
    
            return string.Equals(response, "Healthy", StringComparison.OrdinalIgnoreCase);
        }
        catch
        {
            return false;
        }
    }
  3. Create a HealthChecks folder under the root folder in the FrontEnd project.
  4. Create a file called BackendHealthCheck.cs with the following implementation:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using FrontEnd.Services;
    using Microsoft.Extensions.Diagnostics.HealthChecks;
    
    namespace FrontEnd.HealthChecks
    {
        public class BackendHealthCheck : IHealthCheck
        {
            private readonly IApiClient _client;
    
            public BackendHealthCheck(IApiClient client)
            {
                _client = client;
            }
    
            public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default(CancellationToken))
            {
                if (await _client.CheckHealthAsync())
                {
                    return HealthCheckResult.Healthy();
                }
    
                return HealthCheckResult.Unhealthy();
            }
        }
    }
  5. Register the BackendHealthCheck in ConfigureServices:
    services.AddHealthChecks()
            .AddCheck<BackendHealthCheck>("backend")
           .AddDbContextCheck<IdentityDbContext>();
  6. Test the end point by navigating to /health, it should return the text Healthy.

Publishing locally

https://docs.microsoft.com/en-us/aspnet/core/publishing/#publish-to-a-folder

Publishing to Production (Azure App Service)

Publishing using Docker

Prerequisites

Using Visual Studio

Add Docker support to the BackEnd project by right clicking the project file and selecting Add > Docker Support

A new project is added to the solution docker-compose.dcproj containing the following files.

  • .dockerignore
  • docker-compose.yml
  • docker-compose.override.yml

A Dockerfile is also added to the BackEnd project.

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /src
COPY ConferencePlanner.sln ./
COPY BackEnd/BackEnd.csproj BackEnd/
COPY ConferenceDTO/ConferenceDTO.csproj ConferenceDTO/
RUN dotnet restore -nowarn:msb3202,nu1503
COPY . .
WORKDIR /src/BackEnd
RUN dotnet build -c Release -o /app

FROM build AS publish
RUN dotnet publish -c Release -o /app

FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "BackEnd.dll"]

Repeat the same step for the FrontEnd project. The Dockerfile is added to the project for it.

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /src
COPY ConferencePlanner.sln ./
COPY FrontEnd/FrontEnd.csproj FrontEnd/
COPY ConferenceDTO/ConferenceDTO.csproj ConferenceDTO/
RUN dotnet restore -nowarn:msb3202,nu1503
COPY . .
WORKDIR /src/FrontEnd
RUN dotnet build -c Release -o /app

FROM build AS publish
RUN dotnet publish -c Release -o /app

FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "FrontEnd.dll"]

The docker-compose.yml file is updated to reflect that there are two projects to build images.

version: '3'

services:
  backend:
    image: backend
    build:
      context: .
      dockerfile: BackEnd/Dockerfile

  frontend:
    image: frontend
    build:
      context: .
      dockerfile: FrontEnd/Dockerfile

Linking / Networking

In the previous architecture, there were some setting and manual orchestration to start up and wire the applications together.

  • BackEnd tested for OS type and set the application to use SQL Server or SQLite
  • FrontEnd application had the API url set in the appsetting.json file.
  • BackEnd hard coded the url:port - http://localhost:56009

Using Docker, adding a container for SQL Server and linking the containers in the compose file simplifies this.

Adding SQL Server

Open the docker-compose.yml file and add the following entry. Note the $ is doubled for escaping

  db:
    image: "microsoft/mssql-server-linux"
    environment:
      SA_PASSWORD: "ConferencePlanner1234$$"
      ACCEPT_EULA: "Y

Since the BackEnd application must have connectivity and cannot start until the database container is ready. Add the depends_on entry to the backend definition in the compose file.

  backend:
    image: backend
    build:
      context: .
      dockerfile: BackEnd/Dockerfile
    depends_on:
      - db

Finally, change the connection string for the database in the BackEnd\appsettings.json file.

  "ConnectionStrings": {
    "DefaultConnection": "Server=db;Initial Catalog=ConferencePlanner;User=sa;Password=ConferencePlanner1234$;MultipleActiveResultSets=true"
  }
Linking / Networking FrontEnd & BackEnd

In the docker-compose.yml file, add the links section to the frontend definition. This sets up the host name in the Docker networking allowing for the web application to call the API by name. http://backend

  frontend:
    image: frontend
    build:
      context: .
      dockerfile: FrontEnd/Dockerfile
    links:
      - backend

Change the value for the ServiceUrl in FrontEnd/appsetting.json

{
  "ServiceUrl": "http://backend/",

Remove or comment out the .UseUrls(http://localhost:56009) in BackEnd\Program.cs.

Finally open the docker-compose.override.yml file. Change the ports for the backend entry to 56009:80 and for the frontend, make it 5001:80

version: '3'

services:
  backend:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
    ports:
      - "56009:80"

  frontend:
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
    ports:
      - "5001:80"

By changing these values, the API can still be hit via http://localhost:56009, allowing for testing using the swagger UI. The FrontEnd web application will run on http://localhost:5001, however communicates to the API via internal networking hostname backend.

Starting and Debugging

Once the changes are complete, F5 to build start the application in Docker. Debugging is still available in all projects, but now the application is running in containers.

Changes can be made to Razor pages and seen immediately without rebuilds, however and *.cs file changes require rebuilds.

Using VS Code or other editors

Create and add the following Dockerfile for the BackEnd application.

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /src
COPY ConferencePlanner.sln ./
COPY BackEnd/BackEnd.csproj BackEnd/
COPY FrontEnd/FrontEnd.csproj FrontEnd/
COPY ConferenceDTO/ConferenceDTO.csproj ConferenceDTO/
RUN dotnet restore
COPY . .
WORKDIR /src/BackEnd
RUN dotnet build -c Release -o /app

FROM build AS publish
RUN dotnet publish -c Release -o /app

FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "BackEnd.dll"]

Create and add the following Dockerfile for the BackEnd application.

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build
WORKDIR /src
COPY ConferencePlanner.sln ./
COPY BackEnd/BackEnd.csproj BackEnd/
COPY FrontEnd/FrontEnd.csproj FrontEnd/
COPY ConferenceDTO/ConferenceDTO.csproj ConferenceDTO/
RUN dotnet restore
COPY . .
WORKDIR /src/FrontEnd
RUN dotnet build -c Release -o /app

FROM build AS publish
RUN dotnet publish -c Release -o /app

FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "FrontEnd.dll"]

At the root of the ConferencePlanner solution, add the following docker-compose.yml file.

version: '3'

services:
  backend:
    image: backend
    build:
      context: .
      dockerfile: BackEnd/Dockerfile
    ports:
      - "56009:80"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
    depends_on:
      - db

  frontend:
    image: frontend
    build:
      context: .
      dockerfile: FrontEnd/Dockerfile
    ports:
      - "5002:80"
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
    links:
      - backend

  db:
    image: "microsoft/mssql-server-linux"
    environment:
      SA_PASSWORD: "ConferencePlanner1234$$"
      ACCEPT_EULA: "Y"

SQL Server Configuration

Change the connection string for the database in the BackEnd\appsettings.json file.

  "ConnectionStrings": {
    "DefaultConnection": "Server=db;Initial Catalog=ConferencePlanner;User=sa;Password=ConferencePlanner1234$;MultipleActiveResultSets=true"
  }

Linking / Networking FrontEnd & Backend

Change the value for the ServiceUrl in FrontEnd/appsetting.json

{
  "ServiceUrl": "http://backend/",

Remove or comment out the .UseUrls(http://localhost:56009) in BackEnd\Program.cs.

Building and Running

  • Build the Docker images docker-compose build
  • Run docker-compose up -d to start the application
  • Run docker-compose down to stop the application
  • Open the application at http://localhost:5002

Bonus

Helm Chart

A helm chart is located in /save-points/6-Deployment-docker/helm allowing for deployment to a Kubernetes cluster using helm install ./workshop.

values.yaml

Here values for the service and deployment templates are defined. Docker images and tags should be updated to your repository name in the image and tag fields for each part of the application prior to deplyoyment.

db:
  replicaCount: 1
  name: db
  internalPort: 55555
  externalPort: 55555
  image: microsoft/mssql-server-linux
  tag: latest
  pullPolicy: IfNotPresent
  eula: ACCEPT_EULA
  eulaValue: \Y
  sa: SA_PASSWORD
  saValue: ConferencePlanner1234$
  restart: Always

frontend:
  replicaCount: 1
  name: frontend
  internalPort: 80
  externalPort: 80
  image: frontend
  tag: latest
  environment: Production
  pullPolicy: IfNotPresent
  restart: Always

backend:
  replicaCount: 1
  name: backend
  internalPort: 80
  externalPort: 80
  image: backend
  tag: latest
  environment: Production
  pullPolicy: IfNotPresent
  restart: Always

Next: Session #7 - Challenges | Previous: Session #5 - Add Agenda