Explorar el Código

add support for Hetzner cloud metadata

Nikolay Sivko hace 4 años
padre
commit
10b9282643
Se han modificado 6 ficheros con 76 adiciones y 18 borrados
  1. 3 2
      README.md
  2. 1 0
      go.mod
  3. 1 0
      go.sum
  4. 5 16
      node/metadata/azure.go
  5. 42 0
      node/metadata/hetzner.go
  6. 24 0
      node/metadata/metadata.go

+ 3 - 2
README.md

@@ -65,14 +65,15 @@ The [container_oom_kills_total](https://coroot.com/docs/metrics/node-agent#conta
 
 If a node is a cloud instance, the agent identifies a cloud provider and collects additional information using the related metadata services.
 
-Supported cloud providers: [AWS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html), [GCP](https://cloud.google.com/compute/docs/metadata/overview), [Azure](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/instance-metadata-service?tabs=linux)
+Supported cloud providers: [AWS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html), [GCP](https://cloud.google.com/compute/docs/metadata/overview), [Azure](https://docs.microsoft.com/en-us/azure/virtual-machines/linux/instance-metadata-service?tabs=linux), [Hetzner](https://docs.hetzner.cloud/#server-metadata)
 
 Collected info:
 * AccountID
 * InstanceID
 * Instance/machine type
 * Region
-* AvailabilityZone + AvailabilityZoneId (AWS only)
+* AvailabilityZone
+* AvailabilityZoneId (AWS only)
 * LifeCycle: on-demand/spot (AWS and GCP only)
 * Private & Public IP addresses
 

+ 1 - 0
go.mod

@@ -32,6 +32,7 @@ require (
 	golang.org/x/net v0.0.0-20210913180222-943fd674d43e
 	golang.org/x/sys v0.0.0-20210915083310-ed5796bab164
 	gopkg.in/alecthomas/kingpin.v2 v2.2.6
+	gopkg.in/yaml.v2 v2.4.0
 	inet.af/netaddr v0.0.0-20210903134321-85fa6c94624e
 	k8s.io/klog/v2 v2.20.0
 )

+ 1 - 0
go.sum

@@ -1014,6 +1014,7 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 5 - 16
node/metadata/azure.go

@@ -35,29 +35,18 @@ type azureInstanceMetadata struct {
 }
 
 func getAzureMetadata() *CloudMetadata {
-	req, err := http.NewRequest(http.MethodGet, azureEndpoint, nil)
-	if err != nil {
-		klog.Errorln(err)
-		return nil
-	}
-	req.Header.Add("Metadata", "True")
-	q := req.URL.Query()
+	r, _ := http.NewRequest(http.MethodGet, azureEndpoint, nil)
+	r.Header.Add("Metadata", "True")
+	q := r.URL.Query()
 	q.Add("format", "json")
 	q.Add("api-version", "2021-05-01")
-	req.URL.RawQuery = q.Encode()
+	r.URL.RawQuery = q.Encode()
 
-	client := http.DefaultClient
-	client.Timeout = metadataServiceTimeout
-
-	resp, err := client.Do(req)
+	resp, err := httpGetWithTimeout(r)
 	if err != nil {
 		klog.Errorln(err)
 		return nil
 	}
-	if resp.StatusCode != 200 {
-		klog.Errorln("metadata service response:", resp.Status)
-		return nil
-	}
 	defer resp.Body.Close()
 
 	instanceMd := &azureInstanceMetadata{}

+ 42 - 0
node/metadata/hetzner.go

@@ -0,0 +1,42 @@
+package metadata
+
+import (
+	"gopkg.in/yaml.v2"
+	"k8s.io/klog/v2"
+	"net/http"
+)
+
+const hetznerInstanceMetadataURL = "http://169.254.169.254/hetzner/v1/metadata"
+
+type hetznerInstanceMetadata struct {
+	AvailabilityZone string `yaml:"availability-zone"`
+	InstanceId       string `yaml:"instance-id"`
+	PublicIPv4       string `yaml:"public-ipv4"`
+	LocalIPv4        string `yaml:"local-ipv4"`
+	Region           string `yaml:"region"`
+}
+
+func getHetznerMetadata() *CloudMetadata {
+	r, _ := http.NewRequest(http.MethodGet, hetznerInstanceMetadataURL, nil)
+	resp, err := httpGetWithTimeout(r)
+	if err != nil {
+		klog.Errorln(err)
+		return nil
+	}
+	defer resp.Body.Close()
+	md := &hetznerInstanceMetadata{}
+	decoder := yaml.NewDecoder(resp.Body)
+	if err := decoder.Decode(md); err != nil {
+		klog.Errorln("failed to unmarshall response of Hetzner metadata service:", err)
+		return nil
+	}
+	res := &CloudMetadata{
+		Provider:         CloudProviderHetzner,
+		InstanceId:       md.InstanceId,
+		Region:           md.Region,
+		AvailabilityZone: md.AvailabilityZone,
+		LocalIPv4:        md.LocalIPv4,
+		PublicIPv4:       md.PublicIPv4,
+	}
+	return res
+}

+ 24 - 0
node/metadata/metadata.go

@@ -1,7 +1,9 @@
 package metadata
 
 import (
+	"fmt"
 	"k8s.io/klog/v2"
+	"net/http"
 	"os"
 	"strings"
 	"time"
@@ -15,6 +17,7 @@ const (
 	CloudProviderAWS     CloudProvider = "AWS"
 	CloudProviderGCP     CloudProvider = "GCP"
 	CloudProviderAzure   CloudProvider = "Azure"
+	CloudProviderHetzner CloudProvider = "Hetzner"
 	CloudProviderUnknown CloudProvider = ""
 )
 
@@ -47,6 +50,11 @@ func getCloudProvider() CloudProvider {
 			return CloudProviderAzure
 		}
 	}
+	if vendor, err := os.ReadFile("/sys/class/dmi/id/sys_vendor"); err == nil {
+		if strings.TrimSpace(string(vendor)) == "Hetzner" {
+			return CloudProviderHetzner
+		}
+	}
 	return CloudProviderUnknown
 }
 
@@ -60,6 +68,22 @@ func GetInstanceMetadata() *CloudMetadata {
 		return getGcpMetadata()
 	case CloudProviderAzure:
 		return getAzureMetadata()
+	case CloudProviderHetzner:
+		return getHetznerMetadata()
 	}
 	return nil
 }
+
+func httpGetWithTimeout(r *http.Request) (*http.Response, error) {
+	client := http.DefaultClient
+	client.Timeout = metadataServiceTimeout
+	resp, err := client.Do(r)
+	if err != nil {
+		return nil, err
+	}
+	if resp.StatusCode != 200 {
+		klog.Errorln()
+		return nil, fmt.Errorf("metadata service response: %s", resp.Status)
+	}
+	return resp, nil
+}