1.2 自动化运维

对于管理成百上千台服务器的管理员而言,不可能手动去逐一执行脚本维护机器配置系统,为了能够更自动化地完成海量服务器的配置维护工作,就需要一个远程执行系统来接管这些烦琐的步骤了,本节介绍的就是Ansible,一个无须在服务器上部署agent的服务器批量运维工具。

1.2.1 自动化运维之Ansible

官方这样定义Ansible:“Ansible is a radically simple IT automation platform.”Ansible就是一个简单的自动化运维工具。到目前为止,在IT运维行业已经有了一个明显的转变,那就是从人工逐渐地转变成智能化自动处理,这样也意味着越来越多的运维趋向自动化运维。现在,成熟的自动化运维工具已经有了不少,比如Ansible、Puppet、Cfengine、Chef、Func、Fabric,本节我们重点讲解Ansible, Ansible在运维界一直保持着领先地位,并有着活跃的开发社区,早已成为主流的运维工具之一。

Ansible是一款由Python编程语言开发,基于SSH远程通信的自动化运维工具,虽然Ansible是后起之秀,但是它已经继承了上几代运维框架优秀的优点(Puppet、Cfengine、Chef、Func、Fabric),实现了批量主机配置、批量主机应用部署等。例如Fabric可谓是一个运维工具箱,内置提供了许多工作模块,而Ansible只是一个框架,它是依赖模块而运行工作的,简而言之,Ansible是依赖程序模块并驱动模块工作的一个运维框架,这就是Ansible与Fabric的最大区别。

1. Ansible的特性与框架

对于Ansible的特性主要有如下几个:

● 不需要在被管控主机上安装客户端。

● 无服务器端,使用时直接运行命令即可。

● 基于模块工作,可使用任意语言开发模块。

● 使用yaml语言定制编排剧本playbook。

● 基于SSH远程通信协议。

● 可实现多级指挥。

● 支持sudo。

● 基于Python语言,管理维护简单。

● 支持邮件、日志等多种功能。

Ansible框架由以下核心的组件组成:

● ansible core:它是Ansible本身的核心模块。

● host inventory顾名思义,它是一个主机库,需要管理的主机列表。

● connection plugins连接插件,Ansible支持多种通信协议,默认采取SSH远程通信协议。

● modules core modules:Ansible本身的核心模块。

● custom modules:Ansible自定义扩展模块。

● plugins为Ansible扩展功能组件,可支持扩展组件,毕竟Ansible只是一个框架。

● playbook编排(剧本),按照所设定编排的顺序执行完成安排的任务。

我们来看看Ansible框架工作流程,可以更清清楚它的框架架构,如图1-3所示。

图1-3 Ansible框架工作流程

2. Ansible安装

在Ubuntu上安装:

    user@ops-admin:~$ sudo apt-get install software-properties-common
    user@ops-admin:~$sudo apt-add-repository ppa:ansible/ansible
    user@ops-admin:~$sudo apt-get update
    user@ops-admin:~$sudo apt-get install ansible

在CentOS(7.+)上安装:

    user@ops-admin:~$ sudo rpm -Uvh http://mirrors.zju.edu.cn/epel/7/x86_64/e/epel-
    release-7-8.noarch.rpm
    user@ops-admin:~$ sudo yum install ansible

在macOS上安装:

    user@ops-admin:~$ brew update
    user@ops-admin:~$ brew install ansible

通用安装方式pip(推荐):

    user@ops-admin:~$ pip install ansible

安装注意的地方如下所示。

(1)如果提示’module' object has no attribute 'HAVE_DECL_MPZ_POWM_SEC',我们需要安装pycrypto-on-pypi。

    user@ops-admin:~$ sudo pip install pycrypto-on-pypi

(2)如果是在OS X系统上安装,编译器可能会有警告或出错,需要设置CFLAGS、CPPFLAGS环境变量。

    user@ops-admin:~$ sudo CFLAGS=-Qunused-arguments CPPFLAGS=-Qunused-arguments pip
    install ansible

(3)如果被控端Python版本小于2.4则需要安装python-simplejson。

    user@ops-admin:~$ sudo pip install python-simplejson

1.2.2 Ansible的使用

1. Ansible配置文件详解

在Ubuntu发行版系统上使用apt-get包管理安装的方式,安装完成之后,我们来看一下安装后的重要生成文件有哪些,如下的Ansible相关文件路径基于Ubuntu发行版。

● /etc/ansibel/ansible.cfg:Ansible程序核心配置文件。

● /etc/ansible/host:被管理主机的主机信息文件。

● /etc/ansible/roles:Ansible的角色目录。

● /usr/bin/ansible:Ansible程序的主程序,即命令行在执行程序。

● /usr/bin/ansible-doc:Ansible帮助文档命令。

● /usr/bin/ansible-playbook:运行Ansible剧本( playbook )程序。

Ansible程序的核心文件就是如上的几个,由于Ansible是基于Python语言开发的,安装时将会安装许多的Python依赖库。我们先来了解一下Ansible核心配置文件ansible.cfg, Ansible配置文件的路径位于/etc/ansible/ansible.cfg, Ansible在执行时会按照以下顺序查找配置项。

第一:环境变量的配置指向ANSIBLE_CONFIG。

第二:当前目录下的配置文件ansible.cfg。

第三:用户家目录下的配置文件/home/$USER/.ansible.cfg。

第四:默认安装的配置文件/etc/ansible/ansible.cfg。

当然,我们几乎都是使用默认安装的Ansible配置文件/etc/ansible/ans.cfg,通过cat命令打印该文件有如下的配置项:

    # 通用默认基础配置
    [defaults]
    # 通信主机信息目录位置
    hostfile      = /etc/ansible/hosts
    # ansible依赖库目录位置
    library       = /usr/share/ansible
    # 远程临时文件存储目录位置
    remote_tmp    = $HOME/.ansible/tmp
    # ansible通信的主机匹配,默认对所有主机通信
    pattern       = *
    # 同时与主机通信的进程数
    forks        = 5
    # 定时poll的时间
    poll_interval  = 15
    # sudo使用的用户,默认是root
    sudo_user     = root
    # 在实行sudo指令时是否询问密码
    ask_sudo_pass  = True
    # 控制Ansible playbook是否会自动默认弹出密码
    ask_pass      = True
    #  指定通信机制
    transport     = smart
    # 远程通信的端口,默认是采用SSH的22端口
    remote_port   = 22
    # 角色配置路径
    roles_path    = /etc/ansible/roles
    # 是否检查主机密钥
    host_key_checking = False

    # sudo的执行命令,基本默认都是使用sudo
    sudo_exe = sudo
    # sudo默认之外的参数传递方式
    sudo_flags = -H

    # SSH连接超时(s)
    timeout = 10

    # 指定ansible命令执行的用户,默认使用当前的用户
    remote_user = root

    #  ansible日志文件位置
    #log_path = /var/log/ansible.log

    # ansible命令执行默认的模块
    #module_name = command

    # 指定执行脚本的解析器
    #executable = /bin/sh

    # 特定的优先级覆盖变量,可以设置为’merge'.
    #hash_behaviour = replace

    # playbook变量
    #legacy_playbook_variables = yes

    # 允许开启Jinja2拓展模块
    #jinja2_extensions = jinja2.ext.do, jinja2.ext.i18n

    # 私钥文件存储位置
    #private_key_file = /path/to/file

    # 当Ansible修改了一个文件,可以告知用户
    ansible_managed = Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on
    {host}

    # 是否显示跳过的host主机,默认为False
    #display_skipped_hosts = True

    # by default (as of 1.3), Ansible will raise errors when attempting to dereference
    # Jinja2 variables that are not set in templates or action lines. Uncomment this line
    # to revert the behavior to pre-1.3.
    #error_on_undefined_vars = False

    # 设置相关插件位置
    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

    # don't like cows?  that's unfortunate.
    # set to 1 if you don't want cowsay support or export ANSIBLE_NOCOWS=1
    #nocows = 1

    # 颜色配置
    # don't like colors either
    # 输出是否带上颜色,1-不显示颜色 | 0-显示颜色
    nocolor = 1

    # Unix/Linux各个版本的密钥文件存放位置
    # RHEL/CentOS: /etc/pki/tls/certs/ca-bundle.crt
    # Fedora    : /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
    # Ubuntu    : /usr/share/ca-certificates/cacert.org/cacert.org.crt
    # 指定ca文件路径
    #ca_file_path =

    # 指定http代理用户名
    #http_user_agent = ansible-agent

    #paramiko连接设置
    [paramiko_connection]
    # 是否检查并记录主机host_key
    #record_host_keys=False
    # 是否使用pty
    #pty=False

    # SSH连接配置
    [ssh_connection]

    # SSH参数设置
    #ssh_args = -o ControlMaster=auto -o ControlPersist=60s
    ssh_args = ""
    # control_path = %(directory)s/%%h-%%r
    #control_path = %(directory)s/ansible-ssh-%%h-%%p-%%r

    # ssh密钥文件
    control_path = ./ssh_keys
    #pipelining = False

    # 基于SSH连接,默认是基于sftp
    scp_if_ssh = True

    # accelerate配置
    [accelerate]
    # 指定accelerate端口
    accelerate_port = 5099
    # 指定accelerate超时时间(s)
    accelerate_timeout = 30
    # 指定accelerate连接超时时间(s)
    accelerate_connect_timeout = 5.0

Ansible程序的全部配置项就是上面详解的那些,当我们熟悉配置项时,即可配置一个建议的配置文件,使用时直接通过命令行指向映射即可。

    [defaults]
    inventory         =  /etc/ansible/hosts
    sudo_user         =  root
    remote_port       =  22
    host_key_checking  =  False
    remote_user       =  root
    log_path          =  /var/log/ansible.log
    module_name       =  command
    private_key_file   =  /root/.ssh/id_rsa

2. Ansible相关命令语法

Ansible的命令主要有六个:ansible、ansible-doc、ansible-galaxy、ansible-playbook、ansible-pull以及ansible-vault。

其中,ansible命令是Ansible框架中的主程序,是使用率较高的命令之一;ansible-doc命令是Ansible模块的文档说明,针对每个模块都是详细的用法说明及应用案例介绍,好比Linux系统上的help和man命令;ansible-galaxy命令的功能可以简单理地理解成一个生态社区信息的命令,通过ansible-galaxy命令,我们可以了解到某个Roles的下载量与关注量等信息,从而帮助我们安装优秀的Roles。

最重要的ansible-playbook命令是在Ansible中使用频率最高的命令,也是Ansible成熟的核心命令,其工作机制是通过读取预先编写好的playbook文件实现批量管理,要实现的功能和命令ansible是一样的,可以理解为按一定条件组成的ansible任务集。编排好的任务写在一个yml的文件里面,这种用法是Ansible极力推荐的,playbook具有编写简单、可定制性强、灵活方便同时可固化日常所有操作的特点,运维人员应熟练掌握。

Ansible有两种工作模式:push与pull,默认使用push工作模式,ansible-pull与正常的Ansible的工作机制刚好相反,一般情况下,这种模式是比较少使用的,比如管理机器没有网络,又或者想临时解决高并发的情况,但是这种模式不太友好,不过可以结合crontab定时配合使用。

最后一个,ansible-vault命令主要用于配置文件的加密解密,比如,编写的playbook.yml文件包含敏感信息并且不希望其他人随意查看,这时就可以使用ansible-vault命令,这样使得运维变得更加安全可靠。

下面是一个简单的加密解密示例:

    # 为demo.yml编排文件加密
    user@ops-admin:~$ ansible-vault encrypt demo.yml
    Vault password:
    Confirm Vault password:
    Encryption successful

    # 加密后的文件不能直接查看
    user@ops-admin:~$ cat demo.yml
    $ANSIBLE_VAULT;1.1; AES256
    39623035376236386431373833346538646539373436373066346137393566616265353761383038
    3437653031303539303536343261353834383435393664370a343664373233343437346539633232
    38303866653965333566623033653938636162363032646565643737323439663334316166373633
    6635646534316437360a376238303735663162376139643930616462386665656433616230303035
    3136

    # 为demo.yml编排文件解密
    user@ops-admin:~$ ansible-vault decrypt demo.yml
    Vault password:
    Decryption successful

以上几个命令最常用就是ansible-playbook,本节接下来的内容也大部分围绕它展开。

3.主机与组

Ansible可以同时操作多台主机,也可以同时操作同类型的主机,也就是批量处理。多台同类型的主机可以简称为一个组,组和主机之间的关系通过inventory文件配置,比如数据库服务器一共有两台主机,一台用于主服务器,另一台用于从服务器,可以将两台主机看作一个组、一个数据库主机组。主机列表清单的文件位于/etc/ansible/hosts下。

/etc/ansible/hosts文件的格式与Windows的ini配置文件类似,下面列举一个简单的主机组文件:

    [webservers]
    admin.example.com
    share.example.com

    [dbservers]
    one.example.com
    two.example.com
    three.example.com

从文件内容上很清晰地看到,上面一共有五台主机,被分成了两个组,括号内的为组名,组名下的每一行代表一个主机。注意,一台主机可以属于多个组,比如:一台主机既可以用于Web,即属于Web组,这台主机也可以用于数据库,即属于db组。我们在主机清单定义的主机或组可以直接使用ansible命令来查看。

    # 查看webservers组的主机
    user@ops-admin:~$ ansible webservers --list-hosts
    admin.example.com
    share.example.com

为了服务器的安全,在生产上使用的服务器SSH几乎都是不会使用默认的22端口,会改成其他的端口,此时我们也可以在/etc/ansible/hosts文件的主机信息添加端口,在IP或者域名的后面加上英文冒号接上端口号即可,比如:

    [webservers]
    111.22.33.444:2024
    share.example.com:4202

同时,我们还可以在主机列表清单上配置主机的指定用户名,用户密码,甚至是密钥文件。注意,每一行都代表一台主机,配置项的属性值不用带上引号。比如:

    hare.example.com:4202 ansible_ssh_user=ubuntu
    admin.example.com ansible_ssh_user=root ansible_ssh_pass=Password123@! @#
    172.17.0.1 ansible_ssh_private_key_file=ssh_keys/docker_172.17.0.1.key

我们在配置域名映射的时候,倘若域名很有规则,则可以简写主机,使主机清单文件变得更加简洁,比如有一百台服务器,它们都属于dbservers组,它们的IP分别映射到如下的域名:

    db01.example.com
    db01.example.com
    db02.example.com
    … …
    db99.example.com
    db100.example.com

那么我们可以这样编写我们的主机组,一行即可代表这100台主机,编写两行即可:

    [dbservers]
    db[01:100].example.com

此外,一个组也可以作为另一个组的成员,同时还可以使用变量,变量的使用要特别注意,/usr/bin/ansible-playbook可以解析使用变量,但/usr/bin/ansible是不可以使用变量的。

    # redis服务器
    [redis_servers]
    redisa.example.com
    redisb.example.com
    redisc.example.com

    # mysql服务器
    [mysql_servers]
    mysqla.example.com
    mysqlb.example.com
    mysqlc.example.com

    # 数据库服务器
    [db_servers]
    redis_servers
    mysql_servers

1.2.3 Ansible模块

目前,我们默认安装的Ansible已经自带了不少的模块,比如常用的shell模块、command模块、ansible-playbook模块、copy模块等,同时我们还可以自行安装扩展插件模块,可以使用ansible-doc -l显示所有可用模块,还可以通过ansible-doc <module_name>命令查看模块的介绍以及案例。

    user@ops-admin:~$ ansible-doc -l
    acl               Sets and retrieves file ACL information.
    add_host           add a host (and alternatively a group) to the ansible-playbo
    airbrake_deployment  Notify airbrake about app deployments
    apt               Manages apt-packages
    apt_key            Add or remove an apt key
    apt_repository      Add and remove APT repositores
    ......

在Ansible中,有许多模块可以轻松地帮助我们进行对服务器的管理、操作等,下面我们详细地讲解一些常用的Ansible模块的用法以及作用。

1. shell模块

顾名思义,shell模块的作用就是在被管理的主机上执行shell解析器解析的shell脚本,几乎支持所有原生shell的各种功能,支持各种特殊符号以及管道符。

常用参数:

    chdir=        表示指明命令在远程主机上哪个目录下运行
    creates=      在命令运行时创建一个文件,如果文件已存在,则不会执行创建任务
    removes=      在命令运行时移除一个文件,如果文件不存在,则不会执行移除任务
    executeble=    指明运行命令的shell程序文件,必须是绝对路径

示例:

在demo主机组执行hostname命令,并使每一台主机的返回结果用一行显示。

    user@ops-admin:~$ ansible demo -m shell -a 'hostname' -o
    172.31.131.37 | success | rc=0 | (stdout) ops-node
    172.16.168.1 | success | rc=0 | (stdout) ops-admin

示例分析:

● demo为我们定义的主机组。

● -m指定要使用的模块,这里指定shell模块

● -a指定模块的参数,这里hostname命令作为shell模块的参数。

● -o就是将返回的结果以行作为每一台主机的单位显示。

2. command模块

command模块的作用与shell类似,都是在被管主机上执行命令。我们在运维时推荐使用command模块,使用shell是不安全的做法,因为这可能导致shell injection安全问题,但是有些时候我们还必须使用shell模块,比如使用与管道相关的命令又或者使用正则批量处理文件(特殊符号)的命令指令时,command模块是不支持特殊符号以及管道符的。

常用参数:

    chdir=          指明命令在远程主机上哪个目录下运行。
    creates=        在命令运行时创建一个文件,如果文件已存在,则不会执行创建任务。
    removes=        在命令运行时移除一个文件,如果文件不存在,则不会执行移除任务。
    executeble=     指明运行命令的shell程序文件,必须是绝对路径。

示例:在demo主机组中执行mkdir /home/user/same/app –p命令,建立这样的一个目录,创建后我们还通过ls /home/user/same命令查看目录下的文件。

    user@ops-admin:~$ ansible demo -m command -a 'mkdir /home/user/same/app -p'
    172.31.131.37 | success | rc=0 >>
    172.16.168.1 | success | rc=0 >>

    user@ops-admin:~$ ansible demo -m command -a 'ls /home/user/same'
    172.31.131.37 | success | rc=0 >>
    app
    172.16.168.1 | success | rc=0 >>
    app

3. copy模块

copy模块基本是对文件的操作,比如复制文件。用于复制Ansible管理端的文件到远程主机的指定位置。

常见参数:

    src=         控制端文件路径,可以使用相对路径和绝对路径,支持直接指定目录,如果源是目录,则目
    标也要是目录
    dest=         远程被控机器文件路径,使用绝对路径,如果src是目录,则dest也要是目录,如果目标
    文件已存在,会覆盖原有内容
    mode=         指定目标文件的权限
    owner=        指定目标文件的属主
    group=        指定目标文件的属组
    content=      将内容复制到目标主机上的文件,不能与src一起使用

示例:

复制当前目录下的QR.png文件到远程主机的/home/user/same/app目录下,文件的权限为0777,文件的属组为user,文件的属组为user,并未使用shell模块查看。

    user@ops-admin:~$ ansible demo -m copy -a "src=QR.png dest=/home/user/same/app
    mode=777 owner=user group=user"
    172.16.168.1 | success >> {
    "changed": true,
    "dest": "/home/user/same/app/QR.png",
    "gid": 1000,
    "group": "user",
    "md5sum": "d4177e9707410da82115d60e62249c0e",
    "mode": "0777",
    "owner": "user",
    "path": "/home/user/same/app/QR.png",
    "size": 693,
    "state": "file",
    "uid": 1000
    }

    172.31.131.37 | success >> {
    "changed": true,
    "dest": "/home/user/same/app/QR.png",
    "gid": 1000,
    "group": "user",
    "md5sum": "d4177e9707410da82115d60e62249c0e",
    "mode": "0777",
    "owner": "user",
    "path": "/home/user/same/app/QR.png",
    "size": 693,
    "state": "file",
    "uid": 1000
    }

    user@ops-admin:~$ ansible demo -m shell -a 'ls /home/user/same/app'
    172.31.131.37 | success | rc=0 >>
    QR.png

    172.16.168.1 | success | rc=0 >>
    QR.png

4. script模块

script模块用于本地脚本在被管理远程服务器主机上面执行。大概流程是这样的:Ansible会将脚本复制到被管理的主机,一般情况下是复制到远端主机的/root/.ansible/tmp目录下,然后自动赋予可执行的权限,执行完毕后会自动将脚本删除。

示例:

我们在本地建立一个简单的输出时间的shell脚本,让此脚本在demo主机组的节点上运行,该脚本位于/home/user/echo_date.sh,内容如下:

    #! /bin/bash
    echo "当前的时间为"
    date

使用script模块执行:

    user@ops-admin:~$ ansible demo -m script -a "/home/user/echo_date.sh"
    172.31.131.37 | SUCCESS => {
        "changed": true,
        "rc": 0,
        "stderr": "Shared connection to 172.31.131.37 closed.\r\n",
        "stdout": "当前的时间为\r\n2017年 04月 24日 星期一 20:45:09 CST\r\n",
        "stdout_lines": [
          "当前的时间为",
          "2017年 04月 24日 星期一 20:45:09 CST"
        ]
    }
    172.16.168.1 | SUCCESS => {
        "changed": true,
        "rc": 0,
        "stderr": "Shared connection to 172.16.168.1 closed.\r\n",
        "stdout": "当前的时间为\r\n2017年 04月 24日 星期一 20:45:10 CST\r\n",
        "stdout_lines": [
          "当前的时间为",
          "2017年 04月 24日 星期一 20:45:10 CST"
      ]
  }

上面介绍了一般的自动化操作中最常用到的几个模块,除了这些,Ansible还内置了十几个不同功能的模块,具体可以参考官方文档以及网络资料。

1.2.4 playbook

ansbile-playbook是一系列Ansible命令的集合。该命令在运行时将加载一个任务清单文件,此文件使用yaml语言编写,yaml语言的编程规范入门很简单。Ansible要执行的任务将会按照yml文件自上而下的顺序依次执行。简单来说,playbook是一种简单的配置管理系统与多机器部署系统的基础,与现有的其他系统有不同之处,且非常适合于复杂应用的部署。

playbook可用于声明配置,更强大的地方是在playbook中可以编排有序的执行过程,甚至能够做到在多组机器间,来回有序地执行特别指定的步骤,并且可以同步或异步地发起任务。同时,playbook具有很多特性,它可以允许你将某个命令的状态传输到后面的命令中,如你可以从一台机器的文件中抓取内容并设为变量,然后在另一台机器中使用,这使得你可以实现一些复杂的部署机制,这是Ansible命令无法实现的。

playbook基本由以下五个部分组成。

● hosts:要执行任务管理的主机。

● remote_user:远程执行任务的用户。

● vars:指定要使用的变量。

● tasks:定义将要在远程主机上执行的任务列表。

● handlers:指定task执行完成以后需要调用的任务。

2.编排第一个playbook

在使用playbook时,我们都是将基本的Ansible命令封装在一个yml编排文件里面,与此同时,还使用了变量等其他属性。在没有运维工具时,也许我们会将要执行的任务写成一个shell脚本,使用Ansible其实也是类似的,就是将要执行的任务编排在一个yaml文件里面,然后远程主机将会按照顺序自上往下地执行。下面我们来编写一个入门级的playbook程序。

    ---
    # restart mysql service
    - hosts: cloud
      remote_user: root
      tasks:
      - name: 重启mysql服务
        service: name=mysql state=restarted

上面的代码就是使用yaml编程语言编写的,看起来很舒服、很简洁。简单说一下yaml的语法,这对我们编写的playbook的剧本有很大的帮助!

就如上面的作为一个yaml代码示例:

● 文件的第一行应该以---开头,这说明是yaml文件的开头。

● 使用#符号作为注释的标记,这个和shell语言一样。

● 同一级的列元素需要以-开头,同时-后面必须接上空格,否则语法错误。

● 同一个列表中的元素应该保持相同的缩进,否则语法错误。

● 属性与属性值之间必须有一个空格,比如hosts: cloud这一句,冒号后面就有一个空格。

在语法没有问题的情况下,我们来运行上面这个入门的playbook运维程序:

    user@ops-admin:~$ ansible ansible-playbook mysql.yml
    PLAY [cloud] ***************************************************

    TASK [Gathering Facts] *****************************************
    ok: [172.31.131.37]
    ok: [172.16.168.1]

    TASK [重启mysql服务] ********************************************
    changed: [172.16.168.1]
    changed: [172.31.131.37]

    PLAY RECAP *****************************************************
    172.16.168.1     : ok=2   changed=1   unreachable=0   failed=0
    172.31.131.37    : ok=2   changed=1   unreachable=0   failed=0

从运行的结果上来看,我们很清楚地可以知道,ansible-playbook是按照我们编排的文件内容自上而下地执行的,同时最后的结果中可以更加清晰地查阅远程主机执行命令的结果,当我们在Ansible配置文件中配置nocolor这一个配置项为no时,执行的结果是有打印颜色的,绿色表示执行成功、黄色表示系统某个状态发生了改变、红色表示错误。

在1.4版本以后添加了remote_user参数,也支持sudo操作,如果需要整个编排在sudo下执行操作,那么你可以这么指定:

    ---
    - hosts: webservers
      remote_user: yourname
      sudo: yes

同样,你也可以仅在一个task中使用sudo执行命令,而不是在整个playbook中使用sudo:

    ---
    - hosts: webservers
      remote_user: ubuntu
      tasks:
        - service: name=nginx state=started
        sudo: yes

注意:当使用sudo执行操作时,务必要在运行ansible-playbook命令后加上一个参数--ask-sudo-pass,或者在配置文件中配置ask_sudo_pass = True,不然的话,程序将一直卡在询问sudo密钥那里,处于一个伪挂掉的进程。

3. ansible-playbook命令

ansible-playbook的使用方法很简单,当编辑好yaml文件后,正常执行一个编排,在命令上直接加上yaml文件作为参数即可。下面我们详细讲解ansible-playbook命令。

语法格式:

    ansible-playbook playbook.yml [选项]

常用选项:

    --ask-vault-pass           询问vault密码
    --flush-cache             清空fact缓存
    --force-handlers           强制执行handlers,尽管tasks执行失败
    --list-hosts              打印出要执行任务的主机清单
    --list-tags               打印出所有可用的tags
    --list-tasks              打印出所有的任务
    --skip-tags=SKIP_TAGS       跳过某一个tags
    --start-at-task=START_AT_TASK从哪一个任务开始执行
    --syntax-check             检查yaml文件的语法格式

通常情况下,我们在运行ansible-playbook命令之前,会执行如下命令:

(1)检查yaml文件的语法

    user@ops-admin:~$ ansible-playbook mysql.yml --syntax-check

(2)打印出要执行任务的主机信息

    user@ops-admin:~$ ansible-playbook mysql.yml --list-hosts

(3)打印出要执行的任务

    user@ops-admin:~$ ansible-playbook mysql.yml --list-tasks

(4)打印出所有可用的tags

    user@ops-admin:~$ ansible-playbook mysql.yml --list-tags

(5)执行时,可以指定并发的数量

    user@ops-admin:~$ ansible-playbook mysql.yml -f {$number}

示例:

    user@ops-admin:~$ ansible-playbook mysql.yml --syntax-check
    playbook: mysql.yml

    user@ops-admin:~$ ansible-playbook mysql.yml --list-hosts
    playbook: mysql.yml
      play #1 (cloud): cloud   TAGS: []
        pattern: ['cloud']
        hosts (2):
          172.31.131.37
          172.16.168.1

    user@ops-admin:~$ ansible ansible-playbook mysql.yml --list-tasks
    playbook: mysql.yml
      play #1 (cloud): cloud   TAGS: []
        tasks:
          重启mysql服务TAGS: []

    user@ops-admin:~$ ansible ansible-playbook mysql.yml --list-tags
    playbook: mysql.yml
      play #1 (cloud): cloud   TAGS: []
          TASK TAGS: []

4.变量

在ansible命令中是不可以直接使用变量的,但可以在ansible-playbook命令中使用变量,下面介绍如何定义变量、如何使用定义的变量。

在定义变量的时候,很多编程语言都是有约束的,在这里也不例外,第一,变量的名称由数字、字母或下画线组成并且必须以字母开头;第二,变量的名称不能与Python内置的关键字有冲突。

如何定义变量?最基本的应该有如下四种方式:

(1)通过命令行传递变量(extra vars)

示例:

    user@ops-admin:~$ ansible-playbook release.yml -e "user=root"

说明:

这种方法在简单的测试中可以使用,但是不推荐使用,它会为运维带来许多的不便,因为不常使用的话,可能会造成yml使用了一个未定义的变量。

(2)在inventory中定义变量(inventory vars)

    # 定义主机变量
    [webservers]
    host1 http_port=80 maxRequestsPerChild=808

    # 定义组的变量
    [webservers:vars]
    ntp_server= ntp.example.com

(3)在playbook中定义变量(play vars)

    ---
    - hosts: demo
    vars:
    http_port: 80

(4)从角色和文件包含中定义变量(roles vars)

    http_port: 80
    https_port: 443

既然有多种定义变量的方式,它们定义的变量的优先级自然也是不一样的。所有的定义变量的方式不止如上几种,但最常用的就是如上几种,它们的优先级如下:

    • role defaults
    • inventory vars
    • inventory group_vars
    • inventory host_vars
    • playbook group_vars
    • playbook host_vars
    • host facts
    • play vars
    • play vars_prompt
    • play vars_files
    • registered vars
    • set_facts
    • role and include vars
    • block vars
    • task vars
    • extra vars

倘若在多个地方定义了一个相同的变量,优先级越高的变量就会被加载使用,如上面所示,越下面的优先级越高,比如在所有的地方都定义了同一个变量,将会加载使用extra vars定义的变量。

我们已经知道很多关于定义变量的方式,那么你知道如何使用它们吗?

● 在模板中使用变量

    This dir is {{ install_dir }}

● 在playbook中使用变量

    template: src=/root/data/redis.conf dest={{ remote_install_path }}/redis.conf

在yaml文件使用变量时,我们要特别注意,这是yaml的一个陷阱,同时也是一个低级错误,比如有一些人会这么使用的:

    - hosts: app_servers
      vars:
        app_path: {{ base_path }}/22

这样编写yaml文件是错误的,文件将会解析出错,那么该如何编写呢,加上双引号即可,如下:

    - hosts: app_servers
      vars:
          app_path: "{{ base_path }}/22"

5.条件选择

一般而言,tasks要执行的任务往往是取决于一个变量的值,但在有些情况下,我们需要判断被管理远程服务器的系统内核版本,或者不同系统上可灵活地执行响应的命令时,就需要我们通过条件的选择确定执行哪些操作,Ansible直接提供了条件选择when语句。

在playbook上使用when是相当简单的,我们举例加以说明:

    ---
    - hosts: demo
      tasks:
        - name: 使用when测试
        shell: echo "i am redhat os"
        when: ansible_os_family == "RedHat"

当我们将这个编排基于Ubuntu的Unix/Linux系统运行时,会出现怎样的结果呢?我们来看一下。

    user@ops-admin:~$ ansible-playbook when.yml

    PLAY [demo] *******************************************************

    TASK [Gathering Facts] ***********************************************
    ok: [172.31.131.37]
    ok: [172.16.168.1]

    TASK [使用when测试] *************************************************
    skipping: [172.16.168.1]
    skipping: [172.31.131.37]

    PLAY RECAP *********************************************************
    172.16.168.1     : ok=1   changed=0   unreachable=0   failed=0
    172.31.131.37    : ok=1   changed=0   unreachable=0   failed=0

从返回的信息中我们可以看到,在执行到TASK时已经跳过了这个任务。

条件判断是经常要使用的,就好比上面说的服务器是什么发行版、内核是多少的,这就用到了字符串的比较以及数字的比较,那么我们可以这么编写playbook文件:

    ---
    - hosts: cloud
      tasks:
        - name: 使用when测试字符串、数字的比较
        - shell: echo "only on Red Hat 6, derivatives, and later"
        when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int >= 6

我们还可以通过布尔值来进行比较,如下:

    ---
    - hosts: cloud
      var:
        xuan: True
      tasks:
        - name: 使用when测试布尔值的比较
        - shell: echo "this is true"
        when: xuan

或者:

    ---
    - hosts: cloud
      var:
        xuan: False
      tasks:
        - name: 使用when测试布尔值的比较
        - shell: echo "this is false"
        when: not xuan

很多时候我们使用变量去比较,基本上都是将执行命令的结果作为比较的源值,但是还有一种情况,就是我们会使用系统内置的变量来比较,那我们怎么知道哪些是内置的变量呢,Ansible框架为我们封装了变量,自然也为我们封装了如何查看系统内值变量的命令,那我们如何查看呢?很简单,如下的一条命令即可查询系统内部的所有变量以及系统变量的值:

    user@ops-admin:~$ ansible {$hostname} -m setup

示例:

    user@ops-admin:~$ ansible 172.31.131.37-m setup
    172.31.131.37 | SUCCESS => {
        "ansible_facts": {
          "ansible_all_ipv4_addresses": [
              "192.168.56.1",
              "172.31.131.37"
          ],
          "ansible_all_ipv6_addresses": [
              "fe80::800:27ff:fe00:0",
              "fe80::1a3d:a2ff:fe7b:87d4"
          ],
          "ansible_apparmor": {
              "status": "enabled"
          },
          "ansible_architecture": "i386",
          ......

注册变量,将执行的返回值注册在一个变量里面,也就是在一个register变量中赋值。

示例:

    ---
    - name: register vars
      hosts: 172.31.131.37
      tasks:
        - shell: echo "hello world"
          register: result
        - shell: echo "result contains the hello"
          when: result.stdout.find('hello') ! = -1
        - debug: msg="{{result.stdout}}"

6.循环

如果想在一个任务中干很多事,比如,创建批量用户、安装很多包,或者重复一个轮询步骤直到得到某个特定结果,那么可以使用循环来做,使得编排文件更加简洁,易读。在Ansible运维框架中,循环具体可以分为很多种,我们列举几种常用的循环。

(1)标准循环

    ---
    - name: 测试标准的循环
    hosts: cloud
    tasks:
    - shell: echo "{{ item }}"
    with_items:
    - one
    - two

(2)哈希表循环

比如,我们有如下哈希变量:

    ---
    username: demo_user1
    palce: yj-q
    username: demo_user2
    palce: sz-b

如果想将哈希表所有用户的username以及place的值全部循环读出来,那么这个编排的哈希循环就应该这么写:

    tasks:
    - name: read user username as well as place records
    debug: msg="User {{ item.key }} is {{ item.value.username }} ({{ item.value.palce }})"
    with_dict: "{{users}}"

(3)文件列表循环

with_fileglob可以以非递归的方式来模式匹配单个目录中的文件。

    ---
    - hosts: cloud
    name: copy files to cloud
    tasks:
    - file: dest=/data/www state=directory
    - copy: src={{ item }} dest=/data/www/ owner=www
    with_fileglob:
    - /home/user/www/*

(4)并行数据集收集循环

并行数据集收集循环在运维时使用的频率不高,使用with_together即可做到。

变量数据源如下:

    ---
    softwares: [ 'apache2', 'mysql', 'php' ]
    versions:  [ 2, 5, 7 ]

如果目标是想得到( 'apache2', 2 )、( 'mysql',5 )这样的数据,那么就可以使用with_together,如下:

    tasks:
    - debug: msg=" the {{ item.0 }} version is {{ item.1 }}"
    with_together:
    - "{{softwares}}"
    - "{{versions}}"

(5)整数循环

with_sequence可以以升序的数字顺序生成一组序列,并且你可以指定起始值、终止值,以及一个可选的步长值,指定参数时使用key=value这种键值对的方式。数字值可以被指定为十进制、十六进制,或者八进制:

    ---
    - hosts: cloud
    name: 创建apache映射的10个目录,8000至8010
    tasks:
    - file: dest=/app/www/apache/proxy/{{ item }} state=directory
    with_sequence: start=8000 end=8010 stride=1

(6)do-until循环

    - name: 测试do-until循环
    hosts: cloud
    tasks:
    - shell: echo "error"
    register: result
    until: result.stdout.find("okay") ! = -1
    retries: 5
    delay: 10

上面的例子是递归运行Shell模块,直到模块结果中的stdout输出中包含"okay"字符串为止,或者该任务按照10秒的延迟重试5次。"retries"和"delay"的默认值分别为3和5。

7. roles

通过上面学习的ansible-playbook,我们已经大概懂得了如何使用playbook,但是如果你使用的task任务数量很多,并且有些tasks发现会重复等情况,那该如何去组织一个playbook的良好编排呢?我们可将不同类型的模板进行封装,最终让编排去加载vars、tasks、files等。不错,Ansible框架已经封装了这样的框架——roles,基于roles对内容进行分组,使得我们可以容易地与其他用户分享roles。

roles用于层次性、结构化地组织playbook, roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。要使用roles,只需要在playbook中使用include命令即可。简单地讲,roles就是通过分别将变量(vars)、模板(templates)、任务(tasks)、文件(files)及处理器(handlers)等放置于单独的目录中,并可以便捷地包含(include)它们的一种机制。

我们建立一个简单的项目来详细讲解,先建立一个roles,以及roles结构文件目录:

    user@ops-admin:~$                 sudo                 mkdir                 -p
    /etc/ansible/roles/curl/{files, templates, tasks, handlers, vars, defaults, meta}

目录(详细配置)如下:

    user@ops-admin:/etc/ansible/roles/$  tree -L 2
    .
    ├── curl
    │  ├── defaults
    │  ├── files
    │  ├── handlers
    │  ├── meta
    │  ├── tasks
    │  ├── templates
    │  └── vars
    └── ... ...

这些文件的作用如下。

● defaults:默认寻找路径。

● files:文件存储目录。

● handlers :notify调用部分playbook存放路径。

● meta:角色依赖存储目录。

● tasks:存放playbooks的目录。

● templates:存储模板文件的目录。

● vars:存储变量的目录。

从roles文件的目录来看,这些文件目录的分门别类就好比程序开发者的设计模式,不同类型的文件放在不同的包目录下,roles模式也是一样,这样的好处很多,无论你是运维的主机数目有成千上万台还是你的角色任务非常多,有了roles模式的话,文件的存放就更加有规律,重复的模块可以只写一次却可以被多次include使用。

现在,curl就是我们第一个roles的名称,而这个roles的工作流程是写在tasks/main.yml之中的。现在打开curl/tasks/main.yml并在其中写入以下内容:

    ---
      - name: install curl
        apt:
        name: curl

接着打开playbook.yml文件,修改为以下内容:

    ---
    - hosts: ironman
      roles:
        - { role: curl, become: yes }

如上所示,运行playbook时会执行curl这个我们刚定义好的roles。其中,become代表我们要提权(等效于Unix/Linux中的sudo指令)来运行当前工作。

更加详细的配置过程可以在官方文档(http://docs.ansible.com/ansible/)中找到。

8. Ansible部署容器

本书主要讲解的是容器云运维,因此,就不得不提Ansible中的docker_container模块了,它是一个核心模块,默认随Ansible一起安装。

下面用Ansible演示如何在几台服务器中部署Nginx容器(节点已安装Docker)。

task配置如下:

    ---
    - name: nginx container
      docker:
        name: nginx
        image: nginx
      state: reloaded
      ports:
      - "::"
      cap_drop: all
      cap_add:
        - setgid
        - setuid
      pull: always
      restart_policy: on-failure
      restart_policy_retry: 3
      volumes:
        - /some/nginx.conf:/etc/nginx/nginx.conf:ro
    tags:
      - docker_container
      - nginx
  ...

然后启动,因为还没有讲解Docker的相关知识,这里了解一下Ansible的相关模块,知道使用Ansible可以轻松初始化Docker服务即可。目前Ansible与Docker有关的模块有下面几个,都非常容易使用,文档也详细:

● docker (D)——管理Docker容器(弃用)。

● docker_container——管理Docker容器。

● docker_image——管理Docker镜像。

● docker_image_facts——查看镜像详情。

● docker_login——登录Docker镜像仓库。

● docker_network——管理Docker网络。

● docker_service——管理Docker服务和容器。