This commit is contained in:
2024-02-20 17:15:27 +08:00
committed by huty
parent 6706e1a633
commit 34158042ad
1529 changed files with 177765 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.301-alpine AS builder
WORKDIR /src
COPY src/TimecheckController.csproj .
RUN dotnet restore
COPY src/ .
RUN dotnet publish -c Release -o /out TimecheckController.csproj
# app image
FROM mcr.microsoft.com/dotnet/core/runtime:3.1.5-alpine
WORKDIR /app
ENTRYPOINT ["dotnet", "TimecheckController.dll"]
COPY --from=builder /out/ .

View File

@@ -0,0 +1,8 @@
## Credits
https://radu-matei.com/blog/kubernetes-controller-csharp/
https://github.com/engineerd/kubecontroller-csharp
https://fearofoblivion.com/intro-to-kubernetes-custom-resource-definitions-or-crds

View File

@@ -0,0 +1,26 @@
using k8s;
using k8s.Models;
namespace TimecheckController.CustomResources
{
public class Timecheck : KubernetesObject
{
public V1ObjectMeta Metadata { get; set; }
public TimecheckSpec Spec { get; set; }
public class TimecheckSpec
{
public string Environment { get; set; }
public int Interval { get; set; }
}
public struct Definition
{
public const string Group = "ch20.kiamol.net";
public const string Version= "v1";
public const string Plural = "timechecks";
}
}
}

View File

@@ -0,0 +1,137 @@
using k8s;
using k8s.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using TimecheckController.CustomResources;
namespace TimecheckController.Handlers
{
public class TimecheckAddedHandler
{
private readonly Kubernetes _kubernetes;
public TimecheckAddedHandler(Kubernetes kubernetes)
{
_kubernetes = kubernetes;
}
public async Task HandleAsync(Timecheck timecheck)
{
await EnsureDeploymentAsync(timecheck);
}
private async Task EnsureDeploymentAsync(Timecheck timecheck)
{
var name = timecheck.Metadata.Name;
var deployments = await _kubernetes.ListNamespacedDeploymentAsync(
namespaceParameter: Program.NamespaceName,
fieldSelector: $"metadata.name={name}");
if (!deployments.Items.Any())
{
var deployment = new V1Deployment
{
Metadata = new V1ObjectMeta
{
Name = name,
Labels = new Dictionary<string, string>()
{
{ "kiamol", "ch20" },
}
},
Spec = new V1DeploymentSpec
{
Selector = new V1LabelSelector
{
MatchLabels = new Dictionary<string, string>()
{
{ "app", "timecheck"},
{ "instance", name }
}
},
Template = new V1PodTemplateSpec
{
Metadata = new V1ObjectMeta
{
Labels = new Dictionary<string, string>()
{
{ "app", "timecheck"},
{ "instance", name }
}
},
Spec = new V1PodSpec
{
AutomountServiceAccountToken = false,
Containers = new List<V1Container>
{
new V1Container
{
Name = "tc",
Image = "kiamol/ch07-timecheck",
Env = new List<V1EnvVar>
{
new V1EnvVar
{
Name = "Application__Environment",
Value = timecheck.Spec.Environment
},
new V1EnvVar
{
Name = "Timer__IntervalSeconds",
Value = timecheck.Spec.Interval.ToString()
}
},
VolumeMounts = new List<V1VolumeMount>
{
new V1VolumeMount
{
Name= "logs",
MountPath = "/logs"
}
}
},
new V1Container
{
Name = "logger",
Image = "kiamol/ch03-sleep",
Command = new List<string>
{
"sh", "-c", "tail -f /logs-ro/timecheck.log"
},
VolumeMounts = new List<V1VolumeMount>
{
new V1VolumeMount
{
Name= "logs",
MountPath = "/logs-ro",
ReadOnlyProperty = true
}
}
}
},
Volumes = new List<V1Volume>
{
new V1Volume
{
Name = "logs",
EmptyDir = new V1EmptyDirVolumeSource()
}
}
}
}
}
};
await _kubernetes.CreateNamespacedDeploymentAsync(deployment, Program.NamespaceName);
Console.WriteLine($"** Created Deployment: {name}, in namespace: {Program.NamespaceName}");
}
else
{
Console.WriteLine($"** Deployment exists: {name}, in namespace: {Program.NamespaceName}");
}
}
}
}

View File

@@ -0,0 +1,41 @@
using k8s;
using System;
using System.Linq;
using System.Threading.Tasks;
using TimecheckController.CustomResources;
namespace TimecheckController.Handlers
{
class TimecheckDeletedHandler
{
private readonly Kubernetes _kubernetes;
public TimecheckDeletedHandler(Kubernetes kubernetes)
{
_kubernetes = kubernetes;
}
public async Task HandleAsync(Timecheck timecheck)
{
await DeleteDeploymentAsync(timecheck);
}
private async Task DeleteDeploymentAsync(Timecheck timecheck)
{
var name = timecheck.Metadata.Name;
var deployments = await _kubernetes.ListNamespacedDeploymentAsync(
namespaceParameter: Program.NamespaceName,
fieldSelector: $"metadata.name={name}");
if (deployments.Items.Any())
{
await _kubernetes.DeleteNamespacedDeploymentAsync(name, Program.NamespaceName);
Console.WriteLine($"** Deleted Deployment: {name}, in namespace: {Program.NamespaceName}");
}
else
{
Console.WriteLine($"** No Deployment to delete: {name}, in namespace: {Program.NamespaceName}");
}
}
}
}

View File

@@ -0,0 +1,66 @@
using k8s;
using System;
using System.Threading;
using System.Threading.Tasks;
using TimecheckController.CustomResources;
using TimecheckController.Handlers;
namespace TimecheckController
{
class Program
{
private static ManualResetEvent _ResetEvent = new ManualResetEvent(false);
private static Kubernetes _Client;
static async Task Main(string[] args)
{
KubernetesClientConfiguration config;
if (KubernetesClientConfiguration.IsInCluster())
{
config = KubernetesClientConfiguration.InClusterConfig();
}
else
{
config = new KubernetesClientConfiguration { Host = "http://localhost:8001" };
}
_Client = new Kubernetes(config);
var result = await _Client.ListNamespacedCustomObjectWithHttpMessagesAsync(
group: Timecheck.Definition.Group,
version: Timecheck.Definition.Version,
plural: Timecheck.Definition.Plural,
namespaceParameter: "default",
watch: true);
using (result.Watch<Timecheck, object>(async (type, item) => await Handle(type, item)))
{
Console.WriteLine("* Watching for custom object events");
_ResetEvent.WaitOne();
}
}
public static async Task Handle(WatchEventType type, Timecheck timecheck)
{
switch (type)
{
case WatchEventType.Added:
await new TimecheckAddedHandler(_Client).HandleAsync(timecheck);
Console.WriteLine($"* Handled event: {type}, for Timecheck: {timecheck.Metadata.Name}");
break;
case WatchEventType.Deleted:
await new TimecheckDeletedHandler(_Client).HandleAsync(timecheck);
Console.WriteLine($"* Handled event: {type}, for Timecheck: {timecheck.Metadata.Name}");
break;
default:
Console.WriteLine($"* Ignored event: {type}, for Timecheck: {timecheck.Metadata.Name}");
break;
}
}
// TODO - move to config:
public const string NamespaceName = "default";
}
}

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="KubernetesClient" Version="2.0.26" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30309.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimecheckController", "TimecheckController.csproj", "{D92E46DD-C089-48E5-8B94-371EC9A27704}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D92E46DD-C089-48E5-8B94-371EC9A27704}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D92E46DD-C089-48E5-8B94-371EC9A27704}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D92E46DD-C089-48E5-8B94-371EC9A27704}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D92E46DD-C089-48E5-8B94-371EC9A27704}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB8F2AD8-5410-4F5E-AFC6-D8AA6BCDAAC4}
EndGlobalSection
EndGlobal