作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Mahmud是一名软件开发人员,拥有多年的经验和效率诀窍, scalability, 稳定的解.
设计为水平可伸缩的Web应用程序通常需要一个或多个负载平衡节点. 它们的主要目的是以公平的方式在可用的web服务器上分配传入的流量. 简单地通过增加节点数量和让负载平衡器适应这种变化来增加web应用程序的总体容量,这种能力在生产中被证明是非常有用的.
NGINX是一个提供高性能负载平衡功能的web服务器, 它还有许多其他功能. 其中一些功能仅作为其订阅模式的一部分可用, 但是免费和开源版本仍然具有非常丰富的功能,并且具有开箱即用的最基本的负载平衡功能.
在本教程中,我们将探索控件的内部机制 experimental tool 它允许你配置你的NGINX实例作为一个负载均衡器, 通过提供一个简洁的基于web的用户界面,抽象出NGINX配置文件的所有细节. 本文的目的是展示开始构建这样一个工具是多么容易. 值得一提的是,Loadcat项目很大程度上受到了Linode的启发 NodeBalancers.
NGINX最流行的用途之一是从客户端到web服务器应用程序的反向代理请求. 尽管web应用程序是用像Node这样的编程语言开发的.js和Go可以成为自给自足的web服务器, 在实际的服务器应用程序前面使用反向代理可以提供许多好处. 在NGINX配置文件中,一个简单用例的“server”块看起来像这样:
server {
listen 80;
server_name例子.com;
location / {
proxy_pass http://192.168.0.51:5000;
}
}
这将使NGINX监听端口80上指向示例的所有请求.Com并将它们每个传递给运行在192的web服务器应用程序.168.0.51:5000. 我们也可以使用环回IP地址127.0.0.如果web应用服务器在本地运行,则此处为1. 请注意,上面的代码片段缺少一些在反向代理配置中经常使用的明显调整, 但为了简洁起见,我一直这么说.
但是,如果我们想在同一web应用服务器的两个实例之间平衡所有传入请求,该怎么办呢? 这就是“upstream”指令变得有用的地方. In NGINX, 使用“上游”指令, 可以定义多个后端节点,其中NGINX将平衡所有传入请求. For example:
upstream nodes {
server 192.168.0.51:5000;
server 192.168.0.52:5000;
}
server {
listen 80;
server_name例子.com;
location / {
proxy_pass http://nodes;
}
}
注意我们是如何定义一个名为“nodes”的“上游”块的,它由两个服务器组成. 每个服务器由IP地址和它们正在监听的端口号标识. 这样,NGINX就以最简单的形式成为了一个负载均衡器. By default, NGINX将以循环的方式分发传入的请求, 第一个将被代理到第一个服务器的位置, 第二个到第二个服务器, 第三个到第一个服务器,以此类推.
然而,当涉及到负载平衡时,NGINX提供了更多. 它允许您为每个服务器定义权重, 将它们标记为暂时不可用, 选择不同的平衡算法(例如.g. 有一个是基于客户端的IP哈希的),等等. 这些特性和配置指令都是 在nginx上有很好的文档.org. Furthermore, NGINX允许动态更改和重新加载配置文件,几乎没有中断.
NGINX的可配置性和简单的配置文件使得它很容易适应许多需求. 还有大量的教程 already exist 在互联网上教你如何配置NGINX作为负载均衡器.
程序有一些迷人之处,而不是自己做一些事情, 配置其他工具来为他们做这件事. 除了接收用户输入并生成一些文件之外,它们实际上并没有做太多事情. 您从这些工具中获得的大多数好处实际上是其他工具的特性. 但是,它们确实让生活变得轻松. 在尝试为我自己的一个项目设置负载平衡器时, 我想知道:为什么不为NGINX和它的负载平衡能力做一些类似的事情呢?
Loadcat was born!
Loadcat,内置 Go但它仍处于起步阶段. 目前,该工具只允许您配置NGINX的负载平衡和SSL终止. 它提供了一个简单的基于web的 GUI for the user. 让我们来看看下面是什么,而不是浏览工具的各个功能. Be aware though, 如果有人喜欢手工处理NGINX配置文件, 他们可能觉得这样的工具没什么价值.
选择Go作为编程语言有几个原因. 其中之一是Go生成编译的二进制文件. 这允许我们构建和分发或部署Loadcat作为一个编译的二进制文件到远程服务器,而不用担心解析依赖关系. 这极大地简化了设置过程. 当然,二进制文件假设已经安装了NGINX,并且存在一个systemd单元文件.
如果你不是 Go engineer,你完全不用担心. Go is quite easy and fun 首先. Moreover, 实现本身非常简单,您应该能够轻松地跟随.
Go build工具对如何构建应用程序施加了一些限制,并将其余部分留给开发人员. 在我们的例子中,我们根据它们的目的将它们分成了几个Go包:
如果我们仔细看一下包的结构, 尤其是猫科动物, 我们会注意到所有NGINX特定的代码都保存在子包猫科/ NGINX中. 这样做是为了保持应用程序逻辑的其余部分的通用性,并扩展对其他负载平衡器的支持.g. HAProxy).
让我们从Loadcat的主包开始,在“cmd/loadcatd”中找到. main函数是应用程序的入口点,它做三件事.
func main() {
fconfig := flag.字符串(“配置”、“loadcat.conf", "")
flag.Parse()
cfg.LoadFile(*fconfig)
feline.SetBase(filepath.Join(cfg.Current.Core.Dir, "out"))
data.OpenDB(filepath.Join(cfg.Current.Core.Dir, "loadcat.db"))
defer data.DB.Close()
data.InitDB()
http.Handle("/api", api.Router)
http.Handle("/", ui.Router)
go http.ListenAndServe(cfg.Current.Core.Address, nil)
//等待“中断”信号(大多数终端使用Ctrl+C)
}
为了使事情简单,使代码更容易阅读, 所有错误处理代码都已从上面的代码段(以及本文后面的代码段)中删除。.
从代码中可以看出, 我们正在根据“-config”命令行标志(默认为“loadcat”)加载配置文件.. Conf”(在当前目录下). 接下来,我们将初始化几个组件,即核心猫科包和数据库. 最后,我们为基于web的GUI启动一个web服务器.
加载和解析配置文件可能是这里最简单的部分. 我们使用TOML对配置信息进行编码. Go有一个简洁的TOML解析包. 我们只需要用户提供很少的配置信息, 在大多数情况下,我们可以为这些值确定相同的默认值. The following struct 表示配置文件的结构:
struct {
Core struct {
Address string
Dir string
Driver string
}
Nginx struct {
Mode string
Systemd struct {
Service string
}
}
}
下面是一个典型的“装载猫”.Conf”文件可能看起来像:
[core]
address=":26590"
dir = " / var / lib / loadcat”
driver="nginx"
[nginx]
mode="systemd"
[nginx.systemd]
service="nginx.service"
As we can see, xml编码的配置文件的结构与 struct shown above it. 配置包首先为属性的某些字段设置一些相同的默认值 struct 然后在上面解析配置文件. 如果在指定的路径上找不到配置文件, it creates one, 并首先将默认值转储到其中.
函数LoadFile(name string)错误{
f, _ := os.Open(name)
if os.IsNotExist(err) {
f, _ = os.Create(name)
toml.NewEncoder(f).Encode(Current)
f.Close()
return nil
}
toml.NewDecoder(f).Decode(&Current)
return nil
}
Meet Bolt. 一个用纯Go编写的嵌入式键/值存储. 它是一个带有非常简单API的包,支持开箱即用的事务 disturbingly fast.
在包数据中,我们有 structs 表示每种类型的实体. 例如,我们有:
类型平衡器结构{
Id bson.ObjectId
Label string
设置BalancerSettings
}
type Server struct {
Id bson.ObjectId
BalancerId bson.ObjectId
Label string
设置ServerSettings
}
其中的一个实例 Balancer 表示单个负载平衡器. Loadcat有效地允许你通过一个NGINX实例来平衡多个web应用程序的请求. 每个平衡器可以有一个或多个服务器, 每个服务器可以是一个单独的后端节点.
因为Bolt是一个键值存储, 并且不支持高级数据库查询, 我们有应用程序端逻辑为我们做这些. Loadcat不是用来配置数千个平衡器,每个平衡器中有数千个服务器, 所以这种朴素的方法自然很有效. 此外,Bolt处理作为字节片的键和值,这就是为什么我们对 structs 然后储存在Bolt中. 的列表函数的实现 Balancer structs 数据库中的数据如下所示:
函数listbalancer () ([]Balancer, error) {
bals:= []Balancer{}
DB.View(func(tx *bolt.Tx) error {
b := tx.桶([]字节(“平衡器”))
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
bal := Balancer{}
bson.Unmarshal(v, &bal)
Bals = append(Bals, Bals)
}
return nil
})
return bals, nil
}
ListBalancers 函数启动只读事务, 遍历“balancer”桶中的所有键和值, 将每个值解码为的实例 Balancer struct 并以数组的形式返回.
在桶中存储平衡器几乎同样简单:
function (l *Balancer) Put() error {
if !l.Id.Valid() {
l.Id = bson.NewObjectId()
}
if l.Label == "" {
l.标签= "未标示"
}
if l.Settings.协议== "http" {
//解析证书详细信息
} else {
//清除仅与HTTPS相关的字段,如SSL选项和证书详细信息
}
return DB.更新(func (tx *螺栓.Tx) error {
b := tx.桶([]字节(“平衡器”))
p, err := bson.Marshal(l)
if err != nil {
return err
}
return b.Put([]byte(l.Id.Hex()), p)
})
}
The Put 函数为某些字段分配一些默认值, 在HTTPS设置中解析附加的SSL证书, 开始事务, encodes the struct 实例,并根据平衡器的ID将其存储在桶中.
在解析SSL证书时,将使用 标准包编码/pem and stored in SSLOptions under the Settings 字段:DNS名称和指纹.
我们也有一个功能,查找服务器的平衡器:
函数ListServersByBalancer(Balancer *Balancer)([]服务器,错误){
srvs := []Server{}
DB.View(func(tx *bolt.Tx) error {
b := tx.桶([]字节(“服务器”))
c := b.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
srv := Server{}
bson.Unmarshal(v, &srv)
if srv.BalancerId.Hex() != bal.Id.Hex() {
continue
}
SRVS = append(SRVS, srv)
}
return nil
})
return srvs, nil
}
这个函数显示了我们的方法是多么幼稚. Here, 我们正在有效地读取整个“servers”存储桶,并在返回数组之前过滤掉不相关的实体. 但话说回来,这工作得很好,没有真正的理由去改变它.
The Put 函数对于服务器来说要简单得多 Balancer struct 因为它不需要那么多行代码来设置默认值和计算字段.
在使用Loadcat之前,我们必须配置NGINX来加载生成的配置文件. Loadcat生成“nginx”.根据平衡器的ID(一个短十六进制字符串)为目录下的每个平衡器创建一个“conf”文件。. 这些目录是在“输出”目录下创建的 cwd
. 因此,配置NGINX来加载这些生成的配置文件是很重要的. 这可以使用" http "块中的" include "指令来完成:
编辑/etc/nginx/nginx.配置并在" http "块的末尾添加以下行:
http {
包括/道路/ / / * / nginx.conf;
}
这将导致NGINX扫描“/path/to/out/”下的所有目录。, 查找名为“nginx”的文件.Conf”,并加载它找到的每个目录.
在我们的核心包猫科中,我们定义了一个接口 Driver. Any struct 它提供了两个功能, Generate and Reload,并且签名正确,才有资格成为司机.
type驱动接口{
生成数据(字符串,*.Balancer) error
Reload() error
}
例如,结构体 Nginx 在cat /nginx包下:
type Nginx struct {
sync.Mutex
Systemd *dbus.Conn
}
函数c (n Nginx)生成(dir字符串,bal *data.Balancer) error {
//获取n的锁.互斥,并在返回前释放
f, _ := os.Create(filepath.Join(dir, "nginx.conf"))
TplNginxConf.执行(f, /*模板参数*/)
f.Close()
if bal.Settings.协议== "http" {
//转储私钥和证书到输出目录(以便Nginx可以找到它们)
}
return nil
}
函数c (n Nginx) Reload() error {
//获取n的锁.互斥,并在返回前释放
switch cfg.Current.Nginx.Mode {
case "systemd":
if n.Systemd == nil {
c, err := dbus.NewSystemdConnection ()
n.Systemd = c
}
Ch:= make(chan string)
n.Systemd.ReloadUnit(cfg.Current.Nginx.Systemd.服务,“更换”,ch)
<-ch
return nil
default:
return errors.New(“未知Nginx模式”)
}
}
Generate 可以用包含输出目录路径的字符串和指向 Balancer struct instance. Go提供了一个标准的文本模板包, NGINX驱动程序使用哪个来生成最终的NGINX配置文件. 模板由“上游”块和“服务器”块组成, 根据平衡器的配置方式生成:
var TplNginxConf = template.Must(template.New("").Parse(`
upstream {{.Balancer.Id.Hex}} {
{{if eq .Balancer.Settings.“最少连接算法"}}
least_conn;
{{else if eq .Balancer.Settings.算法“源ip”}}
ip_hash;
{{end}}
{{range $srv := .Balancer.Servers}}
server {{$srv.Settings.地址}}= {{$ srv重量.Settings.权重}}{{如果eq $srv.Settings.可用性“可用”}}{{如果eq $srv.Settings.可用性"backup"}}backup{{else if eq $srv.Settings.可用性“不可用”}}{{结束}};
{{end}}
}
server {
{{if eq .Balancer.Settings.Protocol "http"}}
listen {{.Balancer.Settings.Port}};
{{else if eq .Balancer.Settings.Protocol "http"}}
listen {{.Balancer.Settings.Port}} ssl;
{{end}}
server_name {{.Balancer.Settings.Hostname}};
{{if eq .Balancer.Settings.Protocol "http"}}
ssl 上;
ssl_certificate {{.Dir}}/server.crt;
ssl_certificate_key {{.Dir}}/server.key;
{{end}}
location / {
proxy_set_header $ Host;
proxy_set_header X-Real-IP $remote_addr
$proxy_add_x_forwarded_for;
proxy_set_header x - forward - proto $scheme;
proxy_pass http:// {{.Balancer.Id.Hex}};
proxy_http_version 1.1;
proxy_set_header升级$http_upgrade
proxy_set_header连接'upgrade';
}
}
`))
Reload 另一个函数开了吗 Nginx struct 这使得NGINX重新加载配置文件. 所使用的机制取决于Loadcat的配置方式. 默认情况下,它假定NGINX是作为NGINX运行的系统服务.service, such that [sudo] systemd reload nginx.service
would work. 但是,不是执行shell命令, 通过d总线与系统建立连接 package github.com/coreos/go-systemd/dbus.
有了所有这些组件,我们将用一个普通的Bootstrap用户界面将其包装起来.
对于这些基本功能,一些简单的GET和POST路由处理程序就足够了:
GET /balancers
GET /balancers/new
POST /平衡器/新
得到平衡器/ {id}
GET /平衡器/ {id} /编辑
POST /平衡器/ {id} /编辑
GET /平衡器/ {id} /服务器/新
POST /平衡器/ {id} /服务器/新
GET /servers/{id}
/服务器/ {id} /编辑
POST /服务器/ {id} /编辑
在这里,仔细检查每一条路线可能不是最有趣的事情, 因为这些几乎都是CRUD页面. 你可以随便看一眼 package ui code 查看这些路由的处理程序是如何实现的.
每个handler函数都是一个例程,它要么:
For example:
函数ServeServerNewForm.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bal, _ := data.GetBalancer(bson.ObjectIdHex (var (" id ")))
TplServerNewForm.执行(w, struct {)
Balancer *data.Balancer
}{
Balancer: bal,
})
}
函数HandleServerCreate(.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
bal, _ := data.GetBalancer(bson.ObjectIdHex (var (" id ")))
r.ParseForm()
body := struct {
标签字符串' schema: ' Label ' '
Settings struct {
地址字符串' schema:" Address " '
}的模式:“设置”
}{}
schema.NewDecoder().Decode(&body, r.PostForm)
srv := data.Server{}
srv.BalancerId = bal.Id
srv.Label = body.Label
srv.Settings.Address = body.Settings.Address
srv.Put()
feline.Commit(bal)
http.重定向(w, r, "/servers/"+srv.Id.十六进制()+ /编辑,http.StatusSeeOther)
}
All ServeServerNewForm 函数的作用是从数据存储中获取平衡器并呈现模板, TplServerList 方法检索相关服务器的列表 Servers 平衡器上的功能.
HandleServerCreate 函数将从主体传入的POST有效负载解析为 struct 并使用这些数据实例化和持久化一个新的 Server struct 在使用包猫科重新生成平衡器的NGINX配置文件之前,在数据存储中.
所有页面模板都存储在“ui/templates”中.go "文件和相应的模板HTML文件可以在" ui/templates "目录下找到.
将Loadcat部署到远程服务器甚至本地环境非常简单. 如果您运行的是Linux(64位), 您可以从存储库中获取带有预构建Loadcat二进制文件的归档文件 Releases section. 如果您觉得有点冒险,您可以克隆存储库并自己编译代码. 虽然,这种情况下的经验可能有点 disappointing 因为编译Go程序并不是真正的挑战. 如果您正在运行Arch Linux,那么您很幸运! 为方便起见,已经为发行版构建了一个包. Simply download it 然后使用包管理器安装它. 所涉及的步骤在项目的 README.md file.
配置并运行Loadcat之后, 将web浏览器指向“http://localhost:26590”(假设它在本地运行并在端口26590上侦听). Next, create a balancer, 创建一对服务器, 确保在这些定义的端口上有监听, 瞧,你应该有NGINX负载平衡这些运行的服务器之间的传入请求.
这个工具远非完美,实际上它是一个实验性项目. 该工具甚至没有涵盖NGINX的所有基本功能. For example, 如果你想在NGINX层缓存后端节点提供的资源, 你仍然需要手工修改NGINX的配置文件. 这就是事情令人兴奋的地方. 这里有很多可以做的,这正是下一步要做的:覆盖更多的NGINX的负载平衡特性——基本的,甚至可能是NGINX Plus必须提供的.
Give Loadcat a try. 检查代码,分叉它,修改它,试用它. Also, 如果你已经构建了一个配置其他软件的工具,或者已经使用了一个你真正喜欢的工具,请在下面的评论部分告诉我们.
Mahmud是一名软件开发人员,拥有多年的经验和效率诀窍, scalability, 稳定的解.
13
世界级的文章,每周发一次.
世界级的文章,每周发一次.
Join the Toptal® community.