Playbook
在上一节中,我们详细介绍了Ansible提供的一些常用模块。可以看到,Ansible中的每个模块专注于某一方面的功能。虽然每个模块实现的功能都比较简单,但是,将各个模块组合起来就可以实现比较复杂的功能。在Ansible中,将各个模块组合起来的文件是一个YAML格式的配置文件。这个配置文件,在Ansible中称为Playbook。
在这一节中,我们将循序渐进地介绍Ansible中的Playbook,我们将首先介绍Playbook的定义,然后介绍如何使用Playbook完成远程服务器部署,之后详细介绍Playbook的基本语法,使用Playbook的基本讲法就能够完成大部分的部署任务。
在这一节中,找们将介绍如何使用Playbook的基本语法完成nginx与MongoDB的部署,最后,我们介绍了部分Playbook的高级语法。
1、Playbook的定义
Playbook不同于其他使用单个模块操作远程服务器,Playbook的功能更加强大。如果只使用Playbook的基本功能,那么,Playbook是一个非常简单的配、管理和部署系统。此外,Playbook也可以实现各种高级功能,如指定任务的执行顺序,委派其他主机来执行某一个任务,与监控服务器和负载均衡组件进行交互等。
有一个非常恰当的比喻,,Ansible中的模块类似于Linux下的命令,Ansible中的Playbook类似于Linux下的Shell脚本文件。Shell脚本文件将各个Linux命令组合起来,以此实现复杂的功能,Playbook将各个模块组合起来也可以实现复杂的部署功能。在shell脚本中,除了调用Linux命令以外,还有一些基本的语法,如变量定义、if语句、for循环等。在Playbook中,一方面通过YAML格式进行定义提高Playbook的可读性、可维护性,降低工程师的学习负担;另一方面,Ansible提供了若干可以应用在Playbook中的选项,以便工程师实现更加高级的功能。
一个Playbook可以包含一到多个Play,每一个Play是一个完整的部署任务。在Play中,我们需要指定对哪些远程服务器执行操作,以及对这些远程服务器执行哪些操作。
下面是一个名为first_playbook.yml的Playbook。在这个Playbook中,我们定义了两个Play,前者用来在数据库服务器上部署MongoDB,后者用来在web服务器上部署“应用”。这里只是为了对Playbook进行演示,并没有真的部署应用。
[root@python ~]# vim first_playbook.yml --- - hosts: dbservers become: yes become_method: sudo tasks: - name: install mongodb yum: name=mongodb-server state=present - hosts: webservers tasks: - name: copy file copy: src=/tmp/data.txt dest=/tmp/data.txt - name: change mode file: dest=/tmp/data.txt mode=655 owner=root group=root
这个Playbook中包含了两个Play。一个Playbook可以包含一到多个Play,所以即使Playbook中值包含一个Play,也需要使用列表的形式进行定义。在YAML语法中,“- hosts”前面的“-”表示定义列表。
在Ansible中,一个Play必须包含以下两项:
1. hosts:需要对哪些远程服务器执行操作
2. tasks:需要在这些服务器上执行的任务列表
例如,对web服务器进行部署时,我们仅仅使用了hosts和tasks两个选项。前者表示对哪些服务器执行操作,后者表示对服务器执行哪些操作。在部署数据库服务器时需要安装软件,因此使用了become与become_method两个选项,用来表示使用管理员的身份去安装MongoDB数据库。
一个Play可以包含一到多个task,因此task也必须以YAML的列表形式进行定义。可以看到,在这个例子中,对数据库服务器进行操作时仅包含了一个task,对web服务器进行部署时包含了两个task。
在Ansible中,task有两种定义形式:
1. action:module options
2. module:options
前一种形式是Ansible的旧版本语法,第2种形式是新版本的语法,直接使用模块的名称作为键,使用模块的参数作为值。如下所示:
- name: install httpd yum: name=httpd update_cache=yes state=present
在安装Apache的例子中,“name=httpd update_cache=yes state=present”是一个完整的字符串,而不是一个字典。只是字符串的值是一个“key=value”形式的参数。
在参数较多时,为了增加Playbook的可读性,我们也可以像下面这样定义一个task:
- name: install httpd yum: > name=httpd update_cache=yes state=present
在Ansible中,当参数较长时,除了使用“>”进行折叠换行以外,也可以使用缩进字块的形式:
- name: install httpd yum: name: httpd update_cache: yes state: present
虽然从字面来看,这两种指定参数的方式相差不大。但是,从YAML的语法来说,这是完全不同的两个方法。前者是一个比较长的字符串,后者是一个字典。
task的定义中,name是可选的。所以,像下面这样定义task也是完全合法的:
- yum: name=httpd update_cache=yes state=present
name的作用在于,执行Playbook时作为注释进行显示,以便使用者知道当前执行到哪一步。因此,在定义task时,一般都会定义name字段。
在实际工作中,虽然一个Playbook可以包含多个Play,但是为了Playbook的可读性和可维护性,我们一般只会在Playbook中编写一个Play。例如,对于这里的例子,我们可以将first_playbook.yml这个Playbook拆分成两个Playbook,分别名为db.yml与web.yml。其中,db.yml文件包含了与数据库服务器相关的部署任务,web.yml文件包含了与web服务器相关的部署任务。
当我们需要部署数据库服务器和web服务器时,可以先执行db.yml文件,再执行web.yml文件。除此之外,Ansible还提供了一种便捷方式来处理这种情况。例如,我们可以编写一个名为all.yml的Playbook,它的内容如下:
--- - include: db.yml - include: web.yml
include选项是Ansible提供的,用于在一个Playbook中导入其他Playbook。在Ansible中,只需要使用include选项导入其他Playbook文件,执行这个Playbook时,被导入的Playbook便会依次执行。
上面详细介绍了Ansible的Playbook定义,这个Playbook定义虽然比较简单,但是,是一个比较完整的Playbook例子。在实际工作中使用的Playbook也不会比这个Playbook复杂很多。
我们接下来将介绍如何使用ansible-playbook命令执行Playbook,然后再介绍Playbook的其他语法。
2、ansible拆分playbook.yml
查看一下所需文件是否正确
[root@python ~]# cat hosts 127.0.0.1 [webservers] 192.168.1.60 [dbservers] 192.168.1.80 [common:children] dbservers webservers [root@python ~]# cat /etc/ansible/ansible.cfg [defaults] remote_user = root remote_port = 22 inventory = /root/hosts
拆分playbook.yml
[root@python ~]# cat db.yml --- - hosts: dbservers become: yes become_method: sudo tasks: - name: install mongodb yum: name=mongodb-server state=present #mongodb-server 可欢成其他服务如(git) [root@python ~]# cat web.yml --- - hosts: webservers tasks: - name: copy file copy: src=/tmp/data.txt dest=/opt/data.txt - name: change mode file: dest=/opt/data.txt mode=655 owner=root group=root [root@python ~]# cat all.yml --- - include: db.yml - include: web.yml [root@python ~]# touch /tmp/data.txt [root@python ~]# touch /opt/data.txt
3、使用Ansible-playbook执行Playbook
上一小节中,我们简单地介绍了Playbook的定义。那么,当我们有了一个Playbook文件以后,如何执行这个文件完成应用部署呢?我们知道,Ansible安装完成以后存在多个可执行的命令行工具,其中,ansible-playbook便是用于执行Playbook的命令行工具。
ansible-playbook的执行方式如下:
ansible-playbook first_playbook.yml
ansible-playbook命令也有若干命令行选项,其中,有部分选项与ansible命令相同。Ansible中也存在一些ansible-playbook特有的命令行选项。
ansible-playbook命令与ansible命令相同的命令行选项:
-T --timeout:建立SSH连接的超时时间
--key-file --private-key:建立SSH连接的私钥文件
-i --inventory-file:指定Inventory文件,默认是/etc/ansible/hosts
-f --forks:并发执行的进程数,默认为5
--list-hosts:playbooks匹配的服务器列表。
ansible-playbook也有一个名为--list-hosts的选项,该选项的作用是列出匹配的服务器列表。例如,在我们这个 Playbook的例子中,hosts文件的内容如下:
127.0.0.1 [webservers] 192.168.1.60 [dbservers] 192.168.1.80 [common:children] dbservers webservers
我们知道,Ansible中的Play定义了需要对哪些服务器执行哪些操作,也就是说,每一个Play都可以指定匹配的远程服务器。在我们这个Playbook的例子中,对数据库服务器安装MongoDB,对web服务器部署“应用“。因此,ansible-playbook命令与ansible命令的--list-hosts选项输出的结果将会大不相同。ansible-playbook命令的--list-hosts选项输出的结果如下:
[root@python ~]# ansible-playbook all.yml --list-hosts
ansible-playbook命令有一些特有的选项,如下所示:
--list-tasks:列出任务列表
--step:每执行一个任务后停止,等待用户确认
--syntax-check:检查Playbook语法
-C --check:检查当前这个Playbook是否会修改远程服务器,相当于预测Playbook的执行结果。
这里的几个选项,除了--step以外,其他几个选项都不会执行Playbook中的任务。这些选项存在主要是为了便于调试Playbook。例如,--list-tasks选项,该选项用来显示当前Playbook中的任务列表。当Playbook比较大时,可以通过这个方式快速查看任务列表。如下所示:
[root@python ~]# ansible-playbook all.yml --list-tasks playbook: all.yml play #1 (dbservers): dbservers TAGS: [] tasks: install mongodb TAGS: [] play #2 (webservers): webservers TAGS: [] tasks: copy file TAGS: [] change mode TAGS: []
当我们查看任务列表时,任务的名称就是task的name字段。因此,name的定义需要具有较好的描述性,让使用者通过名字就能知道该任务需要做什么事情。
--step选项类似于编程语言中的单步调试。当我们使--step选项执行Playbook时,ansible-playbook在每一个任务之前都会停住,等侍用户输入yes,、no或continue。如下所示:
[root@python ~]# ansible-playbook all.yml --step [DEPRECATION WARNING]: 'include' for playbook includes. You should use 'import_playbook' instead. This feature will be removed in version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg. PLAY [dbservers] *************************************************************** Perform task: TASK: Gathering Facts (N)o/(y)es/(c)ontinue: y Perform task: TASK: Gathering Facts (N)o/(y)es/(c)ontinue: ********************* TASK [Gathering Facts] ********************************************************* ok: [192.168.1.80] Perform task: TASK: install mongodb (N)o/(y)es/(c)ontinue: y Perform task: TASK: install mongodb (N)o/(y)es/(c)ontinue: ********************* TASK [install mongodb] ********************************************************* changed: [192.168.1.80] PLAY [webservers] ************************************************************** Perform task: TASK: Gathering Facts (N)o/(y)es/(c)ontinue: y Perform task: TASK: Gathering Facts (N)o/(y)es/(c)ontinue: ********************* TASK [Gathering Facts] ********************************************************* ok: [192.168.1.60] Perform task: TASK: copy file (N)o/(y)es/(c)ontinue: y Perform task: TASK: copy file (N)o/(y)es/(c)ontinue: *************************** TASK [copy file] *************************************************************** changed: [192.168.1.60] Perform task: TASK: change mode (N)o/(y)es/(c)ontinue: y Perform task: TASK: change mode (N)o/(y)es/(c)ontinue: ************************* TASK [change mode] ************************************************************* changed: [192.168.1.60] PLAY RECAP ********************************************************************* 192.168.1.60 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.1.80 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
输入yes以后,任务将会继续执行,并在下一个任务时停止,等待用户继续输入。当我们输入continue时,Ansible会执行完当前这个Play,当执行到下一个Play时再停止,并等待用户输入。
二、Playbook的详细语法
到目前为止,我们已经学习了如何编写Playbook以及如何运行Playbook。但是,我们仅仅介绍了最简单的Playbook。在这一节中,我们将会介绍Playbook是如何通过不同的选项提供丰富多样的功能。灵活使用这些选项,能够编写出形式各异的Playbook,以此应对自动部署中的各种情况。
在定义Play时,只有hosts与tasks是必选选项,其他选项都是根据需要添加的。在这一小节中。我们将介绍Playbook提供的不同功能,以Playbook的功能为线索,介绍Play与task中可以使用的选项。
(1)权限
在Ansible中,默认使用当前用户连接远程服务器执行操作。我们也可以在anaible.cfg文件中配置连接远程服务器的默认用户。此外,如果是不同的用户使用不同类型的远程服务器,那么也可以在Playbook的Play定义中指定连接远程服务器的用户。例如,我们可以指定执行Play的用户:
--- - hosts: webservers remote_user: root
用户可以细分每一个task,如下所示:
--- - hosts: werbservers remote_user: root tasks: - name: test connection ping: remote_user: yourname
很多时候,我们需要的不是以某个特定用户连接远程服务器,而是在需要更高级别的权限时,使用管理员身份去执行操作。在ansible中,可以通过become与become_ method选项实现:
--- - hosts: werbservers remote_user: root become: yes
与remote_user选项类似,我们也可以为单个任务使用管理员权限,如下所示:
--- - hosts: werbservers remote_user: yourname tasks: - name: installed nginx service: name=nginx state=started become: yes become_method: sudo
实例
先修改远程服务器中test的权限为(0:0)
[root@192 ~]# vim /etc/passwd test:x:0:0::/home/test:/bin/bash [root@python ~]# chown test:root /etc/ansible/* [root@python ~]# su test [test@python root]$ cd [test@python ~]$ vim hosts [db] 192.168.1.60 [test@python ~]$ vim db.yml --- - hosts: db remote_user: root #远程服务器登陆的用户 tasks: - name: installed nginx become: yes become_method: sudo ping: remote_user: root #远程服务器登陆的用户 [test@python ~]$ vim /etc/ansible/ansible.cfg [defaults] inventory = /home/test/hosts
执行一下
[test@python ~]$ sudo ansible-playbook db.yml --step We trust you have received the usual lecture from the local System Administrator. It usually boils down to these three things: #1) Respect the privacy of others. #2) Think before you type. #3) With great power comes great responsibility. [sudo] password for test: PLAY [db] ********************************************************************** Perform task: TASK: Gathering Facts (N)o/(y)es/(c)ontinue: y Perform task: TASK: Gathering Facts (N)o/(y)es/(c)ontinue: ********************* TASK [Gathering Facts] ********************************************************* Enter passphrase for key '/root/.ssh/id_rsa': ok: [192.168.1.60] Perform task: TASK: installed nginx (N)o/(y)es/(c)ontinue: Perform task: TASK: installed nginx (N)o/(y)es/(c)ontinue: ********************* PLAY RECAP ********************************************************************* 192.168.1.60 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
(2)通知
在Ansible中,模块是幂等的。例如,我们要在远程服务器上创建一个用户,如果该用户已经存在,那么Ansible不会将该用户删除以后重新创建,而是直接返回成功,并通过changed字段表示是否对远程服务器进行了修改。
考虑这样一种需求:我们要通过Ansible修改Apache的配置文件,并重启Apache服务,使得新的配置文件生效。由于Ansible的模块是幂等的,当我们修改Apache的配置文件时,如果配置文件的内容已经与我们想要修改成的内容一样(例如,不小心将Ansible执行了两次的情况),那么,Ansible就什么也不做。既然Apache的配置文件并没有真的被修改,那么我们也不应该去重启Apache的服务器。在Ansible中,通过notify与handler机制来实现这里的功能。
在下面的例子中,我们首先尝试安装Apache,然后修改Apache的配置文件。如果配置文件被修改,则通过notify选项通知handler进行后续处理。
handler是Ansible提供的条件机制,与tasks比较类似,都是去执行某些操作。但是,handler只有在被notify触发以后才会执行,如果没有被触发则不会执行。在Playbook中,如果task后面存在notify选项,那么,当Ansible识别到task改变了系统的状态,就会通过notify去触发handler。
Ansibie是通过什么条件判断notify触发的是哪一个handler呢?很简单,在Ansible中,task使用handler的名字作为参数,以此来触发特定的handler。例如,在我们这里的例子中,notify触发的是“restart apache"这个handler, handlers中也存在一个名为" restart apache“的handler。
--- - hosts: webservers tasks: - name: ensure apache is at the latest version yum: name=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
需要注意的是,handler只会在所有task执行完后执行。并且,即便一个handler被触发多次,它也只会执行一次。handler并不是在被触发时立即执行,而是按照Play中定义的顺序执行。一般情况下,handler都位于Play的最后,即在所有任务执行完成以后再执行。
Ansible官方文档提到handler的唯一用途,就是重启服务与服务器,正如找们这个例子所演示的。
在这个例子中,我们还用到T了template模块。template模块用以渲染Jinja模板。
(3)变量
在Inventory管理章节,我们已经介绍了如何定义变量。在Ansible中,还有其他几种定义变量的方式。对于简单的Playbook,最直接的方式是将变量定义在Playbook的vars选项中。如下所示:
- hosts: dbservers vars: mysql_port: 3307
在Playbook中定义变量,可以在模板渲染时使用。例如:Ansible官方给出的例子中,MySQL配置文件的部分模板如下:
[mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock user=mysql port={{ mysql_port }}
当变量较少的时候,定义在vars选项中完全没有问题。当变量较多时,可以将变量保存在一个独立的文件中,并通过vars_files选项引用该文件。如下所示:
--- - hosts: all vars: favcolor: blue vars_files: - /vars/external_vars.yml tasks: - name: this is just a placeholer command: /bin/echo foo
保存变量的文件是一个简单的YAML格式的字典,如下所示:
--- # in th above example, this would be vars/external_vars.yml somevar: somevalue password: magic
在shell脚本中,我们可以通过获取上一条命令的返回码判断命令是否执行成功。在Ansible中,我们也可以获取任务的执行结果,将任务的执行结果保存在一个变最中,并在之后引用这个变量。这样的变量在Ansible中使用register选项获取,也称为注册变量。
例如,在下面这个例子中,我们首先执行/usr/bin/foo命令,并通过register选项获取命令的执行结果,将结果保存在foo_result中。在之后的task中,使用这个变量名引用/usr/bin/foo命令的执行结果。
- hosts: web_servers tasks: - shell: /usr/bin/foo register: foo_result ignore_errors: True - shell: /usr/bin/bar when: foo_result == 5
这个例子还涉及了两个新的选项,分别是ignore_errors与when。前者表示忽略当前task中的错误,后者是一个条件语句,只有条件为真时才会执行这个task。
(4)Facts变量
在Ansible中,还有一些特殊的变量,这些变量不需要我们进行任何设置就可以直接使用,这样的变量称为Facts变量。Facts变量是Ansible执行远程部署之前从远程服务器中获取的系统信息,包括服务器的名称、IP地址、操作系统、分区信息、硬件信息等。Facts变量可以配合Playbook实现更加个性化的功能需求。例如,将MongoDB数据库的数据保存在/var/mongo-\<hostname>/目录下。
我们可以通过setup模块查看Facts变量的列表,如下所示:
ansible all -m setup
有了Facts变量以后,如何在Ansible中使用它们呢?答案是直接使用。我们可以在Playbook中直接通过变量的名字引用变量,也可以在Jinja2模板中通过变量的名字引用变量。下面是一个名为test_facts.yml的Playbook。在这个Playbook中,我们输出了操作系统的类型,并且只有在操作系统为“RedHat"类操作系统时才会执行安装操作。
--- - hosts: dbservers tasks: - shell: echo {{ ansible_os_family }} register: myecho - debug: var=myecho.stdout_lines - name: install git on Red Hat Linux yum: name=git state=installed when: ansible_os_family == "RedHat"
setup模块为了输出结果的可读性,对模块的输出进行了归类和整理。因此,当我们要访问复杂变量的子属性时,需要使用嵌套结构。例如,我们可以通过下面两种方式访问Ansible中的ipv4地址:
ansible_ens33['ipv4']['address'] ansible_ens33.ipv4.address
访问复杂的变量的Playbook:
--- - hosts: dbservers gather_facts: yes tasks: - shell: echo {{ ansible_ens33['ipv4']['address'] }} register: myecho - debug: var=myecho.stdout_lines - shell: echo {{ ansible_ens33.ipv4.address }} register: myecho - debug: var=myecho.stdout_lines
在实际工作中,我们一般会在Jinja2模板中引用Facts变量。使用方式与这里的例子一样,为了节省篇幅就不再赘述了。
在Playbook中,可以通过gather_ facts选项控制是否收集远程服务器的信息。该选项默认取值为yes,如果确定不需要用到远程服务器的信息,可以将该选项设置为no,以此提高Ansible部署的效率。如下所示:
--- - hosts: dbservers gather_factes: no tasks:
(5)循环
- name: Install Mysql package yum: name={{ item }} state=installed with_items: - mysql-server - MySQL-python - libselinux-python - libsemanage-python
(6)条件
有时候,一个任务是否执行取决于一个变量的取值,或者上一个任务的执行结果,这个时候找们就需要条件语句。再或者说,在循环的时候想要跳过一些特定的元素,在服务器部署时只对某些特定的操作系统进行操作。所有这些行为都可以使用条件语句解决。Ansible的Playbook不是一门编程语言,因此没有相应的条件语句,不过Ansible提供了一个类似的选项。
在Playbook中可以通过when选项执行条件语句,when就类似于编程语言中的if语句。
下面是一个简单的when选项使用示例:
# 查看Linux系统版本:cat /etc/redhat-release tasks: - name: "系统关机" command: /sbin/shutdown -t now when: ansible_os_family == "RedHat"
when选项也支持多个条件语句,下面是一个YAML格式的多条件:
tasks: - name: "shutdown CentOS 7 systems" command: /sbin/shutdown -t now when: - ansible_distribution == "CentOS" - ansible_distribution_major_version == "7"
对于更复杂的条件可以使用and、or与括号进行定义:
tasks: - name: "shutdown CentOS 7 and Debian 6 systems" command: /sbin/shutdown -t now when: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "7") or (ansible_distribution == "Debian" and ansible_distribution_major_version == "6")
在when选项中可以读取变量的取值,例如:
vars: epic: true tasks: - shell: echo "This certainly is epic!" when: epic
when选项可以与循环一起使用,以实现过滤的功能:
tasks: - command: echo {{ item }} with_items: [0, 2, 4, 6, 8, 10] when: item > 5
(7)任务执行策略
在Ansible中,Playbook的执行是以task为单位进行的。Ansible默认使用5个进程对远程服务器执行任务。在默认情况的任务执行策略( linear)中,Ansible首先执行task1,并且等到所有服务器执行完task1以后再开始执行task2,以此类推。从Ansible 2.0开始,Ansible支持名为free的任务执行策略,允许执行较快的远程服务器提前完成Play的部署,不用等待其他远程服务器一起执行task。如下所示:
- hosts: all strategy: free tasks: ……
在这一节中,我们比较详细地介绍了Ansible中的Playbook选项。在Ansible中,Play与task都有很多选项,每个选项可以实现不同的功能。Ansibie官方并没有通过功能的形式介绍不同的选项给出一个完整的选项列表。我们也可以参考https://github.com/lorin/ansible-quickref快速了解Play与task中的选项,以及各个选项的含义。
4、案例:使用Playbook部署nginx
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo //下载源
在这个例子中,我们使用Ansible配置一台服务器运行nginx进程。部署nginx的Playbook如下:
--- - hosts: webservers become: yes become_method: sudo vars: worker_prosess: 4 worker_connections: 768 max_open_files: 65506 tasks: - name: install nginx yum: name=nginx update_cache=yes state=present - name: copy nginx config file template: src=/root/test_ansible/nginx.conf.j2 dest=/etc/nginx/nginx.conf notify: restart nginx - name: copy index.html template: src: /root/test_ansible/index.html.j2 dest: /usr/share/nginx/www/index.html mode: 0644 notify: restart nginx handlers: - name: restart nginx service: name=nginx state=restarted
在这个Playbook中,我们首先通过hosts选项指定了要对哪些远程服务器执行操作。随后,我们通过become与become_method选项声明了部署时使用sudo权限。接下来,我们在vars字段中定义了三个变量,这三个变量将用在nginx的配置文件中。我们在tasks选项下定义了部署nginx服务的任务列表,包括软件安装、模板渲染、定制s首页和重启nginx进程。
为了避免配置文件在没有任何修改的情况下重启了nginx进程,这里使用了Ansible的handler机制。在这个Playbook中,存在两个notify选项,以及一个handler选项。无论是nginx的配置文件,还是定制首页发生了修改,我们都会重启nginx进程。由于我们使用了Ansible的handlers机制,因此,在没有任何修改的情况下,Ansible并不会重启nginx进程。使用handler机制还有一个好处,notify多次,handler也只会执行一次,避免了反复多次重启nginx进程。
在这个部署nginx服务的Playbook中,我们用到了nginx.conf.j2这个配置模板。这个模板使用的是Jinja2的语法,所以后缀名为j2。模板的内容如下:
[root@python ~]# mkdir test_ansible [root@python ~]# vim /root/test_ansible/nginx.conf.j2 worker_processes {{ worker_prosess }}; worker_rlimit_nofile {{ max_open_files }}; events { worker_connections {{ worker_connections }}; } http { server { listen 80; listen 443 ssl; server_name localhost; location / { root /usr/share/nginx/www; index index.html index.htm; tr_files $uri $uri/ =404; } } }
Ansible会使用我们在Playbook的vars字段中定义的变量,将Jinja2模板渲染成真实的配置文件。
我们的Playbook还用到了一个名为index.html.j2的模板,该模板用于渲染网站首页。index.html.j2的内容如下:
[root@python ~]# vim /root/test_ansible/index.html.j2 <html> <meta charset="utf-8"> <head> <title>wellcome to ansible</title> </head> <body> <h1>nginx, configured by ansible</h1> <p>如果你能看到这个页面,说明ansible自动部署nginx成功了!</p> <p>{{ ansible_hostname }}<p> </body> </html>
在index.html.j2中,我们用到了一个名为ansible_hostname的变量。这个变量是Facts变量,是Ansible在执行Playbook之前从远程服务器获取到的信息。因此,我们不需要定义,直接使用即可。
有了Playbook以后,使用ansible-playbook命令进行部署。如下所示:
[root@python ~]# pip install Jinja2 [root@python ~]# vim /etc/ansible/ansible.cfg [defaults] inventory = /root/hosts [root@bogon ~]# ansible-playbook nginx.yml PLAY [webservers] ********************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************************** ok: [127.0.0.1] TASK [install nginx] ******************************************************************************************************* ok: [127.0.0.1] TASK [copy nginx config file] ********************************************************************************************** ok: [127.0.0.1] TASK [copy index.html] ***************************************************************************************************** ok: [127.0.0.1] PLAY RECAP ***************************************************************************************************************** 127.0.0.1 : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 [root@bogon ~]#
如果安装失败,可能是端口被占用,可以停止使用该端口的服务,或者更改nginx端口。
[root@bogon ~]# netstat -ntlp Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN 1/systemd tcp 0 0 0.0.0.0:6000 0.0.0.0:* LISTEN 7470/X tcp 0 0 192.168.122.1:53 0.0.0.0:* LISTEN 7654/dnsmasq tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 7337/sshd tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN 7340/cupsd tcp 0 0 127.0.0.1:6010 0.0.0.0:* LISTEN 31653/sshd: root@pt tcp 0 0 127.0.0.1:6011 0.0.0.0:* LISTEN 31653/sshd: root@pt tcp 0 0 127.0.0.1:6012 0.0.0.0:* LISTEN 31653/sshd: root@pt tcp6 0 0 :::111 :::* LISTEN 1/systemd tcp6 0 0 :::80 :::* LISTEN 17867/httpd tcp6 0 0 :::6000 :::* LISTEN 7470/X tcp6 0 0 :::22 :::* LISTEN 7337/sshd tcp6 0 0 ::1:631 :::* LISTEN 7340/cupsd tcp6 0 0 ::1:6010 :::* LISTEN 31653/sshd: root@pt tcp6 0 0 ::1:6011 :::* LISTEN 31653/sshd: root@pt tcp6 0 0 ::1:6012 :::* LISTEN 31653/sshd: root@pt [root@bogon ~]# systemctl stop httpd.service
第二台服务器启动一下nginx
[root@192 ~]# nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful [root@192 ~]# nginx nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use) nginx: [emerg] still could not bind()
浏览器访问一下