ansible简介

ansible,[ˈæn.sɪ.bəl]发音,Ansible 是一个开源的基于 OpenSSH 的自动化配置管理工具。可以用它来配置系统、部署软件和编排更高级的IT任务,比如持续部署或零停机更新。ansible的主要目标是简单和易用,并且它还高度关注安全性和可靠性。基于这样的目标,Ansible 适用于开发人员、系统管理员、发布工程师、IT 经理,以及介于两者之间的所有人。Ansible 适合管理几乎所有的环境,从拥有少数实例的小型环境到有数千个实例的企业环境。
使用ansible无须在被管理的机器上安装代理,所以不存在如何升级远程守护进程的问题,也不存在由于卸载了守护进程而无法管理系统的问题。

ansible工作方式

使用ansible无须在被管理的客户端电脑上安装代理之类的组件。它通过普通的 SSH 进行通信,以便从远程计算机检索信息、发出命令和复制文件。这是 Ansible 简化服务器管理的一种方式。任何公开 SSH 端口的服务器都可以通过 Ansible 进行配置和管理。

ansible采用模块化的设计,所以非常容易扩展到各种特定的使用场景。模块可以用任何语言编写,并使用标准 JSON 进行通信。Ansible 的配置文件是用 YAML 格式编写的,因为它使用起来非常简单,并且与主流的标记语言很相似。除了通过命令行工具 Ansible 还可以通过配置脚本(Playbooks)与客户端交互。

ansible总结

现在云原生比较流行,k8s来管理容器,达到大规模集群的自动化运维,虽然不在一个维度,但是相比起来效率更高,ansible基于ssh,基于linux本身机器,相比k8s来说,更偏向于底层,所以,还是有用武之地的,如果需要自动化操作linux机器,
ansible本质上来说,就是ssh的加强版,把ssh连接的机器分类,把执行的命令模块化,可编排,做抽象和封装,实现便捷的自动化运维

ansible安装

ansible管理机器分为两种,一种是管理的机器,相当于跳板机,另一种是被管理的机器,因为linux服务器都安装了ssh,所以,可以认为被管理的机器是不需要什么以来的,但是管理的机器,需要安装ansible

安装ansible

ansible安装,分为两种,一种是用linux发行版的pkg安装,这个基本上都有,直接安装就好了,例如ubuntu就是下面的命令

1
2
3
4
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible

还有一种方式是pip安装, 因为本身ansible是python写的,可以用python的包管理安装,但是这种安装以后,需要一些额外的配置,所以,不推荐在服务器上安装的时候使用这种方式,
但是这种方式依赖少,自己电脑上装得时候,可以尝试这种方式

ansible的hello world

ansible是ssh连接客户机器的,ssh连接分为两种,一种是密码,比较麻烦,也不适合自动化运维,不建议使用,一种是使用公钥登陆,而且这种安全性也高,不过需要一些额外的配置

  1. 生成公钥
    1
    ssh-keygen -t ed25519 # rsa已经被废弃了,如果使用rsa,还需要使用额外的配置,非常不推荐
  2. 发送到客户机器
    1
    ssh-copy-id -i ${your pub file} root@192.168.85.35 #使用什么用户,以及该机器的地址
  3. 配置ssh,一般来说,不应该使用默认的名字,而且管理的机器一定会有很多的私钥存放,所以,必须要额外的配置
    1
    2
    3
    4
    5
    6
    7
    Host 192.168.85.35
    Hostname 192.168.85.35
    User root
    PreferredAuthentications publickey
    # 如果出现 no mutual signature algorithm 错误,ssh 连接不上的时候,使用 -vvv,查看详细信息
    # PubkeyAcceptedKeyTypes +ssh-rsa
    IdentityFile ~/.ssh/ssh8535
  4. 配置inventory文件,/etc/ansible/hosts
    1
    vm8535 ansible_ssh_user=root ansible_ssh_host=192.168.85.35 ansible_ssh_port=22
  5. ansible的hello world,看到输出如下,就表示成功
    1
    2
    3
    4
    5
    6
    7
    8
    $ ansible vm8535 -m ping
    vm8535 | SUCCESS => {
    "ansible_facts": {
    "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
    }

ansible相关概念

ansible的主要概念分为 inventory,即管理那些机器,需要提前配置,Playbooks,是一组IT程序运行的命令集合

inventory文件

配置操作的主机,主机的配置,如果集合一样,可以有多个集合,集合之间还可以有并集的概念

简单配置

默认的文件地址是/etc/ansible/hosts,可以通过/etc/ansible/ansible.cfg配置,类似windows
的ini文件

1
2
3
4
5
6
7
8
9
10
mail.example.com

[webservers]
foo.example.com
bar.example.com

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

其中还可以配置主机变量,组变量,合并组,功能比较强大,详细的官方文档在这里
ansible中文权威指南-inventory文件

这里在配置的时候,可以使用别名,非常方便

1
vm8535 ansible_ssh_user=root ansible_ssh_host=192.168.85.35 ansible_ssh_port=22
  1. 这里的vm8535就是主机的名字,以后可以使用这个名字,而不用使用域名或者ip
  2. ansible_ssh_user默认就是你当前的用户,如果你是root,那就是root,这里沿用的是ssh的逻辑,所以,建议显式的配置
  3. ansible_ssh_host就是主机地址,也可以是域名**(/etc/hosts里面的配置,或者公网的域名)**
  4. ansible_ssh_port如果ssh的端口不是默认的,那么这里必须显式的配置

常用的参数如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
ansible_ssh_host
将要连接的远程主机名.与你想要设定的主机的别名不同的话,可通过此变量设置.

ansible_ssh_port
ssh端口号.如果不是默认的端口号,通过此变量设置.

ansible_ssh_user
默认的 ssh 用户名

ansible_ssh_pass
ssh 密码(这种方式并不安全,我们强烈建议使用 --ask-pass 或 SSH 密钥)

ansible_sudo_pass
sudo 密码(这种方式并不安全,我们强烈建议使用 --ask-sudo-pass)

ansible_sudo_exe (new in version 1.8)
sudo 命令路径(适用于1.8及以上版本)

ansible_connection
与主机的连接类型.比如:local, ssh 或者 paramiko. Ansible 1.2 以前默认使用 paramiko.1.2 以后默认使用 'smart','smart' 方式会根据是否支持 ControlPersist, 来判断'ssh' 方式是否可行.

ansible_ssh_private_key_file
ssh 使用的私钥文件.适用于有多个密钥,而你不想使用 SSH 代理的情况.

ansible_shell_type
目标系统的shell类型.默认情况下,命令的执行使用 'sh' 语法,可设置为 'csh' 或 'fish'.

ansible_python_interpreter
目标主机的 python 路径.适用于的情况: 系统中有多个 Python, 或者命令路径不是"/usr/bin/python",比如 \*BSD, 或者 /usr/bin/python
不是 2.X 版本的 Python.我们不使用 "/usr/bin/env" 机制,因为这要求远程用户的路径设置正确,且要求 "python" 可执行程序名不可为 python以外的名字(实际有可能名为python26).

与 ansible_python_interpreter 的工作方式相同,可设定如 ruby 或 perl 的路径....

注意,如果不是root用户的话,会使用sudo命令,这里建议去服务器,把你使用的用户的sudo配置成,不需要密码,
否则的话,就失去了自动化配置的意义,每次手动输入sudo密码也是一件不可忍受的事情,在这里/etc/sudoers添加如下配置即可,无需重新启动

1
${your_user_name} ALL=(ALL) NOPASSWD: ALL

patterns

patterns就是根据inventory选取特定的主机,详情文档
Ansible中文权威指南-patterns

1
2
ansible <pattern_goes_here> -m <module_name> -a <arguments>
ansible webservers -m service -a "name=httpd state=restarted"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
all # 全部

one.example.com # 域名
one.example.com:two.example.com # 多个域名
192.168.1.50 # ip
192.168.1.* # 多个ip,类似ant风格

webservers #一个组
webservers:dbservers # 多个组

webservers:!phoenix # 一个组,排除

webservers:dbservers:&staging:!phoenix #多个组交集并排除,注意这里仅仅dbservers:&staging是交集

*.example.com # 多个域名
*.com # 多个域名

one*.com:dbservers # 域名和组 组合

webservers[0] # 下标0,即该组第一个
webservers[0-25] # 下标 0-25,即该组 0-25,总共26个

~(web|db).*\.example\.com # 使用正则表达式的域名

Playbooks编排

Playbooks 是 Ansible的配置,部署,编排语言.他们可以被描述为一个需要希望远程主机执行命令的方案,或者一组IT程序运行的命令集合.

playbooks配置实例
这里面有一些的例子,比如tomcat的,可以参考一下,稍微修改就能符合我们自己的需求

playbooks基础

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
---
- hosts: webservers #主机
vars: # 环境变量
http_port: 80
max_clients: 200
remote_user: root # 执行用户
tasks: # 任务
- name: ensure apache is at the latest version #名字
yum: pkg=httpd state=latest # action的模块名字 命令参数
- 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

完整的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---

- hosts: webservers

pre_tasks:
- shell: echo 'hello'

roles:
- { role: some_role }

tasks:
- shell: echo 'still busy'

post_tasks:
- shell: echo 'goodbye'

playbooks task 幂等性

在运行 playbook 时(从上到下执行),如果一个 host 执行 task 失败,这个 host 将会从整个 playbook 的 rotation 中移除. 如果发生执行失败的情况,请修正 playbook 中的错误,然后重新执行即可.

每个 task 的目标在于执行一个 moudle, 通常是带有特定的参数来执行.在参数中可以使用变量(variables).

modules 具有”幂等”性,意思是如果你再一次地执行 moudle(译者注:比如遇到远端系统被意外改动,需要恢复原状),moudle 只会执行必要的改动,只会改变需要改变的地方.所以重复多次执行 playbook 也很安全.

对于 command module 和 shell module,重复执行 playbook,实际上是重复运行同样的命令.如果执行的命令类似于 ‘chmod’ 或者 ‘setsebool’ 这种命令,这没有任何问题.也可以使用一个叫做 ‘creates’ 的 flag 使得这两个 module 变得具有”幂等”特性 (不是必要的).

jinja2 模板

ansible几乎所有的地方都可以引用变量,使用jinja2的语法

1
My amp goes to {{ max_amp_value }}
1
template: src=foo.cfg.j2 dest={{ remote_install_path }}/foo.cfg

include 语句

1
2
3
4
5
6
7
8
9
10
11
12
13
- name: this is a play at the top level of a file
hosts: all
remote_user: root

tasks:

- name: say hi
tags: foo
shell: echo "hi..."

- include: load_balancers.yml # 包含其它task
- include: webservers.yml # 包含其它task
- include: dbservers.yml # 包含其它task

roles 定义结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
site.yml
webservers.yml
fooservers.yml
roles/
common/
files/
templates/
tasks/
handlers/
vars/
defaults/
meta/
webservers/
files/
templates/
tasks/
handlers/
vars/
defaults/
meta/

这是playbook

1
2
3
4
5
---
- hosts: webservers
roles:
- common
- webservers

这个 playbook 为一个角色 ‘x’ 指定了如下的行为:

  1. 如果 roles/x/tasks/main.yml 存在, 其中列出的 tasks 将被添加到 play 中
  2. 如果 roles/x/handlers/main.yml 存在, 其中列出的 handlers 将被添加到 play 中
  3. 如果 roles/x/vars/main.yml 存在, 其中列出的 variables 将被添加到 play 中
  4. 如果 roles/x/meta/main.yml 存在, 其中列出的 “角色依赖” 将被添加到 roles 列表中 (1.3 and later)
  5. 所有 copy tasks 可以引用 roles/x/files/ 中的文件,不需要指明文件的路径。
  6. 所有 script tasks 可以引用 roles/x/files/ 中的脚本,不需要指明文件的路径。
  7. 所有 template tasks 可以引用 roles/x/templates/ 中的文件,不需要指明文件的路径。
  8. 所有 include tasks 可以引用 roles/x/tasks/ 中的文件,不需要指明文件的路径。
参数化引用 role
1
2
3
4
5
6
7
---

- hosts: webservers
roles:
- common
- { role: foo_app_instance, dir: '/opt/a', port: 5000 } #两个相同的role,但是参数不一样
- { role: foo_app_instance, dir: '/opt/b', port: 5001 } #

等价于以下的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
- hosts: webservers
roles:
- common
- role: foo_app_instance
vars:
dir: '/opt/a'
app_port: 5000
tags: typeA
- role: foo_app_instance
vars:
dir: '/opt/b'
app_port: 5001
tags: typeB

ansible实践

这里假设要部署一个java的项目,安装jdk,copy jar文件,copy 脚本來实践

项目结构如下,完全按照roles的规范来

site.yml内容

1
2
3
4
5
6
7
---

- hosts: plb_servers
remote_user: root

roles:
- deploy

main.yml内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
---

- name: Make home dir
file:
path: "{{home_dir}}"
state: directory

- name: Copy jdk
copy:
src: jdk-8u291-linux-x64.tar.gz
dest: "{{home_dir}}"

- name: Extract jdk
command: chdir={{home_dir}} /bin/tar xvf jdk-8u291-linux-x64.tar.gz -C . creates=jdk1.8.0_291

- name: Copy fat jar
copy:
src: "{{exec_jar_name}}"
dest: "{{home_dir}}"

- name: Copy start script
template:
src: start.sh
dest: "{{home_dir}}"
mode: 0755

- name: Copy stop script
template:
src: stop.sh
dest: "{{home_dir}}"
mode: 0755

- name: Copy ehcache config
template:
src: ehcache.xml
dest: "{{home_dir}}"

- name: Make ehcache dir
file:
path: "{{home_dir}}/ehcache"
state: directory

- name: start app
command:
cmd: "{{home_dir}}/start.sh"

#- name: wait for app to start
# wait_for:
# port: "{{http_port}}"
# timeout: 12

aisible注意事项

yamljinja2陷阱

yaml语法不能{开头,所以如果开头使用jinja2语法,应该要注意

错误

1
2
3
- hosts: app_servers
vars:
app_path: {{ base_path }}/22

正确

1
2
3
- hosts: app_servers
vars:
app_path: "{{ base_path }}/22"
### db操作

ansible执行db操作,这里要区分,不同的数据库,如mysql还是oracle,还是postgresql,每个都需要使用不同的插件,而且需要不同的依赖,所以,要求还是比较高的,尤其是
oracle,因为不是开源的,所以,没有社区支持,比较麻烦

所以,如果使用ansible来执行db操作,相对于来说麻烦一些,这里可以考虑在应用中,执行操作,可选的方案有flyway

幂等性

脚本应该按照幂等性的原则来说,前面也提到过,也就是,中间任意一个步骤失败,不用去服务器删除文件之类的,直接重新执行即可,如果你使用ansible本身的模块,那么大部分
情况下,你是不用考虑幂等性的,但是如果你执行的是自己的脚本,如shell命令,这里你可能需要额外的使用creates来完成幂等性

### shell后台任务陷阱

ansible本身通过包装ssh命令来执行shell脚本,所以,执行完以后,便会退出,所以,如果你需要执行后台任务,你需要使用setsid或者nohup来确保你的任务
不会被中断,这个你需要写在脚本里面,否则,基本就是刚执行,就中断,几乎就像没执行一样

1
2
3
4
#!/bin/bash
export LANG=zh_CN.UTF-8
setsid {{home_dir}}/jdk1.8.0_291/bin/java -jar {{home_dir}}/{{exec_jar_name}} > all.log 2 >& 1
# 如果不加setsid,那么java启动以后,立马被kill,只会看到一个空的all.log文件