Ansible服务部署与使用-Ansible使用前提

第1章 SSH+Key实现基于密钥连接(Ansible使用前提)

说明:

Ansible其功能实现基于SSH远程连接服务

使用Ansible需要首先实现SSH密钥连接

1.1 部署SSH Key

1.1.1 第一个里程碑: 创建密钥对

ssh-keygen

-t 指定密钥类型 rsa1 dsa(常用) ecdsa

语法:

SYNOPSIS
     ssh-keygen [-q] [-b bits] -t type [-N new_passphrase] [-C comment]
                [-f output_keyfile]
     ssh-keygen -p [-P old_passphrase] [-N new_passphrase] [-f keyfile]
     ssh-keygen -i [-f input_keyfile]
     ssh-keygen -e [-f input_keyfile]
     ssh-keygen -y [-f input_keyfile]
     ssh-keygen -c [-P passphrase] [-C comment] [-f keyfile]
     ssh-keygen -l [-f input_keyfile]
     ssh-keygen -B [-f input_keyfile]
     ssh-keygen -D pkcs11
     ssh-keygen -F hostname [-f known_hosts_file] [-l]
     ssh-keygen -H [-f known_hosts_file]
     ssh-keygen -R hostname [-f known_hosts_file]
     ssh-keygen -r hostname [-f input_keyfile] [-g]
     ssh-keygen -G output_file [-v] [-b bits] [-M memory] [-S start_point]
     ssh-keygen -T output_file -f input_file [-v] [-a num_trials]
                [-W generator]
     ssh-keygen [-n] [-D smartcard]
     ssh-keygen -s ca_key -I certificate_identity [-h] [-Z principals]
                [-O option] [-V validity_interval] [-z serial_number] file ...
     ssh-keygen -L [-f input_keyfile]

创建密钥的过程

[root@m01 ~]# ssh-keygen -t dsa
Generating public/private dsa key pair.
Enter file in which to save the key (/root/.ssh/id_dsa):  #私钥创建后保存的路径
Created directory '/root/.ssh'.
Enter passphrase (empty for no passphrase):             #私钥需不需进行加密,设置密码
Enter same passphrase again:   #私钥需不需进行加密,再次输入密码确认
Your identification has been saved in /root/.ssh/id_dsa.
Your public key has been saved in /root/.ssh/id_dsa.pub.
The key fingerprint is:
31:4a:4f:9f:97:b0:b6:ca:4c:53:78:70:89:83:5f:16 root@m01
The key's randomart image is:
+--[ DSA 1024]----+
|          E      |
|       . . o     |
|      o B *      |
|     . = @ + .   |
|      . S B o    |
|         + o     |
|        o .      |
|       + o       |
|        +        |
+-----------------+

创建出来的文件:

[root@m01 ~]# ll /root/.ssh/
total 8
-rw------- 1 root root 668 Oct 17 18:55 id_dsa       #创建出来的私钥
-rw-r--r-- 1 root root 598 Oct 17 18:55 id_dsa.pub  #创建出来的公钥

1.1.2 第二个里程碑: 分发公钥文件

[root@m01 ~]# man ssh-copy-id
ssh-copy-id  -  install  your  public  key in a remote machine’s autho-rized_keys

注意:密钥分发命令属于openssh-clients软件包

[root@nfs01 ~]# rpm -qf `which ssh-copy-id`
openssh-clients-5.3p1-122.el6.x86_64

语法格式

ssh-copy-id [-i [identity_file]] [user@]machine

-i 指定要分发的公钥文件以及路径信息
[user@] 以什么用户身份进行分发
machine 将公钥分发到哪台主机上,远程主机IP地址

[root@m01 ~]# ssh-copy-id  -i /root/.ssh/id_dsa.pub  [email protected]
The authenticity of host '172.16.1.41 (172.16.1.41)' can't be established.
RSA key fingerprint is d3:41:bb:0d:43:88:da:a3:2c:e8:36:91:11:c9:e4:9c.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '172.16.1.41' (RSA) to the list of known hosts.
[email protected]'s password:
Now try logging into the machine, with "ssh '[email protected]'", and check in

  .ssh/authorized_keys

to make sure we haven't added extra keys that you weren't expecting. 

1.1.3 第三个里程碑: 基于密钥登陆测试

[root@m01 ~]# ssh 172.16.1.41
Last login: Tue Oct 17 18:38:47 2017 from 10.0.0.1
[root@backup ~]#

基于密钥登陆方式成功↑

[root@m01 ~]# ssh [email protected] "hostname -i"
172.16.1.41

不用的登陆到远程主机直接执行命令,返回输出结果↑

说明:

管理主机一旦创建好秘钥对文件,给多个主机分发公钥时,公钥文件相同

1.1.4 ssh服务分发公钥实质执行过程

①. 管理服务器创建私钥和公钥(密钥对)

②. 将公钥文件远程传送复制到被管理服务器相应用户~/.ssh/id_dsa.pub下,并修改.ssh目录权限为700

③. 修改公钥文件文件名称为authorized_keys,授权权限为600

④. 利用ssh服务配置文件的配置参数,进行识别公钥文件authorized_keys

⑤. 进而实现基于密钥远程登录服务器(免密码登录/非交互方式登录)

1.2 默认端口号不是22,如何分发公钥

1.2.1 查询ssh-copy-id命令可以得知这是个脚本文件

[root@m01 ~]# file `which ssh-copy-id `
/usr/bin/ssh-copy-id: POSIX shell script text executable

看看脚本内容发现传输方式

[root@m01 ~]# cat `which ssh-copy-id`|grep ssh
ssh $1 "exec sh -c 'cd; umask 077; test -d .ssh || mkdir .ssh ; cat >> .ssh/authorized_keys && (test -x /sbin/restorecon && /sbin/restorecon .ssh .ssh/authorized_keys >/dev/null 2>&1 || true)'" || exit 1

说明:

1、切换用户到家目录下,临时设置umask值

2、判断客户端相应用户中有没有.ssh目录,如果没有.ssh 目录就进行创建

3、将管理端公钥文件内容添加到客户端~./ssh/authorized_keys, 默认authorized_keys文件不存在,需要创建,文件权限600

1.2.2 实现非22端口的分发

方法一: 修改脚本内容

ssh -p52113 $1 "exec sh -c 'cd; umask 077; test -d .ssh || mkdir .ssh ; cat >> .ssh/authorized_keys && (test -x /sbin/restorecon && /sbin/restorecon .ssh .ssh/authorized_keys >/dev/null 2>&1 || true)'" || exit 1

说明:根据命令脚本,修改$1传参信息,从而实现根据ssh不同端口传送公钥文件

方法二:将传入的参数上添加上端口信息(推荐)

[root@m01 scripts]# ssh-copy-id -i /root/.ssh/id_dsa.pub "-p 52113 [email protected]"
Now try logging into the machine, with "ssh '-p 52113 [email protected]'", and check in:

  .ssh/authorized_keys

to make sure we haven't added extra keys that you weren't expecting.

1.2.3 关于 /usr/bin/ssh-copy-id 脚本中 $1的说明

1.2.3.1 编写脚本shift

[root@m01 scripts]# cat shift.sh
#!/bin/bash

until [ $# -eq 0 ]
do
echo $*
shift
done

测试

[root@m01 scripts]# sh shift.sh 1 2 3 4 5 6
1 2 3 4 5 6
2 3 4 5 6
3 4 5 6
4 5 6
5 6
6

说明:

shift命令用于对参数的移动(左移),通常用于在不知道传入参数个数的情况下依次遍历每个参数然后进行相应处理(常见于Linux中各种程序的启动脚本)。

ssh-copy-id -i /root/.ssh/id_dsa.pub "-p 52113 [email protected]"

由于/usr/bin/ssh-copy-id 脚本中前面使用了两个shift 所有原本该为的参数变为了 3的参数变为了 1.

if [ "-i" = "$1" ]; then
  shift
  # check if we have 2 parameters left, if so the first is the new ID file
  if [ -n "$2" ]; then
    if expr "$1" : ".*.pub" > /dev/null ; then
      ID_FILE="$1"
    else
      ID_FILE="$1.pub"
    fi
    shift         # and this should leave $1 as the target name
  fi
else

1.3 实现自动分发公钥,远程管理多台主机

1.3.1 【预备知识】shell中三种循环

#for 循环

for n in (1..100)
do
      xxx
done

#while循环:循环条件为真时,一直循环;为假时,停止循环

while [ture]
do
      xxx
done

#until 循环: 循环条件为假时,一直循环;为真时,停止循环

until [ture]
do
   xxx
done

1.3.2 实现自动分发公钥,远程管理多台主机的阻碍因素?

01.创建秘钥对需要进行交互

a.需要确认秘钥保存路径

b.需要确认密码信息

02.分发公钥时需要进行交互

a.需要进行确认yes|no

b.第一次分发公钥需要进行密码认证

1.3.3 解决阻碍因素

  • 自动保存路径,并且不密码
ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q

参数说明:

-f filename    Specifies the filename of the key file.

指定密钥文件保存的路径信息(免交互)

-P passphrase      Provides the (old) passphrase.

提供一个密码信息

-N new_passphrase      Provides the new passphrase.

-P -N 都是免交互方式指定密码信息

-q 安静的 不输出信息,减少信息输出

  • 解决分发公钥时需要进行的交互
sshpass -p123456 ssh-copy-id -i ~/.ssh/id_rsa.pub " [email protected].$ip  -o StrictHostKeyChecking=no "

参数说明:

-o option 选择 (man 手册中可以查到有很多选项)
StrictHostKeyChecking=no 对询问的回应(不进行对密钥检查)
要实现免密码,需要一款软件 sshpass 该软件就是为ssh提供密码使用的

[root@m01 ~]# yum install  sshpass  -y

注意:密码与 -p之间不能有空格

1.3.4 最终批量分发脚本内容

[root@m01 scripts]# vim ssh-key.sh 
#!/bin/bash
. /etc/rc.d/init.d/functions

# 创建密钥
rm ~/.ssh/id_rsa* -f
ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q
# 分发公钥
for ip in 31 41 8
do
sshpass -p123456 ssh-copy-id -i ~/.ssh/id_rsa.pub " [email protected].$ip  -o StrictHostKeyChecking=no " &>/dev/null
if [ $? -eq 0 ];then
action  "fenfa 172.16.1.$ip"  /bin/true
else
action  "fenfa 172.16.1.$ip"  /bin/false
fi
echo ""
done

脚本执行效果:

[root@m01 scripts]# sh ssh-key.sh
fenfa 172.16.1.31                                          [  OK  ]
fenfa 172.16.1.41                                          [  OK  ]
fenfa 172.16.1.8                                           [  OK  ]

说明:

脚本中引用 . /etc/rc.d/init.d/functions 函数,可以显示执行结果的判断。

使用if语句进行判断,action 执行相应的动作。true/false

1.3.5 实现基于密钥的批量管理脚本

[root@m01 scripts]# vim piliang_guanli.sh 
#!/bin/bash
CMD=$1

for ip in 8 31 41
do
echo ========host 172.16.1.$ip=======
ssh [email protected].$ip "$CMD"
echo ============END===============
echo ""
done

脚本执行效果:

[root@m01 scripts]# sh piliang_guanli.sh  date
======172.16.1.8======
Thu Oct 19 16:25:08 CST 2017
=========END=============
======172.16.1.31======
Thu Oct 19 16:25:08 CST 2017
=========END=============
======172.16.1.41======
Thu Oct 19 16:25:08 CST 2017
=========END=============

基于密钥登陆方式,分发的公钥文件会识别用户信息,所以能够实现免密码批量管理。

Ansible部署模块的时候出现中文乱码的问题

今天在部署服务的时候遇到了一个很罕见的现象,线上有15台服务器是手机推送消息的服务,新来的小运维使用ansible批量跑部署脚本的时候,发现手机端接收到来的消息全是乱码,然后登陆到服务器,查看日志发现,日志里面就是乱码,如图:

未分类

由于这个问题用户是有感知的,所以属于“事故”级别了,于是小boss大怒,叫运维赶快回滚,然后让开发赶紧重新检查代码,然后开骂测试都是吃屎的么这么大的一个问题都看不出来真是一群猪伤不起啊。

开发看了半天自己的代码,发现没有任何问题,战战兢兢跑来跟新来的小运维窃窃私语,结果我发现这个模块用手动单独部署,日志却是正常的,中文显示十分OK。

未分类

这一下开发就腰杆硬了,说这不是我的锅啊我是无辜的啊老子天天辛苦加班没有功劳也有苦劳没有苦劳也有疲劳老子的代码经得住考验这一切就是部署的问题。

于是我就查看了一下ansible的配置文件,#vim /etc/ansible/ansible.cfg,发现了问题所在:

未分类

这里最后三行需要改成下面的样子,这样就解决了乱码问题。

#module_lang    = C
#module_set_locale = False
module_lang    = zh_CN.UTF-8
module_set_locale = True

精心汇总,史上最全-Ansible运维自动化工具19个常用模块使用实例(root用户角度)

本文章是站在root用户角度对Ansible的19个常用模块进行测试使用。为什么突出是站在root用户角度,因为很多时候,处于服务器安全考虑,我们是禁止root用户直接登录系统,如果要远程登录或者远程控制服务器,必须先登录普通用户,再登录root用户,因此在这个过程中,Ansible中root用户想使用相应的模块则没有像普通用户那么简单。

一、模块列表

1、setup
2、ping
3、file
4、copy
5、command
6、shell
7、script
8、cron
9、yum
10、service
11、group
12、user
13、stat
14、mount
15、fetch
16、synchronize
17、get_url
18、hostname
19、wait_for

二、模块示例

1、setup 功能:搜集系统信息

通过命令获取所有的系统信息,搜集主机的所有系统信息

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m setup

未分类

注意:/etc/ansible/.hosts 文件的写法很关键,官网也出了相关的说明,只不过官方的文档分版本描述的,稍不注意就掉坑了,具体格式如下:

[xxxx-root]
192.168.1.51 ansible_become=True ansible_become_method=su ansible_become_user=root ansible_become_pass=1111111111
192.168.1.52 ansible_become=True ansible_become_method=su ansible_become_user=root ansible_become_pass=1111111111

搜集系统信息并以主机名为文件名分别保存在/tmp/facts 目录

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m setup --tree /tmp/facts

未分类

搜集和内存相关的信息

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m setup -a 'filter=ansible_*_mb'

未分类

搜集网卡信息

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m setup -a 'filter=ansible_eth[0-2]'

未分类

2、ping 功能:测试网络连通性, ping模块没有参数

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m ping

未分类

3、file 功能:文件属性设置

  • force:需要在两种情况下强制创建软链接,一种是源文件不存在,但之后会建立的情况下;另一种是目标软链接已存在,需要先取消之前的软链,然后创建新的软链,有两个选项:yes|no

  • group:定义文件/目录的属组

  • mode:定义文件/目录的权限

  • owner:定义文件/目录的属主

  • path:必选项,定义文件/目录的路径

  • recurse:递归设置文件的属性,只对目录有效

  • src:被链接的源文件路径,只应用于state=link的情况

  • dest:被链接到的路径,只应用于state=link的情况

state包括以下:

  • directory:如果目录不存在,就创建目录

  • file:即使文件不存在,也不会被创建

  • link:创建软链接

  • hard:创建硬链接

  • touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其最后修改时间

  • absent:删除目录、文件或者取消链接文件

创建软链接:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m file -a "src=/etc/resolv.conf dest=/tmp/resolv.conf state=link"

未分类

删除软连接:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m file -a "path=/tmp/resolv.conf state=absent"

未分类

创建目录(文件):

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m file -a "path=/root/testfile group=ihavecar owner=ihavecar mode=700 state=directory"

未分类

4、copy 功能:复制文件到远程主机

  • backup: #在覆盖之前,将源文件备份,备份文件包含时间信息。有两个选项:yes|no

  • content: #用于替代“src”,可以直接设定指定文件的值

  • dest: #必选项。要将源文件复制到的远程主机的绝对路径,如果源文件是一个目录,那么该路径也必须是个目录

  • directory_mode: #递归设定目录的权限,默认为系统默认权限

  • force: #如果目标主机包含该文件,但内容不同,如果设置为yes,则强制覆盖,如果为no,则只有当目标主机的目标位置不存在该文件时,才复制。默认为yes

  • others:#所有的file模块里的选项都可以在这里使用

  • group # 复制到远程主机后,指定文件或目录的属

  • mode # 复制到远程主机后,指定文件或目录权限,类似与 `chmod’指明如 0644

  • owner # 复制到远程主机后,指定文件或目录属主

  • src:被复制到远程主机的本地文件,可以是绝对路径,也可以是相对路径。如果路径是一个目录,它将递归复制。在这种情况下,如果路径使用“/”来结尾,则只复制目录里的内容,如果没有用“/”来结尾,则包含目录在内的整个内容全部复制,类似于rsync。

将本地文件“/etc/ansible/ansible.cfg”复制到远程服务器,设置属主和属组及文件权限

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m copy -a "src=/etc/ansible/ansible.cfg dest=/tmp/ansible.cfg owner=ihavecar group=root mode=400"

未分类

5、command 功能:在远程主机上执行命令

Command不适用于有shell变量的情况,也不适用于有管道符或者&&的情况,如果要使用此种情况,那么可以使用shell模块

相关选项如下:

  • creates:一个文件名,当该文件存在,则该命令不执行

  • free_form:要执行的linux指令

  • chdir:在执行指令之前,先切换到该目录

  • removes:一个文件名,当该文件不存在,则该选项不执行

  • executable:切换shell来执行指令,该执行路径必须是一个绝对路径

远程执行查询系统负载:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m command -a "uptime"

未分类

如带有管道的,则会如下报错,command是不允许有管道的:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m command -a "uptime | grep 192.168.1.50"

未分类

6、shell 功能:切换到某个shell执行指定的指令

切换到某个shell执行指定的指令,与command不同的是,此模块可以支持命令管道,同时还有另一个模块也具备此功能:raw

  • chdir # 执行之前,先cd到指定目录在执行命令

  • creates # 一个文件名,当这个文件存在,则该命令不执行

  • executable # 切换shell来执行命令,需要使用命令的绝对路径

  • free_form= # 执行的命令

  • removes # 一个文件名,这个文件不存在,则该命令不执行

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m shell -a "cat /etc/passwd | grep root"

未分类

可远程执行脚本:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m shell -a "sh /tmp/echo.sh"

未分类

7、script 功能:指定本地的脚本文件,到远程主机运行一次

注意这个模块和shell模块的不同,shell模块是要求客户端上有这个脚本才能执行;script是要求ansible服务端有这个脚本就可以了,执行的时候是不会拷贝这个脚本到客户端的。

  • creates # 一个文件名,当这个文件存在,则该命令不执行

  • free_form= # 本地脚本路径

  • removes # 一个文件名,这个文件不存在,则该命令不执行

ansible端的已有脚本,就只有一条/sbin/ifconfig 命令,该脚本被拿到客户端上执行,并返回结果:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m script -a "/etc/ansible/masterecho.sh"

未分类

8、cron 功能:计划任务管理

  • backup # 如果设置,创建一个crontab备份

  • cron_file # 如果指定, 使用这个文件cron.d,而不是单个用户crontab

  • day # 日应该运行的工作( 1-31, *, */2, etc )

  • hour # 小时 ( 0-23, *, */2, etc )

  • job # 指明运行的命令是什么

  • minute # 分钟( 0-59, *, */2, etc )

  • month # 月( 1-12, *, */2, etc )

  • name # 定时任务描述

  • reboot # 任务在重启时运行,不建议使用,建议使用special_time

  • special_time # 特殊的时间范围,参数:reboot(重启时),annually(每年),monthly(每月),weekly(每周),daily(每天),hourly(每小时)

  • state # 指定状态,prsent表示添加定时任务,也是默认设置,absent表示删除定时任务

  • user # 以哪个用户的身份执行

  • weekday # 周 ( 0-6 for Sunday-Saturday, *, etc )

定义一个时间任务,每隔3分钟执行一次时间记录任务:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m cron -a 'name="ntp datatime job" minute=*/3 hour=* day=* month=* weekday=* job="echo `date` >> /tmp/linshidata.txt"'

未分类

效果如下:

未分类

删除一个时间任务:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m cron -a 'name="ntp datatime job" minute=*/3 hour=* day=* month=* weekday=* job="echo `date` >> /tmp/linshidata.txt" state=absent'

未分类

9、yum 功能:软件包安装管理

  • conf_file # yum的配置文件

  • disable_gpg_check # 关闭gpg_check

  • disablerepo # 不启用某个源

  • enablerepo # 启用某个源

  • List # 非幂等性命令

  • name= # 指定要安装的包,如果有多个版本需要指定版本,否则安装最新的包

  • state # 安装(present’),安装最新版(latest’),卸载程序包(`absent’)

指定版本安装包:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m yum -a "name=httpd-devel-2.2.15 state=present"

未分类

指定安装最新版本的包:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m yum -a "name=httpd-devel-2.2.15 state=latest"

未分类

指定rpm包来安装:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m yum -a "name=/usr/local/src/kel.noarch.rpm state=present"

指定远程网址rpm包来进行安装:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m yum -a "name=http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-centos-6.0.el6.ngx.noarch.rpm state=present"

删除包:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m yum -a "name=httpd-devel-2.2.15 state=absent"

未分类

10、service 功能:系统服务管理

  • arguments # 向服务传递的命令行参数

  • enabled # 设置服务开机自动启动,参数为yes|no

  • name= # 控制服务的名称

  • pattern # 如果通过status指令来查看服务的状态时,没有响应,就会通过ps指令在进程中根据该模式进行查找,如果匹配到,则认为该服务依然在运行

  • runlevel # 设置服务自启动级别

  • sleep # 如果执行了restarted,则在stop和start之间沉睡几秒钟

  • state # 启动started’ 关闭stopped’ 重新启动 restarted’ 重载reloaded’

设置httpd服务为开机自启动模式,并限制开启httpd服务

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m service -a "name=httpd state=started enabled=yes"

重启httpd服务,中间sleep 10秒钟

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m service -a "name=httpd state=restarted sleep=10"

未分类

11、group 功能:系统用户组管理

  • gid # 设置组的GID号

  • name= # 管理组的名称

  • state # 指定组状态,默认为创建,设置值为absent为删除

  • system # 设置值为yes,表示为创建系统组

创建一个foo组,指定gid号

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m group -a "name=foo gid=360 system=no"

未分类

删除一个组:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m group -a "name=foo gid=360 system=no state=absent"

未分类

12、user 功能:系统用户账号管理

  • comment # 用户的描述信息

  • createhome # 是否创建家目录

  • force # 在使用state=absent’时, 行为与userdel –force’一致.

  • group # 指定基本组

  • groups # 指定附加组,如果指定为(‘groups=’)表示删除所有组

  • home # 指定用户家目录

  • login_class # 可以设置用户的登录类 FreeBSD, OpenBSD and NetBSD系统.

  • move_home # 如果设置为`home=’时, 试图将用户主目录移动到指定的目录

  • name= # 指定用户名

  • non_unique # 该选项允许改变非唯一的用户ID值

  • password # 指定用户密码

  • remove # 在使用 state=absent’时, 行为是与userdel –remove’一致.

  • shell # 指定默认shell

  • state #设置帐号状态,不指定为创建,指定值为absent表示删除

  • system # 当创建一个用户,设置这个用户是系统用户。这个设置不能更改现有用户。

  • uid #指定用户的uid

  • update_password # 更新用户密码

添加用户foo,指定密码,设置家目录,不允许远程登录

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m user -a "name=foo password=123456 home=/home/foo shell=/sbin/nologin"

未分类

彻底删除一个用户,包括家目录:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m user -a "name=foo remove=yes state=absent"

13、stat 功能:获取远程文件信息

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m stat -a "path=/etc/passwd"

未分类

14、mount 功能:配置挂载点

  • fstype:必选项,挂载文件的类型

  • name:必选项,挂载点

  • opts:传递给mount命令的参数

  • src:必选项,要挂载的文件

  • state:必选项

  • present:只处理fstab中的配置

  • absent:删除挂载点

  • mounted:自动创建挂载点并挂载之

  • umounted:卸载

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m mount -a "name=/mnt src=/dev/sda5 fstype=ext4 opts=rostate=present"

15、fetch 功能:文件拉取模块,主要是将远程主机中的文件拷贝到本机中

  • 和copy模块的作用刚刚相反,并且在保存的时候使用hostname来进行保存,当文件不存在的时候,会出现错误,除非设置了选项fail_on_missing为yes

  • Dest:用来存放文件的目录,例如存放目录为backup,源文件名称为/etc/profile在主机pythonserver中,那么保存为/backup/pythonserver/etc/profile

  • Fail_on_missing: Yes/no,当源文件不存在的时候,标识为失败
    Flat: 允许覆盖默认行为从hostname/path到/file的,如果dest以/结尾,它将使用源文件的基础名称

  • Src: 在远程拉取的文件,并且必须是一个file,不能是目录

  • Validate_checksum Yes/no,当文件fetch之后进行md5检查

从远程机器上床送文件到ansible服务器

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m fetch -a "src=/root/123 dest=/root"

未分类

src表示为远程主机上需要传送的文件的路径,dest表示为本机上的路径,在传送过来的文件,是按照IP地址或hostname进行分类,然后路径是源文件的路径,例如上面的最终路径为/root/192.168.1.50/root/123在拉取文件的时候,必须拉取的是文件,不能拉取文件夹

指定路径目录进行保存:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m fetch -a "src=/root/Ssh.py dest=/root/kel/ flat=yes"

在使用参数为flat的时候,如果dest的后缀名为/,那么就会保存在目录中,然后直接保存为文件名,如上例的结果为 dest”: “/root/kel/Ssh.py;

当dest后缀不为/的时候,那么就会直接保存为kel的文件,如上例1所示。主要是在于dest是否已/结尾,从而来区分这是个目录还是路径。

16、synchronize 功能:将主控方/root/a目录推送到指定节点的/tmp目录下

选项说明

  • archive # 是否采用归档模式同步,即以源文件相同属性同步到目标地址

  • checksum # 是否效验

  • compress # 开启压缩,默认为开启

  • copy_links # 同步的时候是否复制连接

  • delete # 删除源中没有而目标存在的文件(即以推送方为主)

  • dest= # 目标地址

  • dest_port # 目标接受的端口,ansible配置文件中的 ansible_ssh_port 变量优先级高于该 dest_port 变量

  • dirs # 以非递归的方式传输目录

  • existing_only # Skip creating new files on receiver.

  • group # Preserve group

  • links # Copy symlinks as symlinks.

  • mode # 模式,rsync 同步的方式 PUSHPULL,默认都是推送push。如果你在使用拉取pull功能的时候,可以参考如下来实现mode=pull 更改推送模式为拉取模式

  • recursive # 是否递归 yes/no

  • rsync_opts # 使用rsync 的参数

  • rsync_path # 服务的路径,指定 rsync 命令来在远程服务器上运行。这个参考rsync命令的–rsync-path参数,–rsync-path=PATH # 指定远程服务器上的rsync命令所在路径信息

  • rsync_timeout # 指定 rsync 操作的 IP 超时时间,和rsync命令的 –timeout 参数效果一样.

  • set_remote_user # put user@ for the remote paths. If you have a custom ssh config to define the remote user for

  • src=‘#‘” # 源,同步的数据源

  • times #

  • –exclude=.Git 忽略同步.git结尾的文件

由于模块默认启用了archive参数,该参数默认开启了recursive, links, perms, times, owner,group和-D参数。如果你将该参数设置为no,那么你将停止很多参数,比如会导致如下目的递归失败,导致无法拉取

压缩传输a目录到被控节点:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m synchronize -a 'src=/root/a dest=/tmp/ compress=yes'

注意:主控端的a目录传输到被控节点,文件属性过去被控节点后,只能是remote user的权限,也就是ihavecar的,不能是root的,也不允许传输到ihavecar这个用户无权限的目录下去,不然会报错。

未分类

使用pull模式拉取文件到主控节点:

由于模块,默认都是推送push。因此,如果你在使用拉取pull功能的时候,可以参考如下来实现

mode=pull 更改推送模式为拉取模式。

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m synchronize -a 'mode=pull src=/tmp/a dest=/root/ compress=yes'

注意:从被控节点传目录回到主控节点,可以遵循权限,被控节点端目录的属性是什么,传回主控节点的目录权限就是什么,并不会像push方式那样属性改变成普通用户的。还有一点需要注意的是,从被控节点传输回主控节点,只有一个节点能传输成功,其他的节点都会失败。当然,如果各个被控节点的文件如果不是一样的话,那么各个节点都会传文件回来。那样主控端就会收到各个节点的全部文件。

未分类

17、get_url 功能:将某个url的文件下载到被控节点的某个位置

将simplejson-3.8.2.tar.gz 下载到/tmp/下:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m get_url -a "url=https://pypi.python.org/packages/source/s/simplejson/simplejson-3.8.2.tar.gz dest=/tmp/"

未分类

18、hostname 功能:主要用来修改主机的名称

在查看的时候,主要查看文件/etc/sysconfig/network,重启之后才能生效

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m hostname -a "name=ansible-test"

未分类

19、wait_for 功能:等待一个事件发生或处理完后,再继续运行下面的事情

  • connect_timeout 默认5秒,在下一个事情发生前等待链接的时间,单位是秒

  • delay 延时,大家都懂,在做下一个事情前延时多少秒

  • host 默认127.0.0.1,执行这个模块的host

  • path 当一个文件存在于文件系统中,下一步才继续。

  • port 端口号,如8080

  • state 默认started,对象是端口的时候start状态会确保端口是打开的,stoped状态会确认端口是关闭的

  • present 对象是文件的时候present或者started会确认文件是存在的,而absent会确认文件是不存在的。

  • started

  • stopped

  • absent

10秒后在当前主机开始检查8000端口,直到端口启动后返回:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m wait_for -a "port=22612 delay=10"

未分类

检查path=/tmp/foo直到文件存在后继续:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m wait_for -a "path=/tmp/foo"

未分类

确认/var/lock/file.lock不存在继续:

# ansible -i /etc/ansible/.hosts-root jr-root -c paramiko -m wait_for -a "path=/var/look/file.lock state=absent"

未分类

Logstash的容器化与Ansible多环境下单配置文件发布

原因

为了发布、迁移方便,最近决定将公司项目中用的logstash容器化,最初原打算沿用原多进程方案,在容器内通过supervisor启动多个不同配置进程,但使用官方容器时并支持启动多个实例。

最终解决方式:将集群的配置文件组装在一起,不同的path对应不同的filter与output;由于不同主机上组件不同,日志也不同。因此在ansible发布时通过脚本检测生成log file 的path列表,适配集群上的不同组件。这样的好处是只需要维护一份配置,操作简单。

官方logstash镜像

在dockerhub上搜索了到一个即将被废弃的镜像:https://hub.docker.com/_/logstash/。
最新版镜像在es官方进行维护: https://www.elastic.co/guide/en/logstash/current/docker.html
由于直接使用原版镜像时映射到容器内部的pipeline.conf一直提示No permission,明明都已经设置成755,宿主机上并没有logstash这一用户,chown不方便。所以决定对官方镜像稍加修改,再推送到公司内的dockerhub私有docker registry上。

FROM docker.elastic.co/logstash/logstash:5.6.1
MAINTAINER CodingCrush
ENV LANG en_US.UTF-8
# Disable ES monitoring in the official image
ENV XPACK_MONITORING_ENABLED false
# TimeZone: Asia/Shanghai
ENV TZ Asia/Shanghai
ENV PIPELINE_WORKERS 5
# Default user is logstash
USER root

这个Dockerfile很简单,主要设置了User为root,解决conf文件的权限不足问题。另外关闭容器自带的es xpack检查功能,设置时区与pipeline的 worker数量。
然后打个tag,推到私有docker registry上。

docker build -t dockerhub.xxx.net/logstash:5.6.1
docker push dockerhub.xxx.net/logstash:5.6.1

容器便修改好了,在生产集群上可pull。

pipeline.conf

这个简化的配置文件中path应该是一个列表,但目前用了一个占位字符串,发布时动态检测通过sed进行更换,实际项目中的组件配置更多,这里写4个意思一下。
值得注意的是: 通过if else 对path进行匹配时如果出现/,需要进行转义,否则ruby进行正则匹配时会报错。

input {
    file {
        path => LOG_FILES_PLACEHOLDER
        type => "nginx"
        start_position => "beginning"
        sincedb_path => "/data/projects/logstash/data/logstash.db"
        codec=>plain{charset=>"UTF-8"}
    }
}
filter {
    if [path] =~ "component1/logs/access" {
        grok & mutate
    } else if [path] =~ "component1/logs/error" {
        grok & mutate
    } else if [path] =~ "component2/logs/access" {
        grok & mutate
    } else if [path] =~ "component2/logs/error" {
        grok & mutate
    }
}
output {
    if [path] =~ "component1/logs/access" {
        stdout{codec=>rubydebug}
    }  else if [path] =~ "component1/logs/error" {
        stdout{codec=>rubydebug}
    } else if [path] =~ "component2/logs/access.log" {
        stdout{codec=>rubydebug}
    } else if [path] =~ "component2/logs/error" {
        stdout{codec=>rubydebug}
    }
}

日志文件检测脚本 detect_logfiles.py

检测脚本同样很简单,用os.path.exists进行过滤。往标准输出打一个列表的string。
此处注意,需要用re.escape进行转义,因为后面还需要用sed进行替换。

import os
import sys
import re
paths = [
    "/data/projects/component1/logs/access.log*",
    "/data/projects/component1/logs/error.log",
    "/data/projects/component2/logs/access.log*",
    "/data/projects/component2/logs/error.log*",
    ......
]
available_paths = [path for path in paths if os.path.exists(path.replace("*", ""))]
sys.stdout.write(re.escape(str(available_paths)))

ansible剧本

在发布时通过sed替换占位串,之所以大费周章搞这么个方式,主要因为我们环境比较复杂,将来横向拓展时组件布局不定,多配置文件维护起来挺麻烦。
为什么用shell?而不用ansible的module? 因为我不会,还懒得去学ansible发明的DSL

- name: Modify lostash.conf
  args:
    chdir: "/data/projects/logstash/"
  shell: 'sed -i "s|LOG_FILES_PLACEHOLDER|$(python detect_logfiles.py)|" logstash.conf'
  become: true
  become_method: sudo
- name: Modify docker-compose.yml
  args:
    chdir: "/data/projects/logstash/"
  shell: 'sed -i "s|HOST_PLACEHOLDER|{{ host }}|" docker-compose.yml'
  become: true
  become_method: sudo

docker-compose

挂载整个文件卷,HOST_PLACEHOLDER占位符是因为我希望通过container名字直观看到组件所在的主机名,而避免生产服务器上误操作。

version: '2'
services:
  logstash:
    image: dockerhub.xxx.net/logstash:5.6.1
    volumes:
       - /etc/localtime:/etc/localtime:ro
       - /data/projects/logstash/hosts:/etc/hosts
       - /data/projects:/data/projects
    container_name: HOST_PLACEHOLDER.logstash
    command: logstash -f /data/projects/logstash/logstash.conf --path.data=/data/projects/logstash/data

启动的command时指定工作目录,将运行状态持久化到外部存储上,方便迁移。

Ansible实战之Nginx高可用代理LNMP-wordpress

实验环境:前端使用Nginx做代理服务器,静态资源经由缓存服务器,连接后端web集群,动态资源直接连接后端集群,可由Nginx代理或Varnish实现动静分离,web服务端连接PHP服务,从而更好的提供动态资源,将动态资源数据保存在Mysql关系型数据库上,且Mysql数据库使用主从复制的技术。为验证整体架构的准确性,故将wordpress应用搭建在web服务端,来验证构架的有效性。为了防止单点故障,前端的Nginx代理还使用了keepqlive技术来实现高可用从而达到增加网络的安全性能的目的。

实验拓展:为了增加可用性,可将web集群分为动静两类web 集群组,从来实现动静分离的效果,Varnish集群来为静态资源提供缓存,从而使网络访问速度更快。前端代理也可使用HAProxy及LVS等技术来替代。后端Mysql数据库也可以增加数据备份的案例。

varnish的分离分离参考 http://www.cnblogs.com/JevonWei/p/7499417.html

网络拓扑图
未分类

主机环境

Ansible         172.16.252.82
Nginx_A 代理  172.16.252.207  
Nginx_B 代理  172.16.252.103
Keepalived_A    172.16.252.207  
Keepalived_B    172.16.252.103
Nginx+PHP_A     172.16.252.184  
Nginx+PHP_B     172.16.252.67
Mysql_Master    172.16.252.184  
Mysql_Slave     172.16.252.67

受添加限制
    Nginx_A和Keepalived_A为Nginx1.danran.com上
    Nginx_B和Keepalived_B为Nginx2.danran.com上
    Nginx+PHP_A和Mysql_Mstart在web1.danran.com主机上
    Nginx+PHP_B和Mysql_Slave在web2.danran.com主机上

实验准备

  • 各节点需保持时间同步
  • 确保主机名可以通信
  • 节点间使用秘钥连接

时间同步

[root@ansible ~]# ntpdate 172.16.0.1

节点主机名通信

编辑/etc/hosts主机解析文件或使用DNS解析亦可

[root@ansible ~]# vim /etc/hosts
172.16.252.184  web1.danran.com
172.16.252.67   web2.danran.com
172.16.252.82   ansible.danran.com
172.16.252.103  nginx2.danran.com
172.16.252.82   Ansible.danran.com
[root@ansible ~]# scp /etc/hosts nginx1.danran.com:/etc/
[root@ansible ~]# scp /etc/hosts nginx2.danran.com:/etc/
[root@ansible ~]# scp /etc/hosts web1.danran.com:/etc/
[root@ansible ~]# scp /etc/hosts web2.danran.com:/etc/

节点秘钥连接

[root@ansible ~]# ssh-keygen -t rsa -P ""
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
8e:bb:44:d7:25:df:1b:3e:9b:fa:22:15:b5:6b:e4:19 root@ansible
The key's randomart image is:
+--[ RSA 2048]----+
|                 |
|              .  |
|          . .. . |
|         . +..E  |
|      . S . .+o+ |
|     . +    ..=o |
|      o .  . .+  |
|     . .  . .  + |
|      o.   ..++  |
+-----------------+
[root@ansible ~]# ssh-copy-id -i .ssh/id_rsa.pub [email protected]
[root@ansible ~]# ssh-copy-id -i .ssh/id_rsa.pub [email protected]
[root@ansible ~]# ssh-copy-id -i .ssh/id_rsa.pub [email protected]
[root@ansible ~]# ssh-copy-id -i .ssh/id_rsa.pub [email protected]

Ansible配置文件

[root@ansible ~]# vim ansible.yml 
- hosts: websrvs
  remote_user: root
  roles:
  - nginx_web
- hosts: proxy
  remote_user: root
  roles:
  - nginx_proxy
- hosts: keepalive
  remote_user: root
  roles:
  - keepalive
- hosts: varnish
  remote_user: root
  roles:
  - varnish
- hosts: php-fpm
  remote_user: root
  roles:
  - php-fpm
- hosts: mysql
  remote_user: root
  roles:
  - mariadb
- hosts: websrvs
  remote_user: root
  roles:
  - wordpress 

Ansible主机清单文件

[root@ansible ~]# vim /etc/ansible/hosts 
[websrvs]
172.16.252.184
172.16.252.67

[proxy]
172.16.252.207
172.16.252.103

[keepalive]
172.16.252.207  start1=MASTER start2=BACKUP priority1=100 priority2=90
172.16.252.103  start1=BACKUP start2=MASTER priority1=90 priority2=100

[varnish]
172.16.252.207
172.16.252.103

[php-fpm]
172.16.252.184
172.16.252.67

[mysql]
172.16.252.184 serverid=1  log="log_bin = master-log"
172.16.252.67  serverid=2  log="relay-log = master-log"

定义角色

keepalive

[root@ansible ~]# cd /etc/ansible/roles/
[root@ansible ~]# mkdir keepalived/{files,templates,tasks,handlers,vars,meta,default} -pv

[root@ansible roles]# vim keepalive/tasks/main.yml 
- name: install keepalived
  yum: name=keepalived state=latest
- name: install conf
  template: src=keepalived.j2 dest=/etc/keepalived/keepalived.conf
  tags: conf
  notify: restart keepalived
- name: start keepalived
  service: name=keepalived state=started

[root@ansible roles]# vim keepalive/handlers/main.yml 
- name: restart keepalived
  service: name=keepalived state=restarted

[root@ansible roles]# vim keepalive/templates/keepalived.j2 
global_defs {
    notification_email {
        [email protected]
    }
    notification_email_from [email protected]
    smtp_server 127.0.0.1
    smtp_connect_timeout 30
    router_id keepaliveA
    vrrp_mcast_group4 224.103.5.5
}
vrrp_instance VI_A {
    state {{ start1 }}
    interface {{ ansible_default_ipv4.alias }}
    virtual_router_id 51
    priority {{ priority1 }}
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass qr8hQHuL
    }

    virtual_ipaddress {
    172.16.252.100/32
    }
}
vrrp_instance VI_B {
    state {{ start2 }}
    interface {{ ansible_default_ipv4.alias }}
    virtual_router_id 52
    priority {{ priority2 }}
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass eHTQgK0n
    }
    virtual_ipaddress {
       172.16.252.10/32
    }
}

nginx_web

[root@ansible ~]# cd /etc/ansible/roles/
[root@ansible ~]# mkdir nginx_web/{files,templates,tasks,handlers,vars,meta,default} -pv

[root@ansible roles]# vim nginx_web/tasks/main.yml 
- name: install nginx
  yum: name=nginx state=latest
  when: ansible_os_family == "RedHat"
- name: install conf
  template: src=vhost1.conf.j2 dest=/etc/nginx/conf.d/vhost1.conf
  tags: conf
  notify: restart nginx
- name: install site home directory
  file: path={{ ngxroot }} state=directory
- name: install index page
  copy: src=index.html dest={{ ngxroot }}/
- name: start nginx
  service: name=nginx state=started

[root@ansible roles]# vim nginx_web/handlers/main.yml 
- name: restart nginx
  service: name=nginx state=restarted

[root@ansible roles]# vim nginx_web/vars/main.yml 
ngxroot: /blog

[root@ansible roles]# vim nginx_web/templates/vhost1.conf.j2 
server {
    listen 8080;
    root "/blog/wordpress";
    index index.php index.html;
    location ~ .*.(php|php5)?$ {
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        include fastcgi.conf;
    }
}

nginx_proxy

[root@ansible ~]# cd /etc/ansible/roles/
[root@ansible ~]# mkdir nginx_proxy/{files,templates,tasks,handlers,vars,meta,default} -pv

[root@ansible roles]# vim nginx_proxy/tasks/main.yml 
- name: install nginx
  yum: name=nginx state=latest
  when: ansible_os_family == "RedHat"
- name: install conf
  template: src=proxy.conf.j2 dest=/etc/nginx/conf.d/vhost1.conf
  tags: conf
  notify: restart nginx
- name: install nginx.conf
  copy: src=nginx.conf  dest=/etc/nginx/nginx.conf
- name: start nginx
  service: name=nginx state=started

[root@ansible roles]# vim nginx_proxy/handlers/main.yml 
- name: restart nginx
  service: name=nginx state=restarted

[root@ansible roles]# vim nginx_proxy/templates/proxy.conf.j2 
upstream websrv {
    server 172.16.252.207:6081;
    server 172.16.252.103:6081;
}

server {
    listen 80 default_server;
    server_name www.jevon.com;
    location / {
        proxy_pass http://websrv/;
        proxy_set_header Host $host;
        proxy_set_header X-Forward-For $remote_addr;
    }
}

[root@ansible roles]# vim nginx_proxy/files/nginx.conf  \取消nginx自带默认web主机,将新定义的web虚拟主机作为默认主机
server {
    listen       80 ;
}

varnish

[root@ansible ~]# cd /etc/ansible/roles/
[root@ansible ~]# mkdir varnish/{files,templates,tasks,handlers,vars,meta,default} -pv

[root@ansible roles]# vim varnish/tasks/main.yml 
- name: install varnish
  yum: name=varnish state=latest
- name: install conf
  copy: src=default.vcl dest=/etc/varnish/
  tags: varconf
  notify: restart varnish
- name: start varnish
  service: name=varnish state=started

[root@ansible roles]# vim varnish/handlers/main.yml 
- name: restart varnish
  service: name=varnish  state=restarted

[root@ansible roles]# vim varnish/files/default.vcl 
vcl 4.0;
import directors;
backend web1 {
.host = "172.16.252.184";
.port = "8080";
}
backend web2 {
    .host = "172.16.252.67";
    .port = "8080";
}
sub vcl_init {
    new websrv = directors.round_robin();
    websrv.add_backend(web1);
    websrv.add_backend(web2);
}

sub vcl_purge {
    return (synth(200,"Pruge Fishished"));
}
acl purges {
    "172.16.252.110";
    "127.0.0.0"/8;
}
sub vcl_recv {
    if (req.method == "PURGE") {
        if (client.ip !~ purges) {
            return(synth(403,"Purging not allowed for" + client.ip));
    }
    return(purge);
}
    if (req.url ~ "(?i).(jpg|jpeg|png|gif)$") {
        set req.backend_hint = websrv.backend();
     }else {
        set req.backend_hint = websrv.backend();
    }
    if (req.restarts == 0) {
        if (req.http.X-Forwarded-For) {
            set req.http.X-Forwarded-For = req.http.X-Forwarded-For + "," + client.ip;
        } else {
                set req.http.X-Forwarded-For = client.ip;
        }
    }
}
sub vcl_backend_response {
    unset beresp.http.X-Powered-By;
    if (bereq.url ~ ".(css|js|png|gif|jp(e?)g|swf|ico|txt|eot|svg|woff)") {
    unset beresp.http.cookie;
    set beresp.http.cache-control = "public, max-age=3600";
    }
    if ( beresp.status != 200 && beresp.status != 404 ) {
        set beresp.uncacheable = true;
        set beresp.ttl = 120s;
        return (deliver);
    }
    set beresp.ttl = 1h;
    set beresp.grace = 30s;
    return (deliver);
}
sub vcl_deliver {
    if (obj.hits>0) {
        set resp.http.X-Cache = "Hit Via " + server.ip;
    } else {
        set resp.http.X-Cache = "Miss from " + server.ip;
    }
}

php-fpm

[root@ansible ~]# cd /etc/ansible/roles/
[root@ansible ~]# mkdir php-fpm/{files,templates,tasks,handlers,vars,meta,default} -pv

[root@ansible roles]# vim php-fpm/tasks/main.yml 
- name: install {{ item }} package
  yum: name={{ item }} state=latest
  with_items:
  - php-fpm
  - php-mysql
- name: start php-fpm
  service: name=php-fpm  state=started  enabled=yes

mariadb

[root@ansible ~]# cd /etc/ansible/roles/
[root@ansible ~]# mkdir mariadb/{files,templates,tasks,handlers,vars,meta,default} -pv

[root@ansible roles]# vim mariadb/tasks/main.yml 
- name: install mariadb
  yum: name=mariadb-server   state=latest
- name: install conf
  template: src=server.j2 dest=/etc/my.cnf.d/server.cnf
  tags: conf
  notify: restart mariadb
- name: start mariadb
  service: name=mariadb  state=started  enabled=yes
- name: command master
  shell: /usr/bin/mysql -e "GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'repluser'@'172.16.%.%' IDENTIFIED BY 'replpass';"
  shell: /usr/bin/mysql -e "flush privileges;"
  when: ansible_hostname == "web1"
- name: command slave
  shell: /usr/bin/mysql -e "CHANGE MASTER TO MASTER_HOST='172.16.252.184', MASTER_USER='repluser', MASTER_PASSWORD='replpass', MASTER_LOG_FILE='master-log.000003', MASTER_LOG_POS=245;"
  shell: /usr/bin/mysql -e "start slave;"
  when: ansible_hostname == "web2"
- name: wordpress command
  shell: /usr/bin/mysql -e "create database blog;"
  shell: /usr/bin/mysql -e "grant all on blog.* to 'blog'@'localhost' identified by 'blog';"

[root@ansible roles]# vim mariadb/handlers/main.yml 
- name: restart mariadb
  service: name=mariadb state=restarted

[root@ansible roles]# vim mariadb/templates/server.j2 
[mysqld]

server-id = {{ serverid }}
{{ log }}
innodb_file_per_table = ON
skip_name_resolve = ON

wordpress

[root@ansible ~]# cd /etc/ansible/roles/
[root@ansible ~]# mkdir wordpress/{files,templates,tasks,handlers,vars,meta,default} -pv

[root@ansible roles]# vim wordpress/tasks/main.yml 
- name: install unzip
  yum: name=unzip state=latest
- name: copy file
  copy: src=wordpress-4.8.1-zh_CN.zip dest=/blog
- name: command unzip
  command: /usr/bin/unzip -o  /blog/wordpress-4.8.1-zh_CN.zip -d /blog
- name: copy conf
  copy: src=wp-config.php dest=/blog/wordpress/
- name: mv conf
  command: mv /blog/wordpress/wp-config-sample.php /blog/wordpress/wp-config.php
  command: sed -ri 's/database_name_here/blog/' /blog/wordpress/wp-config.php
  command: sed -ri 's/username_here/blog/' /blog/wordpress/wp-config.php
  command: sed -ri 's/password_here/blog/' /blog/wordpress/wp-config.php

[root@ansible roles]# ls wordpress/files/
wordpress-4.8.1-zh_CN.zip

运行yml样本

[root@ansible ~]# ansible-playbook ansible.yml 
    .....
    .....
PLAY RECAP *********************************************************************
172.16.252.103             : ok=15   changed=4    unreachable=0    failed=0   
172.16.252.184             : ok=20   changed=3    unreachable=0    failed=0   
172.16.252.207             : ok=14   changed=2    unreachable=0    failed=0   
172.16.252.67              : ok=20   changed=3    unreachable=0    failed=0  

访问测试

未分类

Ansible roles实现LAMP架构部署wordpress

为何使用Ansible及Ansible特点? Agentless(去中心化) Stupied Simple SSH by default YAML no code,定制剧本 基于python实现 模块化:调用特定模块 ,完成特定任务,支持自定义模块

整个role目录的结构:

.
├── ansible.cfg
├── hosts
├── httpd-php_roles.retry
├── httpd-php_roles.yml
├── roles
│ ├── httpd
│ │ ├── files
│ │ │ ├── mysql_wp.sh
│ │ │ ├── wordpress-4.7.4-zh_CN.tar.gz
│ │ │ ├── wordpress.conf
│ │ │ └── wp-config.php
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── vars
│ ├── mariadb
│ │ ├── defaults
│ │ ├── files
│ │ ├── handlers
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ ├── templetes
│ │ └── vars
│ └── php
│ ├── defaults
│ ├── files
│ ├── handlers
│ ├── meta
│ ├── tasks
│ │ └── main.yml
│ ├── tmpletes
│ └── vars
└── wp-config.php

1. 定义hosts目标主机:

vim /etc/ansible/hosts
[httpd]
192.168.5.111

2. 创建httpd相关的目录:

mkdir httpd/{tasks,vars,files} -pv

3. 进入tasks目录创建并编辑main.yml

---
- name: install httpd
  yum: name=httpd state=present
- name: conf httpd
  copy: src=wordpress.conf dest=/etc/httpd/conf.d/
- name: file wordpress
  copy: src=wordpress-4.7.4-zh_CN.tar.gz dest=/var/www/html
- name: tar wordpress.tar.gz
  shell: "tar -xf wordpress-4.7.4-zh_CN.tar.gz && chown -R apache.apache wordpress && mv wordpress-4.7.4-zh_CN.tar.gz /usr/local/src"
  args:
    chdir: /var/www/html 
  notify: start httpd  #当上面的执行完成后会触发handler定义的同名的动作 
- name: conf wp-config.php
  copy: src=wp-config.php dest=/var/www/html/wordpress
- name: mysql_wp.sh
  script: mysql_wp.sh
  notify: restart httpd 
- name: restart httpd
    service: name=httpd state=restarted

4. 将所需的文件放入roles/httpd/files中,文件中的内容下面用到会提及

files/
├── mysql_wp.sh
├── wordpress-4.7.4-zh_CN.tar.gz
├── wordpress.conf
└── wp-config.php

5. 在handlers中编辑main.yml定义触发的动作

---
- name: start httpd
  service: name=httpd state=started
- name: restart httpd
  service: name=httpd state=restarted

6. httpd的已经完成,接下来定义php模块的内容,同样在roles中创建php相关目录:

mkdir php/{tmpletes,tasks,files,handlers,meta,defaults,vars} -pv

7. 编辑tasks中的main.yml文件,这里只是安装了一个php模块:

---
- name: install php
  yum: name=php state=present

8. 定义mariadb相关的内容的目录:

mkdir mariadb/{templetes,handlers,vars,files,defaults,tasks} -vp

9. 编辑roles/mariadb/tasks/main.yml文件,顺带安装一下几个模块:

---
- name: install mariadb
  yum: name=mariadb-server state=present
- name: install httpd-mysql
  yum: name=php-mysql state=present
- name: install php-mbstring
  yum: name=php-mbstring state=present
- name: restart mariadb 
  service: name=mariadb state=restarted

10. wordpress程序需要手动更改一个配置文件,事先编辑好直接推送wordpress/目录下,这就是roles/httpd/files中的wp-conf.php,需要更改内容如下:

define('DB_NAME', 'wordpress');

/** MySQL数据库用户名 */
define('DB_USER', 'wpuser');

/** MySQL数据库密码 */
define('DB_PASSWORD', 'wppd');

/** MySQL主机 */
define('DB_HOST', 'localhost');

11. 上面定义的数据库需要数据库等需要自行创建,将其写脚本files/mysql_wp.sh文件:

#!/bin/bash
#
mysql -uroot -e " 
        CREATE DATABASE IF NOT EXISTS wordpress CHARACTER  SET 'utf8';
        GRANT ALL ON wordpress.* TO 'wpuser'@'localhost' IDENTIFIED BY 'wppd'; "

12. 与roles同级创建一个yml文件,将这三个项目整合起来运行:

cd /etc/ansible
vim httpd-php_roles.yml
---
- hosts: httpd
  remote_user: root
  roles:
  - { role: php, tags: [ php-tag,ap-tag ] }
  - { role: mariadb, tags: [ mariadb-tag,ap-tag ] }
  - { role: httpd, tags: [ httpd-tag,ap-tag ] }

13. 向定义的目标主机进行发布程序:

~]# ansible-playbook /etc/ansible/httpd-php_roles.yml

只要目标主机yum源可用,没有遇到报错基本OK.

未分类

访问验证一下:

未分类

利用ansible和jenkins实现tomcat应用的持续交付

在做持续交付这件事,想必大家都是用jenkins这款程序来做基石。当然,我们这次也是用jenkins作为承载工具,jenkins强大的插件是有目共睹的,有些ansible做起来不容易的事情交给jenkins反而简单有效。下面我会详细说明怎么持续交付tomcat应用。

应用架构

本次使用的应用架构是常见的负载均衡实例。

未分类

软件版本

  • os: centos 6.7 X64
  • ansible: 2.3.1.0
  • python: 2.6.6
  • ant: 10.1
  • java: 1.8.0_13
  • tomcat: 8.5.14
  • jenkins: 2.73

Ansible roles

  • Ansible Role 系统环境 之【ant】 (http://www.jianshu.com/p/75a129e71685)
  • Ansible Role 系统环境 之【java】(http://www.jianshu.com/p/1be92c3f65ec)
  • Ansible Role 系统环境 之【iptables】 (http://www.jianshu.com/p/1ce357af03bf)
  • Ansible Role WEB 之【tomcat】 (http://www.jianshu.com/p/fd7cca20c227)
  • Ansible Role WEB 之【nginx】 (http://www.jianshu.com/p/447df6f2335a)
  • Ansible Role 持续集成 之【jenkins】 (http://www.jianshu.com/p/f4bac35454b4)
  • Ansible Role 持续交付 之【deploy-tomcat】 (http://www.jianshu.com/p/eadbdb861fa2)

服务器角色

未分类

集群搭建

本次使用anisble playbook

---

- hosts: node130 node131
  vars:
   - java_version: "1.8"
   - tomcat_version: "8.5.14"
   - iptables_allowed_tcp_ports: ["8080"]
  roles:
  - java
  - { role: tomcat, java_home: "/usr/java/jdk1.8.0_131" }
  - iptables

- hosts: node1
  vars:
   - java_version: "1.8"
   - nginx_version: "1.12.1"
   - nginx_upstreams:
     - name: upstremtest
       servers:
       - 192.168.77.130:8080 max_fails=2 fail_timeout=2
       - 192.168.77.131:8080 max_fails=2 fail_timeout=2
   - nginx_vhosts:
     - listen: 80
       locations:
       - name: /
         proxy_pass: http://upstremtest
   - jenkins_version: "2.73"
   - jenkins_plugins_extra:
     - ansible
     - ansicolor
   - iptables_allowed_tcp_ports: ["80","8080"]
  roles:
  - ant
  - java
  - nginx
  - jenkins
  - iptables
  tasks:
  - name: install ansible
    package: name=ansible

怎么使用ansible roles,请移步到 Ansible Role【怎么用?】

确保正常访问以下服务:

  • nginx http://192.168.77.129/lework
  • jenkins http://192.168.77.129:8080 帐号密码:admin/admin
  • tomcat http://192.168.77.130:8080/lework http://192.168.77.131:8080/lework

node1服务器操作

在服务器上配置ansible playbook

# cd /etc/ansible/
# cat tomcat-deploy.yml
---

- hosts: all
  serial: 1
  roles:
   - deploy-tomcat

# cat hosts
[node130]
192.168.77.130

[node131]
192.168.77.131

[testservers:children]
node130
node131

[testservers:vars]
ansible_ssh_user=root
ansible_ssh_pass=123456

# git clone https://github.com/kuailemy123/Ansible-roles.git /etc/ansible/roles/
# chown jenkins.jenkins /etc/ansible/

jenkins 操作

登录jenkins之后,设置工具

点击“系统管理”==》“Global Tool Configuration”

未分类

未分类

未分类

创建发布项目

未分类

配置参数化构建

未分类

配置源码仓库地址

未分类

repo: https://github.com/kuailemy123/AntSpringMVC.git

配置构建环境

未分类

配置编译

未分类

配置ansible

未分类

配置ansible变量

未分类

这里就不配置邮件通知了。

创建回滚项目

未分类

配置参数化构建

未分类

配置构建环境

未分类

配置ansible

未分类

配置anisble变量

未分类

测试

执行tomcat_deploy任务

未分类

选择发布的节点,默认all

任务执行的日志

Started by user admin
Building in workspace /var/lib/jenkins/workspace/tomcat_deploy
Cloning the remote Git repository
Cloning repository https://github.com/kuailemy123/AntSpringMVC.git
 > git init /var/lib/jenkins/workspace/tomcat_deploy # timeout=10
Fetching upstream changes from https://github.com/kuailemy123/AntSpringMVC.git
 > git --version # timeout=10
 > git fetch --tags --progress https://github.com/kuailemy123/AntSpringMVC.git +refs/heads/*:refs/remotes/origin/*
 > git config remote.origin.url https://github.com/kuailemy123/AntSpringMVC.git # timeout=10
 > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
 > git config remote.origin.url https://github.com/kuailemy123/AntSpringMVC.git # timeout=10
Fetching upstream changes from https://github.com/kuailemy123/AntSpringMVC.git
 > git fetch --tags --progress https://github.com/kuailemy123/AntSpringMVC.git +refs/heads/*:refs/remotes/origin/*
 > git rev-parse refs/remotes/origin/master^{commit} # timeout=10
 > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10
Checking out Revision 989ea3a6549e16e3dd4cd329ab969b47658c9d67 (refs/remotes/origin/master)
Commit message: "Create README.md"
 > git config core.sparsecheckout # timeout=10
 > git checkout -f 989ea3a6549e16e3dd4cd329ab969b47658c9d67
First time build. Skipping changelog.
[tomcat_deploy] $ ant -file build.xml -Ddeploy_node=all
Buildfile: /var/lib/jenkins/workspace/tomcat_deploy/build.xml

clean:
   [delete] Deleting directory /var/lib/jenkins/workspace/tomcat_deploy/war/WEB-INF/classes

init:
    [mkdir] Created dir: /var/lib/jenkins/workspace/tomcat_deploy/target
    [mkdir] Created dir: /var/lib/jenkins/workspace/tomcat_deploy/war/WEB-INF/classes

resolve:
     [echo] Getting dependencies...
[ivy:retrieve] :: Apache Ivy 2.4.0 - 20141213170938 :: http://ant.apache.org/ivy/ ::
[ivy:retrieve] :: loading settings :: url = jar:file:/usr/local/ant/lib/ivy-2.4.0.jar!/org/apache/ivy/core/settings/ivysettings.xml
[ivy:retrieve] :: resolving dependencies :: org.apache#WebProject;working@node1
[ivy:retrieve]  confs: [compile, runtime, test]
[ivy:retrieve]  found org.slf4j#slf4j-api;1.7.6 in public
[ivy:retrieve]  found jstl#jstl;1.2 in public
[ivy:retrieve]  found ch.qos.logback#logback-classic;1.1.2 in public
[ivy:retrieve]  found ch.qos.logback#logback-core;1.1.2 in public
[ivy:retrieve]  found org.springframework#spring-core;4.1.3.RELEASE in public
[ivy:retrieve]  found commons-logging#commons-logging;1.2 in public
[ivy:retrieve]  found org.springframework#spring-beans;4.1.3.RELEASE in public
[ivy:retrieve]  found org.springframework#spring-context;4.1.3.RELEASE in public
[ivy:retrieve]  found org.springframework#spring-aop;4.1.3.RELEASE in public
[ivy:retrieve]  found aopalliance#aopalliance;1.0 in public
[ivy:retrieve]  found org.springframework#spring-expression;4.1.3.RELEASE in public
[ivy:retrieve]  found org.springframework#spring-web;4.1.3.RELEASE in public
[ivy:retrieve]  found org.springframework#spring-webmvc;4.1.3.RELEASE in public
[ivy:retrieve] downloading https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.6/slf4j-api-1.7.6.jar ...
[ivy:retrieve] ............ (28kB)
[ivy:retrieve] .. (0kB)
..... 省略下载的信息
[ivy:retrieve] :: resolution report :: resolve 74135ms :: artifacts dl 120701ms
    ---------------------------------------------------------------------
    |                  |            modules            ||   artifacts   |
    |       conf       | number| search|dwnlded|evicted|| number|dwnlded|
    ---------------------------------------------------------------------
    |      compile     |   13  |   13  |   13  |   0   ||   13  |   13  |
    |      runtime     |   13  |   13  |   13  |   0   ||   13  |   13  |
    |       test       |   13  |   13  |   13  |   0   ||   13  |   13  |
    ---------------------------------------------------------------------
[ivy:retrieve] :: retrieving :: org.apache#WebProject
[ivy:retrieve]  confs: [compile, runtime, test]
[ivy:retrieve]  13 artifacts copied, 0 already retrieved (5920kB/79ms)

compile:
    [javac] Compiling 1 source file to /var/lib/jenkins/workspace/tomcat_deploy/war/WEB-INF/classes
copy-resources:
     [copy] Copying 1 file to /var/lib/jenkins/workspace/tomcat_deploy/war/WEB-INF/classes
package:
[ivy:retrieve] :: retrieving :: org.apache#WebProject
[ivy:retrieve]  confs: [runtime]
[ivy:retrieve]  0 artifacts copied, 13 already retrieved (0kB/5ms)
      [war] Building war: /var/lib/jenkins/workspace/tomcat_deploy/target/helloproject-20170819172002.war

main:

BUILD SUCCESSFUL
Total time: 3 minutes 19 seconds
[tomcat_deploy] $ /usr/bin/ansible-playbook /etc/ansible/tomcat-deploy.yml -i /etc/ansible/hosts -l all -f 5 -e deploy_port=8080 -e deploy_file=/var/lib/jenkins/workspace/tomcat_deploy/target/helloproject-*.war

PLAY [all] *********************************************************************

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

TASK [deploy-tomcat : check | 发布文件是否存在] ****************************************
ok: [192.168.77.130]

TASK [deploy-tomcat : check | 目标应用服务的家目录是否存在] **********************************
ok: [192.168.77.130]

TASK [deploy-tomcat : check | 工作目录如果不存在则创建] ************************************
changed: [192.168.77.130] => (item=/tmp/tomcat-ansible-snap/new)
changed: [192.168.77.130] => (item=/tmp/tomcat-ansible-snap/pre)
changed: [192.168.77.130] => (item=/tmp/tomcat-ansible-snap/old)

TASK [deploy-tomcat : deloy | 解压代码至目标服务器] **************************************
changed: [192.168.77.130]

TASK [deploy-tomcat : deloy | 关闭服务] ********************************************
changed: [192.168.77.130]

TASK [deploy-tomcat : deloy | 等待端口关闭] ******************************************
ok: [192.168.77.130]

TASK [deploy-tomcat : deloy | 移动线上代码] ******************************************
changed: [192.168.77.130]

TASK [deploy-tomcat : deloy | 部署最新代码] ******************************************
changed: [192.168.77.130]

TASK [deploy-tomcat : deloy | 启动服务] ********************************************
changed: [192.168.77.130]

TASK [deploy-tomcat : deloy | 等待端口开启] ******************************************
ok: [192.168.77.130]

TASK [deploy-tomcat : verify | 查看http状态.] **************************************
ok: [192.168.77.130]

TASK [deploy-tomcat : backup | 创建存储备份的文件夹] *************************************
changed: [192.168.77.130]

TASK [deploy-tomcat : backup | 备份上线的代码] ****************************************
changed: [192.168.77.130]

TASK [deploy-tomcat : rollback | 检查/tmp/tomcat-ansible-snap/old是否存在代码] *********
skipping: [192.168.77.130]

TASK [deploy-tomcat : rollback | 关闭服务] *****************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : rollback | 等待端口关闭] ***************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : rollback | 部署上一版代码] **************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : rollback | 启动服务] *****************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : rollback | 等待端口开启] ***************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : verify | 查看http状态.] **************************************
skipping: [192.168.77.130]

PLAY [all] *********************************************************************

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

TASK [deploy-tomcat : check | 发布文件是否存在] ****************************************
ok: [192.168.77.131]

TASK [deploy-tomcat : check | 目标应用服务的家目录是否存在] **********************************
ok: [192.168.77.131]

TASK [deploy-tomcat : check | 工作目录如果不存在则创建] ************************************
ok: [192.168.77.131] => (item=/tmp/tomcat-ansible-snap/new)
ok: [192.168.77.131] => (item=/tmp/tomcat-ansible-snap/pre)
ok: [192.168.77.131] => (item=/tmp/tomcat-ansible-snap/old)

TASK [deploy-tomcat : deloy | 解压代码至目标服务器] **************************************
changed: [192.168.77.131]

TASK [deploy-tomcat : deloy | 关闭服务] ********************************************
changed: [192.168.77.131]

TASK [deploy-tomcat : deloy | 等待端口关闭] ******************************************
ok: [192.168.77.131]

TASK [deploy-tomcat : deloy | 移动线上代码] ******************************************
changed: [192.168.77.131]

TASK [deploy-tomcat : deloy | 部署最新代码] ******************************************
changed: [192.168.77.131]

TASK [deploy-tomcat : deloy | 启动服务] ********************************************
changed: [192.168.77.131]

TASK [deploy-tomcat : deloy | 等待端口开启] ******************************************
ok: [192.168.77.131]

TASK [deploy-tomcat : verify | 查看http状态.] **************************************
ok: [192.168.77.131]

TASK [deploy-tomcat : backup | 创建存储备份的文件夹] *************************************
changed: [192.168.77.131]

TASK [deploy-tomcat : backup | 备份上线的代码] ****************************************
changed: [192.168.77.131]

TASK [deploy-tomcat : rollback | 检查/tmp/tomcat-ansible-snap/old是否存在代码] *********
skipping: [192.168.77.131]

TASK [deploy-tomcat : rollback | 关闭服务] *****************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : rollback | 等待端口关闭] ***************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : rollback | 部署上一版代码] **************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : rollback | 启动服务] *****************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : rollback | 等待端口开启] ***************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : verify | 查看http状态.] **************************************
skipping: [192.168.77.131]

PLAY RECAP *********************************************************************
192.168.77.130             : ok=14   changed=8    unreachable=0    failed=0   
192.168.77.131             : ok=14   changed=7    unreachable=0    failed=0   

Finished: SUCCESS

执行tomcat_rollback任务

未分类

选择回滚的节点,默认all

执行的日志

Started by user admin
Building in workspace /var/lib/jenkins/workspace/tomcat_rollback
[tomcat_rollback] $ /usr/bin/ansible-playbook /etc/ansible/tomcat-deploy.yml -i /etc/ansible/hosts -l all -f 5 -e deploy_rollback=true

PLAY [all] *********************************************************************

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

TASK [deploy-tomcat : check | 发布文件是否存在] ****************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : check | 目标应用服务的家目录是否存在] **********************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : check | 工作目录如果不存在则创建] ************************************
skipping: [192.168.77.130] => (item=/tmp/tomcat-ansible-snap/new) 
skipping: [192.168.77.130] => (item=/tmp/tomcat-ansible-snap/pre) 
skipping: [192.168.77.130] => (item=/tmp/tomcat-ansible-snap/old) 

TASK [deploy-tomcat : deloy | 解压代码至目标服务器] **************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : deloy | 关闭服务] ********************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : deloy | 等待端口关闭] ******************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : deloy | 移动线上代码] ******************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : deloy | 部署最新代码] ******************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : deloy | 启动服务] ********************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : deloy | 等待端口开启] ******************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : verify | 查看http状态.] **************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : backup | 创建存储备份的文件夹] *************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : backup | 备份上线的代码] ****************************************
skipping: [192.168.77.130]

TASK [deploy-tomcat : rollback | 检查/tmp/tomcat-ansible-snap/old是否存在代码] *********
changed: [192.168.77.130]

TASK [deploy-tomcat : rollback | 关闭服务] *****************************************
changed: [192.168.77.130]

TASK [deploy-tomcat : rollback | 等待端口关闭] ***************************************
ok: [192.168.77.130]

TASK [deploy-tomcat : rollback | 部署上一版代码] **************************************
changed: [192.168.77.130]

TASK [deploy-tomcat : rollback | 启动服务] *****************************************
fatal: [192.168.77.130]: FAILED! => {"changed": true, "cmd": "/etc/init.d/tomcat start", "delta": "0:00:20.035003", "end": "2017-08-19 17:24:47.586469", "failed": true, "rc": 1, "start": "2017-08-19 17:24:27.551466", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
...ignoring

TASK [deploy-tomcat : rollback | 等待端口开启] ***************************************
ok: [192.168.77.130]

TASK [deploy-tomcat : verify | 查看http状态.] **************************************
ok: [192.168.77.130]

PLAY [all] *********************************************************************

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

TASK [deploy-tomcat : check | 发布文件是否存在] ****************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : check | 目标应用服务的家目录是否存在] **********************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : check | 工作目录如果不存在则创建] ************************************
skipping: [192.168.77.131] => (item=/tmp/tomcat-ansible-snap/new) 
skipping: [192.168.77.131] => (item=/tmp/tomcat-ansible-snap/pre) 
skipping: [192.168.77.131] => (item=/tmp/tomcat-ansible-snap/old) 

TASK [deploy-tomcat : deloy | 解压代码至目标服务器] **************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : deloy | 关闭服务] ********************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : deloy | 等待端口关闭] ******************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : deloy | 移动线上代码] ******************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : deloy | 部署最新代码] ******************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : deloy | 启动服务] ********************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : deloy | 等待端口开启] ******************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : verify | 查看http状态.] **************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : backup | 创建存储备份的文件夹] *************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : backup | 备份上线的代码] ****************************************
skipping: [192.168.77.131]

TASK [deploy-tomcat : rollback | 检查/tmp/tomcat-ansible-snap/old是否存在代码] *********
changed: [192.168.77.131]

TASK [deploy-tomcat : rollback | 关闭服务] *****************************************
changed: [192.168.77.131]

TASK [deploy-tomcat : rollback | 等待端口关闭] ***************************************
ok: [192.168.77.131]

TASK [deploy-tomcat : rollback | 部署上一版代码] **************************************
changed: [192.168.77.131]

TASK [deploy-tomcat : rollback | 启动服务] *****************************************
changed: [192.168.77.131]

TASK [deploy-tomcat : rollback | 等待端口开启] ***************************************
ok: [192.168.77.131]

TASK [deploy-tomcat : verify | 查看http状态.] **************************************
ok: [192.168.77.131]

PLAY RECAP *********************************************************************
192.168.77.130             : ok=8    changed=4    unreachable=0    failed=0   
192.168.77.131             : ok=8    changed=4    unreachable=0    failed=0   

Finished: SUCCESS

至此,持续交付实验就完成了,但是持续之路还是很漫长了。望大家永远前进。 大家也可在发的过程中,测试发布是否是灰度发布。

for i in `seq 10000`;do curl -s -I http://192.168.77.129 | head -1;sleep 1;done;

Centos7安装配置ansible运维自动化工具

一、简介

ansible 和 saltstack 一样都是基于 Python 开发的,是比 puppet 和 saltstack 更轻量级的运维自动化工具。无服务器端,使用时直接运行命令即可,不需要在被管控主机上安装任何客户端,所以任何一台机器只要安装了 ansible 就可以管控其他主机。基于模块工作,可使用任意语言开发模块。也可使用 yaml 语言定制剧本 playbook;基于SSH工作;可实现多级指挥。

二、安装配置

1、准备工作

准备三台机器 Centos7,这两台机器都关闭 selinux,清空 iptables 规则并保存。

  • master:192.168.67.132

  • slaver:192.168.67.129

  • slaver:192.168.2.133

2、编辑 hosts 文件(非必须)

两台都设置,若机器太多,可以通过搭建 DNS,则不用在每台机器上设置这个

  • 192.168.67.132 master.test.com

  • 192.168.67.129 slaver2.test.com

  • 192.168.67.133 slaver3.test.com

3、设置 hostname(非必须)

在 master 上

[root@tree ~]# vim /etc/sysconfig/network
    HOSTNAME=master.test.com

在 slaver 上
[root@tree ~]# vim /etc/sysconfig/network
    HOSTNAME=slaver.test.com

4、安装

//Ansible仓库默认不在yum仓库中,因此我们需要使用下面的命令启用epel仓库
[root@tree ~]# yum install -y epel-release 
//使用yum安装Ansible
[root@tree ~]# yum install -y ansible

5、SSH密钥配置

在Ansible服务端生成密钥,并且复制公钥到节点中。
[root@tree ~]# ssh-keygen -t rsa    //一路回车下去
[root@tree ~]# cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys

使用ssh-copy-id命令来复制Ansible公钥到节点中
[root@tree ~]# ssh-copy-id -i [email protected]     //输入yes和密码
[root@tree ~]# ssh-copy-id -i [email protected]     //输入yes和密码

6、ansible配置

[root@tree ~]# vim /etc/ansible/hosts
[test-servers]
192.168.67.129
192.168.67.132
192.168.67.133

7、在Ansible服务端运行命令,测试是否成功

未分类

Ansible Config Playbook和Task介绍

Config

Ansible 的配置一般不需要更改,如果需要定制,自定义配置也很简单,在 Ansible 中,寻找配置按照如下顺序:

  • ANSIBLE_CONFIG (一个环境变量)
  • ansible.cfg (位于当前目录中)
  • .ansible.cfg (位于家目录中)
  • /etc/ansible/ansible.cfg

因此只需要按照文档自定义配置即可:http://ansible-tran.readthedocs.io/en/latest/docs/intro_configuration.html

Playbook

为了方便保存执行的操作,Ansible 使用了 Playbook 剧本。剧本使用 yml 格式,来避免成为了一种新语言或者脚本。

Playbook 是有 play 组成的,每个 play 包含了 host,user,tasks。

比如一个 playbook:

---
- hosts: webservers
  vars:
    http_port: 80
    max_clients: 200
  remote_user: root
  tasks:
  - name: ensure apache is at the latest version
    yum: pkg=httpd state=latest
  - name: write the apache config file
    template: src=/srv/httpd.j2 dest=/etc/httpd.conf
    notify:
    - restart apache
  - name: ensure apache is running
    service: name=httpd state=started
  handlers:
    - name: restart apache
      service: name=httpd state=restarted

host 便是指定的 hosts 文件中的主机,可以通过 remote_user 指定在远程使用的用户,也可以用 sudo 为远程操作添加 root 权限。

Task

作为远程部署工具,task 是整个 playbook 的重点。每个 task 都会在指定的所有远程主机执行,如果有执行失败的主机,将会被跳过。

每个 task 目标在于执行一个幂等(moudle)的操作,因此即使是多次执行也会很安全。一个 task 类似于下面的格式:

tasks:
  - name: make sure apache is running
    service: name=httpd state=running

一个 task 包含了名称,model,以及参数。

使用Jenkins Ansible Docker Swarm实现自动化编译部署

自动化部署在项目部署过程中很重要,一旦自动化部署完成,我们就可以减轻我们手动的操作的步骤以及出错的概率。下面介绍一种通用的自动化部署。从打包编译到上线,一条命令就完成所有操作。简单而高效

1、Jenkins部署这里不在赘述,直接从新建项目开始。

项目截图,如下:增加两个变量如图,

  • BranchTobuild 默认是master分支,在编译的时候也可以指定分支。

  • PushDockerImage 默认布尔值是选中,直接把镜像推到镜像仓库。

未分类

增加Pipeline配置,主要是分了几个步骤。所以用pipeline,也可以不用这种方式,直接脚本也行

所有执行步骤写入Jenkinsfile中,并把文件放入项目的根目录,这样才能调用。

未分类

Jenkinsfile内容如下:包括编译、打包、推送docker镜像到仓库,ansible部署。

#cat Jenkinsfile
pipeline {

   //定义了执行jenkins的机器
    agent {
        label ‘master’
    }
    stages {
        stage (‘Prepare’) {
            steps {
                sh “echo Prepare”
                //准备阶段,没有暂不处理
            }
        }

        //build打包过程,最后会生产apk包
        stage (‘build’) {
            steps {
                sh ‘echo “start Build”‘
                script {
                    if (params.buildDebug) {
                        sh ‘echo “build debug version”‘
                        sh ‘chmod +x ./gradlew’
                        sh “./gradlew clean”
                        sh “./gradlew build –debug”
                    } else {
                        sh ‘echo “build release version”‘
                        sh ‘chmod +x ./gradlew’
                        sh “./gradlew clean”
                        sh “./gradlew build”
                    }
                }
            }
        }
        stage (‘Test’) {
            steps {
                sh “echo Test”
                //测试阶段,没有暂不处理
            }
        }

       //发布阶段,项目中包括了gradle.properties文件,几个变量:版本、项目、产品,按照这几个名词生产镜像的名
        stage (‘Deploy’) {
            steps {
                sh ‘echo “Start Docker Build And Push Images”‘
                script {
                    if (params.PushDockerImage) {
                        def props = readProperties  file: ‘gradle.properties’
                                    def VERSION = props[‘version’]
                                    def PRODUCT = props[‘product’]
                                    def ARTIFACT = props[‘artifact’]
                        sh ‘echo “start Build”‘
                                    //开始修改Dockerfile
                                    sh “sed -i ‘s#${PRODUCT}#${PRODUCT}#g’ Dockerfile”
                                    sh “sed -i ‘s#${VERSION}#${VERSION}#g’ Dockerfile”
                                    sh “sed -i ‘s#${ARTIFACT}#${ARTIFACT}#g’ Dockerfile”
                        sh “docker build -t registry.sreop.com:5000/${PRODUCT}/${ARTIFACT}:${VERSION} -f Dockerfile .”
                        sh ‘echo “Publish Images To registry.leautolink.com”‘
                        sh “docker push registry.leautolink.com:5000/${PRODUCT}/${ARTIFACT}:${VERSION}”

                        //ansible playbook 部署到线上或者测试环境
                        sh “sudo /usr/bin/ansible-playbook  /data/base-docker-compose/product/light/prod/playbook.yml”
                    }
                }
            }
        }
    }
}

2、每个项目需要一个Dockefile

cat Dockerfile
# 基础镜像
FROM registry.sreop.com:5000/alpine-java:8u121b13_jdk_unlimited
# 维护者信息
MAINTAINER [email protected]
# 镜像操作命令
RUN mkdir -p /data/bin/${PRODUCT}/${ARTIFACT}
# 指定后续命令的执行目录
WORKDIR /data/bin/${PRODUCT}/${ARTIFACT}
# 对外连接端口号
EXPOSE 12429
# 向镜像中增加文件
ADD ./build/libs/${ARTIFACT}-${VERSION}.jar .
# 容器启动命令
CMD java -Djava.security.egd=file:/dev/./urandom -jar ${ARTIFACT}-${VERSION}.jar –spring.profiles.active=prod

3、写ansible playbook,为推送到Docker集群平台,并部署到线上或者测试平台。

注意:docker-compose.yml要拷贝到每个docker节点机器上。功能是:获取各个变量,拉取镜像。最后生产docker services

– name: light Pull Image
  hosts: docker-swarm-prod
  remote_user: root
  tasks:
    – name: get product var 
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml |head -n1|awk -F’/’ ‘{print $2}’
      register: product
    – name: get artifact var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#1#g’
      register: artifact
    – name: get version var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#2#g’
      register: version
    – name: docker pull image
      shell: docker pull registry.leautolink.com:5000/”{{ product.stdout }}”/”{{ artifact.stdout }}”:”{{ version.stdout }}” || /bin/true

– name: Remove Docker Old Verison
  hosts: docker-swarm-prod-lead 
  remote_user: root
  tasks:
    – name: get product var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1|awk -F’/’ ‘{print $2}’
      register: product
    – name: get artifact var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#1#g’
      register: artifact
    – name: get version var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#2#g’
      register: version
    – name: remove light
      shell: docker stack rm {{ artifact.stdout }} || /bin/true

– name: Start New verison
  hosts: docker-swarm-prod-lead 
  remote_user: root
  tasks:
    – name: get product var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1|awk -F’/’ ‘{print $2}’
      register: product
    – name: get artifact var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#1#g’
      register: artifact
    – name: get version var
      shell: grep image /data/base-docker-compose/product/light/prod/docker-compose.yml|head -n1 |awk -F’/’ ‘{print $3}’|sed ‘s#(.*):(.*)#2#g’
      register: version
    – name: start light
      shell: docker stack deploy -c /data/base-docker-compose/product/light/prod/docker-compose.yml {{ artifact.stdout }} || /bin/true 

4、swarm集群部署compose.yml

# cat docker-compose.yml 
version: “3”
services:
  config-server:
    image: registry.sreop.com:5000/leradio/light:1.0.1-RELEASE
    command: java -Djava.security.egd=file:/dev/./urandom -jar light-1.0.1-RELEASE.jar –spring.cloud.config.profile=prod –spring.profiles.active=prod
    ports:
      – 12429:12429
    deploy:
      replicas: 2
      update_config:
        parallelism: 2
      restart_policy:
        condition: on-failure
networks:
  frontend: