This repository has been archived by the owner on Jul 18, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
KubernetesAgentHostService.cs
138 lines (125 loc) · 5.03 KB
/
KubernetesAgentHostService.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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
using Microsoft.Extensions.Configuration;
using k8s;
using k8s.Models;
public class KubernetesAgentHostService : IAgentHostService
{
private const string DEFAULT_JOB_PREFIX = "agent-job-";
private string _namespace;
private string _jobPrefix;
private string _jobImage;
private string _azpUrl;
private string _azpPat;
private string _dockerSockPath;
private string _jobDefFile;
private V1Job _predefinedJob;
private Kubernetes _kubectl;
private string FormatJobName(long requestId) => $"{_jobPrefix}-{requestId}";
public KubernetesAgentHostService(IConfiguration config)
{
var kubeConfPath = config.GetValue<string>("KUBECONFIG", null);
if (kubeConfPath == null) throw new ArgumentNullException("'KUBECONFIG' environment variable required but missing.");
using var kubeConfigFile = File.OpenRead(kubeConfPath);
_kubectl = new Kubernetes(KubernetesClientConfiguration.BuildConfigFromConfigFile(kubeConfigFile));
_namespace = config.GetValue<string>("JOB_NAMESPACE", "default");
_jobPrefix = config.GetValue<string>("JOB_PREFIX", DEFAULT_JOB_PREFIX);
_jobImage = config.GetValue<string>("JOB_IMAGE");
_azpUrl = config.GetValue<string>("ORG_URL");
_azpPat = config.GetValue<string>("ORG_PAT");
_dockerSockPath = config.GetValue<string>("JOB_DOCKER_SOCKET_PATH");
_jobDefFile = config.GetValue<string>("JOB_DEFINITION_FILE");
if (_jobDefFile != null)
{
_predefinedJob = KubernetesYaml.Deserialize<V1Job>(System.IO.File.ReadAllText(_jobDefFile));
_jobPrefix = _predefinedJob.Metadata.Name ?? _jobPrefix;
}
}
public async Task Initialize()
{
if (!(await _kubectl.CoreV1.ListNamespaceAsync()).Items.Any(ns => ns.Name() == _namespace))
{
await _kubectl.CoreV1.CreateNamespaceAsync(new V1Namespace()
{
Metadata = new V1ObjectMeta()
{
Name = _namespace
}
});
}
}
public async Task<bool> IsJobProvisioned(long requestId) =>
(await _kubectl.ListNamespacedJobAsync(_namespace))
.Items.Any(x => x.Metadata.Name == FormatJobName(requestId));
public async Task StartAgent(long requestId, string agentPool)
{
V1Job job = _predefinedJob;
if (_predefinedJob != null)
{
_predefinedJob.Metadata.Name = FormatJobName(requestId);
}
else
{
job = new V1Job()
{
ApiVersion = "batch/v1",
Kind = "Job",
Metadata = new V1ObjectMeta()
{
Name = FormatJobName(requestId),
NamespaceProperty = _namespace
},
Spec = new V1JobSpec()
{
Template = new V1PodTemplateSpec()
{
Spec = new V1PodSpec()
{
RestartPolicy = "Never",
Containers = new List<V1Container>(){
new V1Container() {
Name = FormatJobName(requestId),
Image = _jobImage,
Env = new List<V1EnvVar>() {
new V1EnvVar() {
Name = "AZP_URL",
Value = _azpUrl
},
new V1EnvVar() {
Name = "AZP_TOKEN",
Value = _azpPat
},
new V1EnvVar() {
Name = "AZP_POOL",
Value = agentPool
}
}
}
}
}
}
}
};
// Not all K8s environments support the docker.sock
// This option allows users to opt-in to adding the volume mount
if (_dockerSockPath != null)
{
var podSpec = job.Spec.Template.Spec;
var volumeName = "docker-volume";
podSpec.Volumes = new List<V1Volume>(){
new V1Volume() {
Name = volumeName,
HostPath = new V1HostPathVolumeSource() {
Path = _dockerSockPath
}
}
};
podSpec.Containers[0].VolumeMounts = new List<V1VolumeMount>() {
new V1VolumeMount() {
MountPath = _dockerSockPath,
Name = volumeName
}
};
}
}
await _kubectl.CreateNamespacedJobAsync(job, namespaceParameter: _namespace);
}
}