nginx后端容器服务的自动发现和动态配置

本文架构为Nginx+Consul+Consul Template+Registrator

服务基本架构图

graph LR; B(clinet) -->|客户端请求| C{Nginx} C -->|后端服务转发| D[docker web server1] C -->|后端服务转发| E[docker web server2] C -->|后端服务转发| F[docker web server3] D -->|容器自动发现| H{Registrator} E -->|容器自动发现| H F -->|容器自动发现| H H -->|服务自动注册| I[Consul] G[Consul Tepmlate] -->|订阅服务| I I -->|拉取更新| G G -->|根据自动注册的服务实时更新Nginx配置模板| C

工具介绍

Nginx

负责接受clint端来的请求

Consul

是一个分布式高可用的服务发现和配置共享的软件,支持服务发现与注册、多数据中心、健康检查和分布式键/值存储, 内嵌实现了服务发现系统,不需要构建自己的系统或使用第三方系统,客户只需要注册服务,并通过DNS或HTTP接口执行服务发现

特性:

  • 服务发现: 通过DNS API和HTTP API来进行服务注册和服务发现
  • 健康状态检测: 可以实时监测服务的可用性 , 如果不可用 , nginx将不会将请求转发到不用的服务上
  • K/V存储: 用来存储K/V值 , 可以通过HTTP接口可以很方便的管理
  • 多数据中心: 避免单点故障
  • 一致性算法: 用的是raft , 而且支持ACL访问控制
  • web UI: 提供了一个简单的web可视化页面来操作

官网的介绍也就是这些 , 具体的可以去官网文档去看看 , 地址是https://www.consul.io/intro/index.html

consul concept

consul中有些概念先理解

先理解consul的架构

上图中:

  • 包含2个数据中心 , 分别是datacenter1 , datacenter2
  • consul集群是B/S架构 , 一个集群有N个server和N个clinet组成

  • clinet表示consul的clinet模式 , 这种模式下, 所有注册到clinet的服务都会被转发到server上 , clinet节点不会做持久化存储

  • server表示consul的server模式,表明这个consul是个server节点。这种模式下,功能和CLIENT都一样,唯一不同的是,它会把所有的信息持久化的本地

  • server leader中间那个SERVER下面有LEADER的描述,表明这个SERVER节点是它们的leader。和其它SERVER不一样的一点是,它需要负责同步注册信息给其它的SERVER,同时也要负责各个节点健康监测

  • 可以看到上图 , client端互相通信用8301端口 , server互相通信用8300端口 , client向server端通信业用8300端口

Registrator简介

Registrator是一个独立于服务注册表的自动服务注册/注销组件,一般以Docker container的方式进行部署。Registrator会自动侦测它所在的宿主机上的所有Docker容器状态(启用/销毁),并根据容器状态到对应的服务注册列表注册/注销服务。

事实上,Registrator通过读取同一台宿主机的其他容器Container的环境变量进行服务注册健康检查定义等操作。

Registrator支持可插拔式服务注册表配置,目前支持包括Consul, etcd和SkyDNS 2三种注册工具。

Consul Template简介

具体详情点我了解——->consul入门指南

Consul Template 提供一个方便的方式从Consul获取数据通过consul-template的后台程序保存到文件系统.

这个后台进程监控Consul示例的变化并更新任意数量的模板到文件系统.作为一个附件功能,模板更新完成后consul-template可以运行任何命令.可以查看示例部分看这个功能将会对哪些应用场景产生帮助.

实验部署

部署分为2种 , 一种是docker化的部署 , 和非docker化的部署, 采用简单的部署方式主要是看效果 , 没有做高可用和集群化, 如果想做的话也是非常简单的 ,无非是增加几台机器和增加几个docker或者几个服务而已

Docker化部署

主机 IP 功用
node01 192.168.200.138 registrator、helloworld、consul-server、consul-template、nginx
node02 192.168.200.139 registrator、helloworld

在node01上部署服务

采用docker-compose的方式

vim docker-compose.yml

web:
  image: liberalman/helloworld:latest
  environment:
    SERVICE_80_NAME: my-web-server
    SERVICE_TAGS: backend-1
    MY_HOST: host-1
  ports:
  - "80"

lb:
  image: liberalman/nginx-consul-template:latest
  hostname: lb
  links:
  - consulserver:consul
  ports:
  - "80:80"

consulserver:
    image: consul:latest
    environment:
      SERVICE_TAGS: consul servers
    hostname: consul_server_master
    ports:
      - "8300:8300"
      - "8301:8301"
      - "8302:8302"
      - "8400:8400"
      - "8500:8500"
      - "8600:8600"
    command: consul agent -server -bootstrap-expect 1 -advertise 192.168.200.148 -node consul_server_master -data-dir /tmp/data-dir -client 0.0.0.0 -ui

registrator:
  image: gliderlabs/registrator:master
  hostname: registrator-1
  volumes:
  - "/var/run/docker.sock:/tmp/docker.sock"
  command: --net=host -ip=192.168.200.148 consul://192.168.200.148:8500

在node02上编写docker-compose

vim docker-compose.yml

web:
  image: liberalman/helloworld:latest
  environment:
    SERVICE_80_NAME: my-web-server
    SERVICE_TAGS: backend-2
    MY_HOST: host-2
  ports:
  - "80"
  
registrator:
  image: gliderlabs/registrator:master
  hostname: registrator-1
  volumes:
  - "/var/run/docker.sock:/tmp/docker.sock"
  command: --net=host -ip=192.168.200.148 consul://192.168.200.148:8500

部署完成之后 , 访问consul的web ui 地址: http://192.168.200.148:8500/ui (就是consul绑定地址的8500端口)

可以看到服务已经全部自动发现 ,访问主机ip的80端口 , 可以看到web服务正常,是随机出现的

健康状态检测也ok

服务也是后端2个随机

非Docker化部署

非docker化部署很简单, 就是把各个服务部署起来即可

Nginx

yum -y install nginx

启动Nginx

systemctl start nginx

Consul

consul的安装可以安装成集群形式 , 用来防止单点故障 , 不过用来测试的 , 可以只安装一个

wget https://releases.hashicorp.com/consul/1.4.0/consul_1.4.0_linux_amd64.zip

unzip consul_1.4.0_linux_amd64.zip

mv consul /usr/local/bin/

mkdir /etc/consul.d   #创建配置文件目录

启动consul

# 在开发者模式中启动Consul代理 一个单节点的consul,如果网络有多个ip, 那么就明确写出 其中-clinet的指的是启动web页面的时候, 谁可以访问, 0.0.0.0就是谁都可以访问,-config-dir指定配置文件目录
consul agent -server -bind=192.168.200.148 -client 0.0.0.0 -config-dir /etc/consul.d/

# 创建一个服务定义配置文件,假设有一个名为web服务,它运行在80端口。
echo '{"service": {"name": "web", "tags": ["web"], "port": 80}}' >/etc/consul.d/web.json

打开web页面 http://IP:8500/ui

Consul Template

wget https://releases.hashicorp.com/consul-template/0.19.5/consul-template_0.19.5_linux_amd64.zip

unzip consul-template_0.19.5_linux_amd64.zip

mv consul-template /usr/local/bin/
consul-template -h  //获取帮助

-consul-auth=<username[:password]>      设置基本的认证用户名和密码。
-consul-addr=<address>                  设置Consul实例的地址。
-max-stale=<duration>                   查询过期的最大频率,默认是1s。
-dedup                                  启用重复数据删除,当许多consul template实例渲染一个模板的时候可以降低consul的负载。
-consul-ssl                             使用https连接Consul。
-consul-ssl-verify                      通过SSL连接的时候检查证书。
-consul-ssl-cert                        SSL客户端证书发送给服务器。
-consul-ssl-key                         客户端认证时使用的SSL/TLS私钥。
-consul-ssl-ca-cert                     验证服务器的CA证书列表。
-consul-token=<token>                   设置Consul API的token。
-syslog                                 把标准输出和标准错误重定向到syslog,syslog的默认级别是local0。
-syslog-facility=<facility>             设置syslog级别,默认是local0,必须和-syslog配合使用。
-template=<template>                    增加一个需要监控的模板,格式是:'templatePath:outputPath(:command)',多个模板则可以设置多次。
-wait=<duration>                        当呈现一个新的模板到系统和触发一个命令的时候,等待的最大最小时间。如果最大值被忽略,默认是最小值的4倍。
-retry=<duration>                       当在和consul api交互的返回值是error的时候,等待的时间,默认是5s。
-config=<path>                          配置文件或者配置目录的路径。
-pid-file=<path>                        PID文件的路径。
-log-level=<level>                      设置日志级别,可以是"debug","info", "warn" (default), and "err"。
-dry                                    Dump生成的模板到标准输出,不会生成到磁盘。
-once                                   运行consul-template一次后退出,不以守护进程运行。
新建一个Nginx模板文件
$ vim nginx.conf.ctmpl

{{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 default_server;

  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}}
}
生成Nginx配置文件后让Nginx自动加载
consul-template  -consul-addr IP:8500 -template="nginx.conf.ctmpl:/usr/local/nginx/conf/conf.d/default.conf:service nginx reload"

这种是用命令行来启动服务, 也可以使用指定配置文件来加载服务 , 配置文件里写上各种参数, 各种具体的参数意义可以去官方样式中看, 地址是: https://github.com/hashicorp/consul-template#configuration-file-format

官方的配置文件为:

consul {

  auth {
    enabled  = true
    username = "test"
    password = "test"
  }

  address = "192.168.2.210:8500"
  token = "abcd1234"

  retry {
    enabled = true
    attempts = 5
    backoff = "250ms"
  }

  ssl {

    enabled = true
    verify = false
    cert = "/path/to/client/cert"
    key = "/path/to/client/key"
    ca_cert = "/path/to/ca"
    ca_path = "path/to/certs/"
    server_name = "my-server.com"
  }
}

reload_signal = "SIGHUP"
dump_signal = "SIGQUIT"
kill_signal = "SIGINT"
max_stale = "10m"
log_level = "warn"
pid_file = "/path/to/pid"


wait {
  min = "5s"
  max = "10s"
}

vault {
  address = "https://vault.service.consul:8200"
  token = "abcd1234"
  unwrap_token = true
  renew_token = true
  retry {
    # ...
  }

  ssl {
    # ...
  }
}


syslog {
  enabled = true
  facility = "LOCAL5"
}


deduplicate {
  enabled = true
  prefix = "consul-template/dedup/"
}


exec {
  command = "/usr/bin/app"
  splay = "5s"
  env {

    pristine = false
    custom = ["PATH=$PATH:/etc/myapp/bin"]
    whitelist = ["CONSUL_*"]
    blacklist = ["VAULT_*"]
  }

  reload_signal = ""
  kill_signal = "SIGINT"
  kill_timeout = "2s"
}

template {

  source = "/path/on/disk/to/template.ctmpl"
  destination = "/path/on/disk/where/template/will/render.txt"
  contents = "{{ keyOrDefault \"service/redis/maxconns@east-aws\" \"5\" }}"
  command = "restart service foo"
  command_timeout = "60s"
  perms = 0600
  backup = true
  left_delimiter  = "{{"
  right_delimiter = "}}"

  wait {
    min = "2s"
    max = "10s"
  }
}

下面写一个简单的配置文件

$ vim nginx.hcl

consul {
address = "192.168.2.210:8500"
}

template {
source = "nginx.conf.ctmpl"
destination = "/usr/local/nginx/conf/conf.d/default.conf"
command = "service nginx reload"
}

执行命令即可让其重新配置

consul-template -config "nginx.hcl"

Registrator

registrator就是docker化部署了 , 它的作用就是不依靠第三方组件 就能发现所在宿主机上的docker服务 , 可以指定consul , 让其自动注册 , 不过registrator和其他的组件不一样 , 要在每个宿主机上要部署一个 , 用来自动发现所在宿主机上的docker服务

docker pull gliderlabs/registrator:latest     //拉取镜像

docker run -d --name=registrator \
             -v /var/run/docker.sock:/tmp/docker.sock \
             --net=host \
             gliderlabs/registrator -ip="192.168.200.148" consul://192.168.200.148:8500

简单的测试web服务

写一个docker化的http服务

我们用python起一个简单的http访问 , 我们用dockerfile写一个

FROM python2.7.14-alpine
EXPOSE  80
CMD ["python","-m","SimpleHTTPServer"]

构建此Dockerfile

docker build -t python/server .

运行

docker run -it \
-p 8000:80 python/server

curl访问是不是正常curl IP:80 即可