kubernetes Service

kubernetes Service

参考文献:
https://blog.csdn.net/watermelonbig/article/details/79693962

A、k8s的Service定义了一个服务的访问入口地址,前端的应用通过这个入口地址访问其背 后的一组由Pod副本组成的集群实例,来自外部的访问请求被负载均衡到后端的各个容器应用上。
B、Service与其后端Pod副本集群之间则是通过Label Selector来实现对接的;
C、而RC的作用相当于是保证Service的服务能力和服务质量始终处于预期的标准。Service 定义可以基于 POST 方式,请求 apiserver 创建新的实例。
D、一个 Service 在 Kubernetes 中是一个 REST 对象。
本文对Service的使用进行详细说明,包括Service的负载均衡、外网访问、DNS服务的搭建、Ingress7层路由机制等。

1、Service 定义详解

1.1 yaml格式的Service定义文件的完整内容

apiVersion: v1
kind: Service
matadata:
  name: string
  namespace: string
  labels:
  - name: string
  annotations:
  - name: string
spec:
  selector: []
  type: string
  clusterIP: string
  sessionAffinity: string
  ports:
  - name: string
    protocol: string
    port: int
    targetPort: int
    nodePort: int
  status:
    loadBalancer:
      ingress:
        ip: string
        hostname: string

1.2 对Service定义文件中各属性的说明表

属性名称 取值类型 是否必须 取值说明
version string Required v1
kind string Required Service
metadata object Required 元数据
metadata.name string Required Service名称
metadata.namespace string Required 命名空间,默认为default
metadata.labels[] list 自定义标签属性列表
metadata.annotation[] list 自定义注解属性列表
spec object Required 详细描述
spec.selector[] list Required Label Selector配置,将选择具有指定Label标签的Pod作为管理范围
spec.type string Required Service的类型,指定Service的访问方式,默认值为ClusterIP。取值范围如下:ClusterIP: 虚拟服务的IP,用于k8s集群内部的pod访问,在Node上kube-proxy通过设置的Iptables规则进行转发。NodePort:使用宿主机的端口,使用能够访问各Node的外部客户端通过Node的IP地址和端口就能访问服务。LoadBalancer: 使用外接负载均衡器完成到服务的负载分发,需要在spec.status.loadBalancer字段指定外部负载均衡器的IP地址,并同时定义nodePort和clusterIP,用于公有云环境。
spec.clusterIP string 虚拟服务的IP地址,当type=clusterIP时,如果不指定,则系统进行自动分配。也可以手工指定。当type=LoadBalancer时,则需要指定。
spec.sessionAffinity string 是否支持Session,可选值为ClientIP,表示将同一个源IP地址的客户端访问请求都转发到同一个后端Pod。默认值为空。
spec.ports[] list Service需要暴露的端口列表
spec.ports[].name string 端口名称
spec.ports[].protocol string 端口协议,支持TCP和UDP,默认值为TCP
spec.ports[].port int 服务监听的端口号
spec.ports[].targetPort int 需要转发到后端Pod的端口号
spec.ports[].nodePort int 当spec.type=NodePort时,指定映射到物理机的端口号
status object 当spec.type=LoadBalancer时,设置外部负载均衡器的地址,用于公有云环境
status.loadBalancer object 外部负载均衡器
status.loadBalancer.ingress object 外部负载均衡器
status.loadBalancer.ingress.ip string 外部负载均衡器的IP地址
status.loadBalancer.ingress.hostname string 外部负载均衡器的主机名

2 Service, RC, Pod 架构层次关系

image

3 VIP和Service 代理

3.1 kube-proxy

运行在每个Node上的kube-proxy进程其实就是一个智能的软件负载均衡器,它会负责把对Service的请求转发到后端的某个Pod实例上并在内部实现服务的负载均衡与会话保持机制。Service不是共用一个负载均衡器的IP,而是被分配了一个全局唯一的虚拟IP地址,称为Cluster IP。
在Service的整个生命周期内,它的Cluster IP不会改变。
kube-proxy 负责为 Service 实现了一种 VIP(虚拟 IP)的形式,而不是 ExternalName 的形式。
在k8s v1.2版本之前默认使用userspace提供vip代理服务,从 Kubernetes v1.2 起,默认是使用 iptables 代理。

3.2 iptables 代理模式

这种模式,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会创建相关 iptables 规则,从而捕获到达该 Service 的 clusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某个上面。 对于每个 Endpoints 对象,它也会创建 iptables 规则,这个规则会选择一个 backend Pod。
默认的策略是,随机选择一个 backend。 实现基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity 的值设置为 "ClientIP" (默认值为 "None")。
和 userspace 代理类似,网络返回的结果是,任何到达 Service 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、Service、或 Pod 的任何信息。 这应该比 userspace 代理更快、更可靠。然而,不像 userspace 代理,如果初始选择的 Pod 没有响应,iptables 代理能够自动地重试另一个 Pod,所以它需要依赖 readiness probes。

image

4、 发布服务---type类型

对一些应用希望通过外部(Kubernetes 集群外部)IP 地址暴露 Service。
Kubernetes ServiceTypes 允许指定一个需要的类型的 Service,默认是 ClusterIP 类型。
Type 的取值以及行为如下:

  • ClusterIP:通过集群的内部 IP 暴露服务,选择该值,服务只能够在集群内部可以访问,这也是默认的 ServiceType。
  • NodePort:通过每个 Node 上的 IP 和静态端口(NodePort)暴露服务。NodePort 服务会路由到 ClusterIP 服务,这个 ClusterIP 服务会自动创建。通过请求<NodeIP>:<NodePort>,可以从集群的外部访问一个 NodePort 服务。
  • LoadBalancer:使用云提供商的负载局衡器,可以向外部暴露服务。外部的负载均衡器可以路由到 NodePort 服务和 ClusterIP 服务。
  • ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com)。 没有任何类型代理被创建,这只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持。

k8s中有3种IP地址:

  • Node IP: Node节点的IP地址,这是集群中每个节点的物理网卡的IP地址;
  • Pod IP: Pod的IP地址,这是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络;
  • Cluster IP:Service 的IP地址,这也是一个虚拟的IP,但它更像是一个“伪造”的IP地址,因为它没有一个实体网络对象,所以无法响应ping命令。它只能结合Service Port组成一个具体的通信服务端口,单独的Cluster IP不具备TCP/IP通信的基础。

在k8s集群之内,Node IP网、Pod IP网与Cluster IP网之间的通信采用的是k8s自己设计的一种编程实现的特殊的路由规则,不同于常见的IP路由实现

5、 服务发现

Kubernetes 支持2种基本的服务发现模式 —— 环境变量和 DNS。
环境变量
当 Pod 运行在 Node 上,kubelet 会为每个活跃的 Service 添加一组环境变量。 它同时支持 Docker links兼容 变量、简单的 {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT 变量,这里 Service 的名称需大写,横线被转换成下划线。
举个例子,一个名称为 "redis-master" 的 Service 暴露了 TCP 端口 6379,同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

这意味着需要有顺序的要求 —— Pod 想要访问的任何 Service 必须在 Pod 自己之前被创建,否则这些环境变量就不会被赋值。DNS 并没有这个限制。
DNS
一个强烈推荐的集群插件 是 DNS 服务器。 DNS 服务器监视着创建新 Service 的 Kubernetes API,从而为每一个 Service 创建一组 DNS 记录。 如果整个集群的 DNS 一直被启用,那么所有的 Pod 应该能够自动对 Service 进行名称解析。
例如,有一个名称为 "my-service" 的 Service,它在 Kubernetes 集群中名为 "my-ns" 的 Namespace 中,为 "my-service.my-ns" 创建了一条 DNS 记录。 在名称为 "my-ns" 的 Namespace 中的 Pod 应该能够简单地通过名称查询找到 "my-service"。 在另一个 Namespace 中的 Pod 必须限定名称为 "my-service.my-ns"。 这些名称查询的结果是 Cluster IP。
Kubernetes 也支持对端口名称的 DNS SRV(Service)记录。 如果名称为 "my-service.my-ns" 的 Service 有一个名为 "http" 的 TCP 端口,可以对 "_http._tcp.my-service.my-ns" 执行 DNS SRV 查询,得到 "http" 的端口号。
Kubernetes DNS 服务器是唯一的一种能够访问 ExternalName 类型的 Service 的方式。 更多信息可以查看DNS Pod 和 Service。
Kubernetes 从 1.3 版本起, DNS 是内置的服务,通过插件管理器 集群插件 自动被启动。Kubernetes DNS 在集群中调度 DNS Pod 和 Service ,配置 kubelet 以通知个别容器使用 DNS Service 的 IP 解析 DNS 名字。

6、 Service的基本用法

    一般来说,对外提供服务的应用程序需要通过某种机制来实现,对于容器应用最简便的方式就是通过TCP/IP机制及监听IP和端口号来实现。
创建一个基本功能的Service
(1) 例如,我们定义一个提供web服务的RC,由两个tomcat容器副本组成,每个容器通过containerPort设置提供服务号为8080:
webapp-rc.yaml

apiVersion: v1
kind: ReplicationController
metadata:
  name: webapp
spec:
  replicas: 2
  template:
    metadata:
      name: webapp
      labels:
        app: webapp
    spec:
      containers:
      - name: webapp
        image: tomcat
        ports:
        - containerPort: 8080

创建该RC

kubectl create -f webapp-rc.yaml 

获取Pod的IP地址:

[root@master service]# kubectl get pods -l app=webapp -o yaml| grep podIP | grep -v cni
    podIP: 192.168.2.94
    podIP: 192.168.1.73
[root@master service]# 

直接通过这两个Pod的IP地址和端口号访问Tomcat服务:

[root@master service]# curl 192.168.2.94:8080



<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Apache Tomcat/8.5.34</title>
        <link href="favicon.ico" rel="icon" type="image/x-icon" />
        <link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
        <link href="tomcat.css" rel="stylesheet" type="text/css" />
    </head>
........

说明可以直接通过podIP进行访问
直接通过Pod的IP地址和端口号可以访问容器内的应用服务,但是Pod的IP地址是不可靠的,例如Pod所在的Node发生故障,Pod将被k8s重新调度到另一台Node。Pod的IP地址将发生变化,更重要的是,如果容器应用本身是分布式的部署方式,通过多个实例共同提供服务,就需要在这些实例的前端设置一个负载均衡器来实现请求的分发。kubernetes中的Service就是设计出来用于解决这些问题的核心组件。

(2) 为了让客户端应用能够访问到两个Tomcat Pod 实例,需要创建一个Service来提供服务
k8s提供了一种快速的方法,即通过kubectl expose命令来创建:

[root@master service]# kubectl expose deployment webapp
service/webapp exposed

查看新创建的Service可以看到系统为它分配了一个虚拟的IP地址(clusterIP),而Service所需的端口号则从Pod中的containerPort复制而来:

[root@master service]# kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
kubernetes   ClusterIP   10.96.0.1      <none>        443/TCP    6d10h
webapp       ClusterIP   10.103.4.220   <none>        8080/TCP   5s
[root@master service]# 

接下来,我们就可以通过Service的IP地址和Service的端口号访问该Service了:

image

这里,对Service地址10.103.4.220:8080的访问被自动负载分发到了后端两个Pod之一。

(3) 除了使用kubectl expose命令创建Service,我们也可以通过配置文件定义Service,再通过kubectl create命令进行创建
例如前面的webapp就用,我们可以设置一个Service:
webapp-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  ports:
  - port: 8081
    targetPort: 8080
  selector:
    app: webapp

本例中ports定义部分指定了Service所需的虚拟端口号为8081,由于与Pod容器端口号8080不一样,所以需要在通过targetPort来指定后端Pod的端口
selector定义部分设置的是后端Pod所拥有的label: app=webapp
(4) 目前kubernetes提供了两种负载分发策略:RoundRobin和SessionAffinity

  • RoundRobin:轮询模式,即轮询将请求转发到后端的各个Pod上
  • SessionAffinity:基于客户端IP地址进行会话保持的模式,第一次客户端访问后端某个Pod,之后的请求都转发到这个Pod上
    默认是RoundRobin模式。

7、 多端口Service

有时候,一个容器应用提供多个端口服务,可以按下面这样定义:

apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  ports:
  - name: web
    port: 8080
    targetPort: 8080
  - name: management
    port: 8005
    targetPort: 8005
  selector:
    app: webapp 

另一个例子是两个端口使用了不同的4层协议,即TCP和UDP

apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  labels:
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "KubeDNS"
spec:
  selector:
    k8s-app: kube-dns
  clusterIP: 10.10.10.100
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP

8、 Headless Service

有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为 "None" 来创建 Headless Service。
这个选项允许开发人员自由寻找他们自己的方式,从而降低与 Kubernetes 系统的耦合性。 应用仍然可以使用一种自注册的模式和适配器,对其它需要发现机制的系统能够很容易地基于这个 API 来构建。
对这类 Service 并不会分配 Cluster IP,kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。仅依赖于Label Selector将后端的Pod列表返回给调用的客户端。

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
  clusterIP: None
  selector:
    app: nginx

这样,Service就不再具有一个特定的ClusterIP地址,对其进行访问将获得包含Label"app=nginx"的全部Pod列表,然后客户端程序自行决定如何处理这个Pod列表。
例如, StatefulSet就是使用Headless Service为客户端返回多个服务地址。
Lable Secector:
配置 Selector:对定义了 selector 的 Headless Service,Endpoint 控制器在 API 中创建了 Endpoints 记录,并且修改 DNS 配置返回 A 记录(地址),通过这个地址直接到达 Service 的后端 Pod上。
不配置 Selector:对没有定义 selector 的 Headless Service,Endpoint 控制器不会创建 Endpoints 记录。

9、 外部服务Service——没有 selector 的 Service

在某些环境中,应用系统需要将一个外部数据库用为后端服务进行连接,或将另一个集群或Namespace中的服务作为服务的后端,这时可以通过创建一个无Label Selector的Service实现:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

Servcie 抽象了该如何访问 Kubernetes Pod,但也能够抽象其它类型的 backend,例如:
希望在生产环境中使用外部的数据库集群。
希望服务指向另一个 Namespace 中或其它集群中的服务。
正在将工作负载同时转移到 Kubernetes 集群和运行在 Kubernetes 集群之外的 backend。
由于这个 Service 没有 selector,就不会创建相关的 Endpoints 对象。可以手动将 Service 映射到指定的 Endpoints:

kind: Endpoints
apiVersion: v1
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 1.2.3.4
    ports:
      - port: 9376

注意:Endpoint IP 地址不能是 loopback(127.0.0.0/8)、 link-local(169.254.0.0/16)、或者 link-local 多播(224.0.0.0/24)。
访问没有 selector 的 Service,与有 selector 的 Service 的原理相同。请求将被路由到用户定义的 Endpoint(该示例中为 1.2.3.4:9376)。
ExternalName Service 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint。 相反地,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务。

kind: Service
apiVersion: v1
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

当查询主机 my-service.prod.svc.CLUSTER时,集群的 DNS 服务将返回一个值为 my.database.example.com 的 CNAME 记录。 访问这个服务的工作方式与其它的相同,唯一不同的是重定向发生在 DNS 层,而且不会进行代理或转发。 如果后续决定要将数据库迁移到 Kubernetes 集群中,可以启动对应的 Pod,增加合适的 Selector 或 Endpoint,修改 Service 的 type。

10、集群外部访问Pod或Service的方法

由于Pod和Service是k8s集群范围内的虚拟概念,所以集群外的客户端系统无法通过Pod的IP地址或者Service的虚拟IP地址和虚拟端口号访问到它们。
为了让外部客户端可以访问这些服务,可以将Pod或Service的端口号映射到宿主机,以使得客户端应用能够通过物理机访问容器应用。

10.1、将容器应用的端口号映射到物理机

(1) 通过设置容器级别的hostPort,将容器应用的端口号映射到物理机上
文件pod-hostport.yaml

apiVersion: v1
kind: Pod
metadata:
  name: webapp
  labels:
    app: webapp
spec:
  containers:
  - name: webapp
    image: tomcat
    ports:
    - containerPort: 8080
      hostPort: 8081

hostPort的值可以跟containerPort的值一样
通过kubectl create创建这个Pod:

kubectl create -f pod-hostport.yaml

通过物理机的IP地址和8081端口号访问Pod内的容器服务:

curl 172.16.91.137:8081

(2) 通过设置Pod级别的hostNetwork=true,该Pod中所有容器的端口号都将被直接映射到物理机上
设置hostWork=true时需要注意,在容器的ports定义部分如果不指定hostPort,则默认hostPort等于containerPort,如果指定了hostPort,则hostPort必须等于containerPort的值。
文件pod-hostnetwork.yaml

apiVersion: v1
kind: Pod
metadata:
  name: webapp-hostnetwork
  labels:
    app: webapp-hostnetwork
spec:
  hostNetwork: true
  containers:
  - name: webapp-hostnetwork
    image: tomcat
    imagePullPolicy: Never
    ports:
    - containerPort: 8080

创建这个Pod:

kubectl create -f pod-hostnetwork.yaml
[root@master pod]# kubectl get pod -owide
NAME                      READY   STATUS    RESTARTS   AGE   IP              NODE     NOMINATED NODE
webapp-77df6fff64-dhczl   1/1     Running   1          14h   192.168.2.95    slave2   <none>
webapp-77df6fff64-kpl6w   1/1     Running   1          14h   192.168.1.74    slave1   <none>
webapp-hostnetwork        1/1     Running   0          7s    172.16.91.136   slave1   <none>

通过物理机的IP地址和8080端口访问Pod的容器服务

curl slave1:8080  

10.2 将Service的端口号映射到物理机

(1) 通过设置nodePort映射到物理机,同时设置Service的类型为NodePort
文件webapp-svc-nodeport.yaml

apiVersion: v1
kind: Service
metadata:
  name: webapp-nodeport
spec:
  type: NodePort
  ports:
  - port: 8090
    targetPort: 8080
    nodePort: 8090
  selector:
    app: webapp

创建这个Service:

kubectl create -f webapp-svc-nodeport.yaml
[root@master service]# kubectl get svc
NAME              TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes        ClusterIP   10.96.0.1      <none>        443/TCP          7d1h
webapp            ClusterIP   10.103.4.220   <none>        8080/TCP         14h
webapp-nodeport   NodePort    10.99.248.88   <none>        8090:31090/TCP   6s

通过物理机的IP和端口访问:(4种方式访问)

#Service的IP,即ClusterIP
curl 10.99.248.88:8090 
#master的IP,
curl master:31090 
curl slave1:31090 
curl slave2:31090 

也就是说,只要是nodePort端口,就会在物理机上开始监听端口号

image

image

如果访问不通,查看下物理机的防火墙设置
同样,对该Service的访问也将被负载分发到后端多个Pod上

(2) 通过设置LoadBalancer映射到云服务商提供的LoadBalancer地址
这种用法仅用于在公有云服务提供商的云平台上设置Service的场景。
status.loadBalancer.ingress.ip设置的146.148.47.155为云服务商提供的负载均衡器的IP地址。对该Service的访问请求将会通过LoadBalancer转发到后端的Pod上,负载分发的实现方式依赖云服务商提供的LoadBalancer的实现机制。

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

推荐阅读更多精彩内容