> For the complete documentation index, see [llms.txt](https://yangsx95.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yangsx95.gitbook.io/notes/devops/ansible.md).

# Ansible

* <http://www.ansible.com.cn>
* <http://galaxy.ansible.com>

## 企业应用场景

### Dev开发环境

* 使用者：程序员
* 功能：程序员开发软件，测试BUG的环境
* 管理者：程序员

### 测试环境

* 使用者：QA测试工程师
* 功能：测试经过Dev环境测试通过的软件的功能
* 管理者：运维

测试环境往往有多套,测试环境满足测试功能即可，不宜过多

1. 测试人员希望测试环境有多套,公司的产品多产品线并发，即多个版本，意味着多个版本同步测试
2. 通常测试环境有多少套和产品线数量保持一样

### 预发布环境

* 使用者：运维
* 功能：使用和生产环境一样的数据库，缓存服务等配置，测试是否正常

### 发布环境

通常是代码发布机或者堡垒机 使用者：运维 功能：发布代码至生产环境 管理者：运维（有经验） 发布机：往往需要有2台（主备）

### 生产环境

使用者：运维，少数情况开放权限给核心开发人员，极少数公司将权限完全 开放给开发人员并其维护 功能：对用户提供公司产品的服务

### 灰度环境

是生产环境的一部分 使用者：运维 功能：在全量发布代码前将代码的功能面向少量精准用户发布的环境,可基于主机或用户执行灰度发布 案例：共100台生产服务器，先发布其中的10台服务器，这10台服务器就是灰度服务器 管理者：运维

往往该版本功能变更较大，为保险起见特意先让一部分用户优化体验该功能，待这部分用户使用没有重大问题的时候，再全量发布至所有服务器

## 自动化运维的场景

1. 文件传输
2. 应用部署
3. 配置管理
4. 任务流编排

## Ansible 特性

1. 模块化：调用特定的模块，完成特定任务
2. Paramiko（python对ssh的实现），PyYAML，Jinja2（模板语言）三个关键模块
3. 支持自定义模块
4. 基于Python语言实现
5. 部署简单，基于python和SSH(默认已安装)，agentless
6. 安全，基于OpenSSH
7. 支持playbook编排任务
8. 幂等性：一个任务执行1遍和执行n遍效果一样，不因重复执行带来意外情况
9. 无需代理不依赖PKI（无需ssl）
10. 可使用任何编程语言写模块
11. YAML格式，编排任务，支持丰富的数据结构
12. 较强大的多层解决方案

## Ansible 架构以及原理

Ansible主控端作为ssh客户端连接其他机器的ssh server，然后控制其他服务器完成一些操作。

## 安装Ansible

### rpm

```shell
yum install ansible
```

### apt

```shell
sudo apt update
sudo apt install ansible
```

### mac brew

```shell
brew install ansible
```

### 编译安装

```shell
yum -y install python-jinja2 PyYAML python-paramiko python-babel
python-crypto
tar xf ansible-1.5.4.tar.gz
cd ansible-1.5.4
python setup.py build
python setup.py install
mkdir /etc/ansible
cp -r examples/* /etc/ansible
```

### Git clone 编译安装

```shell
git clone git://github.com/ansible/ansible.git --recursive
cd ./ansible
source ./hacking/env-setup
```

### Pip安装

```shell
yum install python-pip python-devel
yum install gcc glibc-devel zibl-devel rpm-bulid openssl-devel
pip install --upgrade pip
pip install ansible --upgrade
```

### 确认安装成功

```shell
ansible --version
```

## 相关的主要配置

安装完毕，涉及的相关文件：

```
配置文件
    /etc/ansible/ansible.cfg  主配置文件,配置ansible工作特性(一般无需修改)
    /etc/ansible/hosts        主机清单(将被管理的主机放到此文件)
    /etc/ansible/roles/       存放角色的目录

程序
    /usr/bin/ansible          主程序，临时命令执行工具
    /usr/bin/ansible-doc      查看配置文档，模块功能查看工具
    /usr/bin/ansible-galaxy   下载/上传优秀代码或Roles模块的官网平台
    /usr/bin/ansible-playbook 定制自动化任务，编排剧本工具
    /usr/bin/ansible-pull     远程执行命令的工具
    /usr/bin/ansible-vault    文件加密工具
    /usr/bin/ansible-console  基于Console界面与用户交互的执行工具
```

### ansible.cfg 配置文件

```ini
[defaults]
#inventory     = /etc/ansible/hosts      # 主清单配置文件的位置
#library       = /usr/share/my_modules/  # python库文件存放目录
#local_tmp     = $HOME/.ansible/tmp      # 当执行一条ansible命令，会先在本地生成python脚本，存放到这个路径下
#remote_tmp    = $HOME/.ansible/tmp      # ansible会将上面存放的临时脚本推送到远程主机的该目录下，并执行
#forks         = 5                       # 默认并发数,同时可以执行5次
#sudo_user     = root                    # 指定可使用的sudo的用户
#ask_sudo_pass = True                    # 每次执行ansible命令是否询问ssh密码
#ask_pass      = True                    # 每次执行ansible命令是否询问ssh口令
#remote_port   = 22                      # 远程主机的端口号(默认22)

#下面的配置建议打开
host_key_checking = False               # 检查对应服务器的host_key，如果在.ssh目录下没有对应的 host_key 将会弹出是否连接的确认，当机器过多的时候，该配置建议取消，否则要敲很多yes
deprecation_warnings = False            # 光比deprecation过时警告
log_path=/var/log/ansible.log           # 日志文件
module_name   = command                 # 默认模块，ansible命令在执行动作时，需要使用-m参数指定模块，这个配置默认为命令模块，可以不使用 -m command 直接使用ansible执行shell命令
```

### 主机清单 inventory.ini

1. ansible的主要功用在于批量主机操作，为了便捷地使用其中的部分主机，可以在inventory file中将其分组命名
2. 默认的inventory file为 `/etc/ansible/hosts`
3. inventory file可以有多个，且也可以通过Dynamic Inventory来动态生成

`/etc/ansible/hosts`的格式：

```ini
# ip:port 远程用户名称 关闭known_host提示
[k8smaster]
10.211.55.100:22 ansible_ssh_user=root

[k8snode]
10.211.55.100 ansible_ssh_user=root
10.211.55.101 ansible_ssh_user=root
10.211.55.102 ansible_ssh_user=root
```

如果机器ip连续，可以使用如下方式批量加入：

```ini
[k8smaster]
10.211.55.100

[k8snode]
10.211.55.[100:102]
```

## 相关命令

### ansible-doc

适用于查看ansible文档的工具。

```shell
# 列出所有的ansible模块
ansible-doc -l
# 查看模块数量，目前有3387个模块
ansible-doc -l | wc -l  
# 查询ping名称的模块
ansible-doc -l | grep ping
# 查询ping模块的详细文档
ansible-doc ping
# 查询ping模块的简略文档
ansible-doc -s ping
```

### ansible

```shell
ansible <host-pattern> [-m module_name] [-a args]

# host-pattern 主机选择，可以选择所有或者选择某个组
# 常用的，主要的几个选项
# -m 模块名，可以使用ansible-doc -l
# -a 模块的参数，如果模块没有参数，可选
# -v 查看执行过程
# -vv 查看更详细的执行过程
# -vvv 更更详细
```

其它选项参数：

```shell
--version # 显示版本
--list-hosts # 显示主机清单列表
-k --ask-pass # 提示输入ssh连接密码，默认key验证
-C --check # 检查，并不执行
-T --timeout=TIMEOUT # 执行命令超时时间，默认10s
-u --user=REMOTE_USER # 执行远程执行的用户
-b --become # 代替旧版的sudo切换
--become-user=USERNAME # 指定sudo的runas用户，默认为root
-K --ask-become-pass # 提示输入sudo的口令
```

> 建议使用基于sshkey的验证

示例：

```shell
# 对主机清单中的所有主机执行ping命令
ansible all -m ping
```

ansible 的 hostpattern 支持多种复杂的形式：

```shell
# 匹配所有
ansible all --list

# 通配符匹配(使用通配符需要使用双引号包裹)
ansible "*" -m ping
ansible "10.211.55.*" -m ping

# 匹配master组
ansible k8smaster --list

# 属于组k8snoder的第1台机器
ansible k8snode[0]  -m  ping

# 属于组k8snode的第1到4台机器
ansible webserver[0:5]  -m  ping

# 逻辑或(master组或者node组)
ansible "k8smaster:k8snode" --list

# 逻辑与(在master组并且在node组)
ansible "k8smaster:&k8snode" --list

# 逻辑非(在k8s-node组，但是不在k8s-master组中)
ansible 'k8snode:!k8smaster' --list

# 正则，master或者node组
ansible "~(k8smaster|k8snode)" --list
```

### ansible-galaxy

ansible galaxy 工具类似于operationhub.io或者hub.docker.com，是一个存储了很多playbook的网站，可以从galaxy中查询需要的playbook并下载使用。注意，一般我们不直接使用上面的东西，而是自己检查修改后使用。

```shell
# 列出所哟已经安装的galaxy
ansible-galaxy list
# 安装galaxy
ansible-galaxy install geerlingguy.redis
# 删除galaxy
ansible-galaxy remove geerlingguy.redis
```

### ansible-pull

ansible默认的工作模式为push模式，也就是说不要在远程主机做任何操作只需要在控制机编排playbook，push 到远程主机即可完成任务。而另一个工作模式就是 ansible pull模式：

1. 每台被控端需要安装Ansible 和git(svn)
2. 所有的配置及playbooks 都存放在git 仓库
3. 被控端的ansible-pull 计划任务会定期检查给定的git 的tag 或者分支
4. ansible-pull 执行特定的playbooks 即local.yml 或者hostname.yml
5. 每次更改playbooks 只需要提交到git 即可

pull模式适合：

1. 被控节点在配置时不可用,比如自动伸缩的服务池
2. 被控节点较多，控制机资源有限无法满足高线程和低时间的运行效率

pull模式的缺点：

1. 要求在节点上安装ansible软件

pull模式的优点：

1. 不需要中央管理服务器（取决于repository的类型）
2. 分散存储库是可能的。（再次：取决于repository的类型）
3. 连接可以由节点启动（如果不允许您更改防火墙策略，则可能很重要）
4. 增加并行度节点可用时可以pull（在基于push的模型中，如果节点不可用，则无法对其进行配置）
5. 非常快，因为避免了每个任务的SSH连接开销

使用ansible pull：

```shell
git clone git@xxx:root/pull-test.git
cd pull-test/
vim main.yml
git add main.yml
git commit -m "show route info"
git push -u origin master
# main.yml就是脚本
```

使用 ansible-pull 拉取配置并应用：

```shell
*/5 * * * * ansible-pull  -C master -d /root/test -i /opt/ansible-pull-test/hosts -U <github仓库> -e "role=db" shenchan.yml -o
```

### ansible-playbook

用于执行编写好的playbook任务，playbook实际上就是批量脚本【重点】。

编写`hello.yml` playbook：

```yml
- hosts: k8smaster
  remote_user: root
  tasks:
    - name: hello world
      command: /usr/bin/wall hello world
```

使用ansible-playbook执行脚本:

```shell
 ~/temp ansible-playbook hello.yml

PLAY [k8smaster]  ************************************************

TASK [Gathering Facts]  ******************************************
ok: [10.211.55.100]

TASK [hello world]  **********************************************
changed: [10.211.55.100]

PLAY RECAP  ******************************************************
10.211.55.100              : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```

### ansible-vault

因为playbook脚本中可能存在敏感信息，就提供ansible-vault加密解密yml文件（让我想起了k8s的secret）。

```shell
ansible-vault [create|decrypt|edit|encrypt|rekey|view]
```

范例：

```shell
# 加密playbook，会提示输入秘钥，然后yml文件内容就会被加密
ansible-vault encrypt hello.yml
# 查看playbook，会提示输入秘钥，然后yml文件就会被临时解密输出到终端上
ansible-vault view hello.yml
# 解密playbook，会提示输入秘钥，然后yml文件就会被解密
ansible-vault decrypt hello.yml
# 创建一个新的加密的yml
ansible-vault create new.yml
# 编辑加密的playbook
ansible-vault edit new.yml
# 修改口令秘钥
ansible-vault rekey new.yml
```

### ansible-console

ansible交互执行命令的工具，是2.0新增的功能。

可以通过该命令行进入交互命令行，并提示如下的命令行提示符：

```shell
执行用户@当前操作主机组（组中包含的主机数量）[f:并发数]$
示例：
yangsx@all (3)[f:5]$
```

并通过：

* list
* cd 主机组
* forks 10 设置并发数
* ? 列出所有命令

等一系列命令管理ansible。

## Ansible执行过程

1. 加载自己的配置文件，默认为`/etc/ansible/ansible.cfg`
2. 加载自己的对应的模块文件，如 command
3. 通过ansible将模块或命令生成对应的临时py文件，并将该文件传输到远程服务器的对应执行用户的`$HOME/.ansible/tmp/ansible-tmp-数字/xxx.py`文件
4. 给文件`+x`并执行
5. 执行并返回结果
6. 删除临时py文件，sleep 0 退出

执行状态：

1. 绿色：执行成功并且不需要做改变的操作
2. 黄色：执行成功并且对目标主机做变更
3. 红色：执行失败

## Ansible常用模块

### Command

```shell
ansible all -m command -a '要执行的命令列表'
# 因为command是默认模块，所以-m可以省略
# -a 是ansible用于指定模块参数的选项
ansible all -a '要执行的命令列表'

# 比如，查看所有的主机的磁盘利用率
ansible all -a 'df -h'

# command在某些场景下，命令无法正确执行，比如
ansible all -a 'echo $HOSTNAME'
# 故提供Shell模块
```

### Shell

```shell
# 打印远程主机名称
ansible all -m shell -a 'echo $HOSTNAME'

# 更改远程口令
ansible all -m shell -a 'echo root|passwd --stdin root123'
```

### Script

用于在其他主机上运行脚本，他会先将脚本推送，再执行：

```shell
ansible all -m script -a test.sh
```

### Copy

向其他主机发送文件:

```shell
# 向其他主机发送selinux配置，并且备份原有配置文件
# src代表当前主机的文件位置，dest代表拷贝到远程主机的文件位置，backup代表备份
ansible all -m copy -a 'src=/root/ansible/selinux dest=/etc/selinux/config backup=yes'

# 复制指定文件的权限与所属
ansible all -m copy -a 'src=/etc/shadow dest=/data/ mode=000 owner=root'

# 生成f2文件，内容手动指定
ansible all -m copy -a 'content="hell\nworld" dest=/data/a.txt'
```

### Fetch

与copy相反，用于拉取远程主机的文件到本机，只能抓取文件，不可以抓取目录：

```shell
# 抓取被控端的日志文件到本机
ansible all -m fetch -a "path=/var/log/messages dest=/data"
# 如果要抓取多个文件，或者路径，需要先打包再抓取
ansible all -m shell -a 'tar Jcf /root/log.tar.xz /var/log/*.log'
ansible all -m fetch -a 'src=/root/log.tar.xz dest=/data/'
```

### File

用于操作文件的模块，包含设置文件相关属性，创建目录文件链接，删除等：

```shell
# 创建文件
ansible all -m file -a 'name=/data/f3 state=touch'
# 创建文件夹
ansible all -m file -a 'name=/data/mydir state=directory'
# 删除文件、文件夹
ansible all -m file -a 'name=/data/f3 state=absent'
# 创建软链接
ansible all -m file -a 'src=/etc/fstab dest=/data/fstab.link state=link'
```

### Hostname

主机名管理：

```shell
# 一改就统一改，永久且立即生效
ansible 192.168.30.101 -m hostname -a 'name=server1'
```

### Cron

计划任务，支持的时间有： minute, hour, day, month, weekday

```shell
# 创建时钟同步任务 每五分钟同步一次
ansible all -m cron -a "minute=*5 job='/usr/sbin/update 172.16.0.1 &> /dev/null' name=Synctime"

# 每周一周三周五，每分钟执行FBI Warning
ansible all -m cron -a 'minute=* weekday=1,3,5 job="/usr/bin/wall FBI warning" name=Warning'
# 可以通过 crontab -e查看生成的定时任务

# 禁用定时任务(注意，job和name必须写，否则关闭的可能不是一个定时任务)
ansible all -m cron -a 'disable=true job="/usr/bin/wall FBI warning" name=Warning'

# 重新启用定时任务
ansible all -m cron -a 'disable=false job="/usr/bin/wall FBI warning" name=Warning'

# 删除定时任务
ansible all -m cron -a 'job="/usr/bin/wall FBI warning" name=Warning state=absent'
```

### Yum

yum包管理：

```shell
# 安装httpd
ansible all -m yum -a 'name=httpd state=present'
# 安装httpd最新版
ansible all -m yum -a 'name=httpd state=lasted'
# 安装多个包
ansible all -m yum -a 'name=httpd,memcached state=lasted'
# 删除httpd
ansible all -m yum -a 'name=httpd state=absent'
# 查看已安装的包
ansible all -m yum -a 'name=httpd list=installed'
```

### Service

管理服务。

```shell
# 启动、并开启激动 vsftpd，支持的状态还有 restarted、stopped、reloaded
ansible websrvs -m service -a 'name=vsftpd state=started enable=yes'
```

### User

管理用户。

```shell
# 设置nginx服务账号
ansible nginxs -m user -a 'name=nginx shell=/sbin/nologin system=yes home=/var/nginx groups=root,bin uid=80 comment="Nginx Server"'
# 删除账号
ansible nginxs -m user -a 'name=nginx state=absent'
```

### Group

管理组。

```shell
# 设置nginx组
ansible ngins -m group -a 'name=nginx system=yes gid=80'
```

## PlayBook

剧本，由yaml编写，制定了ansible的任务。

创建playbook yaml文件：

```yaml
# 一个playbook包含多个play
- hosts: all
  remote_user: root

# 一个play包含多个task，一个task对应一个action
  tasks: 
  - name: show me the name
    ansible.builtin.command: hostname
  - name: say hello
    ansible.builtin.shell:
      cmd: echo hello
  - name: other
    # action 等同于上面的写法
    action: shell echo hello2
```

使用ansible-playbook执行剧本：

```yaml
ansible-playbook hello.yaml
```

Playbook是由一个或者多个play组成的列表。一个play主要将事先定义好的一组主机装扮成事先通过ansible的task定义好的角色。从根本上来说，一个task就是调用一个ansible的module，一个paly包含多个task。Playbook将多个Play组织在一起，完成一个复杂的的编排。

如果playbook中某个action返回码不为0，也就是出错状态，如果想要忽略错误继续执行action，共有两种方式：

```yaml
tasks:
- name: test
  shell: /usr/bin/somecommand || /bin/true
# 短路或，如果前面失败，继续执行后面
```

或者（推荐）：

```yaml
tasks:
- name: test
  shell: /usr/bin/somecommand
  ignore_errors: True
```

ansible-playbook的其他属性：

* `--check` 只检测可能发生的改变，但是不会真正执行
* `--lists` 列出将会被应用的主机清单
* `--limit` 只针对某个主机执行，比如`--limit 192.168.23.13`
* `-v -vvv` 详细信息

### Handler和Notify

Handler用于监控task中的某个action，如果达到一定的条件，将会使用Notify出发指定的Handler。

```yaml
# copy action如果发现文件发生变更，将会触发重启
- hosts: all
  remote_user: root

  tasks:
  - name: install httpd package
    ansible.builtin.yum:
      name:
        - httpd
  - name: copy conf file
    ansible.builtin.copy:
      src: /data/httpd/httpd.conf
      dest: /etc/httpd/conf/
      backup: true
    notify: restart service
    tag: update

  handlers:
  - name: restart service
    service: 
      name: httpd
      state: restarted
```

### Tags

上述yaml有一个tags标签，该tags用于标记一个动作。被标记的动作可以在命令行单独执行：

```shell
ansible-playbook xxx.yaml -t update
```

### 使用变量

变量名，只能有数字、字母、下划线组成，且必须以字母开头

变量来源有：

1. Ansible的内置变量。（参考：<https://docs.ansible.com/ansible/latest/reference\\_appendices/special\\_variables.html> ），比较常用的有：
   1. `inventory_dir` 清单文件夹位置
   2. `playbook_dir` 当前playbook的文件夹位置
2. 远程主机信息，`ansible setup facts`，可以使用filter参数过滤变量名称查看

   ```shell
   # 查询所有主机的所有变量
   ansible all -m setup 
   # 查询所有主机的ip地址(支持通配符)
   ansible all -m setup -a 'filter="ansible_default_ipv4"'
   ```
3. 在`/etc/ansible/hosts`中定义
   1. 普通变量：主机组中主机单独定义，优先级高于公共变量

      ```ini
      [k8smaster]
      10.211.55.100:22 ansible_ssh_user=root varname=value
      ```
   2. 公共（组）变量：针对主机组中所有主机定义统一变量

      ```ini
      # k8smaster的统一变量
      [k8smaster:vars]
      nodename=www
      dominname=com
      ```
4. 通过命令行制定变量，优先级最高

   ```shell
   ansible-playbook -e varname=value
   ```
5. 在playbook中定义

   ```yaml
   vars:
   - var1: value1 
   - var2: value2
   ```
6. 在独立的变量yaml文件中定义

   ```yaml
   # 定义vars.yml变量文件
   var1: xxx1
   var2: xxx2

   # 应用变量文件，读取变量
   - hosts: web
     remote_user: root
     vars_files:
     - vars.yml
     ....
   ```
7. 在role中定义

使用变变量：

```yaml
- hosts: appsrvs
  remote_user: root

  tasks:
  - name: install package
    yum: name={{ packagename }}
```

### Template

配置文件通常不能直接copy，因为不同主机中，某些配置可能不同，这个时候可以使用模板来解决。Ansible的模板基于python的Jinja2模版语言。

定义模板文件，需要以 `.j2` 作为后缀，定义`nginx.conf.j2`：

```jinja2
 
```
