以Chef和Ansible为例快速入门服务器配置

571 阅读8分钟
原文链接: mp.weixin.qq.com
作者 | Stephen Mann 译者 | 无明 服务器配置(Server Provisioning):如何在我们的环境中安装和配置软件 服务器配置

在开始介绍现代化的工具之前,我们来看看最基本且经过实战考验的服务器配置工具:shell 脚本。在 Chef、Ansible 或 Puppet 出现之前,很多运营团队使用 Bash 来配置服务器(在 Windows 上则使用 PowerShell 脚本)。

例如,如果想在运行 Ubuntu 的 Amazon EC2 实例上安装 Nginx,可以使用以下脚本(install-nginx.sh):

#!/bin/sh
ssh -t ubuntu@$1 sudo apt-get upgrade
ssh -t ubuntu@$1 sudo apt-get -y install nginx

我们可以使用 shell 脚本来配置服务器上的所有东西。据我所知,所有主流的配置工具都使用了基于安全传输层(如 SSH)的 shell 命令或 PowerShell(Chef 可能是个例外)。即使你使用了配置工具,在某些时候也需要用到脚本。因此,当你开始使用配置工具(如 Chef 或 Ansible)时,学习如何使用基本的 shell 脚本也会为你带来很多好处。

你可能会问自己,为什么在 shell 脚本已经可以完成所有工作的同时还要学习配置工具?很多环境已经使用 shell 脚本进行服务器配置,那么为什么要使用配置工具代替它们?

首先,shell 脚本通常使用的是声明性语法。shell 脚本通过运行命令序列来安装软件,而配置工具只需要指定服务器应该安装哪些软件,这样就可以使用相同的代码在不同的操作系统上、使用不同的包管理器以及指定不同的版本来安装和配置相同的软件。

其次,配置工具通常会提供用于组织基础设施的方式。虽然使用 shell 脚本也可以做到这一点,但配置工具通常会提供更简洁明了的方案。因为是行业标准,开发人员可以更轻松地找出 QA 环境中哪些服务器运行 RabbitMQ。

第三,每个主要的配置工具都有一个蓬勃发展的社区,他们构建可复用的模块来安装大多数开源软件。你可以直接在模块配置中指定内存限制,而不需要记住 Postgres 配置文件在哪里,这样可以节省很多时间。

当然,原因还有很多,这里就不一一例举了。尽管学习曲线有点陡峭,但学习配置工具仍然是值得的。与 shell 脚本相比,配置工具更容易使用,便于思考,也更容易维护

关于命名

学习使用 Chef(服务器配置工具)的前几周给我留下了深刻的印象。入门指南展示了如何创建一个“recipe”,其中包含安装或配置软件的说明,我能够理解这种比喻背后的含义。recipe 必须存在于“cookbook”中,这是有道理的。然后你在“kitchen”里测试 cookbook,但我开始有点怀疑了。

这种比喻有点令人感到困惑,于是我决定去看一下其他工具,如 Ansible。Ansible 文档的第一页介绍了“playbook”的概念,而 playbook 包含一系列“play”。

那么,这些问题很重要吗?当然很重要了,因为 在学习配置工具之前,你应该知道,它们很有可能会引入大量令人费解的术语。即使是为了完成基本的任务,你也必须重新学习很多术语。如果你是刚开始学习配置工具,我强烈建议你随时写下这些术语定义,你还有很多东西要学。

每个软件开发人员都会为现有的单词创建不同的含义,他们甚至还会发明一些单词,比如“uninitialize”和“unregister”。这已经成为软件开发的一部分。

我会尽量用大家熟悉的术语来解释这些工具。

配置管理

你决定使用花哨的配置工具在远程服务器上安装 Nginx。在开始设置数据库备份节点前,一切都很顺利。你已经编写了 MySQL 主服务器的配置文件,但是你不太确定如何配置 MySQL 从服务器的内部 DNS 地址。这个时候配置管理就派上用场了。

在设置服务器时,最好可以将应用程序视为由两部分组成:不可变部分(通常是代码或编译的二进制文件)和可变部分(通常是配置文件或环境变量)。大部分由社区创建的模块默认情况下会安装二进制文件,并提供尽可能合理的配置,而且会为我们暴露出一些属性,方便对其进行覆盖。

这些属性通常包含特定于用户环境的值。大多数配置工具都为用户提供了一种机制,通过模板将特定于环境的值插入到配置文件中,或直接插入到环境变量中。

你可以使用配置工具提供的配置管理来配置 MySQL 主服务器的配置文件,然后在其中配置从服务器。

Secret 管理

这样就可以解决上述的问题,但后来发现,你必须上传 AWS 凭证才能让 MySQL 从服务器访问 S3。你知道不能直接将这些凭证提交到代码库中,因此这些凭证只能存在于你的机器和 NSA 服务器上。

这个时候你需要的是 Secret 管理。

与自动化领域的所有东西一样,你也有很多管理秘钥的可选项。谷歌提供了一项名为 KMS 的服务,AWS 也提供了一项名为 Secret Manager 的服务,Chef 提供了加密数据包,Hashicorp 提供了一款名为 Vault 的产品,Ansible 也有一款名为 Vault 的产品。除了 KMS 会对字符串进行加密之外,所有这些工具都提供了相同的功能:保护对加密秘钥的访问(这些秘钥被用在配置管理中)。

有好几次,我不小心将秘钥提交到了代码库。这类事情一直在发生,而且非常危险。

切勿以明文形式存储 API 密钥或凭证

可以使用 Secret 管理解决方案来存储这些数据,然后将其绑定到配置工具中

一个简单的例子:Chef

首先需要安装 Chef Development Kit(ChefDK)。

如前所述,我们需要一个 recipe 来安装 Nginx。出于教学的目的,我们将从头开始创建它,而不是从社区的 cookbook 中捞一个出来。

我们需要创建一个 cookbook。cookbook 通常存在于cookbooks目录中,在项目的根目录运行以下命令:

mkdir cookbooks

现在让我们创建一个 cookbook,用于放置我们的新 recipe:

chef generate cookbook cookbooks/application

这个命令在cookbooks/application目录中创建了很多文件,我们关心的是 cookbooks/application/recipes/default.rb这个文件。这个文件包含了默认的 recipe,我们将安装 Nginx 的命令放到这个文件中。

apt_update

package 'nginx'

cookbook_file '/var/www/html/index.html' do
  source 'index.html'
  owner 'www-data'
  group 'www-data'
  mode '0755'
  action :create
end

这个文件中的前两个命令将执行你期望的操作:

  • apt_update更新你的 aptitude 包。

  • package ‘nginx’使用操作系统默认包管理器安装 nginx包(在这个示例中,它使用的是 aptitude)。

最后一个命令将cookbooks/application/files/index.html拷贝成远程服务器上的 /var/www/html/index.html,并设置文件的权限,让 Nginx 服务器可以访问它。

这个文件还不存在,所以需要创建它。首先要创建文件目录:

mkdir cookbooks/application/files

然后创建文件cookbooks/application/files/index.html,其中包含以下内容:

<html lang="en-us">
  <head>
    <title>Hello, World!</title>
  </head>
  <body>
    Chef has landed.
  </body>
</html>

更新packer.json,加入 Chef 相关配置:

{
  "builders": [{
    "type": "amazon-ebs",
    "region": "us-east-1",
    "source_ami": "ami-04169656fea786776",
    "instance_type": "t2.small",
    "ssh_username": "ubuntu",
    "ami_name": "Ubuntu 16.04 Nginx - {{timestamp}}",
    "tags": {
      "Image": "application"
    }
  }],
  "provisioners": [{
    "type": "chef-solo",
    "cookbook_paths": ["cookbooks"],
    "run_list": ["recipe[application]"]
  }]
}

我们对之前的packer.json进行了两处更改。

首先,我们为 AMI 添加了一个Image标签。我们之前从 Packer 的输出中复制 AMI ID,并粘贴到 Terraform 代码中。这不是一个可维护的解决方案,因为 AMI ID 会经常发生变化,而且我们不应该在每次发生变化时都要将更改推送到存储库中。相反,我们使用 Terraform 的data资源来动态读取 AMI ID(使用Image=application查询最新的 AMI)。

其次,我们使用chef-solo替换了 shell。我们告诉它在哪里可以找到 cookbooks 目录,以及要运行哪个 recipe。默认情况下,run_list中的 recipe[COOKBOOK]条目将执行recipes/default.rb。我们也可以显式指定 explicity:recipe [COOKBOOK::RECIPE]来覆盖默认行为。由于我们的 recipe 保存在recipes/default.rb中,所以将使用默认行为。

现在开始构建我们的 AMI:

packer build packer.json

我们的新 AMI 有一个Image标签,现在修改 terraform.tf中硬编码的 AMI,让它通过标签来查找 AMI。

将以下内容添加到terraform.tf中:

data "aws_ami" "web" {
  most_recent = true
  owners = ["self"]
  filter {                       
    name = "tag:Image"     
    values = ["application"]
  }                              
}

现在使用aws_ami.web resource输出的 ID 替换aws_instance.web1aws_instance.web2resource 中的 AMI ID:

resource "aws_instance" "web1" {
  ami                    = "${data.aws_ami.web.id}"
  availability_zone      = "us-east-1a"
  instance_type          = "t2.small"
  vpc_security_group_ids = ["${aws_security_group.application.id}"]
  subnet_id              = "${aws_subnet.private1.id}"
}

resource "aws_instance" "web2" {
  ami                    = "${data.aws_ami.web.id}"
  availability_zone      = "us-east-1b"
  instance_type          = "t2.small"
  vpc_security_group_ids = ["${aws_security_group.application.id}"]
  subnet_id              = "${aws_subnet.private2.id}"
}

运行下面的命令创建 Chef 配置的服务器,然后启动浏览器,打开地址为负载均衡器的域名:

terraform plan -out terraform.plan
terraform apply "terraform.plan"
open "http://$(terraform output dns)"

你应该能够在打开的浏览器页面上看到:Chef has landed!

一个简单的例子:Ansible

让我们使用 Ansible 来构建这个相同的示例。首先需要安装 Ansible(https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#installing-the-control-machine)。

Ansible 将安装和配置说明组织到tasks中,然后将 tasks组织到playbook中。让我们为 playbook 创建一个目录结构。

mkdir playbook
mkdir playbook/files

这并不是组织 Ansible playbook 的最佳实践。因为我们的用例很简单,所以使用了简化版本。如果你对 Ansible 感兴趣,应该根据官方提供的建议来构建 playbook(https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html)。

playbook/application.yml中创建 playbook,内容如下:

---
- hosts: all
  gather_facts: False
  become: yes
  pre_tasks:
  - name: Install Python 2.7
    raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
- hosts: applications
  become: yes
  tasks:
  - name: Install Nginx
    apt:
      name: nginx
      state: present
      update_cache: yes
  - name: Update contents of index.html
    copy:
      src: index.html
      dest: /var/www/html/index.html
      owner: www-data
      group: www-data
      mode: 0755

这个 playbook 文件包含配置我们的服务器所需的所有信息。现在让我们来讨论一下它的结构。

每个 playbook 包含一个“play”列表,每个 play 包含一个“tasks”列表,task 用于安装和配置软件。我们的 playbook 包含两个 play。第一个 play 在 Ubuntu 上安装 Python 2.7(用于运行 Ansible)。第二个 play 安装和配置 Nginx。

我们在每个 play 的根节点配置了两个参数:hostsbecomehosts参数告诉 Ansible 应该在哪台机器上运行 playbook(“all”表示在所有机器上运行)。become:yes表示 Ansible 将通过 sudo 运行所有命令,否则将会出现很多权限错误。

play 的第一个 task 负责安装和配置 Nginx,它将更新 aptitude 缓存,并确保nginx包存在。如果已经安装了 nginx包,这个命令将不执行任何操作。

第二个 task 将files/index.html拷贝到远程服务器上,并为其分配正确的权限。

这个文件还不存在,所以让我们创建它。将以下内容加入到playbook/files/index.html中:

<html lang="en-us">
  <head>
    <title>Hello, World!</title>
  </head>
  <body>
    Ansible has landed.
  </body>
</html>

这就是我们配置 Ansible 所需的全部内容。现在让 Packer 使用这个配置。使用以下内容更新packer.json

{
  "builders": [{
    "type": "amazon-ebs",
    "region": "us-east-1",
    "source_ami": "ami-04169656fea786776",
    "instance_type": "t2.small",
    "ssh_username": "ubuntu",
    "ami_name": "Ubuntu 16.04 Nginx - {{timestamp}}",
    "tags": {
      "Image": "application"
    }
  }],
  "provisioners": [{
    "type": "ansible",
    "playbook_file": "./playbook/application.yml",
    "host_alias": "applications"
  }]
}

我们只修改了使用 Ansible 作为配置器,需要提供一个指向 playbook 文件的路径,我们将其设置为./playbook/application.yml。我们可以看到用于安装 Nginx 的 play 顶部有一行:hosts: applications。这是我们用来告诉 Ansible 需要安装应用程序的主机别名。我们需要告诉 Packer 我们正在为其中一个主机构建映像,所以我们将host_alias属性设置为 applications

运行下面的命令来创建 Ansible 配置的服务器,然后启动浏览器,打开地址为负载均衡器的域名:

packer build packer.json
terraform plan -out terraform.plan
terraform apply "terraform.plan"
open "http://$(terraform output dns)"

你应该可以在打开的浏览器页面上看到:Ansible has landed!

英文原文:

http://stephenmann.io/post/a-brief-introduction-to-provisioning/