原文作者:行云创新技术总监 邓冰寒
概述
上一期在使用官方容器镜像快速成功地在 TitanIDE 运行起来了 WeTTY,但是不适合开发人员使用,而我自己编译构建出来的容器镜像无法直接运行指定的应用(/bin/bash 或 /bin/zsh),本来在 WeTTY的开源项目下面提了 issue,但貌似没有得到响应,如果能在一个小时左右快速解决问题,折腾 WeTTY 这个开源软件对我来说是有价值的。
折腾经历
在经过权衡利弊(内心挣扎)之后,为了给开发者提供最好的使用体验,我决定花一个小时左右的时间在 TitanIDE 上看看是什么原因导致 WeTTY 不理会我传递的参数 --command。
首先在 TitanIDE 创建一个 NodeJS 的项目,这里我采用了 VS Code for Node.js 作为 WeTTY 的开发环境。
体验TitanIDE>>www.titanide.cn
创建好项目之后,紧接着就是来执行使用开源项目的三把斧,fork,clone,run。
分析问题
如上一期文章所提到的,既然–command在官方提供的镜像可以正常工作的,没有理由我自己运行的不行,肯定是有环境的差异性。经过一番顺藤摸瓜式的盘查,在我预期的时间内找到了问题的根本原因。
首先,WeTTY 在启动的时候会先获取 cmmand,如下代码所示:
/**
* Starts WeTTy Server
* @name startServer
* @returns Promise that resolves SocketIO server
*/
export async function start(
ssh: SSH = sshDefault,
serverConf: Server = serverDefault,
command: string = defaultCommand,
forcessh: boolean = forceSSHDefault,
ssl: SSL | undefined = undefined,
): Promise<SocketIO.Server> {
// 此处省略部分代码以聚集问题
// ...
logger.info('Connection accepted.');
// WeTTY 在启动的时候会先获取 cmmand
const [args, sshUser] = getCommand(socket, ssh, command, forcessh);
const cmd = args.join(' ');
logger.debug('Command Generated', { user: sshUser, cmd });
wettyConnections.inc();
// 此处省略部分代码以聚集问题
// ...
return io;
}
在 getCommand() 函数内又调用了 localhost() 函数来决定是否使用 --command 参数:
export function getCommand(...): [string[], boolean] {
// 在获取 command 的时候去判断了所否所 localhost 的 IP
const sshAddress = address(headers, user, host);
if (!forcessh && localhost(host)) {
return [loginOptions(command, remoteAddress), true];
}
// 此处省略部分代码以聚集问题
// ...
return [
sshOptions(args, key),
user !== '' || user.includes('@') || sshAddress.includes('@'),
];
}
localhost 的判断函数又有一行代码判断 uid 是否为 0, 即 root 用户才能在 localhost 启动自定义命令,这逻辑很怪,不知道设计者是怎么考虑的. TitanIDE 使用了 Ubuntu 作为操作系统,默认用户为非 root,因此在 Ubuntu 没法正常运行。
const localhost = (host: string): boolean =>
process.getuid() === 0 &&
(host === 'localhost' || host === '0.0.0.0' || host === '127.0.0.1');
解决问题
经过以上分析,问题已经得到解决,我传递的参数 --command /bin/zsh 已经起作用了,以下命令是完整的命令行:
yarn start --port=8080 --base / --command /bin/zsh
如下图所示,我将 process.getuid() === 0 这行代码去掉后,WeTTY 运行一切正常:
制作镜像
和上期所介绍的一样,我们也是在 TitanIDE 使用 TepmlateMaker 创建一个 WeTTY 的项目,用于制作 WeTTY for Ubuntu 的模板镜像。
创建好 template-wetty 之后,先编辑 WeTTY 的启动脚本以在 Dockerfile 备用:
#!/bin/zsh
# WeTTY 默认使用固定的 BASE (/wetty),需要修改成 TitanIDE 的命名规则
BASE="/ide/${POD_NAMESPACE}/${PROJECT_NAME}/"
# 启动 WeTTY
/usr/local/nodejs/current/bin/node ${WETTY_HOME} --port ${TERMINAL_SERVER_PORT} --base ${BASE} --command /bin/zsh
然后编辑 Dockerfile 以实现 WeTTY 在 Ubuntu 下面作为一个开发环境。这里使用了 TitanIDE 的基础镜像 template-core:v20230119-1cfbd2e,该基础镜像基于 Ubuntu 20.04,包含了开发者常用的工具,如 kubectl, helm,git 等 。以下是完整的 Dockerfile:
# TitanIDE Template
# 基础镜像的镜像仓库,根据用户实际需要修改入参,默认情况下保存不变
ARG from_hub
FROM ${from_hub}template-core:v20230119-1cfbd2e
# 指定 WeTTY 监听的端口号
ENV TERMINAL_SERVER_PORT=30000 \
WETTY_HOME=/usr/src/app \
NODE_HOME=/usr/local/nodejs/current \
TMPDIR=/tmp/pkgs
# TitanIDE 的模板图标通过 icon 入参来指定,
ARG icon
# 镜像名字,默认取当前项目名称,如果用户没有修改的情况下,以本文创建的项目名称为例是 template-wetty,最终生成的镜像是 registry/ns/template-wetty:tag,例如
# 当然也可在构建镜像的时候由入参来覆盖,如 make app_name=foo, 则构镜像名称为 registry/ns/foo:tag
ARG app_name
# 镜像的版本(tag),如 titan.hub:5000/demo/template-wetty:v20230129-6a9aa54 中的 v20230129-6a9aa54 是有 TitanIDE 自动生成,用户也可以通过参数指定,如 make app_version=v0.1
ARG app_version
# 模板的图标,在 ARG icon 部分已经解释过
LABEL metadata.icon="${icon}"
# 镜像名称,在 ARG app_name 部分已经解释过
LABEL metadata.appname="${app_name}"
# 镜像版本,在 ARG app_version 部分已经解释过
LABEL metadata.version="${app_version} "
# 应用的类型,WeTTY 是 Web 原生应用,所以需要指定为 webapp
LABEL metadata.type="webapp"
# 右侧工具栏定义,以下选择了 file,port 两个工具,表示 WeTTY 启动后再右侧栏会有这两个工具
LABEL devtools="file,port"
# WeTTY 监听的端口号,TitanIDE 会根据这个端口号来创建访问的链接
LABEL metadata.port=${TERMINAL_SERVER_PORT}
# 告诉 TitanIDE,WeTTY 服务的访问路径 (URL base) 需要重写
LABEL metadata.rewritesubpath="true"
# 先切换为 root 方便安装软件
USER root
# 复制启动命令脚本到 /usr/bin 下
COPY ./bin /usr/bin
# github.com 的代理服务
ARG GH_PROXY
# 安装软件
RUN mkdir -p ${PYTHON_VENV} ${WETTY_HOME} $USER_HOME/.local/share /usr/local/nodejs $USER_HOME/.config \
&& apt update \
# 安装常用的工具
&& apt install -y libwebsockets-dev coreutils openssh-client sshpass \
# 获取并安装 nodejs
&& wget -P ${TMPDIR} https://nodejs.org/dist/v18.13.0/node-v18.13.0-linux-x64.tar.gz \
&& tar -xvzf ${TMPDIR}/node-v18.13.0-linux-x64.tar.gz -C /usr/local/nodejs/ \
&& ln -s /usr/local/nodejs/node-v18.13.0-linux-x64 ${NODE_HOME} \
&& ln -s ${NODE_HOME}/node /usr/bin/node \
&& ln -s ${NODE_HOME}/npm /usr/bin/npm \
# 设置路径环境变量
&& echo "export PATH=${USER_HOME}/.local/bin:${NODE_HOME}/bin:$PATH" >> ${USER_HOME}/.zshrc \
&& export PATH=${NODE_HOME}/bin:$PATH \
# 设置为国内的 npm 仓库镜像
&& npm config set registry https://registry.npm.taobao.org \
&& npm install -g npm@9.3.1 yarn cnpm snowpack \
&& ${NODE_HOME}/bin/yarn config set registry https://registry.npm.taobao.org \
# 克隆 WeTTY 源码,下面是我修改后的版本
&& git clone --branch dev --depth 1 ${GH_PROXY}https://github.com/john-deng/wetty.git ${TMPDIR}/wetty \
&& cd ${TMPDIR}/wetty \
# 编译 WeTTY 源码
&& yarn install \
&& yarn build \
# 将源码复制到最终部署的文件夹
&& cp -r build node_modules package.json ${WETTY_HOME} \
# 清理不再需要的源文件(当然,也可以采用独立的融容器来构建)
&& rm -rf ${TMPDIR}/wetty \
&& chown -R ide:ide /tmp $USER_HOME \
# 过滤掉 WeTTY 的端口在 TitanIDE 工作区内应用端口列表的展示
&& echo "${WETTY_HOME}" > /etc/port-filters
# 切换为普通用户
USER ide
# 切换工作路径
WORKDIR $USER_HOME/workspace
EXPOSE ${TERMINAL_SERVER_PORT}
# 最后 TitanIDE 要求运行 finalize.sh 完成 HOME 目录下的安装文件备份
ARG app_version
RUN /finalize.sh
在 TitanIDE 的工作区直接执行 make 命令构建出 WeTTY 的模版镜像:
template-wetty git:(wetty) make
2023-02-03T15:37:36 info workdir: /home/ide/workspace/template-wetty
2023-02-03T15:37:36 info Already contains credential for user admin in titan.hub:5000, if you want to update it, pleas input with force argument, e.g. make login force=true
2023-02-03T15:37:36 info Building container image on TitanIDE ...
...
INFO[0514] Taking snapshot of full filesystem...
INFO[0520] Pushing image to registry.cn-shenzhen.aliyuncs.com/titanide/template-wetty:v20230131-1b25d1b
INFO[0558] Pushed registry.cn-shenzhen.aliyuncs.com/titanide/template-wetty@sha256:92cdbefce71f63644af1074ec793eecb21fd1b0a9f031fdc00b876fd2d571ec2
2023-02-03T15:31:44 info build phase: Succeeded
2023-02-03T15:31:44 info pushed titan.hub:5000/demo/template-wetty:v20230131-1b25d1b
验证效果
然后使用 WeTTY 模板镜像 titan.hub:5000/demo/template-wetty:v20230131-1b25d1b 创建 WeTTY 模版:
最后,使用 WeTTY 模版来创建 WeTTY 项目。验证最终效果,如下图所示。
在云端使用 Vim,真香!
总结
在选用如 WeTTY 这样的开源软件过程中,和使用其他开源软件一样,并不是无偿完全无偿使用的,为了价值的最大化,这就需要像 TitanIDE 这样的开发环境,在 TitanIDE 上面折腾的这些开源软件有个好处就是,我不需要为了某个开源软件去准备开发环境,因为 TitanIDE 提供了我要的开发环境模板,开箱即用。在这个基础之上,我们又可以创建属于自己的模版,本文展示了如何在 TitanIDE 快速解决开源软件的问题,然后定制一个 WeTTY 云端 Terminal 作为开发环境的完整过程,希望能帮到有需要的开发者。
本文是系列文章《在 TitanIDE 玩转云原生 Terminal 系列》,我将会在下一篇继续就这个话题展开探索,敬请期待!
体验TitanIDE>>www.titanide.cn
最后,感谢阅读!