From e8ae5ef63af13a40bd88bab03c54703eccdbfcc7 Mon Sep 17 00:00:00 2001 From: Evan Johnson Date: Fri, 3 Nov 2023 13:19:53 -0400 Subject: [PATCH] feat: add akamai platform support Add support for the Akamai(Linode) platform Signed-off-by: Evan Johnson Signed-off-by: Andrey Smirnov --- .drone.jsonnet | 2 + Makefile | 2 +- go.mod | 2 + go.sum | 13 ++ hack/release.toml | 6 + .../v1alpha1/platform/akamai/akamai.go | 206 ++++++++++++++++++ .../v1alpha1/platform/akamai/akamai_test.go | 48 ++++ .../platform/akamai/testdata/expected.yaml | 56 +++++ .../platform/akamai/testdata/instance.json | 19 ++ .../platform/akamai/testdata/network.json | 20 ++ .../pkg/runtime/v1alpha1/platform/platform.go | 3 + pkg/imager/profile/default.go | 12 + .../v1.7/introduction/support-matrix.md | 3 +- website/content/v1.7/reference/kernel.md | 3 +- .../install/cloud-platforms/akamai.md | 138 ++++++++++++ 15 files changed, 530 insertions(+), 3 deletions(-) create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/akamai.go create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/akamai_test.go create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/expected.yaml create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/instance.json create mode 100644 internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/network.json create mode 100644 website/content/v1.7/talos-guides/install/cloud-platforms/akamai.md diff --git a/.drone.jsonnet b/.drone.jsonnet index 8af7e186a..f0de639e5 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -1054,6 +1054,8 @@ local release = { draft: true, note: '_out/RELEASE_NOTES.md', files: [ + '_out/akamai-amd64.raw.gz', + '_out/akamai-arm64.raw.gz', '_out/aws-amd64.raw.xz', '_out/aws-arm64.raw.xz', '_out/azure-amd64.vhd.xz', diff --git a/Makefile b/Makefile index e4ac2bf86..b0837762d 100644 --- a/Makefile +++ b/Makefile @@ -372,7 +372,7 @@ image-%: ## Builds the specified image. Valid options are aws, azure, digital-oc images-essential: image-aws image-azure image-gcp image-metal secureboot-installer ## Builds only essential images used in the CI (AWS, GCP, and Metal). -images: image-aws image-azure image-digital-ocean image-exoscale image-gcp image-hcloud image-iso image-metal image-nocloud image-opennebula image-openstack image-oracle image-scaleway image-upcloud image-vmware image-vultr ## Builds all known images (AWS, Azure, DigitalOcean, Exoscale, GCP, HCloud, Metal, NoCloud, OpenNebula, Openstack, Oracle, Scaleway, UpCloud, Vultr and VMware). +images: image-akamai image-aws image-azure image-digital-ocean image-exoscale image-gcp image-hcloud image-iso image-metal image-nocloud image-opennebula image-openstack image-oracle image-scaleway image-upcloud image-vmware image-vultr ## Builds all known images (AWS, Azure, DigitalOcean, Exoscale, GCP, HCloud, Metal, NoCloud, OpenNebula, Openstack, Oracle, Scaleway, UpCloud, Vultr and VMware). .PHONY: iso iso: image-iso ## Builds the ISO and outputs it to the artifact directory. diff --git a/go.mod b/go.mod index 7b2fb4c25..3b5c2dbc1 100644 --- a/go.mod +++ b/go.mod @@ -92,6 +92,7 @@ require ( github.com/jeromer/syslogparser v1.1.0 github.com/jsimonetti/rtnetlink v1.4.1 github.com/jxskiss/base62 v1.1.0 + github.com/linode/go-metadata v0.2.0 github.com/klauspost/cpuid/v2 v2.2.7 github.com/martinlindhe/base36 v1.1.1 github.com/mattn/go-isatty v0.0.20 @@ -242,6 +243,7 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-resty/resty/v2 v2.9.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.0 // indirect diff --git a/go.sum b/go.sum index cedd5f0ef..9ba98375f 100644 --- a/go.sum +++ b/go.sum @@ -283,6 +283,8 @@ github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2Kv github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM= +github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -475,6 +477,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/linode/go-metadata v0.2.0 h1:hlWzkYLa80ikA0NmFX2hcwhcnWFol8F3UIvJnOgdKw4= +github.com/linode/go-metadata v0.2.0/go.mod h1:XraDbSwms0+CtA7/Qh7agkSvGDc6H0s782kpX9MdMu0= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -848,6 +852,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -932,6 +937,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1023,6 +1030,7 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= @@ -1033,6 +1041,8 @@ golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= @@ -1048,11 +1058,14 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/hack/release.toml b/hack/release.toml index b45d95b57..2623b4377 100644 --- a/hack/release.toml +++ b/hack/release.toml @@ -193,6 +193,12 @@ machine: extraTags: server: s03-rack07 ``` +""" + + [notes.platforms] + title = "Platforms" + description = """\ +Talos Linux now supports [Akamai Connected Cloud](https://www.linode.com/) provider (platform `akamai`). """ [make_deps] diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/akamai.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/akamai.go new file mode 100644 index 000000000..9f5d46192 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/akamai.go @@ -0,0 +1,206 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package akamai contains the Akamai implementation of the [platform.Platform]. +package akamai + +import ( + "context" + "fmt" + "net/netip" + "strconv" + "strings" + + "github.com/cosi-project/runtime/pkg/state" + akametadata "github.com/linode/go-metadata" + "github.com/siderolabs/go-procfs/procfs" + + "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" + "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/internal/netutils" + "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/pkg/machinery/nethelpers" + "github.com/siderolabs/talos/pkg/machinery/resources/network" + runtimeres "github.com/siderolabs/talos/pkg/machinery/resources/runtime" +) + +// Akamai is the concrete type that implements the platform.Platform interface. +type Akamai struct{} + +// Name implements the platform.Platform interface. +func (a *Akamai) Name() string { + return "akamai" +} + +// ParseMetadata converts Akamai platform metadata into platform network config. +func (a *Akamai) ParseMetadata(metadata *akametadata.InstanceData, interfaceAddresses *akametadata.NetworkData) (*runtime.PlatformNetworkConfig, error) { + networkConfig := &runtime.PlatformNetworkConfig{} + + if metadata.Label != "" { + hostnameSpec := network.HostnameSpecSpec{ + ConfigLayer: network.ConfigPlatform, + } + + if err := hostnameSpec.ParseFQDN(metadata.Label); err != nil { + return nil, err + } + + networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec) + } + + publicIPs := make([]string, 0, len(interfaceAddresses.IPv4.Public)+len(interfaceAddresses.IPv6.Ranges)) + + // external IP + for _, iface := range interfaceAddresses.IPv4.Public { + publicIPs = append(publicIPs, iface.Addr().String()) + networkConfig.Addresses = append(networkConfig.Addresses, + network.AddressSpecSpec{ + ConfigLayer: network.ConfigPlatform, + LinkName: "eth0", + Address: iface, + Scope: nethelpers.ScopeGlobal, + Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent), + Family: nethelpers.FamilyInet4, + }, + ) + } + + for _, iface := range interfaceAddresses.IPv4.Private { + networkConfig.Addresses = append(networkConfig.Addresses, + network.AddressSpecSpec{ + ConfigLayer: network.ConfigPlatform, + LinkName: "eth0", + Address: iface, + Scope: nethelpers.ScopeGlobal, + Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent), + Family: nethelpers.FamilyInet4, + }, + ) + } + + for _, iface := range interfaceAddresses.IPv6.Ranges { + publicIPs = append(publicIPs, iface.Addr().String()) + + networkConfig.Addresses = append(networkConfig.Addresses, + network.AddressSpecSpec{ + ConfigLayer: network.ConfigPlatform, + LinkName: "eth0", + Address: iface, + Scope: nethelpers.ScopeGlobal, + Flags: nethelpers.AddressFlags(nethelpers.AddressManagementTemp), + Family: nethelpers.FamilyInet6, + }, + ) + } + + networkConfig.Addresses = append(networkConfig.Addresses, + network.AddressSpecSpec{ + ConfigLayer: network.ConfigPlatform, + LinkName: "eth0", + Address: interfaceAddresses.IPv6.LinkLocal, + Scope: nethelpers.ScopeLink, + Family: nethelpers.FamilyInet6, + }, + ) + + ipv6gw, err := netip.ParseAddr(strings.Split(interfaceAddresses.IPv6.LinkLocal.String(), ":")[0] + "::1") + if err != nil { + return nil, err + } + + route := network.RouteSpecSpec{ + ConfigLayer: network.ConfigPlatform, + Gateway: ipv6gw, + OutLinkName: "eth0", + Destination: interfaceAddresses.IPv6.LinkLocal, + Table: nethelpers.TableMain, + Protocol: nethelpers.ProtocolStatic, + Type: nethelpers.TypeUnicast, + Family: nethelpers.FamilyInet6, + Priority: 1024, + } + + route.Normalize() + + networkConfig.Routes = append(networkConfig.Routes, route) + + for _, ipStr := range publicIPs { + if ip, err := netip.ParseAddr(ipStr); err == nil { + networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip) + } + } + + networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{ + Platform: a.Name(), + Hostname: metadata.Label, + Region: metadata.Region, + InstanceType: metadata.Type, + InstanceID: strconv.Itoa(metadata.ID), + ProviderID: fmt.Sprintf("linode://%d", metadata.ID), + } + + return networkConfig, nil +} + +// Configuration implements the platform.Platform interface. +func (a *Akamai) Configuration(ctx context.Context, r state.State) ([]byte, error) { + if err := netutils.Wait(ctx, r); err != nil { + return nil, err + } + + metadataClient, err := akametadata.NewClient(ctx) + if err != nil { + return nil, fmt.Errorf("new metadata client: %w", err) + } + + userData, err := metadataClient.GetUserData(ctx) + if err != nil { + return nil, fmt.Errorf("get user data: %w", err) + } + + return []byte(userData), err +} + +// Mode implements the platform.Platform interface. +func (a *Akamai) Mode() runtime.Mode { + return runtime.ModeCloud +} + +// KernelArgs implements the runtime.Platform interface. +func (a *Akamai) KernelArgs(string) procfs.Parameters { + return []*procfs.Parameter{ + procfs.NewParameter("console").Append("ttyS0").Append("tty0").Append("tty1"), + procfs.NewParameter(constants.KernelParamNetIfnames).Append("0"), + } +} + +// NetworkConfiguration implements the runtime.Platform interface. +func (a *Akamai) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error { + metadataClient, err := akametadata.NewClient(ctx) + if err != nil { + return fmt.Errorf("new metadata client: %w", err) + } + + metadata, err := metadataClient.GetInstance(ctx) + if err != nil { + return fmt.Errorf("get instance data: %w", err) + } + + metadataNetworkConfig, err := metadataClient.GetNetwork(ctx) + if err != nil { + return fmt.Errorf("get network data: %w", err) + } + + networkConfig, err := a.ParseMetadata(metadata, metadataNetworkConfig) + if err != nil { + return fmt.Errorf("parse metadata: %w", err) + } + + select { + case ch <- networkConfig: + case <-ctx.Done(): + return ctx.Err() + } + + return nil +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/akamai_test.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/akamai_test.go new file mode 100644 index 000000000..2d2f9dc90 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/akamai_test.go @@ -0,0 +1,48 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package akamai_test + +import ( + _ "embed" + "encoding/json" + "testing" + + akametadata "github.com/linode/go-metadata" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + + "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai" +) + +//go:embed testdata/instance.json +var rawMetadata []byte + +//go:embed testdata/network.json + +var rawNetwork []byte + +//go:embed testdata/expected.yaml +var expectedNetworkConfig string + +func TestParseMetadata(t *testing.T) { + p := &akamai.Akamai{} + + var metadata akametadata.InstanceData + + var interfaceConfig akametadata.NetworkData + + require.NoError(t, json.Unmarshal(rawMetadata, &metadata)) + + require.NoError(t, json.Unmarshal(rawNetwork, &interfaceConfig)) + + networkConfig, err := p.ParseMetadata(&metadata, &interfaceConfig) + require.NoError(t, err) + + marshaled, err := yaml.Marshal(networkConfig) + require.NoError(t, err) + + assert.Equal(t, expectedNetworkConfig, string(marshaled)) +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/expected.yaml b/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/expected.yaml new file mode 100644 index 000000000..4fa14ba1a --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/expected.yaml @@ -0,0 +1,56 @@ +addresses: + - address: 172.1.2.3/32 + linkName: eth0 + family: inet4 + scope: global + flags: permanent + layer: platform + - address: 192.1.2.3/32 + linkName: eth0 + family: inet4 + scope: global + flags: permanent + layer: platform + - address: 2600:3c05:d011:797::/64 + linkName: eth0 + family: inet6 + scope: global + flags: mngmtmpaddr + layer: platform + - address: fe80::f03c:93ff:fe6e:5cd9/128 + linkName: eth0 + family: inet6 + scope: link + flags: "" + layer: platform +links: [] +routes: + - family: inet6 + dst: fe80::f03c:93ff:fe6e:5cd9/128 + src: "" + gateway: fe80::1 + outLinkName: eth0 + table: main + priority: 1024 + scope: global + type: unicast + flags: "" + protocol: static + layer: platform +hostnames: + - hostname: talos + domainname: "" + layer: platform +resolvers: [] +timeServers: [] +operators: [] +externalIPs: + - 172.1.2.3 + - '2600:3c05:d011:797::' +metadata: + platform: akamai + hostname: talos + region: us-east + instanceType: g6-standard-1 + instanceId: "123456" + providerId: linode://123456 diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/instance.json b/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/instance.json new file mode 100644 index 000000000..2af1a22d2 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/instance.json @@ -0,0 +1,19 @@ +{ + "id": 123456, + "label": "talos", + "region": "us-east", + "type": "g6-standard-1", + "specs": { + "vcpus": 1, + "memory": 2048, + "gpus": 0, + "transfer": 2000, + "disk": 51200 + }, + "backups": { + "enabled": false, + "status": null + }, + "host_uuid": "0c2897331ea446f483f754852b18a67c", + "tags": [] +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/network.json b/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/network.json new file mode 100644 index 000000000..1a11c1339 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/testdata/network.json @@ -0,0 +1,20 @@ +{ + "interfaces": [], + "ipv4": { + "public": [ + "172.1.2.3/32" + ], + "private": [ + "192.1.2.3/32" + ], + "shared": [] + }, + "ipv6": { + "slaac": "2600:3c06::f03c:93ff:fe6e:5cd9/128", + "ranges": [ + "2600:3c05:d011:797::/64" + ], + "link_local": "fe80::f03c:93ff:fe6e:5cd9/128", + "shared_ranges": [] + } +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/platform.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/platform.go index d55c663e3..248e8a509 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/platform.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/platform.go @@ -15,6 +15,7 @@ import ( "github.com/siderolabs/go-procfs/procfs" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" + "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/aws" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/azure" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/container" @@ -90,6 +91,8 @@ func NewPlatform(platform string) (p runtime.Platform, err error) { //nolint:gocyclo func newPlatform(platform string) (p runtime.Platform, err error) { switch platform { + case "akamai": + p = &akamai.Akamai{} case "aws": return aws.NewAWS() case "azure": diff --git a/pkg/imager/profile/default.go b/pkg/imager/profile/default.go index 6833cff2d..51f70ea68 100644 --- a/pkg/imager/profile/default.go +++ b/pkg/imager/profile/default.go @@ -84,6 +84,18 @@ var Default = map[string]Profile{ }, }, // Clouds + "akamai": { + Platform: "akamai", + SecureBoot: pointer.To(false), + Output: Output{ + Kind: OutKindImage, + OutFormat: OutFormatGZ, + ImageOptions: &ImageOptions{ + DiskSize: MinRAWDiskSize, + DiskFormat: DiskFormatRaw, + }, + }, + }, "aws": { Platform: "aws", SecureBoot: pointer.To(false), diff --git a/website/content/v1.7/introduction/support-matrix.md b/website/content/v1.7/introduction/support-matrix.md index d54fa403f..c03587449 100644 --- a/website/content/v1.7/introduction/support-matrix.md +++ b/website/content/v1.7/introduction/support-matrix.md @@ -12,7 +12,7 @@ description: "Table of supported Talos Linux versions and respective platforms." | Kubernetes | 1.30, 1.29, 1.28, 1.27, 1.26, 1.25 | 1.29, 1.28, 1.27, 1.26, 1.25, 1.24 | | Architecture | amd64, arm64 | amd64, arm64 | | **Platforms** | | | -| - cloud | AWS, GCP, Azure, Digital Ocean, Exoscale, Hetzner, OpenNebula, OpenStack, Oracle Cloud, Scaleway, Vultr, Upcloud | AWS, GCP, Azure, Digital Ocean, Exoscale, Hetzner, OpenStack, Oracle Cloud, Scaleway, Vultr, Upcloud | +| - cloud | AWS, GCP, Azure, Digital Ocean, Exoscale, Hetzner, OpenNebula, OpenStack, Oracle Cloud, Scaleway, Vultr, Upcloud | Akamai, AWS, GCP, Azure, Digital Ocean, Exoscale, Hetzner, OpenStack, Oracle Cloud, Scaleway, Vultr, Upcloud | | - bare metal | x86: BIOS, UEFI, SecureBoot; arm64: UEFI, SecureBoot; boot: ISO, PXE, disk image | x86: BIOS, UEFI; arm64: UEFI; boot: ISO, PXE, disk image | | - virtualized | VMware, Hyper-V, KVM, Proxmox, Xen | VMware, Hyper-V, KVM, Proxmox, Xen | | - SBCs | Banana Pi M64, Jetson Nano, Libre Computer Board ALL-H3-CC, Nano Pi R4S, Pine64, Pine64 Rock64, Radxa ROCK Pi 4c, Radxa Rock4c+, Raspberry Pi 4B, Raspberry Pi Compute Module 4 | Banana Pi M64, Jetson Nano, Libre Computer Board ALL-H3-CC, Nano Pi R4S, Orange Pi R1 Plus LTS, Pine64, Pine64 Rock64, Radxa ROCK Pi 4c, Raspberry Pi 4B, Raspberry Pi Compute Module 4 | @@ -43,6 +43,7 @@ description: "Table of supported Talos Linux versions and respective platforms." ### Tier 3 +* Akamai * Exoscale * Hetzner * nocloud diff --git a/website/content/v1.7/reference/kernel.md b/website/content/v1.7/reference/kernel.md index e658af4c6..790f0bf75 100644 --- a/website/content/v1.7/reference/kernel.md +++ b/website/content/v1.7/reference/kernel.md @@ -12,7 +12,7 @@ Several of these are enforced by the Kernel Self Protection Project [KSPP](https **Required** parameters: -* `talos.platform`: can be one of `aws`, `azure`, `container`, `digitalocean`, `equinixMetal`, `gcp`, `hcloud`, `metal`, `nocloud`, `openstack`, `oracle`, `scaleway`, `upcloud`, `vmware` or `vultr` +* `talos.platform`: can be one of `akamai`, `aws`, `azure`, `container`, `digitalocean`, `equinixMetal`, `gcp`, `hcloud`, `metal`, `nocloud`, `openstack`, `oracle`, `scaleway`, `upcloud`, `vmware` or `vultr` * `slab_nomerge`: required by KSPP * `pti=on`: required by KSPP @@ -151,6 +151,7 @@ The platform name on which Talos will run. Valid options are: +* `akamai` * `aws` * `azure` * `container` diff --git a/website/content/v1.7/talos-guides/install/cloud-platforms/akamai.md b/website/content/v1.7/talos-guides/install/cloud-platforms/akamai.md new file mode 100644 index 000000000..bf6a63e93 --- /dev/null +++ b/website/content/v1.7/talos-guides/install/cloud-platforms/akamai.md @@ -0,0 +1,138 @@ +--- +title: "Akamai" +description: "Creating a cluster via the CLI on Akamai Cloud(Linode)." +aliases: + - ../../../cloud-platforms/akamai +--- + +## Creating a Talos Linux Cluster on Akamai Connected Cloud via the CLI + +This guide will demonstrate how to create a highly-available Kubernetes cluster with one worker using the [Akamai Connected Cloud](https://www.linode.com/) provider. + +[Akamai Connected Cloud](https://www.linode.com/) has a very well documented REST API, and an open-source [CLI](https://www.linode.com/docs/products/tools/cli/get-started/) tool to interact with the API which will be used in this guide. +Make sure to follow installation and authentication instructions for the `linode-cli` tool. + +### Upload image + +Download the Akamai image `akamai-amd64.raw.gz` from the [latest Talos release](https://github.com/siderolabs/talos/releases/latest/). + +Upload the image + +```bash +export REGION=us-ord + +linode-cli image-upload --region ${REGION} --label talos _out/akamai-amd64.raw.gz +``` + +### Create a Load Balancer + +```bash +export REGION=us-ord + +linode-cli nodebalancers create --region ${REGION} --no-defaults --label talos +export NODEBALANCER_ID=$(linode-cli nodebalancers list --label talos --format id --text --no-headers) +linode-cli nodebalancers config-create --port 443 --protocol tcp --check connection ${NODEBALANCER_ID} +``` + +### Create the Machine Configuration Files + +Using the IP address (or DNS name, if you have created one) of the loadbalancer, generate the base configuration files for the Talos machines. +Also note that the load balancer forwards port 443 to port 6443 on the associated nodes, so we should use 443 as the port in the config definition: + +```bash +export NODEBALANCER_IP=$(linode-cli nodebalancers list --label talos --format ipv4 --text --no-headers) + +talosctl gen config talos-kubernetes-akamai https://${NODEBALANCER_IP} --with-examples=false +``` + +### Create the Linodes + +#### Create the Control Plane Nodes + +Run the following commands to create three control plane nodes: + +```bash +export NODEBALANCER_ID=$(linode-cli nodebalancers list --label talos --format id --text --no-headers) +export NODEBALANCER_CONFIG_ID=$(linode-cli nodebalancers configs-list ${NODEBALANCER_ID} --format id --text --no-headers) +export REGION=us-ord + +for id in $(seq 3); do + linode_label="talos-control-plane-${id}" + # create linode + linode-cli linodes create \ + --no-defaults \ + --root_pass securepass123! \ + --type g6-standard-4 \ + --region ${REGION} \ + --image ${image_id} \ + --label ${linode_label} \ + --private_ip true \ + --tags talos-control-plane \ + --group "talos-control-plane" \ + --metadata.user_data "$(cat ./controlplane.yaml | base64)" + + # change kernel to "direct disk" + linode_id=$(linode-cli linodes list --label ${linode_label} --format id --text --no-headers) + confiig_id=$(linode-cli linodes configs-list ${linode_id} --format id --text --no-headers) + linode-cli linodes config-update ${linode_id} ${confiig_id} --kernel "linode/direct-disk" + + # add machine to nodebalancer + private_ip=$(linode-cli linodes list --label ${linode_label} --format ipv4 --json | jq -r ".[0].ipv4[1]") + linode-cli nodebalancers node-create ${NODEBALANCER_ID} ${NODEBALANCER_CONFIG_ID} --label ${linode_label} --address ${private_ip}:6443 +done +``` + +#### Create the Worker Nodes + +Run the following to create a worker node: + +```bash +export IMAGE_ID=$(linode-cli images list --label talos --format id --text --no-headers) +export REGION=us-ord +export LINODE_LABEL="talos-worker-1" +linode-cli linodes create \ + --no-defaults \ + --root_pass akamaipass123! \ + --type g6-standard-4 \ + --region us-ord \ + --image ${IMAGE_ID} \ + --label ${LINODE_LABEL} \ + --private_ip true \ + --tags talos-worker \ + --group "talos-worker" \ + --metadata.user_data "$(cat ./worker.yaml | base64)" +linode_id=$(linode-cli linodes list --label ${LINODE_LABEL} --format id --text --no-headers) +confiig_id=$(linode-cli linodes configs-list ${linode_id} --format id --text --no-headers) +linode-cli linodes config-update ${linode_id} ${confiig_id} --kernel "linode/direct-disk" +``` + +### Bootstrap Etcd + +Set the `endpoints` and `nodes`: + +```bash +export LINODE_LABEL=talos-control-plane-1 +export LINODE_IP=$(linode-cli linodes list --label ${LINODE_LABEL} --format ipv4 --json | jq -r ".[0].ipv4[0]") +talosctl --talosconfig talosconfig config endpoint ${LINODE_IP} +talosctl --talosconfig talosconfig config node ${LINODE_IP} +``` + +Bootstrap `etcd`: + +```bash +talosctl --talosconfig talosconfig bootstrap +``` + +### Retrieve the `kubeconfig` + +At this point we can retrieve the admin `kubeconfig` by running: + +```bash +talosctl --talosconfig talosconfig kubeconfig . +``` + +We can also watch the cluster bootstrap via: + +```bash +talosctl --talosconfig talosconfig health +```