一. 简介
上一篇只讲了博客的前端问题,这一篇讲一下后端的微服务搭建。项目的后端使用的 thinkjs 框架,在我之前的博客中已经写过,这里就不重点说明了。
后端项目分为三个:
- 博客前台页面服务端: 在这里。
- 博客后台页面服务端:在这里。
- consul-template+nginx 实现的基于微服务注册发现的负载均衡:在这里。
前两个数据业务相关的服务即下图的 service_web,第三个项目就是 consul-template+nginx 的实现的负载均衡。
如果对 consul 基础概念不了解,建议读完我博客里这两篇文章再继续看下面的内容。
consul+docker 实现服务注册。
consul+docker 实现服务发现及网关。
首先看下架构图:
架构图解析
consul-template 会订阅 consul 注册中心上的服务消息,当 service-web 改变时,consul 注册中心会将新的 service web 信息推送给 consul-template,consul-template 会修改 nginx 配置文件,nginx 重载入配置后,就达到了可以自动修改更新的负载均衡。
二.consul-template+nginx 实现基于微服务的负载均衡
consul-template 介绍
Consul-Template 是基于 Consul 的自动替换配置文件的应用。在 Consul-Template 没出现之前,大家构建服务发现系统大多采用的是 Zookeeper、Etcd+Confd 这样类似的系统。
使用场景:可以查询 Consul 中的服务目录、Key、Key-values 等。这种强大的抽象功能和查询语言模板可以使 Consul-Template 特别适合动态的创建配置文件。例如:创建 Apache/Nginx Proxy Balancers、Haproxy Backends、Varnish Servers、Application Configurations 等。
代码介绍
当 consul 注册中心的的服务改变时,consul-template 会根据 nginx-consul-template 重新生成 nginx.conf。首先看一下 nginx.conf.ctmpl 的代码:
nginx.conf.ctmpl
upstream admin {{{range service "service-admin"}}server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1;
{{else}}server 127.0.0.1:65535; # force a 502{{end}}
}
upstream app {{{range service "service-web"}}server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1;
{{else}}server 127.0.0.1:65535; # force a 502{{end}}
}
server {
listen 80 default_server;
# 映射博客后台管理服务
location /admin{
proxy_pass http://admin/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
#请求博客后台服务的静态资源
location ^~/static/blog-backend-react{
proxy_pass http://admin/static/blog-backend-react;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
#请求博客前台服务的静态资源
location ^~/static/blog-react{
proxy_pass http://app/static/blog-react;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 接口请求,映射博客前台服务
location /font{
proxy_pass http://app;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 接口请求,映射博客后台服务
location /api{
proxy_pass http://admin/api;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 默认映射到博客前端服务
location / {
proxy_pass http://app;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
template 与正常 nginx.conf 区别就是 upstream 里部分,根据服务名——这里是两个服务,self-blog-backend 博客后台管理服务与 self-blog-fontend 博客前台服务——动态生成服务对应的 ip。
template 的反向代理部分,与正常 nginx 配置一致,因为有两个项目,所以每个 url 请求接口,静态资源,服务,都需要映射到特定的服务。
服务启动后,根据模版生成的 nginx 配置文件如下:
upstream admin {
server 172.25.0.7:8362 max_fails=3 fail_timeout=60 weight=1;
server 172.25.0.8:8362 max_fails=3 fail_timeout=60 weight=1;
server 172.25.0.11:8362 max_fails=3 fail_timeout=60 weight=1;
}
upstream app {
server 172.25.0.4:8365 max_fails=3 fail_timeout=60 weight=1;
server 172.25.0.9:8365 max_fails=3 fail_timeout=60 weight=1;
server 172.25.0.10:8365 max_fails=3 fail_timeout=60 weight=1;
}
server {....}
可以看到,upstream 里,根据服务名已经找到了对应的 ip。这里后台,前台项目各启动了三个实例,用户访问的时候,就会根据配置的 nginx 负载均衡的策略,访问其中一个 ip。
nginx.service
用于启动 nginx 服务
#!/bin/sh
# 启动 nginx
# daemon off 关闭守卫进程, 保证 nginx 前台运行
nginx -c /etc/nginx/nginx.conf -t && \
nginx -c /etc/nginx/nginx.conf -g "daemon off;"
consul.template.service
用于启动 consul-template
#!/bin/sh
# 启动 consul-template, 指定 consul 地址
# consul 变化后,根据模板 nginx.conf,生成 nigix 配置文件并 reload
exec consul-template \
-consul-addr=consul:8500 \
-template "/etc/consul-templates/nginx.conf:/etc/nginx/conf.d/app.conf:nginx -s reload"
Dockerfile
因为整个负载均衡服务需要做成镜像,与其他服务一起部署,所以这里需要维护 Dockerfile
FROM nginx
# 声明告诉系统,无需向用户请求输入(非交互式)RUN DEBIAN_FRONTEND=noninteractive \
# 更新软件包列表
apt-get update -qq && \
# 安装 curl runit
apt-get -y install curl runit && \
# rm 删除,-r 全部删除子目录, -f 强制删除
rm -rf /var/lib/apt/lists/*
ADD consul-template_0.19.4_linux_amd64.tgz /usr/local/bin/
# 将 nginx.service 放到指定文件夹,生成 run 文件
ADD nginx.service /etc/service/nginx/run
# 给所有人添加文件的可执行权限
RUN chmod a+x /etc/service/nginx/run
ADD consul-template.service /etc/service/consul-template/run
RUN chmod a+x /etc/service/consul-template/run
RUN rm -v /etc/nginx/conf.d/*
ADD nginx.conf /etc/consul-templates/nginx.conf
# 使用 runit , 当 runsvdir 在 /etc/service/ 目录中发现新的配置时,# 启动 runsv 进程来执行和监控 /etc/service 下的 run 脚本
CMD ["/usr/bin/runsvdir", "/etc/service"]
维护好之后,就可以制作自己的 nginx-consul-template 镜像了。
docker-compose.yml
博客后台与前台服务端项目 github 上,都有自己的 Dockerfile, 将他们做成镜像后,与 nginx-consul-template 镜像一起,通过 docker-compose 统一部署这几个服务。
version: "2.0"
services:
consulserver:
image: progrium/consul:latest
hostname: consulserver
ports:
- "8300"
- "8400"
- 8500:8500
- "53"
command: -server -ui-dir /ui -data-dir /tmp/consul --bootstrap-expect=3
consulserver1:
image: progrium/consul:latest
hostname: consulserver1
depends_on:
- consulserver
ports:
- "8300"
- "8400"
- "8500"
- "53"
command: -server -data-dir /tmp/consul -join consulserver
consulserver2:
image: progrium/consul:latest
hostname: consulserver2
depends_on:
- consulserver
ports:
- "8300"
- "8400"
- "8500"
- "53"
command: -server -data-dir /tmp/consul -join consulserver
registrator:
image: gliderlabs/registrator:master
hostname: registrator
depends_on:
- consulserver
volumes:
- /var/run/docker.sock:/tmp/docker.sock
command: -internal consul://consulserver:8500
serviceadmin1:
image: daocloud.io/sunxing102005/self-blog-backend:latest
depends_on:
- consulserver
environment:
SERVICE_8362_NAME: service-admin
ports:
- 3002:3002
- "8362"
serviceweb1:
image: daocloud.io/sunxing102005/self-blog-fontend:latest
depends_on:
- consulserver
environment:
SERVICE_8365_NAME: service-web
ports:
- 3005:3005
- "8365"
lb:
image: daocloud.io/sunxing102005/consul-template-nginx-blog:latest
hostname: lb
links:
- consulserver:consul
ports:
- 80:80
运行命令,启动服务
docker-compose up -d
运行后,就可以从注册中心看到 consul 上注册的服务:
其中 consul-template-nginx-blog 就是 lb,service-admin,service-web 分别是博客的前台,后台服务(3002,3005 两个服务是留给 prometheus 抓数据用的,不用在意)因为这里各只启动了一个实例,所以他们每个只有一个服务。下面看下 nginx 的效果
访问默认 80 端口,直接访问博客前端服务
访问 80 端口加上 admin 后缀,就会跳到博客管理网站。
三. 开发笔记
这里放一些开发过程中,自己记录的一些问题和知识点。
如何进入 docker 容器查看 consul-template 生成的 nginx.conf
docker ps // 查看 consul-template-nginx 容器 id
docker exec -it exec 22fff6c360f1 /bin/sh // 进入容器
cat /etc/nginx/conf.d/app.conf // 查看文件
docker 运行 nginx 为什么要执行 daemon off
docker 容器后台运行时,前台必须有一个前台进程。
容器运行的命令,如果不是一直挂起的就会自动退出。
nginx 是后台进程模式运行,导致没有前台运行的应用,他就会立即退出应用。
解决方法:
-
将你要运行的容器以前台形式运行,关闭守卫进程
nginx -g "daemon off"
-
添加 tail,top 这种可以前台运行的程序,推荐使用 tail, 然后持续输出 log
service nginx start && tail -f /var/log/nginx/error.log
nginx.conf.ctmpl 注意事项
upstream admin {{{range service "service-admin"}}server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1;
{{else}}server 127.0.0.1:65535; # force a 502{{end}}
}
upstream app {{{range service "service-web"}}server {{.Address}}:{{.Port}} max_fails=3 fail_timeout=60 weight=1;
{{else}}server 127.0.0.1:65535; # force a 502{{end}}
}
server {
listen 80 default_server;
location /admin{
proxy_pass http://admin/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location 匹配的如果不是 /,如果想跳转到这个服务的根目录,里面 proxy_pass 定义的 url,后面要加上 /,比如这里匹配的 /admin,
proxy_pass 是 http://admin/,如果是 http://admin,则反向代理的路径是 http://XXXX:8362/admin,而不是 8362 服务的跟路径 http://XXXX:8362。
nginx 常用命令
nginx 服务器重启命令,关闭
nginx -s reload:修改配置后重新加载生效
nginx -s reopen:重新打开日志文件
nginx -t -c /path/to/nginx.conf 测试 nginx 配置文件是否正确
关闭 nginx:nginx -s stop : 快速停止 nginx
quit:完整有序的停止 nginx
其他的停止 nginx 方式:ps -ef | grep nginx
kill -QUIT 主进程号:从容停止 Nginx
kill -TERM 主进程号:快速停止 Nginx
pkill -9 nginx:强制停止 Nginx
启动 nginx:
nginx -c /path/to/nginx.conf
平滑重启 nginx:kill -HUP 主进程号
nginx 反向代理加 req header
前端每次请求,都 token 放到了 request header 里。之前 header 里设置的字段叫 access_token,传不过去,发现 nginx 反向代理时,会忽略带下划线的 header,所以改成了 accesstoken。
docker 常用命令
启动 systemctl start docker
守护进程重启 sudo systemctl daemon-reload
重启 docker 服务 systemctl restart docker
重启 docker 服务 sudo service docker restart
关闭 docker service docker stop
关闭 docker systemctl stop docker
docker 批量删除容器
docker rm `docker ps -a -q` // 删除所有容器
docker rmi `docker images -q` // 删除所有镜像
// 按条件删除镜像
// 没有打标签
docker rmi `docker images -q | awk '/^<none>/ {print $3}'`
// 镜像关键字
docker rmi `docker images | grep doss api | awk 'print $3'`
linx chmod 命令
- 如果给所有人添加可执行权限:chmod a+x 文件名
- 如果给文件所有者添加可执行权限:chmod u+x 文件名
- 如果给所在组添加可执行权限:chmod g+x 文件名
- 如果给所在组以外的人添加可执行权限:chmod o+x 文件名
- 详细 chmod 命令,可参考这里。
linx service 服务管理
通过 ubuntu 自带 service,可以很方便创建后台运行程序。
service 文件路径:/lib/systemd/ystem
service 文件包含多个部分,下面是简单的后台运行的 service 文件。
启动服务
service leshan-erver start
停止服务
service leshan-erver stop
查看服务状态
systemctl status lenshan-server
重新加载 service 文件
systemctl daemon-reload
四. 总结
本篇主要讲解 consul-template+nginx 的负载均衡的实现,网上类似的讲解有很多,consul+nginx+consul-template 构建简单微服务也是很基础常用的方案。这一部分与之前我转发的 consul+docker 实现服务发现及网关其实也只是差了个网关和负载均衡。稍微值得注意的是,本项目里,用到了两个业务层面的服务——博客前台与后台两个服务——而不是单体项目,稍微有点微服务的感觉了,把大项目拆开管理。所以 nginx 这部分就需要分别反向代理到两个项目里,这里请求接口,静态什么的,两个服务要有区分,让 nginx 有代理区分的标准。
至此博客的前端,后端微服务搭建都讲完了,有空的话下一篇讲一下 daocloud 平台部署和 prometheus+grafana 的监控系统。
五. 参考
https://www.jianshu.com/p/a4c…
http://blog.zongwu233.com/Con…