实现动态服务负载均衡(docker+consul+consul_template+nginx)

2022-09-13 16:18:49 浏览数 (1)

环境说明

  • ubuntu16.04.7 LTS xenial
  • boxes 下载地址
  • 我这里用的ubuntu/xenial64下载地址
  • 具体安装细节请参考Ubuntu安装docker准备篇

主要技术以及版本

  • vagrant
代码语言:txt复制
- vagrant是一个对虚拟环境管理的工具。virtualbox
代码语言:txt复制
- VirtualBox 是一款开源虚拟机软件。docker
代码语言:txt复制
- version 20.10.7
- docker是一个开源的应用容器引擎。
- 安装[docker](https://www.runoob.com/docker/ubuntu-docker-install.html)
- 具体安装细节以及权限问题解决请参考[Ubuntu安装docker](https://niuzheng.net/archives/1146/)consul
代码语言:txt复制
- version 1.11.3
- consul是一个用来实现分布式系统的服务发现与配置的开源工具。
- [官网下载地址](https://www.consul.io/downloads)consul-template
代码语言:txt复制
- version 0.19.0
- consul-template基于consul的自动替换配置文件的应用。
- [consul-template 下载地址](https://releases.hashicorp.com/consul-template)nginx
代码语言:txt复制
- version 1.21.6
- 地球人都知道的嘛!!!额外说明
代码语言:txt复制
- `virtualbox`和`vagrant`版本不兼容会出现很多未知问题(坑),官方网站也没有介绍哪个版本对应兼容,我这里分享一个兼容性不错的版本下载连接放下面,供大家参考。
- [virtualbox5.2.8](https://www.virtualbox.org/wiki/Download_Old_Builds_5_2)
- [vagrant2.1.1](https://www.vagrantup.com/downloads)

简要说明

  • 传统负载均衡架构图
代码语言:txt复制
- 假如目前服务需要扩容,增加了一台Web Server5,你需要手动修改Nginx配置并且重启Nginx。
- 假如目前Web Server1、Web Server4服务大半夜突然挂了,你岂不是需要爬起来修改Nginx配置,这种情况重复来几次,这可不是什么好的体验,会让人崩溃的呢!!!我们要实现什么呢?下面我们附上图来说明!!
代码语言:txt复制
- 因需要或者意外情况而造成增加/减少机器及或者某个服务而动态实现nginx配置自动更新并实现更新重载。
- web server 通过 registrator 注册到consul服务里,然后consul-template订阅consul实现nginx配置更新重载,达到动态扩容(增加/减少),保证服务稳定可靠运行的目的。

实现

  • 假设你已经安装好虚拟机(以ubuntu为例)和docker应用容器引擎。
  • 这里可以先完整安装一台机器的软件和应用,然后使用vagrant打包剩下的两个机器。
机器说明
  • 服务器1: IP: 192.168.56.2
  • 服务器2: IP: 192.168.56.3
  • 服务器3: IP: 192.168.56.4
consul集群角色分配(我这里电脑配置有限,搞太多虚拟机测试主机windows机器容易卡死,所以我的consul集群和webserver以及nginx转发服务器都用这三个机器实现啦)
  • 服务器1: IP: 192.168.56.2 leader
  • 服务器2: IP: 192.168.56.3 service
  • 服务器3: IP: 192.168.56.4 client nginx转发服务器
应用程序角色分配
  • 服务器1: IP: 192.168.56.2 直播服务API1
  • 服务器2: IP: 192.168.56.3 直播服务API2
  • 服务器3: IP: 192.168.56.4 直播服务API3
部署consul集群

consul常用命令

  • server : 定义agent运行在server模式
  • bootstrap-expect :在一个datacenter中期望提供的server节点数目,当该值提供的时候,consul一直等到达到指定sever数目的时候才会引导整个集群,该标记不能和bootstrap共用
  • bind:该地址用来在集群内部的通讯,集群内的所有节点到地址都必须是可达的,默认是0.0.0.0
  • node:节点在集群中的名称,在一个集群中必须是唯一的,默认是该节点的主机名
  • ui-dir: 提供存放web ui资源的路径,该目录必须是可读的
  • rejoin:使consul忽略先前的离开,在再次启动后仍旧尝试加入集群中。
  • config-dir:配置文件目录,里面所有以.json结尾的文件都会被加载
  • client:consul服务侦听地址,这个地址提供HTTP、DNS、RPC等服务,默认是127.0.0.1所以不对外提供服务,如果你要对外提供服务改成0.0.0.0
代码语言:javascript复制
# 拉取镜像
docker pull consul 
# 192.168.56.2 (leader server)
docker run --net=host --name consul1 -d  consul  agent -server -bootstrap-expect 1 -ui -node=s0 -bind=192.168.56.2 -client=0.0.0.0
# 192.168.56.3 (server)
docker run --net=host --name consul3 -d  consul  agent -server  -ui -node=s3 -bind=192.168.56.3 -client=0.0.0.0 -join=192.168.56.2
# 192.168.56.4 (client)
docker run --net=host --name consul4 -d  consul  agent  -ui -node=s4 -bind=192.168.56.4 -client=0.0.0.0 -join 192.168.56.2
# 查看consul cluster中consul节点的信息
docker exec -it consul1 consul members
#查看consul集群已经启动以及选举的leader
docker exec -it consul1 consul operator raft list-peers

浏览器访问UI界面 http://192.168.56.2:8500/

编写直播API服务

我这里直接使用swoole简单模拟搭建一个直播的HTTP服务,前提是你的php安装了swoole扩展(server.php)

代码语言:javascript复制
<?php

#192.168.56.3
$http = new SwooleHttpServer('0.0.0.0', 8080);

$http->on('Request', function ($request, $response) {
  $response->header('Content-Type', 'text/html; charset=utf-8');
  $response->end('<h1>直播服务API2 #IP:192.168.56.3</h1>');
});

$http->start();
代码语言:javascript复制
<?php

#192.168.56.3
$http = new SwooleHttpServer('0.0.0.0', 8080);

$http->on('Request', function ($request, $response) {
  $response->header('Content-Type', 'text/html; charset=utf-8');
  $response->end('<h1>直播服务API2 #IP:192.168.56.3</h1>');
});

$http->start();
代码语言:javascript复制
<?php

#192.168.56.4
$http = new SwooleHttpServer('0.0.0.0', 8080);

$http->on('Request', function ($request, $response) {
  $response->header('Content-Type', 'text/html; charset=utf-8');
  $response->end('<h1>直播服务API3 #IP:192.168.56.4</h1>');
});

$http->start();

注册直播服务API到consul集群

consul api 文档

代码语言:javascript复制
//composer require php-curl-class/php-curl-class
require __DIR__ . '/vendor/autoload.php';
use CurlCurl;

function _curl($u = '',$method = 'get', $data = null)
{
  $curl = new Curl();
  $baseUrl = 'http://192.168.56.4:8500/v1';
  $url = $baseUrl. $u;
  if (in_array(strtolower($method), ['put','post']) && !empty($data)) {
      return $curl->$method($url,$data);
  } else {
      return $curl->$method($url);
  }
}
$res = 'init';
//注册开始
$servers = [
 ['ip' => '192.168.56.2','port' => 8080,'id' => 'zhibo-1','Name' => 'zhibo_service'],
 ['ip' => '192.168.56.3','port' => 8080,'id' => 'zhibo-2','Name' => 'zhibo_service'],
 ['ip' => '192.168.56.4','port' => 8080,'id' => 'zhibo-3','Name' => 'zhibo_service']
];
foreach ($servers as $serve)
{
  //注意同一个服务Name必须一样ID可以不同
  $data = array(
      'ID' => $serve['id'],
      'Name' => $serve['Name'],
      'Tags' => ["primary", "v2"],
      'Address' => $serve['ip'],
      'Port' => $serve['port'],
      'EnableTagOverride' => false,
      'check' => array(
          'DeregisterCriticalServiceAfter' => '90m',
          'HTTP' => "http://{$serve['ip']}:{$serve['port']}",
          'Interval' => '10s',
          "Timeout" => "5s"
      )
  );
  
  $data = json_encode($data,JSON_UNESCAPED_SLASHES);
  $res = _curl('/agent/service/register','put',$data);
  //$res = _curl('/agent/service/register?replace-existing-checks=true','put',$data);
  echo "<pre>";
  print_r($res);
}
##其他API大概描述
//注销直播-3服务
//$res = _curl('/agent/service/deregister/zhibo-3','put');

//启用维护模式(enable=true/false)
//$res = _curl('/agent/service/maintenance/zhibo-3?enable=false&reason=维护原因','put');

//检查是否健康 status 为passing的为通过
//$res = _curl('/health/checks/zhibo_service');

//列出服务
//$res = _curl('/agent/services');

//获取服务配置
//$res = _curl('/agent/service/zhibo-3');

//获取本地服务运行状况
//json
//$res = _curl('/agent/health/service/name/zhibo_service');
//text
//$res = _curl('/agent/health/service/name/zhibo_service?format=text');

//通过 ID 获取本地服务运行状况
//$res = _curl('/agent/health/service/id/zhibo-3');
//$res = _curl('/agent/health/service/id/zhibo-3?format=text');
echo '<pre>';
print_r($res);
die;

搭建nginx转发服务,安装consul-template并关联订阅服务consul集群更新nginx转发配置,重启nginx。
  • 目录结构

consul-template文档地址

模板文件

代码语言:javascript复制
{{range services}} {{$name := .Name}} {{$service := service .Name}}
  upstream {{$name}} {
    zone upstream-{{$name}} 64k;
    {{range $service}}
         server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1;
    {{else}}
         server 127.0.0.1:65535; # force a 502
    {{end}}
  }
{{end}}

server {
  listen 80 zhibo_service.com;

  location / {
      root /usr/share/nginx/html/;
      index index.html;
  }

  location /stub_status {
      stub_status;
  }

 {{range services}} {{$name := .Name}}
     location /{{$name}} {
       proxy_pass http://{{$name}};
     }
 {{end}}
}

Dockerfile 文件

代码语言:javascript复制
#指定基础镜像
FROM nginx:latest
#指定维护人
#MAINTAINER zhengniu
LABEL authors="zhengniu <771036148@qq.com>"
#https://releases.hashicorp.com/consul-template
ARG CONSUL_TMEPLATE_VERSION="0.19.0"
RUN rm -rf /etc/nginx/sites-available/
ENV CONSUL_URL 127.0.0.1:8500
#ADD https://releases.hashicorp.com/consul-template/${CONSUL_TMEPLATE_VERSION}/consul-template_${CONSUL_TMEPLATE_VERSION}_linux_amd64.zip /usr/local/bin/
ADD  consul-template_0.19.0_linux_amd64.tgz /usr/local/bin/
VOLUME /templates
ADD service.ctmpl /templates/
EXPOSE 80
RUN mkdir -p /etc/service/nginx && rm -rf /etc/service/nginx/*
ADD start.sh /etc/service/nginx/run
CMD ["/etc/service/nginx/run"]

start.sh

代码语言:javascript复制
#!/bin/bash
service nginx start &
consul-template -consul-addr=$CONSUL_URL -template="/templates/service.ctmpl:/etc/nginx/conf.d/service.conf:service nginx reload"

构建镜像以及启动镜像
代码语言:javascript复制
 docker build -t nginx:consuleTemplate .
 docker run --net=host -d --name nginx nginx:consuleTemplate

验证

查看是否生成配置文件

代码语言:javascript复制
#查看
docker exec -it nginx sh  -c "cat /etc/nginx/conf.d/service.conf"

#结果
upstream consul {
zone upstream-consul 64k;
server 192.168.56.2:8300 max_fails=3 fail_timeout=60 weight=1;
server 192.168.56.3:8300 max_fails=3 fail_timeout=60 weight=1;

}
upstream zhibo_service {
zone upstream-zhibo_service 64k;
server 127.0.0.1:65535; # force a 502
}

server {
listen 80 zhibo_service.com;

location / {
  root /usr/share/nginx/html/;
  index index.html;
}

location /stub_status {
  stub_status;
}


location /consul {
  proxy_pass http://consul;
}

location /zhibo_service {
  proxy_pass http://zhibo_service;
}

}

分别启动直播-1、直播-2、直播-3服务

代码语言:javascript复制
#192.168.56.2
php server.php

#192.168.56.3
php server.php

#192.168.56.4
php server.php
#查看
docker exec -it nginx sh  -c "cat /etc/nginx/conf.d/service.conf"


#查看结果动态增加了
upstream consul {
zone upstream-consul 64k;
server 192.168.56.2:8300 max_fails=3 fail_timeout=60 weight=1;
server 192.168.56.3:8300 max_fails=3 fail_timeout=60 weight=1;

}
upstream zhibo_service {
zone upstream-zhibo_service 64k;
server 192.168.56.2:8080 max_fails=3 fail_timeout=60 weight=1;
server 192.168.56.3:8080 max_fails=3 fail_timeout=60 weight=1;
server 192.168.56.4:8080 max_fails=3 fail_timeout=60 weight=1;

}

server {
listen 80 zhibo_service.com;

location / {
  root /usr/share/nginx/html/;
  index index.html;
}

location /stub_status {
  stub_status;
}


location /consul {
  proxy_pass http://consul;
}

location /zhibo_service {
  proxy_pass http://zhibo_service;
}

}
# 浏览器访问 http://zhibo.com/zhibo_service  

0 人点赞