From 0d1c5ac30575f7157cd16d88305a31b76c97e680 Mon Sep 17 00:00:00 2001 From: Andrew Rynhard Date: Mon, 28 Oct 2019 15:43:59 +0000 Subject: [PATCH] feat: add support for Digital Ocean This adds a Digital Ocean platform implementation. Signed-off-by: Andrew Rynhard --- Makefile | 13 ++ docs/website/content/v0.3.en.json | 4 + .../v0.3/en/guides/cloud/digitalocean.md | 143 ++++++++++++++++++ .../platform/digitalocean/digitalocean.go | 91 +++++++++++ .../digitalocean/digitalocean_test.go | 14 ++ internal/pkg/runtime/platform/platform.go | 3 + 6 files changed, 268 insertions(+) create mode 100644 docs/website/content/v0.3/en/guides/cloud/digitalocean.md create mode 100644 internal/pkg/runtime/platform/digitalocean/digitalocean.go create mode 100644 internal/pkg/runtime/platform/digitalocean/digitalocean_test.go diff --git a/Makefile b/Makefile index b604b33dc..2f71ac647 100644 --- a/Makefile +++ b/Makefile @@ -190,6 +190,19 @@ image-azure: @tar -C $(PWD)/build -czf $(PWD)/build/azure.tar.gz azure.vhd @rm -rf $(PWD)/build/azure.raw $(PWD)/build/azure.vhd +.PHONY: image-digital-ocean +image-digital-ocean: + @docker run --rm -v /dev:/dev -v $(PWD)/build:/out \ + --privileged $(DOCKER_ARGS) \ + autonomy/installer:$(TAG) \ + install \ + -n digital-ocean \ + -r \ + -p digital-ocean \ + -u none \ + -e console=ttyS0 + @gzip $(PWD)/build/digital-ocean.raw + .PHONY: image-gcp image-gcp: @docker run --rm -v /dev:/dev -v $(PWD)/build:/out \ diff --git a/docs/website/content/v0.3.en.json b/docs/website/content/v0.3.en.json index db4da50c1..93765fd52 100644 --- a/docs/website/content/v0.3.en.json +++ b/docs/website/content/v0.3.en.json @@ -19,6 +19,10 @@ "title": "Azure", "path": "v0.3/en/guides/cloud/azure" }, + { + "title": "Digital Ocean", + "path": "v0.3/en/guides/cloud/digitalocean" + }, { "title": "GCP", "path": "v0.3/en/guides/cloud/gcp" diff --git a/docs/website/content/v0.3/en/guides/cloud/digitalocean.md b/docs/website/content/v0.3/en/guides/cloud/digitalocean.md new file mode 100644 index 000000000..68e51767f --- /dev/null +++ b/docs/website/content/v0.3/en/guides/cloud/digitalocean.md @@ -0,0 +1,143 @@ +--- +title: 'Digital Ocean' +--- + +## Creating a Cluster via the CLI + +In this guide we will create an HA Kubernetes cluster with 1 worker node. +We assume an existing [Space](https://www.digitalocean.com/docs/spaces/), and some familiarity with Digital Ocean. +If you need more information on Digital Ocean specifics, please see the [official Digital Ocean documentation](https://www.digitalocean.com/docs/). + +### Create the Image + +First, download the Digital Ocean image from a Talos release. + +Using an upload method of your choice (`doctl` does not have Spaces support), upload the image to a space. +Now, create an image using the URL of the uploaded image: + +```bash +doctl compute image create \ + --region $REGION \ + --image-name talos-digital-ocean-tutorial \ + --image-url https://talos-tutorial.$REGION.digitaloceanspaces.com/digital-ocean.raw.gz \ + Talos +``` + +### Create a Load Balancer + +```bash +doctl compute load-balancer create \ + --region $REGION \ + --name talos-digital-ocean-tutorial-lb \ + --tag-name talos-digital-ocean-tutorial-control-plane \ + --health-check protocol:tcp,port:443,check_interval_seconds:10,response_timeout_seconds:5,healthy_threshold:5,unhealthy_threshold:3 + --forwarding-rules entry_protocol:tcp,entry_port:443,target_protocol:tcp,target_port:6443 +``` + +We will need the IP of the load balancer. +To retrieve it, run: + +```bash +doctl compute load-balancer get --format IP +``` + +Save it, as we will need it in the next step. + +### Create the Machine Configuration Files + +#### Generating Base Configurations + +Using the DNS name of the loadbalancer created earlier, generate the base configuration files for the Talos machines: + +```bash +$ osctl config generate talos-k8s-digital-ocean-tutorial https://$LB_IP +created init.yaml +created controlplane.yaml +created join.yaml +created talosconfig +``` + +At this point, you can modify the generated configs to your liking. + +#### Validate the Configuration Files + +```bash +$ osctl validate --config init.yaml --mode cloud +init.yaml is valid for cloud mode +$ osctl validate --config controlplane.yaml --mode cloud +controlplane.yaml is valid for cloud mode +$ osctl validate --config join.yaml --mode cloud +join.yaml is valid for cloud mode +``` + +### Create the Droplets + +#### Create the Bootstrap Node + +```bash +doctl compute droplet create \ + --region $REGION \ + --image 54190082 \ + --size s-2vcpu-4gb \ + --enable-private-networking \ + --tag-names talos-digital-ocean-tutorial-control-plane \ + --user-data-file init.yaml \ + --ssh-keys '' \ + talos-control-plane-1 +``` + +#### Create the Remaining Control Plane Nodes + +Run the following twice, to give ourselves three total control plane nodes: + +```bash +doctl compute droplet create \ + --region $REGION \ + --image 54190082 \ + --size s-2vcpu-4gb \ + --enable-private-networking \ + --tag-names talos-digital-ocean-tutorial-control-plane \ + --user-data-file controlplane.yaml \ + --ssh-keys '' \ + talos-control-plane-2 +doctl compute droplet create \ + --region $REGION \ + --image 54190082 \ + --size s-2vcpu-4gb \ + --enable-private-networking \ + --tag-names talos-digital-ocean-tutorial-control-plane \ + --user-data-file controlplane.yaml \ + --ssh-keys '' \ + talos-control-plane-3 +``` + +#### Create the Worker Nodes + +Run the following to create a worker node: + +```bash +doctl compute droplet create \ + --region $REGION \ + --image 54190082 \ + --size s-2vcpu-4gb \ + --enable-private-networking \ + --tag-names talos-digital-ocean-tutorial-control-plane \ + --user-data-file join.yaml \ + --ssh-keys '' \ + talos-worker-1 +``` + +### Retrieve the `kubeconfig` + +To configure `osctl` we will need the first controla plane node's IP: + +```bash +doctl compute droplet get --format PublicIPv4 +``` + +At this point we can retrieve the admin `kubeconfig` by running: + +```bash +osctl --talosconfig talosconfig config target +osctl --talosconfig talosconfig kubeconfig > kubeconfig +``` diff --git a/internal/pkg/runtime/platform/digitalocean/digitalocean.go b/internal/pkg/runtime/platform/digitalocean/digitalocean.go new file mode 100644 index 000000000..e8c3aa8da --- /dev/null +++ b/internal/pkg/runtime/platform/digitalocean/digitalocean.go @@ -0,0 +1,91 @@ +// 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 digitalocean + +import ( + "fmt" + "io/ioutil" + "net" + "net/http" + + "github.com/talos-systems/talos/internal/pkg/runtime" + "github.com/talos-systems/talos/pkg/download" +) + +const ( + // DigitalOceanExternalIPEndpoint displays all external addresses associated with the instance + DigitalOceanExternalIPEndpoint = "http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address" + // DigitalOceanHostnameEndpoint is the local endpoint for the hostname. + DigitalOceanHostnameEndpoint = "http://169.254.169.254/metadata/v1/hostname" + // DigitalOceanUserDataEndpoint is the local endpoint for the config. + DigitalOceanUserDataEndpoint = "http://169.254.169.254/metadata/v1/user-data" +) + +// DigitalOcean is the concrete type that implements the platform.Platform interface. +type DigitalOcean struct{} + +// Name implements the platform.Platform interface. +func (a *DigitalOcean) Name() string { + return "Digital Ocean" +} + +// Configuration implements the platform.Platform interface. +func (a *DigitalOcean) Configuration() ([]byte, error) { + return download.Download(DigitalOceanUserDataEndpoint) +} + +// Mode implements the platform.Platform interface. +func (a *DigitalOcean) Mode() runtime.Mode { + return runtime.Cloud +} + +// Hostname gets the hostname from the DigitalOcean metadata endpoint. +func (a *DigitalOcean) Hostname() (hostname []byte, err error) { + resp, err := http.Get(DigitalOceanHostnameEndpoint) + if err != nil { + return + } + // nolint: errcheck + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return hostname, fmt.Errorf("failed to fetch hostname from metadata service: %d", resp.StatusCode) + } + + return ioutil.ReadAll(resp.Body) +} + +// ExternalIPs provides any external addresses assigned to the instance +func (a *DigitalOcean) ExternalIPs() (addrs []net.IP, err error) { + var ( + body []byte + req *http.Request + resp *http.Response + ) + + if req, err = http.NewRequest("GET", DigitalOceanExternalIPEndpoint, nil); err != nil { + return + } + + client := &http.Client{} + if resp, err = client.Do(req); err != nil { + return + } + + // nolint: errcheck + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return addrs, fmt.Errorf("failed to retrieve external addresses for instance") + } + + if body, err = ioutil.ReadAll(resp.Body); err != nil { + return + } + + addrs = append(addrs, net.ParseIP(string(body))) + + return addrs, err +} diff --git a/internal/pkg/runtime/platform/digitalocean/digitalocean_test.go b/internal/pkg/runtime/platform/digitalocean/digitalocean_test.go new file mode 100644 index 000000000..b0e5d9094 --- /dev/null +++ b/internal/pkg/runtime/platform/digitalocean/digitalocean_test.go @@ -0,0 +1,14 @@ +// 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 digitalocean_test + +import "testing" + +func TestEmpty(t *testing.T) { + // added for accurate coverage estimation + // + // please remove it once any unit-test is added + // for this package +} diff --git a/internal/pkg/runtime/platform/platform.go b/internal/pkg/runtime/platform/platform.go index 49686e9ff..720605bac 100644 --- a/internal/pkg/runtime/platform/platform.go +++ b/internal/pkg/runtime/platform/platform.go @@ -14,6 +14,7 @@ import ( "github.com/talos-systems/talos/internal/pkg/runtime/platform/aws" "github.com/talos-systems/talos/internal/pkg/runtime/platform/azure" "github.com/talos-systems/talos/internal/pkg/runtime/platform/container" + "github.com/talos-systems/talos/internal/pkg/runtime/platform/digitalocean" "github.com/talos-systems/talos/internal/pkg/runtime/platform/gcp" "github.com/talos-systems/talos/internal/pkg/runtime/platform/iso" "github.com/talos-systems/talos/internal/pkg/runtime/platform/metal" @@ -44,6 +45,8 @@ func NewPlatform() (p runtime.Platform, err error) { p = &aws.AWS{} case "azure": p = &azure.Azure{} + case "digital-ocean": + p = &digitalocean.DigitalOcean{} case "metal": p = &metal.Metal{} case "container":