QuXiao's Blog

Life && Tech && Thoughts

Ansible 简易教程

Written on

随着团队人数、以及线上模块的逐步增加,感觉逐渐暴露了不少问题,上线流程不规范就是其中之一。以前模块少,机器也少,线上用户也不是很多,因此可以随便怎么搞,比如之前scp过去、甚至停服个1~2分钟再更新模块上线,也是可以容忍的。现在的情况和初期已经有所不同,模块和机器增多了,每次上线变得越发麻烦,如果还是人工上线,效率太低了,还容易出错。因此,需要比较专业的上线工具的支持。

经过调研,决定采用Ansible这个工具,写了一份建议教程,仅大家参考。

Ansible 是什么?

Ansible 是一套IT自动化工具,因为基于SSH,所以不需要像Salt这些同类工具一样需要在每台机器上安装agent,因此添加机器几乎不需要进行额外的操作。

Inventory

既然Ansible是无agent模式的,那么就需要维护一份主机/分组的数据,其格式如下:

# 主机可以单独列出
# 如果ssh port不是22,直接显式表示
www.hangzhou-bak00.hangzhou01.example.com:7000

# web 分组
[web]
www.hangzhou-web00.hangzhou01.example.com
www.beijing-web01.beijing01.example.com

[dataparser]
www.hangzhou-dataparser00.hangzhou01.example.com
www.hangzhou-dataparser01.hangzhou01.example.com
www.beijing-dataparser00.beijing01.example.com

# 分组可以按照任意的逻辑,主机和分组之间是多对多的关系
# 比如按照机房进行分组
[hangzhou]
www.hangzhou-web00.hangzhou01.example.com
www.hangzhou-dataparser00.hangzhou01.example.com
www.hangzhou-dataparser01.hangzhou01.example.com

Ansible Ad-Hoc 命令

临时执行的命令,直接通过例子来学习吧。

ping主机

ansible -i inventory/production ngxdata -m ping --ask-pass -uuser
SSH password:
www.hangzhou-ngxdata00.hangzhou01.example.com | success >> {
    "changed": false,
    "ping": "pong"
}

www.beijing-ngxdata00.beijing01.example.com | success >> {
    "changed": false,
    "ping": "pong"
}

其中:

  • inventory/production 是inventory文件
  • ngxdata 表示一个分组的名称
  • -m ping 表示使用一个叫做ping的module(ansible提供了各种功能丰富的module)
  • --ask-pass 表示执行命令后输入对端机器的密码(如果建立信任关系,则不需要该选项)
  • -uuser 表示以 user 用户执行命令

批量执行命令

ansible -i inventory/production web -m shell -a 'date' --ask-pass -uuser
SSH password:
www.hangzhou-web00.hangzhou01.example.com | success | rc=0 >>
Wed Aug 26 13:08:43 CST 2015

www.beijing-web01.beijing01.example.com | success | rc=0 >>
Wed Aug 26 13:08:43 CST 2015

copy文件

ansible -i inventory/production bak -m copy -a 'src=/home/user/local/ansible/inventory/production dest=/home/user/opbin/quxiao/test/production' --ask-pass -uuser
SSH password:
www.hangzhou-bak00.hangzhou01.example.com | success >> {
    "changed": false,
    "checksum": "73cf77952a9379ba8564cef216a93aeda22ee150",
    "dest": "/home/user/opbin/quxiao/test/production",
    "gid": 500,
    "group": "user",
    "mode": "0664",
    "owner": "user",
    "path": "/home/user/opbin/quxiao/test/production",
    "size": 1710,
    "state": "file",
    "uid": 500
}

服务启停

Ansible可以通过service或者supervisor来操作服务的启停,就以我们使用最多的supervisor为例:

ansible -i inventory/production bak -m supervisorctl -a 'name=web state=restarted config=/home/user/local/supervisor/conf/supervisord.conf' --ask-pass -uuser --check
SSH password:

www.hangzhou-bak00.hangzhou01.example.com | success >> {
    "changed": false,
    "name": "web",
    "state": "restarted"
}

注意,参数中多了一个 --check ,他表示命令只是进行『预演』,并没有在机器上真正的执行命令。

Ansible Playbook

Ad-Hoc命令只能执行一些临时性的、简单的命令,而模块部署上线都是需要经过多个步骤的,复杂的还会涉及到不同机器的多个模块之间的依赖关系。因此,Ansible提供了一个叫Playbook(剧本)的工具,来定义这些步骤以及依赖。

结构

根据Ansible官方Doc说明,Playbook建议采用如下结构:

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

group_vars/
   group1                 # here we assign variables to particular groups
   group2                 # ""
host_vars/
   hostname1              # if systems need specific variables, put them here
   hostname2              # ""

library/                  # if any custom 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

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

不过,很多地方暂时还用不到,我把结构精简了下:

├── inventory                        # 存放主机/分组配置
│   └── production
└── playbooks                        # 第一层存放不同的剧本
    ├── playbook1.yml
    ├── playbook2.yml
    ├── playbook3.yml
    ├── playbook4.yml
    ├── roles                        # 不同的角色
    └── common
        │   ├── defaults
        │   ├── handlers
        │   ├── library
        │   ├── meta
        │   ├── tasks
        │   │   ├── main.yml
        │   │   ├── rsync.yml
        │   │   └── selinux.yml
        │   └── vars
        └── monitor                    # 一个名字叫monitor的模块
            ├── defatuls
            ├── files                  # 待上线的binary
            │   └── monitor
            │       └── bin
            │           └── monitor
            ├── handlers               # binary或者conf更新之后,执行的操作
            │   └── main.yml
            ├── meta
            ├── tasks                  # 上线步骤
            │   └── main.yml
            ├── templates              # 待上线的conf
            │   └── monitor
            │       └── conf
            │           └── conf.ini
            └── vars

上面的结构中,『roles』文件夹下面可以理解为一个个单独的(有时间可以复用)的模块,假设有一个『monitor』模块;『playbooks』下面就是一个个不同粒度的任务序列。

样例

举个例子,『monitor』的playbook如下:

---
- hosts: all
  remote_user: user
  roles:
    - monitor

表示对于所有的机器上面,应用『monitor』这个角色,当应用的时候,首先会执行『tasks/main.yml』的内容。

monitor/tasks/main.yml

---

# 更新配置文件,如果更新了,则执行『restart monitor』这个handler
- name: be sure monitor is configured
  template: src=monitor/conf/monitor.ini dest=/home/user/local/monitor/conf/monitor.ini backup=yes
  notify:
    - restart monitor

# 更新binary,如果更新了,则执行『restart monitor』这个handler
- name: be sure monitor binary is updated
  copy: src=monitor/bin/monitor dest=/home/user/local/monitor/bin/monitor backup=yes
  notify:
    - restart monitor

# 保证binary是运行的
- name: be sure monitor is running
  supervisorctl: name=monitor state=started config=/home/user/local/supervisor/conf/supervisord.conf username=user password=password

再来看下handler部分,注意,tasks中的notify下面的名称(restart monitor),和handler中的name是一一对应的。

roles/monitor/handlers/main.yml

---

- name: restart monitor
  supervisorctl: name=monitor state=restarted config=/home/user/local/supervisor/conf/supervisord.conf username=user password=2012user

对于一些较为具体的playbook,也可以合并为一个playbook。例如,一个功能是由很多模块组成,只需要将不同的playbook 『include』进来,合并为一个统一的playbook:

playbooks/monitor.yml

---
- include: monitor_sub1.yml
- include: monitor_sub2.yml
- include: monitor_sub3.yml

运行Playbook

最后,我们看一下执行playbook的效果,比如我们需要上线『monitor』这个模块。

全部执行的样例如下:

nsible-playbook -i inventory/production playbooks/monitor.yml --diff --ask-pass --check -uuser
SSH password:

PLAY [all] ********************************************************************

GATHERING FACTS ***************************************************************

ok: [www.hangzhou-web00.hangzhou01.example.com]
......

TASK: [monitor | be sure monitor is configured] *****************

ok: [www.hangzhou-web00.hangzhou01.example.com]
......

TASK: [monitor | be sure monitor binary is updated] *************

ok: [www.beijing-web01.beijing01.example.com]
......

TASK: [monitor | be sure monitor is running] ********************

ok: [www.hangzhou-web00.hangzhou01.example.com]
......

PLAY RECAP ********************************************************************
           to retry, use: --limit @/home/user/monitor.retry

www.hangzhou-web00.hangzhou01.example.com : ok=4    changed=0    unreachable=0    failed=0
......

分组选择

可能只需要执行其中的一个分组,那么可以通过『--limit』选择来限定。

ansible-playbook -i inventory/production playbooks/monitor.yml --limit web --diff --ask-pass --check -uuser
SSH password:

PLAY [all] ********************************************************************

GATHERING FACTS ***************************************************************

ok: [www.beijing-web01.beijing01.example.com]

ok: [www.hangzhou-web00.hangzhou01.example.com]

TASK: [monitor | be sure monitor is configured] *****************

ok: [www.hangzhou-web00.hangzhou01.example.com]

ok: [www.beijing-web01.beijing01.example.com]

TASK: [monitor | be sure monitor binary is updated] *************

ok: [www.hangzhou-web00.hangzhou01.example.com]

ok: [www.beijing-web01.beijing01.example.com]

TASK: [monitor | be sure monitor is running] ********************

ok: [www.hangzhou-web00.hangzhou01.example.com]

ok: [www.beijing-web01.beijing01.example.com]

PLAY RECAP ********************************************************************
www.beijing-web01.beijing01.example.com : ok=4    changed=0    unreachable=0    failed=0
www.hangzhou-web00.hangzhou01.example.com : ok=4    changed=0    unreachable=0    failed=0

更详细的,比如只想某一个分组中的一台机器,可以在『–limit』后面的分组加上下标:

ansible-playbook -i inventory/production playbooks/monitor.yml --limit web[0] --diff --ask-pass --check -uuser
SSH password:

PLAY [all] ********************************************************************

GATHERING FACTS ***************************************************************

ok: [www.hangzhou-web00.hangzhou01.example.com]

TASK: [monitor | be sure monitor is configured] *****************

ok: [www.hangzhou-web00.hangzhou01.example.com]

TASK: [monitor | be sure monitor binary is updated] *************

ok: [www.hangzhou-web00.hangzhou01.example.com]

TASK: [monitor | be sure monitor is running] ********************

ok: [www.hangzhou-web00.hangzhou01.example.com]

PLAY RECAP ********************************************************************
www.hangzhou-web00.hangzhou01.example.com : ok=4    changed=0    unreachable=0    failed=0

This entry is posted in note.

comments powered by Disqus