本文讲的是在Nginx反向代理后面运行Docker,【编者的话】想让Docker拥有公共网关,能支持多租户,以及我们期望的安全认证等功能?请看作者通过重新编译Nginx让其像支持Websockets一样支持Docker自带协议来实现该需求。
共享主机环境通常基于某个公共网关建立,网关可用于管理大量流入和流出的流量,包括FTP和SSH。环境中最大的部分(与冰山没有什么不同)会“隐藏”在这些网关后面的私有网络中。
因此,我的问题是有没有一种方式可以使得Docker拥有类似行为的网关,包含多租户支持和你所期望的所有功能?
事实证明,有。
Docker二进制文件扮演着3种角色:
- Docker 命令行 -> 使得Docker易用且非常简单
- Docker Daemon -> 在幕后默默做着苦差事
- Docker init -> 做幕后的早期容器设置
命令行和Docker Daemon主要基于HTTP协议来通信。我说“主要”是因为有几个API会“拦截”连接,尤其是container/attach
命令,它又称为“forward my container’s console”。
网上常见的文章都推荐设置一个Nginx反向代理,并添加基本的身份认证,以保证安全。
不幸的是,这种方法有两个缺点:
- 现有的Docker客户端无法与HTTP基础身份认证通信
- 当Docker拦截连接的时候,现存的Nginx会完全丢失连接
关于认证的问题,我推荐使用Docker的TLS认证,因为它们支持开箱即用。同时,使用Lua magic(译者注:Lua里的Magic字符),我们可以使用它们作为“公钥”来达到适当的平衡。
那我们应该怎么处理第二个问题(Nginx丢失连接)呢?
如果确认拦截方式,那事情就变得简单多了:HTTP可以看成是“半双工”的协议,也就是说,在同一时刻流量只能单向流动:客户端向服务器发送请求(单向),然后服务器响应请求(单向)。当执行docker attach
时,Docker可以使用原始的“全双工”模式的TCP连接,任何一端都可以在任何时候发起请求。这就是为什么反向代理会丢失:因为HTTP协议需要请求之后得到确认回复(这段作者讲的有些模糊,有理解的读者可以在评论中和我们交流)。
有趣的是,这里有另外一个主流的协议可以做到这个。事实证明,这个标准协议是如此流行以至于几年前已经被Nginx所接受,我称它为 WebSocket
。
因此,本质上,这个想法是告诉Nginx 怎样像处理Websocket一样处理Docker的自带协议。这是补丁:
--- a/src/http/ngx_http_upstream.c Tue Nov 04 19:56:23 2014 +0900 +++ b/src/http/ngx_http_upstream.c Sat Nov 15 16:21:58 2014 +0100 @@ -89,6 +89,8 @@ ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_process_content_length(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t ngx_http_upstream_process_content_type(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_process_last_modified(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, @@ -175,7 +177,7 @@ ngx_http_upstream_copy_header_line, 0, 0 }, { ngx_string("Content-Type"), - ngx_http_upstream_process_header_line, + ngx_http_upstream_process_content_type, offsetof(ngx_http_upstream_headers_in_t, content_type), ngx_http_upstream_copy_content_type, 0, 1 }, @@ -2716,6 +2718,7 @@ u->write_event_handler = ngx_http_upstream_upgraded_write_upstream; r->read_event_handler = ngx_http_upstream_upgraded_read_downstream; r->write_event_handler = ngx_http_upstream_upgraded_write_downstream; + u->headers_in.chunked = 0; if (clcf->tcp_nodelay) { tcp_nodelay = 1; @@ -3849,6 +3852,25 @@ static ngx_int_t +ngx_http_upstream_process_content_type(ngx_http_request_t *r, ngx_table_elt_t *h, + ngx_uint_t offset) +{ + ngx_int_t ret = ngx_http_upstream_process_header_line(r, h, offset); + if (ret != NGX_OK) { + return ret; + } + + // is docker header ? + if (ngx_strstrn(h->value.data, + "application/vnd.docker.raw-stream", 34 - 1) != NULL) { + r->upstream->upgrade = 1; + } + + return NGX_OK; +} + + +static ngx_int_t ngx_http_upstream_process_last_modified(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) {
唯一剩下的步骤就是配置反向代理了。顺便说一句,这是我的 nginx 测试配置文件 nginx.conf
:
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 9000; location / { proxy_buffering off; proxy_pass http://localhost:8080; } } }
你仅仅需要使用以下命令在8080端口运行Docker或者是把你的参数添加进/etc/default/docker
:
docker -d -H tcp://localhost:8080
我们就完成任务了!
最后的问题
虽然hacking了这个,但是我注意到所有的Nginx都需要为Websocket协议切换到合适的HTTP头:
Request
Connection: Upgrade Upgrade: websocket
Response
HTTP/1.1 101 Upgraded Connection: Upgrade Upgrade: websocket
因此我想另外一个方法是在Docker协议中注入合适的头。
原文链接: How to run Docker behind an Nginx reverse proxy(翻译:叶可强 审校:郭蕾)
原文发布时间为:2015-01-05
本文作者:叶可强
本文来自云栖社区合作伙伴DockerOne,了解相关信息可以关注DockerOne。
原文标题:在Nginx反向代理后面运行Docker
原文链接:https://developer.aliyun.com/article/224539