codecamp

Ansible 维护大型的 Playbooks

在学时,冻仁曾借由大型程式来撰写复杂的 C 语言。如今当 Ansible Playbooks 写久之后,其架构也会变得复杂,这时该怎么写才好维护呢?就让冻仁娓娓道来吧!

automate_with_ansible_practice-24.jpg

图片来源:https://goo.gl/jBL5i3

官方早在 Playbooks Best Practices 文件中列了 12 条最佳实践 (Best Practices),底下将以冻仁认为最重要的部份进行介绍。

什么是最佳实践 (Best Practices)?

来自维基百科的解释:

最佳实践,是一个管理学概念,认为存在某种技术、方法、过程、活动或机制可以使生产或管理实践的结果达到最优,并减少出错的可能性。(more)

冻仁认为,Best Practices 就如同 80/20 法则,只要掌握了 Best Practices,就可以用 20% 的投入获得 80% 的成效。

在许多技术文件里 (如 AndroidAWS 和 Vue.js) 也会有这个特别的章节,甚至还有前辈提到接触一门新技术时,应该要先从 Best Practices 开始看呢。

官方建议的目录结构

以下修改至官方建议的目录结构 (Directory Layout) 范例,只留下冻仁目前觉得最重要的部份。

production        # inventory file for production.
staging           # inventory file for staging.

site.yml          # master playbook
webservers.yml    # playbook for webserver tier
dbservers.yml     # playbook for dbserver tier

roles/
  common/         # role name
    tasks/        #   
      main.yml    # main tasks file.
    handlers/     #
      main.yml    # handlers file.
    templates/    #
      ntp.conf.j2 # templates end in .j2.
    files/        #   
      bar.txt     # files
      foo.sh      # script files
    vars/         #
      main.yml    # variables with this role.
    defaults/     #
      main.yml    # default variables.
    meta/         #
      main.yml    # role dependencies
  • productionstaging:借由 inventory file 来切换环境。我们在「Ansible 使用 Template 系统」一章时已提过,在 Best Practices 里就有特别说明此手法。
  • site.yml:主要的 playbook。
  • webservers.yml:网页服务器一层的 playbook。
  • dbservers.yml:资料库服务器一层的 playbook。

实战用的目录结构

底下将以某个用 django 开发的 API web server 专案为例。

README.md               # 该专案的说明文件。
Vagrantfile
ansible.cfg             # configure for ansible
files/
  id_rsa_deploy         # 用 Git 部署的 ssh key。
  ssl_key/
    ...
group_vars/
  main.yml              # 各环境共用的 vars。
  local.yml             # 本机开发的 vars。
  production.yml        # 正式环境的 vars。
  staging.yml           # 测试环境的 vars。
production              # 正式环境的 inventory file。
requirements.yml
restart_service.yml     # 重开 API 服务的 playbook。
roles/
  chusiang.switch-apt-mirror/
  ...
setup.yml               # 主要 playbook。
staging                 # 测试环境的 inventory file。
tasks/
  restart_api.yml       # 重开 api 的 tasks。
  setting_api.yml       # 设定 api 的 tasks。
  setting_nginx.yml     # 设定 nginx 的 tasks。
  setup.yml             # 主要安装流程的 tasks。
templates/
  local_settings.py.j2
  nginx.conf.j2         # nginx vhost.
  nginx_ssl.conf        # nginx vhost (ssl).
  supervisor.conf.j2
tests/
  Dockerfile            # 用 Docker 跑测试。
update_config.yml
  • Vagrantfile:在本机主要使用 Vagrant 搭配 group_vars/local.yml 进行开发。
  • ansible.cfg:依各专案客制 Ansible 相关环境。
  • files/:集中管理要使用 files module 复制到 Managed node 的档案。
  • group_vars/:设定 staging, production 等不同环境变数至各个档案,若有共用的部份则写在 main.yml 里。
  • setup.yml:include 各种 tasks 的主要 playbook。
  • tasks/:将各种不同任务独立出来的 tasks,在裡分别为 restart_api.ymlrestart_api.ymlsetting_nginx.yml 和 setup.yml
  • templates/:集中管理要使用 templates module 复制到 Managed node 的档案。

总结

大家可以先照着官方的规划练习,写久了再依个人风格调整即可,毕竟要让大型的 Playbooks 变得好维护,不外乎为以下原则:

  1. 通过 inventory file 和变数把 local, staging 和 production 等环境。
  2. 尽可能的把重复的 tasks 独立出来,然后让 playbook 依不同需求 include 进来用。在这个例子里,冻仁把 setup.yml 和 update_config.yml 两个 playbook 中重复的 tasks 独立成 tasks/setting_api.yml,并通过 include 重复使用。
  3. 可以用 Roles 就用 Roles!x3

后话

我们可以借由 Roles 让大型的 Playbooks 更易于维护,在此文中也一直提到它的重要性,这部份就让冻仁留到下一章介绍了。

相关连结


Ansible 在 Playbooks 使用 loops
Ansible 什么是Roles
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }