docker+jenkins+gitlab 部署springcloud 项目

最近玩了下docker和jenkins并且实战了微服务的项目,在这里记录下实现细节。
这也是我第一次写博文,以前学习的东西都是记录在印象笔记上,此次打算分享出来,如果写的不好或有错误的地方,请见谅、请指正。废话不多说,现在开始 。 ==本教程适合新手新手新新手,大神请绕道==
此次用到的工具有docker、jenkins、gitlab, 项目是springboot2.0+ 的springcloud 项目 使用gradle构建,项目的构建过程不在赘述,不会的可自行百度,jenkins使用通用的war包方式运行,本地git和gitlab的搭建,便不再这里写了,官网有很详细的教程地址:https://about.gitlab.com/installation/。后面我会在写一篇在docker中搭建gitlab和jenkins的文章 ,至于docker 的安装就同样不在赘述,网上教程一大堆。

1.安装jenkins

jenkins下载地址 然后下载稳定版本的war包
image.png
下载下来之后,放到自己喜欢的目录,在jenkins.war 包所在的目录下,运行 java -jar jenkins.war --httpPort=9090 命令,由于spring项目占用了8080端口,所以jenkins不在使用默认的8080,指定--httpPort=9090 端口 效果如下
image.png
第一次运行jenkins时,控制台会打印一段比较重要的信息,新手经常容易忽略,如下图
image.png
其中1是jenkins生成的默认密码,第一次登陆是需要用的, 2是密码存放位置 如果以后还使用默认密码,则可以再这个目录下找到,这段信息只有第一次运行时才打印。 好了,现在在浏览器输入 localhost:9090试试,你会看到如下界面,此界面也会告诉你密码存放路径,并且也是第一次运行才提示。输入密码,然后点击继续
image.png
image.png
点击安装推荐的插件,然后等待安装完成,进入管理界面,输入用户名,密码和邮件地址,邮件地址必输,否则无法保存。然后一路点击完成
image.png
最后开始愉快的玩耍吧。。
image.png
开始使用时,我们需要配置你的jdk,gradle和Gitlab地址, 这里我们只配置最基础和必须的项,先实现自动化部署在完善别的。点击系统管理 -> 全局工具配置
image.png
image.png
1.点击新增jdk,这里有两种方式,一种是本机JAVA_HOME路径配置,注意,不带bin目录,一种是在线安装,在线安装有个要求是需要注册oracle账户,嫌麻烦,用本地的。2.点击安装gradle ,同样自动安装或本机GRADLE_HOME 路径,这个没什么要求,只要你开心,选哪个都行,我用本机的,上图
image.png
点击保存,然后下载gitlab插件,点击系统管理 -> 插件管理->可选插件,搜索gitlab 选择gitlab插件,点击直接安装。等待安装完成
image.png
回到系统管理->系统设置 找到gitlab 配置项,这里有三个选项,1.连接名称,可随便输,2.gitlab地址,如:http://git.xxxxxx.com,3.秘钥
image.png
点击ADD添加TOKEN令牌, 选择gitlab api token,token的获取方式我在下面会上图
image.png
获取gitlab private key 登录gitlab, 点击右上角头像->settings -> account 复制private token ,此处token就是上面需要的api token
image.png
输入api token,点击添加,在令牌那一项,选择你刚才输入的项,点击Test Connection ,显示 Success表示配置成功,最后点击保存。如图:
image.png
到这里,基本配置就完成了,可以构建项目了。点击新建任务 -> 随便输入一个任务名称 -> 选择构建一个自由分隔的软件 -> 点击确定
image.png
这个界面上,gitlab Connection 便是你刚才配置的gitlab,描述中可以随便输写内容,不输也可以
image.png
源码管理 -> 选择git 输入你要构建gitlab 项目的路径 -> 选择刚才配置的令牌 -> 输入要拉取得git分支 -> 点击保存
到这里git的配置就完成了,我们测试下项目是否拉取成功,回到主页,你会看到你所配置的工程 -> 点击工程名称
image.png
点击立即构建,你会在左下方看到构建历史窗口
image.png
这里显示你的构建历史,#1表示第一次构建,后面会有#2. #3......
image.png
点击 #1 进入构建界面 -> 点击 控制台输出, 你会看到刚才构建的日志信息
image.png
最后显示Finished: SUCCESS 表示你构建成功
image.png
此时,你的项目被clone到 jenkins目录下,就是开头说过的你存放密码的路径的上一级。或者本机当前用户下有个 .jenkins/workspace,在此目录下你还看到你拉取下来的项目
image.png

2.使用gradle将项目打包并使用docker启动

在这里我先说明下我项目工程的目录结构,在这里我只用三个服务,eureka-service、bussines-service、admin-service,整个项目的结构如下(注:本教程主要是讲解如何将项目自动化搭建到docker上,而不是部署spring-cloud微服务,所以像config-Service等待啥的都不要了,部署细节啥的也都不讲了,毕竟网上教程一大堆)
image.png
分解过程步骤 ,首先,我先说明下我的实现一种简单的方式
1. 使用gradle将各个服务打成 jar 包
2. 将新打的jar包copy 到指定docker-compose所在目录( ==dockerfile 和 docker-compose 的编写我在后面会详细讲解== )
3. 停止并删除容器
4. 删除镜像
5. 启动docker-compose 重新构建镜像并且启动容器
先实现第一步,打jar 包, 这里 会用到shell 脚本, 不过放心,shell 脚本非常非常非常简单,而且我会给详细注释, 首先在我们项目根目录下建个build.sh文件 内容如下
#!/bin/sh

echo "更新服务开始。。。。"

###jenkins 从 gitlab 上拉取的项目所在路径
home="/Users/Function/.jenkins/workspace/spring-cloud"

### 自定义的docker-compose.yml 文件所在路径
docker_home="/Users/Function/Documents/workspace/docker-workspace/spring-cloud"

### admin服务文件夹名
admin_home="/admin-service"
### eureka服务文件夹名
eureka_home="/eureka-service"
### business服务文件夹名
business_home="/business-service/business-service-impl"


### 判断admin服务路径是否存在

if [ ! -e ${home}${admin_home} ];then
echo "${admin_home} 不存在"
exit 1;
fi

### 判断eureka服务路径是否存在
if [ ! -e "${home}${eureka_home}" ];then
echo "${eureka_home} 不存在"
exit 1;
fi

### 判断bussines_home服务路径是否存在
if [ ! -e "${home}${business_home}" ];then
echo "${business_home} 不存在"
exit 1;
fi


### 执行项目根目录下gradlew文件

echo "清除上次构建文件......"
cd ${home}
sh ${home}/gradlew clean
sleep 3

echo "开始构建Admin服务...."
cd ${home}${admin_home}
sh ${home}/gradlew bootJar -x test
echo "Admin服务构建完成...."

sleep 3

echo "开始构建eureka服务...."
cd ${home}${eureka_home}
sh ${home}/gradlew bootJar -x test
echo "开始构建eureka服务构建完成...."

sleep 3

echo "开始构建business服务...."
cd ${home}${business_home}
sh ${home}/gradlew bootJar -x test
echo "Service服务构建完成...."

if [ ! $? -eq 0  ];then
echo "gradle 执行失败: $?"
exit 1;
fi


echo "将最新jar包移动到DOCKER目录. ${docker_home}"
if [ ! -e "${docker_home}" ]; then
echo "${docker_home} 不存在"
exit 1;
fi

echo "删除旧的jar..."
rm -rf ${docker_home}/*.jar


echo "开始移动Eurake.."
mv ${home}${eureka_home}/build/libs/eureka-service.jar ${docker_home}

echo "开始移动Admin.."
mv ${home}${admin_home}/build/libs/admin-service.jar ${docker_home}


echo "开始移动Business.."
mv ${home}${business_home}/build/libs/business-service-impl.jar ${docker_home}

### 进入 docker-compose 文件所在目录
cd ${docker_home}

echo "停止并删除启动的容器....."
### 先停止并删除 上一次根据docker-compose启动的容器 注:echo 中输入的是你本机电脑密码,用 shell 执行 docker 命令时需要sudo 权限
echo "ccz123456..." | sudo -S docker-compose down
sleep 1

echo "删除spring-cloud 服务镜像....."
### 删除上一次构建的spring-cloud项目镜像   其中 grep service 中的service 是镜像名中包含的字段,是生成镜像时可以指定,如镜像名为: sc-service ,编写docker-compose时会说明
echo "ccz123456..." | sudo -S docker rmi --force `docker images | grep service | awk '{print $3}'`
sleep 1

echo "构建spring-cloud新的服务镜像....."
### 构建镜像镜像
echo "ccz123456..." | sudo -S docker-compose build
sleep 1

echo "启动服务容器....."
### 构建镜像并启动容器
echo "ccz123456..." | sudo -S docker-compose up -d
shell就是这样了,是不是非常简单? 其中有很多地方你们可以自行优化。下面编写dockerfile 文件,dockerfile文件我也会写的非常简单,多余的东西就不往上加了,在 脚本中的docker_home目录下新建三个dockerfile文件,命名为dockerfile-admin、dockerfile-business、dockerfile-eureka (文件名随意,你们开心就行)其中dockerfile-admin内容如下
# 依赖的java版本
FROM java:8
# 在容器中创建工作目录
RUN mkdir /app
# 指定工作目录
WORKDIR /app
#将你本机docker-home 目录下的 admin-service.jar ADD 到 容器 工作目录下并命名为 app.jar (不重命名也行)
ADD admin-service.jar  /app/app.jar
# 执行环境 其中-Djava.security.egd=file:/dev/./urandom 是JVM 熵池 参数,想了解的可百度 
# --spring.profiles.active=docker 是springcloud 配置文件 application.yml文件配置项,当有多个application.yml时用来指定加载哪个配置文件
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/app.jar","--spring.profiles.active=docker"]
# 开放8080端口
EXPOSE 8080

dockerfile-bussines、dockerfile-eureka 文件内容一样,只需将 ADD admin-service.jar /app/app.jar 中的 admin-service.jar 替换成 business-service.jar 和 eureka.jar 并且修改 EXPOSE business-service 端口 9090 和 eureka-service端口 8761. 接下来是docker-compose.yml,同样,在docker-home目录下新建文件 docker-compose.yml内容如下
version: "3" # docker-compose 版本
services:  # 服务
  eureka-service:  # 定义eureka-service 名字随意
      container_name: eureka-container  # 指定容器名 随意
      build:
         context: . # dockerfile 文件所在路径  点 指的是 dockerfile 文件在当前docker-compose.yml所在目录下
         dockerfile: dockerfile-eureka  # dockerfile 文件名
      image: eureka-service:v1  # 指定生成的镜像名 并打上tag 标签 v1
      hostname: eurekaservice   # 容器hostname
      ports:
        - "8761:8761"   # 暴露容器端口到主机


  business-service:
        container_name: business-container
        build:
          context: .
          dockerfile: dockerfile-business
        image: business-service:v1
        ports:
          -  "8020:8020"
        depends_on: # 当前容器依赖于eureka 容器 ,当前容器会在 eureka-service 容器启动后执行
          -  eureka-service
        links:  # 链接 注册到eureka   eurekaservice 我会在下面说明
          -  "eureka-service:eurekaservice"


  admin-service:
      container_name: admin-container
      build:
        context: .
        dockerfile: dockerfile-admin
      image: admin-service:v1
      ports:
        - "8080:8080"
      depends_on: # 当前容器依赖于以上两个容器
        - eureka-service
        - business-service

      links:
        - "eureka-service:eurekaservice"
        - "business-service"
这里需要说明下docker-compose.yml 文件中 links : - "eureka-service:eurekaservice" docker中,容器直接链接的方式有两种,一种是服务名方式,一种是别名的方式,而别名的方式必须是以 服务名:别名 来指定。不然,你会发现你的服务无法注册到eureka中 文件中的 eurekaservice 为spring-cloud项目配置文件application.yml 中eureka url 中的hostname ,原因是docker 默认的网络模式为 bridge, 每个容器的IP都不同,使用localhost无法满足需求,所以用主机名来访问,三个服务的url都改用容器主机名的方式
image.png
好了,到这里一切都已准备就绪,我们实验下效果。提交修改过得项目到Gitlab ,打开jenkins -> 进入工程 -> 配置当前工程
image.png
找到构建项 -> 增加构建步骤 -> 选择 执行shell
image.png
在命令中 输入你刚才建的 build.sh 脚本的路径 或者 直接将脚本内容考皮到命令框中,如果是路径,应该是 ~/.jenkins/workspace/spring-cloud/build.sh , 然后点击保存,点击立即构建
image.png
见证奇迹的时刻就要到了,当你点击立即构建之后,然后进入控制台,你会看到构建过程日志
由用户 xxx 启动
构建中 在工作空间 /Users/Function/.jenkins/workspace/spring-cloud 中
 > git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
 > git config remote.origin.url [http://git.xxxx.com/spring-cloud.git](http://git.xxxx.com/spring-cloud.git) # timeout=10
Fetching upstream changes from [http://git.xxxx.com/spring-cloud.git](http://git.xxxx.com/spring-cloud.git)
 > git --version # timeout=10
using GIT_SSH to set credentials 
 > git fetch --tags --progress [http://git.xxxx.com/spring-cloud.git](http://git.xxxx.com/spring-cloud.git) +refs/heads/*:refs/remotes/origin/*
 > git rev-parse refs/remotes/origin/master^{commit} # timeout=10
 > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
Checking out Revision c85486e6447c023c0e70f98885760761aaaa928e (refs/remotes/origin/master)
 > git config core.sparsecheckout # timeout=10
 > git checkout -f c85486e6447c023c0e70f98885760761aaaa928e
Commit message: "修改shell了嘞嘞"
 > git rev-list --no-walk c85486e6447c023c0e70f98885760761aaaa928e # timeout=10
[spring-cloud] $ /bin/sh -xe /var/folders/26/b1nkkk7j22v7dx9mvdty84vr0000gn/T/jenkins3559310015219383127.sh
+ cd /Users/Function/.jenkins/workspace/spring-cloud/
+ chmod u+x build.sh
+ ./build.sh
更新服务开始。。。。
清除上次构建文件......
:admin-service:clean
:business-service:clean UP-TO-DATE
:eureka-service:clean
:business-service:business-service-api:clean
:business-service:business-service-impl:clean

BUILD SUCCESSFUL in 0s
5 actionable tasks: 4 executed, 1 up-to-date
开始构建Admin服务....
:business-service:business-service-api:compileJava
:business-service:business-service-api:processResources NO-SOURCE
:business-service:business-service-api:classes
:business-service:business-service-api:jar
:admin-service:compileJava
:admin-service:processResources
:admin-service:classes
:admin-service:bootJar

BUILD SUCCESSFUL in 4s
5 actionable tasks: 5 executed
Admin服务构建完成....
开始构建eureka服务....
:eureka-service:compileJava
:eureka-service:processResources
:eureka-service:classes
:eureka-service:bootJar

BUILD SUCCESSFUL in 2s
3 actionable tasks: 3 executed
开始构建eureka服务构建完成....

开始构建business服务....

:business-service:business-service-api:compileJava UP-TO-DATE
:business-service:business-service-api:processResources NO-SOURCE
:business-service:business-service-api:classes UP-TO-DATE
:business-service:business-service-api:jar UP-TO-DATE

:business-service:business-service-impl:compileJava
:business-service:business-service-impl:processResources
:business-service:business-service-impl:classes
:business-service:business-service-impl:bootJar

BUILD SUCCESSFUL in 2s
5 actionable tasks: 3 executed, 2 up-to-date
Service服务构建完成....

将最新jar包移动到DOCKER目录. /Users/Function/Documents/workspace/docker-workspace/spring-cloud
删除旧的jar...
开始移动Eurake..
开始移动Admin..
开始移动Business..
停止并删除启动的容器.....

Password:Removing admin-container   ... 
Removing busines-container ... 
Removing eureka-container  ... 
�[3A�[2K
Removing admin-container   ... �[32mdone�[0m
�[3B�[2A�[2K
Removing busines-container ... �[32mdone�[0m
�[2B�[1A�[2K
Removing eureka-container  ... �[32mdone�[0m
�[1BRemoving network spring-cloud_default

删除spring-cloud 服务镜像.....
Untagged: admin-service:v1
Deleted: sha256:8bb51f82162535270d6bc7274ea8cac1e0a98ca103facf6548056ca3a1bcb484
Deleted: sha256:60257e125ebc783f930f79f16516091204e99a4a9d0390dcefa63703cb8c598c
Deleted: sha256:0e7e5c3a29df6fb7142fa37da4fc00526341f5b966099e6125f728efd2c52a6e
Deleted: sha256:169fc43041641f26b1046cd843d1e347bfd557625b4b39e798b621042721437c
Untagged: busines-service:v1
Deleted: sha256:d8cd89f0b7eba9a0b31eda5ec36b83bb1da4ebabfc549a915af36a62a4173b32
Deleted: sha256:03fc157106a95d58a0233655e55c616a68ca76d1d72fbf58705072e6729a4caf
Deleted: sha256:67e010d66dbf3e30adbd94f5eb33ad893ce7bba97f0cccec676d5f0834c7f44f
Deleted: sha256:2e2005d82ca03e80ed71f3b5c3c9087d720d6e727e2286f01967ec6fb0318023
Untagged: eureka-service:v1
Deleted: sha256:bf7b59c1673b09613df678a0cfe0156e645dad6aa9230a0fe90af2fd89163acc
Deleted: sha256:df664e41f222cb957a484110824f4bb977011698247e13b1413ca4097d17f003
Deleted: sha256:93cb206e2f8772c8b7d1f84a7a46d67393cdd0d8c7e61d28461087c82c0fdeec
Deleted: sha256:60863060e250a714a33dea8bef0393a8ea5a4f75fa96c28cef084392059e1128


构建spring-cloud新的服务镜像.....
Building eureka-service

Step 1/6 : FROM java:8
 ---> d23bdf5b1b1b
Step 2/6 : RUN mkdir /app
 ---> Using cache
 ---> 27c94e92d2a7
Step 3/6 : WORKDIR /app
 ---> Using cache
 ---> 49efed4c9ca0
Step 4/6 : ADD eureka-service.jar  /app/app.jar
 ---> 56270e100c2a
Step 5/6 : ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/app.jar","--spring.profiles.active=default"]
 ---> Running in 8a997301d5b3
Removing intermediate container 8a997301d5b3
 ---> 2134b5a800a4
Step 6/6 : EXPOSE 8761
 ---> Running in 26c507af1c50
Removing intermediate container 26c507af1c50
 ---> 593b51aa6fa4
Successfully built 593b51aa6fa4
Successfully tagged eureka-service:v1
Building busines-service

Step 1/6 : FROM java:8
 ---> d23bdf5b1b1b
Step 2/6 : RUN mkdir /app
 ---> Using cache
 ---> 27c94e92d2a7
Step 3/6 : WORKDIR /app
 ---> Using cache
 ---> 49efed4c9ca0
Step 4/6 : ADD business-service-impl.jar  /app/app.jar
 ---> 638b8674ee26
Step 5/6 : ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/app.jar","--spring.profiles.active=docker"]
 ---> Running in 5774538bd4d9
Removing intermediate container 5774538bd4d9
 ---> 0505d456c863
Step 6/6 : EXPOSE 8020
 ---> Running in f9714296fe56
Removing intermediate container f9714296fe56
 ---> 8d2ab6dd3c19
Successfully built 8d2ab6dd3c19
Successfully tagged busines-service:v1
Building admin-service

Step 1/6 : FROM java:8
 ---> d23bdf5b1b1b
Step 2/6 : RUN mkdir /app
 ---> Using cache
 ---> 27c94e92d2a7
Step 3/6 : WORKDIR /app
 ---> Using cache
 ---> 49efed4c9ca0
Step 4/6 : ADD admin-service.jar  /app/app.jar
 ---> 8eb5a94001ff
Step 5/6 : ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/app.jar","--spring.profiles.active=default"]
 ---> Running in f7e7ce480b15

Removing intermediate container f7e7ce480b15
 ---> cd478f10383b
Step 6/6 : EXPOSE 8080
 ---> Running in 8509924c9984
Removing intermediate container 8509924c9984
 ---> d31a4a4c3358
Successfully built d31a4a4c3358
Successfully tagged admin-service:v1
启动服务容器.....


Creating network "spring-cloud_default" with the default driver
Creating eureka-container ... 
�[1A�[2K
Creating eureka-container ... �[32mdone�[0m
�[1BCreating busines-container ... 

�[1A�[2K
Creating busines-container ... �[32mdone�[0m
�[1BCreating admin-container   ... 
�[1A�[2K
Creating admin-container   ... �[32mdone�[0m
�[1BFinished: SUCCESS


当最后 显示finished:SUCCESS 时,表示已经成功,由于在 build 中设置了 项目后台启动,所以这里只看到了构建过程,没有项目启动日志,如果需要,看官们可自行修改,只需将build.sh 最后 -d 去掉即可
image.png
最后,在终端输入docker-container ls 你会看到启动的三个容器
image.png
然后访问 0.0.0.0:8761,你会看到服务已经注册到eureka 上了
image.png
OK 大功告成!后面你可以在优化一些关于jenkins自动构建的步骤,这种教程网上也很多,我就不列举了。有什么错误的地方请留言指正。项目代码和 docker文件我都放到了GitHub上,GitHup地址
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容