ansible报错

报错:

[root@jenkins ~]# ansible go_activity -m cron -a "name='log_clear' minute=0 hour=2 job=find /home/golanger/log/ -type f -name 'log$(date +%d -d -1day)' -delete"

    ERROR! this task 'cron' has extra params, which is only allowed in the following modules: command, win_command, shell, win_shell, script, include, include_vars, add_host, group_by, set_fact, raw, meta

解决:

这个需要在job上加引号,另外如下,如果有一些特殊符号的话,需要转义

[root@jenkins ~]# ansible go_activity -m cron -a "name=log_clear minute=0 hour=2 job='find /home/golanger/log/ -type f -name "log$(date +%d -d -1day)" -delete'"

虽然加了计划任务,但是还是需要删除下今天的日志,手动执行

[root@jenkins ~]# ansible go_weiai_project -m shell -a "find 
/home/golanger/log/ -type f -name "log$(date +%d -d -1day)" -delete"

自动化运维之Ansible服务部署详述

一、概述分析

由于互联网的快速发展导致产品更新换代速度逐渐加快,运维人员每天都要进行大量的维护操作,仍旧按照传统方式进行维护会使得工作效率低下。这时,部署自动化运维就可以尽可能安全、高效地完成这些工作。
一般会把自动化运维工具划分为两类:一类是需要使用代理工具的,也就是基于专用的ABem程序来完成管理功能,如: Puppet、Func、 Zabbix等;另外一类是不需要配置代理工具的,可以直接基于SSH服务来完成管理功能,如: Ansible、 Fabric等。-

下面介绍几款功能类似的自动化运维工具:

1. Puppet

Pup基于Rpy开发,支持Linx、UNDX、 Windows平台,可以针对用户、系统服务配置文件、软件包等进行管理,有很强的扩展性,但远程执行命令相对较弱。

2. SaltStack

CallStack基于 Python开发,允许管理员对多个操作系统创建统一的管理系统,比pet更轻量级

工具       开发语言   结构   配置文件格式     运行任务
Ansible    Python    无     YAML            支持命令行
SaltStack  Python    C/S    YAML            支持命令行
Puppet     Ruby      C/S    Ruby语法格式     通过模块实现

3. Ansible

Ansible基于 Python开发,集合了众多优秀运维工具的优点,实现了批量运行命令部署程序、配置系统等功能。默认通过SSH协议进行远程命令执行或下发配置,无需部署任何客户端代理软件,从而使得自动化环境部署变得更加简单。可同时支持多台主机并行管理,使得管理主机更加便捷。

官方的title是“Ansible is Simple IT Automation”——简单的自动化IT工具。
Ansible通过SSH协议实现远程节点和管理节点之间的通信。理论上说,只要管理员通过ssh登录到一台远程主机上能做的操作,Ansible都可以做到。

Ansible跟其他IT自动化技术的区别在于其关注点并非配置管理、应用部署或IT流程工作流,而是提供一个统一的界面来协调所有的IT自动化功能,因此Ansible的系统更加易用,部署更快。
Ansible可以让用户避免编写脚本或代码来管理应用,同时还能搭建工作流实现IT任务的自动化执行。IT自动化可以降低技术门槛及对传统IT的依赖,从而加快项目的交付速度。

未分类

Ansible基本架构由六个部分组成:

  • Ansible core 核心引擎。
  • Host inventory 主机清单:用来定义Ansible 所管理的主机,默认是在Ansible的host配置文件中定义被管理主机,同时也支持自定义动态主机清单和指定其他配置文件的位置。
  • Connection plugins连接插件:负责和被管理主机实现通信。除支持使用ssh连接被管理主机外, Ansible还支持其他的连接方式,所以需要有连接插件将各个主机用连接插件连接到 Ansible。
  • Playbooks(yaml, injaz2)剧本:用来集中定义 Ansible任务的配置文件,即将多个任务定义在一个剧本中由 Ansible自动执行,可以由控制主机针对多台被管理主机同时运行多个任务。
  • Core modules核心模块:是 Ansible自带的模块,使用这些模块将资源分发到被管理主机,使其执行特定任务或匹配特定的状态。
  • Custom modules自定义模块:用于完成模块功能的补充,可借助相关插件完成记录日志、发送邮件等功能。

ansible功能特性:

  • 应用代码自动化部署
  • 系统管理配置自动化
  • 支持持续交付自动化
  • 支持云计算,大数据平台环境
  • 轻量级,无序在客户端安装agent,更新时只需在控制机上进行更行即可
  • 批量任务执行可以写成脚本,不用分发到远程就可以执行
  • 支持非root用户管理操作,支持sudo
  • 使用python编写,维护更简单

二、Ansible安装

Ansible 自动化运维环境由控制主机与被管理主机组成,由于Ansible是基于SSH协议进行通信的,所以控制主机安装Ansible软件后不需要重启或运行任何程序,被管理主机也不需要安装和运行任何代理程序。

实验安装环境:

角色        主机名    IP地址             组名
控制主机     01       192.168.100.129    
被管理主机   02       192.168.100.128    webserver
被管理主机   03       192.168.100.130    mysql

三台主机关闭防火墙:

[root@localhost ~]# systemctl stop firewalld.service
[root@localhost ~]# setenforce 0

安装步骤:

控制主机安装ansible并生成密钥对批量发送给被管理主机

1.yum安装环境包与ansible:

yum install epel-release -y
yum install ansible –y

2.查看ansible版本

[root@01 ~]# ansible  --version

未分类

3.yum安装完成后会生成3个文件

[root@01 ~]# cd /etc/ansible/
[root@01 ansible]# ls

未分类

4.配置被管理端主机IP清单

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

未分类

5.虽然ansible的配置文件已经设置完成被管理端的IP地址,但是因为ansible是基于ssh协议,所以还需要配置密钥对验证

[root@01 ~]# ssh-keygen -t rsa           //生成密钥对

未分类

未分类

6.ssh协议免交互代理

[root@01 ~]# ssh-agent bash
[root@01 ~]# ssh-add

未分类

shell脚本批量发送公钥

(1).下载安装expect

[root@01 .ssh]# yum install expect -y                   //yum安装expect

(2). ping通所有可互通的主机

[root@01 .ssh]# ansible all -m ping       //使用ansible中的ping模块

未分类

ansible是基于SSH协议,所以可以ping通的主机储存在.ssh/known_hosts的文件当中。当然就算不ping通也可以用shell脚本实现批量推送公钥。

在最新版本ansible 2.7.0中,在没有推送公钥形成密钥对的情况下,无法使用ping模块ping通的情况下,很难用authorized_key模块去推送公钥的。所以我更改了下shell脚本,这样就可以在无法用ping模块ping通的情况下直接实现批量推送公钥形成密钥对。

[root@01 ~]# cd ~/.ssh/
[root@01 .ssh]# ls
id_rsa  id_rsa.pub  known_hosts
[root@01 .ssh]# vim known_hosts                   //查看下已经记录在SSH协议的主机,不做任何修改操作

未分类

(2).编写shell脚本实现批量推送公钥

[root@01 .ssh]# vim ~/.ssh/pushssh.sh 

脚本如下:

#!/bin/sh
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
host1=`cat /etc/ansible/hosts | awk -F " " '{print $1}' | grep '^192'`
#在生产情况中,有很多种获得IP的方法,本脚本最重要的就是获得IP地址,脚本只是提供一个思路。

for i in $host1;

do

command1="scp ~/.ssh/authorized_keys root@$i:~/.ssh/authorized_keys"

password="123123" 

/usr/bin/expect -c "
        spawn ssh-copy-id root@$i 
        expect {
        "*password" { send "$passwordr"; exp_continue }
        }     
expect eof"

done

#编写脚本完成后保存退出

[root@01 .ssh]# sh pushssh.sh        //执行脚本

PS:想要执行这个脚本,首先需要下载安装expect,同时被管理端主机的密码需要是一致的。

———————-验证——————-

查看下脚本是否执行成功:

未分类

未分类

此时就可以进行ansible批量部署操作

[root@01 ~]# ansible all -m command -a 'date'

未分类

使用Ansible添加Grafana数据源的方法

本文向你展示如何在不使用Grafana Web界面的情况下轻松地向Grafana添加数据源。Grafana支持的数据源有:Graphite、Elasticsearch、CloudWatch、InfluxDB、OpenTSDB、Prometheus、MySQL、Postgres、Microsoft SQL Server (MSSQL)。每个数据源都有一个特定的查询编辑器,该编辑器针对特定数据源公开的特性和功能进行了自定义。安装Grafana请参考在Ubuntu 18.04/Debian 9上安装Grafana的方法

一、在Linux上安装Ansible

你需要在Linux系统上安装和使用ansible才能使用此方法,参考在Ubuntu 18.04系统中安装Ansible 2.7.5的方法

你也可以使用python pip包管理器在任何Linux上轻松安装ansible,pip是一个包管理系统,用于安装和管理用Python编写的软件包

在Ubuntu/Debian上安装pip:

sudo apt-get -y install python-pip

在CentOS上安装pip:

sudo yum -y install python-pip

在Arch Linux上安装pip:

sudo paman -S python-pip

安装pip后,将其升级到最新版本:

sudo pip install --upgrade pip

同时在安装Pip后可以参考在RHEL 8/CentOS 8系统上安装和配置Ansible一文来完成安装。

二、设置Ansible环境

创建ansible基目录:

mkdir -p ~/ansible

切换到ansible基目录并创建一个目录来存储所有Ansible角色:

cd ~/ansible

mkdir roles

在roles目录下,我们将有tasks和defaults变量文件夹:

mkdir -p roles/grafana-datasource/{tasks,defaults]

三、定义Ansible变量

我们用于向Grafana添加数据源的变量将在fileroles/defaults/main.yml上定义,在此示例中,我们将向Grafana添加InfluxDB数据源,下面是我们的/defaults/main.yml,我将稍微解释一下它的内容:

$ cat defaults/main.yml

---

grafana_url: "http://192.168.50.3:3000"

grafana_user: admin

grafana_password: "GrafanaAdminPassword"

org_id: "1"

data_source:

- name: ldap.example.com

ds_type: "influxdb"

url: "http://192.168.50.4:8086"

user: "influx_user"

password: "StrongPassword"

解释如下:

http://192.168.50.3:3000是grafana的URL,它在默认端口3000上运行。

Grafana管理员用户是admin,密码为GrafanaAdminPassword。

要添加的数据源名为ldap.example.com。

数据源类型是Influxdb。

http://192.168.50.4:8086是InfluxDB服务器的URL。

对于具有身份验证的InfluxDB(推荐),分别定义用户名和密码:Influx_user和StrongPassword。

请记住用正确的值替换值。

安装InfluxDB请参考在Ubuntu 18.04/Debian 9系统上安装InfluxDB的方法

四、创建Ansible任务

如果定义了用于创建数据源的所有变量,请继续创建任务:

$ cat tasks/main.yml 

---

- name: Create influxdb datasource

grafana_datasource:

name: "{{ item.name }}"

grafana_url: "{{ grafana_url }}"

grafana_user: "{{ grafana_user }}"

grafana_password: "{{ grafana_password }}"

ds_type: "{{ item.ds_type }}"

url: "{{ item.url }}"

database: "{{ item.name }}"

user: "{{ item.user }}"

password: "{{ item.password }}"

state: present

with_items: "{{ data_source }}"

filedefaults/main.yml上定义的任务引用值。

五、运行Ansible Playbook

切换到root ansible目录并创建playbook执行文件:

cd ~/ansible/

创建一个包含以下内容的文件:

$ cat grafana-datasource.yml 

---

- name: Add data source to grafana

hosts: localhost

roles:

- grafana-datasource

最后,通过运行执行playbook:

$ ansible-playbook grafana-datasource.yml

输出如下信息:

# ansible-playbook grafana-datasource.yml

PLAY [Add data source to grafana] *********************************

TASK [Gathering Facts] *********************************

ok: [localhost]

TASK [grafana-datasource : Create influxdb datasource] *********************************

changed: [localhost] => (item={u'url': u'http://192.168.50.4:8086', u'password': u'StrongPassword', u'ds_type': u'influxdb', u'name': u'ldap.example.com', u'user': u'influx_user'})

PLAY RECAP *********************************

localhost                  : ok=2    changed=1    unreachable=0    failed=0

到这里就可以在sectionData Sources下确认Grafana上的数据源:

未分类

你现在就可以通过仅为所有InfluxDB数据源编辑名称来添加许多数据源了。

CENTOS 使用ANSIBLE 将PYTHON 包安装到VIRTUALENV环境中

使用root用户,则直接安装

pip: name=pkgname virtualenv=虚拟环境目录

如果以!root用户安装,ansible无法获取virtualenv可执行文件,需要手动将执行路径添加到PATH环境变量,在用户家目录的.local/bin目录下

environment:
      PATH: "{{ansible_env.PATH}}/{{ansible_user_dir}}/.local/bin"

完整实例:

tasks:
  - name: install pip packages
    pip: name={{item}} virtualenv=envdir
    with_items:
      - requests
      - flask
    environment:
      PATH: "{{ansible_env.PATH}}/{{ansible_user_dir}}/.local/bin"

在docker中使用ansible来源码编译nginx服务

说明:
1)在VM上装了一个4核8G的centos7.5系统
2)docker版本为 18.06.0-ce

docker的安装不再讲述

1、创建一个带有含有ssh的镜像,通过编写Dockerfile

# Set the base image to centos
FROM centos:latest
MAINTAINER fei
#mount volume
VOLUME ["/root/docker/ansible-demo/volume2"]
################## BEGIN INSTALLATION ######################
#install EPEL
RUN rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm 
&& rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 
&& yum install -y yum-priorities
RUN yum install -y sudo
RUN yum install -y 
net-tools 
openssh-clients 
openssh-server 
ansible 
vim
################## END INSTALLATION ######################
# 将sshd的UsePAM参数设置成no,优化ssh连接
RUN sed -i 's/UsePAM yes/UsePAM no/g' /etc/ssh/sshd_config
# 修改root用户密码,这里密码为:devilf
RUN echo "root:devilf"|chpasswd
RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
# 启动sshd服务并且暴露22端口
RUN mkdir /var/run/sshd
EXPOSE 22
ENTRYPOINT ["/usr/sbin/sshd","-D"]

2、开始构建镜像

docker build --no-cache -t fei/centos:ssh_ansible .

3、启动容器(需要开启特权模式,否则会报错:Failed to get D-Bus connection: Operation not permitted)

docker run -itd -p 20021:22 --privileged=true --name node1 fei/centos:ssh_ansible
docker run -itd -p 20022:22 --privileged=true --name node2 fei/centos:ssh_ansible
...
...
docker run -itd -p 20020:22 --privileged=true --name ansible_server fei/centos:ssh_ansible

4、配置ansible主机清单,并建立互信关系

修改ansible.cfg文件,将默认的hosts文件改为一个目录,修改为:
inventory     = /etc/ansible/conf.d

设置清单

# cat conf.d/docker
[nodes]
172.17.0.2
172.17.0.3
172.17.0.4
172.17.0.5

生成密钥

ssh-keygen

下发密钥

ssh-copy-id root@172.17.0.2

5、测试

ansible nodes -m ping

注意:
查看容器IP的方法:

docker inspect --format '{{ .NetworkSettings.IPAddress }}' container_id

停止容器:

docker stop container_id

删除容器:

docker container rm container_id

下面就要开始通过playbook来源码编译安装nginx

可以针对所有的服务安装创建一个专门的目录,例如这里安装nginx,可以创建一个目录,目录结构为:

tree roles/
roles/
├── conf
│   ├── default
│   ├── files
│   ├── handlers
│   │   └── main.yml
│   ├── meta
│   ├── tasks
│   │   └── main.yml
│   ├── templates
│   │   └── temp_server.conf
│   └── vars
│       └── main.yml
├── install
│   ├── default
│   ├── files
│   │   └── nginx-1.12.0.tar.gz
│   ├── handlers
│   │   └── main.yml
│   ├── meta
│   ├── tasks
│   │   └── main.yml
│   ├── templates
│   │   ├── nginx.conf
│   │   ├── web1.conf
│   │   └── web2.conf
│   └── vars
│       └── main.yml
├── nginx.retry
├── nginx.yaml
└── site.yml

分为两部分,conf目录主要是方便增加站点,存放配置文件;install目录主要是为了安装nginx,该目录下会存放安装所用的源码包,配置文件等
install目录下定义一个任务:

# cat tasks/main.yml
- name: cp nginx package to remote host
  copy: src=nginx-1.12.0.tar.gz dest=/tmp/nginx-1.12.0.tar.gz   #去files目录中拉取源码包
  tags: cp-nginx-pkg
- name: tar nginx package
  shell: cd /tmp; tar zxf nginx-1.12.0.tar.gz
- name: install nginx depend pkg
  yum: name={{ item }} state=latest       #item是一个变量,用来指定下面的一些依赖包名
  with_items:
    - openssl-devel
    - pcre-devel
    - gcc
    - gcc-c++
    - autoconf
    - automake
    - libtool
    - make
    - cmake
    - zlib
    - zlib-devel
    - openssl
    - pcre-devel
    - libxslt-devel
- name: install nginx
  shell: cd /tmp/nginx-1.12.0; ./configure --user=www --group=www --prefix=/usr/local/nginx 
         --with-http_stub_status_module 
         --with-http_ssl_module 
         --with-pcre && make && make install
- name: cp conf
  template: src=nginx.conf dest=/usr/local/nginx/conf/nginx.conf     #这个是去templates目录中拉取配置文件
  tags: nginx-conf
- name: cp shell
  copy: src=/ansible/script/create_users.sh dest=/tmp/create_users.sh    #这个脚本的目的是检测目标机器是否已经存在所建的用户,如果存在机会创建用户会报错
- name: create nginx user
  shell: /bin/bash /tmp/create_users.sh
  tags: add-nginx
  notify: start nginx service

上面脚本内容:

# cat /ansible/script/create_users.sh
#!/bin/bash
name="www"
num=$(grep -c $name /etc/passwd)
if [ $num -eq 0 ];then
        groupadd $name
        useradd -g $name $name -s /sbin/nologin
fi

给nginx的主配置文件指定一个端口,通过设置一个变量,后面主配置里面会去引用

# cat vars/main.yml
ngxport: "8080"

主配置文件

# cat templates/nginx.conf
user  www;
worker_processes  {{ ansible_processor_vcpus }};

events {
    worker_connections  65535;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    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 logs/access.log  main;

    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       {{ ngxport }};  
        server_name  www.a.com;
        access_log logs/a.com;

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
   include conf.d/*.conf;
}

定义触发器

# cat handlers/main.yml
- name: start nginx service
  shell: /usr/local/nginx/sbin/nginx

我们这里要新增一个站点做测试,需要修改的目录(需要切换到conf目录中)有:
定义变量,用于配置文件的引用:

# cat vars/main.yml
server_name: "www.a.com"    #每次新增站点时,可以修改此域名
root_dir: "/data/web"

因为新增站点时,是基于域名的虚拟主机,所以端口均为默认的80端口
编写新增站的配置文件:

# cat templates/temp_server.conf
server
{
listen 80;
server_name {{server_name}};
index index.php index.html;
root {{root_dir}};
}

在var目录中定义变量:

cat main.yml
server_name: "www.a.com"
root_dir: "/data/web"

编写配置nginx的tasks步骤哦:

cd tasks
cat main.yml
- name: create vhosts
  shell: mkdir -p /usr/local/nginx/conf/conf.d/
  tags: create_dir
- name: cp file nginx.conf
  template: src=temp_server.conf dest=/usr/local/nginx/conf/conf.d/{{server_name}}.conf
  tags: ngxconf
  notify: reload nginx service

定义角色路径

#回到roles的上级目录下
cat nginx.yaml
- hosts: web1
  remote_user: root
  roles:
    - install
    - conf

测试:

ansible-playbook -C nginx.yaml

测试通过后可以真正去执行

ansible-playbook nginx.yaml

在虚拟机中快速搭建 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 { acassen@firewall.loc failover@firewall.loc sysadmin@firewall.loc } notification_email_from Alexandre.Cassen@firewall.loc 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对齐合并,会是什么效果呢?

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

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