一点小背景
docker起了几个服务,没有配置端口映射,导致不能通过网络访问。当然,更简单的方式是加端口映射,笔者的情况更复杂一些,就想到了用nginx映射一下。
Nginx(发音同“engine X”)是异步框架的网页服务器,也可以用作反向代理、负载平衡器和HTTP缓存。
—— https://zh.wikipedia.org/wiki/Nginx
第一个demo
最简单启动
docker run -itd --name tnginx -p 10010:80 nginx
访问一下
静态网页
nginx默认读取/usr/share/nginx/html
下面的静态文件
创建1个存放静态文件的文件夹映射给容器
mkdir html
echo "<h1>Hello Nginx!</h1>" > ./html/index.html # 创建1个静态页面
# docker rm -f tnginx # 删掉之前存在的,否则会冲突
docker run -itd --name tnginx -p 10010:80 -v "$PWD/html":/usr/share/nginx/html nginx
映射物理机文件的好处在于即使容器停止或者被清理,再次启动映射相同的文件还能恢复到同一状态。修改物理机文件也会在容器内生效。后续的配置修改只需要修改物理机上的配置文件,然后重启nginx容器即可docker restart tnginx
再访问一下
配置文件
默认读/etc/nginx/
文件夹下的nginx配置文件,咱也不知道长什么样,从docker容器中捞一个出来
docker cp tnginx:/etc/nginx ./
用的是nginx/nginx.conf
配置文件
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
改一下静态文件的目录地址,在配置文件里http
配置中添加以下配置
http {
...
server {
listen 80 default_server;
root /www/html;
index index.html;
}
}
映射配置文件和静态文件的目录
# docker rm -f tnginx # 删掉之前存在的,否则会冲突
docker run -itd --name tnginx -p 10010:80 -v "$PWD/html":/www/html -v "$PWD/nginx":/etc/nginx nginx
再访问一下http://192.168.9.109:10010/
仍然是work的
测试服务
为了直观体现下文操作的效果,用python简单起个http服务,不感兴趣可跳过~
# service.py
from flask import Flask, request
import multiprocessing
from multiprocessing import Process
multiprocessing.set_start_method("fork") # 防止Process的pickle报错
class MyFlask(Flask):
def getResponse(self):
return "{}_{}> {}".format(self.name, multiprocessing.current_process().name, request.base_url), \
200, {'Access-Control-Allow-Origin': '*'} # 最后的header避免fetch访问时跨域报错
app1 = MyFlask('service_1')
@app1.route('/hello')
@app1.route('/user/add')
@app1.route('/user/get')
def service1_handler():
return app1.getResponse()
app2 = MyFlask('service_2')
@app2.route('/book/add')
@app2.route('/book/get')
def service2_handler():
return app2.getResponse()
if __name__ == '__main__':
Process(target=app1.run, args=('0.0.0.0', 10001)).start()
Process(target=app1.run, args=('0.0.0.0', 10002)).start()
Process(target=app2.run, args=('0.0.0.0', 20001)).start()
Process-1、Process-2启动了app1,Process-3启动了app2
做了个页面直观一点看看响应
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<script src="https://cdn.staticfile.org/jquery/2.0.0/jquery.min.js"></script>
</head>
<body>
<form class="form-inline">
<button type="button" class="btn btn-primary" onclick="visit()">访问</button>
<select class="form-control" id="s_url">
<option><span>http://192.168.9.109:10010/hello</span></option>
<option><span>http://192.168.9.109:10010/user/add</span></option>
<option><span>http://192.168.9.109:10010/user/get</span></option>
<option><span>http://192.168.9.109:10010/book/add</span></option>
<option><span>http://192.168.9.109:10010/book/get</span></option>
</select>
<div class="form-group">
<input type="text" id="num" class="form-control" style="width:50px" value="1">
</div>
<span>次</span>
</form>
<div id="result">
<div>=================================================</div>
</div>
<script type="text/javascript">
function visit(){
var url=$("#s_url option:selected").val();
var num=$("#num").val();
for (var i=0;i<num;i++)
{
fetch(url)
.then(function(data) {
return data.text();
}).then(function(data) {
$("#result").append("<div>"+data+"</div>");
});
}
}
</script>
</body>
</html>
效果如下
路由规则
规则优先更精准的匹配
http {
...
server {
...
location / { # 未被其他配置规则命中的走这里
proxy_pass http://192.168.9.109:10001; # app1(Process-1)
}
location /book { # /book 开头的走这里
proxy_pass http://192.168.9.109:20001; # app2(Process-2)
}
}
}
/book/add
路由到了Process-3上的app2,/user/add
路由到了默认去了app1,应为没有配置app2的/hello
,/hello
去访问了app1
负载均衡
不加额外配置是轮询的
http {
upstream myservice { # 这里是负载均衡的配置
server 192.168.9.109:10001; # 不加其他配置默认是轮询的
server 192.168.9.109:10002;
}
server {
...
location / {
proxy_pass http://myservice; # 给server指定proxy到下面的 myservice
}
}
}
更改配置按权重选择
upstream myservice { # 这里是负载均衡的配置
server 192.168.9.109:10001 weight=4; # 配置权重
server 192.168.9.109:10002 weight=1;
}
其他配置
upstream myservice {
server 192.168.9.109:10001 weight=4 max_fails=3 fail_timeout=15;
server 192.168.9.109:10002 backup; # 备份,所有不可用才会生效
server 192.168.9.109:10003 down; # 标识不可用
server 192.168.9.109:10004 max_conns=1000;
}
其他
设置header
经由nginx转发以后,客户端的信息变成nginx的信息,可以通过设置header把客户端的信息传过去
location / {
...
proxy_set_header Host $http_host; # nginx的ip:port
proxy_set_header X-Upstream-Addr $upstream_addr; # 理应是真实服务的ip:port,但是我这里没生效
proxy_set_header X-Real-IP $remote_addr; # 发起请求的ip
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 发起请求的ip
}
内置变量
如上使用的$http_host
是nginx的内置变量,更多可参见
http://nginx.org/en/docs/http/ngx_http_core_module.html#variables
内置变量还可以在nginx的输出日志中使用,即
http {
...
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
...
}
参考
配置路由的 https://www.cnblogs.com/jackylee92/p/6836948.html
负载均衡的策略 https://www.cnblogs.com/itzgr/p/13330613.html
https://www.ruanyifeng.com/blog/2018/02/nginx-docker.html
upsteam_addr http://nginx.org/en/docs/http/ngx_http_upstream_module.html#variables