k8s运行容器之deployment(三)

deployment

我们已经知道k8s是通过各种controller来管理pod的生命周期。为了满足不同业务场景,k8s开发了Deployment、ReplicaSet、DaemonSet、StatefuleSet、Job 等多种 Controller。我们首先学习最常用的 Deployment。

运行一个deployment

[root@ken ~]# kubectl run httpd-ken1--generator=run-pod/v1 --image=httpd --replicas=2

下面详细分析 Kubernetes 都做了些什么工作。

[root@ken ~]# kubectl get deployment
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
httpd-ken   2/2     2            2           35m

kubectl get deplouyment命令可以查看 httpd-ken 的状态,输出显示两个副本正常运行。

接下来我们用 kubectl describe deployment 了解更详细的信息。

[root@ken ~]# kubectl  describe deployment httpd-ken
Name:                   httpd-ken
Namespace:              default
CreationTimestamp:      Tue, 29 Jan 2019 15:27:40 +0800
Labels:                 run=httpd-ken
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               run=httpd-ken
Replicas:               2 desired | 2 updated | 2 total | 2 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  run=httpd-ken
  Containers:
   httpd-ken:
    Image:        httpd
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   httpd-ken-5c949b96f (2/2 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  18m   deployment-controller  Scaled up replica set httpd-ken-5c949b96f to 2

大部分内容都是自解释的,我们重点看最下面部分。这里告诉我们创建了一个 ReplicaSet httpd-ken-5c949b96,Events 是 Deployment 的日志,记录了 ReplicaSet 的启动过程。

通过上面的分析,也验证了 Deployment 通过 ReplicaSet 来管理 Pod 的事实。接着我们将注意力切换到 httpd-ken-5c949b96,执行 kubectl describe replicaset:

[root@ken ~]# kubectl get replicaset
NAME                  DESIRED   CURRENT   READY   AGE
httpd-ken-5c949b96f   2         2         2       20m

两个副本已经就绪,用 kubectl describe replicaset 查看详细信息:

[root@ken ~]# kubectl describe replicaset
Name:           httpd-ken-5c949b96f
Namespace:      default
Selector:       pod-template-hash=5c949b96f,run=httpd-ken
Labels:         pod-template-hash=5c949b96f
                run=httpd-ken
Annotations:    deployment.kubernetes.io/desired-replicas: 2
                deployment.kubernetes.io/max-replicas: 3
                deployment.kubernetes.io/revision: 1
Controlled By:  Deployment/httpd-ken
Replicas:       2 current / 2 desired
Pods Status:    2 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
  Labels:  pod-template-hash=5c949b96f
           run=httpd-ken
  Containers:
   httpd-ken:
    Image:        httpd
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  20m   replicaset-controller  Created pod: httpd-ken-5c949b96f-twdsd
  Normal  SuccessfulCreate  20m   replicaset-controller  Created pod: httpd-ken-5c949b96f-9cd52

Controlled By 指明此 ReplicaSet 是由 Deployment httpd-ken 创建。Events 记录了两个副本 Pod 的创建。接着我们来看 Pod,执行 kubectl get pod:

[root@ken ~]# kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
httpd-ken-5c949b96f-9cd52   1/1     Running   0          22m
httpd-ken-5c949b96f-twdsd   1/1     Running   0          22m

两个副本 Pod 都处于 Running 状态,用 kubectl describe pod 查看更详细的信息:

root@ken ~]# kubectl describe pod
Name:               httpd-ken-5c949b96f-9cd52
Namespace:          default
Priority:           0
PriorityClassName:  <none>
Node:               host1/172.20.10.7
Start Time:         Tue, 29 Jan 2019 15:46:45 +0800
Labels:             pod-template-hash=5c949b96f
                    run=httpd-ken
Annotations:        <none>
Status:             Running
IP:                 10.244.1.3
Controlled By:      ReplicaSet/httpd-ken-5c949b96f
Containers:
  httpd-ken:
    Container ID:   docker://e59bda9941a16f20027c89a0d8fa8e17797b517f6f5461e905c0d29b57369dde
    Image:          httpd
    Image ID:       docker-pullable://httpd@sha256:44daa8e932a32ab6e50636d769ca9a60ad412124653707e5ed59c0209c72f9b3
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Tue, 29 Jan 2019 15:47:10 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-vb7lm (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  default-token-vb7lm:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-vb7lm
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  23m   default-scheduler  Successfully assigned default/httpd-ken-5c949b96f-9cd52 to host1
  Normal  Pulling    23m   kubelet, host1     pulling image "httpd"
  Normal  Pulled     22m   kubelet, host1     Successfully pulled image "httpd"
  Normal  Created    22m   kubelet, host1     Created container
  Normal  Started    22m   kubelet, host1     Started container

Controlled By 指明此 Pod 是由 ReplicaSet httpd-ken-5c949b96f创建。Events 记录了 Pod 的启动过程。如果操作失败(比如 image 不存在),也能在这里查看到原因。

总结一下这个过程:

用户通过 kubectl 创建 Deployment。

Deployment 创建 ReplicaSet。

ReplicaSet 创建 Pod

也可以看出,对象的命名方式是:子对象的名字 = 父对象名字 + 随机字符串或数字。

命令vs配置文件

k8s支持两种创建资源的方式:

1.用 kubectl 命令直接创建,比如:

kubectl run nginx-deployment –image=nginx:1.7.9 –replicas=2

在命令行中通过参数指定资源的属性。

2. 通过配置文件和 kubectl apply 创建,要完成前面同样的工作,可执行命令:

kubectl apply -f nginx.yml

nginx.yml 的内容为:

image

资源的属性写在配置文件中,文件格式为 YAML。

下面对这两种方式进行比较。

基于命令的方式:

简单直观快捷,上手快。

适合临时测试或实验。

基于配置文件的方式:

配置文件描述了 What,即应用最终要达到的状态。

配置文件提供了创建资源的模板,能够重复部署。

可以像管理代码一样管理部署。

适合正式的、跨环境的、规模化部署。

这种方式要求熟悉配置文件的语法,有一定难度。

后面我们都将采用配置文件的方式,大家需要尽快熟悉和掌握。

kubectl apply 不但能够创建 Kubernetes 资源,也能对资源进行更新,非常方便。不过 Kubernets 还提供了几个类似的命令,例如 kubectl create、kubectl replace、kubectl edit 和 kubectl patch。

为避免造成不必要的困扰,我们会尽量只使用 kubectl apply,

此命令已经能够应对超过 90% 的场景,事半功倍。

deployment yaml

既然要用 YAML 配置文件部署应用,现在就很有必要了解一下 Deployment 的配置格式,其他 Controller(比如 DaemonSet)非常类似。

image

① apiVersion 是当前配置格式的版本。

先执行kubectl api-resources找到所有的资源

在执行命令 kubectl explain deploy即可获取到版本和类型信息

image

② kind 是要创建的资源类型,这里是 Deployment。

③ metadata 是该资源的元数据,name 是必需的元数据项。

④ spec 部分是该 Deployment 的规格说明。

⑤ replicas 指明副本数量,默认为 1。

⑥ template 定义 Pod 的模板,这是配置文件的重要部分。

⑦ metadata 定义 Pod 的元数据,至少要定义一个 label。label 的 key 和 value 可以任意指定。

⑧ spec 描述 Pod 的规格,此部分定义 Pod 中每一个容器的属性,name 和 image 是必需的。

注意:最后name前面加个横线是因为container后面是一个列表

此 nginx.yml 是一个最简单的 Deployment 配置文件,后面我们学习 Kubernetes 各项功能时会逐步丰富这个文件。

执行 kubectl apply -f nginx.yml:

[root@ken ~]# kubectl apply -f nginx.yml
deployment.extensions/nginx-deployment created

查看nginx-deployment各种资源

[root@ken ~]# kubectl get deployment
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
httpd-ken          2/2     2            2           73m
nginx-deployment   2/2     2            2           107s
[root@ken ~]# kubectl get replicaset
NAME                          DESIRED   CURRENT   READY   AGE
httpd-ken-5c949b96f           2         2         2       54m
nginx-deployment-65998d8886   2         2         2       111s
[root@ken ~]# kubectl get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE    IP           NODE    NOMINATED NODE   READINESS GATES
httpd-ken-5c949b96f-9cd52           1/1     Running   0          54m    10.244.1.3   host1   <none>           <none>
httpd-ken-5c949b96f-twdsd           1/1     Running   0          54m    10.244.2.3   host2   <none>           <none>
nginx-deployment-65998d8886-9qrrv   1/1     Running   0          2m4s   10.244.2.4   host2   <none>           <none>
nginx-deployment-65998d8886-vnbgt   1/1     Running   0          2m4s   10.244.1.4   host1   <none>           <none>

Deployment、ReplicaSet、Pod 都已经就绪。如果要删除这些资源,执行 kubectl delete deployment nginx-deployment 或者 kubectl delete -f nginx.yml (编写的nginx.yml文件不会被删除)。

[root@ken ~]# kubectl delete -f nginx.yml
deployment.extensions "nginx-deployment" deleted

Scale Up/Down

伸缩(Scale Up/Down)是指在线增加或减少 Pod 的副本数。

Deployment nginx-deployment 初始是两个副本。

[root@ken ~]# kubectl apply -f nginx.yml
[root@ken ~]# kubectl get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP           NODE    NOMINATED NODE   READINESS GATES
nginx-deployment-65998d8886-5b5rc   1/1     Running   0          84s   10.244.1.5   host1   <none>           <none>
nginx-deployment-65998d8886-tnpcx   1/1     Running   0          84s   10.244.2.5   host2   <none>           <none>

k8s-node1 和 k8s-node2 上各跑了一个副本。现在修改 nginx.yml,将副本改成 5 个。

image

再次执行kubectl apply

[root@ken ~]# kubectl apply -f nginx.yml
deployment.extensions/nginx-deployment configured

查看pod

[root@ken ~]# kubectl get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE     IP           NODE    NOMINATED NODE   READINESS GATES
nginx-deployment-65998d8886-4hfgp   1/1     Running   0          3m      10.244.1.7   host1   <none>           <none>
nginx-deployment-65998d8886-5b5rc   1/1     Running   0          5m48s   10.244.1.5   host1   <none>           <none>
nginx-deployment-65998d8886-btrsq   1/1     Running   0          3m      10.244.2.6   host2   <none>           <none>
nginx-deployment-65998d8886-tnpcx   1/1     Running   0          5m48s   10.244.2.5   host2   <none>           <none>
nginx-deployment-65998d8886-x4pbd   1/1     Running   0          3m      10.244.1.6   host1   <none>           <none>

三个新副本被创建并调度到 k8s-node1 和 k8s-node2 上。

接下来修改配置文件,将副本数减少为 3 个,重新执行 kubectl apply:

[root@ken ~]# kubectl apply -f nginx.yml
deployment.extensions/nginx-deployment configured
[root@ken ~]# kubectl get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE     IP           NODE    NOMINATED NODE   READINESS GATES
nginx-deployment-65998d8886-5b5rc   1/1     Running   0          7m6s    10.244.1.5   host1   <none>           <none>
nginx-deployment-65998d8886-btrsq   1/1     Running   0          4m18s   10.244.2.6   host2   <none>           <none>
nginx-deployment-65998d8886-tnpcx   1/1     Running   0          7m6s    10.244.2.5   host2   <none>           <none>

可以看到两个副本被删除,最终保留了 3 个副本。

模拟故障

上面我们有 3 个 nginx 副本分别运行在 k8s-node1 和 k8s-node2 上。现在模拟 k8s-node2 故障,关闭该节点(poweroff)。

首先查看节点

[root@ken ~]# kubectl get node
NAME    STATUS     ROLES    AGE     VERSION
host1   Ready      <none>   5h25m   v1.13.2
host2   NotReady   <none>   5h43m   v1.13.2
ken     Ready      master   6h18m   v1.13.2

发现host2状态为NotReady

等待一段时间,Kubernetes 会检查到 k8s-node2 不可用,将 k8s-node2 上的 Pod 标记为 Terminating状态,并在 k8s-node1 上新创建两个 Pod,维持总副本数为 3。

[root@ken ~]# kubectl get pod -o wide
NAME                                READY   STATUS        RESTARTS   AGE   IP           NODE    NOMINATED NODE   READINESS GATES
nginx-deployment-65998d8886-5b5rc   1/1     Running       0          16m   10.244.1.5   host1   <none>           <none>
nginx-deployment-65998d8886-8647d   1/1     Running       0          79s   10.244.1.8   host1   <none>           <none>
nginx-deployment-65998d8886-btrsq   1/1     Terminating   0          13m   10.244.2.6   host2   <none>           <none>
nginx-deployment-65998d8886-qp6jj   1/1     Running       0          79s   10.244.1.9   host1   <none>           <none>
nginx-deployment-65998d8886-tnpcx   1/1     Terminating   0          16m   10.244.2.5   host2   <none>           <none>

当 k8s-node2 恢复后, Terminating的 Pod 会被删除,不过已经运行的 Pod 不会重新调度回 k8s-node2。

[root@ken ~]# kubectl get node
NAME    STATUS   ROLES    AGE     VERSION
host1   Ready    <none>   5h33m   v1.13.2
host2   Ready    <none>   5h51m   v1.13.2
ken     Ready    master   6h26m   v1.13.2
[root@ken ~]# kubectl get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE     IP           NODE    NOMINATED NODE   READINESS GATES
nginx-deployment-65998d8886-5b5rc   1/1     Running   0          19m     10.244.1.5   host1   <none>           <none>
nginx-deployment-65998d8886-8647d   1/1     Running   0          4m33s   10.244.1.8   host1   <none>           <none>
nginx-deployment-65998d8886-qp6jj   1/1     Running   0          4m33s   10.244.1.9   host1   <none>           <none>

删除 nginx-deployment:

[root@ken ~]# kubectl delete -f nginx.yml
deployment.extensions "nginx-deployment" deleted

label 控制 Pod 的位置

默认配置下,Scheduler 会将 Pod 调度到所有可用的 Node。不过有些情况我们希望将 Pod 部署到指定的 Node,比如将有大量磁盘 I/O 的 Pod 部署到配置了 SSD 的 Node;或者 Pod 需要 GPU,需要运行在配置了 GPU 的节点上。

Kubernetes 是通过 label 来实现这个功能的。

label 是 key-value 对,各种资源都可以设置 label,灵活添加各种自定义属性。比如执行如下命令标注 k8s-node1 是配置了 SSD 的节点。

第一步:定义标签

disk为自定义字符串

[root@ken ~]# kubectl label node host1 disk=ssd

第二步:查看标签

[root@ken ~]# kubectl get node --show-labels
NAME    STATUS   ROLES    AGE   VERSION   LABELS
host1   Ready    <none>   8h    v1.13.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,disk=ssd,kubernetes.io/hostname=host1
host2   Ready    <none>   8h    v1.13.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=host2
ken     Ready    master   8h    v1.13.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=ken,node-role.kubernetes.io/master=

disk=ssd 已经成功添加到 host1,除了 disk,Node 还有几个 Kubernetes 自己维护的 label。

第三步:配置nginx.yml

有了 disk 这个自定义 label,接下来就可以指定将 Pod 部署到 host1。编辑 nginx.yml:

image

在 Pod 模板的 spec 里通过 nodeSelector 指定将此 Pod 部署到具有 label disktype=ssd 的 Node 上。

注意:1. nodeSelector需要与containers位置保持一致

2. S必须大写

第四步:部署

部署 Deployment

[root@ken ~]# kubectl apply -f nginx.yml
deployment.extensions/nginx-deployment created

第五步:查看 Pod 的运行节点

[root@ken ~]# kubectl get pod -o wide
NAME                                READY   STATUS              RESTARTS   AGE    IP       NODE    NOMINATED NODE   READINESS GATES
nginx-deployment-5d8db4598d-2gdmz   0/1     ContainerCreating   0          102s   <none>   host1   <none>           <none>
nginx-deployment-5d8db4598d-cq55q   0/1     ContainerCreating   0          102s   <none>   host1   <none>           <none>
nginx-deployment-5d8db4598d-qjh4x   0/1     ContainerCreating   0          102s   <none>   host1   <none>           <none>

全部 3个副本都运行在 host1 上,符合我们的预期。

要删除 label disktype,执行如下命令:

kubectl label node k8s-node1 disktype-

– 即删除。

[root@ken ~]# kubectl label node host1 disk-
node/host1 labeled
[root@ken ~]# kubectl get node --show-labels
NAME    STATUS     ROLES    AGE   VERSION   LABELS
host1   NotReady   <none>   8h    v1.13.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=host1
host2   Ready      <none>   8h    v1.13.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=host2
ken     Ready      master   9h    v1.13.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=ken,node-role.kubernetes.io/master=

不过此时 Pod 并不会重新部署,依然在 host1 上运行。

[root@ken ~]# kubectl get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
nginx-deployment-5d8db4598d-2dbw9   1/1     Running   0          39s   10.244.1.12   host1   <none>           <none>
nginx-deployment-5d8db4598d-4brh5   1/1     Running   0          39s   10.244.1.11   host1   <none>           <none>
nginx-deployment-5d8db4598d-p87mj   1/1     Running   0          39s   10.244.1.13   host1   <none>           <none>

除非在 nginx.yml 中删除 nodeSelector 设置,然后通过 kubectl apply 重新部署。

不需要删除之前的deployment,直接部署即可

Kubernetes 自己会删除之前的 Pod 并调度和运行新的 Pod。

[root@ken ~]# kubectl apply -f nginx.yml
[root@ken ~]# kubectl get pod -o wide
NAME                                READY   STATUS        RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
nginx-deployment-5d8db4598d-p87mj   0/1     Terminating   0          2m    10.244.1.13   host1   <none>           <none>
nginx-deployment-65998d8886-t5nmv   1/1     Running       0          7s    10.244.2.9    host2   <none>           <none>
nginx-deployment-65998d8886-wz7c2   1/1     Running       0          4s    10.244.2.10   host2   <none>           <none>
nginx-deployment-65998d8886-xdlz4   1/1     Running      

文章转自:http://www.kendd.cn/?p=685

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