diff --git a/.drone.jsonnet b/.drone.jsonnet index b44a2a546..e1b22d111 100644 --- a/.drone.jsonnet +++ b/.drone.jsonnet @@ -556,6 +556,8 @@ local release = { '_out/nocloud-arm64.raw.xz', '_out/openstack-amd64.tar.gz', '_out/openstack-arm64.tar.gz', + '_out/oracle-amd64.qcow2.xz', + '_out/oracle-arm64.qcow2.xz', '_out/scaleway-amd64.raw.xz', '_out/scaleway-arm64.raw.xz', '_out/talos-amd64.iso', diff --git a/Makefile b/Makefile index 2fbb930bd..96a850868 100644 --- a/Makefile +++ b/Makefile @@ -228,7 +228,7 @@ image-%: ## Builds the specified image. Valid options are aws, azure, digital-oc images-essential: image-aws image-gcp image-metal ## Builds only essential images used in the CI (AWS, GCP, and Metal). -images: image-aws image-azure image-digital-ocean image-gcp image-hcloud image-metal image-nocloud image-openstack image-scaleway image-upcloud image-vmware image-vultr ## Builds all known images (AWS, Azure, DigitalOcean, GCP, HCloud, Metal, NoCloud, Openstack, Scaleway, UpCloud, Vultr and VMware). +images: image-aws image-azure image-digital-ocean image-gcp image-hcloud image-metal image-nocloud image-openstack image-oracle image-scaleway image-upcloud image-vmware image-vultr ## Builds all known images (AWS, Azure, DigitalOcean, GCP, HCloud, Metal, NoCloud, Openstack, Oracle, Scaleway, UpCloud, Vultr and VMware). sbc-%: ## Builds the specified SBC image. Valid options are rpi_4, rock64, bananapi_m64, libretech_all_h3_cc_h5, rockpi_4 and pine64 (e.g. sbc-rpi_4) @docker pull $(REGISTRY_AND_USERNAME)/imager:$(IMAGE_TAG) diff --git a/cmd/installer/cmd/image.go b/cmd/installer/cmd/image.go index 974d8fa35..04752f079 100644 --- a/cmd/installer/cmd/image.go +++ b/cmd/installer/cmd/image.go @@ -84,7 +84,7 @@ func runImageCmd() (err error) { if options.ConfigSource == "" { switch p.Name() { - case "aws", "azure", "digital-ocean", "gcp", "hcloud", "nocloud", "scaleway", "upcloud", "vultr": + case "aws", "azure", "digital-ocean", "gcp", "hcloud", "nocloud", "oracle", "scaleway", "upcloud", "vultr": options.ConfigSource = constants.ConfigNone case "vmware": options.ConfigSource = constants.ConfigGuestInfo @@ -169,6 +169,19 @@ func finalize(platform runtime.Platform, img, arch string) (err error) { if err = tar(fmt.Sprintf("openstack-%s.tar.gz", arch), file, dir); err != nil { return err } + case "oracle": + name = fmt.Sprintf("oracle-%s.qcow2", arch) + file = filepath.Join(outputArg, name) + + if err = qemuimg.Convert("raw", "qcow2", "cluster_size=8k", img, file); err != nil { + return err + } + + log.Println("compressing image") + + if err = xz(file); err != nil { + return err + } case "scaleway": file = filepath.Join(outputArg, fmt.Sprintf("scaleway-%s.raw", arch)) diff --git a/hack/release.toml b/hack/release.toml index 12dad8e70..a7b575472 100644 --- a/hack/release.toml +++ b/hack/release.toml @@ -52,6 +52,13 @@ Talos now preserves machine configuration as it was submitted to the node. There is some work still going on various cloud platforms to stop modifying machine configuration on the fly. """ + [notes.platforms] + title = "Platform Support" + description="""\ +Talos now supports Oracle Cloud. +""" + + [make_deps] [make_deps.tools] diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/oracle.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/oracle.go new file mode 100644 index 000000000..890928c70 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/oracle.go @@ -0,0 +1,164 @@ +// 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 oracle + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "log" + "net" + + "github.com/AlekSi/pointer" + "github.com/talos-systems/go-procfs/procfs" + + "github.com/talos-systems/talos/internal/app/machined/pkg/runtime" + "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors" + "github.com/talos-systems/talos/pkg/download" + "github.com/talos-systems/talos/pkg/machinery/config" + "github.com/talos-systems/talos/pkg/machinery/config/configloader" + "github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1" +) + +// Ref: https://docs.oracle.com/en-us/iaas/Content/Compute/Tasks/gettingmetadata.htm +const ( + // OracleHostnameEndpoint is the local metadata endpoint for the hostname. + OracleHostnameEndpoint = "http://169.254.169.254/opc/v2/instance/hostname" + // OracleUserDataEndpoint is the local metadata endpoint inside of Oracle Cloud. + OracleUserDataEndpoint = "http://169.254.169.254/opc/v2/instance/metadata/user_data" + // OracleNetworkEndpoint is the local network metadata endpoint inside of Oracle Cloud. + OracleNetworkEndpoint = "http://169.254.169.254/opc/v2/vnics/" +) + +// NetworkConfig holds network interface meta config. +type NetworkConfig struct { + HWAddr string `json:"macAddr"` + PrivateIP string `json:"privateIp"` + VirtualRouterIP string `json:"virtualRouterIp"` + SubnetCidrBlock string `json:"subnetCidrBlock"` + Ipv6SubnetCidrBlock string `json:"ipv6SubnetCidrBlock,omitempty"` + Ipv6VirtualRouterIP string `json:"ipv6VirtualRouterIp,omitempty"` +} + +// Oracle is the concrete type that implements the platform.Platform interface. +type Oracle struct{} + +// Name implements the platform.Platform interface. +func (o *Oracle) Name() string { + return "oracle" +} + +// ConfigurationNetwork implements the network configuration interface. +func (o *Oracle) ConfigurationNetwork(metadataNetworkConfig []byte, confProvider config.Provider) (config.Provider, error) { + var machineConfig *v1alpha1.Config + + machineConfig, ok := confProvider.(*v1alpha1.Config) + if !ok { + return nil, fmt.Errorf("unable to determine machine config type") + } + + if machineConfig.MachineConfig == nil { + machineConfig.MachineConfig = &v1alpha1.MachineConfig{} + } + + if machineConfig.MachineConfig.MachineNetwork == nil { + machineConfig.MachineConfig.MachineNetwork = &v1alpha1.NetworkConfig{} + } + + var interfaceAddresses []NetworkConfig + + if err := json.Unmarshal(metadataNetworkConfig, &interfaceAddresses); err != nil { + return nil, err + } + + if machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces == nil { + for idx, iface := range interfaceAddresses { + ipv6 := iface.Ipv6SubnetCidrBlock != "" && iface.Ipv6VirtualRouterIP != "" + + if ipv6 { + device := &v1alpha1.Device{ + DeviceInterface: fmt.Sprintf("eth%d", idx), + DeviceDHCP: true, + DeviceDHCPOptions: &v1alpha1.DHCPOptions{DHCPIPv6: pointer.ToBool(true)}, + } + + machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces, device) + } + } + } + + return confProvider, nil +} + +// Configuration implements the platform.Platform interface. +func (o *Oracle) Configuration(ctx context.Context) ([]byte, error) { + log.Printf("fetching network config from %q", OracleNetworkEndpoint) + + metadataNetworkConfig, err := download.Download(ctx, OracleNetworkEndpoint, + download.WithHeaders(map[string]string{"Authorization": "Bearer Oracle"})) + if err != nil { + return nil, fmt.Errorf("failed to fetch network config from metadata service") + } + + log.Printf("fetching machine config from: %q", OracleUserDataEndpoint) + + machineConfigDl, err := download.Download(ctx, OracleUserDataEndpoint, + download.WithHeaders(map[string]string{"Authorization": "Bearer Oracle"}), + download.WithErrorOnNotFound(errors.ErrNoConfigSource), + download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource)) + if err != nil { + return nil, err + } + + machineConfig, err := base64.StdEncoding.DecodeString(string(machineConfigDl)) + if err != nil { + return nil, errors.ErrNoConfigSource + } + + confProvider, err := configloader.NewFromBytes(machineConfig) + if err != nil { + return nil, fmt.Errorf("error parsing machine config: %w", err) + } + + confProvider, err = o.ConfigurationNetwork(metadataNetworkConfig, confProvider) + if err != nil { + return nil, err + } + + return confProvider.Bytes() +} + +// Hostname implements the platform.Platform interface. +func (o *Oracle) Hostname(ctx context.Context) (hostname []byte, err error) { + log.Printf("fetching hostname from: %q", OracleHostnameEndpoint) + + hostname, err = download.Download(ctx, OracleHostnameEndpoint, + download.WithHeaders(map[string]string{"Authorization": "Bearer Oracle"}), + download.WithErrorOnNotFound(errors.ErrNoHostname), + download.WithErrorOnEmptyResponse(errors.ErrNoHostname)) + if err != nil { + return nil, err + } + + return hostname, nil +} + +// Mode implements the platform.Platform interface. +func (o *Oracle) Mode() runtime.Mode { + return runtime.ModeCloud +} + +// ExternalIPs implements the runtime.Platform interface. +func (o *Oracle) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) { + return nil, nil +} + +// KernelArgs implements the runtime.Platform interface. +func (o *Oracle) KernelArgs() procfs.Parameters { + return []*procfs.Parameter{ + procfs.NewParameter("console").Append("tty1").Append("ttyS0"), + } +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/oracle_test.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/oracle_test.go new file mode 100644 index 000000000..10dd7c851 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle/oracle_test.go @@ -0,0 +1,60 @@ +// 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 oracle_test + +import ( + "testing" + + "github.com/AlekSi/pointer" + "github.com/stretchr/testify/suite" + + "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle" + "github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1" +) + +type ConfigSuite struct { + suite.Suite +} + +func (suite *ConfigSuite) TestNetworkConfig() { + cfg := []byte(` +[ { + "vnicId" : "ocid1.vnic.oc1.eu-amsterdam-1.asdasd", + "privateIp" : "172.16.1.11", + "vlanTag" : 1, + "macAddr" : "02:00:17:00:00:00", + "virtualRouterIp" : "172.16.1.1", + "subnetCidrBlock" : "172.16.1.0/24", + "ipv6SubnetCidrBlock" : "2603:a:b:c::/64", + "ipv6VirtualRouterIp" : "fe80::a:b:c:d" +} ] +`) + a := &oracle.Oracle{} + + defaultMachineConfig := &v1alpha1.Config{} + + machineConfig := &v1alpha1.Config{ + MachineConfig: &v1alpha1.MachineConfig{ + MachineNetwork: &v1alpha1.NetworkConfig{ + NetworkInterfaces: []*v1alpha1.Device{ + { + DeviceInterface: "eth0", + DeviceDHCP: true, + DeviceDHCPOptions: &v1alpha1.DHCPOptions{DHCPIPv6: pointer.ToBool(true)}, + }, + }, + }, + }, + } + + result, err := a.ConfigurationNetwork(cfg, defaultMachineConfig) + + suite.Require().NoError(err) + suite.Assert().Equal(machineConfig, result) +} + +func TestConfigSuite(t *testing.T) { + suite.Run(t, new(ConfigSuite)) +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/platform.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/platform.go index b35489995..057a34e1f 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/platform.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/platform.go @@ -21,6 +21,7 @@ import ( "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/metal" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack" + "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/oracle" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/packet" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/scaleway" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud" @@ -72,6 +73,8 @@ func newPlatform(platform string) (p runtime.Platform, err error) { p = &metal.Metal{} case "openstack": p = &openstack.Openstack{} + case "oracle": + p = &oracle.Oracle{} case "nocloud": p = &nocloud.Nocloud{} case "packet": diff --git a/website/content/docs/v0.15/Cloud Platforms/oracle.md b/website/content/docs/v0.15/Cloud Platforms/oracle.md new file mode 100644 index 000000000..71ab59562 --- /dev/null +++ b/website/content/docs/v0.15/Cloud Platforms/oracle.md @@ -0,0 +1,105 @@ +--- +title: "Oracle" +description: "Creating a cluster via the CLI (oci) on OracleCloud.com." +--- + +## Upload image + +Oracle Cloud at the moment does not have a Talos official image. +So you can use [Bring Your Own Image (BYOI)](https://docs.oracle.com/en-us/iaas/Content/Compute/References/bringyourownimage.htm) approach. + +Once the image is uploaded, set the ```Boot volume type``` to ``Paravirtualized`` mode. + +OracleCloud has highly available NTP service, it can be enabled in Talos machine config with: + +```yaml +machine: + time: + servers: + - 169.254.169.254 +``` + +## Creating a Cluster via the CLI + +```bash +``` + +### Create a Load Balancer + +Create a load balancer by issuing the commands shown below. +Save the IP/DNS name, as this info will be used in the next step. + +```bash +``` + +### Create the Machine Configuration Files + +#### Generating Base Configurations + +Using the IP/DNS name of the loadbalancer created earlier, generate the base configuration files for the Talos machines by issuing: + +```bash +$ talosctl gen config talos-k8s-oracle-tutorial https://:6443 +created controlplane.yaml +created worker.yaml +created talosconfig +``` + +At this point, you can modify the generated configs to your liking. +Optionally, you can specify `--config-patch` with RFC6902 jsonpatches which will be applied during the config generation. + +#### Validate the Configuration Files + +Validate any edited machine configs with: + +```bash +$ talosctl validate --config controlplane.yaml --mode cloud +controlplane.yaml is valid for cloud mode +$ talosctl validate --config worker.yaml --mode cloud +worker.yaml is valid for cloud mode +``` + +### Create the Servers + +#### Create the Control Plane Nodes + +Create the control plane nodes with: + +```bash +``` + +#### Create the Worker Nodes + +Create the worker nodes with the following command, repeating (and incrementing the name counter) as many times as desired. + +```bash +``` + +### Bootstrap Etcd + +To configure `talosctl` we will need the first control plane node's IP. +This can be found by issuing: + +```bash +``` + +Set the `endpoints` and `nodes` for your talosconfig with: + +```bash +talosctl --talosconfig talosconfig config endpoint +talosctl --talosconfig talosconfig config node +``` + +Bootstrap `etcd` on the first control plane node with: + +```bash +talosctl --talosconfig talosconfig bootstrap +``` + +### Retrieve the `kubeconfig` + +At this point we can retrieve the admin `kubeconfig` by running: + +```bash +talosctl --talosconfig talosconfig kubeconfig . +``` diff --git a/website/content/docs/v0.15/Introduction/support-matrix.md b/website/content/docs/v0.15/Introduction/support-matrix.md index b34131c36..73457478f 100644 --- a/website/content/docs/v0.15/Introduction/support-matrix.md +++ b/website/content/docs/v0.15/Introduction/support-matrix.md @@ -11,7 +11,7 @@ weight: 6 | Kubernetes | 1.23, 1.22, 1.21 | 1.23, 1.22, 1.21 | | Architecture | amd64, arm64 | | **Platforms** | | | -| - cloud | AWS, GCP, Azure, Digital Ocean, Hetzner, OpenStack, Scaleway, Vultr, Upcloud | +| - cloud | AWS, GCP, Azure, Digital Ocean, Hetzner, OpenStack, Oracle Cloud, Scaleway, Vultr, Upcloud | AWS, GCP, Azure, Digital Ocean, Hetzner, OpenStack, Scaleway, Vultr, Upcloud | | - bare metal | x86: BIOS, UEFI; arm64: UEFI; boot: ISO, PXE, disk image | | - virtualized | VMware, Hyper-V, KVM, Proxmox, Xen | | - SBCs | Raspberry Pi4, Banana Pi M64, Pine64, and other | @@ -46,6 +46,7 @@ Tier 3: Not tested by core Talos team, community tested. * Hetzner * nocloud +* Oracle Cloud * Scaleway * Vultr * Upcloud