Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/install/installer/pkg/cluster/checks.go
2501 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package cluster
6
7
import (
8
"context"
9
"fmt"
10
"net"
11
"net/netip"
12
"strings"
13
14
"github.com/Masterminds/semver"
15
certmanager "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
16
corev1 "k8s.io/api/core/v1"
17
"k8s.io/apimachinery/pkg/api/errors"
18
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
"k8s.io/client-go/rest"
20
)
21
22
const (
23
// Allow pre-release range as GCP (and potentially others) use the patch for
24
// additional information, which get interpreted as pre-release (eg, 1.2.3-rc4)
25
kernelVersionConstraint = ">= 5.4.0-0"
26
kubernetesVersionConstraint = ">= 1.21.0-0"
27
)
28
29
// checkCertManagerInstalled checks that cert-manager is installed as a cluster dependency
30
func checkCertManagerInstalled(ctx context.Context, config *rest.Config, namespace string) ([]ValidationError, error) {
31
client, err := certmanager.NewForConfig(config)
32
if err != nil {
33
return nil, err
34
}
35
36
clusterIssuers, err := client.CertmanagerV1().ClusterIssuers().List(ctx, metav1.ListOptions{})
37
if err != nil {
38
// If cert-manager not installed, this will error
39
return []ValidationError{
40
{
41
Message: err.Error(),
42
Type: ValidationStatusError,
43
},
44
}, nil
45
}
46
47
if len(clusterIssuers.Items) == 0 {
48
// Treat as warning - may be bringing their own certs
49
return []ValidationError{
50
{
51
Message: "no cluster issuers configured",
52
Type: ValidationStatusWarning,
53
},
54
}, nil
55
}
56
57
return nil, nil
58
}
59
60
// checkContainerDRuntime checks that the nodes are running with the containerd runtime
61
func checkContainerDRuntime(ctx context.Context, config *rest.Config, namespace string) ([]ValidationError, error) {
62
nodes, err := ListNodesFromContext(ctx, config)
63
if err != nil {
64
return nil, err
65
}
66
67
var res []ValidationError
68
for _, node := range nodes {
69
runtime := node.Status.NodeInfo.ContainerRuntimeVersion
70
if !strings.Contains(runtime, "containerd") {
71
res = append(res, ValidationError{
72
Message: "container runtime not containerd on node: " + node.Name + ", runtime: " + runtime,
73
Type: ValidationStatusError,
74
})
75
}
76
}
77
78
return res, nil
79
}
80
81
func checkKubernetesVersion(ctx context.Context, config *rest.Config, namespace string) ([]ValidationError, error) {
82
// Allow pre-releases in case provider appends anything to the version
83
constraint, err := semver.NewConstraint(kubernetesVersionConstraint)
84
if err != nil {
85
return nil, err
86
}
87
88
server, err := serverVersion(ctx, config)
89
if err != nil {
90
return nil, err
91
}
92
93
var res []ValidationError
94
95
serverVersion, err := semver.NewVersion(server.GitVersion)
96
if err != nil {
97
res = append(res, ValidationError{
98
Message: err.Error() + " Kubernetes version: " + server.GitVersion,
99
Type: ValidationStatusWarning,
100
})
101
}
102
valid := constraint.Check(serverVersion)
103
if !valid {
104
res = append(res, ValidationError{
105
Message: "Kubernetes version " + server.GitVersion + " does not satisfy " + kubernetesVersionConstraint,
106
Type: ValidationStatusError,
107
})
108
}
109
110
nodes, err := ListNodesFromContext(ctx, config)
111
if err != nil {
112
return nil, err
113
}
114
for _, node := range nodes {
115
kubeletVersion := node.Status.NodeInfo.KubeletVersion
116
117
version, err := semver.NewVersion(kubeletVersion)
118
if err != nil {
119
// This means that the given version doesn't conform to semver format - user must decide
120
res = append(res, ValidationError{
121
Message: err.Error() + " Kubernetes version: " + kubeletVersion,
122
Type: ValidationStatusWarning,
123
})
124
break
125
}
126
127
valid := constraint.Check(version)
128
129
if !valid {
130
res = append(res, ValidationError{
131
Message: "Kubelet version " + kubeletVersion + " does not satisfy " + kubernetesVersionConstraint + " on node: " + node.Name,
132
Type: ValidationStatusError,
133
})
134
}
135
}
136
137
return res, nil
138
}
139
140
type checkSecretOpts struct {
141
RequiredFields []string
142
RecommendedFields []string
143
Validator func(*corev1.Secret) ([]ValidationError, error)
144
}
145
146
type CheckSecretOpt func(*checkSecretOpts)
147
148
func CheckSecretRequiredData(entries ...string) CheckSecretOpt {
149
return func(cso *checkSecretOpts) {
150
cso.RequiredFields = append(cso.RequiredFields, entries...)
151
}
152
}
153
154
func CheckSecretRecommendedData(entries ...string) CheckSecretOpt {
155
return func(cso *checkSecretOpts) {
156
cso.RecommendedFields = append(cso.RecommendedFields, entries...)
157
}
158
}
159
160
func CheckSecretRule(validator func(*corev1.Secret) ([]ValidationError, error)) CheckSecretOpt {
161
return func(cso *checkSecretOpts) {
162
cso.Validator = validator
163
}
164
}
165
166
// CheckSecret produces a new check for an in-cluster secret
167
func CheckSecret(name string, opts ...CheckSecretOpt) ValidationCheck {
168
var cfg checkSecretOpts
169
for _, o := range opts {
170
o(&cfg)
171
}
172
173
return ValidationCheck{
174
Name: name + " is present and valid",
175
Description: "ensures the " + name + " secret is present and contains the required data",
176
Check: func(ctx context.Context, config *rest.Config, namespace string) ([]ValidationError, error) {
177
client, err := clientsetFromContext(ctx, config)
178
if err != nil {
179
return nil, err
180
}
181
182
secret, err := client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
183
if errors.IsNotFound(err) {
184
return []ValidationError{
185
{
186
Message: "secret " + name + " not found",
187
Type: ValidationStatusError,
188
},
189
}, nil
190
} else if err != nil {
191
return nil, err
192
}
193
194
var res []ValidationError
195
for _, k := range cfg.RequiredFields {
196
_, ok := secret.Data[k]
197
if !ok {
198
res = append(res, ValidationError{
199
Message: fmt.Sprintf("secret %s has no %s entry", name, k),
200
Type: ValidationStatusError,
201
})
202
}
203
}
204
for _, k := range cfg.RecommendedFields {
205
_, ok := secret.Data[k]
206
if !ok {
207
res = append(res, ValidationError{
208
Message: fmt.Sprintf("secret %s has no %s entry", name, k),
209
Type: ValidationStatusWarning,
210
})
211
}
212
}
213
214
if cfg.Validator != nil {
215
vres, err := cfg.Validator(secret)
216
if err != nil {
217
return nil, err
218
}
219
res = append(res, vres...)
220
}
221
222
return res, nil
223
},
224
}
225
}
226
227
// checkKernelVersion checks the nodes are using the correct linux Kernel version
228
func checkKernelVersion(ctx context.Context, config *rest.Config, namespace string) ([]ValidationError, error) {
229
constraint, err := semver.NewConstraint(kernelVersionConstraint)
230
if err != nil {
231
return nil, err
232
}
233
234
nodes, err := ListNodesFromContext(ctx, config)
235
if err != nil {
236
return nil, err
237
}
238
239
var res []ValidationError
240
for _, node := range nodes {
241
kernelVersion := node.Status.NodeInfo.KernelVersion
242
// Some GCP kernel versions contain a non-semver compatible suffix
243
kernelVersion = strings.TrimSuffix(kernelVersion, "+")
244
version, err := semver.NewVersion(kernelVersion)
245
if err != nil {
246
// This means that the given version doesn't conform to semver format - user must decide
247
res = append(res, ValidationError{
248
Message: err.Error() + " kernel version: " + kernelVersion,
249
Type: ValidationStatusWarning,
250
})
251
break
252
}
253
254
valid := constraint.Check(version)
255
256
if !valid {
257
res = append(res, ValidationError{
258
Message: "kernel version " + kernelVersion + " does not satisfy " + kernelVersionConstraint + " on node: " + node.Name,
259
Type: ValidationStatusError,
260
})
261
}
262
}
263
264
return res, nil
265
}
266
267
func checkNamespaceExists(ctx context.Context, config *rest.Config, namespace string) ([]ValidationError, error) {
268
client, err := clientsetFromContext(ctx, config)
269
if err != nil {
270
return nil, err
271
}
272
273
namespaces, err := client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
274
if err != nil {
275
return nil, err
276
}
277
namespaceExists := false
278
for _, nsp := range namespaces.Items {
279
if !namespaceExists && nsp.Name == namespace {
280
namespaceExists = true
281
break
282
}
283
}
284
285
if !namespaceExists {
286
return []ValidationError{
287
{
288
Message: fmt.Sprintf("Namespace %s does not exist", namespace),
289
Type: ValidationStatusError,
290
},
291
}, nil
292
}
293
294
return nil, nil
295
}
296
297
func CheckWorkspaceCIDR(networkCIDR string) ValidationCheck {
298
return ValidationCheck{
299
Name: "workspace CIDR is present and valid",
300
Description: "ensures the workspace CIDR contains a valid network address range",
301
Check: func(ctx context.Context, config *rest.Config, namespace string) ([]ValidationError, error) {
302
netIP, ipNet, err := net.ParseCIDR(networkCIDR)
303
if err != nil {
304
return []ValidationError{
305
{
306
Message: fmt.Sprintf("invalid workspace CIDR: %v", err),
307
Type: ValidationStatusError,
308
},
309
}, nil
310
}
311
312
ipNet.Mask.Size()
313
mask, _ := ipNet.Mask.Size()
314
if mask > 30 {
315
return []ValidationError{
316
{
317
Message: "the workspace CIDR does not have a mask less than or equal to /30",
318
Type: ValidationStatusError,
319
},
320
}, nil
321
}
322
323
addr, err := netip.ParseAddr(netIP.String())
324
if err != nil {
325
return []ValidationError{
326
{
327
Message: fmt.Sprintf("invalid workspace CIDR: %v", err),
328
Type: ValidationStatusError,
329
},
330
}, nil
331
}
332
333
vethIp := addr.Next()
334
if !vethIp.IsValid() {
335
return []ValidationError{
336
{
337
Message: fmt.Sprintf("workspace CIDR is not big enough (%v)", networkCIDR),
338
Type: ValidationStatusError,
339
},
340
}, nil
341
}
342
343
cethIp := vethIp.Next()
344
if !cethIp.IsValid() {
345
return []ValidationError{
346
{
347
Message: fmt.Sprintf("workspace CIDR is not big enough (%v)", networkCIDR),
348
Type: ValidationStatusError,
349
},
350
}, nil
351
}
352
353
return nil, nil
354
},
355
}
356
}
357
358