diff --git a/docs/configs/kubernetes.md b/docs/configs/kubernetes.md index 06685f3a..b0665e40 100644 --- a/docs/configs/kubernetes.md +++ b/docs/configs/kubernetes.md @@ -8,6 +8,7 @@ The Kubernetes connectivity has the following requirements: - Kubernetes 1.19+ - Metrics Service - An Ingress controller + - Optionally: Gateway-API The Kubernetes connection is configured in the `kubernetes.yaml` file. There are 3 modes to choose from: @@ -19,6 +20,12 @@ The Kubernetes connection is configured in the `kubernetes.yaml` file. There are mode: default ``` +To enable Kubernetes gateway-api compatibility, add the following setting: + +```yaml +route: gateway +``` + ## Services Once the Kubernetes connection is configured, individual services can be configured to pull statistics. Only CPU and Memory are currently supported. @@ -140,6 +147,9 @@ spec: If the `href` attribute is not present, Homepage will ignore the specific IngressRoute. +### Gateway API HttpRoute support +Homepage also features automatic service discovery for gateway-api. Service definitions are read by annotating the HttpRoute custom resource definition and are indentical to the Ingress example as defined in [Automatic Service Discovery](#automatic-service-discovery). + ## Caveats Similarly to Docker service discovery, there currently is no rigid ordering to discovered services and discovered services will be displayed above those specified in the `services.yaml`. diff --git a/docs/installation/k8s.md b/docs/installation/k8s.md index 6805139b..1267af9e 100644 --- a/docs/installation/k8s.md +++ b/docs/installation/k8s.md @@ -215,6 +215,15 @@ rules: verbs: - get - list + # if using gateway api add the following: + # - apiGroups: + # - gateway.networking.k8s.io + # resources: + # - httproutes + # - gateways + # verbs: + # - get + # - list - apiGroups: - metrics.k8s.io resources: diff --git a/src/utils/config/kubernetes.js b/src/utils/config/kubernetes.js index 83efe3b0..8332a724 100644 --- a/src/utils/config/kubernetes.js +++ b/src/utils/config/kubernetes.js @@ -7,30 +7,27 @@ import { KubeConfig } from "@kubernetes/client-node"; import checkAndCopyConfig, { CONF_DIR, substituteEnvironmentVars } from "utils/config/config"; const extractKubeData = (config) => { - //kubeconfig + // kubeconfig const kc = new KubeConfig(); kc.loadFromCluster(); - //route + // route let route = "ingress"; - if (config?.route == "gateway") { + if (config?.route === "gateway") { route = "gateway"; } - //traefik + // traefik let traefik = true; - if (config?.traefik == "disable") { + if (config?.traefik === "disable") { traefik = false; } - //traefik - let metrics = true; - if (config?.metrics == "disable") { - metrics = false; - } - - //return - return { config: kc, route: route, traefik: traefik, metrics: metrics }; + return { + "config": kc, + "route": route, + "traefik": traefik + }; }; export default function getKubeArguments() { diff --git a/src/utils/config/service-helpers.js b/src/utils/config/service-helpers.js index f327b318..6ef7f87e 100644 --- a/src/utils/config/service-helpers.js +++ b/src/utils/config/service-helpers.js @@ -3,7 +3,6 @@ import path from "path"; import yaml from "js-yaml"; import Docker from "dockerode"; -import { ApiextensionsV1Api } from "@kubernetes/client-node"; import createLogger from "utils/logger"; import checkAndCopyConfig, { CONF_DIR, getSettings, substituteEnvironmentVars } from "utils/config/config"; @@ -159,7 +158,7 @@ export async function servicesFromKubernetes() { checkAndCopyConfig("kubernetes.yaml"); try { - const routeList = await getRouteList(); + const routeList = await getRouteList(ANNOTATION_BASE); if (!routeList) { return []; diff --git a/src/utils/kubernetes/kubernetes-routes.js b/src/utils/kubernetes/kubernetes-routes.js index aa31b910..337534c7 100644 --- a/src/utils/kubernetes/kubernetes-routes.js +++ b/src/utils/kubernetes/kubernetes-routes.js @@ -1,4 +1,5 @@ import { CustomObjectsApi, NetworkingV1Api, CoreV1Api, ApiextensionsV1Api } from "@kubernetes/client-node"; + import getKubeArguments from "utils/config/kubernetes"; import createLogger from "utils/logger"; @@ -16,7 +17,7 @@ let networking; let routingType; let traefik; -export async function checkCRD(kc, name) { +export async function checkCRD(name) { const apiExtensions = kc.makeApiClient(ApiextensionsV1Api); const exist = await apiExtensions .readCustomResourceDefinitionStatus(name) @@ -37,19 +38,24 @@ export async function checkCRD(kc, name) { } const getSchemaFromGateway = async (gatewayRef) => { - try { - const gateway = await crd.getNamespacedCustomObject( - apiGroup, - version, - gatewayRef.namespace, - "gateways", - gatewayRef.name, - ); - const listener = gateway.body.spec.listeners.filter((listener) => listener.name == gatewayRef.sectionName)[0]; - return listener.protocol.toLowerCase(); - } catch (err) { - console.error(err); - } + + const schema = await crd.getNamespacedCustomObject( + apiGroup, + version, + gatewayRef.namespace, + "gateways", + gatewayRef.name, + ) + .then((response) => { + const listner = response.body.spec.listeners.filter((listener) => listener.name === gatewayRef.sectionName)[0] + return listner.protocol.toLowerCase(); + }) + .catch((error) => { + logger.error("Error getting gateways: %d %s %s", error.statusCode, error.body, error.response); + logger.debug(error); + return ""; + }); + return schema; }; async function getUrlFromHttpRoute(ingress) { @@ -67,7 +73,7 @@ function getUrlFromIngress(ingress) { } async function getHttpRouteList() { - const httpRouteList = new Array(); + const httpRouteList = []; const namespaces = await core .listNamespace() @@ -95,7 +101,7 @@ async function getHttpRouteList() { return httpRouteList; } -async function getIngressList() { +async function getIngressList(ANNOTATION_BASE) { const ingressList = await networking .listIngressForAllNamespaces(null, null, null, null) .then((response) => response.body) @@ -106,8 +112,8 @@ async function getIngressList() { }); if (traefik) { - const traefikContainoExists = await checkCRD(kc, "ingressroutes.traefik.containo.us"); - const traefikExists = await checkCRD(kc, "ingressroutes.traefik.io"); + const traefikContainoExists = await checkCRD("ingressroutes.traefik.containo.us"); + const traefikExists = await checkCRD("ingressroutes.traefik.io"); const traefikIngressListContaino = await crd .listClusterCustomObject("traefik.containo.us", "v1alpha1", "ingressroutes") @@ -156,8 +162,8 @@ async function getIngressList() { return ingressList.items; } -export async function getRouteList() { - let routeList = new Array(); +export async function getRouteList(ANNOTATION_BASE) { + let routeList = []; if (!kc) { return []; @@ -172,13 +178,13 @@ export async function getRouteList() { switch (routingType) { case "ingress": - routeList = await getIngressList(); + routeList = await getIngressList(ANNOTATION_BASE); break; case "gateway": routeList = await getHttpRouteList(); break; default: - routeList = await getIngressList(); + routeList = await getIngressList(ANNOTATION_BASE); } return routeList;