高階k8s HA 集群搭建(二)

介紹

在上一期HA k8s搭建介紹有說到,目前有兩種比較火的HA集群,那么今天我們來說說第二種,也是目前比較主流的部署方式,采用的是haproxy+keepalived+etcd+k8s的模式,和第一種集群相比較多了有個haproxy,它是干嘛的呢,下面是它的大概簡介

HAProxy簡介

(1)HAProxy 是一款提供高可用性、負載均衡以及基于TCP(第四層)和HTTP(第七層)應用的代理軟件,支持虛擬主機,它是免費、快速并且可靠的一種解決方案。 HAProxy特別適用于那些負載特大的web站點,這些站點通常又需要會話保持或七層處理。HAProxy運行在時下的硬件上,完全可以支持數以萬計的 并發連接。并且它的運行模式使得它可以很簡單安全的整合進您當前的架構中, 同時可以保護你的web服務器不被暴露到網絡上。

(2)HAProxy 實現了一種事件驅動、單一進程模型,此模型支持非常大的并發連接數。多進程或多線程模型受內存限制 、系統調度器限制以及無處不在的鎖限制,很少能處理數千并發連接。事件驅動模型因為在有更好的資源和時間管理的用戶端(User-Space) 實現所有這些任務,所以沒有這些問題。此模型的弊端是,在多核系統上,這些程序通常擴展性較差。這就是為什么他們必須進行優化以 使每個CPU時間片(Cycle)做更多的工作。

(3)HAProxy 支持連接拒絕 : 因為維護一個連接的打開的開銷是很低的,有時我們很需要限制攻擊蠕蟲(attack bots),也就是說限制它們的連接打開從而限制它們的危害。 這個已經為一個陷于小型DDoS攻擊的網站開發了而且已經拯救

了很多站點,這個優點也是其它負載均衡器沒有的。

(4)HAProxy 支持全透明代理(已具備硬件防火墻的典型特點): 可以用客戶端IP地址或者任何其他地址來連接后端服務器. 這個特性僅在Linux?2.4/2.6內核打了cttproxy補丁后才可以使用. 這個特性也使得為某特殊服務器處理部分流量同時又不修改服務器的地址成為可能。

性能

HAProxy借助于OS上幾種常見的技術來實現性能的最大化。

1,單進程、事件驅動模型顯著降低了上下文切換的開銷及內存占用。

2,O(1)事件檢查器(event checker)允許其在高并發連接中對任何連接的任何事件實現即時探測。

3,在任何可用的情況下,單緩沖(single buffering)機制能以不復制任何數據的方式完成讀寫操作,這會節約大量的CPU時鐘周期及內存帶寬;

4,借助于Linux 2.6 (>= 2.6.27.19)上的splice()系統調用,HAProxy可以實現零復制轉發(Zero-copy forwarding),在Linux 3.5及以上的OS中還可以實現零復制啟動(zero-starting);

5,內存分配器在固定大小的內存池中可實現即時內存分配,這能夠顯著減少創建一個會話的時長;

6,樹型存儲:側重于使用作者多年前開發的彈性二叉樹,實現了以O(log(N))的低開銷來保持計時器命令、保持運行隊列命令及管理輪詢及最少連接隊列;

7,優化的HTTP首部分析:優化的首部分析功能避免了在HTTP首部分析過程中重讀任何內存區域;

8,精心地降低了昂貴的系統調用,大部分工作都在用戶空間完成,如時間讀取、緩沖聚合及文件描述符的啟用和禁用等;

所有的這些細微之處的優化實現了在中等規模負載之上依然有著相當低的CPU負載,甚至于在非常高的負載場景中,5%的用戶空間占用率和95%的系統空間占用率也是非常普遍的現象,這意味著HAProxy進程消耗比系統空間消耗低20倍以上。因此,對OS進行性能調優是非常重要的。即使用戶空間的占用率提高一倍,其CPU占用率也僅為10%,這也解釋了為何7層處理對性能影響有限這一現象。由此,在高端系統上HAProxy的7層性能可輕易超過硬件負載均衡設備。

在生產環境中,在7層處理上使用HAProxy作為昂貴的高端硬件負載均衡設備故障故障時的緊急解決方案也時長可見。硬件負載均衡設備在“報文”級別處理請求,這在支持跨報文請求(request across multiple packets)有著較高的難度,并且它們不緩沖任何數據,因此有著較長的響應時間。對應地,軟件負載均衡設備使用TCP緩沖,可建立極長的請求,且有著較大的響應時間。

????????????????????????????????????????????????????????????????????????摘抄至HAProxy用法詳解 全網最詳細中文文檔


一般情況下haproxy會和keepalived一起使用。

老規矩,下面附上k8s理想的高可用架構圖。

k8s 理想HA高可用

那在本文中,我們的k8s架構應該是怎么樣的呢,我從網上找到了一個非常符合我們整個K8S HA架構的圖,然后稍微做了修改。本文大致高可用架構圖如下


本文高可用集群架構圖

在原圖中使用的是Nginx作為負載均衡,而我們本文中則使用的是haproxy。好了下面開始目前主流的k8s架構搭建

主機節點清單

主機名IP地址說明組件,如果是沒搭建過k8s集群的,需要先看看我之前寫的初階k8s集群搭建,需要安裝的一些軟件和容器鏡像。

master1節點 192.168.100.1 4核4G內存?

keepalived、haproxy、etcd、kubelet、kube-apiserver、kube-scheduler、kube-proxy、kube-dashboard、heapster

master2節點 192.168.100.2 ?4核4G內存?

keepalived、haproxy、etcd、kubelet、kube-apiserver、kube-scheduler、kube-proxy、kube-dashboard、heapster

master3節點 192.168.100.3?4核4G內存?

keepalived、haproxy、etcd、kubelet、kube-apiserver、kube-scheduler、kube-proxy、kube-dashboard、heapster

vip 192.168.100.4 keepalived?4核4G內存?

在每個master節點上設置好hostname 、hosts

192.168.100.1 master1

192.168.100.2?master2

192.168.100.3?master3

由于本次安裝全部容器化部署,因此部署過程相對比較簡單。

ECTD部署

在每個master節點上

先拉取最新的etcd官方鏡像,這可能需要翻墻,童鞋們可以改下阿里云的加速鏡像地址,這里就不做多概述了,官方最新的etcd鏡像版本對應常規版的etcd是3.2.17版本

docker pull?quay.io/coreos/etcd

docker tag?quay.io/coreos/etcd?quay.io/coreos/etcd:3.2.17

新建一個etcd數據存儲路徑,下面只是例子,童鞋們自行修改

mkdir /u03/etcd_docker?


master1

docker run -d \

--restart always \

-v /etc/etcd/ssl/certs:/etc/ssl/certs \

-v /u03/etcd_docker:/var/lib/etcd \

-p 2380:2380 \

-p 2379:2379 \

--name etcd \

quay.io/coreos/etcd:3.2.17 \

etcd --name=master1 \

--advertise-client-urls=http://192.168.100.1:2379 \

--listen-client-urls=http://0.0.0.0:2379 \

--initial-advertise-peer-urls=http://192.168.100.1:2380 \

--listen-peer-urls=http://0.0.0.0:2380 \

--initial-cluster-token=etcd-cluster-0 \

--initial-cluster=master1=http://192.168.100.1:2380,master2=http://192.168.100.2:2380,master3=http://192.168.100.3:2380 \

--initial-cluster-state=new \

--auto-tls \

--peer-auto-tls \

--data-dir=/var/lib/etcd

master2

docker run -d \

--restart always \

-v /etc/etcd/ssl/certs:/etc/ssl/certs \

-v /u03/etcd_docker:/var/lib/etcd \

-p 2380:2380 \

-p 2379:2379 \

--name etcd \

quay.io/coreos/etcd:3.2.17 \

etcd --name=master2 \

--advertise-client-urls=http://192.168.100.2:2379 \

--listen-client-urls=http://0.0.0.0:2379 \

--initial-advertise-peer-urls=http://192.168.100.2:2380 \

--listen-peer-urls=http://0.0.0.0:2380 \

--initial-cluster-token=etcd-cluster-0 \

--initial-cluster=master1=http://192.168.100.1:2380,master2=http://192.168.100.2:2380,master3=http://192.168.100.3:2380 \

--initial-cluster-state=new \

--auto-tls \

--peer-auto-tls \

--data-dir=/var/lib/etcd

master3

docker run -d \

--restart always \

-v /etc/etcd/ssl/certs:/etc/ssl/certs \

-v /u03/etcd_docker:/var/lib/etcd \

-p 2380:2380 \

-p 2379:2379 \

--name etcd \

quay.io/coreos/etcd:3.2.17 \

etcd --name=master3 \

--advertise-client-urls=http://192.168.100.3:2379 \

--listen-client-urls=http://0.0.0.0:2379 \

--initial-advertise-peer-urls=http://192.168.100.3:2380 \

--listen-peer-urls=http://0.0.0.0:2380 \

--initial-cluster-token=etcd-cluster-0 \

--initial-cluster=master1=http://192.168.100.1:2380,master2=http://192.168.100.2:2380,master3=http://192.168.100.3:2380 \

--initial-cluster-state=new \

--auto-tls \

--peer-auto-tls \

--data-dir=/var/lib/etcd


這樣在三臺節點上完成ETCD集群的部署,在這里沒有使用https,是因為想簡化部署過程,也不需要涉及到外部網絡,如果有安全認證方面的需求可以按照我上一篇高階k8s HA 集群搭建(一)里搭建etcd集群的方式,照葫蘆畫瓢。

如果在部署過程中出現問題,etcd容器沒有啟動或者一直再重啟則通過docker ps -a查看容器id,通過docker logs?CONTAINER ID來排查問題。

驗證方式:

1.如果你安裝了etcdctl則

etcdctl --endpoints=http://192.168.100.1:2379,http://192.168.100.2:2379,http://192.168.100.3:2379 \

cluster-health

etcdctl --endpoints=http://192.168.100.1:2379,http://192.168.100.2:2379,http://192.168.100.3:2379 \

member list

2.直接到etcd容器里驗證

docker exec -ti etcd ash

etcdctl member list

etcdctl cluster-health

exit

keepalived

在每個master節點上執行

拉取keepalived鏡像

docker pull osixia/keepalived:1.4.4

載入內核相關模塊

lsmod | grep ip_vs

modprobe ip_vs

部署keepalived

docker run --net=host --cap-add=NET_ADMIN \

-e KEEPALIVED_INTERFACE=eno16780032 \

-e KEEPALIVED_VIRTUAL_IPS="#PYTHON2BASH:['192.168.100.4']" \

-e KEEPALIVED_UNICAST_PEERS="#PYTHON2BASH:['192.168.100.1','192.168.100.2','192.168.100.3']" \

-e KEEPALIVED_PASSWORD=admin \

--name k8s-keepalived \

--restart always \

-d?osixia/keepalived:1.4.4

其中KEEPALIVED_INTERFACE填的是192.168.100.0/24網段所在的網卡,可以通過ip a命令來查看。KEEPALIVED_VIRTUAL_IPS設置的是需要用到的vip。

三臺master節點部署完成后通過ping?192.168.100.4的方式驗證是否通,或者直接ip a查看第一臺部署的master,網卡上是不是多了一個ip。

如果出現問題,也可以通過docker logs k8s-keepalived來調試。

如果失敗后清理,重新部署

docker rm -f k8s-keepalived

haproxy

在每個master上執行

拉取haproxy鏡像

docker pull haproxy:1.7.8-alpine

創建haproxy配置文件夾

mkdir /etc/haproxy

創建haproxy配置

cat >/etc/haproxy/haproxy.cfg<<EOF

global

? log 127.0.0.1 local0 err

? maxconn 50000

? uid 99

? gid 99

? #daemon

? nbproc 1

? pidfile haproxy.pid

defaults

? mode http

? log 127.0.0.1 local0 err

? maxconn 50000

? retries 3

? timeout connect 5s

? timeout client 30s

? timeout server 30s

? timeout check 2s

listen admin_stats

? mode http

? bind 0.0.0.0:1080

? log 127.0.0.1 local0 err

? stats refresh 30s

? stats uri? ? /haproxy-status

? stats realm? Haproxy\ Statistics

? stats auth ? ?admin:admin

? stats hide-version

? stats admin if TRUE

frontend k8s-https

? bind 0.0.0.0:8443

? mode tcp

? #maxconn 50000

? default_backend k8s-https

backend k8s-https

? mode tcp

? balance roundrobin

? server master1 192.168.100.1:6443 weight 1 maxconn 1000 check inter 2000 rise 2 fall 3

? server master1 192.168.100.2:6443 weight 1 maxconn 1000 check inter 2000 rise 2 fall 3

? server master1 192.168.100.3:6443 weight 1 maxconn 1000 check inter 2000 rise 2 fall 3

EOF

啟動haproxy

docker run -d --name my-haproxy \

-v /etc/haproxy:/usr/local/etc/haproxy:ro \

-p 8443:8443 \

-p 1080:1080 \

--restart always \

haproxy:1.7.8-alpine

驗證

瀏覽器查看狀態

http://192.168.100.1:1080/haproxy-status

http://192.168.100.2:1080/haproxy-status

http://192.168.100.3:1080/haproxy-status

master節點初始化

ps:首先需要將相關軟件安裝一下,k8s相關的組件和鏡像

在第一臺master上創建新的token并記住(一會兒都要用到)

kubeadm token generate

制作配置文件

vim config.yaml

apiVersion: kubeadm.k8s.io/v1alpha1

kind: MasterConfiguration

kubeProxy:

? config:

? ? featureGates:

? ? ? SupportIPVSProxyMode: true

? ? mode: ipvs

etcd:

? endpoints:

? - http://192.168.100.1:2379

? - http://192.168.100.2:2379

? - http://192.168.100.3:2379

? dataDir: /u03/etcd_docker

networking:

? serviceSubnet: 10.96.0.0/12

? podSubnet: 10.244.0.0/16

kubernetesVersion: 1.10.0

api:

? advertiseAddress: "192.168.100.1" #每個節點的ip

apiServerExtraArgs:

? endpoint-reconciler-type: lease

controllerManagerExtraArgs:

? node-monitor-grace-period: 10s

? pod-eviction-timeout: 10s

apiServerCertSANs:

- master1

-?master2

-?master3

-?192.168.100.1

-?192.168.100.2

-?192.168.100.3

-?192.168.100.4

token: hpobow.vw1g1ya5dre7sq06 #之前生成的token

tokenTTL: "0"?#token失效時間,0表示永不失效

featureGates:

? CoreDNS: true

我們使用CoreDNS作為k8s內部網絡域名解析,使用ipvs做proxy內網負載均衡

kubeadm init --config?config.yaml?

systemctl enable kubelet

保存初始化完成之后的join命令

如果丟失可以使用命令"kubeadm token list"獲取

# kubeadm join 192.168.100.1:6443 --token hpobow.vw1g1ya5dre7sq06 --discovery-token-ca-cert-hash sha256:0e4f738348be836ff810bce754e059054845f44f01619a37b817eba83282d80f

配置kubectl使用

mkdir -p$HOME/.kube

sudo cp -i /etc/kubernetes/admin.conf$HOME/.kube/config

sudo chown $(id -u):$(id -g)$HOME/.kube/config

或者root 用戶可以直接

echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile

source ~/.bash_profile

安裝網絡插件(只用安裝一次)

下載配置

部署網絡插件我們這里使用canal,既Calico的策略+flannel的網絡,因為在Calico目前還沒有很好的支持ipvs

https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/flannel

kubectl apply -f \

https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/canal/rbac.yaml

kubectl apply -f \

https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/canal/canal.yaml

由于canal一些組件需要翻墻,先wget?https://docs.projectcalico.org/v3.1/getting-started/kubernetes/installation/hosted/canal/canal.yaml 下來,單獨下載里面的鏡像,再部署

如果Node有多個網卡的話,參考flannel issues 39701,

https://github.com/kubernetes/kubernetes/issues/39701

需要在canal.yaml中使用--iface參數指定集群主機內網網卡的名稱,

否則可能會出現dns無法解析。容器無法通信的情況,需要將canal.yaml下載到本地

修改如下,網卡名稱通過ip a自行查看

- name: kube-flannel

? ? ? ? ? image: quay.io/coreos/flannel:v0.9.1

? ? ? ? ? command: [ "/opt/bin/flanneld", "--ip-masq", "--kube-subnet-mgr", "--iface=eno16780032"?]

查看系統組件運行狀況

kubectl get pods --namespace kube-system

kubectl get svc --namespace kube-system

kubectl get nodes

在另外兩臺master節點上初始化

將在master1 的kubeadm生成證書密碼文件分發到master2和master3上面去

scp -r /etc/kubernetes/pki master2:/etc/kubernetes/

scp -r /etc/kubernetes/pki master3:/etc/kubernetes/

生成配置文件

使用和之前master1一樣的配置文件

token保持一致

制作配置文件

vim config.yaml

apiVersion: kubeadm.k8s.io/v1alpha1

kind: MasterConfiguration

kubeProxy:

? config:

? ? featureGates:

? ? ? SupportIPVSProxyMode: true

? ? mode: ipvs

etcd:

? endpoints:

? - http://192.168.100.1:2379

? - http://192.168.100.2:2379

? - http://192.168.100.3:2379

? dataDir: /u03/etcd_docker

networking:

? serviceSubnet: 10.96.0.0/12

? podSubnet: 10.244.0.0/16

kubernetesVersion: 1.10.0

api:

? advertiseAddress: "192.168.100.2" #每個節點的ip

apiServerExtraArgs:

? endpoint-reconciler-type: lease

controllerManagerExtraArgs:

? node-monitor-grace-period: 10s

? pod-eviction-timeout: 10s

apiServerCertSANs:

- master1

-?master2

-?master3

-?192.168.100.1

-?192.168.100.2

-?192.168.100.3

-?192.168.100.4

token: hpobow.vw1g1ya5dre7sq06 #之前生成的token

tokenTTL: "0"?#token失效時間,0表示永不失效

featureGates:

? CoreDNS: true

執行初始化

kubeadm init --config config.yaml

systemctl enable kubelet

保存初始化完成之后的join命令(方便工作節點加入)

等另外兩臺master部署完成之后

驗證

查看狀態

kubectl get pod --all-namespaces -o wide | grep master1

kubectl get pod --all-namespaces -o wide | grep?master2

kubectl get pod --all-namespaces -o wide | grep?master3

kubectl get nodes -o wide

修改master節點相關組件配置指向vip(每一臺master都要執行

sed -i's@server: https://192.168.100.*:6443@server: https://192.168.100.4:8443@g'/etc/kubernetes/{admin.conf,kubelet.conf,scheduler.conf,controller-manager.conf}

重啟kubelet

systemctl daemon-reload

systemctl restart kubelet docker

查看所有節點狀態

kubectl get nodes -o wide

修改kube-proxy的配置

修改kube-proxy的配置指定vip

執行命令

kubectl edit -n kube-system configmap/kube-proxy

將server修改為server: https://192.168.100.4:8443并保存

查看設置

kubectl get -n kube-system configmap/kube-proxy -o yaml

刪除重建kube-proxy

kubectl get pods --all-namespaces -o wide | grep proxy

all_proxy_pods=$(kubectl get pods --all-namespaces -o wide | grep proxy | awk'{print $2}'| xargs)

echo$all_proxy_pods

kubectl delete pods$all_proxy_pods-n kube-system

kubectl get pods --all-namespaces -o wide | grep proxy

這樣三臺HA master節點就搭建完成了

*工作節點

工作節點需要用到剛剛的kubeadm join命令添加

kubeadm join 192.168.100.4:8443 --token hpobow.vw1g1ya5dre7sq06 --discovery-token-ca-cert-hash sha256:0e4f738348be836ff810bce754e059054845f44f01619a37b817eba83282d80f

systemctl enable kubelet

需要的軟件參考我的第一篇k8s集群搭建

修改工作節點kubelet配置并重啟

sed -i's@server: https://192.168.100.*:6443@server: https://192.168.100.4:8443@g'/etc/kubernetes/kubelet.conf

重啟kubelet

systemctl daemon-reload

systemctl restart kubelet docker

查看所有節點狀態

kubectl get nodes -o wide


這樣高可用集群就搭建完成了。這樣的master節點相對于第一種高可用的方式多了haproxy負載均衡,通過負載均衡將三臺master apiserver調用起來,實現資源高可用。

本文大量照搬了以下的文獻并做了一些自己的修改

centos7使用kubeadm配置高可用k8s集群

kubeadm安裝Kubernetes V1.10集群詳細文檔

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

推薦閱讀更多精彩內容