ansible完全攻略

本文介绍了Ansible的基本用法,包括安装、常用模块如command、shell、file、service等的使用,以及如何创建和执行playbook进行自动化任务。还涉及到变量、条件语句和模板引擎Jinja2在Ansible中的应用。
摘要由CSDN通过智能技术生成

ansible完全攻略

ansible安装:

apt安装:

apt install ansible  #安装ansible

ansible --version #ansible

ansible简单使用:

ansible 主机列表 参数
主机列表默认值得是 /etc/ansible/hosts
可默认指定为仅可以用localhost但是不隐式指定
ansible localhost -m command -a "参数"  #-m指定模块,默认为command
ex:
ansible localhost -m command -a "ls"  #在本机执行ls命令,可以用-v,-vv,-vvv来显示更加详细的命令执行信息
ansible localhost -m ping #执行ping命令操作

ansible-doc -l #列出所有模块,按Q退出
ansible-doc -h #ansible-doc帮助命令
ansible-doc -s [模块] #显示某个模块的简单参数信息

ex:
root@dokcer:~# ansible localhost -m command -a  "chdir=/tmp pwd" #使用command的chdir参数改变执行命令的目录
 [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit
localhost does not match 'all'

localhost | SUCCESS | rc=0 >>
/tmp   #显示已经更改了执行命令的目录

ansible-config -h #显示配置的帮助信息
ansible-config list #以yaml的格式来显示配置信息
ansible-config view #以json的格式来显示配置信息
ansible-config dump #显示所有配置的变量信息

主机清单:

主机清单文件:

文件位置:/etc/ansible/hosts

主机属性:(用于过滤主机)
all 所有主机
ungrouped 所有没有组的主机
localhost 表示本机

主机格式:
	散列主机列表
		主机名/IP地址:[ssh端口]
  主机组列表
		[主机组名]
		主机列表1
		[主机组:children]
		子组1
		子组2
		...
	主机范围
		主机域名[001:006] 
		IP地址[01:60]
		
	
  
 

如果连接过主机但是失败了,会在~/.ssh/known_hosts中有记录,删除之后重新连接就可以了。

报错:

to use the 'ssh' connection type with passwords, you must install the sshpass program
sudo apt-get install sshpass  #安装sshpass模块

PermitRootLogin yes #ansible默认执行命令的用户为root,设置为root可以登录

ansible连接主机的属性

192.168.0.50 ansible_connection=local  #设置主机连接方式为本地连接
ansible 192.168.0.50 -a "ls" #不用输入密码就可以连接,相当于localhost方式

192.168.0.50 ansible_ssh_pass=ssh登录密码 
ansible 192.168.0.50 -a "ls" #不用输入ssh密码就可以输入命令了

ansible免密钥认证:
1.生成密钥对
root@dokcer:~# ssh-keygen -t rsa   
2.发送控制端的公钥到目标主机
ssh-copy-id -i /root/.ssh/id_rsa.pub lion@192.168.0.50  #输入lion用户的密码就传输完成了

error:
No such file or directory #没有.ssh文件夹
lion@dokcer:~$ ssh localhost  #连接本地生成ssh文件夹
3.实现免密码登录

ansible配置文件:

配置文件目录:

root@dokcer:/root/.ssh# tree /etc/ansible/
/etc/ansible/
├── ansible.cfg  #核心配置文件
└── hosts        #主机列表文件
└── roles        #角色管理文件

核心配置文件为ansible.cfg
通过修改
1.环境变量$ANSIBLE_CONFIG #通过环境变量来定义配置文件的位置
2.工作目录    #仅限当前目录
3.用户家目录  #仅限当前系统用户
4.软件目录    #全局生效

配置格式:
[配置段]
配置项:属性值

root@dokcer:~# egrep -v '^$|^#' /etc/ansible/ansible.cfg  #过滤掉空格和#号显示
[defaults]
[inventory]
[privilege_escalation]
[paramiko_connection]
[ssh_connection]
[persistent_connection]
[accelerate]
[selinux]
[colors]
[diff]

[defaults]                                     #默认配置

# some basic default values...

#inventory      = /etc/ansible/hosts            #主机配置列表
#library        = /usr/share/my_modules/        #模块位置
#module_utils   = /usr/share/my_module_utils/
#remote_tmp     = ~/.ansible/tmp                #远程主机临时执行命令目录          
#local_tmp      = ~/.ansible/tmp                #本地主机临时执行命令目录
#plugin_filters_cfg = /etc/ansible/plugin_filters.yml
#forks          = 5
#poll_interval  = 15
#sudo_user      = root                             
#ask_sudo_pass = True
#ask_pass      = True
#transport      = smart
#remote_port    = 22                            #ssh端口
#remote_user    = root                          #远程登录用户
#module_lang    = C
#module_set_locale = False

ansilbe利用指定用户提权执行命令

ansible localhost -u lion -a "ls /root" -b -K  #输入sudo密钥后执行超级管理员命令

设置某一个用户拥有root权限

root@dokcer:~# cat /etc/sudoers
#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults        env_reset
Defaults        mail_badpass
Defaults        secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin"

# Host alias specification

# User alias specification

# Cmnd alias specification

# User privilege specification
root    ALL=(ALL:ALL) ALL

# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL          #admin组用用户拥有root权限
# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL     #sudo组用户拥有sudo来执行任何命令

# See sudoers(5) for more information on "#include" directives:

#includedir /etc/sudoers.d
root@dokcer:~#  grep sudo /etc/gshadow #在gshandown文件中查看sudo组的成员
sudo:*::lion
root@dokcer:~# usermod -G sudo lion  #增加某一用户到sudo组中

%sudo   ALL=(ALL:ALL) ALL  / %sudo   ALL=(ALL:ALL) NOPASSWD:ALL  #默认切换sudo执行命令时不需要输入密码

root@dokcer:~# ansible localhost -u lion -a "ls /root" -b   #不用-K输入密码就可以执行超级命令
localhost | SUCCESS | rc=0 >>
snap

ansible主机匹配:

匹配所有: all
正则匹配 *(通配符)
逻辑或 :(并集),一般由于主机组
逻辑或 :&(交集),一般由于主机组
逻辑非 :!(补集),一般由于主机组

root@dokcer:/root/.ssh# ansible '*' -m ping -k  #匹配所有主机进行ping,用单引号括住主机列表部分
SSH password:
192.168.0.50 | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

ansible命令模块:

命令模块
command:默认模块,可以远程权限运行所有的shell命令,不支持特殊符号,但是支持系统变量,不支持命令别名
shell:用于执行某些带特殊符号的命令<>!#$
scripts:可以执行脚本文件,也可以从控制主机的脚本运行到远程主机

**command模块:**
root@dokcer:~# ansible localhost -a "chdir=/tmp ls"  #切换执行命令的目录

root@dokcer:~# ansible localhost -a 'chdir=/tmp creates=1.txt ls'  #如果creates文件存在,则跳过命令执行,反之亦然
localhost | SUCCESS | rc=0 >>
skipped, since 1.txt exists

root@dokcer:~# ansible localhost -a 'chdir=/tmp removes=1.txt ls'  #如果removes的文件存在则执行命令,反之亦然
localhost | SUCCESS | rc=0 >>
1.txt
ansible_VtVGmv
snap.docker
systemd-private-66d00680e20c4dba801130d9387e8e6f-systemd-resolved.service-KVfCH2
systemd-private-66d00680e20c4dba801130d9387e8e6f-systemd-timesyncd.service-xhgHcZ

root@dokcer:~# ansible localhost -a 'echo $SHELL'  #command值支持系统变量不支持自定义变量
localhost | SUCCESS | rc=0 >>
/bin/bash

root@dokcer:~# ansible localhost -a 'la=lala; echo $la'
localhost | FAILED | rc=2 >>
[Errno 2] No such file or directory

root@dokcer:~# ansible localhost -a 'env | grep 1'   #command不支持特殊管道命令
localhost | FAILED | rc=127 >>
env: ‘|’: No such file or directorynon-zero return code

**shell模块**:
root@dokcer:~# ansible localhost -m shell -a "la=lala; echo $la"  #双引号有特殊符号时不生效
localhost | SUCCESS | rc=0 >>

root@dokcer:~# ansible localhost -m shell -a 'la=lala; echo $la'  #单引号命令成功
localhost | SUCCESS | rc=0 >>
lala

root@dokcer:~# ansible localhost -m shell -a 'env | grep $SHELL'  #shell执行管道符命令
localhost | SUCCESS | rc=0 >>
SUDO_COMMAND=/bin/bash
SHELL=/bin/bash

root@dokcer:~# ansible localhost -m shell -a '/bin/bash /tmp/1.sh admin' #shell执行远程脚本
localhost | SUCCESS | rc=0 >>
user is admin

**script模块:**

root@dokcer:~# ansible 192.168.0.50 -m script -a '/bin/bash /tmp/1.sh amdin' -kSSH password:
192.168.0.50 | SUCCESS => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to 192.168.0.50 closed.\r\n",
    "stdout": "user is amdin\r\n",
    "stdout_lines": [
        "user is amdin"                         #可以执行存在于控制端,但是不存在被控端的脚本文件
    ]
}

#script使用executable参数来指定不存在于被控端的脚本文件
root@dokcer:~# ansible 192.168.0.50 -m script -a 'executable=/bin/bash /tmp/1.sh amdin' -k
SSH password:
192.168.0.50 | SUCCESS => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to 192.168.0.50 closed.\r\n",
    "stdout": "user is amdin\r\n",
    "stdout_lines": [
        "user is amdin"
    ]
}

ansible系统模块:

hostname模块
修改主机名,立刻生效,并且永久生效,hostname只会修改/etc/hostname,而不会修改/etc/hosts
root@dokcer:~# ansible localhost  -m hostname -a 'name=lion'  #修改hostname
localhost | SUCCESS => {
    "ansible_facts": {
        "ansible_domain": "",
        "ansible_fqdn": "lion",
        "ansible_hostname": "lion",
        "ansible_nodename": "lion"
    },
    "changed": true,
    "name": "lion"
}
root@dokcer:~# ansible localhost -a 'hostname'  #修改成功
localhost | SUCCESS | rc=0 >>
docker

user
创建一个用户,name lion2 系统输入,用户组是root, uid 10010 注释是lion2 禁止登录  state=presen状态为存在
root@dokcer:~# ansible localhost -m user -a 'name=lion2 system=yes groups=root uid=10010 comment=lion2 shell=/sbin/nologin state=present'
localhost | SUCCESS => {
    "changed": true,
    "comment": "lion2",
    "create_home": true,
    "group": 999,
    "groups": "root",
    "home": "/home/lion2",
    "name": "lion2",
    "shell": "/sbin/nologin",
    "state": "present",
    "system": true,
    "uid": 10010
}

root@dokcer:~# ansible localhost -a "id lion2"  #创建用户成功,要注意uid
localhost | SUCCESS | rc=0 >>
uid=10010(lion2) gid=999(lion2) groups=999(lion2),0(root)

root@dokcer:~# ansible localhost -a "getent passwd lion2" #查看lion2用户的密码
localhost | SUCCESS | rc=0 >>
lion2:x:10010:999:lion2:/home/lion2:/sbin/nologin

#创建带ssh私钥的用户
root@dokcer:~# ansible localhost -m user -a 'name=lion2 system=yes generate_ssh_key=yes ssh_key_bits=2048 ssh_key_file=.ssh/id_ras'
localhost | SUCCESS => {
    "append": false,
    "changed": true,
    "comment": "lion2",
    "group": 999,
    "home": "/home/lion2",
    "move_home": false,
    "name": "lion2",
    "shell": "/sbin/nologin",
    "ssh_fingerprint": "2048 SHA256:9hjxzWM3jQFqBIqhEceXCdWMRv94CYthiSTB3PoPnCg ansible-generated on docker (RSA)",
    "ssh_key_file": "/home/lion2/.ssh/id_ras",
    "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0LwW+HVOsSCjDMX9U64ugIU+P6O+NWmly4Dy+XHXPShYumFUxAFe7WIq000YBOQgFbrTreOzoGkH5712uZIiUll3Gmbq+zNgY2231KrkpxsVAMjcjq8VidsiOihUaO3CgZAwsIUucuYZo19UTZTThl7h3i28xtaGXbJaWNbC9Xhx2Jrs+QU904JnVWxw8jmDJkePSDiasYsagpICHOL6LpKNUB4QUSnhwwk02wxyIWhAxVEFf59Ub6FCvp77a3K/vBeFoLDlMkySqYoCbieT943ibmBhiNWDkF//YNYlnIa9Fjb5tx3bovic3Br8vgh47PkaE937OY8+6gU3hPTjj ansible-generated on docker",
    "state": "present",
    "uid": 10010
}
#删除用户state=absent remove=ye 用户禁用和删除
root@dokcer:~# ansible localhost -m user -a 'name=lion2 state=absent remove=yes'
localhost | SUCCESS => {
    "changed": true,
    "force": false,
    "name": "lion2",
    "remove": true,
    "state": "absent",
    "stderr": "userdel: lion2 mail spool (/var/mail/lion2) not found\n",
    "stderr_lines": [
        "userdel: lion2 mail spool (/var/mail/lion2) not found"
    ]
}
group模块:
#创建和删除用户组,gid要唯一
#创建用户组
root@dokcer:~# ansible localhost -m group -a 'name=admin system=yes gid=10010'
localhost | SUCCESS => {
    "changed": true,
    "gid": 10010,
    "name": "admin",
    "state": "present",
    "system": true
}
root@dokcer:~# ansible localhost -a 'getent group admin' #获取用户组
localhost | SUCCESS | rc=0 >>
admin:x:10010:
#删除用户组
root@dokcer:~# ansible localhost -m group -a 'name=admin state=absent'
localhost | SUCCESS => {
    "changed": true,
    "name": "admin",
    "state": "absent"
}
root@dokcer:~# ansible localhost -a 'getent group admin'      localhost | FAILED | rc=2 >>
non-zero return code

cron模块:
#指定定时任务,禁用定时任务,启用定时任务,删除定时任务
#指定定时任务
root@dokcer:~# ansible localhost -m cron -a 'name="cron_test" minute=*/1 hour=* day=* month=* weekday=* state=present job="/usr/sbin/ntpdate 192.168.0.5" '
localhost | SUCCESS => {
    "changed": true,
    "envs": [],
    "jobs": [
        "cron_test"
    ]
}
root@dokcer:~# ansible localhost -a 'crontab -l'
localhost | SUCCESS | rc=0 >>
#Ansible: cron_test
*/1 * * * * /usr/sbin/ntpdate 192.168.0.5
#禁用定时任务
root@dokcer:~# ansible localhost -m cron -a 'disabled=yes name="cron_test" minute=*/1 hour=* day=* month=* weekday=* state=present job="/usr/sbin/ntpdate 192.168.0.5" '
localhost | SUCCESS => {
    "changed": true,
    "envs": [],
    "jobs": [
        "cron_test"
    ]
}
root@dokcer:~# ansible localhost -a 'crontab -l'              localhost | SUCCESS | rc=0 >>
#Ansible: cron_test
#*/1 * * * * /usr/sbin/ntpdate 192.168.0.5

#启用定时任务
root@dokcer:~# ansible localhost -m cron -a 'disabled=no name="cron_test" minute=*/1 hour=* day=* month=* weekday=* state=present job="/usr/sbin/ntpdate 192.168.0.5" '
localhost | SUCCESS => {
    "changed": true,
    "envs": [],
    "jobs": [
        "cron_test"
    ]
}
root@dokcer:~# ansible localhost -a 'crontab -l'              localhost | SUCCESS | rc=0 >>
#Ansible: cron_test
*/1 * * * * /usr/sbin/ntpdate 192.168.0.5
#删除定时任务
root@dokcer:~# ansible localhost -m cron -a 'state=absent name="cron_test"  job="/usr/sbin/ntpdate 192.168.0.5" '           localhost | SUCCESS => {
    "changed": true,
    "envs": [],
    "jobs": []
}
root@dokcer:~# ansible localhost -a 'crontab -l'              
localhost | SUCCESS | rc=0 >>

setup模块:
收集目标主机的属性信息
root@dokcer:~# ansible localhost -m setup  #获取主机所有属性信息

用filter获取被控主机的内存信息,filter支持正则表达式
root@dokcer:~# ansible localhost -m setup -a 'filter=ansible_memtotal_mb'
localhost | SUCCESS => {
    "ansible_facts": {
        "ansible_memtotal_mb": 1993
    },
    "changed": false
}

ansible文件模块:

copy模块:
#文件纯拷贝,拷贝时更改属性,拷贝时备份,根据内容增加文件
#文件复制,src控制端源路径,dest为被控端目标路径,目标和源路径最好一致
root@docker:~# ansible localhost -m copy -a 'src=/home/lion/1.txt dest=/tmp/1.txt'
localhost | SUCCESS => {
    "changed": false,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "/tmp/1.txt",
    "gid": 0,
    "group": "root",
    "mode": "0644",
    "owner": "root",
    "path": "/tmp/1.txt",
    "size": 0,
    "state": "file",
    "uid": 0
}
root@docker:~# ansible localhost -a 'ls /tmp -l' -o    #-o参数代表一行输出
localhost | CHANGED | rc=0 | (stdout) total 20\n-rw-r--r-- 1 root root   30 Jul  8 13:24 1.sh\n-rw-r--r-- 1 root root    0 Jul  8 13:08 1.txt\ndrwx------ 

#拷贝时更改属主和文件权限 owner属主 group组 mode文件模式
root@docker:~# ansible localhost -m copy -a 'src=/home/lion/1.txt dest=/tmp/1.txt owner=root group=sudo mode=666'
localhost | SUCCESS => {
    "changed": true,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "/tmp/1.txt",
    "gid": 27,
    "group": "sudo",
    "md5sum": "d41d8cd98f00b204e9800998ecf8427e",
    "mode": "0666",
    "owner": "root",
    "size": 0,
    "src": "/home/lion/.ansible/tmp/ansible-tmp-1657418921.53-172677670998462/source",
    "state": "file",
    "uid": 0
}
root@docker:~# ansible localhost -a 'ls /tmp -l'
localhost | SUCCESS | rc=0 >>
total 20
-rw-r--r-- 1 root root   30 Jul  8 13:24 1.sh   
-rw-rw-rw- 1 root sudo    0 Jul 10 02:08 1.txt #属性666 2-4-1 #读写执行

#文件备份 backup=yes,备份时文件一点要有变化否则copy失败,
root@docker:~# ansible localhost -m copy -a 'src=/home/lion/1.txt dest=/tmp/1.txt owner=root group=sudo mode=666 backup=yes'
localhost | SUCCESS => {
    "backup_file": "/tmp/1.txt.8363.2022-07-10@02:15:01~", #备份文件包含时间戳
    "changed": true,
    "checksum": "a48f53309f72df9fe7124c65605c328c0c4dd415",
    "dest": "/tmp/1.txt",
    "gid": 27,
    "group": "sudo",
    "md5sum": "6e5599d95ad03eb9ead7390bd1be4146",
    "mode": "0666",
    "owner": "root",
    "size": 7,
    "src": "/home/lion/.ansible/tmp/ansible-tmp-1657419301.17-100695084433159/source",
    "state": "file",
    "uid": 0
}

content模块:
#利用content直接把字符串写入文件
root@docker:~# ansible localhost -m copy -a 'content="hello world" dest=/tmp/content.txt'
localhost | SUCCESS => {
    "changed": true,
    "checksum": "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
    "dest": "/tmp/content.txt",
    "gid": 0,
    "group": "root",
    "md5sum": "5eb63bbbe01eeed093cb22bb8f5acdc3",
    "mode": "0644",
    "owner": "root",
    "size": 11,
    "src": "/home/lion/.ansible/tmp/ansible-tmp-1657419516.99-173271494695010/source",
    "state": "file",
    "uid": 0
}
root@docker:~# ansible localhost -a 'cat /tmp/content.txt'
localhost | SUCCESS | rc=0 >>
hello world

fetch模块:
fetch模块与copy模块正好相反,是从远程主机被控端传输文件到控制端的本地主机
#拉取文件
#localhost上的/tmp/2.txt文件拉取到/home/lion/localhost/tmp/2.txt
#目录为dest/[主机]/src
root@docker:~# ansible localhost -m fetch -a 'src=/tmp/2.txt dest=/home/lion'
localhost | SUCCESS => {
    "changed": true,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "/home/lion/localhost/tmp/2.txt",
    "md5sum": "d41d8cd98f00b204e9800998ecf8427e",
    "remote_checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "remote_md5sum": null
}
root@docker:~# ls /home/lion
1.txt  localhost
root@docker:~# ls /home/lion/localhost/
tmp
root@docker:~# ls /home/lion/localhost/tmp
2.txt

#多文件拷贝通过压缩包的方式
#添加/tmp文件下的所有文件到压缩包
root@docker:~# ansible localhost -a 'tar zcf tmp.tar.gz /tmp' 

#拉取压缩包文件
root@docker:~# ansible localhost -m fetch -a 'src=tmp.tar.gz dest=/tmp'
localhost | SUCCESS => {
    "changed": true,
    "checksum": "385215126769067cd0ea21c9a4461458f52d2344",
    "dest": "/tmp/localhost/tmp.tar.gz",
    "md5sum": "819dee6bdbd3fef5707637139d0dff6a",
    "remote_checksum": "385215126769067cd0ea21c9a4461458f52d2344",
    "remote_md5sum": null
}

file模块:
文件创建,设置属性,文件删除
#创建目录
root@docker:~# ansible localhost -m file -a 'path=/file/ state=directory'
localhost | SUCCESS => {
    "changed": true,
    "gid": 0,
    "group": "root",
    "mode": "0755",
    "owner": "root",
    "path": "/file/",
    "size": 4096,
    "state": "directory",
    "uid": 0
}
#创建文件 state=touch
root@docker:~# ansible localhost -m file -a 'path=/file/1.txt state=touch'
localhost | SUCCESS => {
    "changed": true,
    "dest": "/file/1.txt",
    "gid": 0,
    "group": "root",
    "mode": "0644",
    "owner": "root",
    "size": 0,
    "state": "file",
    "uid": 0
}
#创建link文件
root@docker:~# ansible localhost -m file -a 'src=/etc/fstab dest=/file/1.fstab state=link'
localhost | SUCCESS => {
    "changed": true,
    "dest": "/file/1.fstab",
    "gid": 0,
    "group": "root",
    "mode": "0777",
    "owner": "root",
    "size": 10,
    "src": "/etc/fstab",
    "state": "link",
    "uid": 0
}

#file模块修改文件属主和权限
root@docker:~# ansible localhost -m file -a 'path=/file/1.txt owner=root mode=777'
localhost | SUCCESS => {
    "changed": true,
    "gid": 0,
    "group": "root",
    "mode": "0777",
    "owner": "root",
    "path": "/file/1.txt",
    "size": 0,
    "state": "file",
    "uid": 0
}

#file删除文件,要确保文件没有正在被使用
root@docker:~# ansible localhost -m file -a 'path=/file/1.txt state=absent'    
localhost | SUCCESS => {
    "changed": true,
    "path": "/file/1.txt",
    "state": "absent"
}

#file查看文件属性
root@docker:~# ansible localhost -m stat -a 'path=/tmp/1.txt'

ansible应用模块:

apt模块
#安装软件
ansible localhost -m apt -a 'package=wget state=present'
#卸载软件
ansible localhost -m apt -a 'package=wget state=absent'
#更新软件
root@docker:~# ansible localhost -m apt -a 'package=wget state=latest'
#更新软件
#root@docker:~# ansible localhost -m apt -a 'upgrade=yes'

server模块使用:
#开机自启动,服务重启,启动,停止
#服务自启动
root@docker:~# ansible localhost -m service -a 'name=nginx enabled=yes'
#验证服务是否自启动
root@docker:~# ansible localhost -a 'systemctl is-enabled nginx'
localhost | SUCCESS | rc=0 >>
enabled                                     #服务已设置自启动

#服务启动
root@docker:~# ansible localhost -m service -a 'name=nginx state=started'
#服务停止
root@docker:~# ansible localhost -m service -a 'name=nginx state=stopped'
#服务重载
root@docker:~# ansible localhost -m service -a 'name=nginx state=reloaded'
#服务重启
root@docker:~# ansible localhost -m service -a 'name=nginx state=restarted'

debug模块:
#查看帮助信息,主要在playbook中使用
#显示默认debug信息
root@docker:~# ansible localhost -m debug
localhost | SUCCESS => {
    "msg": "Hello world!"
}
#设置debug信息输出
root@docker:~# ansible localhost -m debug -a 'msg='ss''
localhost | SUCCESS => {
    "msg": "ss"
}

ansible命令:

ansible-doc 
-t 指定特殊的插件类型来显示,默认为module
ansible-glaxy 
官方的模板网站
ansible-playbook #执行playbook脚本

ansible-vault
采用安全加密的方式来操作文件
#创建文件,输入密码和确认密码
root@docker:~# ansible-vault create 1.txt
#输入密码查看文件
root@docker:~# ansible-vault view 12.txt
#编辑文件
root@docker:~# ansible-vault edit 12.txt
#解密文件
root@docker:~# ansible-vault decrypt 12.txt
#加密文件
root@docker:~# ansible-vault encrypt 12.txt
#重新设置密钥
root@docker:~# ansible-vault rekey 12.txt

ansible-console
#可交互式终端执行命令

root@docker:~# ansible-console
Welcome to the ansible console.
Type help or ? to list commands.

root@all (1)[f:5]$ list        #主机数为1 fork数量为5
192.168.0.50
root@192.168.0.50 -k (1)[f:5]$ cd localhost  #进入主机列表中的主机
root@localhost (1)[f:5]$ ls  #就和一样执行命令
localhost | SUCCESS | rc=0 >>
12.txt
1.txt
localhost
tmp.tar.gz

root@localhost (1)[f:5]$
#apt模块安装wget软件
wget -o /文件/  /http路径
root@localhost (1)[f:5]$ apt package=wget state=present

ansible-inventory #获取主机清单信息的专用命令
#列出所有主机
root@docker:~# ansible-inventory --export --list

#列出所有主机
root@docker:~# ansible-inventory --vars --list

ansible-playbook模块:

playbook其实就是一个自动化执行ansible的命令方式罢了
使用ansible-playbook执行playbook文件,playbook文件
使用yaml语言编写
ansible在执行playbook的时候,执行效果具有幂等性(即一个命令执行多次与执行一次效果一样)

playbook文件内容组成

组成功能常见属性备注
Target section执行playbook的目标主机hosts remote_user order必备内容
Variable section执行playbook的需要的变量vars可选内容
Task sectionplaybook中的功能任务task(name 模块名称)必备内容
Handler section各种任务之间的关联关系handler(name 模块名称)可选内容

YAML语法:

YAML文件示例:

---
- hosts: 192.168.0.50
  remote_user: root
  tasks:
	- name: hello world
    command: wall 'hello world'
    tags:
      - hello world

YAML语法特点
大小写敏感
使用缩进表示层级关系
缩进时不允许使用TAB键,**只允许使用空格**
缩进的空格不重要,只要**相同层级的元素左侧对齐**即可
#表示注释
---表示分割符,是多个文件合并在一个文件中的分割效果

playbook文件实例:

1.安装httpd服务

2.开启httpd server

- hosts: 192.168.0.50,localhost
  remote_user: root
  tasks:
		- name: install httpd_package
      apt: package=nginx state=present
    - name: start service
			service: name=nginx state=started enabled=yes
root@docker:~# ansible-playbook test.yaml --syntax-check  #语法检测
root@docker:~# ansible-playbook test.yaml -C test.yaml #模拟执行
root@docker:~# ansible-playbook test.yaml -l#执行playbook文件
#-***l表示指定某一台主机执行playbook文件***
root@docker:~# netstat -tnulp                      #查看端口监听状态
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      14869/nginx: master
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      885/systemd-resolve

playbook host属性:

单个主机:
-hosts: 192.168.0.50
-hosts: master.ansible.com
多个主机:
-hosts: 192.168.0.*
-hosts: *.ansible.com
特定主机:(交集,并集,补集)
-hosts: web:mysql
-hosts: web:&mysql
-hosts: web:!mysql

playbook remote_user属性:

remote_user作用相当于-u(登录用户)+ -k(登录密码) + -K(提权密码)

默认登录用户(全局用户),用于所有task任务的默认执行用户

- hosts: localhost
  remote_user: root
或者
- hosts: localhost
  remote_user: otheruser

特殊登录用户:

- hosts: localhost
  remote_user: root
  tasks:
    - name test
      ping:
      remote_user: lion
      sudo: yes                 #sudo切换到root用户
      sudo_user: otheruser      #sudo切换到其他用户身份

#在task中sudoh和sudo_user只存在一个即可
#子任务在切换任务时候,要和全局默认的登录用户识别清楚

playbook tasks:

playbook文件只要就是实现各种功能,每一个tasks代表一个实现功能,playbook中的功能具有幂等特性

tasks根据顺序从上到下依次执行,如果中途某一个tasks出现错误,那么整个任务就会中断

tasks格式

tasks:
 - name: install_nginx #名称
   ping:               #执行的动作,也就是ansible模块命令,格式二
 - name: ping
   action: ping        #格式一

ansible模块的动作分为两种格式
1: action: 模块 具体动作
2: 模块: 具体动作
对于
shell和command模块来说具体动作就是可执行命令,对于其他模块,具体动作就是参数key=value格式
**在一个tasks中有多个action动作,只有最后一个action动作生效
如果想要执行多条命令,只有写多个子任务,也就是多个name
不过如果想要用shell模块执行多个命令,可以使用&;之类的命令连接符号,但是动作只能有一个
可以使用
-v,-vv,-vvvv参数查看执行过程**

playbook异常中断

playbook是从上到下依次执行的,如果中间出现异常就会中断执行,必须要解决中断
palybook的任务能否按顺序执行下去,主要是依据任务之间的指令的执行状态返回码来
判断的,正常执行为0否则为非0,可以通过||逻辑符号来强制让指令返回0
1:强制正确(|| /bin/true)
2:忽略错误,并抛出异常

- hosts: localhost
  remote_user: root
  tasks:
    - name: touch1
      shell: rm 4.txt || /bin/true    #4.txt文件不存在,所以会报错,/bin/true强制执行正确
 

playbook有一种专门在处理任务列表意外中断的属性值ignore_erros,用于判断任务是否异常

- hosts: localhost
  remote_user: root
  tasks:
    - name: touch1
      shell: rm 4.txt
      ignore_errors: True    #使用ignore_errors值为true,这样就可忽略异常然后抛出错误

playbook任务依赖:

playbook中存在一种依赖关系,分为监控器和触发器

notify: notify是task中的一个子属性,监控我们指定的动作涉及的内容是否发生了变化,比如配置文件

notify: handler名称

handlers: 接收到变化的动作后,自动执行的其他操作命令

应该先写handlers,然后再在notify中调用,要先写handlers

- hosts: localhost
  remote_user: root
  tasks:
    - name: copy4
      copy: src=/home/lion/4.txt dest=/tmp/4.txt
      notify: touch5                #监听器当4.txt出现修改时就会执行handlers中的touch5子任务
      ignore_errors: True
  handlers:                         #触发器
    - name: touch5
      shell: touch 5.txt

让一个notify执行多个触发器

- hosts: localhost
  remote_user: root
  tasks:
    - name: copy4
      copy: src=/home/lion/4.txt dest=/tmp/4.txt  
      notify:
        - touch5       #监听器当4.txt文件出现修改时,会同时触发touch5和touch6两个动作
        - touch6
      ignore_errors: True
  handlers:
    - name: touch5     #触发器动作
      shell: touch 5.txt
    - name: touch6
      shell: touch 6.txt

playbook标签:

通过playbook标签可以在playbook中执行某一个任务,或者让某一个主机执行任务


- hosts: localhost
  tags: localhost         #localhost主机
  remote_user: root
  tasks:
    - name: touch1
      shell: touch 7.txt
      tags: touch         #标签为touch,可以直接用-t参数来执行touch任务
      ignore_errors: True
    - name: touch2
      command: ls

root@docker:~# ansible-playbook 4.yaml --list-tags  #查看yaml文件中的标签

playbook: 4.yaml

  play #1 (localhost): localhost        TAGS: [localhost]
      TASK TAGS: [localhost, touch]

root@docker:~# ansible-playbook 4.yaml -t touch  #执行taskz中的标签任务

root@docker:~# ansible-playbook 4.yaml -t localhost #用localhost主机执行任务

#可以在不同的动作下添加同一个标签,这样就可以在执行同一个标签的同时在执行同一个标签的任务,
实现任务分割同时来增加文件层次

- hosts: localhost
  remote_user: root
  tasks:
    - name: touch1
      shell: touch 11.txt
      tags: touch
    - name: touch2
      shell: touch 10.txt
      tags: touch

root@docker:~# ansible-playbook 4.yaml -t touch   #同时执行touch1和touch2两个动作

playbook变量:

变量实现方式优先级编号
目标主机默认属性最低1
主机清单中的特有属性次低2
命令行定义的特定属性最高3
playbook通过vars定义的属性4
专用yaml文件定义的属性文件次高5
1<2<4<5<3
我们可以基于获取目标主机的主机属性来定制化yaml文件
可以用setup的方式来获取主机属性
root@docker:~# ansible localhost -m setup | grep ' "' | wc -l
847                                                         #默认情况下

#获取cpu型号  -o输出通过一行
root@docker:~# ansible localhost -m setup -a 'filter=ansible_processor' -o
localhost | SUCCESS => {"ansible_facts": {"ansible_processor": ["0", "AuthenticAMD", "AMD Athlon(tm) X4 870K Quad Core Processor"]}, "changed": false}

#playbook变量实例
通过ansible setup模块获取cpu信号并且写入cpu.txt
- hosts: localhost
  remote_user: root
  tasks:
    - name: get_cpu_name
      shell: echo '{{ ansible_processor[2] }}' >> cpu.txt
    - name: debug
      debug: msg='{{ ansible_processor[2] }}'      #通过debug模块打印出获取的变量,方便调试

#效果:
TASK [debug] 
ok: [localhost] => {
    "msg": "AMD Athlon(tm) X4 870K Quad Core Processor"  #debug模块显示
}
root@docker:~# cat cpu.txt                          #把cpu型号写入cpu.txt,查看cpu.txt
AMD Athlon(tm) X4 870K Quad Core Processor

play变量种类和优先级

在/etc/ansible/host文件中定义主机变量有两种样式:
公共变量和普通变量
**普通变量比公共变量的优先级高,如果普通变量不存在然后才会使用公共变量**

**普通变量**
root@docker:~#cat /etc/ansible/hosts
[nginx]
192.168.0.50 ansible_connection=local hostname=localhost       #定义普通变量,ansible定义为本地连接

root@docker:~# cat local.yaml      #打印获取到的hosts文件中的普通变量
- hosts: all
  tasks:
    - name: print_hostname
      debug: msg='{{ hostname }}'

#效果:
ok: [192.168.0.50] => {
    "msg": "localhost"            #打印了获取到的localhost变量
}

**公共变量**
/etc/ansible/hosts
****root@docker:~# tail -n 4 /etc/ansible/hosts 
[nginx]
192.168.0.50 ansible_connection=local hostname=localhost
[nginx:vars]                                          #公共变量
hostname=local

ok: [192.168.0.50] => {                      #由于普通变量优先级更高,所以变量仍然等于localhost
    "msg": "localhost"
}

root@docker:~# tail -n 4 /etc/ansible/hosts           
[nginx]
192.168.0.50 ansible_connection=local            #删除了普通变量,所以所以hostname等于local
[nginx:vars]                                      #[主机组:vars]定义变量
hostname=local

ok: [192.168.0.50] => {
    "msg": "local"
}

**playbook命令行变量:**

通过-e 参数在命令行指定变量,所有变量用单引号括住,多个变量用空格隔开
****root@docker:~# ansible all -e host='localhost' -m debug -a "msg='this is {{ host }}'"
192.168.0.50 | SUCCESS => {
    "msg": "this is localhost"
}

通过命令行定义多个变量:
root@docker:~# ansible all -e 'host=localhost user=lion' -m debug -a "msg='this is {{ host }} user is {{ user }}'"
192.168.0.50 | SUCCESS => {
    "msg": "this is localhost user is lion"
}

**playbook yaml文件中定义变量**:
通过vars列表的方式来定义变量
root@docker:~# cat var.yaml 
- hosts: all
  remote_user: root
  vars:
    - user: root
    - hostname: master
  tasks:
    - name: print_user_hostname
      debug: msg='user is {{ user }} hostname is {{ hostname }}'

ok: [192.168.0.50] => {
    "msg": "user is root hostname is master"     #打印出定义的变量
}

#命令行的变量优先级高于yaml文件中的变量
root@docker:~# ansible-playbook -e user='lion' -C var.yaml 

TASK [print_user_hostname] 
ok: [192.168.0.50] => {
    "msg": "user is lion hostname is master"   #打印出命令行中定义的user变量
}

PLAY RECAP
192.168.0.50   : ok=2    changed=0    unreachable=0    failed=0

ansible专用yaml变量文件
可以在专用的yaml文件中定义统一变量,可以应用多个变量文件,推荐用绝对路径,一般情况下
放到和yaml执行文件的同一路径下,使用相对路径

**vars.yaml专用变量文件**
root@docker:~# cat vars.yaml 
user: lion
hostname: localhost

root@docker:~# cat var.yaml 
- hosts: all
  remote_user: root
  vars_files:
    - vars.yaml       #变量文件
  vars:               #playbook中定义的变量     
    - user: root
    - hostname: master
  tasks:
    - name: print_user_hostname
      debug: msg='user is {{ user }} hostname is {{ hostname }}'  

执行结果:
变量文件中的变量的优先级大于yaml文件中的变量
ok: [192.168.0.50] => {
    "msg": "user is lion hostname is localhost"
}

**#显示root组是否存在**
root@docker:~# getent group | grep root
root:x:0:

ansible模板模块:

ansible可以通过jinja2模块来实现主机配置文件的灵活定制
使用jinja2模板语言来定制配置文件,j2模板文件推荐使用相对目录,文件路径在playbook文件下的
template目录下

root@docker:~# cat template.yaml 
- hosts: all
  remote_user: root
  vars:
   - cpu: 'x4 870k'
  tasks:
   - name: get_cpu_model
     template: src=./template/cpu.txt.j2 dest=/tmp/cpu.txt   #使用j2模板文件,生成/tmp/cpu.txt文件

root@docker:~# cat /tmp/cpu.txt    
#cpu model
CPU Model is x4 870k

#模板语法条件判断
**when支持算数运算符和比较运算符和逻辑运算符**

- hosts: all
  remote_user: root
  tasks:
    - name: AMD
      debug: msg='CPU is AMD'
      when: ansible_processor[1] == 'AuthenticAMD'   #当cpu为AMD时
    - name: INTEL
      debug: msg='CPU is INTEL'
      when: ansible_processor[1] != 'AuthenticAMD'   #当cpu不是AMD时

#执行结果:
ok: [192.168.0.50] => {
    "msg": "CPU is AMD"
}

TASK [INTEL]
skipping: [192.168.0.50]         #跳过执行

#模板迭代执行
with_itmes可以是列表和字典
一个任务中一个action动作只能执行一个,所以可以通过类似于循环的方式通过一个action执行多个动作

- hosts: all
  remote_user: root
  tasks:
    - name: print_process_info
      debug: msg='{{ item }}'      #把item替换成with_items列表中的每一项,with_items可以是列表和字典
      with_items:
         - '{{ ansible_processor[0] }}'
         - '{{ ansible_processor[1] }}'
         - '{{ ansible_processor[2] }}'

打印结果:
ok: [192.168.0.50] => (item=None) => {
    "msg": "0"
}
ok: [192.168.0.50] => (item=None) => {
    "msg": "AuthenticAMD"                     #打印cpu种类
}
ok: [192.168.0.50] => (item=None) => {
    "msg": "AMD Athlon(tm) X4 870K Quad Core Processor"  #打印cpu型号
}

#迭代和流程流程判断结合

- hosts: all
  remote_user: root
  tasks:
    - name: AMD
      debug: msg='CPU is AMD'
      when: ansible_processor[1] == 'AuthenticAMD'
    - name: INTEL
      debug: msg='CPU is INTEL'
      when: ansible_processor[1] != 'AuthenticAMD'
    - name: AMD_OR_INTEL
      debug: msg='{{ item }}'
      when: ansible_processor[1] == 'AuthenticAMD'   #当when成立时,才执行迭代操作
      with_items:
         - the cpu is AMD_CPU
         - AMD_yyds!

#迭代进阶
使用多值迭代,通过类似python字典的方式来,使用多个字段来多值迭代

- hosts: all
  remote_user: root
  tasks:
    - name: AMD_OR_INTEL
      debug: msg='the cpu is {{ item.model }} {{ item.cpu_info }}'
      with_items:
        - { model: 'AMD', cpu_info: 'AMD YYDS!!' }

#模板流程控制
{{}} #用于表达式,变量
{% %} #流程控制,if,for
{# #} #用于注释内容

条件控制语句
{% if 条件 %} 执行语句 {% endif %}
条件判断 {%if 变量 is denfind %}
循环语句
{% for 条件 %} 执行语句 {% end for %}
fo循环 {% for 变量 in 变量列表 %}
只有当条件成立时,执行语句才会执行

#模板文件:
#cpu_info
{%if cpu is defined %}
the cpu is {{ cpu }};
{% endif %}

#yaml文件

- hosts: all
  remote_user: root
  tasks:
    - name: cpu_info
      template: src=./template/cpuz.txt.j2 dest=/tmp/cpuz.txt

#/etc/ansible/hosts
192.168.0.50 ansible_connection=local cpu=AMD  #定义了cpu变量

#cpu_info for循环实例
yaml文件
- hosts: all
  remote_user: root
  vars:
    cpu_info:
     - INTEL
     - AMD  
  tasks:
    - name: cpu_info
      template: src=./template/cpus.txt.j2 dest=/tmp/cpus.txt

模板文件
#cpu_info
{% for cpu in cpu_info %}
{%if cpu is defined %}
the cpu is {{ cpu }};
{% endif %}
{% endfor%}

执行结果:
cpus.txt
root@docker:~# cat /tmp/cpus.txt 
#cpu_info
the cpu is INTEL;
the cpu is AMD;

ansible role:

ansible role角色
ansible role可以将不同的功能的yaml文件整合在一起,比如tasks,template,file,等不同功能的模块
分割成单个yaml文件,然后再在入口的yaml(mian.yaml)文件中导入其他的功能模块(include)。这样就可以实现更加灵活的管理和增加功能,实现模块化管理。

role不适用中小场景管理环境,不复杂的场景不必应用role,role适用于大型业务管理

#创建roles文件夹

root@docker:~# mkdir test_role
root@docker:~# cd test_role/
root@docker:~/test_role# mkdir roles
root@docker:~/test_role# mkdir roles/role_cpu
root@docker:~/test_role# tree
.
└── roles
    └── role_cpu

2 directories, 0 files
root@docker:~/test_role# mkdir roles/role_cpu/{tasks,vars}  #创建tasks,vars模块文件夹
root@docker:~/test_role# tree
.
└── roles
    └── role_cpu
        ├── tasks
        └── vars

4 directories, 0 files

root@docker:~/test_role# touch roles/role_cpu/tasks/{main,debug}.yaml #创建子任务的yaml文件
root@docker:~/test_role# tree
.
└── roles
    └── role_cpu
        ├── tasks
        │   ├── debug.yaml
        │   └── main.yaml
        └── vars

4 directories, 2 files
root@docker:~/test_role# touch roles/role_cpu/vars/main.yaml  #创建变量main目录
root@docker:~/test_role# tree
.
└── roles
    └── role_cpu
        ├── tasks
        │   ├── debug.yaml
        │   └── main.yaml
        └── vars
            └── main.yaml

4 directories, 3 files

#test_role.yaml
- hosts: all
  remote_user: root
  roles:
    - role: role_cpu         #引入角色,标准写法
------------------------------------------------------------------------------------
- hosts: all
  remote_user: root
  roles:
    - role_cpu            #引入角色,简写

#tasks/main.yaml
- include: debug.yaml    #导入其他task模块,在项目中使用相对路径以main.yaml目录为准
													(./main.yaml 当前目录,../ main.yaml的上一级目录)

#tasks/debug.yaml
- name: cpu_info
  debug: msg={{ ansible_processor[1] }}    #打印出cpu型号
- name: os
  debug: msg={{ systeminfo[0] }}    #在var/main.yaml中定义的变量
- name: hostname
  debug: msg='hostname is {{ ansible_facts['nodename'] }}'  #打印hostname

#vars/main.yaml

systeminfo:
  - '{{ ansible_os_family }}'  #变量定于格式必须时变量字典的形式,否则会报错

执行结果:
root@docker:~/test_role# ansible-playbook -C test_role.yaml
ok: [192.168.0.50]

TASK [role_cpu : cpu_info] 
    "msg": "AuthenticAMD"
}

TASK [role_cpu : os] 
ok: [192.168.0.50] => {
    "msg": "Debian"
}

TASK [role_cpu : hostname]
ok: [192.168.0.50] => {
    "msg": "hostname is docker"
}

#可以在role主yaml文件中定义变量,执行role时引用定义的变量
{ role: role角色目录,变量:变量值}

- hosts: all
  remote_user: root
  roles:
   - { role: role_cpu, user: root }

#在debug目录中使用user变量
- name: cpu_info
  debug: msg={{ ansible_processor[1] }}
- name: os
  debug: msg={{ systeminfo[0] }}
- name: hostname
  debug: msg='hostname is {{ ansible_facts['nodename'] }}'
- name: user
  debug: msg='{{ user }}'   #使用定义的变量

#roles结合条件判断,只有当when条件判断成立时,才可以定义变量,才会执行roles角色tasks
#执行roles角色
- hosts: all
  remote_user: root
  roles:
    - { role: role_cpu, user: root, when: ansible_facts.nodename == 'docker' }

#不执行roles角色
- hosts: all
  remote_user: root
  roles:
    - { role: role_cpu, user: root, when: ansible_facts.nodename == 'doc' }

#定义多个role角色,用标签的方式调用不同的roles,执行不同的tasks
- hosts: all
  remote_user: root
  roles:
    - { role: role_cpu, user: root, tags: ['cpu_info', 'localhost'] }

#用-t运行指定标签的role,用-l指定执行的主机
root@docker:~/test_role# ansible-playbook -C test.yaml -t cpu_info -l all

ansible示例:

ansbile部署prometheus,并创建prometheus service,设置开机启动

prometheus.yml:

- hosts: localhost
  remote_user: root
  vars:
    - version: 2.37.0         #下载的版本
  tasks:
    - name: downlaod prometheus
      command: wget https://github.com/prometheus/prometheus/releases/download/v{{ version }}/prometheus-{{ version }}.linux-amd64.tar.gz
    - debug: msg='wget https://github.com/prometheus/prometheus/releases/download/v{{ version }}/prometheus-{{ version }}.linux-amd64.tar.gz'

    - name: tar prometheus
      shell: tar -xzvf prometheus-{{ version }}.linux-amd64.tar.gz -C /usr/local/

    - name: link 
      shell: ln -sv /usr/local/prometheus-{{ version }}.linux-amd64/prometheus /usr/local/bin/prometheus

    - name: touch prometheus service
      template: src=./prometheus.service dest=/etc/systemd/system/prometheus.service
      tags: prometheus_service

    - name: start prometheus service
      shell: systemctl start prometheus.service

    - name: enable prometheus service
      shell: systemctl enable prometheus.service

prometheus.service文件模板:

[Unit]
Description=prometheus

[Service]
Restart=on-failure
ExecStart=/usr/local/bin/prometheus --config.file=/usr/local/prometheus-{{ version }}.linux-amd64/prometheus.yml

[Install]
WantedBy=multi-user.target
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值