0%

Linux云服务器远程任务随笔

前言:由于本地设备的性能限制,通常一些大型科学计算都会用到云服务器来解决算力问题。不过在使用过程中遇到了不少问题比如内存不够、断连、jupyter notebook 启动失败等,都将记录在本文。


1. 云服务器的选择

国内比较知名的几个云服务器大概有:腾讯云、阿里云、百度云、华为云,还有比较知名的亚马逊和谷歌,但总觉得有点水土不服。其他的一些云服务器我也没有每个都探索一遍,如果发现有更实惠的选择再更新进来吧。

先说说这四个国内云服务器给我的第一感吧。总体来说,BAT 三家云服务器还是让我感觉到了作为国内互联网三巨头的一股气势,单从我个人感受而言,似乎给我一种“你爱用就用”的态度,当然,文档详尽、自定义性高等各方面条件都是相当优秀的,不过在用户引导这个方面仍然感觉略有不足,其中腾讯云要稍微好一些,阿里次之,当然我不是有意黑百度。最开始我已经准备入坑阿里云,资料、认证都已经完成,但是在选配置的时候遇到一个很尴尬的问题:阿里云的按需计费模式,需要预充 50 元以上,但是对于首次使用的我来说,我其实只是想花个几块钱,就用一个小时尝试一下预期配置的效果,尽管四大云都提供了免费试用,但免费提供的服务器配置并达不到我的要求,所以基于这一点,我 Pass 了阿里云(当然我认为就服务器整体实力而言,阿里云应是最强的)。如果要长期使用并且对维护要求比较高的,我个人还是更推荐阿里云一些,可能质量上而言这几个服务器差别都不会太大,但是阿里云的应用量是远超其他几个云的,从服务案例、维护参考等角度出发,使用阿里云,至少出了问题你能在网上搜到最多的答案。

第二次我搜索云服务器的时候,度娘给我推荐的第一条搜索结果居然是 华为云,抱着试试看的心态点进去看了看,惊喜的发现:华为云按需计费是不需要预充金额(其他两家没有尝试)的!这直接导致了我后续选择华为云作为服务器的结果。

说完第一感,再来浅略谈谈这几个云服务器的选择。首先,腾讯云和百度云是没有 ECS 弹性云服务器 这么个独立选项的,最后在腾讯找到了 云服务器 CVM,在百度找到了 云服务器 BCC,看起来跟配置 ECS 差不多,也就权当是不同叫法了。此外,还有一点需要提一下,只有阿里云里面选择配置的时候,是有详细配置参数的,包括 CPU 主频等:

阿里云详细配置参数

而且整体配置选择界面一目了然,各种搭配方案覆盖面也很广,用起来给我个人感觉是最好的。其次是华为云,虽然没有标出主频参数,但至少还能看出 CPU 的规格信息,还能直接搜出来具体的参数:

华为云详细配置参数

其他两家就差了一些,只告诉我这是几核的,配多大内存,虽然也对 CPU 根据用途做了分类,但终归不直观:

其他云详细配置参数

此外,在价格上各家也有差异,由于我自己的需求是约 8 核以上 CPU + 约 32G 以上内存,再加上只是临时跑一下数据,所以选择按需计费,并且都转换成以下标准配置再进行价格对比:

  • 地区:广州(华南地区)
  • CPU:8核 2xlarge.4(3.1GHz 高性能计算型)
  • 内存:32G
  • 硬盘:50G 普通硬盘
  • 带宽:5Mbps
  • 系统:Ubuntu
  • 公网:无
  • 时长:1小时

各个云的价格计算器链接:腾讯云百度云阿里云华为云

根据这个选择,得到费用表如下(单位:元 / 小时):

腾讯云 百度云 阿里云 华为云
无公网 6.58 3.6721 4.7874 2.73
有公网 6.58 3.6721 4.7874 3.05

当看到腾讯云价格的时候,我有一点吃惊,但是仔细看了一下,我确定我选择的是 SkyLake 架构 3.2GHz 规格的 CPU,应该是 Xeon E5-2667v4,这款在阿里云的价格平均下来要 7.11 元 / 小时,所以这个价格也还差不多,不过相比标准配置,相同架构,提高 0.1 主频就要贵这么多,还是略不值得。另外,阿里云原本是支持按小时计费,但是这次没找到,因此按照月费用除以 31 天计算。

从价格表也基本可以看出来,华为云的价格优势还是很大的,综合不需要预充值、引导友好等特点,最终入了华为云的坑。


2. 连接远程服务器

云服务器给的 Linux 系统通常是命令行形式的,也就是所有的操作都使用 Linux 命令完成,当然有些云也提供支持视图模式的选项,但通常伴随着较高的价格。

连接远程服务器有两种方式:(1)用远程连接工具、(2)用系统自带工具

  1. 远程连接工具有很多
  2. 系统自带工具不依赖额外的软件,因此更适合仅需要建立连接而不需要集群管理等高级功能的情况
    • Mac 下的“终端”
    • Win 下的 CMD 和 PowerShell

由于我只是为了利用云 ECS 的算力和持续运行,因此不需要什么高级功能,选择系统自带工具就足够了。命令格式为:ssh [用户名]@[服务器地址],例如:

1
ssh root@127.0.0.1

对于新建的云 ECS,通常都有一个内网 ip 地址,但仅有这个地址的时候是无法远程访问的,还需要购买一个 弹性公网ip,华为云在默认情况下会选择购买,阿里云默认情况下不购买,需要自己区分。以下是华为云在购买 ECS 时弹性公网 IP (红圈所标)的设置部分:

购买弹性公网 IP

关于 ECS 的内网 IP 和公网 IP,可以这么粗略理解:公网 IP 是通过远程连接等利用公网访问云、或从云访问公网时必须用到的,内网 IP 通常是集群服务器间,某些云没有联网需求,但需要和其他同属一个网段的云(例如都是华为云,或都是阿里云等)交互数据,那么这些云就可以不购买公网 IP 而仅通过内网 IP 通信。

有了公网 IP 后,就能同过命令行进行远程连接了。如果设置了连接密码,则会在下一条命令中提示输入,注意输入该密码时屏幕上不会显示输入过程,输完回车即可,密码也即在购买服务器时设置登录账户时输入的密码:

远程连接密码

在 Mac 下,如果不想每次都手输这个命令,可以右键“终端”,选择“新建远程连接”:

新建远程连接

然后在弹出窗口内左侧选择服务为 SSH,在下面输入连接用户,并输入服务器的地址:

添加 SSH 服务

通常连接用户是 root,如果想减少权限以防意外操作,要先以 root 用户登录一次,再在 Linux 系统内新建一个普通用户,下一次即可选择新用户登录。


3. 配置服务器环境

到这一步,云服务器已是可用状态,通过 SSH 登录后,会新开一个 SSH 终端用于操作云上的 Linux 系统,就和自己手动操作时时一样的。不过需要注意的是,SSH 云服务器的终端只有一个,当然还有一些别的技巧可以达到类似多开终端的效果,会在后面介绍。

既然是要在云上跑训练集,当然 Python 环境、各种科学计算工具包是必不可少的,我个人更推荐直接使用 Anaconda 一次性集成开发环境。

3.1 下载安装Anaconda

首先在任何一个可以联网的设备上进入 Anaconda 安装包归档,并找到自己想要安装的版本,以 Linux 下 64 位集成 Python3 环境的最新版本为例,其安装包名称为:Anaconda3-2019.03-Linux-x86_64.sh,在 SSH 终端中输入以下指令:

1
2
3
4
5
6
7
8
# 下载安装包(默认在用户目录下,通过切换当前目录更改下载路径):
wget https://repo.continuum.io/archive/Anaconda3-2019.03-Linux-x86_64.sh

# 安装 Anaconda(默认在用户目录内,通过切换当前目录更改下载路径):
bash Anaconda3-5.0.1-Linux-x86_64.sh

# 配置环境变量:
source .bashrc

完成后,在 SSH 终端内输入 python,正确进入 Python 编辑环境则表示安装成功。某些情况下,可能需要手动配置环境变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 编辑环境变量(所有用户有效):
vim /etc/profile
# 编辑环境变量(仅当前用户有效):
# vim .bash_profile


# 在文件末尾添加一行(用户名和安装目录请按实际路径填写):
export PATH=/home/root/anaconda3/bin:$PATH

# 保存并使其生效(所有用户有效):
source /etc/profile
# 保存并使其生效(仅当前用户有效):
# source .bash_profile

3.2 配置Jupyter Notebook

使用命令 pip install jupyter 安装 Jupyter 到当前环境中,如果需要切换环境,可以先在 Anaconda 中新建环境并切换至新环境后执行命令。

安装好 Jupyter 后,像往常一样输入 jupyter notebook 启动,结果却发现连不上 Jupyter 服务,这是因为服务器上的 Jupyter 还没有配置外网访问。配置方式如下:

  1. 生成 Jupyter 配置文件:

    1
    2
    3
    4
    # 非 root 用户:
    jupyter notebook --generate-config
    # root 用户:
    jupyter notebook --generate-config --allow-root
  2. 打开ipython,创建一个登录jupyter的密码:

    1
    2
    3
    4
    5
    6
    7
    8
    # 进入 ipython 环境
    ipython

    # 导入 password 模块
    from notebook.auth import passwd

    # 修改密码
    passwd()

    然后会要求输入一次密码和一次确认密码,同样,输入过程屏幕上不会有任何显示。正确输入密码后,会输出一个哈希值(省略后半部分),请复制或记录这个值:

    1
    'sha1:5311cd8b9da9:70dd3321..............'
  3. 修改 Jupyter 配置:

    1
    2
    3
    4
    # 非 root 用户:
    vi ~/.jupyter/jupyter_notebook_config.py
    # root 用户:
    # vi /root/.jupyter/jupyter_notebook_config.py

    在打开的配置中添加以下参数:

    1
    2
    3
    4
    c.NotebookApp.ip='*'
    c.NotebookApp.password = u'sha1:5311cd8b9da9:70dd3321..............'
    c.NotebookApp.open_browser = False
    c.NotebookApp.port = 8888

    其中 password 项输入上一步复制的哈希值,port 项推荐使用默认的 8888 端口,也可以自己定义。

3.3 配置服务器端口

至此服务器上的环境均已配置就绪,但如果这时候在 SSH 终端使用命令打开 Jupyter Notebook:

1
2
3
4
# 非 root 用户:
jupyter notebook
# root 用户:
# jupyter notebook --allow-root

或是在浏览器通过:

1
2
http://公网ip:端口
# 例:http://127.0.0.1:8888

仍然会出现无法连接的情况,这是因为 ECS 的安全组还没有配置,8888 端口(或自己设置的端口号)还没有开放给公网。设置安全组的过程如下:

  1. 进入云服务器控制台
  2. 进入安全组设置
  3. 添加安全组规则
  4. 选择协议类型为 自定义 TCP,输入端口号为 8888(或自定义端口号),授权 IP 设置为 0.0.0.0(即不限访问 IP,不论哪里的网络都可以连接访问云服务器的 Jupyter)

配置完成后,即可通过 SSH 终端或浏览器输入地址的方式进入服务器的 Jupyter Notebook 了!如果仍旧提示无法访问,且云服务器所选的 Linux 系统为 Ubuntu(其他系统没试过),或许是因为防火墙的缘故,通过以下方式关闭:

1
ufw disable

接下来尽情享受云服务器带来的快感吧!

3.4 保持SSH会话

在使用云服务器的过程中,有时去做别的事了过了一段时间后再看 SSH 终端,发现先是没反应,无法输入,再过一会儿就提示连接已断开,这是因为默认情况下 SSH 会话有一个连接时间,一段时间后就会断开会话。如果需要处理一个耗时很长的事件,这可不是什么好事。为此,可以通过以下方式修改时间,以达到 SSH 会话保活的目的:

  1. 登陆远程服务器后在 SSH 终端输入:

    1
    TMOUT=0

    该方式方便、快捷,但缺点是仅对本次登陆有效,退出 SSH 后重新登录则需要再次设置。

  2. 修改ssh配置文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 打开配置文件
    vi /etc/ssh/sshd_config

    # 找到 ClientAliveInterval,该参数指定了服务器端向客户端请求消息的时间间隔。默认是0,不发送
    # 修改 ClientAliveInterval 的值,单位为秒。如设置 600,即每 10 分钟发送一次请求保持会话。
    ClientAliveInterval = 600

    # 找到 ClientAliveCountMax,该参数表示允许超时的次数。
    # 如果发现客户端没有响应,则判断一次超时,请根据实际需要进行设置。比如设置为10,表示允许超时6000 秒 = 100 分钟。
    ClientAliveCountMax = 10

3.5 本地与云的文件传输

为了使用云服务器跑数据,还需要在云服务器上准备一份数据文件。由于提高网络带宽的价格并不便宜,而且使用云跑数据本身对带宽并没有什么要求,因此我比较推荐在将数据上传至云或从云上下载时,临时提高 ECS 的带宽,并在完成之后重新恢复较低带宽以节省成本。数据文件比较大时,临时提高带宽更划算一些。文件总量比较大时,临时购买流量更划算一些。本地与服务器的文件拷贝指令为:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 本地文件复制到服务器:
scp [本地文件路径] [服务器登录用户名]@[服务器地址]:[服务器文件存储路径]
# 例如将本地用户目录下的 test.txt 复制到服务器 root 用户目录下并重命名为 db.txt:
scp /Users/user/test.txt root@127.0.0.1:/root/db.txt
# 若需要复制的是文件夹,则将命令 scp 改为 scp -r:
scp -r /Users/user/test root@127.0.0.1:/root/db

# 服务器复制到本地:
scp [服务器登录用户名]@[服务器地址]:[服务器文件存储路径] [本地文件路径]
# 例如将服务器 root 用户目录下的 db.txt复制到本地用户目录下并重命名为 test.txt :
scp root@127.0.0.1:/root/db.txt /Users/user/test.txt
# 若需要复制的是文件夹,则将命令 scp 改为 scp -r:
scp -r root@127.0.0.1:/root/db /Users/user/test

3.6 服务器后台运行Jupyter

在选择云服务器训练数据时,除了考虑到算力的问题,当然也有持续运行的问题。尽管通过设置 SSH 的会花时间已经可以使得服务器的 Jupyter 一直在激活状态,但如果是自己的工作电脑,甚至办公本,平时还有大量的工作需要使用电脑,,一直在本地挂着一个 SSH 会话不能关机不能断开,总还是不妥,更何况如果遇到突然断网、突然死机等问题时,会话一关就中断运行,想想就让人抓狂。为此,给服务器的当前任务配置一个后台运行就显得尤为必要了。

通常,在 Linux 中,可以用 nohup [command] 来保持一项任务不被挂起,使用 nohup [command] & 来将任务转至后台并保持不被挂起。指令执行后,Linux 会立即将任务转至后台运行,且返回输出一个进程号:PID,当我们需要手动停止该进程时,可以通过 kill -TRM [PID] 来终止该进程。同时,该进程的所有输出均会默认存储至当前目录下的 nohup.out 文件中,使用命令 tail -f nohup.out 来实时查看动态输出,或者也可以使用:nohup [command] > out.log & 命令将输出重定向至自定义的文件(本例为out.log)并查看。

这对于 Jupyter 任务来说也是一样的,我们可以通过将 Jupyter 任务转至后台持续运行来释放本地电脑的工作压力:

1
2
3
4
# 非 root 用户:
nohup jupyter notebook &
# root 用户:
# nohup jupyter notebook --allow-root &

这样,当我们在 Jupyter 开始一项耗时任务后,即可直接断开 SSH 会话。注意:在本地需要离线时,不能终止 Jupyter 的进程,而应该直接断开 SSH 连接。

另外还有一点,由于 Jupyter 本身的局限性,当一个正在运行任务的 Jupyter 页面被关闭(而没有关闭服务)后,再次打开,只能保留前一次的结果,而不能恢复任务运行过程,因此,使用 nohup 命令转至后台的 Jupyter 任务应当是具有完整结果的,而不能再带有需要交互的部分,例如:

  1. 将某个数据集预先确定好的特征进行哈希编码,并将编码完的数据重新输出存储。
  2. 将某个数据集预先确定好的特征进行哈希编码,并询问我是否存储,根据选择完成相应任务。

假设在任务开始后,本地立即断开 SSH 连接,并等待任务完成后再重新连接。那么:
任务 1 是合理的,因为任务过程不需要交互,重新连接后,可以在服务器的本地找到输出的已完成哈希编码的数据集。
任务 2 是不合理的,因为任务执行完编码后,不能完成交互因此任务将直接运行结束,重新连接后相当于白费力气。

另外,除了基本的后台防挂起 nohup 外,还有一些工具也可以用来保持任务现场并恢复,例如:screen 和 tmux,可参考附录【7】、【8】,但终究因为 Jupyter 本身的原因,还是不太适合这种长时间可恢复的后台运行模式,我的建议是通过 Python 脚本进行长时间的后台操作,并尽量减少交互部分,及时将后续可能要用的数据保存成文件,而在 Jupyter 上仅作为一些可视化或测试、调整等的工具使用。

3.7 调整服务器的虚拟内存

终于可以随心所欲折腾服务器的算力了,但在运行大量数据集的时候,很有可能报这个错:Memory Error,这个错在 Python 里面也不算罕见了,一般表示瞬间内存占用超过了可用内存总和导致代码崩了,但同样的代码,在本地运行成功,在配置更高的服务器却反而失败?后来通过 free -m 命令查看内存时发现原因其实很简单,就是因为 ECS 给我们的系统太纯净、太初始化了,以至于连交换内存都没有,所以即便是选购了 32G 的服务器,当进行一些编码、训练等大数据量任务时,瞬时内存占用会一下飙升,导致内存不足。好在现在的 ECS 大多都使用了固态硬盘,即便默认非固态硬盘的,升级成本也比较低,因此可以拿出一部分硬盘作为交换内存使用(参考附录【9】)。

  1. 创建swap文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 在 /usr 目录下创建交换内存文件
    cd /usr
    mkdir swap
    cd swap

    # 查看目前总用量
    ll

    # 创建一个循环,每次复制 1024 字节,共复制 1024000 次(10G)并输出为 swapfile1 文件
    dd if=/dev/zero of=/usr/swap/swapfile1 bs=1024 count=1024000

    最后的 dd 命令中,if 表示 infile,of 表示 outfile,bs=1024 表示写入的每个块的大小为 1024B,即 1KB。

  2. 查看刚才创建的 swap 文件大小即路径:

    1
    du -sh /usr/swap/swapfile1
  3. 将目标文件设置为 swap 分区文件:

    1
    mkswap /usr/swap/swapfile1
  4. 激活 swap,立即启用交换分区文件:

    1
    swapon /usr/swap/swapfile1

    至此交换内存已经设置好了,并已激活可用,但仅限本次开机可用,想要每次开机都自动加载这个交换内存,则需要修改 /etc/fstab 文件

  5. 编辑文件 /etc/fstab 中的 swap 行:

    1
    vi /etc/fstab

    /etc/fstab 编辑状态下(UUID 省略后半部分),添加下面这段的最后一行:

    1
    2
    3
    4
    5
    /dev/mapper/vg_localhost-lv_root   /         ext4    defaults        1 1
    UUID=cef520a0-df77-4........ /boot ext4 defaults 1 2
    # /dev/mapper/vg_localhost-lv_swap swap swap defaults 0 0
    # 下面这行是关键:
    /usr/swap/swapfile1 swap swap defaults 0 0

保存并重启系统生效。

3.8 验证服务器

重启系统后,再次输入 free -m 命令,发现多了一个 swap 内存,大小为 10G,设置成功,服务器的基本配置已全部完成!


4. 附录[参考指导]