zoukankan      html  css  js  c++  java
  • ansible详解

    Ansible默认通过 SSH 协议管理机器.

    安装Ansible之后不需要启动或运行一个后台进程,或是添加一个数据库.只要在一台电脑(可以是一台笔记本)上安装好,就可以通过这台电脑管理一组远程的机器.在远程被管理的机器上,不需要安装运行任何软件,因此升级Ansible版本不会有太多问题.
    目前,只要机器上安装了 Python 2.6 或 Python 2.7 (windows系统不可以做控制主机),都可以运行Ansible.

    对托管节点的要求
    通常我们使用 ssh 与托管节点通信,默认使用 sftp.如果 sftp 不可用,可在 ansible.cfg 配置文件中配置成 scp 的方式. 在托管节点上也需要安装 Python 2.4 或以上的版本.如果版本低于 Python 2.5 ,还需要额外安装一个模块:
    python-simplejson


    该文档中都是用的centos6.8
    内核:
    # cat /etc/centos-release
      CentOS release 6.8 (Final)
    # uname -a
      Linux master 2.6.32-642.el6.x86_64 #1 SMP Tue May 10 17:27:01 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux


    IP:
    管理主机端:192.168.0.111/24
    被管理端:192.168.0.223/24

    主机名:
    管理端:master
    被管理端:client


    该文档中的操作所有操作都是用的 root 用户

    安装管理主机:
    从yum安装的步骤:
    RHEL/CentOS 6:
    yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm
    RHEL/CentOS 7:
    # yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

    # yum install ansible
    # yum -y install python-pip


    管理主机端设置免密码登录远程主机:
    # ssh-keygen -t rsa            (一路回车)
    默认在用户的家目录(/root/.ssh/)生成两个文件:
    id_rsa:       # 私钥
    id_rsa.pub:     # 公钥
    把公钥导入到认证文件:
    # cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys
    验证:(可跳过)
    # ssh localhost
    # ssh 192.168.0.223
    在本机操作,发到目标主机:(前提是目标主机上得有.ssh这个目录,至少centos7上得先在客户端执行ssh-keygen -t rsa后生成公私钥)
    # scp /root/.ssh/id_rsa.pub root@192.168.0.223:/root/.ssh/      (需要对端的root密码)

    登录到目标主机把公钥导入到认证文件
    (使用要被免密码登录的用户名登录到目标主机)
    # cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys
    更改文件的权限:(仅被管理端)
    # chmod 700 /root/.ssh/
    # chmod 600 /root/.ssh/authorized_keys


    加入远程系统地址:
    # vim /etc/ansible/hosts   (追加形式)
    192.168.0.111      # 管理地址
    192.168.0.223      # 被管理地址,有多少被管理地址就写多少,只写地址即可
    ********************************************************************
    定义组:方括号[ ]中是组名,用于对系统进行分类,便于对不同系统进行个别的管理.
    [webservers]
    foo.example.com
    bar.example.com

    [dbservers]
    one.example.com
    two.example.com
    three.example.com
    # 按需定义组,该实验中没定义,如果有主机的SSH端口不是标准的22端口,可在主机名之后加上端口号,用冒号分隔
    # 如 one.example.com:1082

    [webservers]
    www[01:50].example.com                     // 表示 1 至 50
    [databases]
    db-[a:f].example.com
    [mysql_test]
    192.168.0.55
    192.168.0.66
    192.168.0.77
    # 组成员可以使用通配符来匹配,如192.168.0.[55:77]表示匹配192.168.0.55--192.168.0.77的主机
    # 有关组的用法参见 http://www.ansible.com.cn/docs/intro_inventory.html
    ********************************************************************

    ansible配置:
    #vim /etc/ansible/ansible.cfg
    host_key_checking = False         # 禁用每次执行ansbile命令检查ssh key host
    log_path = /var/log/ansible.log         # 开启日志记录
    [accelerate]               # ansible连接加速配置
    accelerate_port = 10000
    accelerate_multi_key = yes
    测试:
    [root@master ~]# ansible all -m ping
    192.168.0.111 | SUCCESS => {
    "changed": false,
    "ping": "pong"
    }
    192.168.0.223 | SUCCESS => {
    "changed": false,
    "ping": "pong"
    }

    Ansible文档使用:
    ansible-doc -l       # 列出所有模块
    ansible-doc cron      # 查看指定模块文档

    ansible-doc yum

    ansible-doc service

    // ansible-doc -s 模块名        # 查看具体模块对应的用法

    ansible 192.168.0.223 -m ping {all}      # ping命令模块
    command命令模块:

    例:

    ansible opop -m command -a 'w'       # 批量执行命令(opop是组名)-m 后面跟模块名;-a 跟命令,command也是一个模块
    ansible opop -m shell -a 'w'
    ansible opop -m shell -a 'cat /etc/passwd'
    ansible 192.168.0.223 -m command -a 'w'      # 单独执行命令
    例:ansible 192.168.0.223 -m shell -a 'w'
    ansible 192.168.0.223 -m shell -a 'cat /etc/passwd'
    说明:shell包含command,并且支持管道和远程执行脚本,可能用到的扩展包:yum install -y libselinux-python

    copy命令模块-拷贝目录和文件:
    说明:可同时指定用户属主和权限,源目录拷贝到目标目录下面去,如果目标目录不存在,则会自动创建
    # ansible opop -m copy -a "src=~/passwd dest=/etc/mnt/ owner=root group=root mode=0644"
    // 将~/下的passwd文件发送到opop组中主机的/etc/mnt/ 拥有人是root组也是root 权限是0644

    # ansible 192.168.0.223 -m copy -a "src=~/passwd dest=/etc/mnt/ owner=root group=root mode=0644"
    // 将~/下的passwd文件发送到192.168.0.223这台主机的/etc/mnt/ 拥有人是root组也是root 权限是0644
    注:可以用backup=yes  {no}  来定义是否开启备份功能

    给定内容生成文件:
    # ansible 192.168.0.223 -m copy -a "content='hello world' dest=/test.sh mode=0664 backup=yes"
    // 在/下创建一个test.sh的文件,内容为 hello world,并开启了备份功能,第一次执行完该命令后 "changed": 是 true
    再执行一遍上一条命令:
    # ansible 192.168.0.223 -m copy -a "content='hello world' dest=/test.sh mode=0664 backup=yes"
    // 此时 "changed": 为false
    改变一下文件中的内容后:
    # ansible 192.168.0.223 -m copy -a "content='hello world-opop' dest=/test.sh mode=0664 backup=yes"
    在被监控端的 / 下会多一个 test.sh.4708.2018-03-15@12:22:25~ 的备份文件切test.sh和test.sh.4708.2018-03-15@12:22:25~ 文件中的内容是不一样的


    file模块:
    创建文件属性
    创建目录:
    # ansible 192.168.0.223 -m file -a "path=/opop state=directory"
    # ansible 192.168.0.223 -m shell -a "rm -rf /kkkk.txt" 或者 ansible 192.168.0.223 -m file -a "path=/opop state=absent"
    创建软连接:(src必须是提前创建好的文件)
    # ansible 192.168.0.223 -m file -a "src=/kkkk.txt dest=/mnt/opop.txt state=link"
    注:
    force 需要在两种情况下强制创建软连接,
        一种是源文件不存在,但之后会建立的情况
        一种是目标软连接已存在,需要先取消的软连接,然后创建新的软连接,force有两个选项:{yes | no}
    group:定义文件/目录的属组 mode:定义文件/目录的权限
    owner:定义文件/目录的属主 path:定义文件/目录的路径
    recurse递归设置文件属性,只对目录有效
    src被链接的源文件路径,只应用于state=link的情况
    dest:目标路径
    file即使文件不存在也不会被创建
    touch如果文件不存在则会创建一个新文件,如果文件或目录已存在。则更新其最后修改时间

    mode:   定义文件/目录的权限

    path:   必选项,定义文件/目录的路径

    state:   定义文件/目录的参数,常用参数如下:

        directory:如果目录不存在,就创建目录
        file:即使文件不存在,也不会被创建
        link:创建软链接
        hard:创建硬链接
        touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其最后修改时间
        absent:删除目录、文件或者取消链接文件

    例:

    #  ansible opop -m file -a "state=touch owner=root group=root mode=755 path=/tmp/test-01"                   // 创建文件

    #  ansible opop -m command -a "ls -al /tmp/test-01"                             // 查看具体属性


    fetch模块:
    从远程某主机获取文件到本地:
    此时的src是远端主机上的文件地址,而dest是本地的地址。需注意!!!
    # ansible 192.168.0.223 -m fetch -a "src=/kkkk.txt dest=/mnt/"
    // 下载到本地后会根据主机名分别创建文件夹,是分开保存的。需注意


    cron命令模块-添加和删除计任务:
    说明:name:定时任务描述,job:知名运行的命令是什么
    cron_file=       # 如果指定,使用这个文件cron.d而不是单个用户
    special_time        # 特殊的时间范围,参数:reboot、annually(每年)、monthly(每月)、weekly(每周)
    day              # 日应该运行的工作(1-31、*、*/2)
    hour            # 小时(0-23、*、*/2)
    minute        # 分钟(0-59、*、*/2)
    weekday               # 周(0-6)
    # ansible all -m cron -a 'name="ban IP of login" minute=* hour=*/2 day=* month=* weekday=* job="sh /111.sh"'
    # cat /var/spool/cron/root             // 查看上面的定时任务
    # ansible 192.168.0.223 -m cron -a "name='ntp update' minute=*/5 job='/sbin/ntpdate 192.168.0.111 &>/dev/null'"
                                                                 起个名字           每5分钟执行一次         执行任务是更新时间
    注:如果只需要有分钟时就只写上minute,如果有分钟有小时就写上
    # ansible 192.168.0.223 -m command -a "crontab -l"               // // 查看上一条定义的定时任务
    # ansible 192.168.0.223 -m cron -a "name='ntp update' minute=*/5 job='/sbin/ntpdate 192.168.0.111 &>/dev/null' state=absent"
    // 删除上面定义定时任务

    service命令模块-管理rpm服务:(先安装上才能用该模块)
    说明:
    arguments      # 命令行提供额外的参数
    enabled          # 设置开机启动 { true | false }
    name                         # 服务名称
    runlevel                   # 开机启动的级别,一般不指定
    sleep                        # 在重启服务过程中是否等待,如在服务关闭以后等待2秒后再重启
    state                         # started启动、stopped停止、reloaded重新载入、restarted重启;
    # ansible 192.168.0.223 -m service -a "name=firewalld state=stopped enabled=no"      # centos 7的命令
    # ansible 192.168.0.223 -m yum -a "name=httpd state=present"                # 安装
    # ansible 192.168.0.223 -m service -a "name=httpd state=started enabled=true"                    # 启动
    # ansible 192.168.0.223 -m shell -a "netstat -anpt |grep httpd"                                                # 查看

    yum命令模块:
    conf_file                                   #设定远程yum安装时所依赖的配置文件,例如配置文件没有在默认的位置
    disable_gpg_check                # 是否进制GPG checking,只用于present 或者 latest
    disablerepo                             # 临时进制yum库,只用于安装或更新时。
    enablerepo                              # 临时使用的yum库,只用于安装或更新时
    name                                        # 所安装的包的名称
    state                                        # present安装,latest安装最新的,absent卸载软件
    update_cache                        #强制更新yum的缓存
    # ansible 192.168.0.223 -m yum -a "name=tree"                  # 注:双引号中没有单引号
    # ansible 192.168.0.223 -m yum -a "name=vsftpd state=present disable_gpg_check=yes"            # 安装
    # ansible 192.168.0.223 -m yum -a "name=vsftpd state=absent disable_gpg_check=yes"             # 卸载

    shell模块:
    在远程主机上调用shell解释器运行命令,支持shell的各种功能。
    例如:
    ansible 192.168.0.223 -m shell -a 'netstat -anpt | grep :22'
    ansible 192.168.0.223 -m shell -a 'pstree'

    user模块:
    该模块是用户模块:
    comment                      # 用户的描述信息
    createhome                 # 是否创建家目录
    force                            # 在使用state=absent时,行为和userdel -force同等效果
    group                           # 指定基本组
    groups                         # 指定附加组,如果指定为(groups=)表示删除所有组
    home                            # 指定用户家目录
    move_home                # 如果设置为home=时,试图将用户主目录移动到指定的目录
    name                            # 指定用户名
    password                    # 指定用户密码
    non_unique                # 该选项允许改变非唯一的用户ID值
    remove                       # 在使用state=absent时,行为和userdel -remove同等效果
    shell                            # 指定默认shell
    state                            # 设置账号状态,不指定为创建,指定值为sbsent表示删除
    system                        # 当创建一个用户,设置这个用户是系统用户,这个设置不能改现有用户
    uid                               # 指定用户的uid
    update_password     # 更新用户密码
    # ansible 192.168.0.223 -m user -a "name=opop password=123456 uid=10001 shell=/bin/bash"                  // 创建
    # ansible 192.168.0.223 -m shell -a "cat /etc/passwd |grep opop"                                  // 查看
    # ansible 192.168.0.223 -m user -a "name=opop password=123456 uid=10001 shell=/bin/bash state=absent"        // 删除

    group模块:
    用于用户组模块,添加或删除组
    gid                  # 设置组的GID号
    name             # 管理组的命令
    state              # 指定组状态,默认为创建,设置值为sbsent是删除
    system           # 设置值为yes,表示该组创建的是系统组

    script模块:
    用于在指定节点运行服务端的脚本
    用法:
    首先在控制端创建个脚本
    # vim script_test.sh
      #!/bin/bash
      mkdir /date
      date >> /date/2.txt
    # chmod 755 script_test.sh
    # ansible 192.168.0.223 -m script -a "script_test.sh"                        // 本地脚本在远端机器上执行
    # ansible 192.168.0.223 -m shell -a "cat /date/2.txt"                         // 查看被监控端上的结果

    setup模块:
    facts组件是ansible用于采集被管机器设备信息的一个功能,可以用setup模块查机器的所有facts信息,也可以使用filter来查看指定信息。
    整个facts信息被包装在一个JSON格式的数据结构中,ansible_facts是最上层的值
    facts就是内建变量,每个主机的各种信息,CPU颗数、内存大小等,都会在facts中的某个变量中。调用后返回很多对应主机的信息,在后面的操作中可以根据不通的信息来做不同的操作,redhat系列用yum安装,而debian系列用apt来安装软件

    setup模块主要用来获取远端主机信息,在playbook里经常会用到的一个参数gether_facts就与该模块相关、setup模块下经常使用的一个参数是filter
    # ansible 192.168.0.223 -m setup -a "filter=ansible_*_mb"     { | more}          // 查看被监管端主机内存信息

    # ansible 192.168.0.223 -m setup -a "filter=ansible_eth*"                // 查看所有网卡的信息,或者 { eth[0-2] }
    # ansible 192.168.0.223 -m setup --tree /tmp/1.txt                // 将0.223主机的信息输入到/tmp/1.txt目录中,每台主机的信息输入到主机名文件中(/etc/ansbile/hosts里的主机名)
    # ansible 192.168.0.223 -m setup -a "filter=*ipv4*"                      // 查看IPv4的信息
    # ansible 192.168.0.223 -m setup -a "filter=*vcpu*"                    // 查看CPU相关的信息,该变量叫 ansible_processor_vcpus


    一些简单的命令:
    # ansible all --list-host                               # 查看所有组
    # ansible web --list-host                            # 查看web组
    # ansible all -m ping                                  # ping所有主机。可以在后面加 -v -vv -vvv查看详细内容
    # ansible 192.168.0.223 -m command -a 'ls'            # 列出0.223这台机器家目录下的列表


    // 命令模块接受命令名称,后面是空格分隔的列表参数。给定的命令将在所有选定的节点上执行。它不会通过shell进行处理.
    // 比如$HOME和操作如”小于”<“,”>”, “|”, “;”,”&”‘ 工作(需要使用(shell)模块实现这些功能)
    # ansible 192.168.0.223 -m command -a 'chdir=/etc/ cat passwd'     // chdir是在执行命令之前先切换到等号后面的目录内,再执行后面的命令

    # ansible 192.168.0.223 -m command -a 'creates=/etc/passwd cat passwd'
      192.168.0.223 | SUCCESS | rc=0 >>
      skipped, since /etc/passwd exists
    // 当/etc/passwd文件存在时,不执行后面的命令。可以用来做判断

     以上方式称为 adhoc的方式来运行ansible,适用于单行命令的场景。


    Ansible playbook (剧本)

    我们使用 adhoc 时,主要是使用 /usr/bin/ansible 程序执行任务,而使用 playbook 时,更多是将之放入源码控制之中,用之推送你的配置或是用于确认你的远程系统的配置是否符合配置规范。

    playbooks也属于ansible核心的一个部分,用来定义一系列ansible要去执行的任务。
    play主要的功能就是将实现归并为一组的主机装扮成实现通过ansible的task定义好的角色,所谓task就是调用ansible的模块。

    而所谓的playbook就是将多个play统一去完成。简单来说,playbooks 是一种简单的配置管理系统与多机器部署系统的基础,非常适合于复杂应用的部署

    相当于将各命令模块内容写进配置文件中,然后集中执行,类似于shell脚本,不过playbook有自己的语法格式。
    例如:实际生产中,需批量管理很多机器,yum安装,管理配置文件、服务等.
    使用playbook你可以方便的重用这些代码,可以移植到不同的机器上面,像函数一样,最大化的利用代码。
    在使用Ansible的过程中会发现所处理的大部分操作都是编写playbook。可以把常见的应用都编写成playbook,之后管理服务器会变得十分简单。

    playbook语法格式

    于 Ansible,,每一个 YAML 文件都是从一个列表开始。列表中的每一项都是一个键值对, 通常它们被称为一个“哈希” 或 “字典”。所以需要知道如何在 YAML 中编写列表和字典

    所有的 YAML 文件(无论和 Ansible 有没有关系)的第一行应该以 ”---” (三个连字符)开始,表明YMAL文件的开始。

    列表中的所有成员(列表元素)都开始于相同的缩进级别,并且使用一个“- ”作为开头(一个横杠和一个空格):

    在同一行中,#之后的内容表示注释,类似于shell,python。
    例: - apple - banana - orange等价于JSON的这种格式
            [ “apple”, “banana”, “orange” ]
    同一个列表中的元素应该保持相同的缩进。否则会被当做错误处理。

    play中hosts,variables,roles,tasks等对象的表示方法都是键值中间以 " : " 分隔表示, ":" 后面还要增加一个空格
    例:
    house:
    family: { name: Doe, parents: [John, Jane], children: [Paul, Mark, Simone] }
    address: { number: 34, street: Main Street, city: Nowheretown, zipcode: 12345 }


    playbook内容:
    playbook的核心元素:
    hosts:运行在哪些主机之上
    users:远程主机上,运行此任务的身份,不指名默认为root
    tasks:任务,也就是定义的具体任务,由模块定义的操作的列表
    variables:变量
    templates:模板,包含了模板语法编写的模板的文本文件
    handlers:处理器,类似Tasks,只是在特定的条件下才会触发的任务
    某任务的状态在运行后为changed时,可通过"notify"通知给相应的handlers进行触发执行
    roles:角色,将Hosts剥离出去,由Tasks、Variables、Templates、Handlers所组成的一种特定的结构的集合


    playbook的基础组件:
    hosts: 运行指定任务的而目标主机,多个主机用:冒号分隔
    remote_user: 在远程主机上执行任务的用户;可以全局指定,也可以单个任务指定
    sudo_user: 表示以sudo方式运行任务时,切换为哪个用户身份运行
    tasks:
    任务列表,ansible运行任务的方式为,将第一个任务在所有主机上运行完成,然后再将第二个任务在所有主机上运行…,当某个任务在某个主机上运行出现故障,会造成任务终止,再次执行任务只需直接执行即可

    定义任务列表,实际就是指明使用的模块和对应的模块参数来完成的任务的列表,其格式有两种:
      (1)action:MODULE ARGUMENTS
      (2)MODULE:ARGUMENTS
    注意:shell和command模块后面直接跟命令,而不是key=value的参数列表

    例:

    vim /etc/ansible/hosts
    [opop]
    192.168.0.223 ansible_user=root ansible_ssh_pass="aaaaaa"
    192.168.0.111 ansible_user=root ansible_ssh_pass="aaaaaa"


    vim test.yml      # 编辑一个playbook(可任意指定地方编写playbook)
    ---
    - hosts: opop       // 指明运行该playbook剧本的被管理主机有哪些,一个playbook文件可有多个hosts
    remote_user: root    // 指明运行该剧本的远程主机上的用户是谁
    tasks:      // 任务列表
      - name: ssh-copy    // 提前执行ssh-keygen -t rsa生成秘钥
      authorized_key: user=root key="{{lookup('file', '/root/.ssh/id_rsa.pub')}}"           // 给客户端传送秘钥
      - name: install apache
      yum: name=httpd state=present     {absent | latest}
      - name: add a user      // 指明某个具体任务的名称
      yum: name=kkkk  system=no       // 具体的任务是什么,也就是调用那个模块,传递给该模块哪些参数来实现某个具体的功能
      - name: add a group
      group: name=testgroup  system=no               // 表明组名和是系统用户
      - name: start apache
      service: name=httpd state=started


    playbook文件的执行需要利用ansible-playbook命令进行调用:
    ansible-playbook命令用法:
    <1> 检测语法
        ansible-playbook --syntax-check ~/test.yml
    <2> 测试运行
        ansible-playbook -C /PATH/TO/PLAYBOOK.yaml
    注:只检测执行指定的YAML文件可能会发生改变,但不真正执行操作,相当于测试运行

        –list-hosts 检测YAML文件可能影响到的主机列表
        –list-tasks 列出YAML文件的任务列表
        –list-tags 列出YAML文件中的标签
    <3> 运行
        ansible-playbook  /PATH/TO/PLAYBOOK.yml
    可用选项:
        不加任何选项表示完整运行整个playbook文件
        -t TAGS,–tags=TAGS 表示只执行那个标签的任务
        –skip-tags=SKIP_TAGS 表示除了指明的标签的任务,其他任务都执行
        –start-at-task=START_AT 从指明的任务开始往下运行

    <4> 通常情况下剧本的执行过程
        1、先要利用 ansible-playbook -C /PATH/TO/PLAYBOOK.yaml进行测试,测试没问题后
           或者:ansible-playbook --check /PATH/TO/PLAYBOOK.yaml
        # ansible-playbook --check ~/test.yml                   // 检测

        # ansible-playbook ~/test.yml            // 真正执行剧本

    PLAY [opop] ***********************(表示该剧本在哪些机器上运行,可以是组)****************************

    TASK [Gathering Facts] *******(默认每个剧本执行时都会执行该任务,是用来收集被管理主机上的各个facts变量的信息)*******
    ok: [192.168.0.223]
    ok: [192.168.0.111]

    TASK [ssh-copy] ****(第一个任务,changed:执行该任务时会改变远程主机的状态,ok:执行该任务没有改变远端机的现有状态)**
    ok: [192.168.0.111]
    ok: [192.168.0.223]

    TASK [install apache] ***********************(第二个任务)***************************
    ok: [192.168.0.111]
    changed: [192.168.0.223]

    TASK [useradd a user] ************************(第三个任务)***************************
    changed: [192.168.0.111]
    changed: [192.168.0.223]

    TASK [add a group] *********************(第四个任务)*********************************
    changed: [192.168.0.111]
    changed: [192.168.0.223]

    TASK [qidong apache] ********************(第五个任务)************************************
    ok: [192.168.0.111]
    changed: [192.168.0.223]

    PLAY RECAP ****************************************************************************
    192.168.0.111 : ok=7 changed=3 unreachable=0 failed=0
    192.168.0.223 : ok=7 changed=5 unreachable=0 failed=0

    // (执行该剧本的统计信息,ok表示可成功执行的任务的个数(加上了默认执行收集的facts变量的任务),changge表示执行该剧本会改变远程主机任务的个数,unreachable表示任务不可达的个数,failed表示执行失败的任务的个数)

    // 剧本不会有任何的任务输出信息

  • 相关阅读:
    Java的反射机制
    并发编程--锁--悲观锁和乐观锁
    SpringCloud --服务调用Feign
    微服务
    项目中处理数据常用Excel公式
    接口参数选择
    你真会看idea中的Log吗?
    MySQL--索引
    Redis介绍
    MySQL--SQL执行顺序,Explain
  • 原文地址:https://www.cnblogs.com/smlile-you-me/p/9020353.html
Copyright © 2011-2022 走看看