Golang基于Gitlab CI/CD部署方案

本文為轉(zhuǎn)載,原文:Golang基于Gitlab CI/CD部署方案

docker

概述

持續(xù)集成 (Continuous integration)是一種軟件開發(fā)實(shí)踐唠粥,即團(tuán)隊(duì)開發(fā)成員經(jīng)常集成它們的工作句各,通過每個(gè)成員每天至少集成一次剔蹋,也就意味著每天可能會(huì)發(fā)生多次集成幅狮。每次集成都通過自動(dòng)化的構(gòu)建(包括編譯闯估,發(fā)布睹欲,自動(dòng)化測試)來驗(yàn)證供炼,從而盡早地發(fā)現(xiàn)集成錯(cuò)誤。

持續(xù)部署(continuous deployment)是通過自動(dòng)化的構(gòu)建窘疮、測試和部署循環(huán)來快速交付高質(zhì)量的產(chǎn)品袋哼。某種程度上代表了一個(gè)開發(fā)團(tuán)隊(duì)工程化的程度,畢竟快速運(yùn)轉(zhuǎn)的互聯(lián)網(wǎng)公司人力成本會(huì)高于機(jī)器闸衫,投資機(jī)器優(yōu)化開發(fā)流程化相對(duì)也提高了人的效率涛贯,讓 engineering productivity 最大化。

1. 環(huán)境準(zhǔn)備

本次試驗(yàn)是基于Centos 7.3, docker 17.03.2-ce環(huán)境下的蔚出。docker的安裝這里就不贅述了弟翘,提供了官方鏈接吧: Get Docker CE for CentOS

1.1. docker啟動(dòng)gitlab

啟動(dòng)命令如下:

docker run --detach \
--hostname gitlab.chain.cn \
--publish 8443:443 --publish 8080:80 --publish 2222:22 \
--name gitlab \
--restart always \
--volume /Users/zhangzc/gitlab/config:/etc/gitlab \
--volume /Users/zhangzc/gitlab/logs:/var/log/gitlab \
--volume /Users/zhangzc/gitlab/data:/var/opt/gitlab \
gitlab/gitlab-ce

port,hostname, volume根據(jù)具體情況具體設(shè)置

1.2. docker啟動(dòng)gitlab-runner

啟動(dòng)命令如下:

sudo docker run -d /
--name gitlab-runner /
--restart always /
-v /Users/zhangzc/gitlab-runner/config:/etc/gitlab-runner /
-v /Users/zhangzc/gitlab-runner/run/docker.sock:/var/run/docker.sock /
gitlab/gitlab-runner:latest

volume根據(jù)具體情況具體設(shè)置

1.3. 用于集成部署的鏡像制作

我們的集成和部署都需要放在一個(gè)容器里面進(jìn)行骄酗,所以稀余,需要制作一個(gè)鏡像并安裝一些必要的工具,用于集成和部署相關(guān)操作趋翻。目前我們的項(xiàng)目都是基于golang 1.9.2的睛琳,這里也就基于golang:1.9.2的鏡像制定一個(gè)特定的鏡像。

Dockerfile內(nèi)容如下:

# Base image: https://hub.docker.com/_/golang/
FROM golang:1.9.2
USER root
# Install golint
ENV GOPATH /go
ENV PATH ${GOPATH}/bin:$PATH
RUN mkdir -p /go/src/golang.org/x
RUN mkdir -p /go/src/github.com/golang
COPY source/golang.org /go/src/golang.org/x/
COPY source/github.com /go/src/github.com/golang/
RUN go install github.com/golang/lint/golint

# install docker
RUN curl -O https://get.docker.com/builds/Linux/x86_64/docker-latest.tgz \
    && tar zxvf docker-latest.tgz \
    && cp docker/docker /usr/local/bin/ \
    && rm -rf docker docker-latest.tgz

# install expect
RUN apt-get update
RUN apt-get -y install tcl tk expect

其中golint是用于golang代碼風(fēng)格檢查的工具踏烙。
docker是由于需要在容器里面使用宿主的docker命令师骗,這里就需要安裝一個(gè)docker的可執(zhí)行文件,然后在啟動(dòng)容器的時(shí)候讨惩,將宿主的 /var/run/docker.sock 文件掛載到容器內(nèi)的同樣位置丧凤。
expect是用于ssh自動(dòng)登錄遠(yuǎn)程服務(wù)器的工具,這里安裝改工具是為了可以實(shí)現(xiàn)遠(yuǎn)程服務(wù)器端部署應(yīng)用

另外步脓,在安裝golint的時(shí)候愿待,是需要去golang.org下載源碼的,由于墻的關(guān)系靴患,go get命令是執(zhí)行不了的仍侥。為了處理這個(gè)問題,首先通過其他渠道先下載好相關(guān)源碼鸳君,放到指定的路徑下农渊,然后copy到鏡像里,并執(zhí)行安裝即可或颊。

下面有段腳本是用于生成鏡像的:

#!/bin/bash

echo "提取構(gòu)建鏡像時(shí)需要的文件"
source_path="source"
mkdir -p $source_path/golang.org
mkdir -p $source_path/github.com
cp -rf $GOPATH/src/golang.org/x/lint $source_path/golang.org/
cp -rf $GOPATH/src/golang.org/x/tools $source_path/golang.org/
cp -rf $GOPATH/src/github.com/golang/lint $source_path/github.com

echo "構(gòu)建鏡像"
docker build -t go-tools:1.9.2 .

echo "刪除構(gòu)建鏡像時(shí)需要的文件"
rm -rf $source_path

生成鏡像后砸紊,推送到鏡像倉庫传于,并在gitlab-runner的服務(wù)器上拉取該鏡像

本次試驗(yàn)的gitlab和gitlab-runner是運(yùn)行在同一服務(wù)器的docker下的。

2. runner注冊及配置

2.1. 注冊

環(huán)境準(zhǔn)備好后醉顽,在服務(wù)器上執(zhí)行以下命令沼溜,注冊runner:

docker exec -it gitlab-runner gitlab-ci-multi-runner register

按照提示輸入相關(guān)信息

Please enter the gitlab-ci coordinator URL:
# gitlab的url, 如:https://gitlab.chain.cn/
Please enter the gitlab-ci token for this runner:
# gitlab->你的項(xiàng)目->settings -> CI/CD ->Runners settings
Please enter the gitlab-ci description for this runner:
# 示例:demo-test
Please enter the gitlab-ci tags for this runner (comma separated):
# 示例:demo
Whether to run untagged builds [true/false]:
# true
Please enter the executor: docker, parallels, shell, kubernetes, docker-ssh, ssh, virtualbox, docker+machine, docker-ssh+machine:
# docker
Please enter the default Docker image (e.g. ruby:2.1):
# go-tools:1.9.2 (之前自己制作的鏡像)
token

成功后,可以看到gitlab->你的項(xiàng)目->settings -> CI/CD ->Runners settings 頁面下面有以下內(nèi)容:

runner注冊成功

2.2. 配置

注冊成功之后游添,還需要在原有的配置上做一些特定的配置系草,如下:

[[runners]]
  name = "demo-test"
  url = "https://gitlab.chain.cn/"
  token = "c771fc5feb1734a9d4df4c8108cd4e"
  executor = "docker"
  [runners.docker]
    tls_verify = false
    image = "go-tools:1.9.2"
    privileged = false
    disable_cache = false
    volumes = ["/var/run/docker.sock:/var/run/docker.sock"]
    extra_hosts = ["gitlab.chain.cn:127.0.0.1"]
    network_mode = "host"
    pull_policy = "if-not-present"
    shm_size = 0
  [runners.cache]

這里先解釋下gitlab-runner的流程吧,gitlab-runner在執(zhí)行的時(shí)候唆涝,會(huì)根據(jù)上面的配置啟動(dòng)一個(gè)容器找都,即配置中的go-tools:1.9.2,b其中所有的啟動(dòng)參數(shù)都會(huì)在[runners.docker]節(jié)點(diǎn)下配置好廊酣,包括掛載啊能耻,網(wǎng)絡(luò)啊之類的。容器啟動(dòng)成功之后亡驰,會(huì)使用這個(gè)容器去gitlab上pull代碼晓猛,然后根據(jù)自己定義的規(guī)則進(jìn)行檢驗(yàn),全部檢測成功之后便是部署了隐解。

volumes: 是為了在容器中可以執(zhí)行宿主機(jī)的docker命令鞍帝。

extra_hosts: 給gitlab添加個(gè)host映射,映射到127.0.0.1

network_mode: 令容器的網(wǎng)絡(luò)與宿主機(jī)一致煞茫,只有這樣才能通過127.0.0.1訪問到gitlab帕涌。

pull_policy: 當(dāng)指定的鏡像不存在的話,則通過docker pull拉取

3. 定義規(guī)則

在gitlab項(xiàng)目根目錄創(chuàng)建.gitlab-ci.yml文件续徽,填寫runner規(guī)則蚓曼,具體語法課參考官方文檔:https://docs.gitlab.com/ee/ci/yaml/

3.1. go集成命令

下面介紹幾個(gè)golang常見的集成命令

  1. 包列表
    正如在官方文檔中所描述的那樣,go項(xiàng)目是包的集合钦扭。下面介紹的大多數(shù)工具都將使用這些包纫版,因此我們需要的第一個(gè)命令是列出包的方法。我們可以用go list子命令來完成
go list ./...

請(qǐng)注意客情,如果我們要避免將我們的工具應(yīng)用于外部資源其弊,并將其限制在我們的代碼中。 那么我們需要去除vendor 目錄膀斋,命令如下:

go list ./... | grep -v /vendor/
  1. 單元測試
    這些是您可以在代碼中運(yùn)行的最常見的測試梭伐。每個(gè).go文件需要一個(gè)能支持單元測試的_test.go文件⊙龅#可以使用以下命令運(yùn)行所有包的測試:
go test -short $(go list ./... | grep -v /vendor/)
  1. 數(shù)據(jù)競爭
    這通常是一個(gè)難以逃避解決的問題糊识,go工具默認(rèn)具有(但只能在linux / amd64、freebsd / amd64、darwin / amd64和windows / amd64上使用)
go test -race -short $(go list . /…| grep - v /vendor/)
  1. 代碼覆蓋
    這是評(píng)估代碼的質(zhì)量的必備工具赂苗,并能顯示哪部分代碼進(jìn)行了單元測試愉耙,哪部分沒有。
    要計(jì)算代碼覆蓋率拌滋,需要運(yùn)行以下腳本:
PKG_LIST=$(go list ./... | grep -v /vendor/)
for package in ${PKG_LIST}; do
    go test -covermode=count -coverprofile "cover/${package##*/}.cov" "$package" ;
done
tail -q -n +2 cover/*.cov >> cover/coverage.cov
go tool cover -func=cover/coverage.cov

如果我們想要獲得HTML格式的覆蓋率報(bào)告朴沿,我們需要添加以下命令:

go tool cover -html=cover/coverage.cov -o coverage.html
  1. 構(gòu)建
    最后一旦代碼經(jīng)過了完全測試,我們要對(duì)代碼進(jìn)行編譯鸠真,從而構(gòu)建可以執(zhí)行的二進(jìn)制文件悯仙。
go build .
  1. linter
    這是我們在代碼中使用的第一個(gè)工具:linter龄毡。它的作用是檢查代碼風(fēng)格/錯(cuò)誤吠卷。這聽起來像是一個(gè)可選的工具,或者至少是一個(gè)“不錯(cuò)”的工具沦零,但它確實(shí)有助于在項(xiàng)目上保持一致的代碼風(fēng)格祭隔。
    linter并不是go本身的一部分,所以如果要使用路操,你需要手動(dòng)安裝它(之前的go-tools鏡像我們已經(jīng)安裝過了)疾渴。
    使用方法相當(dāng)簡單:只需在代碼包上運(yùn)行它(也可以指向. go文件):
$ golint -set_exit_status $(go list ./... | grep -v /vendor/)

注意-set_exit_status選項(xiàng)。 默認(rèn)情況下屯仗,golint僅輸出樣式問題搞坝,并帶有返回值(帶有0返回碼),所以CI不認(rèn)為是出錯(cuò)魁袜。 如果指定了-set_exit_status桩撮,則在遇到任何樣式問題時(shí),golint的返回碼將不為0峰弹。

3.2. Makefile

如果我們不想在.gitlab-ci.yml文件中寫的太復(fù)雜店量,那么我們可以把持續(xù)集成環(huán)境中使用的所有工具,全部打包在Makefile中鞠呈,并用統(tǒng)一的方式調(diào)用它們融师。

這樣的話,.gitlab-ci.yml文件就會(huì)更加簡潔了蚁吝。當(dāng)然了旱爆,Makefile同樣也可以調(diào)用*.sh腳本文件

3.3. 配置示例

3.3.1. .gitlab-ci.yml

image: go-tools:1.9.2

stages: 
  - build
  - test
  - deploy

before_script:
  - mkdir -p /go/src/gitlab.chain.cn/ZhangZhongcheng /go/src/_/builds
  - cp -r $CI_PROJECT_DIR /go/src/gitlab.chain.cn/ZhangZhongcheng/demo
  - ln -s /go/src/gitlab.chain.cn/ZhangZhongcheng /go/src/_/builds/ZhangZhongcheng  
  - cd /go/src/_/builds/ZhangZhongcheng/demo

unit_tests:
  stage: test
  script: 
    - make test
  tags: 
    - demo

race_detector:
  stage: test
  script:
    - make race
  tags: 
    - demo

code_coverage:
  stage: test
  script:
    - make coverage
  tags: 
    - demo

code_coverage_report:
  stage: test
  script:
    - make coverhtml
  only:
  - master
  tags: 
    - demo

lint_code:
  stage: test
  script:
    - make lint    

build:
  stage: build
  script:
    - pwd
    - go build .
  tags:
    - demo

build_image:
  stage: deploy
  script:
    - make build_image
  tags:
    - demo    

3.3.2. Makefile

PROJECT_NAME := "demo"
PKG := "gitlab.chain.cn/ZhangZhongcheng/$(PROJECT_NAME)"
PKG_LIST := $(shell go list ./... | grep -v /vendor/)
GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go)

test: ## Run unittests
    @go test -v ${PKG_LIST}

lint: ## Lint the files
    @golint ${PKG_LIST} 

race: ## Run data race detector
    @go test -race -short ${PKG_LIST}

coverage: ## Generate global code coverage report
    ./scripts/coverage.sh;
    
coverhtml: ## Generate global code coverage report in HTML
    ./scripts/coverage.sh html;

build_image:
    ./scripts/buildDockerImage.sh   

3.3.3. coverage.sh

#!/bin/bash
#
# Code coverage generation

COVERAGE_DIR="${COVERAGE_DIR:-coverage}"
PKG_LIST=$(go list ./... | grep -v /vendor/)

# Create the coverage files directory
mkdir -p "$COVERAGE_DIR";

# Create a coverage file for each package
for package in ${PKG_LIST}; do
    go test -covermode=count -coverprofile "${COVERAGE_DIR}/${package##*/}.cov" "$package" ;
done ;

# Merge the coverage profile files
echo 'mode: count' > "${COVERAGE_DIR}"/coverage.cov ;
tail -q -n +2 "${COVERAGE_DIR}"/*.cov >> "${COVERAGE_DIR}"/coverage.cov ;

# Display the global code coverage
go tool cover -func="${COVERAGE_DIR}"/coverage.cov ;

# If needed, generate HTML report
if [ "$1" == "html" ]; then
    go tool cover -html="${COVERAGE_DIR}"/coverage.cov -o coverage.html ;
fi

# Remove the coverage files directory
rm -rf "$COVERAGE_DIR";

3.3.4. buildDockerImage.sh

#!/bin/bash

#檢測GOPATH
echo "檢測GOPATH"
if [ -z "$GOPATH" ];then
echo "GOPATH 未設(shè)定"
exit 1
else
echo "GOPATH=$GOPATH"
fi

#初始化數(shù)據(jù)
echo "初始化數(shù)據(jù)"
new_version="1.0.0"
old_version="1.0.0"
golang_version="1.9.2"
app_name="application"
projust_root="demo"
DOCKER_IMAGE_NAME="demo"
REGISTRY_HOST="xxx.xxx.xxx.xxx:5000"
path="/go/src/_/builds/ZhangZhongcheng/demo"


#當(dāng)前容器更換為舊標(biāo)簽
echo "當(dāng)前容器更換為舊標(biāo)簽"
docker rmi $REGISTRY_HOST/$DOCKER_IMAGE_NAME:$old_version

# 基于golang:1.9.2鏡像啟動(dòng)的容器實(shí)例,編譯本項(xiàng)目的二進(jìn)制可執(zhí)行程序
echo "基于golang:1.9.2鏡像啟動(dòng)的容器實(shí)例窘茁,編譯本項(xiàng)目的二進(jìn)制可執(zhí)行程序"
cd $path
go build -o $app_name

echo "檢測 $app_name 應(yīng)用"
FILE="$path/$app_name"
if [ -f "$FILE" ];then
echo "$FILE 已就緒"
else
echo "$FILE 應(yīng)用不存在"
exit 1
fi

#docker構(gòu)建鏡像 禁止在構(gòu)建上下文之外的路徑 添加復(fù)制文件
#所以在此可以用命令把需要的文件cp到 dockerfile 同目錄內(nèi) ,構(gòu)建完成后再用命令刪除
cd $path/scripts
echo "提取構(gòu)建時(shí)需要的文件"
cp ../$app_name $app_name

# 基于當(dāng)前目錄下的Dockerfile構(gòu)建鏡像
echo "基于當(dāng)前目錄下的Dockerfile構(gòu)建鏡像"
echo "docker build -t $REGISTRY_HOST/$DOCKER_IMAGE_NAME:$new_version ."
docker build -t $REGISTRY_HOST/$DOCKER_IMAGE_NAME:$new_version .

# 刪除本次生成的可執(zhí)行文件 以及構(gòu)建所需要的文件
echo "刪除本次生成的可執(zhí)行文件 以及構(gòu)建所需要的文件"
rm -rf $app_name
rm -rf ../$app_name

#查看鏡像
echo "查看鏡像"
docker images | grep $DOCKER_IMAGE_NAME

#推送鏡像
echo "推送鏡像"
echo "docker push $REGISTRY_HOST/$DOCKER_IMAGE_NAME:$new_version"
docker push $REGISTRY_HOST/$DOCKER_IMAGE_NAME:$new_version

echo "auto deploy"
./automationDeployment.sh $new_version $old_version

3.3.5. automationDeployment.sh

#!/usr/bin/expect
#指定shebang
#設(shè)定超時(shí)時(shí)間為3秒
set ip xxx.xxx.xxx.xxx
set password "xxxxxxx"

set new_version [lindex $argv 0]
set old_version [lindex $argv 1]

spawn ssh root@$ip
expect {
    "*yes/no" { send "yes\r"; exp_continue}
    "*password:" { send "$password\r" }
}
expect "#*"
send "cd /root/demo/\r"
send "./docker_run_demo.sh $new_version $old_version\r"
expect eof

3.3.6. Dockerfile

FROM golang:1.9.2

#定義環(huán)境變量 alpine專用
#ENV TIME_ZONE Asia/Shanghai

ADD application /go/src/demo/

WORKDIR /go/src/demo

ADD run_application.sh /root/
RUN chmod 755 /root/run_application.sh
CMD sh /root/run_application.sh

EXPOSE 8080

3.3.7. run_application.sh

#!/bin/bash

#映射ip
cp /usr/share/zoneinfo/Asia/Shanghai  /etc/localtime

cd /go/src/demo/

./application

4. 結(jié)果

以下為部署成功后的截圖:


result

轉(zhuǎn)載請(qǐng)注明出處: Golang基于Gitlab CI/CD部署方案

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末怀伦,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子庙曙,更是在濱河造成了極大的恐慌空镜,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吴攒,居然都是意外死亡张抄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門洼怔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來署惯,“玉大人,你說我怎么就攤上這事镣隶〖辏” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵安岂,是天一觀的道長轻猖。 經(jīng)常有香客問我,道長域那,這世上最難降的妖魔是什么咙边? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮次员,結(jié)果婚禮上败许,老公的妹妹穿的比我還像新娘。我一直安慰自己淑蔚,他們只是感情好市殷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著刹衫,像睡著了一般醋寝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绪妹,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天甥桂,我揣著相機(jī)與錄音,去河邊找鬼邮旷。 笑死黄选,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的婶肩。 我是一名探鬼主播办陷,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼律歼!你這毒婦竟也來了民镜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤险毁,失蹤者是張志新(化名)和其女友劉穎制圈,沒想到半個(gè)月后们童,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲸鹦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年慧库,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馋嗜。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡齐板,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出葛菇,到底是詐尸還是另有隱情甘磨,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布眯停,位于F島的核電站济舆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏庵朝。R本人自食惡果不足惜吗冤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一又厉、第九天 我趴在偏房一處隱蔽的房頂上張望九府。 院中可真熱鬧,春花似錦覆致、人聲如沸侄旬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽儡羔。三九已至,卻和暖如春璧诵,著一層夾襖步出監(jiān)牢的瞬間汰蜘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工之宿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留族操,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓比被,卻偏偏與公主長得像色难,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子等缀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348