ansible入门(二)之ansible-playbook

一、ansible playbook简介

playbook可以理解为ansible的剧本,按剧本中的设定,指定的主机完成一系列规定的操作。

1.1 适用场景

ansible的ad-hoc模式,适用于单个ansible模块操作;当远程主机需要进行大量复杂的操作时,这时候就需要将大量ansible模块操作进行集合成playbook。
这种关系就像,shell命令和shell脚本。

1.2 核心元素

  • Hosts 需要执行tasks的远程主机名,调用inventory中名称
  • Remote_user 指定哪个用户的身份完成tasks
  • Variables:定义变量引用,其变量值来源有多种
  • Tasks:任务,即调用模块完成的某操作
  • Templates:模板,调用jia2文件进行配置管理
  • Handlers:处理器,由某事件触发而执行的操作
  • Tags: 标签,将某个任务标出,供单独执行
  • Roles:角色,将复杂的任务拆分若干小文件

1.3 语法格式

playbook适用yaml语法编写,其文件后缀名为.yaml。其基本语法如下

文件的第一行应该以 "---" (三个连字符)开始,表明YMAL文件的开始。
# 后的内容表示注释。
 - 用于表示数据之间的关系是属于同一列表,-后需要空一格。 
:用于表示数据之间的关系是键值对,:后需要空一格。
通过缩进不同(通常是两个空格)表示不同层结构,同一个列表中的元素应该保持相同的缩进。否则会被当做错误处理。

举例如下

- hosts: web
  remote_user: root
  gather_facts: no #整个playbook当中都没有使用到fact变量,可以关闭fact以提升执行效率
  tasks:

    - name: install nginx
      yum: name=nginx state=present
    - name: copy nginx.conf
      copy: src=/tmp/nginx.conf dest=/etc/nginx/nginx.conf backup=yes
      notify: reload    #当此步骤执行后通知给名为reload的handlers
      tags: reloadnginx   #给此步骤打标签
    - name: start nginx service
      service: name=nginx state=started
      tags: startnginx   #给此步骤打标签

  handlers:  #注意,前面没有-,与hosts、tasks等相同缩进
    - name: reload
      service: name=nginx state=restarted  

1.4 执行playbook

使用ansible-playbook命令运行playbook文件,得到输出内容为JSON格式。并且由不同颜色组成,便于识别。一般而言

  • 绿色代表执行成功,系统保持原样
  • 黄色代表系统代表系统状态发生改变
  • 红色代表执行失败,显示错误输出

执行有三个步骤:1、收集facts 2、执行tasks 3、报告结果

二、常用元素详解

2.1 variables变量

在playbook中定义为变量引用,其格式为{{变量名}},可引用的来源有如下几种

  1. 当使用ansible-playbook时,第一步会将远程主机的信息收集存放在facts集合中,其中的变量可以直接在playbook中引用{{}}。

  2. 当运行ansible-playbook命令时,可以使用—extra-vars或-e为playbook中的变量传递值,级别最高,例如

     ansible-playbook test.yml -e "hosts=www user=mageedu" 
    
  3. 使用register元素把任务的输出定义为某个变量的值,供后面任务引用,例如

    tasks:
      - shell: /usr/bin/foo
        register: foo_result
    
  4. 当给一个主机应用角色的时候可以传递变量,然后在角色内使用这些变量,示例如下:

    - hosts: webservers
      roles:
    	- common
        - { role: foo_app, dir: '/web/htdocs/a.com',  port: 8080 }
    
  5. 在inventor文件中定义变量

    #test组中包含两台主机,通过对test组指定vars,相当于为host1和host2相指定了ntp_server和proxy变量参数值
    [test]
    host1
    host2
    [test:vars]
    ntp_server=192.168.1.10
    proxy=192.168.1.20
    
    # 下面是一个示例,指定了一个武汉组有web1、web2;随州组有web3、web4主机;又指定了一个湖北组,同时包含武汉和随州;同时为该组内的所有主机指定了2个vars变量。
    [wuhan]
    web1
    web2
    
    [suizhou]
    web4
    web3
    
    [hubei:children]
    wuhan
    suizhou
    
    [hubei:vars]
    ntp_server=192.168.1.10
    zabbix_server=192.168.1.10
    
  6. 在playbook文件中使用vars为整个playbook定义变量

    - hosts: webservers
      vars:
        - role: foo_app
          port: 8080
    
  7. 使用ansible的魔法变量
    具体参见https://www.cnblogs.com/breezey/p/9275763.html

2.2 templates模板

template模块实现模板文件的分发,其用法与copy模块基本相同,唯一的区别是,copy模块会将原文件原封不动的复制到远程主机,而template会将原文件复制到远程主机,并且使用变量的值将文件中的变量替换以生成完整的配置文件。

template使用了Jinjia2语言编写原文件模版,templates文件必须存放于playbook剧本同级templates目录下,且命名为 .j2 结尾 。

Jinjia2语言拥有如下形式:

  • 字符串:使用单引号或双引号
  • 数字:整数,浮点数
  • 列表: [item1, item2, …]
  • 元组: (item1, item2,…)
  • 字典: {key1:value1, key2:value2, …}
  • 布尔型: true/false
  • 算术运算: +, -,*, /, //, %, **
  • 比较操作: ==, !=, >, >=, <, <=
  • 逻辑运算: and, or, not
  • 流程控制表达式: For If When

模板文件之if判断
部分Jinjia2模板内容

......
{% if ansible_eth0.ipv4.address %}
bind {{ ansible_eth0.ipv4.address }} 127.0.0.1
#如果网卡eth0存在ip地址则以上面的bind为模板
{% elif ansible_bond0.ipv4.address %}
bind {{ ansible_bond0.ipv4.address }} 127.0.0.1
#如果网卡eth0不存在地址网卡bond0存在ip地址则以上面的bind为模板
{% else%}
bind 0.0.0.0
#如果都不存在则以上面的bind为模板
{% endif %}
.....

模板文件之for循环
inventory文件内容

....
[webserver]
192.168.80.111
192.168.80.121
....

部分Jinjia2模板内容

....
    upstream web {
    {% for host in groups['webserver'] %}
        server {{ hostvars[host]['ansible_eth0']['ipv4']['address'] }};
    {% endfor %}
    }
....

2.3 playbook之判断

playbook中的某个task的执行依赖某个条件,这个条件可以是变量或前一个任务的执行结果等;这时候就需要进行条件判断,playbook中的关于task执行前的条件判断使用关键字when。

举例

- name: Install vim
  hosts: all
  tasks:
    - name:Install VIM via yum
      yum: 
        name: vim-enhanced 
        state: installed
      when: ansible_os_family =="RedHat"
      
    - name:Install VIM via apt
      apt: 
        name: vim 
        state: installed
      when: ansible_os_family =="Debian"
      
    - name: Unexpected OS family
      debug: msg="OS Family {{ ansible_os_family }} is not supported" fail=yes
      when: not ansible_os_family =="RedHat" or ansible_os_family =="Debian"

tasks中有三个任务task,当远程主机是RedHat系统则执行第一个,当远程主机是Debian则执行第二个,当都不是时候执行第三个。

条件表达式中可以使用逻辑运算and,or,not,()

条件表达式为判断变量

  • defined:判断变量是否已定义,已定义则返回真
  • undefined:判断变量是否未定义,未定义则返回真
  • none:判断变量的值是否为空,如果变量已定义且值为空,则返回真
- hosts: test
  gather_facts: no
  vars:
    testvar: "test"
    testvar1:
  tasks:
    - debug:
        msg: "testvar is defined"
      when: testvar is defined
    - debug:
        msg: "testvar2 is undefined"
      when: testvar2 is undefined
    - debug:
        msg: "testvar1 is none"
      when: testvar1 is none

条件表达式为判断前面任务执行结果

  • sucess或succeeded:通过任务执行结果返回的信息判断任务的执行状态,任务执行成功则返回true
  • failure或failed:任务执行失败则返回true
  • change或changed:任务执行状态为changed则返回true
  • skip或skipped:任务被跳过则返回true
 - hosts: test
  gather_facts: no
  vars:
    doshell: true
    
  tasks:
    - shell: 'cat /testdir/aaa'
      when: doshell
      register: result
      ignore_errors: true
      
    - debug:
        msg: "success"
      when: result is success
      
    - debug:
        msg: "failed"
      when: result is failure
      
    - debug:
        msg: "changed"
      when: result is change
      
    - debug:
        msg: "skip"
      when: result is skip

条件表达式为判断路径

  • file:判断指定路径是否为一个文件,是则为真
  • directory:判断指定路径是否为一个目录,是则为真
  • link:判断指定路径是否为一个软链接,是则为真
  • mount:判断指定路径是否为一个挂载点,是则为真
  • exists:判断指定路径是否存在,存在则为真
- hosts: test
  gather_facts: no
  vars:
    testpath1: "/testdir/test"
    testpath2: "/testdir"
  tasks:
    - debug:
        msg: "file"
      when: testpath1 is file
    - debug:
        msg: "directory"
      when: testpath2 is directory

注意:关于路径的所有判断均是判断主控端上的路径,而非被控端上的路径

条件表达式为判断字符串

  • lower:判断字符串中的所有字母是否都是小写,是则为真
  • upper:判断字符串中的所有字母是否都是大写,是则为真
  • in:判断一个字符串是否存在于另一个字符串中,也可用于判断某个特定的值是否存在于列表中
  • string:判断对象是否为一个字符串,是则为真
  • number:判断对象是否为一个数字,是则为真
- hosts: test
  vars:
    supported_distros:
      - RedHat
      - CentOS
  tasks:
    - debug:
        msg: "{{ ansible_distribution }} in supported_distros"
      when: ansible_distribution in supported_distros

条件判断之block
当我们要使用同一个条件判断执行多个任务的时候,可以对多个任务进行绑定为同一block整天进行判断。
举例

- hosts: test
  tasks:
    - debug:
        msg: "task1 not in block"
    - block:
        - debug:
            msg: "task2 in block1"
        - debug:
            msg: "task3 in block1"
      when: 2 > 1

rescue

- hosts: test
  tasks:
    - block:
        - shell: 'ls /testdir'
      rescue:
        - debug:
            msg: '/testdir is not exists'

在上面的例子中,当block中的任务执行失败时,则运行rescue中的任务。如果block中的任务正常执行,则rescue的任务就不会被执行。如果block中有多个任务,则任何一个任务执行失败,都会执行rescue。block中可以定义多个任务,同样rescue当中也可以定义多个任务

always
当block执行失败时,rescue中的任务才会被执行;而无论block执行成功还是失败,always中的任务都会被执行

- hosts: test
  tasks:
    - block:
        - shell: 'ls /testdir'
      rescue:
        - debug:
            msg: '/testdir is not exists'
      always:
        - debug:
            msg: 'This task always executes'

条件判断之错误处理
fail模块
在shell中,可能会有这样的需求:当脚本执行至某个阶段时,需要对某个条件进行判断,如果条件成立,则立即终止脚本的运行。在shell中,可以直接调用"exit"即可执行退出。事实上,在playbook中也有类似的模块可以做这件事。即fail模块。

fail模块用于终止当前playbook的执行,通常与条件语句组合使用,当满足条件时,终止当前play的运行。

选项只有一个:

  • msg:终止前打印出信息
- hosts: test
  tasks:
    - shell: echo "Just a test--error" 
      register: result
    - fail:
        msg: "Conditions established,Interrupt running playbook"
      when: "'error' in result.stdout"
    - debug:
        msg: "Inever execute,Because the playbook has stopped"

failed_when
事实上,当fail和when组合使用的时候,还有一个更简单的写法,即failed_when,当满足某个条件时,ansible主动触发失败

#如果在command_result存在错误输出,且错误输出中,包含了`FAILED`字串,即返回失败状态:
- name: this command prints FAILED when it fails
  command: /usr/bin/example-command -x -y -z
  register: command_result
  failed_when: "'FAILED' in command_result.stderr"

也可以直接通过fail模块和when条件语句,写成如下

- name: this command prints FAILED when it fails
  command: /usr/bin/example-command -x -y -z
  register: command_result
  ignore_errors: True

- name: fail the play if the previous command did not succeed
  fail: msg="the command failed"
  when: " command_result.stderr and 'FAILED' in command_result.stderr"

ansible一旦执行返回失败,后续操作就会中止,所以failed_when通常可以用于满足某种条件时主动中止playbook运行的一种方式。

ansible默认处理错误的机制是遇到错误就停止执行。但有些时候,有些错误是计划之中的。我们希望忽略这些错误,以让playbook继续往下执行。这个时候就可以使用ignore_errors忽略错误,从而让playbook继续往下执行

2.3 playbook之循环

loop关键字
启动httpd和postfilx服务

tasks:
  - name: postfix and httpd are running
    service:
      name: "{{ item }}"
      state: started
    loop:
      - postfix
      - httpd

也可以将loop循环的列表提前赋值给一个变量,然后在循环语句中调用

#cat test_services.yml
test_services:
  - postfix
  - httpd

# cat install_pkgs.yml 
- name: start services
  hosts: test
  vars_files:
    - test_services.yml
  tasks:
    - name: postfix and httpd are running
      service:
        name: "{{ item }}"
        state: started
      loop: "{{ test_services }}"

下面是一个循环更复杂类型数据的示例

# cat test_loop.yml 
- name: test loop
  hosts: test
  tasks:
  - name: add www group
    group: 
      name: www
  - name: add several users
    user: 
      name: "{{ item.name }}"
      state: present 
      groups: "{{ item.groups }}"
    loop:
      - { name: 'testuser1', groups: 'wheel' }
      - { name: 'testuser2', groups: 'www' }

在循环语句中注册变量

- name: Loop Register test
  gather_facts: no
  hosts: test
  tasks:
    - name: Looping Echo Task
      shell: "echo this is my item: {{ item }}"
      loop:
        - one
        - two
      register: echo_results
    - name: Show echo_results variable
      debug:
        var: echo_results

with_items 单词循环

- hosts: test
  vars:
    data:
      - user0
      - user1
      - user2
  tasks:
    - name: "with_items"
      debug:
        msg: "{{ item }}"
      with_items: "{{ data }}"

with_nested 嵌套循环

tasks: 
  - name: debug loops
    debug: msg="name is {{ item[0] }}  vaule is {{ item[1] }} num is {{ item[2] }}"
    with_nested:
      - ['alice','bob']
      - ['a','b','c']
      - ['1','2','3']
#item[0]是循环的第一个列表的值['alice','bob']。item[1]是第二个列表的值;item[2]则是第三个列表的值

#部分执行结果
TASK [debug loops] ***********************************************************************************************
ok: [10.1.61.187] => (item=['alice', 'a', '1']) => {
    "msg": "name is alice  vaule is a num is 1"
}
ok: [10.1.61.187] => (item=['alice', 'a', '2']) => {
    "msg": "name is alice  vaule is a num is 2"
}
ok: [10.1.61.187] => (item=['alice', 'a', '3']) => {
    "msg": "name is alice  vaule is a num is 3"
}
ok: [10.1.61.187] => (item=['alice', 'b', '1']) => {
    "msg": "name is alice  vaule is b num is 1"
}

with_dict 循环字典

# 假如有如下变量内容:
users:
  alice:
    name: Alice Appleworth
    telephone: 123-456-7890
  bob:
    name: Bob Bananarama
    telephone: 987-654-3210

# 现在需要输出每个用户的用户名和手机号:
tasks:
  - name: Print phone records
    debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
    with_dict: "{{ users }}"

with_fileglob 循环指定目录中的文件

- hosts: test
  tasks:
    - name: Make key directory     
      file: 
        path: /root/.sshkeys 
        state: directory 
        mode: 0700 
        owner: root 
        group: root 
        
    - name: Upload public keys     
      copy: 
        src: "{{ item }}"
        dest: /root/.sshkeys
        mode: 0600 
        owner: root 
        group: root  
      with_fileglob:
        - /root/.ssh/*.pub 
        
    - name: Assemble keys into authorized_keys file     
      assemble: 
        src: /root/.sshkeys 
        dest: /root/.ssh/authorized_keys
        mode: 0600 
        owner: root 
        group: root

with_lines 循环一个文件中的所有行

with_lines循环结构会让你在控制主机上执行任意命令,并对命令的输出进行逐行迭代。假设我们有一个 文件test.txt包含如下行
Breeze Yan
Bernie Yang
jerry Qing
我们可以通过如下方法进行逐行输出

- name: print all names
  debug: msg="{{ item }}"
  with_lines:
    - cat test.txt

do-until循环

- action: shell /usr/bin/foo
  register: result
  until: result.stdout.find("all systems go") != -1
  retries: 5  #重试次数
  delay: 10   #每次循环之间的间隔,默认为5秒

2.4 playbook高级用法

任务委托
在有些时候,我们希望运行一个与选定的主机有关联的task,但是这个task又不需要在选定的主机上执行,而需要在另一台服务器上执行
这就是任务委托。

可以使用delegate_to语句来在另一台主机上运行task

- name: enable alerts for web servers
  hosts: webservers
  tasks:
    - name: enable alerts
      nagios: action=enable_alerts service=web host="{{ inventory_hostname }}"
      delegate_to: nagios.example.com

注意以上面为例,如果hosts有多个远程主机,那么每个远程主机都会委托一次,导致enable alerts操作执行多次。如果只需要一个主机执行就可以的话,可配合run_once使用

任务暂停
有些情况下,一些任务的运行需要等待一些状态的恢复,比如某一台主机或者应用刚刚重启,我们需要需要等待它上面的某个端口开启,此时就需要将正在运行的任务暂停,直到其状态满足要求。

Ansible提供了wait_for模块以实现任务暂停的需求

wait_for模块常用参数:

  • connect_timeout:在下一个任务执行之前等待连接的超时时间
  • delay:等待一个端口或者文件或者连接到指定的状态时,默认超时时间为300秒,在这等待的300s的时间里,wait_for模块会一直轮询指定的对象是否到达指定的状态,delay即为多长时间轮询一次状态
  • host:wait_for模块等待的主机的地址,默认为127.0.0.1
  • port:wait_for模块待待的主机的端口,向该端口发请求以进行判定
  • path:文件路径,只有当这个文件存在时,下一任务才开始执行,即等待该文件创建完成
  • state:等待的状态,即等待的文件或端口或者连接状态达到指定的状态时,下一个任务开始执行。当等的对象为端口时,状态有started,stoped,即端口已经监听或者端口已经关闭;当等待的对象为文件时,状态有present或者started,absent,即文件已创建或者删除;当等待的对象为一个连接时,状态有drained,即连接已建立。默认为started
  • timeout:wait_for的等待的超时时间,默认为300秒
#等待8080端口已正常监听,才开始下一个任务,直到超时
- wait_for: 
    port: 8080 
    state: started  
    
#等待8000端口正常监听,每隔10s检查一次,直至等待超时
- wait_for: 
    port: 8000 
    delay: 10 
    
#等待8000端口直至有连接建立
- wait_for: 
    host: 0.0.0.0 
    port: 8000 
    delay: 10 
    state: drained
    
#等待8000端口有连接建立,如果连接来自10.2.1.2或者10.2.1.3,则忽略。
- wait_for: 
    host: 0.0.0.0 
    port: 8000 
    state: drained 
    exclude_hosts: 10.2.1.2,10.2.1.3 
    
#等待/tmp/foo文件已创建    
- wait_for: 
    path: /tmp/foo 

#等待/tmp/foo文件已创建,而且该文件中需要包含completed字符串    
- wait_for: 
    path: /tmp/foo 
    search_regex: completed 

#等待/var/lock/file.lock被删除    
- wait_for: 
    path: /var/lock/file.lock 
    state: absent 
    
#等待指定的进程被销毁
- wait_for: 
    path: /proc/3466/status 
    state: absent 
    
#等待openssh启动,10s检查一次
- wait_for: 
    port: 22 
    host: "{{ ansible_ssh_host | default(inventory_hostname) }}" search_regex: OpenSSH 
    delay: 10 

注意以上面为例,如果hosts有多个远程主机,那么每个远程主机都会wait_for一次。如果只需要一个主机wait_for就足够,可配合run_once使用。

滚动执行
默认情况下,ansible会并行的在所有选定的主机或主机组上执行每一个task,但有的时候,我们会希望能够逐台运行。最典型的例子就是对负载均衡器后面的应用服务器进行更新时。通常来讲,我们会将应用服务器逐台从负载均衡器上摘除,更新,然后再添加回去。我们可以在play中使用serial语句来告诉ansible限制并行执行play的主机数量。

下面是一个在amazon EC2的负载均衡器中移除主机,更新软件包,再添加回负载均衡的配置示例:

- name: upgrade pkgs on servers behind load balancer
  hosts: myhosts
  serial: 1
  tasks:
    - name: get the ec2 instance id and elastic load balancer id
      ec2_facts:

    - name: take the host out of the elastic load balancer id
      local_action: ec2_elb
      args:
        instance_id: "{{ ansible_ec2_instance_id }}"
        state: absent

    - name: upgrade pkgs
      apt: 
          update_cache: yes 
          upgrade: yes

    - name: put the host back n the elastic load balancer
      local_action: ec2_elb
      args:
        instance_id: "{{ ansible_ec2_instance_id }}"
        state: present
        ec2_elbs: "{{ items }}"
      with_items: ec2_elbs

在上述示例中,serial的值为1,即表示在某一个时间段内,play只在一台主机上执行。如果为2,则同时有2台主机运行play。

一般来讲,当task失败时,ansible会停止执行失败的那台主机上的任务,但是继续对其他 主机执行。在负载均衡的场景中,我们会更希望ansible在所有主机执行失败之前就让play停止,否则很可能会面临所有主机都从负载均衡器上摘除并且都执行失败导致服务不可用的场景。这个时候,我们可以使用serial语句配合max_fail_percentage语句使用。max_fail_percentage表示当最大失败主机的比例达到多少时,ansible就让整个play失败。示例如下:

- name: upgrade pkgs on fservers behind load balancer
  hosts: myhosts
  serial: 1
  max_fail_percentage: 25
  tasks:
    ......

假如负载均衡后面有4台主机,并且有一台主机执行失败,这时ansible还会继续运行,要让Play停止运行,则必须超过25%,所以如果想一台失败就停止执行,我们可以将max_fail_percentage的值设为24。如果我们希望只要有执行失败,就放弃执行,我们可以将max_fail_percentage的值设为0

调试模块
调试模块,用于在调试中输出信息
常用参数:
msg:调试输出的消息
var:将某个任务执行的输出作为变量传递给debug模块,debug会直接将其打印输出
verbosity:debug的级别(默认是0级,全部显示)

例子:

- name: Print debug infomation eg1 
   hosts: test2 
   gather_facts: F 
  vars: 
    user: jingyong 
  tasks: 
  - name: Command run line 
    shell: date 
    register: result 
  - name: Show debug info 
    debug: var=result verbosity=0
#程序是将命令date返回信息使用debug模块打印出来。

#返回结果如下:

PLAY [Print debug infomation eg] *********************************************** 

TASK [Show debug info] ********************************************************* 
ok: [192.168.0.1] ={ 
"result": { 
"changed": true, 
"cmd": "date", 
"delta": "0:00:00.002400", 
"end": "2016-08-27 13:42:16.502629", 
"rc": 0, 
"start": "2016-08-27 13:42:16.500229", 
"stderr": "", 
"stdout": "2016年 08月 27日 星期六 13:42:16 CST", 
"stdout_lines": [ 
"2016年 08月 27日 星期六 13:42:16 CST" 
], 
"warnings": [] 
} 
} 

PLAY RECAP ********************************************************************* 
192.168.0.1 : ok=2changed=1unreachable=0failed=0   

可以看到debug不光输出了date命令结果,还返回了很多相关调试信息,只需要date返回值,可以使用变量属性过滤 如:result.stdout 就是命令的返回值。

程序改成:

- name: Print debug infomation eg 
  hosts: test2 
  gather_facts: F 
  tasks: 
  - name: Command run line 
    shell: date 
    register: result 
  - name: Show debug info 
    debug: var=result.stdout verbosity=0
#运行结果:

PLAY [Print debug infomation eg] *********************************************** 

TASK [Command run line] ******************************************************** 
changed: [192.168.0.1] 

TASK [Show debug info] ********************************************************* 
ok: [192.168.0.1] ={ 
"result.stdout": "2002年 01月 12日 星期六 03:16:26 CST" 
} 

PLAY RECAP ********************************************************************* 
192.168.0.1  : ok=3changed=1unreachable=0failed=0   

实际案例

备份老的war包

- hosts: "vcs-{{ csport }}-{{ project }}"
  user: root
  vars:
    war_path: /data/apps/{{ project }}.war
    backup_ahead_path: "/data/backup/{{ project }}"
    backup_link_path: "/data/backup/{{ project }}_laster"
    
  tasks:
    - name: 1/6 Get war time 获取老war的时间作为版本号
      shell: stat -c %y {{ war_path }}|awk -F. '{print $1}'|awk '{print $1"_"$2}'|awk -F":" '{print $1"_"$2}'
      register: war_time
      
    - name: 2/6 Create backup dir 以老war包的时间创建备份目录
      file: dest={{ backup_ahead_path }}_{{ war_time.stdout }} state=directory
      
    - name: 3/6 Backup  将老的war包复制到备份目录下
      shell: /bin/cp -ar {{ war_path }} {{ backup_ahead_path }}_{{ war_time.stdout }}
      
    - name: 4/6 Delete old war  删除老的war包
      file: path={{ war_path }} state=absent
      
    - name: 5/6 Delete old soft link 删除老的链接文件 laster
      file: path={{ backup_link_path }} state=absent
      
    - name: 6/6 Create new soft link 以新的备份目录创建链接文件 laster
      file: src={{ backup_ahead_path }}_{{ war_time.stdout }} dest={{ backup_link_path }} state=link

通过nginx更新tomcat的war包

- hosts: "vcs-{{ csport }}-{{ project }}"
  user: root
  serial: 1
  vars:
    package: "{{ project }}.war"
    storage_path: /data/upload
    war_path: /data/apps
    webapp_path: /data/app/tomcat8_{{ csport }}/webapps/ROOT
  tasks:
    - name: 1/11 Create project_path 保证tomcat存在项目目录
      file: dest={{ webapp_path }} state=directory
    - name: 2/11 Copy war  将war包复制到tomcat上存放war包的目录
      copy: src={{ storage_path }}/{{ package }} dest={{ war_path }}
    - name: 3/11 nginx upstream down  nginx修改配置将tomcat服务器标记为服务不可用
      replace:
        dest: /data/app/nginx/nginx/conf/nginx.conf
        regexp: '(server {{ ansible_eth0.ipv4.address }}:{{ csport}});'
        replace: '\1 down;'
      delegate_to: 127.0.0.1
    - name: 4/11 nginx restart  nginx重启新配置生效
      shell: /data/app/nginx/nginx/sbin/nginx -s reload
      delegate_to: 127.0.0.1 ##将此任务委托给新节点执行
    - name: 5/11 Stop tomcat  停止tomcat
      shell: /bin/bash -c "/data/app/tomcat8_{{ csport }}/bin/catalina.sh stop"
      ignore_errors: true
    - name: wait process stop  等待tomcat进程关闭,超时时间15s
      wait_for: port={{ csport }} state=stopped timeout=15
    - name: 6/11  Kill process  强制杀tomcat进程
      shell: ps -ef|grep tomcat8_{{ csport }}|grep -v grep |awk '{print $2}'|xargs kill -9
      ignore_errors: true
    - name: 7/11 Remove the old ROOT dir  删除老的项目目录
      file: path={{ webapp_path }} state=absent
    - name: sleep 10s    
      command: sleep 10s
    - name: 8/11 Start tomcat  启动tomcat
      shell: /bin/bash -c "nohup /data/app/tomcat8_{{ csport }}/bin/catalina.sh start &"
    - name: wait process start  
      wait_for: port={{ csport }} state=started timeout=15 
    - name: 9/11 healthcheck  健康状态检查新war
      shell: curl -I -o /dev/null -s -w %{http_code}"\n"  "{{ ansible_eth0.ipv4.address }}:{{ csport }}/1.html"
      register: httpcode
    - debug: 
        msg: httpcode is {{ httpcode.stdout }}
    - name: 10/11 nginx upstream up 将tomcat标记为生效
      replace:
        dest: /data/app/nginx/nginx/conf/nginx.conf
        regexp: '(server {{ ansible_eth0.ipv4.address }}:{{ csport}}) down;'
        replace: '\1;'
      delegate_to: 127.0.0.1
      failed_when: httpcode.stdout !="200"  健康状态检查失败则终止
    - name: 11/11 nginx restart
      shell: /data/app/nginx/nginx/sbin/nginx -s reload
      delegate_to: 127.0.0.1
     ```
发布了40 篇原创文章 · 获赞 2 · 访问量 2048

猜你喜欢

转载自blog.csdn.net/weixin_42155272/article/details/103296117