Kubernetes架構學習筆記

Kubernetes是Google開源的容器集群管理系統,其提供應用部署、維護、 擴展機制等功能,利用Kubernetes能方便地管理跨機器運行容器化的應用,是Docker分布式系統的解決方案。k8s里所有的資源都可以用yaml或Json定義。

1 K8s基本概念

1.1 Master

Master節點負責整個集群的控制和管理,所有的控制命令都是發給它,上面運行著一組關鍵進程:

  • kube-apiserver:提供了HTTP REST接口,是k8s所有資源增刪改查等操作的唯一入口,也是集群控制的入口。
  • kube-controller-manager:所有資源的自動化控制中心。當集群狀態與期望不同時,kcm會努力讓集群恢復期望狀態,比如:當一個pod死掉,kcm會努力新建一個pod來恢復對應replicas set期望的狀態。
  • kube-scheduler:負責Pod的調度。

實際上,Master只是一個名義上的概念,三個關鍵的服務不一定需要運行在一個節點上。


1.1.1 API Server的原理

集群中的各個功能模塊通過 apiserver將信息存儲在Etcd,當需要修改這些信息的時候通過其REST接口來實現。


1.1.2 Controller Manager的原理

內部包含:

  • Replication Controller
  • Node Controller
  • ResourceQuota Controller
  • Namespace Controller
  • ServiceAccount Controller
  • Token Controller
  • Service Controller
  • Endpoint Controller等

這些Controller通過API Server實時監控各個資源的狀態,當有資源因為故障導致狀態變化,Controller就會嘗試將系統由“現有狀態”恢復到“期待狀態”。


1.1.3 Scheduler的原理

作用是將apiserver或controller manager創建的Pod調度和綁定到具體的Node上,一旦綁定,就由Node上的kubelet接手Pod的接下來的生命周期管理。


1.2 Node

Node是工作負載節點,運行著Master分配的負載(Pod),但一個Node宕機時,其上的負載會被自動轉移到其他Node上。其上運行的關鍵組件是:

  • kubelet:負責Pod的生命周期管理,同時與Master密切協作,實現集群管理的基本功能。
  • kube-proxy:實現Service的通信與負載均衡機制的重要組件,老版本主要通過設置iptables規則實現,新版1.9基于kube-proxy-lvs 實現。
  • Docker Engine:Docker引擎,負責Docker的生命周期管理。
2018030420563054.jpg

1.2.1 kube-proxy的原理

每個Node上都運行著一個kube-proxy進程,它在本地建立一個SocketServer接收和轉發請求,可以看作是Service的透明代理和負載均衡器,負載均衡策略模式是Round Robin。也可以設置會話保持,策略使用的是“ClientIP”,將同一個ClientIP的請求轉發同一個Endpoint上。

Service的Cluster IP和NodePort等概念都是kube-proxy服務通過Iptables的NAT轉換實現,Iptables機制針對的是kube-proxy監聽的端口,所以每個Node上都要有kube-proxy。


20180304205645562.png

1.2.2 kubelet原理

每個Node都會啟動一個kubelet,主要作用有:

(1)Node管理

  • 注冊節點信息;
  • 通過cAdvisor監控容器和節點的資源;
  • 定期向Master(實際上是apiserver)匯報本節點資源消耗情況

(2)Pod管理

所以非通過apiserver方式創建的Pod叫Static Pod,這里我們討論的都是通過apiserver創建的普通Pod。kubelet通過apiserver監聽etcd,所有針對Pod的操作都會被監聽到,如果其中有涉及到本節點的Pod,則按照要求進行創建、修改、刪除等操作。

(3)容器健康檢查

kubelet通過兩類探針檢查容器的狀態:

LivenessProbe:判斷一個容器是否健康,如果不健康則會刪除這個容器,并按照restartPolicy看是否重啟這個容器。實現的方式有ExecAction(在容器內部執行一個命令)、TCPSocketAction(如果端口可以被訪問,則健康)、HttpGetAction(如果返回200則健康)。

ReadinessProbe:用于判斷容器是否啟動完全。如果返回的是失敗,則Endpoint Controller會將這個Pod的Endpoint從Service的Endpoint列表中刪除。也就是,不會有請求轉發給它。


1.3 Pod

Pod是k8s進行資源調度的最小單位,每個Pod中運行著一個或多個密切相關的業務容器這些業務容器共享這個Pause容器的IP和Volume,我們以這個不易死亡的Pause容器作為Pod的根容器,以它的狀態表示整個容器組的狀態。一個Pod一旦被創建就會放到Etcd中存儲,然后由Master調度到一個Node綁定,由這個Node上的Kubelet進行實例化。

每個Pod會被分配一個單獨的Pod IP,Pod IP + ContainerPort 組成了一個Endpoint。


1.4 Service

K8s中一個Service相當于一個微服務的概念,一個Service對應后端多個Pod計算實例,使用LabelSelector將一類Pod都綁定到自己上來。一般還會需要一個Deployment或者RC來幫助這個Service來保證這個Service的服務能力和質量。

20180304205656579.png

1.4.1 kube-proxy負載均衡

運行在每個Node上的kube-proxy其實就是一個智能的軟件負載均衡器,它負載將發給Service的請求轉發到后端對應的Pod,也就是說它負責會話保持和負責均衡。


1.4.2 Cluster IP

負載均衡的基礎是負載均衡器要維護一個后端Endpoint列表,但是Pod的Endpoint會隨著Pod的銷毀和重建而改變,k8s使這個問題透明化。一旦Service被創建,就會立刻分配給它一個Cluster IP,在Service的整個生命周期內,這個Cluster IP不會改變。于是,服務發現的問題也解決了:只要用Service Name和Service Cluster IP做一個DNS域名映射就可以了。


1.4.3 DNS

從Kubernetes 1.3開始,DNS通過使用插件管理系統cluster add-on,成為了一個內建的自啟動服務。Kubernetes DNS在Kubernetes集群上調度了一個DNS PodService,并配置kubelet,使其告訴每個容器使用DNS Service的IP來解析DNS名稱。

(1)Service

集群中定義的每個Service(包括DNS Service它自己)都被分配了一個DNS名稱。默認的,Pod的DNS搜索列表中會包含Pod自己的命名空間和集群的默認域,下面我們用示例來解釋以下。 假設有一個名為foo的Service,位于命名空間bar中。運行在bar命名空間中的Pod可以通過DNS查找foo關鍵字來查找到這個服務,而運行在命名空間quux中的Pod可以通過關鍵字foo.bar來查找到這個服務。

普通(非headless)的Service都被分配了一個DNS記錄,該記錄的名稱格式為my-svc.my-namespace.svc.cluster.local,通過該記錄可以解析出服務的集群IP。 Headless(沒有集群IP)的Service也被分配了一個DNS記錄,名稱格式為my-svc.my-namespace.svc.cluster.local。與普通Service不同的是,它會解析出Service選擇的Pod的IP列表。

(2)Pod

Pod也可以使用DNS服務。pod會被分配一個DNS記錄,名稱格式為pod-ip-address.my-namespace.pod.cluster.local。 比如,一個pod,它的IP地址為1.2.3.4,命名空間為default,DNS名稱為cluster.local,那么它的記錄就是:1-2-3-4.default.pod.cluster.local。 當pod被創建時,它的hostname設置在Pod的metadata.name中。

在v1.2版本中,用戶可以指定一個Pod注解,pod.beta.kubernetes.io/hostname,用于指定Pod的hostname。這個Pod注解,一旦被指定,就將優先于Pod的名稱,成為pod的hostname。比如,一個Pod,其注解為pod.beta.kubernetes.io/hostname: my-pod-name,那么該Pod的hostname會被設置為my-pod-name。 v1.2中還引入了一個beta特性,用戶指定Pod注解,pod.beta.kubernetes.io/subdomain,來指定Pod的subdomain。比如,一個Pod,其hostname注解設置為“foo”,subdomain注解為“bar”,命名空間為“my-namespace”,那么它最終的FQDN就是“foo.bar.my-namespace.svc.cluster.local”。 在v1.3版本中,PodSpec有了hostnamesubdomain字段,用于指定Pod的hostname和subdomain。它的優先級則高于上面提到的pod.beta.kubernetes.io/hostnamepod.beta.kubernetes.io/subdomain。


1.4.4 外部訪問Service的問題

先明確這樣幾個IP:

  • Node IP:Node主機的IP,與它是否屬于K8s無關。
  • Pod IP:是Dokcer Engine通過docker0網橋的IP地址段進行分配的,通常是一個虛擬的二層網絡。k8s中一個Pod訪問另一個Pod就是通過Pod IP。
  • Cluster IP:僅用于Service對象,屬于k8s的內部IP,外界無法直接訪問。

(1)NodePort

在Service的yaml中定義NodePort,k8s為集群中每個Node都增加對這個端口的監聽,使用這種方式往往需要一個獨立與k8s之外的負載均衡器作為流量的入口。

(2)使用External IP

  • 運行Hello World應用程序的五個實例。
  • 創建一個暴露外部IP地址的Service對象。
  • 使用Service對象訪問正在運行的應用程序。

使用deployment創建暴露的Service對象:

~ kubectl expose deployment hello-world --type=LoadBalancer --name=my-service

顯示關于Service的信息:

~ kubectl get services my-service

 NAME         CLUSTER-IP     EXTERNAL-IP      PORT(S)    AGE
 my-service   10.3.245.137   104.198.205.71   8080/TCP   54s

~  kubectl describe services my-service
 Name:           my-service
 Namespace:      default
 Labels:         run=load-balancer-example
 Selector:       run=load-balancer-example
 Type:           LoadBalancer
 IP:             10.3.245.137
 LoadBalancer Ingress:   104.198.205.71
 Port:           <unset> 8080/TCP
 NodePort:       <unset> 32377/TCP
 Endpoints:      10.0.0.6:8080,10.0.1.6:8080,10.0.1.7:8080 + 2 more...
 Session Affinity:   None
 Events:

在此例子中,外部IP地址為104.198.205.71。還要注意Port的值。在這個例子中,端口是8080。在上面的輸出中,您可以看到該服務有多個端點:10.0.0.6:8080,10.0.1.6:8080,10.0.1.7:8080 + 2 more…。這些是運行Hello World應用程序的pod的內部地址。

使用外部IP地址訪問Hello World應用程序:

~  curl http://<external-ip>:<port>
 Hello Kubernetes!

刪除服務

~ kubectl delete services my-service
~ kubectl delete deployment hello-world

1.5 Ingress

通常情況下,service和pod僅可在集群內部網絡中通過IP地址訪問。所有到達邊界路由器的流量或被丟棄或被轉發到其他地方。Ingress是授權入站連接到達集群服務的規則集合。你可以給Ingress配置提供外部可訪問的URL、負載均衡、SSL、基于名稱的虛擬主機等。用戶通過POST Ingress資源到API server的方式來請求ingress。 Ingress controller負責實現Ingress,通常使用負載平衡器,它還可以配置邊界路由和其他前端,這有助于以HA方式處理流量。

最簡化的Ingress配置:

 apiVersion: extensions/v1beta1
 kind: Ingress
 metadata:
   name: test-ingress
 spec:
   rules:
   - http:
       paths:
       - path: /testpath
        backend:
           serviceName: test
           servicePort: 80
      - path: /bar
        backend:
          serviceName: s2
          servicePort: 80
  • 1-4行:跟Kubernetes的其他配置一樣,ingress的配置也需要apiVersion,kind和metadata字段。配置文件的詳細說明請查看部署應用, 配置容器和 使用resources.

  • 5-7行: Ingress spec 中包含配置一個loadbalancer或proxy server的所有信息。最重要的是,它包含了一個匹配所有入站請求的規則列表。目前ingress只支持http規則。

  • 8-9行:每條http規則包含以下信息:一個host配置項(比如for.bar.com,在這個例子中默認是*),path列表(比如:/testpath),每個path都關聯一個backend(比如test:80)。在loadbalancer將流量轉發到backend之前,所有的入站請求都要先匹配host和path。

  • 10-12行:backend是一個service:port的組合。Ingress的流量被轉發到它所匹配的backend。

配置TLS證書

你可以通過指定包含TLS私鑰和證書的secret來加密Ingress。 目前,Ingress僅支持單個TLS端口443,并假定TLS termination。 如果Ingress中的TLS配置部分指定了不同的主機,則它們將根據通過SNI TLS擴展指定的主機名(假如Ingress controller支持SNI)在多個相同端口上進行復用。 TLS secret中必須包含名為tls.crt和tls.key的密鑰,這里面包含了用于TLS的證書和私鑰,例如:

(1)創建Secret

apiVersion: v1
data:
  tls.crt: base64 encoded cert
  tls.key: base64 encoded key
kind: Secret
metadata:
  name: testsecret
  namespace: default
type: Opaque

(2)創建Ingress:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: no-rules-map
spec:
  tls:
    - secretName: testsecret
  backend:
    serviceName: s1
    servicePort: 80

2 高可用

Kubernetes服務本身的穩定運行對集群管理至關重要,影響服務穩定的因素一般來說分為兩種,一種是服務本身異?;蛘叻账跈C器宕機,另一種是因為網絡問題導致的服務不可用。本文將從存儲層、管理層、接入層三個方面介紹高可用Kubernetes集群的原理。

2.1 Etcd高可用方案

Kubernetes的存儲層使用的是Etcd。Etcd是CoreOS開源的一個高可用強一致性的分布式存儲服務,Kubernetes使用Etcd作為數據存儲后端,把需要記錄的pod、rc、service等資源信息存儲在Etcd中。

Etcd使用raft算法將一組主機組成集群,raft 集群中的每個節點都可以根據集群運行的情況在三種狀態間切換:follower, candidate 與 leader。leader 和 follower 之間保持心跳。如果follower在一段時間內沒有收到來自leader的心跳,就會轉為candidate,發出新的選主請求。

集群初始化的時候內部的節點都是follower節點,之后會有一個節點因為沒有收到leader的心跳轉為candidate節點,發起選主請求。當這個節點獲得了大于一半節點的投票后會轉為leader節點。當leader節點服務異常后,其中的某個follower節點因為沒有收到leader的心跳轉為candidate節點,發起選主請求。只要集群中剩余的正常節點數目大于集群內主機數目的一半,Etcd集群就可以正常對外提供服務。

當集群內部的網絡出現故障集群可能會出現“腦裂”問題,這個時候集群會分為一大一小兩個集群(奇數節點的集群),較小的集群會處于異常狀態,較大的集群可以正常對外提供服務。

2.2 Master高可用方案

Master上有三個關鍵的服務:apiserver、controller-manager和scheduler,這三個不一定要運行在一臺主機上。

2.2.1 controller-manager和scheduler的選舉配置

Kubernetes的管理層服務包括kube-scheduler和kube-controller-manager。kube-scheduer和kube-controller-manager使用一主多從的高可用方案,在同一時刻只允許一個服務處以具體的任務。Kubernetes中實現了一套簡單的選主邏輯,依賴Etcd實現scheduler和controller-manager的選主功能。

如果scheduler和controller-manager在啟動的時候設置了leader-elect參數,它們在啟動后會先嘗試獲取leader節點身份,只有在獲取leader節點身份后才可以執行具體的業務邏輯。它們分別會在Etcd中創建kube-scheduler和kube-controller-manager的endpoint,endpoint的信息中記錄了當前的leader節點信息,以及記錄的上次更新時間。leader節點會定期更新endpoint的信息,維護自己的leader身份。每個從節點的服務都會定期檢查endpoint的信息,如果endpoint的信息在時間范圍內沒有更新,它們會嘗試更新自己為leader節點。

scheduler服務以及controller-manager服務之間不會進行通信,利用Etcd的強一致性,能夠保證在分布式高并發情況下leader節點的全局唯一性。整體方案如下圖所示:

20180304205710646.png

當集群中的leader節點服務異常后,其它節點的服務會嘗試更新自身為leader節點,當有多個節點同時更新endpoint時,由Etcd保證只有一個服務的更新請求能夠成功。通過這種機制sheduler和controller-manager可以保證在leader節點宕機后其它的節點可以順利選主,保證服務故障后快速恢復。當集群中的網絡出現故障時對服務的選主影響不是很大,因為scheduler和controller-manager是依賴Etcd進行選主的,在網絡故障后,可以和Etcd通信的主機依然可以按照之前的邏輯進行選主,就算集群被切分,Etcd也可以保證同一時刻只有一個節點的服務處于leader狀態。

2.2.2 apiserver的高可用

Kubernetes的接入層服務主要是kube-apiserver。apiserver本身是無狀態的服務,它的主要任務職責是把資源數據存儲到Etcd中,后續具體的業務邏輯是由scheduler和controller-manager執行的。所以可以同時起多個apiserver服務,使用nginx把客戶端的流量轉發到不同的后端apiserver上實現接入層的高可用。具體的實現如下圖所示:

20180304205724387.png

接入層的高可用分為兩個部分,一個部分是多活的apiserver服務,另一個部分是一主一備的nginx服務。

2.3 Keepalived簡介

Keepalived軟件起初是專為LVS負載均衡軟件設計的,用來管理并監控LVS集群系統中各個服務節點的狀態,后來又加入了可以實現高可用的VRRP功能。因此,Keepalived除了能夠管理LVS軟件外,還可以作為其他服務(例如:Nginx、HaproxyMySQL等)的高可用解決方案軟件。Keepalived軟件主要是通過VRRP協議實現高可用功能的。VRRP是Virtual Router RedundancyProtocol(虛擬路由器冗余協議)的縮寫,VRRP出現的目的就是為了解決靜態路由單點故障問題的,它能夠保證當個別節點宕機時,整個網絡可以不間斷地運行。所以,Keepalived 一方面具有配置管理LVS的功能,同時還具有對LVS下面節點進行健康檢查的功能,另一方面也可實現系統網絡服務的高可用功能。

<u style="box-sizing: border-box;">故障切換轉移原理</u>

Keepalived高可用服務對之間的故障切換轉移,是通過 VRRP (Virtual Router Redundancy Protocol ,虛擬路由器冗余協議)來實現的。在 Keepalived服務正常工作時,主 Master節點會不斷地向備節點發送(多播的方式)心跳消息,用以告訴備Backup節點自己還活看,當主 Master節點發生故障時,就無法發送心跳消息,備節點也就因此無法繼續檢測到來自主 Master節點的心跳了,于是調用自身的接管程序,接管主Master節點的 IP資源及服務。而當主 Master節點恢復時,備Backup節點又會釋放主節點故障時自身接管的IP資源及服務,恢復到原來的備用角色。


3 容器網絡

3.1 docker默認容器網絡

在默認情況下會看到三個網絡,它們是Docker Deamon進程創建的。它們實際上分別對應了Docker過去的三種『網絡模式』,可以使用docker network ls來查看:

master@ubuntu:~$ sudo docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
18d934794c74        bridge              bridge              local
f7a7b763f013        host                host                local
697354257ae3        none                null                local

這 3 個網絡包含在 Docker 實現中。運行一個容器時,可以使用 the –net標志指定您希望在哪個網絡上運行該容器。您仍然可以使用這 3 個網絡。

  • bridge 網絡表示所有 Docker 安裝中都存在的 docker0 網絡。除非使用 docker run –net=選項另行指定,否則 Docker 守護進程默認情況下會將容器連接到此網絡。在主機上使用 ifconfig命令,可以看到此網橋是主機的網絡堆棧的一部分。
  • none 網絡在一個特定于容器的網絡堆棧上添加了一個容器。該容器缺少網絡接口。
  • host 網絡在主機網絡堆棧上添加一個容器。您可以發現,容器中的網絡配置與主機相同。

3.2 跨主機通信的方案

和host共享network namespace

這種接入模式下,不會為容器創建網絡協議棧,即容器沒有獨立于host的network namespace,但是容器的其他namespace(如IPC、PID、Mount等)還是和host的namespace獨立的。容器中的進程處于host的網絡環境中,與host共用L2-L4的網絡資源。該方式的優點是,容器能夠直接使用host的網絡資源與外界進行通信,沒有額外的開銷(如NAT),缺點是網絡的隔離性差,容器和host所使用的端口號經常會發生沖突。

和host共享物理網卡

2與1的區別在于,容器和host共享物理網卡,但容器擁有獨立于host的network namespace,容器有自己的MAC地址、IP地址、端口號。這種接入方式主要使用SR-IOV技術,每個容器被分配一個VF,直接通過PCIe網卡與外界通信,優點是旁路了host kernel不占任何計算資源,而且IO速度較快,缺點是VF數量有限且對容器遷移的支持不足。

Behind the POD

這種方式是Google在Kubernetes中的設計中提出來的。Kubernetes中,POD是指一個可以被創建、銷毀、調度、管理的最小的部署單元,一個POD有一個基礎容器以及一個或一組應用容器,基礎容器對應一個獨立的network namespace并擁有一個其它POD可見的IP地址(以IP A.B.C.D指代),應用容器間則共享基礎容器的network namespace(包括MAC、IP以及端口號等),還可以共享基礎容器的其它的namespace(如IPC、PID、Mount等)。POD作為一個整體連接在host的vbridge/vswitch上,使用IP地址A.B.C.D與其它POD進行通信,不同host中的POD處于不同的subnet中,同一host中的不同POD處于同一subnet中。這種方式的優點是一些業務上密切相關的容器可以共享POD的全部資源(它們一般不會產生資源上的沖突),而這些容器間的通信高效便利。

3.3 Flannel

在k8s的網絡設計中,服務以POD為單位,每個POD的IP地址,容器通過Behind the POD方式接入網絡(見“容器的網絡模型”),一個POD中可包含多個容器,這些容器共享該POD的IP地址。另外,k8s要求容器的IP地址都是全網可路由的,那么顯然docker0+iptables的NAT方案是不可行的。

實現上述要求其實有很多種組網方法,Flat L3是一種(如Calico),Hierarchy L3(如Romana)是一種,另外L3 Overlay也是可以的,CoreOS就采用L3 Overlay的方式設計了flannel, 并規定每個host下各個POD屬于同一個subnet,不同的host/VM下的POD屬于不同subnet。我們來看flannel的架構,控制平面上host本地的flanneld負責從遠端的ETCD集群同步本地和其它host上的subnet信息,并為POD分配IP地址。數據平面flannel通過UDP封裝來實現L3 Overlay,既可以選擇一般的TUN設備又可以選擇VxLAN設備(注意,由于圖來源不同,請忽略具體的IP地址)。

20180304205738256.png
2018030420574958.png

flannel是CoreOS提供用于解決Dokcer集群跨主機通訊的覆蓋網絡工具。它的主要思路是:預先留出一個網段,每個主機使用其中一部分,然后每個容器被分配不同的ip;讓所有的容器認為大家在同一個直連的網絡,底層通過UDP/VxLAN等進行報文的封裝和轉發。

20180304205800286.png

flannel默認使用8285端口作為UDP封裝報文的端口,VxLan使用8472端口。那么一條網絡報文是怎么從一個容器發送到另外一個容器的呢?

  1. 容器直接使用目標容器的ip訪問,默認通過容器內部的eth0發送出去。
  2. 報文通過veth pair被發送到vethXXX。
  3. vethXXX是直接連接到虛擬交換機docker0的,報文通過虛擬bridge docker0發送出去。
  4. 查找路由表,外部容器ip的報文都會轉發到flannel0虛擬網卡,這是一個P2P的虛擬網卡,然后報文就被轉發到監聽在另一端的flanneld。
  5. flanneld通過etcd維護了各個節點之間的路由表,把原來的報文UDP封裝一層,通過配置的iface發送出去。
  6. 報文通過主機之間的網絡找到目標主機。
  7. 報文繼續往上,到傳輸層,交給監聽在8285端口的flanneld程序處理。
  8. 數據被解包,然后發送給flannel0虛擬網卡。
  9. 查找路由表,發現對應容器的報文要交給docker0。
  10. docker0找到連到自己的容器,把報文發送過去。

應用部署工具Helm

https://www.kubernetes.org.cn/2700.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容