[运维] - Ansible / Ansible AWX安装和使用

虽说有了docker或者shell脚本等等一系列自动化运维工具,但是使用后往往会发现难以维护。

  • shell脚本只能单纯的执行,而且编写起来也非常麻烦
  • docker在部署好docker之前每次安装docker等等一系列操作也是完全重复的
  • CI/CD更多是程序的测试和发布和后续的持续集成,对于全新的机器等待使用CI/CD部署初期环境总感觉违反了单一原则啥的,而且也麻烦,不好复用等等

所以我们需要一个更好的专门的自动化运维工具。

要求

  • 基础运维知识
  • 基础python或者编程基础
  • 如果你没有服务器,最好知道一点docker来方便测试

内容很多很杂,完全记录太麻烦了,请更多参考官方文档

Ansible简介

Ansible就是新出现的自动化运维工具,免去那些复杂的描述,简单的说明它可以储存一些预设的主机,可以对他们进行分组,然后可以对单个主机或者一个组执行一系列的操作。

而且最棒的是,Ansible只需要在主机上安装,在你需要部署的机器上不需要任何安装,Ansible会使用ssh自行连接进行操作,所以需要做的就是在远程主机上放好ssh的key。

基本概念

  • Inventory: 记录远程主机的信息(IP,端口,密码等等)
  • Modules: 基本的模块功能(如apt,ping)
    • 我们可以对主机运行的功能
  • Tasks:任务
    • 模块的更详细的设置
  • Playbooks:定义一系列任务

Ansible使用yaml文件进行配置

组件

  • Hosts:同时操作属于一个组的多台主机,组和主机之间的关系通过 inventory 文件配置. 默认的文件路径为/etc/ansible/hosts
  • Tasks:任务,由模板定义的操作列表
  • Variable:变量
  • Templates:模板,一般是.j2为文件后缀
  • Handlers:处理器,当某条件满足时,出发执行的操作
  • Roles:角色

安装

1
sudo apt install ansible

即可在ubuntu上安装,你可以在这里查看更多的安装方法。

当然我是推荐使用docker进行安装的,但是ansible稍微特殊一点,而且纯ansible也不方便使用,后面我们会使用Ansible AWX,但是Ansible AWX需要使用ansible进行安装(禁止套娃),所以无奈我们先安装ansible。

  • 配置文件目录:/etc/ansible/
  • 模组目录:
    • ~/.ansible/plugins/modules
    • /usr/share/ansible/plugins/modules
  • 可执行文件:/usr/bin/ansible-playbook

Ansible如何工作

Ansible由节点和控制机器组成。 控制机器是安装Ansibles的地方,节点由这些机器通过SSH管理。 借助SSH协议,控制机器可以部署临时存储在远程节点上的模块。

控制机器使用ansible或者ansible-playbooks在服务器终端输入的Ansible命令集或者playbook后,Ansible会遵循预先编排的规则将playbook逐条拆解为Play,再将Play组织成Ansible可以识别的任务tasks,随后调用任务涉及到的所有Module及PLUGINS,根据主机清单Inventory中定义的主机列表通过SSH协议将任务集以临时文件或者命令的形式传输到远程节点并返回结果,如果是临时文件则执行完毕后自动删除。

Inventory

首先是/etc/ansible/hosts,也就是Inventory,我们可以定义主机

1
名字 ansible_port=端口 ansible_host=IP

这样就可以定义一个主机,其他的参数还有

  • ansible_host
    • 将要连接的远程主机名.与你想要设定的主机的别名不同的话,可通过此变量设置.
  • ansible_port
    • ssh端口号.如果不是默认的端口号,通过此变量设置.
  • ansible_user
    • 默认的 ssh 用户名
  • ansible_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_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 的路径

Ansible 2.0 has deprecated the “ssh” from ansible_ssh_user, ansible_ssh_host, and ansible_ssh_port to become ansible_user, ansible_host, and ansible_port. If you are using a version of Ansible prior to 2.0, you should continue using the older style variables (ansible_ssh_*). These shorter variables are ignored, without warning, in older versions of Ansible.


对于一个组的定义

1
2
3
[group_name]
名字 ansible_port=端口 ansible_host=IP
名字 ansible_port=端口 ansible_host=IP

这样就可以定义一个组

/etc/ansible/hosts不是唯一的,你可以创建自己的hosts文件,然后指定文件使用

1
2
$ ansible-playbook -i hosts xxx.yml
$ ansible <pattern_goes_here> -m <module_name> -a <arguments>

我们还可以从云端动态拉取Inventory

主机匹配

  • all*代表目标为仓库(inventory)中的所有机器
  • 也可以写IP地址或系列主机名192.168.1.*
  • 必须隶属webservers组但同时不在phoenix组webservers:!phoenix
  • 正则表达式shiyong~开头~(web|db).*\.example\.com
  • 从文件读取hosts,文件名以@为前缀开头@retry_hosts.txt
1
2
3
*.example.com
webservers[0]
webservers[0-25]

ad-hoc命令

Ansible提供两种方式去完成任务

  • ad-hoc命令
  • Ansible playbook

如果我们敲入一些命令去比较快的完成一些事情,而不需要将这些执行的命令特别保存下来,这样的命令就叫做ad-hoc命令
Ansible 能够以并行的方式同时运行ad-hoc命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ansible atlanta -a "/sbin/reboot" -f 10
$ ansible atlanta -a "/usr/bin/foo" -u username
$ ansible atlanta -a "/usr/bin/foo" -u username --sudo [--ask-sudo-pass]
# 批量复制
$ ansible atlanta -m copy -a "src=/etc/hosts dest=/tmp/hosts"
# 修改权限
$ ansible webservers -m file -a "dest=/srv/foo/a.txt mode=600"
# 安装但不升级
$ ansible webservers -m yum -a "name=acme state=present"
# 确认一个软件包的安装版本
$ ansible webservers -m yum -a "name=acme-1.5 state=present"
# 确认一个软件包还没有安装
$ ansible webservers -m yum -a "name=acme state=absent"
# 确认某个服务在所有的webservers上都已经启动
$ ansible webservers -m service -a "name=httpd state=started"
# Gathering Facts
$ ansible all -m setup

Playbooks

Playbooks是Ansible的配置、部署和编排语言。他们可以描述您希望远程系统实施的策略。我们把操作定义在Playbooks里面,Playbooks是一个yaml文件,文件名随意。playbook由一个或多个‘plays’组成.它的内容是一个以‘plays’为元素的列表.

每一个play包含了一个task列表(任务列表),一个task在其所对应的所有主机上执行完毕之后,下一个task才会执行
如果一个host执行task失败,这个host将会从整个playbook的rotation中移除. 如果发生执行失败的情况,请修正playbook中的错误,然后重新执行即可。

每个 task 的目标在于执行一个module

  • modules 具有”幂等”性
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
- 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
- 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
- name: template configuration file
template: src=template.j2 dest=/etc/foo.conf
# 在发生改变时执行的操作, 通过名字来引用
# handlers 会按照声明的顺序执行
notify:
- restart memcached
- restart apache
- name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors: True

handlers:
- name: restart apache
service: name=httpd state=restarted

如果写过Drone的CI/CD文件大概就可以知道,我们先定义了哪个hosts,然后哪个用户去执行
下面就是我们要执行的一系列tasks,全部tasks是一个Playbooks,他们是按照从上到下依次执行的

ansible会去指定的inventory文件里寻找对应的webservers主机

每个tasks有一个name和一个module

  • tasks
    • name
    • module

其实和docker-compose.yml很相似,只不过这里定义的是动作

1
2
# 并行的级别是10
ansible-playbook playbook.yml -f 10

Ansible-Pull

Ansible-pull是一个小脚本,它从git上checkout一个关于配置指令的repo,然后以这个配置指令来运行ansible-playbook.

include语句

include语句引用task文件的方法,可允许你将一个配置策略分解到更小的文件中。使用include语句引用tasks是将tasks从其他文件拉取过来。因为handlers也是tasks,所以你也可以使用include语句去引用handlers文件。

Playbook同样可以使用include引用其他playbook文件中的play。这时被引用的play会被插入到当前的playbook中,当前的playbook中就有了一个更长的的play列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tasks:
- include: tasks/foo.yml
# 传递变量
- include: wordpress.yml wp_user=timmy
- { include: wordpress.yml, wp_user: timmy, ssh_keys: [ 'keys/one.txt', 'keys/two.txt' ] }
- include: wordpress.yml
vars:
wp_user: timmy
some_list_variable:
- alpha
- beta
- gamma

handlers:
- include: handlers/handlers.yml

Include 语句也可用来将一个 playbook 文件导入另一个 playbook 文件。这种方式允许你定义一个 顶层的 playbook,这个顶层 playbook 由其他 playbook 所组成。

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
- include: webservers.yml
- include: dbservers.yml

当你在 playbook 中引用其他 playbook 时,不能使用变量替换。

变量

1
2
3
4
5
6
7
8
---
- name: Hello World
hosts: localhost

tasks:
- name: Hello World debug
debug:
msg: "Hello World"

上面可以输出一个Hello World,但是如果我们想设置一些变量且输出,我们可以这样

1
2
3
4
5
6
7
8
9
10
11
---
- name: Hello World
hosts: localhost

vars:
greeting: "hello from vars"

tasks:
- name: Hello World debug
debug:
msg: "{{ greeting }}"

我们可以定义类似字典和列表的变量

1
2
3
4
5
6
7
vars:
greeting: "hello from vars"
demo:
a:
- a: 1
- b: 2
b: test

如果我们想很好的规划变量,我们可以把变量放进单独文件里面,比如vars/demo.yml里,内容为

1
greeting: "hello from vars"

然后Playbooks里面写上

1
2
vars_files:
- "vars/demo.yml"

我们可以定义多个变量文件,如果变量名相同,最下方的变量文件会覆盖之前的

Host/组变量

上方Inventory设置的就是host级别的变量,如果一组服务器用户相同,我们一一设置起来非常麻烦,我们就可以设置组级别的变量

1
2
3
4
5
6
7
[all]
host1 http_port=80
host2 http_port=443

[all:vars]
ansible_user=root
ansible_password=ansible

host变量级别大于组变量级别

同理,为了方便维护,我们可以把组变量等分开,再这个项目根目录下,我们这样创建文件夹

1
2
3
4
5
6
.
├── group_vars
│  └── all.yml
├── host_vars
│  └── host1.yml
└── host

将文件名对应正确的组或主机名即可。于是定义我们需要管理的主机可以变为

hosts

1
2
3
[all]
host1
host2

host1.yml

1
2
ansible_user=root
ansible_password=ansible

host2.yml

1
2
ansible_user=root
ansible_password=ansible

等等各种方式

ansible.cfg

ansible.cfg在一个项目级别上定义配置

1
2
[default]
inventory = inventory/hosts

一般情况下,我们需要指定inventory文件,这样很麻烦,遵循一切皆代码的原则,我们可以把这些也弄成配置
于是就有了ansible.cfg文件,默认我们在执行ansible-playbook的时候,我们会按照一下顺序搜索ansible.cfg文件

  • 当前目录下ansible.cfg
  • 环境变量ANSIBLE_CONFIG
  • 当前用户home下
  • /etc/ansible.cfg

然后我们就可以直接运行

1
ansible-playbook main.yml

详细参见这里Ansible配置文件

项目结构设计

目前

1
2
3
4
5
6
7
8
9
10
.
├── inventory
│ ├── group_vars
│ │  └── all.yml
│ ├── host_vars
│ │  ├── host1.yml
│ │ └── host2.yml
│ └── host
├── ansible.cfg
└── main.yml

对于测试环境和生成环境,我们可以这样设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.
├── inventory
│ ├── production
│ │ ├── group_vars
│ │ │  └── all.yml
│ │ ├── host_vars
│ │ │  ├── host1.yml
│ │ │ └── host2.yml
│ │ └── host
│ └── test
│ ├── group_vars
│ │  └── all.yml
│ ├── host_vars
│ │  ├── host1.yml
│ │ └── host2.yml
│ └── host
├── ansible.cfg
└── main.yml

目前流程

            graph TD
            写hosts --> 写hosts变量;
写hosts变量 --> 写playhooks;
写playhooks --> 运行
          

模板

对于一些配置,我们可能针对不同情况需要不同的配置,就像渲染html页面一样,不过基本的框架是一样的
于是我们可以使用模板

1
2
3
4
5
6
7
- name: template
template:
src: templates/config.j2
dest: /etc/file.conf
owner: bin
group: wheel
mode: '0664'

而对于config.j2文件,采取和django一样的模板语法(jinja)

1
2
3
[default]

http_port = {{ http_port }}

执行条件

对于不同系统,我们可以有不同的安装指令,我们就需要对task进行条件设置

1
2
3
4
tasks:
- name: "shut down Debian flavored systems"
command: /sbin/shutdown -t now
when: ansible_facts['os_family'] == "Debian"

还有条件引入。如果操作系统是CentOS,Ansible导入的第一个文件将是vars/CentOS.yml,紧接着是/var/os_defaults.yml,如果这个文件不存在.而且在列表中没有找到,就会报错. 在Debian最先查看的将是vars/Debian.yml而不是vars/CentOS.yml, 如果没找到,则寻找默认文件vars/os_defaults.yml

1
2
3
vars_files:
- "vars/common.yml"
- [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ]

循环

对于某一些操作,可能要对不同值执行多次,所以需要用到循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- name: add several users
user:
name: "{{ item.name }}"
state: present
groups: "{{ item.groups }}"
loop:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
- name: add several users
user: name={{ item }} state=present groups=wheel
with_items:
- testuser1
- testuser2
# 嵌套循环
- name: give users access to multiple databases
mysql_user: name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=yes password=foo
with_nested:
- [ 'alice', 'bob' ]
- [ 'clientdb', 'employeedb', 'providerdb' ]

字典循环

1
2
3
4
5
6
7
8
9
10
11
users:
alice:
name: Alice Appleworth
telephone: 123-456-7890
bob:
name: Bob Bananarama
telephone: 987-654-3210
---
- name: Print phone records
debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
with_dict: "{{users}}"

Do-Until循环

1
2
3
4
5
- action: shell /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1
retries: 5
delay: 10

Block

对于一些指令,可能会导致执行失败,如果失败了,我们希望进行一些补救或者处理,类似python里面的

1
2
3
4
5
try:

except:

finally:

同时,可以定义一个task块,对于这一部分task块,同一使用某些条件或者权限等等
比如我们接下来一系列操作都需要sudo,或者只能在ubuntu上执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
tasks:
- name: Install, configure, and start Apache
block:
- name: install httpd and memcached
yum:
name:
- httpd
- memcached
state: present

- name: apply the foo config template
template:
src: templates/src.j2
dest: /etc/foo.conf
when: ansible_facts['distribution'] == 'CentOS'
become: true

而对于错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tasks:
- name: Handle the error
block:
- debug:
msg: 'I execute normally'
- name: i force a failure
command: /bin/false
- debug:
msg: 'I never execute, due to the above task failing, :-('
rescue:
- debug:
msg: 'I caught an error, can do stuff here to fix it, :-)'
always:
- debug:
msg: "This always executes, :-)"

角色

playbook里面有一系列task,很多都可以复用,而且把每个步骤的task分开管理也是很方便的,所以就有了role

可以把role看作python里面的模组

我们在根目录下创建一个roles文件夹,然后再创建一个文件夹,名字随意,即为一个role,这里记为demo
demo下按照规范会有这些文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.
└── demo
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
└── main.yml

下面的每一个子文件夹都对应着一些配置

  • tasks: 任务
  • defaults: 基本的常量变量
  • vars: 这个任务专用变量
  • templates: 模板文件
  • files: 要传输的文件
  • handlers: tasks的触发处理器

我们可以在主文件里面引用role

  • 如果roles/x/tasks/main.yml存在, 其中列出的tasks将被添加到 play 中
  • 如果roles/x/handlers/main.yml存在, 其中列出的handlers将被添加到 play 中
  • 如果roles/x/vars/main.yml存在, 其中列出的variables将被添加到 play 中
  • 如果roles/x/meta/main.yml存在, 其中列出的角色依赖将被添加到 roles 列表中 (1.3 and later)
  • 所有copy tasks可以引用roles/x/files/中的文件,不需要指明文件的路径。
  • 所有script tasks可以引用roles/x/files/中的脚本,不需要指明文件的路径。
  • 所有template tasks可以引用roles/x/templates/中的文件,不需要指明文件的路径。
  • 所有include tasks可以引用roles/x/tasks/中的文件,不需要指明文件的路径。

如果 roles 目录下有文件不存在,这些文件将被忽略。比如 roles 目录下面缺少了 ‘vars/’ 目录,这也没关系。

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

或者其他语法

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
---
- hosts: webservers
roles:
- common
- role: foo_app_instance
vars:
dir: '/opt/a'
app_port: 5000
- role: foo_app_instance
vars:
dir: '/opt/b'
app_port: 5001
---
- hosts: webservers
tasks:
- import_role:
name: example
- include_role:
name: example
---
- hosts: webservers
roles:
- role: '/path/to/my/roles/common'
---
- hosts: webservers
tasks:
- include_role:
name: foo_app_instance
vars:
dir: '/opt/a'
app_port: 5000

如果你希望定义一些 tasks,让它们在 roles 之前以及之后执行,你可以这样做

1
2
3
4
5
6
7
8
9
10
11
12
13
- hosts: webservers

pre_tasks:
- shell: echo 'hello'

roles:
- { role: some_role }

tasks:
- shell: echo 'still busy'

post_tasks:
- shell: echo 'goodbye'

handlers

对于某一些操作,可能我们希望它执行过一次后就不要再执行了,我们就可以使用handlers。
对于每一个tasks,它可以是changed或者是ok的状态(还有其他状态),如果是ok则表示没有实际运行
因为它已经运行过了,比如安装过git了

1
2
3
4
5
6
7
- hosts: webservers
tasks:
- name: clone
git:
repo: '{{ repo_url }}'
dest: ~/demo
notify: test handlers

然后在handles/main.yml里面

1
2
3
- name: test handlers
debug:
msg: "Message from handles"

这样test handlers只会在clonechanged的时候运行

handler的本质还是一个task

异步操作和轮询

默认情况下playbook中的任务执行时会一直保持连接,直到该任务在每个节点都执行完毕.有时这是不必要的,比如有些操作运行时间比SSH超时时间还要长.

有些任务可能需要执行很长时间,但是又不需要一直看着他,就可以使用异步模式

1
2
3
4
5
tasks:
- name: simulate long running op (15 sec), wait for up to 45 sec, poll every 5 sec
command: /bin/sleep 15
async: 45
poll: 5

对于要求排它锁的操作,如果你需要在其之后对同一资源执行其它任务,那么你不应该对该操作使用”启动并忽略”.比如yum事务.

Module

Module是基本功能的单位,这里介绍一些常见的模块

debug

debug – Print statements during execution

1
2
3
- name: print variable
debug:
msg: "Hello World"
1
2
3
- name: print variable
debug:
msg: "Hello World"

复制文件

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
51
52
53
54
55
56
57
58
59
60
61
62
- name: Copy file with owner and permissions
copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
owner: foo
group: foo
mode: '0644'

- name: Copy file with owner and permission, using symbolic representation
copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
owner: foo
group: foo
mode: u=rw,g=r,o=r

- name: Another symbolic mode example, adding some permissions and removing others
copy:
src: /srv/myfiles/foo.conf
dest: /etc/foo.conf
owner: foo
group: foo
mode: u+rw,g-wx,o-rwx

- name: Copy a new "ntp.conf file into place, backing up the original if it differs from the copied version
copy:
src: /mine/ntp.conf
dest: /etc/ntp.conf
owner: root
group: root
mode: '0644'
backup: yes

- name: Copy a new "sudoers" file into place, after passing validation with visudo
copy:
src: /mine/sudoers
dest: /etc/sudoers
validate: /usr/sbin/visudo -csf %s

- name: Copy a "sudoers" file on the remote machine for editing
copy:
src: /etc/sudoers
dest: /etc/sudoers.edit
remote_src: yes
validate: /usr/sbin/visudo -csf %s

- name: Copy using inline content
copy:
content: '# This file was moved to /etc/other.conf'
dest: /etc/mine.conf

- name: If follow=yes, /path/to/file will be overwritten by contents of foo.conf
copy:
src: /etc/foo.conf
dest: /path/to/link # link to /path/to/file
follow: yes

- name: If follow=no, /path/to/link will become a file and be overwritten by contents of foo.conf
copy:
src: /etc/foo.conf
dest: /path/to/link # link to /path/to/file
follow: no

gather_facts

收集机器的相关的基本信息,默认是开启的,并且可以在变量中直接引用gather_facts里面的变量

1
2
3
4
5
6
7
8
- name: test
hosts: all
gather_facts: no

tasks:
- name: print facts
debug:
msg: "{{ ansible_deta_time }}"

yum

yum-module

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
- name: install the latest version of Apache
yum:
name: httpd
state: latest

- name: ensure a list of packages installed
yum:
name: "{{ packages }}"
vars:
packages:
- httpd
- httpd-tools

- name: remove the Apache package
yum:
name: httpd
state: absent

- name: install the latest version of Apache from the testing repo
yum:
name: httpd
enablerepo: testing
state: present

- name: install one specific version of Apache
yum:
name: httpd-2.2.29-1.4.amzn1
state: present

- name: upgrade all packages
yum:
name: '*'
state: latest

- name: upgrade all packages, excluding kernel & foo related packages
yum:
name: '*'
state: latest
exclude: kernel*,foo*

- name: install the nginx rpm from a remote repo
yum:
name: http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6-0.el6.ngx.noarch.rpm
state: present

- name: install nginx rpm from a local file
yum:
name: /usr/local/src/nginx-release-centos-6-0.el6.ngx.noarch.rpm
state: present

- name: install the 'Development tools' package group
yum:
name: "@Development tools"
state: present

- name: install the 'Gnome desktop' environment group
yum:
name: "@^gnome-desktop-environment"
state: present

- name: List ansible packages and register result to print with debug later.
yum:
list: ansible
register: result

- name: Install package with multiple repos enabled
yum:
name: sos
enablerepo: "epel,ol7_latest"

- name: Install package with multiple repos disabled
yum:
name: sos
disablerepo: "epel,ol7_latest"

- name: Install a list of packages
yum:
name:
- nginx
- postgresql
- postgresql-server
state: present

- name: Download the nginx package but do not install it
yum:
name:
- nginx
state: latest
download_only: true

apt

apt-module

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
- name: Install apache httpd  (state=present is optional)
apt:
name: apache2
state: present

- name: Update repositories cache and install "foo" package
apt:
name: foo
update_cache: yes

- name: Remove "foo" package
apt:
name: foo
state: absent

- name: Install the package "foo"
apt:
name: foo

- name: Install a list of packages
apt:
pkg:
- foo
- foo-tools

- name: Install the version '1.00' of package "foo"
apt:
name: foo=1.00

- name: Update the repository cache and update package "nginx" to latest version using default release squeeze-backport
apt:
name: nginx
state: latest
default_release: squeeze-backports
update_cache: yes

- name: Install latest version of "openjdk-6-jdk" ignoring "install-recommends"
apt:
name: openjdk-6-jdk
state: latest
install_recommends: no

- name: Upgrade all packages to the latest version
apt:
name: "*"
state: latest

- name: Update all packages to the latest version
apt:
upgrade: dist

- name: Run the equivalent of "apt-get update" as a separate step
apt:
update_cache: yes

- name: Only run "update_cache=yes" if the last one is more than 3600 seconds ago
apt:
update_cache: yes
cache_valid_time: 3600

- name: Pass options to dpkg on run
apt:
upgrade: dist
update_cache: yes
dpkg_options: 'force-confold,force-confdef'

- name: Install a .deb package
apt:
deb: /tmp/mypackage.deb

- name: Install the build dependencies for package "foo"
apt:
pkg: foo
state: build-dep

- name: Install a .deb package from the internet.
apt:
deb: https://example.com/python-ppq_0.1-1_all.deb

- name: Remove useless packages from the cache
apt:
autoclean: yes

- name: Remove dependencies that are no longer required
apt:
autoremove: yes

pip

pip-module

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# Install (Bottle) python package.
- pip:
name: bottle

# Install (Bottle) python package on version 0.11.
- pip:
name: bottle==0.11

# Install (bottle) python package with version specifiers
- pip:
name: bottle>0.10,<0.20,!=0.11

# Install multi python packages with version specifiers
- pip:
name:
- django>1.11.0,<1.12.0
- bottle>0.10,<0.20,!=0.11

# Install python package using a proxy - it doesn't use the standard environment variables, please use the CAPITALIZED ones below
- pip:
name: six
environment:
HTTP_PROXY: '127.0.0.1:8080'
HTTPS_PROXY: '127.0.0.1:8080'

# Install (MyApp) using one of the remote protocols (bzr+,hg+,git+,svn+). You do not have to supply '-e' option in extra_args.
- pip:
name: svn+http://myrepo/svn/MyApp#egg=MyApp

# Install MyApp using one of the remote protocols (bzr+,hg+,git+).
- pip:
name: git+http://myrepo/app/MyApp

# Install (MyApp) from local tarball
- pip:
name: file:///path/to/MyApp.tar.gz

# Install (Bottle) into the specified (virtualenv), inheriting none of the globally installed modules
- pip:
name: bottle
virtualenv: /my_app/venv

# Install (Bottle) into the specified (virtualenv), inheriting globally installed modules
- pip:
name: bottle
virtualenv: /my_app/venv
virtualenv_site_packages: yes

# Install (Bottle) into the specified (virtualenv), using Python 2.7
- pip:
name: bottle
virtualenv: /my_app/venv
virtualenv_command: virtualenv-2.7

# Install (Bottle) within a user home directory.
- pip:
name: bottle
extra_args: --user

# Install specified python requirements.
- pip:
requirements: /my_app/requirements.txt

# Install specified python requirements in indicated (virtualenv).
- pip:
requirements: /my_app/requirements.txt
virtualenv: /my_app/venv

# Install specified python requirements and custom Index URL.
- pip:
requirements: /my_app/requirements.txt
extra_args: -i https://example.com/pypi/simple

# Install specified python requirements offline from a local directory with downloaded packages.
- pip:
requirements: /my_app/requirements.txt
extra_args: "--no-index --find-links=file:///my_downloaded_packages_dir"

# Install (Bottle) for Python 3.3 specifically,using the 'pip3.3' executable.
- pip:
name: bottle
executable: pip3.3

# Install (Bottle), forcing reinstallation if it's already installed
- pip:
name: bottle
state: forcereinstall

# Install (Bottle) while ensuring the umask is 0022 (to ensure other users can use it)
- pip:
name: bottle
umask: "0022"
become: True

get_url

get_url – Downloads files from HTTP, HTTPS, or FTP to node

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: Download foo.conf
get_url:
url: http://example.com/path/file.conf
dest: /etc/foo.conf
mode: '0440'

- name: Download file and force basic auth
get_url:
url: http://example.com/path/file.conf
dest: /etc/foo.conf
force_basic_auth: yes

- name: Download file with custom HTTP headers
get_url:
url: http://example.com/path/file.conf
dest: /etc/foo.conf
headers:
key1: one
key2: two

- name: Download file with check (sha256)
get_url:
url: http://example.com/path/file.conf
dest: /etc/foo.conf
checksum: sha256:b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c

- name: Download file with check (md5)
get_url:
url: http://example.com/path/file.conf
dest: /etc/foo.conf
checksum: md5:66dffb5228a211e61d6d7ef4a86f5758

- name: Download file with checksum url (sha256)
get_url:
url: http://example.com/path/file.conf
dest: /etc/foo.conf
checksum: sha256:http://example.com/path/sha256sum.txt

- name: Download file from a file path
get_url:
url: file:///tmp/afile.txt
dest: /tmp/afilecopy.txt

- name: < Fetch file that requires authentication.
username/password only available since 2.8, in older versions you need to use url_username/url_password
get_url:
url: http://example.com/path/file.conf
dest: /etc/foo.conf
username: bar
password: '{{ mysecret }}'

ansible-galaxy

这个东西就像python里面的pip,管理的对象是role或者collection,我们可以发布自己role,或者下载别人的role

  • ansible-galaxy role init name: 初始化一个模块
  • ansible-galaxy role remove name: 移除一个模块

我们可以在根目录下创建一个文件requirements.yml

1
2
3
4
roles:
- src: https://.....
scm: git
version: master

安装指令:

1
ansible_galaxy install -r requirements.yml

ansible-galaxy

Ansible AWX

通过Ansible AWX,可以利用网页界面可视化管理Ansible

Ansible AWX安装

通过github的方法,我们采取docker-compose安装,但是docker-compose需要文件需要通过官方的安装程序生成
clone

1
git clone https://github.com/ansible/awx.git

按照官方的配置方法,配置一些数据库密码,或者一些初始的东西,然后进入到installer文件夹里,生成配置文件

1
2
3
4
5
# Set the working directory to installer
cd installer

# Run the Ansible playbook
ansible-playbook -i inventory install.yml

不过官方默认会启动容器,且绑定80端口,如果你已经有占用80端口的容器了,那么应该会启动失败
如果启动了部分容器我们先把他们删除掉,包括volume。

通过配置docker_compose_dir的文件夹找到生成的docker-compose.yml文件,手动配置一下反向代理或者其他一些需要自定义的地方,然后就可以up -d了,启动后需要等待一段时间,网站才会初始化好。然后就能登录进去了。


普通的使用:

  • 清单:对应Inventory
    • 创建清单后,可以在清单页面的主机子页面添加机器
  • 凭证:添加密钥密码等等

Ansible AWX在Ansible基础上添加了更多功能,就像管理docker的程序一样

实战

一键创建一个sudoer用户,并添加ssh密钥,关闭root登录并更改默认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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
---
- hosts: all
gather_facts: no

vars:
ansible_user: root
ansible_port: "22"

tasks:
- name: print host
debug:
msg: "{{ ansible_host }}:{{ ansible_port }}"

- name: Check if we're using the default SSH port
wait_for:
port: "22"
state: "started"
host: "{{ ansible_host }}"
connect_timeout: "5"
timeout: "10"
delegate_to: "localhost"
ignore_errors: "yes"
register: default_ssh

# If reachable, continue the following tasks with this port
- name: Set inventory ansible_port to default
set_fact:
ansible_port: "22"
when: default_ssh is defined and
default_ssh.state == "started"
register: ssh_port_set

# If unreachable on port 22, check if we're able to reach
# {{ ansible_host }} on {{ ansible_port }} provided by the inventory
# from localhost
- name: Check if we're using the inventory-provided SSH port
wait_for:
port: "{{ ansible_port }}"
state: "started"
host: "{{ ansible_host }}"
connect_timeout: "5"
timeout: "10"
delegate_to: "localhost"
ignore_errors: "yes"
register: configured_ssh
when: default_ssh is defined and
default_ssh.state is undefined

# If {{ ansible_port }} is reachable, we don't need to do anything special
- name: SSH port is configured properly
debug:
msg: "SSH port is configured properly"
when: configured_ssh is defined and
configured_ssh.state is defined and
configured_ssh.state == "started"
register: ssh_port_set

- name: Fail if SSH port was not auto-detected (unknown)
fail:
msg: "The SSH port is neither 22 or {{ ansible_port }}."
when: ssh_port_set is undefined

- name: Confirm host connection works
ping:

- name: Make sure we have a "wheel" group
group:
name: wheel
state: present

- name: Allow "wheel" group to have passwordless sudo
lineinfile:
dest: /etc/sudoers
state: present
regexp: "^%wheel"
line: "%wheel ALL=(ALL) NOPASSWD: ALL"
validate: "visudo -cf %s"

- name: Create a sudoer login user
user:
name: "{{ username }}"
password: "{{ '{{ password }}' | password_hash('bcrypt') }}"
state: present
shell: /bin/bash
append: yes
groups:
- wheel
system: no
create_home: yes
home: /home/{{ username }}
ssh_key_bits: 2048
ssh_key_file: .ssh/id_rsa

- name: Set authorized keys for the user copying it from current user
authorized_key:
user: "{{ username }}"
key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}"
comment: my key

- name: Run deferred setup to gather facts
setup:

# package
- include_role:
name: robertdebock.update
vars:
update_autoremove: yes
update_upgrade_command: dist

# selinux TODO
- name: test to see if selinux is running
command: getenforce
register: sestatus
changed_when: false
when: ansible_facts['distribution'] == "CentOS"

- block:
- name: ensure a list of packages installed
yum:
name: "{{ packages }}"
vars:
packages:
- libselinux-python
- policycoreutils-python
when: ansible_facts['distribution'] == "CentOS"

# sshd
- name: Setup selinux for alternate SSH port
seport:
ports: "2022"
proto: "tcp"
setype: "ssh_port_t"
state: "present"
# SELinux is disabled on this host
ignore_errors: "yes"
when: ansible_facts['distribution'] == "CentOS" and
sestatus is defined and
sestatus.stdout is defined and
'"Enabled" in sestatus.stdout'

- include_role:
name: arillso.sshd
vars:
ssh_server_ports: ['2022'] # sshd
ssh_client_password_login: true # ssh
ssh_server_enabled: true
ssh_server_password_login: false # sshd
ssh_sftp_enabled: true # sftp

参考