返回介绍

11.2 用 Ansible 实现自动化作业

发布于 2024-01-21 17:11:03 字数 14154 浏览 0 评论 0 收藏 0

上面我们大致总结了搭建环境的流程,现在来看看如何用 Ansible 实现自动化搭建环境。

11.2.1 Ansible 简介

Ansible 是基于 Python 研发的结构管理工具。不过它除了能进行结构管理之外,还能将许多针对服务器的操作自动化。

Ansible 本身由 Python 实现,但运行所需的设置文件均以 INI 格式或 YAML 格式描述,因此没有 Python 知识也能使用它。

另外,Ansible 不像 Chef 和 Puppet,它不需要在被操作的服务器上安装代理程序。只要服务器允许 ssh 登录,Ansible 就能执行相关操作。

Ansible 的主要概念有以下 5 个,我们随后将依次进行了解。

· inventory

· module

· role

· playbook

· vars

NOTE

本书只介绍了 Ansible 的一部分功能,想了解其基本使用方法以及其他功能的读者可以参考 Ansible 的官方文档。

http://docs.ansible.com/

◉ inventory

inventory 是保存有执行对象(即主机)清单的 INI 格式的设置文件。主机通过 IP 地址或主机名指定。

192.168.0.1 192.168.0.2 192.168.0.3 192.168.0.4 192.168.0.5

Ansible 只能访问这里指定的主机,因此我们需要为不同的执行环境准备不同的 inventory 文件。

另外,主机可以用段进行分组。后面讲到的 playbook 和 vars 会用到这里定义的组。

[load-balancers] 192.168.0.2 [app-servers] 192.168.0.3 192.168.0.4 [db-servers] 192.168.0.5

同一个主机可以同时出现在多个组里。比如在所有功能全由一台主机实现的开发环境中,inventory 就是下面这个样子。

[load-balancers] 192.168.0.6 [app-servers] 192.168.0.6 [db-servers] 192.168.0.6

Inventory - Ansible Documentation

http://docs.ansible.com/intro_inventory.html

专栏 Dynamic Inventory

inventory 文件除了可以指定 INI 格式的文件外,还可以指定可执行的脚本文件。我们将这类文件称为 Dynamic Inventory,适用于 Amazon EC2、Google Compute Engine、Docker 等需要频繁变更对象主机信息的情况。

Ansible 的版本库中有以上述 EC2 等为对象的示例 Dynamic Inventory,各位不妨加以参考。

Ansible plugins/inventory

https://github.com/ansible/ansible/tree/devel/contrib/inventory

Dynamic Inventory

http://docs.ansible.com/intro_dynamic_inventory.html

◉ module

Ansible 以 module 为单位定义对服务器的操作。

Ansible 本身自带了许多 module,可以将它们组合起来,实现多种操作。

Module Index - Ansible Documentation

http://docs.ansible.com/modules_by_category.html

只要遵循一定的规则,module 可用任何语言来实现。当标准模块无法满足需求时,可以直接将现有脚本封装成 module 来使用。

Developing Modules - Ansible Documentation

http://docs.ansible.com/developing_modules.html

执行 module 时以 task 形式描述自身的传值参数及其他参数。

- name: install nginx
  sudo: yes
  apt: name=nginx
- name: install mysql
  sudo: yes
  apt:
  name: mysql-server
  state: latest
  update_cache: yes

module 的传值参数以 key=value 的形式描述,各传值参数之间用空格区分。或者也可以用字典形式描述。传值参数较多时建议使用字典形式。

◉ role

role 可以批量重复利用 task。

role 的结构如 LIST 11.2 所示。

LIST 11.2 role 的目录结构

roles/
  +-- nginx/
     +-- tasks/
      +-- main.yml
     +-- handlers/
      +-- main.yml
     +-- vars/
      +-- main.yml
     +-- defaults/
      +-- main.yml
     +-- files/
      +-- nginx.repo
     +-- templates/
      +-- conf.d/
         +-- ap.conf
     +-- meta/
      +-- main.yml

· tasks

task定义。定义描述在该目录的main.yml中。

· handlers

handler的定义。由task定义的notify指定并调用。同上,在main.yml中描述。

· vars

该role使用的变量。同上,在main.yml中描述。

· defaults

上述变量的默认值。同上,在main.yml中描述。

· files

该role的task中,文件关联模块用到的文件。

· templates

该role的task中,template模块用到的Jinja2模板文件。

· meta

元信息。可定义role之间的依赖关系等。

专栏 role 的共享

role 也能像 PyPI 一样在 Web 上公开及共享。Ansible Galaxy1 是 Ansible 公司运营的网站,任何人都可以在这里免费上传和下载 role。

从 Ansible Galaxy 上下载 role 时,需要用 ansible-galaxy install 命令。这个命令只需安装 Ansible 就可以使用了。下载的 role 默认安装到 /etc/ansible/roles 目录下,我们可以通过 -p 选项指定安装位置。

$ ansible-galaxy install username.rolename -p ROLES_PATH

使用 ansible-galaxy init 命令可以生成包含 meta/main.yml 和tasks 目录等内容的样板文件。这个命令原本是为方便用户向 Ansible Galaxy 上传role 而设计的,但对于不想公开的 role 同样好用。因此各位在制作 role 的时候不妨试试这个命令。

$ ansible-galaxy init rolename

1 https://galaxy.ansible.com/

◉ playbook

playbook 是 YAML 格式的文件,用来定义要执行的处理。

- hosts: load-balancers
  sudo_user: mainte
  roles:
  - django

在 hosts 处指定对象服务器。这里指定 inventory 中定义的群名,可以对该群中的主机执行 task。指定为 all 则以所有主机为对象。

定义 sudo: yes 之后,该 playbook 将全部由 sudo 用户执行。sudo 用户默认为 root。想使用 root 以外的用户时需要在 sudo_user 处指定。

roles 处以 YAML 的列表形式指定要执行的 role。虽然可以在 tasks 处直接描述 task,但除了没有现成脚本的情况以外,还是建议使用 role。

Playbooks - Ansible Documentation

http://docs.ansible.com/playbooks.html

◉ vars

playbook、task、role 的模板中都可以使用变量。vars 的设置方法有很多种,这里只介绍比较有代表性的。

role 的 defaults

定义该 role 使用的变量的默认值。这里设置的值一般都是无法直接运行的临时值或空值,实际的值在 group_vars 中描述,等到运行时再进行覆盖。这样一来只需看 defaults 就能掌握 role 所需的全部变量,使 role 的重复利用成为可能。

group_vars

针对 inventory 中定义的组进行设置。Ansible 会读取 group_vars 目录下的 YAML 格式文件。文件名对应组名。另外,文件名为 all,会对所有组套用变量。

host_vars

针对 inventory 中定义的主机进行设置。Ansible 会读取 host_vars 目录下的 YAML 格式文件。文件名对应组名。

11.2.2 文件结构

与 Ansible 关联的文件全都要在一个目录下统一管理。本章示例的目录结构如 LIST 11.3 所示。

LIST 11.3 ansible 脚本群的文件结构

+-- deployment/
  +-- group_vars/
    +-- all
    ...
  +-- host_vars/
  +-- roles/
    +-- environ
    +-- nginx
    +-- mysql
    ...
  +-- inventory/
    +-- production
    +-- dev
    ...
  +-- site.yml
  +-- ap.yml
  +-- lb.yml
  ...

这些文件也都要放在版本库中进行管理。管理方法可以有以下 2 种。

· 创建专用的版本库进行管理

· 与应用的源码放在同一个版本库中进行管理

◉ 在专用的版本库中管理

如果没必要或者不希望应用的开发与环境搭建同步,可以把源码与 Ansible 的文件群放在不同版本库中进行管理。

Ansible 关联文件的数量通常很大,如果不想让源码的版本库太复杂,同样可以用这个方法。

◉ 与应用的源码放在同一个版本库中进行管理

在应用的版本库中专门创建一个用于管理这些文件的目录。这样做的好处是能在开发过程中让应用开发与环境搭建内容的变更对应起来。

我们大部分项目都采用了这种方法。没有特殊要求的情况下文件路径以 myproject/deployment/ 为基准。

11.2.3 执行 Ansible

创建好的脚本用 ansible-playbook 命令执行。对象环境的选择是通过切换 inventory 来实现的,因此要用 -i 选项明确指定 inventory。

$ ansible-playbook -i inventory/production.ini site.yml

在本章的结构中,会频繁用到指定了 tag 的执行操作。-t 选项可以在执行时指定任意标签。

$ ansible-playbook -i inventory/production -t deploy site.yml

11.2.4 与最初确定的结构相对应

11.1 节确定的搭建内容可以与 Ansible 的各概念对应起来。图 11.5 与图 11.2 相对应。

图 11.5 Ansible 的概念与服务器搭建内容的对应

· 环境 = inventory + vars

针对各个环境(正式环境、开发环境、过渡环境等)编写inventory文件。另外,Ansible 会自动读取与inventory的描述内容相对应的vars。

· 服务器组 = inventory 的段

前面定义的服务器组在inventory中是以段形式定义的,各段中列举了主机。

· 各服务器组的搭建流程 = playbook

给各个组分别创建lb.yml、ap.yml、db.yml、gateway.yml文件,将组设置成hosts。另外,创建一个include所有playbook的playbook,方便一次性搭建所有环境。这个playbook一般命名为site.yml。

· 针对各个中间件的搭建流程 = role

以中间件为单位创建role,各服务器组用对应其所需role的playbook统一管理。

· 其他环境设置 = role

不依赖于特定中间件的环境设置(创建用户等)也以role形式描述。

11.2.5 将各步骤 Ansible 化

◉ playbook/role 的设计

着手细节步骤之前,要先探讨搭建流程的各个步骤应该整合到怎样的 role/playbook 之中。

我们对前面学习的搭建操作流程进行如下分类,然后分割到各个 role 中。

① 应该套用到所有服务器中的操作

除了本章讲到的创建用户之外,比较常见的还有 ntp 和时区设置。

我们将这些处理整合到名为 environ 的 role 中,对所有服务器组进行套用。

② 特定服务器组使用的中间件的设置

为每个中间件编写一个 role,以达到分割的目的。

③ 部署

包含到对象中间件的 role 里。设置 tag 以保证能单独执行部署操作。然后将分割好的 role 组合成 playbook。以下是正式环境的例子。

- hosts: app-servers
  roles:
  - environ
  - python
  - repository
  - django
  - appserver
- hosts: db-servers
  roles:
  - environ
  - mysql
- hosts: load-balancers
  roles:
  - environ
  - nginx

大致的设计完成后就可以开始实际编写所需操作,边测试边调整了。

◉ 用户设置

用户的创建和设置可以用 user 模块完成。

tasks:
  - user: name=mainte
  - user: name=www

借助 file 模块向 sudoers.d 目录下放置文件。本例的文件内容比较简单,因此可以通过 content 传值参数直接描述脚本内容来进行设置。

tasks:
  - file:
    dest: /etc/sudoers.d/mainte
    content: "%mainte ALL=(ALL) NOPASSWD:ALL"

公开密钥的设置用 authorized_keys 模块。传值参数处需要些公开密钥的字符串,由于字符串太长,我们用 group_vars/all 文件的变量代替。

mainte_pubkey: AAAA1234512345...
tasks:
  - authorized_keys:
    user: mainte
    key: "{{mainte_pubkey}}"

密钥文件在版本库中管理,通过 file 模块放置。这里不要忘记修改权限。

tasks:
  - file:
    src: mainte_seckey
    dest: /home/mainte/.ssh/id_rsa
    mode: 0600

本例的 src 只指定了文件名,它在这里其实是相对路径,该相对路径是基于下面两个中的一个。

· playbook 的目录

· 包含该 task 的 role 的 files 目录

◉ deb 程序包的安装

安装 deb 程序包时使用 apt 模块。指定版本的方式与 apt-get 相同。

- apt:
  name: mysql-server-5.5=5.5.40-0ubuntu0.14.04.1

用 with_items 可以将多个程序包的安装流程描述在一个 task 中。

- apt:
  name: "{{ item }}"
  with_items:
  - mysql-server-5.5=5.5.40-0ubuntu0.14.04.1
  - nginx

◉ 通过 pip 安装程序包

Ansible 有兼容 pip 的 pip 模块,使用前需要先安装 pip。

- name: install pip
  shell: curl -L https://bootstrap.pypa.io/get-pip.py | python creates=/usr/local/bin/pip

使用 shell 模块时,如果传值参数 creates 处指定的文件已经存在,该步骤将被强制跳过。本例是通过检查是否存在 /user/local/bin/pip 来判断是否需要执行该步骤的。

由于这些模块都可以自由执行命令,因此幂等性需要我们自己来验证。只要给传值参数 creates 指定了文件,当被指定的文件存在时,当前步骤就会被强制跳过。本例是通过检查 /user/local/bin/pip 是否存在来判断是否需要执行该步骤。

安装完成后 pip 模块就能用了。

- name install packages
  pip: name={{item}}
  with_items:
  - virtualenv

我们在第 1 章中也了解到,应用所需的程序包要安装在 virtualenv 环境中。因此我们把搭建 virtualenv 环境的步骤也写了进来。

- name: create virtualenv
  sudo_user: www
  command: virtualenv venv chdir=/var/www/ creates=venv/bin/activate
- name: install
  sudo_user: www
  pip: requirements=/var/www/myproject/requirements.txt virtualenv=/var/www/venv

pip 模块会向 virtualenv 参数处指定的环境安装程序包,所以我们在这里指定虚拟环境的路径。另外,只把 name 参数替换为 requirements 参数并指定 requirements.txt 的路径,就可以使用 requirements.txt 了。

◉ 中间件设置的反映

放置、更新设置文件要用 template 模块。

之前我们在设置文件中使用了一些尚未定义的变量,现在在 group_vars 和 role 的 defaults/main.yml 中定义它们。比如对 nginx.conf 中使用的变量要作如下定义(LIST 11.4)。

LIST 11.4 group_vars/all

DOMAIN: myproject.example.com
NGINX_UPSTREAMS:
  - 192.168.0.3
  - 192.168.0.4
SSL:
  CERTIFICATE: myproject.crt
  KEY: myproject.key

变量准备完毕后开始写放置配置文件的 task。涉及多个文件时推荐使用 with_items。

tasks:
  - template:
    src: "{{ item }}"
    dest: /etc/nginx/"{{ item }}"
  with_items:
    - conf.d/gunicorn.conf

template 模块的文件路径引用的是包含该 task 的 role 的 templates 目录。保持 templates 目录的相对路径与文件放置目标目录的相对路径一致能够简化描述。

◉ 部署

部署的 task 也写在各个 role 的 tasks 中。描述时要给该 task 设置 tag。设置 tag 能让我们单独拿出与部署相关的 task 来执行。

按照本章的结构,我们定义下述 tag。

· configure

设置文件的更新

· update

源码的更新

· reload

重新加载中间件设置

· deploy

执行update、configure、reload

◉ 设置的更新

给在“中间件设置的反映”部分编写的 template 模块的 task 添加 configure 和 deploy 标签。

- template:
  src: "{{ item }}"
  dest: /etc/nginx/"{{ item }}"
  with_items:
  - conf.d/gunicorn.conf
  tags:
  - configure
  - deploy

◉ 中间件的重启

经由 sysvinit 或 upstart 启动的中间件可以用 service 模块重启或重新加载。

- service:
  name: nginx
  state: reloaded
  tags:
  - reload
  - deploy

如果使其与 Ansible 的 Notify 机制相结合,则可以规定仅在设置文件被修改时自动重新加载中间件。

通过 Notify 调用的 task 在 role 的 handlers/main.yml 中描述。name 会被视为标识符,所以要指定一个在整个项目中独一无二的名字。

LIST 11.5 roles/nginx/handlers/main.yml

- name: reload nginx
  service:
  name: nginx
  state: reloaded

接下来在调用方的 notify 参数处指定通过 LIST 11.5 设置的 name。这个 task 的返回值 changed 为 True 时(对 template 模块而言是文件内容被更新时)将执行 notify 指定的 task(LIST 11.6)。

LIST 11.6 roles/nginx/tasks/main.yml

...
- name: configure nginx
  template:
  src: "{{ item }}"
  dest: /etc/nginx/"{{ item }}"
  with_items:
  - conf.d/gunicorn.conf
  tags:
  - configure
  - deploy
  notify:
  - reload nginx

使用 Notify 可以让我们不必分神去注意重新加载设置的问题,但会使单独执行重新加载或重启,以及只修改设置不重启等类似操作变得难以实现。因此需要根据项目的实际情况选择合适的方法。

◉ 源码的更新

通过 Mercurial 放置源码的操作可以用 hg 模块来完成,但需要事前完成安装 Mercurial、创建目录、设置权限等准备工作。

把这一系列工作整合到名为 repository 的 role 中,让需要放置源码的服务器组能够使用该 role。

- pip:
  name: mercurial
- file:
  state: directory
  dest: /var/www/
  owner: www
  mode: 755
- hg:
  repo: ssh://user@myrepository.com/myproject
  dest: /var/www/myproject
  revision: "{{ revision }}"
  tags:
  - update
  - deploy

为了能够更新成任意版本而不仅限于 default,这里将 hg 模块的传值参数 revision 设为变量。

根据需要还可以将放置源码的目标项目名、版本库 URL 也设为变量,方便替换和引用,从而灵活应对多种需求。

11.2.6 整理Ansible 的执行环境

本例中的环境如 11.1.1 节所述,除 gateway 服务器以外全都无法从外部进行 ssh,因此我们无法从本地环境操作所有服务器。另外,如果给所有相关人员的本地环境中都搭建 Ansible 的执行环境,那么每次调换或新增负责人时都要搭建一次环境。

这种时候,最好的解决办法就是在一个项目全体成员共享的、可连接环境中所有服务器并且与应用运行没有直接关系的 gateway 服务器上搭建 Ansible 的执行环境。

虽然我们也希望通过 Ansible 完成 gateway 服务器的整理,但这又涉及到用 Ansible 整理 Ansible 执行环境的自举问题。

这里我们从手头环境执行 Ansilbe,以解决这一问题。但有一点要注意,执行这部分操作时 gateway 服务器上还不存在 mainte 用户。虽然我们设想用 mainte 进行维护工作,但构建环境时无法使用该用户。所以要为 root 或 ubuntu 用户创建一份临时的 inventory 文件(LIST 11.7 ~ LIST 11.9)。

LIST 11.7 gateway 搭建时所需的 ini

[gateway]
gateway.example.com ansible_ssh_user=ubuntu

LIST 11.8 gateway.yml

- hosts: gateway
  sudo: yes
  roles:
  - environ
  - python
  - ansible

LIST 11.9 roles/ansible/tasks/main.yml

- name: install Ansible
  pip: name=ansible

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文