Skip to content

Blog

A simple testing mail server

If you need a quick mail server to test your mail logic works without actually sending these test emails, I suggest using Mailhog as it will not only swallow your test emails but it also provides a simple UI to see the messages you've sent.

To get it up and running requires one simple Docker command:

docker run -p 25:1025 -p 8025:8025 -d --restart always --name mailhog mailhog/mailhog

With this, you can send mail on port 25 via SMTP and view the dashboard at http://localhost:8025/ and see any mail you've received.

Using NGINX Ingress Controller with MetalLB

This document describes getting the NGINX Ingress Controller (the community version rather than the one from NGINX Inc) to work easily with MetalLB.

For the purposes of this guide, it's assumed that MetalLB is set up. In the example, we'll assume it's using an IP range of 192.168.10.x

Firstly, we'll download the manifest so we can make a change:

curl -o ingress-nginx.yaml https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.43.0/deploy/static/provider/baremetal/deploy.yaml

Once downloaded, we need to edit the file:

nano ingress-nginx.yaml

We now need to find the service that handles the ingress so press Ctrl+W and search for:

name: ingress-nginx-controller

Next, replace the line type: NodePort with the following:

  type: LoadBalancer
  loadBalancerIP: 192.168.10.254

You can use any IP within the MetalLB range but it's recommended you specify one to prevent it somehow changing in the future.

Another change you can make is update the deployment so it's a daemon set rather than a standard deployment. To do this, replace the line:

kind: Deployment

With:

kind: DaemonSet

When you're finished, save the document using control+s and then quit using control+x

Finally, we'll install everything by running the following command:

kubectl apply -f ingress-nginx.yaml

The ingress controller, once an ingress is configured, will be accessible on http(s)://192.168.10.254/

Getting a local Seq instance up and running

To get Seq up and running locally, the quickest way is using Docker. For our example, we'll create a directory called SeqData on our D drive and then start a container pointing at this folder. We'll expose the UI on port 8090 and the ingest API on it's default port of 5341.

If you require a different folder location or to use different ports, adjust as needed.

If you're using WSL2 (which I recommended), you shouldn't need to set up “Shared Drives” in advance.

Run the following commands:

mkdir D:\SeqData
docker run -e ACCEPT_EULA=Y --name seq -d --restart always -p 8090:80 -p 5341:5341 -v D:/SeqData:/data datalust/seq:latest

You should now be able to access the UI on http://localhost:8090/ and you should point your logging source (e.g. your Serilog sink) at http://localhost:5341.

Adding Serilog to an application and pointing it at Seq

Firstly, let's add all the NuGet packages we'll need:

  • Serilog.AspNetCore
  • Serilog.Enrichers.Environment
  • Serilog.Enrichers.Process
  • Serilog.Enrichers.Thread
  • Serilog.Settings.Configuration
  • Serilog.Sinks.Seq

Create a new file called serilog.json and populate it with the following, replacing the serverUrl and apiKey (or completely removing that element if not required).

{
  "Serilog": {
    "Using": [],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId", "WithThreadId" ],
    "WriteTo": [
      {
        "Name": "Seq",
        "Args": {
          "serverUrl": "http://seq.yourdomain.com:5341",
          "apiKey": "YourApiKey"
        }
      },
      { "Name": "Console" }
    ]
  }
}

Next we'll modify Main in Program.cs. If all your Main methods contains is CreateHostBuilder(args).Build().Run(); then you can simply replace the contents with the following:

var loggingConfiguration = new ConfigurationBuilder()
    .AddJsonFile("serilog.json")
    .AddEnvironmentVariables()
    .Build();

Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(loggingConfiguration)
    .CreateLogger();

try
{
    Log.Information("Application starting up");
    CreateHostBuilder(args).Build().Run();
}
catch(Exception ex)
{
    Log.Fatal(ex, "Application start failed");
}
finally
{
    Log.CloseAndFlush();
}

Then, in the CreateHostBuilder method, after Host.CreateDefaultBuilder(args) add the following:

.UseSerilog()

Finally, if you want to log requests, in the Configure method of Startup.cs, before app.UseRouting(), add the following line:

app.UseSerilogRequestLogging();

If you encounter any missing usings, use Ctrl+. and add them as needed.

Don't forget to inject and use an ILogger instance of your controllers to log errors, warnings, etc…

The last thing to do is remove the Logging section from appsettings.json.

If you want to override the API key with an environment variable, for example as part of a deployment process, you would use the following variable name:

Serilog__WriteTo__0__Args__apiKey

If you want to exclude health checks from request logging, Andrew Lock's blog has a great solution. I've packaged this in a NuGet package, Serilog.Extras, along with a couple of enrichers that I required. Note this package is targeting .NET 6.

Once you've referenced the above NuGet package, replace:

app.UseSerilogRequestLogging();

With the following:

app.UseSerilogRequestLoggingWithoutHealthChecks();

You'll also need to add a using for Serilog.Extras to enable this extension.

The two enrichers are WithVersionNumber and WithDeploymentEnvironment which add the values of the environment variables VERSION_NUMBER and ASPNETCORE_ENVIRONMENT respectively to your output logs.

Seq on Kubernetes

This guide will tell you how to set up an HTTPS protected sec instance. This is using LetsEncrypt with cert-manager to get an SSL certificate so it assumes your Seq instance is public facing. It also uses Longhorn for storage.

First, create a ClusterIssuer:

mkdir seq
cd seq
nano certmanager.yaml

Then insert the following:

apiVersion: cert-manager.io/v1alpha3
kind: ClusterIssuer
metadata:
  name: letsencrypt-seq
  namespace: cert-manager
spec:
  acme:
    # The ACME server URL
    server: https://acme-v02.api.letsencrypt.org/directory
    # Email address used for ACME registration
    email: your.email@yourdomain.com
    # Name of a secret used to store the ACME account private key
    privateKeySecretRef:
      name: letsencrypt-seq
    # Enable the HTTP-01 challenge provider
    solvers:
    - http01:
        ingress:
          class: nginx

Don't forget to update your email address and then Ctrl+s and Ctrl+x to save and exit.

Once that's done, we'll create another file called config.yaml and save that. Populate it as follows, replacing with your domain in the three necessary places:

control# config.yaml
ingress:
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: "letsencrypt-seq"
  tls:
    - hosts:
        - seq.yourdomain.com
      secretName: letsencrypt-seq

baseURI: https://seq.yourdomain.com

ui:
  ingress:
    enabled: true
    path: /
    hosts:
      - seq.yourdomain.com

persistence:
  storageClass: longhorn

resources:
  limits:
    memory: 4Gi

baseURI: https://seq.yourdomain.com

Once saved, we'll install the instance using Helm.

helm install -f config.yaml seq datalust/seq

After about a minute, you should be able to access Seq. I highly recommend turning on user authentication immediately.