Skip to content

Commit 5413010

Browse files
committed
edgenet-kubernetes: draft new EdgeNet service in Go
1 parent 88f4463 commit 5413010

23 files changed

Lines changed: 1949 additions & 309 deletions

File tree

.github/workflows/build.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Build
2+
3+
on:
4+
push:
5+
paths-ignore: [ '**.md' ]
6+
pull_request:
7+
paths-ignore: [ '**.md' ]
8+
release:
9+
types: [published]
10+
11+
jobs:
12+
build-amd64:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v2
16+
- uses: actions/setup-go@v2
17+
with:
18+
go-version: '^1.16'
19+
- run: GOARCH=amd64 go build -o node-linux-amd64
20+
- uses: actions/upload-artifact@v2
21+
with:
22+
name: node-linux-amd64
23+
path: node-linux-amd64
24+
- if: github.event_name == 'release'
25+
run: gh release upload --clobber ${{ github.event.release.tag_name }} node-linux-amd64
26+
env:
27+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28+
29+
build-arm64:
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v2
33+
- uses: actions/setup-go@v2
34+
with:
35+
go-version: '^1.16'
36+
- run: GOARCH=arm64 go build -o node-linux-arm64
37+
- uses: actions/upload-artifact@v2
38+
with:
39+
name: node-linux-arm64
40+
path: node-linux-arm64
41+
- if: github.event_name == 'release'
42+
run: gh release upload --clobber ${{ github.event.release.tag_name }} node-linux-arm64
43+
env:
44+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.github/workflows/tests.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ playbook.retry
44
*.log
55
*.tfstate*
66
.terraform*
7+
node

.pre-commit-config.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ exclude: ^(ansible_collections).*$
22

33
repos:
44
- repo: https://github.com/ansible-community/ansible-lint.git
5-
rev: v5.0.12
5+
rev: v5.1.0a1
66
hooks:
77
- id: ansible-lint
88

@@ -11,6 +11,11 @@ repos:
1111
hooks:
1212
- id: shellcheck
1313

14+
- repo: git://github.com/dnephin/pre-commit-golang
15+
rev: v0.4.0
16+
hooks:
17+
- id: go-fmt
18+
1419
- repo: https://github.com/pre-commit/pre-commit-hooks
1520
rev: v4.0.1
1621
hooks:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# EdgeNet Node Setup
22

3-
[![Tests](https://github.com/EdgeNet-project/node/actions/workflows/tests.yml/badge.svg)](https://github.com/EdgeNet-project/node/actions/workflows/tests.yml)
3+
[![Build](https://github.com/EdgeNet-project/node/actions/workflows/build.yml/badge.svg)](https://github.com/EdgeNet-project/node/actions/workflows/build.yml)
44

55
**For instructions on how to use and how to contribute a node to EdgeNet, please see the [EdgeNet website](https://edgenet-project.github.io/).**
66

go.mod

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module github.com/EdgeNet-project/node
2+
3+
go 1.16
4+
5+
require (
6+
cloud.google.com/go v0.84.0
7+
github.com/EdgeNet-project/edgenet v1.0.0-alpha.1.0.20210701214804-887efc4a97b0
8+
github.com/aws/aws-sdk-go v1.38.67
9+
github.com/coreos/go-iptables v0.6.0
10+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7
11+
github.com/thanhpk/randstr v1.0.4
12+
github.com/txn2/txeh v1.3.0
13+
github.com/vishvananda/netlink v1.1.0
14+
github.com/yumaojun03/dmidecode v0.1.4
15+
golang.org/x/sys v0.0.0-20210603125802-9665404d3644
16+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
17+
k8s.io/apimachinery v0.21.2
18+
k8s.io/client-go v0.21.2
19+
)

go.sum

Lines changed: 1032 additions & 0 deletions
Large diffs are not rendered by default.

main.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
Copyright 2021 Contributors to the EdgeNet project.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"fmt"
21+
"github.com/EdgeNet-project/node/pkg/cluster"
22+
"github.com/EdgeNet-project/node/pkg/network"
23+
"github.com/EdgeNet-project/node/pkg/platforms"
24+
"github.com/thanhpk/randstr"
25+
"gopkg.in/yaml.v3"
26+
"log"
27+
"net"
28+
"os"
29+
"path/filepath"
30+
"strings"
31+
)
32+
33+
const defaultKubeconfigURL = "https://raw.githubusercontent.com/EdgeNet-project/edgenet/master/configs/public.cfg"
34+
const edgenetConfigFile = "/opt/edgenet/config.yaml"
35+
const kubeletEnvFile = "/etc/default/kubelet"
36+
37+
func check(err error) {
38+
if err != nil {
39+
log.Panic(err)
40+
}
41+
}
42+
43+
type edgenetConfig struct {
44+
// HostnameRoot is the deterministic component of the hostname (e.g. `aws-eu-west-1a`).
45+
HostnameRoot string `yaml:"hostnameRoot"`
46+
// HostnameSuffix is the random component of the hostname (e.g. `3fe8`).
47+
HostnameSuffix string `yaml:"hostnameSuffix"`
48+
// KubeconfigURL is the URL of the cluster public kubeconfig file.
49+
KubeconfigURL string `yaml:"kubeconfigURL"`
50+
// Platform is the host platform (e.g. `ec2`)
51+
Platform string `yaml:"platform"`
52+
// LocalIPv4 is the IPv4 address associated to the main network interface.
53+
// In presence of NAT, this will be a private IP.
54+
LocalIPv4 net.IP `yaml:"localIPv4"`
55+
// PublicIPv4 is the public IPv4 address of the host.
56+
// This address must be reachable from the Internet.
57+
PublicIPv4 net.IP `yaml:"publicIPv4"`
58+
}
59+
60+
// load the EdgeNet configuration from the specified file.
61+
func (c *edgenetConfig) load(file string) {
62+
buf, err := os.ReadFile(file)
63+
if os.IsNotExist(err) {
64+
return
65+
}
66+
check(err)
67+
check(yaml.Unmarshal(buf, c))
68+
}
69+
70+
// save the EdgeNet configuration to the specified filed.
71+
func (c edgenetConfig) save(file string) {
72+
buf, err := yaml.Marshal(&c)
73+
check(err)
74+
check(os.WriteFile(file, buf, 0644))
75+
}
76+
77+
// getHostnameRoot returns the deterministic component of the hostname.
78+
func getHostnameRoot(platform string) string {
79+
switch platform {
80+
case platforms.Azure:
81+
region := platforms.AzureGetMetadata("compute/location")
82+
return fmt.Sprintf("az-%s", region)
83+
case platforms.EC2:
84+
region := platforms.EC2GetMetadata("placement/availability-zone")
85+
return fmt.Sprintf("aws-%s", region)
86+
case platforms.GENI:
87+
// TODO: From slice name?
88+
geoIP := network.GeoIP()
89+
return strings.ToLower(fmt.Sprintf("geni-%s-%s", geoIP.CountryCode, geoIP.RegionCode))
90+
case platforms.GCP:
91+
region := platforms.GCPGetMetadata("zone")
92+
region = strings.Split(region, "/")[3]
93+
return fmt.Sprintf("gcp-%s", region)
94+
case platforms.NUC:
95+
geoIP := network.GeoIP()
96+
return strings.ToLower(fmt.Sprintf("nuc-%s-%s", geoIP.CountryCode, geoIP.RegionCode))
97+
case platforms.SCW:
98+
meta := platforms.SCWGetMetadata()
99+
return fmt.Sprintf("scw-%s", meta.Location.ZoneID)
100+
default:
101+
geoIP := network.GeoIP()
102+
return strings.ToLower(fmt.Sprintf("%s-%s", geoIP.CountryCode, geoIP.RegionCode))
103+
}
104+
}
105+
106+
// getIPv4 returns the local and public IPv4 addresses associated to the host.
107+
func getIPv4(platform string) (net.IP, net.IP) {
108+
switch platform {
109+
case platforms.Azure:
110+
// The NIC public IP is not available through Azure metadata...
111+
// https://docs.microsoft.com/en-us/answers/questions/7932/public-ip-not-available-via-metadata.html
112+
localIP := net.ParseIP(platforms.AzureGetMetadata("network/interfaces/0/ipv4/ipAddress/0/privateIpAddress"))
113+
publicIP := network.PublicIPv4()
114+
return localIP, publicIP
115+
case platforms.EC2:
116+
localIP := net.ParseIP(platforms.EC2GetMetadata("local-ipv4"))
117+
publicIP := net.ParseIP(platforms.EC2GetMetadata("public-ipv4"))
118+
return localIP, publicIP
119+
case platforms.GCP:
120+
localIP := net.ParseIP(platforms.GCPGetMetadata("network-interfaces/0/ip"))
121+
publicIP := net.ParseIP(platforms.GCPGetMetadata("network-interfaces/0/access-configs/0/external-ip"))
122+
return localIP, publicIP
123+
case platforms.SCW:
124+
meta := platforms.SCWGetMetadata()
125+
localIP := net.ParseIP(meta.PrivateIP)
126+
publicIP := net.ParseIP(meta.PublicIP.Address)
127+
return localIP, publicIP
128+
default:
129+
localIP := network.LocalIPv4()
130+
publicIP := network.PublicIPv4()
131+
return localIP, publicIP
132+
}
133+
}
134+
135+
func main() {
136+
log.Println("step=ensure-dir")
137+
check(os.MkdirAll(filepath.Dir(edgenetConfigFile), 0755))
138+
139+
log.Println("step=load-config")
140+
config := edgenetConfig{}
141+
config.load(edgenetConfigFile)
142+
log.Printf("config=%+v\n", config)
143+
144+
if config.KubeconfigURL == "" {
145+
config.KubeconfigURL = defaultKubeconfigURL
146+
}
147+
148+
if config.Platform == "" {
149+
log.Println("step=detect-platform")
150+
config.Platform = platforms.Detect()
151+
}
152+
153+
if config.HostnameRoot == "" {
154+
log.Println("step=get-hostname-root")
155+
config.HostnameRoot = getHostnameRoot(config.Platform)
156+
}
157+
158+
if config.HostnameSuffix == "" {
159+
log.Println("step=get-hostname-suffix")
160+
config.HostnameSuffix = randstr.Hex(2)
161+
}
162+
163+
if config.LocalIPv4 == nil || config.PublicIPv4 == nil {
164+
log.Println("step=get-ip")
165+
config.LocalIPv4, config.PublicIPv4 = getIPv4(config.Platform)
166+
}
167+
168+
log.Println("step=save-config")
169+
log.Printf("config=%+v\n", config)
170+
config.save(edgenetConfigFile)
171+
172+
if !config.LocalIPv4.Equal(config.PublicIPv4) {
173+
log.Println("step=set-public-ip")
174+
network.AssignPublicIP(config.LocalIPv4, config.PublicIPv4)
175+
network.RewritePublicIP(config.LocalIPv4, config.PublicIPv4)
176+
network.SetKubeletNodeIP(kubeletEnvFile, config.PublicIPv4)
177+
}
178+
179+
log.Println("step=set-hostname")
180+
hostname := fmt.Sprintf("%s-%s", config.HostnameRoot, config.HostnameSuffix)
181+
network.SetHostname(hostname)
182+
183+
log.Println("step=join-cluster")
184+
cluster.Join(defaultKubeconfigURL, config.PublicIPv4, hostname)
185+
}

pkg/cluster/cluster.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
Copyright 2021 Contributors to the EdgeNet project.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cluster
18+
19+
import (
20+
"context"
21+
"github.com/EdgeNet-project/edgenet/pkg/apis/core/v1alpha"
22+
"github.com/EdgeNet-project/edgenet/pkg/generated/clientset/versioned"
23+
"io/ioutil"
24+
"k8s.io/apimachinery/pkg/api/errors"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/client-go/rest"
27+
"k8s.io/client-go/tools/clientcmd"
28+
"log"
29+
"net"
30+
"net/http"
31+
"strings"
32+
)
33+
34+
func check(err error) {
35+
if err != nil {
36+
panic(err)
37+
}
38+
}
39+
func configFromUrl(url string) (*rest.Config, error) {
40+
resp, err := http.Get(url)
41+
if err != nil {
42+
return nil, err
43+
}
44+
buf, err := ioutil.ReadAll(resp.Body)
45+
if err != nil {
46+
return nil, err
47+
}
48+
return clientcmd.RESTConfigFromKubeConfig(buf)
49+
}
50+
51+
func Join(configURL string, externalIP net.IP, hostname string) {
52+
config, err := configFromUrl(configURL)
53+
check(err)
54+
clientset, err := versioned.NewForConfig(config)
55+
check(err)
56+
nodeContributionClient := clientset.CoreV1alpha().NodeContributions()
57+
nodeContribution := &v1alpha.NodeContribution{
58+
ObjectMeta: metav1.ObjectMeta{
59+
Name: strings.ReplaceAll(hostname, ".edge-net.io", ""),
60+
},
61+
Spec: v1alpha.NodeContributionSpec{
62+
Enabled: true,
63+
Host: externalIP.String(),
64+
Port: 22,
65+
Tenant: nil,
66+
User: "edgenet",
67+
},
68+
}
69+
_, err = nodeContributionClient.Create(context.TODO(), nodeContribution.DeepCopy(), metav1.CreateOptions{})
70+
if errors.IsAlreadyExists(err) {
71+
log.Print("node-contribution-status=already-exists")
72+
} else {
73+
panic(err)
74+
}
75+
log.Print("node-contribution-status=created")
76+
}

0 commit comments

Comments
 (0)