标签:.so interval ast hosts queue date 3.3 upd 升级
ansible小结(一)ansible的安装
常用的自动化运维工具:
CFengine 最早的自动化运维工具
Chef
Puppet --基于Ruby开发,采用C/S架构,扩展性强,基于SSL认证 (远程命令执行比较短板)
SaltStack --基于Python开发,采用C/S架构,比Puppet更轻量级,配置YMAL,配置更为简单
Ansible --基于Python开发,无客户端,更轻量级,配置语言也是采用YMAL,无守护进程 (目前市场上运用较多的还是puppet和saltstack)
一、简介
Ansible 是一个配置管理和应用部署工具,功能类似于目前业界的配置管理工具 Chef,Puppet,Saltstack。Ansible 是通过 Python 语言开发。Ansible 平台由 Michael DeHaan 创建,他同时也是知名软件 Cobbler 与 Func 的作者。Ansible 的第一个版本发布于 2012 年 2 月。Ansible 默认通过 SSH 协议管理机器,所以 Ansible 不需要安装客户端程序在服务器上。您只需要将 Ansible 安装在一台服务器,在 Ansible 安装完后,您就可以去管理控制其它服务器。不需要为它配置数据库,Ansible 不会以 daemons 方式来启动或保持运行状态。Ansible 可以实现以下目标:
根据 Ansible 官方提供的信息,当前使用 Ansible 的用户有:evernote、rackspace、NASA、Atlassian、twitter 等。
注:以上简介来自于ibm developerworks 中国的介绍。
二、Ansible的安装
以centos为例,默认在源里没有ansible,不过在fedora epel源里有ansible,配置完epel 源后,可以直接通过yum 进行安装。这里以centos6.8为例:
yum install http://mirrors.sohu.com/fedora-epel/6/x86_64/epel-release-6-8.noarch.rpm
yum install ansible
2、apt-get安装
在ubuntu及其衍生版中,可以通过增加ppa源进行apt-get安装,具体如下:
sudo apt-get install software-properties-common sudo apt-add-repository ppa:ansible/ansible sudo apt-get update sudo apt-get install ansible
3、源码安装
源码安装需要python2.6以上版本,其依赖模块paramiko、PyYAML、Jinja2、httplib2、simplejson、pycrypto模块,以上模块可以通过pip或easy_install 进行安装,不过本部分既然提到的是源码安装,主要针对的无法上外网的情况下,可以通过pypi 站点搜索以上包,下载后通过python setup.py install 进行安装。
最后通过github或pypi上下载ansible源码包,通过python setup.py install 安装即可。由于安装过程相对简单,这里略过,主要介绍安装后,可能遇到的问题。
python setup.py install libyaml is not found or a compiler error: forcing --without-libyaml (if libyaml is installed correctly, you may need to specify the option --include-dirs or uncomment and modify the parameter include_dirs in setup.cfg) running install_lib running install_egg_info Removing /usr/lib64/python2.6/site-packages/PyYAML-3.11-py2.6.egg-info Writing /usr/lib64/python2.6/site-packages/PyYAML-3.11-py2.6.egg-info
在centos6.8系统中,可以通过yum -y install libyaml 包解决,或者从ISO文件中提供该包,通过rpm -ivh进行安装。
b、安装完ansible是,报错如下:
[root@361way.com ansible-1.9.1]# ansible -h Traceback (most recent call last): File "/usr/local/src/ansible-devel/bin/ansible", line 36, in <module> from ansible.runner import Runner File "/usr/local/src/ansible-devel/lib/ansible/runner/__init__.py", line 62, in <module> from Crypto.Random import atfork File "/usr/lib64/python2.6/site-packages/Crypto/Random/__init__.py", line 29, in <module> from Crypto.Random import _UserFriendlyRNG File "/usr/lib64/python2.6/site-packages/Crypto/Random/_UserFriendlyRNG.py", line 38, in <module> from Crypto.Random.Fortuna import FortunaAccumulator File "/usr/lib64/python2.6/site-packages/Crypto/Random/Fortuna/FortunaAccumulator.py", line 39, in <module> import FortunaGenerator File "/usr/lib64/python2.6/site-packages/Crypto/Random/Fortuna/FortunaGenerator.py", line 34, in <module> from Crypto.Util.number import ceil_shift, exact_log2, exact_div File "/usr/lib64/python2.6/site-packages/Crypto/Util/number.py", line 56, in <module> if _fastmath is not None and not _fastmath.HAVE_DECL_MPZ_POWM_SEC: AttributeError: ‘module‘ object has no attribute ‘HAVE_DECL_MPZ_POWM_SEC‘
import paramiko包时,报错如下:
>>> import paramiko Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python2.6/site-packages/paramiko/__init__.py", line 69, in <module> from transport import randpool, SecurityOptions, Transport File "/usr/lib/python2.6/site-packages/paramiko/transport.py", line 32, in <module> from paramiko import util File "/usr/lib/python2.6/site-packages/paramiko/util.py", line 32, in <module> from paramiko.common import * File "/usr/lib/python2.6/site-packages/paramiko/common.py", line 98, in <module> from rng import StrongLockingRandomPool File "/usr/lib/python2.6/site-packages/paramiko/rng.py", line 22, in <module> from Crypto.Util.randpool import RandomPool as _RandomPool File "/usr/lib64/python2.6/site-packages/Crypto/Util/randpool.py", line 30, in <module> import Crypto.Random File "/usr/lib64/python2.6/site-packages/Crypto/Random/__init__.py", line 29, in <module> from Crypto.Random import _UserFriendlyRNG File "/usr/lib64/python2.6/site-packages/Crypto/Random/_UserFriendlyRNG.py", line 38, in <module> 19. from Crypto.Random.Fortuna import FortunaAccumulator 20. File "/usr/lib64/python2.6/site-packages/Crypto/Random/Fortuna/FortunaAccumulator.py", line 39, in <module> 21. import FortunaGenerator 22. File "/usr/lib64/python2.6/site-packages/Crypto/Random/Fortuna/FortunaGenerator.py", line 34, in <module> 23. from Crypto.Util.number import ceil_shift, exact_log2, exact_div 24. File "/usr/lib64/python2.6/site-packages/Crypto/Util/number.py", line 56, in <module> 25. if _fastmath is not None and not _fastmath.HAVE_DECL_MPZ_POWM_SEC: 26.AttributeError: ‘module‘ object has no attribute ‘HAVE_DECL_MPZ_POWM_SEC‘
经网上查找,确认为pycrypto包安装时依赖的GMP版本不对的问题,具体可以通过以下步骤验证:
[root@361way.com pycrypto-2.6.1]# python setup.py build running build running build_py running build_ext running build_configure warning: GMP or MPIR library not found; Not building Crypto.PublicKey._fastmath.
解决方法:
打开 /usr/lib64/python2.6/site-packages/Crypto/Util/number.py 文件,可以 看到 56 行上的注释说明,要求 libgmp 为 v5 以上版本。而系统现有版本为 4.1.4,把以下两行暂时注释掉,Ansible 执行正常。
if _fastmath is not None and not _fastmath.HAVE_DECL_MPZ_POWM_SEC: _warn("Not using mpz_powm_sec. You should rebuild using libgmp >= 5 to avoid timing attack vulnerability.", PowmInsecureWarning)
不过,此方法只是临时加以解决,更好的方式是去将 libgmp 升级到符合要求的版本
c、执行时报错
[root@361way.com src]# ansible test -m raw -a ‘uptime‘ 10.212.52.14 | FAILED => to use the ‘ssh‘ connection type with passwords, you must install the sshpass program 10.212.52.16 | FAILED => to use the ‘ssh‘ connection type with passwords, you must install the sshpass program
安装sshpass程序。默认源里没有,我这里选择直接从sohu源里下载安装。
三、Ansible的配置与验证
这里以pypi上下载的源码内有一个examles包,可以将使用该示例文件做为默认配置,具体如下:
[root@361way.com ansible-1.9.1]# mkdir -p /etc/ansible [root@361way.com ansible-1.9.1]# cp -rp examples/* /etc/ansible/ [root@361way.com ansible-1.9.1]# cd /etc/ansible/
使用默认示例配置文件后,编辑/etc/ansible/hosts文件,通过以下方式验证ansible是否可用:
[root@361way.com ~]# cat /etc/ansible/hosts [test] 10.212.52.252 ansible_ssh_user=root ansible_ssh_pass=361way.com 10.212.52.14 ansible_ssh_user=root ansible_ssh_pass=abc123 10.212.52.16 ansible_ssh_user=root ansible_ssh_pass=91it.org
以上的配置中,我配置了一个test组,该组下有三台主机,三台都使用root验证,三台的密码分别是361way.com、abc123、91it.org 。
注:后面的用户和密码项是非必须的,在配置key认证的情况下,不使用密码也可以直接操作 。未使用key的,也可以在ansible通过 -k参数在操作前询问手动输入密码。
[root@361way.com ~]# ansible test -a ‘uptime‘ 10.212.52.252 | success | rc=0 >> 18:01pm up 21 days 3:24, 3 users, load average: 0.39, 0.38, 0.35 10.212.52.16 | success | rc=0 >> 18:09pm up 329 days 1:01, 2 users, load average: 0.08, 0.03, 0.05 10.212.52.14 | success | rc=0 >> 18:08pm up 329 days 0:23, 2 users, load average: 0.06, 0.06, 0.05
执行以上指令后,有结果输出,证明安装成功。
ansible小结(二)ansible架构
一、Ansible基本架构
Ansible 是一个模型驱动的配置管理器,支持多节点发布、远程任务执行。默认使用 SSH 进行远程连接。无需在被管理节点上安装附加软件,可使用各种编程语言进行扩展。
上图为ansible的基本架构,从上图可以了解到其由以下部分组成:
二、ansible工作原理
以上是从网上找到的两张ansible工作原理图,两张图基本都是在架构图的基本上进行的拓展。从上面的图上可以了解到:
1、管理端支持local 、ssh、zeromq 三种方式连接被管理端,默认使用基于ssh的连接---这部分对应基本架构图中的连接模块;
2、可以按应用类型等方式进行Host Inventory(主机群)分类,管理节点通过各类模块实现相应的操作---单个模块,单条命令的批量执行,我们可以称之为ad-hoc;
3、管理节点可以通过playbooks 实现多个task的集合实现一类功能,如web服务的安装部署、数据库服务器的批量备份等。playbooks我们可以简单的理解为,系统通过组合多条ad-hoc操作的配置文件 。
三、ansible的七个命令
安装完ansible后,发现ansible一共为我们提供了七个指令:ansible、ansible-doc、ansible-galaxy、ansible-lint、ansible-playbook、ansible-pull、ansible-vault 。这里我们只查看usage部分,详细部分可以通过 “指令 -h” 的方式获取。
1、ansible
[root@localhost ~]# ansible -h Usage: ansible <host-pattern> [options]
ansible是指令核心部分,其主要用于执行ad-hoc命令,即单条命令。默认后面需要跟主机和选项部分,默认不指定模块时,使用的是command模块。如:
[root@localhost ~]# ansible 127.0.0.1 -a ‘date‘ 127.0.0.1 | SUCCESS | rc=0 >> Sun May 28 11:00:40 CST 2017
默认使用的模块是可以在ansible.cfg 中进行修改的。ansible命令下的参数部分解释如下
参数: -a ‘Arguments‘, --args=‘Arguments‘ 命令行参数 -m NAME, --module-name=NAME 执行模块的名字,默认使用 command 模块,所以如果是只执行单一命令可以不用 -m参数 -i PATH, --inventory=PATH 指定库存主机文件的路径,默认为/etc/ansible/hosts. -u Username, --user=Username 执行用户,使用这个远程用户名而不是当前用户 -U --sud-user=SUDO_User sudo到哪个用户,默认为 root -k --ask-pass 登录密码,提示输入SSH密码而不是假设基于密钥的验证 -K --ask-sudo-pass 提示密码使用sudo -s --sudo sudo运行 -S --su 用 su 命令 -l --list 显示所支持的所有模块 -s --snippet 指定模块显示剧本片段 -f --forks=NUM 并行任务数。NUM被指定为一个整数,默认是5。 #ansible testhosts -a "/sbin/reboot" -f 10 重启testhosts组的所有机器,每次重启10台 --private-key=PRIVATE_KEY_FILE 私钥路径,使用这个文件来验证连接 -v --verbose 详细信息 all 针对hosts 定义的所有主机执行 -M MODULE_PATH, --module-path=MODULE_PATH 要执行的模块的路径,默认为/usr/share/ansible/ --list-hosts 只打印有哪些主机会执行这个 playbook 文件,不是实际执行该 playbook 文件 -o --one-line 压缩输出,摘要输出.尝试一切都在一行上输出。 -t Directory, --tree=Directory 将内容保存在该输出目录,结果保存在一个文件中在每台主机上。 -B 后台运行超时时间 -P 调查后台程序时间 -T Seconds, --timeout=Seconds 时间,单位秒s -P NUM, --poll=NUM 调查背景工作每隔数秒。需要- b -c Connection, --connection=Connection 连接类型使用。可能的选项是paramiko(SSH),SSH和地方。当地主要是用于crontab或启动。 --tags=TAGS 只执行指定标签的任务 例子:ansible-playbook test.yml --tags=copy 只执行标签为copy的那个任务 --list-hosts 只打印有哪些主机会执行这个 playbook 文件,不是实际执行该 playbook 文件 --list-tasks 列出所有将被执行的任务 -C, --check 只是测试一下会改变什么内容,不会真正去执行;相反,试图预测一些可能发生的变化 --syntax-check 执行语法检查的剧本,但不执行它 -l SUBSET, --limit=SUBSET 进一步限制所选主机/组模式 --limit=192.168.0.15 只对这个ip执行 --skip-tags=SKIP_TAGS 只运行戏剧和任务不匹配这些值的标签 --skip-tags=copy_start -e EXTRA_VARS, --extra-vars=EXTRA_VARS 额外的变量设置为键=值或YAML / JSON #cat update.yml --- - hosts: {{ hosts }} remote_user: {{ user }} .............. #ansible-playbook update.yml --extra-vars "hosts=vipers user=admin" 传递{{hosts}}、{{user}}变量,hosts可以是 ip或组名 -l,--limit 对指定的 主机/组 执行任务 --limit=192.168.0.10,192.168.0.11 或 -l 192.168.0.10,192.168.0.11 只对这个2个ip执行任务
2、ansible-doc
[root@localhost ~]# ansible-doc -h Usage: ansible-doc [options] [module...] Options: -a, --all Show documentation for all modules -h, --help show this help message and exit -l, --list List available modules -M MODULE_PATH, --module-path=MODULE_PATH specify path(s) to module library (default=None) -s, --snippet Show playbook snippet for specified module(s) -v, --verbose verbose mode (-vvv for more, -vvvv to enable connection debugging) --version show program‘s version number and exit
该指令用于查看模块信息,常用参数有两个-l 和 -s ,具体如下
//列出所有已安装的模块 # ansible-doc -l //查看具体某模块的用法,这里如查看command模块 # ansible-doc -s command
3、ansible-galaxy
ansible-galaxy 指令用于方便的从https://galaxy.ansible.com/ 站点下载第三方扩展模块,我们可以形象的理解其类似于centos下的yum、python下的pip或easy_install 。如下示例:
[root@localhost ~]# ansible-galaxy install aeriscloud.docker - downloading role ‘docker‘, owned by aeriscloud - downloading role from https://github.com/AerisCloud/ansible-docker/archive/v1.0.0.tar.gz - extracting aeriscloud.docker to /etc/ansible/roles/aeriscloud.docker - aeriscloud.docker was installed successfully
这个安装了一个aeriscloud.docker组件,前面aeriscloud是galaxy上创建该模块的用户名,后面对应的是其模块。在实际应用中也可以指定txt或yml 文件进行多个组件的下载安装。这部分可以参看官方文档。
4、ansible-lint
ansible-lint是对playbook的语法进行检查的一个工具。用法是ansible-lint playbook.yml 。
5、ansible-playbook
该指令是使用最多的指令,其通过读取playbook 文件后,执行相应的动作,这个后面会做为一个重点来讲。
该指令使用需要谈到ansible的另一种模式---pull 模式,这和我们平常经常用的push模式刚好相反,其适用于以下场景:你有数量巨大的机器需要配置,即使使用非常高的线程还是要花费很多时间;你要在一个没有网络连接的机器上运行Anisble,比如在启动之后安装。这部分也会单独做一节来讲。
ansible-vault主要应用于配置文件中含有敏感信息,又不希望他能被人看到,vault可以帮你加密/解密这个配置文件,属高级用法。主要对于playbooks里比如涉及到配置密码或其他变量时,可以通过该指令加密,这样我们通过cat看到的会是一个密码串类的文件,编辑的时候需要输入事先设定的密码才能打开。这种playbook文件在执行时,需要加上 –ask-vault-pass参数,同样需要输入密码后才能正常执行。具体该部分可以参查官方博客。
ansible小结(三)Inventory与Patterns
Ansible的Inventory文件,可以理解为saltstack中的salt-key中的所有minion的列表以及用户自定义的nodegroup的概念,默认情况下这个文件是/etc/ansible/hosts ,后面还会讲到Dynamic Inventory,本节主要讲静态主机群部分。Patterns(模式)部分我们可以理解为正则表达式,通过Patterns我们可以匹配Inventory分组中的部分主机。
一、Hosts and Groups(主机与组)
对于/etc/ansible/hosts最简单的定义格式像下面:
1、简单的主机和组
mail.361way.com [webservers] web1.361way.com web2.361way.com [dbservers] db1.361way.com db2.361way.com
a、中括号中的名字代表组名,你可以根据你自己的需求将庞大的主机分成具有标识的组,如上面我分了两个组webservers和dbservers组;
b、主机(hosts)部分可以使用域名、主机名、IP地址表示;当然使用前两者时,也需要主机能反解析到相应的IP地址,一般此类配置中多使用IP地址;
2、端口与别名
如果某些主机的SSH运行在自定义的端口上,ansible使用Paramiko进行ssh连接时,不会使用你SSH配置文件中列出的端口,但是如果修改ansible使用openssh进行ssh连接时将会使用:
192.168.0.10:5309
假如你想要为某些静态IP设置一些别名,类似于SaltStack中minion配置文件中id的参数配置。你可以这样做:
jumper ansible_ssh_port = 5555 ansible_ssh_host = 192.168.1.50
3、指定主机范围
[webservers] www[01:50].361way.com [databases] db-[a:f].91it.org
上面指定了从web1到web50,webservers组共计50台主机;databases组有db-a到db-f共6台主机
4、使用主机变量
以下是Hosts部分中经常用到的变量部分
ansible_ssh_host # 要连接的主机名 ansible_ssh_port # 端口号默认是22 ansible_ssh_user # ssh连接时默认使用的用户名 ansible_ssh_pass # ssh连接时的密码 ansible_sudo_pass # 使用sudo连接用户是的密码 ansible_ssh_private_key_file # 秘钥文件如果不想使用ssh-agent管理时可以使用此选项 ansible_shell_type # shell的类型默认sh ansible_connection # SSH 连接的类型: local , ssh , paramiko在 ansible 1.2 之前默认是 paramiko ,后来智能选择,优先使用基于 ControlPersist 的 ssh (支持的前提) ansible_python _ interpreter #用来指定 python 解释器的路径,同样可以指定ruby 、perl 的路径
示例
[test] 10.212.52.252 ansible_ssh_user=root ansible_ssh_pass=‘361way.com‘ 10.212.52.14 ansible_ssh_user=test1 ansible_ssh_pass=‘91it.org‘ 10.212.52.16 ansible_ssh_user=test2 ansible_ssh_port=7788 ansible_ssh_pass=‘123456‘
上面的示例中指定了三台主机,三台主机的用密码分别是361way.com、91it.org、123456,指定的ssh连接的用户名分别为root、test1、test2,ssh 端口分别为22、22、7788 ,这样在ansible命令执行的时候就不用再指令用户和密码等了,执行结果如下:
[root@361way.com ~]# ansible test -a ‘uptime‘ 10.212.52.252 | success | rc=0 >> 01:34am up 23 days 10:57, 2 users, load average: 0.42, 0.39, 0.41 10.212.52.16 | success | rc=0 >> 01:41am up 331 days 8:33, 2 users, load average: 0.00, 0.01, 0.05 10.212.52.14 | success | rc=0 >> 01:40am up 331 days 7:55, 2 users, load average: 0.09, 0.03, 0.05
5、组内变量
变量也可以通过组名,应用到组内的所有成员:
[test] host1 host2 [test:vars] ntp_server=ntp.361way.com proxy=proxy.361way.com
上面test组中包含两台主机,通过对test组指定vars变更,相应的host1和host2相当于相应的指定了ntp_server和proxy变量参数值 。
[hangzhou] host1 host2 [jiaxing] host2 host3 [zhejiang:children] hangzhou jiaxing [zhejiang:vars] some_server=foo.southeast.example.com halon_system_timeout=30 self_destruct_countdown=60 escape_pods=2 [china:children] zhejiang henan shandong hebei
如上面的示例中,我指定了杭州组我有host1、hosts2;嘉兴组我有host3、host4主机;我又指定了一个组浙江组,同时包含杭州和嘉兴;同时为该组内的所有主机指定了四个vars变量。后面我又设定了一个组中国组,包含浙江、河南、山东、河北
注:由于vars变量在ansible ad-hoc部分中基本用不到,主要用在ansible-playbook中,后面的章节部分也会提到。
以上部分基本上是完全按照官方Inventory 文档部分进行了翻译和微小的变化。英文感觉还可以的可以直接查看官方页面。
二、Patterns(主机与组正则匹配部分)
把Patterns 直接理解为正则实际是不完全准确的,正常的理解为patterns意味着在ansible中管理哪些主机,也可以理解为,要与哪台主机进行通信。在探讨这个问题之前我们先看下ansible的用法:
ansible <pattern_goes_here> -m <module_name> -a <arguments>
直接上一个示例:
ansible webservers -m service -a "name=httpd state=restarted"
这里是对webservers 组或主机重启httpd服务 ,其中webservers 就是Pattern部分。而之所以上面我说Pattern(模式)可以理解为正则,主要针对下面经常用到的用法而言的。
1、表示所有的主机可以使用all 或 *
2、通配符与逻辑或
利用通配符还可以指定一组具有规则特征的主机或主机名,冒号表示or---逻辑或
one.361way.com one.361way:two.361way.com 192.168.1.50 192.168.1.*
当然,这里的*通配符也可以用在前面,如:
*.361way.com *.com
上面的用法,在多个组之间同样适用 ,如:
webservers webservers:dbservers //表示两个组中所有的主机
3、逻辑非与逻辑and
当然你可以做出非的表达式,例如,目标主机必须在组webservers但不在phoenix组中
webserver:!phoenix
你还可以做出交集的表达式,例如,目标主机必须即在组webservers中又在组staging中
webservers:&staging
一个更复杂的示例:
webserver:dbservers:&staging:!phoenix
上面这个复杂的表达式最后表示的目标主机必须满足:在webservers或者dbservers组中,必须还存在于staging组中,但是不在phoenix组中。这些可以看作是SaltStack中Compound matchers
4、混合高级用法
*.361way.com:*.org
还可以在开头的地方使用”~”,用来表示这是一个正则表达式:
~(web|db).*\.91it\.org
到这里估计你应该用能明白为什么前面我会提到Patterns 可以理解为正则的原因了。最后部分给两个ansible-playbook中具体可能用的用法:
a、在ansible-palybook命令中,你也可以使用变量来组成这样的表达式,但是你必须使用“-e”的选项来指定这个表达式(通常我们不这样用):
ansible-palybook -e webservers:!{{excluded}}:&{{required}}
b、在ansible和ansible-playbook中,还可以通过一个参数”–limit”来明确指定排除某些主机或组:
ansible-playbook site.yml --limit datacenter2
ansible小结(四)ansible.cfg与默认配置
Ansible默认安装好后有一个配置文件/etc/ansible/ansible.cfg,该配置文件中定义了ansible的主机的默认配置部分,如默认是否需要输入密码、是否开启sudo认证、action_plugins插件的位置、hosts主机组的位置、是否开启log功能、默认端口、key文件位置等等。
[defaults] # some basic default values... hostfile = /etc/ansible/hosts \\指定默认hosts配置的位置 # library_path = /usr/share/my_modules/ remote_tmp = $HOME/.ansible/tmp pattern = * forks = 5 poll_interval = 15 sudo_user = root \\远程sudo用户 #ask_sudo_pass = True \\每次执行ansible命令是否询问ssh密码 #ask_pass = True \\每次执行ansible命令时是否询问sudo密码 transport = smart remote_port = 22 module_lang = C gathering = implicit host_key_checking = False \\关闭第一次使用ansible连接客户端是输入命令提示 log_path = /var/log/ansible.log \\需要时可以自行添加。chown -R root:root ansible.log system_warnings = False \\关闭运行ansible时系统的提示信息,一般为提示升级 # set plugin path directories here, separate with colons action_plugins = /usr/share/ansible_plugins/action_plugins callback_plugins = /usr/share/ansible_plugins/callback_plugins connection_plugins = /usr/share/ansible_plugins/connection_plugins lookup_plugins = /usr/share/ansible_plugins/lookup_plugins vars_plugins = /usr/share/ansible_plugins/vars_plugins filter_plugins = /usr/share/ansible_plugins/filter_plugins fact_caching = memory [accelerate] accelerate_port = 5099 accelerate_timeout = 30 accelerate_connect_timeout = 5.0 # The daemon timeout is measured in minutes. This time is measured # from the last activity to the accelerate daemon. accelerate_daemon_timeout = 30
本篇就结合一个示例对其进行下了解。我在对之前未连接的主机进行连结时报错如下:
[root@361way.com ~]# ansible test -a ‘uptime‘ 10.212.52.14 | FAILED => Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host‘s fingerprint to your known_hosts file to manage this host. 10.212.52.16 | FAILED => Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support this. Please add this host‘s fingerprint to your known_hosts file to manage this host.
从上面的输出提示上基本可以了解到由于在本机的~/.ssh/known_hosts文件中并有fingerprint key串,ssh第一次连接的时候一般会提示输入yes 进行确认为将key字符串加入到 ~/.ssh/known_hosts 文件中。
方法1:
了解到问题原因为,我们了解到进行ssh连接时,可以使用-o参数将StrictHostKeyChecking设置为no,使用ssh连接时避免首次连接时让输入yes/no部分的提示。通过查看ansible.cfg配置文件,发现如下行:
[ssh_connection] # ssh arguments to use # Leaving off ControlPersist will result in poor performance, so use # paramiko on older platforms rather than removing it #ssh_args = -o ControlMaster=auto -o ControlPersist=60s
所以这里我们可以启用ssh_args 部分,使用下面的配置,避免上面出现的错误:
ssh_args = -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no
方法2:
在ansible.cfg配置文件中,也会找到如下部分:
# uncomment this to disable SSH key host checking host_key_checking = False
默认host_key_checking部分是注释的,通过找开该行的注释,同样也可以实现跳过 ssh 首次连接提示验证部分。由于配置文件中直接有该选项,所以推荐用方法2 。
其他部分
由于官方给的说明比较详细,同时ansible.cfg 文件本身默认也有注释提示部分,所以不做过多说明,这里再举个例子,默认ansible 执行的时候,并不会输出日志到文件,不过在ansible.cfg 配置文件中有如下行:
# logging is off by default unless this path is defined # if so defined, consider logrotate log_path = /var/log/ansible.log
同样,默认log_path这行是注释的,打开该行的注释,所有的命令执行后,都会将日志输出到/var/log/ansible.log 文件,便于了解在何时执行了何操作及其结果,如下:
[root@361way.com ansible]# cat /var/log/ansible.log 2015-05-04 01:57:19,758 p=4667 u=root | 2015-05-04 01:57:19,759 p=4667 u=root | /usr/bin/ansible test -a uptime 2015-05-04 01:57:19,759 p=4667 u=root | 2015-05-04 01:57:20,563 p=4667 u=root | 10.212.52.252 | success | rc=0 >> 01:57am up 23 days 11:20, 2 users, load average: 0.38, 0.38, 0.40 7.2015-05-04 01:57:20,831 p=4667 u=root | 10.212.52.14 | success | rc=0 >> 02:03am up 331 days 8:19, 2 users, load average: 0.08, 0.05, 0.05 2015-05-04 01:57:20,909 p=4667 u=root | 10.212.52.16 | success | rc=0 >> 02:05am up 331 days 8:56, 2 users, load average: 0.00, 0.01, 0.05
ansible小结(五)Dynamic Inventory
Ansible Inventory实际上是包含静态Inventory和动态Inventory两部分,静态Inventory指的是在文件/etc/ansible/hosts中指定的主机和组,Dynamic Inventory指通过外部脚本获取主机列表,并按照ansible 所要求的格式返回给ansilbe命令的。这部分一般会结合CMDB资管系统、zabbix 监控系统、crobble安装系统、云计算平台等获取主机信息。由于主机资源一般会动态的进行增减,而这些系统一般会智能更新。我们可以通过这些工具提供的API 或者接入库查询等方式返回主机列表。
一、最简单示例
由于Ansible在接受脚本动态获取主机信息时支持的是json格式,这里我也不从其他系统中取了,向通过一段代码打印一个段json格式的主机信息:
#!/usr/bin/env python # coding=utf-8 import json host1ip = [‘10.212.52.252‘,‘10.212.52.14‘] host2ip = [‘10.212.52.16‘] group = ‘test1‘ group2 = ‘test2‘ hostdata = {group:{"hosts":host1ip},group2:{"hosts":host2ip}} print json.dumps(hostdata,indent=4)
注:
1、主机部分必须是列表格式的;
2、hostdata行,其中的"hosts" 部分可以省略,但如果使用时,必须是"hosts" ,不能是其他如‘‘hostlist’’ 等字符串。省略后可以这样写:
hostdata = {group:host1ip,group2:host2ip}
直接执行该段代码结果如下:
[root@361way.com ~]# python twogroup.py { "test1": { "hosts": [ "10.212.52.252", "10.212.52.14" ] }, "test2": { "hosts": [ "10.212.52.16" ] } }
上面定义了两个主机组,test1组内包含主机10.212.52.252、10.212.52.14,test2组内包含主机10.212.52.16 。ansible可以通过如下方法调用:
[root@361way.com ~]# ansible -i twogroup.py test1 -m command -a ‘uptime‘ -k SSH password: 10.212.52.252 | success | rc=0 >> 23:01pm up 24 days 8:24, 2 users, load average: 0.21, 0.35, 0.39 10.212.52.14 | success | rc=0 >> 23:08pm up 332 days 5:23, 2 users, load average: 0.00, 0.01, 0.05 [root@361way.com ~]# ansible -i twogroup.py test2 -m command -a ‘uptime‘ -k SSH password: 10.212.52.16 | success | rc=0 >> 23:09pm up 332 days 6:00, 2 users, load average: 0.08, 0.06, 0.05
二、复杂示例
在静态主机配置文件示例中,会有组变量(vars),组之间的包含,如下图(点击图片看大图):
如果以上部分想要,通过脚本获取实现,实现后返回的json格式应该如下图:
像上面这种复杂的返回格式,一般不会用在ad-hoc环境中,多数会用在ansible-playbook 中,应为playbook文件中有时假会涉及到vars 参数的传参。
三、从第三方平台获取主机示例
这个在本篇一开头就提到了,我们从如cobbler、cmdb等平台上获取的示例。由于ansible 的发起者(作者)同时又是cobbler软件的创建者,所以官方文档给了我们cobbler的示例,同时给出了一个从AWS 去上获取主机信息的示例 。如下:
关于如何从aws上获取主机信息并入库,这个我之前也写过相关的篇章,具体也可以参看我之前的博文----AWS主机资产管理 (该篇也是纯python实现的)。
通过zabbix api 获取主机信息,我也写过一篇zabbix小结(八)Zabbix api ,想要通过zabbix平台上获取主机列表信息也可以参考下。
四、其他
1、ansible -i 参数后调用的脚本并非一定是py文件,也可以是其他脚本输出的结果,这里做了个简单的测试:
[root@361way.com yaml]# ansible -i group.sh test1 -m command -a ‘uptime‘ -k SSH password: 10.212.52.16 | success | rc=0 >> 00:18am up 332 days 7:10, 2 users, load average: 0.00, 0.01, 0.05 10.212.52.14 | success | rc=0 >> 00:17am up 332 days 6:32, 2 users, load average: 0.01, 0.03, 0.05 10.212.52.252 | success | rc=0 >> 00:11am up 24 days 9:33, 2 users, load average: 0.49, 0.42, 0.41 [root@localhost yaml]# cat group.sh #!/bin/bash groups=‘‘‘ { "test1": { "hosts": [ "10.212.52.252", "10.212.52.14", "10.212.52.16" ] } } ‘‘‘ echo $groups
2、-i 参数指定的脚本需要有可执行权限 ,不然会报错,如下:
[root@361way.com yaml]# ansible -i hostjson.py AA -a ‘uptime‘ ERROR: The file hostjson.py looks like it should be an executable inventory script, but is not marked executable. Perhaps you want to correct this with `chmod +x hostjson.py`?
ansible小结(六)Ad-hoc与commands模块
Ad-Hoc 是指ansible下临时执行的一条命令,并且不需要保存的命令,对于复杂的命令后面会说playbook。讲到Ad-hoc 就要提到模块,所有的命令执行都要依赖于事先写好的模块,默认安装好的ansible 里面已经自带了很多模块,如:command、raw、shell、file、cron等,具体可以通过ansible-doc -l 进行查看 。
这里还是先来一个上几篇幅经常用到的一个例子:
root@361way ~]# ansible 10.212.52.252 -a ‘uptime‘ -k SSH password: 10.212.52.252 | success | rc=0 >> 10:10am up 27 days 19:33, 2 users, load average: 0.39, 0.34, 0.33
一个ad-hoc命令的执行,需要按以下格式进行执行:
ansible 主机或组 -m 模块名 -a ‘模块参数‘ ansible参数
主机和组,是在/etc/ansible/hosts 里进行指定的部分,当然动态Inventory 使用的是脚本从外部应用里获取的主机,这部分具体可以参考ansible小结(五)Dynamic Inventory ;
2、后台执行
当命令执行时间比较长时,也可以放到后台执行,这里会用到-B、-P参数,如下:
ansible all -B 3600 -a "/usr/bin/long_running_operation --do-stuff" \\后台执行命令 3600s,-B 表示后台执行的时间 ansible all -m async_status -a "jid=123456789" \\检查任务的状态 ansible all -B 1800 -P 60 -a "/usr/bin/long_running_operation --do-stuff" \\后台执行命令最大时间是 1800s 即 30 分钟,-P 每 60s 检查下状态默认 15s
示例如下:
[root@361way ~]# ansible 10.212.52.252 -B 3600 -P 0 -a ‘watch ls‘ background launch... 10.212.52.252 | success >> { "ansible_job_id": "411650646689.13501", "results_file": "/root/.ansible_async/411650646689.13501", "started": 1 } [root@361way ~]# ansible 10.212.52.252 -m async_status -a ‘jid=411650646689.13501‘ 10.212.52.252 | success >> { "ansible_job_id": "411650646689.13501", "changed": false, "finished": 0, "results_file": "/root/.ansible_async/411650646689.13501", "started": 1 }
不指定-P或-P参数为非0时,该任务就会按-P直接的参数一直刷新下去,直到超出-B参数指定的时间或命令执行完成:
[root@361way ~]# ansible 10.212.52.252 -B 3600 -a ‘watch ls‘ background launch... 10.212.52.252 | success >> { "ansible_job_id": "397200656414.15008", "results_file": "/root/.ansible_async/397200656414.15008", "started": 1 } 10.212.52.252 | success >> { "ansible_job_id": "397200656414.15008", "changed": false, "finished": 0, "results_file": "/root/.ansible_async/397200656414.15008", "started": 1 } <job 397200656414.15008> polling on 10.212.52.252, 3585s remaining …………………………………………略
二、commands模块
上面已经提到,ansbile自身已经自带了很多模块,可以通过ansible-doc -l 进行查看。这里就结合command、shell、raw、script模块了解下其用法。
上面四个模块都属于commands 类。
注:raw模块和comand、shell 模块不同的是其没有chdir、creates、removes参数,chdir参数的作用就是先切到chdir指定的目录后,再执行后面的命令,这在后面很多模块里都会有该参数 。
command模块包含如下选项:
command模块、raw模块、shell模块示例:
[root@361way ~]# ansible 10.212.52.252 -m command -a ‘ps auxf|grep snmp‘ 10.212.52.252 | FAILED | rc=1 >> ERROR: Unsupported option (BSD syntax) ********* simple selection ********* ********* selection by list ********* -A all processes -C by command name -N negate selection -G by real group ID (supports names) -a all w/ tty except session leaders -U by real user ID (supports names) -d all except session leaders -g by session OR by effective group name -e all processes -p by process ID T all processes on this terminal -s processes in the sessions given a all w/ tty, including other users -t by tty g OBSOLETE -- DO NOT USE -u by effective user ID (supports names) r only running processes U processes for specified users x processes w/o controlling ttys t by tty *********** output format ********** *********** long options *********** -o,o user-defined -f full --Group --User --pid --cols --ppid -j,j job control s signal --group --user --sid --rows --info -O,O preloaded -o v virtual memory --cumulative --format --deselect -l,l long u user-oriented --sort --tty --forest --version -F extra full X registers --heading --no-heading --context ********* misc options ********* -V,V show version L list format codes f ASCII art forest -m,m,-L,-T,H threads S children in sum -y change -l format -M,Z security data c true command name -c scheduling class -w,w wide output n numeric WCHAN,UID -H process hierarchy [root@361way ~]# ansible 10.212.52.252 -m raw -a ‘ps auxf|grep snmp‘ 10.212.52.252 | success | rc=0 >> root 5580 25.0 0.0 12876 1792 pts/2 Ss+ 12:36 0:00 \_ bash -c ps auxf|grep snmp root 5607 0.0 0.0 5720 832 pts/2 S+ 12:36 0:00 \_ grep snmp root 24364 0.0 0.0 70416 6696 ? SNl May15 0:22 /usr/sbin/snmpd -r -A -LF i /var/log/net-snmpd.log -p /var/run/snmpd.pid [root@361way ~]# ansible 10.212.52.252 -m shell -a ‘ps auxf|grep snmp‘ 10.212.52.252 | success | rc=0 >> root 5803 0.0 0.0 11308 1308 pts/2 S+ 12:36 0:00 \_ /bin/sh -c ps auxf|grep snmp root 5805 0.0 0.0 4260 572 pts/2 S+ 12:36 0:00 \_ grep snmp root 24364 0.0 0.0 70416 6696 ? SNl May15 0:22 /usr/sbin/snmpd -r -A -LF i /var/log/net-snmpd.log -p /var/run/snmpd.pid 上面的执行结果可以看到,我这里加了管道,command模块执行时出错,而使用raw模块和shell 模块都正常。
使用chdir的示例:
[root@361way ~]# ansible 10.212.52.252 -m command -a ‘chdir=/tmp/361way touch test.file‘ 10.212.52.252 | success | rc=0 >> [root@361way ~]# ansible 10.212.52.252 -m shell -a ‘chdir=/tmp/361way touch test2.file‘ 10.212.52.252 | success | rc=0 >> [root@361way ~]# ansible 10.212.52.252 -m raw -a ‘chdir=/tmp/361way touch test3.file‘ 10.212.52.252 | success | rc=0 >>
从上面执行结果来看,三个命令都执行成功了。不过通过在远程主机上查看,前两个文件被成功创建
linux-wdh1:/tmp/361way # ls /tmp/361way test.file test2.file
使用raw模块的执行的结果文件也被正常创建了,不过不是在chdir 指定的目录,而是在当前执行用户的家目录。
linux-wdh1:~ # ls ~/test3.file /root/test3.file
creates与removes示例:
这里我在测试主机上创建/tmp/361way/server.txt文件,执行结果如下:
[root@361way ~]# ansible 10.212.52.252 -a ‘creates=/tmp/361way/server.txt uptime‘ 10.212.52.252 | success | rc=0 >> skipped, since /tmp/361way/server.txt exists [root@361way ~]# ansible 10.212.52.252 -a ‘removes=/tmp/361way/server.txt uptime‘ 10.212.52.252 | success | rc=0 >> 15:11pm up 28 days 0:34, 2 users, load average: 0.75, 0.46, 0.39
script模块示例:
[root@361way ~]# cat script.sh #!/bin/bash df -hl ifconfig ps auxf|grep snmp [root@361way ~]# ansible 10.212.52.252 -m script -a ‘scrip.sh‘ 10.212.52.252 | FAILED => file or module does not exist: /root/scrip.sh [root@361way ~]# ansible 10.212.52.252 -m script -a ‘script.sh‘ 10.212.52.252 | success >> { "changed": true, "rc": 0, "stderr": "OpenSSH_5.3p1, OpenSSL 1.0.1e-fips 11 Feb 2013\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\nControl socket connect(/root/.ansible/cp/ansible-ssh-10.212.52.252-22-root): Connection refused\r\ndebug1: Connecting to 10.212.52.252 [10.212.52.252] port 22.\r\ndebug1: fd 3 clearing O_NONBLOCK\r\ndebug1: Connection established.\r\ndebug1: permanently_set_uid: 0/0\r\ndebug1: identity file /root/.ssh/identity type -1\r\ndebug1: identity file /root/.ssh/identity-cert type -1\r\ndebug1: identity file /root/.ssh/id_rsa type -1\r\ndebug1: identity file /root/.ssh/id_rsa-cert type -1\r\ndebug1: identity file /root/.ssh/id_dsa type -1\r\ndebug1: identity file /root/.ssh/id_dsa-cert type -1\r\ndebug1: identity file /root/.ssh/id_ecdsa type -1\r\ndebug1: identity file /root/.ssh/id_ecdsa-cert type -1\r\ndebug1: Remote protocol version 2.0, remote software version OpenSSH_6.2\r\ndebug1: match: OpenSSH_6.2 pat OpenSSH*\r\ndebug1: Enabling compatibility mode for protocol 2.0\r\ndebug1: Local version string SSH-2.0-OpenSSH_5.3\r\ndebug1: SSH2_MSG_KEXINIT sent\r\ndebug1: SSH2_MSG_KEXINIT received\r\ndebug1: kex: server->client aes128-ctr hmac-md5 zlib@openssh.com\r\ndebug1: kex: client->server aes128-ctr hmac-md5 zlib@openssh.com\r\ndebug1: SSH2_MSG_KEX_DH_GEX_REQUEST(1024<1024<8192) sent\r\ndebug1: expecting SSH2_MSG_KEX_DH_GEX_GROUP\r\ndebug1: SSH2_MSG_KEX_DH_GEX_INIT sent\r\ndebug1: expecting SSH2_MSG_KEX_DH_GEX_REPLY\r\ndebug1: Host ‘10.212.52.252‘ is known and matches the RSA host key.\r\ndebug1: Found key in /root/.ssh/known_hosts:1\r\ndebug1: ssh_rsa_verify: signature correct\r\ndebug1: SSH2_MSG_NEWKEYS sent\r\ndebug1: expecting SSH2_MSG_NEWKEYS\r\ndebug1: SSH2_MSG_NEWKEYS received\r\ndebug1: SSH2_MSG_SERVICE_REQUEST sent\r\ndebug1: SSH2_MSG_SERVICE_ACCEPT received\r\ndebug1: Authentications that can continue: publickey,password,keyboard-interactive\r\ndebug1: Next authentication method: keyboard-interactive\r\ndebug1: Enabling compression at level 6.\r\ndebug1: Authentication succeeded (keyboard-interactive).\r\ndebug1: setting up multiplex master socket\r\nControlSocket /root/.ansible/cp/ansible-ssh-10.212.52.252-22-root already exists, disabling multiplexing\r\ndebug1: channel 0: new [client-session]\r\ndebug1: Requesting no-more-sessions@openssh.com\r\ndebug1: Entering interactive session.\r\ndebug1: Sending environment.\r\ndebug1: Sending env LANG = en_US.UTF-8\r\ndebug1: Sending command: LANG=C LC_CTYPE=C /root/.ansible/tmp/ansible-tmp-1431924855.88-242473611260231/script.sh \r\ndebug1: client_input_channel_req: channel 0 rtype exit-status reply 0\r\ndebug1: client_input_channel_req: channel 0 rtype eow@openssh.com reply 0\r\ndebug1: channel 0: free: client-session, nchannels 1\r\ndebug1: fd 1 clearing O_NONBLOCK\r\ndebug1: fd 2 clearing O_NONBLOCK\r\nConnection to 10.212.52.252 closed.\r\nTransferred: sent 1928, received 3920 bytes, in 0.1 seconds\r\nBytes per second: sent 37017.0, received 75262.7\r\ndebug1: Exit status 0\r\ndebug1: compress outgoing: raw data 537, compressed 375, factor 0.70\r\ndebug1: compress incoming: raw data 1837, compressed 1019, factor 0.55\r\n", "stdout": "Filesystem Size Used Avail Use% Mounted on\r\n/dev/sda2 9.9G 872M 8.5G 10% /\r\nudev 3.9G 128K 3.9G 1% /dev\r\ntmpfs 3.9G 76K 3.9G 1% /dev/shm\r\n/dev/sda3 5.0G 219M 4.5G 5% /boot\r\n/dev/sda8 40G 15G 23G 40% /home\r\n/dev/sda9 9.9G 5.2G 4.3G 55% /opt\r\n/dev/sda6 5.0G 2.7G 2.1G 57% /tmp\r\n/dev/sda5 9.9G 3.4G 6.0G 36% /usr\r\n/dev/sda7 9.9G 823M 8.6G 9% /var\r\neth0 Link encap:Ethernet HWaddr 00:50:56:A8:65:7E \r\n inet addr:10.212.52.252 Bcast:10.212.52.255 Mask:255.255.255.0\r\n inet6 addr: fe80::250:56ff:fea8:657e/64 Scope:Link\r\n UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1\r\n RX packets:24112135 errors:0 dropped:792372 overruns:0 frame:0\r\n TX packets:10697339 errors:0 dropped:0 overruns:0 carrier:0\r\n collisions:0 txqueuelen:1000 \r\n RX bytes:17137233328 (16343.3 Mb) TX bytes:13390377826 (12770.0 Mb)\r\n\r\nlo Link encap:Local Loopback \r\n inet addr:127.0.0.1 Mask:255.0.0.0\r\n inet6 addr: ::1/128 Scope:Host\r\n UP LOOPBACK RUNNING MTU:16436 Metric:1\r\n RX packets:3407332 errors:0 dropped:0 overruns:0 frame:0\r\n TX packets:3407332 errors:0 dropped:0 overruns:0 carrier:0\r\n collisions:0 txqueuelen:0 \r\n RX bytes:262675450 (250.5 Mb) TX bytes:262675450 (250.5 Mb)\r\n\r\nroot 25332 0.0 0.0 4260 568 pts/2 S+ 12:54 0:00 \\_ grep snmp\r\nroot 24364 0.0 0.0 70416 6696 ? SNl May15 0:22 /usr/sbin/snmpd -r -A -LF i /var/log/net-snmpd.log -p /var/run/snmpd.pid\r\n" }
输出结果很多,看起来也很乱,不过查下stdout部分,这个部分是实际上执行后的结果。这里可以配合管道一起使用,可以如下使用:
[root@361way ~]# ansible 10.212.52.252 -m script -a ‘script.sh‘ |egrep ‘>>|stdout‘
ansible小结(七)常用模块
在上一篇中介绍了commands部分模块,本篇承接上篇介绍下常用的模块。根据官方的分类,将模块按功能分类为:云模块、命令模块、数据库模块、文件模块、资产模块、消息模块、监控模块、网络模块、通知模块、包管理模块、源码控制模块、系统模块、单元模块、web设施模块、windows模块 ,具体可以参看官方页面。
一、ping模块
测试主机是否是通的,用法很简单,不涉及参数:
[root@361way ~]# ansible 10.212.52.252 -m ping 10.212.52.252 | success >> { "changed": false, "ping": "pong" }
二、setup模块
setup模块,主要用于获取主机信息,在playbooks里经常会用到的一个参数gather_facts就与该模块相关。setup模块下经常使用的一个参数是filter参数,具体使用示例如下(由于输出结果较多,这里只列命令不写结果):
[root@361way ~]# ansible 10.212.52.252 -m setup -a ‘filter=ansible_*_mb‘ //查看主机内存信息 [root@361way ~]# ansible 10.212.52.252 -m setup -a ‘filter=ansible_eth[0-2]‘ //查看地接口为eth0-2的网卡信息 [root@361way ~]# ansible all -m setup --tree /tmp/facts //将所有主机的信息输入到/tmp/facts目录下,每台主机的信息输入到主机名文件中(/etc/ansible/hosts里的主机名)
三、file模块
file模块主要用于远程主机上的文件操作,file模块包含如下选项:
使用示例:
标签:.so interval ast hosts queue date 3.3 upd 升级
原文地址:http://www.cnblogs.com/gandefeng/p/6915478.html