Skip to content

[release-4.18] OCPBUGS-54337: Debug pod logs are not accessible when debugging a node via OpenShift Console #14908

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 56 additions & 23 deletions frontend/packages/console-app/src/components/nodes/NodeTerminal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,15 @@ const getDebugImage = async (): Promise<string> => {
}
};

const getDebugPod = async (name: string, namespace: string, nodeName: string): Promise<PodKind> => {
const getDebugPod = async (
name: string,
namespace: string,
nodeName: string,
isWindows: boolean,
): Promise<PodKind> => {
const image = await getDebugImage();
return {
// configuration as specified in https://github.com/openshift/oc/blob/master/pkg/cli/debug/debug.go#L1024-L1114
const template: PodKind = {
kind: 'Pod',
apiVersion: 'v1',
metadata: {
Expand All @@ -48,43 +54,68 @@ const getDebugPod = async (name: string, namespace: string, nodeName: string): P
},
},
spec: {
activeDeadlineSeconds: 21600,
volumes: [
{
name: 'host',
hostPath: {
path: '/',
type: 'Directory',
},
},
],
containers: [
{
name: 'container-00',
image,
command: ['/bin/sh'],
resources: {},
volumeMounts: [
env: [
{
name: 'host',
mountPath: '/host',
// Set the Shell variable to auto-logout after 15m idle timeout
name: 'TMOUT',
value: '900',
},
{
// this env requires to be set in order to collect more sos reports
name: 'HOST',
value: '/host',
},
],
image,
name: 'container-00',
resources: {},
securityContext: {
privileged: true,
runAsUser: 0,
},
stdin: true,
stdinOnce: true,
tty: true,
volumeMounts: [
{
name: 'host',
mountPath: '/host',
},
],
},
],
restartPolicy: 'Never',
nodeName,
hostNetwork: true,
hostIPC: true,
hostPID: true,
hostNetwork: true,
nodeName,
restartPolicy: 'Never',
volumes: [
{
name: 'host',
hostPath: {
path: '/',
type: 'Directory',
},
},
],
},
};

if (isWindows) {
template.spec.OS = 'windows';
template.spec.hostPID = false;
template.spec.hostIPC = false;
const containerUser = 'ContainerUser';
template.spec.containers[0].securityContext = {
windowsOptions: {
runAsUserName: containerUser,
},
};
}
return template;
};

const NodeTerminalError: React.FC<NodeTerminalErrorProps> = ({ error }) => {
Expand Down Expand Up @@ -128,6 +159,8 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
const [resources, setResources] = React.useState<FirehoseResource[]>([]);
const [errorMessage, setErrorMessage] = React.useState('');
const nodeName = node.metadata.name;
const isWindows = node.status?.nodeInfo?.operatingSystem === 'windows';

React.useEffect(() => {
let namespace;
const name = `${nodeName?.replace(/\./g, '-')}-debug`;
Expand Down Expand Up @@ -160,7 +193,7 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
},
},
});
const podToCreate = await getDebugPod(name, namespace.metadata.name, nodeName);
const podToCreate = await getDebugPod(name, namespace.metadata.name, nodeName, isWindows);
// wait for the namespace to be ready
await new Promise((resolve) => setTimeout(resolve, 1000));
const debugPod = await k8sCreate(PodModel, podToCreate);
Expand Down Expand Up @@ -188,7 +221,7 @@ const NodeTerminal: React.FC<NodeTerminalProps> = ({ obj: node }) => {
deleteNamespace(namespace.metadata.name);
window.removeEventListener('beforeunload', closeTab);
};
}, [nodeName]);
}, [nodeName, isWindows]);

return errorMessage ? (
<NodeTerminalError error={errorMessage} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,15 @@ import { Terminal } from './terminal';
import { WSFactory } from '../module/ws-factory';
import { resourceURL } from '../module/k8s';
import { PodModel } from '../models';
import { isWindowsPod } from '../module/k8s/pods';

// pod exec WS protocol is FD prefixed, base64 encoded data (sometimes json stringified)
// pod attach WS protocol is FD prefixed, base64 encoded data (sometimes json stringified)

// Channel 0 is STDIN, 1 is STDOUT, 2 is STDERR (if TTY is not requested), and 3 is a special error channel - 4 is C&C
// The server only reads from STDIN, writes to the other three.
// see also: https://github.com/kubernetes/kubernetes/pull/13885

const NO_SH =
'starting container process caused "exec: \\"sh\\": executable file not found in $PATH"';

const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
class PodExec extends React.PureComponent {
const PodAttach_ = connectToFlags(FLAGS.OPENSHIFT)(
class PodAttach extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
Expand All @@ -47,19 +43,16 @@ const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
metadata: { name, namespace },
} = this.props.obj;
const { activeContainer } = this.state;
const usedClient = this.props.flags[FLAGS.OPENSHIFT] ? 'oc' : 'kubectl';
const command = isWindowsPod(this.props.obj) ? ['cmd'] : ['sh', '-i', '-c', 'TERM=xterm sh'];
const params = {
ns: namespace,
name,
path: 'exec',
path: 'attach',
queryParams: {
stdout: 1,
stdin: 1,
stderr: 1,
tty: 1,
container: activeContainer,
command: command.map((c) => encodeURIComponent(c)).join('&command='),
},
};

Expand All @@ -72,7 +65,6 @@ const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
const impersonate = getImpersonate(store.getState()) || {};
const subprotocols = (impersonate.subprotocols || []).concat('base64.channel.k8s.io');

let previous;
this.ws = new WSFactory(`${name}-terminal`, {
host: 'auto',
reconnect: true,
Expand All @@ -82,26 +74,12 @@ const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
})
.onmessage((raw) => {
const { current } = this.terminal;
// error channel
if (raw[0] === '3') {
if (previous.includes(NO_SH)) {
current.reset();
current.onConnectionClosed(
`This container doesn't have a /bin/sh shell. Try specifying your command in a terminal with:\r\n\r\n ${usedClient} -n ${namespace} exec ${name} -ti <command>`,
);
this.ws.destroy();
previous = '';
return;
}
}
const data = Base64.decode(raw.slice(1));
current && current.onDataReceived(data);
previous = data;
})
.onopen(() => {
const { current } = this.terminal;
current && current.reset();
previous = '';
this.setState({ open: true, error: null });
})
.onclose((evt) => {
Expand Down Expand Up @@ -227,4 +205,4 @@ const PodExec_ = connectToFlags(FLAGS.OPENSHIFT)(
},
);

export const PodExec = withTranslation()(PodExec_);
export const PodAttach = withTranslation()(PodAttach_);
2 changes: 1 addition & 1 deletion frontend/public/components/pod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -974,7 +974,7 @@ export const PodExecLoader: React.FC<PodExecLoaderProps> = ({
<div className="col-xs-12">
<div className="panel-body">
<AsyncComponent
loader={() => import('./pod-exec').then((c) => c.PodExec)}
loader={() => import('./pod-attach').then((c) => c.PodAttach)}
obj={obj}
message={message}
infoMessage={infoMessage}
Expand Down