六、Kubernetes CICD之旅

1、概述

​ 在前面的章节中我们已经演示了不少案例与各种组件的介绍与使用,我们也实战搭建了项目。通过K8S的帮助,我们可以很方便的部署项目,但还不是很完美。如果我们部署的是自己的项目,而我们的项目又是要经常改动的,这就意味着我们需要经常进行 打包、上传、重启、测试等,这些都是重复性的工作,一次两次还好,次数多了也烦人。所以下面要讲的 CI/CD 就是来帮我们解放双手,摆脱这些重复性的工作!

1.1、CI/CD是什么?

  • CI/CD 是一种通过在应用开发阶段引入 自动化 来频繁向客户交付应用的方法。

  • CI/CD 的核心概念是持续集成、持续交付和持续部署。

  • CI/CD 主要针对在集成新代码时所引发的问题。

  • CI/CD 可让持续自动化和持续监控贯穿于应用的整个生命周期(从集成和测试阶段,到交付和部署)。

  • 这些关联的事务通常被统称为 “CI/CD 管道”,由开发和运维团队以敏捷方式协同支持。

1.2、 CI 和 CD 有什么区别?

  • CI/CD 中的 “CI” 始终指持续集成,它属于开发人员的自动化流程。
  • 成功的 CI 意味着应用代码的新更改会定期构建、测试并合并到共享存储库中。
  • 该解决方案可以解决在一次开发中有太多应用分支,从而导致相互冲突的问题。
  • CI/CD 中的“CD”指的是持续交付和/或持续部署,这些相关概念有时会交叉使用。
  • 两者都事关管道后续阶段的自动化,但它们有时也会单独使用,用于说明自动化程度。

更多概念可以到 红帽 看官方介绍: https://www.redhat.com/zh/topics/devops/what-is-ci-cd

下面这张图就相对完整的介绍了本章节要进行的工作:

  • 开发手动提交代码到git仓库
  • 自动拉取代码
  • 自动进行Maven构建、代码检查、单元测试
    • 这个步骤在生产环境一般不会做自动构建,因为公司可能进行代码检查,确认无误后由QA手动进行部署
    • 本章节只进行Maven构建,代码检查和单元测试需要集成其他组件,读者可以自行了解
  • 自动build镜像、上传镜像至镜像仓库
  • 自动拉取镜像进行部署
image.png

2、环境准备

  • 准备一个项目
  • 准备一个 Git 仓库
    • 可以是自己搭建的也可以是GitHub或者码云都可以
  • 准备一个镜像仓库
    • 这里使用阿里云的镜像仓库
  • 准备一个安装了 Kubernetes 的机器
    • 直接使用之前搭建好的 K8S 集群
    • 安装 Java
    • 安装 Maven
    • 安装 Git
    • 安装 Jenkins
      • Jenkins必须在k8s集群中,因为后面需要在jenkins的目录下创建文件执行K8S的命令
      • 其他环境在这台机器安装的原因在于Jenkins需要依赖这些环境

2.1、环境搭建

  • 安装 Java

    • 下载 Java 安装包上传并解压到服务器

    • 配置环境变量

      • 采用 profile.d 目录设置环境变量,好处在于想删掉该环境可以直接删除文件,无需修改配置文件
      # 1、配置环境变量
      [root@worker01-kubeadm-k8s profile.d]# pwd
      /etc/profile.d
      [root@worker01-kubeadm-k8s profile.d]# vi java.sh
      
      export JAVA_HOME=/root/develop/java/jdk1.8.0_181
      export JRE_HOM=$JAVA_HOME/jre
      export CLASSPATH=$JAVA_HOME/lib/
      
      export PATH=$PATH:$JAVA_HOME/bin
      
      # 2、赋予权限
      [root@worker01-kubeadm-k8s profile.d]# chmod 755 ./java.sh
      
      # 3、编译文件
      [root@worker01-kubeadm-k8s profile.d]# source ./java.sh
      
      # 4、测试
      [root@worker01-kubeadm-k8s profile.d]# java -version
      java version "1.8.0_181"
      Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
      Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
      
  • 安装 Maven

    • 下载 Maven 安装包上传并解压到服务器

    • 配置环境变量

      # 1、配置环境变量
      [root@worker01-kubeadm-k8s profile.d]# vi mvn.sh
      
      export MAVEN_HOME=/root/develop/maven/apache-maven-3.6.3
      export PATH=$PATH:$MAVEN_HOME/bin
      
      # 2、赋予权限
      [root@worker01-kubeadm-k8s profile.d]# chmod 755 ./mvn.sh
      
      # 3、编译文件
      [root@worker01-kubeadm-k8s profile.d]# source ./mvn.sh
      
      # 4、测试
      [root@worker01-kubeadm-k8s profile.d]# mvn -v
      Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
      Maven home: /root/develop/maven/apache-maven-3.6.3
      Java version: 1.8.0_181, vendor: Oracle Corporation, runtime: /root/develop/java/jdk1.8.0_181/jre
      Default locale: en_US, platform encoding: UTF-8
      OS name: "linux", version: "3.10.0-1062.18.1.el7.x86_64", arch: "amd64", family: "unix"
      
    • 配置本地仓库路径、阿里云镜像

      # 1、本地仓库路径,改成自己的
      <localRepository>/root/develop/maven/repo</localRepository>
          
      # 2、配置阿里云镜像
      <mirror>
          <id>aliyunmaven</id>
          <mirrorOf>*</mirrorOf>
          <name>阿里云公共仓库</name>
          <url>https://maven.aliyun.com/repository/public</url>
      </mirror>
      
  • 安装 Git

    • 下载安装

      # 1、安装 Git
      [root@worker01-kubeadm-k8s ~]# yum install git
      
      # 2、测试
      [root@worker01-kubeadm-k8s ~]# git --version
      git version 1.8.3.1
      
    
    + 配置账号
    
      ```ruby
      [root@worker01-kubeadm-k8s ~]# git config --global user.name "Sunny"
      [root@worker01-kubeadm-k8s ~]# git config --global user.email "yzy_zhaoyang@163.com"
      [root@worker01-kubeadm-k8s ~]# ssh-keygen -t rsa -C "yzy_zhaoyang@163.com"
      
      # 出现需要输入密码的地方可以设置自己的ssh密码, 也可以直接回车表示不设置密码
      Generating public/private rsa key pair.
      Enter file in which to save the key (/root/.ssh/id_rsa):
      Enter passphrase (empty for no passphrase):
      Enter same passphrase again:
      Your identification has been saved in /root/.ssh/id_rsa.
              
      # 这里的路径文件指的是公钥,我们要打开这个文件将公钥填到 Git 仓库
      Your public key has been saved in /root/.ssh/id_rsa.pub.
      The key fingerprint is:
      ae:ca:9b:84:t5:3e:b7:as:98:51:d1:77:31:34:0e:tt yzy_zhaoyang@163.com
      The key's randomart image is:
      +--[ RSA 2048]----+
      |  ...o           |
      | . E. o          |
      |o .. o .         |
      |.o  . o          |
      |.  .    S        |
      |  ..   .         |
      | ....   .        |
      |.o=+.. .         |
      |.=+o*o.          |
      +-----------------+
    

2.2、准备项目

image.png
  • 上传到 Git 仓库

    image.png

2.3、搭建 Jenkins

  • 前情提要

    Jenkins 官网:https://jenkins.io/

    入门指南:https://jenkins.io/zh/doc/pipeline/tour/getting-started/

    • 下载并上传 Jenkins 到服务器

    • 启动 Jenkins

      #以后台启动方式启动 Jenkins,设置端口为 8088
      [root@worker01-kubeadm-k8s jenkins]#  [root@sunny jenkins]# nohup java -jar jenkins.war --httpPort=8088 &
      
      # 查看启动日志
      [root@worker01-kubeadm-k8s jenkins]#  tail -f nohup.out
      Running from: /root/jenkins/jenkins.war
      webroot: $user.home/.jenkins
      2020-04-16 15:20:49.849+0000 [id=1]     INFO    org.eclipse.jetty.util.log.Log#initialized: Logging initialized @753ms to org.eclipse.jetty.util.log.JavaUtilLog
      
      # ... 省略 ...
      
      *************************************************************
      *************************************************************
      *************************************************************
      
      Jenkins initial setup is required. An admin user has been created and a password generated.
      Please use the following password to proceed to installation:
      
      # 这是进入 Jenkins 管理页面所需要的密码
      f9025b52a4824997b09cb62d5c89f7cc
      
      # 这是存放密码的文件
      This may also be found at: /root/.jenkins/secrets/initialAdminPassword
      
      *************************************************************
      *************************************************************
      *************************************************************
      
image.png

2.3.1、配置 Jenkins

  • 安装推荐的插件

    image.png

等待安装即可,需要的时间比较久

也可以先点推荐安装,然后刷新页面选择跳过安装,之后手动下载插件安装,这样会更快

如果跳过,先截图保存一下推荐插件的列表,后面手动安装要去搜索的

  • 创建用户

    username: sunny
    password: sunny
    
    image.png
  • 安装插件

    这里说的是推荐插件安装完后还需要安装的一些插件,如果你选择了跳过安装,那要先记好它推荐了哪些插件,有些插件是必要的

    • 中文插件
      • Locale
      • Localization: Chinese (Simplified)
      • Localization Support
    • git插件
      • GitHub / Gitee
      • Gitlab Hook
      • Gitlab Plugin
  • 配置Java、Maven、Git路径

    • Manage Jenkins — Global Tool Configuration

    • 配置机器中的路径

      image.png

2.3.2、镜像仓库与Kubernetes集群

  • 镜像仓库采用国内的阿里云镜像仓库

  • Kubernetes 集群直接采用我们之前章节中已经搭建好的 Kubernetes 集群即可。

3、Pipeline任务

我们一个步骤一个步骤的演示,从最开始的拉取代码,慢慢延伸到最终的CICD!

3.1 拉取项目代码

Jenkins要实现自动拉取代码,需要安装Git相关插件,以及要到Git仓库去进行一些配置,插件之前都已经安装了,接下来就演示一下Jenkins的配置。

  • 创建任务

    image.png
  • 创建流水线任务

    image.png
  • 编写pipeline

    注意这里要勾选上,保存webhook的链接,后面配置自动拉取时要用到

    image.png
image.png
node {
   // Pull
   stage('Pull') { //stage表示一个任务
      //这里是任务的内容
      //因为我们的机器已经生成了秘钥且与Gieee进行了绑定,所以无需输入账号密码就可以进行拉取代码
      git 'https://gitee.com/suny95/springboot-demo-k8s.git'
   }
}

配置完成后点击保存

  • 构建任务

    image.png

构建完成后会显示绿色,构建失败是红色,可以去查看错误信息

image.png

这里是Jenkins工作目录:/root/.jenkins/workspace目录

代码是拉取到这个目录下的

# Jenkins工作目录
[root@worker01-kubeadm-k8s workspace]# pwd
/root/.jenkins/workspace
[root@worker01-kubeadm-k8s workspace]# ll
total 0
drwxr-xr-x. 4 root root 62 Apr 18 14:37 springboot-k8s-task

# 新建的任务目录
[root@worker01-kubeadm-k8s workspace]# cd springboot-k8s-task/
[root@worker01-kubeadm-k8s springboot-k8s-task]# ll
total 4
-rw-r--r--. 1 root root 1277 Apr 18 14:37 pom.xml
drwxr-xr-x. 3 root root   18 Apr 18 14:37 src

3.2 Maven自动编译

修改之前编写好的流水线任务,增加一个Stage用来做Maven自动编译

  • 修改Pipeline脚本内容

    node {
       // Pull
       stage('Pull') { //stage表示一个任务
          //这里是任务的内容
          //因为我们的机器已经生成了秘钥且与Gieee进行了绑定,所以无需输入账号密码就可以进行拉取代码
          git 'https://gitee.com/suny95/springboot-demo-k8s.git'
       }
       
       //Maven
       stage('AutoCompile') {
           sh "mvn clean package"
       }
    }
    
  • 构建任务

    开始构建任务后,可以通过Console Output监控当前任务执行情况

    image.png
image.png
# 再次查看目录,发现已经成功编译出 target 目录了
[root@worker01-kubeadm-k8s springboot-k8s-task]# ll
total 4
-rw-r--r--. 1 root root 1277 Apr 18 14:37 pom.xml
drwxr-xr-x. 3 root root   18 Apr 18 14:37 src
drwxr-xr-x. 6 root root  181 Apr 18 14:56 target

到这里为止,手动构建的方式已经完成, 下面将要开始配置自动构建

3.3 Git Push 触发 Jenkins 自动构建

当用户进行git push提交代码到 GitHub / Git EE 时,能够通知 jenkins 自动构建

注意: Jenkins 的 ip 一定要是 Git EE 能够访问到的地址

我这里由于是本地搭建的虚拟机,就不做演示了,百度搜一下有很多的教程可以参考

3.4 build 与 push 镜像

经过前面的演示,现在已经可以获取到代码,并且用maven进行构建了,最终拿到一个target/xxx.jar

现在我们要演示Docker的镜像构建与Push镜像到镜像仓库!

  • 准备执行文件

    • 到 Jenkins 的工作目录

      • cd /root/.jenkins/workspace
    • 创建 .sh 执行文件

      # 创建专门存放脚本的目录
      [root@worker01-kubeadm-k8s workspace]# mkdir scripts
      [root@worker01-kubeadm-k8s workspace]# cd scripts/
      
      # 编写执行文件
      [root@worker01-kubeadm-k8s scripts]# vi springboot-demo-k8s-build-image.sh
      
      # ================== 执行文件内容 ======================
      # 进入到springboot-k8s-task目录
      cd ../springboot-k8s-task
      
      # 编写Dockerfile文件, 这里也可以先在项目里写好
      
      cat <<EOF > Dockerfile
      FROM openjdk:8-jre-alpine
      COPY target/springboot-demo-0.0.1-SNAPSHOT.jar /springboot-demo.jar
      ENTRYPOINT ["java","-jar","/springboot-demo.jar"]
      EOF
      
      echo "Dockerfile created successfully!"
      
      # 基于指定目录下的Dockerfile构建镜像
      # 注意 zhao_yang 这个命名空间是公开的,如果是私有的,还需要配置K8S的认证
      docker build -t registry.cn-hangzhou.aliyuncs.com/zhao_yang/springboot-demo-k8s:v1.0 .
      
      # 登录docker账号
      # 注意: 如果密码不能开放的话,可以先手动在机器上登录一次,那这里就无需再次登录了
      docker login --username=用户名 --password=密码 registry.cn-shanghai.aliyuncs.com
      
      # push镜像
      docker push registry.cn-hangzhou.aliyuncs.com/zhao_yang/springboot-demo-k8s:v1.0
      # ================== 执行文件内容 ======================
      
      # 增加执行权限
      [root@worker01-kubeadm-k8s scripts]# chmod +x ./springboot-demo-k8s-build-image.sh
      
  • 修改任务的Pipeline脚本

    node {
       // Pull
       stage('Pull') { //stage表示一个任务
          //这里是任务的内容
          //因为我们的机器已经生成了秘钥且与Gieee进行了绑定,所以无需输入账号密码就可以进行拉取代码
          git 'https://gitee.com/suny95/springboot-demo-k8s.git'
       }
       
       //Maven
       stage('AutoCompile') {
           sh "mvn clean package"
       }
       
       //增加任务,执行脚本文件
       stage('Build And Push Image') { 
          sh "/root/.jenkins/workspace/scripts/springboot-demo-k8s-build-image.sh"
       }
    }
    
  • 构建任务,监控输出

    image.png
    image.png

build 镜像与 push 镜像至镜像仓库 都已成功!!!

3.1.5 Kubernetes 拉取镜像运行

这个过程需要提前准备的也就是image镜像和ingress,image刚才我们都已经上传到阿里云镜像仓库了,ingress我们在之前也准备过了,如果没有ingress环境的,可以去看前面的 kubernetes 网络模型 ,里面有安装ingress的流程!

  • 编写 YAML 文件

    YAML文件可以写到项目中,也可以直接在机器上创建

    • vi /root/.jenkins/workspace/scripts/springboot-demo-k8s.yaml
    # 以Deployment部署Pod
    apiVersion: apps/v1
    kind: Deployment
    metadata: 
      name: springboot-demo-k8s
    spec: 
      selector: 
        matchLabels: 
          app: springboot-demo-k8s
      replicas: 1
      template: 
        metadata:
          labels: 
            app: springboot-demo-k8s
        spec: 
          containers: 
          - name: springboot-demo-k8s
            image: registry.cn-hangzhou.aliyuncs.com/zhao_yang/springboot-demo-k8s:v1.0
            ports: 
            - containerPort: 8080
    ---
    # 创建Pod的Service
    apiVersion: v1
    kind: Service
    metadata: 
      name: springboot-demo-k8s
    spec: 
      ports: 
      - port: 80
        protocol: TCP
        targetPort: 8080
      selector: 
        app: springboot-demo-k8s
    ---
    # 创建Ingress,定义访问规则
    apiVersion: networking.k8s.io/v1beta1
    kind: Ingress
    metadata: 
      name: springboot-demo-k8s
    spec: 
      rules: 
      - host: springboot.sunny.com
        http: 
          paths: 
          - path: /
            backend: 
              serviceName: springboot-demo-k8s
              servicePort: 80
    
  • 编写 .sh 执行文件

    [root@worker01-kubeadm-k8s scripts]# vi /root/.jenkins/workspace/scripts/k8s-deploy-springboot-demo.sh
    
    # ================== YAML文件内容 ============
    kubectl delete -f /root/.jenkins/workspace/scripts/springboot-demo-k8s.yaml
    kubectl apply -f /root/.jenkins/workspace/scripts/springboot-demo-k8s.yaml
    echo "k8s deploy success!"
    # ================== YAML文件内容 ============
    
    [root@worker01-kubeadm-k8s scripts]# chmod +x k8s-deploy-springboot-demo.sh
    
  • 修改Pipeline

    node {
       // Pull
       stage('Pull') { //stage表示一个任务
          //这里是任务的内容
          //因为我们的机器已经生成了秘钥且与Gieee进行了绑定,所以无需输入账号密码就可以进行拉取代码
          git 'https://gitee.com/suny95/springboot-demo-k8s.git'
       }
       
       //Maven
       stage('AutoCompile') {
           sh "mvn clean package"
       }
       
       //build push
       stage('Build And Push Image') { 
          sh "/root/.jenkins/workspace/scripts/springboot-demo-k8s-build-image.sh"
       }
       
       //k8s
       stage('K8S Deploy') { 
          sh "/root/.jenkins/workspace/scripts/k8s-deploy-springboot-demo.sh"
       }
    }
    
  • 构建任务并监控输出

    image.png

发现出现异常了,这个问题其实是因为我们选择的机器在 K8S 集群中不是 Master 节点,它没有执行kubectl 命令的权限,我们需要配置一下!

  • 复制 .kube 文件目录到 Worker01 节点

    [root@master-kubeadm-k8s .kube]# pwd
    /root/.kube
    
    # 这里的 config 是权限认证的文件
    [root@master-kubeadm-k8s .kube]# ll
    total 12
    drwxr-xr-x. 3 root root   23 Mar 24 14:32 cache
    -rw-------. 1 root root 5454 Mar 24 14:30 config
    drwxr-xr-x. 3 root root 4096 Apr  4 08:59 http-cache
    
    image.png
```ruby
[root@worker01-kubeadm-k8s ~]# mkdir .kube
[root@worker01-kubeadm-k8s ~]# cd .kube/

# 将 Master 节点中config文件内容复制进去即可
[root@worker01-kubeadm-k8s .kube]# vi config
[root@worker01-kubeadm-k8s .kube]# ll
total 8
-rw-r--r--. 1 root root 5454 Apr 19 09:45 config
```
  • 重新构建并监控

    image.png
  • 测试

    # pod 运行成功
    [root@worker01-kubeadm-k8s scripts]# kubectl get pods
    NAME                                   READY   STATUS    RESTARTS   AGE
    springboot-demo-k8s-79c867fd58-ts2f7   1/1     Running   0          5m53s
    
    # service 也创建成功
    [root@worker01-kubeadm-k8s scripts]# kubectl get svc
    NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
    kubernetes            ClusterIP   10.96.0.1       <none>        443/TCP   25d
    springboot-demo-k8s   ClusterIP   10.99.138.171   <none>        80/TCP    6m46s
    
    # ingress 也在
    [root@worker01-kubeadm-k8s scripts]# kubectl get ingress
    NAME                  HOSTS                ADDRESS   PORTS   AGE
    springboot-demo-k8s   springboot.sunny.com             80      10m
    
    # 访问 Service IP + 路径, 成功访问到结果
    [root@worker01-kubeadm-k8s scripts]# curl 10.99.138.171/k8s
    hello K8S
    
    • 通过Ingress访问也成功
image.png

由于机器问题,我没办法演示 Git Push 就触发自动构建的任务,大家如果有公网的机器的话,可以去尝试弄一下!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353

推荐阅读更多精彩内容