好玩的K8s之读书笔记01:《K8权威指南》(第4版)

下内容整理自这本书的读书笔记:《Kubernetes权威指南:从Docker到Kubernetes实践全接触(第4版),各大书店有售

一、无状态的服务

1.pod

1)基本概念

pod的组成


pod、容器和node之间的关系
Pod与周边对象


2)配置文件样例

apiVersion: v1

kind: Pod

metadata:

  name: myweb

  labels:

    app: myweb

spec:

  containers:

  - name: myweb

    image: kubeguide/tomcat-app:v1

    ports:

    - containerPort: 8080

    env:

    - name: MYSQL_SERVICE_HOST

      value: 'mysql'

    - name: MYSQL_SERVICE_PORT

      value: '3306'

3)命令行例子

kubectl get pod -o wide


kubectl describe pod 

里面的IP是POD的IP

kubectl describe pod frontend-5cb785b459-6cn6w

里面有个container可以查看pod里各个容器的情况:包括容器name、image,服务端口等,加上ip就可以获取EndPoint

kubectl get endpoints

kubectl edit pod frontend-5cb785b459-6cn6w 

执行pod内容器的命令

kubectl exec -it frontend-5cb785b459-6cn6w -c tomcat-demo -- mkdir /tmp/abc

kubectl exec -it frontend-5cb785b459-6cn6w -c tomcat-demo -- bash

会出现shell提示符,可以使用env,ls等命令

也可以在相应的node节点执行以下命令,进入到docker中的容器

sudo docker exec -it 5e45393d667c bash


2.label

1)基本概念

一个Label是一个key=value的键值对,其中key与value由用户自己指定。Label可以被附加到各种资源对象上,例如Node、Pod、Service、RC等

YAML中的标签定义格式:

  labels:

    key: value

labels:

    app: myweb

给某个资源对象定义一个Label,随后可以通过Label Selector(标签选择器)查询和筛选拥有某些Label的资源对象,当前有两种Label Selector表达式:基于等式的(Equality-based)和基于集合的(Set-based)

基于等式的操作符:=,!=

基于集合的操作符:in,notin,exists,doesnotexist

matchLabels用于定义一组Label,与直接写在Selector中的作用相同

selector:

    app: mysql

与下面的表达式等价

selector:

    matchLabels

        app: mysql

matchExpressions用于定义一组基于集合的筛选条件,可用的条件运算符包括in,notin,exists,doesnotexist。

selector: 

    matchExpressions: 

        - {key: name1,  operator: In , values:[v1,v2]}

        - {key: name2,  operator: In , values:[v3,v4]}

可以通过多个Label Selector表达式的组合实现复杂的条件选择,多个表达式之间用“,”进行分隔即可,几个条件之间是“AND”的关系,即同时满足多个条件

如果同时设置了matchLabels和matchExpressions,则两组条件为AND关系

Label Selector在Kubernetes中的重要使用场景如下。

◎ kube-controller进程通过在资源对象RC上定义的Label Selector来筛选要监控的Pod副本数量,使Pod副本数量始终符合预期设定的全自动控制流程。

◎ kube-proxy进程通过Service的Label Selector来选择对应的Pod,自动建立每个Service到对应Pod的请求转发路由表,从而实现Service的智能负载均衡机制。

◎ 通过对某些Node定义特定的Label,并且在Pod定义文件中使用NodeSelector这种标签调度策略,kube-scheduler进程可以实现Pod定向调度的特性。

3.Replication Controller/Replica Set

注:Replica Set与Deployment这资源对象逐步替代了之前RC的作用

1)基本概念

定义了一个期望的场景,即声明某种Pod的副本数量在任意时刻都符合某个预期值,所以RC的定义包括如下几个部分。

◎ Pod期待的副本数量。

◎ 用于筛选目标Pod的Label Selector。

◎ 当Pod的副本数量小于预期数量时,用于创建新Pod的Pod模板(template)。

在定义了一个RC并将其提交到Kubernetes集群中后,Master上的Controller Manager组件就得到通知,定期巡检系统中当前存活的目标Pod,并确保目标Pod实例的数量刚好等于此RC的期望值,如果有过多的Pod副本在运行,系统就会停掉一些Pod,否则系统会再自动创建一些Pod。可以说,通过RC,Kubernetes实现了用户应用集群的高可用性

Node 2上的Pod 2意外终止,则根据RC定义的replicas数量2,Kubernetes将会自动创建并启动一个新的Pod,以保证在整个集群中始终有两个redis-slave Pod运行。

在运行时,我们可以通过修改RC的副本数量,来实现Pod的动态缩放(Scaling),这可以通过执行kubectl scale命令来一键完成

kubectl scale rc redis-slave --replicas=3

Replication Controller由于与Kubernetes代码中的模块Replication Controller同名,同时“Replication Controller”无法准确表达它的本意,所以在Kubernetes 1.2中,升级为另外一个新概念—Replica Set,官方解释其为“下一代的RC”。Replica Set与RC当前的唯一区别是,Replica Sets支持基于集合的Label selector(Set based selector),而RC只支持基于等式的Label Selector(equality-based selector),这使得Replica Set的功能更强。

2)配置文件样例

apiVersion: v1

kind: ReplicationController

metadata:

  name: mysql

spec:

  replicas: 1

  selector:

    app: mysql

  template:

    metadata:

      labels:

        app: mysql

    spec:

      containers:

      - name: mysql

        image: mysql

        ports:

        - containerPort: 3306

        env:

        - name: MYSQL_ROOT_PASSWORD

          value: "123456"

上述的ReplicationController,可以替换为ReplicaSet关键字

3.Deployment

1)基本概念

Deployment在内部使用了Replica Set来实现目的,无论从Deployment的作用与目的、YAML定义,还是从它的具体命令行操作来看,我们都可以把它看作RC的一次升级,两者的相似度超过90%

Deployment的典型使用场景有以下几个。

◎ 创建一个Deployment对象来生成对应的Replica Set并完成Pod副本的创建。

◎ 检查Deployment的状态来看部署动作是否完成(Pod副本数量是否达到预期的值)。

◎ 更新Deployment以创建新的Pod(比如镜像升级)。

◎ 如果当前Deployment不稳定,则回滚到一个早先的Deployment版本。

◎ 暂停Deployment以便于一次性修改多个PodTemplateSpec的配置项,之后再恢复Deployment,进行

新的发布。

◎ 扩展Deployment以应对高负载。

◎ 查看Deployment的状态,以此作为发布是否成功的指标。

◎ 清理不再需要的旧版本ReplicaSets。

2)配置文件样例

apiVersion: apps/v1

kind: Deployment

metadata:

  name: frontend

spec:

  replicas: 1

  selector:

    matchLabels:

      tier: frontend

    matchExpressions:

      - {key: tier, operator: In, values: [frontend]}

  template:

    metadata:

      labels:

        app: app-demo

        tier: frontend

    spec:

      containers:

      - name: tomcat-demo

        image: tomcat

        imagePullPolicy: IfNotPresent

        ports:

        - containerPort: 8080

3)命令行例子

创建deployment
kubectl create -f frontend-deployment.yaml

查看Deployment的信息

kubectl get deploy -o wide

查看相应的Replica Set

kubectl get rs -o wide

kubectl describe rs frontend-5cb785b459

查看相应的Pod

kubectl get pod -o wide

kubectl describe pod frontend-5cb785b459-7trhb

注意这个pod的名称前缀,与相应的deploy和rs的名称是相同的

要是pod还处在Pulling image ...,还可以到相应的node上查看images的pull状态

sudo docker images

sudo docker pull tomcat

直到出现Running

基于deploy的伸缩

kubectl scale deploy frontend --replicas=2

5.Horizontal Pod Autoscaler

1)基本概念

通过手工执行kubectl scale命令,我们可以实现Pod扩容或缩容。如果仅仅到此为止,显然不符合谷歌对Kubernetes的定位目标—自动化、智能化。在谷歌看来,分布式系统要能够根据当前负载的变化自动触发水平扩容或缩容,因为这一过程可能是频繁发生的、不可预料的,所以手动控制的方式是不现实的。

HPA有以下两种方式作为Pod负载的度量指标。

◎ CPUUtilizationPercentage。

◎ 应用程序自定义的度量指标,比如服务在每秒内的相应请求数(TPS或QPS)。

2)配置文件样例

apiVersion: autoscaling/v1

kind: HorizontalPodAutoscaler

metadata:

  name: frontend

  namespace: default

spec:

  maxReplicas: 10

  minReplicas: 1

  scaleTargetRef:

    kind: Deployment

    name: frontend

  targetCPUUtilizationPercentage: 90

3)命令行例子

命令行格式:kubectl autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU] [options]

kubectl autoscale deploy frontend --min=1 --max=10 --cpu-percent=90

二、有状态的服务

6.StatefulSet

1)基本概念

在Kubernetes系统中,Pod的管理对象RC、Deployment、DaemonSet和Job都面向无状态的服务。但现实中有很多服务是有状态的,特别是一些复杂的中间件集群,例如MySQL集群、MongoDB集群、Akka集群、ZooKeeper集群等,这些应用集群有4个共同点。

(1)每个节点都有固定的身份ID,通过这个ID,集群中的成员可以相互发现并通信。

(2)集群的规模是比较固定的,集群规模不能随意变动。

(3)集群中的每个节点都是有状态的,通常会持久化数据到永久存储中。

(4)如果磁盘损坏,则集群里的某个节点无法正常运行,集群功能受损。

StatefulSet,StatefulSet从本质上来说,可以看作Deployment/RC的一个特殊变种,它有如下特性。

◎ StatefulSet里的每个Pod都有稳定、唯一的网络标识,可以用来发现集群内的其他成员。假设StatefulSet的名称为kafka,那么第1个Pod叫kafka-0,第2个叫kafka-1,以此类推。

◎ StatefulSet控制的Pod副本的启停顺序是受控的,操作第n个Pod时,前n-1个Pod已经是运行且准备好的状态。

◎ StatefulSet里的Pod采用稳定的持久化存储卷,通过PV或PVC来实现,删除Pod时默认不会删除与StatefulSet相关的存储卷(为了保证数据的安全)。

StatefulSet除了要与PV卷捆绑使用以存储Pod的状态数据,还要与Headless Service配合使用,即在每个StatefulSet定义中都要声明它属于哪个Headless Service。Headless Service与普通Service的关键区别在于,它没有Cluster IP,如果解析Headless Service的DNS域名,则返回的是该Service对应的全部Pod的Endpoint列表。StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod实例都创建了一个DNS域名,这个域名的格式为:

$(podname).$(HeadlessServicename)

比如一个3节点的Kafka的StatefulSet集群对应的Headless Service的名称为kafka,StatefulSet的名称为kafka,则StatefulSet里的3个Pod的DNS名称分别为kafka-0.kafka、kafka-1.kafka、kafka-3.kafka,这些DNS名称可以直接在集群的配置文件中固定下来。

7.Service

1)基本概念

Kubernetes里的每个Service其实就是我们经常提起的微服务架构中的一个微服务,下图显示了Pod、RC与Service的逻辑关系:Kubernetes的Service定义了一个服务的访问入口地址,前端的应用(Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实例,Service与其后端Pod副本集群之间则是通过Label Selector来实现无缝对接的。RC的作用实际上是保证Service的服务能力和服务质量始终符合预期标准。

2)配置文件样例

deploymentYAML文件

#nginx-deployment.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

  name: nginx-web1

spec:

  replicas: 1

  selector:

    matchLabels:

      app: nginx-web1

    matchExpressions:

      - {key: tier, operator: In, values: [frontend]}

  template:

    metadata:

      labels:

        app: nginx-web1

        tier: frontend

    spec:

      containers:

      - name: nginx-web1

        image: nginx

        imagePullPolicy: IfNotPresent

        ports:

        - containerPort: 80


单端口svc版本

#nginx-service.yaml

apiVersion: v1

kind: Service

metadata:

  name: nginx-web1-svc-s

spec:

  ports:

  - port: 80

  selector:

    app: nginx-web1

    tier: frontend


在spec.ports的定义中,targetPort属性用来确定提供该服务的容器所暴露(EXPOSE)的端口号,即具体业务进程在容器内的targetPort上提供TCP/IP接入;port属性则定义了Service的虚端口。前面定义Tomcat服务时没有指定targetPort,则默认targetPort与port相同,如下面的作用与上述是等价的

  ports:

  - port: 80

    targetPort: 80

(targetPort与port的缩进要相同)

多端口版本

#nginx-service-multiple-ports.yaml
apiVersion: v1

kind: Service

metadata:

  name: nginx-web1-svc-m

spec:

  ports:

  - port: 81

    name: service-port

    targetPort: 80

  - port: 82   

    name: shutdown-port

    targetPort: 80

  selector:

    app: nginx-web1

    tier: frontend


nodePort svc版本

#nginx-service-nodeport.yaml

apiVersion: v1

kind: Service

metadata:

  name: nginx-web1-svc-n

spec:

type: NodePort

ports:

  - port: 83

    targetPort: 80

    nodePort: 31002

selector:

    app: nginx-web1

    tier: frontend


3)命令行例子

kubectl create -f nginx-deployment.yaml

kubectl create -f nginx-service.yaml

kubectl create -f nginx-service-multiple-ports.yaml

kubectl create -f nginx-service-nodeport.yaml

kubectl get svc -o wide

kubectl get pod -o wide

以下命令在master或者node上都可以执行

#Pod IP:port

curl 10.244.36.73:80    

#单端口版本ClusterIP类型Svc,用 ClusterIP:port

curl 10.105.151.82:80    

#多端口版本ClusterIP类型Svc,用 ClusterIP:port

curl 10.106.206.123:81

curl 10.106.206.123:82 

#NodePort类型Svc,用 ClusterIP:port或者NodeIP:EXPOSE_PORT

curl 10.100.25.130:83    

curl 10.1.13.26:31002    

或者在浏览器里浏览:http://10.1.13.26:31002    

可以查看监听端口

sudo netstat -tlp|grep 31002

查看svc详情

kubectl describe svc nginx-web1-svc-m

可以看出svc的ip:port与pod的endpoints之间的对应关系

8.Job(批处理任务)

1)基本概念

批处理任务通常并行(或者串行)启动多个计算进程去处理一批工作项(work item),在处理完成后,整个批处理任务结束。

Job是一种特殊的Pod副本自动控制器,同时Job控制Pod副本与RC等控制器的工作机制有以下重要差别。

(1)Job所控制的Pod副本是短暂运行的,可以将其视为一组Docker容器,其中的每个Docker容器都仅仅运行一次。当Job控制的所有Pod副本都运行结束时,对应的Job也就结束了。Kubernetes在1.5版本之后又提供了类似crontab的定时任务——CronJob,解决了某些批处理任务需要定时反复执行的问题。

(2)Job所控制的Pod副本的工作模式能够多实例并行计算,以TensorFlow框架为例,可以将一个机器学习的计算任务分布到10台机器上,在每台机器上都运行一个worker执行计算任务,这很适合通过Job生成10个Pod副本同时启动运算。

9.Volume(存储卷)

1)基本概念

Volume(存储卷)是Pod中能够被多个容器访问的共享目录。

Kubernetes中的Volume被定义在Pod上,然后被一个Pod里的多个容器挂载到具体的文件目录下

Kubernetes中的Volume与Pod的生命周期相同,但与容器的生命周期不相关,当容器终止或者重启时,Volume中的数据也不会丢失

Kubernetes支持多种类型的Volume,例如GlusterFS、Ceph等先进的分布式文件系统

通过以下命令可以查看系统支持得存储卷类型

kubectl explain pods.spec.volumes

临时存储:emptyDir

半持久化存储:hostPath

持久化存储:pvc,pv,nfs

分布式存储: glusterfs,rbd,cephfs,云存储(EBS,等)

各种类型的存储卷配置方法参见下面的例子

1) emptyDir

volumes:

      - name: tmp

        emptyDir: {}

2) hostPath

volumes:

      - name: data

        hostPath:

            path: "/data"

2) nfs

volumes:

- name: nfs

        nfs:

            server: 10.1.1.1 

            path: "/data"

2)配置文件样例

#volume-deploy.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

  name: web-volume

spec:

  replicas: 1

  selector:

    matchLabels:

      app: web-volume

  template:

    metadata:

      labels:

        app: web-volume

    spec:

      volumes:

      - name: data

        hostPath:

            path: "/data"

      - name: tmp

        emptyDir: {}

      containers:

      - name: web-volume

        image: nginx

        volumeMounts:

        - mountPath: /test/data

          name: data

        - mountPath: /test/tmp

          name: tmp

        imagePullPolicy: IfNotPresent

        ports:

        - containerPort: 80

3)命令行例子

kubectl explain pods.spec.volumes

kubectl create -f volume-deploy.yaml

kubectl get pod

kubectl exec -it web-volume-869968c6b-bqqvw -- bash

ls看看/test有没有两个子目录

9.Persistent Volume(持久化 存储卷)

1)基本概念

PV可以被理解成Kubernetes集群中的某个网络存储对应的一块存储,它与Volume类似,但有以下区别。

◎ PV只能是网络存储,不属于任何Node,但可以在每个Node上访问。

◎ PV并不是被定义在Pod上的,而是独立于Pod之外定义的。

◎ PV目前支持的类型包括:gcePersistentDisk、AWSElasticBlockStore、AzureFile、AzureDisk、FC(Fibre Channel)、Flocker、NFS、iSCSI、RBD(Rados Block Device)、CephFS、Cinder、GlusterFS、VsphereVolume、Quobyte Volumes、VMware Photon、Portworx Volumes、ScaleIO Volumes和HostPath(仅供单机测试)。

◎ 需要定义一个PersistentVolumeClaim对象, PV 和 PVC 的 storageClassName 字段必须一样。

◎ 创建Pod是引用和mount

比较重要的是PV的accessModes属性,目前有以下类型。

◎ ReadWriteOnce:读写权限,并且只能被单个Node挂载。

◎ ReadOnlyMany:只读权限,允许被多个Node挂载。

◎ ReadWriteMany:读写权限,允许被多个Node挂载。

2)配置文件样例

#pv.yaml

kind: PersistentVolume

apiVersion: v1

metadata:

  name: pv-volume

  labels:

    type: local

spec:

  storageClassName: manual

  capacity:

    storage: 1Gi

  accessModes:

    - ReadWriteOnce

  hostPath:

    path: "/data/pv"

#pvc.yaml

kind: PersistentVolumeClaim

apiVersion: v1

metadata:

  name: pv-claim

spec:

  storageClassName: manual

  accessModes:

    - ReadWriteOnce

  resources:

    requests:

      storage: 1Gi

#pv-pod.yaml

kind: Pod

apiVersion: v1

metadata:

  name: pv-pod

spec:

  volumes:

    - name: pv-storage

      persistentVolumeClaim:

          claimName: pv-claim

  containers:

    - name: pv-container

      image: nginx

      ports:

        - containerPort: 80

          name: "http-server"

      volumeMounts:

        - mountPath: "/usr/share/nginx/html"

          name: pv-storage

#pv-deploy.yaml

apiVersion: apps/v1

kind: Deployment

metadata:

  name: pv-web

spec:

  replicas: 1

  selector:

    matchLabels:

      app: pv-web

  template:

    metadata:

      labels:

        app: pv-web

    spec:

      volumes:

      - name: pv-storage

        persistentVolumeClaim:

            claimName: pv-claim

      containers:

      - name: pv-web

        image: nginx

        volumeMounts:

        - mountPath: /data

          name: pv-storage

        imagePullPolicy: IfNotPresent

        ports:

        - containerPort: 80

3)命令行例子

kubectl create -f pv.yaml

kubectl create -f pvc.yaml

kubectl create -f pv-pod.yaml

kubectl get pv

kubectl get pvc

#对应的deploy挂载的卷

kubectl exec -it pv-pod bash

ls /usr/share/nginx/html

#对应的deploy挂载的卷

kubectl exec -it pv-web-66c4dc975c-q2mjp -- bash

ls /data

在node上/data/pv目录下touch文件,回到容器中看看是否有这个文件

10.Namespace

1)基本概念

Namespace(命名空间)是Kubernetes系统中的另一个非常重要的概念,Namespace在很多情况下用于实现多租户的资源隔离。Namespace通过将集群内部的资源对象“分配”到不同的Namespace中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。

2)配置文件样例

#ns.yaml

---

apiVersion: v1

kind: Namespace

metadata:

  name: dev

---

apiVersion: apps/v1

kind: Deployment

metadata:

  name: web-ns1

  namespace: dev

spec:

  replicas: 1

  selector:

    matchLabels:

      app: web-ns1

  template:

    metadata:

      labels:

        app: web-ns1

    spec:

      containers:

      - name: web-ns1

        image: nginx

        imagePullPolicy: IfNotPresent

        ports:

        - containerPort: 80

3)命令行例子

kubectl get ns

kubectl get pod -n dev


11.Annotation

Annotation(注解)与Label类似,也使用key/value键值对的形式进行定义。不同的是Label具有严格的命名规则,它定义的是Kubernetes对象的元数据(Metadata),并且用于Label Selector。Annotation则是用户任意定义的附加信息,以便于外部工具查找。在很多时候,Kubernetes的模块自身会通过Annotation标记资源对象的一些特殊信息

12.ConfigMap

Docker通过将程序、依赖库、数据及配置文件“打包固化”到一个不变的镜像文件中的做法,解决了应用的部署的难题,但这同时带来了棘手的问题,即配置文件中的参数在运行期如何修改的问题。我们不可能在启动Docker容器后再修改容器里的配置文件,然后用新的配置文件重启容器里的用户主进程。为了解决这个问题,Docker提供了两种方式:

◎ 在运行时通过容器的环境变量来传递参数;

◎ 通过Docker Volume将容器外的配置文件映射到容器内。

这两种方式都有其优势和缺点,

◎ 在目标主机上先创建好对应的配置文件,然后才能映射到容器里。

◎ 在分布式情况下变得更为严重,写入(修改)多台服务器上的某个指定文件,并确保这些文件保持一致

Kubernetes使用ConfigMap资源对象:把所有的配置项都当作key-value字符串,比如password=123456、user=root,这些配置项可以作为Map表中的一个项,整个Map的数据可以被持久化存储在Kubernetes的Etcd数据库中,然后提供API以方便Kubernetes相关组件或客户应用CRUD操作这些数据。


2)配置文件样例

#cm-appvars.yaml

appvars.yaml

apiVersion: v1

kind: ConfigMap

metadata:

  name: cm-appvars

data:

  apploglevel: info

  appdatadir: /var/data

#test-pod-envfrom-and-use-envvar.yaml

apiVersion: v1

kind: Pod

metadata:

  name: cm-test-pod

spec:

  containers:

  - name: cm-test

    image: nginx

    command: [ "/bin/sh", "-c", "env" ]

    envFrom:

    - configMapRef:

      name: cm-appvars

    env:

    - name: APPLOGLEVEL

      valueFrom:

        configMapKeyRef:

          name: cm-appvars

          key: apploglevel

    - name: APPDATADIR

      valueFrom:

        configMapKeyRef:

          name: cm-appvars

          key: appdatadir

  restartPolicy: Never


3)命令行例子

kubectl create -f cm-appvars.yaml

kubectl create -f test-pod-envfrom-and-use-envvar.yaml

kubectl log cm-test-pod

就可以看到cm-appvars.yaml中定义的环境变量

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

推荐阅读更多精彩内容