feat(node): Show which IP address / port the cluster API is listening on (#4134)
* feat(cluster): add kubernetes endpoint resource * feat(cluster): add kubernetes endpoint service * feat(node): Show which IP address / port the cluster API is listening on * fix(cluster): support multi-master clusters * fix(cluster): support multi-master clusters * feat(k8s/cluster): minor UI update * refactor(k8s/cluster): rename variable * refactor(k8s/endpoints): refactor KubernetesEndpointsFactory Co-authored-by: Anthony Lapenna <lapenna.anthony@gmail.com>pull/4156/head
parent
6756b04b67
commit
148ccd1bc4
|
@ -114,7 +114,10 @@
|
||||||
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
dir-paginate="item in ($ctrl.state.filteredDataSet = ($ctrl.dataset | filter:$ctrl.state.textFilter | orderBy:$ctrl.state.orderBy:$ctrl.state.reverseOrder | itemsPerPage: $ctrl.state.paginatedItemLimit))"
|
||||||
>
|
>
|
||||||
<td ng-if="$ctrl.isAdmin">
|
<td ng-if="$ctrl.isAdmin">
|
||||||
<a ui-sref="kubernetes.cluster.node({ name: item.Name })">{{ item.Name }}</a>
|
<a ui-sref="kubernetes.cluster.node({ name: item.Name })">
|
||||||
|
{{ item.Name }}
|
||||||
|
</a>
|
||||||
|
<span class="label label-primary image-tag" style="margin-left: 5px;" ng-if="item.Api">api</span>
|
||||||
</td>
|
</td>
|
||||||
<td ng-if="!$ctrl.isAdmin"> {{ item.Name }}</td>
|
<td ng-if="!$ctrl.isAdmin"> {{ item.Name }}</td>
|
||||||
<td>{{ item.Role }}</td>
|
<td>{{ item.Role }}</td>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { KubernetesEndpoint, KubernetesEndpointAnnotationLeader } from 'Kubernetes/endpoint/models';
|
import { KubernetesEndpoint, KubernetesEndpointAnnotationLeader, KubernetesEndpointSubset } from 'Kubernetes/endpoint/models';
|
||||||
import _ from 'lodash-es';
|
import _ from 'lodash-es';
|
||||||
|
|
||||||
class KubernetesEndpointConverter {
|
class KubernetesEndpointConverter {
|
||||||
|
@ -13,6 +13,16 @@ class KubernetesEndpointConverter {
|
||||||
const split = _.split(parsedJson.holderIdentity, '_');
|
const split = _.split(parsedJson.holderIdentity, '_');
|
||||||
res.HolderIdentity = split[0];
|
res.HolderIdentity = split[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.subsets) {
|
||||||
|
res.Subsets = _.map(data.subsets, (item) => {
|
||||||
|
const subset = new KubernetesEndpointSubset();
|
||||||
|
subset.Ips = _.map(item.addresses, 'ip');
|
||||||
|
const port = _.find(item.ports, { name: 'https' });
|
||||||
|
subset.Port = port ? port.port : undefined;
|
||||||
|
return subset;
|
||||||
|
});
|
||||||
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ const _KubernetesEndpoint = Object.freeze({
|
||||||
Name: '',
|
Name: '',
|
||||||
Namespace: '',
|
Namespace: '',
|
||||||
HolderIdentity: '',
|
HolderIdentity: '',
|
||||||
|
Subsets: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
export class KubernetesEndpoint {
|
export class KubernetesEndpoint {
|
||||||
|
@ -15,3 +16,14 @@ export class KubernetesEndpoint {
|
||||||
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesEndpoint)));
|
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesEndpoint)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _KubernetesEndpointSubset = Object.freeze({
|
||||||
|
Ips: [],
|
||||||
|
Port: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export class KubernetesEndpointSubset {
|
||||||
|
constructor() {
|
||||||
|
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesEndpointSubset)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,9 @@ const _KubernetesNode = Object.freeze({
|
||||||
Memory: '',
|
Memory: '',
|
||||||
Version: '',
|
Version: '',
|
||||||
IPAddress: '',
|
IPAddress: '',
|
||||||
|
Api: false,
|
||||||
Taints: [],
|
Taints: [],
|
||||||
|
Port: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
export class KubernetesNode {
|
export class KubernetesNode {
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
angular.module('portainer.kubernetes').factory('KubernetesEndpoints', function KubernetesEndpointsFactory($resource, API_ENDPOINT_ENDPOINTS, EndpointProvider) {
|
||||||
|
'use strict';
|
||||||
|
return function (namespace) {
|
||||||
|
const url = API_ENDPOINT_ENDPOINTS + '/:endpointId/kubernetes/api/v1' + (namespace ? '/namespaces/:namespace' : '') + '/endpoints/:id';
|
||||||
|
return $resource(
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
endpointId: EndpointProvider.endpointID,
|
||||||
|
namespace: namespace,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
get: {
|
||||||
|
method: 'GET',
|
||||||
|
timeout: 15000,
|
||||||
|
ignoreLoadingBar: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
});
|
|
@ -51,7 +51,7 @@
|
||||||
<!-- !cluster-status -->
|
<!-- !cluster-status -->
|
||||||
|
|
||||||
<!-- leader-status -->
|
<!-- leader-status -->
|
||||||
<div ng-if="ctrl.endpoints.length > 0">
|
<div ng-if="ctrl.systemEndpoints.length > 0">
|
||||||
<div class="col-sm-12 form-section-title">
|
<div class="col-sm-12 form-section-title">
|
||||||
Leader status
|
Leader status
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
<td style="border-top: none; width: 25%;">Component</td>
|
<td style="border-top: none; width: 25%;">Component</td>
|
||||||
<td style="border-top: none; width: 25%;">Leader node</td>
|
<td style="border-top: none; width: 25%;">Leader node</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr ng-repeat="ep in ctrl.endpoints">
|
<tr ng-repeat="ep in ctrl.systemEndpoints">
|
||||||
<td style="width: 25%;">
|
<td style="width: 25%;">
|
||||||
{{ ep.Name }}
|
{{ ep.Name }}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -51,8 +51,17 @@ class KubernetesClusterController {
|
||||||
|
|
||||||
async getEndpointsAsync() {
|
async getEndpointsAsync() {
|
||||||
try {
|
try {
|
||||||
const endpoints = await this.KubernetesEndpointService.get('kube-system');
|
const endpoints = await this.KubernetesEndpointService.get();
|
||||||
this.endpoints = _.filter(endpoints, (ep) => ep.HolderIdentity);
|
const systemEndpoints = _.filter(endpoints, { Namespace: 'kube-system' });
|
||||||
|
this.systemEndpoints = _.filter(systemEndpoints, (ep) => ep.HolderIdentity);
|
||||||
|
|
||||||
|
const kubernetesEndpoint = _.find(endpoints, { Name: 'kubernetes' });
|
||||||
|
if (kubernetesEndpoint && kubernetesEndpoint.Subsets) {
|
||||||
|
const ips = _.flatten(_.map(kubernetesEndpoint.Subsets, 'Ips'));
|
||||||
|
_.forEach(this.nodes, (node) => {
|
||||||
|
node.Api = _.includes(ips, node.IPAddress);
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.Notifications.error('Failure', err, 'Unable to retrieve endpoints');
|
this.Notifications.error('Failure', err, 'Unable to retrieve endpoints');
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,16 @@
|
||||||
<tbody ng-if="ctrl.node">
|
<tbody ng-if="ctrl.node">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Hostname</td>
|
<td>Hostname</td>
|
||||||
<td>{{ ctrl.node.Name }}</td>
|
<td>
|
||||||
|
{{ ctrl.node.Name }}
|
||||||
|
<span class="label label-primary image-tag" style="margin-left: 5px;" ng-if="ctrl.node.Api">api</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr ng-if="ctrl.endpoint.Subsets">
|
||||||
|
<td>
|
||||||
|
Kubernetes API
|
||||||
|
</td>
|
||||||
|
<td>{{ ctrl.node.IPAddress }}:{{ ctrl.node.Port }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Role</td>
|
<td>Role</td>
|
||||||
|
|
|
@ -6,7 +6,17 @@ import KubernetesEventHelper from 'Kubernetes/helpers/eventHelper';
|
||||||
|
|
||||||
class KubernetesNodeController {
|
class KubernetesNodeController {
|
||||||
/* @ngInject */
|
/* @ngInject */
|
||||||
constructor($async, $state, Notifications, LocalStorage, KubernetesNodeService, KubernetesEventService, KubernetesPodService, KubernetesApplicationService) {
|
constructor(
|
||||||
|
$async,
|
||||||
|
$state,
|
||||||
|
Notifications,
|
||||||
|
LocalStorage,
|
||||||
|
KubernetesNodeService,
|
||||||
|
KubernetesEventService,
|
||||||
|
KubernetesPodService,
|
||||||
|
KubernetesApplicationService,
|
||||||
|
KubernetesEndpointService
|
||||||
|
) {
|
||||||
this.$async = $async;
|
this.$async = $async;
|
||||||
this.$state = $state;
|
this.$state = $state;
|
||||||
this.Notifications = Notifications;
|
this.Notifications = Notifications;
|
||||||
|
@ -15,18 +25,44 @@ class KubernetesNodeController {
|
||||||
this.KubernetesEventService = KubernetesEventService;
|
this.KubernetesEventService = KubernetesEventService;
|
||||||
this.KubernetesPodService = KubernetesPodService;
|
this.KubernetesPodService = KubernetesPodService;
|
||||||
this.KubernetesApplicationService = KubernetesApplicationService;
|
this.KubernetesApplicationService = KubernetesApplicationService;
|
||||||
|
this.KubernetesEndpointService = KubernetesEndpointService;
|
||||||
|
|
||||||
this.onInit = this.onInit.bind(this);
|
this.onInit = this.onInit.bind(this);
|
||||||
this.getNodeAsync = this.getNodeAsync.bind(this);
|
this.getNodeAsync = this.getNodeAsync.bind(this);
|
||||||
this.getEvents = this.getEvents.bind(this);
|
this.getEvents = this.getEvents.bind(this);
|
||||||
this.getEventsAsync = this.getEventsAsync.bind(this);
|
this.getEventsAsync = this.getEventsAsync.bind(this);
|
||||||
this.getApplicationsAsync = this.getApplicationsAsync.bind(this);
|
this.getApplicationsAsync = this.getApplicationsAsync.bind(this);
|
||||||
|
this.getEndpointsAsync = this.getEndpointsAsync.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
selectTab(index) {
|
selectTab(index) {
|
||||||
this.LocalStorage.storeActiveTab('node', index);
|
this.LocalStorage.storeActiveTab('node', index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getEndpointsAsync() {
|
||||||
|
try {
|
||||||
|
const endpoints = await this.KubernetesEndpointService.get();
|
||||||
|
this.endpoint = _.find(endpoints, { Name: 'kubernetes' });
|
||||||
|
if (this.endpoint && this.endpoint.Subsets) {
|
||||||
|
_.forEach(this.endpoint.Subsets, (subset) => {
|
||||||
|
return _.forEach(subset.Ips, (ip) => {
|
||||||
|
if (ip === this.node.IPAddress) {
|
||||||
|
this.node.Api = true;
|
||||||
|
this.node.Port = subset.Port;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.Notifications.error('Failure', err, 'Unable to retrieve endpoints');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getEndpoints() {
|
||||||
|
return this.$async(this.getEndpointsAsync);
|
||||||
|
}
|
||||||
|
|
||||||
async getNodeAsync() {
|
async getNodeAsync() {
|
||||||
try {
|
try {
|
||||||
this.state.dataLoading = true;
|
this.state.dataLoading = true;
|
||||||
|
@ -118,6 +154,7 @@ class KubernetesNodeController {
|
||||||
await this.getNode();
|
await this.getNode();
|
||||||
await this.getEvents();
|
await this.getEvents();
|
||||||
await this.getApplications();
|
await this.getApplications();
|
||||||
|
await this.getEndpoints();
|
||||||
|
|
||||||
this.state.viewReady = true;
|
this.state.viewReady = true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue