第五章 可编程性与自动化

5.0 介绍

可编程性是指通过编程与事物进行交互的能力。NGINX Plus的API提供了这样的功能:通过HTTP接口与NGINX Plus的配置和行为进行交互的能力。这个API提供了通过HTTP请求添加或删除上游服务器来重新配置NGINX Plus的能力。NGINX Plus的键值存储特性提供了另一层动态配置——你可以利用HTTP调用注入信息,NGINX Plus可以动态路由或控制流量。本章将介绍NGINX Plus API和该API公开的键值存储模块。

配置管理工具自动化了服务器的安装和配置,这在云计算时代是一个非常宝贵的实用工具。大型web应用程序的工程师不再需要手动配置服务器;与之对应的,他们可以使用其中的许多可用的配置管理工具。借助这些工具,工程师可以一次性编写配置和代码,以可重复、可测试和模块化的方式生成具有相同配置的多个服务器。本章介绍了一些最流行的配置管理工具,以及如何使用它们来安装NGINX和模块化基础配置。这些示例非常基础,但是演示了如何从相应的平台开始使用NGINX服务器。

5.1 NGINX Plus API

问题

你有一个动态环境,需要动态重新配置NGINX Plus。

解答

配置NGINX Plus API,通过API调用来添加和删除服务器:

upstream backend {
    zone http_backend 64k;
}
server {
    # ...
    location /api {
        api [write=on];
        # 限制访问API的指令
        # 请查看第七章
    }
    location = /dashboard.html {
       root /usr/share/nginx/html;
    }
}

这个NGINX Plus配置创建了一个具有共享内存区的上游服务器,在/apilocation块中启用了API,并为NGINX Plus仪表盘dashboard提供了location

当服务器上线时,你可以利用API添加服务器:

$ curl -X POST -d '{"server":"172.17.0.3"}' \
  'http://nginx.local/api/3/http/upstreams/backend/servers/'

执行结果:

{
    "id":0,
    "server":"172.17.0.3:80",
    "weight":1,
    "max_conns":0,
    "max_fails":1,
    "fail_timeout":"10s",
    "slow_start":"0s",
    "route":"",
    "backup":false,
    "down":false
}

此示例中的curl命令向NGINX Plus发出请求,以将新服务器添加到后端上游配置。采用的是HTTP POST方法,并且将JSON对象作为正文参数传递。NGINX Plus API采用的是RESTful API模式;因此带参数的请求,URL采用如下方式请求:

/api/{version}/http/upstreams/{httpUpstreamName}/servers/

你可以利用NGINX Plus API列出上游池中的服务器:

$ curl 'http://nginx.local/api/3/http/upstreams/backend/servers/'

执行结果:

[
    {
        "id":0,
        "server":"172.17.0.3:80",
        "weight":1,
        "max_conns":0,
        "max_fails":1,
        "fail_timeout":"10s",
        "slow_start":"0s",
        "route":"",
        "backup":false,
        "down":false
    }
]

在此示例中,curl命令向NGINX Plus发出请求,以列出上游池中名为backend的所有服务器。当前,只返回得到一个服务器,这还是上一个curl命令通过添加服务器API添加的。该请求将返回一个上游服务器对象,该对象包含服务器的所有可配置选项。

使用NGINX Plus API排空上游服务器的连接,为从上游池中正常删除做好准备。你可以在2.8章节中找到有关连接排空的详细信息:

$ curl -X PATCH -d '{"drain":true}' \
  'http://nginx.local/api/3/http/upstreams/backend/servers/0'

执行结果:

{
    "id":0,
    "server":"172.17.0.3:80",
    "weight":1,
    "max_conns":0,
    "max_fails":1,
    "fail_timeout":"10s",
    "slow_start":"0s",
    "route":"",
    "backup":false,
    "down":false,
    "drain":true
}

在此curl命令中,我们指定请求方法为PATCH,传递一个JSON主体以指示它排空服务器的连接,并通过将服务器ID附加到URI中来指定对应服务器。对应的服务器的ID,我们可以通过上面的curl命令中列出上游池中的服务器的示例方法中找到。

NGINX Plus将开始耗尽连接。该过程可能需要花费与应用程序session会话相同的时间。要检查已开始排空的服务器正在服务的活动连接数,可以使用以下调用查找正在消耗的服务器的active属性:

$ curl 'http://nginx.local/api/3/http/upstreams/backend'

执行结果:

{
    "zone" : "http_backend",
    "keepalive" : 0,
    "peers" : [
        {
            "backup" : false,
            "id" : 0,
            "unavail" : 0,
            "name" : "172.17.0.3",
            "requests" : 0,
            "received" : 0,
            "state" : "draining",
            "server" : "172.17.0.3:80",
            "active" : 0,
            "weight" : 1,
            "fails" : 0,
            "sent" : 0,
            "responses" : {
                "4xx" : 0,
                "total" : 0,
                "3xx" : 0,
                "5xx" : 0,
                "2xx" : 0,
                "1xx" : 0
            },
            "health_checks" : {
                "checks" : 0,
                "unhealthy" : 0,
                "fails" : 0
            },
            "downtime" : 0
        }
    ],
    "zombies" : 0
}

在所有连接排空之后,请使用NGINX Plus API从上游池中完全删除服务器:

$ curl -X DELETE \
  'http://nginx.local/api/3/http/upstreams/backend/servers/0'

执行结果:

[]

上面的curl命令与更新服务器状态的URI相同,只是请求发送的是DELETE方法。DELETE方法指示NGINX删除服务器。这个API调用返回池中仍然保留的所有服务器及其ID。因为我们刚开始时就是一个空池,通过API只添加了一个服务器,接着排空了它,最后又删除了它,所以现在又变成了一个空池。所以返回的是一个空列表。

讨论

NGINX Plus专有API使动态应用程序服务器可以动态地将自身在NGINX配置中添加或删除。当服务器是上线状态时,它们可以注册到上游池中,NGINX则将开始发送负载。当服务器需要删除时,服务器可以请求NGINX Plus排空连接,然后在关闭之前将自己从上游池中删除。这使得基础架构可以通过一些自动化来进行扩展,而无需人工干预。

另请参见

NGINX Plus API Swagger Documentation

5.2 键值存储

问题

你需要NGINX Plus根据应用程序的输入做出动态流量管理决策。

解答

设置群集可感知的键值存储和API,然后添加键和值:

keyval_zone zone=blacklist:1M;
keyval $remote_addr $num_failures zone=blacklist;
server {
    # ...
    location / {
        if ($num_failures) {
            return 403 'Forbidden';
        }
        return 200 'OK';
    }
}
server {
    # ...
    # 限制访问API的指令
    # 请查看第6章
    location /api {
        api write=on;
    }
}

上面的NGINX Plus配置使用keyval_zone指令构建了一个名为blacklist的键值存储共享内存区,并配置限制该内存区为1MB大小。然后,keyval指令将匹配第一个参数$remote_addr的键值映射到区域中名为$num_failure的新变量。然后,使用这个新变量来确定NGINX Plus是否为请求提供服务还是返回403禁止代码。

使用此配置启动NGINX Plus服务器后,你可以使用curl对本地机器进行请求,并期望收到200 OK响应。

$ curl 'http://127.0.0.1/'
OK

现在将本地机器的IP地址添加到值为1的键值存储中:

$ curl -X POST -d '{"127.0.0.1":"1"}' \
  'http://127.0.0.1/api/3/http/keyvals/blacklist'

这个curl命令使用一个JSON对象提交一个HTTP POST请求,该JSON对象包含一个要提交到blacklist共享内存区域的键值对象。键值存储API URI的格式如下:

/api/{version}/http/keyvals/{httpKeyvalZoneName}

本地机器的IP地址现在被添加到名为blacklist的键值区域,值为1。在接下来的请求中,NGINX Plus在键值区域中查找$remote_addr,找到条目,并将该值映射到变量$num_failure。然后在if语句中对该变量求值。当变量有值而不是空值或值,则if的计算得到为True,则NGINX Plus返回403 Forbidden状态码:

$ curl 'http://127.0.0.1/'
Forbidden

你可以通过发出PATCH方法请求来更新或删除键值:

$ curl -X PATCH -d '{"127.0.0.1":null}' \
  'http://127.0.0.1/api/3/http/keyvals/blacklist'

如果该值为null,NGINX Plus则将删除这个键,最后上面的请求将再次返回200 OK。

讨论

键值存储是NGINX Plus的专有功能,这使得应用程序能够将信息注入NGINX Plus。在上面提供的示例中,$remote_addr变量用于创建动态黑名单。你可以使用NGINX Plus提供的任意变量来构建键值存储(例如,一个session cookie),并为NGINX Plus提供外部值。在NGINX Plus R16版本中,键值存储是集群感知的,这意味着你只需要向一台NGINX Plus服务器提供键值更新,它们都将收到信息。

另请参见

动态带宽限制(英文)

5.3 使用Puppet进行安装

问题

你需要使用Puppet作为代码来安装、配置和管理NGINX,并与你的其他Puppet配置保持一致。

解答

创建一个安装 NGINX 的模块,管理你需要的文件,并确保 NGINX 正在运行:

class nginx {
    package {"nginx": ensure => 'installed',}
    service {"nginx":
        ensure => 'true',
        hasrestart => 'true',
        restart => '/etc/init.d/nginx reload',
    }
    file { "nginx.conf":
        path => '/etc/nginx/nginx.conf',
        require => Package['nginx'],
        notify => Service['nginx'],
        content => template('nginx/templates/nginx.conf.erb'),
        user=>'root',
        group=>'root',
        mode='0644';
    }
}

这个模块很实用的利用包管理来确保NGINX的安装。它确保系统在启动时自动运行NGINX。这个配置指出Puppet服务有一个名为hasrestart 的重启指令。并且我们可以使用NGINX的reload重载来覆盖restart命令。在这里文件资源通过嵌入式Ruby模板语言(Embedded Ruby (ERB))来组织和管理nginx.conf文件。因为使用了require指令,模板文件将在NGINX包被安装后生成。接着,因为notify指令,文件资源会通知NGINX服务重新加载。在这里模板文件没有被包含列出来。然而,安装默认的 NGINX 配置文件可能很简单,但如果使用 ERB 或 EPP 模板语言循环和变量替换则变的非常复杂。

讨论

Puppet 是一个基于 Ruby 编程语言的配置管理工具。以特定的域语言构建模块,通过调用一个清单文件来定义服务器配置。Puppet可以在主-从或无主配置中运行。使用Puppet,清单在主服务器上运行,然后发送给从服务器。这是很重要的,因为这确保从服务器仅专注于接受为其指定的配置,而不会为其他服务器提供另外的配置。Puppet还有很多非常高级的公共模块。这些模块使你的配置工作能够更加快速的开始。一个来自GitHub上的voxpupuli的公共NGINX模块将会为你模板化出NGINX配置。

另请参见

Puppet Documentation

Puppet Package Documentation

Puppet Service Documentation

Puppet File Documentation

Puppet Templating Documentation

Voxpupuli NGINX Module

5.4 使用Chef进行安装

问题

你需要使用Chef作为代码来安装、配置和管理NGINX,并与你的其他Chef配置保持一致。

解答

创建一个带有配方的食谱,以安装 NGINX 并通过模板配置配置文件,并确保在配置到位后重新加载 NGINX。 以下是配方示例:

package 'nginx' do
    action :install
end

service 'nginx' do
    supports :status => true, :restart => true, :reload => true
    action [ :start, :enable ]
end

template 'nginx.conf' do
    path "/etc/nginx.conf"
    source "nginx.conf.erb"
    owner 'root'
    group 'root'
    mode '0644'
    notifies :reload, 'service[nginx]', :delayed
end

这里的package块安装NGINX,service块确保NGINX在系统启动时被启动和启用,然后向Chef的其他部分声明nginx服务将支持哪些操作。template块模板了一个ERB文件,并把它放在/etc/nginx.conf文件中,文件的所有者和组为root。模板块还会将mode(Linux文件权限)设置为644,并通知nginx服务重新reload,但得等到:delayed语句声明的 Chef 运行结束。同样在这里模板文件没有被包含列出来。然而,安装默认的 NGINX 配置文件可能很简单,但如果使用 ERB 模板语言循环和变量替换则变的非常复杂。

讨论

Chef是一个基于Ruby的配置管理工具。Chef可以在主从配置或单独配置中运行,现在称为Chef Zero。Chef有一个非常大的社区,里面有很多公开的实战示例,叫做超级市场。可以通过名为 Berkshelf 的命令行实用程序来安装和维护来自超级市场公开的实战示例。Chef非常能干,我们展示的只是一个小样本。超级市场中的公开的NGINX实战示例非常灵活,提供了从包管理器或源代码轻松安装NGINX的选项,以及编译和安装许多不同模块以及模板化基本配置的能力。

另请参见

Chef documentation

Chef Package

Chef Service

Chef Template

Chef Supermarket for NGINX

5.5 使用Ansible进行安装

问题

你需要使用Ansible作为代码来安装、配置和管理NGINX,并与你的其他Ansible配置保持一致。

解答

创建一个Ansible playbook来安装NGNIX和管理nginx.conf文件。以下是playbook安装NGINX的示例任务文件。确保它的运行并模板化配置文件:

- name: NGINX | Installing NGINX
  package: name=nginx state=present

- name: NGINX | Starting NGINX
  service:
    name: nginx
    state: started
    enabled: yes

- name: Copy nginx configuration in place.
  template:
    src: nginx.conf.j2
    dest: "/etc/nginx/nginx.conf"
    owner: root
    group: root
    mode: 0644
  notify:
    - reload nginx

这里的package块安装NGINX,service块确保NGINX在系统启动时被启动和启用。template块模板一个Jinja2文件,并将结果放在/etc/nginx.conf文件中,其所有者和用户组为root。template 块还将对应的文件权限属性设置为644并通知 nginx服务重新reload。同样在这里模板文件没有被包含列出来。然而,安装默认的 NGINX 配置文件可能很简单,但如果使用 Jinja2 模板语言循环和变量替换则变的非常复杂。

讨论

Ansible 是一款基于 Python 的应用广泛且功能强大的配置管理工具。任务配置文件使用的是YAML格式,同样你可以使用Jinja2模板语言编写模板文件。Ansible 在订阅模型上提供了一个名为 Ansible Tower 的 master。它通常用于从本地机器或直接向客户端或在无主模型中构建服务器,Ansible 可以批量 SSH 连接到其服务器并运行配置。与其他配置管理工具非常相似,Ansible也有一个庞大的公共角色社区。Ansible 将其称之为 Ansible Galaxy。 你可以在你的playbooks中找到非常复杂的角色。

另请参见

Ansible Documentation

Ansible Packages

Ansible Service

Ansible Template

Ansible Galaxy

5.6 使用SaltStack进行安装

问题

你需要使用SaltStack作为代码来安装、配置和管理NGINX,并与你的其他SaltStack配置保持一致。

解答

通过包管理模块安装NGINX,并管理您需要的配置文件。下面是一个示例状态文件(sls),它将安装nginx包,并确保服务正在运行,在系统重启时启用,并在配置文件更改时重新reload:

nginx:
  pkg:
    - installed
  service:
    - name: nginx
    - running
    - enable: True
    - reload: True
    - watch:
      - file: /etc/nginx/nginx.conf
/etc/nginx/nginx.conf:
  file:
    - managed
    - source: salt://path/to/nginx.conf
    - user: root
    - group: root
    - template: jinja
    - mode: 644
    - require:
      - pkg: nginx

这是一个通过包管理工具安装NGINX和管理ningx.conf文件的基础示例。NGINX包将会被安装,并且对应的service服务运行,并在系统重启时自动运行。使用SaltStack,你可以声明一个由Salt管理的文件,如示例中所示,并通过许多不同的模板语言进行模板化。同样在这里模板文件没有被包含列出来。然而,安装默认的 NGINX 配置文件可能很简单,但如果使用 Jinja2 模板语言循环和变量替换则变的非常复杂。因为require语句,这个配置还指定了NGINX必须在管理文件之前安装。文件就位后,NGINX 会因为watch 指令重新加载 服务,在这里是reload而不是restart,是因为 reload 指令设置为 True

讨论

SaltStack 是一个强大的配置管理工具,它使用YAML文件定义服务器状态。SaltStack的模块可以使用Python编写。Salt 公开了用于状态和文件的 Jinja2 模板语言。但是,对于文件,还有许多其他选项,例如Mako、Python 本身等。Salt可以在主从配置以及无主配置中工作。从服务器被称为minions。然而,主从传输通信与其他传输通信不同,这并使得SaltStack与众不同。使用 Salt,您可以选择 ZeroMQ、TCP 或可靠异步事件传输 (Reliable Asynchronous Event Transport(RAET)) 来传输到 Salt 代理,或者你不能使用代理,master 可以使用 SSH 代替。因为传输层在默认情况下是异步的,所以构建SaltStack时能够将其消息传递给大量负载较低的主服务器。

另请参见

SaltStack

Installed Packages

Managed Files

Templating with Jinja

5.7 使用 Consul 模板进行自动化配置

问题

你需要通过使用Consul来自动化你的NGINX配置,以响应你环境中的变化。

解答

使用 consul-template 守护程序和模板文件来模板化你选择的 NGINX 配置文件:

upstream backend { {{range service "app.backend"}}
    server {{.Address}};{{end}}
}

这个例子是一个 Consul 模板文件,它模板化了一个upstream配置块。 该模板将循环遍历 Consul 中定义为app.backend的节点。对于 Consul 中的每个节点,模板将生成一个带有该节点 IP 地址的服务器指令

consul-template守护进程通过命令行运行,可以在每次配置文件模板化有变化时重新加载NGINX:

# consul-template -consul consul.example.internal -template \
  template:/etc/nginx/conf.d/upstream.conf:"nginx -s reload"

此命令指示 consul-template 守护进程连接到 consul.example.internal 处的 Consul 集群,并使用当前工作目录中名为template的文件对该文件进行模板化处理,并将生成的内容输出到/etc/nginx/conf.d/ upstream.conf,然后在每次模板文件更改时重新加载NGINX。 这里的-template选项接受模板文件的字符串、输出位置和模板过程发生后要运行的命令字符串;这三个变量用冒号":"隔开。如果正在运行的命令有空格,请确保将其用双引号括起来。-consul 选项告诉守护进程连接到哪一个 Consul 集群。

讨论

Consul 是一个强大的服务发现工具和配置存储。Consul以类似目录的结构存储关于节点和键值对的信息,并允许使用restful API进行交互。Consul 还为每个客户端提供了一个 DNS 接口,允许对连接到集群的节点进行域名查找。每一个使用 Consul 集群的项目被分割为一个 consul-template 守护进程;这个工具模板化文件以响应 Consul 节点、服务或键值对的变化。这使得 Consul 成为自动化 NGINX 的一个强有力的选择。使用 consul-template 你还可以指示守护程序在模板发生更改后运行特定命令。通过这,我们可以重新加载NGINX配置,并使你的NGINX配置随之生效。使用 Consul,您可以对每个客户端进行健康检查,以检查预期服务的健康状况。有了这种故障检测,你就能够相应地将NGINX配置模板化,只将流量发送到健康的主机。

另请参见

Consul Home Page

Introduction to Consul Template

Consul Template GitHub

Last updated