K8S

vi /etc/kubernets

部署第一个应用程序 中,我们使用了 kubectl 命令行界面部署了 nginx 并且查看了 Deployment 和 Pod。kubectl 还有如下四个常用命令,在我们排查问题时可以提供帮助:

kubectl get - 显示资源列表


# kubectl get 资源类型

#获取类型为Deployment的资源列表

kubectl get deployments     

 #获取类型为Pod的资源列表

kubectl get pods       

#获取类型为Node的资源列表

kubectl get nodes

名称空间

在命令后增加 -A 或 --all-namespaces 可查看所有 名称空间中 的对象,使用参数 -n 可查看指定名称空间的对象,例如


# 查看所有名称空间的 

Deploymentkubectl get deployments -A

kubectl get deployments --all-namespaces

# 查看 kube-system 名称空间的 

Deploymentkubectl get deployments -n kube-system

并非所有对象都在名称空间里

kubectl describe - 显示有关资源的详细信息


# kubectl describe 资源类型 资源名称

#查看名称为nginx-XXXXXX的Pod的信息

kubectl describe pod nginx-XXXXXX

#查看名称为nginx的Deployment的信息

kubectl describe deployment nginx

**kubectl logs** - 查看pod中的容器的打印日志(和命令docker logs 类似)

# kubectl logs Pod名称

#查看名称为nginx-pod-XXXXXXX的Pod内的容器打印的日志

本案例中的 nginx-pod 没有输出日志,所以您看到的结果是空的


kubectl logs -f nginx-pod-XXXXXXX

**kubectl exec** - 在pod中的容器环境内执行命令(和命令docker exec 类似)

kubectl exec Pod名称 操作命令# 在名称为nginx-pod-xxxxxx的Pod中运行bash


kubectl exec -it nginx-pod-xxxxxx /bin/bash

kubectl -n kube-system get po

kubectl -n kube-system get po -o wide

kubectl get node

journalctl -fu docker

journalctl -fu kubectl

kubectl get nodes

kubectl get pods

kubectl api-resources

docker logs -f <ID>

###查看namespace

kubectl get ns

###创建namespace

kubectl create ns nie

###配置好yaml,通过“one-pod.yaml”创建pod

kubectl create -f one-pod.yaml

在执行请求时设定namespace

执行 kubectl 命令时,可以使用 --namespace 参数指定名称空间,例如:

kubectl run nginx --image=nginx --namespace=<您的名称空间>
kubectl get pods --namespace=<您的名称空间>

设置名称空间偏好

可以通过 set-context 命令改变当前的名称空间,后续所有命令都默认在此名称空间下执行。

kubectl config set-context --current --namespace=<您的名称空间>
# 验证结果
kubectl config view --minify | grep namespace:

并非所有对象都在名称空间里

大部分的 Kubernetes 对象(例如,Pod、Service、Deployment、StatefulSet等)都必须在名称空间里。但是某些更低层级的对象,是不在任何名称空间中的,例如 [nodes]、[storageClass] 等

执行一下命令可查看哪些 Kubernetes 对象在名称空间里,哪些不在:

# 在名称空间里
kubectl api-resources --namespaced=true

# 不在名称空间里
kubectl api-resources --namespaced=false

查看名称空间
查看集群中的名称空间列表:

kubectl get namespaces

输出结果如下所示:

NAME          STATUS    AGE
default       Active    11d
kube-system   Active    11d
kube-public   Active    11d

Kubernetes 安装成功后,默认有初始化了三个名称空间:

default 默认名称空间,如果 Kubernetes 对象中不定义 metadata.namespace 字段,该对象将放在此名称空间下
kube-system Kubernetes系统创建的对象放在此名称空间下
kube-public 此名称空间自动在安装集群是自动创建,并且所有用户都是可以读取的(即使是那些未登录的用户)。主要是为集群预留的,例如,某些情况下,某些Kubernetes对象应该被所有集群用户看到。
查看名称空间的概要信息:

kubectl describe namespaces <name>

输出结果如下所示:

Name:           default
Labels:         <none>
Annotations:    <none>
Status:         Active

No resource quota.

Resource Limits
 Type       Resource    Min Max Default
 ----               --------    --- --- ---
 Container          cpu         -   -   100m

创建名称空间

使用 kubectl 有两种方式可以创建名称空间
1.通过 yaml 文件,创建文件 my-namespace.yaml 内容如下:

apiVersion: v1
kind: Namespace
metadata:
  name: <名称空间的名字>

执行命令

```
kubectl create -f ./my-namespace.yaml

```

2.直接使用命令创建名称空间:

```
kubectl create namespace <名称空间的名字>

```

在 Kuboard 中创建名称空间,请参考 [名称空间管理]

删除名称空间

执行如下命令可删除名称空间:

kubectl delete namespaces <名称空间的名字>

WARNING

该操作将删除名称空间中的所有内容

此删除操作是异步的,您可能会观察到名称空间停留会在 Terminating 状态停留一段时间。

使用名称空间切分集群)使用名称空间切分集群

理解-default-名称空间)理解 default 名称空间

默认情况下,安装Kubernetes集群时,会初始化一个 default 名称空间,用来将承载那些未指定名称空间的 Pod、Service、Deployment等对象

创建新的名称空间)创建新的名称空间

在此练习中,我们将创建两个 Kubernetes 名称空间。

假设企业使用同一个集群作为开发环境和生产环境(注意:通常开发环境和生产环境是物理隔绝的):

  • 开发团队期望有一个集群中的空间,以便他们可以查看查看和使用他们创建的 Pod、Service、Deployment等。在此空间中,Kubernetes对象被创建又被删除,为了适应敏捷开发的过程,团队中的许多人都可以在此空间内做他们想做的事情。
  • 运维团队也期望有一个集群中的空间,在这里,将有严格的流程控制谁可以操作 Pod、Service、Deployment等对象,因为这些对象都直接服务于生产环境。

此时,该企业可以将一个Kubernetes集群切分成两个名称空间:developmentproduction。创建名称空间的 yaml 文件如下所示:

apiVersion: v1
kind: Namespace
metadata:
  name: development
  labels:
    name: development

执行命令以创建 development 名称空间:

kubectl create -f https://kuboard.cn/statics/learning/namespace/dev.yaml

执行命令以创建 production 名称空间:

kubectl create -f https://kuboard.cn/statics/learning/namespace/prod.yaml

执行命令查看已创建的名称空间

kubectl get namespaces --show-labels

输出结果如下所示

NAME          STATUS    AGE       LABELS
default       Active    32m       <none>
development   Active    29s       name=development
production    Active    23s       name=production

在每个名称空间中创建-pod)在每个名称空间中创建 Pod

Kubernetes名称空间为集群中的 Pod、Service、Deployment 提供了一个作用域。可以限定使用某个名称空间的用户不能看到另外一个名称空间中的内容。我们可以在 development 名称空间中创建一个简单的 Deployment 和 Pod 来演示这个特性。

  • 首先,执行命令以检查当前的 [kubectl切换当前访问的集群)

    kubectl config view
    
    

    输出结果如下所示:

    apiVersion: v1
    clusters:
    - cluster:
        certificate-authority-data: REDACTED
        server: https://130.211.122.180
      name: lithe-cocoa-92103_kubernetes
    contexts:
    - context:
        cluster: lithe-cocoa-92103_kubernetes
        user: lithe-cocoa-92103_kubernetes
      name: lithe-cocoa-92103_kubernetes
    current-context: lithe-cocoa-92103_kubernetes
    kind: Config
    preferences: {}
    users:
    - name: lithe-cocoa-92103_kubernetes
      user:
        client-certificate-data: REDACTED
        client-key-data: REDACTED
        token: 65rZW78y8HbwXXtSXuUw9DbP4FLjHi4b
    - name: lithe-cocoa-92103_kubernetes-basic-auth
      user:
        password: h5M0FtUUIflBSdI7
        username: admin
    
    

    执行命令

    kubectl config current-context
    
    

    输出结果如下所示

    lithe-cocoa-92103_kubernetes
    
    
  • 接下来,为 kubectl 定义一个上下文,以便在不同的名称空间中工作。clusteruser 字段的取值从前面的 current context 复制过来:

    kubectl config set-context dev --namespace=development --cluster=lithe-cocoa-92103_kubernetes --user=lithe-cocoa-92103_kubernetes
    kubectl config set-context prod --namespace=production --cluster=lithe-cocoa-92103_kubernetes --user=lithe-cocoa-92103_kubernetes
    
    

    上面的命令创建了两个 kubectl 的上下文,使得您可以在两个不同的名称空间中工作:

  • 切换到 development 名称空间:

    kubectl config use-context dev
    
    

    验证

    kubectl config current-context
    dev
    
    

    此时,通过 kubectl 向 Kubernetes 集群发出的所有指令都限定在名称空间 development

    创建一个 nginx

    kubectl run snowflake --image=nginx:1.7.9 --replicas=2
    
    

    刚刚创建的 Deployment 副本数为 2,运行了一个 nginx 容器。

    kubectl get deployment
    
    

    输出结果如下

    NAME        DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    snowflake   2         2         2            2           2m
    
    

    执行命令

    kubectl get pods -l run=snowflake
    
    

    输出结果如下

    NAME                         READY     STATUS    RESTARTS   AGE
    snowflake-3968820950-9dgr8   1/1       Running   0          2m
    snowflake-3968820950-vgc4n   1/1       Running   0          2m
    
    

    此时,开发人员可以做任何他想要做的操作,所有操作都限定在名称空间 development 里,而无需担心影响到 production 名称空间中的内容。

  • 切换到 production 名称空间:

    kubectl config use-context prod
    
    

    production 名称空间应该是空的,下面两个命令将返回的结果都应该为空:

    kubectl get deployment
    kubectl get pods
    
    

    此时,我们在 production 名称空间运行另一个 deployment:

    kubectl run cattle --image=nginx:1.7.9 --replicas=5
    kubectl get deployment
    
    

    输出结果如下所示:

    NAME      DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    cattle    5         5         5            5           10s
    
    

    执行命令

    kubectl get pods -l run=cattle
    
    

    输出结果如下所示:

    NAME                      READY     STATUS    RESTARTS   AGE
    cattle-2263376956-41xy6   1/1       Running   0          34s
    cattle-2263376956-kw466   1/1       Running   0          34s
    cattle-2263376956-n4v97   1/1       Running   0          34s
    cattle-2263376956-p5p3i   1/1       Running   0          34s
    cattle-2263376956-sxpth   1/1       Running   0          34s
    
    

    至此,我们可以了解到,用户在一个名称空间创建的内容对于另外一个名称空间来说是不可见的。

    也可以为不同的名称空间定义不同的访问权限控制。敬请期待后续更新。

标签的 value 必须:

  • 不能多于 63 个字符
  • 可以为空字符串
  • 如果不为空,则
    • 必须由字母、数字开始和结尾
    • 可以包含字母、数字、减号-、下划线_、小数点.

例如,下面的例子中的Pod包含两个标签 environment: productionapp:nginx

apiVersion: v1
kind: Pod
metadata:
  name: label-demo
  labels:
    environment: production
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
    ports:
    - containerPort: 80

标签选择器)标签选择器

与 [name 和 UID]不同,标签不一定是唯一的。通常来讲,会有多个Kubernetes对象包含相同的标签。通过使用标签选择器(label selector),用户/客户端可以选择一组对象。标签选择器(label selector)是 Kubernetes 中最主要的分类和筛选手段。
Kubernetes api server支持两种形式的标签选择器,equality-based 基于等式的set-based 基于集合的。标签选择器可以包含多个条件,并使用逗号分隔,此时只有满足所有条件的 Kubernetes 对象才会被选中。
如果使用空的标签选择器或者不指定选择器,其含义由具体的 API 接口决定。

基于等式的选择方式)基于等式的选择方式

Equality- 或者 inequality-based 选择器可以使用标签的名和值来执行过滤选择。只有匹配所有条件的对象才被选中(被选中的对象可以包含未指定的标签)。可以使用三种操作符 ===!=。前两个操作符含义是一样的,都代表相等,后一个操作符代表不相等。例如:

# 选择了标签名为 `environment` 且 标签值为 `production` 的Kubernetes对象
environment = production
# 选择了标签名为 `tier` 且标签值不等于 `frontend` 的对象,以及不包含标签 `tier` 的对象
tier != frontend

也可以使用逗号分隔的两个等式 environment=production,tier!=frontend,此时将选中所有 environmentproductiontier 不为 frontend 的对象。
以 [Pod 的节点选择器]节点选择器-nodeselector) 为例,下面的 Pod 可以被调度到包含标签 accelerator=nvidia-tesla-p100 的节点上:

apiVersion: v1
kind: Pod
metadata:
  name: cuda-test
spec:
  containers:
    - name: cuda-test
      image: "k8s.gcr.io/cuda-vector-add:v0.1"
      resources:
        limits:
          nvidia.com/gpu: 1
  nodeSelector:
    accelerator: nvidia-tesla-p100

基于集合的选择方式)基于集合的选择方式

Set-based 标签选择器可以根据标签名的一组值进行筛选。支持的操作符有三种:innotinexists。例如:

# 选择所有的包含 `environment` 标签且值为 `production` 或 `qa` 的对象
environment in (production, qa)
# 选择所有的 `tier` 标签不为 `frontend` 和 `backend`的对象,或不含 `tier` 标签的对象
tier notin (frontend, backend)
# 选择所有包含 `partition` 标签的对象
partition
# 选择所有不包含 `partition` 标签的对象
!partition

可以组合多个选择器,用 , 分隔,, 相当于 AND 操作符。例如:

# 选择包含 `partition` 标签(不检查标签值)且 `environment` 不是 `qa` 的对象
partition,environment notin (qa)

基于集合的选择方式是一个更宽泛的基于等式的选择方式,例如,environment=production 等价于 environment in (production)environment!=production 等价于 environment notin (production)
基于集合的选择方式可以和基于等式的选择方式可以混合使用,例如: partition in (customerA, customerB),environment!=qa

API

查询条件

LIST 和 WATCH 操作时,可指定标签选择器作为查询条件,以筛选指定的对象集合。两种选择方式都可以使用,但是要符合 URL 编码,例如:

  • 基于等式的选择方式: ?labelSelector=environment%3Dproduction,tier%3Dfrontend
  • 基于集合的选择方式: ?labelSelector=environment+in+%28production%2Cqa%29%2Ctier+in+%28frontend%29

两种选择方式都可以在 kubectl 的 list 和 watch 命令中使用,例如:

  • 使用基于等式的选择方式

    kubectl get pods -l environment=production,tier=frontend
    
    
  • 使用基于集合的选择方式

    kubectl get pods -l 'environment in (production),tier in (frontend)'
    
    

#Kubernetes对象引用

某些 Kubernetes 对象中(例如,Service和Deployment),使用标签选择器指定一组其他类型的 Kubernetes 对象(例如,Pod)

#Service

Service 中通过 spec.selector 字段来选择一组 Pod,并将服务请求转发到选中的 Pod 上。

在 yaml 或 json 文件中,标签选择器用一个 map 来定义,且支持基于等式的选择方式,例如:

"selector": {
  "component" : "redis",
}

selector:
  component: redis

上面的例子中定义的标签选择器等价于 component=rediscomponent in (redis)

#有些对象支持基于集合的选择方式

JobDeploymentReplicaSetDaemonSet 同时支持基于等式的选择方式和基于集合的选择方式。例如:

selector:
  matchLabels:
    component: redis
  matchExpressions:
    - {key: tier, operator: In, values: [cache]}
    - {key: environment, operator: NotIn, values: [dev]}

matchLabels 是一个 {key,value} 组成的 map。map 中的一个 {key,value} 条目相当于 matchExpressions 中的一个元素,其 key 为 map 的 key,operatorInvalues 数组则只包含 value 一个元素。matchExpression 等价于基于集合的选择方式,支持的 operatorInNotInExistsDoesNotExist。当 operatorInNotIn 时,values 数组不能为空。所有的选择条件都以 AND 的形式合并计算,即所有的条件都满足才可以算是匹配。

引用一组节点

可以通过标签选择器将 Pod 调度到指定的节点上,请参考 [将容器组调度到指定的节点]

向Kubernetes对象添加注解
Kubernetes 对象的 metadata 字段可以添加自定义的标签(label)或者注解(annotation)。标签用来选择对象或者用来查找符合指定条件的一组对象。与此相对,注解不是用来标记对象或者选择对象的。metadata 中的注解可以很大,也可以很小;可以是结构化的,也可以是非结构化的;还可以包括标签中不允许出现的字符。

与标签相似,注解也是 key/value map,例如:

metadata:
  annotations:
    key1: value1
    key2: value2

类似于下面的信息可以记录在注解中:

声明式配置层用到的状态信息。
Build、release、image信息,例如 timestamp、release ID、git branch、PR number、image hash、registry address
日志、监控、分析、审计系统的参数
第三方工具所需要的信息,例如 name、version、build information、URL
轻量级的发布工具用到的信息,例如,config、checkpoint
负责人的联系方式,例如,电话号码、网址、电子信箱
用户用来记录备忘信息的说明,例如,对标准镜像做了什么样的修改、维护过程中有什么特殊信息需要记住
下面是一个来自于实际 Deployment 的注解:

metadata:
  annotations:
    deployment.kubernetes.io/revision: 7  # 由Deployment控制器添加,用于记录当前发布的修改次数
    k8s.eip.work/displayName: busybox   # Kuboard添加,Deployment显示在Kuboard界面上的名字
    k8s.eip.work/ingress: false     # Kuboard添加,根据此参数显示Deployment是否配置了Ingress
    k8s.eip.work/service: none      # Kuboard添加,根据此参数显示Deployment是否配置了Service
除了使用注解,您也可以将这类信息存放在一个外部的数据库,然而,在使用、分享这些信息的时候,可能会变得难以管理。

句法和字符集

注解是一组名值对。

注解的 key 有两个部分:可选的前缀和标签名,通过 / 分隔。

注解名:
标签名部分是必须的
不能多于 63 个字符
必须由字母、数字开始和结尾
可以包含字母、数字、减号-、下划线_、小数点.
注解前缀:
注解前缀部分是可选的
如果指定,必须是一个DNS的子域名,例如:k8s.eip.work
不能多于 253 个字符
使用 / 和标签名分隔
如果省略注解前缀,则注解的 key 将被认为是专属于用户的。Kubernetes的系统组件(例如,kube-scheduler、kube-controller-manager、kube-apiserver、kubectl 或其他第三方组件)向用户的Kubernetes对象添加注解时,必须指定一个前缀。

kubernetes.io/ 和 k8s.io/ 这两个前缀是 Kubernetes 核心组件预留的。Kuboard 使用 k8s.eip.work 这个前缀。

下面的例子中,Pod包含一个注解 imageregistry: https://hub.docker.com/

apiVersion: v1
kind: Pod
metadata:
  name: annotations-demo
  annotations:
    imageregistry: "https://hub.docker.com/"
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
    ports:
    - containerPort: 80

概述

字段选择器(Field Selector)可以用来基于的一个或多个字段的取值来选取一组Kubernetes对象。下面有一些示例性的字段选择器:

  • metadata.name=my-service
  • metadata.namespace!=default
  • status.phase=Pending

下面的 kubectl 命令选择了所有字段 status.phase 的取值为 Running 的 Pod:

kubectl get pods --field-selector status.phase=Running

字段选择器本质上是一个 filter。默认情况下,没有添加 selector/filter 时,代表着指定资源类型的所有对象都被选中。下面个两个 kubectl 查询时等价的:

kubectl get pods
kubectl get pods --field-selector ""

#支持的字段

不同的 Kubernetes 对象类型,可以用来查询的字段不一样。所有的对象类型都支持的两个字段是 metadata.namemetadata.namespace。在字段选择器中使用不支持的字段,将报错。例如:

kubectl get ingress --field-selector foo.bar=baz

输出结果为:

Error from server (BadRequest): Unable to find "ingresses" that match label selector "", field selector "foo.bar=baz": "foo.bar" is not a known field selector: only "metadata.name", "metadata.namespace"

支持的操作符

字段选择器中可以使用的操作符有 ===!==== 含义相同)。例如,下面的 kubectl 命令,查询不在 default 名称空间中的 Service:

kubectl get services  --all-namespaces --field-selector metadata.namespace!=default

多选择器

可以指定多个字段选择器,用逗号 , 分隔。下面的 kubectl 命令查询所有的 status.phase 不等于 Runningspec.restartPolicy 等于 Always 的 Pod:

kubectl get pods --field-selector=status.phase!=Running,spec.restartPolicy=Always

多种对象类型

字段选择器可以跨资源类型使用。下面的 kubectl 命令查询所有的不在 default 名称空间的 StatefulSet 和 Service:

kubectl get statefulsets,services --all-namespaces --field-selector metadata.namespace!=default

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

推荐阅读更多精彩内容