This context provides a guide on how to create a CronJob in Kubernetes using .NET, emphasizing the benefits of this approach, such as avoiding job concurrency, facilitating monitoring, and centralizing jobs in the same Docker image and Git repository.
Abstract
The article discusses the advantages of using a CronJob in Kubernetes (K8s) for .NET applications, as opposed to other libraries like Quartz or Hangfire. It highlights the benefits of preventing concurrent job execution, utilizing Kubernetes' monitoring ecosystem, and centralizing jobs for easier maintenance and streamlined development. The author demonstrates how to implement a background processor using the "Strategy" design pattern, with an example of a job that calls an endpoint and logs its health check status. The guide also covers testing strategies and deploying the CronJob using a Helm chart.
Opinions
The author assumes the reader's familiarity with CronJob K8s and its benefits, focusing on the development aspect rather than delving into specifics.
The author prefers using the .NET Runtime Docker image instead of the ASP.NET image to prove that a CronJob can be built with minimal dependencies.
The author recommends using the "Strategy" design pattern to execute only the necessary code associated with the scheduled job in the CronJob, which can reduce the workload when adding future processes.
The author suggests using an abstract class to incorporate common log features for each job, although it is not mandatory.
The author advises injecting the strategy pattern into the "Program.cs" Main function to retrieve environment variables, including the job type for execution, to ensure accurate flagging of job success or failure.
The author recommends maintaining the project's structure by using an architecture test (using ArchUnitNET) to ensure that all implementations of JobProcessorBase reside in the same namespace.
The author emphasizes the importance of adding a monitoring layer to ensure that jobs are running smoothly after deploying the CronJob.
CronJob K8s in .NET: the right way
🟩🟩🟩 Expert
Disclaimer: assuming your familiarity with CronJob K8s’ existence and benefits, I’d rather concentrate on the development part instead of delving into specifics, which could be time-consuming.
Why this choice?
There are several libraries to perform background processing in .NET, such as Quartz or Hangfire. You can also manage it yourself with a BackgroundService. So why choose a CronJob K8s?
Avoiding job concurrency: Even if there are some limitations, K8s is pretty good at preventing concurrent job execution without relying on a database.
Monitoring: You benefit from the whole ecosystem around Kubernetes (like Prometheus) to detect as quickly as possible an error during execution. You can configure the number of pods in error to keep during the analysis of the problem.
Centralizing all your jobs in the same Docker image and Git repository, not only does make maintenance easier but also streamlines the development process.
As light as possible
Consider an existing solution composed of an empty “console app” project named Runner.csproj.
For this project, we’ll only use the .NET Runtime Docker image instead of the ASP.NET in my other stories to prove that we don’t need to pull many dependencies to build a CronJob.
Since we don’t depend on ASP.NET, which typically includes pre-installed dependencies, we must add some required ones manually in our .csproj file.
Choose your strategy
⚠️ This paragraph will call upon notions described in this story that are necessary for the proper understanding of the following.
We are ready to start implementing our background processor. The goal is to centralize all our jobs in the same project. It would be nice to reduce the workload when adding a future process.
Consider using the “Strategy” design pattern to execute only the necessary code associated with the scheduled job in our CronJob.
First of all, we need an interface to impose the choice of a type of job to execute, let’s name it “IJobProcessor”.
And finally, we have to implement our job. For our example, I will call an endpoint and log whether or not its health check is valid.
This example has no business value. You can imagine a report generation process or anonymization to be compliant with the GDPR.
We are now ready to finalize our CronJob. We have to use our strategy pattern.
We’ll inject it into the “Program.cs” Main function to retrieve environment variables, including the job type for execution.
The objective is to ensure that a return value of 0 means the job succeeds of the job, while a return value of 1 signifies failure. This convention enables K8s to accurately flag the current execution as a success or a failure.
You can run an execution of a specific job by updating the docker-compose.yml file.
version:'3.7'services:runner:image:runner:${TAG:-latest}environment:# specify the job type to execute-JOB=TestJob# add other configuration variables-TEST_ENDPOINT_BASE_URL=https://sample-service.com/build:context:.dockerfile:Runner/Dockerfile
Don’t forget to test!
The setup for my integration tests closely looks like the one in the story: Integration Testing in .NET: the right way. I recommend referring to it to avoid burdening my explanations.
To maintain the project’s structure, you can use an architecture test (using ArchUnitNET) to ensure that all implementations of JobProcessorBase reside in the same namespace.
Testing the “Program.cs” can be challenging, so it’s more convenient to test your use cases directly via dependency injection.
Here’s an example that enables you to validate your HTTP calls and the conformity of your log entries.
Time to schedule!
We are building a CronJob and still haven’t talked about scheduling. That’s pretty strange, right?
The scheduling is not managed at the code level but during the deployment on K8s.
While you can create your deployment.yml file directly, I recommend using a Helm chart for better maintainability.
Below is an example of a configuration file to integrate into a Helm chart to deploy a CronJob that runs a Docker image corresponding to the code discussed earlier.
Conclusion
Tic, tac, tic, tac 🕑, job is done! You have successfully built a CronJob K8s in .NET while remaining as light as possible.
From now on, it will become essential to add a monitoring layer to ensure that your jobs are running smoothly.
You can access the entire source code of this post on GitHub, stay tuned!