feat: add OpenNebula platform support
Initial support without documentation. Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com> Signed-off-by: shurkys <no@mail.com>
This commit is contained in:
parent
914f887788
commit
989ca3ade1
@ -932,6 +932,8 @@ local release = {
|
||||
'_out/metal-nanopi_r4s-arm64.raw.xz',
|
||||
'_out/nocloud-amd64.raw.xz',
|
||||
'_out/nocloud-arm64.raw.xz',
|
||||
'_out/opennebula-amd64.raw.xz',
|
||||
'_out/opennebula-arm64.raw.xz',
|
||||
'_out/openstack-amd64.raw.xz',
|
||||
'_out/openstack-arm64.raw.xz',
|
||||
'_out/oracle-amd64.qcow2.xz',
|
||||
|
2
Makefile
2
Makefile
@ -317,7 +317,7 @@ image-%: ## Builds the specified image. Valid options are aws, azure, digital-oc
|
||||
|
||||
images-essential: image-aws 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-openstack image-oracle image-scaleway image-upcloud image-vmware image-vultr ## Builds all known images (AWS, Azure, DigitalOcean, Exoscale, GCP, HCloud, Metal, NoCloud, Openstack, Oracle, Scaleway, UpCloud, Vultr and VMware).
|
||||
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).
|
||||
|
||||
sbc-%: ## Builds the specified SBC image. Valid options are rpi_generic, rock64, bananapi_m64, libretech_all_h3_cc_h5, rockpi_4, rockpi_4c, pine64, jetson_nano and nanopi_r4s (e.g. sbc-rpi_generic)
|
||||
@docker pull $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG)
|
||||
|
@ -55,6 +55,12 @@ machine:
|
||||
title = "Kubernetes API Server Service Account Key"
|
||||
description = """\
|
||||
Talos Linux starting from this release uses RSA key for Kubernetes API Server Service Account instead of ECDSA key to provide better compatibility with external OpenID Connect implementations.
|
||||
"""
|
||||
|
||||
[notes.opennebula]
|
||||
title = "OpenNebula"
|
||||
description = """\
|
||||
Talos Linux now supports OpenNebula platform.
|
||||
"""
|
||||
|
||||
[make_deps]
|
||||
|
@ -0,0 +1,69 @@
|
||||
// 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 opennebula provides the OpenNebula platform implementation.
|
||||
package opennebula
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/siderolabs/go-blockdevice/blockdevice/filesystem"
|
||||
"github.com/siderolabs/go-blockdevice/blockdevice/probe"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
configISOLabel = "context"
|
||||
oneContextPath = "context.sh"
|
||||
mnt = "/mnt"
|
||||
)
|
||||
|
||||
func (o *OpenNebula) contextFromCD() (oneContext []byte, err error) {
|
||||
var dev *probe.ProbedBlockDevice
|
||||
|
||||
dev, err = probe.GetDevWithFileSystemLabel(strings.ToLower(configISOLabel))
|
||||
if err != nil {
|
||||
dev, err = probe.GetDevWithFileSystemLabel(strings.ToUpper(configISOLabel))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find %s iso: %w", configISOLabel, err)
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
defer dev.Close()
|
||||
|
||||
sb, err := filesystem.Probe(dev.Path)
|
||||
if err != nil || sb == nil {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
log.Printf("found config disk (context) at %s", dev.Path)
|
||||
|
||||
if err = unix.Mount(dev.Path, mnt, sb.Type(), unix.MS_RDONLY, ""); err != nil {
|
||||
return nil, fmt.Errorf("failed to mount iso: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("fetching context from: %s/", oneContextPath)
|
||||
|
||||
oneContext, err = os.ReadFile(filepath.Join(mnt, oneContextPath))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read config: %s", err.Error())
|
||||
}
|
||||
|
||||
if err = unix.Unmount(mnt, 0); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmount: %w", err)
|
||||
}
|
||||
|
||||
if oneContext == nil {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
return oneContext, nil
|
||||
}
|
@ -0,0 +1,252 @@
|
||||
// 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 opennebula
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/hashicorp/go-envparse"
|
||||
"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/errors"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/internal/address"
|
||||
"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"
|
||||
)
|
||||
|
||||
// OpenNebula is the concrete type that implements the runtime.Platform interface.
|
||||
type OpenNebula struct{}
|
||||
|
||||
// Name implements the runtime.Platform interface.
|
||||
func (o *OpenNebula) Name() string {
|
||||
return "opennebula"
|
||||
}
|
||||
|
||||
// ParseMetadata converts opennebula metadata to platform network config.
|
||||
//
|
||||
//nolint:gocyclo
|
||||
func (o *OpenNebula) ParseMetadata(st state.State, oneContextPlain []byte) (*runtime.PlatformNetworkConfig, error) {
|
||||
// Initialize the PlatformNetworkConfig
|
||||
networkConfig := &runtime.PlatformNetworkConfig{}
|
||||
|
||||
oneContext, err := envparse.Parse(bytes.NewReader(oneContextPlain))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse context file %q: %w", oneContextPlain, err)
|
||||
}
|
||||
|
||||
// Create HostnameSpecSpec entry
|
||||
hostnameValue := oneContext["HOSTNAME"]
|
||||
if hostnameValue == "" {
|
||||
hostnameValue = oneContext["SET_HOSTNAME"]
|
||||
if hostnameValue == "" {
|
||||
hostnameValue = oneContext["NAME"]
|
||||
}
|
||||
}
|
||||
|
||||
if oneContext["NETWORK"] == "YES" {
|
||||
// Iterate through parsed environment variables
|
||||
for key := range oneContext {
|
||||
// Dereference the pointer here
|
||||
if strings.HasPrefix(key, "ETH") && strings.HasSuffix(key, "_MAC") {
|
||||
ifaceName := strings.TrimSuffix(key, "_MAC")
|
||||
ifaceNameLower := strings.ToLower(ifaceName)
|
||||
|
||||
if oneContext[ifaceName+"_METHOD"] == "dhcp" {
|
||||
// Create DHCP4 OperatorSpec entry
|
||||
networkConfig.Operators = append(networkConfig.Operators,
|
||||
network.OperatorSpecSpec{
|
||||
Operator: network.OperatorDHCP4,
|
||||
LinkName: ifaceNameLower,
|
||||
RequireUp: true,
|
||||
DHCP4: network.DHCP4OperatorSpec{
|
||||
RouteMetric: 1024,
|
||||
SkipHostnameRequest: true,
|
||||
},
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
// Parse IP address and create AddressSpecSpec entry
|
||||
ipPrefix, err := address.IPPrefixFrom(oneContext[ifaceName+"_IP"], oneContext[ifaceName+"_MASK"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse IP address: %w", err)
|
||||
}
|
||||
|
||||
networkConfig.Addresses = append(networkConfig.Addresses,
|
||||
network.AddressSpecSpec{
|
||||
Address: ipPrefix,
|
||||
LinkName: ifaceNameLower,
|
||||
Family: nethelpers.FamilyInet4,
|
||||
Scope: nethelpers.ScopeGlobal,
|
||||
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
|
||||
AnnounceWithARP: false,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
},
|
||||
)
|
||||
var mtu uint32
|
||||
if oneContext[ifaceName+"_MTU"] == "" {
|
||||
mtu = 0
|
||||
} else {
|
||||
var mtu64 uint64
|
||||
|
||||
mtu64, err = strconv.ParseUint(oneContext[ifaceName+"_MTU"], 10, 32)
|
||||
// check if any error happened
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse MTU: %w", err)
|
||||
}
|
||||
|
||||
mtu = uint32(mtu64)
|
||||
}
|
||||
|
||||
// Create LinkSpecSpec entry
|
||||
networkConfig.Links = append(networkConfig.Links,
|
||||
network.LinkSpecSpec{
|
||||
Name: ifaceNameLower,
|
||||
Logical: false,
|
||||
Up: true,
|
||||
MTU: mtu,
|
||||
Kind: "",
|
||||
Type: nethelpers.LinkEther,
|
||||
ParentName: "",
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
},
|
||||
)
|
||||
|
||||
// Parse gateway address and create RouteSpecSpec entry
|
||||
gateway, err := netip.ParseAddr(oneContext[ifaceName+"_GATEWAY"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse gateway ip: %w", err)
|
||||
}
|
||||
|
||||
route := network.RouteSpecSpec{
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
Gateway: gateway,
|
||||
OutLinkName: ifaceNameLower,
|
||||
Table: nethelpers.TableMain,
|
||||
Protocol: nethelpers.ProtocolStatic,
|
||||
Type: nethelpers.TypeUnicast,
|
||||
Family: nethelpers.FamilyInet4,
|
||||
Priority: network.DefaultRouteMetric,
|
||||
}
|
||||
|
||||
route.Normalize()
|
||||
|
||||
networkConfig.Routes = append(networkConfig.Routes, route)
|
||||
|
||||
// Parse DNS servers
|
||||
dnsServers := strings.Fields(oneContext[ifaceName+"_DNS"])
|
||||
var dnsIPs []netip.Addr
|
||||
|
||||
for _, dnsServer := range dnsServers {
|
||||
ip, err := netip.ParseAddr(dnsServer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse DNS server IP: %w", err)
|
||||
}
|
||||
dnsIPs = append(dnsIPs, ip)
|
||||
}
|
||||
|
||||
// Create ResolverSpecSpec entry with multiple DNS servers
|
||||
networkConfig.Resolvers = append(networkConfig.Resolvers,
|
||||
network.ResolverSpecSpec{
|
||||
DNSServers: dnsIPs,
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Create HostnameSpecSpec entry
|
||||
networkConfig.Hostnames = append(networkConfig.Hostnames,
|
||||
network.HostnameSpecSpec{
|
||||
Hostname: hostnameValue,
|
||||
Domainname: oneContext["DNS_HOSTNAME"],
|
||||
ConfigLayer: network.ConfigPlatform,
|
||||
},
|
||||
)
|
||||
|
||||
// Create Metadata entry
|
||||
networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
|
||||
Platform: o.Name(),
|
||||
Hostname: hostnameValue,
|
||||
InstanceID: oneContext["VMID"],
|
||||
}
|
||||
|
||||
return networkConfig, nil
|
||||
}
|
||||
|
||||
// Configuration implements the runtime.Platform interface.
|
||||
func (o *OpenNebula) Configuration(ctx context.Context, r state.State) (machineConfig []byte, err error) {
|
||||
oneContextPlain, err := o.contextFromCD()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oneContext, err := envparse.Parse(bytes.NewReader(oneContextPlain))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse environment file %q: %w", oneContextPlain, err)
|
||||
}
|
||||
|
||||
userData, ok := oneContext["USER_DATA"]
|
||||
if !ok {
|
||||
return nil, errors.ErrNoConfigSource
|
||||
}
|
||||
|
||||
machineConfig, err = base64.StdEncoding.DecodeString(userData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode USER_DATA: %v", err)
|
||||
}
|
||||
|
||||
return machineConfig, nil
|
||||
}
|
||||
|
||||
// Mode implements the runtime.Platform interface.
|
||||
func (o *OpenNebula) Mode() runtime.Mode {
|
||||
return runtime.ModeCloud
|
||||
}
|
||||
|
||||
// KernelArgs implements the runtime.Platform interface.
|
||||
func (o *OpenNebula) KernelArgs() procfs.Parameters {
|
||||
return []*procfs.Parameter{
|
||||
procfs.NewParameter("console").Append("tty1").Append("ttyS0"),
|
||||
procfs.NewParameter(constants.KernelParamNetIfnames).Append("0"),
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkConfiguration implements the runtime.Platform interface.
|
||||
func (o *OpenNebula) NetworkConfiguration(ctx context.Context, st state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
|
||||
oneContext, err := o.contextFromCD()
|
||||
if stderrors.Is(err, errors.ErrNoConfigSource) {
|
||||
err = nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networkConfig, err := o.ParseMetadata(st, oneContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
select {
|
||||
case ch <- networkConfig:
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
// 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/.
|
||||
// go test -v ./internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula
|
||||
package opennebula_test
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/cosi-project/runtime/pkg/state"
|
||||
"github.com/cosi-project/runtime/pkg/state/impl/inmem"
|
||||
"github.com/cosi-project/runtime/pkg/state/impl/namespaced"
|
||||
"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/opennebula"
|
||||
)
|
||||
|
||||
//go:embed testdata/metadata.yaml
|
||||
var oneContextPlain []byte
|
||||
|
||||
//go:embed testdata/expected.yaml
|
||||
var expectedNetworkConfig string
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
o := &opennebula.OpenNebula{}
|
||||
st := state.WrapCore(namespaced.NewState(inmem.Build))
|
||||
|
||||
networkConfig, err := o.ParseMetadata(st, oneContextPlain)
|
||||
require.NoError(t, err)
|
||||
|
||||
marshaled, err := yaml.Marshal(networkConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
fmt.Print(marshaled)
|
||||
assert.Equal(t, expectedNetworkConfig, string(marshaled))
|
||||
}
|
45
internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula/testdata/expected.yaml
vendored
Normal file
45
internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula/testdata/expected.yaml
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
addresses:
|
||||
- address: 192.168.1.92/24
|
||||
linkName: eth0
|
||||
family: inet4
|
||||
scope: global
|
||||
flags: permanent
|
||||
layer: platform
|
||||
links:
|
||||
- name: eth0
|
||||
logical: false
|
||||
up: true
|
||||
mtu: 0
|
||||
kind: ""
|
||||
type: ether
|
||||
layer: platform
|
||||
routes:
|
||||
- family: inet4
|
||||
dst: ""
|
||||
src: ""
|
||||
gateway: 192.168.1.1
|
||||
outLinkName: eth0
|
||||
table: main
|
||||
priority: 1024
|
||||
scope: global
|
||||
type: unicast
|
||||
flags: ""
|
||||
protocol: static
|
||||
layer: platform
|
||||
hostnames:
|
||||
- hostname: code-server
|
||||
domainname: ""
|
||||
layer: platform
|
||||
resolvers:
|
||||
- dnsServers:
|
||||
- 192.168.1.1
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
layer: platform
|
||||
timeServers: []
|
||||
operators: []
|
||||
externalIPs: []
|
||||
metadata:
|
||||
platform: opennebula
|
||||
hostname: code-server
|
||||
instanceId: "14"
|
28
internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula/testdata/metadata.yaml
vendored
Normal file
28
internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula/testdata/metadata.yaml
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# Context variables generated by OpenNebula
|
||||
DISK_ID = "1"
|
||||
ETH0_DNS = "192.168.1.1 8.8.8.8 1.1.1.1"
|
||||
ETH0_EXTERNAL = ""
|
||||
ETH0_GATEWAY = "192.168.1.1"
|
||||
ETH0_IP = "192.168.1.92"
|
||||
ETH0_IP6 = ""
|
||||
ETH0_IP6_GATEWAY = ""
|
||||
ETH0_IP6_METHOD = ""
|
||||
ETH0_IP6_METRIC = ""
|
||||
ETH0_IP6_PREFIX_LENGTH = ""
|
||||
ETH0_IP6_ULA = ""
|
||||
ETH0_MAC = "02:00:c0:a8:01:5c"
|
||||
ETH0_MASK = "255.255.255.0"
|
||||
ETH0_METHOD = ""
|
||||
ETH0_METRIC = ""
|
||||
ETH0_MTU = ""
|
||||
ETH0_NETWORK = "192.168.1.0"
|
||||
ETH0_SEARCH_DOMAIN = ""
|
||||
ETH0_VLAN_ID = "3"
|
||||
ETH0_VROUTER_IP = ""
|
||||
ETH0_VROUTER_IP6 = ""
|
||||
ETH0_VROUTER_MANAGEMENT = ""
|
||||
NETWORK = "YES"
|
||||
SSH_PUBLIC_KEY = ""
|
||||
TARGET = "hda"
|
||||
VMID = "14"
|
||||
NAME = "code-server"
|
@ -25,6 +25,7 @@ import (
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/hcloud"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/metal"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/opennebula"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle"
|
||||
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/scaleway"
|
||||
@ -103,6 +104,8 @@ func newPlatform(platform string) (p runtime.Platform, err error) {
|
||||
p = &hcloud.Hcloud{}
|
||||
case constants.PlatformMetal:
|
||||
p = &metal.Metal{}
|
||||
case "opennebula":
|
||||
p = &opennebula.OpenNebula{}
|
||||
case "openstack":
|
||||
p = &openstack.Openstack{}
|
||||
case "oracle":
|
||||
|
@ -167,6 +167,18 @@ var Default = map[string]Profile{
|
||||
},
|
||||
},
|
||||
},
|
||||
"opennebula": {
|
||||
Platform: "opennebula",
|
||||
SecureBoot: pointer.To(false),
|
||||
Output: Output{
|
||||
Kind: OutKindImage,
|
||||
OutFormat: OutFormatXZ,
|
||||
ImageOptions: &ImageOptions{
|
||||
DiskSize: MinRAWDiskSize,
|
||||
DiskFormat: DiskFormatRaw,
|
||||
},
|
||||
},
|
||||
},
|
||||
"openstack": {
|
||||
Platform: "openstack",
|
||||
SecureBoot: pointer.To(false),
|
||||
|
@ -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, 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 | 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, Raspberry Pi 4B, Raspberry Pi Compute Module 4 | Banana Pi M64, Jetson Nano, Libre Computer Board ALL-H3-CC, Nano Pi R4S, Pine64, Pine64 Rock64, Radxa ROCK Pi 4c, Raspberry Pi 4B, Raspberry Pi Compute Module 4 |
|
||||
@ -46,6 +46,7 @@ description: "Table of supported Talos Linux versions and respective platforms."
|
||||
* Exoscale
|
||||
* Hetzner
|
||||
* nocloud
|
||||
* OpenNebula
|
||||
* Oracle Cloud
|
||||
* Scaleway
|
||||
* Vultr
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
title: "OpenNebula"
|
||||
---
|
||||
|
||||
Talos is known to work on [OpenNebula](https://opennebula.io/).
|
Loading…
x
Reference in New Issue
Block a user