10.3 使用Statefulset

為了恰當(dāng)?shù)卣故維tatefulset的行為诈茧,將會創(chuàng)建一個小的集群數(shù)據(jù)存儲叠荠。沒有太多功能墓拜,就像石器時代的一個數(shù)據(jù)存儲潮售。

10.3.1 創(chuàng)建應(yīng)用和容器鏡像

你將使用書中一直使用的kubia應(yīng)用作為你的基礎(chǔ)來擴展它,達(dá)到它的每個pod實例都能用來存儲和接收一個數(shù)據(jù)項动壤。 下面列舉了你的數(shù)據(jù)存儲的關(guān)鍵代碼萝喘。

代碼清單10.1 一個簡單的有狀態(tài)應(yīng)用:kubia-pet-image/app.js

const http = require('http');
const os = require('os');
const fs = require('fs');

const dataFile = "/var/data/kubia.txt";

function fileExists(file) {
  try {
    fs.statSync(file);
    return true;
  } catch (e) {
    return false;
  }
}

var handler = function(request, response) {
  if (request.method == 'POST') {
    var file = fs.createWriteStream(dataFile);
    file.on('open', function (fd) {
      request.pipe(file); #存儲到一個數(shù)據(jù)文件中
      console.log("New data has been received and stored.");
      response.writeHead(200);
      response.end("Data stored on pod " + os.hostname() + "\n");
    });
  } else {
    var data = fileExists(dataFile) ? fs.readFileSync(dataFile, 'utf8') : "No data posted yet"; #返回主機名和數(shù)據(jù)文件名稱
    response.writeHead(200);
    response.write("You've hit " + os.hostname() + "\n");
    response.end("Data stored on this pod: " + data + "\n");
  }
};

var www = http.createServer(handler);
www.listen(8080);

當(dāng)應(yīng)用接收到一個POST請求時,它把請求中的body數(shù)據(jù)內(nèi)容寫入 /var/data/kubia.txt 文件中狼电。而在收到GET請求時蜒灰,它返回主機名和存儲數(shù)據(jù)(文件中的內(nèi)容)。是不是很簡單呢肩碟?這是你的應(yīng)用的第一版本强窖。它還不是一個集群應(yīng)用,但它足夠讓你可以開始工作削祈。在本章的后面翅溺,你會來擴展這個應(yīng)用脑漫。

用來構(gòu)建這個容器鏡像的Dockerfile文件與之前的一樣,如下面的代碼清單所示咙崎。

代碼清單10.2 有狀態(tài)應(yīng)用的Dockerfile:kubia-pet-image/Dockerfile

FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]

現(xiàn)在來構(gòu)建容器鏡像优幸,或者使用筆者上傳的鏡像:docker.io/luksa/kubia-pet

10.3.2 通過Statefulset部署應(yīng)用

為了部署你的應(yīng)用褪猛,需要創(chuàng)建兩個(或三個)不同類型的對象:

  • 存儲你數(shù)據(jù)文件的持久卷(當(dāng)集群不支持持久卷的動態(tài)供應(yīng)時网杆,需要手動創(chuàng)建)
  • Statefulset必需的一個控制Service
  • Statefulset本身

對于每一個pod實例,Statefulset都會創(chuàng)建一個綁定到一個持久卷上的持久卷聲明伊滋。如果你的集群支持動態(tài)供應(yīng)碳却,就不需要手動創(chuàng)建持久卷(可跳過下一節(jié))。如果不支持的話笑旺,可以按照下一節(jié)所述創(chuàng)建它們昼浦。

創(chuàng)建持久化存儲卷

因為你會調(diào)度Statefulset創(chuàng)建三個副本,所以這里需要三個持久卷筒主。如果你計劃調(diào)度創(chuàng)建更多副本关噪,那么需要創(chuàng)建更多持久卷。

如果你使用Minikube乌妙,請參考本書代碼附件中的 Chapter06/persistentvolumes-hostpath.yaml 來部署持久卷使兔。

如果你在使用谷歌的Kubernetes引擎,需要首先創(chuàng)建實際的GCE持久磁盤:

$ gcloud compute disks create --size=1GiB --zone=europe-west1-b pv-a
$ gcloud compute disks create --size=1GiB --zone=europe-west1-b pv-b
$ gcloud compute disks create --size=1GiB --zone=europe-west1-b pv-c

注意 保證創(chuàng)建的持久磁盤和運行的節(jié)點在同一區(qū)域藤韵。

然后通過 persistent-volumes-hostpath.yaml 文件創(chuàng)建需要的持久卷火诸,如下面的代碼清單所示。

代碼清單10.3 三個持久卷:persistent-volumes-hostpath.yaml

kind: List
apiVersion: v1
items:
- apiVersion: v1
  kind: PersistentVolume  #持久卷的描述
  metadata:
    name: pv-a  #持久卷的名稱
  spec:
    capacity:
      storage: 1Mi  #持久卷的大小
    accessModes:
      - ReadWriteOnce
    persistentVolumeReclaimPolicy: Recycle
    hostPath:
      path: /tmp/pv-a
- apiVersion: v1
  kind: PersistentVolume
  metadata:
    name: pv-b
  spec:
    capacity:
      storage: 1Mi
    accessModes:
      - ReadWriteOnce
    persistentVolumeReclaimPolicy: Recycle #當(dāng)卷聲明釋放后,空間會被回收利用
    hostPath:
      path: /tmp/pv-b
- apiVersion: v1
  kind: PersistentVolume
  metadata:
    name: pv-c
  spec:
    capacity:
      storage: 1Mi
    accessModes:
      - ReadWriteOnce
    persistentVolumeReclaimPolicy: Recycle
    hostPath:
      path: /tmp/pv-c

注意 在上一節(jié)通過在同一YAML文件中添加三個橫杠(---)來區(qū)分定義多個資源荠察,這里使用另外一種方法,定義一個List對象奈搜,然后把各個資源作為List對象的各個項目悉盆。上述兩種方法的效果是一樣的。

通過上訴文件創(chuàng)建了pv-a馋吗、pv-b和pv-c三個持久卷焕盟。

創(chuàng)建控制Service

如我們之前所述,在部署一個Statefulset之前宏粤,需要創(chuàng)建一個用于在有狀態(tài)的pod之間提供網(wǎng)絡(luò)標(biāo)識的headless Service脚翘。下面的代碼顯示了Service的詳細(xì)信息。

代碼清單10.4 在Statefulset中使用的 kubia-service-headless.yaml

apiVersion: v1
kind: Service
metadata:
  name: kubia 
spec:
  clusterIP: None   #Statefulset的控制Service必須是headless模式
  selector:
    app: kubia  #標(biāo)簽選擇器 
  ports:
  - name: http
    port: 80

上面指定了clusterIP為None绍哎,這就標(biāo)記了它是一個headless Service来农。它使得你的pod之間可以彼此發(fā)現(xiàn)(后續(xù)會用到這個功能)。創(chuàng)建完這個Service之后崇堰,就可以繼續(xù)往下創(chuàng)建實際的Statefulset了沃于。

創(chuàng)建Statefulset詳單

最后可以創(chuàng)建Statefulset了涩咖,下面的代碼清單顯示了其詳細(xì)信息。

代碼清單10.5 Statefulset詳單:kubia-statefulset.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: kubia
spec:
  serviceName: kubia
  replicas: 2
  selector:
    matchLabels:
      app: kubia # has to match .spec.template.metadata.labels
  template:
    metadata:
      labels:
        app: kubia  #定義標(biāo)簽
    spec:
      containers:
      - name: kubia
        image: luksa/kubia-pet
        ports:
        - name: http
          containerPort: 8080
        volumeMounts:
        - name: data
          mountPath: /var/data  #pvc數(shù)據(jù)卷嵌入指定目錄
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      resources:
        requests:
          storage: 1Mi
      accessModes:
      - ReadWriteOnce

這個Statefulset詳單與之前創(chuàng)建的ReplicaSet和Deployment的詳單沒太多區(qū)別繁莹,這里使用的新組件是volumeClaimTemplates列表檩互。其中僅僅定義了一個名為data的卷聲明,會依據(jù)這個模板為每個pod都創(chuàng)建一個持久卷聲明咨演。如之前在第6章中介紹的闸昨,pod通過在其詳單中包含一個PersistentVolumeClaim卷來關(guān)聯(lián)一個聲明。但在上面的pod模板中并沒有這樣的卷薄风,這是因為在Statefulset創(chuàng)建指定pod時饵较,會自動將PersistentVolumeClaim卷添加到pod詳述中,然后將這個卷關(guān)聯(lián)到一個聲明上村刨。

創(chuàng)建Statefulset

現(xiàn)在就要創(chuàng)建Statefulset了:

$ kubectl create -f kubia-statefulset.yaml

現(xiàn)在列出你的pod:

$ kubectl get po

有沒有發(fā)現(xiàn)不同之處告抄?是否記得一個ReplicaSet會同時創(chuàng)建所有的pod實例?你的Statefulset配置去創(chuàng)建兩個副本嵌牺,但是它僅僅創(chuàng)建了單個pod打洼。

不要擔(dān)心,這里沒有出錯逆粹。第二個pod會在第一個pod運行并且處于就緒狀態(tài)后創(chuàng)建募疮。Statefulset這樣的行為是因為:狀態(tài)明確的集群應(yīng)用對同時有兩個集群成員啟動引起的競爭情況是非常敏感的。所以依次啟動每個成員是比較安全可靠的僻弹。特定的有狀態(tài)應(yīng)用集群在兩個或多個集群成員同時啟動時引起的競態(tài)條件是非常敏感的阿浓,所以在每個成員完全啟動后再啟動剩下的會更加安全。

再次列出pod并查看pod的創(chuàng)建過程:

$ kubectl get poNAME      READY   STATUS    RESTARTS   AGEkubia-0   1/1     Running   0          2m11skubia-1   1/1     Running   0          81s

可以看到蹋绽,第一個啟動的pod狀態(tài)是running芭毙,第二個pod已經(jīng)創(chuàng)建并在啟動過程中。

檢查生成的有狀態(tài)pod

現(xiàn)在讓我們看一下第一個pod的詳細(xì)參數(shù)卸耘,看一下Statefulset如何從pod模板和持久卷聲明模板來構(gòu)建pod退敦,如下面的代碼清單所示。

代碼清單10.6 Statefulset創(chuàng)建的有狀態(tài)pod

$ k get po kubia-0 -o yamlmetadata:   creationTimestamp: "2021-07-16T01:18:16Z"   generateName: kubia-  labels:                  app: kubia              controller-revision-hash: kubia-c94bcb69b         statefulset.kubernetes.io/pod-name: kubia-0   name: kubia-0    namespace: custom........................spec:              containers:        - image: luksa/kubia-pet       imagePullPolicy: Always      name: kubia          ports:              - containerPort: 8080          name: http       protocol: TCP     resources: {}      terminationMessagePath: /dev/termination-log      terminationMessagePolicy: File        volumeMounts:      - mountPath: /var/data    #存儲掛載點      name: data       - mountPath: /var/run/secrets/kubernetes.io/serviceaccount       name: kube-api-access-fb4qg       readOnly: true  .......................  volumes:  - name: data    persistentVolumeClaim:  #Statefulset創(chuàng)建的數(shù)據(jù)卷      claimName: data-kubia-0    - name: kube-api-access-fb4qg  #數(shù)據(jù)卷相關(guān)聲明    projected:      defaultMode: 420      sources:

通過持久卷聲明模板來創(chuàng)建持久卷聲明和pod中使用的與持久卷聲明相關(guān)的數(shù)據(jù)卷蚣抗。

檢查生成的持久卷聲明

現(xiàn)在列出生成的持久卷聲明來確定它們被創(chuàng)建了:

$ kubectl get pvc

生成的持久卷聲明的名稱由在volumeClaimTemplate字段中定義的名稱和每個pod的名稱組成侈百。可以檢查聲明的YAML文件來確認(rèn)它們符合模板的定義翰铡。

10.3.3 使用你的pod

現(xiàn)在你的數(shù)據(jù)存儲集群的節(jié)點都已經(jīng)運行钝域,可以開始使用它們了。因為之前創(chuàng)建的Service處于headless模式锭魔,所以不能通過它來訪問你的pod例证。需要直接連接每個單獨的pod來訪問(或者創(chuàng)建一個普通的Service,但是這樣還是不允許你訪問指定的pod)迷捧。

前面已經(jīng)介紹過如何直接訪問pod:借助另一個pod战虏,然后在里面運行curl命令或者使用端口轉(zhuǎn)發(fā)拣宰。這次來介紹另外一種方法,通過API服務(wù)器作為代理烦感。

通過API服務(wù)器與pod通信

API服務(wù)器的一個很有用的功能就是通過代理直接連接到指定的pod巡社。如果想請求當(dāng)前的kubia-0 pod,可以通過如下URL:

<apiServerHost>:<port>/api/v1/namespaces/default/pods/kubia-0/proxy/<path>

因為API服務(wù)器是有安全保障的手趣,所以通過API服務(wù)器發(fā)送請求到pod是煩瑣的(需要額外在每次請求中添加授權(quán)令牌)晌该。幸運的是,在第8章中已經(jīng)學(xué)習(xí)了如何使用kubectl proxy來與API服務(wù)器通信绿渣,而不必使用麻煩的授權(quán)和SSL證書朝群。再次運行代理如下:

$ kubectl proxy

現(xiàn)在,因為要通過kubectl代理來與API服務(wù)器通信中符,將使用 localhost:8001 來代替實際的API服務(wù)器主機地址和端口姜胖。你將發(fā)送一個如下所示的請求到kubia-0 pod:

$ curl localhost:8001/api/v1/namespaces/custom/pods/kubia-0/proxy/

返回的消息表明你的請求被正確收到,并在kubia-0 pod的應(yīng)用中被正確處理淀散。

注意 如果你收到一個空的回應(yīng)右莱,請確保在URL的最后沒有忘記輸入/符號(或者用curl的-L選項來允許重定向)

因為你正在使用代理的方式,通過API服務(wù)器與pod通信档插,每個請求都會經(jīng)過兩個代理(第一個是kubectl代理漓雅,第二個是把請求代理到pod的API服務(wù)器)非区。詳細(xì)的描述如圖10.10所示岭粤。

img

image

圖10.10 通過kubectl代理和API服務(wù)器代理來與一個pod通信

上面介紹的是發(fā)送一個GET請求到pod星著,也可以通過API服務(wù)器發(fā)送POST請求。發(fā)送POST請求使用的代理URL與發(fā)送GET請求一致则剃。

當(dāng)你的應(yīng)用收到一個POST請求時耘柱,它把請求的主體內(nèi)容保存到本地一個文件中。發(fā)送一個POST請求到kubia-0 pod的示例:

$ curl -X POST -d "Hey there! This greeting was submitted to kubia-0." localhost:8001/api/v1/namespaces/custom/pods/kubia-0/proxy/

你發(fā)送的數(shù)據(jù)現(xiàn)在已經(jīng)保存到pod中棍现,那讓我們檢查一下當(dāng)你再次發(fā)送一個GET請求時帆谍,它是否返回存儲的數(shù)據(jù):

$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/

挺好的,到目前為止都工作正持嵩郏。現(xiàn)在讓我們看看集群其他節(jié)點(kubia-1 pod):

$ curl localhost:8001/api/v1/namespaces/custom/pods/kubia-1/proxy/

與期望的一致烈涮,每個節(jié)點擁有獨自的狀態(tài)朴肺。那這些狀態(tài)是否是持久的呢?讓我們進(jìn)一步驗證坚洽。

刪除一個有狀態(tài)pod來檢查重新調(diào)度的pod是否關(guān)聯(lián)了相同的存儲

你將會刪除kubia-0 pod戈稿,等待它被重新調(diào)度,然后就可以檢查它是否會返回與之前一致的數(shù)據(jù):

$ kubectl delete po kubia-0

如果你列出當(dāng)前pod讶舰,可以看到該pod正在終止運行:

$ kubectl get po

當(dāng)它一旦成功終止鞍盗,Statefulset會重新創(chuàng)建一個具有相同名稱的新的pod:

$ kubectl get po

請記住需了,新的pod可能會被調(diào)度到集群中的任何一個節(jié)點,并不一定保持與舊的pod所在的節(jié)點一致般甲。舊的pod的全部標(biāo)記(名稱肋乍、主機名和存儲)實際上都會轉(zhuǎn)移到新的pod上。如果你在使用Minikube敷存,你將看不到這些墓造,因為它僅僅運行在單個節(jié)點上,但是對于多個節(jié)點的集群來說锚烦,可以看到新的pod會被調(diào)度到與之前pod不一樣的節(jié)點上觅闽。

img

圖10.11 一個有狀態(tài)pod會被重新調(diào)度到新的節(jié)點,但會保留它的名稱涮俄、主機名和存儲

現(xiàn)在新的pod已經(jīng)運行了蛉拙,那讓我們檢查一下它是否擁有與之前的pod一樣的標(biāo)記。pod的名稱是一樣的彻亲,那它的主機名和持久化數(shù)據(jù)呢孕锄?可以通過訪問pod來確認(rèn):

$ curl localhost:8001/api/v1/namespaces/custom/pods/kubia-0/proxy/

從pod返回的信息表明它的主機名和持久化數(shù)據(jù)與之前pod是完全一致的,所以可以確認(rèn)Statefulset會使用一個完全一致的pod來替換被刪除的pod睹栖。

擴縮容Statefulset

縮容一個Statefulset硫惕,然后在完成后再擴容它,與刪除一個pod后讓Statefulset立馬重新創(chuàng)建它的表現(xiàn)是沒有區(qū)別的野来。需要記住的是恼除,縮容一個Statefulset只會刪除對應(yīng)的pod,留下卸載后的持久卷聲明曼氛』砘裕可以嘗試縮容一個Statefulset,來進(jìn)行確認(rèn)舀患。

需要明確的關(guān)鍵點是徽级,縮容/擴容都是逐步進(jìn)行的,與Statefulset最初被創(chuàng)建時會創(chuàng)建各自的pod一樣聊浅。當(dāng)縮容超過一個實例的時候餐抢,會首先刪除擁有最高索引值的pod。只有當(dāng)這個pod被完全終止后低匙,才會開始刪除擁有次高索引值的pod旷痕。

通過一個普通的非headless的Service暴露Statefulset的pod

在閱讀這一章的最后一部分之前,需要為你的pod添加一個適當(dāng)?shù)姆莌eadless Service顽冶,這是因為客戶端通常不會直接連接pod欺抗,而是通過一個服務(wù)。

你應(yīng)該知道了如何創(chuàng)建Service强重,如果不知道的話绞呈,請看下面的代碼清單贸人。

代碼清單10.7 一個用來訪問有狀態(tài)pod的常規(guī)Service:kubia-servicepublic.yaml

apiVersion: v1kind: Servicemetadata:  name: kubia-publicspec:  selector:    app: kubia  ports:  - port: 80    targetPort: 8080

因為它不是外部暴露的Service(它是一個常規(guī)的ClusterIP Service,不是一個NodePort或LoadBalancer-type Service)佃声,只能在你的集群內(nèi)部訪問它艺智。那是否需要一個pod來訪問它呢?答案是不需要秉溉。

通過API服務(wù)器訪問集群內(nèi)部的服務(wù)

不通過額外的pod來訪問集群內(nèi)部的服務(wù)的話力惯,與之前使用訪問單獨pod的方法一樣,可以使用API服務(wù)器提供的相同代理屬性來訪問召嘶。

代理請求到Service的URL路徑格式如下:

/api/v1/namespaces/<namespace>/services/<service name>/proxy/<path>

因此可以在本地機器上運行curl命令父晶,通過kubectl代理來訪問服務(wù)(之前啟動過kubectl proxy,現(xiàn)在它應(yīng)該還在運行著):

$ kubectl proxy --address='0.0.0.0' --port=8001 --accept-hosts='.*'
$ curl localhost:8001/api/v1/namespaces/custom/services/kubia-public/proxy/

客戶端(集群內(nèi)部)同樣可以通過kubia-public服務(wù)來存儲或者讀取你的集群中的數(shù)據(jù)弄跌。當(dāng)然甲喝,每個請求會隨機分配到一個集群節(jié)點上,所以每次都會隨機獲取一個節(jié)點上的數(shù)據(jù)铛只。后面我們會改進(jìn)它埠胖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市淳玩,隨后出現(xiàn)的幾起案子直撤,更是在濱河造成了極大的恐慌,老刑警劉巖蜕着,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谋竖,死亡現(xiàn)場離奇詭異,居然都是意外死亡承匣,警方通過查閱死者的電腦和手機蓖乘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來韧骗,“玉大人嘉抒,你說我怎么就攤上這事∨郾” “怎么了些侍?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長政模。 經(jīng)常有香客問我岗宣,道長,這世上最難降的妖魔是什么览徒? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮颂龙,結(jié)果婚禮上习蓬,老公的妹妹穿的比我還像新娘纽什。我一直安慰自己,他們只是感情好躲叼,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布芦缰。 她就那樣靜靜地躺著,像睡著了一般枫慷。 火紅的嫁衣襯著肌膚如雪让蕾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天或听,我揣著相機與錄音探孝,去河邊找鬼。 笑死誉裆,一個胖子當(dāng)著我的面吹牛顿颅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播足丢,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼粱腻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了斩跌?” 一聲冷哼從身側(cè)響起绍些,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎耀鸦,沒想到半個月后柬批,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡揭糕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年萝快,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片著角。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡揪漩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吏口,到底是詐尸還是另有隱情奄容,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布产徊,位于F島的核電站昂勒,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏舟铜。R本人自食惡果不足惜戈盈,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧塘娶,春花似錦归斤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至虹曙,卻和暖如春迫横,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酝碳。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工矾踱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人击敌。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓介返,卻偏偏與公主長得像,于是被迫代替她去往敵國和親沃斤。 傳聞我的和親對象是個殘疾皇子圣蝎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359