VPS 再入门:Ansible 使用篇

技术
这篇文章大约需要9分钟阅读。

话说最早接触 VPS 大约还在刚入大学那会儿,我还没有信用卡、PayPal,国外 IDC 对于支付宝等的支持还非常少,记得那时候还是上淘宝找的代购。之后陆陆续续基本上都有一两台在服役,搭建个网站,跑点脚本、做个代理之类的。但老是不太长久,几乎隔段时间就会重新配置一下,虽然也累计了一些脚本,但我想初始化环境、日常维护之类的工作应该可以更方便些。

就在大约一个月前,在 Oracle Cloud 上薅羊毛薅了一台免费的 VPS,又正好最近转职活动告一段落赋闲在家,就开始了对这台 VPS 的调教之路,主要使用的就是一个叫做 Ansible 的工具。

Ansible 基本介绍

至于为什么要使用 Ansible,主要的原因还是因为简单。管理工作从之前的直接告诉服务器一步一步该怎么做,变成了告诉 Ansible 每一步的结果是什么样的,这个每一步结果的确定性由 Ansible 的模块(module)去确保,于是让 Ansible 去执行任务,每次任务结果应该都一致,这一特性称为幂等性(当然 Ansible 的 shell 模块等可以执行 shell 脚本,这类任务的幂等性需要用户自己保证)。如果这里有点儿云里雾里没关系,下面会有具体例子解释。总而言之,Ansible 给我们管理服务器提供一个非常好的脚手架,让我们可以少关心具体怎么操作服务器,而把重心转移到描述预期的服务器应该成为什么样子上去。

话题转到怎么初始化一台服务器上,当我们从服务商那租用一台 VPS 的时候一般会提供给我们用 SSH 登录的配置,Ansible 正是使用 SSH 来与服务器交互,这也是 Ansible 的另一个特点,不需要在远端服务器运行监听程序(Agentless),只需要有 Python 运行环境就行,这个特性至少对资源非常有限的 VPS 来说算是一个不错的加分项。接下来我就以几个具体的例子来一探 Ansible 的能力。

Ansible 模块的使用

示例一:管理用户

比如说,通常我们会创建一个管理员用户而不是一直使用 root,Ansible 里我们可以使用 user 模块这么做:

---
- name: Add a user
  user:
    name: yuanji
    shell: /bin/bash
    groups:
      - sudo
      - docker
    append: true

这就描述了我们的目标服务器上,应该有个叫 yuanji 的用户,他使用 bash,至少在 sudo 和 docker 两个组里。但是如果要声明多个用户,我们可以像下面那样用模板加变量的方式来实现,把逻辑和数据分割开更有助于组织代码。

---
- name: Add users
  user:
    name: "{{ item.name }}"
    shell: "{{ item.shell }}"
    groups: "{{ item.groups }}"
    append: "{{ item.append }}"
  with_items: "{{ users }}"
---
users:
  - name: dog
    shell: /bin/bash
    groups:
      - sudo
      - docker
    append: true
  - name: cat
    groups:
      - sudo
      - docker
    append: true
    shell: /bin/fish

示例二:上传 SSH 登录用的公钥

再来一个经常用到的配置,比如上面刚创建的 yuanji 用户,我要以 yuanji 的身份用 SSH 登录服务器怎么办呢?用 ssh-copy-id 把自己的公钥追加到 VPS 的 yuanji 的 .ssh/authorized_keys 文件里。而借助 Ansible 的 authorized_key 模块,只要简单声明下就行了,同样的如果复数个人的公钥要配置可以用模板。

---
- name: Set authorized_key
  authorized_key:
    user: yuanji
    state: present # absent 可以确保公钥不在 authorized_key 文件里
    key: https://github.com/yuanji-dev.keys # 当然也可以用本地的文件

总而言之,Ansible 里已经内置了五花八门的模块供使用,基本能够满足日常的需求。我们要做的就是尽量写出满足幂等性要求的 Playbook(Ansible 的术语,就是书写一系列指令的地方)。

Ansible 组织复杂配置:使用 Role

但是如果配置很多,写在一个 Playbook 里就很难看,于是 Ansible 引入了一个叫作 Role 的概念,就是把一些种类差不多的指令集合在一块,然后在 Playbook 里引入就行了。Role 的另一个作用是屏蔽一些琐碎的细节,比如为了兼容不同包管理的发行版,或者兼容使用 systemd 和不使用 systemd 的发行版等等。另外一个重要的特性是,为了复用代码( 偷懒 ),可以使用社区贡献的一些通用的 Role,可以在 Ansible Galaxy 或者 GitHub 上找找,不过运行前最好自己 review 一下代码(也是个学习别人怎么写的机会)。经过前几天的书写,目前我 VPS 的基础配置用的 Playbook 叫 bootstrap.yml 大概长这样(可以看到我使用了很多 geerlingguy 写的第三方 Role)。

---
- hosts: edo
  become: true

  vars_files:
    - vars/users.yml
    - vars/sudoers.yml
    - vars/packages.yml
    - vars/ssh.yml
    - vars/ntp.yml
    - vars/firewall.yml
    - vars/pip.yml
    - vars/nodequery.yml

  vars:
    swap_file_size_mb: '1024'

  roles:
    - role: geerlingguy.ntp
      tags: ntp
    - role: geerlingguy.firewall
      tags: firewall
    - role: geerlingguy.pip
      tags: pip
    - role: geerlingguy.docker
      tags: docker
    - role: geerlingguy.swap
      tags: swap
    - role: users
      tags: users
    - role: packages
      tags: packages
    - role: sudo
      tags: sudo
    - role: ssh
      tags: ssh
    - role: nodequery
      tags: nodequery,monitor

有了这个基础的环境之后,我再在另外的 yaml 文件里书写各个服务不同的配置,目前觉得还是比较好管理的。具体搭建的哪些服务在接下来的文章里再写吧。

使用 Ansible 的一些小技巧

在复数台服务器上运行相同的指令

虽说通过 Playbook 里任务的声明来使用 Ansible 是比较正统的做法,有些时候只是单纯地想运行一个简单的命令(很多时候可能是对好几台服务器运行同样的命令)我们可以直接用 Ansible 的命令行工具来实现。

$ ansible all -i inventory -a "uname -a"
edo.gimo.me | CHANGED | rc=0 >>
Linux edo 4.15.0-1026-oracle #29-Ubuntu SMP Wed Sep 18 10:17:09 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

shin.gimo.me | CHANGED | rc=0 >>
Linux shin 5.3.6-arch1-1-ARCH #1 SMP PREEMPT Fri Oct 11 18:28:05 UTC 2019 x86_64 GNU/Linux

使用 tags 执行指定的指令

一个 Playbook 里可能包含了不少 task 或者 role,有时可能只需要运行其中一个,我们可以给他们设置 tags,然后执行的时候指定 tags 就行了,可以节省不少时间。比如说,改变了防火墙开放端口的配置,我们只想重新执行下防火墙相关的任务可以这样:

$ ansible-playbook -i inventory bootstrap.yml --tags firewall

从指定的任务开始执行

有的时候(调试的时候居多)一个包含好几个任务的 Playbook 执行到某个任务时失败了,修正了错误后不想从头开始跑,可以指定从哪个任务开始。

$ ansible-playbook -i inventory web.yml --start-at-task "Upload include files"

感想

除了上面简单提到的几个简单例子,Ansible 还有很多其他功能,例如 block、handler 之类的来更好的组织代码、用 ansible-vault 来加密敏感信息等等。官方提供的文档也非常详细,可能是这个原因近两年似乎没有啥出版的新书。我这几天读了两本关于 Ansible 的书,作为入门应该足够了。分别是 Ansible : From Beginner to ProAnsible for DevOps: Server and configuration management for humans 尤其是第二本的作者在社区里维护了为数众多的 Role,看一看作为模仿的例子还是非常不错的。

另外我自己使用的感受上,Role 的存在虽然很好的组织了代码,不过想引用 Role 里部分代码似乎不太方便,比如我用的一个第三方的 Nginx 的 Role 里有个重启服务的 handler 我要在其他的 Playbook 使用就不太可能,调查了一圈的结果是可以创建一个空任务的 Role,然后把想要复用的 handlers 统统放到那个 Role 里,有想要使用的 Playbook 就引入那个装满了 handler 的 Role。

期间还有一个小插曲,我用 Ansible 配置 SSH,把默认的端口改成非 22 的时候,执行完发现防火墙的配置没有开放新的那个端口,结果把自己隔离在了 VPS 之外……于是只能重新创建一台新的 VPS 然后把之前那台的存储挂载在新的 VPS 上修改配置文件后再重新挂回到原 VPS 上,最后把新开的 VPS 实例删除才救回来。

最后想折腾但并没有实际开始的是,使用 Ansible 来管理自己日常使用的电脑,我看到 GitHub 上有人这么配置 macOSArch Linux等我有空折腾一下,基于 Ansible 幂等性的特点,想必不会出什么大问题。

评论

  1. 依云说道:

    Ansible 看上去是状态的描述,实际上只是依序调用函数而已(它不是 systemd,顺序弄错了会得到错误的结果),而且你很难自定义可复用的模块(比如同样是复制一些文件,有一些服务器使用 root 登录的,所以不需要 become: yes,而另一些服务器你需要用 sudo。你问我为什么这些服务器的配置不一样?比如说这两群服务器本来就用法不一样,只是刚好共享了一部分配置而已(比如在 nginx 中使用 realip 模块处理 Cloudflare 反代过来的请求。

    而 Ansible 的 YAML 语法更是让问题复杂化:某个字符需不需要加引号呢?YAML 的字符串有十几种变种用来不同地处理缩进和空白,你用的是你想要的那个吗?

    我倒是有想法直接使用 Python 语法来完成这些操作的描述,这样配置也可以接受参数地复用。
    另一个问题是 Ansible gather facts 阶段非常非常非常耗时。我只是拷点文件上去跑一跑而已,你收集那些信息根本用不上啊。

    我比较欣赏的是 mosh、rsync 和 git 这种执行方式,不需要长期运行 daemon,只在需要的时候通过 ssh 在对端运行起来干活。

    幂等性的特点……我手写的 shell 或者 Python 自动配置脚本几乎个个都是幂等的,因为编写过程中需要测试啊,而每次写了新的东西,测的时候要是从头开始跑又太耗时间了,所以写着写着就成了幂等的了 233

    • yuanji yuanji说道:

      1. 不同种类的服务器感觉要强行共用一个 Playbook 确实很牵强,而且感觉出错的可能性也更大。因为我之前的工作也不是 SA,这种情况感觉非常取决于个人的判断,或者通过(Ansible 以外的)其他的途径解决。2. YAML 语法这个我确实写的时候查了好几次 stackoverflow,我的看法是如果在 YAML 里需要过多的考虑字符串的问题,可能是没有抽出用模块解决,实在麻烦的就把逻辑抽出来当成一个脚本文件,在 Ansible 的 YAML 文件里用 shell 模块吧。(虽然感觉有种本末倒置感)3. 用 Python 语法描述操作,虽然不太清楚具体是什么样的,如果过于灵活感觉时间长了可能不太可控?收集 facts 这个如果一般的任务用不到可以关闭,也确实挺耗时间的。4. 其实我这次用 Ansible 看中的还是它的基本模块比较多(很好的脚手架),部署一个马上可以用的 VPS 基础环境比较方便,同时代码兼具文档的作用。逻辑稍微复杂一点儿的说实话我还是偷偷 mosh 上去了(不过我还是尽可能的不这么干,既然这次的主要目的是使用 Ansible 的话)

标题和URL已复制