K8s 系列(六) - 淺談 CNI

1. 概述

進(jìn)入 K8s 的世界,會(huì)發(fā)現(xiàn)有很多方便擴(kuò)展的 Interface,包括 CNI, CSI, CRI 等挣郭,將這些接口抽象出來(lái),是為了更好的提供開(kāi)放疗韵、擴(kuò)展、規(guī)范等能力侄非。

K8s 網(wǎng)絡(luò)模型采用 CNI(Container Network Interface, 容器網(wǎng)絡(luò)接口) 協(xié)議蕉汪,只要提供一個(gè)標(biāo)準(zhǔn)的接口,就能為同樣滿足該協(xié)議的所有容器平臺(tái)提供網(wǎng)絡(luò)功能逞怨。

CNI 是 CoreOS 提出的一種容器網(wǎng)絡(luò)規(guī)范者疤,目前已被 Apache Mesos、Cloud Foundry叠赦、Kubernetes驹马、Kurma、rkt 等眾多開(kāi)源項(xiàng)目所采用除秀,同時(shí)也是一個(gè) CNCF(Cloud Native Computing Foundation) 項(xiàng)目糯累。可以預(yù)見(jiàn)册踩,CNI 將會(huì)成為未來(lái)容器網(wǎng)絡(luò)的標(biāo)準(zhǔn)泳姐。

本文將從 kubelet 啟動(dòng)、Pod 創(chuàng)建/刪除暂吉、Docker 創(chuàng)建/刪除 Container胖秒、CNI RPC 調(diào)用、容器網(wǎng)絡(luò)配置等核心流程慕的,對(duì) CSI 實(shí)現(xiàn)機(jī)制進(jìn)行了解析阎肝。

流程概覽如下:

K8s-CNI.png

本文及后續(xù)相關(guān)文章都基于 K8s v1.22

2. 從網(wǎng)絡(luò)模型說(shuō)起

容器的網(wǎng)絡(luò)技術(shù)日新月異,經(jīng)過(guò)多年發(fā)展肮街,業(yè)界逐漸聚焦到 Docker 的 CNM(Container Network Model, 容器網(wǎng)絡(luò)模型) 和 CoreOS 的 CNI(Container Network Interface, 容器網(wǎng)絡(luò)接口)风题。

2.1 CNM 模型

CNM 是一個(gè)被 Docker 提出的規(guī)范。現(xiàn)在已經(jīng)被 Cisco Contiv, Kuryr, Open Virtual Networking (OVN), Project Calico, VMware 和 Weave 這些公司和項(xiàng)目所采納低散。

Libnetwork 是 CNM 的原生實(shí)現(xiàn)俯邓。它為 Docker daemon 和網(wǎng)絡(luò)驅(qū)動(dòng)程序之間提供了接口。網(wǎng)絡(luò)控制器負(fù)責(zé)將驅(qū)動(dòng)和一個(gè)網(wǎng)絡(luò)進(jìn)行對(duì)接熔号。每個(gè)驅(qū)動(dòng)程序負(fù)責(zé)管理它所擁有的網(wǎng)絡(luò)以及為該網(wǎng)絡(luò)提供的各種服務(wù)稽鞭,例如 IPAM 等等。由多個(gè)驅(qū)動(dòng)支撐的多個(gè)網(wǎng)絡(luò)可以同時(shí)并存引镊。原生驅(qū)動(dòng)包括 none, bridge, overlay 以及 MACvlan朦蕴。

但是篮条,container runtime 會(huì)在不同情況下使用到不同的插件,這帶來(lái)了復(fù)雜性吩抓。另外涉茧,CNM 需要使用分布式存儲(chǔ)系統(tǒng)來(lái)保存網(wǎng)絡(luò)配置信息,例如 etcd疹娶。

CNM-model.png
  • Network Sandbox:容器內(nèi)部的網(wǎng)絡(luò)棧伴栓,包括網(wǎng)絡(luò)接口、路由表雨饺、DNS 等配置的管理钳垮。Sandbox 可用 Linux 網(wǎng)絡(luò)命名空間、FreeBSD Jail 等機(jī)制進(jìn)行實(shí)現(xiàn)额港。一個(gè) Sandbox 可以包含多個(gè) Endpoint饺窿。
  • Endpoint:用于將容器內(nèi)的 Sandbox 與外部網(wǎng)絡(luò)相連的網(wǎng)絡(luò)接口∫普叮可以使用 veth pair肚医、Open vSwitch 的內(nèi)部 port 等技術(shù)進(jìn)行實(shí)現(xiàn)。一個(gè) Endpoint 僅能夠加入一個(gè) Network向瓷。
  • Network:可以直接互連的 Endpoint 的集合肠套。可以通過(guò) Linux bridge风罩、VLAN 等技術(shù)進(jìn)行實(shí)現(xiàn)糠排。一個(gè) Network 包含多個(gè) Endpoint。

2.2 CNI 模型

CNI 是由 CoreOS 提出的一個(gè)容器網(wǎng)絡(luò)規(guī)范超升。已采納改規(guī)范的包括 Apache Mesos, Cloud Foundry, Kubernetes, Kurma 和 rkt入宦。另外 Contiv Networking, Project Calico 和 Weave 這些項(xiàng)目也為 CNI 提供插件。

CNI 對(duì)外暴露了從一個(gè)網(wǎng)絡(luò)里面添加和剔除容器的接口室琢。CNI 使用一個(gè) json 配置文件保存網(wǎng)絡(luò)配置信息乾闰。和 CNM 不一樣,CNI 不需要一個(gè)額外的分布式存儲(chǔ)引擎盈滴。

一個(gè)容器可以被加入到被不同插件所驅(qū)動(dòng)的多個(gè)網(wǎng)絡(luò)之中涯肩。一個(gè)網(wǎng)絡(luò)有自己對(duì)應(yīng)的插件和唯一的名稱。CNI 插件需要提供兩個(gè)命令:ADD 用來(lái)將網(wǎng)絡(luò)接口加入到指定網(wǎng)絡(luò)巢钓,DEL 用來(lái)將其移除病苗。這兩個(gè)接口分別在容器被創(chuàng)建和銷(xiāo)毀的時(shí)候被調(diào)用。

CNI-model.png

CNI支持與第三方 IPAM 的集成症汹,可以用于任何容器 runtime硫朦。CNM 從設(shè)計(jì)上就僅僅支持 Docker。由于 CNI 簡(jiǎn)單的設(shè)計(jì)背镇,許多人認(rèn)為編寫(xiě) CNI 插件會(huì)比編寫(xiě) CNM 插件來(lái)得簡(jiǎn)單咬展。

3. CNI 插件

CNI 插件是二進(jìn)制可執(zhí)行文件泽裳,會(huì)被 kubelet 調(diào)用。啟動(dòng) kubelet --network-plugin=cni, --cni-conf-dir 指定 networkconfig 配置破婆,默認(rèn)路徑是:/etc/cni/net.d涮总。另外,--cni-bin-dir 指定 plugin 可執(zhí)行文件路徑祷舀,默認(rèn)路徑是:/opt/cni/bin瀑梗。

看一個(gè) CNI Demo:
在默認(rèn)網(wǎng)絡(luò)配置目錄,配置兩個(gè) xxx.conf:一個(gè) type: "bridge" 網(wǎng)橋裳扯,另一個(gè) type: "loopback" 回環(huán)網(wǎng)卡夺克。

$ mkdir -p /etc/cni/net.d
$ cat >/etc/cni/net.d/10-mynet.conf <<EOF
{
    "cniVersion": "0.2.0", // CNI Spec 版本
    "name": "mynet", // 自定義名稱
    "type": "bridge", // 插件類型 bridge
    "bridge": "cni0", // 網(wǎng)橋名稱
    "isGateway": true, // 是否作為網(wǎng)關(guān)
    "ipMasq": true, // 是否設(shè)置 IP 偽裝
    "ipam": {
        "type": "host-local", // IPAM 類型 host-local
        "subnet": "10.22.0.0/16", // 子網(wǎng)段
        "routes": [
            { "dst": "0.0.0.0/0" } // 目標(biāo)路由段
        ]
    }
}
EOF
$ cat >/etc/cni/net.d/99-loopback.conf <<EOF
{
    "cniVersion": "0.2.0", // CNI Spec 版本
    "name": "lo", // 自定義名稱
    "type": "loopback" // 插件類型 loopback
}
EOF

CNI 插件可分為三類:

  • Main 插件:用來(lái)創(chuàng)建具體網(wǎng)絡(luò)設(shè)備的二進(jìn)制文件。比如嚎朽,bridge、ipvlan柬帕、loopback哟忍、macvlan、ptp(point-to-point, Veth Pair 設(shè)備)陷寝,以及 vlan锅很。如開(kāi)源的 Flannel、Weave 等項(xiàng)目凤跑,都屬于 bridge 類型的 CNI 插件爆安,在具體的實(shí)現(xiàn)中,它們往往會(huì)調(diào)用 bridge 這個(gè)二進(jìn)制文件仔引。

  • Meta 插件:由 CNI 社區(qū)維護(hù)的內(nèi)置 CNI 插件扔仓,不能作為獨(dú)立的插件使用,需要調(diào)用其他插件咖耘。tuning翘簇,是一個(gè)通過(guò) sysctl 調(diào)整網(wǎng)絡(luò)設(shè)備參數(shù)的二進(jìn)制文件;portmap儿倒,是一個(gè)通過(guò) iptables 配置端口映射的二進(jìn)制文件版保;bandwidth,是一個(gè)使用 Token Bucket Filter (TBF) 來(lái)進(jìn)行限流的二進(jìn)制文件夫否。

  • IPAM 插件:IP Address Management彻犁,它是負(fù)責(zé)分配 IP 地址的二進(jìn)制文件。比如凰慈,dhcp汞幢,這個(gè)文件會(huì)向 DHCP 服務(wù)器發(fā)起請(qǐng)求;host-local溉瓶,則會(huì)使用預(yù)先配置的 IP 地址段來(lái)進(jìn)行分配急鳄。

K8s-CNI-plugins.png

4. kubelet 啟動(dòng)

kubelet 在 Node 節(jié)點(diǎn)上負(fù)責(zé) Pod 的創(chuàng)建谤民、銷(xiāo)毀、監(jiān)控上報(bào)等核心流程疾宏,通過(guò) Cobra 命令行解析參數(shù)啟動(dòng)二進(jìn)制可執(zhí)行文件张足。

啟動(dòng)入口如下:

// kubernetes/cmd/kubelet/kubelet.go
func main() {
    command := app.NewKubeletCommand()

    // kubelet uses a config file and does its own special
    // parsing of flags and that config file. It initializes
    // logging after it is done with that. Therefore it does
    // not use cli.Run like other, simpler commands.
    code := run(command)
    os.Exit(code)
}

接著,一路往下進(jìn)行初始化:

cmd -> Run -> PreInitRuntimeService -> RunKubelet -> createAndInitKubelet -> startKubelet -> Run

其中 PreInitRuntimeService 會(huì)進(jìn)一步初始化 dockershim坎藐,一方面探測(cè)環(huán)境中的網(wǎng)絡(luò)配置文件(默認(rèn)路徑為:/etc/cni/net.d/*.conf/.conflist/.json)为牍,進(jìn)行 CNI 網(wǎng)絡(luò)配置;另一方面啟動(dòng) gRPC docker server 監(jiān)聽(tīng) client 請(qǐng)求岩馍,進(jìn)行具體的操作如 PodSandbox碉咆、Container 創(chuàng)建與刪除。

當(dāng)監(jiān)聽(tīng)到 Pod 事件時(shí)蛀恩,進(jìn)行對(duì)應(yīng) Pod 的創(chuàng)建或刪除疫铜,流程如下:

Run -> syncLoop -> SyncPodCreate/Kill -> UpdatePod -> syncPod/syncTerminatingPod -> dockershim gRPC -> Pod running/teminated

5. Pod 創(chuàng)建/刪除

K8s 中 Pod 的調(diào)諧采用 channel 生產(chǎn)者-消費(fèi)者模型實(shí)現(xiàn),具體通過(guò) PLEG(Pod Lifecycle Event Generator) 進(jìn)行 Pod 生命周期事件管理双谆。

// kubernetes/pkg/kubelet/pleg/pleg.go
// 通過(guò) PLEG 進(jìn)行 Pod 生命周期事件管理
type PodLifecycleEventGenerator interface {
    Start() // 通過(guò) relist 獲取所有 Pods 并計(jì)算事件類型
    Watch() chan *PodLifecycleEvent // 監(jiān)聽(tīng) eventChannel壳咕,傳遞給下游消費(fèi)者
    Healthy() (bool, error)
}

Pod 事件生產(chǎn)者 - 相關(guān)代碼:

// kubernetes/pkg/kubelet/pleg/generic.go
// 生產(chǎn)者:獲取所有 Pods 列表,計(jì)算出對(duì)應(yīng)的事件類型顽馋,進(jìn)行 Sync
func (g *GenericPLEG) relist() {
    klog.V(5).InfoS("GenericPLEG: Relisting")
    ...
    // 獲取當(dāng)前所有 Pods 列表
    podList, err := g.runtime.GetPods(true)
    if err != nil {
        klog.ErrorS(err, "GenericPLEG: Unable to retrieve pods")
        return
    }
    
    for pid := range g.podRecords {
        allContainers := getContainersFromPods(oldPod, pod)
        for _, container := range allContainers {

            // 計(jì)算事件類型:running/exited/unknown/non-existent
            events := computeEvents(oldPod, pod, &container.ID)
            for _, e := range events {
                updateEvents(eventsByPodID, e)
            }
        }
    }

    // 遍歷所有事件
    for pid, events := range eventsByPodID {
        for i := range events {
            // Filter out events that are not reliable and no other components use yet.
            if events[i].Type == ContainerChanged {
                continue
            }
            select {
            case g.eventChannel <- events[i]: // 生產(chǎn)者:發(fā)送到事件 channel谓厘,對(duì)應(yīng)監(jiān)聽(tīng)的 goroutine 會(huì)消費(fèi)
            default:
                metrics.PLEGDiscardEvents.Inc()
                klog.ErrorS(nil, "Event channel is full, discard this relist() cycle event")
            }
        }
    }
    ...
}

Pod 事件消費(fèi)者 - 相關(guān)代碼:

// kubernetes/pkg/kubelet/kubelet.go
// 消費(fèi)者:根據(jù) channel 獲取的各類事件,進(jìn)行 Pod Sync
func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
    syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
    select {
    ...
    // 消費(fèi)者:監(jiān)聽(tīng) plegCh 的事件
    case e := <-plegCh:
        if e.Type == pleg.ContainerStarted {
            // 更新容器的最后啟動(dòng)時(shí)間
            kl.lastContainerStartedTime.Add(e.ID, time.Now())
        }
        if isSyncPodWorthy(e) {
            if pod, ok := kl.podManager.GetPodByUID(e.ID); ok {
                klog.V(2).InfoS("SyncLoop (PLEG): event for pod", "pod", klog.KObj(pod), "event", e)

                // 進(jìn)行相關(guān) Pod 事件的 Sync
                handler.HandlePodSyncs([]*v1.Pod{pod})
            } else {
                // If the pod no longer exists, ignore the event.
                klog.V(4).InfoS("SyncLoop (PLEG): pod does not exist, ignore irrelevant event", "event", e)
            }
        }

        // 容器銷(xiāo)毀事件處理:清除 Pod 內(nèi)相關(guān) Container
        if e.Type == pleg.ContainerDied {
            if containerID, ok := e.Data.(string); ok {
                kl.cleanUpContainersInPod(e.ID, containerID)
            }
        }
        ...
    }
    return true
}

6. Docker 忙起來(lái)

經(jīng)過(guò)上一步 Pod 事件的生產(chǎn)與消費(fèi)傳遞寸谜,PodWorkers 會(huì)將事件轉(zhuǎn)化為 gRPC client 請(qǐng)求竟稳,然后調(diào)用 dockershim gRPC server,進(jìn)行 PodSandbox熊痴、infra-container(也叫 pause 容器) 的創(chuàng)建他爸。

接著,會(huì)調(diào)用 CNI 接口 SetUpPod 進(jìn)行相關(guān)網(wǎng)絡(luò)配置與啟動(dòng)愁拭,此時(shí)建立起來(lái)的容器網(wǎng)絡(luò)讲逛,就可以直接用于之后創(chuàng)建的業(yè)務(wù)容器如 initContainers、containers 進(jìn)行共享網(wǎng)絡(luò)岭埠。

相關(guān)代碼如下:

// kubernetes/pkg/kubelet/dockershim/docker_sandbox.go
// 啟動(dòng)運(yùn)行 Pod Sandbox
func (ds *dockerService) RunPodSandbox(ctx context.Context, r *runtimeapi.RunPodSandboxRequest) (*runtimeapi.RunPodSandboxResponse, error) {
    config := r.GetConfig()

    // Step 1: 拉取基礎(chǔ)鏡像(infra-container: k8s.gcr.io/pause:3.6)
    image := defaultSandboxImage
    if err := ensureSandboxImageExists(ds.client, image); err != nil {
        return nil, err
    }

    // Step 2: 創(chuàng)建 Sandbox 容器
    createConfig, err := ds.makeSandboxDockerConfig(config, image)
    if err != nil {
        return nil, fmt.Errorf("failed to make sandbox docker config for pod %q: %v", config.Metadata.Name, err)
    }
    createResp, err := ds.client.CreateContainer(*createConfig)
    if err != nil {
        createResp, err = recoverFromCreationConflictIfNeeded(ds.client, *createConfig, err)
    }

    // Step 3: 創(chuàng)建 Sandbox 檢查點(diǎn)(用于記錄當(dāng)前執(zhí)行到哪一步了)
    if err = ds.checkpointManager.CreateCheckpoint(createResp.ID, constructPodSandboxCheckpoint(config)); err != nil {
        return nil, err
    }

    // Step 4: 啟動(dòng) Sandbox 容器
    err = ds.client.StartContainer(createResp.ID)
    if err != nil {
        return nil, fmt.Errorf("failed to start sandbox container for pod %q: %v", config.Metadata.Name, err)
    }

    // Step 5: 對(duì) Sandbox 容器進(jìn)行網(wǎng)絡(luò)配置
    err = ds.network.SetUpPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID, config.Annotations, networkOptions)
    if err != nil {
        // 如果網(wǎng)絡(luò)配置失敗盏混,則回滾:刪除建立起來(lái)的 Pod 網(wǎng)絡(luò)
        err = ds.network.TearDownPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID)
        if err != nil {
            errList = append(errList, fmt.Errorf("failed to clean up sandbox container %q network for pod %q: %v", createResp.ID, config.Metadata.Name, err))
        }
        
        // 停止容器運(yùn)行
        err = ds.client.StopContainer(createResp.ID, defaultSandboxGracePeriod)
        ...
    }

    return resp, nil
}

流程圖小結(jié)如下:

K8s-CNI-flow.png

根據(jù)社區(qū)討論(https://kubernetes.io/blog/2020/12/02/dockershim-faq/),dockershim 相關(guān)代碼將會(huì)在 2021 底左右移出 K8s 主干代碼惜论,之后將統(tǒng)一使用 CRI(Container Runtime Interface, 容器運(yùn)行時(shí)接口) 進(jìn)行容器生命周期管理许赃。

7. CNI RPC 接口

CNI 標(biāo)準(zhǔn)規(guī)范接口,包含了添加馆类、檢查混聊、驗(yàn)證、刪除網(wǎng)絡(luò)等接口乾巧,并提供了按列表或單個(gè)進(jìn)行網(wǎng)絡(luò)配置的兩組接口句喜,方便用戶靈活使用预愤。

CNI 從容器管理系統(tǒng)(dockershim) 處獲取運(yùn)行時(shí)信息(Container Runtime),包括 network namespace 的路徑咳胃,容器 ID 以及 network interface name植康,再?gòu)娜萜骶W(wǎng)絡(luò)的配置文件中加載網(wǎng)絡(luò)配置信息,再將這些信息傳遞給對(duì)應(yīng)的插件展懈,由插件進(jìn)行具體的網(wǎng)絡(luò)配置工作销睁,并將配置的結(jié)果再返回到容器管理系統(tǒng)中。

K8s-CNI-RPC.png

用戶若要編寫(xiě)自己的 CNI 插件存崖,則可專注于實(shí)現(xiàn)圖中這些 RPC 接口即可冻记,然后可以與官方維護(hù)的三類基礎(chǔ)插件自由組合,形成多種多樣的容器網(wǎng)絡(luò)解決方案来惧。

8. 小結(jié)

本文通過(guò)分析 K8s 中 kubelet 啟動(dòng)冗栗、Pod 創(chuàng)建/刪除、Docker 創(chuàng)建/刪除 Container供搀、CNI RPC 調(diào)用贞瞒、容器網(wǎng)絡(luò)配置等核心流程,對(duì) CNI 實(shí)現(xiàn)機(jī)制進(jìn)行了解析趁曼,通過(guò)源碼、圖文方式說(shuō)明了相關(guān)流程邏輯棕洋,以期更好的理解 K8s CNI 運(yùn)行流程挡闰。

K8s 網(wǎng)絡(luò)模型采用 CNI(Container Network Interface, 容器網(wǎng)絡(luò)接口) 協(xié)議,只要提供一個(gè)標(biāo)準(zhǔn)的接口掰盘,就能為同樣滿足該協(xié)議的所有容器平臺(tái)提供網(wǎng)絡(luò)功能摄悯。CNI 目前已被眾多開(kāi)源項(xiàng)目所采用,同時(shí)也是一個(gè) CNCF(Cloud Native Computing Foundation) 項(xiàng)目愧捕∩菅保可以預(yù)見(jiàn),CNI 將會(huì)成為未來(lái)容器網(wǎng)絡(luò)的標(biāo)準(zhǔn)次绘。

PS: 更多內(nèi)容請(qǐng)關(guān)注 k8s-club

參考資料

  1. CNI 規(guī)范
  2. Kubernetes 源碼
  3. CNI 插件介紹
  4. CNI 插件源碼
  5. CNI and CNM model
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘪阁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子邮偎,更是在濱河造成了極大的恐慌管跺,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禾进,死亡現(xiàn)場(chǎng)離奇詭異豁跑,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)泻云,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)艇拍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)狐蜕,“玉大人,你說(shuō)我怎么就攤上這事卸夕〔闶停” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵娇哆,是天一觀的道長(zhǎng)湃累。 經(jīng)常有香客問(wèn)我,道長(zhǎng)碍讨,這世上最難降的妖魔是什么治力? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮勃黍,結(jié)果婚禮上宵统,老公的妹妹穿的比我還像新娘。我一直安慰自己覆获,他們只是感情好马澈,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著弄息,像睡著了一般痊班。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上摹量,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天涤伐,我揣著相機(jī)與錄音,去河邊找鬼缨称。 笑死凝果,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的睦尽。 我是一名探鬼主播器净,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼当凡!你這毒婦竟也來(lái)了山害?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤沿量,失蹤者是張志新(化名)和其女友劉穎粗恢,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體欧瘪,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眷射,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妖碉。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡涌庭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出欧宜,到底是詐尸還是另有隱情坐榆,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布冗茸,位于F島的核電站席镀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏夏漱。R本人自食惡果不足惜豪诲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挂绰。 院中可真熱鬧屎篱,春花似錦、人聲如沸葵蒂。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)践付。三九已至秦士,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間永高,已是汗流浹背伍宦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乏梁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓关贵,卻偏偏與公主長(zhǎng)得像遇骑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子揖曾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容