使用Dockerfile構建Docker鏡像

目錄

  1. 前言
  2. Dockerfile的書寫規則及指令使用方法
  3. 創建Dockerfile,構建運行環境
  4. 構建鏡像
  5. Dockerfile參考示例
  6. Dockerfile最佳實踐

1. 前言

Dockfile是一種被Docker程序解釋的腳本,Dockerfile由一條一條的指令組成,每條指令對應Linux下面的一條命令。Docker程序將這些Dockerfile指令翻譯真正的Linux命令。Dockerfile有自己書寫格式和支持的命令,Docker程序解決這些命令間的依賴關系,類似于Makefile。Docker程序將讀取Dockerfile,根據指令生成定制的image。相比image這種黑盒子,Dockerfile這種顯而易見的腳本更容易被使用者接受,它明確的表明image是怎么產生的。有了Dockerfile,當我們需要定制自己額外的需求時,只需在Dockerfile上添加或者修改指令,重新生成image即可,省去了敲命令的麻煩。

2. Dockerfile的書寫規則及指令使用方法

Dockerfile的指令是忽略大小寫的,建議使用大寫,使用#作為注釋,每一行只支持一條指令,每條指令可以攜帶多個參數。

Dockerfile的指令根據作用可以分為兩種,構建指令和設置指令。構建指令用于構建image,其指定的操作不會在運行image的容器上執行;設置指令用于設置image的屬性,其指定的操作將在運行image的容器中執行。

(1). FROM(指定基礎image)

構建指令,必須指定且需要在Dockerfile其他指令的前面。后續的指令都依賴于該指令指定的image。FROM指令指定的基礎image可以是官方遠程倉庫中的,也可以位于本地倉庫。

該指令有兩種格式:

FROM <image>

指定基礎image為該image的最后修改的版本。或者:

FROM <image>:<tag>

指定基礎image為該image的一個tag版本。

(2). MAINTAINER(用來指定鏡像創建者信息)

構建指令,用于將image的制作者相關的信息寫入到image中。當我們對該image執行docker inspect命令時,輸出中有相應的字段記錄該信息。

指令格式:

MAINTAINER <name>

(3). RUN(安裝軟件用)

構建指令,RUN可以運行任何被基礎image支持的命令。如基礎image選擇了ubuntu,那么軟件管理部分只能使用ubuntu的命令。

  • RUN命令將在當前image中執行任意合法命令并提交執行結果。命令執行提交后,就會自動執行Dockerfile中的下一個指令。
  • 層級 RUN 指令和生成提交是符合Docker核心理念的做法。它允許像版本控制那樣,在任意一個點,對image 鏡像進行定制化構建。
  • RUN 指令緩存不會在下個命令執行時自動失效。比如 RUN apt-get dist-upgrade -y 的緩存就可能被用于下一個指令. --no-cache 標志可以被用于強制取消緩存使用。

指令格式:

RUN <command> (the command is run in a shell - /bin/sh -c)
RUN ["executable", "param1", "param2" ... ] (exec form)

(4). CMD(設置container啟動時執行的操作)

設置指令,用于container啟動時指定的操作。該操作可以是執行自定義腳本,也可以是執行系統命令。該指令只能在文件中存在一次,如果有多個,則只執行最后一條。

該指令有三種格式:

CMD ["executable","param1","param2"] (like an exec, this is the preferred form)
CMD command param1 param2 (as a shell)

當Dockerfile指定了ENTRYPOINT,那么使用下面的格式:

CMD ["param1","param2"] (as default parameters to ENTRYPOINT)

ENTRYPOINT指定的是一個可執行的腳本或者程序的路徑,該指定的腳本或者程序將會以param1和param2作為參數執行。所以如果CMD指令使用上面的形式,那么Dockerfile中必須要有配套的ENTRYPOINT。

(5). ENTRYPOINT(設置container啟動時執行的操作)

設置指令,指定容器啟動時執行的命令,可以多次設置,但是只有最后一個有效。

兩種格式:

ENTRYPOINT ["executable", "param1", "param2"] (like an exec, the preferred form)
ENTRYPOINT command param1 param2 (as a shell)

該指令的使用分為兩種情況,一種是獨自使用,另一種和CMD指令配合使用。

當獨自使用時,如果你還使用了CMD命令且CMD是一個完整的可執行的命令,那么CMD指令和ENTRYPOINT會互相覆蓋只有最后一個CMD或者ENTRYPOINT有效。

# CMD指令將不會被執行,只有ENTRYPOINT指令被執行  
CMD echo “Hello, World!”  
ENTRYPOINT ls -l  

另一種用法和CMD指令配合使用來指定ENTRYPOINT的默認參數,這時CMD指令不是一個完整的可執行命令,僅僅是參數部分;ENTRYPOINT指令只能使用JSON方式指定執行命令,而不能指定參數。

FROM ubuntu  
CMD ["-l"]  
ENTRYPOINT ["/usr/bin/ls"]  

(6). USER(設置container容器的用戶)

設置指令,設置啟動容器的用戶,默認是root用戶。

# 指定memcached的運行用戶  
ENTRYPOINT ["memcached"]  
USER daemon  
或  
ENTRYPOINT ["memcached", "-u", "daemon"]  

(7). EXPOSE(指定容器需要映射到宿主機器的端口)

設置指令,該指令會將容器中的端口映射成宿主機器中的某個端口。當你需要訪問容器的時候,可以不是用容器的IP地址而是使用宿主機器的IP地址和映射后的端口。要完成整個操作需要兩個步驟,首先在Dockerfile使用EXPOSE設置需要映射的容器端口,然后在運行容器的時候指定-p選項加上EXPOSE設置的端口,這樣EXPOSE設置的端口號會被隨機映射成宿主機器中的一個端口號。也可以指定需要映射到宿主機器的那個端口,這時要確保宿主機器上的端口號沒有被使用。EXPOSE指令可以一次設置多個端口號,相應的運行容器的時候,可以配套的多次使用-p選項。

指令格式:

EXPOSE <port> [<port>...]

# 映射一個端口  
EXPOSE port1  
# 相應的運行容器使用的命令  
docker run -p port1 image  
  
# 映射多個端口  
EXPOSE port1 port2 port3  
# 相應的運行容器使用的命令  
docker run -p port1 -p port2 -p port3 image  
# 還可以指定需要映射到宿主機器上的某個端口號  
docker run -p host_port1:port1 -p host_port2:port2 -p host_port3:port3 image  

端口映射是docker比較重要的一個功能,原因在于我們每次運行容器的時候容器的IP地址不能指定而是在橋接網卡的地址范圍內隨機生成的。宿主機器的IP地址是固定的,我們可以將容器的端口的映射到宿主機器上的一個端口,免去每次訪問容器中的某個服務時都要查看容器的IP的地址。對于一個運行的容器,可以使用docker port加上容器中需要映射的端口和容器的ID來查看該端口號在宿主機器上的映射端口。

(8). ENV(用于設置環境變量)

  • ENV指令可以用于為docker容器設置環境變量
  • ENV設置的環境變量,可以使用docker inspect命令來查看。同時還可以使用docker run --env <key>=<value>來修改環境變量。

格式:

ENV <key> <value>

設置了后,后續的RUN命令都可以使用,container啟動后,可以通過docker inspect查看這個環境變量,也可以通過在docker run --env key=value時設置或修改環境變量。

假如你安裝了JAVA程序,需要設置JAVA_HOME,那么可以在Dockerfile中這樣寫:

ENV JAVA_HOME /path/to/java/dirent

(9). ADD(從src復制文件到container的dest路徑)

構建指令,所有拷貝到container中的文件和文件夾權限為0755,uid和gid為0;如果是一個目錄,那么會將該目錄下的所有文件添加到container中,不包括目錄;如果文件是可識別的壓縮格式,則docker會幫忙解壓縮(注意壓縮格式);如果<src>是文件且<dest>中不使用斜杠結束,則會將<dest>視為文件,<src>的內容會寫入<dest>;如果<src>是文件且<dest>中使用斜杠結束,則會<src>文件拷貝到<dest>目錄下。

格式:

ADD <src> <dest>

  • <src> 是相對被構建的源目錄的相對路徑,可以是文件或目錄的路徑,也可以是一個遠程的文件url;
  • <dest> 是container中的絕對路徑

(10). VOLUME (指定掛載點)

創建一個可以從本地主機或其他容器掛載的掛載點,一般用來存放數據庫和需要保持的數據等。

Volume設置指令,使容器中的一個目錄具有持久化存儲數據的功能,該目錄可以被容器本身使用,也可以共享給其他容器使用。我們知道容器使用的是AUFS,這種文件系統不能持久化數據,當容器關閉后,所有的更改都會丟失。當容器中的應用有持久化數據的需求時可以在Dockerfile中使用該指令。

格式:

VOLUME ["<mountpoint>"]

例如:

FROM base  
VOLUME ["/tmp/data"] 

運行通過該Dockerfile生成image的容器,/tmp/data目錄中的數據在容器關閉后,里面的數據還存在。例如另一個容器也有持久化數據的需求,且想使用上面容器共享的/tmp/data目錄,那么可以運行下面的命令啟動一個容器:

docker run -t -i -rm -volumes-from container1 image2 bash 

說明:container1為第一個容器的ID,image2為第二個容器運行image的名字。

(11). WORKDIR(切換目錄)

設置指令,可以多次切換(相當于cd命令),對RUN,CMD,ENTRYPOINT生效。

格式:

WORKDIR /path/to/workdir

示例:

# 在 /p1/p2 下執行 vim a.txt  
WORKDIR /p1 
WORKDIR p2 
RUN vim a.txt  

(12). ONBUILD(在子鏡像中執行)

ONBUILD 指定的命令在構建鏡像時并不執行,而是在它的子鏡像中執行。

格式:

ONBUILD <Dockerfile關鍵字>

(13). COPY(復制本地主機的src文件為container的dest)

復制本地主機的src文件(為Dockerfile所在目錄的相對路徑、文件或目錄 )到container的dest。目標路徑不存在時,會自動創建。

格式:

COPY <src> <dest>

當使用本地目錄為源目錄時,推薦使用COPY

(14). ARG(設置構建鏡像時變量)

ARG指令在Docker1.9版本才加入的新指令,ARG 定義的變量只在建立 image 時有效,建立完成后變量就失效消失

格式:

ARG <key>=<value>

(15). LABEL(定義標簽)

定義一個 image 標簽 Owner,并賦值,其值為變量 Name 的值。

格式:

LABEL Owner=$Name

3. 創建Dockerfile,構建運行環境

Dockerfile文件

# 指定基于的基礎鏡像
FROM ubuntu:13.10  

# 維護者信息
MAINTAINER zhangjiayang "zhangjiayang@sczq.com.cn"  
  
# 鏡像的指令操作
# 獲取APT更新的資源列表
RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe"> /etc/apt/sources.list
# 更新軟件
RUN apt-get update  
  
# Install curl  
RUN apt-get -y install curl  
  
# Install JDK 7  
RUN cd /tmp &&  curl -L 'http://download.oracle.com/otn-pub/java/jdk/7u65-b17/jdk-7u65-linux-x64.tar.gz' -H 'Cookie: oraclelicense=accept-securebackup-cookie; gpw_e24=Dockerfile' | tar -xz  
RUN mkdir -p /usr/lib/jvm  
RUN mv /tmp/jdk1.7.0_65/ /usr/lib/jvm/java-7-oracle/  
  
# Set Oracle JDK 7 as default Java  
RUN update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-7-oracle/bin/java 300     
RUN update-alternatives --install /usr/bin/javac javac /usr/lib/jvm/java-7-oracle/bin/javac 300     

# 設置系統環境
ENV JAVA_HOME /usr/lib/jvm/java-7-oracle/  
  
# Install tomcat7  
RUN cd /tmp && curl -L 'http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.8/bin/apache-tomcat-7.0.8.tar.gz' | tar -xz  
RUN mv /tmp/apache-tomcat-7.0.8/ /opt/tomcat7/  
  
ENV CATALINA_HOME /opt/tomcat7  
ENV PATH $PATH:$CATALINA_HOME/bin  

# 復件tomcat7.sh到容器中的目錄 
ADD tomcat7.sh /etc/init.d/tomcat7  
RUN chmod 755 /etc/init.d/tomcat7  
  
# Expose ports.  指定暴露的端口
EXPOSE 8080  
  
# Define default command.  
ENTRYPOINT service tomcat7 start && tail -f /opt/tomcat7/logs/catalina.out

tomcat7.sh命令文件

export JAVA_HOME=/usr/lib/jvm/java-7-oracle/  
export TOMCAT_HOME=/opt/tomcat7  
  
case $1 in  
start)  
  sh $TOMCAT_HOME/bin/startup.sh  
;;  
stop)  
  sh $TOMCAT_HOME/bin/shutdown.sh  
;;  
restart)  
  sh $TOMCAT_HOME/bin/shutdown.sh  
  sh $TOMCAT_HOME/bin/startup.sh  
;;  
esac  
exit 0  

4. 構建鏡像

根據配置完的dockerfile構建Docker鏡像,并啟動docker容器。

docker build -t wechat-tomcat.  
docker run -d -p 8090:8080 wechat-tomcat 

默認情況下,tomcat會占用8080端口,所以在啟動container的時候,指定了 -p 8090:8080,映射到宿主機端口就是8090。

5. Dockerfile參考示例

示例1:構建Wordpress + nginx運行環境

# 指定基于的基礎鏡像
FROM ubuntu:14.04

# 維護者信息
MAINTAINER Eugene Ware <eugene@noblesamurai.com>

# Keep upstart from complaining
RUN dpkg-divert --local --rename --add /sbin/initctl
RUN ln -sf /bin/true /sbin/initctl

# Let the conatiner know that there is no tty
ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update
RUN apt-get -y upgrade

# Basic Requirements
RUN apt-get -y install mysql-server mysql-client nginx php5-fpm php5-mysql php-apc pwgen python-setuptools curl git unzip

# Wordpress Requirements
RUN apt-get -y install php5-curl php5-gd php5-intl php-pear php5-imagick php5-imap php5-mcrypt php5-memcache php5-ming php5-ps php5-pspell php5-recode php5-sqlite php5-tidy php5-xmlrpc php5-xsl

# mysql config, 配置MySQL運行參數
RUN sed -i -e"s/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/" /etc/mysql/my.cnf

# nginx config, 配置Nginx運行參數
RUN sed -i -e"s/keepalive_timeout\s*65/keepalive_timeout 2/" /etc/nginx/nginx.conf
RUN sed -i -e"s/keepalive_timeout 2/keepalive_timeout 2;\n\tclient_max_body_size 100m/" /etc/nginx/nginx.conf
RUN echo "daemon off;" >> /etc/nginx/nginx.conf

# php-fpm config
RUN sed -i -e "s/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g" /etc/php5/fpm/php.ini
RUN sed -i -e "s/upload_max_filesize\s*=\s*2M/upload_max_filesize = 100M/g" /etc/php5/fpm/php.ini
RUN sed -i -e "s/post_max_size\s*=\s*8M/post_max_size = 100M/g" /etc/php5/fpm/php.ini
RUN sed -i -e "s/;daemonize\s*=\s*yes/daemonize = no/g" /etc/php5/fpm/php-fpm.conf
RUN sed -i -e "s/;catch_workers_output\s*=\s*yes/catch_workers_output = yes/g" /etc/php5/fpm/pool.d/www.conf
RUN find /etc/php5/cli/conf.d/ -name "*.ini" -exec sed -i -re 's/^(\s*)#(.*)/\1;\2/g' {} \;

# nginx site conf,將本地Nginx配置文件復制到容器中的目錄
ADD ./nginx-site.conf /etc/nginx/sites-available/default

# Supervisor Config
RUN /usr/bin/easy_install supervisor
RUN /usr/bin/easy_install supervisor-stdout
ADD ./supervisord.conf /etc/supervisord.conf

# Install Wordpress
ADD https://wordpress.org/latest.tar.gz /usr/share/nginx/latest.tar.gz
RUN cd /usr/share/nginx/ && tar xvf latest.tar.gz && rm latest.tar.gz
RUN mv /usr/share/nginx/html/5* /usr/share/nginx/wordpress
RUN rm -rf /usr/share/nginx/www
RUN mv /usr/share/nginx/wordpress /usr/share/nginx/www
RUN chown -R www-data:www-data /usr/share/nginx/www

# Wordpress Initialization and Startup Script
ADD ./start.sh /start.sh
RUN chmod 755 /start.sh

# private expose
EXPOSE 3306
EXPOSE 80

# volume for mysql database and wordpress install
VOLUME ["/var/lib/mysql", "/usr/share/nginx/www"]

# 容器啟動時執行命令
CMD ["/bin/bash", "/start.sh"]

示例2:構建Ruby on Rails環境

# 指定基礎鏡像
FROM fcat/ubuntu-universe:12.04

# development tools
RUN apt-get -qy install git vim tmux

# ruby 1.9.3 and build dependencies
RUN apt-get -qy install ruby1.9.1 ruby1.9.1-dev build-essential libpq-dev libv8-dev libsqlite3-dev

# bundler
RUN gem install bundler

# create a "rails" user
# the Rails application will live in the /rails directory
RUN adduser --disabled-password --home=/rails --gecos "" rails

# copy the Rails app
# we assume we have cloned the "docrails" repository locally
#  and it is clean; see the "prepare" script
ADD docrails/guides/code/getting_started /rails

# Make sure we have rights on the rails folder
RUN chown rails -R /rails

# copy and execute the setup script
# this will run bundler, setup the database, etc.
ADD scripts/setup /setup
RUN su rails -c /setup

# copy the start script
ADD scripts/start /start

EXPOSE 3000

# 創建用戶
USER rails

# 設置容器啟動命令
CMD /start

示例3: 構建Nginx運行環境

# 指定基礎鏡像
FROM sameersbn/ubuntu:14.04.20161014

# 維護者信息
MAINTAINER sameer@damagehead.com

# 設置環境
ENV RTMP_VERSION=1.1.10 \
    NPS_VERSION=1.11.33.4 \
    LIBAV_VERSION=11.8 \
    NGINX_VERSION=1.10.1 \
    NGINX_USER=www-data \
    NGINX_SITECONF_DIR=/etc/nginx/sites-enabled \
    NGINX_LOG_DIR=/var/log/nginx \
    NGINX_TEMP_DIR=/var/lib/nginx \
    NGINX_SETUP_DIR=/var/cache/nginx

# 設置構建時變量,鏡像建立完成后就失效
ARG BUILD_LIBAV=false
ARG WITH_DEBUG=false
ARG WITH_PAGESPEED=true
ARG WITH_RTMP=true

# 復制本地文件到容器目錄中
COPY setup/ ${NGINX_SETUP_DIR}/
RUN bash ${NGINX_SETUP_DIR}/install.sh

# 復制本地配置文件到容器目錄中
COPY nginx.conf /etc/nginx/nginx.conf
COPY entrypoint.sh /sbin/entrypoint.sh

# 運行指令
RUN chmod 755 /sbin/entrypoint.sh

# 允許指定的端口
EXPOSE 80/tcp 443/tcp 1935/tcp

# 指定網站目錄掛載點
VOLUME ["${NGINX_SITECONF_DIR}"]

ENTRYPOINT ["/sbin/entrypoint.sh"]
CMD ["/usr/sbin/nginx"]

示例4:構建Postgres鏡像

# 指定基礎鏡像
FROM sameersbn/ubuntu:14.04.20161014

# 維護者信息
MAINTAINER sameer@damagehead.com

# 設置環境變量
ENV PG_APP_HOME="/etc/docker-postgresql"\
    PG_VERSION=9.5 \
    PG_USER=postgres \
    PG_HOME=/var/lib/postgresql \
    PG_RUNDIR=/run/postgresql \
    PG_LOGDIR=/var/log/postgresql \
    PG_CERTDIR=/etc/postgresql/certs

ENV PG_BINDIR=/usr/lib/postgresql/${PG_VERSION}/bin \
    PG_DATADIR=${PG_HOME}/${PG_VERSION}/main

# 下載PostgreSQL
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
 && echo 'deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main' > /etc/apt/sources.list.d/pgdg.list \
 && apt-get update \
 && DEBIAN_FRONTEND=noninteractive apt-get install -y acl \
      postgresql-${PG_VERSION} postgresql-client-${PG_VERSION} postgresql-contrib-${PG_VERSION} \
 && ln -sf ${PG_DATADIR}/postgresql.conf /etc/postgresql/${PG_VERSION}/main/postgresql.conf \
 && ln -sf ${PG_DATADIR}/pg_hba.conf /etc/postgresql/${PG_VERSION}/main/pg_hba.conf \
 && ln -sf ${PG_DATADIR}/pg_ident.conf /etc/postgresql/${PG_VERSION}/main/pg_ident.conf \
 && rm -rf ${PG_HOME} \
 && rm -rf /var/lib/apt/lists/*

COPY runtime/ ${PG_APP_HOME}/
COPY entrypoint.sh /sbin/entrypoint.sh
RUN chmod 755 /sbin/entrypoint.sh

# 指定端口
EXPOSE 5432/tcp

# 指定數據掛載點
VOLUME ["${PG_HOME}", "${PG_RUNDIR}"]

# 切換目錄
WORKDIR ${PG_HOME}

# 設置容器啟動時執行命令
ENTRYPOINT ["/sbin/entrypoint.sh"]

具體用例可以參考Github的Docker文件相應的示例。

6. Dockerfile最佳實踐

  • 使用.dockerignore文件

為了在docker build過程中更快上傳和更加高效,應該使用一個.dockerignore文件用來排除構建鏡像時不需要的文件或目錄。例如,除非.git在構建過程中需要用到,否則你應該將它添加到.dockerignore文件中,這樣可以節省很多時間。

  • 避免安裝不必要的軟件包

為了降低復雜性、依賴性、文件大小以及構建時間,應該避免安裝額外的或不必要的包。例如,不需要在一個數據庫鏡像中安裝一個文本編輯器。

  • 每個容器都跑一個進程

在大多數情況下,一個容器應該只單獨跑一個程序。解耦應用到多個容器使其更容易橫向擴展和重用。如果一個服務依賴另外一個服務,可以參考 Linking Containers Together

  • 最小化層

我們知道每執行一個指令,都會有一次鏡像的提交,鏡像是分層的結構,對于 Dockerfile,應該找到可讀性和最小化層之間的平衡。

  • 多行參數排序

如果可能,通過字母順序來排序,這樣可以避免安裝包的重復并且更容易更新列表,另外可讀性也會更強,添加一個空行使用 \ 換行:

RUN apt-get update && apt-get install -y \
  bzr \
  cvs \
  git \
  mercurial \
  subversion
  • 創建緩存

鏡像構建過程中會按照 Dockerfile 的順序依次執行,每執行一次指令 Docker 會尋找是否有存在的鏡像緩存可復用,如果沒有則創建新的鏡像。如果不想使用緩存,則可以在docker build 時添加--no-cache=true選項。

從基礎鏡像開始就已經在緩存中了,下一個指令會對比所有的子鏡像尋找是否執行相同的指令,如果沒有則緩存失效。在大多數情況下只對比 Dockerfile 指令和子鏡像就足夠了。ADD 和 COPY 指令除外,執行 ADD 和 COPY 時存放到鏡像的文件也是需要檢查的,完成一個文件的校驗之后再利用這個校驗在緩存中查找,如果檢測的文件改變則緩存失效。RUN apt-get -y update命令只檢查命令是否匹配,如果匹配就不會再執行更新了。

為了有效地利用緩存,你需要保持你的Dockerfile一致,并且盡量在末尾修改。

參考資料:

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

推薦閱讀更多精彩內容