Docker安装、自定义镜像、网络配置、持久化数据、compose、swarm

Docker简介

Docker是一个开源的引擎,它提供了为应用程序创建轻量级/可移植/高效的容器方案。支持部署到本地和云平台环境。使用Docker有很多好处,比如更快的交付和部署,更高效的资源利用,更轻松的迁移。它的应用场景包括Web应用的自动化打包部署,自动化测试,持续集成等。

Docker基本概念

docker的核心: 镜像、容器、仓库

镜像

Docker中的镜像类似于虚拟机的镜像,可以理解为一个只读的模板。比如,一个镜像包含一个基本操作系统环境,或者包含某个应用程序正确运行所需的运行环境。镜像是创建Docker容器的基础,通过镜像的版本控制,Docker提供了十分简单的机制来创建和更新镜像,也可以直接下载已经做好的应用镜像,并直接使用。

容器

Docker容器类似一个轻量级的沙盒,Docker利用容器来运行和隔离应用,容器是从镜像创建的应用运行示例,可以对它进行启动,停止,删除等操作。
可以理解容器为一个简易版的Linux操作系统环境,包括某些应用程序打包而成的盒子。

仓库

Docker仓库类似代码仓库,是Docker用来集中存放镜像的地方。Docker仓库根据镜像是否公开分为公开仓库和私有仓库,目前最大的公开仓库是官方提供的Docker Hub,其中存放了大量的镜像供用户下载。同时,国内不少云服务提供商也提供了仓库的本地源,可以提高稳定的国内访问。

关于私有仓库,用户可以自行搭建本地仓库,用于维护私有的镜像。

分层存储

镜像构建时,会一层一层的构建,前一层是后一层的基础,每一层构建完就不会发生改变,后一层所有的改变都不会影响前一层的。比如删除前一层的文件,其实并不是删除了前一层的文件,只是标记为已删除,在容器运行的时候,不会看到这个文件,但是实际上文件还存在。

分层存储的特征让镜像的复用变得容易,比如可以以之前建好的镜像作为基础层,然后逐步添加新的层,以定制自己所需的内容。

Docker的安装

删除旧版本的 Docker

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

安装所需的软件包

sudo yum install -y yum-utils \
  device-mapper-persistent-data \
  lvm2

设置docker默认的镜像仓库

sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

一下代码是设置阿里云的 docker 镜像仓库

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
  "registry-mirrors": ["https://y5j5hcnv.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

安装docker

sudo yum install docker-ce docker-ce-cli containerd.io

启动docker

sudo systemctl start docker

docker常用命令

docker images                                       # 查看所有模板
docker pull tomcat                                  # 从远程仓库拉取 image 镜像
docker rmi tomcat                                   # 删除某个镜像
docker run -d --name mytomcat -p 80:8080 tomcat     # 运行docker image 镜像
docker exec -it tomcat /bin/bash                    # 进入到容器中执行命令
docker ps                                           # 查看正在运行的  container
docker kill tomcat                                  # 杀死进程
docker build -t vitshop-image                       # 构建 image 镜像
docker logs -f vitshop                              # 查看日志
docker container ls -a                              # 查看所有的container
docker container rm tomcat                          # 删除container
docker inspect tomcat                               # 查看 image 镜像详细信息
docker history tomcat                               # 查看镜像历史
docker volume ls                                    # 查看所有的 volume
docker volume inspect tomcat                        # 查看 volume 详情
docker volume create                                # 创建volume
docker volume rm                                    # 删除 volume
docker start mytomcat                               # 启动已有的container

docker官网命令集合

Docker | 自定义image 自定义docker镜像

Dockerfile 什么是 Dockerfile?

Dockerfile是一个用来构建镜像的文本文件,文本内容包含了一条条构建镜像所需的指令和说明。

Dockerfile 指令的作用

FROM centos     # 基于什么镜像之上构建自己的自定义镜像
RUN             # 用于执行后面命令,RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline
CMD             # 类似于 RUN 命令,用于运行程序。 CMD在docker run 时运行。 run 是在docker build时运行
COPY            # 复制指令,从上下文目录中复制文件或者目录到容器的制定位置
ADD             # 跟复制命令使用格式一致(同样的需求下,官方建议使用 copy)
VOLUME          # 定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载匿名的数据卷。   做持久化数据

ENTRYPOINT      # 类似于 cmd 命令但其不会被 docker run的命令行参数指定的指令所覆盖,而且这些命令行的参数会被当做参数传给 
                # ENTRYPOINT 所指定的程序,但是如果运行 docker run 时候使用了 --entrypoint 选项,
                # 此选项的参数可当做要运行的程序覆盖 ENTRYPOINT 指定的程序
                # 优点: 在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数
                # 注意: 如果 Dockerfile 文件中存在多个 ENTRYPOINT 指令,仅最后一个生效。

ENV             # 设置环境变量,定义了环境变量,那么在后续的指令中就可以使用这个变量。

ARG             # 构建参数,与 ENV 作用一至。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,
                # 也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。
                # 构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。

EXPOSE          # 仅仅只是声明端口。帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。
                # 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

WORKDIR         # 指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在(WORKDIR 指定的工作目录,必须是提前创建好的)
                # docker build 构建镜像过程中的,每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在。

USER            # 用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。
HEALTHCHECK     # 用于指定某个程序或者指令来监控 docker 容器服务的运行状态。
ONBUILD         # 用于延迟构建命令的执行。简单的说,就是 Dockerfile 里用 ONBUILD 指定的命令,在本次构建镜像的过程中不会执行
                # (假设镜像为 test-build)。当有新的 Dockerfile 使用了之前构建的镜像 FROM test-build ,
                # 这是执行新镜像的 Dockerfile 构建时候,会执行 test-build 的 Dockerfile 里的 ONBUILD 指定的命令。

使用 Dockerfile 创建自定义 image 镜像

以springboot项目为例:把打包好的 springboot.jar 放入服务器自定义目录中,并创建 Dockerfile 文件。

mkdir vitshop-image  # 创建自定义目录,
vim Dockerfile       # 创建 Dockerfile 文件

编辑 Dockerfile 文件

FROM openjdk:8
MAINTAINER itcrazy2016
LABEL name="dockerfile-dome" version="1.0" author="itcrazy2016"
COPY vitshop-0.0.1.jar vitshop.jar
CMD ["java","-jar","vitshop.jar"]

保存 Dockerfile 文件之后,使用 docker build -t vitshop-image 命令创建 image。
等待创建完毕之后就可以,运行或者查看 image 镜像了。

docker build -t vitshop-image .                         # -t vitshop-image 是自定义的名称
docker iamges                                           # 查看所有的镜像
docker run -d --name vitshop -p 80:8080 vitshop-image   # 运行自己创建的  image镜像

把 image 镜像推送到阿里云镜像仓库

** 登陆阿里云控制台,找到容器镜像服务标签,选择下面的镜像仓库开通空间之后照下面命令上传自己的镜像。 **

# 登陆到阿里云镜像仓库
$ sudo docker login --username=tb4005142_00 registry.cn-beijing.aliyuncs.com
# 给自定的镜像打包
$ sudo docker tag [ImageId] registry.cn-beijing.aliyuncs.com/daozhuang/daozhuang:[镜像版本号]
# 推送到阿里云仓库
$ sudo docker push registry.cn-beijing.aliyuncs.com/daozhuang/daozhuang:[镜像版本号]
# 从Registry中拉取镜像
$ sudo docker pull registry.cn-beijing.aliyuncs.com/daozhuang/daozhuang:[镜像版本号]
# 从ECS推送镜像时,可以选择使用镜像仓库内网地址。推送速度将得到提升并且将不会损耗您的公网流量。
# 如果您使用的机器位于VPC网络,请使用 registry-vpc.cn-beijing.aliyuncs.com 作为Registry的域名登录,并作为镜像命名空间前缀。

深入探讨Container

Docker commit 命令
docker commit :从容器创建一个新的镜像。
语法:

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
docker commit -a "xxxx" -m "xxxx" a404c6c174a2  mymysql:v1 

OPTIONS说明:
-a :提交的镜像作者;
-c :使用Dockerfile指令来创建镜像;
-m :提交时的说明文字;
-p :在commit时,将容器暂停。

container资源限制

语法:

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
docker run --name mynginx -d nginx:latest
# 内存限制
docker run -d --memory 100M --name tomcat01 tomcat
# cpu限制  设置CPU权重
docker run -d --cpu-shares 10 --name tomcat01 tomcat

OPTIONS说明:
-a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项;
-d: 后台运行容器,并返回容器ID;
-i: 以交互模式运行容器,通常与 -t 同时使用;
-P: 随机端口映射,容器内部端口随机映射到主机的高端口
-p: 指定端口映射,格式为:主机(宿主)端口:容器端口
-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
--name="nginx-lb": 为容器指定一个名称;
--dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致;
--dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致;
-h "mars": 指定容器的hostname;
-e username="ritchie": 设置环境变量;
--env-file=[]: 从指定文件读入环境变量;
--cpuset="0-2" or --cpuset="0,1,2": 绑定容器到指定CPU运行;
-m :设置容器使用内存最大值;
--net="bridge": 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;
--link=[]: 添加链接到另一个容器;
--expose=[]: 开放一个端口或一组端口;
--volume , -v: 绑定一个卷

Docker | 容器网络配置

Docker的四种网络模式

docker默认网路 bridge
Docker启动的时候会在主机上自动创建一个docker0网桥,实际上是一个Linux网桥,所有容器的启动如果在docker run的时候没有指定网络模式的情况下都会挂载到docker0网桥上。这样容器就可以和主机甚至是其他容器之间通讯了。
我们可以使用 命令 ip a 查看所有的网卡

host模式网络
该模式中将禁用Docker容器的网络隔离,容器共享宿主机的网络命名空间,直接暴露在公网中,容器会继承宿主机的IP地址。使用host模式会将容器直接暴露在公网,会存在安全隐患。

container模式网络
该模式中,容器会使用另一个容器的网络命名空间。使用方式为:--net=container:containername

none模式网络
该模式将容器放置在自己的网络栈中,并不进行任何配置,该模式实际上是关闭了容器的网络功能。

外部访问容器:
外部要访问容器使用: -p 参数把容器端口映射到宿主机上即可访问,-p 和 -P是有区别的 -P是随机映射端口范围是 49000~49900;
-p是要自己指定端口号, 如:-p hostPort:containerPort

容器访问外部:
默认情况下,容器可以访问到外部网络的连接,因为容器的默认网络接口为docker0网桥上的接口,也即是主机上的本地接口。其原理是通过Linux系统的转发功能实现的,在Linux中检查转发是否打开:

[root@VM_0_14_centos ~]# sysctl net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

0表示转发未打开,1表示转发已经打开。可以在docker run时使用--ip-forward=true来指定,Docker会打开主机的转发功能。

同时可以通过上述的nat表中找到对应的规则,在POSTRONTING的第一个规则就是把所有原地址在172.17.0.0/16网段但不是属于Docker0的网包(即容器中的网包),动态伪装为0.0.0.0/0网段,即主机网卡。

自定义 bridge 网络

docker默认的网络模式
在使用此网络模式的时候需要端口映射,
Docker网络操作:

# 创建网路
docker network create -d bridge --subnet 172.25.0.0/16 mynet
# 连接一个网络到容器中
docker network connect [OPTIONS] NETWORK CONTAINER
docker network connect mynet containername
# 查看所有网络
docker network ls
# 删除网络
docker network rm
# 容器和网络断开连接
docker network disconnect [OPTIONS] NETWORK CONTAINER
# 查看网络详情
docker network inspect

Docker link方式互联

容器之间相互通讯可以通过docker run中的--link=container_name:alias参数来达到效果。
此种方式可以很方便让容器使用容器名进行通讯,而不需要依赖ip地址,不过link方式仅仅解决了单机容器间的互联,多机的情况下,需要通过别的方式进行连接。

Docker 持久化数据

Docker文件系统

Docker的镜像是由多个文件系统(只读)叠加而成,当我们启动一个容器的时候,Docker会加载只读镜像层并在其上添加一层’读写层’.如果运行中的容器需要了一个文件,那么并不会修改只读层的文件,只会把该文件复制到’读写层’然后对他修改,只读层的文件就被隐藏了,当删除了Docker容器之后,或者重启容器之后,之前对文件的更改会丢失,镜像的只读层以及容器运行时的’读写层’被称为Union File System(联合文件系统)

为了能够保存数据的持久化以及共享容器之间的数据,提出了Volume的概念。简单来说Volume就是一个文件或者目录。它可以绕过联合文件系统,以正常的文件或者目录的形式保存在主机上。可以通过两种方式来初始化Volume,这两种方式有一些细微区别。

运行时创建 volume

# 使用 -v 参数指定挂载的目录  声明volume
# 以下命令会创建一个CentOs的容器并且把容器内部的/data目录作为数据卷挂载
docker run -it --name centOs -v /data centos
# 查看挂载的volume,可以看到容器已经把/var/lob/docker/volumes/xxx/_data目录挂载到容器的/data目录了。
docker inspect -f {{.Mounts}} centOs
[{volume centOs /var/lib/docker/volumes/XXXXX/_data /data local  true }]
# 该命令是将主机的/home/centos/data目录挂载到容器的/data目录。
docker run -it --name centOs -v /home/centos/data:/data centos

另外值得注意的是,只要将主机的目录挂载到容器上,那改变就会立即生效。挂载目录不会将镜像的文件复制到Volume中,只会把主机中不存在的文件夹创建出来。

数据共享
如果要授权一个容器访问另一个容器的Volume,可以使用-volumes-from参数来执行docker run命令:

docker run -it --name centOs1 --volumes-from centOs centos

不管容器是否正在运行,Volume都不会被删除。

数据备份与恢复

如果使用数据卷容器,想做备份和恢复就非常容易了。假设有较多的镜像从centOs中挂载了数据卷,那么备份与恢复只需针对centOs这个容器即可。

备份

docker run --volumes-from centOs -v $(pwd):/backup --name worker centos tar cvf /backup/backup.tar /data 

这个命令的意思是 将当前目录挂载到容器的/backup 目录,然后容器的工作就是压缩 /data的内容到 /back/backup.tar文件中。执行完之后就会看见在当前目录中生成了backup.tar文件

恢复

docker run --volumes-from centOs -v $(pwd):/backup centos tar xvf /backup/backup.tar -C /data

删除数据卷
Volume可以使用两种方式删除数据卷:

  • 使用docker rm -v命令,这个命令移除容器的同时也会移除数据卷
  • 使用docker run --rm这个命令会在容器退出时清除数据
  • docker volume ls 列出所有数据卷
  • docker volume prune 删除无用的数据卷

Docker Compose

Compose 简介

Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose,您可以使用 YML 文件来配置应用程序需要的所有服务。然后,使用一个命令,就可以从 YML 文件配置中创建并启动所有服务。

Compose 使用的三个步骤:

  • 使用 Dockerfile 定义应用程序的环境。
  • 使用 docker-compose.yml 定义构成应用程序的服务,这样它们可以在隔离环境中一起运行。
  • 最后,执行 docker-compose up 命令来启动并运行整个应用程序。

Compose 安装

Linux 上我们可以从 Github 上下载它的二进制包来使用,最新发行的版本地址:https://github.com/docker/compose/releases
运行以下命令以下载 Docker Compose 的当前稳定版本:要安装其他版本的 Compose,请替换 1.24.1。

sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
docker-compose --version

注意: 对于 alpine,需要以下依赖包: py-pip,python-dev,libffi-dev,openssl-dev,gcc,libc-dev,和 make。

使用:

# 创建一个测试目录:
mkdir composetest
cd composetest
# 在此文件夹中放置 需要运行的项目 如: springboot.jar,还有Dockerfile,和docker-compose.yml
springboot.jar
Dockerfile
docker-compose.yml

docker-compose.yml 配置文件:

# yaml 配置
version: '3'                    # 指定本 yml 依从的 compose 哪个版本制定的。
services:                       # 
  web:                          # 该 web 服务使用从 Dockerfile 当前目录中构建的镜像。
    build: .                    # 指定为构建镜像上下文路径:也就是Dockerfile文件的路径
    networks:                   # 指定容器连接的网络,使用顶级 networks 下的条目 。
      - app-net                 # 
  redis:                        # 
    image: "redis:alpine"       # 指定容器运行的镜像。
    networks:                   # 指定容器连接的网络,使用顶级 networks 下的条目 。
      - app-net
networks:                       # 配置网络 如果没有会自动创建
  app-net:
    driver: bridge

使用 Compose 命令构建和运行您的应用:

# 在测试目录中,执行以下命令来启动应用程序:
docker-compose up
# 如果你想在后台执行该服务可以加上 -d 参数:
docker-compose up -d
# 若要对容器进行扩缩容
docker-compose up --scale web=5 -d
docker-compose ps
docker-compose logs web

Swarm 集群管理

简介

Docker Swarm 是 Docker 的集群管理工具。它将 Docker 主机池转变为单个虚拟 Docker 主机。 Docker Swarm 提供了标准的 Docker API,所有任何已经与 Docker 守护程序通信的工具都可以使用 Swarm 轻松地扩展到多个主机。

支持的工具包括但不限于以下各项: Dokku、Docker Compose、Docker Machine、Jenkins

原理

如下图所示,swarm 集群由管理节点(manager)和工作节点(work node)构成。

  1. swarm mananger:负责整个集群的管理工作包括集群配置、服务管理等所有跟集群有关的工作。
  2. work node:即图中的 available node,主要负责运行相应的服务来执行任务(task)。


    在这里插入图片描述

搭建Swarm集群

  1. 进入manager: manager node也可以作为 worker node提供服务
docker swarm init --advertise-addr=192.168.0.11     # 这里的ip对应的是宿主机的ip

注意观察日志,拿到worker node加入manager node的信息


在这里插入图片描述
  1. 进入两个worker 执行以下命令,加入到manager 集群中
docker swarm join --token SWMTKN-1-3zzim2k6p9v18ed2l94li2dngt10kvkibfa94gpjqvlt280y0f-9gkawx7564dzh4fvmqccwhnyh 192.168.0.48:2377
# 日志打印
This node joined a swarm as a worker.
# 进入到 manager node 查看所有node节点
docker node ls
# 将 worker node 升级为 manager
docker node promote NODENAME
# 将 manager node 降级为 worker
docker node demote NODENAME
# 停止某个节点
docker node update --availability drain swarm-worker1
# 从新启动某个节点
docker node update --availability active swarm-worker1

注意:swarm-worker1 状态变为 Drain。不会影响到集群的服务,只是 swarm-worker1 节点不再接收新的任务,集群的负载能力有所下降。

Swarm基本操作

# 创建一个tomcat的service
docker service create --name my-tomcat tomcat
# 查看当前swarm的service
docker service ls
# 查看service的启动日志
docker service logs my-tomcat
# 查看 service详情
docker service inspect my-tomcat
# 查看 my-tomcat 运行在哪个node上
docker service ps my-tomcat
# 水平扩展service
docker service scale my-tomcat=5
# 查看所有service
docker service ls
# 删除 service
docker service rm my-tomcat

参考文章 https://blog.csdn.net/u012943767/article/details/79767670

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