k8s环境搭建

k8s环境搭建

文档介绍在Docker for mac中的k8s如何把环境搭建好。

清单:

  • docker for mac中启动k8s
  • k8s网络环境介绍
  • ingress-nginx安装(网关)
  • k8s-dashboard安装(官方提供的UI管理)
  • weavescope安装(集群监控)
  • mysql安装(事例)

通过以上清单,可以完成大部分日常开发工作。

docker for mac中启动k8s

要启动docker for mac中的k8s,需要下载很多k8s相关镜像,这部分镜像国内网络无法下载,我会列出来所有的镜像可以自己从其他途径下载,也可以直接通过我给的脚本下载。

镜像下载:

#!/bin/bash
kube_version=v1.15.5
kube_proxy_version=$kube_version
kube_controller_manager_version=$kube_version
kube_scheduler_version=$kube_version
kube_apiserver_version=$kube_version

coredns_version=1.3.1
pause_version=3.1
etcd_version=3.3.10
# 镜像列表的前面的是需要下载的镜像,等号后面的是能够正常下载下来的镜像
image_list=(
  "k8s.gcr.io/kube-proxy:${kube_proxy_version}=gotok8s/kube-proxy:${kube_proxy_version}"
  "k8s.gcr.io/kube-controller-manager:${kube_controller_manager_version}=gotok8s/kube-controller-manager:${kube_controller_manager_version}"
  "k8s.gcr.io/kube-scheduler:${kube_scheduler_version}=gotok8s/kube-scheduler:${kube_scheduler_version}"
  "k8s.gcr.io/kube-apiserver:${kube_apiserver_version}=gotok8s/kube-apiserver:${kube_apiserver_version}"
  "k8s.gcr.io/coredns:${coredns_version}=gotok8s/coredns:${coredns_version}"
  "k8s.gcr.io/pause:${pause_version}=gotok8s/pause:${pause_version}"
  "k8s.gcr.io/etcd:${etcd_version}=gotok8s/etcd:${etcd_version}"
)
OLD_IFS="$IFS"
for value in "${image_list[@]}"; do
  IFS="="
  read -r -a image <<<"$value"
  image_name=${image[0]}
  repeat_image_name=${image[1]}
  echo "pull $image_name from $repeat_image_name"
  docker pull "$repeat_image_name"
  docker tag "$repeat_image_name" "$image_name"
  docker rmi "$repeat_image_name"
done
IFS="$OLD_IFS"

下载好了后直接通过docker for mac打开就好了, 大概等5分钟左右:


image.png

k8s单节点集群就安装完成了。

k8s网络环境介绍

k8s中的网络不通比较难找问题,下面先澄清一下:

ingress-nginx安装

有了k8s集群,集群里面部署的应用我们没办法直接访问,除非使用nodePort的服务形式暴露出来,这样暴露出来有个问题就是:我们直接通过pod所在的机器ip和端口来访问,每次重启应用可能会在不同的机器上,这样就导致访问的ip一直处于变化中。为了解决这个问题,我们私有自建集群就可以通过以下几种方式来解决:

  • MetaLB。和提供给云厂商的负载均衡一样的。

  • ingress。Ingress 公开了从集群外部到集群内service的HTTP和HTTPS路由。 流量路由由 Ingress 资源上定义的规则控制。

         internet
            |
       [ Ingress ]
       --|-----|--
       [ Services ]
    

以上两种是比较推荐来解决ip漂移问题的方案。

安装ingress

  1. 安装ingress-controller。下载文件:https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.28.0/deploy/static/mandatory.yaml保存成ingress-nginx.yaml
kubectl apply -f ingress-nginx.yaml

这里可以直接把这个文件下载到本机,里面有一个镜像:quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.28.0这个镜像在国内同样可能无法下载,需要自己去下载下来。对于这个版本,提供一个替代下载地址:registry.cn-shenzhen.aliyuncs.com/yangqiang-pub/kubernetes-ingress-controller:0.28.0可以把文件里面的镜像替换成这个地址。

  1. 安装service。
kubectl apply -f service.yaml

我们主要通过NodePort的方式暴露出去,还有其他选择参考:https://kubernetes.github.io/ingress-nginx/deploy/baremetal/

service.yaml

kind: Service
apiVersion: v1
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  externalTrafficPolicy: Local
  type: NodePort
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: http
      nodePort: 30000
    - name: https
      port: 443
      protocol: TCP
      targetPort: https
      nodePort: 30001

这里的端口号必须是30000-32767。所以我们这里直接把30000和30001当成80以及443来使用。尽管使用--service-node-port-rangeAPI服务器标志重新配置NodePort范围以包含非特权端口并能够公开端口80和443。这样做可能会导致意外问题。

到此ingress-nginx就安装完成了。

k8s-dashboard安装(官方提供的UI管理)

  1. 安装dashboard。如果镜像下载不下来,也要替换。

    kubectl apply -f dashboard.yaml
    

    dashboard.yaml

    # Copyright 2017 The Kubernetes Authors.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    # 修改说明:修改两个镜像的拉起策略都为imagePullPolicy: IfNotPresent 防止每次从远程拉不到镜像的问题
    apiVersion: v1
    kind: Namespace
    metadata:
      name: kubernetes-dashboard
    
    ---
    
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard
      namespace: kubernetes-dashboard
    
    ---
    
    kind: Service
    apiVersion: v1
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard
      namespace: kubernetes-dashboard
    spec:
      ports:
        - port: 443
          targetPort: 8443
      selector:
        k8s-app: kubernetes-dashboard
    
    ---
    
    apiVersion: v1
    kind: Secret
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard-certs
      namespace: kubernetes-dashboard
    type: Opaque
    
    ---
    
    apiVersion: v1
    kind: Secret
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard-csrf
      namespace: kubernetes-dashboard
    type: Opaque
    data:
      csrf: ""
    
    ---
    
    apiVersion: v1
    kind: Secret
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard-key-holder
      namespace: kubernetes-dashboard
    type: Opaque
    
    ---
    
    kind: ConfigMap
    apiVersion: v1
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard-settings
      namespace: kubernetes-dashboard
    
    ---
    
    kind: Role
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard
      namespace: kubernetes-dashboard
    rules:
      # Allow Dashboard to get, update and delete Dashboard exclusive secrets.
      - apiGroups: [""]
        resources: ["secrets"]
        resourceNames: ["kubernetes-dashboard-key-holder", "kubernetes-dashboard-certs", "kubernetes-dashboard-csrf"]
        verbs: ["get", "update", "delete"]
        # Allow Dashboard to get and update 'kubernetes-dashboard-settings' config map.
      - apiGroups: [""]
        resources: ["configmaps"]
        resourceNames: ["kubernetes-dashboard-settings"]
        verbs: ["get", "update"]
        # Allow Dashboard to get metrics.
      - apiGroups: [""]
        resources: ["services"]
        resourceNames: ["heapster", "dashboard-metrics-scraper"]
        verbs: ["proxy"]
      - apiGroups: [""]
        resources: ["services/proxy"]
        resourceNames: ["heapster", "http:heapster:", "https:heapster:", "dashboard-metrics-scraper", "http:dashboard-metrics-scraper"]
        verbs: ["get"]
    
    ---
    
    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard
    rules:
      # Allow Metrics Scraper to get metrics from the Metrics server
      - apiGroups: ["metrics.k8s.io"]
        resources: ["pods", "nodes"]
        verbs: ["get", "list", "watch"]
    
    ---
    
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard
      namespace: kubernetes-dashboard
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: Role
      name: kubernetes-dashboard
    subjects:
      - kind: ServiceAccount
        name: kubernetes-dashboard
        namespace: kubernetes-dashboard
    
    ---
    
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: kubernetes-dashboard
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: kubernetes-dashboard
    subjects:
      - kind: ServiceAccount
        name: kubernetes-dashboard
        namespace: kubernetes-dashboard
    
    ---
    
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
      name: kubernetes-dashboard
      namespace: kubernetes-dashboard
    spec:
      replicas: 1
      revisionHistoryLimit: 10
      selector:
        matchLabels:
          k8s-app: kubernetes-dashboard
      template:
        metadata:
          labels:
            k8s-app: kubernetes-dashboard
        spec:
          containers:
            - name: kubernetes-dashboard
              image: kubernetesui/dashboard:v2.0.0-rc5
              imagePullPolicy: IfNotPresent
              ports:
                - containerPort: 8443
                  protocol: TCP
              args:
                - --auto-generate-certificates
                - --namespace=kubernetes-dashboard
                # Uncomment the following line to manually specify Kubernetes API server Host
                # If not specified, Dashboard will attempt to auto discover the API server and connect
                # to it. Uncomment only if the default does not work.
                # - --apiserver-host=http://my-address:port
              volumeMounts:
                - name: kubernetes-dashboard-certs
                  mountPath: /certs
                  # Create on-disk volume to store exec logs
                - mountPath: /tmp
                  name: tmp-volume
              livenessProbe:
                httpGet:
                  scheme: HTTPS
                  path: /
                  port: 8443
                initialDelaySeconds: 30
                timeoutSeconds: 30
              securityContext:
                allowPrivilegeEscalation: false
                readOnlyRootFilesystem: true
                runAsUser: 1001
                runAsGroup: 2001
          volumes:
            - name: kubernetes-dashboard-certs
              secret:
                secretName: kubernetes-dashboard-certs
            - name: tmp-volume
              emptyDir: {}
          serviceAccountName: kubernetes-dashboard
          nodeSelector:
            "beta.kubernetes.io/os": linux
          # Comment the following tolerations if Dashboard must not be deployed on master
          tolerations:
            - key: node-role.kubernetes.io/master
              effect: NoSchedule
    
    ---
    
    kind: Service
    apiVersion: v1
    metadata:
      labels:
        k8s-app: dashboard-metrics-scraper
      name: dashboard-metrics-scraper
      namespace: kubernetes-dashboard
    spec:
      ports:
        - port: 8000
          targetPort: 8000
      selector:
        k8s-app: dashboard-metrics-scraper
    
    ---
    
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      labels:
        k8s-app: dashboard-metrics-scraper
      name: dashboard-metrics-scraper
      namespace: kubernetes-dashboard
    spec:
      replicas: 1
      revisionHistoryLimit: 10
      selector:
        matchLabels:
          k8s-app: dashboard-metrics-scraper
      template:
        metadata:
          labels:
            k8s-app: dashboard-metrics-scraper
          annotations:
            seccomp.security.alpha.kubernetes.io/pod: 'runtime/default'
        spec:
          containers:
            - name: dashboard-metrics-scraper
              image: kubernetesui/metrics-scraper:v1.0.3
              imagePullPolicy: IfNotPresent
              ports:
                - containerPort: 8000
                  protocol: TCP
              livenessProbe:
                httpGet:
                  scheme: HTTP
                  path: /
                  port: 8000
                initialDelaySeconds: 30
                timeoutSeconds: 30
              volumeMounts:
              - mountPath: /tmp
                name: tmp-volume
              securityContext:
                allowPrivilegeEscalation: false
                readOnlyRootFilesystem: true
                runAsUser: 1001
                runAsGroup: 2001
          serviceAccountName: kubernetes-dashboard
          nodeSelector:
            "beta.kubernetes.io/os": linux
          # Comment the following tolerations if Dashboard must not be deployed on master
          tolerations:
            - key: node-role.kubernetes.io/master
              effect: NoSchedule
          volumes:
            - name: tmp-volume
              emptyDir: {}
    
  2. 安装好后还无法访问,需要创建账号和绑定角色。

    kubectl apply -f admin-role.yaml
    

    admin-role.yaml

    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1beta1
    metadata:
      name: admin
      annotations:
        rbac.authorization.kubernetes.io/autoupdate: "true"
    roleRef:
      kind: ClusterRole
      name: cluster-admin
      apiGroup: rbac.authorization.k8s.io
    subjects:
    - kind: ServiceAccount
      name: admin
      namespace: kube-system
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: admin
      namespace: kube-system
      labels:
        kubernetes.io/cluster-service: "true"
        addonmanager.kubernetes.io/mode: Reconcile
    
  3. 启动。执行以下命令,然后访问:http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

    kubectl proxy
    

    这样可以直接访问,稍后会使用ingress配置来访问。

  4. 配置通过ingress启动。

    kubectl apply -f ingress.yaml
    

    ingress.yaml

    apiVersion: networking.k8s.io/v1beta1
    kind: Ingress
    metadata:
      name: kubernetes-dashboard
      namespace: kubernetes-dashboard
      annotations:
        kubernetes.io/ingress.class: "nginx"
        nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
    
    spec:
      tls:
      - hosts:
        # 访问的域名
        - dashboard.yangqiang.im
        # 证书名称
        secretName: yangqiang.im
      rules:
        - host: dashboard.yangqiang.im
          http:
            paths:
            - path: /
              backend:
                serviceName: kubernetes-dashboard
                servicePort: 443
    
    1. 创建证书查看教程:https://yangqiang.im/?p=745

    2. 在k8s集群中创建证书。

      kubectl create secret tls yangqiang.im --cert=/Users/carlton/Documents/Ssl/yangqiang.im/fullchain.cer --key=/Users/carlton/Documents/Ssl/yangqiang.im/yangqiang.im.key -n kubernetes-dashboard
      
    3. 因为你的域名没办法直接指向到你自己的机器。所以这里在hosts中新增:127.0.0.1 dashboard.yangqiang.im。然后通过地址:https://dashboard.yangqiang.im就能直接访问了。

    注意:nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"这个配置是对应到pod提供的服务是否是https还是http的。当前应用就是提供的443所以用HTTS,一般我们自己的应用都是80或者其他端口这里需要修改成nginx.ingress.kubernetes.io/backend-protocol: "HTTP"

weavescope安装(集群监控)

weavescope是一个k8s集群监控软件,贴个图:

image.png

这个安装很简单,就不贴细节了。官网安装参考:https://www.weave.works/docs/scope/latest/installing/#k8s

ingress配置:

kubectl apply -f ingress.yaml

ingress.yaml

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: weave-scope 
  namespace: weave
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
spec:
  rules:
    - host: weave-scope.yangqiang.im
      http:
        paths:
        - path: /
          backend:
            serviceName: weave-scope-app
            servicePort: app

可以通过:http://weave-scope.yangqiang.im来访问,同样hosts新增:127.0.0.1 weave-scope.yangqiang.im

mysql安装

因为mysql是走的tcp端口,ingress-nginx配置tcp不是标准协议,所以拿来示范如何配置tcp。

  1. 安装mysql。
   kubectl apply -f mysql.yaml

mysql.yaml

   kind: Deployment
   apiVersion: apps/v1beta2
   metadata:
     labels:
       app: mysql
     name: mysql
   spec:
     replicas: 1
     revisionHistoryLimit: 1
     selector:
       matchLabels:
         app: mysql
     template:
       metadata:
         labels:
           app: mysql
       spec:
         volumes:
         - name: pvc
           hostPath: 
             # 这里是mysql数据存储的地方,是宿主机上的地址
             path: /Users/carlton/Documents/K8s/data
         containers:
         - name: mysql
           image: mysql:5.7.13 
           volumeMounts:
           - name: pvc
             mountPath: "/var/lib/mysql"
             subPath: "mysql/data"
           - name: pvc
             mountPath: "/etc/mysql/conf.d"
             subPath: "mysql/conf.d"
           ports:
           - containerPort: 3306
             protocol: TCP
           env:
           - name: TZ
             value: 'Asia/Shanghai'
           - name: MYSQL_ROOT_PASSWORD
             value: "123456"
  1. 安装service。

    kubectl apply -f service.yaml
    

    service.yaml

    kind: Service
    apiVersion: v1
    metadata:
     name: mysql
     labels:
       app: mysql   
    spec:
     type: ClusterIP
     ports:
       - name: mysql-3306
         port: 3306
         targetPort: 3306
         protocol: TCP
     selector:
       app: mysql
    
  2. 配置ingress。tcp或者udp服务不需要安装ingress,因为不是标准的协议。如果要通过ingress访问,则需要在ingress-controller应用里面配置。

    1. 配置服务转发。找到ingress部署文件ingress-nginx.yaml,然后更新:
      kubectl apply -f ingress-nginx.yaml
    

    ingress-nginx.yaml

    ......
    kind: ConfigMap
    apiVersion: v1
     metadata:
        name: tcp-services
        namespace: ingress-nginx
        labels:
        app.kubernetes.io/name: ingress-nginx
     app.kubernetes.io/part-of: ingress-nginx
    data:
       30100: "default/mysql:3306"
    ......
    

    注意:30100: "default/mysql:3306"这个的意思是,default命名空间下面的mysql应用提供的服务端口是3306。然后ingress通过30100端口往外暴露。

    1. 配置ingress的服务暴露给外网,修改ingress的service.yaml文件

      kubectl apply -f service.yaml
      

      service.yaml

      kind: Service
      apiVersion: v1
      metadata:
        name: ingress-nginx
       namespace: ingress-nginx
        labels:
          app.kubernetes.io/name: ingress-nginx
          app.kubernetes.io/part-of: ingress-nginx
      spec:
        externalTrafficPolicy: Local
        type: NodePort
        selector:
          app.kubernetes.io/name: ingress-nginx
          app.kubernetes.io/part-of: ingress-nginx
        ports:
          - name: http
            port: 80
            protocol: TCP
            targetPort: http
            nodePort: 30000
          - name: https
            port: 443
            protocol: TCP
            targetPort: https
            nodePort: 30001
          # 在ingress-controller的config-map中配置了30100 -> 3306。这里是暴露30100
          # 整个过程:mysql容器c暴露3306,然后mysql服务s也是暴露的3306,ingress配置"30100": "dev/mysql:3306(服务暴露的端口)",ingerss服务拿着30100暴露30100给宿主。
          # c3306->s3306->ingress_30100->外网宿主机30100
          - name: mysql-3306
            # 随便一个只要不冲突就可以,这个是ingress-controller的服务向集群暴露端口
            port: 30100
            protocol: TCP
            # 这个必须是config-map中配置的对应端口
         targetPort: 30100
            # 这个是主机暴露给外网的端口
      nodePort: 30100
      

    到此为止,我们可以通过127.0.0.1:30100来直接连接到mysql数据库。这是pod的tcp端口暴露的方式。

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

推荐阅读更多精彩内容