CentOS安装新版git——超简单

忘了从哪里弄来的,CentOS6安装新版git的利器:

CentOS6:

#安装Git
yum install -y epel-release
rpm -ivh https://centos6.iuscommunity.org/ius-release.rpm
yum list git2u
yum install -y git2u
git --version

CentOS7:

[html] view plain copy
<code class="language-html">#安装Git  
yum install -y epel-release  
rpm -ivh https://centos7.iuscommunity.org/ius-release.rpm  
yum list git2u  
yum install -y git2u  
git --version  
</code>  

可以说是见到过的最简单的办法了

在虚拟机中快速搭建 Ansible 跟 Ceph 环境

在上一篇文章 Vagrant 单机快速模拟集群 https://imquanquan.net/archives/Vagrant-single-machine-fast-simulation-cluster.html 中,介绍了如何在本机中,快速利用 Vagrant 虚拟机管理工具快速起四台虚拟机来模拟集群,这篇文章将利用这些虚拟机来搭建一个 Ansible 跟 Ceph 的集群环境。

虚拟机配置

未分类

以上是各虚拟机节点的配置情况。四各节点内存都是 512 M,CPU 一核,系统 Dabian 9。

安装过程

1. 改 hostname 跟 hosts 文件

改 hostname 跟 hosts 有利于识别节点还有可以让节点之间靠 hostname 来通信。

创建虚拟机的时候已经设好了 hostname,把下面一段加入每个节点的 /etc/hosts 即可:

10.1.0.101    node1
10.1.0.102    node2
10.1.0.103    node3
10.1.0.104    deploy

2. 添加用户

下一件要做的就是,让添加部署的时候要用的用户了。在每个节点都执行:

sudo useradd -d /home/test -m test
sudo echo "test ALL = (root) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/test
sudo chmod 0440 /etc/sudoers.d/test
sudo passwd test

还顺便添加了无密码 sudo 的权限

3. 无密码 ssh 登录

添加部署节点 deploy 对 node 的无密码 ssh 登录,以搭建 Ansible 然后可以批量对 node 执行操作。在 deploy 执行:

su test
ssh-keygen
cat /home/test/.ssh/id_rsa.pub

三下回车生成密钥对,然后拷贝 /home/test/.ssh/id_rsa.pub 文件的内容到各个 node 节点:

su test
mkdir ~/.ssh
vi ~/.ssh/authorized_keys
sudo chmod 600 ~/.ssh/authorized_keys

编辑 ~/.ssh/config 文件:

Host node1
User test
Port 22

Host node2
User test
Port 22

Host node3
User test
Port 22

安装 Ansible

以下操作无特殊说明都在 deploy 节点进行操作。

1. 从包管理工具安装

sudo apt update && sudo apt install ansible

2. 修改 Ansible 配置文件

在 /etc/ansible/hosts 文件加入:

[ceph-deploy]
localhost  ansible_connection=local


[ceph-node]
node1
node2
node3

3. 验证&&测试:

ansible all -m ping

Ceph deploy 节点安装

1. 从包管理工具安装

添加 release key,软件源。这里安装的是 jewel 版本的 ceph:

wget -q -O- 'https://download.ceph.com/keys/release.asc' | sudo apt-key add -
echo deb http://download.ceph.com/debian-jewel/ $(lsb_release -sc) main | sudo tee /etc/apt/sources.list.d/ceph.list

更新,安装:

sudo apt-get update && sudo apt-get install ceph-deploy

Ceph 节点安装准备

1. 安装 ntp

修改时区:

ansible all -a "cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime" --sudo

建议在所有 Ceph 节点上安装 NTP 服务(特别是 Ceph Monitor 节点),并跟同一个 ntp 服务器进行时间同步,以免因时钟漂移导致故障:

ansible all -a   "apt update" --sudo
ansible all -a   "apt install ntp -y" --sudo

编辑 ntp 配置文件:

vi ntp.conf

restrict cn.pool.ntp.org
server cn.pool.ntp.org

分发,重启服务:

ansible all -m copy -a "src=/home/test/ntp.conf dest=/etc/ntp.conf" --sudo
ansible all -a "systemctl restart ntp" --sudo 

2. 安装依赖

安装 python-minimal:

ansible all -a "apt -y install python-minimal -y" --sudo

3. 开放端口

ansible all -a "iptables -A INPUT -i eth0 -p tcp -s 10.1.0.0/24 --dport 6789 -j ACCEPT" --sudo
ansible all -a "iptables -A INPUT -i eth0 -p tcp -s 10.1.0.0/24 --dport 6800:7300 -j ACCEPT" --sudo
ansible all -a "iptables-save" --sudo

ceph-mon 安装

1. 添加 mon 节点

mkdir ceph-cluster && cd ceph-cluster
ceph-deploy new node1 node2 node3

2. 修改配置文件

修改 ceph.conf 文件,添加:

# osd 节点个数
osd_pool_default_size = 3
# osd 节点最小个数
osd_pool_default_min_size = 1
# ceph 公共网络
public network = 10.1.0.0/24

2. 安装 ceph 节点

使用华中科大镜像源安装:

ansible all -a "wget -q -O- 'https://download.ceph.com/keys/release.asc' | sudo apt-key add -" sudo
ceph-deploy install --repo-url http://mirrors.ustc.edu.cn/ceph/debian-jewel/ node1 node2 node3

3. 初始化 mon 节点

ceph-deploy mon create-initial
ceph-deploy admin  node1 node2 node3

ceph osd 节点安装

1. 查看集群 uuid

集群的 uuid 就是这个 ceph 集群的唯一标识,后面要用:

cat ceph.conf

[global]
fsid = 88fc281e-b9d0-4de3-b662-eaf3bef46943
mon_initial_members = node1, node2, node3
mon_host = 10.1.0.101,10.1.0.102,10.1.0.103
auth_cluster_required = cephx
auth_service_required = cephx
auth_client_required = cephx

osd_pool_default_size = 3
osd_pool_default_min_size = 1
public network = 10.1.0.0/24

其中 88fc281e-b9d0-4de3-b662-eaf3bef46943 就是 uuid

2. 安装 osd

ssh 到各个节点,执行以下命令:

mkdir /home/test/osd0
sudo chown ceph: /home/test/osd0/
sudo ceph-disk prepare --cluster ceph   --cluster-uuid 88fc281e-b9d0-4de3-b662-eaf3bef46943 --fs-type  ext4 /home/test/osd0/
sudo ceph-disk activate /home/test/osd0/

以上命令是在 node1 上执行的,请将 uuid 替换成自己的,–fs-type 是 ext4,请换成自己的类型。然后将 osd0 替换掉对应节点的 osd 编号。

3. 查看集群健康状况

当所有节点都安装完成,可以在任一节点执行以下命令查看集群健康状况:

ceps -s

Ansible自动部署nginx+keepalived高可用负载均衡

本篇文章记录通过Ansible自动化部署nginx的负载均衡高可用,前端代理使用nginx+keepalived,后端web server使用3台nginx用于负载效果的体现,结构图如下:

未分类

1. 部署前准备工作

主机规划

  • Ansible : 192.168.214.144
  • Keepalived-node-1 : 192.168.214.148
  • Keepalived-node-2 : 192.168.214.143
  • web1 : 192.168.214.133
  • web2 : 192.168.214.135
  • web3 : 192.168.214.139

2. Ansible主机与远程主机秘钥认证

#!/bin/bash

keypath=/root/.ssh
[ -d ${keypath} ] || mkdir -p ${keypath}
rpm -q expect &> /dev/null || yum install expect -y
ssh-keygen -t rsa -f /root/.ssh/id_rsa  -P ""
password=centos
while read ip;do
expect <<EOF
set timeout 5
spawn ssh-copy-id $ip
expect {
"yes/no" { send "yesn";exp_continue }
"password" { send "$passwordn"  }
}
expect eof
EOF
done < /home/iplist.txt

iplist.txt

192.168.214.148
192.168.214.143
192.168.214.133
192.168.214.135
192.168.214.139
192.168.214.134

执行脚本

[root@Ansible script]# ./autokey.sh

测试验证

[root@Ansible script]# ssh 192.168.214.148 'date'
Address 192.168.214.148 maps to localhost, but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT!
Sat Jul 14 11:35:21 CST 2018

配置Ansible基于主机名认证,方便单独管理远程主机

vim  /etc/hosts
#
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.214.148 node-1
192.168.214.143 node-2
192.168.214.133 web-1
192.168.214.135 web-2
192.168.214.139 web-3

3. 安装配置Ansible

#安装ansible
[root@Ansible ~]# yum install ansible -y

#配置ansible主机清单
[root@Ansible ~]# vim /etc/ansible/hosts 
[all]

192.168.214.148
192.168.214.143
192.168.214.133
192.168.214.135
192.168.214.139

[node]

192.168.214.148
192.168.214.143

[web]
192.168.214.133 
192.168.214.135
192.168.214.139

#Ansible执行ping测试 
 [root@Ansible ~]# ansible all -m ping

4. 编写roles,实现web的部署

先看一下web的目录结构

[root@Ansible ~]# tree /opt/roles/web
/opt/roles/web
.
├── tasks
│   ├── install_nginx.yml
│   ├── main.yml
│   ├── start.yml
│   ├── temps.yml
│   └── user.yml
└── templates
    ├── index.html.j2
    └── nginx.conf.j2

2 directories, 7 files

按照角色执行的顺序编写

编写user.yml

- name: create group nginx
  group: name=nginx
- name: create user nginx
  user: name=nginx group=nginx system=yes shell=/sbin/nologin

编写install_nginx.yml

- name: install nginx webserver
  yum: name=nginx

创建nginx配置文件的template模板

由于是测试,后端web服务的nginx.conf配置文件基本保持默认,只只更具后端主机情况设置worker进程数,使用ansible的setup模块中的变量获取远程主机的cpu的数量值

#将配置文件转换成template文件
[root@Ansible conf]# cp nginx.conf /opt/roles/web/templates/nginx.conf.j2
#做出修改的内容如下
worker_processes {{ansible_proccessor_vcpus}};

#在templates目录写一个测试页内如下
vim index.html.j2
{{ ansible_hostname }} test page.

编写temps.yml

- name: cp nginx.conf.j2 to nginx web server rename nginx.conf
  template: src=/opt/roles/web/templates/nginx.conf.j2 dest=/etc/nginx/nginx.conf
- name: cp index test page to nginx server
  template: src=/opt/roles/web/templates/index.html.j2 dest=/usr/share/nginx/html/index.html

编写start.yml

- name: restart nginx
  service: name=nginx state=started

编写main.yml

- import_tasks: user.yml
- import_tasks: install_nginx.yml
- import_tasks: temps.yml
- import_tasks: start.yml

编写执行主文件web_install.yml,执行文件不能与web角色放在同一目录,通常放在roles目录

[root@Ansible ~]# vim /opt/roles/web_install.yml 


---
- hosts: web
  remote_user: root
  roles:
    - web

安装前测试: -C选项为测试

[root@Ansible ~]# ansible-playbook -C /opt/roles/web_install.yml 

如没有问题则执行安装

[root@Ansible ~]# ansible-playbook /opt/roles/web_install.yml 

测试访问

[root@Ansible ~]# ansible web -m shell -a 'iptables -F'
192.168.214.139 | SUCCESS | rc=0 >>


192.168.214.135 | SUCCESS | rc=0 >>


192.168.214.133 | SUCCESS | rc=0 >>


[root@Ansible ~]# curl 192.168.214.133
web-1 test page.

5. 编写roles角色部署nginx+keepalived

部署高可用集群需要注意各节点包括后端主机的时间问题,保证各主机时间一致。

[root@Ansible ~]# ansible all -m shell -a 'yum install ntpdate -y'

[root@Ansible ~]# ansible all -m shell -a 'ntpdate gudaoyufu.com'

6. 编写roles角色

编写user.yml

- name: create nginx group
  group: name=nginx
- name: create nginx user
  user: name=nginx group=nginx system=yes shell=/sbin/nologin

编写install_server.yml

- name: install nginx and keepalived
  yum: name={{ item }} state=latest
  with_items:
    - nginx
    - keepalived

编写temps.yml

- name: copy nginx proxy conf and rename
  template: src=/opt/roles/ha_proxy/templates/nginx.conf.j2  dest=/etc/nginx/nginx.conf

- name: copy master_keepalived.conf.j2 to MASTER node
  when: ansible_hostname == "node-1"
  template: src=/opt/roles/ha_proxy/templates/master_keepalived.conf.j2 dest=/etc/keepalived/keepalived.conf

- name: copy backup_keepalived.conf.j2 to BACKUP node
  when: ansible_hostname == "node-2"
  template: src=/opt/roles/ha_proxy/templates/backup_keepalived.conf.j2 dest=/etc/keepalived/keepalived.conf

配置nginx proxy配置文件模板

[root@Ansible ~]# cp /opt/conf/nginx.conf /opt/roles/ngx_proxy/templates/nginx.conf.j2
[root@Ansible ~]# vim /opt/roles/ngx_proxy/templates/nginx.conf.j2

user nginx;
worker_processes {{ ansible_processor_vcpus }};
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.

events {
    worker_connections  1024;
}


http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;


    include /etc/nginx/conf.d/*.conf;

    upstream web {

        server 192.168.214.133:80 max_fails=3 fail_timeout=30s;
        server 192.168.214.135:80 max_fails=3 fail_timeout=30s;
        server 192.168.214.139:80 max_fails=3 fail_timeout=30s;


    }



    server {

    listen       80 default_server;
    server_name  {{ ansible_hostname }};
    root         /usr/share/nginx/html;
    index index.html index.php;

         location / {
                proxy_pass http://web;
             }

         error_page 404 /404.html;

          }


}

配置keepalived配置文件模板

[root@Ansible ~]# cp /opt/conf/keepalived.conf /opt/roles/ha_proxy/templates/master_keepalived.conf.j2

[root@Ansible templates]# vim master_keepalived.conf.j2 # ! Configuration File for keepalived global_defs { notification_email { [email protected] [email protected] [email protected] } notification_email_from [email protected] smtp_server 192.168.214.1 smtp_connect_timeout 30 router_id LVS_DEVEL vrrp_skip_check_adv_addr vrrp_strict vrrp_garp_interval 0 vrrp_gna_interval 0 vrrp_iptables vrrp_mcast_group4 224.17.17.17 } vrrp_script chk_nginx { script "killall -0 nginx" interval 1 weight -20 fall 2 rise 1 } vrrp_instance VI_1 { state MASTER interface ens33 virtual_router_id 55 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 12345678 } virtual_ipaddress { 192.168.214.100 } track_script { chk_nginx } }

同样,在master_keepalived.conf.j2基础修改另存为backup_keepalived.conf.j2,只修改角色与优先级即可。注意:master_keepalived.conf.j2文件中的检测故障降低优先级的值要确保降低后MASTER优先级小于BACKUP的优先级
编写start.yml

- name: start nginx proxy server
  service: name=nginx state=started

编写main.yml

- import_tasks: user.yml
- import_tasks: install_server.yml
- import_tasks: temps.yml
- import_tasks: start.yml

编写执行主文件

[root@Ansible ~]# vim /opt/roles/ha_proxy_install.yml


---
- hosts: node
  remote_user: root
  roles:
    - ha_proxy

执行检测roles

[root@Ansible ~]# ansible-playbook -C /opt/roles/ha_proxy_install.yml 

执行测试没问题即可执行自动部署

执行过程如下:

[root@Ansible ~]# ansible-playbook  /opt/roles/ha_proxy_install.yml 



PLAY [node] **********************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************
ok: [192.168.214.148]
ok: [192.168.214.143]

TASK [ha_proxy : create nginx group] *********************************************************************************************
changed: [192.168.214.148]
ok: [192.168.214.143]

TASK [ha_proxy : create nginx user] **********************************************************************************************
changed: [192.168.214.148]
ok: [192.168.214.143]

TASK [ha_proxy : install nginx and keepalived] ***********************************************************************************
changed: [192.168.214.143] => (item=[u'nginx', u'keepalived'])
changed: [192.168.214.148] => (item=[u'nginx', u'keepalived'])

TASK [ha_proxy : copy nginx proxy conf and rename] *******************************************************************************
changed: [192.168.214.148]
changed: [192.168.214.143]

TASK [ha_proxy : copy master_keepalived.conf.j2 to MASTER node] ******************************************************************
skipping: [192.168.214.143]
changed: [192.168.214.148]

TASK [ha_proxy : copy backup_keepalived.conf.j2 to BACKUP node] ******************************************************************
skipping: [192.168.214.148]
changed: [192.168.214.143]

TASK [ha_proxy : start nginx proxy server] ***************************************************************************************
changed: [192.168.214.143]
changed: [192.168.214.148]

PLAY RECAP ***********************************************************************************************************************
192.168.214.143            : ok=7    changed=4    unreachable=0    failed=0   
192.168.214.148            : ok=7    changed=6    unreachable=0    failed=0   

至此,自动部署nginx+keepalived高可用负载均衡完成了

最后看一下roles目录的结构

[root@Ansible ~]# tree /opt/roles/
/opt/roles/
├── ha_proxy
│   ├── tasks
│   │   ├── install_server.yml
│   │   ├── main.yml
│   │   ├── start.yml
│   │   ├── temps.yml
│   │   └── user.yml
│   └── templates
│       ├── backup_keepalived.conf.j2
│       ├── master_keepalived.conf.j2
│       └── nginx.conf.j2
├── ha_proxy_install.retry
├── ha_proxy_install.yml
├── web
│   ├── tasks
│   │   ├── install_nginx.yml
│   │   ├── main.yml
│   │   ├── start.yml
│   │   ├── temps.yml
│   │   └── user.yml
│   └── templates
│       ├── index.html.j2
│       └── nginx.conf.j2
├── web_install.retry
└── web_install.yml

6 directories, 19 files

下面测试服务:keepalived的服务没有在ansible中设置自动启动,到keepalived节点启动即可。

测试node节点

[root@Ansible ~]# for i in {1..10};do curl 192.168.214.148;done
web-3 test page.
web-1 test page.
web-2 test page.
web-3 test page.
web-1 test page.
web-2 test page.
web-3 test page.
web-1 test page.
web-2 test page.
web-3 test page.

将node-1 的MASTER服务停掉测试故障转移,同时查看node-2状态变化

执行: nginx -s stop

查看vrrp通知,可以看到主备切换正常:

[root@node-2 ~]# tcpdump -i ens33 -nn host 224.17.17.17

listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes

16:55:20.804327 IP 192.168.214.148 > 224.17.17.17: VRRPv2, Advertisement, vrid 55, prio 100, authtype simple, intvl 1s, length 20
16:55:25.476397 IP 192.168.214.148 > 224.17.17.17: VRRPv2, Advertisement, vrid 55, prio 0, authtype simple, intvl 1s, length 20
16:55:26.128474 IP 192.168.214.143 > 224.17.17.17: VRRPv2, Advertisement, vrid 55, prio 90, authtype simple, intvl 1s, length 20
16:55:27.133349 IP 192.168.214.143 > 224.17.17.17: VRRPv2, Advertisement, vrid 55, prio 90, authtype simple, intvl 1s, length 20

再测试访问:

[root@Ansible ~]# for i in {1..10};do curl 192.168.214.148;done
web-1 test page.
web-2 test page.
web-3 test page.
web-1 test page.
web-2 test page.
web-3 test page.
web-1 test page.
web-2 test page.
web-3 test page.
web-1 test page.

node-1恢复主节点,抢回MASTER角色

node-1节点执行nginx指令,可以看到VIP漂移回到node-1节点,测试访问

[root@Ansible ~]# for i in {1..10};do curl 192.168.214.148;done
web-1 test page.
web-2 test page.
web-3 test page.
web-1 test page.
web-2 test page.
web-3 test page.
web-1 test page.
web-2 test page.
web-3 test page.
web-1 test page.

7. 其他问题

上面的自动部署方式还有可以改进的地方,比如,可以将配置keepalived的配置文件中的许多参数在roles中以统一变量的方式定义,然后在template模板文件中引用参数就可以了

此外还有一个需要注意的地方是:keepalived的配置文件中使用了killall指令检测本地的nginx服务状态,如果检测结果状态为非0就会执行vrrp_script中定义的降级操作,要确保系统这个指令可以执行,有时该指令没有被安装,如果该指令没有存在,即使MASTER节点发生故障也不会发生变化。

ansible笔记(22):循环(四)

话接前文,我们继续来聊聊关于循环的关键字。

今天聊聊 “with_indexed_items”的用法,顾名思义,”with_indexed_items”应该与”索引”有关,没错,”with_indexed_items”的作用就是在循环处理列表时为列表中的每一项添加”数字索引”,”索引”从0开始,这样说可能不够直观,我们来看一个小示例,示例playbook如下:

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{ item }}"
    with_indexed_items:
    - test1
    - test2
    - test3

上例中我们定义了一个列表,列表中有3个值,test1、test2、test3,我们使用”with_indexed_items”关键字处理这个列表,然后使用debug模块输出了item的信息,那么上例playbook执行后输出的信息如下:

TASK [debug] **********************************
ok: [test70] => (item=(0, u'test1')) => {
    "changed": false,
    "item": [
        0,
        "test1"
    ],
    "msg": [
        0,
        "test1"
    ]
}
ok: [test70] => (item=(1, u'test2')) => {
    "changed": false,
    "item": [
        1,
        "test2"
    ],
    "msg": [
        1,
        "test2"
    ]
}
ok: [test70] => (item=(2, u'test3')) => {
    "changed": false,
    "item": [
        2,
        "test3"
    ],
    "msg": [
        2,
        "test3"
    ]
}

从上述输出信息的msg中可以看到,”with_indexed_items”在处理列表中的每一项时,按照顺序为每一项添加了编号,test1对应的索引编号是0,test2的编号是1,test3的编号是2,”with_indexed_items”将添加过编号的每一项放入到了item中,所以,我们可以在处理每一项的时候同时获取到对应的编号,playbook如下

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "index is : {{ item.0 }} , value is {{ item.1 }}"
    with_indexed_items:
    - test1
    - test2
    - test3

上例中,我们已经能够通过”with_indexed_items”获取到列表中每个项的值以及对应的编号,但是,上述两个示例都是简单的单层列表,如果遇到像前文中出现的多层嵌套列表,”with_indexed_items”会怎样处理呢?我们来试试,示例playbook如下:

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "index is : {{ item.0 }} , value is {{ item.1 }}"
    with_indexed_items:
    - [ test1, test2 ]
    - [ test3, test4, test5 ]
    - [ test6, test7 ]

如上例所示,我们定义了一个嵌套的列表,列表中的每一项又是一个小列表,我们使用”with_indexed_items”处理这个列表,上例执行后,输出如下

TASK [debug] *****************************
ok: [test70] => (item=(0, u'test1')) => {
    "changed": false,
    "item": [
        0,
        "test1"
    ],
    "msg": "index is : 0 , value is test1"
}
ok: [test70] => (item=(1, u'test2')) => {
    "changed": false,
    "item": [
        1,
        "test2"
    ],
    "msg": "index is : 1 , value is test2"
}
ok: [test70] => (item=(2, u'test3')) => {
    "changed": false,
    "item": [
        2,
        "test3"
    ],
    "msg": "index is : 2 , value is test3"
}
ok: [test70] => (item=(3, u'test4')) => {
    "changed": false,
    "item": [
        3,
        "test4"
    ],
    "msg": "index is : 3 , value is test4"
}
ok: [test70] => (item=(4, u'test5')) => {
    "changed": false,
    "item": [
        4,
        "test5"
    ],
    "msg": "index is : 4 , value is test5"
}
ok: [test70] => (item=(5, u'test6')) => {
    "changed": false,
    "item": [
        5,
        "test6"
    ],
    "msg": "index is : 5 , value is test6"
}
ok: [test70] => (item=(6, u'test7')) => {
    "changed": false,
    "item": [
        6,
        "test7"
    ],
    "msg": "index is : 6 , value is test7"
}

你目光如炬,一定发现了,当我们定义了两层的嵌套列表时,”with_indexed_items”会将嵌套的两层列表”拉平”,”拉平”后按照顺序为每一项编号,”拉平”效果跟之前总结的”with_flattened”效果类似(如果忘了怎样使用”with_flattened”请回顾前文),但是,当处理这种嵌套的多层列表时,”with_indexed_items”的拉平效果与”with_flattened”的完全一致么,我们再来实验一下,我们把上例的嵌套列表改的更加复杂一些,再多嵌套一层,示例playbook如下

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{ item }}"
    with_indexed_items:
    - [ test1, test2 ]
    - [ test3, [ test4, test5 ] ]
    - [ test6 ]

如上例所示,我们又在之前示例的基础上,多嵌套了一层列表,那么执行上例playbook,输出信息如下

TASK [debug] ********************************
ok: [test70] => (item=(0, u'test1')) => {
    "changed": false,
    "item": [
        0,
        "test1"
    ],
    "msg": [
        0,
        "test1"
    ]
}
ok: [test70] => (item=(1, u'test2')) => {
    "changed": false,
    "item": [
        1,
        "test2"
    ],
    "msg": [
        1,
        "test2"
    ]
}
ok: [test70] => (item=(2, u'test3')) => {
    "changed": false,
    "item": [
        2,
        "test3"
    ],
    "msg": [
        2,
        "test3"
    ]
}
ok: [test70] => (item=(3, [u'test4', u'test5'])) => {
    "changed": false,
    "item": [
        3,
        [
            "test4",
            "test5"
        ]
    ],
    "msg": [
        3,
        [
            "test4",
            "test5"
        ]
    ]
}
ok: [test70] => (item=(4, u'test6')) => {
    "changed": false,
    "item": [
        4,
        "test6"
    ],
    "msg": [
        4,
        "test6"
    ]
}

你肯定看出了问题所在,没错,当多加了一层嵌套以后,”with_indexed_items”并不能像”with_flattened”一样将嵌套的列表”完全拉平”,第二层列表中的项如果仍然是一个列表,”with_indexed_items”则不会拉平这个列表,而是将其当做一个整体进行编号。

关于”with_indexed_items”的使用就总结到这里,希望能够对你有所帮助。

ansible笔记(21):循环(三)

前文中我们已经了解了一些用于循环的关键字,比如 with_list、with_items、with_flattened、with_together,这篇文章我们继续。

假设,现在我有一个需求,我需要在目标主机的测试目录中创建a、b、c三个目录,这三个目录都有相同的子目录,它们都有test1和test2两个子目录,使用最原始的办法,我们可以在目标主机上执行如下一堆命令

# pwd
/testdir/testdir
# mkdir -p a/test1
# mkdir -p a/test2
# mkdir -p b/test1
# mkdir -p b/test2
# mkdir -p c/test1
# mkdir -p c/test2

当然,我们也可以使用稍微讨巧一点的方法,执行如下命令

# mkdir -p {a,b,c}/{test1,test2}

如果我们想要使用ansible完成上述需求,我们该怎么做呢?我们能够使用shell模块执行上述命令吗?我们来试试,在ansible主机上执行如下命令

# ansible test70 -m shell -a "mkdir -p /testdir/testdir/{a,b,c}/{test1,test2}"

我们使用上述命令成功的在目标主机上创建了符合我们要求的目录结构,其实,我们还能够使用循环完成上述工作,如果想要使用循环来完成上述工作,则需要先了解一个用于循环的关键字,这个关键字就是”with_cartesian”,这个关键字怎么使用呢?有怎样的效果的呢?来看一个小示例playbook,如下

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{ item }}"
    with_cartesian:
    - [ a, b, c ]
    - [ test1, test2 ]

上例中定义了一个嵌套的列表,其中嵌套了两个小列表,第一个小列表中有a、b、c三个元素,第二个小列表中有test1、test2两个元素,然后我们使用 with_cartesian关键字处理这个嵌套的列表,然后循环调用debug模块输出了item的值,执行上例playbook后,debug模块的输出信息如下

TASK [debug] ***********************************
ok: [test70] => (item=[u'a', u'test1']) => {
    "changed": false,
    "item": [
        "a",
        "test1"
    ],
    "msg": [
        "a",
        "test1"
    ]
}
ok: [test70] => (item=[u'a', u'test2']) => {
    "changed": false,
    "item": [
        "a",
        "test2"
    ],
    "msg": [
        "a",
        "test2"
    ]
}
ok: [test70] => (item=[u'b', u'test1']) => {
    "changed": false,
    "item": [
        "b",
        "test1"
    ],
    "msg": [
        "b",
        "test1"
    ]
}
ok: [test70] => (item=[u'b', u'test2']) => {
    "changed": false,
    "item": [
        "b",
        "test2"
    ],
    "msg": [
        "b",
        "test2"
    ]
}
ok: [test70] => (item=[u'c', u'test1']) => {
    "changed": false,
    "item": [
        "c",
        "test1"
    ],
    "msg": [
        "c",
        "test1"
    ]
}
ok: [test70] => (item=[u'c', u'test2']) => {
    "changed": false,
    "item": [
        "c",
        "test2"
    ],
    "msg": [
        "c",
        "test2"
    ]
}

从输出信息可以看出,第一个小列表中的每个元素与第二个小列表中的每个元素都”两两组合在了一起”,如下图所示

未分类

聪明如你一定看出来了,上图中的排列组合方式就好像笛卡尔乘积中的一样,所以,”with_cartesian”关键字的作用就是将每个小列表中的元素按照”笛卡尔的方式”组合后,循环的处理每个组合,所以,你一定已经想到了,我们可以利用这个特性,完成我们之前创建目录的需求,示例playbook如下:

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - file:
      state: directory
      path: "/testdir/testdir/{{ item.0 }}/{{ item.1 }}"
    with_cartesian:
    - [ a, b, c ]
    - [ test1, test2 ]

上例playbook执行后,即可在目标主机中创建出符合我们要求的目录,有了前文中的各种示例作为基础,我想你一定已经看明白了。

其实,还有一个关键字可以代替”with_cartesian”,它就是”with_nested”,”with_nested”与”with_cartesian”的效果一致,可以无差别使用他们。

好了,这篇文章就总结到这里,希望对你有所帮助。

ansible笔记(20):循环(二)

前文中,我们总结了with_items的用法,你肯定还有印象,前文中有如下两个示例,它们的执行效果是相同的

示例一
---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{item}}"
    with_items:
    - 1
    - 2
    - 3

示例二
---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{item}}"
    with_items: [ 1, 2, 3 ]

上述两个示例分别使用了不同的语法自定义了一个列表,虽然语法不同,但是最终的效果是相同的,其实,我们可以把上述两种语法结合起来使用,结合后可以定义出稍微复杂一些的结构,比如嵌套的列表(序列中的序列),示例如下

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{item}}"
    with_items:
    - [ 1, 2, 3 ]
    - [ a, b ]

上例中我们将之前的两种语法结合,定义出了一个列表,而这个列表中的每一项都是列表,相当于一个大列表中嵌套了多个小列表,那么,当我们使用with_items遍历上述列表时,会是什么样的效果呢?我们试试,执行后的信息如下

TASK [debug] ********************************
ok: [test70] => (item=1) => {
    "changed": false,
    "item": 1,
    "msg": 1
}
ok: [test70] => (item=2) => {
    "changed": false,
    "item": 2,
    "msg": 2
}
ok: [test70] => (item=3) => {
    "changed": false,
    "item": 3,
    "msg": 3
}
ok: [test70] => (item=a) => {
    "changed": false,
    "item": "a",
    "msg": "a"
}
ok: [test70] => (item=b) => {
    "changed": false,
    "item": "b",
    "msg": "b"
}

可以看到,debug模块循环的将每个小列表中的值都输出了一遍,这可能与我们想象的不太一样,因为在之前的示例中, 并没有列表嵌套列表的情况,按照之前的思路,with_items会循环的输出列表(最外层大列表)中的每一项,也就是说,按照之前的思路debug模块应该会将每个小列表作为一个小整体输出,而不应该输出小列表中的每个元素,但是事实却是with_items将嵌套在大列表中的每个小列表都”展开”了,并且将小列表中的元素都输出了,如果,我们想要将每个小列表作为一个整体输出,该怎么办呢?

我们可以使用with_list关键字,替换上例playbook中的with_items关键字,那么with_list关键字与with_items关键字有什么区别呢?将上例的with_items替换成with_list以后又能不能实现我们想要的效果呢?我们一起来试试,示例playbook如下

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{item}}"
    with_list:
    - [ 1, 2, 3 ]
    - [ a, b ]

如上例所示,上例playbook中的列表与之前示例playbook中的列表完全相同,都是嵌套的列表,只是将原来的with_items关键字替换为了with_list关键字,那么我们来看一下执行效果,上例playbook执行后debug模块的输出结果如下

TASK [debug] *******************************
ok: [test70] => (item=[1, 2, 3]) => {
    "changed": false,
    "item": [
        1,
        2,
        3
    ],
    "msg": [
        1,
        2,
        3
    ]
}
ok: [test70] => (item=[u'a', u'b']) => {
    "changed": false,
    "item": [
        "a",
        "b"
    ],
    "msg": [
        "a",
        "b"
    ]
}

如上述信息所示,经过with_list处理后,每个嵌套在大列表中的小列表都被当做一个整体存放在item变量中,最终被debug作为一个小整体输出了,而不会像with_items一样将小列表”展开拉平”后一并将小列表中的元素循环输出。

前一篇文章中有很多示例,其实这些示例中的with_items关键字都可以替换成with_list关键字,替换后都可正常执行,这是因为,前一篇文章中的示例中的列表都是简单的单层列表,当处理单层的简单列表时,with_list与with_items没有任何区别,只有在处理上例中的”嵌套列表”时,才会体现出区别,区别就是,with_items会将嵌套在内的小列表”拉平”,拉平后循环处理所有元素,而with_list则不会”拉平”嵌套的列表,with_list只会循环的处理列表(最外层列表)中的每一项。

其实,当处理这种嵌套的列表时,如果想要实现”拉平”的效果,我们还能使用另外一个关键字,它就是with_flattened关键字,示例playbook如下:

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{item}}"
    with_flattened:
    - [ 1, 2, 3 ]
    - [ a, b ]

执行上例playbook以后,你会发现,执行效果与with_items效果完全相同。

此刻,你一定已经明白了with_list、with_items、with_flattened之间的区别了,在处理简单的单层列表时,他们没有区别,但是当处理嵌套的多层列表时,with_items与with_flattened会将嵌套列表”拉平展开”,循环的处理每个元素,而with_list只会处理最外层的列表,将最外层的列表中的每一项循环处理。

话说,我们还能使用如下方法定义嵌套的列表,示例如下:

    with_list:
    -
      - 1
      - 2
      - 3
    -
      - a
      - b

上述方法通过缩进对齐的方式,定义出了一个嵌套有列表的列表,与如下定义完全相同

    with_list:
    - [ 1, 2, 3 ]
    - [ a, b ]

目前为止,我们已经了解到了三个关键字可以用于循环操作,它们是with_list、with_items、with_flattened,那么我们再来认识一个新的关键字,它就是”with_together”,with_together可以将两个列表中的元素”对齐合并”,单单用语言来描述,不是特别容易理解,不如来看一个小示例,示例playbook如下:

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{ item }}"
    with_together:
    - [ 1, 2, 3 ]
    - [ a, b, c ]

如上例所示,我们定义了一个嵌套的列表,大列表内一共有两个小列表,每个小列表内有三个值,然后使用with_together关键字处理这个嵌套列表,上例playbook执行结果如下

TASK [debug] ******************************
ok: [test70] => (item=[1, u'a']) => {
    "changed": false,
    "item": [
        1,
        "a"
    ],
    "msg": [
        1,
        "a"
    ]
}
ok: [test70] => (item=[2, u'b']) => {
    "changed": false,
    "item": [
        2,
        "b"
    ],
    "msg": [
        2,
        "b"
    ]
}
ok: [test70] => (item=[3, u'c']) => {
    "changed": false,
    "item": [
        3,
        "c"
    ],
    "msg": [
        3,
        "c"
    ]
}

从上述结果可以看出:

第一个小列表中的第1个值与第二个小列表中的第1个值合并在一起输出了,

第一个小列表中的第2个值与第二个小列表中的第2个值合并在一起输出了,

第一个小列表中的第3个值与第二个小列表中的第3个值合并在一起输出了,

这就是with_together所谓的”对齐合并”功能,聪明如你一定已经明白了。

不过上例中,两个小列表中的元素数量相同,如果元素数量不同的小列表使用with_together对齐合并,会是什么效果呢?

这里就不进行示例了,快动手试试吧。

这篇文章就总结到这里,希望能够对你有所帮助。

ansible笔记(19):循环(一)

在使用ansible的过程中,我们经常需要处理一些返回信息,而这些返回信息中,通常可能不是单独的一条返回信息,而是一个信息列表,如果我们想要循环的处理信息列表中的每一条信息,我们该怎么办呢?这样空口白话的描述有些费力,不如通过一些小示例,结合场景来描述。

假设,我的清单配置如下

10.1.1.60
test70.zsythink.net ansible_host=10.1.1.70
test71 anisble_host=10.1.1.71

[testA]
test60 ansible_host=10.1.1.60
test61 ansible_host=10.1.1.61

[testB]
test70 ansible_host=10.1.1.70

[test:children]
testA
testB

如果我想要获取到清单中所有未分组的主机的主机名,则可以执行如下命令

# ansible test70 -m debug -a "msg={{groups.ungrouped}}"
test70 | SUCCESS => {
    "changed": false,
    "msg": [
        "10.1.1.60",
        "test70.zsythink.net",
        "test71"
    ]
}

从返回信息可以看出,一共有3个未分组主机,上例的返回信息中就不只有一条信息记录,如果我们想要获取到上述返回信息中的第二条信息,则可以使用如下方法

# ansible test70 -m debug -a "msg={{groups.ungrouped[1]}}"
test70 | SUCCESS => {
    "changed": false,
    "msg": "test70.zsythink.net"
}

但是问题是,我们通常不能确定返回信息有几条,我们可能需要循环的处理返回信息中的每一条信息,那么怎么才能循环处理返回信息中的每一条信息呢?示例playbook如下

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{item}}"
    with_items: "{{groups.ungrouped}}"

上例中,我们通过”{{groups.ungrouped}}”获取到了未分组的返回信息,并使用关键字”with_items”接收了返回信息,然后使用debug模块输出了名为”item”变量的变量值,聪明如你一定已经明白了,”with_items”关键字会把返回的列表信息自动处理,将每一条信息单独放在一个名为”item”的变量中,我们只要获取到名为”item”变量的变量值,即可循环的获取到列表中的每一条信息,所以,上例中返回信息中的每一条信息都会循环的被debug模块处理,执行上述playbook后结果如下

TASK [debug] *********************************************
ok: [test70] => (item=10.1.1.60) => {
    "changed": false,
    "item": "10.1.1.60",
    "msg": "10.1.1.60"
}
ok: [test70] => (item=test70.zsythink.net) => {
    "changed": false,
    "item": "test70.zsythink.net",
    "msg": "test70.zsythink.net"
}
ok: [test70] => (item=test71) => {
    "changed": false,
    "item": "test71",
    "msg": "test71"
}

从执行playbook的结果可以看出,debug模块对每条信息都单独输出了一次,而不是三条信息同时一次输出,由于对应play是针对test70主机操作的,所以上例中debug模块的三次操作都是在test70主机上进行的,只不过debug模块只是输出信息,并不会对test70主机做出什么实际的改动而已,通过上述方法,就能够符合我们循环操作的要求了。

上例中,我们使用的是返回值中的信息,那么我们能不能自定义一个列表,然后循环使用列表中的值呢?必须能的,示例如下

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{item}}"
    with_items:
    - 1
    - 2
    - 3

如上例所示,我们自定义了3个值,分别是1、2、3,debug模块会循环的输出这三个值。

或者我们换一种写法,如下写法与上述写法的效果完全相同。

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{item}}"
    with_items: [ 1, 2, 3 ]

我们还能够自定义稍微复杂些的列表,示例如下

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{item.test1}}"
    with_items:
    - { test1: a, test2: b }
    - { test1: c, test2: d }

上例中自定义列表中的每一个条目都是一个对象,我们可以通过对象的属性对应的”键”,获取到对应的”值”,如上例所示,第一个条目的test1键对应的值是a,第二个条目的test1键对应的值是c,所以执行上例playbook以后,”a”和”c”会被输出。

学会使用循环,能够让我们事半功倍。

比如,在没有学会使用循环之前,如果想要在同一主机中创建四个文件,那么你可能会编写如下playbook

---
- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - file:
      path: "/opt/a"
      state: touch
  - file:
      path: "/opt/b"
      state: touch
  - file:
      path: "/opt/c"
      state: touch
  - file:
      path: "/opt/d"
      state: touch

我们重复的书写了file模块4次,其实每次只是改变了file模块的path参数的值而已,如果使用循环,上例的playbook则可以改写成如下

---
- hosts: test70
  remote_user: root
  gather_facts: no
  vars:
    dirs:
    - "/opt/a"
    - "/opt/b"
    - "/opt/c"
    - "/opt/d"
  tasks:
  - file:
      path: "{{item}}"
      state: touch
    with_items: "{{dirs}}"

正如上述示例所示,重复的操作越多,使用循环则越方便。

那么我们再来看一个小示例,这个示例与循环没有直接关系,但是可以引出我们要说的话题,示例playbook如下:

---
- hosts: test70
  gather_facts: no
  tasks:
  - shell: "ls /opt"
    register: returnvalue
  - debug:
      var: returnvalue

看到上例,你一定不会觉得陌生,在之前总结变量的文章中,我们已经总结过”register”的用法,我们可以通过”register”获取到模块执行后的”返回值”信息,如果你忘记了”register”的用法,请回顾前文,上例中,我们将shell模块执行后的返回值写入了名为”returnvalue”的变量中,然后使用debug模块输出了”returnvalue”变量的值,上述playbook执行后结果如下

TASK [debug] ******************
ok: [test70] => {
    "returnvalue": {
        "changed": true,
        "cmd": "ls /opt",
        "delta": "0:00:01.006690",
        "end": "2018-07-12 17:43:01.445936",
        "failed": false,
        "rc": 0,
        "start": "2018-07-12 17:43:00.439246",
        "stderr": "",
        "stderr_lines": [],
        "stdout": "rh",
        "stdout_lines": [
            "rh"
        ]
    }
}

可以看到shell模块的返回值信息如上,但是,上例并没有借助循环重复调用shell模块,如果使用循环多次调用shell模块执行不同的命令,返回信息的格式还会和原来一样吗?我们来实验一下,我们将上例的playbook修改如下

---
- hosts: test70
  gather_facts: no
  tasks:
  - shell: "{{item}}"
    with_items:
    - "ls /opt"
    - "ls /home"
    register: returnvalue
  - debug:
      var: returnvalue

如上例所示,我们使用循环重复调用了shell模块两次,分别执行了两条命令,然后将shell模块的返回值存放到了”returnvalue”变量中,最后使用debug模块输出了”returnvalue”变量的值,那么当我们使用循环时,返回值信息会和原来一样么?上例的playbook执行后debug模块的输出如下

未分类

细心如你一定发现了,当使用了循环以后,每次shell模块执行后的返回值都会放入一个名为”results”的序列中,其实,”results”也是一个返回值,当模块中使用了循环时,模块每次执行的返回值都会追加存放到”results”这个返回值中,所以,我们可以通过”results”关键字获取到每次模块执行后的返回值,示例如下

---
- hosts: test70
  gather_facts: no
  tasks:
  - shell: "{{item}}"
    with_items:
    - "ls /opt"
    - "ls /home"
    register: returnvalue
  - debug:
      msg: "{{item.stdout}}"
    with_items: "{{returnvalue.results}}"

上例中,我们先使用循环重复的调用了shell模块,然后将shell模块每次执行后的返回值注册到了变量”returnvalue”中,之后,在使用debug模块时,通过返回值”results”获取到了之前每次执行shell模块的返回值(shell每次执行后的返回值已经被放入到item变量中),最后又通过返回值”stdout”获取到了每次shell模块执行后的标准输出,你可以执行一下上例的playbook,执行后,输出结果的msg关键字对应的值就是每次shell模块执行后的标准输出。

你可能还会看到有的朋友使用如下方法输出”{{returnvalue.results}}”列表中的信息

---
- hosts: test70
  gather_facts: no
  tasks:
  - shell: "{{item}}"
    with_items:
    - "ls /opt"
    - "ls /home"
    register: returnvalue
  - debug:
      msg:
       "{% for i in returnvalue.results %}
          {{ i.stdout }}
        {% endfor %}"

你一定看出来了,上例使用了一个for循环遍历了” returnvalue.results”列表,上例中for循环的语法为jinja2语言中的for循环语法,jinja2是一种模板语言,jinja2是一个基于python的模板引擎,所以,在ansible中,我们可以使用jinja2编写模板文件,然后再根据模板文件来生成配置文件,jinja2中也有一些控制语句结构,比如for循环,上例中使用的就是jinja2语法中的for循环,如果你执行了上例的playbook,你会发现,debug模块只执行了一次,msg中对应的信息是所有shell模块执行后的stdout返回值,因为debug模块只是输出了经过jinja2的for循环处理过的信息而已,debug模块并没有因为for循环而被重复的调用。如果你对jinja2的语法不是很了解,不用在意,此处只是展示一个小示例,当我们总结模板的用法时,再去了解jinja2相应的使用方法也不迟,所以看不懂也不用纠结。

这篇文章就总结到这里,希望能够对你有所帮助~

ansible笔记(18):变量(五)

ansible中还有一些内置变量可供我们使用,当然,这些内置变量的变量名是被ansible保留的,我们定义变量时不能使用这些变量名。

内置变量ansible_version

先从一个简单的内置变量说起,比如,我们可以通过内置变量ansible_version获取到ansible的版本号,示例命令如下

ansible test70 -m debug -a "msg={{ansible_version}}"

内置变量hostvars

除了ansible_version,还有一些非常有用的内置变量。比如内置变量hostvars

hostvars可以帮助我们在操作当前主机时获取到其他主机中的信息。

假设,我想要在操作test70主机时获取到test71主机中的facts信息,我该怎么办呢?示例如下

---
- name: "play 1: Gather facts of test71"
  hosts: test71
  remote_user: root

- name: "play 2: Get facts of test71 when operating on test70"
  hosts: test70
  remote_user: root
  tasks:
  - debug:
      msg: "{{hostvars['test71'].ansible_ens35.ipv4}}"

上例中有两个play,第一个play针对test71主机执行,但是第一个play中没有显式指定任何task(后文会解释原因),第二个play针对test70主机执行,在第二个play中只有一个task,即使用debug模块,输出了test71主机中的ens35网卡的IP信息,如你所见,我们可以借助hostvars在操作当前主机时输出其他主机中的facts信息,上例中使用hostvars加上清单中的主机名称再加上facts的key,即可获取到对应的facts信息,有了前文的总结作为基础,你一定想到了,上例中的msg的值改为如下写法也是可以的。

"{{hostvars.test71.ansible_ens35.ipv4}}"

上例中的第一个play中并没有任何的task,为什么还需要第一个play呢?如果你将上例的第一个play删除,只保留第二个play,运行时则会报错,这是因为,虽然第一个play中没有任何task,但是当第一个play执行时,默认会调用”[Gathering Facts]”任务,也就是说,默认会收集test71主机的facts信息,只有被收集过的facts信息才能被后面的play引用到,如果压根没有收集对应主机的facts信息,即使使用hostvars内置变量,也无法获取到对应主机的facts信息,我们来做个试验,我们可以直接把上例的第一个play从playbook中删除,也可以指明让第一个play不收集对应的facts信息,使用”gather_facts”关键字可以控制当前play是否收集对应主机的facts信息,示例如下:

---
- name: "play 1: Gather facts of test71"
  hosts: test71
  remote_user: root
  gather_facts: no

- name: "play 2: Get facts of test71 when operating on test70"
  hosts: test70
  remote_user: root
  tasks:
  - debug:
      msg: "{{hostvars['test71'].ansible_ens35.ipv4}}"

如上例所示,第一个play中的”gather_facts: no”表示设置当前play不收集对应主机的信息,运行上例playbook会报错,因为第二个play在操作test70时,无法获取到test71主机中的facts信息,原因是test71的facts信息并未被收集过,所以,调用其他主机的facts信息的前提是对应主机的facts信息已经被收集过。

其实,除了facts信息,我们还能够利用hostvars内置变量从别的主机中获取到其他类型的一些变量信息,比如,其他主机的注册变量、主机变量、组变量等信息,我们先来看一个获取其他主机的注册变量的小示例,如下:

---
- hosts: test71
  remote_user: root
  gather_facts: no
  tasks:
  - shell: "echo register_var_in_play1"
    register: shellreturn

- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{hostvars.test71.shellreturn.stdout}}"

如上例所示,通过hostvars内置变量可以直接获取到其他主机中的注册变量,你一定发现了,注册变量并不用像facts信息那样需要事先收集,即可直接通过hostvars跨主机被引用到,同理,如果你在清单中为test71主机配置了主机变量,或者为test71主机所在的组配置了组变量,也是可以通过hostvars直接跨主机引用的,这里就不进行示例了,动手试试吧。

你可能会问,如果我直接在play中为当前主机定义一个变量,可以在之后的play中操作其他主机时被引用到吗?那么我们来做个实验,示例如下

---
- hosts: test71
  remote_user: root
  gather_facts: no
  vars:
    testvar: testvar_in_71
  tasks:
  - debug:
      msg: "{{testvar}}"

- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{hostvars.test71.testvar}}"

在上例的第一个play中我们为test71主机定义了一个变量,变量名称为testvar,在第二个play中操作test70主机时,使用hostvars尝试引用test71主机中的变量,如果执行上述playbook则会报错,看来通过vars关键字定义的变量使用上例中的方法是无法被跨主机引用的,聪明如你,一定想到了解决方案,前一篇文章中,我们总结了怎样使用”set_fact”关键字定义变量,通过”set_fact”关键字定义的变量拥有类似”facts”信息的特性(如果不明白可以参考前文),所以,我们可以把”vars”关键字中定义的变量通过”set_fact”关键字去定义,这样这些变量就好像facts信息被收集过一样,能被之后的play引用到了,示例如下

---
- hosts: test71
  remote_user: root
  gather_facts: no
  tasks:
  - set_fact:
      testvar: "testvar_in_71"
  - debug:
      msg: "{{testvar}}"

- hosts: test70
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{hostvars.test71.testvar}}"

上例通过”set_fact”结合”hostvars”的方式,实现了跨play获取其他主机中的变量信息的功能,还是很方便的。

内置变量inventory_hostname

通过inventory_hostname变量可以获取到被操作的当前主机的主机名称,这里所说的主机名称并不是linux系统的主机名,而是对应主机在清单中配置的名称,假设我的清单配置如下

[test_group]
10.1.1.60
test70.zsythink.net ansible_host=10.1.1.70
test71 anisble_host=10.1.1.71

清单中配置了三个主机,第一个主机以IP的形式配置,第二个主机和第三个主机都以别名的方式配置,他们同属于test_group组。

那么我们使用内置变量inventory_hostname获取一下各个主机的对应的主机名,看看会返回什么,示例如下

# ansible test_group -m debug -a "msg={{inventory_hostname}}"
test70.zsythink.net | SUCCESS => {
    "changed": false, 
    "msg": "test70.zsythink.net"
}
10.1.1.60 | SUCCESS => {
    "changed": false, 
    "msg": "10.1.1.60"
}
test71 | SUCCESS => {
    "changed": false, 
    "msg": "test71"
}

从返回信息可以看出,如果使用IP配置主机,inventory_hostname的值就是IP,如果使用别名,inventory_hostname的值就是别名。

内置变量inventory_hostname_short

与内置变量inventory_hostname类似,通过inventory_hostname_short也可以获取当前play操作的主机在清单中对应的名称,但是这个名称更加简短,假设我的清单配置如下

[test_group]
10.1.1.60
test70.zsythink.net ansible_host=10.1.1.70
test71 anisble_host=10.1.1.71

那么通过内置变量inventory_hostname_short获取到的主机的简短名称如下:

# ansible test_group -m debug -a "msg={{inventory_hostname_short}}"
test70.zsythink.net | SUCCESS => {
    "changed": false, 
    "msg": "test70"
}
10.1.1.60 | SUCCESS => {
    "changed": false, 
    "msg": "10"
}
test71 | SUCCESS => {
    "changed": false, 
    "msg": "test71"
}

可以看到,无论是IP还是别名,如果清单的主机名称中包含”.”,inventory_hostname_short都会取得主机名中第一个”.”之前的字符作为主机的简短名称。

内置变量play_hosts

通过内置变量play_hosts可以获取到当前play所操作的所有主机的主机名列表,示例playbook如下:

---
- hosts: test70,test71
  remote_user: root
  gather_facts: no
  tasks:
  - debug:
      msg: "{{play_hosts}}"

执行上例的playbook,返回信息如下

TASK [debug] *************************
ok: [test70] => {
    "msg": [
        "test71", 
        "test70"
    ]
}
ok: [test71] => {
    "msg": [
        "test71", 
        "test70"
    ]
}

可以看到,此play每操作一个主机,都会将当前play操作的所有主机的主机名列表返回。

没错,inventory_hostname和play_hosts都是返回主机名,只不过,inventory_hostname只返回当前被操作的主机的主机名,而play_hosts则返回当前play中所有被操作主机的主机名列表。

内置变量groups

通过groups内置变量可以获取到清单中”所有分组”的”分组信息”,什么意思呢?我们先来看一个清单配置,假设我的清单配置如下:

10.1.1.60
test70.zsythink.net ansible_host=10.1.1.70
test71 anisble_host=10.1.1.71

[testA]
test60 ansible_host=10.1.1.60
test61 ansible_host=10.1.1.61

[testB]
test70 ansible_host=10.1.1.70

[test:children]
testA
testB

上述清单中,显式的指定了三个组,testA组、testB组、test组,其中,testA组与testB组是test组的子组,除了组中的主机,还有三台主机没有任何分组,直接写在了清单中。

现在,我们获取一下groups变量的值,看看会返回哪些信息,随便操作清单中的任意一台主机即可,示例如下

# ansible test70 -m debug -a "msg={{groups}}"
test70 | SUCCESS => {
    "changed": false, 
    "msg": {
        "all": [
            "10.1.1.60", 
            "test70.zsythink.net", 
            "test71", 
            "test60", 
            "test61", 
            "test70"
        ], 
        "test": [
            "test60", 
            "test61", 
            "test70"
        ], 
        "testA": [
            "test60", 
            "test61"
        ], 
        "testB": [
            "test70"
        ], 
        "ungrouped": [
            "10.1.1.60", 
            "test70.zsythink.net", 
            "test71"
        ]
    }
}

从上述返回信息可以看出,所有主机默认被分成了组名为”all”的组,testA组中有两台主机,testB组中有一台主机,由于testA组和testB组都属于test组的子组,所以testA组与testB组中的主机都属于test组,由于有三台主机在清单中并未分组,所以,ansible自动将没有分组的主机分到了名为”ungrouped”的组中,即组名为”未分组”的组。

我们还能够通过组名,获取到指定组的分组信息,假设,我想要获取到上例中test组中的主机名称,则可以使用如下方法。

# ansible test70 -m debug -a "msg={{groups.test}}"

当然,语法也可以改为如下

# ansible test70 -m debug -a "msg={{groups['test']}}"

聪明如你一定已经会举一反三了,所以,如果我们想要获取到所有未分组主机的主机名,则可以使用如下方法

# ansible test70 -m debug -a "msg={{groups.ungrouped}}"

内置变量group_names

见名知义,我们可以通过内置变量group_names获取到当前主机所在分组的组名,比如,我的清单配置如下

10.1.1.60
test70.zsythink.net ansible_host=10.1.1.70
test71 anisble_host=10.1.1.71

[testA]
test60 ansible_host=10.1.1.60
test61 ansible_host=10.1.1.61

[testB]
test70 ansible_host=10.1.1.70

[test:children]
testA
testB

那么,当我操作test70主机时,group_names变量值如下

# ansible test70 -m debug -a "msg={{group_names}}"
test70 | SUCCESS => {
    "changed": false, 
    "msg": [
        "test", 
        "testB"
    ]
}

如上例返回值所示,test70主机属于testB组,而testB组又是test组的子组,所以test70主机同时属于testB组和test组,所以,最终返回的信息中包括test与testB

当我们操作未分组的主机时,group_names的值为”ungrouped”,示例如下

# ansible 10.1.1.60 -m debug -a "msg={{group_names}}"
10.1.1.60 | SUCCESS => {
    "changed": false,
    "msg": [
        "ungrouped"
    ]
}

内置变量inventory_dir

我们可以通过inventory_dir变量获取到ansible主机中清单文件的存放路径,我使用的是默认的清单文件/etc/ansible/hosts,所以,inventory_dir变量对应的值为/etc/ansible,如下例所示

# ansible test71 -m debug -a "msg={{inventory_dir}}"
test71 | SUCCESS => {
    "changed": false, 
    "msg": "/etc/ansible"
}

ansible中还有其他的一些变量的使用方法,但是需要结合其他的一些知识点,所以之后遇到了实际的使用场景,我们再进行介绍吧。

这篇文章就先总结到这里,希望能够对你有所帮助。

ansible笔记(17):变量(四)

这篇文章继续总结一些ansible中使用变量的方法。

在清单中配置变量

在ansible系列文章的前几篇文章中,我们总结了ansible清单的配置方法,在清单中,可以配置需要被管理的远程主机,也可以将部分远程主机分为一组,其实,在配置清单时,还可以为主机或主机组设置变量,具体方法见如下总结

主机变量

在清单中配置远程主机时,可以同时为主机配置对应的变量,当操作这个主机时,即可直接使用对应的变量。

比如,我在/etc/ansible/hosts中定义test70主机时,可以为test70主机配置一个名为testhostvar的变量,变量值为test70_host_var,示例如下

test70 ansible_host=10.1.1.70 testhostvar=test70_host_var

如上例所示,只要在定义主机时将变量名和变量值写在主机配置的后面即可,可以为一个主机定义多个主机变量,用空格隔开即可,很方便吧,那么我们来测试一下,看看在操作test70主机时,能否引用到这个变量,为了方便示例就不编写playbook了,输入如下ad-hoc命令即可测试出效果

未分类

如上图所示,操作test70主机时,testhostvar已经被引用到了,当然,testhostvar是test70的主机变量,其他主机并不能引用到这个变量,主机变量的生效范围只限于对应的主机。

前文中总结过,配置清单时可以使用INI格式或者YAML格式的语法,刚才的示例为INI风格的语法配置,YAML格式的配置中,可以使用如下方法配置变量

all:
 hosts:
   test70:
     ansible_host: 10.1.1.70
     ansible_port: 22
     testhostvar: test70_host_var
     testhostvar1: test70_host_var1

如上例所示,我们为test70主机配置了两个变量,testhostvar和testhostvar1,没错,就是这么简单,直接在test70的下一级写明变量与变量值即可。

你也可以使用如下方法配置有”层级”的变量,如下

all:
 hosts:
   test70:
     ansible_host: 10.1.1.70
     ansible_port: 22
     testhostvar: test70_host_var
     testhostvar1: test70_host_var1
     testhostvar3:
       thv31: 3.1
       thv32: 3.2

根据前文的总结,聪明如你一定已经想到了,我们可以使用如下两种方法引用变量中的值

未分类

主机组变量

在清单中,我们能将多个主机分为一组,这样方便我们成批的操作远程主机。

比如,我在清单中将test70与test71分为一组,组名为testB,INI格式的配置如下

[testB]
test70 ansible_host=10.1.1.70
test71 anisble_host=10.1.1.71

如果我们想为testB组配置组变量,该怎么办呢?示例如下

[testB]
test70 ansible_host=10.1.1.70
test71 anisble_host=10.1.1.71

[testB:vars]
test_group_var1='group var test'
test_group_var2='group var test2'

如上例所示,”[testB:vars]”表示为testB组配置变量,上例中,testB组中一共定义了两个组变量,”test_group_var1″和”test_group_var2″

组变量的使用范围为组中的所有主机,上例中,无论test70还是test71,都可以使用到上述两个变量,效果如下。

未分类

上例为INI格式中配置组变量的方法,YAML格式中配置组变量的示例如下

all:
 children:
   testB:
     hosts:
       test70:
         ansible_host: 10.1.1.70
         ansible_port: 22
       test71:
         ansible_host: 10.1.1.71
         ansible_port: 22
     vars:
       test_group_var1: 'group var test1'
       test_group_var2: 'group var test2'

如上例所示,使用vars关键字可以指定组变量,vars关键字位于对应组的下一级,上例中,vars关键字位于testB的下一级,调用组变量的效果如下

未分类

通过set_fact定义变量

set_fact是一个模块,我们可以通过set_fact模块在tasks中定义变量,先来看一个小示例,如下

---
- hosts: test70
  remote_user: root
  tasks:
  - set_fact:
      testvar: "testtest"
  - debug:
      msg: "{{testvar}}"

如上例所示,我们通过set_fact模块定义了一个名为testvar的变量,变量值为testtest,然后使用debug模块输出了这个变量。

是不是很简单,通过set_fact模块就能够在tasks中定义变量了,我们也可以通过set_fact将一个变量的值赋予另一个变量,示例如下

---
- hosts: test70
  remote_user: root
  vars:
    testvar1: test1_string
  tasks:
  - shell: "echo test2_string"
    register: shellreturn
  - set_fact:
      testsf1: "{{testvar1}}"
      testsf2: "{{shellreturn.stdout}}"
  - debug:
      msg: "{{testsf1}} {{testsf2}}"

上例中,我们先定义了一个变量testvar1,又使用register将shell模块的返回值注册到了变量shellreturn中,

之后,使用set_fact模块将testvar1变量的值赋予了变量testsf1,将shellreturn变量中的stdout信息赋值给了testsf2变量,

最后,使用debug模块输出了testsf1与testsf2的值。

如上述示例所示,set_fact模块可以让我们在tasks中创建变量,也可以将一个变量的值赋值给另一个变量。

其实,通过set_fact模块创建的变量还有一个特殊性,通过set_fact创建的变量就像主机上的facts信息一样,可以在之后的play中被引用,什么意思呢?我们慢慢聊。

前文中已经总结过,默认情况下,每个play执行之前都会执行一个名为”[Gathering Facts]”的默认任务,这个任务会收集对应主机的相关信息,我们可以称这些信息为facts信息,我们已经总结过怎样通过变量引用这些facts信息,此处不再赘述,而通过set_fact模块创建的变量可以在之后play中被引用,就好像主机的facts信息可以在play中引用一样,这样说可能还是不是特别容易理解,不如来看一个小例子,如下

---
- hosts: test70
  remote_user: root
  vars:
    testvar1: tv1
  tasks:
  - set_fact:
      testvar2: tv2
  - debug:
      msg: "{{testvar1}} ----- {{testvar2}}"

- hosts: test70
  remote_user: root
  tasks:
  - name: other play get testvar2
    debug:
      msg: "{{testvar2}}"
  - name: other play get testvar1
    debug:
      msg: "{{testvar1}}"

上例中一共有两个play,第一个play中,我们通过两种方式创建了两个变量,第一个变量testvar1使用vas关键字创建,第二个变量使用set_fact创建。

如果执行上例的playbook,可以发现,这两个变量在第一个play中都可以正常的输出。但是在第二个play中,testvar2可以被正常输出了,testvar1却不能被正常输出,会出现未定义testvar1的错误,因为在第一个play中针对test70主机进行操作时,testvar1是通过vars关键字创建的,而testvar2是通过set_fact创建的,所以testvar2就好像test70的facts信息一样,可以在第二个play中引用到,而创建testvar1变量的方式则不能达到这种效果,虽然testvar2就像facts信息一样能被之后的play引用,但是在facts信息中并不能找到testvar2,只是”效果上”与facts信息相同罢了。

前文已经总结了注册变量的用法,其实注册变量也可以在之后的play操作同一主机时被调用到,示例如下

---
- hosts: test70
  remote_user: root
  vars:
    testvar3: tv3
  tasks:
  - shell: "echo tv4"
    register: testvar4
  - debug:
      msg: "{{testvar3}} -- {{testvar4.stdout}}"

- hosts: test70
  remote_user: root
  tasks:
  - name: other play get testvar4
    debug:
      msg: "{{testvar4.stdout}}"
  - name: other play get testvar3
    debug:
      msg: "{{testvar3}}"

执行上例的playbook时,在第二个play中获取”testvar3″时会报错,而在第二个play中获取注册变量”testvar4″时则正常,但是,注册变量中的信息是模块的返回值,这并不是我们自定义的信息,所以,如果想要在tasks中给变量自定义信息,并且在之后的play操作同一个主机时能够使用到之前在tasks中定义的变量时,则可以使用set_facts定义对应的变量。

细心如你一定发现了,上述示例中,即使是跨play获取变量,也都是针对同一台主机,但是某些时候,我们可能想要在操作一台主机时,获取到之前操作的另一台主机中定义的变量,那么该怎样做呢?具体做法就放在下一篇文章中总结吧,这篇文章就先总结到这里,希望能够对你有所帮助。

ansible笔记(16):变量(三)

承接前文,这篇文章将继续介绍变量的一些使用方法。

注册变量

ansible的模块在运行之后,其实都会返回一些”返回值”,只是默认情况下,这些”返回值”并不会显示而已,我们可以把这些返回值写入到某个变量中,这样我们就能够通过引用对应的变量从而获取到这些返回值了,这种将模块的返回值写入到变量中的方法被称为”注册变量”,那么怎样将返回值注册到变量中呢?我们来看一个playbook示例

---
- hosts: test70
  remote_user: root
  tasks:
  - name: test shell
    shell: "echo test > /var/testshellfile"
    register: testvar
  - name: shell module return values
    debug:
      var: testvar

上例中共有两个任务,第一个任务使用shell模块在test70主机中创建了一个测试文件 /var/testshellfile,将字符”test”输入到了测试文件中,然后使用”register”关键字将当前shell任务的返回值写入了名为”testvar”的变量中,第二个任务使用debug模块输出了第一个任务中的注册变量的值,没错,注册变量就是这么简单,使用register关键字指定对应的变量名即可。

上述playbook执行后,可以在控制台中看到名为”[shell module return values]”的任务中已经显示了第一个任务的返回值的信息,返回信息如下

TASK [shell module return values] **********************************************************************
ok: [test70] => {
    "testvar": {
        "changed": true, 
        "cmd": "echo test > /var/testshellfile", 
        "delta": "0:00:00.003808", 
        "end": "2018-06-17 20:42:37.675382", 
        "failed": false, 
        "rc": 0, 
        "start": "2018-06-17 20:42:37.671574", 
        "stderr": "", 
        "stderr_lines": [], 
        "stdout": "", 
        "stdout_lines": []
    }
}

从上述返回信息可以看出,返回值是json格式的,上述返回值中包含一些键值对,比如 “changed”: true 或 “cmd”: “echo test > /var/testshellfile”等, 如果你只是想要获取到返回值中的某一项特定值,只需要指定键值对中的key即可,假设,我只是想要获取到上述返回信息中cmd的值,则可以使用如下两种语法(前文中已经对如下两种语法进行过示例,此处不再赘述)。

语法一
  - name: shell module return values
    debug:
      msg: "{{testvar.cmd}}"
语法二
  - name: shell module return values
    debug:
      msg: "{{testvar['cmd']}}"

上述示例的返回信息为shell模块的返回值,如果你想要了解返回值中每一项的含义,则可以查看官方手册,我使用的是2.4版本的ansible,所以我可以参考2.4版本的官网文档,找到shell模块的介绍,官网链接如下

https://docs.ansible.com/ansible/2.4/shell_module.html

不同的模块,返回值也不尽相同,ansible官网对一些常见的返回值进行了总结,链接如下

https://docs.ansible.com/ansible/2.4/common_return_values.html

如果你想要查看模块对应的返回值,可以先查找官方手册,但是,并不是所有模块的官方手册中都对模块的返回值进行了描述,你可以使用上述示例中的方法,自己查看模块的返回值,这些返回值不仅仅能够用于输出,通常我们会利用到这些返回值,比如,通过模块的返回值决定之后的一些动作,所以,注册变量在playbook中还是会被经常用到的,在之后的文章中我们会给出示例,此处不用纠结。

提示用户输入信息并写入变量

在运行某些脚本时,有时候脚本会提示用户输入一些信息,脚本需要根据用户输入的信息决定下一步的动作,这种”交互”有时候是必须的,那么,在playbook中该怎样实现这种交互呢?我们可以这样做,提示用户输入信息,然后将用户输入的信息存入到指定的变量中,当我们需要使用这些”输入的信息”时,只要引用对应的变量即可。

我们来看一个小示例,如下

---
- hosts: test70
  remote_user: root
  vars_prompt:
    - name: "your_name"
      prompt: "What is your name"
    - name: "your_age"
      prompt: "How old are you"
  tasks:
   - name: output vars
     debug:
      msg: Your name is {{your_name}},You are {{your_age}} years old.

如上例所示,我们使用”vars_prompt”关键字创建了两个变量,这两个变量的名称分别为”your_name” 和 “your_age”,当运行上例playbook时,会出现 “What is your name”的提示信息,然后用户输入的信息会存入到”your_name”变量中,之后,会出现 “How old are you”的提示信息,用户输入的信息会存入到”your_age”变量中,上例中的”output vars”任务会输出一句话,这句话中包含了上述两个变量的值,我们来看一下上例的执行效果。

未分类

如上图所示,运行playbook时会提示输入你的名字,输入你的年龄,你输入的内容并不会显示在屏幕上,在完成提示输入的内容后,在”output vars”任务的输出中可以看到用户输入的名字和年龄。

如你所见,当你使用这种方式提示用户时,默认情况下不会显示用户输入的信息,这种方式比较适合用户输入密码时的场景,如果你想要显示用户输入的信息,可以使用如下示例中的方法。

  vars_prompt:
    - name: "your_name"
      prompt: "What is your name"
      private: no
    - name: "your_age"
      prompt: "How old are you"
      private: no

如上例所示,我们在定义” vars_prompt”中的变量时,使用private关键字,将变量的private属性设置为no即可, “private: no”表示变量值为非私有的,可见的,默认情况下 private值为yes,表示不可见。

我们还能为提示信息设置默认值,即如果用户不输入任何信息,则将默认值赋予变量,示例playbook如下。

---
- hosts: test70
  remote_user: root
  vars_prompt:
    - name: "solution"
      prompt: "Choose the solution you want n
      A: solutionAn
      B: solutionBn
      C: solutionCn"
      private: no
      default: A
  tasks:
   - name: output vars
     debug:
      msg: The final solution is {{solution}}.

如上例所示,我们使用了default关键字设置了”solution”变量的默认值,如果用户没有输入任何值(直接回车),则将”solution”变量的值设置为A,如果用户输入了值,则”solution”变量值为用户输入的值。

之前的示例中,我们提到可以利用提示信息让用户设置密码,有了这项功能,我们就可以编写出一个playbook,这个playbook可以让用户手动输入用户名和密码,然后根据用户输入的信息去创建系统用户了,聪明如你一定想到了,创建系统用户可以使用user模块,前文已经总结过user模块,此处不再赘述,那么我们来尝试编写一个可交互创建系统用户的playbook吧,经过思考,我编写了如下playbook,你可以帮我看看如下playbook中存在什么问题。

---
- hosts: test70
  remote_user: root
  vars_prompt:
    - name: "user_name"
      prompt: "Enter user name"
      private: no
    - name: "user_password"
      prompt: "Enter user password"
  tasks:
   - name: create user
     user:
      name: "{{user_name}}"
      password: "{{user_password}}"

上例的playbook似乎没有什么不妥,但是细心如你一定发现了,user模块的password参数虽然可以指定用户的密码,但是password参数对应的值必须是一个”明文密码哈希过后的字符串”(如果你不明白我在说什么,可以参考之前文章中总结的user模块的使用方法),而上例中,用户经过提示后输入的密码字符串并未经过哈希操作,所以,即使通过上述playbook可以创建用户,创建后的用户也无法通过设置的密码进行登录,因为保存在/etc/shadow文件中的密码字段是一个未哈希的明文的密码字段。那么,我们该怎么办呢?没错,我们需要对用户输入的密码字符串进行哈希,然后将哈希过后的字符串传入user模块的password参数中,ansible已经为我们考虑到了这一点,我们可以使用”encrypt”关键字,对用户输入的字符串进行哈希,用户输入的信息被哈希以后会存入对应的变量中,示例如下:

---
- hosts: test70
  remote_user: root
  vars_prompt:
    - name: "hash_string"
      prompt: "Enter something"
      private: no
      encrypt: "sha512_crypt"
  tasks:
   - name: Output the string after hash
     debug:
      msg: "{{hash_string}}"

如上例所示(先不要着急运行上述playbook),encrypt关键字表示对用户输入的信息进行哈希,encrypt: “sha512_crypt”表示使用sha512算法对用户输入的信息进行哈希,哈希后的字符串会存入到上例中的”hash_string”变量中,利用encrypt关键字,就可以解决之前遇到的创建用户时指定密码字符串的问题,但是需要注意,当使用”encrypt”关键字对字符串进行哈希时,ansible需要依赖passlib库完成哈希操作,如果未安装passlib库(一个用于哈希明文密码的python库),执行playbook时会报如下错误

ERROR! passlib must be installed to encrypt vars_prompt values

我的ansible主机的操作系统为centos7.4,默认自带python2.7.5,为了能够正常执行上述playbook,需要先安装passlib库。

此处通过pip安装passlib库,由于当前主机也没有安装pip,所以先下载安装pip

# tar -xvf pip-10.0.1.tar.gz
# cd pip-10.0.1/
# python setup.py install

pip安装完成后,通过pip安装passlib库

# pip install passlib

passlib库安装完成后,执行上例中的剧本,可以看到,你输入的信息在经过哈希以后被输出了,当然,上例中我们指定了使用sha512算法对字符串进行哈希,你也可以指定其他passlib库支持的算法,算法名称可以参考如下连接

https://docs.ansible.com/ansible/2.4/playbooks_prompts.html

除了能够使用”encrypt”关键字对字符串进行哈希加密,还能够使用”confirm”关键字实现类似确认密码的功能,我们在为用户设置密码时,通常需要输入两次完全相同的密码,才能够设置成功,通过”confirm”关键字就能实现类似的效果,示例playbook如下

---
- hosts: test70
  remote_user: root
  vars_prompt:
    - name: "user_name"
      prompt: "Enter user name"
      private: no
    - name: "user_password"
      prompt: "Enter user password"
      encrypt: "sha512_crypt"
      confirm: yes
  tasks:
   - name: create user
     user:
      name: "{{user_name}}"
      password: "{{user_password}}"

具体的执行效果我就不截图了,动手试试吧。

通过命令行传入变量

除了之前总结过的定义变量的方法,我们还能够在执行playbook时直接传入需要使用的变量,我们来看一小示例,如下:

---
- hosts: test70
  remote_user: root
  tasks:
  - name: "Passing Variables On The Command Line"
    debug:
      msg: "{{pass_var}}"

上例中的playbook中,并没有定义pass_var变量,而是直接引用了pass_var变量,我们可以在调用上述playbook时直接从命令行传入pass_var变量,方法如下

ansible-playbook cmdvar.yml --extra-vars "pass_var=cmdline pass var"

如上例所示,在调用playbook时使用 “–extra-vars” 选项可以传递对应的变量与变量值, “–extra-vars” 是长选项,对应的短选项是”-e”,我们也可以一次性传入多个变量,变量之间用空格隔开,如下

ansible-playbook cmdvar.yml -e 'pass_var="test" pass_var1="test1"'

上例中的playbook中并没有定义pass_var变量,如果在调用playbook时也没有传入pass_var变量,则会报错,其实,我们也可以先在playbook中定义好变量,然后在执行playbook时,再次传入相同名称的变量,最终还是以传入的变量值为准,示例如下

---
- hosts: test70
  remote_user: root
  vars:
    pass_var: test_default
  tasks:
  - name: "Passing Variables On The Command Line"
    debug:
      msg: "{{pass_var}}"

上例的playbook中定义了pass_var变量,其值为”test_default”,在执行上述playbook时,从命令行再次传入pass_var变量,命令如下

ansible-playbook cmdvar.yml -e 'pass_var="test"'

执行上述命令后,你会发现,最终输出的值为”test”而非”test_default”,也就是说,命令行传入的变量的优先级要高于playbook中的变量,通过这种方法,我们就能够更加灵活的指定变量的值了。

不仅ansible-playbook命令可以使用”-e”传递变量,ansible命令也同样可以,所以在执行ad-hoc命令时也可以使用同样的方法传入变量,如下

ansible test70 -e "testvar=test" -m shell -a "echo {{testvar}}"

上述的几个示例从命令行中传递变量时,都是使用了”key=value”的形式,除了使用”key=value”的方式传递变量,ansible还支持通过json的格式传入变量,示例如下

通过json格式传入两个变量

ansible-playbook cmdvar.yml -e '{"testvar":"test","testvar1":"test1"}'

通过json格式传入稍微复杂一点的变量

ansible-playbook cmdvar.yml -e '{"countlist":["one","two","three","four"]}'

在剧本中引用上述命令传入的countlist变量时,如果想要获取到值”one”,则可以使用如下两种语法引用变量

{{countlist[0]}} 或者 {{countlist.0}}

命令行不仅能够传入变量,还能传入变量文件,变量文件中的变量都会一并被传入,变量文件可以是json格式的,也可以是YAML格式的,此处使用YAML格式的变量文件进行示例,示例文件内容如下

# cat /testdir/ansible/testvar
testvar: testvarinfile
countlist:
- one
- two
- three
- four

测试用playbook内容如下

---
- hosts: test70
  remote_user: root
  tasks:
  - name: "Passing Variables On The Command Line"
    debug:
      msg: "{{testvar}} {{countlist[0]}}"

如playbook所示,playbook中引用了变量文件中定义的两个变量,那么,我们怎样从命令行中将变量文件中的变量传入playbook呢?示例如下

ansible-playbook cmdvar.yml -e "@/testdir/ansible/testvar"

如上述命令所示,使用”@”符号加上变量文件的路径,即可在命令行中传入对应的变量文件,变量文件中的所有变量都可以在playbook中引用,还是很方便的吧。

好了,这篇文章就总结到这里,希望能够对你有所帮助。