ClusterManager: Implements support of remote clusters.
After this change users will be able to:
* Create cluster and add/remove servers to it
* Install apps on remote cluster
* Move already installed apps between clusters
* Apps running on server being removed will auto-migrate
to another server from that same cluster
This is achieved by:
* Installing and running minimal version of dodo on remote cluster
* Ingress-nginx is installed automatically on new clusters
* Next to nginx we run VPN client in the same pod, so that
default cluster can establish secure communication with it
* Multiple reverse proxies are configured to get to the
remote cluster service from ingress installed on default cluster.
Next steps:
* Support remote clusters in dodo apps (prototype ready)
* Clean up old cluster when moving app to the new one. Currently
old cluster keeps running app pods even though no ingress can
reach it anymore.
Change-Id: Iffc908c93416d4126a8e1c2832eae7b659cb8044
diff --git a/core/installer/derived.go b/core/installer/derived.go
index 0351b21..6c1305c 100644
--- a/core/installer/derived.go
+++ b/core/installer/derived.go
@@ -6,6 +6,8 @@
"strings"
)
+const defaultClusterName = "default"
+
type Release struct {
AppInstanceId string `json:"appInstanceId"`
Namespace string `json:"namespace"`
@@ -69,6 +71,7 @@
values any,
schema Schema,
networks []Network,
+ clusters []Cluster,
vpnKeyGen VPNAPIClient,
) (map[string]any, error) {
ret := make(map[string]any)
@@ -95,7 +98,9 @@
// TODO(gio): Improve getField
enabled, ok = getField(root, v).(bool)
if !ok {
- return nil, fmt.Errorf("could not resolve enabled: %+v %s %+v", def.Meta(), v, root)
+ enabled = false
+ // TODO(gio): validate that enabled field exists in the schema
+ // return nil, fmt.Errorf("could not resolve enabled: %+v %s %+v", def.Meta(), v, root)
}
}
if !enabled {
@@ -164,20 +169,36 @@
picked = append(picked, n)
}
ret[k] = picked
+ case KindCluster:
+ name, ok := v.(string)
+ if !ok {
+ // TODO(gio): validate that value has cluster schema
+ ret[k] = v
+ } else {
+ c, err := findCluster(clusters, name)
+ if err != nil {
+ return nil, err
+ }
+ if c == nil {
+ delete(ret, k)
+ } else {
+ ret[k] = c
+ }
+ }
case KindAuth:
- r, err := deriveValues(root, v, AuthSchema, networks, vpnKeyGen)
+ r, err := deriveValues(root, v, AuthSchema, networks, clusters, vpnKeyGen)
if err != nil {
return nil, err
}
ret[k] = r
case KindSSHKey:
- r, err := deriveValues(root, v, SSHKeySchema, networks, vpnKeyGen)
+ r, err := deriveValues(root, v, SSHKeySchema, networks, clusters, vpnKeyGen)
if err != nil {
return nil, err
}
ret[k] = r
case KindStruct:
- r, err := deriveValues(root, v, def, networks, vpnKeyGen)
+ r, err := deriveValues(root, v, def, networks, clusters, vpnKeyGen)
if err != nil {
return nil, err
}
@@ -274,6 +295,16 @@
return nil, err
}
ret[k] = r
+ case KindCluster:
+ vm, ok := v.(map[string]any)
+ if !ok {
+ return nil, fmt.Errorf("expected map")
+ }
+ name, ok := vm["name"]
+ if !ok {
+ return nil, fmt.Errorf("expected cluster name")
+ }
+ ret[k] = name
default:
return nil, fmt.Errorf("Should not reach!")
}
@@ -289,3 +320,15 @@
}
return Network{}, fmt.Errorf("Network not found: %s", name)
}
+
+func findCluster(clusters []Cluster, name string) (*Cluster, error) {
+ if name == defaultClusterName {
+ return nil, nil
+ }
+ for _, c := range clusters {
+ if c.Name == name {
+ return &c, nil
+ }
+ }
+ return nil, fmt.Errorf("Cluster not found: %s", name)
+}