Docker学习笔记(一)

Docker应用容器

1. 初识 Docker

  • 我们写的代码会接触到好几个环境:开发环境、测试环境以及生产环境。

  • 这期间可能出现不同环境下的 “水土不服” 导致正常的程序无法运行。这时就需要将我们的环境一起打包到容器发过去。解决:软件跨环境迁移的问题。

image.png

1.1 - Docker的概念

  • Docker是一个开源的应用容器引擎。Docker诞生于2013年, dotCloud 公司出品(后改名为Docker Inc)。

  • Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上。

  • 容器是完全使用沙箱机制,相互隔离。

  • 容器性能开销极低。

  • Docker 从 17.03 版本之后分为 CE(Community Edition: 社区版) 和 EE(Enterprise Edition: 企业版) 。

1.2 安装Docker

# 1、yum 包更新到最新 
yum update
# 2、安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的 
yum install -y yum-utils device-mapper-persistent-data lvm2
# 3、 设置yum源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 4、 安装docker,出现输入的界面都按 y 
yum install -y docker-ce
# 5、 查看docker版本,验证是否验证成功
docker -v

1.3 Docker 的架构

image.png
  • 镜像(Image) :Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。

  • 容器(Container):镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和对象一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

  • 仓库(Repository) :仓库可看成一个代码控制中心,用来保存镜像。

1.4 配置 Docker 镜像加速器

  • 默认情况下,将来从docker hub(https://hub.docker.com/)上下载docker镜像,太慢。一般都会配置镜像加速器:

(1)USTC:中科大镜像加速器(https://docker.mirrors.ustc.edu.cn
(2)阿里云
(3)网易云
(4)腾讯云

  • 如何在CentOS7 中配置 Docker镜像加速器
切换到 docker目录下 :cd /etc/docker 
创建并编辑文件 : vim daemon.json 
编辑文件内容:  
{
  "registry-mirrors": ["https://ndirti7e.mirror.aliyuncs.com"]
}
重启docker服务 : systemctl restart docker 
附加命令 (linux修改文件后缀):mv filename.js filename.json (因为我的后缀写错了一直没有反应,hahaha) 

2. Docker 命令 *

2.1 Docker 服务相关命令

systemctl start docker : 启动服务
systemctl start docker : 查看Docker的状态
systemctl stop docker : 停止Docker服务
systemctl restart docker : 重启Docker服务
systemctl enable docker : 开机启动Docker 

2.2 Docker 镜像相关命令

1. 查看镜像(本地):docker images 
2. 搜索镜像:docker search '软件名称'
3. 拉取镜像:docker pusll redis :版本号
4. 删除镜像(本地):docker rmi 镜像id 
5. 查看当前所有镜像的id: docker images -q
6. 删除当前所有镜像: docker rmi `docker images -q`
  • 如何查找镜像的版本号 :https://hub.docker.com/
  • 如果镜像中存在两个id相同的镜像:如何删除?
1. 可以通过 镜像名称加上版本号 删除   docker rmi redis:5.0 

2.3 Docker 容器相关命令 **

  • 容器是有镜像这个物理的文件运行出来的实例。命令主要是如何通过镜像创建出容器。并且去操作容器。
1. 查看容器:docker ps (查看正在运行的容器)docker ps -a(查看运行或没有运行的容器)
2. 创建容器:docker run  -i(保持一致运行) -t (给容器一个终端)--name=名字(给容器起一个名字)     centos:7 (指定根据哪个镜像) /bin/bash (进入容器的指令)
 案例 docker run -i -t --name=cl centos:7 /bin/bash (通过这种方式创建的容器一旦退出就会关闭)  使用 exit 退出容器 
docker run -i -d(后台运行容器,但是创建完之后不会自动进入容器) --name=ct centos:7
3. 进入容器: docker exec -it 容器名称 /bin/bash
4. 启动容器:docker stop 容器名称 
5. 停止容器:docker start 容器名称 
6. 删除容器:docker rm 容器id/容器名称    删除所有容器 docker rm `docker ps -aq` 开启的容器是不能被删除的。
7. 查看容器信息:docker inspect 容器名称
image.png

3. Docker的应用部署

  1. 在Docker上安装常见的软件。

3.1 MySQL部署

  1. 在Docker容器中部署MySQL,并通过外部mysql客户端操作MySQL Server。
  2. 外部如何访问到docker容器中的mysql服务?容器内的网络服务不能直接和外部直接通信。
  3. 外部机器和宿主机可以直接通信。
  4. 宿主机和容器可以直接通信。
  5. 当容器中的网络服务需要外部机器访问时,可以将容器中提供服务的端口映射到宿主机的端口上。外部机器访问宿主机的该端口,从而直接访问容器的服务。

3.1.1 实现步骤

  1. 搜索mysql镜像。
docker search mysql
  1. 拉取mysql镜像。
docker pull mysql:5.6
  1. 创建容器。
- 首先创建一个存放mysql数据的文件,并切换进去 
# 在/root目录下创建mysql目录用于存储mysql数据信息
mkdir ~/mysql
cd ~/mysql

# 复制下面命令到终端执行 ,注意 \ 后面不能有空格之类的影响命令的执行 

docker run -id \
-p 3307:3306 \
--name=c_mysql \
-v $PWD/conf:/etc/mysql/conf.d \
-v $PWD/logs:/logs \
-v $PWD/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=123456 \
mysql:5.6

# 使用命令 进入到容器中 docker exec -it  c_mysql /bin/bash

参数说明:

- -p 3307:3306:将容器的 3306 端口映射到宿主机的 3307 端口。
- -v $PWD/conf:/etc/mysql/conf.d:将主机当前目录下的 conf/my.cnf 挂载到容器的 /etc/mysql/my.cnf。配置目录
- -v $PWD/logs:/logs:将主机当前目录下的 logs 目录挂载到容器的 /logs。日志目录
- -v $PWD/data:/var/lib/mysql :将主机当前目录下的data目录挂载到容器的 /var/lib/mysql 。数据目录
- -e MYSQL_ROOT_PASSWORD=123456:初始化 root 用户的密码。

  1. 操作容器中的mysql。


    连接到docker容器中的mysql

3.2 Tomcat部署

  1. 需求:在Docker容器中部署Tomcat,并通过外部机器访问Tomcat部署的项目。

3.2.1 实现步骤

  1. 搜索Tomcat镜像
docker search tomcat
  1. 拉取Tomcat镜像
docker pull tomcat
  1. 创建容器
  # 在/root目录下创建tomcat目录用于存储tomcat数据信息
mkdir ~/tomcat
cd ~/tomcat

docker run -id --name=c_tomcat \
-p 8080:8080 \
-v $PWD:/usr/local/tomcat/webapps \
tomcat 

参数说明:
- -p 8080:8080:将容器的8080端口映射到主机的8080端口
- -v $PWD:/usr/local/tomcat/webapps:将主机中当前目录挂载到容器的webapps

  1. 部署项目
  • 在宿主机之前创建的tomcat目录下新建一个项目文件夹 test
mkdir test
  • 在test文件夹中创建一个 index.html文件
vim index.html 
  1. 测试访问
  • 使用远程主机IP进行访问。


    访问远程docker容器中的tomcat应用成功

3.3 Nginx部署

  1. 在Docker容器中部署Nginx,并通过外部机器访问Nginx。

3.3.1 实现步骤

  1. 搜索Nginx
docker search nginx
  1. 拉取Nginx,并编写配置文件
docker pull nginx 

# 在/root目录下创建nginx目录用于存储nginx数据信息
mkdir ~/nginx
cd ~/nginx
mkdir conf
cd conf
# 在~/nginx/conf/下创建nginx.conf文件,粘贴下面内容
vim nginx.conf



# nginx.conf 配置文件内容 

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}


  1. 创建容器

# 切换到Nginx目录下执行如下命令创建Nginx的容器。

docker run -id --name=c_nginx \
-p 80:80 \
-v $PWD/conf/nginx.conf:/etc/nginx/nginx.conf \
-v $PWD/logs:/var/log/nginx \
-v $PWD/html:/usr/share/nginx/html \
nginx



参数说明:
- -p 80:80:将容器的 80端口映射到宿主机的 80 端口。
- -v $PWD/conf/nginx.conf:/etc/nginx/nginx.conf:将主机当前目录下的 /conf/nginx.conf 挂载到容器的 :/etc/nginx/nginx.conf。配置目录
- -v $PWD/logs:/var/log/nginx:将主机当前目录下的 logs 目录挂载到容器的/var/log/nginx。日志目录

  1. 测试访问
切换到html 文件夹下创建一个index.html文件之后进行访问测试。
测试访问Nginx成功!

3.4 Redis部署

  1. 在Docker容器中部署Redis,并通过外部机器访问 Redis。

3.4.1 实现步骤

  1. 搜索Redis镜像。
docker search redis
  1. 拉取Redis镜像。
docker pull redis:5.0
  1. 创建容器。
docker run -id --name=c_redis -p 6379:6379 redis:5.0  //注意端口可能被绑定了,需要替换端口
  1. 测试访问。
redis-cli.exe -h 192.168.149.135 -p 6379

4. 备份与迁移

5. Dockerfile

5.1 Dockerfile的原理

  1. linux文件系统由bootfs和rootfs两部分组成。

  2. bootfs:包含bootloader(引导加载程序)和kernelkernel(内核)。

  3. rootfs:root文件系统,包含的就是典型linux系统中的/devproc,/bin、 /etc等标准目录和文件

  4. 不同的linux发行版,bootfs基本是一样的,而rootfs不同,如ubuntu、centos等。

  5. Docker镜像是由特殊的文件系统叠加而成

  6. 最底端是 bootfs,并使用宿主机的bootfs

  7. 第二层是 root文件系统rootfs,称为base image

  8. 然后再往上可以叠加其他的镜像文件

  9. 统一文件系统(Union File System) 技术能够将不同的层整合成一个文件系统,为这些层提供了一个。

  10. 统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。

  11. 一个镜像可以放在另一个镜像的上面。位于下面的镜像称为父镜像,最底部的镜像成为基础镜像。

  12. 当从一个镜像启动容器时,Docker会在最顶层加载一个读写文件系统作为容器

Docker原理

5.2 Dockerfile概念及作用

  1. Docker的本质是啥?
  • 答:是一个分层文件系统
  1. Docker中一个centos镜像为什么只有200MB,而是一个centos操作系统的iso文件要几个G?
  • 答:Centos的iso镜像文件包含bootfs和rootfs,而docker的centos镜像复用操作系统的bootfs,只有rootfs和其他镜像层
  1. Docker中一个tomcat镜像为什么有500mb而一个tomcat的 安装包只有70多mb?
  • 答:Centos的iso镜像文件包含bootfs和rootfs,而docker的centos镜像复用操作系统的bootfs,只有rootfs和其他镜像

5.2.1 镜像制作

5.2.1.1 容器转为镜像的方式
docker commit 容器id 镜像名称:自定义版本号
 通过commit的方式转化,以数据卷的方式挂载的文件是不会生效的。在root目录下创建的文件夹会打包到镜像中。会在重新加载的时候生效。

# 1. 打包一个镜像 

docker commit  需要打包的容器id  lyp_tomcat:1.0.0

# 2. 需要将镜像镜像压缩之后才能够进行传输 
docker save -o  压缩文件名称.tar  镜像文件名称:1.0.0

3. 将镜像的压缩文件进行解压

docker load -i 压缩文件名称

5.2.1.2 Dockerfile的方式制作镜像

5.2.2 DockerFile的概念

  1. Dockerfile 是一个文本文件
  2. 包含了一条条的指令
  3. 每一条指令构建一层,基于基础镜像,最终构建出一个新的镜像
  4. 对于开发人员:可以为开发团队提供一个完全一致的开发环境
  5. 对于测试人员:可以直接拿开发时所构建的镜像或者通过Dockerfile文件构建一个新的镜像开始工作了
  6. 对于运维人员:在部署时,可以实现应用的无缝移植


    dockerfile的概念

5.3 Dockerfile关键字

关键字 作用 备注
FROM 指定父镜像 指定dockerfile基于那个image构建
MAINTAINER 作者信息 用来标明这个dockerfile谁写的
LABEL 标签 用来标明dockerfile的标签 可以使用Label代替Maintainer 最终都是在docker image基本信息中可以查看
RUN 执行命令 执行一段命令 默认是/bin/sh 格式: RUN command 或者 RUN ["command" , "param1","param2"]
CMD 容器启动命令 提供启动容器时候的默认命令 和ENTRYPOINT配合使用.格式 CMD command param1 param2 或者 CMD ["command" , "param1","param2"]
ENTRYPOINT 入口 一般在制作一些执行就关闭的容器中会使用
COPY 复制文件 build的时候复制文件到image中
ADD 添加文件 build的时候添加文件到image中 不仅仅局限于当前build上下文 可以来源于远程服务
ENV 环境变量 指定build时候的环境变量 可以在启动的容器的时候 通过-e覆盖 格式ENV name=value
ARG 构建参数 构建参数 只在构建的时候使用的参数 如果有ENV 那么ENV的相同名字的值始终覆盖arg的参数
VOLUME 定义外部可以挂载的数据卷 指定build的image那些目录可以启动的时候挂载到文件系统中 启动容器的时候使用 -v 绑定 格式 VOLUME ["目录"]
EXPOSE 暴露端口 定义容器运行的时候监听的端口 启动容器的使用-p来绑定暴露端口 格式: EXPOSE 8080 或者 EXPOSE 8080/udp
WORKDIR 工作目录 指定容器内部的工作目录 如果没有创建则自动创建 如果指定/ 使用的是绝对地址 如果不是/开头那么是在上一条workdir的路径的相对路径
USER 指定执行用户 指定build或者启动的时候 用户 在RUN CMD ENTRYPONT执行的时候的用户
HEALTHCHECK 健康检查 指定监测当前容器的健康监测的命令 基本上没用 因为很多时候 应用本身有健康监测机制
ONBUILD 触发器 当存在ONBUILD关键字的镜像作为基础镜像的时候 当执行FROM完成之后 会执行 ONBUILD的命令 但是不影响当前镜像 用处也不怎么大
STOPSIGNAL 发送信号量到宿主机 该STOPSIGNAL指令设置将发送到容器的系统调用信号以退出。
SHELL 指定执行脚本的shell 指定RUN CMD ENTRYPOINT 执行命令的时候 使用的shell

5.4 案例

5.4.1 自定义centos7镜像 。

  1. 默认登录路径为 :/usr.
  2. 可以使用vim
  3. 实现步骤 :
# 创建文件夹 
mkdir dockerfile

# 创建一个文件 
vim centos_dockerfile 

# 编写下面的命令 

# 定义父镜像 
From centos:7
# 定义作者的信息 
MAINTAINER lyp 
# 执行安装 vim 的命令 
RUN yum install -y vim 
# 设置工作路径 
WORKDIR / usr 
# 定义容器启动执行的命令 
CMD /bin/bash
# 将上面的命令 统一放到一个文件中 ,最后使用 命令执行该文件 
docker build -f ./存放命令的路径及文件名 -t 创建的镜像的名称 : 版本   .  (最后是一个点)

# 根据新创建的镜像创建容器
docker  run -it --name=lyp_centos lyp_centos:1 /bin/bash

5.4.2 定义 dockerfile,发布springBoot项目

  1. 将事先准备好的jar文件上传到linux服务器上面 。

  2. 将jar添加到容器中去。

  3. 实现步骤 :

# 定义父镜像 
FROM java:8 

# 定义作者信息 
MAINTAINER lyp

# 添加jar包到容器中 
ADD  项目名称 .jar app.jar 

# 执行命令 启动 
CMD  java -jar app.jar 

# 使用build命令执行上面的命令文件 
docker build -f ./存放命令的路径及文件名 -t 创建的镜像的名称 : 版本   .  (最后是一个点)

6. Docker相关概念

7. 服务编排

服务编排: 按照一定的业务规则批量管理容器

7.1 服务编排的概念

  1. 微服务架构的应用系统中一般包含若干个微服务,每个微服务一般都会部署多个实例,如果每个微服务都要手动启停,维护的工作量会很大。

  2. 要从Dockerfile build image 或者去dockerhub拉取image

  3. 要创建多个container

  4. 要管理这些container(启动停止删除)

7.2 Docker Compose概述

Docker Compose是一个编排多容器分布式部署的工具,提供命令集管理容器化应用的完整开发周期,包括服务构建,启动和停止。使用步骤:

  1. 利用 Dockerfile 定义运行环境镜像

  2. 使用 docker-compose.yml 定义组成应用的各服务

  3. 运行 docker-compose up 启动应用

7.3 Docker Compose 安装使用

7.4 案例

8. Docker容器数据卷

  1. 数据卷:是宿主机中的一个文件或者目录 。
  2. 当数据卷目录和数据卷目录绑定后,对方的修改会立即同步。
  3. 一个数据卷可以被多个容器同时挂载。

8.1 数据卷的概念以及作用

  1. 思考:当Docker 删除后在容器中产生的数据还存在吗?
  2. 思考:Docker容器和外部机器可以直接交换文件吗?
  3. 思考:容器之间如何进行数据的交互?

8.1.1数据卷的作用

  1. 容器数据持久化。
  2. 外部机器和容器之间的间接通信。
  3. 容器之间数据交换。

8.2 配置数据卷

  1. 创建启动容器的时候使用-v设置数据卷 。
  2. 创建的目录必须是绝对路径。
  3. 路径之间使用 ‘:’ 分割不能有空格。
  4. 如果目录不存在会自动创建。
  5. 可以挂在多个数据卷。
docker run -it --name=cf -v /root/data:/root/data-container /centos:7 /bin/bash

8.2.1 操作

1. 创建容器并挂在数据卷 : docker run -it --name=cf -v /root/data:/root/data-container :centos:7 /bin/bash
2. 在容器中创建文件 : touch a.txt
3. 在宿主机中写入文件 : echo 123 > b.txt 
-------------------------------------------
1. 创建一个守护模式的容器并挂载数据卷:docker run -id --name=ct -v /root/data:/root/data-container centos:7 
2. 进入到容器中 : docker exec -it ct /bin/bash 

-------------------------------------------
1. 创建容器并挂载两个以上数据卷 : docker run -it --name=cs -v ~/data:/root/data1\  -v ~/data:/root/data-container \ centos:7 /bin/bash      其中~代表 /root (但是只能将宿主机的路径写成这样) \ 代表没有写完命令 换行继续写。

8.2.2 两个容器同时挂载到一个数据卷上实现两个容器之间的数据交换

1. 创建第一个容器绑定到宿主机的data目录上 :  docker run -it --name=cp -v /root/data:/root/data centos:7   
2. 创建第二个容器也绑定到宿主机的data目录上:docker -it --name=cd -v /root/data:/root/data centos:7 
3. 在第二个容器中写入内容到c.txt文件 : echo cd-container > c.txt 
4. 查看该文件 : cat c.txt

8.3 配置数据卷容器

8.3.1 多容器进行数据交换

  1. 多个容器挂载同一个数据卷,但是工作其来比较麻烦。
  2. 数据卷容器。


    容器数据卷

8.3.2 操作

1. 创建c3数据卷容器,使用-v参数设置数据卷:docker run -it --name=c3 -v /volume centos:7 /bin/bash
2. 创建并启动c2 容器,使用 -volumes-from 参数设置数据卷 : docker run -it --name=c1 --volumes-from c3 centos:7 /bin/bash 
3. 创建并启动c2容器 ,使用 -volumes-from 参数设置数据卷:docker run -it --name=c2 --volumes-from c3 centos:7 /bin/bash
4. 查看容器详情 : docker inspect 容器名称
docker inspect 容器

9. Docker私有仓库

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