Sitelet https://github.com/kubernetes/kops/commit/9c0d822f5c48cb0b34b85edb778e1f8944ff8847
Skip to content

Commit 9c0d822

Browse files
committed
feat(linode): Add Linode (Akamai) instance management capabilities
Signed-off-by: Moshe Vayner <moshe@vayner.me>
1 parent aa3ff3c commit 9c0d822

13 files changed

Lines changed: 1525 additions & 10 deletions

File tree

‎pkg/model/context.go‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
3636
"k8s.io/kops/upup/pkg/fi/cloudup/gce"
3737
"k8s.io/kops/upup/pkg/fi/cloudup/hetzner"
38+
"k8s.io/kops/upup/pkg/fi/cloudup/linode"
3839
"k8s.io/kops/upup/pkg/fi/cloudup/scaleway"
3940

4041
"github.com/blang/semver/v4"
@@ -183,6 +184,13 @@ func (b *KopsModelContext) CloudTagsForInstanceGroup(ig *kops.InstanceGroup) (ma
183184
labels[hetzner.TagKubernetesNodeLabelPrefix+k] = v
184185
case kops.CloudProviderGCE:
185186
// TODO: Do nothing for now while we figure out how to address GCE label length limit of 63
187+
case kops.CloudProviderLinode:
188+
// Akamai Cloud (Linode) tags have a 50 character limit
189+
// Only store the critical kops.k8s.io/instancegroup label
190+
// Role labels will be derived from the instance role tag by the identifier
191+
if k == linode.TagKubernetesInstanceGroup {
192+
labels[k] = v
193+
}
186194
default:
187195
labels[nodeidentityaws.ClusterAutoscalerNodeTemplateLabel+k] = v
188196
}
@@ -214,6 +222,10 @@ func (b *KopsModelContext) CloudTagsForInstanceGroup(ig *kops.InstanceGroup) (ma
214222
if ig.Spec.Role.HasControlPlane() {
215223
labels[gce.GceLabelNameRolePrefix+"master"] = "master"
216224
}
225+
case kops.CloudProviderLinode:
226+
labels[linode.TagKubernetesClusterName] = b.ClusterName()
227+
labels[linode.TagKubernetesInstanceGroup] = ig.Name
228+
labels[linode.TagKubernetesInstanceRole] = string(ig.Spec.Role)
217229
default:
218230
// The system tags take priority because the cluster likely breaks without them...
219231

‎pkg/model/linodemodel/instances.go‎

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
Copyright 2026 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package linodemodel
18+
19+
import (
20+
"fmt"
21+
22+
"k8s.io/kops/pkg/apis/kops"
23+
"k8s.io/kops/pkg/model"
24+
"k8s.io/kops/upup/pkg/fi"
25+
"k8s.io/kops/upup/pkg/fi/cloudup/linode"
26+
"k8s.io/kops/upup/pkg/fi/cloudup/linodetasks"
27+
)
28+
29+
// InstanceModelBuilder configures the Linode (Akamai) instances (aka Linodes) for the cluster.
30+
type InstanceModelBuilder struct {
31+
*LinodeModelContext
32+
Lifecycle fi.Lifecycle
33+
BootstrapScriptBuilder *model.BootstrapScriptBuilder
34+
}
35+
36+
var _ fi.CloudupModelBuilder = &InstanceModelBuilder{}
37+
38+
func (b *InstanceModelBuilder) Build(c *fi.CloudupModelBuilderContext) error {
39+
for _, ig := range b.InstanceGroups {
40+
var sshKeyTasks []*linodetasks.SSHKey
41+
42+
subnets, err := b.GatherSubnets(ig)
43+
if err != nil {
44+
return err
45+
}
46+
if len(subnets) != 1 {
47+
return fmt.Errorf("expected exactly one subnet for InstanceGroup %q; subnets was %s", ig.Name, ig.Spec.Subnets)
48+
}
49+
subnetSpec := subnets[0]
50+
subnetTaskName := linode.NormalizeLinodeLabel(b.ClusterName() + "-" + subnetSpec.Name)
51+
subnetTask, err := findSubnetTask(c, subnetTaskName, ig)
52+
if err != nil {
53+
return err
54+
}
55+
56+
for _, task := range c.Tasks {
57+
if sshKey, ok := task.(*linodetasks.SSHKey); ok {
58+
sshKeyTasks = append(sshKeyTasks, sshKey)
59+
}
60+
}
61+
62+
userData, err := b.BootstrapScriptBuilder.ResourceNodeUp(c, ig)
63+
if err != nil {
64+
return err
65+
}
66+
67+
tagsMap, err := b.CloudTagsForInstanceGroup(ig)
68+
if err != nil {
69+
return err
70+
}
71+
tags := make([]string, 0, len(tagsMap))
72+
for k, v := range tagsMap {
73+
tags = append(tags, fmt.Sprintf("%s:%s", k, v))
74+
}
75+
76+
instanceGroup := linodetasks.Instance{
77+
Name: new(ig.Name),
78+
Lifecycle: b.Lifecycle,
79+
Region: subnetSpec.Region,
80+
Type: ig.Spec.MachineType,
81+
Subnet: subnetTask,
82+
RequirePublicInterface: requirePublicInterface(subnetSpec, ig),
83+
AuthorizedKeys: sshKeyTasks,
84+
Count: int(fi.ValueOf(ig.Spec.MinSize)),
85+
Image: ig.Spec.Image,
86+
UserData: userData,
87+
Tags: tags,
88+
}
89+
90+
c.AddTask(&instanceGroup)
91+
}
92+
93+
return nil
94+
}
95+
96+
// requirePublicInterface checks whether the instance group requires a public interface based on the subnet type and instance group settings.
97+
func requirePublicInterface(subnet *kops.ClusterSubnetSpec, ig *kops.InstanceGroup) *bool {
98+
requirePublic := false
99+
100+
switch subnet.Type {
101+
case kops.SubnetTypePublic, kops.SubnetTypeUtility:
102+
requirePublic = true
103+
if ig.Spec.AssociatePublicIP != nil {
104+
requirePublic = fi.ValueOf(ig.Spec.AssociatePublicIP)
105+
}
106+
case kops.SubnetTypePrivate, kops.SubnetTypeDualStack:
107+
requirePublic = false
108+
}
109+
110+
return &requirePublic
111+
}
112+
113+
// findInstanceTask searches for the instance task corresponding to the given instance group name in the provided context.
114+
func findSubnetTask(c *fi.CloudupModelBuilderContext, subnetTaskName string, ig *kops.InstanceGroup) (*linodetasks.Subnet, error) {
115+
for _, task := range c.Tasks {
116+
subnet, ok := task.(*linodetasks.Subnet)
117+
if !ok {
118+
continue
119+
}
120+
if fi.ValueOf(subnet.Name) == subnetTaskName {
121+
return subnet, nil
122+
}
123+
}
124+
125+
return nil, fmt.Errorf("subnet task %q not found for InstanceGroup %q", subnetTaskName, ig.Name)
126+
}

0 commit comments

Comments
 (0)