文章

合租房中建家:在共用的Linux普通账户下配置开发环境

动机

使用 Linux 最好有 root 账户,可是很多时候我们只有一个普通账户,而且这个账户还是共用的。例如,集群中心只给我们导师分配了一个普通账户,我们整个组都得用这个账户,所以我们在 Linux 系统的权限级别可以说不能再卑微了,而且很多集群用的都是老掉牙的系统(例如 REHL 6,Linux 2.6.32),难受得一批。这时配置专用开发环境就像在合租房里装修自己的房间,要尽可能用有限的资源,要避免自己对其它人以及别人对自己造成影响。

为了避免自己对别人的影响,我一般做法都是在家目录下建一个自己名字的目录,然后我的所有文件都放里面。而为了避免别人对自己的影响,就要在这个家目录里建一个尽可能闭包的软件运行环境,隔绝掉用户级配置的影响(例如 ~/.bashrc),系统级环境的影响无法隔绝,但可以尽量减小。

总的思路就是利用系统中提供的基础编译器和 Spack,从底到上地把整个应用软件栈给构建起来,这个过程其实也非常有意思,可以清晰地看到整个计算机软件世界是如何像盖楼一样地搭建起来的。

配置网络代理

如果你到了使用这种合租房服务器的地步,你有很大的可能也会遇到服务器上连接不上互联网的问题,这时只能使用 SSH 远地端口转发来缓解这个问题。

在本地机器上执行:

1
ssh PublicUser@PublicServer -R 10000:proxyserver:10000

它会在 SSH 登录成功后远地服务器上监听 10000 端口,所有到这个端口的请求都会被转发到本地机器的 proxyserver:10000,在使用网络期间需要一直保持这个 SSH 连接。

然后在远地服务器上设置环境变量:

1
2
3
_proxy=127.0.0.1:10000
export http_proxy=http://$_proxy
export https_proxy=http://$_proxy

就行了,这样至少 Spack 就能正常工作了。

可以在 SSH 配置文件里写好这个转发,这样就不用每次都手动输入了:

1
2
3
Host PublicServer
   User PublicUser
   RemoteForward 10000 proxyserver:10000

准备 Spack

最好别直接用 git clone 的方式安装 Spack,而是用他们的 stable releases,这样安装的软件更稳定。

本次我选择将 0.21.2 版本安装在 ~/gyh/+/spack/0.21.2 目录下,考虑到我可能会要安装多个版本的 Spack。

1
2
wget https://github.com/spack/spack/releases/download/v0.21.2/spack-0.21.2.tar.gz
tar -xvf spack-0.21.2.tar.gz

Spack 主要是用 Python 编写的,至少需要 Python 3.6 版本(或者更新),但有一些太老的系统上可能没有 Python 3,这时我们必须想办法自己搞一个过来,比如,我们可以在自己的机器上编译一个 Python 3.6,然后把它复制到共用账户的机器上。总之,这第一个启动用的 Python 3.6 是必要的,无论如何都得想办法搞到。3.6 就行了,不需要很新,因为一旦后面 Spack 安装好了,我们就可以用 Spack 安装的 Python 了。

完全隔离地使用 Spack

Spack 会将软件包就安装在它自己的安装目录(~/gyh/+/spack/0.21.2),但仍然有一些用户级的全局数据会存储在 ~/.spack 中,我们必须要排除这些数据,以排除共用账户的影响,获得完全隔离的 Spack 环境。Spack 提供了排除的方法,是在启动前设置环境变量 SPACK_DISABLE_LOCAL_CONFIGSPACK_USER_CACHE_PATH,为了避免对其他人造成影响,我们不在 ~/.bashrc 中设置它们,而是之后在我们自己的 ~/gyh/.bashrc 中设置。

从共用账户的角度来说,这个变量就应该设置在 ~/.bashrc 中,因为任何人都不应该在家目录中存储只服务于他自己的配置。

1
2
export SPACK_DISABLE_LOCAL_CONFIG=true
export SPACK_USER_CACHE_PATH=~/gyh/+/spack/0.21.2/user_cache

调整 Spack 的构建并行度

Spack 默认最多只用 -j 16 来构建软件包,这在现代服务器上是远远不够的,我们可以在 ~/+/spack/0.21.2/etc/spack/config.yaml 中设置 config:build_jobs 来调整并行度。

升级 GCC

GCC 就是万物之母,只要有了 GCC,我们就可以用 Spack 把整个用户软件栈给它编译出来,构建我们想要的开发环境。但是服务器上的 GCC 版本可能太低,一些新的软件包可能根本编译不通过,所以我们需要先“左右脚互踩升天”,用旧的 GCC 自举出一个新的 GCC。

我已经试过了使用 LLVM 的 Clang 代替 GCC 给 Spack,但是没成功,主要原因是:

  • Linux 上 Clang 默认使用 libstdc++,而不是他自己的 libc++,但是它找到的 libstdc++ 是系统里的老掉牙版本,而不是 Spack 后来自己编译的版本。
  • LLVM 套件里没有 fortran 编译器可用,Flang 不够成熟,很多软件包编译不过。

所以一时半会 GCC 在 Linux 上的“万物之母”地位还是改变不了。

本次我的出发点是:

1
2
gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44)
Linux centos204 4.14.0-115.el7a.0.1.aarch64 #1 SMP Sun Nov 25 20:54:21 UTC 2018 aarch64 aarch64 aarch64 GNU/Linux

这是非常老的 GCC 版本(Bing Copilot 说 4.8 是第一个支持 AArch64 的 GCC 版本),使用下面的命令添加到 Spack:

1
python3 ~/gyh/+/spack/0.21.2/bin/spack compiler find

如果它没找到,或者服务器上连 GCC 都没有,是我们自己传上去的二进制,那么手动创建 ~/+/spack/0.21.2/etc/spack/compilers.yaml 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
compilers:
  - compiler:
      spec: gcc@=4.8.5
      paths:
        cc: /usr/bin/gcc
        cxx: /usr/bin/g++
        f77: /usr/bin/gfortran
        fc: /usr/bin/gfortran
      flags: {}
      operating_system: centos7
      target: aarch64
      modules: []
      environment: {}
      extra_rpaths: []

配置 compilers.yaml: https://spack.readthedocs.io/en/latest/getting_started.html#compiler-configuration

可以在 flags 里加上 -O3 来确保我们使用的是优化编译的软件包:

1
2
3
4
5
6
flags:
  cflags: -O3
  cxxflags: -O3
  fflags: -O3
  cppflags: -O3
  ldflags: -O3

我计划升级到 11.4.0:

1
2
python3 ~/gyh/+/spack/0.21.2/bin/spack install gcc@11.4.0 -bootstrap
# bootstrap 默认开启,可能会因为自举检查失败而失败,所以加上 -bootstrap 选项。

然后把这个新的 GCC 加入到 Spack 的编译器列表中:

1
2
python3 ~/gyh/+/spack/0.21.2/bin/spack load gcc@11.4.0
python3 ~/gyh/+/spack/0.21.2/bin/spack compiler find

GCC 自举

前面安装的 GCC 11.4.0 是用系统自带的 GCC 4.8.5 编译的,可能会有一些问题(谁晓得)。保险起见,我们再用 GCC 11.4.0 编译一个 GCC 11.4.0,把版本给坐实了:

1
python3 ~/gyh/+/spack/0.21.2/bin/spack install gcc@11.4.0 %gcc@11.4.0 -bootstrap

如果安装失败,可能是版本跨度太大,这是要用中间版本慢慢过渡,例如先跳到 9.5.0,再跳到 11.4.0。

然后,把 compilers.yaml 里的 gcc@11.4.0 改为这个版本的路径:

1
2
3
4
python3 ~/gyh/+/spack/0.21.2/bin/spack unload -a
python3 ~/gyh/+/spack/0.21.2/bin/spack load gcc@11.4.0 %gcc@11.4.0
which gcc
# 把打印出来的路径复制到 compilers.yaml 里

设置默认的编译器偏好

~/gyh/+/spack/0.21.2/etc/spack/packages.yaml 里设置 packages:all:compiler,这是一个列表,在最前面加上 gcc@11.4.0,这样之后再 spack install 时就会默认使用这个编译器。

https://spack.readthedocs.io/en/latest/packages_yaml.html#package-preferences

升级 Python

我们前面用了一个老版本的 Python 3.6 来启动 Spack,现在我们可以用 Spack 来安装一个新的 Python:

1
2
3
python3 ~/gyh/+/spack/0.21.2/bin/spack install python
python3 ~/gyh/+/spack/0.21.2/bin/spack load python
which python

记下这个路径,之后我们把它的父目录加到我们自己的 SHELL 启动脚本里。

需要特别指出,Spack 自己安装的这个 Python 一般是不能复制出来拉到其它 Linux 上运行的。其原因是 Spack 为了解决动态链接库加载路径的问题,给它编译出的所有程序都使用了 RPATH 机制,可以用 patchelf --print-rpath 来查看,运行之后就能看到它设置的 RPATH 全都是绝对路径。因此,除非把这个 Spack 安装目录原封不动地复制到另一个 Linux 系统的完全相同路径,否则这个 Python 大概率无法正常工作。

使用 ZSH

我是喜欢用 ZSH + Oh My Zsh 的,幸运的是 Spack 也有 ZSH 包,我们可以用 Spack 来安装 ZSH:

1
2
3
python3 ~/gyh/+/spack/0.21.2/bin/spack install zsh
python3 ~/gyh/+/spack/0.21.2/bin/spack load zsh
which zsh

注意,Oh My Zsh 应该安装到 ~/gyh/.oh-my-zsh ,而不是 ~/.oh-my-zsh,这样就不会影响到别人。

让 SSH 直接进入 ZSH

由于是共用的账户,我们没办法使用 chsh 来切换 SSH 登录的 SHELL,但是我们可以使用 RemoteCommand 来在 SSH 登录时直接进入 ZSH。

修改 SSH 配置文件 ~/.ssh/config,添加:

1
2
3
4
5
Host PublicServer
   User PublicUser
   RemoteForward 10000 proxyserver:10000
   RequestTTY yes
   RemoteCommand cd ~/gyh && ZDOTDIR=$PWD ~/gyh/+/spack/0.21.2/opt/spack/linux-centos7-aarch64/gcc-11.4.0/zsh-5.8-.../bin/zsh

更新:

  1. 不再建议使用这种方式直接进入 ZSH,因为这种方式会禁用掉 ssh 的直接执行命令的使用方式,例如 ssh PublicServer ls -al / 会无法使用。
  2. 除了 ZDOTDIR= 变量外,还可以通过设置 HOME=$HOME/gyh /.../zsh 的方式来进入 ZSH,这种将 SHELL 启动在自己目录下的方式适用性会更好。

通过设置 ZDOTDIR,我们可以保证 ZSH 会读取我们自己的 ~/gyh/.zshrc,而不是共用账户的 ~/.zshrc,就可以完全消除别人对我们的影响了。

现在,我们可以把对环境变量的配置写在 ~/gyh/.zshenv 里,这包括:

  1. 设置 SPACK_DISABLE_LOCAL_CONFIGSPACK_USER_CACHE_PATH

  2. 设置 http_proxyhttps_proxy,这样一登录进服务器就有网了。

    😂 这给人的感觉就像自己给合租房接水电……

  3. 将前文记下的 Spack 安装的 Python 解释器路径加到 PATH 里。

使用 Spack Environments

对于一些常用的开发工具(htop、tmux 等),使用 Spack 环境,我们就不用每次苦哈哈地一个个敲 spack load 了。

一些基本操作:

  1. 创建一个 default 环境:

    1
    
    spack env create default
    
  2. 激活这个环境:

    1
    
    spack env activate default
    
  3. 添加软件包:

    1
    2
    
    spack add htop
    spack add tmux
    
  4. 安装所添加的软件

    1
    
    spack install
    

    一般的 spack install htop 不起作用,必须得先 addinstall

  5. 退出这个环境:

    1
    
    spack env deactivate
    

然后我们在 ~/gyh/.zshrc 里直接激活这个环境,省的我们每次都得手动激活:

1
2
. ~/gyh/+/spack/0.21.2/share/spack/setup-env.sh
spack env activate default

爽了,现在一个近乎干净的,不受别人影响的开发环境就搭建好了,虽然不能完全避免别人的影响,但这至少能让自己在合租房里过得舒服一点。

然后,你只要心里不停祈祷不要有傻 X 把你的 ~/gyh 目录给删了就行了。

后续

合租房中建家(二):普通账户复用与 VSCode 使能

本文由作者按照 CC BY 4.0 进行授权