49.Ansible PlayBook重构

Ansible PlayBook初识

什么是PlayBook

PlayBook,意为“剧本”或“兵书”,由以下部分组成:

  • play:定义主机的角色(主角还是配角,找哪个明星)
  • task:定义具体执行的任务(角色的台词和动作)
  • playbook:由一个或多个play(角色)组成,一个play(角色)可以包含多个task(台词,动作)

简单理解为:使用不同的模块完成一件事情。

文件格式

  • Ansible中的“剧本文件”以.yml结尾。
  • SaltStack中的“剧本文件”以.sls结尾。
  • 两者都使用YAML语法。
  • ansible-playbook –syntax-check wget.yml检查文件内容语法

PlayBook与ad-hoc

  • PlayBook语法展现更直观。
  • PlayBook可以持久使用,而ad-hoc无法持久使用。

PlayBook—《孙子兵法》编写

  • host:对哪些主机进行操作(演员)
  • remote_user:使用什么用户执行(通行证)
  • tasks:具体执行任务(台词和动作)

YAML语法

  • 缩进:YAML使用固定的缩进风格表示层级结构,每个缩进由两个空格组成,不能使用TAB。
  • 冒号:以冒号结尾的除外,其他所有冒号后面所有必须有空格。
  • 短横线:表示列表项,使用一个短横线加一个空格,多个项使用同样的缩进级别作为同一列表。

示例:

yum:
  name: vsftpd
  state: present

yum:
  name:
    - httpd
    - nginx
    - php-fpm
  state: present

示例2:

playbook安装wget

1#编写yml
[root@ansible ansible]# cat wget.yml
- hosts: backup
  tasks:
    - name: Install wget
      yum:
        name: wget
        state: present

2#检查playbook的语法
[root@ansible ansible]# ansible-playbook  --syntax-check wget.yml
playbook: wget.yml


3#执行playbook
[root@ansible ansible]# ansible-playbook wget.yml

客户端信息获取 setup变量

[root@Ansible /etc/ansible]# ansible web01 -m setup
/usr/local/lib/python3.7/site-packages/ansible/parsing/vault/__init__.py:44: CryptographyDeprecationWarning: Python 3.7 is no longer supported by the Python core team and support for it is deprecated in cryptography. A future release of cryptography will remove support for Python 3.7.
  from cryptography.exceptions import InvalidSignature
web01 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "10.0.0.7",
            "172.16.1.7"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::20c:29ff:fe6d:9e0c",
            "fe80::20c:29ff:fe6d:9e16"
        ],
        "ansible_apparmor": {
            "status": "disabled"
        },
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "11/12/2020",
        "ansible_bios_vendor": "Phoenix Technologies LTD",
        "ansible_bios_version": "6.00",
        "ansible_board_asset_tag": "NA",
        "ansible_board_name": "440BX Desktop Reference Platform",
        "ansible_board_serial": "None",
        "ansible_board_vendor": "Intel Corporation",
        "ansible_board_version": "None",
        "ansible_chassis_asset_tag": "No Asset Tag",
        "ansible_chassis_serial": "None",
        "ansible_chassis_vendor": "No Enclosure",
        "ansible_chassis_version": "N/A",
        "ansible_cmdline": {
            "BOOT_IMAGE": "/vmlinuz-4.19.90-52.22.v2207.ky10.x86_64",
            "audit": "0",
            "crashkernel": "1024M,high",
            "quiet": true,
            "resume": "UUID=fbeb99c6-031d-417c-8c36-d0471127b5d2",

playbook重构backup

定义主机清单

[root@ansible ansible]# cat /etc/ansible/hosts
nfs ansible_ssh_host=10.0.0.31
backup ansible_ssh_host=10.0.0.41

编写Playbook

backup-rsync.yml

[root@Ansible ~]# cat /etc/ansible/backup-rsync.yml 
- hosts: backup
  tasks:
    - name: 01-Install rsync
      yum:
        name: rsync
        state: present

    - name: 02-Create group www
      group: 
        name: www
        gid: 666
        state: present

    - name: 03-Create user www
      user: 
        name: www
        uid: 666
        group: www
        shell: /sbin/nologin
        create_home: false
        state: present

    - name: 04-Create user and pass
      copy: 
       content: rsync_backup:123456
       dest: /etc/rsync.user.password
       mode: 0600

    - name: 05-Create password
      copy:
       content: 123456
       dest: /etc/rsync.password
       mode: 0600

    - name: 06-Create share directory 
      file: 
        path: /backup/
        state: directory
        owner: www
        group: www

    - name: 07-Copy rsyncd.conf to backup
      copy: 
        src: rsyncd.conf
        dest: /etc/

    - name: 08-Systemd rsyncd
      systemd: 
        name: rsyncd
        state: started
        enabled: yes

检查Playbook语法

[root@ansible ansible]# ansible-playbook --syntax-check backup-rsync.yml
or
[root@ansible ansible]# ansible-playbook  -C  backup-rsync.yml  #模拟运行  

执行Playbook

[root@ansible ansible]# ansible-playbook backup-rsync.yml 

测试,其他主机推

[root@Ansible ~]# rsync -av /etc/hosts rsync_backup@10.0.0.41::data

playbook重构nfs和lsync

定义主机清单

[root@ansible ansible]# cat /etc/ansible/hosts
nfs ansible_ssh_host=10.0.0.31
backup ansible_ssh_host=10.0.0.41

编写Playbook

nfs-lsync.yml

[root@Ansible ~]# cat /etc/ansible/nfs-lsync.yml 
- hosts: nfs
  tasks: 
    - name: 01-Install nfs-utils
      yum: 
        name: nfs-utils
        state: present

    - name: 02-Create exports
      copy: 
        src: exports
        dest: /etc/

    - name: 03-Create group www
      group: 
        name: www
        gid: 666
        state: present
    - name: 04-Create user  www
      user: 
        name: www
        uid: 666
        group: www
        shell: /sbin/nologin
        create_home: false
        state: present

    - name: 05-Create dir /date
      file:
        path: /data
        state: directory
        owner: www
        group: www


    - name: 06-Copy tar static data
      unarchive: 
        src: data.tar.gz
        dest: /
        #creates: /data

    - name: 07-systemd nfs
      systemd: 
        name: nfs
        state: started
        enabled: yes


    - name: 08-Install lsyncd
      yum: 
        name: lsyncd
        state: present

    - name: 09-Create lsyncd.conf
      copy: 
        src: lsyncd.conf
        dest: /etc/

    - name: 10-Create rsync.passwd
      copy: 
        content: 123456
        dest: /etc/rsync.passwd
        mode: 0600

    - name: 11-systemd lsyncd
      systemd: 
        name: lsyncd
        state: started
        enabled: yes

检查Playbook语法

执行Playbook

测试,其他主机推

nfs测试:别的主机挂载创建文件,nfs服务器查看
lsync测试:在监控目录创建文件,在backup模块服务器查看

playbook重构db01

  • 重构mariadb
  • 两种方法,一种从新单台搭建,mysqldump出来使用。另一种用旧的all.sql数据库数据

定义主机清单

[root@ansible ansible]# cat /etc/ansible/hosts
web01 ansible_ssh_host=10.0.0.7
web02 ansible_ssh_host=10.0.0.8
nfs ansible_ssh_host=10.0.0.31
backup ansible_ssh_host=10.0.0.41
db01 ansible_ssh_host=10.0.0.51

编写Playbook

/etc/ansible/db01.yml

[root@Ansible ~]# cat /etc/ansible/db01.yml 
- hosts: db01
  tasks: 
    - name: 01-install mariadb,pyt3
      yum: 
        name: mariadb-server,python3-mysqlclient
        state: present

    - name: 02-start mariadb
      systemd: 
        name: mariadb
        state: started
        enabled: yes

    - name: 03-copy all.sql to db01
      copy: 
        src: all.sql
        dest: /root/

    - name: 04-configure maridb
      mysql_db: 
        login_user: root
        login_host: localhost
        login_port: 3306
        name: all
        target: /root/all.sql
        state: import

    - name: 05-restart mariadb
      systemd: 
        name: mariadb
        state: restarted

检查Playbook语法

执行Playbook

测试,其他主机推

远程连接数据库:mysql -h 10.0.0.51 -uhsc -p
查看数据库:show databases;

playbook重构web01

  • 重构nginx、php、mariadb

定义主机清单

[root@ansible ansible]# cat /etc/ansible/hosts
web01 ansible_ssh_host=10.0.0.7
web02 ansible_ssh_host=10.0.0.8
nfs ansible_ssh_host=10.0.0.31
backup ansible_ssh_host=10.0.0.41

编写Playbook

/etc/ansible/webs.yml

[root@Ansible ~]# cat /etc/ansible/webs.yml 
- hosts: web01
  tasks: 
    - name: 01-create nginx.repo
      yum_repository: 
        name: nginx
        description: cnetos7 nginx.repo
        baseurl: http://nginx.org/packages/centos/7/$basearch/
        gpgcheck: no
        enabled: yes

    - name: 02-install nginx-php-mariadb 
      yum: 
        name: nginx,php,php-bcmath,php-cli,php-common,php-devel,php-embedded,php-fpm,php-gd,php-intl,php-mbstring,php-mysqlnd,php-opcache,php-pdo,php-process,php-xml,php-json,mariadb-server
        state: present 

    - name: 03-Create php/www.conf
      copy:
        src: www.conf
        dest: /etc/php-fpm.d/

    - name: 04-Create nginx.conf
      copy:
        src: www.conf
        dest: /etc/nginx/       

    - name: 05-Create conf.d/wp.conf
      copy:
        src: wp.conf
        dest: /etc/nginx/conf.d 

    - name: 06-Create rmove de
      file:
        path: /etc/nginx/conf.d/default.conf
        state: absent

    - name: 07-Create group www
      group:
        name: www
        gid: 666
        state: present

    - name: 08-Create user  www
      user:
        name: www
        uid: 666
        group: www
        shell: /sbin/nologin
        create_home: false
        state: present

    - name: 09-Create dir /code
      file:
        path: /code
        state: directory
        owner: www
        group: www


    - name: 10-systemd mariadb
      systemd:
        name: mariadb
        state: stopped
        enabled: no

    - name: 11-systemd nginx
      systemd:
        name: nginx
        state: started
        enabled: yes

    - name: 12-systemd php-fpm
      systemd:
        name: php-fpm
        state: started
        enabled: yes

    - name: 13-mount nfs
      mount:
        path: /code
        src: 172.16.1.31:/data
        state: mounted
        fstype: nfs

检查Playbook语法

执行Playbook

测试,其他主机推

访问网站测试

playbook一键重构集群

  • 重构web01、nfs、backup、db01
  • 两种方法,一种写到一个yml里执行,另一种写到一个脚本里执行脚本

脚本集合:

vim refactoring-all.sh

#!/bin/bash

sshpass -p 'haoshuaicong123.com' ssh-copy-id 10.0.0.31
sshpass -p 'haoshuaicong123.com' ssh-copy-id 10.0.0.41
sshpass -p 'haoshuaicong123.com' ssh-copy-id 10.0.0.51
sshpass -p 'haoshuaicong123.com' ssh-copy-id 10.0.0.7

ansible-playbook /etc/ansible/backup-rsync.yml
ansible-playbook /etc/ansible/nfs-lsync.yml
ansible-playbook /etc/ansible/db01.yml
ansible-playbook /etc/ansible/web01.yml

增加执行权限,执行

chmod +x refactoring-all.sh
sh -x refactoring-all.sh

测试,其他主机推

解析10.0.0.7
www.wp.com
访问网站测试

playbook模块使用说明:

各模块使用方法

  1. yum:用于在基于 Yum 的系统中管理软件包的安装、删除等操作。
  2. group:用于管理用户组的创建、删除等。
  3. user:用于管理用户的创建、删除等属性设置。
  4. copy:用于将文件从一处复制到另一处,也可直接创建包含指定内容的文件。
  5. file:用于管理文件和目录的状态,如创建、删除、设置权限等。
  6. systemd:用于管理系统服务,如启动、停止、重启服务以及设置服务开机自启状态。
  7. mysql_db:用于管理 MySQL 数据库相关操作,如导入数据库等。
  8. yum_repository:用于管理 Yum 仓库的配置。
  9. mount:用于管理文件系统的挂载操作。
  10. unarchive:用于解压归档文件。

1.yum 模块

  • 语法
yum:
  name: <软件包名或软件包列表,逗号分隔>
  state: <present|absent|latest等>
  • 说明
  • name:指定要操作的软件包名称。可以是单个软件包,也可以是多个软件包组成的列表。
  • statepresent表示安装软件包;absent表示删除软件包;latest表示安装最新版本的软件包。
  • 示例
- name: 安装rsync
  yum:
    name: rsync
    state: present

2.group 模块

  • 语法
group:
  name: <用户组名>
  gid: <组ID(可选)>
  state: <present|absent>
  • 说明
  • name:指定要操作的用户组名称。
  • gid:指定用户组的组 ID,若不指定则系统自动分配。
  • statepresent表示创建用户组;absent表示删除用户组。
  • 示例
- name: 创建组www
  group:
    name: www
    gid: 666
    state: present

3.user 模块

  • 语法
user:
  name: <用户名>
  uid: <用户ID(可选)>
  group: <所属组名>
  shell: <用户默认shell(可选)>
  create_home: <true|false(可选)>
  state: <present|absent>
  • 说明
  • name:指定要操作的用户名。
  • uid:指定用户的用户 ID,若不指定则系统自动分配。
  • group:指定用户所属的用户组。
  • shell:指定用户的默认 shell,默认通常为/bin/bash
  • create_home:指定是否为用户创建家目录,默认true
  • statepresent表示创建用户;absent表示删除用户。
  • 示例
- name: 创建用户www
  user:
    name: www
    uid: 666
    group: www
    shell: /sbin/nologin
    create_home: false
    state: present

4.copy 模块

  • 语法
copy:
  src: <源文件路径(可选)>
  dest: <目标文件路径>
  mode: <文件权限(可选)>
  content: <文件内容(可选)>
  • 说明
  • src:指定源文件的路径,若使用content则可不填。
  • dest:指定目标文件的路径。
  • mode:指定目标文件的权限,格式如0600
  • content:用于直接指定文件内容,而不通过复制源文件。
  • 示例 1(复制文件)
- name: 复制rsyncd.conf到/etc/
  copy:
    src: rsyncd.conf
    dest: /etc/
  • 示例 2(创建含指定内容的文件)
- name: 创建用户和密码文件
  copy:
    content: rsync_backup:123456
    dest: /etc/rsync.user.password
    mode: 0600

5.file 模块

  • 语法
file:
  path: <文件或目录路径>
  state: <directory|file|link软连接|absent|touch等>
  owner: <所有者(可选)>
  group: <所属组(可选)>
  • 说明
  • path:指定要操作的文件或目录的路径。
  • statedirectory表示创建目录;file表示创建文件;link表示创建链接;absent表示删除文件或目录;touch表示创建或更新文件时间戳。
  • owner:指定文件或目录的所有者。
  • group:指定文件或目录的所属组。
  • 示例
- name: 创建共享目录/backup
  file:
    path: /backup/
    state: directory
    owner: www
    group: www

6.systemd 模块

  • 语法
systemd:
  name: <服务名>
  state: <started|stopped|restarted|reloaded|enabled|disabled等>
  • 说明
  • name:指定要操作的系统服务名称。
  • statestarted表示启动服务;stopped表示停止服务;restarted表示重启服务;reloaded表示重新加载服务配置;enabled表示设置服务开机自启;disabled表示设置服务开机不自启。
  • 示例
- name: 启动并设置开机自启rsyncd服务
  systemd:
    name: rsyncd
    state: started
    enabled: yes

7.mysql_db 模块

  • 语法
mysql_db:
  login_user: <登录用户名>
  login_host: <登录主机(可选)>
  login_port: <登录端口(可选)>
  name: <数据库名>
  target: <导入文件路径(可选)>
  state: <import等>
  • 说明
  • login_user:指定登录 MySQL 数据库的用户名。
  • login_host:指定登录的主机,默认localhost
  • login_port:指定登录的端口,默认3306
  • name:指定要操作的数据库名称。
  • target:指定要导入的 SQL 文件路径。
  • stateimport表示导入数据库。
  • 示例
- name: 导入数据库
  mysql_db:
    login_user: root
    login_host: localhost
    login_port: 3306
    name: all
    target: /root/all.sql
    state: import

8.yum_repository 模块

  • 语法
yum_repository:
  name: <仓库名>
  description: <仓库描述(可选)>
  baseurl: <仓库地址>
  gpgcheck: <yes|no(可选)>
  enabled: <yes|no(可选)>
  • 说明
  • name:指定 Yum 仓库的名称。
  • description:对仓库的描述信息。
  • baseurl:指定仓库的 URL 地址。
  • gpgcheck:指定是否检查 GPG 签名,yes表示检查,no表示不检查。
  • enabled:指定该仓库是否启用,yes表示启用,no表示禁用。
  • 示例
- name: 创建nginx.repo
  yum_repository:
    name: nginx
    description: cnetos7 nginx.repo
    baseurl: http://nginx.org/packages/centos/7/$basearch/ 
    gpgcheck: no
    enabled: yes

9.mount 模块

  • 功能
    通过 mount 命令进行挂载操作,可修改 /etc/fstab 实现永久挂载。
  • mount 命令选项
  • src:原地址(nfs 服务端共享目录,例如 172.16.1.31/data)。
  • path:挂载点(指定源挂载到哪里)。
  • fstype:文件系统类型(指定文件系统,如 xfs、ext4、iso9660 等)。
  • state:参考如下值。
  • mount 模块的 state 参数可用值
  • absent:卸载并删除 /etc/fstab里的这条挂载。
  • unmounted:卸载不删除 /etc/fstab里的这条挂载。
  • present:仅修改 /etc/fstab添加开机挂载,不立即挂载。
  • mounted:立即挂载,并 /etc/fstab 添加开机自动挂载。
  • remounted:重新挂载设备,用于强制刷新挂载本身。
  • 语法
mount:
  path: <挂载点路径>
  src: <源设备或远程共享路径>
  state: <mounted|unmounted等>
  fstype: <文件系统类型(可选)>
  • 说明
  • path:指定要挂载的目标路径。
  • src:指定源设备或远程共享的路径。
  • statemounted表示挂载;unmounted表示卸载。
  • fstype:指定文件系统类型,如nfsext4等。
  • 示例
- name: 挂载NFS共享
  mount:
    path: /code
    src: 172.16.1.31:/data
    state: mounted
    fstype: nfs

10.unarchive 模块

  • 语法
unarchive:
  src: <归档文件路径>
  dest: <解压目标目录>
  creates: <文件或目录(可选)>
  • 说明
  • src:指定要解压的归档文件路径。
  • dest:指定解压后的目标目录。
  • creates:指定一个文件或目录,如果该文件或目录已存在,则不执行解压操作。
  • 示例
- name: 解压静态数据
  unarchive:
    src: data.tar.gz
    dest: /

11.debug 模块

  • 语法
debug:
  msg: <要打印的消息内容>
  • 说明
  • msg:使用 Jinja2 模板语法来指定要打印的消息内容。可以是变量、表达式等。
  • 示例
- name: print ng_re env
  debug:
    msg: "{{ ng_re }}"

这个示例的作用是打印ng_re变量的值。通过debug模块,在 Ansible 执行过程中可以输出调试信息,帮助排查问题。

12.notify 模块与 handlers

notify并非一个独立的模块,而是 Ansible 中用于触发handlers的一种机制。

handlers是 Ansible 中一组任务的集合,只有在被notify通知或者手动调用时才会执行。

- name: Configure Nginx Server
  copy:
    src: nginx.conf
    dest: /etc/nginx/
  notify: Restart Nginx Server


handlers:
  - name: Restart Nginx Server
    systemd:
      name: nginx
      state: restarted
    when: ng_re.stderr_lines is search "ok"

此例中,若文件内容有变动,就会通知并触发名为Restart Nginx Serverhandler

13.mysql_user模块

  tasks:
    - name: Grant all privileges to hsc user
      mysql_user:
        name: hsc
        host: '%'
        password: 'hsc123.com'
        priv: '*.*:ALL'
        state: present
        login_user: root  # 用于登录MySQL的用户名
        login_password: your_root_password  # root用户的当前密码

解释:

模块:用于管理 MySQL 用户。

  • name:要授予权限的 MySQL 用户名。
  • host:用户允许登录的主机,% 表示允许从任何主机登录。
  • password:用户的密码。
  • priv:授予的权限,*.*:ALL 表示对所有数据库的所有表授予所有权限。
  • state: present:确保用户存在并具有指定的权限。
  • login_user:用于登录 MySQL 的用户名,这里使用 root 用户。
  • login_passwordroot 用户的当前密码。

14.mysql_db模块

mysql_db:
  login_user: root    # 用于登录MySQL的用户名
  login_host: localhost    # MySQL服务器的主机地址
  login_port: 3306    # 连接MySQL服务器的端口号
  login_password: hsc123.com    # 登录MySQL的密码
  name: wordpress    # 要操作的数据库名称
  state: present    # 数据库的期望状态,present表示若不存在则创建

解释:

  • 模块:用于管理 MySQL 数据库。
  • login_user:指定登录 MySQL 数据库的用户名,此处为 root。
  • login_host:指定 MySQL 服务器所在的主机地址,localhost表示本地。
  • login_port:指定连接 MySQL 服务器的端口,默认是 3306。
  • login_password:用于登录 MySQL 的密码,这里是hsc123.com
  • name:要进行操作(创建或检查是否存在)的数据库名字,这里是 wordpress。
  • state:期望数据库所处的状态,present 表示如果数据库不存在就创建它。

15.loop循环

1. 基本概念

loop允许你对一个列表、字典或其他可迭代对象中的每个元素执行相同的任务。它取代了早期 Ansible 版本中的with_*系列的循环语句。

2. 使用示例

简单列表循环
- name: Install multiple packages
  yum:
    name: "{{ item }}"
    state: present
  loop:
    - httpd
    - mysql
    - php

在这个例子中,yum模块会对loop中的每个软件包进行安装操作。item是一个特殊的变量,它在每次循环时代表列表中的一个元素。

字典循环
- name: Create multiple users with specific UIDs
  user:
    name: "{{ item.name }}"
    uid: "{{ item.uid }}"
    state: present
  loop:
    - { name: 'user1', uid: 1000 }
    - { name: 'user2', uid: 1001 }

这里loop是一个字典列表,item在每次循环时会代表一个字典,通过item.nameitem.uid可以访问字典中的键值。

3. 进阶用法

与条件语句结合
- name: Process items based on condition
  debug:
    msg: "Processing item: {{ item }}"
  loop:
    - a
    - b
    - c
  when: item!= "b"

在这个例子中,when条件语句用于筛选出item不等于b的元素进行处理。

嵌套循环
- name: Nested loops
  debug:
    msg: "Outer item: {{ outer_item }}, Inner item: {{ inner_item }}"
  loop:
    - outer1
    - outer2
  with_nested:
    - [1, 2]
    - [3, 4]

这里展示了一个嵌套循环的示例,外层循环遍历loop中的元素,内层循环遍历with_nested中的元素。

4. 注意事项

  • 变量作用域:在循环内部定义的变量(如item)只在循环内部有效。
  • 性能考虑:如果循环处理的数据量非常大,可能会影响执行效率,需要合理设计任务。

loop模块极大地增强了 Ansible 在批量处理和自动化操作方面的能力,通过合理利用它,可以更高效地管理系统和部署应用。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇