mirror of https://github.com/portainer/portainer
feat(kubernetes): cluster setup reasonable defaults EE-4518 (#8082)
parent
0436be7bc4
commit
046738c967
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/portainer/portainer/api/crypto"
|
"github.com/portainer/portainer/api/crypto"
|
||||||
"github.com/portainer/portainer/api/http/client"
|
"github.com/portainer/portainer/api/http/client"
|
||||||
"github.com/portainer/portainer/api/internal/edge"
|
"github.com/portainer/portainer/api/internal/edge"
|
||||||
|
"github.com/portainer/portainer/api/internal/endpointutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type endpointCreatePayload struct {
|
type endpointCreatePayload struct {
|
||||||
|
@ -244,6 +245,22 @@ func (handler *Handler) endpointCreate(w http.ResponseWriter, r *http.Request) *
|
||||||
for _, stackID := range relatedEdgeStacks {
|
for _, stackID := range relatedEdgeStacks {
|
||||||
relationObject.EdgeStacks[stackID] = true
|
relationObject.EdgeStacks[stackID] = true
|
||||||
}
|
}
|
||||||
|
} else if endpointutils.IsKubernetesEndpoint(endpoint) {
|
||||||
|
endpointutils.InitialIngressClassDetection(
|
||||||
|
endpoint,
|
||||||
|
handler.DataStore.Endpoint(),
|
||||||
|
handler.K8sClientFactory,
|
||||||
|
)
|
||||||
|
endpointutils.InitialMetricsDetection(
|
||||||
|
endpoint,
|
||||||
|
handler.DataStore.Endpoint(),
|
||||||
|
handler.K8sClientFactory,
|
||||||
|
)
|
||||||
|
endpointutils.InitialStorageDetection(
|
||||||
|
endpoint,
|
||||||
|
handler.DataStore.Endpoint(),
|
||||||
|
handler.K8sClientFactory,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.DataStore.EndpointRelation().Create(relationObject)
|
err = handler.DataStore.EndpointRelation().Create(relationObject)
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package kubernetes
|
||||||
|
|
||||||
|
type K8sMetrics struct {
|
||||||
|
Resources []K8sMetricsResources `json:"resources"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type K8sMetricsResources struct {
|
||||||
|
Kind string `json:"Kind,omitempty"`
|
||||||
|
Name string `json:"Name,omitempty"`
|
||||||
|
Namespaced bool `json:"Namespaced,omitempty"`
|
||||||
|
SingularName string `json:"SingularName,omitempty"`
|
||||||
|
Verbs []string `json:"Verbs,omitempty"`
|
||||||
|
}
|
|
@ -4,6 +4,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
portainer "github.com/portainer/portainer/api"
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
"github.com/portainer/portainer/api/dataservices"
|
||||||
|
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||||
|
log "github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IsLocalEndpoint returns true if this is a local environment(endpoint)
|
// IsLocalEndpoint returns true if this is a local environment(endpoint)
|
||||||
|
@ -69,3 +72,79 @@ func EndpointSet(endpointIDs []portainer.EndpointID) map[portainer.EndpointID]bo
|
||||||
|
|
||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InitialIngressClassDetection(endpoint *portainer.Endpoint, endpointService dataservices.EndpointService, factory *cli.ClientFactory) {
|
||||||
|
cli, err := factory.GetKubeClient(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug().Err(err).Msg("unable to create kubernetes client for ingress class detection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controllers, err := cli.GetIngressControllers()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug().Err(err).Msg("failed to fetch ingressclasses")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedClasses []portainer.KubernetesIngressClassConfig
|
||||||
|
for i := range controllers {
|
||||||
|
var updatedClass portainer.KubernetesIngressClassConfig
|
||||||
|
updatedClass.Name = controllers[i].ClassName
|
||||||
|
updatedClass.Type = controllers[i].Type
|
||||||
|
updatedClasses = append(updatedClasses, updatedClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint.Kubernetes.Configuration.IngressClasses = updatedClasses
|
||||||
|
err = endpointService.UpdateEndpoint(
|
||||||
|
portainer.EndpointID(endpoint.ID),
|
||||||
|
endpoint,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug().Err(err).Msg("unable to store found IngressClasses inside the database")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitialMetricsDetection(endpoint *portainer.Endpoint, endpointService dataservices.EndpointService, factory *cli.ClientFactory) {
|
||||||
|
cli, err := factory.GetKubeClient(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug().Err(err).Msg("unable to create kubernetes client for initial metrics detection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = cli.GetMetrics()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug().Err(err).Msg("unable to fetch metrics: leaving metrics collection disabled.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endpoint.Kubernetes.Configuration.UseServerMetrics = true
|
||||||
|
err = endpointService.UpdateEndpoint(
|
||||||
|
portainer.EndpointID(endpoint.ID),
|
||||||
|
endpoint,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug().Err(err).Msg("unable to enable UseServerMetrics inside the database")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitialStorageDetection(endpoint *portainer.Endpoint, endpointService dataservices.EndpointService, factory *cli.ClientFactory) {
|
||||||
|
cli, err := factory.GetKubeClient(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug().Err(err).Msg("unable to create Kubernetes client for initial storage detection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
storage, err := cli.GetStorage()
|
||||||
|
if err != nil {
|
||||||
|
log.Debug().Err(err).Msg("unable to fetch storage classes: leaving storage classes disabled")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
endpoint.Kubernetes.Configuration.StorageClasses = storage
|
||||||
|
err = endpointService.UpdateEndpoint(
|
||||||
|
portainer.EndpointID(endpoint.ID),
|
||||||
|
endpoint,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug().Err(err).Msg("unable to enable storage class inside the database")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
models "github.com/portainer/portainer/api/http/models/kubernetes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (kcl *KubeClient) GetMetrics() (models.K8sMetrics, error) {
|
||||||
|
var metrics models.K8sMetrics
|
||||||
|
resp, err := kcl.cli.CoreV1().RESTClient().Get().AbsPath("apis/metrics.k8s.io/v1beta1/nodes").DoRaw(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return metrics, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(resp, &metrics)
|
||||||
|
return metrics, err
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
portainer "github.com/portainer/portainer/api"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (kcl *KubeClient) GetStorage() ([]portainer.KubernetesStorageClassConfig, error) {
|
||||||
|
var storages []portainer.KubernetesStorageClassConfig
|
||||||
|
|
||||||
|
storageClient := kcl.cli.StorageV1().StorageClasses()
|
||||||
|
storageList, err := storageClient.List(context.Background(), metav1.ListOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return storages, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range storageList.Items {
|
||||||
|
var storage portainer.KubernetesStorageClassConfig
|
||||||
|
|
||||||
|
v, ok := s.Annotations["storageclass.kubernetes.io/is-default-class"]
|
||||||
|
if !ok || v != "true" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
storage.Name = s.Name
|
||||||
|
storage.Provisioner = s.Provisioner
|
||||||
|
storage.AccessModes = []string{"RWO"}
|
||||||
|
if s.AllowVolumeExpansion != nil {
|
||||||
|
storage.AllowVolumeExpansion = *s.AllowVolumeExpansion
|
||||||
|
}
|
||||||
|
|
||||||
|
storages = append(storages, storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return storages, nil
|
||||||
|
}
|
|
@ -1375,6 +1375,8 @@ type (
|
||||||
DeleteNamespace(namespace string) error
|
DeleteNamespace(namespace string) error
|
||||||
GetConfigMapsAndSecrets(namespace string) ([]models.K8sConfigMapOrSecret, error)
|
GetConfigMapsAndSecrets(namespace string) ([]models.K8sConfigMapOrSecret, error)
|
||||||
GetIngressControllers() (models.K8sIngressControllers, error)
|
GetIngressControllers() (models.K8sIngressControllers, error)
|
||||||
|
GetMetrics() (models.K8sMetrics, error)
|
||||||
|
GetStorage() ([]KubernetesStorageClassConfig, error)
|
||||||
CreateIngress(namespace string, info models.K8sIngressInfo) error
|
CreateIngress(namespace string, info models.K8sIngressInfo) error
|
||||||
UpdateIngress(namespace string, info models.K8sIngressInfo) error
|
UpdateIngress(namespace string, info models.K8sIngressInfo) error
|
||||||
GetIngresses(namespace string) ([]models.K8sIngressInfo, error)
|
GetIngresses(namespace string) ([]models.K8sIngressInfo, error)
|
||||||
|
|
|
@ -225,10 +225,8 @@ class KubernetesConfigureController {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await Promise.all(storagePromises);
|
await Promise.all(storagePromises);
|
||||||
|
this.$state.reload();
|
||||||
this.Notifications.success('Success', 'Configuration successfully applied');
|
this.Notifications.success('Success', 'Configuration successfully applied');
|
||||||
|
|
||||||
this.$state.go('portainer.home');
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to apply configuration');
|
this.Notifications.error('Failure', err, 'Unable to apply configuration');
|
||||||
} finally {
|
} finally {
|
||||||
|
|
Loading…
Reference in New Issue