kubernetes(k8s) jenkins CI/CD(pipeline流水線)

Jenkins Pipeline 介紹

要實(shí)現(xiàn)在 Jenkins 中的構(gòu)建工作轻掩,可以有多種方式炸裆,這里采用比較常用的 Pipeline 這種方式娜搂。Pipeline薛闪,簡(jiǎn)單來(lái)說(shuō)辛馆,就是一套運(yùn)行在 Jenkins 上的工作流框架,將原來(lái)獨(dú)立運(yùn)行于單個(gè)或者多個(gè)節(jié)點(diǎn)的任務(wù)連接起來(lái)豁延,實(shí)現(xiàn)單個(gè)任務(wù)難以完成的復(fù)雜流程編排可視化的工作昙篙。

Jenkins Pipeline 有幾個(gè)核心概念:

  • Node:節(jié)點(diǎn),一個(gè) Node 就是一個(gè) Jenkins 節(jié)點(diǎn)诱咏,Master 或者 Agent苔可,是執(zhí)行 Step 的具體運(yùn)行環(huán)境,比如我們之前動(dòng)態(tài)運(yùn)行的 Jenkins Slave 就是一個(gè) Node 節(jié)點(diǎn)
  • Stage:階段袋狞,一個(gè) Pipeline 可以劃分為若干個(gè) Stage焚辅,每個(gè) Stage 代表一組操作,比如:Build苟鸯、Test同蜻、Deploy,Stage 是一個(gè)邏輯分組的概念早处,可以跨多個(gè) Node
  • Step:步驟埃仪,Step 是最基本的操作單元,可以是打印一句話陕赃,也可以是構(gòu)建一個(gè) Docker 鏡像卵蛉,由各類 Jenkins 插件提供,比如命令:sh 'make'么库,就相當(dāng)于我們平時(shí) shell 終端中執(zhí)行 make 命令一樣傻丝。

那么如何創(chuàng)建 Jenkins Pipline 呢?

  • Pipeline 腳本是由 Groovy 語(yǔ)言實(shí)現(xiàn)的诉儒,但是沒(méi)必要單獨(dú)去學(xué)習(xí) Groovy葡缰,當(dāng)然你會(huì)的話最好
  • Pipeline 支持兩種語(yǔ)法:Declarative(聲明式)和 Scripted Pipeline(腳本式)語(yǔ)法
  • Pipeline 也有兩種創(chuàng)建方法:可以直接在 Jenkins 的 Web UI 界面中輸入腳本;也可以通過(guò)創(chuàng)建一個(gè) Jenkinsfile 腳本文件放入項(xiàng)目源碼庫(kù)中
  • 一般我們都推薦在 Jenkins 中直接從源代碼控制(SCMD)中直接載入 Jenkinsfile Pipeline 這種方法

創(chuàng)建一個(gè)簡(jiǎn)單的 Pipeline

創(chuàng)建一個(gè)簡(jiǎn)單的 Pipeline忱反,直接在 Jenkins 的 Web UI 界面中輸入腳本運(yùn)行泛释。

  • 新建 Job:在 Web UI 中點(diǎn)擊 New Item -> 輸入名稱:pipeline-demo -> 選擇下面的 Pipeline -> 點(diǎn)擊 OK

  • 配置:在最下方的 Pipeline 區(qū)域輸入如下 Script 腳本,然后點(diǎn)擊保存温算。

    node {
      stage('Clone') {
        echo "1.Clone Stage"
      }
      stage('Test') {
        echo "2.Test Stage"
      }
      stage('Build') {
        echo "3.Build Stage"
      }
      stage('Deploy') {
        echo "4. Deploy Stage"
      }
    }
    
    
  • 構(gòu)建:點(diǎn)擊左側(cè)區(qū)域的 Build Now份殿,可以看到 Job 開始構(gòu)建了

隔一會(huì)兒,構(gòu)建完成欧募,可以點(diǎn)擊左側(cè)區(qū)域的 Console Output,可以看到如下輸出信息:

console output

可以看到上面Pipeline 腳本中的4條輸出語(yǔ)句都打印出來(lái)了魂贬,證明是符合預(yù)期的。

Pipeline 語(yǔ)法鏈接Pipeline Syntax中進(jìn)行查看裙顽,這里有很多關(guān)于 Pipeline 語(yǔ)法的介紹付燥,也可以自動(dòng)幫我們生成一些腳本。

在 Slave 中構(gòu)建任務(wù)

上面創(chuàng)建了一個(gè)簡(jiǎn)單的 Pipeline 任務(wù)愈犹,但是可以看到這個(gè)任務(wù)并沒(méi)有在 Jenkins 的 Slave 中運(yùn)行键科,那么如何讓任務(wù)跑在 Slave 中呢?之前在添加 Slave Pod 的時(shí)候漩怎,需要用到這個(gè) label萝嘁,我們重新編輯上面創(chuàng)建的 Pipeline 腳本,給 node 添加一個(gè) label 屬性扬卷,如下:

node('hwzx-cmp') {
    stage('Clone') {
      echo "1.Clone Stage"
    }
    stage('Test') {
      echo "2.Test Stage"
    }
    stage('Build') {
      echo "3.Build Stage"
    }
    stage('Deploy') {
      echo "4\. Deploy Stage"
    }
}

這里只是給 node 添加了一個(gè) hwzx-cmp 這樣的一個(gè)label牙言,然后保存,構(gòu)建之前查看下 kubernetes 集群中的 Pod:

$ kubectl get pods -n kube-ops
NAME                       READY     STATUS              RESTARTS   AGE
jenkins-7c85b6f4bd-rfqgv   1/1       Running             4          6d

然后重新觸發(fā)立刻構(gòu)建:

$ kubectl get pods -n kube-ops
NAME                       READY     STATUS    RESTARTS   AGE
jenkins-7c85b6f4bd-rfqgv   1/1       Running   4          6d
jnlp-0hrrz                 1/1       Running   0          23s

會(huì)發(fā)現(xiàn)多了一個(gè)名叫jnlp-0hrrz的 Pod 正在運(yùn)行怪得,隔一會(huì)兒這個(gè) Pod 就不在了:

$ kubectl get pods -n kube-ops
NAME                       READY     STATUS    RESTARTS   AGE
jenkins-7c85b6f4bd-rfqgv   1/1       Running   4          6d

這也證明Job 構(gòu)建完成了咱枉,同樣回到 Jenkins 的 Web UI 界面中查看 Console Output,可以看到如下的信息:

pipeline demo2

是不是也證明當(dāng)前的任務(wù)在跑在上面動(dòng)態(tài)生成的這個(gè) Pod 中徒恋,也符合預(yù)期蚕断。回到 Job 的主界面入挣,也可以看到大家可能比較熟悉的 Stage View 界面:

pipeline demo3

部署 Kubernetes 應(yīng)用

上面已經(jīng)知道了如何在 Jenkins Slave 中構(gòu)建任務(wù)了亿乳,那么如何來(lái)部署一個(gè)原生的 Kubernetes 應(yīng)用呢? 要部署 Kubernetes 應(yīng)用径筏,就得對(duì)之前部署應(yīng)用的流程要非常熟悉才行葛假,之前的流程是怎樣的:

  • 編寫代碼
  • 測(cè)試
  • 編寫 Dockerfile
  • 構(gòu)建打包 Docker 鏡像
  • 推送 Docker 鏡像到倉(cāng)庫(kù)
  • 編寫 Kubernetes YAML 文件
  • 更改 YAML 文件中 Docker 鏡像 TAG
  • 利用 kubectl 工具部署應(yīng)用

之前在 Kubernetes 環(huán)境中部署一個(gè)原生應(yīng)用的流程應(yīng)該基本上是上面這些流程吧?現(xiàn)在需要把上面這些流程放入 Jenkins 中來(lái)自動(dòng)幫我們完成(當(dāng)然編碼除外)滋恬,從測(cè)試到更新 YAML 文件屬于 CI 流程聊训,后面部署屬于 CD 的流程。如果按照上面的示例恢氯,現(xiàn)在要來(lái)編寫一個(gè) Pipeline 的腳本带斑。

node('hwzx-cmp') {
    stage('Clone') {
      echo "1.Clone Stage"
    }
    stage('Test') {
      echo "2.Test Stage"
    }
    stage('Build') {
      echo "3.Build Docker Image Stage"
    }
    stage('Push') {
      echo "4.Push Docker Image Stage"
    }
    stage('YAML') {
      echo "5. Change YAML File Stage"
    }
    stage('Deploy') {
      echo "6. Deploy Stage"
    }
}

這里將一個(gè)簡(jiǎn)單 golang 程序部署到 kubernetes 環(huán)境中,代碼鏈接:https://github.com/cnych/jenkins-demo勋拟。如果按照之前的示例勋磕,我們是不是應(yīng)該像這樣來(lái)編寫 Pipeline 腳本:

  • 第一步,clone 代碼敢靡,這個(gè)沒(méi)得說(shuō)吧
  • 第二步挂滓,進(jìn)行測(cè)試,如果測(cè)試通過(guò)了才繼續(xù)下面的任務(wù)
  • 第三步醋安,由于 Dockerfile 基本上都是放入源碼中進(jìn)行管理的杂彭,所以我們這里就是直接構(gòu)建 Docker 鏡像了
  • 第四步,鏡像打包完成吓揪,就應(yīng)該推送到鏡像倉(cāng)庫(kù)中吧
  • 第五步亲怠,鏡像推送完成,是不是需要更改 YAML 文件中的鏡像 TAG 為這次鏡像的 TAG
  • 第六步柠辞,萬(wàn)事俱備团秽,只差最后一步,使用 kubectl 命令行工具進(jìn)行部署了

到這里整個(gè) CI/CD 的流程是不是就都完成了叭首。

接下來(lái)就來(lái)對(duì)每一步具體要做的事情進(jìn)行詳細(xì)描述就行了:

第一步习勤,Clone 代碼

stage('Clone') {
    echo "1.Clone Stage"
    git url: "https://github.com/cnych/jenkins-demo.git"
}

第二步,測(cè)試

由于我們這里比較簡(jiǎn)單焙格,忽略該步驟即可

第三步图毕,構(gòu)建鏡像

stage('Build') {
    echo "3.Build Docker Image Stage"
    sh "docker build -t qienda/jenkins-demo:${build_tag} ."
}

平時(shí)構(gòu)建的時(shí)候是不是都是直接使用docker build命令進(jìn)行構(gòu)建就行了,那么這個(gè)地方呢眷唉?之前提供的 Slave Pod 的鏡像里面是不是采用的 Docker In Docker 的方式予颤,也就是說(shuō)也可以直接在 Slave 中使用 docker build 命令,所以這里直接使用 sh 執(zhí)行 docker build 命令即可冬阳。
但是鏡像的 tag 呢蛤虐?如果使用鏡像 tag,則每次都是 latest 的 tag肝陪,這對(duì)于以后的排查或者回滾之類的工作會(huì)帶來(lái)很大麻煩驳庭,這里采用和git commit的記錄為鏡像的 tag,這里有一個(gè)好處就是鏡像的 tag 可以和 git 提交記錄對(duì)應(yīng)起來(lái)氯窍,也方便日后對(duì)應(yīng)查看饲常。但是由于這個(gè) tag 不只是我們這一個(gè) stage 需要使用,下一個(gè)推送鏡像是不是也需要狼讨,所以這里把這個(gè) tag 編寫成一個(gè)公共的參數(shù)不皆,把它放在 Clone 這個(gè) stage 中,這樣一來(lái)前兩個(gè) stage 就變成了下面這個(gè)樣子:

stage('Clone') {
    echo "1.Clone Stage"
    git url: "https://github.com/cnych/jenkins-demo.git"
    script {
        build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
    }
}
stage('Build') {
    echo "3.Build Docker Image Stage"
    sh "docker build -t qienda/jenkins-demo:${build_tag} ."
}

第四步熊楼,推送鏡像

鏡像構(gòu)建完成了霹娄,現(xiàn)在就需要將此處構(gòu)建的鏡像推送到鏡像倉(cāng)庫(kù)中去,直接使用 docker hub 即可鲫骗。

docker hub 是公共的鏡像倉(cāng)庫(kù)犬耻,任何人都可以獲取上面的鏡像,但是要往上推送鏡像就需要用到一個(gè)帳號(hào)了执泰,所以需要提前注冊(cè)一個(gè) docker hub 的帳號(hào)枕磁,記住用戶名和密碼,术吝。正常來(lái)說(shuō)我們?cè)诒镜赝扑?docker 鏡像的時(shí)候计济,需要使用docker login命令茸苇,然后輸入用戶名和密碼,認(rèn)證通過(guò)后沦寂,就可以使用docker push命令來(lái)推送本地的鏡像到 docker hub 上面去了学密,如果是這樣的話,這里的 Pipeline 是不是就該這樣寫了:

stage('Push') {
    echo "4.Push Docker Image Stage"
    sh "docker login -u cnych -p xxxxx"
    sh "docker push cnych/jenkins-demo:${build_tag}"
}

如果只是在 Jenkins 的 Web UI 界面中來(lái)完成這個(gè)任務(wù)的話传藏,這里的 Pipeline 是可以這樣寫的腻暮,但是是不是推薦使用 Jenkinsfile 的形式放入源碼中進(jìn)行版本管理,這樣的話直接把 docker 倉(cāng)庫(kù)的用戶名和密碼暴露給別人這樣很顯然是非常非常不安全的毯侦,更何況這里使用的是 github 的公共代碼倉(cāng)庫(kù)哭靖,所有人都可以直接看到源碼,所以應(yīng)該用一種方式來(lái)隱藏用戶名和密碼這種私密信息侈离,幸運(yùn)的是 Jenkins 提供了解決方法试幽。

在首頁(yè)點(diǎn)擊 Credentials -> Stores scoped to Jenkins 下面的 Jenkins -> Global credentials (unrestricted) -> 左側(cè)的 Add Credentials:添加一個(gè) Username with password 類型的認(rèn)證信息,如下:


2.jpg

輸入 docker hub 的用戶名和密碼卦碾,ID 部分我們輸入dockerHub抡草,注意,這個(gè)值非常重要蔗坯,在后面 Pipeline 的腳本中我們需要使用到這個(gè) ID 值康震。

有了上面的 docker hub 的用戶名和密碼的認(rèn)證信息,現(xiàn)在我們可以在 Pipeline 中使用這里的用戶名和密碼了:

stage('Push') {
    echo "4.Push Docker Image Stage"
    withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
        sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}"
        sh "docker push qienda/jenkins-demo:${build_tag}"
    }
}

注意這里在 stage 中使用了一個(gè)新的函數(shù)withCredentials宾濒,其中有一個(gè) credentialsId 值就是我們剛剛創(chuàng)建的 ID 值腿短,而對(duì)應(yīng)的用戶名變量就是 ID 值加上 User,密碼變量就是 ID 值加上 Password绘梦,然后我們就可以在腳本中直接使用這里兩個(gè)變量值來(lái)直接替換掉之前的登錄 docker hub 的用戶名和密碼橘忱,現(xiàn)在是不是就很安全了,我只是傳遞進(jìn)去了兩個(gè)變量而已卸奉,別人并不知道我的真正用戶名和密碼钝诚,只有自己的 Jenkins 平臺(tái)上添加的才知道。

第五步榄棵,更改 YAML

上面已經(jīng)完成了鏡像的打包凝颇、推送的工作,接下來(lái)更新 Kubernetes 系統(tǒng)中應(yīng)用的鏡像版本了疹鳄,當(dāng)然為了方便維護(hù)拧略,用 YAML 文件的形式來(lái)編寫應(yīng)用部署規(guī)則,比如這里的 YAML 文件:(k8s.yaml)

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: jenkins-demo
spec:
  template:
    metadata:
      labels:
        app: jenkins-demo
    spec:
      containers:
      - image: qienda/jenkins-demo:<BUILD_TAG>
        imagePullPolicy: IfNotPresent
        name: jenkins-demo
        env:
        - name: branch
          value: <BRANCH_NAME>

該 Pod 使用的就是上面推送的鏡像瘪弓,唯一不同的地方是 Docker 鏡像的 tag 不是平常見的具體的 tag垫蛆,而是一個(gè)的標(biāo)識(shí),實(shí)際上如果將這個(gè)標(biāo)識(shí)替換成上面的 Docker 鏡像的 tag,是不是就是最終本次構(gòu)建需要使用到的鏡像袱饭?怎么替換呢川无?其實(shí)也很簡(jiǎn)單,使用一個(gè)sed命令就可以實(shí)現(xiàn)了:

stage('YAML') {
    echo "5. Change YAML File Stage"
    sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
    sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
}

上面的 sed 命令就是將 k8s.yaml 文件中的 標(biāo)識(shí)給替換成變量 build_tag 的值虑乖。

第六步懦趋,部署

Kubernetes 應(yīng)用的 YAML 文件已經(jīng)更改完成了,之前手動(dòng)部署的環(huán)境下决左,接使用 kubectl apply 命令就可以直接更新應(yīng)用了愕够,當(dāng)然這里只是寫入到了 Pipeline 里面走贪,思路都是一樣的:

stage('Deploy') {
    echo "6. Deploy Stage"
    sh "kubectl apply -f k8s.yaml"
}

這樣到這里整個(gè)流程就算完成了佛猛。

人工確認(rèn)

理論上來(lái)說(shuō)上面的6個(gè)步驟其實(shí)已經(jīng)完成了,但是一般在我們的實(shí)際項(xiàng)目實(shí)踐過(guò)程中坠狡,可能還需要一些人工干預(yù)的步驟继找,比如開發(fā)提交了一次代碼,測(cè)試也通過(guò)了逃沿,鏡像也打包上傳了婴渡,但是這個(gè)版本并不一定就是要立刻上線到生產(chǎn)環(huán)境的,可能需要將該版本先發(fā)布到測(cè)試環(huán)境凯亮、QA 環(huán)境边臼、或者預(yù)覽環(huán)境之類的,總之直接就發(fā)布到線上環(huán)境去還是挺少見的假消,所以需要增加人工確認(rèn)的環(huán)節(jié)柠并,一般都是在 CD 的環(huán)節(jié)才需要人工干預(yù),比如這里的最后兩步富拗,就可以在前面加上確認(rèn):

stage('YAML') {
    echo "5. Change YAML File Stage"
    def userInput = input(
        id: 'userInput',
        message: 'Choose a deploy environment',
        parameters: [
            [
                $class: 'ChoiceParameterDefinition',
                choices: "Dev\nQA\nProd",
                name: 'Env'
            ]
        ]
    )
    echo "This is a deploy step to ${userInput.Env}"
    sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
    sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
}

這里使用了 input 關(guān)鍵字臼予,里面使用一個(gè) Choice 的列表來(lái)讓用戶進(jìn)行選擇,然后在選擇了部署環(huán)境后啃沪,當(dāng)然也可以針對(duì)不同的環(huán)境再做一些操作粘拾,比如可以給不同環(huán)境的 YAML 文件部署到不同的 namespace 下面去,增加不同的標(biāo)簽等操作:

stage('Deploy') {
    echo "6. Deploy Stage"
    if (userInput.Env == "Dev") {
      // deploy dev stuff
    } else if (userInput.Env == "QA"){
      // deploy qa stuff
    } else {
      // deploy prod stuff
    }
    sh "kubectl apply -f k8s.yaml"
}

由于這一步也屬于部署的范疇创千,所以可以將最后兩步都合并成一步缰雇,最終的 Pipeline 腳本如下:

node('hwzx-cmp') {
    stage('Clone') {
        echo "1.Clone Stage"
        git url: "https://github.com/cnych/jenkins-demo.git"
        script {
            build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
        }
    }
    stage('Test') {
      echo "2.Test Stage"
    }
    stage('Build') {
        echo "3.Build Docker Image Stage"
        sh "docker build -t qienda/jenkins-demo:${build_tag} ."
    }
    stage('Push') {
        echo "4.Push Docker Image Stage"
        withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {
            sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}"
            sh "docker push qienda/jenkins-demo:${build_tag}"
        }
    }
    stage('Deploy') {
        echo "5. Deploy Stage"
        def userInput = input(
            id: 'userInput',
            message: 'Choose a deploy environment',
            parameters: [
                [
                    $class: 'ChoiceParameterDefinition',
                    choices: "Dev\nQA\nProd",
                    name: 'Env'
                ]
            ]
        )
        echo "This is a deploy step to ${userInput}"
        sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
        sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
        if (userInput == "Dev") {
            // deploy dev stuff
        } else if (userInput == "QA"){
            // deploy qa stuff
        } else {
            // deploy prod stuff
        }
        sh "kubectl apply -f k8s.yaml"
    }
}

現(xiàn)在 Jenkins Web UI 中重新配置 jenkins-demo 這個(gè)任務(wù),將上面的腳本粘貼到 Script 區(qū)域追驴,重新保存寓涨,然后點(diǎn)擊左側(cè)的 Build Now,觸發(fā)構(gòu)建氯檐,然后過(guò)一會(huì)兒就可以看到 Stage View 界面出現(xiàn)了暫停的情況:

pipeline demo5

這就是上面 Deploy 階段加入了人工確認(rèn)的步驟戒良,所以這個(gè)時(shí)候構(gòu)建暫停了,需要人為的確認(rèn)下冠摄,比如這里選擇 QA糯崎,然后點(diǎn)擊 Proceed几缭,就可以繼續(xù)往下走了,然后構(gòu)建就成功了沃呢,在 Stage View 的 Deploy 這個(gè)階段可以看到如下的一些日志信息:

pipeline demo6

打印出來(lái)了 QA年栓,和剛剛的選擇是一致的,現(xiàn)在去 Kubernetes 集群中觀察下部署的應(yīng)用:

$ kubectl get deployment -n kube-ops
NAME           DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
jenkins        1         1         1            1           7d
jenkins-demo   1         1         1            0           1m
$ kubectl get pods -n kube-ops
NAME                           READY     STATUS      RESTARTS   AGE
jenkins-7c85b6f4bd-rfqgv       1/1       Running     4          7d
jenkins-demo-f6f4f646b-2zdrq   0/1       Completed   4          1m
$ kubectl logs jenkins-demo-f6f4f646b-2zdrq -n kube-ops
Hello, Kubernetes薄霜!I'm from Jenkins CI某抓!

可以看到應(yīng)用已經(jīng)正確的部署到了 Kubernetes 的集群環(huán)境中了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惰瓜,一起剝皮案震驚了整個(gè)濱河市否副,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崎坊,老刑警劉巖备禀,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異奈揍,居然都是意外死亡曲尸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門男翰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)另患,“玉大人,你說(shuō)我怎么就攤上這事蛾绎±セ” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵秘通,是天一觀的道長(zhǎng)为严。 經(jīng)常有香客問(wèn)我,道長(zhǎng)肺稀,這世上最難降的妖魔是什么第股? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮话原,結(jié)果婚禮上夕吻,老公的妹妹穿的比我還像新娘。我一直安慰自己繁仁,他們只是感情好涉馅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著黄虱,像睡著了一般稚矿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天晤揣,我揣著相機(jī)與錄音桥爽,去河邊找鬼。 笑死昧识,一個(gè)胖子當(dāng)著我的面吹牛钠四,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跪楞,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼缀去,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了甸祭?” 一聲冷哼從身側(cè)響起缕碎,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎淋叶,沒(méi)想到半個(gè)月后阎曹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伪阶,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煞檩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了栅贴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片斟湃。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖檐薯,靈堂內(nèi)的尸體忽然破棺而出凝赛,到底是詐尸還是另有隱情,我是刑警寧澤坛缕,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布墓猎,位于F島的核電站,受9級(jí)特大地震影響赚楚,放射性物質(zhì)發(fā)生泄漏毙沾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一宠页、第九天 我趴在偏房一處隱蔽的房頂上張望左胞。 院中可真熱鬧,春花似錦举户、人聲如沸烤宙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)躺枕。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間拐云,已是汗流浹背蔓姚。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留慨丐,地道東北人坡脐。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像房揭,于是被迫代替她去往敵國(guó)和親备闲。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354