docker01 Docker入门

CentOS7安装Docker

[root@docker ~]# yum install -y docker
[root@docker ~]# systemctl start docker

# 也可以使用官方安装脚本
curl -sSL https://get.docker.com/ | sh
  • 运行镜像并打印hello world
[root@docker ~]# docker run busybox echo hello world
Unable to find image 'busybox:latest' locally
Trying to pull repository docker.io/library/busybox ... 
latest: Pulling from docker.io/library/busybox
e2334dd9fee4: Pull complete 
Digest: sha256:a8cf7ff6367c2afa2a90acd081b484cbded349a7076e7bdf37a05279f276bc12
Status: Downloaded newer image for docker.io/busybox:latest
hello world

# 容器一旦完成工作就会停止,使用-it参数可以获得一个交互式会话,但是退出终端,容器也会停止运行
  • 以后台方式运行Docker容器
[root@docker ~]# docker run -d -p 1234:1234 python:2.7 python -m SimpleHTTPServer 1234

创建、启动、停止和移除容器

  • create
  • start
  • stop:发送SIGTREM信号到容器,如果在一定时间内容器没有停止,则会再发送SIGKILL信号强制停止
  • kill:发送SIGKILL信号到容器
  • rm

一次性删除、停止所有容器

  • docker rm $(docker ps -a -q):-q参数只会返回容器的ID信息
  • docker rm -v $(docker ps -q):-v参数用来删除镜像中定义的数据卷
  • docker stop $(docker ps -a -q)

使用Dockerfile构建Docker镜像

# 创建一个名为Dockerfile的文件
[root@docker ~]# cat Dockerfile 
FROM busybox

ENV foo=bar

# 通过docker build命令构建一个新镜像,名为busybox2
[root@docker ~]# docker build -t busybox2 .
Sending build context to Docker daemon 465.9 kB
Step 1/2 : FROM busybox
 ---> be5888e67be6
Step 2/2 : ENV foo bar
 ---> Running in b823072a4306
 ---> 42c79cbf4787
Removing intermediate container b823072a4306
Successfully built 42c79cbf4787

# 查看新构建的镜像
[root@docker ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
busybox2            latest              42c79cbf4787        22 seconds ago      1.22 MB
docker.io/busybox   latest              be5888e67be6        3 weeks ago         1.22 MB

单一容器中使用Supervisor运行WordPress

  • 使用Supervisor来监控并运行MySQL和HTTPD.Supervisor不是一个init系统,二十一个用来控制多个进程的普通程序

两个链接在一起的容器运行WordPress

docker pull wordpress:latest
docker pull mysql:latest

# -e MYSQL_ROOT_PASSWORD:通过MYSQL_ROOT_PASSWORD环境变量来设置MySQL密码
docker run --name mysqlwp -e MYSQL_ROOT_PASSWORD=wordpressdocker -d mysql
docker run --name wordpress --link mysqlwp:mysql -p 80:80 wordpress

Docker设置MySQL环境变量

  • 通过设置环境变量来创建数据库
docker run --name mysqlwp -e MYSQL_ROOT_PASSWORD=wordpressdocker -e MYSQL_DATABASE=wordpress -e MYSQL_USER=wordpress -e MYSQL_PASSWORD=wordpresspwd -d mysql

docker run --name wordpress --link mysqlwp:mysql -p 80:80 -e WORDPRESS_DB_NAME=wordpress -e WORDPRESS_DB_USER=wordpress -e WORDPRESS_DB_PASSWORD=wordpresspwd -d wordpress

备份在容器中的数据库

  • 将宿主机的卷挂载到容器上
docker run --name mysqlwp -e MYSQL_ROOT_PASSWORD=wordpressdocker -e MYSQL_DATABASE=wordpress -e MYSQL_USER=wordpress -e MYSQL_PASSWORD=wordpresspwd -v /home/docker/mysql:/var/lib/mysql -d mysql
  • 进行备份
docker exec mysqlwp mysqldump --all-databases --password=wordpressdocker > wordpress.backup

宿主机容器之间共享数据

  • 通过-v选项将宿主机的卷挂载到容器中
  • 默认情况下会以读写的模式挂载数据卷.如果只想以只读模式挂载,可以在卷名称后通过冒号设置相应的权限
docker run -it -v $pwd:/cookbook centos:7.6.1810 /bin/bash
docker run -it -v $pwd:/cookbook:ro centos:7.6.1810 /bin/bash

# 通过docker inspect查看数据卷的挂载映射情况
docker inspect -f {{.Mounts}} 997
[{bind  /data /cookbook   true rprivate}]

容器之间共享数据

  • 通过-v选项省略宿主机的路径,就创建了一个称为数据容器的容器
docker run -it -v /cookbook centos:7.6.1810 /bin/bash
touch /share/test.txt
exit

# 在容器中创建的文件会存储在宿主机上
docker inspect -f {{.Mounts}} cbe
[{volume 9d4... /var/lib/docker/volumes/9d4.../_data /share local  true }]

ls /var/lib/docker/volumes/9d446354a6118f5e6b55bbee92ff5e6ef8a2ce19da60a16b1ef4a5cbeb13601d/_data/
test.txt
  • 将这个容器中的卷共享给其他容器,可以使用--volumes-from选项
docker run -v /data --name data centos:7.6.1810
# 这个容器没有处于运行状态,但是它的映射关系已经存在,并且被持久化到/var/lib/docker/volumes/下.可以通过docker rm -v data命令来删除容器和它的卷.
docker inspect -f {{.Mounts}} data
[{volume e5ebbc18722a2d43b81d373321aa183fd2452e9ae0f47c4f958e5fc8b3201468 /var/lib/docker/volumes/e5ebbc18722a2d43b81d373321aa183fd2452e9ae0f47c4f958e5fc8b3201468/_data /data local  true }]

# 即使容器没有被运行,也可以通过--volumes-from来挂载其中的卷
[root@docker supervisor]# docker run -it --volumes-from data centos:7.6.1810 /bin/bash
[root@2cf48f4d03f7 /]# ls /data/
[root@2cf48f4d03f7 /]# touch /data/qqq.txt
# 在宿主机上查看
[root@docker supervisor]# docker inspect -f {{.Mounts}} 2cf
[{ e5ebbc18722a2d43b81d373321aa183fd2452e9ae0f47c4f958e5fc8b3201468 /var/lib/docker/volumes/e5ebbc18722a2d43b81d373321aa183fd2452e9ae0f47c4f958e5fc8b3201468/_data /data local  true }]
[root@docker supervisor]# ls /var/lib/docker/volumes/e5ebbc18722a2d43b81d373321aa183fd2452e9ae0f47c4f958e5fc8b3201468/_data
qqq.txt

对容器数据进行复制

  • 有一个运行的容器没有进行任何卷映射,但是要从容器内复制数据出来或者将数据复制到容器里
# 启动一个容器,并让它执行睡眠状态
# 将文件从容器复制到宿主机
[root@docker supervisor]# docker run -d --name testcopy centos:7.6.1810 sleep 360
22161f5fb220205c7b451684b1075c1e5949874c30611c2bfea726ac8b5fa966
[root@docker supervisor]# docker exec -it testcopy /bin/bash
[root@22161f5fb220 /]# cd /root
[root@22161f5fb220 ~]# ls
anaconda-ks.cfg
[root@22161f5fb220 ~]# echo "hello" > file.txt
[root@22161f5fb220 ~]# exit
[root@docker ~]# docker cp testcopy:/root/file.txt .
[root@docker ~]# ls
anaconda-ks.cfg  file.txt  supervisor
[root@docker ~]# cat file.txt 
hello

# 将文件从宿主机复制到容器
[root@docker ~]# echo host > host.txt
[root@docker ~]# docker cp host.txt testcopy:/root
[root@docker ~]# docker exec -it testcopy /bin/bash
[root@22161f5fb220 /]# ls /root
anaconda-ks.cfg  file.txt  host.txt
[root@22161f5fb220 /]# cat /root/host.txt 
host

# 将一个容器中的文件复制到另一个容器
# 可以通过将临时主机作为文件的中转站,执行两次docker cp来实现
[root@docker ~]# docker cp testcopy:/root/file.txt /opt
[root@docker ~]# docker cp /opt/file.txt wordpress:/root
[root@docker ~]# docker exec -it wordpress /bin/bash
root@815a3662b5a6:/var/www/html# ls /root/
.bashrc   .profile  file.txt  
root@815a3662b5a6:/var/www/html# cat /root/file.txt
hello

创建和共享镜像

  • 在容器内部进行一些改动,对改动进行保存并且创建一个新镜像
[root@docker ~]# docker run -it ubuntu:14.04 /bin/bash
root@fa5f6c21e261:/# apt-get update
[root@docker ~]# docker commit fa5 ubuntu:update
sha256:76252050fc5133bf8b45891fb6a63cb83e274655b04d5bf1dbf2f1e9357a8347
[root@docker ~]# docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
ubuntu                update              76252050fc51        13 seconds ago      210 MB
docker.io/ubuntu      14.04               6e4f1fe62ff1        4 months ago        197 MB
  • 可以通过docker diff命令来查看容器中对镜像做出的修改
[root@docker ~]# docker diff fa5
C /root
A /root/.bash_history
C /run
D /run/secrets
# A表示文件或目录时新增加的
# C表示文件内容有修改
# D表示项目已经删除

将容器和镜像保存为tar文件进行共享

  • 对于已有镜像,可以使用Docker命令行的save和load命令来创建一个压缩包.对于容器,可以使用import和export进行导入导出操作
# 容器
[root@docker ~]# docker export cbe > update.tar
[root@docker ~]# ls
update.tar
[root@docker ~]# docker import - update < update.tar
sha256:f825b0e2c16889837bab19e9a899c0b612cc1f92383f053a25395762964d3312
[root@docker ~]# docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
update                latest              f825b0e2c168        5 seconds ago       202 MB
# 镜像
[root@docker ~]# docker save -o update1.tar update
[root@docker ~]# ls
update1.tar  update.tar
[root@docker ~]# docker rmi update
Untagged: update:latest
Deleted: sha256:f825b0e2c16889837bab19e9a899c0b612cc1f92383f053a25395762964d3312
Deleted: sha256:cfec12fe565a566d3dfdec679a709e337c20fb2a2eec598e1a968bbd1e5c4fc4
[root@docker ~]# docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
ubuntu                update              76252050fc51        7 minutes ago       210 MB
[root@docker ~]# docker load < update1.tar
cfec12fe565a: Loading layer [==================================================>] 210.2 MB/210.2 MB
Loaded image: update:latest
[root@docker ~]# docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
update                latest              f825b0e2c168        2 minutes ago       202 MB
ubuntu                update              76252050fc51        7 minutes ago       210 MB

编写Dockerfile

# ENTRYPOINT指令设置了从该镜像创建的容器启动时需要执行的命令
[root@docker ~]# cat Dockerfile
FROM ubuntu:14.04

ENTRYPOINT ["/bin/echo"]

# 构建镜像
[root@docker ~]# docker build .
Sending build context to Docker daemon 420.8 MB
Step 1/2 : FROM ubuntu:14.04
 ---> 6e4f1fe62ff1
Step 2/2 : ENTRYPOINT /bin/echo
 ---> Running in 61ae0c8d7a05
 ---> baca18a408d5
Removing intermediate container 61ae0c8d7a05
Successfully built baca18a408d5
[root@docker ~]# docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
<none>                <none>              baca18a408d5        7 seconds ago       197 MB
# 基于新构建的镜像启动容器
[root@docker ~]# docker run bac Hi Docker!
Hi Docker!

# 也可以使用CMD指令.这个指令的优点是,在启动容器时,通过在docker run命令后面指定新的CMD参数来覆盖Dockerfile文件中设置的内容
[root@docker ~]# cat Dockerfile 
FROM ubuntu:14.04

CMD ["/bin/echo","Hi Docker!"]
[root@docker ~]# docker build .
Sending build context to Docker daemon 420.8 MB
Step 1/2 : FROM ubuntu:14.04
 ---> 6e4f1fe62ff1
Step 2/2 : CMD /bin/echo Hi Docker!
 ---> Running in 3451081c9675
 ---> c22a69815ca9
Removing intermediate container 3451081c9675
Successfully built c22a69815ca9
[root@docker ~]# docker run c22
Hi Docker!
# 指定command,结果是Dockerfile中的CMD被覆盖
[root@docker ~]# docker run c22 /bin/date
Fri May  8 13:32:42 UTC 2020
  • 注意:CMD可以通过docker run后面的参数来覆盖,而ENTRYPOINT只能通过docker run的--entrypoint参数来覆盖
  • 指定仓库名和标签:docker build -t cookbook:hello .

将Flask打包到镜像

cat hello.py
#!/usr/bin/env python
from flask import Flask
app = Flask(__name__)

@app.route('/hi')
def hello_world():
    return "Hello World!"

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=5000)
cat Dockerfile
FROM ubuntu:14.04

RUN apt-get update
RUN apt-get install -y python
RUN apt-get install -y python-pip
RUN apt-get clean all

RUN pip install flask

ADD hello.py /tmp/hello.py

EXPOSE 5000

CMD ["python","/tmp/hello.py"]
  • 构建镜像:docker build -t flask .
  • 以后台方式运行镜像:docker run -d -P flask,-P选项表示随机映射一个宿主机端口到Dockerfile中设置的端口
  • 覆盖CMD指令:docker run -it -P flask python3 hello.py
  • 访问浏览器查看结果:10.0.0.180/hi

优化Dockerfile

  • 使用多条RUN指令安装软件包会向镜像增加不必要的层数
  • 更应该使用COPY而不是ADD,ADD用于更复杂的文件复制场景
FROM ubuntu:14.04

RUN apt-get update && apt-get install -y \
    python
    python-pip

RUN pip install flask

COPY hello.py /tmp/hello.py

EXPOSE 5000

CMD ["python","/tmp/hello.py"]
  • 使用官方python镜像
FROM python:3.6
RUN pip install flask

COPY hello.py /tmp/hello.py

EXPOSE 5000

CMD ["python3","/tmp/hello.py"]

通过标签对镜像进行版本管理

  • 通过标签对镜像和镜像的版本进行跟踪,而不是使用镜像ID

  • 使用docker tag命令为镜像打标签,允许对已有的镜像进行重命名,或者为同一个镜像创建新的标签

[root@docker supervisor]# docker tag docker.io/ubuntu:14.04 foobar
[root@docker supervisor]# docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
docker.io/ubuntu      14.04               6e4f1fe62ff1        4 months ago        197 MB
foobar                latest              6e4f1fe62ff1        4 months ago        197 MB

[root@docker supervisor]# docker tag ubuntu:14.04 foobar:cookbook
[root@docker supervisor]# docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
docker.io/ubuntu      14.04               6e4f1fe62ff1        4 months ago        197 MB
foobar                cookbook            6e4f1fe62ff1        4 months ago        197 MB
foobar                latest              6e4f1fe62ff1        4 months ago        197 MB
  • 正确设置标签镜像
[root@docker supervisor]# docker tag -h
Usage:  docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]

将镜像发布到Docker Hub

  • 在登录过程中,会将Docker Hub凭据保存到文件中
  • 账号:danche1013
[root@docker ~]# docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username (danche1013): danche1013
Password: 
Login Succeeded

[root@docker ~]# cat .docker/config.json 
{
    "auths": {
        "https://index.docker.io/v1/": {
            "auth": "..."
        }
    }

# 给镜像打标签
[root@docker ~]# docker tag flask how2dock/flask

# 上传镜像,需要注意镜像名遵循命令规则
# $ docker push <hub-user>/<repo-name>:<tag>
[root@docker ~]# docker tag  danche1013/how2dock/flask danche1013/study:how2dock_flask
[root@docker ~]# docker push danche1013/study:how2dock_flask 
The push refers to a repository [docker.io/danche1013/study]
f786468a7bca: Pushing [==================================================>]  2.56 kB
420c10a558b8: Pushing [==================================================>] 4.107 MB
c7e9a837d262: Pushing 3.584 kB
b0261a6321e2: Pushing [===>                                               ] 7.582 MB/121.6 MB
e8304754711e: Waiting 
b2463481acc8: Waiting 
7f8323ac7756: Waiting 
3da511183950: Waiting 
48dc77435ad5: Waiting 
f2fa9f4cf8fd: Waiting 

ONBUILD镜像

  • ONBUILD指令定义了一个会在未来执行的触发器.这个触发器是一个常规Dockerfile指令,比如RUN或ADD.包含ONBUILD指令的镜像称之为父镜像.当一个父镜像被用作基础镜像时(即通过FROM指令被引用),新镜像也被称为子镜像,子镜像构建过程中会触发在父镜像ONBUILD中定义的指令
  • 换句话说,父镜像会指定子镜像在构建时需要做些什么
  • 仍然可以在子镜像的Dockerfile文件中添加指令,但是父镜像中ONBUILD指令的内容会最先执行
# 案例
FROM node:0.12.6

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

ONBUILD COPY package.json /usr/src/app
ONBUILD RUN npm install
ONBUILD COPY . /usr/src/app

CMD ["npm","start"]

# 子镜像调用
FROM node:0.12.6-onbuild

# 在构建这个子镜像时,Docker会自动将package.json文件从本地复制到/usr/src/app

运行私有registry

  • 使用Docker registry镜像创建一个容器.这样就拥有一个私有的registry
[root@docker ~]# docker pull registry:2
[root@docker ~]# docker run -d -p 5000:5000 docker.io/registry:2
5f2b8888025efe5631981f2781cef602360f31203ebea51179a9d4f3a8ef982d
[root@docker ~]# curl -i http://localhost:5000/v2/
HTTP/1.1 200 OK
Content-Length: 2
Content-Type: application/json; charset=utf-8
Docker-Distribution-Api-Version: registry/2.0
X-Content-Type-Options: nosniff
Date: Sat, 09 May 2020 13:58:59 GMT

# 将镜像推送到私有仓库
[root@docker ~]# docker tag busybox localhost:5000/busy
[root@docker ~]# docker push localhost:5000/busy
The push refers to a repository [localhost:5000/busy]
5b0d2d635df8: Pushed 
latest: digest: sha256:a2490cec4484ee6c1068ba3a05f89934010c85242f736280b35343483b2264b6 size: 527
  • 如果从其他计算机访问私有仓库,会受到错误消息.编辑/etc/default/docker(Ubuntu)或/etc/docker/daemon.json(CentOS),Ubuntu添加:DOCKER_OPTS="--insecure-registry <IP_OF_REGISTRY>:5000",CentOS添加{"insecure-registries": ["10.0.0.111:5000"]}
  • 访问http://10.0.0.180:5000/v2/_catalog
image.png
[root@docker ~]# curl http://10.0.0.180:5000/v2/_catalog
{"repositories":["busy","flask"]}
  • 通过/v2/manifests/这个API来获得镜像的清单描述,其中<name>是镜像的名称,<reference>是镜像的标签
[root@docker ~]# curl http://10.0.0.180:5000/v2/busy/manifests/latest
{
   "schemaVersion": 1,
   "name": "busy",
   "tag": "latest",
   "architecture": "amd64",
   "fsLayers": [
      {
         "blobSum": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4"
      },
      {
         "blobSum": "sha256:e2334dd9fee4b77e48a8f2d793904118a3acf26f1f2e72a3d79c6cae993e07f0"
      }
   ],
  • 其中每一个blob都代表一个镜像层.可以通过registry API来进行上传、下载或删除blob操作
  • 通过/v2/tags/listURI列出一个镜像的所有标签
[root@docker ~]# curl http://10.0.0.180:5000/v2/flask/tags/list
{"name":"flask","tags":["latest"]}

[root@docker ~]# docker tag flask localhost:5000/flask:foobar
[root@docker ~]# docker push localhost:5000/flask:foobar
The push refers to a repository [localhost:5000/flask]
f786468a7bca: Layer already exists 
420c10a558b8: Layer already exists 
c7e9a837d262: Layer already exists 
b0261a6321e2: Layer already exists 
e8304754711e: Layer already exists 
b2463481acc8: Layer already exists 
7f8323ac7756: Layer already exists 
3da511183950: Layer already exists 
48dc77435ad5: Layer already exists 
f2fa9f4cf8fd: Layer already exists 
foobar: digest: sha256:7151e122a35714bafe3852585cd309c1c32ac181eb63c8e36fb407821e2cb69a size: 2417
[root@docker ~]# curl http://10.0.0.180:5000/v2/flask/tags/list
{"name":"flask","tags":["latest","foobar"]}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345