Skip to main content

Ansible 快速上手到项目最佳实战 🚀

·1947 字·10 分钟
DevOps ansible
Table of Contents

Ansible,运维工具里的瑞士军刀,简单、粗暴

💬 很多人会告诉你自动化工具(automation)不是 DevOps,但他们没有告诉你什么是 DevOps 的核心,如果你把自动化(automation)从他们的表述中除去,你看看 DevOps 还是什么呢。Ansible 从核心上讲,就是一个可编程的 automation 框架,通过各种扩展,完成运维里各式各样的脏活累活,如创建用户,安装配置软件,等。

Why Ansible #

做 IT 的的都知道,除了设计,写代码,离不开装机器,装软件,装系统,原始的办法是手动安装和配置,但对于一个大型项目、产品或系统:

  • 需要装很多次(1000+)
  • 需要在很多机器上装(1000+)
  • 需要在不同的环境和不同的 OS 上装
  • 需要为不同的客户定制安装
  • 如果是手工行为,步骤超多,如何防止人为疏忽
  • 安装、运维文档如何更新,维护

最原始的解决办法就是写脚本,尽量自动化,让机器来替我们干活 - 机器不累,不出错。但如何写出高质量,可复用,可叠加的脚本呢 ⏤ 脚本就比较难胜任了。这就是为什么需要 Ansible,它的核心完成两个最基本的事情:

  • 配置管理(configuration or change management):Ansible 不是专门的配置管理系统 CMDB(configuration management database),但没有这些配置信息无法干活啦,一般软件项目需要的基础配置也就是 ip, hostname, 环境变量,config file, password,等等。Ansible 能够提供配置管理,但更像是 CMDB 的执行者,把配置实现到机器上。Ansible 可以和 各种 CMDB 实时结合工作,达成配置动态收集,动态更改,动态实现,这个“高级”用法就不在本文讨论范围。

  • 自动化框架(automation framework):注意不只是 automation,这里其实包含了几重意思:

    • Ansible 是用 Python 实现的,可以干几乎任何编程语言能干的事情,所以是可编程的自动化框架,实现自动化的扩展和重用,Python 是一种超级”胶水“语言,贴近自然语言,运维二次开发非常容易;
    • Ansible 使用上采用类似 SQL“声明式”的 命令(yaml),而不是一大段具体的自动化动作细节,关注点在“What”上而不是“How”,这是自动化层面的抽象,有别于脚本,使用者也不必纠缠于五花八门的命令行用法细节;
    • ssh instead of agent:ssh,Python 在 Linux 几乎所有的版本上自带,所以 Ansible 的使用依赖几乎为 0(唯一问题是诞生之初不考虑 Windows,最近才支持);
    • Ansible 没有像 Chef、Puppet 那样复杂,二次,三次,四次抽象的概念和架构,有运维知识,1,2 小时基本就可上手,Ansible 贴近 shell/Linux 原生系统,一条 Ansible 命令影响成百上千台机器,任何复杂系统的配置包装一下就可以融入 Ansible,简单粗暴,但非常灵活,对于运维这种千奇百怪的脏活、累活,超级耐操;
    • 编排自动化的动作(orchestration:把几个事情串在一起执行像交响乐演奏需要各种乐器的一起配合),这样才能完成用户特定的复杂事情,这就比脚本和脚本库高一个档次;

在 IT 界,重复发明轮子的事多了去,Ansible 也属于此,但和老牌的 Chef、Puppet 相比,优势在于易用、好用、耐用。

YAML 标记语言 #

Ansible 的主要工作都是通过编写 YAML 文件完成的。YAML,一种标记语言,Python 社区发明的,现在广泛采纳用来写配置 template,和 Json、XML 类似。Json 可以说是完全极其抛弃臃肿的 XML,YAML 则是以 Json 的基础,更为简练,同时加入更为丰富的表达,如可以有 comment,比较一下:

yaml

YAML 语法以健值对为基础,可以表达散列表、标量等数据结构,结构通过空格和新的一行来展示,文件扩展名为 yml 或 yaml。YAML 简单直观,非常容易理解。

语法注意:

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进时不允许使用 Tab 键,只允许使用空格
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
  • 基本数据类型:支持整型、浮点型、时间戳类型、布尔(true/false,yes/no)、字符串等基本数据类型
  • 序列(数组)里配置项通过 - 来代表
  • 序列(数组)的元素是 Primary(字符串,数值,布尔)可以用 [] 来表示
  • 多个健值对可构造成复杂结构(Object),如上图所示的 microservices 数组元素
  • 键值用 : 来分隔
  • 注释用 #,没有多行注释
  • 字符串可以用‘或“来包裹,也可以完全不用,特殊字符则用 \ 表达,如换行符 \n
  • 多行字符串用 打头
  • 通过 \$xxx 来引用环境变量xxx
  • 动态值用 {{ }} (placeholder)来表示

下面是个例子(Ansible 的 playbook):

---
- hosts: webservers

  vars:
      http_port: 80
      max_clients: 200

  remote_user: root

  tasks:
      - name: ensure apache is at the latest version
        yum:
            name: httpd
            state: latest

      - name: write the apache config file
        template:
            src: /srv/httpd.j2
            dest: /etc/httpd.conf
        notify:
            - restart apache

      - name: ensure apache is running
        service:
            name: httpd
            state: started

  handlers:
      - name: restart apache
        service:
            name: httpd
            state: restarted
Ansible 由于历史原因,其使用的 YAML 根据版本和所谓标准的 YAML 有细微差别。

工作原理和基本概念 #

Ansible 从一个管理节点(management node)上通过 ssh 发送命令(Python 程序)到各个机器上执行,基本执行原理可以用下面这张图概述:

Ansible 执行

图中包含了 Ansible 的基本概念/术语:

  • Ansible Inventory:定义目标机器,程序在哪里执行
  • Ansible Modules: Ansible 自带或自己扩展的程序,运行后达到一定的作用/目的
  • Ansible Playbook:指示如何执行 Modules,把 Inventory 和 Modules 连接在一起

inventory #

inventory 以 一个或多个 YAML 文本文件形式存在(非标准 YAML),可定义机器的信息(也称为机器变量),包含 主机名、ip、端口、登录用户名 等。执行时通常要给 Ansible 指定一个 inventory 。动态机器信息 或外部 inventory 不在这里讨论。

编写 inventory 也是个技巧,下面 inventory 的例子定义了 dev 环境中的机器:

localhost ansible_connection=local
myapp-db1 ansible_ssh_host=172.16.0.62
myapp-db2 ansible_ssh_host=172.16.0.63
myapp-app1 ansible_ssh_host=172.16.0.60
myapp-app2 ansible_ssh_host=172.16.0.61
myapp-web1 ansible_ssh_host=172.16.0.64
myapp-web2 ansible_ssh_host=172.16.0.66

[dev:children]
myapp-dbs
myapp-apps
myapp-webs

[myapp-dbs]
myapp-db1
myapp-db2

[myapp-apps]
myapp-app1
myapp-app2

[myapp-webs]
myapp-web1
myapp-web2

[dev:vars]
ansible_ssh_user=ansible
ansible_ssh_private_key_file="/home/ansible/.ssh/id_rsa"

myapp-db1 ansible_ssh_host=172.16.0.62
定义了 myapp-db1 这台机器 host,ansible_ssh_host 是系统变量名,用来定义此台机器的 IP 地址(其实是隶属于 myapp-db1 的一个 key),自己可以加入自定义的变量。

[dev:children]
children 这个关键字说明 dev 的成员 myapp-dbs 和 myapp-apps 不是单个 host,而是一个组,[myapp-dbs] 和 [myapp-apps] 则分别定义了他们的组成员

[dev:vars]
vars 这个关键字说明里面的变量将作用在所有的 dev 成员上(dev 的通用变量),ansible_ssh_user 和 ansible_ssh_private_key_file 也都是系统变量名。

task & module #

task & module - task 就是最小执行单位(类似编程里的方法),module 模块就是提供一组相关的 task(类似编程里的类或者包)。Ansible 提供的 task 接近 2000 个(linux shell 的命令基本都用 Python 实现了),核心模块超过 400 个,运维需要的东西基本都包括了,具体见 Ansbile Module 文档。自己也可以通过 Python 扩充任务模块( 编写自己的模块),这个也不在本文讨论范畴,现有的模块已经基本够用了。

playbook #

playbook 也是 yaml 文本文件存在,类似于编程中你自己开发的程序,把不同的 task 串联在一起(类似调用不同的方法和类),完成你想要的事情。

编写 playbook 是使用 Ansible 进行运维的主要工作,playbook 里定义了一系列的 tasks,每一段包含下面的一些核心元素:

名称含义
name任务组名称或描述
hosts目标机器
become,become_user指定登录执行任务的用户名,目标机器和执行任务的用户可能不同,例如执行某些任务需要 root 权限
tags任务标签,可用于选择选择性执行
Tasks任务集,一组具体操作,可以调用某个或多个任务模块(task module)、某个或多个角色(task role)来完成
Varniables变量,内置变量或自定义变量在 playbook 中调用
Templates模板,即使用模板语法的文件,比如配置文件等
Handlers处理器,和 notity 结合使用,由特定条件触发的操作,满足条件方才执行,否则不执行
Roles角色,可以重复调用

一个例子:

#!/usr/bin/env ansible-playbook
# provision-dbs.yml

- name: set fact-related variables (always run)
  hosts:
      - myapp-dbs
  gather_facts: true
  tags: always
  tasks:
      - include: vars.yml

- name: db server common
  hosts:
      - myapp-dbs
  become: true
  roles:
      - { role: server-common, tags: ["common"] }

- name: install & config mongodb
  hosts:
      - myapp-db1
  become: true
  roles:
      - { role: mongodb, tags: ["mongodb"] }

- name: install & config mysql
  hosts:
      - myapp-db2
  become: true
  roles:
      - { role: mysql, tags: ["mysql"] }

role #

role 由几部分组成:

  • defaults:定义该 role 用到默认变量,该 role 运行时自动加入这里定义的变量,但其定义的变量优先级别最低,若已在其他地方定义,可被覆盖
  • vars: 该 role 运行时自动加入这里定义的变量,通常可把需要传入的变量定义在这里,vars 和 defaults 很类似,通常把必须用到变量的放在 defaults 里,可变的放在 vars 里,例如 tasks 里针对不同的 OS,版本用到的变量,需要到的时候可以引入,include_vars 可自动在此目录中寻找变量文件(相对路径)
  • files:存放各种文件,该 role 需要执行相关 file tasks 时,file module 可自动在此目录中寻找文件(相对路径)
  • templates:该 role 需要执行 template task 时,可自动在此目录中寻找 template 文件(相对路径),template 这个 task 专门用来修改配置文件
  • tasks:该 role 需要执行的 tasks,类似 playbook,include 可在此目录中寻找 tasks 文件(相对路径)
  • handlers:定义该 role 需要的回调 task,在 tasks 里可以调用

role 类似你自己已经开发好的一个小程序,完成固定的功能,可以重复使用。你也可以直接使用社区里别人开发和分享的 role。社区里包含大量的 role,基本上你想做的事情都有人实现了,或者把 source code 拷下来,自己稍微修改,顺便学习如何编写自己的 role: 一个安装和配置 mysql 的 role

playbook 级别可以用 role: 来调用 role,task 级别可以用 import_role: 或者 include_role: 来调用 role

在运行时,Ansbile 把 playbook 结合 inventory 和 module/roles 编译成 Python 程序,managed node 通过 SSH 将其发送至目标机器,然后在目标机器上执行。 Ansible 把目标机器看成是一个状态机,每做一个 task,状态会发生改变,通过若干 task 后,目标机器就从原始状态达到你想要的状态,实现“状态变化管理”。如果目标机器已经处于目标状态中,该 task 就不会被执行。每一步变化是“等幂”的 - 可重复,可验证。

Ansible 结构

task,module,playbook,role 这些概念结合 Ansible 实际例子一看就会明白。

项目实战 #

项目模版 #

🔥 快速实战必读: Best Practices

production                # inventory file for production servers
staging                   # inventory file for staging environment

group_vars/
   group1.yml             # here we assign variables to particular groups
   group2.yml
host_vars/
   hostname1.yml          # here we assign variables to particular systems
   hostname2.yml

library/                  # if any custom modules, put them here (optional)
module_utils/             # if any custom module_utils to support modules, put them here (optional)
filter_plugins/           # if any custom filter plugins, put them here (optional)

site.yml                  # master playbook
webservers.yml            # playbook for webserver tier
dbservers.yml             # playbook for dbserver tier

roles/
    common/               # this hierarchy represents a "role"
        tasks/            #
            main.yml      #  <-- tasks file can include smaller files if warranted
        handlers/         #
            main.yml      #  <-- handlers file
        templates/        #  <-- files for use with the template resource
            ntp.conf.j2   #  <------- templates end in .j2
        files/            #
            bar.txt       #  <-- files for use with the copy resource
            foo.sh        #  <-- script files for use with the script resource
        vars/             #
            main.yml      #  <-- variables associated with this role
        defaults/         #
            main.yml      #  <-- default lower priority variables for this role
        meta/             #
            main.yml      #  <-- role dependencies
        library/          # roles can also include custom modules
        module_utils/     # roles can also include custom module_utils
        lookup_plugins/   # or other types of plugins, like lookup in this case

    webtier/              # same kind of structure as "common" was above, done for the webtier role
    monitoring/           # ""
    fooapp/               # ""

当然此模版有瑕疵,看下面的重点。

四部曲 #

如何编写和组织 inventory,role,playbook 是 Ansible 自动化实施的主要工作内容,我总结了这个四部曲的套路:

Ansible Steps

3,4 由 Ansible 完成,所以通常我会有两个对应 playbook ⏤ sys.yml 和 site.yml:

#!/usr/bin/env ansible-playbook
# sys.yml

- name: display environment name
  hosts: localhost
  tags: always
  tasks:
      - debug:
            msg: "MyApp environment >>> {{ app_env }}"

- name: display environment name
  hosts: localhost
  tags: always
  tasks:
      - debug:
            msg: "MyApp OS >>> {{ ansible_os_family }}"

- include: sys-dbs.yml

- include: sys-apps.yml

- include: sys-webs.yml
#!/usr/bin/env ansible-playbook
# site.yml

- name: display environment name
  hosts: localhost
  tags: always
  tasks:
      - debug:
            msg: "MyApp environment >>> {{ app_env }}"

- name: display environment name
  hosts: localhost
  tags: always
  tasks:
      - debug:
            msg: "MyApp OS >>> {{ ansible_os_family }}"

- include: site-cots.yml

- include: site-apps.yml

- include: site-misc.yml

重点:变量(vars) #

运维的复杂性和灵活性由变量(vars)来体现的,Ansible 的 var 可在多处定义,并有 优先顺序。不过不用那么复杂,关键是搞清楚变量应用的范围:

  • 全局变量 vars/main:不受部署环境影响,到处都要用到,全局变量的例子比如 project_name, app_user,db_port, 等等。

  • 环境变量 env_xxx/group_vars/all/xxx:项目开发中通常有多个环境 xxx:local, dev, stag, uat, prod,有些变量随环境改变而改变,但相当于该环境下的全局变量,除了 inventory/机器变量外,其它的变量例子如 host file, resource_dir,repo_url,等等。

💬 Ansible 没有提出全局变量和环境变量的使用和区别,只有 机器变量(group_vars & host_vars),Ansible Best Practices 里把它们当做了全局变量,但结构上是支持的,也就是每个环境下都可以分别有 inventory 和 group_vars & host_vars,而 all 就是默认的机器组,所以 env_xxx/group_vars/all/下所有的变量文件都会被识别为机器全局变量,而且 all 的机器变量引用时不需要前缀指定,特定的 group_vars & host_vars 变量 xxx 必须指明是哪个 group {{ groups[“dbs”].xxx }} 或 host{{ hostvars[“db1”].xxx }},“groups”和“hostvars”是 Ansible 内置变量。为了支持全局变量(各个环境都使用),可以通过 file link 的方法让每个 env_xxx/group_vars/all/main 指向同一个文件,上图中的 env_dev/group_vars/all/main 指向 vars/main。

  • 支持多 OS,多基础设施的变量:这种变量主要是支撑产品级的软件项目,通常是动态的,通过任务执行来设定,例如上面的 dbservers.yml 里引用的 vars.yml:
- name: define os family (suse)
  set_fact:
      os_family: "suse"
  when: ansible_os_family == "SuSe"

- name: define os family (redhat)
  set_fact:
      os_family: "redhat"
  when: ansible_os_family == "RedHat"

- name: define os family (debian)
  set_fact:
      os_family: "debian"
  when: ansible_os_family == "Debian"

- name: check os_family
  fail:
      msg: "ERROR - can not detect supported os_family"
  when: os_family is not defined

变量 os_family 在以后的 tasks 和 roles 中都可能用到。

  • role 变量:这些变量应用范畴只是该 role,是传统意义上各软件 component 的参数变量,在 role 里定义和使用,但 playbook 调用时可以进行指定或覆盖默认值,例如一个 Tomcat 的 war 部署时传入 war_name:
- name: deploy my app1
  hosts:
      - myapp-apps
  become: true
  become_user: "{{ app_user }}"
  roles:
      - { role: tomcat-deploy, war_name: "app1", tags: ["app1"] }
  • Ansible 流程和控制变量:上面的变量主要用来支持配置的,还有一些属于控制或者辅助控制 Ansible 流程的,这些通过 Ansible 提供的功能来指定,例如从命令行输入:
- name: create app user name & password
  hosts: all
  become: true
  become_user: root
  vars_prompt:
	- name: app_user prompt: "app user name: "
	- name: app_password prompt: "app password: "
	- name: app_group prompt: "app group: "
  user:
    name: "{{ app_user }}"
    password: "{{ app_password }}"
    group: "{{ app_group }}"
    append: yes
    generate_ssh_key: yes
    shell: /bin/bash
    home: "/home/{{ app_user }}"
    createhome: yes
    state: present

流程控制变量的例子见下面。

  • Ansible 默认/内置变量:例如 {{ playbook_dir }} 获得当前运行的 playbook 路径, {{ role_path }} 则是当前 role 的路径,这里有个 非常好的参考

重点:标签(tags) #

在调试和部署的时候,有些任务需要重复多次的,而 playbook 包含从头到尾全部的操作,所以需要指定特定的任务(过滤掉其它的),这时候标签(tags)发挥作用。Ansible 命令行支持两种标签指定方式:

  • -t or --tags
  • --skip-tags

怎么打标签关键是看需要做什么样的动作,例如 provision-dbs.yml 里:

- name: db server common
  hosts:
      - myapp-dbs
  become: true
  roles:
      - { role: server-common, tags: ["common"] }

如果指定标签 common,就可以执行所有 common 的任务。而在 common 这个 role 的 tasks 里:

- include: repo.yml
  tags: repo

- include: hostfile.yml
  tags: hostfile

- include: misc.yml

如果我在命令行里指定标签 hostfile,这样就能执行部分特定的 common 任务。通过不同标签的组合也可以完成一定的任务,例如用于升级系统的 tomcat:

> play appservers.yml -t tomcat,app1

标签 tag 最终是落在每一个 task 上的(透过 playbook,role),但直接给每个 task 打标签就很麻烦,为保持 role 的可移植,我基本不在 role 里打标签。我遇到了 playbook 的 include 打上标签,但不起作用(Ansible bug or defect?)。

有的时候 tags 还是不好用,例如 mysql 里要不要做 clustering,这时可以通过变量来控制,参考下面的 “有条件执行”。

基本操作和技巧 #

安装 #

Ansible 只安装在 management node 上,安装 Ansible 通过 Linux 上各种包安装工具如 RedHat 的 yum,Suse 的 zypper,Mac 的 brew 等,很容易搞定),如果 offline 安装,手动安装所需要的 rpm。由于 Ansible 是 Python 编写的,所以也可以用 pip 来安装。具体参见:  官方安装文档

Ansible 的目标机器可以是 Linux 或 Windows,但目前 management node 必须是 Linux,虽然有各种 hack,但强烈不建议 Windows 作为 management node。

设置 #

Ansible 的设置文件可以按顺序下面找到:

1.  \$ANSIBLE_CONFIG
2.  ./ansible.cfg
3.  ~/.ansible.cfg
4.  /etc/ansible/ansible.cfg
他们不会一起生效或者相互覆盖,遇到第一个设置文件就停止。

下面是 ansible.cfg 例子:

[defaults]
forks = 5
host_key_checking = False
gathering  = smart
roles_path = /usr/local/repository/ansible/roles

[priviledge_escalation]
become_method = sudo

[ssh_connection]
ssh_args = -o ForwardAgent=yes
scp_if_ssh = True
pipelining = True

执行 #

Ansible 的运行有两种主要模式,一种是 playbook 模式,另一种是随机模式。

playbook 例子,一个命令把整个系统搞起:

> ansible-play site.yml

随机例子 1,执行某个命令行"service status-all":

> ansible db1 -a 'service --status-all' -u ansible -b

随机例子 2,执行某个 task module(shell),通过 -a 输入该 task module 附加参数,效果和上面一样:

> ansible db1 -m shell -a 'service --status-all' -u ansible

当然 playbook 是主要模式。

gathering facts #

在 ansible.cfg 中加入:

gathering  = smart
fact_caching = jsonfile
fact_caching_connection = /app/myapp/deployment
fact_caching_timeout = 86400

可以收集机器信息,并放入缓存,大大加快每次执行的速度。

过滤 host #

除了 tags,可以通过 -l 来限定目标机器,Ansible 支持比较复杂的机器组合方式:

  • OR 关系: h1:h2
  • NOT 关系: !h1
  • Wildcard: web*.app.com
  • Regx: (~web[0-9]+)

通过 -t-l 就能指定在 {某个目标机器} 做 {某个动作},例如只在 db1 这台机器上执行 mysql:

> play dbservers.yml -t mysql -l db1

SSH Keys #

通过 key 免去登录密码输入,有几种选项:

  • 一个 key 登录所有机器,通过默认的 key 实现,在 ansible.cfg 文件里的 defaults 定义:
private_key_file=/etc/ansible/keys/access.pem
  • 一个 key 登录一组机器,例如在上面的 inventory 文件里:
[dev:vars]
ansible_ssh_user=ansible
ansible_ssh_private_key_file="/home/ansible/.ssh/id_rsa"
  • 一个 key 登录一台机器, 也是在在上面的 inventory 文件里定义:
myapp-db1 ansible_ssh_host=172.16.0.62 ansible_ssh_private_key_file=/etc/ansible/keys/db1.pem
myapp-db2 ansible_ssh_host=172.16.0.63 ansible_ssh_private_key_file=/etc/ansible/keys/db2.pem
myapp-app1 ansible_ssh_host=172.16.0.60 ansible_ssh_private_key_file=/etc/ansible/keys/app1.pem
myapp-app2 ansible_ssh_host=172.16.0.61 ansible_ssh_private_key_file=/etc/ansible/keys/app2.pem
  • 让 SSH/SSH-Agent 自己去解决

有条件执行 #

register, when, changed_when, failed_when 例子:

- name: install jdk rpm (suse)
  shell: zypper -n in {{ java_rpm }}
  when: os_family == "suse"
  register: install_java
  changed_when: install_java.rc == 0 and "already installed" not in install_java.stdout

- name: validate java home
  shell: . /etc/profile && echo $JAVA_HOME
  register: java_home_result
  failed_when: java_home_result.stdout is not defined or java_home_result.stdout

有些配置只需要做一次,例如数据库初始化,虽然 Ansible 提供了 run_once,但感觉不好用,还是直接用变量做开关来控制。

循环执行 #

下面的例子循环构建所有的 docker image:

# docker-build.yml

- name: build all service docker images
  host: app-build
  become: true
  tasks:
      - name: build a docker image
        include_role:
            name: docker-build
        vars:
            - image_name: "{{ item.name }}"
            - image_tags: "{{ item.tags }}"
        with_items: "{{ service_images }}"
        when: (includes is not defined) or (item.name in includes)

Ansible 通过 with_items、with_dict 等达到循环的目的,上面的例子还加入命令行动态变量 includes 来控制指定的 image:

# play docker-build.yml -e "includes=[service1, service2]"

多重循环:

- name: build all service docker images
  host: app-build
  become: true
  tasks:
      - name: build a docker image
        include_role:
            name: docker-build
        vars:
            - module_name: "{{ item.0.name }}"
            - image_name: "{{ item.1.name }}"
            - image_tags: "{{ item.1.tags }}"
        with_subelements::
            - “{{ modules }}”
            - service_images
        when: (includes is not defined) or (item.0.name in includes)

item.0 和 item.1 对应循环体的循环变量。

ansible-vault 存储敏感信息 #

密码等属于敏感信息,可以把包含敏感信息的文件通过 ansible-vault 加密,加密后的文件看起来就像乱码而非明文,例如:

> cat password.txt
$ANSIBLE_VAULT;1.1;AES256
66633936653834616130346436353865303665396430383430353366616263323161393639393136
3737316539353434666438373035653132383434303338640a396635313062386464306132313834
34313336313338623537333332356231386438666565616537616538653465333431306638643961
3636663633363562320a613661313966376361396336383864656632376134353039663662666437
39393639343966363565636161316339643033393132626639303332373339376664

创建 create,加密 encrypt,解密 decript,编辑加密文件 edit,都可以通过 ansible-vault 进行:

ansible@smarthub-repo:~> ansible-vault
Usage: ansible-vault [create|decrypt|edit|encrypt|encrypt_string|rekey|view] [--help] [options] vaultfile.yml

当直接运行包含这些加密 vars 文件的 playbook 时,会出错(加密后的 vars 已经乱码了),要操作加密文件必须有密码保护,ansible 或 ansible-playbook 要加上 ask-vault-pass:

$ ansible-playbook site.yml --ask-vault-pass

或者创建一个.vault_pass 的文件,把密码放进去,让 Ansible 自动去读取:

$ echo "my_vault_password" > .vault_pass
$ ansible-playbook site.yml --vault-password-file=.vault_pass

记住在生存环境上创建这个文件.vault_pass,不要把它 commit 进 git 或 svn,否则人人都知道解密密码啦:

$ echo '.vault_pass' >> .gitignore

由于 Ansible 只能支持整个文件加密,所以如果把变量分成要加密的,不要加密的,就会破坏变量本事的结构,导致可读性变差,例如:

$ cat group_vars/database/vars
# nonsensitive data
mysql_port: 3306
mysql_host: 10.0.0.3
mysql_user: fred

# sensitive data
mysql_password: this_is_my_password

想要对 mysql_password 加密的话,要么对整个文件加密,要么把 mysql_password 移出到另一加密文件,但这都不是我们想要的,技巧是创建另一变量,通过引入来解决:

$ cat group_vars/database/vars
# nonsensitive data
mysql_port: 3306
mysql_host: 10.0.0.3
mysql_user: fred

# sensitive data
mysql_password: “{{ vault_mysql_password }}$ ansible-vault view group_vars/database/vault
vault_mysql_password: this_is_my_password

debug #

通过环境变量:

$ export ANSIBLE_STRATEGY=debug

或者直接在 playbook:

- hosts: localhost
  strategy: debug
    tasks:
      -

当该任务出错时,Ansible 暂停执行,调用 debugger,这时可以查看或调整变量:

(debug) task.args

详细文档: https://docs.ansible.com/ansible/playbooks_debugger.html

另一个常用的方法时 debug module,拿来输出信息:

- name: display environment name
  hosts: localhost
  tasks:
    - debug:
        msg: "environment >>> {{ app_env }}"
        verbosity:3

- name: display OS name
  hosts: localhost
  tasks:
    - debug:
        msg: "OS >>> {{ ansible_os_family }}"
        verbosity:3

注意 verbosity,Ansible 命令行用 -v,-vv, -vvv, -vvvv 等来控制 debug 输出的 verbosity 程度,当 verbosity: 3 时,-v,-vv 时将看不到 debug 的输出。

其它运行技巧 #

把 ansible -playbook 匿名成 play,下面是几个有用的选项:

  • play site.yml –list-tasks:列出所有的 tasks
  • play site.yml –list-tags:列出所有的 tags
  • play site.yml –syntax-check:做语法检查
  • play site.yml –check :“虚假”运行,可告知那些 tasks 会产生改变,如果带上 -D or –diff,如果相关 module 支持,如 template,会显示前后改变的具体内容
  • play site.yml –step:“单步”执行