1
0
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:
Piotr Roszatycki 2023-10-23 11:09:28 +02:00
parent f1a960fd78
commit 83b2ce6afc
4 changed files with 109 additions and 35 deletions

View File

@ -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

View File

@ -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,
}, },

View File

@ -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 />

View File

@ -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;
} }