In previous posts, we explored setting up a HomeLab Kubernetes Cluster, connecting it to a Load Balancer, and deploying a simple Java Spring Boot API to the Cluster.
This time, we’ll delve into creating and automatically deploying a simple .NET Core API in the similar ways. We’ll also utilize GitHub Actions to automate the deployment process to Kubernetes.
The following diagram illustrates the connection between your home network and the outside world with a relatively secure approach:
-
By connecting your computers through an internet firewall instead of directly to your ISP’s modem (or router), you add a layer of security.
-
Each ISP’s router configuration and port forwarding settings are different, so you’ll need to access your router’s management UI and locate the Port Forwarding menu to configure it.
-
Following the firewall, the connection passes through a Proxy (in here Nginx Proxy Manager as covered previously) and then connects to the Load Balancer we set up in the previous article.
The Simplest gRPC API
Here’s the modified code from the default .NET Core gRPC Service project (with TLS certificate implementation, as gRPC requires HTTP2 by default):
- Program.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System.Reflection;
using api.grpc.cavecafe.app;
using api.grpc.cavecafe.app.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc( options =>
{
options.EnableDetailedErrors = true;
});
builder.WebHost.ConfigureKestrel((context, options) =>
{
var assembly = Assembly.GetExecutingAssembly();
var assemblyDir = Path.GetDirectoryName(assembly.Location);
Console.WriteLine($"Assembly Dir: {assemblyDir}");
var kestrel = builder.Configuration.GetSection("Kestrel");
ServiceHelper.ShowAppConfig(kestrel);
options.Configure(kestrel);
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseRouting();
app.MapGrpcService<GreeterService>();
app.MapGet("/",
() =>
"Communication with gRPC endpoints must be made through a gRPC client.\n" +
"To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909"
);
app.Run();
- ServiceHelper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System.Collections;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace api.grpc.cavecafe.app;
public abstract class ServiceHelper
{
private static X509Certificate2 CreateFromPublicPrivateKeyFiles(string fullchainPemFile, string privkeyPemFile)
{
byte[] publicPemBytes = File.ReadAllBytes(fullchainPemFile);
using var publicX509 = new X509Certificate2(publicPemBytes);
var privkeyPem = File.ReadAllText(privkeyPemFile);
var privateKeyBlocks = privkeyPem.Split("-", StringSplitOptions.RemoveEmptyEntries);
var privateKeyBytes = Convert.FromBase64String(privateKeyBlocks[1]);
return GetRsaKeyPair(privateKeyBlocks, privateKeyBytes, publicX509);
}
private static X509Certificate2 GetRsaKeyPair(string[] privateKeyBlocks, byte[] privateKeyBytes,
X509Certificate2 publicX509)
{
using RSA rsa = RSA.Create();
if (privateKeyBlocks[0] == "BEGIN PRIVATE KEY")
{
rsa.ImportPkcs8PrivateKey(privateKeyBytes, out _);
}
else if (privateKeyBlocks[0] == "BEGIN RSA PRIVATE KEY")
{
rsa.ImportRSAPrivateKey(privateKeyBytes, out _);
}
else
{
throw new InvalidOperationException("Invalid private key format");
}
X509Certificate2 keyPair = publicX509.CopyWithPrivateKey(rsa);
return keyPair;
}
private static X509Certificate2 CreateFromPublicPrivateKey(string fullchainBase64, string privkeyBase64)
{
byte[] publicPemBytes = Convert.FromBase64String(fullchainBase64);
using var publicX509 = new X509Certificate2(publicPemBytes);
var privkeyPem = Encoding.UTF8.GetString(Convert.FromBase64String(privkeyBase64));
var privateKeyBlocks = privkeyPem.Split("-", StringSplitOptions.RemoveEmptyEntries);
var privateKeyBytes = Convert.FromBase64String(privateKeyBlocks[1]);
return GetRsaKeyPair(privateKeyBlocks, privateKeyBytes, publicX509);
}
}
- greet.proto
- Dockerfile
- manifest.yml (preparation for namespace, secrets, services, ingress before app build and deployment)
- deployment.yml
Finally, to start the GitHub Actions event, prepare the following GitHub Action YAML file.
- .github/workflows/build-deploy.yml
Pushing the above files to the GitHub repository will automatically trigger the connected GitHub Action CI Runner, which will complete all the steps and deploy the application to the cluster.
As defined in the gRPC proto file, you can verify that the response to the API request has come out as follows.
We have briefly reviewed the process of creating an ASP.NET Core application, building a Docker image according to the desired service specifications, and automatically deploying it to a Kubernetes cluster. We just covered very minimum implementation of CI/CD except for QA validation automation.
Conclusion
Through this series of posts, we’ve explored the potential of utilizing Kubernetes, a powerful container orchestration platform, even within the confines of personal computers and home internet connections. While often associated with large-scale deployments by enterprises, Kubernetes can be a valuable tool for individual developers and smaller teams seeking to experiment, learn, and build scalable applications.
Our exploration has revealed that Kubernetes can be readily implemented on personal hardware, offering a cost-effective and accessible learning environment. This empowers individuals to gain hands-on experience with containerization and orchestration, fostering valuable skills for modern software development.
Furthermore, the availability of Kubernetes plugins within various development tools enhances the development experience by enabling seamless monitoring and management of Kubernetes clusters directly within the IDE. This integration streamlines the workflow and provides developers with a comprehensive view of their applications and infrastructure.
In today’s cloud-centric landscape, Kubernetes stands as a versatile platform that transcends the boundaries of large corporations. Its adoption by cloud providers underscores its significance and paves the way for organizations to build robust and adaptable infrastructure independent of proprietary cloud services.
Using Kubernetes, even on a personal level, developers empower themselves to learn, experiment, and contribute to the growing ecosystem of containerized applications. This journey not only enhances our technical expertise but also positions us to leverage the power of Kubernetes in building the future of software development.
My HomeLab Cluster currently houses eight operational services and websites, a testament to the rapid progress of my learning and hobby project journey. With continued exploration, this number is poised for exponential growth.