mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Allow to run node shell on Bootlerocket and Windows nodes
Signed-off-by: Piotr Roszatycki <piotr.roszatycki@gmail.com>
This commit is contained in:
parent
f1a960fd78
commit
83b2ce6afc
@ -108,6 +108,7 @@ export interface ClusterPreferences extends ClusterPrometheusPreferences {
|
|||||||
httpsProxy?: string;
|
httpsProxy?: string;
|
||||||
hiddenMetrics?: string[];
|
hiddenMetrics?: string[];
|
||||||
nodeShellImage?: string;
|
nodeShellImage?: string;
|
||||||
|
nodeShellWindowsImage?: string;
|
||||||
imagePullSecret?: string;
|
imagePullSecret?: string;
|
||||||
defaultNamespace?: string;
|
defaultNamespace?: string;
|
||||||
}
|
}
|
||||||
@ -177,7 +178,12 @@ export enum ClusterMetricsResourceType {
|
|||||||
/**
|
/**
|
||||||
* The default node shell image
|
* The default node shell image
|
||||||
*/
|
*/
|
||||||
export const initialNodeShellImage = "docker.io/alpine:3.13";
|
export const initialNodeShellImage = "docker.io/library/alpine";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default node shell image for Windows
|
||||||
|
*/
|
||||||
|
export const initialNodeShellWindowsImage = "mcr.microsoft.com/powershell";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data representing a cluster's state, for passing between main and renderer
|
* The data representing a cluster's state, for passing between main and renderer
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import { NodeApi } from "@k8slens/kube-api";
|
|||||||
import { TerminalChannels } from "../../../common/terminal/channels";
|
import { TerminalChannels } from "../../../common/terminal/channels";
|
||||||
import type { CreateKubeJsonApiForCluster } from "../../../common/k8s-api/create-kube-json-api-for-cluster.injectable";
|
import type { CreateKubeJsonApiForCluster } from "../../../common/k8s-api/create-kube-json-api-for-cluster.injectable";
|
||||||
import type { CreateKubeApi } from "../../../common/k8s-api/create-kube-api.injectable";
|
import type { CreateKubeApi } from "../../../common/k8s-api/create-kube-api.injectable";
|
||||||
import { initialNodeShellImage } from "../../../common/cluster-types";
|
import { initialNodeShellImage, initialNodeShellWindowsImage } from "../../../common/cluster-types";
|
||||||
import type { LoadProxyKubeconfig } from "../../cluster/load-proxy-kubeconfig.injectable";
|
import type { LoadProxyKubeconfig } from "../../cluster/load-proxy-kubeconfig.injectable";
|
||||||
import type { Pod } from "@k8slens/kube-object";
|
import type { Pod } from "@k8slens/kube-object";
|
||||||
|
|
||||||
@ -71,34 +71,12 @@ export class NodeShellSession extends ShellSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const env = await this.getCachedShellEnv();
|
const env = await this.getCachedShellEnv();
|
||||||
const args = ["exec", "-i", "-t", "-n", "kube-system", this.podName, "--"];
|
const args = ["attach", "-q", "-i", "-t", "-n", "kube-system", this.podName];
|
||||||
const nodeApi = this.dependencies.createKubeApi(NodeApi, {
|
|
||||||
request: this.dependencies.createKubeJsonApiForCluster(this.cluster.id),
|
|
||||||
});
|
|
||||||
const node = await nodeApi.get({ name: this.nodeName });
|
|
||||||
|
|
||||||
if (!node) {
|
|
||||||
throw new Error(`No node with name=${this.nodeName} found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeOs = node.getOperatingSystem();
|
|
||||||
|
|
||||||
switch (nodeOs) {
|
|
||||||
default:
|
|
||||||
this.dependencies.logger.warn(`[NODE-SHELL-SESSION]: could not determine node OS, falling back with assumption of linux`);
|
|
||||||
// fallthrough
|
|
||||||
case "linux":
|
|
||||||
args.push("sh", "-c", "((clear && bash) || (clear && ash) || (clear && sh))");
|
|
||||||
break;
|
|
||||||
case "windows":
|
|
||||||
args.push("powershell");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.openShellProcess(await this.kubectl.getPath(), args, env);
|
await this.openShellProcess(await this.kubectl.getPath(), args, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createNodeShellPod(coreApi: CoreV1Api) {
|
protected async createNodeShellPod(coreApi: CoreV1Api) {
|
||||||
const {
|
const {
|
||||||
imagePullSecret,
|
imagePullSecret,
|
||||||
nodeShellImage,
|
nodeShellImage,
|
||||||
@ -110,6 +88,60 @@ export class NodeShellSession extends ShellSession {
|
|||||||
}]
|
}]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
const nodeApi = this.dependencies.createKubeApi(NodeApi, {
|
||||||
|
request: this.dependencies.createKubeJsonApiForCluster(this.cluster.id),
|
||||||
|
});
|
||||||
|
const node = await nodeApi.get({ name: this.nodeName });
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
throw new Error(`No node with name=${this.nodeName} found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeOs = node.getOperatingSystem();
|
||||||
|
const nodeOsImage = node.getOperatingSystemImage();
|
||||||
|
const nodeKernelVersion = node.getKernelVersion();
|
||||||
|
|
||||||
|
let image: string;
|
||||||
|
let command: string[];
|
||||||
|
let args: string[];
|
||||||
|
let securityContext: any;
|
||||||
|
|
||||||
|
switch (nodeOs) {
|
||||||
|
default:
|
||||||
|
this.dependencies.logger.warn(`[NODE-SHELL-SESSION]: could not determine node OS, falling back with assumption of linux`);
|
||||||
|
// fallthrough
|
||||||
|
case "linux":
|
||||||
|
image = nodeShellImage || initialNodeShellImage;
|
||||||
|
command = ["nsenter"];
|
||||||
|
|
||||||
|
if (nodeOsImage && nodeOsImage.startsWith("Bottlerocket OS")) {
|
||||||
|
args = ["-t", "1", "-m", "-u", "-i", "-n", "-p", "--", "apiclient", "exec", "admin", "bash", "-l"];
|
||||||
|
} else {
|
||||||
|
args = ["-t", "1", "-m", "-u", "-i", "-n", "-p", "--", "bash", "-l"];
|
||||||
|
}
|
||||||
|
|
||||||
|
securityContext = {
|
||||||
|
privileged: true,
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case "windows":
|
||||||
|
if (nodeKernelVersion) {
|
||||||
|
image = nodeShellImage || initialNodeShellWindowsImage;
|
||||||
|
} else {
|
||||||
|
throw new Error(`No status with kernel version for node ${this.nodeName} found`);
|
||||||
|
}
|
||||||
|
command = ["cmd.exe"];
|
||||||
|
args = ["/c", "%CONTAINER_SANDBOX_MOUNT_POINT%\\Program Files\\PowerShell\\latest\\pwsh.exe", "-nol", "-wd", "C:\\"];
|
||||||
|
securityContext = {
|
||||||
|
privileged: true,
|
||||||
|
windowsOptions: {
|
||||||
|
hostProcess: true,
|
||||||
|
runAsUserName: "NT AUTHORITY\\SYSTEM",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return coreApi
|
return coreApi
|
||||||
.createNamespacedPod("kube-system", {
|
.createNamespacedPod("kube-system", {
|
||||||
metadata: {
|
metadata: {
|
||||||
@ -129,12 +161,13 @@ export class NodeShellSession extends ShellSession {
|
|||||||
priorityClassName: "system-node-critical",
|
priorityClassName: "system-node-critical",
|
||||||
containers: [{
|
containers: [{
|
||||||
name: "shell",
|
name: "shell",
|
||||||
image: nodeShellImage || initialNodeShellImage,
|
image,
|
||||||
securityContext: {
|
securityContext,
|
||||||
privileged: true,
|
command,
|
||||||
},
|
args,
|
||||||
command: ["nsenter"],
|
stdin: true,
|
||||||
args: ["-t", "1", "-m", "-u", "-i", "-n", "sleep", "14000"],
|
stdinOnce: true,
|
||||||
|
tty: true,
|
||||||
}],
|
}],
|
||||||
imagePullSecrets,
|
imagePullSecrets,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import React from "react";
|
|||||||
import { Input } from "../input/input";
|
import { Input } from "../input/input";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Icon } from "@k8slens/icon";
|
import { Icon } from "@k8slens/icon";
|
||||||
import { initialNodeShellImage } from "../../../common/cluster-types";
|
import { initialNodeShellImage, initialNodeShellWindowsImage } from "../../../common/cluster-types";
|
||||||
import Gutter from "../gutter/gutter";
|
import Gutter from "../gutter/gutter";
|
||||||
|
|
||||||
export interface ClusterNodeShellSettingProps {
|
export interface ClusterNodeShellSettingProps {
|
||||||
@ -20,6 +20,7 @@ export interface ClusterNodeShellSettingProps {
|
|||||||
@observer
|
@observer
|
||||||
export class ClusterNodeShellSetting extends React.Component<ClusterNodeShellSettingProps> {
|
export class ClusterNodeShellSetting extends React.Component<ClusterNodeShellSettingProps> {
|
||||||
@observable nodeShellImage = this.props.cluster.preferences?.nodeShellImage || "";
|
@observable nodeShellImage = this.props.cluster.preferences?.nodeShellImage || "";
|
||||||
|
@observable nodeShellWindowsImage = this.props.cluster.preferences?.nodeShellWindowsImage || "";
|
||||||
@observable imagePullSecret = this.props.cluster.preferences?.imagePullSecret || "";
|
@observable imagePullSecret = this.props.cluster.preferences?.imagePullSecret || "";
|
||||||
|
|
||||||
constructor(props: ClusterNodeShellSettingProps) {
|
constructor(props: ClusterNodeShellSettingProps) {
|
||||||
@ -30,6 +31,7 @@ export class ClusterNodeShellSetting extends React.Component<ClusterNodeShellSet
|
|||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.props.cluster.preferences.nodeShellImage = this.nodeShellImage || undefined;
|
this.props.cluster.preferences.nodeShellImage = this.nodeShellImage || undefined;
|
||||||
|
this.props.cluster.preferences.nodeShellWindowsImage = this.nodeShellWindowsImage || undefined;
|
||||||
this.props.cluster.preferences.imagePullSecret = this.imagePullSecret || undefined;
|
this.props.cluster.preferences.imagePullSecret = this.imagePullSecret || undefined;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -38,7 +40,7 @@ export class ClusterNodeShellSetting extends React.Component<ClusterNodeShellSet
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section>
|
<section>
|
||||||
<SubTitle title="Node shell image" id="node-shell-image"/>
|
<SubTitle title="Node shell image for Linux" id="node-shell-image"/>
|
||||||
<Input
|
<Input
|
||||||
theme="round-black"
|
theme="round-black"
|
||||||
placeholder={`Default image: ${initialNodeShellImage}`}
|
placeholder={`Default image: ${initialNodeShellImage}`}
|
||||||
@ -58,7 +60,32 @@ export class ClusterNodeShellSetting extends React.Component<ClusterNodeShellSet
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<small className="hint">
|
<small className="hint">
|
||||||
Node shell image. Used for creating node shell pod.
|
Node shell image. Used for creating node shell pod on Linux nodes.
|
||||||
|
</small>
|
||||||
|
</section>
|
||||||
|
<Gutter />
|
||||||
|
<section>
|
||||||
|
<SubTitle title="Node shell image for Windows" id="node-shell-windows-image"/>
|
||||||
|
<Input
|
||||||
|
theme="round-black"
|
||||||
|
placeholder={`Default image: ${initialNodeShellWindowsImage}`}
|
||||||
|
value={this.nodeShellWindowsImage}
|
||||||
|
onChange={value => this.nodeShellWindowsImage = value}
|
||||||
|
iconRight={
|
||||||
|
this.nodeShellWindowsImage
|
||||||
|
? (
|
||||||
|
<Icon
|
||||||
|
smallest
|
||||||
|
material="close"
|
||||||
|
onClick={() => this.nodeShellWindowsImage = ""}
|
||||||
|
tooltip="Reset"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<small className="hint">
|
||||||
|
Node shell image. Used for creating node shell pod on Windows nodes.
|
||||||
</small>
|
</small>
|
||||||
</section>
|
</section>
|
||||||
<Gutter />
|
<Gutter />
|
||||||
|
|||||||
@ -247,6 +247,14 @@ export class Node extends KubeObject<ClusterScopedMetadata, NodeStatus, NodeSpec
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOperatingSystemImage(): string | undefined {
|
||||||
|
return this.status?.nodeInfo?.osImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
getKernelVersion(): string | undefined {
|
||||||
|
return this.status?.nodeInfo?.kernelVersion;
|
||||||
|
}
|
||||||
|
|
||||||
isUnschedulable() {
|
isUnschedulable() {
|
||||||
return this.spec.unschedulable;
|
return this.spec.unschedulable;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user