为Google Cloud配置深度学习环境(CUDA、cuDNN、Tensorflow2、VScode远程ssh等)

前言

本文基于作者使用环境:macOS Catalina。仅使用系统自带Terminal进行配置。
配置google cloud上的深度学习环境真的是一把鼻涕一把泪。不过总算是弄好了。记录一下,也算是帮助后来的人。

总的来说,最近这段时间(2020.07)配置环境是挺麻烦的,网上教程有的也已经不再管用。原因有二:

  • Tensorflow于去年更新至2.0版本,GPU版本的tensorflow安装命令已与之前不同,支持的驱动和CUDA版本也在变化。教程还没有跟上。
  • NVIDIA在本月初发布了CUDA 11,但目前Tf还未支持,版本选择需要注意。

那么就开始吧!

目录

  1. 前言
  2. Google Cloud Platform(GCP)的注册与虚拟机实例的设置
  3. 连接至虚拟机实例并配置GPU计算环境
  4. 配置服务器上的jupyter notebook
  5. 配置VScode上的remote-SSH以远程管理和运行代码
  6. 参考

Google Cloud Platform(GCP)的注册与虚拟机实例的设置

  • 目前,GCP注册与以前相比没有太大变化。可以参考这篇文章的注册部分。选择地区时没有中国,可以选择香港。具体的详细地址可随便填写。但信用卡账单寄送地址需要正确填写。

  • 填写完毕后,会尝试从信用卡扣款8港币来测试卡有效性。之后会退回。

  • 点击验证邮件里的链接,再填写几个问卷。就可以使用了。

  • 进入GCP的主页后,添加虚拟机实例:
    左边Menu -> Compute Engine -> 虚拟机实例 -> 创建实例


    注意:这里区域选择asia-east1台湾,地区可以选择a

机器配置选择通用N1即可,将来可以更改。目前就先使用默认配置,GPU由于目前没有配额,添加了也没有用,暂时先不添加。


启动磁盘点击更改后,选择Ubuntu,版本16.04 LTS,启动磁盘大小40GB


防火墙两个都勾选上:

点击创建。实例就创建好了。创建之后可能是开机状态。把机器停止,先去申请GPU配额。

  • 配额申请
    左边Menu -> IAM和管理 -> 配额

一般来说,在这里上面会弹出一个小黄条,提示你升级账户后才可以有完整体验。直接点击升级就可以。不会扣款。点击后会有个升级成功提示。升级后才可以申请GPU配额。

随后,在配额界面过滤表中点选:限制名称 -> GPUs (all regions)


点击上方修改配额并勾选筛选出的名为GPUs (all regions)的服务。

在右方填写信息,下一步


这里我已经提额过了,所以当前限制写的是1。在新限额里填1,请求说明里就说自己要做科研项目即可。点击完成、下方点击提交请求后,会在邮箱内收到一封Google的邮件,表示已经收到提额申请。

一般来说,会告诉你配额申请在2天内会通过。但是我两分钟之后就收到了邮件,不知道是不是运气好,哈哈哈。不过暂时没有收到也没关系,我们先配置其他环境。


  • 静态外部IP配置
    左边Menu -> VPC网络 -> 外部IP地址

    这里已经能看到我们刚刚创建好的虚拟机实例了。将类型改为静态,随便输入名称即可。
  • SSH连接配置
    接下来是SSH连接的配置。主要有3步:生成密钥-上传密钥-SSH登陆。
    首先打开Terminal:
#进入.ssh文件夹
cd ~/.ssh
#生成密钥(google为密钥名)
ssh-keygen -f google

会提示你输入一个保护密码:

Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 

输入密码,确认输入密码后就生成好了,会有一张randomart image显示出来。接下来查看你的公钥:

若你不了解vim命令。那么注意这里使用vim命令之后别随便碰键盘,如果碰了。Control-C可以退出编辑模式,具体操作请另搜索vim指令。我们只会用到:wq保存并退出和i编辑两个指令。

vim google.pub

将文件内容全部Command-C复制。回到谷歌云网页左边Menu -> Compute Engine -> 元数据 -> SSH密钥,将自己的公钥直接复制进去。保存即可。


为了登陆的方便,我们需要在本地.ssh文件夹下创建一个config文件:

vim config

进入后,按i可以进入编辑模式,进入模式后能看到terminal下方出现了INSERT字样。将以下内容粘贴进去:

Host gcp
    HostName {这里填写之前固定的虚拟机实例外部IP}
    User {这里填写刚刚元数据里的用户名}
    IdentityFile ~/.ssh/google

随后按Control-C退出编辑模式,输入:wq保存并退出。

至此,虚拟机实例和连接就已经配置好了。

重要:为了安全,GCP默认每120s没有收到从SSH发来的消息就断开SSH链接,在Windows上使用的软件可以直接设定发送心跳包的时间间隔,这里我们需要在本机上设定:

sudo vim /etc/ssh/ssh_config

在打开的文件中添加一行ServerAliveInterval 20,保存退出。

连接至虚拟机实例并配置GPU计算环境

弄完刚才这些,提额申请可能已经通过了。那么我们就来试一试吧。首先添加GPU:

进入虚拟机实例页面,点击我们的虚拟机实例进入详细设定。在实例已经停止的情况下,点击修改,展开CPU平台和GPU一项并添加一个GPU,默认的是Tesla K80。保存。

好了!可以启动我们的实例了。可能需要个几秒。一般来说会成功启动,不过也有可能遇到报错。最常见的是当前服务器GPU不够了。那就睡一觉或者等会儿再来配置吧。

  • 通过SSH连接至实例
    由于我们之前已经在.ssh文件夹内添加了config文件,在连接的时候不需要在命令中声明私钥地址、登陆用户、实例IP了。直接:
ssh gcp

第一次登陆会核对一些信息,可以直接yes,随后就输入我们之前的密码。
登陆成功后,命令行会变为绿色。

接下来就是环境配置了。

  • 安装显卡驱动、CUDA以及cuDNN
    这里千万要注意,tensorflow的版本、CUDA、cuDNN的版本是紧密相关的,当你看到这篇教程时,记得去Tensorflow官网看看GPU配置里面的软件要求一栏。目前的版本是这样的:
  • 方法一(官网给出,本人未测试):
    在网页的下方有Tensorflow给出的方便安装的脚本
# Add NVIDIA package repositories
# Add HTTPS support for apt-key
sudo apt-get install gnupg-curl
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/cuda-repo-ubuntu1604_10.1.243-1_amd64.deb
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu1604/x86_64/7fa2af80.pub
sudo dpkg -i cuda-repo-ubuntu1604_10.1.243-1_amd64.deb
sudo apt-get update
wget http://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu1604/x86_64/nvidia-machine-learning-repo-ubuntu1604_1.0.0-1_amd64.deb
sudo apt install ./nvidia-machine-learning-repo-ubuntu1604_1.0.0-1_amd64.deb
sudo apt-get update

# Install NVIDIA driver
# Issue with driver install requires creating /usr/lib/nvidia
sudo mkdir /usr/lib/nvidia
sudo apt-get install --no-install-recommends nvidia-418
# Reboot. Check that GPUs are visible using the command: nvidia-smi

# Install development and runtime libraries (~4GB)
sudo apt-get install --no-install-recommends \
    cuda-10-1 \
    libcudnn7=7.6.4.38-1+cuda10.1  \
    libcudnn7-dev=7.6.4.38-1+cuda10.1


# Install TensorRT. Requires that libcudnn7 is installed above.
sudo apt-get install -y --no-install-recommends \
    libnvinfer6=6.0.1-1+cuda10.1 \
    libnvinfer-dev=6.0.1-1+cuda10.1 \
    libnvinfer-plugin6=6.0.1-1+cuda10.1
  • 方法二(亲自测试):
    直接安装。先用ssh gcp并输入密码连接实例。
    我们直接安装Tensorflow2目前支持的CUDA10.1版本。因为现在CUDA安装时会自动帮你安装显卡驱动:
wget https://developer.nvidia.com/compute/cuda/10.1/Prod/local_installers/cuda-repo-ubuntu1604-10-1-local-10.1.105-418.39_1.0-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu1604-10-1-local-10.1.105-418.39_1.0-1_amd64.deb
sudo apt-key add /var/cuda-repo-<version>/7fa2af80.pub
sudo apt-get update
sudo apt-get install cuda

输入最后一条之后,需要输入y确认两次。然后就等命令行跑完。会先安装CUDA10.1,随后会安装418版本的显卡驱动。安装大约需要10分钟。安装的时候可以去cuDNN网页上注册一下,方便后面下载。点击Download就会自动跳转注册。

然后sudo reboot重启一下机器。重启后需要连接断开。再次使用ssh连接。输入nvidia-smi可以看到显卡的情况:

Thu Jul 23 08:05:10 2020       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 418.39       Driver Version: 418.39       CUDA Version: 10.1     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla K80           Off  | 00000000:00:04.0 Off |                    0 |
| N/A   58C    P8    30W / 149W |     16MiB / 11441MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0      1743      G   /usr/lib/xorg/Xorg                            15MiB |
+-----------------------------------------------------------------------------+

显示正常的话,就是安装成功啦!

接下来安装cuDNN,需要到上面说的网址注册登陆。进入后Agree同意协议。


点击Download cuDNN v7.6.5 (November 5th, 2019), for CUDA 10.1,注意,这里不能直接复制cuDNN Library for Linux的下载链接到我们的实例,否则会403 Forbidden。需要做的是直接点击下载,在chrome下载页面右键复制下载链接地址。

像我这里复制到的链接,后面一串就是我的Token。直接复制我的链接是无效的

https://developer.download.nvidia.com/compute/machine-learning/cudnn/secure/7.6.5.32/Production/10.1_20191031/cudnn-10.1-linux-x64-v7.6.5.32.tgz?iceCj5uvMJUMcXOqWNvKyAO44He_JQgo6I71TxNKcGdbiqcPoJ4wAXWNb3WpQYhBUZN8NMAk_5El5kyADfUx4r8af5WfLPk4WumDm27oJZJhWzBMgsBtJPH9sSPmlMxY0ID_6eQod-8WgkBT0HvM_BwDaGWQ_w05f1rRBjTGqm-X-VeCBppxtKUFcxYY2AW91s492p06zkVhMwIWOl5LHQ4p7eycCRGv3Q

将链接复制好后,打开Terminal下载cuDNN会得到一个巨长的文件名。用mv重命名一下方便我们使用。然后解压。

wget {你的链接}
mv {下载文件的名字} cudnn.tgz
tar -xzvf cudnn.tgz

之后复制文件:

sudo cp cuda/include/cudnn.h /usr/local/cuda-10.1/include/
sudo cp cuda/lib64/* /usr/local/cuda-10.1/lib64/

安装好啦!

  • Anaconda的安装
    最好到官网查询最新版本的链接。这里提供一个2020.02版本的:
wget https://repo.anaconda.com/archive/Anaconda3-2020.02-Linux-x86_64.sh
bash Anaconda3-2020.02-Linux-x86_64.sh

运行后进入Anaconda的安装。ENTER后进入用户协议阅读,按空格快速跳过。后面需要输入yes,再ENTER确认安装地址。就开始安装了。最后会提示是否conda init,一定要yes。安装完毕后测试一下conda命令。如果不行的话运行source ~/.bashrc试试。如果仍然不行,可能是anaconda位置没有配置好。执行vim ~/.bashrc,并在最后一行加上anaconda的位置 :export PATH=$PATH:/home/{你的用户名}/anaconda3/bin。之后source ~/.bashrc。再次测试conda命令。若提示需要init,按照步骤执行。完成后需要reboot。

安装成功后,命令行前会出现conda环境的指示,如果不activate特定环境的话,就是(base)

  • 安装tensorflow的gpu版本
conda install tensorflow-gpu

同样需要输入y确认安装。至此,tensorflow的gpu环境就配置完成了。

  • Tensorflow启用GPU的测试
    我们编写一个python文件,调用tensorflow自己的测试模块来看GPU是否启用成功。
vim test.py

以下是文件内容:

import tensorflow as tf
print(tf.test.is_gpu_available())

接下来执行python test.py,看看print出来的结果:

(base) f@instance-2:~$ python test.py
WARNING:tensorflow:From test.py:2: is_gpu_available (from tensorflow.python.framework.test_util) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.config.list_physical_devices('GPU')` instead.
2020-07-23 12:50:56.552239: I tensorflow/core/platform/cpu_feature_guard.cc:143] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2 FMA
2020-07-23 12:50:56.560837: I tensorflow/core/platform/profile_utils/cpu_utils.cc:102] CPU Frequency: 2200000000 Hz
2020-07-23 12:50:56.561175: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x560faca2aab0 initialized for platform Host (this does not guarantee that XLA will be used). Devices:
2020-07-23 12:50:56.561213: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Host, Default Version
2020-07-23 12:50:56.562632: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcuda.so.1
2020-07-23 12:50:56.582969: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-07-23 12:50:56.583828: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1561] Found device 0 with properties: 
pciBusID: 0000:00:04.0 name: Tesla K80 computeCapability: 3.7
coreClock: 0.8235GHz coreCount: 13 deviceMemorySize: 11.17GiB deviceMemoryBandwidth: 223.96GiB/s
2020-07-23 12:50:56.584151: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.1
2020-07-23 12:50:56.586376: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcublas.so.10
2020-07-23 12:50:56.588334: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcufft.so.10
2020-07-23 12:50:56.588812: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcurand.so.10
2020-07-23 12:50:56.591138: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusolver.so.10
2020-07-23 12:50:56.592375: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusparse.so.10
2020-07-23 12:50:56.598009: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudnn.so.7
2020-07-23 12:50:56.598203: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-07-23 12:50:56.599128: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-07-23 12:50:56.599942: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1703] Adding visible gpu devices: 0
2020-07-23 12:50:56.600047: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.1
2020-07-23 12:50:56.948921: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1102] Device interconnect StreamExecutor with strength 1 edge matrix:
2020-07-23 12:50:56.948999: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1108]      0 
2020-07-23 12:50:56.949022: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1121] 0:   N 
2020-07-23 12:50:56.949809: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-07-23 12:50:56.950670: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-07-23 12:50:56.951456: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-07-23 12:50:56.952191: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1247] Created TensorFlow device (/device:GPU:0 with 10676 MB memory) -> physical GPU (device: 0, name: Tesla K80, pci bus id: 0000:00:04.0, compute capability: 3.7)
2020-07-23 12:50:56.954963: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x560fad3ee950 initialized for platform CUDA (this does not guarantee that XLA will be used). Devices:
2020-07-23 12:50:56.955001: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Tesla K80, Compute Capability 3.7
True

看到最后的True了吗!!!总算是成功了。

配置服务器上的jupyter notebook

  • 配置防火墙
    左边Menu -> VPC网络 -> 防火墙 -> 创建防火墙规则
    名称:jupyter
    来源IP地址范围:0.0.0.0/0
    协议和端口 -> 指定的协议和端口 -> tcp:5000

  • 创建jupyter-notebook的config文件

jupyter notebook --generate-config

此时会告诉你生成的config文件位置,编辑一下config:

vim /home/{你的用户名}/.jupyter/jupyter_notebook_config.py

插入几行:

c = get_config()
c.NotebookApp.ip = '*' 
c.NotebookApp.open_browser = False 
c.NotebookApp.port = 5000 

:wq保存之后直接运行jupyter notebook即可打开jupyter notebook app
运行后会弹出几个带有token的链接:

[I 14:11:24.544 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 14:11:24.549 NotebookApp] 
    
    To access the notebook, open this file in a browser:
        file:///home/f/.local/share/jupyter/runtime/nbserver-4059-open.html
    Or copy and paste one of these URLs:
        http://localhost:5000/?token=c8e314854ec5c254e4b2b09e5284c403bf9186eed6f69a28
     or http://127.0.0.1:5000/?token=c8e314854ec5c254e4b2b09e5284c403bf9186eed6f69a28

将链接复制到浏览器,并把127.0.0.1改为服务器实例的外部ip就能在任意终端访问了。


注:在跑深度学习时有时我们希望创建的notebook在SSH断开后仍然运行,那么启动jupyter notebook时,使用nohup jupyter notebook --allow-root &

配置VScode上的remote-SSH以远程管理和运行代码

VScode上有remote-SSH插件可以安装。安装后按command+shift+p唤出设置,输入remote-SSH选择Connet to Host,选Configure SSH Hosts。这里让我们选择config的位置,我们就选择之前自己创建用于快捷登陆gcp的config文件,位于.ssh文件夹中。不用修改,保存并关闭文件即可。

再次按command+shift+p选择Connet to Host后,就能看到我们之前写的gcp。回车后弹出新窗口。一样需要输入我们设置的密码。初次启动会初始化一段时间。

然后在扩展页面给虚拟机实例安装插件。包括我们常用的python、anaconda extension和code runner

打开文件夹,就可以正常操作运行python文件啦。记得在下面选择合适的interpreter。

遗憾的是,本来想使用远程jupyter notebook的kernel,在VScode上编辑ipynb并在远程运行。但无奈本人能力不足,暂时无法实现。目前仅支持浏览器访问的jupyter notebook界面。

这里给出一篇教程可供参考:VScode连接远程服务器上的jupyter notebook

参考

手把手教你用Google云平台搭建自己的深度学习工作站
在谷歌云服务器上搭建深度学习平台
jupyter notebook相关配置
云端部署jupyter notebook
Tensorflow-gpu 测试代码
VScode上的remote-SSH
SSH自动断线设置

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容