Docker鏡像:第一部分-減小鏡像大小

原文地址:Docker Images : Part I - Reducing Image Size

介紹

在開(kāi)始使用容器時(shí)突雪,我們很容易對(duì)生成的鏡像大小感到震驚擦秽。在不犧牲開(kāi)發(fā)人員和操作人員的便利性的前提下,我們將回顧多種減少鏡像大小的技術(shù)。在第一部分中,我們將討論多階段構(gòu)建挂捻,因?yàn)槿魏稳讼胍獪p小鏡像大小,都應(yīng)該從這里開(kāi)始船万。我們還將說(shuō)明靜態(tài)鏈接和動(dòng)態(tài)鏈接之間的區(qū)別刻撒,以及我們?yōu)槭裁匆P(guān)注這些。這也是介紹Alpine的機(jī)會(huì)耿导。

在第二部分中声怔,我們將看到與各種流行語(yǔ)言相關(guān)的一些特殊性。我們將討論Go碎节,以及Java捧搞,Node抵卫,Python狮荔,Ruby和Rust胎撇。我們還將討論有關(guān)Alpine的更多信息,以及如何全面利用Alpine殖氏。

在第三部分中晚树,我們將介紹一些與大多數(shù)語(yǔ)言和框架相關(guān)的模式(和反模式!)雅采,例如使用通用基本鏡像爵憎,剝離二進(jìn)制文件并減小大小。我們將總結(jié)一些更奇特的或高級(jí)的方法婚瓜,例如Bazel宝鼓,Distroless,DockerSlim或UPX巴刻。我們將看到其中的一些在某些情況下會(huì)適得其反愚铡,但在某些特定情況下可能會(huì)有用。

請(qǐng)注意胡陪,示例代碼以及此處提到的所有Dockerfile沥寥,都可以在公共GitHub存儲(chǔ)庫(kù)中方便地獲得,所有鏡像都帶有用來(lái)構(gòu)建的Compose文件柠座,并可以輕松比較它們的大小邑雅。

我們要解決的問(wèn)題

我敢打賭,每個(gè)構(gòu)建了第一個(gè)Docker鏡像并編譯了一些代碼的人都對(duì)該鏡像的大新杈(不是很好)感到驚訝淮野。

看一下用C編寫(xiě)的這個(gè)“ hello world”程序:

/* hello.c */
int main () {
  puts("Hello, world!");
  return 0;
}

我們可以使用以下Dockerfile構(gòu)建它:

FROM gcc
COPY hello.c .
RUN gcc -o hello hello.c
CMD ["./hello"]

…但是生成的鏡像將超過(guò)1 GB,因?yàn)樗鼘麄€(gè)gcc鏡像狂塘!

如果使用Ubuntu鏡像录煤,安裝C編譯器并構(gòu)建程序,則會(huì)得到300 MB的鏡像荞胡;看起來(lái)更好了妈踊,但對(duì)于小于20 kB的二進(jìn)制文件而言,仍然太多了:

$ ls -l hello
-rwxr-xr-x   1 root root 16384 Nov 18 14:36 hello

與等效的Go程序的情況相同:

package main

import "fmt"

func main () {
  fmt.Println("Hello, world!")
}

使用該golang鏡像構(gòu)建此代碼泪漂,即使hello程序只有2 MB 廊营,生成的鏡像仍為800 MB:

$ ls -l hello
-rwxr-xr-x 1 root root 2008801 Jan 15 16:41 hello

一定有更好的方法!

讓我們看看如何大幅度減小這些鏡像的大小萝勤。在某些情況下露筒,我們可以實(shí)現(xiàn)99.8%的尺寸減小(但是敌卓,這么大的減幅并不總是一個(gè)好事)慎式。

提示:為了輕松比較鏡像的大小,我們將使用相同的鏡像名稱(chēng),但使用不同的標(biāo)簽瘪吏。舉例來(lái)說(shuō)癣防,我們的鏡像會(huì)叫做hello:gcc,hello:ubuntu掌眠,hello:thisweirdtrick等蕾盯,我們就可以運(yùn)行docker images hello,它會(huì)列出所有標(biāo)簽為hello的鏡像蓝丙,并標(biāo)記它們的大小级遭,在我們自己的Docker引擎上不會(huì)被其他的鏡像干擾。

多階段構(gòu)建

這是減小鏡像尺寸的第一步(也是最有效的一步)渺尘。不過(guò)挫鸽,我們需要小心,因?yàn)槿绻幚聿徽_鸥跟,可能會(huì)導(dǎo)致鏡像難以繼續(xù)操作(甚至可能完全損壞)掠兄。

多階段構(gòu)建來(lái)自一個(gè)簡(jiǎn)單的想法:“我不需要在最終的鏡像中包括C或Go編譯器以及整個(gè)構(gòu)建工具鏈。我只想傳輸二進(jìn)制文件锌雀!”

我們通過(guò)在Dockerfile中添加另一行FROM來(lái)獲得多階段構(gòu)建蚂夕。看下面的例子:

FROM gcc AS mybuildstage
COPY hello.c .
RUN gcc -o hello hello.c
FROM ubuntu
COPY --from=mybuildstage hello .
CMD ["./hello"]

我們使用gcc鏡像來(lái)構(gòu)建我們的hello.c程序腋逆。然后婿牍,我們使用該ubuntu鏡像開(kāi)始一個(gè)新階段(我們稱(chēng)為“運(yùn)行階段”)。我們從上一階段復(fù)制hello二進(jìn)制文件惩歉。最終鏡像為64 MB等脂,而不是1.1 GB,因此大小減少了約95%:

$ docker images minimage
REPOSITORY          TAG                    ...         SIZE
minimage            hello-c.gcc            ...         1.14GB
minimage            hello-c.gcc.ubuntu     ...         64.2MB

還不錯(cuò)吧撑蚌?我們可以做得更好上遥。但是首先來(lái)了解一些技巧和警告。

在聲明構(gòu)建階段時(shí)争涌,不必使用AS關(guān)鍵字粉楚。從上一個(gè)階段復(fù)制文件時(shí),你只需指明該構(gòu)建階段的編號(hào)(從零開(kāi)始)亮垫。

換句話說(shuō)模软,以下兩行是等效的:

COPY --from=mybuildstage hello .
COPY --from=0 hello .

就個(gè)人而言,我認(rèn)為在構(gòu)建階段中饮潦,對(duì)于較短的Dockerfile(例如燃异,少于10行)使用數(shù)字是很好的,但是當(dāng)Dockerfile變長(zhǎng)(并且可能更復(fù)雜继蜡,具有多個(gè)構(gòu)建階段)回俐,最好用明確的命名逛腿。這將有助于你的團(tuán)隊(duì)成員進(jìn)行維護(hù)(以及未來(lái)幾個(gè)月后你可能也會(huì)回來(lái)檢查)。

警告:使用經(jīng)典鏡像
我強(qiáng)烈建議在“運(yùn)行”階段使用經(jīng)典鏡像仅颇■猓“經(jīng)典”是指CentOS,Debian灵莲,F(xiàn)edora,Ubuntu等一些熟悉的鏡像殴俱。你可能聽(tīng)說(shuō)過(guò)Alpine政冻,并很想使用它。千萬(wàn)不要线欲!至少現(xiàn)在還不是時(shí)候明场。稍后我們將討論Alpine,并解釋為什么我們需要謹(jǐn)慎使用Alpine李丰。

警告:COPY --from 使用絕對(duì)路徑
從上一階段復(fù)制文件時(shí)苦锨,路徑被解釋為相對(duì)于上一階段的根目錄的相對(duì)路徑。

一旦我們使用帶有WORKDIR的構(gòu)建器鏡像(例如golang鏡像)趴泌,問(wèn)題就會(huì)出現(xiàn)舟舒。

如果我們嘗試構(gòu)建此Dockerfile:

FROM golang
COPY hello.go .
RUN go build hello.go
FROM ubuntu
COPY --from=0 hello .
CMD ["./hello"]

我們會(huì)得到與以下錯(cuò)誤類(lèi)似的錯(cuò)誤:

COPY failed: stat /var/lib/docker/overlay2/1be...868/merged/hello: no such file or directory

這是因?yàn)镃OPY命令嘗試復(fù)制/hello,但是由于WORKDIR在 golang是/go嗜憔,所以程序路徑實(shí)際上是/go/hello秃励。

如果我們?cè)跇?gòu)建中使用正式(或非常穩(wěn)定)的鏡像,則可以指定完整的絕對(duì)路徑吉捶。

但是夺鲜,如果將來(lái)我們的構(gòu)建或運(yùn)行鏡像可能會(huì)更改,我建議在構(gòu)建映像中指定一個(gè)WORKDIR呐舔。這將確保文件在期望的位置币励,即使未來(lái)用于構(gòu)建的基礎(chǔ)鏡像發(fā)生更改。

遵循此原則珊拼,用于構(gòu)建Go程序的Dockerfile如下所示:

FROM golang
WORKDIR /src
COPY hello.go .
RUN go build hello.go
FROM ubuntu
COPY --from=0 /src/hello .
CMD ["./hello"]

如果想知道Golang多階段構(gòu)建的效率食呻,它可以從800 MB的鏡像下降到66 MB的鏡像:

$ docker images minimage
REPOSITORY     TAG                              ...    SIZE
minimage       hello-go.golang                  ...    805MB
minimage       hello-go.golang.ubuntu-workdir   ...    66.2MB

使用 FROM scratch

回到我們的“ Hello World”程序。C版本為16 kB澎现,Go版本為2 MB搁进。我們可以得到這么大的鏡像嗎?

我們可以?xún)H使用二進(jìn)制文件而不用其他文件來(lái)構(gòu)建鏡像嗎昔头?

可以! 我們要做的就是使用多階段構(gòu)建饼问,然后選擇scratch作為我們的運(yùn)行鏡像。scratch是虛擬鏡像揭斧,不能拉取或運(yùn)行它莱革,因?yàn)樗耆强盏木摺_@就是為什么Dockerfile以FROM scratch開(kāi)頭的原因,這意味著我們是從頭開(kāi)始構(gòu)建的盅视,沒(méi)有使用任何預(yù)先存在的成分捐名。

這為我們提供了以下Dockerfile:

FROM golang
COPY hello.go .
RUN go build hello.go
FROM scratch
COPY --from=0 /go/hello .
CMD ["./hello"]

如果我們構(gòu)建該鏡像,則其大小恰好是二進(jìn)制文件的大心只鳌(2 MB)镶蹋,并且可以正常工作!

但是赏半,在scratch用作基礎(chǔ)時(shí)贺归,需要牢記一些注意事項(xiàng)。

沒(méi)有shell
該scratch鏡像沒(méi)有命令解析器外殼断箫。這意味著我們不能將字符串語(yǔ)法與CMD(或RUN)一起使用拂酣。考慮以下Dockerfile:

...
FROM scratch
COPY --from=0 /go/hello .
CMD ./hello

如果嘗試docker run生成結(jié)果鏡像仲义,則會(huì)收到以下錯(cuò)誤消息:

docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "exec: \"/bin/sh\": stat /bin/sh: no such file or directory": unknown.

它的顯示方式不是很清楚婶熬,但是核心信息在這里:鏡像中缺少/bin/sh。

發(fā)生這種情況是因?yàn)楫?dāng)我們將字符串語(yǔ)法與CMD或RUN 一起使用時(shí)埃撵,參數(shù)將傳遞給/bin/sh赵颅。這意味著我們CMD ./hello上面會(huì)執(zhí)行/bin/sh -c "./hello",因?yàn)樵趕cratch鏡像中不存在/bin/sh暂刘,進(jìn)而導(dǎo)致失敗性含。

解決方法很簡(jiǎn)單:在Dockerfile中使用JSON語(yǔ)法。CMD ./hello改為CMD ["./hello"]鸳惯。當(dāng)Docker檢測(cè)到JSON語(yǔ)法時(shí)商蕴,它將直接運(yùn)行參數(shù),而無(wú)需使用shell芝发。

沒(méi)有調(diào)試工具

根據(jù)scratch定義绪商,該鏡像為空;因此它沒(méi)有任何幫助我們查詢(xún)?nèi)萜鲉?wèn)題的方法辅鲸。無(wú)shell(正如我們?cè)谏弦欢握f(shuō)的)也沒(méi)什么ls格郁,psping独悴,等等例书。這意味著我們無(wú)法向容器輸入內(nèi)容(使用docker execkubectl exec進(jìn)行查看)。

(請(qǐng)注意刻炒,嚴(yán)格來(lái)說(shuō)决采,有一些方法可以對(duì)我們的容器進(jìn)行故障追蹤。我們可以docker cp用來(lái)從容器中取出文件坟奥;我們可以docker run --net container:用來(lái)與網(wǎng)絡(luò)堆棧進(jìn)行交互树瞭;像nsenter這樣的低級(jí)工具可能非常強(qiáng)大拇厢。 Kubernetes最近的版本具有臨時(shí)容器的概念,但它仍然處于alpha狀態(tài)晒喷。請(qǐng)記住孝偎,所有這些技術(shù)肯定會(huì)使我們的工作變得更加復(fù)雜,尤其是當(dāng)我們有很多事情要做的時(shí)候A骨谩)

這里的一個(gè)解決辦法是使用類(lèi)似busyboxalpine的鏡像代替scratch衣盾。當(dāng)然,它們更大(分別為1.2 MB和5.5 MB)爷抓,但是在龐大的程序中势决,如果將其與原始圖像的數(shù)百兆字節(jié)或千兆字節(jié)進(jìn)行比較,付出的代價(jià)其實(shí)很小废赞。

沒(méi)有l(wèi)ibc
這是一個(gè)更加棘手的問(wèn)題。我們?cè)贕o中使用簡(jiǎn)單的“ hello world”可以很好地工作叮姑,但是唉地,如果我們嘗試在scratch鏡像中放置C程序,或者是在更復(fù)雜的Go程序(例如传透,使用網(wǎng)絡(luò)類(lèi)庫(kù)的任何程序)耘沼,則會(huì)收到以下錯(cuò)誤消息:

standard_init_linux.go:211: exec user process caused "no such file or directory"

某些文件似乎丟失了。但這并不能告訴我們確切缺少哪個(gè)文件朱盐。

丟失的文件是運(yùn)行我們的程序所必需的動(dòng)態(tài)庫(kù)群嗤。

什么是動(dòng)態(tài)庫(kù),為什么我們需要它兵琳?

程序編譯后狂秘,將與所使用的庫(kù)鏈接。(很簡(jiǎn)單躯肌,我們的“ hello world”程序也在使用庫(kù)者春,就是puts函數(shù)。)很久以前(90年代之前)清女,我們主要使用靜態(tài)鏈接钱烟,這意味著所使用的所有庫(kù)將包含在二進(jìn)制文件中。當(dāng)從軟盤(pán)或磁帶執(zhí)行軟件時(shí)嫡丙,或者根本沒(méi)有標(biāo)準(zhǔn)庫(kù)時(shí)拴袭,這是完美的選擇。但是曙博,在像Linux這樣的分時(shí)系統(tǒng)上拥刻,我們運(yùn)行許多并發(fā)程序,這些程序存儲(chǔ)在硬盤(pán)上父泳。這些程序幾乎總是使用標(biāo)準(zhǔn)的C庫(kù)泰佳。

在這種情況下盼砍,使用動(dòng)態(tài)鏈接會(huì)變得更加有利。使用動(dòng)態(tài)鏈接逝她,最終的二進(jìn)制文件不包含它使用的所有庫(kù)的代碼浇坐。相反,它包含這些庫(kù)的引用黔宛,如“這個(gè)程序需要的功能cossintan來(lái)自libtrigonometry.so近刘。執(zhí)行程序時(shí),系統(tǒng)會(huì)查找libtrigonometry.so并將其與程序一起加載臀晃,以便程序可以調(diào)用這些函數(shù)觉渴。

動(dòng)態(tài)鏈接具有多個(gè)優(yōu)點(diǎn)。

  1. 由于不再需要復(fù)制通用庫(kù)徽惋,因此可以節(jié)省磁盤(pán)空間案淋。
  2. 由于這些庫(kù)可以從磁盤(pán)加載一次,然后在多個(gè)程序之間共享险绘,因此可以節(jié)省內(nèi)存踢京。
  3. 這使維護(hù)更加容易,因?yàn)樵诟聨?kù)時(shí)宦棺,我們不需要使用該庫(kù)重新編譯所有程序瓣距。

(如果我們想更透徹一點(diǎn),內(nèi)存節(jié)省不是動(dòng)態(tài)庫(kù)的結(jié)果代咸,而是共享庫(kù)的結(jié)果蹈丸。也就是說(shuō),兩者通衬沤妫可以并存逻杖。你知道嗎,在Linux上思瘟,動(dòng)態(tài)庫(kù)文件通常具有擴(kuò)展名.so弧腥,即代表共享庫(kù)(share object)。在Windows上是.DLL潮太,它代表動(dòng)態(tài)鏈接庫(kù)(Dynamic-link library管搪。

回過(guò)頭看我們的程序:默認(rèn)情況下,C程序是動(dòng)態(tài)鏈接的铡买。對(duì)于某些包更鲁,Go程序也是如此。我們的特定程序使用標(biāo)準(zhǔn)的C庫(kù)奇钞,該庫(kù)在最新的Linux系統(tǒng)上將在libc.so.6文件中澡为。因此,要運(yùn)行我們的程序景埃,需要將該文件放到在容器鏡像中媒至。如果使用scratch顶别,則顯然沒(méi)有該文件。如果我們使用busybox或alpine情況是相同的拒啰,因?yàn)閎usybox它不包含標(biāo)準(zhǔn)庫(kù)驯绎,alpine正在使用另一個(gè)不兼容的庫(kù)。稍后我們將詳細(xì)介紹谋旦。

我們?cè)撊绾谓鉀Q剩失?至少有3種方案。

構(gòu)建靜態(tài)二進(jìn)制文件
我們可以告訴我們的工具鏈制作一個(gè)靜態(tài)二進(jìn)制文件册着。有多種方法可以實(shí)現(xiàn)這一目標(biāo)(取決于我們起初構(gòu)建程序的方式)拴孤,但是如果使用gcc,我們需要添加-static到命令行中:

gcc -o hello hello.c -static

現(xiàn)在生成的二進(jìn)制文件是760 kB(在我的系統(tǒng)上)甲捏,而不是16 kB演熟。當(dāng)然,我們將庫(kù)嵌入到了二進(jìn)制文件中司顿,因此它要大得多芒粹。但是該二進(jìn)制文件現(xiàn)在可以在scratch鏡像中正確運(yùn)行。

如果使用Alpine構(gòu)建靜態(tài)二進(jìn)制文件免猾,則可以得到更小的圖像是辕。結(jié)果小于100 kB囤热!

將庫(kù)添加到我們的鏡像

我們可以使用ldd工具找出程序需要哪些庫(kù):

$ ldd hello
    linux-vdso.so.1 (0x00007ffdf8acb000)
    libc.so.6 => /usr/lib/libc.so.6 (0x00007ff897ef6000)
    /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007ff8980f7000)

我們可以看到程序所需的庫(kù)猎提,以及系統(tǒng)找到它們的實(shí)際路徑。

在上面的示例中旁蔼,唯一的“真實(shí)”庫(kù)是libc.so.6锨苏。linux-vdso.so.1與一種稱(chēng)為VDSO(虛擬動(dòng)態(tài)共享對(duì)象)的機(jī)制有關(guān),該機(jī)制可以加速某些系統(tǒng)調(diào)用棺聊。讓我們假裝它不在那里伞租。至于ld-linux-x86-64.so.2,它實(shí)際上是動(dòng)態(tài)鏈接器本身限佩。(從技術(shù)上講葵诈,我們的hello二進(jìn)制文件包含的信息表述:“嘿,這是一個(gè)動(dòng)態(tài)程序祟同,并且知道如何將其所有部分放在一起作喘,這就是ld-linux-x86-64.so.2”。)

如果我們?cè)敢?/em>晕城,可以將上面ldd列出的所有文件手動(dòng)添加到鏡像中泞坦。這將是相當(dāng)繁瑣且難以維護(hù)的,尤其是對(duì)于程序有很多依賴(lài)的情況砖顷。對(duì)于我們小的hello world程序贰锁,我們可以這么做赃梧。但是對(duì)于更復(fù)雜的程序,例如使用DNS的程序豌熄,我們會(huì)遇到另一個(gè)問(wèn)題授嘀。GNU C庫(kù)(在大多數(shù)Linux系統(tǒng)上使用)通過(guò)相當(dāng)復(fù)雜的稱(chēng)為名稱(chēng)服務(wù)開(kāi)關(guān)(簡(jiǎn)稱(chēng)為NSS )的機(jī)制實(shí)現(xiàn)DNS(以及其他一些功能)。該機(jī)制需要一個(gè)配置文件/etc/nsswitch.conf和其他庫(kù)房轿。但是這些庫(kù)沒(méi)有在ldd中展現(xiàn)粤攒,因?yàn)樗鼈兩院髸?huì)在程序運(yùn)行時(shí)加載。如果我們希望DNS解析正常工作囱持,我們?nèi)匀恍枰ㄋ鼈儯夯接。ㄟ@些庫(kù)通常位于/lib64/libnss_*。)

我個(gè)人不建議這樣做纷妆,因?yàn)樗苌衩乜福y以維護(hù),將來(lái)很可能會(huì)被改變掩幢。

使用 busybox:glibc
有專(zhuān)門(mén)為解決所有這些問(wèn)題而設(shè)計(jì)的鏡像:busybox:glibc逊拍。busybox是一個(gè)小鏡像(5 MB),提供了許多用于故障排除和操作的有用工具际邻,并提供了GNU C庫(kù)(或glibc)芯丧。該鏡像恰好包含我們前面提到的所有這些討厭的文件。如果要在小的鏡像中運(yùn)行動(dòng)態(tài)二進(jìn)制文件世曾,可以使用此方法缨恒。

但是請(qǐng)記住,如果我們的程序使用其他庫(kù)轮听,則也需要復(fù)制這些庫(kù)骗露。

總結(jié)和(部分)結(jié)論

讓我們看看在C. Spoiler alert中如何為“ hello world”程序做些事情:此列表包括了通過(guò)使用Alpine獲得的結(jié)果,本系后續(xù)文章會(huì)介紹這種方式血巍。

  • 原始鏡像內(nèi)置gcc:1.14 GB
  • gcc和ubuntu多級(jí)構(gòu)建:64.2 MB
  • 使用alpine萧锉,靜態(tài)glibc二進(jìn)制:6.5 MB
  • 使用alpine,動(dòng)態(tài)二進(jìn)制:5.6 MB
  • 使用scratch述寡,靜態(tài)二進(jìn)制:940 kB
  • 使用scratch柿隙,靜態(tài)musl二進(jìn)制:94 kB

大小減少了12000倍,磁盤(pán)空間減少了99.99%鲫凶。

不錯(cuò)禀崖。

就個(gè)人而言,我不會(huì)使用scratch鏡像(因?yàn)閷?duì)它們進(jìn)行故障定位可能會(huì)很麻煩)掀序,但是如果你要這樣做帆焕,那么它們就在這里!

在下一部分中,我們將介紹Go語(yǔ)言特定的一些方面叶雹,包括cgo和標(biāo)簽财饥。我們還將介紹其他流行語(yǔ)言,并且我們將討論更多有關(guān)Alpine的信息,它非常棒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瑟由,一起剝皮案震驚了整個(gè)濱河市叭披,隨后出現(xiàn)的幾起案子阿纤,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異宁改,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)魂莫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)还蹲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人耙考,你說(shuō)我怎么就攤上這事谜喊。” “怎么了倦始?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵斗遏,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我鞋邑,道長(zhǎng)诵次,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任炫狱,我火速辦了婚禮藻懒,結(jié)果婚禮上剔猿,老公的妹妹穿的比我還像新娘视译。我一直安慰自己,他們只是感情好归敬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布酷含。 她就那樣靜靜地躺著,像睡著了一般汪茧。 火紅的嫁衣襯著肌膚如雪椅亚。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天舱污,我揣著相機(jī)與錄音呀舔,去河邊找鬼。 笑死扩灯,一個(gè)胖子當(dāng)著我的面吹牛媚赖,可吹牛的內(nèi)容都是我干的霜瘪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼惧磺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼颖对!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起磨隘,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缤底,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后番捂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體个唧,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年设预,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坑鱼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡絮缅,死狀恐怖鲁沥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情耕魄,我是刑警寧澤画恰,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站吸奴,受9級(jí)特大地震影響允扇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜则奥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一考润、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧读处,春花似錦糊治、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至管闷,卻和暖如春粥脚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背包个。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工刷允, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓树灶,卻偏偏與公主長(zhǎng)得像搀菩,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子破托,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348