20171118 Ansible

  • ansible介绍
  • ansible常用模块使用
  • playbook
  • templates,模板
  • 条件测试和循环迭代
  • roles,角色

一、ansible介绍

  • ansible:自动化运维工具,实现了批量系统配置、批量程序部署、批量运行命令

  • 特性:
    模块化:调用特定的模块,完成特定任务
    基于Python语言实现:包含Paramiko, PyYAML和Jinja2三个关键模块
    部署简单:agentless,不需在被配置主机安装客户端
    支持自定义模块
    支持playbook
    幂等性:任意多次执行所产生的影响均与一次执行的影响相同

  • 文件结构:
    配置文件:/etc/ansible/ansible.cfg
    主机清单:/etc/ansible/hosts

    • 主机清单配置文件格式:
    [webservers]      // 主机组名,包含下面的主机
    alpha.example.org
    beta.example.org
    192.168.1.100
    192.168.1.110
    
  • 主程序:
    /usr/bin/ansible
    /usr/bin/ansible-playbook
    /usr/bin/ansible-doc

二、ansible常用模块使用

(一)ansible使用语法:

// 调用模块执行操作
ansible  HOST-PATTERN  -m MOD_NAME  -a  MOD_ARGS -f FORKS -C -u USERNAME -c CONNECTION
// 列出所有可以调用的模块
ansible-doc  -l
// 列出指定模块的使用方法
ansible-doc -s MOD_NAME
  • 测试1:ping模块,测试管理的主机的网络连接状态,连接成功返回pong
    ansible all -m ping

(二)command模块:在远程主机运行命令

  • chdir=/PATH/TO/DIR:
    执行命令前切换工作目录至指定的位置
  • creates=/PATH/TO/SOMEFILE_OR_DIR:
    如果此处给定的文件或目录存在,则不执行命令
  • removes=/PATH/TO/SOMEFILE_OR_DIR:
    如果此处给定的文件或目录不存在,则不执行命令;即此处给定的文件或目录存在时方执行命令
  • 测试2:查询管理主机指定网卡状态
    ansible all -m command -a 'ip a show ens33'
  • 测试3:chdir的作用为在执行命令前切换至相应目录
    ansible 192.168.136.230 -m command -a 'pwd chdir=/tmp'
    ansible 192.168.136.230 -m command -a 'pwd'
  • 省略-m MOD_NAME时,则代表command模块

(三)shell模块:在远程主机的shell进程下运行命令,支持shell特性,如管道等

  • chdir=:
    执行命令前切换工作目录至指定的位置
  • creates=/PATH/TO/SOMEFILE_OR_DIR:
    如果此处给定的文件或目录存在,则不执行命令
  • removes=/PATH/TO/SOMEFILE_OR_DIR:
    如果此处给定的文件或目录不存在,则不执行命令;即此处给定的文件或目录存在时方执行命令
  • executable=/PATH/TO/SHELL:指定运行命令使用的shell解释器
  • 测试4:管理远程主机——修改账号密码
    ansible websrvs -m shell -a 'echo centos | passwd --stdin user1 '

(四)group模块:管理组账号

  • name=:必须参数,组账号名
  • state=present | absent:建立/删除组账号
  • system=yes | no:是否为系统账号
  • gid=:组ID
  • 测试5:管理远程主机——建立组
    ansible websrvs -m group -a 'name=haproxy system=yes state=present'

(五)user模块:管理用户账号

  • name=:必须参数,用户账号名
  • system=yes | no:是否为系统账号
  • uid=:用户ID
  • shell=:指定shell类型
  • group=:指定主组
  • groups=:指定附加组
  • comment=:指定说明
  • home=:指定家目录
  • generate_ssh_key=:是否生成ssh密钥
  • 测试6:管理远程主机——建立用户
    ansible websrvs -m user -a 'name=john groups=haproxy state=present shell=/bin/tcsh generate_ssh_key=true'

(六)copy模块: Copies files to remote locations

  • 用法1:src= dest=:
    将本地的文件复制到管理的主机组中
  • 用法2:content= dest=:
    在管理的主机组中中创建文件,由content关键词指定内容
  • owner=:指定所有者
  • group=:指定所属组
  • mode=:指定权限
  • 测试7:复制文件至远程主机指定目录
    ansible all -m copy -a 'src=test.txt dest=/tmp/ owner=nobody group=daemon mode=664'
  • 测试8:将内容装入文件复制至远程主机指定目录
    ansible all -m copy -a "content='hello everyone\n' dest=/tmp/test2.txt owner=nobody group=daemon mode=664"

(七)fetch模块:Fetches a file from remote nodes

从远程节点获取文件

(八)file模块: Sets attributes of files

  • 用法:
    创建链接文件:path= src= state=link
    修改属性:path= owner= mode= group=
    创建目录:path= state=directory

  • 注意:state属性的可用值
    file:仅在修改已经存在文件的属性时使用
    directory:目录
    link:软链接
    hard:硬链接
    touch:创建空白文件
    absent:删除

  • 测试9:在远程主机建立目录
    ansible all -m file -a "path=/tmp/hidir state=directory mode=770"

  • 测试10:在远程主机建立空文件
    ansible all -m file -a "path=/tmp/hifile state=touch mode=660"

  • 测试11:在远程主机建立软链接
    ansible all -m file -a "path=/tmp/mytext.txt src=/tmp/test2.txt state=link"

  • 测试12:在远程主机删除软链接
    ansible all -m file -a "path=/tmp/mytext.txt state=absent"

(九)get_url模块: Downloads files from HTTP, HTTPS, or FTP to node

  • url=:必须参数
  • dest=:必须参数
  • sha256sum=:校验完整性
  • owner, group, mode:所有者,所属组,权限
  • 测试13:在远程主机上从网络链接下载文件
    ansible all -m get_url -a "url=http://172.16.0.1/centos/7/Packages/ImageMagick-6.7.8.9-15.el7_2.x86_64.rpm dest=/tmp/"

(十)cron 模块:Manage cron.d and crontab entries.

  • minute=:时间
  • day=:天
  • month=:月
  • weekday=:工作日
  • hour=:消失
  • job=:执行的程序
  • name=:名称,必须参数
  • state=present|absent:创建或删除
  • 测试14:设置远程主机每5分钟向主机172.18.0.1同步时间
    ansible all -m cron -a "name='timesync' job='/usr/sbin/ntpdate 172.18.0.1 &> /dev/null' minute='*/5' "

(十一)hostname模块:Manage hostname

  • name=:主机名

(十二)包管理相关模块

(1)yum模块:Manages packages with the 'yum' package manager
  • name=:程序包名称,可以带版本号
  • state=
    present, latest, installed:安装
    absent, removed:卸载
  • 测试15:远程安装软件包
    ansible websrvs -m yum -a "name=nginx state=latest"
  • 查看远程主机指定软件包的安装状态
    ansible websrvs -m yum -a "list=nginx"
(2)pip模块:Manages Python library dependencies
  • name=
  • state=
  • version=
(3)npm模块:Manage node.js packages with npm
  • name=
  • state=
  • version=

(十三)service模块:管理服务

  • name=:服务名称,必备参数
  • state=started | stopped | restarted:服务启动,关闭,重启
  • enabled=:是否开机启动
  • runlevel=:运行级别,适用于init脚本启动
  • 测试16:远程启动指定服务,并设置为开机启动服务
    ansible websrvs -m service -a "name=nginx enabled=true state=started"

(十四)其他模块

(1)git模块:Deploy software (or files) from git checkouts
  • repo=:git源
  • dest=:存储的本地路径
  • version=
(2)deploy_helper模块:Manages some of the steps common in deploying projects
(3)haproxy模块:Enable, disable, and set weights for HAProxy backend servers using socket commands.
  • backend=:后端主机
  • host=:必备参数,后端主机名称
  • state=:必备参数,后端主机状态
  • weight=:后端主机权重

三、playbook

  • playbook:一种简单的配置管理系统与多机器部署系统的基础,可以编排有序的执行过程,甚至于做到在多组机器间,来回有序的执行特别指定的步骤,并且可以同步或异步的发起任务

(一)YAML介绍:

  • playbook采用YAML格式
  • YAML:Yet Another Markup Language,一个可读性高,用来表达数据序列的格式
  • YAML能表达的基本数据结构:标量、数组、关联数组

(二)playbook的运用:

(1)playbook的核心元素:
  • hosts:主机
  • tasks:任务列表
  • variables:变量
  • templates:包含了模板语法的文本文件
  • handlers:由特定条件触发的任务
(2)playbook的基础组件:
  • hosts:运行指定任务的目标主机

  • remoute_user:在远程主机上执行任务的用户,权限提升时可能要用到sudo_user

  • tasks:任务列表

    • 格式:
      (1) action: module arguments
      (2) module: arguments
      注意:shell和command模块后面直接跟命令,而非key=value类的参数列表;
    • 某任务的状态在运行后为changed时,可通过notify通知给相应的handlers;
    • 任务可以通过tags打标签,而后可在ansible-playbook命令上使用-t指定进行调用;
  • handlers:处理器,在特定条件下触发任务
    接收到其它任务的通知时被触发
    notify: HANDLER TASK NAME

(三)变量:variables

(1)直接调用
  • 注意:可使用setup模块直接获取目标主机的facters
(2)用户自定义变量:
  • 方法一:ansible-playbook命令行中指定
    -e VARS, --extra-vars=VARS

  • 方法二:在playbook中定义变量
    - var1: value1
    变量引用:{{ variable }},注意变量名前后均有空格

  • 方法三:通过roles传递变量

  • 方法四:Host Inventory,主机清单

    • 用户自定义变量

      • 向不同的主机传递不同的变量
        IP/HOSTNAME varaiable=value var2=value2
      • 向组中的主机传递相同的变量
        [groupname:vars]
        variable=value
      • 注意:优先级低于playbook中的定义
    • invertory参数
      用于定义ansible远程连接目标主机时使用的参数,而非传递给playbook的变量
      IP/HOSTNAME [ ansible_ssh_host | ansible_ssh_port | ansible_ssh_user | ansible_ssh_pass | ansbile_sudo_pass],危险不建议使用

(3)测试playbook:
  • ansible-playbook --check|-C
    只检测可能会发生的改变,但不真正执行操作
  • ansible-playbook --list-hosts
    列出运行任务的主机
  • ansible-playbook --list-tasks
    列出要运行的任务列表
  • ansible-playbook --syntax-check
    语法检查

(四)实验1:playbook实现基本的远程配置

  • 实验环境:
    web服务器:192.168.136.230, 192.168.136.130
    数据库服务器:192.168.136.131

  • 实现目标:
    web服务器:自动安装并启动nginx服务
    数据库服务器:自动安装redis,统一配置文件内容,最后启动redis服务

(1)实现过程:
  • 准备步骤:在ansible主机上使用ssh-keygen -t rsa生成公钥和私钥,使用ssh-copy-id ip_address将公钥复制到需要管理的每台主机上

  • 步骤1:配置/etc/ansible/hosts文件,将服务器归类称为不同的组,方便管理

[websrvs]     // web服务器组,命名为websrvs
192.168.136.230
192.168.136.130

[dbsrvs]     // web服务器组,命名为dbsrvs
192.168.136.131
  • 步骤2:配置playbook文件
- hosts: websrvs
  remote_user: root
  tasks:
  - name: install nginx package     // 安装nginx服务
    yum: name=nginx state=latest
  - name: start nginx service       // 启动nginx服务
    service: name=nginx enabled=true state=started

- hosts: dbsrvs
  remote_user: root
  tasks:
  - name: install redis package     // 安装redis服务
    yum: name=redis state=latest
  - name: install conf file         // 复制redis配置文件至远程主机
    copy: src=/root/redis.conf dest=/etc/redis.conf owner=redis group=root mode=644     
    // 提前准备好redis配置文件
  - name: start redis service       // 启动redis服务
    service: name=redis state=started
  • 步骤3:测试并运行
ansible-playbook --list-hosts nginx.yaml    // 列出playbook中的主机
ansible-playbook --list-tasks nginx.yaml    // 列出playbook中的任务
ansible-playbook --syntax-check nginx.yaml  // 检查YAML文件的语法
ansible-playbook -C nginx.yaml              // 预测执行playbook可能发生的变化,但实际不执行
ansible-playbook nginx.yaml                 // 执行playbook
ansible websrvs -m setup                    // 查看执行playbook时收集的信息

列出playbook中的主机

列出playbook中的任务

预测执行playbook可能发生的变化

(2)缺陷分析:
  • 问题1:当配置文件修改后,执行playbook会重复执行所有任务,而大部分任务没有修改
  • 问题2:虽然配置得到了更新,但是服务已经开启,故服务不会重启载入配置
  • 解决:问题1可以通过定义标签,指定只执行标签处的任务;问题2可以通过定义重新启动服务的任务,并声明只在配置更改时执行:
  • 实现步骤1:修改playbook文件
vim nginx.yaml
- hosts: websrvs
  remote_user: root
  tasks:
  - name: install nginx package
    yum: name=nginx state=latest
  - name: start nginx service
    service: name=nginx enabled=true state=started

- hosts: dbsrvs
  remote_user: root
  tasks:
  - name: install redis package
    yum: name=redis state=latest
  - name: install conf file
    copy: src=/root/redis.conf dest=/etc/redis.conf owner=redis group=root mode=644
    tags: instconf                   // 对install conf file任务打标签
    notify: restart redis service    // 当配置文件变更时,才执行名为restart redis service的handler
  - name: start redis service
    service: name=redis state=started
  handlers:                          // 定义handler,由notify语句在某些情况下被触发
  - name: restart redis service
    service: name=redis state=restarted
  • 实现步骤2:修改/root/redis.conf文件,执行playbook并指定只执行标签instconf处的任务
    ansible-playbook -t instconf nginx.yaml

可以看到执行结果中只执行了tag处的任务,并且触发执行名为restart redis service 的handler

  • 实现步骤3:再次执行上一条命令

可以看到只执行了标签instconf的任务,但由于配置没有改变,故没有触发执行名为restart redis service 的handler

(五)实验2:playbook中定义变量

  • 编辑instalpkg.yml文件,实现:当不指定变量pkgname时,自动安装tree;当指定pkgname的值时,安装指定的软件包
- hosts: websrvs
  remote_user: root
  vars:
  - pkgname: tree
  tasks:
  - name: install package
    yum: name={{ pkgname }} state=latest
  • 可以看到被管理的主机成功安装;变量的引用也可以应用到name中,这样可以看到当前被安装的软件名称,对instalpkg.yml文件做修改后如下:
- hosts: websrvs
  remote_user: root
  vars:
  - pkgname: tree
  tasks:
  - name: install package {{ pkgname }}     // 做修改处
    yum: name={{ pkgname }} state=latest
  • 此时可以看到默认安装tree
  • 指定变量pkgname的值
    ansible-playbook -e "pkgname=memcached" -C instalpkg.yml
    此时可以看到安装软件为memcached

四、templates,模板

(一)template介绍:

  • 定义:基于模板方式生成一个文件复制到远程主机

  • 实质:文本文件,嵌套有脚本(使用模板编程语言编写),类比php嵌入html文件的关系

(二)template配置:

  • template模块的使用:类似copy
    src, dest, owner, group, mode

  • 脚本语言:Jinja2,语法如下

    • 字符串:使用单引号或双引号;
    • 数字:整数,浮点数;
    • 列表:[item1, item2, ...]
    • 元组:(item1, item2, ...)
    • 字典:{key1:value1, key2:value2, ...}
    • 布尔型:true/false
    • 算术运算:+, -, *, /, //, %, **
    • 比较操作:==, !=, >, >=, <, <=
    • 逻辑运算:and, or, not

(三)实验3:实现自动安装redis,之后修改配置文件/etc/redis.conf的maxmemory值为主机总内存大小的一半,并且自动重启服务使配置生效

  • 步骤1:编辑/root/redis_install_and_conf.yml文件,实现实验要求
vim /root/redis_install_and_conf.yml
- hosts: dbsrvs
  remote_user: root
  tasks:
// 安装
  - name: install redis
    yum: name=redis state=latest
// 复制配置文件模板
  - name: install redis conf
    template: src=/root/redis.conf.j2 dest=/etc/redis.conf owner=redis group=root mode=644
    notify: restart redis service
    tags: install_conf
// 启动服务
  - name: start redis service
    service: name=redis state=started
// 条件触发任务
  handlers:
  - name: restart redis service
    service: name=redis state=restarted
  • 步骤2:准备配置文件
cp /etc/redis.conf /root/redis.conf.j2
vim /root/redis.conf.j2
maxmemory {{ ansible_memtotal_mb /2  }}mb     // 添加此行,自动根据安装主机的内存容量设置
  • 步骤3:测试并执行playbook
ansible-playbook -C /root/redis_install_and_conf.yml 
ansible-playbook /root/redis_install_and_conf.yml 

可以看到install redis conf任务成功执行

查看被安装的主机上的/etc/redis.conf文件,maxmemory值根据主机内存情况动态设置

五、条件测试和循环迭代:

(一)条件测试:when

  • 适用于根据被管理主机参数的不同采取不同的操作

  • when语句:在task中使用,jinja2的语法格式

tasks: 
- name: install conf file to centos7
  template: src=/etc/nginx.conf.c7.j2 dest=/etc/nginx.conf owner=root group=root
  when: ansible_distribution_major_version == "7"
- name: install conf file to centos6
  template: src=/etc/nginx.conf.c6.j2 dest=/etc/nginx.conf owner=root group=root
  when: ansible_distribution_major_version == "6"

(二)循环:迭代,需要重复执行的任务

  • 对迭代项的引用,固定变量名为"item"

  • 而后,要在task中使用with_items给定要迭代的元素列表

  • 列表方法:字符串,字典

// 字符串列表方法                                  
- name: install some packages
  yum: name={{ item }} state=present
  with_items:
  - nginx
  - memcached
  - php-fpm
              
- name: add some groups
  group: name={{ item }} state=present
  with_items:
  - group11
  - group12
  - group13

// 字典列表方法
- name: add some users
  user: name={{ item.name }} group={{ item.group }} state=present
  with_items:
  - { name: 'user11', group: 'group11' }
  - { name: 'user12', group: 'group12' }
  - { name: 'user13', group: 'group13' }

六、roles,角色

(一)角色的目录结构

  • 角色集合:/etc/ansible/roles/目录下每个角色一个目录

  • 每个角色以特定的层级目录结构进行组织:

    • files/ :存放由copy或script模块等调用的文件
    • templates/:template模块查找所需要模板文件的目录
    • tasks/:至少应该包含一个名为main.yml的文件;其它的文件需要在此文件中通过include进行包含
    • handlers/:至少应该包含一个名为main.yml的文件;其它的文件需要在此文件中通过include进行包含
    • vars/:至少应该包含一个名为main.yml的文件;其它的文件需要在此文件中通过include进行包含
    • meta/:至少应该包含一个名为main.yml的文件,定义当前角色的特殊设定及其依赖关系;其它的文件需要在此文件中通过include进行包含
    • default/:设定默认变量时使用此目录中的main.yml文件

(二)playbook中角色的调用

  • 调用角色的语法:
- hosts: websrvs
  remote_user: root
  roles:
  - mysql
  - nginx
  • 调用角色同时传递变量给角色
- hosts: 
  remote_user: root
  roles:
  - { role: nginx, username: nginx }
  • 基于条件测试实现角色调用
- hosts: 
  remote_user: root
  roles:
  - { role: nginx, when: "ansible_distribution_major_version == '7' " }

(三)实验4:

  • 步骤1:建立目录结构
mkdir /etc/ansible/roles/nginx/{tasks,handlers,vars,files,templates} -pv
cd /etc/ansible/roles
  • 步骤2:编辑task目录,定义需要执行的任务
vim nginx/tasks/main.yml
- name: install nginx package          // 安装nginx
  yum: name=nginx state=latest
- name: install conf file              // 复制配置模板文件
  template: src=web.conf.j2 dest=/etc/nginx/conf.d/web.conf
  notify: reload nginx service         // 触发服务重载
  tags: instconf
- name: create doc root                // 建立网页文件根目录
  file: path={{ ngx_doc_root }} state=directory
  tags: instconf
- name: start nginx service            // 启动nginx服务
  service: name=nginx enabled=true state=started
  • 步骤3:编辑模板配置文件
vim nginx/templates/web.conf.j2
server {
        listen {{ ngx_server_port }};
        server_name {{ ngx_server_name }};
        location / {
                root {{ ngx_doc_root }};
        }
}
  • 步骤4:编辑变量配置文件
vim nginx/vars/main.yml
ngx_server_port: 80
ngx_server_name: www.hellopeiyang.com
ngx_doc_root: /app/webdata
  • 步骤5: 配置handlers配置文件
vim nginx/handlers/main.yml
- name: reload nginx service
  service: name=nginx state=reloaded
  • 步骤6: 编辑playbook
vim /root/mywebsrvs.yml
- hosts: websrvs
  remote_user: root
  roles:
  - nginx
  • 步骤7: 测试并执行playbook
ansible-playbook -C /root/mywebsrvs.yml
ansible-playbook /root/mywebsrvs.yml
  • 测试过程1:成功执行playbook
  • 执行后可以看到配置文件中的信息按照定义的变量值配置
  • 在运行playbook时指定变量值,优先级高于配置文件,同时指定只执行标签instconf处的任务
    ansible-playbook -e "ngx_server_port=1008" -t instconf /root/mywebsrvs.yml
  • 执行后可以看到配置文件中相应的变化
  • 也可以在playbook中直接传递变量给角色
vim /root/mywebsrvs.yml
- hosts: websrvs
  remote_user: root
  roles:
  - { role: nginx, ngx_server_port: 8090 }   // 指定传入的角色,变量名:变量值
ansible-playbook -t instconf /root/mywebsrvs.yml
  • 同样,执行后变量定义处的配置信息也发生了相应的变化
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容