1. Ansible简介 认识Ansible
Ansible是一个为软件提供系统配置管理 和批量部署 的开源工具集 ,最初由Michael DeHaan于2012年编写,并于2015年被Red Hat公司收购,此后,Red Hat公司和开源社区进一步开发和改进了Ansible。
Ansible的核心组件 :
Module (模块)
文档地址:https://docs.ansible.com/ansible/latest/collections/index_module.html
模块涵盖领域:
得益于Ansible可扩展的原生架构,支持自定义模块
ad-hoc (Ansible命令行工具)
Ansible安装成功后即可执行Ansible指令,可使用/usr/bin/ansible
命令行工具在一个或多个管理节点上执行单个任务的命令:
有益于创建项目和测试Ansible配置 Ansible Modules的使用和测试更加方便 Playbook (剧本)
Ansible安装成功后可编写剧本:
使用人类易读的配置部署和编排语言编写 可完成一组任务 Inventory (清单)
清单是目标的集合:
最常见的由主机组成,但也可以是与之相关的组件,如网络交换机、容器、存储阵列、其他物理或虚拟组件 有用信息,如包含目标选择的文本文件 动态清单,可通过执行程序动态获取数据 Ansible的特点 :
基于Python开发,容易扩展 功能强大,内置模块丰富,满足多样需求 管理模式简单,上手容易 无代理模式,通过SSH通信,跨平台支持 2. 准备环境 安装Docker,配置Ansible实验环境,配置主机间SSH免密码连接,配置Ansible课程的代码库
2.1 安装Docker Docker是一个容器产品,利用操作系统级虚拟化,允许软件作为容器交付,容器是相互隔离的,每个容器内部都可以捆绑软件库和配置文件,Docker在Mac、Windows、Linux上都可用。
为了方便学习,我选择在Windows系统上安装Docker桌面级产品。
下载地址:https://www.docker.com/
安装过程非常简单,安装后如果出现提示,按照提示去做就ok。
测试:
C:\Users\zouxiongbin>docker run -it --rm ubuntu bash Unable to find image 'ubuntu:latest' locally latest: Pulling from library/ubuntu 6 b851dcae6ca: Pull completeDigest: sha256:2 a357c4bd54822267339e601ae86ee3966723bdbcae640a70ace622cc9470c83 Status: Downloaded newer image for ubuntu:latest root@1 eb3165473b6:/ Linux 1 eb3165473b6 5.15 .90.1 -microsoft -standard -WSL2 root@1 eb3165473b6:/ PRETTY_NAME="Ubuntu 22.04.2 LTS" NAME="Ubuntu" VERSION_ID="22.04" VERSION="22.04.2 LTS (Jammy Jellyfish)" VERSION_CODENAME=jammy ID=ubuntu ID_LIKE=debian HOME_URL="https://www.ubuntu.com/" SUPPORT_URL="https://help.ubuntu.com/" BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" UBUNTU_CODENAME=jammy root@1 eb3165473b6:/
2.2 安装Ansible实验环境 Ansible实验环境代码地址: https://github.com/spurin/diveintoansible-lab,安装方法参考该地址文档,这里记录下Windows的安装方法(前提:本地Windows操作系统安装了git):
C:\Users\zouxiongbin>git version git version 2.37 .1 .windows.1 C:\Users\zouxiongbin>git clone https://github.com/spurin/diveintoansible-lab .git C:\Users\zouxiongbin\diveintoansible-lab \.env C:\Users\zouxiongbin\diveintoansible-lab \DiveIntoAnsible_Cover.png C:\Users\zouxiongbin\diveintoansible-lab \README.md C:\Users\zouxiongbin\diveintoansible-lab \docker-compose .yaml C:\Users\zouxiongbin\diveintoansible-lab \config\guest_name C:\Users\zouxiongbin\diveintoansible-lab \config\guest_passwd C:\Users\zouxiongbin\diveintoansible-lab \config\guest_shell C:\Users\zouxiongbin\diveintoansible-lab \config\root_passwd C:\Users\zouxiongbin\diveintoansible-lab >docker-compose up ...... Attaching to centos1, centos2, centos3, docker, portal, ubuntu-c , ubuntu1, ubuntu2, ubuntu3 docker-compose pull docker-compose down
访问http://localhost:1000/即可进入Ansible实验环境,如下:
实验环境由7台主机组成,分别是ubuntu-c(ansible管理主机,其他为ubuntu系统或centos系统的被管理主机)、ubuntu1、ubuntu2、ubuntu3、centos1、centos2、centos3
登录ubuntu-c主机,账号是ansible
,密码是password
# 登录Ubuntu-C主机 ubuntu-c login: ansible Password: Welcome to Ubuntu 22.04.1 LTS (GNU/Linux 5.15.90.1-microsoft-standard-WSL2 x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/advantage The programs included with the Ubuntu system are free software; the exact distribution terms for each program are described in the individual files in /usr/share/doc/*/copyright. Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by applicable law. To run a command as administrator (user "root"), use "sudo <command>". See "man sudo_root" for details. ansible@ubuntu-c:~$
除了使用本地的系统搭建Ansible实验环境外,还可以使用Google Cloud使用云服务器搭建Ansible实验环境,地址为:https://diveinto.com/p/playground
2.3 配置主机间SSH免密码连接 Ansible是一个无代理架构,意味着被管理主机无需安装Agent,只需要配置主机间的SSH免密码连接。
SSH连接建立过程如图:
配置Ansible SSH免密码连接
# ubuntu-c ansible管理主机 # 生成ssh密钥 ansible@ubuntu-c:~$ ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/home/ansible/.ssh/id_rsa): Created directory '/home/ansible/.ssh'. Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /home/ansible/.ssh/id_rsa Your public key has been saved in /home/ansible/.ssh/id_rsa.pub The key fingerprint is: SHA256:p498cMfcCkbqBCie6F26a9f3vGVKJIP7nBgFP53/pmA ansible@ubuntu-c The key's randomart image is: +---[RSA 3072]----+ | | | | | . . | | . . . + o . | | o o oSO.* . | |. o . *oO = . | |. . o .=.+ E = | | . + . o*+* * .. | | .o+ .+=o=..o. | +----[SHA256]-----+ # 查看公钥 ansible@ubuntu-c:~$ cat .ssh/id_rsa.pub ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC3qePtp0SWcYxUAg+05EW/s7silEEMWU1gYgdyJlbqO3miImGQKt+jit4eLcKjwcduF1CpiIJ2bR2CWFx2eCOT1esg9xwNy7J9Mu52kzkwoSgvIumlczkHMla4k03KERp7kIeX3oDae98XpRULu+b7rknKh02HGWmsPwMuhq0pX0928BWRiEa4SJmFzvNGoKoJbyXCVF8ZcAiZAJI0TaMe1aEEUuIm41dRhYZf/6AftChWFcJ7x1YgXG6EbG0xlacImgYE9/n+X1Hhjklm+MmBnXE6VMde4E8CdVP5OOkMNqg3H2s23UFj+4tPha5juLfSEKxm2mXOfE5rqtPJZQImgGwHINgiZv4qbTg6t78CsCl2YXzBHk8oAPnpE/YFZ8tc5vozdhdhphbN2a5Q9xZ9abByGV7bHB5MS+GQuuGSHSnSp9Ai87gb43QoU1V76jlbngYzwQFwhxLnfoNCwzr3FQj9OJ8KrwQS8zuKKujGzG5UIytGQDlpgJOE97OTS2s= ansible@ubuntu-c # 使用ssh-copy-id将公钥的内容复制到被管理主机的authorized_keys文件 ansible@ubuntu-c:~$ ssh-copy-id ansible@ubuntu1 /usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/ansible/.ssh/id_rsa.pub" The authenticity of host 'ubuntu1 (172.18.0.3)' can't be established. ED25519 key fingerprint is SHA256:Bq0T7Bg1OWZDjSsLlhWtp7QjqtZitWuPQCgcXX+pXas. This host key is known by the following other names/addresses: ~/.ssh/known_hosts:1: [hashed name] Are you sure you want to continue connecting (yes/no/[fingerprint])? yes /usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed /usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys ansible@ubuntu1's password: Number of key(s) added: 1 Now try logging into the machine, with: "ssh 'ansible@ubuntu1'" and check to make sure that only the key(s) you wanted were added. # 测试连接 ansible@ubuntu-c:~$ ssh ubuntu1 Last login: Fri Jun 16 12:28:32 2023 from 172.18.0.9 To run a command as administrator (user "root"), use "sudo <command>". See "man sudo_root" for details. ansible@ubuntu1:~$
不难发现,上面的做法存在弊端,每次将公钥的内容复制到新的被管理主机时,都需要键入yes 和被管理主机的密码 ,被管理主机很多时设置起来比较费时,因此,可以使用sshpass脚本将这一过程自动化。
安装sshpass
# 更新apt ansible@ubuntu-c:~$ sudo apt update [sudo] password for ansible: Get:1 http://security.ubuntu.com/ubuntu jammy-security InRelease [110 kB] ...... Get:18 http://archive.ubuntu.com/ubuntu jammy-backports/universe amd64 Packages [27.0 kB] Fetched 25.2 MB in 28s (904 kB/s) Reading package lists... Done Building dependency tree... Done Reading state information... Done 90 packages can be upgraded. Run 'apt list --upgradable' to see them. # 下载sshpass ansible@ubuntu-c:~$ sudo apt install sshpass
编写公钥分发脚本
# 编写公钥分发脚本 ansible@ubuntu-c:~$ cat ssh_key_send.sh # !/bin/bash rm -rf ~/.ssh/id_rsa* ssh-keygen -f ~/.ssh/id_rsa -P "" > /dev/null 2>&1 Pass_Text=password.txt Key_Path=~/.ssh/id_rsa.pub for user in ansible root do for os in ubuntu centos do for instance in 1 2 3 do sshpass -f $Pass_Text ssh-copy-id -i $Key_Path -o StrictHostKeyChecking=no ${user}@${os}${instance} done done done # 非交互式分发公钥命令,使用sshpass指定ssh密码,通过 -o StrictHostKeyChecking=no 跳过ssh连接确认信息
执行脚本
# 执行脚本 ansible@ubuntu-c:~$ sh ssh_key_send.sh # 安全起见,删除password.txt ansible@ubuntu-c:~$ rm password.txt
此时ubuntu-c连接任何被管理主机都不需要输入账号密码
# 测试连接 # ansible # -i 指定清单文件,也可以接 ,和主机列表 # -m 指定模板 ansible@ubuntu-c:~$ ansible -i,ubuntu1,ubuntu2,ubuntu3,centos1,centos2,centos3 all -m ping ubuntu1 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } ubuntu3 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } ubuntu2 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } centos1 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false, "ping": "pong" } centos2 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false, "ping": "pong" } centos3 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false, "ping": "pong" }
2.4 配置Ansible课程的代码库 Ansible课程代码库地址:https://github.com/spurin/diveintoansible,建议github上star
ansible@ubuntu-c:~$ git clone https://github.com/spurin/diveintoansible.git
3. Ansible架构和设计 Ansible配置文件,Ansible Inventory清单和Module模块
3.1 Ansible配置文件 # 查看ansible版本信息 ansible@ubuntu-c:~$ ansible --version ansible [core 2.14.2] config file = None configured module search path = ['/home/ansible/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules'] ansible python module location = /usr/local/lib/python3.10/dist-packages/ansible ansible collection location = /home/ansible/.ansible/collections:/usr/share/ansible/collections executable location = /usr/local/bin/ansible python version = 3.10.6 (main, Nov 14 2022, 16:10:14) [GCC 11.3.0] (/usr/bin/python3) jinja version = 3.1.2 libyaml = True
配置文件优先级
可以发现Ansible config file还未配置,Ansible配置文件位置和环境变量可以用于影响config file,优先级从高到低依次是:
ANSIBLE_CONFIG 环境变量,带ansible配置文件路径./ansible.cfg 当前目录,当前目录可以有自己单独的配置文件,推荐 ~/.ansible.cfg 隐藏文件,在用户的主目录中/etc/ansible/ansible.cfg 通常由Ansible通过包或系统安装提供,如apt install ansible
3.2 Ansible清单 停止Ansible实验环境
C:\Users\zouxiongbin\diveintoansible-lab >docker-compose down
修改docker-compose.yml文件为:
version: '3.8' services: ubuntu-c: hostname: ubuntu-c container_name: ubuntu-c image: spurin/diveintoansible:ansible ports: - ${UBUNTUC_PORT_SSHD}:22 - ${UBUNTUC_PORT_TTYD}:7681 privileged: true volumes: - ${CONFIG}:/config - ${ANSIBLE_HOME}/shared:/shared - ${ANSIBLE_HOME}/ubuntu-c/ansible:/home/ansible - ${ANSIBLE_HOME}/ubuntu-c/root:/root networks: - diveinto.io ubuntu1: hostname: ubuntu1 container_name: ubuntu1 image: spurin/diveintoansible:ubuntu ports: - ${UBUNTU1_PORT_SSHD}:22 - ${UBUNTU1_PORT_TTYD}:7681 privileged: true volumes: - ${CONFIG}:/config - ${ANSIBLE_HOME}/shared:/shared - ${ANSIBLE_HOME}/ubuntu1/ansible:/home/ansible - ${ANSIBLE_HOME}/ubuntu1/root:/root networks: - diveinto.io ubuntu2: hostname: ubuntu2 container_name: ubuntu2 image: spurin/diveintoansible:ubuntu ports: - ${UBUNTU2_PORT_SSHD}:22 - ${UBUNTU2_PORT_TTYD}:7681 privileged: true volumes: - ${CONFIG}:/config - ${ANSIBLE_HOME}/shared:/shared - ${ANSIBLE_HOME}/ubuntu2/ansible:/home/ansible - ${ANSIBLE_HOME}/ubuntu2/root:/root networks: - diveinto.io ubuntu3: hostname: ubuntu3 container_name: ubuntu3 image: spurin/diveintoansible:ubuntu ports: - ${UBUNTU3_PORT_SSHD}:22 - ${UBUNTU3_PORT_TTYD}:7681 privileged: true volumes: - ${CONFIG}:/config - ${ANSIBLE_HOME}/shared:/shared - ${ANSIBLE_HOME}/ubuntu3/ansible:/home/ansible - ${ANSIBLE_HOME}/ubuntu3/root:/root networks: - diveinto.io centos1: hostname: centos1 container_name: centos1 image: spurin/diveintoansible:centos-sshd-2222 ports: - ${CENTOS1_PORT_SSHD}:2222 - ${CENTOS1_PORT_TTYD}:7681 privileged: true volumes: - ${CONFIG}:/config - ${ANSIBLE_HOME}/shared:/shared - ${ANSIBLE_HOME}/centos1/ansible:/home/ansible - ${ANSIBLE_HOME}/centos1/root:/root networks: - diveinto.io centos2: hostname: centos2 container_name: centos2 image: spurin/diveintoansible:centos ports: - ${CENTOS2_PORT_SSHD}:22 - ${CENTOS2_PORT_TTYD}:7681 privileged: true volumes: - ${CONFIG}:/config - ${ANSIBLE_HOME}/shared:/shared - ${ANSIBLE_HOME}/centos2/ansible:/home/ansible - ${ANSIBLE_HOME}/centos2/root:/root networks: - diveinto.io centos3: hostname: centos3 container_name: centos3 image: spurin/diveintoansible:centos ports: - ${CENTOS3_PORT_SSHD}:22 - ${CENTOS3_PORT_TTYD}:7681 privileged: true volumes: - ${CONFIG}:/config - ${ANSIBLE_HOME}/shared:/shared - ${ANSIBLE_HOME}/centos3/ansible:/home/ansible - ${ANSIBLE_HOME}/centos3/root:/root networks: - diveinto.io docker: hostname: docker container_name: docker image: spurin/diveintoansible:dind privileged: true volumes: - ${ANSIBLE_HOME}/shared:/shared networks: - diveinto.io portal: hostname: portal container_name: portal image: spurin/diveintoansible:portal environment: - NGINX_ENTRYPOINT_QUIET_LOGS=1 depends_on: - centos1 - centos2 - centos3 - ubuntu1 - ubuntu2 - ubuntu3 ports: - "1000:80" networks: - diveinto.io networks: diveinto.io: name: diveinto.io
重新启动Ansible实验环境
C:\Users\zouxiongbin\diveintoansible-lab >docker-compose up ...... Attaching to centos1, centos2, centos3, docker, portal, ubuntu-c , ubuntu1, ubuntu2, ubuntu3
访问http://localhost:1000/进入ubuntu-c环境
ini清单文件
ansible@ubuntu-c:~$ ls diveintoansible ssh_key_send.sh this_is_example_ansible.cfg # 新建文件夹并进入 ansible@ubuntu-c:~$ mkdir demo01 && cd demo01 # 创建配置文件和清单文件 ansible@ubuntu-c:~/demo01$ touch ansible.cfg hosts
配置文件 ansible.cfg :
[defaults] inventory = hostshost_key_checking = False
清单文件 hosts :
[control] ubuntu-c ansible_connection =local [centos] centos1 ansible_port =2222 centos[2:3] [centos:vars] ansible_user =root[ubuntu] ubuntu[1:3] [ubuntu:vars] ansible_become =true ansible_become_pass =password[linux:children] centos ubuntu [linux:vars] ansible_port =1234
上面的清单文件包括了:
测试:
ansible@ubuntu-c:~/demo01$ ansible all -m ping -o centos2 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host centos2 port 1234: Connection refused centos3 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host centos3 port 1234: Connection refused ubuntu1 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host ubuntu1 port 1234: Connection refused ubuntu2 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host ubuntu2 port 1234: Connection refused ubuntu3 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host ubuntu3 port 1234: Connection refused ubuntu-c | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
注释掉hosts文件里最后两行,再进行测试:
ansible@ubuntu-c:~/demo01$ ansible all -m ping -o ubuntu-c | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
yaml清单文件
新增清单文件 hosts.yaml :
--- control: hosts: ubuntu-c: ansible_connection: local centos: hosts: centos1: ansible_port: 2222 centos2: centos3: vars: ansible_user: root ubuntu: hosts: ubuntu1: ubuntu2: ubuntu3: vars: ansible_become: true ansible_become_pass: password linux: children: centos: ubuntu: ...
将清单改为yaml类型,只需修改配置文件 ansible.cfg :
[defaults] inventory = hosts.yamlhost_key_checking = False
再次进行测试:
ansible@ubuntu-c:~/demo01$ ansible linux -m ping -o centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
json清单文件
使用python根据hosts.yaml 生成清单文件 hosts.json (前提是已安装python3):
ansible@ubuntu-c:~/demo01$ python3 --version Python 3.10.6 ansible@ubuntu-c:~/demo01$ python3 -c 'import sys, yaml, json; json.dump(yaml.load(sys.stdin, Loader=yaml.FullLoader), sys.stdout, indent=4)' < hosts.yaml > hosts.json ansible@ubuntu-c:~/demo01$ cat hosts.json { "control": { "hosts": { "ubuntu-c": { "ansible_connection": "local" } } }, "centos": { "hosts": { "centos1": { "ansible_port": 2222 }, "centos2": null, "centos3": null }, "vars": { "ansible_user": "root" } }, "ubuntu": { "hosts": { "ubuntu1": null, "ubuntu2": null, "ubuntu3": null }, "vars": { "ansible_become": true, "ansible_become_pass": "password" } }, "linux": { "children": { "centos": null, "ubuntu": null } } }
将清单改为json类型,只需修改配置文件 ansible.cfg :
[defaults] inventory = hosts.jsonhost_key_checking = False
再次进行测试:
ansible@ubuntu-c:~/demo01$ ansible centos -m ping -o centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
命令行工具参数
也可以使用ansible命令行工具参数,指定清单文件运行,如:
# ansible # -i 指定清单文件,会覆盖配置文件的inventory参数 # -e 指定清单命令,会覆盖清单文件对应的变量 # -m 指定模块,默认使用command 模块 # -a 指定在被管理主机上执行的命令,命令需要使用引号 ansible@ubuntu-c:~/demo01$ ansible all -i hosts.yaml --list-hosts hosts (7): ubuntu-c centos1 centos2 centos3 ubuntu1 ubuntu2 ubuntu3 ansible@ubuntu-c:~/demo01$ ansible linux -m ping -e 'ansible_port=22' -o centos1 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host centos1 port 22: Connection refused centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} ansible@ubuntu-c:~/demo01$ ansible ubuntu -a 'id' -o ubuntu1 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root) ubuntu3 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root) ubuntu2 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
3.3 Ansible模块 执行期间颜色含义
红色 —— 代表失败黄色 —— 代表成功,有改变绿色 —— 代表成功,无改变幂等性
幂等性指多次执行产生的结果不会发生改变,ansible的大部分模块都能够保持操作的幂等性,即相关操作的多次执行能够达到相同结果,但也有不满足幂等性原则的模块,比如shell模块和raw模块。
setup模块
setup模块自动被剧本调用,收集远程主机的基本信息,随后可以在剧本中使用,可以使用ansible命令直接调用该模块查看目标的信息
ansible@ubuntu-c:~/demo01$ ansible centos1 -m setup | more
file模块
file模块用于文件管理操作,包括文件/文件夹/链接文件的增删改查,其他模块如copy、template、assemble也可进行文件管理操作,对于windows目标系统,则需要使用win_file模块。
# 创建文件 ansible@ubuntu-c:~/demo01$ ansible all -m file -a 'path=/tmp/test state=touch' # 修改文件权限 ansible@ubuntu-c:~/demo01$ ansible all -m file -a 'path=/tmp/test state=file mode=600'
copy模块
copy模块作用是复制文件,通常用于将ansible管理主机上的文件拷贝到远程主机中,也可以将远程主机的文件拷贝到远程主机
# 拷贝管理主机上的文件到远程主机中 ansible@ubuntu-c:~/demo01$ ansible all -m copy -a 'src=/tmp/x dest=/tmp/x' # 拷贝远程主机上的文件到远程主机中 ansible@ubuntu-c:~/demo01$ ansible all -m copy -a 'remote_src=yes src=/tmp/x dest=/tmp/y'
fetch模块
fetch模块作用也是复制文件,通常用于将到远程主机上的文件拷贝到ansible管理主机上
# 创建文件 ansible@ubuntu-c:~/demo01$ ansible all -m file -a 'path=/tmp/test_modules.txt state=touch mode=600' -o # 远程文件拷贝到本地 ansible@ubuntu-c:~/demo01$ ansible all -m fetch -a 'src=/tmp/test_modules.txt dest=/tmp/' -o
command模块
command模块是ansible的默认基本模块,也可以省略不写,但是使用command模块,不得出现shell变量,如$name
,也不得出现特殊符号> < | ; &
,如果需要使用,请使用shell
模块
# 所有主机执行 hostname 命令 ansible@ubuntu-c:~/demo01$ ansible all -a 'hostname' -o ubuntu-c | CHANGED | rc=0 | (stdout) ubuntu-c centos1 | CHANGED | rc=0 | (stdout) centos1 centos2 | CHANGED | rc=0 | (stdout) centos2 ubuntu1 | CHANGED | rc=0 | (stdout) ubuntu1 centos3 | CHANGED | rc=0 | (stdout) centos3 ubuntu2 | CHANGED | rc=0 | (stdout) ubuntu2 ubuntu3 | CHANGED | rc=0 | (stdout) ubuntu3
shell模块
shell模块作用是在远程主机上执行命令
# 远程主机执行 ps -ef | grep vim 命令 ansible@ubuntu-c:~/demo01$ ansible linux -m shell -a 'ps -ef | grep vim' -o centos1 | CHANGED | rc=0 | (stdout) root 864 863 0 07:33 pts/0 00:00:00 /bin/sh -c ps -ef | grep vim\nroot 866 864 0 07:33 pts/0 00:00:00 grep vim centos2 | CHANGED | rc=0 | (stdout) root 769 768 0 07:33 pts/0 00:00:00 /bin/sh -c ps -ef | grep vim\nroot 771 769 0 07:33 pts/0 00:00:00 grep vim centos3 | CHANGED | rc=0 | (stdout) root 682 681 0 07:33 pts/0 00:00:00 /bin/sh -c ps -ef | grep vim\nroot 684 682 0 07:33 pts/0 00:00:00 grep vim ubuntu2 | CHANGED | rc=0 | (stdout) root 1338 1337 0 07:33 pts/1 00:00:00 /bin/sh -c ps -ef | grep vim\nroot 1340 1338 0 07:33 pts/1 00:00:00 grep vim ubuntu1 | CHANGED | rc=0 | (stdout) root 1345 1344 0 07:33 pts/1 00:00:00 /bin/sh -c ps -ef | grep vim\nroot 1347 1345 0 07:33 pts/1 00:00:00 grep vim ubuntu3 | CHANGED | rc=0 | (stdout) root 1335 1334 0 07:33 pts/1 00:00:00 /bin/sh -c ps -ef | grep vim\nroot 1337 1335 0 07:33 pts/1 00:00:00 grep vim
script模块
script模块的作用是在远程主机上执行ansible管理主机上的脚本,脚本存在ansible管理主机上,不需要手动拷贝到远程主机后再执行
# 创建脚本 ansible@ubuntu-c:~/demo01$ cat show_pwd.sh # !/bin/bash pwd # 去/tmp目录下 批量执行脚本 ansible@ubuntu-c:~/demo01$ ansible all -m script -a 'chdir=/tmp /home/ansible/demo01/show_pwd.sh' -o ubuntu-c | CHANGED => {"changed": true,"rc": 0,"stderr": "","stderr_lines": [],"stdout": "/tmp\n","stdout_lines": ["/tmp"]} centos1 | CHANGED => {"changed": true,"rc": 0,"stderr": "Shared connection to centos1 closed.\r\n","stderr_lines": ["Shared connection to centos1 closed."],"stdout": "/tmp\r\n","stdout_lines": ["/tmp"]} centos2 | CHANGED => {"changed": true,"rc": 0,"stderr": "Shared connection to centos2 closed.\r\n","stderr_lines": ["Shared connection to centos2 closed."],"stdout": "/tmp\r\n","stdout_lines": ["/tmp"]} centos3 | CHANGED => {"changed": true,"rc": 0,"stderr": "Shared connection to centos3 closed.\r\n","stderr_lines": ["Shared connection to centos3 closed."],"stdout": "/tmp\r\n","stdout_lines": ["/tmp"]} ubuntu1 | CHANGED => {"changed": true,"rc": 0,"stderr": "Shared connection to ubuntu1 closed.\r\n","stderr_lines": ["Shared connection to ubuntu1 closed."],"stdout": "\r\n/tmp\r\n","stdout_lines": ["","/tmp"]} ubuntu2 | CHANGED => {"changed": true,"rc": 0,"stderr": "Shared connection to ubuntu2 closed.\r\n","stderr_lines": ["Shared connection to ubuntu2 closed."],"stdout": "\r\n/tmp\r\n","stdout_lines": ["","/tmp"]} ubuntu3 | CHANGED => {"changed": true,"rc": 0,"stderr": "Shared connection to ubuntu3 closed.\r\n","stderr_lines": ["Shared connection to ubuntu3 closed."],"stdout": "\r\n/tmp\r\n","stdout_lines": ["","/tmp"]}
Ansible-doc
适合用于查看指定模块的变量和语法
ansible@ubuntu-c:~/demo01$ ansible-doc shell
4. Ansible Playbook介绍 YAML,Playbook剧本,变量,Facts 变量, Jinja2模板,Playbook的编写和执行
4.1 YAML YAML是一种面向数据的语言,ansible剧本可以使用YAML和json编写,使用YAML编写的剧本具有易读易写的特性,便于分享合作。
编写yaml文件test.yaml
--- example_key_1: this is a string example_key_2: this is another string no_quotes: this is a string example double_quotes: "this is a string example" single_quotes: 'this is a string example' escape_no_quotes: this is a string example\n escape_double_quotes: "this is a string example\n" escape_single_quotes: 'this is a string example\n' multilines_example_key_1: | this is a string that goes over multiple lines multilines_example_key_2: > this is a string that goes over multiple lines multilines_example_key_3: >- this is a string that goes over multiple lines example_integer_1: 1 example_integer_2: "1" is_false_01: false is_false_02: False is_false_03: FALSE is_false_04: no is_false_05: No is_false_06: NO is_false_07: off is_false_08: Off is_false_09: OFF is_false_10: n is_true_01: true is_true_02: True is_true_03: TRUE is_true_04: yes is_true_05: Yes is_true_06: YES is_true_07: on is_true_08: On is_true_09: ON is_true_10: y example_key_4: [item 1 , item 2 , item 3 , item 4 , item 5 ] example_key_5: example_key_5 example_key_6: example_key_6 example_key_9: example_key_10: sub_example_value1 example_key_11: example_key_12: sub_example_value2 example_key_13: - item_1 - item_2 - item_3 exmaple_key_14: - item_4 - item_5 - item_6 example_dictionary_1: - example_dictioanry_2: - 1 - 2 - 3 - example_dictionary_3: - 4 - 5 - 6 - example_dictioanry_4: - 7 - 8 - 9 ...
编写展示脚本show_yaml_python.sh
python3 -c 'import yaml,pprint;pprint.pprint(yaml.load(open("test.yaml").read(), Loader=yaml.FullLoader))'
测试:
ansible@ubuntu-c:~/demo02$ chmod +x show_yaml_python.sh ansible@ubuntu-c:~/demo02$ ./show_yaml_python.sh {'double_quotes' : 'this is a string example' , 'escape_double_quotes' : 'this is a string example\n' , 'escape_no_quotes' : 'this is a string example\\n' , 'escape_single_quotes' : 'this is a string example\\n' , 'example_dictionary_1' : [{'example_dictioanry_2' : [1, 2, 3]}, {'example_dictionary_3' : [4, 5, 6]}, {'example_dictioanry_4' : [7, 8, 9]}], 'example_integer_1' : 1, 'example_integer_2' : '1' , 'example_key_1' : 'this is a string' , 'example_key_11' : {'example_key_12' : 'sub_example_value2' }, 'example_key_13' : ['item_1' , 'item_2' , 'item_3' ], 'example_key_2' : 'this is another string' , 'example_key_4' : ['item 1' , 'item 2' , 'item 3' , 'item 4' , 'item 5' ], 'example_key_5' : 'example_key_5' , 'example_key_6' : 'example_key_6' , 'example_key_9' : {'example_key_10' : 'sub_example_value1' }, 'exmaple_key_14' : ['item_4' , 'item_5' , 'item_6' ], 'is_false_01' : False, 'is_false_02' : False, 'is_false_03' : False, 'is_false_04' : False, 'is_false_05' : False, 'is_false_06' : False, 'is_false_07' : False, 'is_false_08' : False, 'is_false_09' : False, 'is_false_10' : 'n' , 'is_true_01' : True, 'is_true_02' : True, 'is_true_03' : True, 'is_true_04' : True, 'is_true_05' : True, 'is_true_06' : True, 'is_true_07' : True, 'is_true_08' : True, 'is_true_09' : True, 'is_true_10' : 'y' , 'multilines_example_key_1' : 'this is a string\n' 'that goes over\n' 'multiple lines\n' , 'multilines_example_key_2' : 'this is a string that goes over multiple lines\n' , 'multilines_example_key_3' : 'this is a string that goes over multiple lines' , 'no_quotes' : 'this is a string example' , 'single_quotes' : 'this is a string example' }
YAML学习资源
4.2 初识剧本 先通过例子来熟悉下Ansible Playbook剧本。
# 新建文件夹并进入 ansible@ubuntu-c:~$ mkdir demo03 && cd demo03 # 创建配置文件和清单文件 ansible@ubuntu-c:~/demo03$ touch ansible.cfg hosts motd_playbook.yaml
配置文件 ansible.cfg :
[defaults] inventory = hostshost_key_checking = False
清单文件 hosts :
[control] ubuntu-c ansible_connection =local [centos] centos1 ansible_port =2222 centos[2:3] [centos:vars] ansible_user =root[ubuntu] ubuntu[1:3] [ubuntu:vars] ansible_become =true ansible_become_pass =password[linux:children] centos ubuntu
剧本文件motd_playbook.yaml :
--- - hosts: centos user: root vars: motd: "Welcome to CentOS Linux - Ansible Rocks\n" tasks: - name: Configure a MOTD (message of day) copy: content: "{{ motd }} " dest: /etc/motd ...
测试:
ansible@ubuntu-c:~/demo03$ ansible-playbook motd_playbook.yaml PLAY [centos] ******************************************************************************************************************* TASK [Gathering Facts] ********************************************************************************************************** ok: [centos2] ok: [centos3] ok: [centos1] TASK [Configure a MOTD (message of the day)] ************************************************************************************ changed: [centos1] changed: [centos2] changed: [centos3] PLAY RECAP ********************************************************************************************************************** centos1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos3 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
其中,TASK [Gathering Facts] 是默认执行的任务,通过setup模块收集目标系统的Facts变量。所以,虽然只定义了1个任务,但是每个目标主机都执行了2个任务。
上面定义了变量motd ,在执行时也可以通过-e 'motd="Testing the motd playbook\n"'
重写
登录任意centos系统,可以看到:
centos1 login: ansible Password: Last login: Tue Jun 20 03:15:36 from 172.19.0.3 Welcome to CentOS Linux - Ansible Rocks ansible@centos1:~$
修改剧本文件,添加任务的通知键及处理程序:
--- - hosts: centos user: root gather_facts: False vars: motd: "Welcome to CentOS Linux - Ansible Rocks\n" tasks: - name: Configure a MOTD (message of day) copy: content: "{{ motd }} " dest: /etc/motd notify: MOTD changed handlers: - name: MOTD changed debug: msg: The MOTD was changed ...
gather_facts —— 默认为True,设置为False时不执行默认的TASK [Gathering Facts] 任务
测试:
ansible@ubuntu-c:~/demo03$ ansible-playbook motd_playbook.yaml PLAY [centos] ******************************************************************************************************************* TASK [Configure a MOTD (message of day)] **************************************************************************************** changed: [centos2] changed: [centos1] changed: [centos3] RUNNING HANDLER [MOTD changed] ************************************************************************************************** ok: [centos1] => { "msg": "The MOTD was changed" } ok: [centos2] => { "msg": "The MOTD was changed" } ok: [centos3] => { "msg": "The MOTD was changed" } PLAY RECAP ********************************************************************************************************************** centos1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos3 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
因为MOTD任务更改了,通知键的处理程序 RUNNING HANDLER [MOTD changed] 任务也执行了,调试输出msg: The MOTD was changed ,并且 ok=2
利用系统信息的facts变量ansible_distribution ,配合when 指令来区分不同系统发行版,从而在不同系统上执行不同内容,修改剧本文件:
--- - hosts: linux vars: motd_centos: "Welcome to CentOS Linux - Ansible Rocks\n" motd_ubuntu: "Welcome to Ubuntu Linux - Ansible Rocks\n" tasks: - name: Configure a MOTD (message of day) copy: content: "{{ motd_centos }} " dest: /etc/motd notify: MOTD changed when: ansible_distribution == "CentOS" - name: Configure a MOTD (message of day) copy: content: "{{ motd_ubuntu }} " dest: /etc/motd notify: MOTD changed when: ansible_distribution == "Ubuntu" handlers: - name: MOTD changed debug: msg: The MOTD was changed ...
测试:
ansible@ubuntu-c:~/demo03$ ansible-playbook motd_playbook.yaml PLAY [linux] ******************************************************************************************************************** TASK [Gathering Facts] ********************************************************************************************************** ok: [centos3] ok: [centos1] ok: [centos2] ok: [ubuntu1] ok: [ubuntu2] ok: [ubuntu3] TASK [Configure a MOTD (message of day)] **************************************************************************************** skipping: [ubuntu1] skipping: [ubuntu2] skipping: [ubuntu3] ok: [centos3] ok: [centos1] ok: [centos2] TASK [Configure a MOTD (message of day)] **************************************************************************************** skipping: [centos1] skipping: [centos2] skipping: [centos3] changed: [ubuntu1] changed: [ubuntu3] changed: [ubuntu2] RUNNING HANDLER [MOTD changed] ************************************************************************************************** ok: [ubuntu1] => { "msg": "The MOTD was changed" } ok: [ubuntu2] => { "msg": "The MOTD was changed" } ok: [ubuntu3] => { "msg": "The MOTD was changed" } PLAY RECAP ********************************************************************************************************************** centos1 : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 centos2 : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 centos3 : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 ubuntu1 : ok=3 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 ubuntu2 : ok=3 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0 ubuntu3 : ok=3 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
点击查看更多的剧本关键字:http://docs.ansible.com/ansible/devel/playbooks_keywords.html
4.3 剧本变量 新建变量文件external_vars.yaml
--- external_example_key: example value external_dict: dict_key: This is a dictionary value external_inline_dict: {inline_dict_key: This is an inline dictionary value } external_named_list: - item1 - item2 - item3 - item4 external_inline_named_list: [ item1 , item2 , item3 , item4 ] ...
新建剧本variables_playbook.yaml
--- - hosts: centos1 gather_facts: False vars_files: - external_vars.yaml tasks: - name: Test external dictionary key value debug: msg: "{{ external_example_key }} " - name: Test external named dictionary dictionary debug: msg: "{{ external_dict }} " - name: Test external named dictionary dictionary key value with dictionary dot notation debug: msg: "{{ external_dict.dict_key }} " - name: Test external named dictionary dictionary key value with brackets notation debug: msg: "{{ external_dict['dict_key'] }} " - name: Test external named inline dictionary dictionary debug: msg: "{{ external_inline_dict }} " - name: Test external named inline dictionary dictionary key value with dictionary dot notation debug: msg: "{{ external_inline_dict.inline_dict_key }} " - name: Test external named inline dictionary dictionary key value with brackets notation debug: msg: "{{ external_inline_dict['inline_dict_key'] }} " - name: Test external named list debug: msg: "{{ external_named_list }} " - name: Test external named list first item dot notation debug: msg: "{{ external_named_list.0 }} " - name: Test external named list first item brackets notation debug: msg: "{{ external_named_list[0] }} " - name: Test external inline named list debug: msg: "{{ external_inline_named_list }} " - name: Test external inline named list first item dot notation debug: msg: "{{ external_inline_named_list.0 }} " - name: Test external inline named list first item brackets notation debug: msg: "{{ external_inline_named_list[0] }} " ...
vars_files
—— 通过文件引入变量,如:
vars_files: - external_vars.yaml
vars_prompt
—— 提示用户输入变量值,如:
vars_promt: - name: username private: False - name: password private: True
hostvars[ansible_hostname]['ansible_port'] | default('22')
—— 获取主机的变量,如:
tasks: - name: Test hostvars with an ansible fact and collect ansible_port, dict notation debug: msg: "{{ hostvars[ansible_hostname]['ansible_port'] | default('22') }} "
ansible-playbook通过-e
传入变量值时有5种方式:
# 1.通过ini格式传入 ansible-playbook variable_playbook.yaml -e extra_vars_key="extra vars value" # 2.通过json格式传入 ansible-playbook variable_playbook.yaml -e {"extra_vars_key": "extra vars value"} # 3.通过yaml格式传入 ansible-playbook variable_playbook.yaml -e {extra_vars_key: extra vars value} # 4.通过yaml文件传入 ansible-playbook variable_playbook.yaml -e @extra_vars_file.yaml # 5.通过json文件传入 ansible-playbook variable_playbook.yaml -e @extra_vars_file.json
另外,主机变量和组变量可以分布使用单独的yaml文件host_vars/centos1、host_vars/ubuntu-c和group_vars/centos、group_vars/ubuntu来保存和区分,而不是写在一个host文件里
hosts文件内容变为:
[control] ubuntu-c [centos] centos[1:3] [ubuntu] ubuntu[1:3] [linux:children] centos ubuntu
新建的host_vars/centos1内容为:
新建的host_vars/ubuntu-c内容为:
--- ansible_connection: local ...
新建的group_vars/centos内容为:
新建的group_vars/ubuntu内容为:
--- ansible_become: true ansible_become_pass: password ...
4.4 使用和自定义Facts变量 **Ansible Facts 是 Ansible 在被托管主机上自动收集的变量。**它是通过在执行 ad-hoc 以及 Playbook 时使用 setup 模块进行收集的,并且这个操作是默认的。
setup模块官方文档地址:https://docs.ansible.com/ansible/latest/collections/ansible/builtin/setup_module.html
ad-hoc使用setup模块示例:
# 显示来自centos1主机所有关于network子集的facts变量 ansible@ubuntu-c:~/demo03$ ansible centos1 -m setup -a 'gather_subset=network' # 显示来自centos1主机特定network子集的facts变量 ansible@ubuntu-c:~/demo03$ ansible centos1 -m setup -a 'gather_subset=!all,!min,network' # 使用过滤器选项显示来自centos1主机facts变量 - ansible_memfree_mb ansible@ubuntu-c:~/demo03$ ansible centos1 -m setup -a 'filter=ansible_memfree_mb' # 使用过滤器选项+通配符显示来自centos1主机facts变量 - 包含ansible_mem 字段的变量 ansible@ubuntu-c:~/demo03$ ansible centos1 -m setup -a 'filter=ansible_mem*'
ad-hoc命令模式中,setup模块收集的返回数据是字典结构,其中键为ansible_facts包含着收集到的facts变量,如:
ansible@ubuntu-c:~/demo03$ ansible centos1 -m setup -a 'filter=ansible_mem*' centos1 | SUCCESS => { "ansible_facts": { "ansible_memfree_mb": 10619, "ansible_memory_mb": { "nocache": { "free": 13976, "used": 1882 }, "real": { "free": 10619, "total": 15858, "used": 5239 }, "swap": { "cached": 0, "free": 4096, "total": 4096, "used": 0 } }, "ansible_memtotal_mb": 15858, "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": false }
playbook剧本模式中,setup模块收集的返回数据是字典结构,没有ansible_facts键,可以直接用facts变量,如:
ansible@ubuntu-c:~$ mkdir demo04 && cd demo04 ansible@ubuntu-c:~/demo04$ cp ../demo03/hosts . ansible@ubuntu-c:~/demo04$ cp ../demo03/ansible.cfg . ansible@ubuntu-c:~/demo04$ cat facts_playbook.yaml --- - hosts: all tasks: - name: Show Ip Address debug: msg: "{{ ansible_default_ipv4.address }}" ... ansible@ubuntu-c:~/demo04$ ansible-playbook facts_playbook.yaml PLAY [all] ********************************************************************************************************************** TASK [Gathering Facts] ********************************************************************************************************** ok: [ubuntu-c] ok: [centos2] ok: [centos1] ok: [centos3] ok: [ubuntu1] ok: [ubuntu2] ok: [ubuntu3] TASK [Show Ip Address] ********************************************************************************************************** ok: [ubuntu-c] => { "msg": "172.19.0.2" } ok: [centos1] => { "msg": "172.19.0.7" } ok: [centos2] => { "msg": "172.19.0.5" } ok: [centos3] => { "msg": "172.19.0.8" } ok: [ubuntu1] => { "msg": "172.19.0.9" } ok: [ubuntu2] => { "msg": "172.19.0.4" } ok: [ubuntu3] => { "msg": "172.19.0.3" } PLAY RECAP ********************************************************************************************************************** centos1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos3 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu-c : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu3 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
创造自定义facts变量
能够使用任何语言编写 返回JSON结构或ini结构 默认放置在/etc/ansible/facts.d 创建一个自定义facts变量用于收集系统日期信息:
# 自定义facts变量(返回JSON结构) ansible@ubuntu-c:~/demo04$ cat getdate1.fact # !/bin/bash echo {\""date\"": \""`date`""} ansible@ubuntu-c:~/demo04$ chmod +x getdate1.fact ansible@ubuntu-c:~/demo04$ ./getdate1.fact {"date" : "Mon Jun 26 09:45:11 UTC 2023"} # 自定义facts变量(返回ini结构) ansible@ubuntu-c:~/demo04$ cat getdate2.fact # !/bin/bash echo [date] echo date=`date` ansible@ubuntu-c:~/demo04$ chmod +x getdate2.fact ansible@ubuntu-c:~/demo04$ ./getdate2.fact [date] date=Mon Jun 26 09:47:31 UTC 2023 ansible@ubuntu-c:~/demo04$ sudo mkdir -p /etc/ansible/facts.d ansible@ubuntu-c:~/demo04$ sudo cp getdate* /etc/ansible/facts.d/ ansible@ubuntu-c:~/demo04$ ansible ubuntu-c -m setup -a 'filter=ansible_local' ubuntu-c | SUCCESS => { "ansible_facts": { "ansible_local": { "getdate1": { "date": "Mon Jun 26 09:53:15 UTC 2023" }, "getdate2": { "date": { "date": "Mon Jun 26 09:53:15 UTC 2023" } } }, "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false }
修改剧本facts_playbook.yaml,使用自定义的facts变量
ansible@ubuntu-c:~/demo04$ cat facts_playbook.yaml --- - hosts: all tasks: - name: Show Ip Address debug: msg: "{{ ansible_default_ipv4.address }}" - name: Show Custom Fact 1 debug: msg: "{{ ansible_local.getdate1.date }}" - name: Show Custom Fact 2 debug: msg: "{{ ansible_local.getdate2.date.date }}" ... ansible@ubuntu-c:~/demo04$ ansible-playbook facts_playbook.yaml -l ubuntu-c PLAY [all] ********************************************************************************************************************** TASK [Gathering Facts] ********************************************************************************************************** ok: [ubuntu-c] TASK [Show Ip Address] ********************************************************************************************************** ok: [ubuntu-c] => { "msg": "172.19.0.2" } TASK [Show Custom Fact 1] ******************************************************************************************************* ok: [ubuntu-c] => { "msg": "Mon Jun 26 09:58:49 UTC 2023" } TASK [Show Custom Fact 2] ******************************************************************************************************* ok: [ubuntu-c] => { "msg": "Mon Jun 26 09:58:49 UTC 2023" } PLAY RECAP ********************************************************************************************************************** ubuntu-c : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
目前自定义的facts变量只在ubuntu-c管理主机上生效,要想其他被管理主机也能使用自定义的facts变量,则可将自定义facts变量的文件拷贝到各自主机的/etc/ansible/facts.d目录下。
修改剧本facts_playbook.yaml:
--- - hosts: linux tasks: - name: Make Facts Dir file: path: /etc/ansible/facts.d recurse: yes state: directory - name: Copy Fact 1 copy: src: /etc/ansible/facts.d/getdate1.fact dest: /etc/ansible/facts.d/getdate1.fact mode: 0755 - name: Copy Fact 2 copy: src: /etc/ansible/facts.d/getdate2.fact dest: /etc/ansible/facts.d/getdate2.fact mode: 0755 - name: Refresh Facts setup: - name: Show Ip Address debug: msg: "{{ ansible_default_ipv4.address }} " - name: Show Custom Fact 1 debug: msg: "{{ ansible_local.getdate1.date }} " - name: Show Custom Fact 2 debug: msg: "{{ ansible_local.getdate2.date.date }} " - name: Show Custom Fact 1 in hostvars debug: msg: "{{ hostvars[ansible_hostname].ansible_local.getdate1.date }} " - name: Show Custom Fact 2 in hostvars debug: msg: "{{ hostvars[ansible_hostname].ansible_local.getdate2.date.date }} " ...
默认情况下,Ansible期望自定义facts变量文件位于需要root访问权限的位置/etc/ansible/facts.d,如何在没有root访问权限的环境中使用自定义facts变量。
# 首先删除被管理主机上的自定义facts变量文件 ansible@ubuntu-c:~/demo04$ ansible linux -m file -a 'path=/etc/ansible/facts.d/getdate1.fact state=absent' ansible@ubuntu-c:~/demo04$ ansible linux -m file -a 'path=/etc/ansible/facts.d/getdate2.fact state=absent' # 然后将控制主机上的自定义facts变量文件移动 ansible@ubuntu-c:~/demo04$ ls ansible.cfg facts_playbook.yaml getdate1.fact getdate2.fact hosts ansible@ubuntu-c:~/demo04$ mkdir facts.d ansible@ubuntu-c:~/demo04$ mv getdate* facts.d ansible@ubuntu-c:~/demo04$ ls ansible.cfg facts.d facts_playbook.yaml hosts
修改剧本facts_playbook.yaml,使用fact_path制定自定义facts变量文件位置:
--- - hosts: linux tasks: - name: Make Facts Dir file: path: /home/ansible/facts.d recurse: yes state: directory owner: ansible - name: Copy Fact 1 copy: src: facts.d/getdate1.fact dest: /home/ansible/facts.d/getdate1.fact owner: ansible mode: 0755 - name: Copy Fact 2 copy: src: /facts.d/getdate2.fact dest: /home/ansible/facts.d/getdate2.fact owner: ansible mode: 0755 - name: Refresh Facts setup: fact_path: /home/ansible/facts.d - name: Show Ip Address debug: msg: "{{ ansible_default_ipv4.address }} " - name: Show Custom Fact 1 debug: msg: "{{ ansible_local.getdate1.date }} " - name: Show Custom Fact 2 debug: msg: "{{ ansible_local.getdate2.date.date }} " - name: Show Custom Fact 1 in hostvars debug: msg: "{{ hostvars[ansible_hostname].ansible_local.getdate1.date }} " - name: Show Custom Fact 2 in hostvars debug: msg: "{{ hostvars[ansible_hostname].ansible_local.getdate2.date.date }} " ...
测试:
ansible@ubuntu-c:~/demo04$ ansible-playbook facts_playbook.yaml PLAY [linux] ******************************************************************************************************************** TASK [Gathering Facts] ********************************************************************************************************** ok: [centos3] ok: [centos1] ok: [centos2] ok: [ubuntu2] ok: [ubuntu1] ok: [ubuntu3] TASK [Make Facts Dir] *********************************************************************************************************** changed: [centos1] changed: [centos3] changed: [centos2] changed: [ubuntu1] changed: [ubuntu2] changed: [ubuntu3] TASK [Copy Fact 1] ************************************************************************************************************** changed: [ubuntu1] changed: [centos3] changed: [ubuntu2] changed: [centos1] changed: [centos2] changed: [ubuntu3] TASK [Copy Fact 2] ************************************************************************************************************** changed: [centos1] changed: [centos2] changed: [ubuntu1] changed: [ubuntu2] changed: [centos3] changed: [ubuntu3] TASK [Refresh Facts] ************************************************************************************************************ ok: [centos1] ok: [centos3] ok: [centos2] ok: [ubuntu1] ok: [ubuntu2] ok: [ubuntu3] TASK [Show Ip Address] ********************************************************************************************************** ok: [centos1] => { "msg": "172.19.0.7" } ok: [centos2] => { "msg": "172.19.0.5" } ok: [centos3] => { "msg": "172.19.0.8" } ok: [ubuntu1] => { "msg": "172.19.0.9" } ok: [ubuntu2] => { "msg": "172.19.0.4" } ok: [ubuntu3] => { "msg": "172.19.0.3" } TASK [Show Custom Fact 1] ******************************************************************************************************* ok: [centos1] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [centos2] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [centos3] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu1] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu2] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu3] => { "msg": "Mon Jun 26 10:23:48 UTC 2023" } TASK [Show Custom Fact 2] ******************************************************************************************************* ok: [centos1] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [centos2] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [centos3] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu1] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu2] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu3] => { "msg": "Mon Jun 26 10:23:48 UTC 2023" } TASK [Show Custom Fact 1 in hostvars] ******************************************************************************************* ok: [centos1] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [centos2] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [centos3] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu1] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu2] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu3] => { "msg": "Mon Jun 26 10:23:48 UTC 2023" } TASK [Show Custom Fact2 in hostvars] ******************************************************************************************** ok: [centos1] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [centos2] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [centos3] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu1] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu2] => { "msg": "Mon Jun 26 10:23:47 UTC 2023" } ok: [ubuntu3] => { "msg": "Mon Jun 26 10:23:48 UTC 2023" } PLAY RECAP ********************************************************************************************************************** centos1 : ok=10 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos2 : ok=10 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos3 : ok=10 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu1 : ok=10 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu2 : ok=10 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu3 : ok=10 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 # 测试完毕后删除自定义的facts变量 ansible@ubuntu-c:~/demo04$ ansible linux -m file -a 'path=/home/ansible/facts.d state=absent' centos1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "path": "/home/ansible/facts.d", "state": "absent" } centos3 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "path": "/home/ansible/facts.d", "state": "absent" } centos2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/libexec/platform-python" }, "changed": true, "path": "/home/ansible/facts.d", "state": "absent" } ubuntu1 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": true, "path": "/home/ansible/facts.d", "state": "absent" } ubuntu2 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": true, "path": "/home/ansible/facts.d", "state": "absent" } ubuntu3 | CHANGED => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": true, "path": "/home/ansible/facts.d", "state": "absent" }
4.5 剧本使用Jinja2模板语言 ansible@ubuntu-c:~$ mkdir demo05 && cd demo05 ansible@ubuntu-c:~/demo05$ cp ../demo04/ansible.cfg . ansible@ubuntu-c:~/demo05$ cp ../demo04/hosts . ansible@ubuntu-c:~/demo05$ touch jinja2_playbook.yaml ansible@ubuntu-c:~/demo05$ echo jinja2_extensions = jinja2.ext.loopcontrols >> ansible.cfg
jinja2_playbook.yaml文件内容如下:
--- - hosts: all tasks: - name: Ansible Jinja2 if elif else statement debug: msg: > --== Ansible Jinja2 if elif else statement ==-- { {% if ansible_hostname == "ubuntu-c" -% } This is ubuntu-c {% elif ansible_hostname == "centos1" -% } This is centos1 with it's modified SSH Port {% else -% } This is good old {{ ansible_hostname }} {% endif % } - name: Ansible Jinja2 if variable is defined ( where variable is defined ) debug: msg: > --== Ansible Jinja2 if variable is defined ( where variable is defined ) ==-- {% set example_variable = 'defined' -% } {% if example_variable is defined -% } example_variable is defined {% else -% } example_variable is not defiend {% endif % } - name: Ansible Jinja2 for statement debug: msg: > --== Ansible Jinja2 for statement ==-- {% for entry in ansible_interfaces -% } Interface entry {{ loop.index }} = {{ entry }} {% endfor % } - name: Ansible Jinja2 for range debug: msg: > --== Ansible Jinja2 for range ==-- {% for entry in range(1 , 11 ) -% } {{ entry }} {% endfor % } - name: Ansible Jinja2 for range , reversed (simulate while greater 5 ) debug: msg: > --== Ansible Jinja2 for range, reversed (simulate while greater 5) ==-- {% for entry in range(10 , 0 , -1 ) -% } {% if entry == 5 -% } {% break % } {% endif -% } {{ entry }} {% endfor % } - name: Ansible Jinja2 for range , reversed(continue if odd) debug: msg: > --== Ansible Jinja2 for range, reversed (continue if odd) {% for entry in range(10, 0, -1) -%} {% if entry is odd -%} {% continue %} {% endif -%} {{ entry }} {% endfor %} - name: Ansible Jinja2 filters debug: msg: > --=== Ansible Jinja2 filters ===-- --== min [1 , 2 , 3 , 4 , 5 ] ==-- {{ [1 , 2 , 3 , 4 , 5 ] | min }} --== max [1 , 2 , 3 , 4 , 5 ] ==-- {{ [1 , 2 , 3 , 4 , 5 ] | max }} --== unique [1 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , 5 , 5 ] ==-- {{ [1 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , 5 , 5 ] | unique }} --== difference [1 , 2 , 3 , 4 , 5 ] vs [2 , 3 , 4 ] ==-- {{ [1 , 2 , 3 , 4 , 5 ] | difference( [2 , 3 , 4 ]) }} --== random ['rod' , 'jane' , 'freddy' ] ==-- {{ ['rod' , 'jane' , 'freddy' ] | random }} --== urlsplit hostname ==-- {{ "http://docs.ansible.com/ansible/latest/playbook_filters.html" | urlsplit('hostname') }} ...
剧本过滤器官方文档地址:https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html
jinjia2相关内容可以写成单独的文件,然后在Ansible通过template模块引入。如:
新建template.j2文件,内容如下:
--== Ansible Jinja2 if statement ==-- {# If the hostname is ubuntu-c, include a message -#} {% if ansible_hostname == "ubuntu-c" -%} This is ubuntu-c {% endif %} --== Ansible Jinja2 if elif statement ==-- {% if ansible_hostname == "ubuntu-c" -%} This is ubuntu-c {% elif ansible_hostname == "centos1" -%} This is centos1 with it's modified SSH Port {% endif %} --== Ansible Jinja2 if elif else statement ==-- {% if ansible_hostname == "ubuntu-c" -%} This is ubuntu-c {% elif ansible_hostname == "centos1" -%} This is centos1 with it's modified SSH Port {% else -%} This is good old {{ ansible_hostname }} {% endif %} --== Ansible Jinja2 if variable is defined ( where variable is not defined ) ==-- {% if example_variable is defined -%} example_variable is defined {% else -%} example_variable is not defined {% endif %} --== Ansible Jinja2 if varible is defined ( where variable is defined ) ==-- {% set example_variable = 'defined' -%} {% if example_variable is defined -%} example_variable is defined {% else -%} example_variable is not defined {% endif %} --== Ansible Jinja2 for statement ==-- {% for entry in ansible_all_ipv4_addresses -%} IP Address entry {{ loop.index }} = {{ entry }} {% endfor %} --== Ansible Jinja2 for range {% for entry in range(1, 11) -%} {{ entry }} {% endfor %} --== Ansible Jinja2 for range, reversed (simulate while greater 5) ==-- {% for entry in range(10, 0, -1) -%} {% if entry == 5 -%} {% break %} {% endif -%} {{ entry }} {% endfor %} --== Ansible Jinja2 for range, reversed (continue if odd) ==-- {% for entry in range(10, 0, -1) -%} {% if entry is odd -%} {% continue %} {% endif -%} {{ entry }} {% endfor %} ---=== Ansible Jinja2 filters ===--- --== min [1, 2, 3, 4, 5] ==-- {{ [1, 2, 3, 4, 5] | min }} --== max [1, 2, 3, 4, 5] ==-- {{ [1, 2, 3, 4, 5] | max }} --== unique [1, 1, 2, 2, 3, 3, 4, 4, 5, 5] ==-- {{ [1, 1, 2, 2, 3, 3, 4, 4, 5, 5] | unique }} --== difference [1, 2, 3, 4, 5] vs [2, 3, 4] ==-- {{ [1, 2, 3, 4, 5] | difference([2, 3, 4]) }} --== random ['rod', 'jane', 'freddy'] ==-- {{ ['rod', 'jane', 'freddy'] | random }} --== urlsplit hostname ==-- {{ "http://docs.ansible.com/ansible/latest/playbooks_filters.html" | urlsplit('hostname') }}
修改jinja2_playbook.yaml:
--- - hosts: all tasks: - name: Jinja2 template template: src: template.j2 dest: "/tmp/{{ ansible_hostname }} _template.out" trim_blocks: true mode: 0644 ...
4.6 剧本实际应用 在不同的linux系统上安装nginx,并配置index.html,引用变量
配置hosts指向目标linux组 创建任务 Install EPEL:CentOS使用yum安装epel-release 分别创建任务 Install Nginx CentOS、Install Nginx Ubuntu:CentOS使用yum安装nginx,Ubuntu使用apt安装包安装nginx 删除任务 Install Nginx CentOS、Install Nginx Ubuntu,新增任务 Install Nginx:改为使用package模块安装nginx,不需要区分CentOS和Ubuntu 创建任务 Restart Nginx:使用service模块重启目标linux系统的nginx服务器 创建处理程序 Check HTTP Service:使用uri模块测试HTTP服务 修改任务 Restart Nginx:添加notify指向Check HTTP Service处理程序 创建组变量 nginx_root_location:CentOS组的nginx_root_location=/usr/share/nginx/html、Ubuntu组的nginx_root_location=/var/www/html 创建任务 Template index.html-base.j2 to index.html on target: 使用template模块,将自定义的index.html-base.j2 Jinja2模板指向nginx的/index.html 修改ansible.cfg:echo ‘ansible_managed = Managed by Ansible - file: {file} - host: {host} - uid:{uid}’ >> ansible.cfg 更新任务 Template index.html-base.j2 to index.html on target: 改为Template index.html-ansible_managed.j2 to index.html on target,更新自定义的模板文件为index.html-ansible_managed.j2 更新剧本,引入变量文件vars/logos.yaml 更新任务 Template index.html-ansible_managed.j2 to index.html on target:改为 Template index.html-logos.j2 to index.html on target,更新自定义的模板文件为index.html-logos.j2 使用package模块安装unzip包,创建任务 Unarchive playbook stacker game,使用unarchive模块解压缩 更新任务 Template index.html-ansible_managed.j2 to index.html on target:改为 Template index.html-logos.j2 to index.html on target,更新自定义的模板文件为index.html-logos.j2 更新任务 Template index.html-logos.j2 to index.html on target,改为 Template index.html-easter_egg.j2 to index.html on target,更新自定义的模板文件为index.html-easter_egg.j2 最终,nginx_playbook.yaml内容为:
--- - hosts: linux vars_files: - vars/logos.yaml tasks: - name: Install EPEL yum: name: epel-release update_cache: yes state: latest when: ansible_distribution == 'CentOS' - name: Install Nginx package: name: nginx state: latest - name: Restart nginx service: name: nginx state: restarted notify: Check HTTP Service - name: Template index.html-easter_egg.j2 to index.html on target template: src: index.html-easter_egg.j2 dest: "{{ nginx_root_location }} /index.html" mode: 0644 - name: Install unzip package: name: unzip state: latest - name: Unarchive playbook stacker game unarchive: src: playbook_stacker.zip dest: "{{ nginx_root_location }} " mode: 0755 handlers: - name: Check HTTP Service uri: url: http://{{ ansible_default_ipv4.address }} status_code: 200 ...
详细资源可以参见Ansible课程代码
5. 深入Ansible Playbooks 剧本 Module模块,动态Inventory清单,Register,When和Loops循环的使用,异步、串行、并行的性能,任务委派,Ansible魔法变量,Ansible Blocks块,Ansible Vault信息保护
5.1 剧本常用模块 Ansible内置了数以千计的模块,涵盖了众多领域和技术方向,接下来将介绍一些剧本常用模块
set_fact 模块
作用:允许执行期间动态添加或改变facts变量
官方文档地址:https://docs.ansible.com/ansible/latest/collections/ansible/builtin/set_fact_module.html
示例:
--- - hosts: ubuntu3,centos3 tasks: - name: Set a fact set_fact: our_fact: Ansible Rocks! ansible_distribution: "{{ ansible_distribution | upper }} " - name: Set our installation variables for CentOS set_fact: webserver_application_port: 80 webserver_application_path: /usr/share/nginx/html webserver_application_user: root when: ansible_distribution == 'CENTOS' - name: Set our installation variables for Ubuntu set_fact: webserver_application_port: 8080 webserver_application_path: /var/www/html webserver_application_user: nginx when: ansible_distribution == 'UBUNTU' - name: Show our_fact debug: msg: "{{ our_fact }} " - name: Show ansible_distribution debug: msg: "{{ ansible_distribution }} " - name: Show pre-set distribution based facts debug: msg: "webserver_application_port: {{ webserver_application_port }} webserver_application_path: {{ webserver_application_path }} webserver_application_user: {{ webserver_application_user }} " ...
pause 模块
作用:允许暂停给定时间的剧本执行,或者暂停直到确认特定提示
官方文档地址:https://docs.ansible.com/ansible/latest/collections/ansible/builtin/pause_module.html
示例:
--- - hosts: ubuntu3,centos3 tasks: - name: Pause our playbook for 10 seconds pause: seconds: 10 - name: Prompt user to verify before continue pause: prompt: Please check that the webserver is running, press enter to continue - name: Wait for the webserver to be running on port 80 wait_for: port: 80 ...
wait_for 模块
作用:允许等待指定的时间后、等待指定的端口可用时、等待指定模块启动并准备好时、等待正则匹配文件中字符串存在时,继续执行剧本
官方文档地址:https://docs.ansible.com/ansible/latest/collections/ansible/builtin/wait_for_module.html
示例:
--- - hosts: ubuntu3,centos3 tasks: - name: Wait for the webserver to be running on port 80 wait_for: port: 80 ...
assemble 模块
作用:允许将文件的集合组装成一个文件,可将获取本地或被管理主机的文件目录,并将他们连接在一起生成目标文件
官方文档地址:https://docs.ansible.com/ansible/latest/collections/ansible/builtin/assemble_module.html
示例:
--- - hosts: ubuntu-c tasks: - name: Assemble conf.d to sshd_config assemble: src: conf.d dest: sshd_config ...
add_host 模块
作用:允许动态添加目标主机到正在执行的剧本中,已备后续使用
官方文档地址:https://docs.ansible.com/ansible/latest/collections/ansible/builtin/add_host_module.html
示例:
--- - hosts: ubuntu-c tasks: - name: Add centos1 to adhoc_group add_host: name: centos1 groups: adhoc_group1, adhoc_group2 - hosts: adhoc_group1 tasks: - name: Ping all in adhoc_group ping: ...
group_by 模块
作用:允许使用facts变量创建临时组,以便稍后在剧本中使用
官方文档地址:https://docs.ansible.com/ansible/latest/collections/ansible/builtin/group_by_module.html
示例:
--- - host: all tasks: - name: Create group based on ansible_distribution group_by: key: "custom_{{ ansible_distribution | lower }} " - hosts: custom_centos tasks: - name: Ping all in custom_centos ping: ...
fetch 模块
作用:允许从远程计算机获取文件并将它们存储在本地文件树中,按主机名组织
官方文档地址:https://docs.ansible.com/ansible/latest/collections/ansible/builtin/fetch_module.html
示例:
--- - hosts: centos tasks: - name: Fetch /etc/redhat-release fetch: src: /etc/redhat-release dest: /tmp/redhat-release ...
5.2 动态清单 目前,我们都是在ansible.cfg文件中配置清单hosts文件,然后在hosts文件内或host_vars、group_vars文件夹内指定清单变量,在命令行工具中也可以使用-i
选项指定或覆盖之前配置的清单文件,如果指定的清单文件为可执行文件,则Ansible将执行此文件并将结果作为清单。
Ansible可以使用可执行文件作为动态清单,并使用文件执行结果作为清单
动态清单需要满足的条件:
必须是可执行文件,能够从 命令行执行,语言不限 接受 --list
、--host
命令行选项 使用--list
选项,返回JSON编码字典,包含清单内容 使用--host
选项,返回基本的JSON编码字典,包含主机内容 示例:
ansible.cfg
[defaults] host_key_checking = False
inventory.py
''' Dynamic inventory for Ansible in Python ''' from __future__ import print_functionimport argparseimport loggingtry : import json except ImportError: import simplejson as json class Inventory (object ): def __init__ (self, include_hostvars_in_list ): self.include_hostvars_in_list = include_hostvars_in_list parser = argparse.ArgumentParser() parser.add_argument('--list' , action='store_true' , help ='list inventory' ) parser.add_argument('--host' , action='store' , help ='show HOST variables' ) self.args = parser.parse_args() if not (self.args.list or self.args.host): parser.print_usage() raise SystemExit self.define_inventory() if self.args.list : self.print_json(self.list ()) elif self.args.host: self.print_json(self.host()) def define_inventory (self ): self.groups = { "centos" : { "hosts" : ["centos1" , "centos2" , "centos3" ], "vars" : { "ansible_user" : 'root' } }, "control" : { "hosts" : ["ubuntu-c" ], }, "ubuntu" : { "hosts" : ["ubuntu1" , "ubuntu2" , "ubuntu3" ], "vars" : { "ansible_become" : True , "ansible_become_pass" : 'password' } }, "linux" : { "children" : ["centos" , "ubuntu" ], }} self.hostvars = { 'centos1' : { 'ansible_port' : 2222 }, 'ubuntu-c' : { 'ansible_connection' : 'local' } } def print_json (self, content ): print (json.dumps(content, indent=4 , sort_keys=True )) def list (self ): self.logger.info('list executed' ) if self.include_hostvars_in_list: merged = self.groups merged['_meta' ] = {} merged['_meta' ]['hostvars' ] = self.hostvars return merged else : return self.groups def host (self ): self.logger.info('host executed for {}' .format (self.args.host)) if self.args.host in self.hostvars: return self.hostvars[self.args.host] else : return {} def configure_logger (self ): self.logger = logging.getLogger('ansible_dynamic_inventory' ) self.hdlr = logging.FileHandler('/var/tmp/ansible_dynamic_inventory.log' ) self.formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s' ) self.hdlr.setFormatter(self.formatter) self.logger.addHandler(self.hdlr) self.logger.setLevel(logging.DEBUG) Inventory(include_hostvars_in_list=False )
测试:
ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Dynamic Inventories/01$ ansible all -i inventory.py --list-hosts hosts (7): ubuntu-c centos1 centos2 centos3 ubuntu1 ubuntu2 ubuntu3 ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Dynamic Inventories/01$ ansible all -i inventory.py -m ping -o ubuntu-c | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"} ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
将脚本inventory.py中的最后一行改为Inventory(include_hostvars_in_list=True),对比发现,使用_meta可提高性能表现
5.3 Register和When Register
使用Register指令可以存储输出为一个变量,后续可以直接使用:
--- - hosts: linux tasks: - name: Exploring register command: hostname -c register: hostname_output - name: Show hostname_output debug: var: hostname_output.stdout ...
register与when结合使用可以针对命令的执行结果进行而额外的处理,从而覆盖多种情况,如:
--- - hosts: linux tasks: - name: Exploring register command: hostname -s when: - ansible_distribution == "CentOS" - ansible_distribution_major_version | int >= 8 register: command_register - name: Install patch when changed yum: name: patch state: present when: command_register is changed - name: Install patch when skipped apt: name: patch state: present when: command_register is skipped ...
5.4 循环的使用 用好循环能够使剧本的编写更加简洁美观,提高编写效率。Ansible的剧本可以使用多种方式的循环,下面简单了解下:
with_items循环
遍历列表
--- - hosts: linux tasks: - name: Configure a MOTD (message of the day) copy: content: "Welcome to {{ item }} Linux - Ansible Rocks!\n" dest: /ect/motd notify: MOTD changed with_items: [ 'CentOS' , 'Ubuntu' ] when: ansible_distribution == item - name: Creating user user: name: "{{ item }} " with_items: - jane - michael - tom - danel - name: Removing user user: name: "{{ item }} " state: absent with_items: - jane - michael - tom - danel handlers: - name: MOTD changed debug: msg: The MOTD was changed ...
with_dict循环
遍历字典
--- - hosts: linux tasks: - name: Creating user user: name: "{{ item.key }} " comment: "{{ item.value.full_name }} " with_dict: jane: full_name: Jane Smith michael: full_name: Michael Smith tom: full_name: Tom Smith danel: full_name: Danel Smith - name: Removing user user: name: "{{ item.key }} " comment: "{{ item.value.full_name }} " state: absent with_dict: jane: full_name: Jane Smith michael: full_name: Michael Smith tom: full_name: Tom Smith danel: full_name: Danel Smith ...
with_subelements循环
组合子元素后,然后遍历
--- - hosts: linux tasks: - name: Creating user user: name: "{{ item.1 }} " comment: "{{ item.1 | title }} {{ item.0.surname }} " password: "{{ lookup('password', 'dev/null length=15 chars=ascii_letters,digits,hexdigits,punctuation') | password_hash('sha512') }} " with_subelements: - family: surname: Smith members: - jane - michael - tom - danel - members - name: Creating user user: name: "{{ item.1 }} " comment: "{{ item.1 | title }} {{ item.0.surname }} " password: "{{ lookup('password', 'dev/null length=15 chars=ascii_letters,digits,hexdigits,punctuation') | password_hash('sha512') }} " with_subelements: - - surname: Smith members: - jane - michael - tom - danel - surname: James members: - Kangkang - Lihua - surname: Angne members: - Richu - members ...
with_nested循环
排列组合后,遍历
--- - hosts: linux tasks: - name: Creating user directories file: dest: "/home/{{ item.0 }} /{{ item.1 }} " owner: "{{ item.0 }} " group: "{{ item.0 }} " state: directory with_nested: - [ jane , michael , tom , danel ] - [ photos , movies , documents ] ...
with_together循环
一一对应后,遍历
--- - hosts: linux tasks: - name: Creating user directories file: dest: "/home/{{ item.0 }} /{{ item.1 }} " owner: "{{ item.0 }} " group: "{{ item.0 }} " state: directory with_together: - [ jane , michael , tom , danel ] - [ photos , movies , documents , music ] ...
with_file 循环
遍历读取文件内容
--- - hosts: linux tasks: - name: Create authorized key authorized_key: user: jane key: "{{ item }} " with_file: - /home/ansible/.ssh/id_rsa.pub - custom_key.pub ...
with_sequence循环
遍历序列
--- - hosts: linux tasks: - name: Create sequence directories file: dest: "/home/jane/sequence_{{ item }} " state: directory with_sequence: start=0 end=100 stride=10 - name: Create sequence directories file: dest: "{{ item }} " state: directory with_sequence: start=110 end=120 stride=2 format=/home/jane/hex_sequence_%d - name: Create hex sequence directories file: dest: "{{ item }} " state: directory with_sequence: start=0 end=16 stride=1 format=/home/jane/hex_sequence_%x - name: Create hex sequence directories file: dest: "{{ item }} " state: directory with_sequence: count=5 format=/home/jane/count_sequence_%x ...
with_random_choice循环
遍历选项,随机选择其一
--- - hosts: linux tasks: - name: Create random directory file: dest: "/home/jane/{{ item }} " state: directory with_random_choice: - "google" - "apple" - "microsoft" - "tencent" ...
util循环
直到达到条件,循环停止
--- - hosts: linux tasks: - name: Run a script until we hit 10 script: random.sh register: result retries: 100 util: result.stdout.find("10") != -1 delay: 1 ...
# random.sh # !/bin/bash echo $((1 + RANDOM % 10))
5.5 异步、串行和并行 剧本执行也需要关注其执行的性能和速度
先看一个执行效率低下的剧本:
--- - hosts: linux gather_facts: False tasks: - name: Task 1 command: /bin/sleep 5 - name: Task 2 command: /bin/sleep 5 - name: Task 3 command: /bin/sleep 5 - name: Task 4 command: /bin/sleep 5 - name: Task 5 command: /bin/sleep 5 - name: Task 6 command: /bin/sleep 5 ...
ansible的剧本执行默认使用线性策略:执行该任务的所有主机都执行完毕后,才开始进入下一个任务的执行。如果执行该任务的某一个主机因为性能或别的原因,需要很长时间才能执行完该任务,其他所有主机都需要等待。
ansible的剧本支持异步执行策略,有益于需要长执行时间的任务。
异步指的是程序或任务可以并发执行,当前任务不必等待前一个任务的完成。在异步方式下,任务可以提交给其他线程、进程或服务进行处理,而当前任务可以继续执行其他操作。
应用场景:异步通常用于需要提高系统的并发性和响应性能的情况,比如处理大量的并发请求或执行耗时操作。
修改上面剧本为异步剧本:
--- - hosts: linux gather_facts: False tasks: - name: Task 1 command: /bin/sleep 5 async: 10 poll: 0 register: result1 - name: Task 2 command: /bin/sleep 5 async: 10 poll: 0 register: result2 - name: Task 3 command: /bin/sleep 5 async: 10 poll: 0 register: result3 - name: Task 4 command: /bin/sleep 30 async: 60 poll: 0 register: result4 - name: Task 5 command: /bin/sleep 5 async: 10 poll: 0 register: result5 - name: Task 6 command: /bin/sleep 5 async: 10 poll: 0 register: result6 - name: Capture Job IDs set_fact: jobids: > {% if item.ansible_job_id is defined -%} {{ jobids + [item.ansible_job_id] }} {% else -%} {{ jobids }} {% endif %} with_items: "{{ [ result1, result2, result3, result4, result5, result6 ] }} " - name: Show Job IDs debug: var: jobids - name: 'Wait for Job IDs' aysnc_status: jid: "{{ item }} " with_items: "{{ jobids }} " register: jobs_result util: jobs_result.finished retries: 30 ...
设置async参数为10,代表等待至少10秒,设置poll参数为0,代表每秒轮询状态,不等待上个任务所有主机都执行完毕就开始下一个任务,但是最终会等待所有任务执行完成。
上面的方法需要用到很多剧本的知识,还有其他简单的方法设置异步剧本,如执行echo forks=6 >> ansible.cfg
,不修改原剧本的情况下就能实现执行的异步。
ansible的剧本支持串行批量执行策略。
串行是一种任务执行方式,指的是任务按照顺序依次执行,每个任务在前一个任务完成后才能开始执行。在串行执行中,任务之间没有并发或并行的特性。
应用场景:串行通常用于必须按照严格的顺序执行任务的情况,比如单线程的程序或依赖关系严格的任务流。
修改原剧本为串行批量执行剧本:
--- - hosts: linux gather_facts: False serial: 2 tasks: - name: Task 1 command: /bin/sleep 5 - name: Task 2 command: /bin/sleep 5 - name: Task 3 command: /bin/sleep 5 - name: Task 4 command: /bin/sleep 5 - name: Task 5 command: /bin/sleep 5 - name: Task 6 command: /bin/sleep 5 ...
serial设置分批次执行,2个执行主机为1批,比如说,centos1和centos2为第1批次,执行任务1,然后执行任务2,。。。,直到执行完成任务6。然后centos3和ubuntu1为第2个批次,执行任务1,然后执行任务2,。。。,直到执行完成任务6。最后ubuntu2和ubuntu3为第3个批次,执行任务1,然后执行任务2,。。。,直到执行完成任务6。
serial也可以指定为数字列表或百分比列表,即每个批次的执行主机数可以不一样。
ansible的剧本支持自由随机执行策略,适合没有执行顺序和主机要求的任务。
修改原剧本为自由随机执行剧本:
--- - hosts: linux gather_facts: False strategy: free tasks: - name: Task 1 command: /bin/sleep {{ 10 | random }} - name: Task 2 command: /bin/sleep {{ 10 | random }} - name: Task 3 command: /bin/sleep {{ 10 | random }} - name: Task 4 command: /bin/sleep {{ 10 | random }} - name: Task 5 command: /bin/sleep {{ 10 | random }} - name: Task 6 command: /bin/sleep {{ 10 | random }} ...
任务执行的顺序和每次任务执行的主机数目都是随机的。
5.6 任务委派 委派特定的任务在特定的目标上执行,因为存在在管理主机或其他被管理主机运行特定的命令或任务的需求。
如在主机ubuntu-c上设置ubuntu3的tcpwrappers规则,约束SSH连接只有来自ubuntu-c、centos1、ubuntu1主机才能成功。
--- - hosts: ubuntu-c gather_facts: False tasks: - name: Generate an OpenSSH keypair for ubuntu3 openssh_keypair: path: ~/.ssh/ubuntu3_id_rsa - hosts: linux gather_facts: False tasks: - name: Copy ubuntu3 OpenSSH keypair with permissions copy: owner: root src: "{{ item.0 }} " dest: "{{ item.0 }} " mode: "{{ item.1 }} " with_together: - [ ~/.ssh/ubuntu3_id_rsa , ~/.ssh/ubuntu3_id_rsa.pub ] - [ "0600" , "0644" ] - hosts: ubuntu3 gather_facts: False tasks: - name: Add public key to the ubuntu3 authorized_keys file authorized_key: user: root state: present key: "{{ lookup('file', '~/.ssh/ubuntu3_id_rsa.pub') }} " - hosts: all gather_facts: False tasks: - name: Check that ssh can connect to ubuntu3 using the ssh tool command: ssh -i ~/.ssh/ubuntu3_id_rsa -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@ubuntu3 date changed_when: False ignore_errors: True - hosts: ubuntu-c, centos1, ubuntu1 serial: 1 tasks: - name: Add host to /etc/hosts.allow for sshd lineinfile: path: /etc/hosts.allow line: "sshd: {{ ansible_hostname }} .diveinto.io" create: True delegate_to: ubuntu3 - hosts: all gather_facts: False tasks: - name: Check that ssh can connect to ubuntu3 using the ssh tool command: ssh -i ~/.ssh/ubuntu3_id_rsa -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@ubuntu3 date changed_when: False ignore_errors: True - hosts: ubuntu3 gather_facts: False tasks: - name: Drop SSH connectivity from everywhere else lineinfile: path: /etc/hosts.deny line: "sshd: ALL" create: True - hosts: all gather_facts: False tasks: - name: Check that ssh can connect to ubuntu3 using the ssh tool command: ssh -i ~/.ssh/ubuntu3_id_rsa -o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null root@ubuntu3 date changed_when: False ignore_errors: True - hosts: ubuntu-c, centos1, ubuntu1 serial: 1 tasks: - name: Remove specific host entries in /etc/hosts.allow for sshd lineinfile: path: /etc/hosts.allow line: "sshd: {{ ansible_hostname }} .diveinto.io" state: absent delegate_to: ubuntu3 - hosts: ubuntu3 gather_facts: False tasks: - name: Allow SSH connectivity from everywhere lineinfile: path: /etc/hosts.deny line: "sshd: ALL" state: absent ...
5.7 魔法变量 Ansible默认会提供一些内置的变量以实现一些特定的功能,这些变量不能由用户直接设置,我们称之为魔法变量,如:
hostvars inventory_hostname inventory_hostname_short groups group_names 参考文档:https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html
示例:
--- - hosts: all tasks: - name: Using template, create a remote file that contains all variables available to the play template: src: templates/dump_variables dest: /tmp/ansible_variables - name: Fetch the templated file with all variables, back to the control host fetch: src: /tmp/ansible_variables dest: "captured_variables/{{ ansible_hostname }} " flat: yes - name: Clean up left over files file: name: /tmp/ansible_variables state: absent ...
其中,templates/dump_variables的内容为:
PLAYBOOK VARS (Ansible vars): {{ vars | to_nice_yaml }}
5.8 块的使用 块可以将任务进行分组,并且可以块级别上应用任务变量,同时支持在块内进行异常处理
常用语法:
- block: 定义块
rescue: 当出现异常时,执行的语句
always: 无论结果如何都要执行的语句
示例如下:
--- - hosts: linux tasks: - name: A block of modules being executed block: - name: Example 1 CentOS only debug: msg: Example 1 CentOS only when: ansible_distribution == 'CentOS' - name: Example 2 Ubuntu only debug: msg: Example 2 Ubuntu only when: ansible_distribution == 'Ubuntu' - name: Example 3 with items debug: msg: "Example 3 with items - {{ item }} " with_items: ['x' , 'y' , 'z' ] - name: Install patch and python-dns block: - name: Install patch package: name: patch - name: Install python-dnspython package: name: python-dnspython rescue: - name: Rollback python package: name: patch state: absent - name: Rollback python-dnspython package: name: python-dnspython state: absent always: - debug: msg: This always runs, regardless ...
5.9 Vault信息保护 Ansible Vault是一项安全功能,用于加密或保护剧本或文件中的敏感信息,而不是明文保存
加密和解密变量
# 加密变量 ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/01$ ansible-vault encrypt_string --ask-vault-pass --name 'ansible_become_pass' 'password' New Vault password: Confirm New Vault password: Encryption successful ansible_become_pass: !vault | $ANSIBLE_VAULT;1.1;AES256 34396561636439353966346563616432643335646135656133313163613862383439656565363334 3263326331356230396662656636323365663830346461350a396436373862383237643739643134 32303234386534323634313635303163346466356361656238356530393734306665383737656264 6163376237306532630a393531323666626262363538616566626136356462353430336361653864 3239
然后修改group_vars/ubuntu为:
ansible_become: true ansible_become_pass: !vault | $ANSIBLE_VAULT 34396561636439353966346563616432643335646135656133313163613862383439656565363334 3263326331356230396662656636323365663830346461350a396436373862383237643739643134 32303234386534323634313635303163346466356361656238356530393734306665383737656264 6163376237306532630a393531323666626262363538616566626136356462353430336361653864 3239
然后执行下面命令:
# 解密变量 ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/01$ ansible --ask-vault-pass ubuntu -m ping -o Vault password: ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"} ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
加密和解密文件
# 加密文件 ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-vault encrypt external_vault_vars.yaml New Vault password: Confirm New Vault password: Encryption successful
剧本内容:
--- - hosts: linux vars_files: - external_vault_vars.yaml tasks: - name: Show external_vault_var debug: var: external_vault_var ...
执行:
ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-playbook --ask-vault-pass vault_playbook.yaml Vault password:
解密:
ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-vault decrypt external_vault_vars.yaml Vault password: Decryption successful
重新加密数据
# 加密文件 ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-vault encrypt external_vault_vars.yaml New Vault password: Confirm New vault password: Encryption successful # 重新加密文件 ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-vault rekey external_vault_vars.yaml New Vault password: Confirm New Vault password: Rekey successful # 查看加密文件 ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-vault view external_vault_vars.yaml Vault password: external_vault_var: Example External Vault Var # 查看加密文件(使用密码文件) ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-vault view --vault-password-file password_file external_vault_vars.yaml external_vault_var: Example External Vault Var # 查看加密文件(使用密码文件) ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-vault view --vault-id @password_file external_vault_vars.yaml external_vault_var: Example External Vault Var # 加密文件至命名valut变量 —— vars ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-vault encrypt --vault-id vars@prompt external_vault_vars.yaml New vault passowrd (vars): Confirm New vault password (vars): Encryption successful # 加密变量至命名valut变量 —— ssh ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-vault encrypt_string --vault-id ssh@prompt --name 'ansible_become_pass' 'password' New vault password (ssh): Confirm new vault password (ssh): Encryption successful ansible_become_pass: !vault | $ANSIBLE_VAULT;1.2;AES256;ssh 30373934396234613766353262633936373238643366326131653735393237663830326362623432 6564663637656537366163323763316139616238633433340a633436323664643635383465383064 35633963306665626237306566376666383130396333326366663661653666663535316638303839 6131396362313266300a386331336665376562663631316564306138333534383131643439663364 6432
使用加密的变量和文件
ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-vault encrypt_string --vault-id ssh@prompt --name 'ansible_become_pass' 'password' New vault password (ssh): Confirm new vault password (ssh): Encryption successful ansible_become_pass: !vault | $ANSIBLE_VAULT;1.2;AES256;ssh 30373934396234613766353262633936373238643366326131653735393237663830326362623432 6564663637656537366163323763316139616238633433340a633436323664643635383465383064 35633963306665626237306566376666383130396333326366663661653666663535316638303839 6131396362313266300a386331336665376562663631316564306138333534383131643439663364 6432ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ^C ansible@ubuntu-c:~/diveintoansible/Ansible Playbooks, Deep Dive/Vault/02$ ansible-playbook --vault-id vars@prompt --vault-id ssh@prompt vault_playbook.yaml Vault password (vars): Vault password (ssh): PLAY [linux] ******************************************************************************************* TASK [Gathering Facts] ********************************************************************************* ok: [centos2] ok: [centos1] ok: [centos3] ok: [ubuntu1] ok: [ubuntu2] ok: [ubuntu3] TASK [Show external_vault_var] ************************************************************************* ok: [centos1] => { "external_vault_var": "Example External Vault Var" } ok: [centos2] => { "external_vault_var": "Example External Vault Var" } ok: [centos3] => { "external_vault_var": "Example External Vault Var" } ok: [ubuntu1] => { "external_vault_var": "Example External Vault Var" } ok: [ubuntu2] => { "external_vault_var": "Example External Vault Var" } ok: [ubuntu3] => { "external_vault_var": "Example External Vault Var" } PLAY RECAP ********************************************************************************************* centos1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos3 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu3 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
还可以加密剧本到命名vault变量,类似上述操作,在此不再赘述。
6. 构建Ansible Playbooks 使用includes和imports、Tags标签和Roles角色
6.1 使用includes和imports 先准备tasks文件,如play1_task2.yaml:
--- - name: Play 1 - Task 2 debug: msg: Play 1 - Task 2 ...
include_tasks指令
动态导入
使用include_tasks指令在下面的剧本文件中将play1_task2.yaml文件导入:
--- - hosts: all tasks: - name: Play 1 - Task 1 debug: msg: Play 1 - Task 1 - include_tasks: play1_task2.yaml ...
测试:
ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Include and Import/01$ ansible-playbook include_tasks_playbook.yaml PLAY [all] *********************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************** ok: [ubuntu-c] ok: [centos1] ok: [centos2] ok: [centos3] ok: [ubuntu1] ok: [ubuntu2] ok: [ubuntu3] TASK [Play 1 - Task 1] *********************************************************************************************************** ok: [ubuntu-c] => { "msg": "Play 1 - Task 1" } ok: [centos1] => { "msg": "Play 1 - Task 1" } ok: [centos2] => { "msg": "Play 1 - Task 1" } ok: [centos3] => { "msg": "Play 1 - Task 1" } ok: [ubuntu1] => { "msg": "Play 1 - Task 1" } ok: [ubuntu2] => { "msg": "Play 1 - Task 1" } ok: [ubuntu3] => { "msg": "Play 1 - Task 1" } TASK [include_tasks] ************************************************************************************************************* included: /home/ansible/diveintoansible/Structuring Ansible Playbooks/Using Include and Import/01/play1_task2.yaml for ubuntu-c, centos1, centos2, centos3, ubuntu1, ubuntu2, ubuntu3 TASK [Play 1 - Task 2] *********************************************************************************************************** ok: [ubuntu-c] => { "msg": "Play 1 - Task 2" } ok: [centos1] => { "msg": "Play 1 - Task 2" } ok: [centos2] => { "msg": "Play 1 - Task 2" } ok: [centos3] => { "msg": "Play 1 - Task 2" } ok: [ubuntu1] => { "msg": "Play 1 - Task 2" } ok: [ubuntu2] => { "msg": "Play 1 - Task 2" } ok: [ubuntu3] => { "msg": "Play 1 - Task 2" } PLAY RECAP *********************************************************************************************************************** centos1 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos2 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos3 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu-c : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu1 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu2 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu3 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
import_tasks指令
静态导入
使用import_tasks指令在下面的剧本文件中将play1_task2.yaml文件导入:
--- - hosts: all tasks: - name: Play 1 - Task 1 debug: msg: Play 1 - Task 1 - import_tasks: play1_task2.yaml ...
测试:
ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Include and Import/02$ ansible-playbook import_tasks_playbook.yaml PLAY [all] *********************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************** ok: [ubuntu-c] ok: [centos1] ok: [centos2] ok: [centos3] ok: [ubuntu1] ok: [ubuntu2] ok: [ubuntu3] TASK [Play 1 - Task 1] *********************************************************************************************************** ok: [ubuntu-c] => { "msg": "Play 1 - Task 1" } ok: [centos1] => { "msg": "Play 1 - Task 1" } ok: [centos2] => { "msg": "Play 1 - Task 1" } ok: [centos3] => { "msg": "Play 1 - Task 1" } ok: [ubuntu1] => { "msg": "Play 1 - Task 1" } ok: [ubuntu2] => { "msg": "Play 1 - Task 1" } ok: [ubuntu3] => { "msg": "Play 1 - Task 1" } TASK [Play 1 - Task 2] *********************************************************************************************************** ok: [ubuntu-c] => { "msg": "Play 1 - Task 2" } ok: [centos1] => { "msg": "Play 1 - Task 2" } ok: [centos2] => { "msg": "Play 1 - Task 2" } ok: [centos3] => { "msg": "Play 1 - Task 2" } ok: [ubuntu1] => { "msg": "Play 1 - Task 2" } ok: [ubuntu2] => { "msg": "Play 1 - Task 2" } ok: [ubuntu3] => { "msg": "Play 1 - Task 2" } PLAY RECAP *********************************************************************************************************************** centos1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 centos3 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu-c : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ubuntu3 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
静态导入vs动态导入
静态导入 动态导入 在解析剧本时处理 在剧本执行时处理 每个任务将独立针对when条件执行 when 语句执行一次,如果满足条件,则执行所有任务 import指令 include指令
为了对比,分别创建import_tasks.yaml和include_tasks.yaml,其中:
import_task.yaml
--- - set_fact: import_tasks_var: foo - name: 2nd Task debug: msg: 2nd Task - name: 3rd Task debug: msg: 3rd Task ...
include_tasks.yaml
--- - set_fact: include_tasks_var: foo - name: 2nd Task debug: msg: 2nd Task - name: 3rd Task debug: msg: 3rd Task ...
比较import指令和include指令的剧本include_import_tasks_playbook.yaml:
--- - hosts: centos1 tasks: - debug: msg: ===================== Testing include_tasks ===================== - include_tasks: include_tasks.yaml when: include_tasks_var is not defined - debug: msg: ===================== Testing import_tasks ====================== - import_tasks: import_tasks.yaml when: import_tasks_var is not defined ...
测试:
ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Include and Import/03$ ansible-playbook include_import_tasks_playbook.yaml PLAY [centos1] ******************************************************************************************************************* TASK [Gathering Facts] *********************************************************************************************************** ok: [centos1] TASK [debug] ********************************************************************************************************************* ok: [centos1] => { "msg": "===================== Testing include_tasks =====================" } TASK [include_tasks] ************************************************************************************************************* included: /home/ansible/diveintoansible/Structuring Ansible Playbooks/Using Include and Import/03/include_tasks.yaml for centos1 TASK [set_fact] ****************************************************************************************************************** ok: [centos1] TASK [2nd Task] ****************************************************************************************************************** ok: [centos1] => { "msg": "2nd Task" } TASK [3rd Task] ****************************************************************************************************************** ok: [centos1] => { "msg": "3rd Task" } TASK [debug] ********************************************************************************************************************* ok: [centos1] => { "msg": "===================== Testing import_tasks ======================" } TASK [set_fact] ****************************************************************************************************************** ok: [centos1] TASK [2nd Task] ****************************************************************************************************************** skipping: [centos1] TASK [3rd Task] ****************************************************************************************************************** skipping: [centos1] PLAY RECAP *********************************************************************************************************************** centos1 : ok=8 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
import_playbook指令
先准备剧本文件,如imported_playbook.yaml:
--- - hosts: centos1 tasks: - set_fact: import_playbook_var: true - debug: msg: Playbook executed ...
使用import_playbook指令在下面的剧本文件中将imported_playbook.yaml文件静态导入
--- - import_playbook: imported_playbook.yaml when: import_playbook_var is not defined ...
每个任务都会进行一次when条件的判断。
标签在处理大型剧本或剧本中包含其他剧本时非常有用,你可以运行部分配置而无需运行整个剧本
如:
--- - hosts: linux - hosts: linux tags: - webapp vars_files: - vars/logos.yaml tasks: - name: Install EPEL yum: name: epel-release update_cache: yes state: latest when: ansible_distribution == 'CentOS' tags: - install-epel - name: Install Nginx package: name: nginx state: latest tags: - install-nginx - name: Restart nginx service: name: nginx state: restarted notify: Check HTTP Service tags: - always - name: Template index.html-easter_egg.j2 to index.html on target template: src: index.html-easter_egg.j2 dest: "{{ nginx_root_location }} /index.html" mode: 0644 tags: - deploy-app - name: Install unzip package: name: unzip state: latest - name: Unarchive playbook stacker game unarchive: src: playbook_stacker.zip dest: "{{ nginx_root_location }} " mode: 0755 tags: - deploy-app handlers: - name: Check HTTP Service uri: url: http://{{ ansible_default_ipv4.address }} status_code: 200 ...
执行时添加--tags
可只运行部分任务,添加--skip-tags
可不执行部分命令,always 标签的任务总是会指向,可通过--skip-tags "always"
指令跳过
ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Tags/03$ ansible-playbook nginx_playbook.yaml --tags "install-epel" ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Tags/03$ ansible-playbook nginx_playbook.yaml --tags "install-nginx,restart-nginx" ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Tags/03$ ansible-playbook nginx_playbook.yaml --skip-tags "deploy-app"
除了给任务打tag外,也可以给剧本打tag,但是可能会影响facts变量的值,因为默认情况下facts变量指向default标签。
特殊标签 :
tagged: 只有打了tag的任务才会执行 untagged: 打了tag的任务不会执行 all:运行所有任务,默认ansible 使用 --tags all运行 如:
# 运行所有任务 ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Tags/04$ ansible-playbook nginx_playbook.yaml --tags "all" # 只运行打了tag的任务 ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Tags/04$ ansible-playbook nginx_playbook.yaml --tags "tagged" # 只运行没打tag的任务 ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Tags/04$ ansible-playbook nginx_playbook.yaml --tags "untagged"
除了任务、剧本可以打标签外,导入也可以打标签,如下:
--- - hosts: ubuntu3 tasks: - include_tasks: include_tasks.yaml tags: - include_tasks - import_tasks: import_tasks.yaml tags: - import_tasks - import_playbook: import_playbook.yaml tags: - import_playbook ...
6.3 使用roles roles(角色)用于层次性,结构化 地组织剧本,角色分别将变量、文件、任务、模块及处理器放置于单独的目录中,而在剧本中使用include指令导入。
使用角色的好处:
简单的角色结构示例:
roles/ \ansible所有的信息都放到此目录下面对应的目录中 └── example-role \角色名称 ├── default \为当前角色设定默认变量时使用此目录,应当包含一个main.yml文件; ├── files \存放有copy或script等模块调用的文件,或压缩安装包等 ├── handlers \此目录总应当包含一个main.yml文件,用于定义各角色用到的各handler ├── meta \应当包含一个main.yml,用于定义角色的特殊设定及其依赖关系 ├── tasks \至少包含一个名为main.yml的文件,定义了此角色的任务列表,可使用include指令 ├── templates \template模块会自动在此目录中寻找Jinja2模板文件 └── vars \应当包含一个main.yml文件,用于定义此角色用到的变量
将已有剧本改成角色结构
有一个批量部署nginx应用的剧本,./nginx_playbook.yaml 内容为:
--- - hosts: linux - hosts: linux tags: - webapp vars_files: - vars/logos.yaml tasks: - name: Install EPEL yum: name: epel-release update_cache: yes state: latest when: ansible_distribution == 'CentOS' tags: - install-epel - name: Install Nginx package: name: nginx state: latest tags: - install-nginx - name: Restart nginx service: name: nginx state: restarted notify: Check HTTP Service tags: - always - name: Template index.html-easter_egg.j2 to index.html on target template: src: templates/index.html-easter_egg.j2 dest: "{{ nginx_root_location }} /index.html" mode: 0644 tags: - deploy-app - name: Install unzip package: name: unzip state: latest - name: Unarchive playbook stacker game unarchive: src: playbook_stacker.zip dest: "{{ nginx_root_location }} " mode: 0755 tags: - deploy-app handlers: - name: Check HTTP Service uri: url: http://{{ ansible_default_ipv4.address }} status_code: 200 ...
另外,模板内容可参见Ansible课程代码
使用ansible-galaxy 指令创建roles
ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Roles/02$ ansible-galaxy init nginx - Role nginx was created successfully ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Roles/02$ find . . ./ansible.cfg ./files ./files/playbook_stacker.zip ./group_vars ./group_vars/centos ./group_vars/ubuntu ./hosts ./host_vars ./host_vars/centos1 ./host_vars/ubuntu-c ./nginx ./nginx/defaults ./nginx/defaults/main.yml ./nginx/files ./nginx/handlers ./nginx/handlers/main.yml ./nginx/meta ./nginx/meta/main.yml ./nginx/README.md ./nginx/tasks ./nginx/tasks/main.yml ./nginx/templates ./nginx/tests ./nginx/tests/inventory ./nginx/tests/test.yml ./nginx/vars ./nginx/vars/main.yml ./nginx_playbook.yaml ./templates ./templates/index.html-ansible_managed.j2 ./templates/index.html-base.j2 ./templates/index.html-easter_egg.j2 ./templates/index.html-logos.j2 ./templates/index.html.j2 ./vars ./vars/logos.yaml
将nginx_playbook.yaml 文件中handlers部分的内容写入nginx/handlers/main.yml ,如下:
--- - name: Check HTTP Service uri: url: http://{{ ansible_default_ipv4.address }} status_code: 200 ...
移动现有的模板进入nginx/templates 文件夹
ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Roles/02$ mv templates/* nginx/templates/ && rm -rf templates/
移动现有文件进入nginx/files 目录
ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Roles/02$ mv files/* nginx/files/ && rm -rf files/
复制现有变量内容到nginx/var/main.yml ,进入文件删除多余的破折号和点
ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Roles/02$ cat vars/logos.yaml >> nginx/vars/main.yml ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Roles/02$ rm -rf vars/
将nginx_playbook.yaml 文件中tasks部分的内容写入nginx/tasks/main.yml ,并修改template部分,如下:
--- - name: Install EPEL yum: name: epel-release update_cache: yes state: latest when: ansible_distribution == 'CentOS' tags: - install-epel - name: Install Nginx package: name: nginx state: latest tags: - install-nginx - name: Restart nginx service: name: nginx state: restarted notify: Check HTTP Service tags: - always - name: Template index.html-easter_egg.j2 to index.html on target template: src: index.html-easter_egg.j2 dest: "{{ nginx_root_location }} /index.html" mode: 0644 tags: - deploy-app - name: Install unzip package: name: unzip state: latest - name: Unarchive playbook stacker game unarchive: src: playbook_stacker.zip dest: "{{ nginx_root_location }} " mode: 0755 tags: - deploy-app ...
修改nginx_playbook.yaml 为:
--- - hosts: linux roles: - nginx ...
此时,文件结构变为:
ansible@ubuntu-c:~/diveintoansible/Structuring Ansible Playbooks/Using Roles/02$ find . . ./ansible.cfg ./group_vars ./group_vars/centos ./group_vars/ubuntu ./hosts ./host_vars ./host_vars/centos1 ./host_vars/ubuntu-c ./nginx ./nginx/.travis.yml ./nginx/defaults ./nginx/defaults/main.yml ./nginx/files ./nginx/files/playbook_stacker.zip ./nginx/handlers ./nginx/handlers/main.yml ./nginx/meta ./nginx/meta/main.yml ./nginx/README.md ./nginx/tasks ./nginx/tasks/main.yml ./nginx/templates ./nginx/templates/index.html-ansible_managed.j2 ./nginx/templates/index.html-base.j2 ./nginx/templates/index.html-easter_egg.j2 ./nginx/templates/index.html-logos.j2 ./nginx/templates/index.html.j2 ./nginx/tests ./nginx/tests/inventory ./nginx/tests/test.yml ./nginx/vars ./nginx/vars/main.yml ./nginx_playbook.yaml
经过测试,发现可以正常运行。
上面的nginx角色还可以继续把webapp部分拆分出来做为独立的角色。
角色也可以覆盖参数,如下:
--- - hosts: linux roles: - nginx - { role: webapp , target_dir: "{%- if ansible_distribution == 'CentOS' -%}/usr/share/nginx/html{%- elif ansible_distribution == 'Ubuntu' -%}/var/www/html{%- endif %}" } ...
角色依赖
可以在webapp/meta/main.yml 文件最后为webapp角色添加对nginx角色的依赖:
...... 省略大量内容 dependencies: - nginx
剧本内容变为:
--- - hosts: linux roles: - { role: webapp , target_dir: "{%- if ansible_distribution == 'CentOS' -%}/usr/share/nginx/html{%- elif ansible_distribution == 'Ubuntu' -%}/var/www/html{%- endif %}" } ...
7. 云服务、容器、Ansible AWS、Docker与Ansible
7.1 AWS与Ansible 使用Ansible在AWS中自动化部署实例
首先在AWS的EC2控制台中创建密钥对,然后去个人账号创建访问密钥,进入VPCs创建默认的VPC
然后在AWS的EC2控制台中创建实例,选定系统镜像
配置AWS模块
ansible@ubuntu-c:~/diveintoansible/Using Ansible with Cloud Services and Containers/AWS with Ansible/01$ export AWS_ACCESS_KEY_ID="your_accesskey" ansible@ubuntu-c:~/diveintoansible/Using Ansible with Cloud Services and Containers/AWS with Ansible/01$ export AWS_SECRET_ACCESS_KEY="your_accesskey_secret" ansible@ubuntu-c:~/diveintoansible/Using Ansible with Cloud Services and Containers/AWS with Ansible/01$ sudo pip install boto boto3
ansible.cfg 文件
[defaults] inventory =hostshost_key_checking =False forks =6
hosts 文件
[control] ubuntu-c [centos] centos[1:3] [ubuntu] ubuntu[1:3] [linux:children] centos ubuntu
ec2_playbook.yaml 文件
在 AWS 中创建用于 SSH 访问和 HTTP 的安全组 预置一组实例 将所有实例公共 IP 添加到主机组 --- - hosts: localhost connection: local gather_facts: false tasks: - name: Create a security group in AWS for SSH access and HTTP ec2_group: name: ansible description: Ansible Security Group region: us-east-1 rules: - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0 .0 .0 /0 - proto: tcp from_port: 22 to_port: 22 cidr_ip: 0.0 .0 .0 /0 - name: Provision a set of instances ec2: key_name: ansible group: ansible instance_type: t2.micro image: ami-096fda3c22c1c990a region: us-east-1 wait: true exact_count: 20 count_tag: Name: AnsibleNginxWebservers instance_tags: Name: Ansible register: ec2 ignore_errors: true - name: Add all instance public IPs to host group add_host: hostname: "{{ item.public_ip }} " groups: ansiblehosts with_items: "{{ ec2.instances }} " - name: Show group debug: var: groups.ansiblehosts ...
使用AWS动态清单
ansible@ubuntu-c:~/diveintoansible/Using Ansible with Cloud Services and Containers/AWS with Ansible/04$ mkdir inventory && cd inventory ansible@ubuntu-c:~/diveintoansible/Using Ansible with Cloud Services and Containers/AWS with Ansible/04$ wget https://raw.githubusercontent.com/ansible/ansible/stable-2.9/contrib/inventory/ec2.py ansible@ubuntu-c:~/diveintoansible/Using Ansible with Cloud Services and Containers/AWS with Ansible/04$ wget https://raw.githubusercontent.com/ansible/ansible/stable-2.9/contrib/inventory/ec2.ini ansible@ubuntu-c:~/diveintoansible/Using Ansible with Cloud Services and Containers/AWS with Ansible/04$ chmod u+x ec2.py
修改ec2.py文件,第1行的python变为python3,注释掉第172行内容,即# from ansible.module_utils import ec2 as ec2_utils
修改ec2.ini文件:cache_max_go=0
配置:
ansible@ubuntu-c:~/diveintoansible/Using Ansible with Cloud Services and Containers/AWS with Ansible/04$ export EC2_INI_PATH=inventory/ec2.ini
修改ansible.cfg为:
[defaults] inventory =inventory/ec2host_key_checking =False forks =20 ansible_managed =Managed by Ansible - file:{file} - host:{host} - uid:{uid}
创建~./ssh/ansible.pem,将密钥复制到此处,并且设置权限为600
创建group_vars/tag_Name_Ansible:
--- ansible_ssh_private_key_file:~./ssh/ansible.pem ansible_user: ec2-user ansible_become: true ...
修改剧本内容为:
--- - hosts: localhost connection: local gather_facts: false tasks: - name: Create a security group in AWS for SSH access and HTTP ec2_group: name: ansible description: Ansible Security Group region: us-east-1 rules: - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0 .0 .0 /0 - proto: tcp from_port: 22 to_port: 22 cidr_ip: 0.0 .0 .0 /0 - name: Provision a set of instances ec2: key_name: ansible group: ansible instance_type: t2.micro image: ami-096fda3c22c1c990a region: us-east-1 wait: true exact_count: 20 count_tag: Name: AnsibleNginxWebservers instance_tags: Name: Ansible register: ec2 ignore_errors: true - name: Refresh inventory to ensure new instances exist in inventory meta: refresh_inventory - hosts: tag_Name_Ansible roles: - { role: webapp , target_dir: /usr/share/nginx/html } ...
并将其改为角色结构,详细代码可查看提供的教程代码
测试:
ansible@ubuntu-c:~/diveintoansible/Using Ansible with Cloud Services and Containers/AWS with Ansible/04$ ansible tag_Name_Ansible -m ping -o
最终剧本为:
--- - hosts: localhost connection: local gather_facts: false tasks: - name: Create a security group in AWS for SSH access and HTTP ec2_group: name: ansible description: Ansible Security Group region: us-east-1 rules: - proto: tcp from_port: 80 to_port: 80 cidr_ip: 0.0 .0 .0 /0 - proto: tcp from_port: 22 to_port: 22 cidr_ip: 0.0 .0 .0 /0 - name: Provision a set of instances ec2: key_name: ansible group: ansible instance_type: t2.micro image: ami-096fda3c22c1c990a region: us-east-1 wait: true exact_count: 20 count_tag: Name: AnsibleNginxWebservers instance_tags: Name: Ansible register: ec2 ignore_errors: true - name: Refresh inventory to ensure new instances exist in inventory meta: refresh_inventory - hosts: tag_Name_Ansible roles: - { role: webapp , target_dir: /usr/share/nginx/html } - hosts: tag_Name_Ansible tasks: - debug: msg: "Check http://{{ ansible_host }} " - pause: prompt: "Verify service availability and continue to terminate" - name: Remove tagged EC2 instances from security group by setting an empty group ec2: state: running region: "{{ ec2_region }} " instance_ids: "{{ ec2_id }} " group_id: "" delegate_to: localhost - name: Terminate EC2 instances ec2: state: absent region: "{{ ec2_region }} " instance_ids: "{{ ec2_id }} " wait: true delegate_to: localhost - hosts: localhost connection: local gather_facts: false tasks: - name: Remove ansible security group ec2_group: name: ansible region: us-east-1 state: absent ...
7.2 Docker与Ansible 配置Docker实验室
install_docker.sh脚本:
install_docker.sh sudo apt update sudo apt install -y docker.io pip3 install docker
执行:
ansible@ubuntu-c:~/diveintoansible/Using Ansible with Cloud Services and Containers/Docker with Ansible/01$ bash -x install_docker.sh
设置环境变量envdocker
export DOCKER_HOST=tcp://docker:2375
使之生效
ansible@ubuntu-c:~/diveintoansible/Using Ansible with Cloud Services and Containers/Docker with Ansible/01$ source envdocker
编写剧本 docker_playbook.yaml:
--- - hosts: ubuntu-c tasks: - name: Pull images docker_host: tcp://docker:2375 name: "{{ item }} " source: pull with_items: - centos - ubuntu - redis - nginx - wernight/funbox - name: Create a customised index.html copy: dest: /shared/index.html mode: 0644 content: Customised page for nginxcustomised - name: Create a customised Dockerfile copy: dest: /shared/Dockerfile mode: 0644 content: FROM nginx COPY index.html /usr/share/nginx/html/index.html - name: Build a customised image docker_image: docker_host: tcp://docker:2375 name: nginxcustomised:latest source: build build: path: /shared pull: yes state: present force_source: yes - name: Create an nginxcustomised container docker_container: docker_host: tcp://docker:2375 name: containerwebserver image: nginxcustomised:latest ports: - 80 :80 container_default_behavior: no_defaults recreate: yes ...
其他例子:
--- - hosts: ubuntu-c tasks: - name: Pull python image docker_image: docker_host: tcp://docker:2375 name: python:3.8.5 source: pull - name: Create 3 python containers docker_container: docker_host: tcp://docker:2375 name: "python{{ item }} " image: python:3.8.5 container_default_behavior: no_defaults command: sleep infinity with_sequence: 1 -3 - hosts: containers gather_facts: False tasks: - name: Ping containers ping: ...
其中,containers的配置为:
[containers] python[1:3] ansible_connection =docker ansible_python_interpreter=/usr/bin/python3
终止并移除容器
--- - hosts: ubuntu-c tasks: - name: Remove old containers docker_container: docker_host: tcp://docker:2375 name: "{{ item }} " state: absent container_default_behavior: no_defaults with_items: - containerwebserver - python1 - python2 - python3 - name: Remove images docker_image: docker_host: tcp://docker:2375 name: "{{ item }} " state: absent with_items: - centos - ubuntu - redis - nginx - wernight/funbox - nginxcustomised - python:3.8.5 - name: Remove files file: path: "{{ item }} " state: absent with_items: - /shared/Dockerfile - /shared/index.html ...
8. 创建模块和插件 创建自己的模块和插件
8.1 创建模块 下载ansible源码
git clone https://github.com/ansible/ansible.git
使用开发工具Hacking去调试模块,如:
~/ansible/hacking/test_module -m ~/ansible/lib/ansible/modules/command.py -a hostname
生成模块测试成功和失败报告
脚本icmp.sh
#!/bin/bash source $1 >/dev/null 2>&1TARGET=${target:-127.0.0.1} ping -c 1 ${TARGET} >/dev/null 2>/dev/null if [ $? == 0 ];then echo "{\"changed\": true, \"rc\": 0}" else echo "{\"failed\": true, \"msg\": \"failed to ping\", \"rc\": 1}" fi
测试:
~ansible/hacking/test-module -m icmp.sh ~ansible/hacking/test-module -m icmp.sh -a 'target=centos1'
模块测试结果保存在 /home/ansible/.ansible_module_generated 中
模块输入的参数保存在**/home/ansible/.ansible_test_module_arguments** 中
两个文件中有数据的前提是使用了source $1>/dev/null 2>&1
捕获输入
创建一个简单的ping模块
在ansible工作目录下新建一个library文件夹,将icmp.sh放入library文件夹并去掉.sh文件后缀,就算创建了一个简单的ping模块
剧本内容为:
--- - hosts: linux tasks: - name: Test icmp module icmp: target: 127.0 .0 .1 。。。
如果想要将自定义模块发布到ansible源,需要遵循规范,可以参考:http://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html
官方给的模块模板内容如下:
from __future__ import (absolute_import, division, print_function)__metaclass__ = type DOCUMENTATION = r''' --- module: my_test short_description: This is my test module # If this is part of a collection, you need to use semantic versioning, # i.e. the version is of the form "2.5.0" and not "2.4". version_added: "1.0.0" description: This is my longer description explaining my test module. options: name: description: This is the message to send to the test module. required: true type: str new: description: - Control to demo if the result of this module is changed or not. - Parameter description can be a list as well. required: false type: bool # Specify this value according to your collection # in format of namespace.collection.doc_fragment_name extends_documentation_fragment: - my_namespace.my_collection.my_doc_fragment_name author: - Your Name (@yourGitHubHandle) ''' EXAMPLES = r''' # Pass in a message - name: Test with a message my_namespace.my_collection.my_test: name: hello world # pass in a message and have changed true - name: Test with a message and changed output my_namespace.my_collection.my_test: name: hello world new: true # fail the module - name: Test failure of the module my_namespace.my_collection.my_test: name: fail me ''' RETURN = r''' # These are examples of possible return values, and in general should use other names for return values. original_message: description: The original name param that was passed in. type: str returned: always sample: 'hello world' message: description: The output message that the test module generates. type: str returned: always sample: 'goodbye' ''' from ansible.module_utils.basic import AnsibleModuledef run_module (): module_args = dict ( name=dict (type ='str' , required=True ), new=dict (type ='bool' , required=False , default=False ) ) result = dict ( changed=False , original_message='' , message='' ) module = AnsibleModule( argument_spec=module_args, supports_check_mode=True ) if module.check_mode: module.exit_json(**result) result['original_message' ] = module.params['name' ] result['message' ] = 'goodbye' if module.params['new' ]: result['changed' ] = True if module.params['name' ] == 'fail me' : module.fail_json(msg='You requested this to fail' , **result) module.exit_json(**result) def main (): run_module() if __name__ == '__main__' : main()
因此,将library文件夹下的icmp替换为icmp.py ,简单的ping模块可以改为:
ANSIBLE_METADATA = { 'metadata_version' : '1.1' , 'status' : ['preview' ], 'supported_by' : 'community' } DOCUMENTATION = ''' --- module: icmp short_description: simple module for icmp ping version_added: "2.10" description: - "simple module for icmp ping" options: target: description: - The target to ping required: true author: - James Spurin (@spurin) ''' EXAMPLES = ''' # Ping an IP - name: Ping an IP icmp: target: 127.0.0.1 # Ping a host - name: Ping a host icmp: target: centos1 ''' RETURN = ''' ''' from ansible.module_utils.basic import AnsibleModuledef run_module (): module_args = dict ( target=dict (type ='str' , required=True ) ) result = dict ( changed=False ) module = AnsibleModule( argument_spec=module_args, supports_check_mode=True ) if module.check_mode: return result ping_result = module.run_command('ping -c 1 {}' .format (module.params['target' ])) if module.params['target' ]: result['debug' ] = ping_result result['rc' ] = ping_result[0 ] if result['rc' ]: result['failed' ] = True module.fail_json(msg='failed to ping' , **result) else : result['changed' ] = True module.exit_json(**result) def main (): run_module() if __name__ == '__main__' : main()
执行下面指令可以查看模板使用说明:
ansible-doc -M library icmp
8.2 创建插件 ansible插件是增强ansible的核心功能的代码片段,ansible使用插件架构来实现丰富,灵活和可扩展的功能集。
Ansible提供了许多方便的插件,也轻松自定义的插件。
官方lookup插件的源码:
https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/lookup/items.py
官方vars插件的源码:
https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/vars/host_group_vars.py
开发插件的官方文档:
http://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html
现有插件:
Action插件 Cache插件 Callback插件 Connection插件 Filters插件 Lookup插件 Strategy插件 Shell插件 Test插件 Vars插件 inventory插件 自定义lookup插件实现排序遍历
mkdir lookup_plugins && cd lookup_plugins wget https://raw.githubusercontent.com/ansible/ansible/devel/lib/ansible/plugins/lookup/items.py mv items.py sorted_items.py
sorted_items.py内容为:
from __future__ import (absolute_import, division, print_function)__metaclass__ = type DOCUMENTATION = """ name: items author: Michael DeHaan version_added: historical short_description: list of items description: - this lookup returns a list of items given to it, if any of the top level items is also a list it will flatten it, but it will not recurse notes: - this is the standard lookup used for loops in most examples - check out the 'flattened' lookup for recursive flattening - if you do not want flattening nor any other transformation look at the 'list' lookup. options: _terms: description: list of items required: True """ EXAMPLES = """ - name: "loop through list" ansible.builtin.debug: msg: "An item: {{ item }}" with_items: - 1 - 2 - 3 - name: add several users ansible.builtin.user: name: "{{ item }}" groups: "wheel" state: present with_items: - testuser1 - testuser2 - name: "loop through list from a variable" ansible.builtin.debug: msg: "An item: {{ item }}" with_items: "{{ somelist }}" - name: more complex items to add several users ansible.builtin.user: name: "{{ item.name }}" uid: "{{ item.uid }}" groups: "{{ item.groups }}" state: present with_items: - { name: testuser1, uid: 1002, groups: "wheel, staff" } - { name: testuser2, uid: 1003, groups: staff } """ RETURN = """ _raw: description: - once flattened list type: list """ from ansible.plugins.lookup import LookupBaseclass LookupModule (LookupBase ): def run (self, terms, **kwargs ): return self._flatten(terms)
lookup插件引用了下面地址的类方法
https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/lookup/ init .py
其内容为:
from __future__ import (absolute_import, division, print_function)__metaclass__ = type from abc import abstractmethodfrom ansible.errors import AnsibleFileNotFoundfrom ansible.plugins import AnsiblePluginfrom ansible.utils.display import Displaydisplay = Display() __all__ = ['LookupBase' ] class LookupBase (AnsiblePlugin ): def __init__ (self, loader=None , templar=None , **kwargs ): super (LookupBase, self).__init__() self._loader = loader self._templar = templar self._display = display def get_basedir (self, variables ): if 'role_path' in variables: return variables['role_path' ] else : return self._loader.get_basedir() @staticmethod def _flatten (terms ): ret = [] for term in terms: if isinstance (term, (list , tuple )): ret.extend(term) else : ret.append(term) return ret @staticmethod def _combine (a, b ): results = [] for x in a: for y in b: results.append(LookupBase._flatten([x, y])) return results @staticmethod def _flatten_hash_to_list (terms ): ret = [] for key in terms: ret.append({'key' : key, 'value' : terms[key]}) return ret @abstractmethod def run (self, terms, variables=None , **kwargs ): """ When the playbook specifies a lookup, this method is run. The arguments to the lookup become the arguments to this method. One additional keyword argument named ``variables`` is added to the method call. It contains the variables available to ansible at the time the lookup is templated. For instance:: "{{ lookup('url', 'https://toshio.fedorapeople.org/one.txt', validate_certs=True) }}" would end up calling the lookup plugin named url's run method like this:: run(['https://toshio.fedorapeople.org/one.txt'], variables=available_variables, validate_certs=True) Lookup plugins can be used within playbooks for looping. When this happens, the first argument is a list containing the terms. Lookup plugins can also be called from within playbooks to return their values into a variable or parameter. If the user passes a string in this case, it is converted into a list. Errors encountered during execution should be returned by raising AnsibleError() with a message describing the error. Any strings returned by this method that could ever contain non-ascii must be converted into python's unicode type as the strings will be run through jinja2 which has this requirement. You can use:: from ansible.module_utils.common.text.converters import to_text result_string = to_text(result_string) """ pass def find_file_in_search_path (self, myvars, subdir, needle, ignore_missing=False ): ''' Return a file (needle) in the task's expected search path. ''' if 'ansible_search_path' in myvars: paths = myvars['ansible_search_path' ] else : paths = [self.get_basedir(myvars)] result = None try : result = self._loader.path_dwim_relative_stack(paths, subdir, needle) except AnsibleFileNotFound: if not ignore_missing: self._display.warning("Unable to find '%s' in expected paths (use -vvvvv to see paths)" % needle) return result def _deprecate_inline_kv (self ): pass
修改sorted_items.py最后一行返回有序列表:return self._flatten(sorted(terms, key=str))
,并将所有的with_items修改为with_sorted_items。
创建一个剧本用于测试:
--- hosts: centos1 tasks: - name: loop through list debug: msg: "An item: {{item}} " with_sorted_items: - 3 - 2 - 1 - Z - A - M ...
自定义filter插件实现字符串逆序+大写
mkdir filter_plugins && cd filter_plugins wget https://raw.githubusercontent.com/ansible/ansible/devel/lib/ansible/plugins/filter/core.py mv core.py reverse_upper.py
修改reverse_upper.py内容为:
from __future__ import (absolute_import, division, print_function)__metaclass__ = type import base64import globimport hashlibimport jsonimport ntpathimport os.pathimport reimport shleximport sysimport timeimport uuidimport yamlimport datetimefrom collections.abc import Mappingfrom functools import partialfrom random import Random, SystemRandom, shufflefrom jinja2.filters import pass_environmentfrom ansible.errors import AnsibleError, AnsibleFilterError, AnsibleFilterTypeErrorfrom ansible.module_utils.six import string_types, integer_types, reraise, text_typefrom ansible.module_utils.common.text.converters import to_bytes, to_native, to_textfrom ansible.module_utils.common.collections import is_sequencefrom ansible.module_utils.common.yaml import yaml_load, yaml_load_allfrom ansible.parsing.ajson import AnsibleJSONEncoderfrom ansible.parsing.yaml.dumper import AnsibleDumperfrom ansible.template import recursive_check_definedfrom ansible.utils.display import Displayfrom ansible.utils.encrypt import passlib_or_crypt, PASSLIB_AVAILABLEfrom ansible.utils.hashing import md5s, checksum_sfrom ansible.utils.unicode import unicode_wrapfrom ansible.utils.vars import merge_hashdisplay = Display() UUID_NAMESPACE_ANSIBLE = uuid.UUID('361E6D51-FAEC-444A-9079-341386DA8E2E' ) def reverse_upper (string ): """Reverse and upper string """ return string[::-1 ].upper() class FilterModule (object ): ''' Ansible core jinja2 filters ''' def filters (self ): return { 'reverse_upper' : reverse_upper, }
创建一个剧本用于测试:
--- - hosts: all tasks: - name: Reverse and upper ansible_distribution debug: msg: "Reverse and upper of ansible_distribution: {{ ansible_distribution | reverse_upper }} " ...
9. 故障排除和最佳实践 故障排除和最佳实践
9.1 故障排除 SSH连接错误
模拟:从ubuntu-c连接ubuntu1后,修改ubuntu1主机上~/.ssh/authorized_keys的权限为777
ssh ubuntu1 chmod 777 ~/.ssh/authorized_keys exit
此时,重新登录时SSH免密码登录失效,提示输入密码
解决:
使用ssh -v ubuntu1
查看有用信息,但还是提示输入登录ubuntu1的密码
在新窗口连接ubuntu1,输入root用户名和密码,登录ubuntu后,执行/usr/sbin/sshd -d -p 1234
ubuntu-c主机执行ssh ubuntu1 -p 1234
,然后ssh -v ubuntu1
信息窗口会出现因为所有权模式身份验证拒绝错误,由此确认了问题所在。
剧本语法检查
如:
ansible-playbook xxx_playbook.yaml --syntax-check
每个任务都选择是否执行
如:
ansible-playbook xxx_playbook.yaml --step
指定开始执行的任务
如:
ansible-playbook xxx_playbook.yaml --start-at-task="Install python-dnspython"
日志路径
在ansible.cfg配置log_path,指定保存执行日志的文件,如:
[default] inventory =hostshost_key_checking =False forks =6 log_path =log.txt
详细程度
详细程度有4级
-v 1级 输出数据显示 -vv 2级 输入输出显示 -vvv 3级 获取提供的附加信息,用于连接到托管主机 -vvvv 4级 提供额外的详细信息,包括连接插件和脚本以及用户上下文 如:
ansible-playbook -vvvv xxx_playbook.yaml
9.2 最佳实践 官方最佳实践:https://docs.ansible.com/ansible/latest/tips_tricks/index.html