diff --git a/app/kubernetes/__module.js b/app/kubernetes/__module.js
index 6bc5c909a..0d47611b1 100644
--- a/app/kubernetes/__module.js
+++ b/app/kubernetes/__module.js
@@ -77,7 +77,7 @@ angular.module('portainer.kubernetes', ['portainer.app']).config([
const applicationConsole = {
name: 'kubernetes.applications.application.console',
- url: '/:pod/console',
+ url: '/:pod/:container/console',
views: {
'content@': {
component: 'kubernetesApplicationConsoleView',
@@ -87,7 +87,7 @@ angular.module('portainer.kubernetes', ['portainer.app']).config([
const applicationLogs = {
name: 'kubernetes.applications.application.logs',
- url: '/:pod/logs',
+ url: '/:pod/:container/logs',
views: {
'content@': {
component: 'kubernetesApplicationLogsView',
diff --git a/app/kubernetes/components/datatables/application/pods-datatable/podsDatatable.html b/app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.html
similarity index 88%
rename from app/kubernetes/components/datatables/application/pods-datatable/podsDatatable.html
rename to app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.html
index 0b5ba805a..b9a86574f 100644
--- a/app/kubernetes/components/datatables/application/pods-datatable/podsDatatable.html
+++ b/app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.html
@@ -65,10 +65,17 @@
-
+
+ Pod
+
+
+
+ |
+
+
Image
-
-
+
+
|
@@ -101,9 +108,8 @@
pagination-id="$ctrl.tableKey"
>
| {{ item.Name }} |
- {{ image }}
|
+ {{ item.PodName }} |
+ {{ item.Image }} |
{{ item.Status }} |
@@ -117,8 +123,8 @@
{{ item.CreationDate | getisodate }} |
- Logs
-
+ Logs
+
Console
|
diff --git a/app/kubernetes/components/datatables/application/pods-datatable/podsDatatable.js b/app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.js
similarity index 59%
rename from app/kubernetes/components/datatables/application/pods-datatable/podsDatatable.js
rename to app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.js
index faa7df25f..6d8b5ca99 100644
--- a/app/kubernetes/components/datatables/application/pods-datatable/podsDatatable.js
+++ b/app/kubernetes/components/datatables/application/containers-datatable/containersDatatable.js
@@ -1,5 +1,5 @@
-angular.module('portainer.kubernetes').component('kubernetesPodsDatatable', {
- templateUrl: './podsDatatable.html',
+angular.module('portainer.kubernetes').component('kubernetesContainersDatatable', {
+ templateUrl: './containersDatatable.html',
controller: 'GenericDatatableController',
bindings: {
titleText: '@',
diff --git a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html
index f1efdf899..5d0ece02c 100644
--- a/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html
+++ b/app/kubernetes/components/datatables/applications-datatable/applicationsDatatable.html
@@ -147,7 +147,9 @@
{{ item.ResourcePool }}
|
- {{ item.Image }} |
+ {{ item.Image }} + {{ item.Containers.length - 1 }} |
{{ item.ApplicationType | kubernetesApplicationTypeText }} |
Replicated
diff --git a/app/kubernetes/components/datatables/node-applications-datatable/nodeApplicationsDatatable.html b/app/kubernetes/components/datatables/node-applications-datatable/nodeApplicationsDatatable.html
index 4ab919bd1..f6f2e66ea 100644
--- a/app/kubernetes/components/datatables/node-applications-datatable/nodeApplicationsDatatable.html
+++ b/app/kubernetes/components/datatables/node-applications-datatable/nodeApplicationsDatatable.html
@@ -118,7 +118,9 @@
|
{{ item.ResourcePool }}
|
- {{ item.Image }} |
+ {{ item.Image }} + {{ item.Containers.length - 1 }} |
{{ item.CPU | kubernetesApplicationCPUValue }} |
{{ item.Memory | humansize }} |
diff --git a/app/kubernetes/components/datatables/resource-pool-applications-datatable/resourcePoolApplicationsDatatable.html b/app/kubernetes/components/datatables/resource-pool-applications-datatable/resourcePoolApplicationsDatatable.html
index c08cd865b..2ba9f703b 100644
--- a/app/kubernetes/components/datatables/resource-pool-applications-datatable/resourcePoolApplicationsDatatable.html
+++ b/app/kubernetes/components/datatables/resource-pool-applications-datatable/resourcePoolApplicationsDatatable.html
@@ -107,7 +107,9 @@
external
{{ item.StackName }} |
- {{ item.Image }} |
+ {{ item.Image }} + {{ item.Containers.length - 1 }} |
{{ item.CPU | kubernetesApplicationCPUValue }} |
{{ item.Memory | humansize }} |
diff --git a/app/kubernetes/converters/application.js b/app/kubernetes/converters/application.js
index 4fb313acc..ff2ef43b0 100644
--- a/app/kubernetes/converters/application.js
+++ b/app/kubernetes/converters/application.js
@@ -50,6 +50,7 @@ function _apiPortsToPublishedPorts(pList, pRefs) {
class KubernetesApplicationConverter {
static applicationCommon(res, data, service, ingresses) {
+ const containers = _.without(_.concat(data.spec.template.spec.containers, data.spec.template.spec.initContainers), undefined);
res.Id = data.metadata.uid;
res.Name = data.metadata.name;
res.StackName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationStackNameLabel] || '-' : '-';
@@ -57,16 +58,16 @@ class KubernetesApplicationConverter {
res.Note = data.metadata.annotations ? data.metadata.annotations[KubernetesPortainerApplicationNote] || '' : '';
res.ApplicationName = data.metadata.labels ? data.metadata.labels[KubernetesPortainerApplicationNameLabel] || res.Name : res.Name;
res.ResourcePool = data.metadata.namespace;
- res.Image = data.spec.template.spec.containers[0].image;
+ res.Image = containers[0].image;
res.CreationDate = data.metadata.creationTimestamp;
- res.Pods = data.Pods;
- res.Env = data.spec.template.spec.containers[0].env;
+ res.Env = _.without(_.flatMap(_.map(containers, 'env')), undefined);
+
const limits = {
Cpu: 0,
Memory: 0,
};
res.Limits = _.reduce(
- data.spec.template.spec.containers,
+ containers,
(acc, item) => {
if (item.resources.limits && item.resources.limits.cpu) {
acc.Cpu += KubernetesResourceReservationHelper.parseCPU(item.resources.limits.cpu);
@@ -84,7 +85,7 @@ class KubernetesApplicationConverter {
Memory: 0,
};
res.Requests = _.reduce(
- data.spec.template.spec.containers,
+ containers,
(acc, item) => {
if (item.resources.requests && item.resources.requests.cpu) {
acc.Cpu += KubernetesResourceReservationHelper.parseCPU(item.resources.requests.cpu);
@@ -109,7 +110,7 @@ class KubernetesApplicationConverter {
}
}
- const portsRefs = _.concat(..._.map(data.spec.template.spec.containers, (container) => container.ports));
+ const portsRefs = _.concat(..._.map(containers, (container) => container.ports));
const ports = _apiPortsToPublishedPorts(service.spec.ports, portsRefs);
const rules = KubernetesIngressHelper.findSBoundServiceIngressesRules(ingresses, service.metadata.name);
_.forEach(ports, (port) => (port.IngressRules = _.filter(rules, (rule) => rule.Port === port.Port)));
@@ -147,7 +148,8 @@ class KubernetesApplicationConverter {
const persistedFolders = _.filter(res.Volumes, (volume) => volume.persistentVolumeClaim || volume.hostPath);
res.PersistedFolders = _.map(persistedFolders, (volume) => {
- const matchingVolumeMount = _.find(data.spec.template.spec.containers[0].volumeMounts, { name: volume.name });
+ const volumeMounts = _.uniq(_.flatMap(_.map(containers, 'volumeMounts')), 'name');
+ const matchingVolumeMount = _.find(volumeMounts, { name: volume.name });
if (matchingVolumeMount) {
const persistedFolder = new KubernetesApplicationPersistedFolder();
@@ -169,7 +171,7 @@ class KubernetesApplicationConverter {
data.spec.template.spec.volumes,
(acc, volume) => {
if (volume.configMap || volume.secret) {
- const matchingVolumeMount = _.find(data.spec.template.spec.containers[0].volumeMounts, { name: volume.name });
+ const matchingVolumeMount = _.find(_.flatMap(_.map(containers, 'volumeMounts')), { name: volume.name });
if (matchingVolumeMount) {
let items = [];
@@ -262,6 +264,7 @@ class KubernetesApplicationConverter {
res.Configurations = KubernetesApplicationHelper.generateConfigurationFormValuesFromEnvAndVolumes(app.Env, app.ConfigurationVolumes, configurations);
res.AutoScaler = KubernetesApplicationHelper.generateAutoScalerFormValueFromHorizontalPodAutoScaler(app.AutoScaler, res.ReplicaCount);
res.PublishedPorts = KubernetesApplicationHelper.generatePublishedPortsFormValuesFromPublishedPorts(app.ServiceType, app.PublishedPorts);
+ res.Containers = app.Containers;
const isIngress = _.filter(res.PublishedPorts, (p) => p.IngressName).length;
if (app.ServiceType === KubernetesServiceTypes.LOAD_BALANCER) {
diff --git a/app/kubernetes/converters/deployment.js b/app/kubernetes/converters/deployment.js
index a38126d5e..dad947923 100644
--- a/app/kubernetes/converters/deployment.js
+++ b/app/kubernetes/converters/deployment.js
@@ -29,6 +29,7 @@ class KubernetesDeploymentConverter {
res.CpuLimit = formValues.CpuLimit;
res.MemoryLimit = KubernetesResourceReservationHelper.bytesValue(formValues.MemoryLimit);
res.Env = KubernetesApplicationHelper.generateEnvFromEnvVariables(formValues.EnvironmentVariables);
+ res.Containers = formValues.Containers;
KubernetesApplicationHelper.generateVolumesFromPersistentVolumClaims(res, volumeClaims);
KubernetesApplicationHelper.generateEnvOrVolumesFromConfigurations(res, formValues.Configurations);
return res;
diff --git a/app/kubernetes/helpers/application/index.js b/app/kubernetes/helpers/application/index.js
index f14d493db..7a39186ca 100644
--- a/app/kubernetes/helpers/application/index.js
+++ b/app/kubernetes/helpers/application/index.js
@@ -28,6 +28,43 @@ class KubernetesApplicationHelper {
return _.filter(pods, { Labels: app.spec.selector.matchLabels });
}
+ static associateContainerPersistedFoldersAndConfigurations(app, containers) {
+ _.forEach(containers, (container) => {
+ container.PersistedFolders = _.without(
+ _.map(app.PersistedFolders, (pf) => {
+ if (pf.MountPath && _.includes(_.map(container.VolumeMounts, 'mountPath'), pf.MountPath)) {
+ return pf;
+ }
+ }),
+ undefined
+ );
+
+ container.ConfigurationVolumes = _.without(
+ _.map(app.ConfigurationVolumes, (cv) => {
+ if (cv.rootMountPath && _.includes(_.map(container.VolumeMounts, 'mountPath'), cv.rootMountPath)) {
+ return cv;
+ }
+ }),
+ undefined
+ );
+ });
+ }
+
+ static associateContainersAndApplication(app) {
+ if (!app.Pods) {
+ return [];
+ }
+ const containers = app.Pods[0].Containers;
+ KubernetesApplicationHelper.associateContainerPersistedFoldersAndConfigurations(app, containers);
+ return containers;
+ }
+
+ static associateAllContainersAndApplication(app) {
+ const containers = _.flatMap(_.map(app.Pods, 'Containers'));
+ KubernetesApplicationHelper.associateContainerPersistedFoldersAndConfigurations(app, containers);
+ return containers;
+ }
+
static portMappingsFromApplications(applications) {
const res = _.reduce(
applications,
diff --git a/app/kubernetes/helpers/resourceReservationHelper.js b/app/kubernetes/helpers/resourceReservationHelper.js
index df24ee494..5df674fc5 100644
--- a/app/kubernetes/helpers/resourceReservationHelper.js
+++ b/app/kubernetes/helpers/resourceReservationHelper.js
@@ -9,13 +9,13 @@ class KubernetesResourceReservationHelper {
return _.reduce(
containers,
(acc, container) => {
- if (container.resources && container.resources.requests) {
- if (container.resources.requests.memory) {
- acc.Memory += filesizeParser(container.resources.requests.memory, { base: 10 });
+ if (container.Requests) {
+ if (container.Requests.memory) {
+ acc.Memory += filesizeParser(container.Requests.memory, { base: 10 });
}
- if (container.resources.requests.cpu) {
- acc.CPU += KubernetesResourceReservationHelper.parseCPU(container.resources.requests.cpu);
+ if (container.Requests.cpu) {
+ acc.CPU += KubernetesResourceReservationHelper.parseCPU(container.Requests.cpu);
}
}
diff --git a/app/kubernetes/models/application/formValues.js b/app/kubernetes/models/application/formValues.js
index 8b658141a..6d789c10b 100644
--- a/app/kubernetes/models/application/formValues.js
+++ b/app/kubernetes/models/application/formValues.js
@@ -21,6 +21,7 @@ const _KubernetesApplicationFormValues = Object.freeze({
PublishingType: KubernetesApplicationPublishingTypes.INTERNAL,
DataAccessPolicy: KubernetesApplicationDataAccessPolicies.SHARED,
Configurations: [], // KubernetesApplicationConfigurationFormValue list
+ Containers: [],
AutoScaler: {},
OriginalIngresses: undefined,
});
diff --git a/app/kubernetes/models/application/models/index.js b/app/kubernetes/models/application/models/index.js
index dbd7b8fbb..35520acfd 100644
--- a/app/kubernetes/models/application/models/index.js
+++ b/app/kubernetes/models/application/models/index.js
@@ -13,6 +13,7 @@ const _KubernetesApplication = Object.freeze({
Image: '',
CreationDate: 0,
Pods: [],
+ Containers: [],
Limits: {},
ServiceType: '',
ServiceId: '',
diff --git a/app/kubernetes/pod/converter.js b/app/kubernetes/pod/converter.js
index 121f312bc..3aae29482 100644
--- a/app/kubernetes/pod/converter.js
+++ b/app/kubernetes/pod/converter.js
@@ -1,5 +1,5 @@
import _ from 'lodash-es';
-import { KubernetesPod, KubernetesPodToleration, KubernetesPodAffinity } from 'Kubernetes/pod/models';
+import { KubernetesPod, KubernetesPodToleration, KubernetesPodAffinity, KubernetesPodContainer, KubernetesPodContainerTypes } from 'Kubernetes/pod/models';
function computeStatus(statuses) {
const containerStatuses = _.map(statuses, 'state');
@@ -13,6 +13,21 @@ function computeStatus(statuses) {
return 'Running';
}
+function computeContainerStatus(statuses, name) {
+ const status = _.find(statuses, { name: name });
+ if (!status) {
+ return 'Terminated';
+ }
+ const state = status.state;
+ if (state.waiting) {
+ return 'Waiting';
+ }
+ if (!state.running) {
+ return 'Terminated';
+ }
+ return 'Running';
+}
+
function computeAffinity(affinity) {
const res = new KubernetesPodAffinity();
if (affinity) {
@@ -33,6 +48,44 @@ function computeTolerations(tolerations) {
});
}
+function computeContainers(data) {
+ const containers = data.spec.containers;
+ const initContainers = data.spec.initContainers;
+
+ return _.concat(
+ _.map(containers, (item) => {
+ const res = new KubernetesPodContainer();
+ res.Type = KubernetesPodContainerTypes.APP;
+ res.PodName = data.metadata.name;
+ res.Name = item.name;
+ res.Image = item.image;
+ res.Node = data.spec.nodeName;
+ res.CreationDate = data.status.startTime;
+ res.Status = computeContainerStatus(data.status.containerStatuses, item.name);
+ res.Limits = item.resources.limits;
+ res.Requests = item.resources.requests;
+ res.VolumeMounts = item.volumeMounts;
+ res.Env = item.env;
+ return res;
+ }),
+ _.map(initContainers, (item) => {
+ const res = new KubernetesPodContainer();
+ res.Type = KubernetesPodContainerTypes.INIT;
+ res.PodName = data.metadata.name;
+ res.Name = item.name;
+ res.Image = item.image;
+ res.Node = data.spec.nodeName;
+ res.CreationDate = data.status.startTime;
+ res.Status = computeContainerStatus(data.status.containerStatuses, item.name);
+ res.Limits = item.resources.limits;
+ res.Requests = item.resources.requests;
+ res.VolumeMounts = item.volumeMounts;
+ res.Env = item.env;
+ return res;
+ })
+ );
+}
+
export default class KubernetesPodConverter {
static apiToModel(data) {
const res = new KubernetesPod();
@@ -44,7 +97,7 @@ export default class KubernetesPodConverter {
res.Restarts = _.sumBy(data.status.containerStatuses, 'restartCount');
res.Node = data.spec.nodeName;
res.CreationDate = data.status.startTime;
- res.Containers = data.spec.containers;
+ res.Containers = computeContainers(data);
res.Labels = data.metadata.labels;
res.Affinity = computeAffinity(data.spec.affinity);
res.NodeSelector = data.spec.nodeSelector;
diff --git a/app/kubernetes/pod/models/index.js b/app/kubernetes/pod/models/index.js
index cf76e0386..3cf9488b7 100644
--- a/app/kubernetes/pod/models/index.js
+++ b/app/kubernetes/pod/models/index.js
@@ -40,3 +40,30 @@ export class KubernetesPodToleration {
Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPodToleration)));
}
}
+
+const _KubernetesPodContainer = Object.freeze({
+ Type: 0,
+ PodName: '',
+ Name: '',
+ Image: '',
+ Node: '',
+ CreationDate: '',
+ Status: '',
+ Limits: {},
+ Requests: {},
+ VolumeMounts: {},
+ ConfigurationVolumes: [],
+ PersistedFolders: [],
+ Env: [],
+});
+
+export class KubernetesPodContainer {
+ constructor() {
+ Object.assign(this, JSON.parse(JSON.stringify(_KubernetesPodContainer)));
+ }
+}
+
+export const KubernetesPodContainerTypes = {
+ INIT: 1,
+ APP: 2,
+};
diff --git a/app/kubernetes/pod/service.js b/app/kubernetes/pod/service.js
index 90bca7be5..c2de34a2e 100644
--- a/app/kubernetes/pod/service.js
+++ b/app/kubernetes/pod/service.js
@@ -36,11 +36,15 @@ class KubernetesPodService {
*
* @param {string} namespace
* @param {string} podName
+ * @param {string} containerName
*/
- async logsAsync(namespace, podName) {
+ async logsAsync(namespace, podName, containerName) {
try {
const params = new KubernetesCommonParams();
params.id = podName;
+ if (containerName) {
+ params.container = containerName;
+ }
const data = await this.KubernetesPods(namespace).logs(params).$promise;
return data.logs.length === 0 ? [] : data.logs.split('\n');
} catch (err) {
@@ -48,8 +52,8 @@ class KubernetesPodService {
}
}
- logs(namespace, podName) {
- return this.$async(this.logsAsync, namespace, podName);
+ logs(namespace, podName, containerName) {
+ return this.$async(this.logsAsync, namespace, podName, containerName);
}
/**
diff --git a/app/kubernetes/services/applicationService.js b/app/kubernetes/services/applicationService.js
index 1afe88de5..27ce3e9d9 100644
--- a/app/kubernetes/services/applicationService.js
+++ b/app/kubernetes/services/applicationService.js
@@ -115,6 +115,7 @@ class KubernetesApplicationService {
application.Yaml = rootItem.value.Yaml;
application.Raw = rootItem.value.Raw;
application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods.value, application.Raw);
+ application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
const boundScaler = KubernetesHorizontalPodAutoScalerHelper.findApplicationBoundScaler(autoScalers.value, application);
const scaler = boundScaler ? await this.KubernetesHorizontalPodAutoScalerService.get(namespace, boundScaler.Name) : undefined;
@@ -144,6 +145,7 @@ class KubernetesApplicationService {
const service = KubernetesServiceHelper.findApplicationBoundService(services, item);
const application = converterFunc(item, service, ingresses);
application.Pods = KubernetesApplicationHelper.associatePodsAndApplication(pods, item);
+ application.Containers = KubernetesApplicationHelper.associateContainersAndApplication(application);
return application;
};
diff --git a/app/kubernetes/views/applications/console/console.html b/app/kubernetes/views/applications/console/console.html
index 51bb6ea3e..b4fa3a5a1 100644
--- a/app/kubernetes/views/applications/console/console.html
+++ b/app/kubernetes/views/applications/console/console.html
@@ -3,7 +3,7 @@
{{ ctrl.application.ResourcePool }} >
Applications >
{{ ctrl.application.Name }} > Pods >
- {{ ctrl.podName }} > Console
+ {{ ctrl.podName }} > Containers > {{ ctrl.containerName }} > Console
diff --git a/app/kubernetes/views/applications/console/consoleController.js b/app/kubernetes/views/applications/console/consoleController.js
index ccf8e37ed..39310b140 100644
--- a/app/kubernetes/views/applications/console/consoleController.js
+++ b/app/kubernetes/views/applications/console/consoleController.js
@@ -54,7 +54,7 @@ class KubernetesApplicationConsoleController {
endpointId: this.EndpointProvider.endpointID(),
namespace: this.application.ResourcePool,
podName: this.podName,
- containerName: this.application.Pods[0].Containers[0].name,
+ containerName: this.containerName,
command: this.state.command,
};
@@ -92,8 +92,10 @@ class KubernetesApplicationConsoleController {
const podName = this.$transition$.params().pod;
const applicationName = this.$transition$.params().name;
const namespace = this.$transition$.params().namespace;
+ const containerName = this.$transition$.params().container;
this.podName = podName;
+ this.containerName = containerName;
try {
this.application = await this.KubernetesApplicationService.get(namespace, applicationName);
diff --git a/app/kubernetes/views/applications/create/createApplication.html b/app/kubernetes/views/applications/create/createApplication.html
index f30fa9a95..c6eb5f637 100644
--- a/app/kubernetes/views/applications/create/createApplication.html
+++ b/app/kubernetes/views/applications/create/createApplication.html
@@ -54,7 +54,15 @@