Skip to content

Commit

Permalink
Merge pull request #57 from kubero-dev/feature/56-improve-app-status-…
Browse files Browse the repository at this point in the history
…visibility

Feature/56 improve app status visibility
  • Loading branch information
mms-gianni authored Dec 22, 2022
2 parents 5ee7853 + d5d258c commit 343ded5
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 7 deletions.
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@ Kubero is Kubernetes native and runs on any Kubernetes instance.
- Install and manage your operators
- Give access to your container CLI

## Supported GIT repositories
<img src="docs/screenshots/gitrepositories.png">

## Which languages are supported
## Supported GIT repositories (hosted and self-hosted)
- Gitea
- Forgejo (WIP)
- Gogs
- Github
- Gitlab
- Bitbucket

## Tested languages/frameworks
Basicly *everything* that can be shipped in a single container. Kubero uses official images to build and run the apps. But they can be replaced or extended to fit your needs.

So far tested languages/frameworks:
- GoLang (including Hugo, gin-gonic)
- Python (including Flask)
- JavaScript/NodeJS
Expand All @@ -46,13 +50,12 @@ So far tested languages/frameworks:
- Rust (including Rocket)
- ...

## Preconfigured Buildpacks
You find the preconfigured buildpacks and examples here:
https://github.com/kubero-dev/buildpacks

## Quickstart
1) Download and unpack the <a href="https://github.com/kubero-dev/kubero-cli/releases/latest">Kubero CLI</a><p>
2) Run `kubero install` to install all components on your cluster
2) Run `kubero install` to install all components on an new or your existing cluster

## How it works
<a href="https://github.com/kubero-dev/kubero/tree/main/docs/screenshots">Screenshots</a><p>
Expand Down
2 changes: 2 additions & 0 deletions alternativeto.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Entry: https://alternativeto.net/software/kubero/
Maintainer: https://alternativeto.net/user/shoks_uno/
63 changes: 63 additions & 0 deletions client/src/components/pipelines/appcard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,29 @@

</v-card-text>

<v-divider></v-divider>
<v-card-text v-if="metricsDisplay == 'bars'">
<v-row>
<v-col cols="6" class="pb-0 text-left">CPU</v-col>
<v-col cols="6" class="pb-0 text-right">Memory</v-col>
</v-row>
<v-row v-for="metric in metrics" :key="metric.name" style="height:20px">
<v-col cols="6" class="text-left"><v-progress-linear :value="metric.cpu.percentage" color="#8560A9" class="mr-6 float-left"></v-progress-linear></v-col>
<v-col cols="6" class="text-right"><v-progress-linear :value="metric.memory.percentage" color="#8560A9" class="float-left" ></v-progress-linear></v-col>
</v-row>
</v-card-text>
<v-card-text v-if="metricsDisplay == 'table'">
<v-row>
<v-col cols="8" class="pb-0 text-left">Pod</v-col>
<v-col cols="2" class="pb-0 text-left">CPU</v-col>
<v-col cols="2" class="pb-0 text-right">Memory</v-col>
</v-row>
<v-row v-for="metric in metrics" :key="metric.name" id="metrics">
<v-col cols="8" class="py-0 text-left">{{metric.name}}</v-col>
<v-col cols="2" class="py-0 text-left">{{metric.cpu.usage}}{{metric.cpu.unit}}</v-col>
<v-col cols="2" class="py-0 text-right">{{metric.memory.usage}}{{metric.memory.unit}}</v-col>
</v-row>
</v-card-text>
<v-divider></v-divider>

<v-card-actions class="ml-2">
Expand Down Expand Up @@ -128,7 +151,13 @@ export default {
},
data: () => ({
loadingState: false,
metrics: [],
metricsDisplay: "dots",
}),
mounted() {
this.loadMetrics();
setInterval(this.loadMetrics, 10000);
},
methods: {
restartApp() {
axios.get(`/api/pipelines/${this.pipeline}/${this.phase}/${this.app.name}/restart`)
Expand All @@ -139,6 +168,26 @@ export default {
.catch(error => {
console.log(error);
});
},
loadMetrics() {
axios.get(`/api/metrics/${this.pipeline}/${this.phase}/${this.app.name}`)
.then(response => {
for (var i = 0; i < response.data.length; i++) {
if (response.data[i].cpu.percentage != null && response.data[i].memory.percentage != null) {
this.metricsDisplay = "bars";
}
if (
(response.data[i].cpu.percentage == null && response.data[i].memory.percentage == null) &&
(response.data[i].cpu.usage != null && response.data[i].memory.usage != null)
){
this.metricsDisplay = "table";
}
}
this.metrics = response.data;
})
.catch(error => {
console.log(error);
});
}
}
}
Expand All @@ -161,4 +210,18 @@ export default {
.v-application .v-card__title {
font-size: 1.1rem;
}
#metrics:nth-child(even) {
background-color: rgba(133, 96, 169, .1);
}
#metrics:nth-child(odd) {
background-color: rgba(133, 96, 169, .2);
}
.theme--light#metrics:nth-child(odd) {
background-color: rgba(133, 96, 169, .2);
}
.theme--dark#metrics:nth-child(odd) {
background-color: rgba(133, 96, 169, .2);
}
</style>
9 changes: 9 additions & 0 deletions src/kubero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -678,4 +678,13 @@ export class Kubero {
public getEvents(namespace: string) {
return this.kubectl.getEvents(namespace);
}

public getPodMetrics(pipelineName: string, phaseName: string, appName: string) {
const namespace = pipelineName+'-'+phaseName;
return this.kubectl.getPodMetrics(namespace);
}

public getNodeMetrics() {
return this.kubectl.getNodeMetrics();
}
}
99 changes: 99 additions & 0 deletions src/modules/kubectl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import {
CoreV1EventList,
V1ConfigMap,
V1Namespace,
Metrics,
PodMetric,
PodMetricsList,
NodeMetric,
} from '@kubernetes/client-node'
import { IPipeline, IKubectlPipeline, IKubectlPipelineList, IKubectlAppList, IKuberoConfig} from '../types';
import { App, KubectlApp } from './application';
Expand All @@ -29,6 +33,7 @@ export class Kubectl {
private versionApi: VersionApi;
private coreV1Api: CoreV1Api;
private appsV1Api: AppsV1Api;
private metricsApi: Metrics;
private customObjectsApi: CustomObjectsApi;
private kubeVersion: VersionInfo | void;
private patchUtils: PatchUtils;
Expand Down Expand Up @@ -61,6 +66,7 @@ export class Kubectl {
this.versionApi = this.kc.makeApiClient(VersionApi);
this.coreV1Api = this.kc.makeApiClient(CoreV1Api);
this.appsV1Api = this.kc.makeApiClient(AppsV1Api);
this.metricsApi = new Metrics(this.kc);
this.patchUtils = new PatchUtils();
this.customObjectsApi = this.kc.makeApiClient(CustomObjectsApi);

Expand Down Expand Up @@ -345,4 +351,97 @@ export class Kubectl {
let events = await this.coreV1Api.listNamespacedEvent(namespace);
return events.body.items;
}

public async getPodMetrics(namespace: string): Promise<any> { //TODO make this a real type
const ret = [];

try {
const metrics = await this.metricsApi.getPodMetrics(namespace);

for (let i = 0; i < metrics.items.length; i++) {
const metric = metrics.items[i];
const pod = await this.coreV1Api.readNamespacedPod(metric.metadata.name, namespace);
const requestCPU = this.normalizeCPU(pod.body.spec?.containers[0].resources?.requests?.cpu || '0');
const requestMemory = this.normalizeMemory(pod.body.spec?.containers[0].resources?.requests?.memory || '0');
const limitsCPU = this.normalizeCPU(pod.body.spec?.containers[0].resources?.limits?.cpu || '0');
const limitsMemory = this.normalizeMemory(pod.body.spec?.containers[0].resources?.limits?.memory || '0');
const usageCPU = this.normalizeCPU(metric.containers[0].usage.cpu);
const usageMemory = this.normalizeMemory(metric.containers[0].usage.memory);
const percentageCPU = Math.round(usageCPU / limitsCPU * 100);
const percentageMemory = Math.round(usageMemory / limitsMemory * 100);

/* debug caclulation *//*
console.log("resource CPU : " + requestCPU)
console.log("limits CPU : " + limitsCPU)
console.log("usage CPU : " + usageCPU)
console.log("percent CPU : " + percentageCPU)
console.log("resource Memory : " + requestMemory)
console.log("limits Memory : " + limitsMemory)
console.log("usage Memory : " + usageMemory)
console.log("percent Memory : " + percentageMemory)
console.log("------------------------------------")
/* end debug calculations*/

const m = {
name: metric.metadata.name,
namespace: metric.metadata.namespace,
memory : {
unit: 'Mi',
request: requestMemory,
limit: limitsMemory,
usage: usageMemory,
percentage: percentageMemory
},
cpu : {
unit: 'm',
request: requestCPU,
limit: limitsCPU,
usage: usageCPU,
percentage: percentageCPU
}
}
ret.push(m);
}
} catch (error: any) {
debug.log('ERROR fetching metrics: '+ error);
}

return ret;
}

private normalizeCPU(resource: string): number {
const unit = resource.slice(-1);
const value = parseInt(resource.slice(0, -1));
switch (unit) {
case 'm':
return value / 1;
case 'n':
return Math.round(value / 1000);
default:
return value;
}
return 0;
}


private normalizeMemory(resource: string): number {
const unit = resource.slice(-2);
const value = parseInt(resource.slice(0, -2));
switch (unit) {
case 'Gi':
return value * 1000;
case 'Mi':
return value / 1;
case 'Ki':
return Math.round(value / 1000);
default:
return value;
}
return 0;
}

public async getNodeMetrics(): Promise<NodeMetric[]> {
const metrics = await this.metricsApi.getNodeMetrics();
return metrics.items;
}
}
16 changes: 16 additions & 0 deletions src/routes/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,20 @@ Router.get('/events', authMiddleware, async function (req: Request, res: Respons
console.log('namespace', namespace);
const events = await req.app.locals.kubero.getEvents(namespace);
res.send(events);
});

Router.get('/metrics/:pipeline/:phase/:app', authMiddleware, async function (req: Request, res: Response) {

const metrics = await req.app.locals.kubero.getPodMetrics(
req.params.pipeline,
req.params.phase,
req.params.app
);
res.send(metrics);
});

Router.get('/metrics', authMiddleware, async function (req: Request, res: Response) {

const metrics = await req.app.locals.kubero.getNodeMetrics();
res.send(metrics);
});

0 comments on commit 343ded5

Please sign in to comment.