在 Nginx 反向代理后面运行 Docker

jopen 10年前

想让 Docker 拥有公共网关,能支持多租户,以及我们期望的安全认证等等功能?请看作者通过重新编译 Nginx 让其像支持 websockets 一样支持 Docker 自带协议来实现上述需求。

几周以前,我想做些试验看看 Docker 怎样运行在云共享宿主环境。同时,Docker 1.4 发行版带来了额外的安全和身份认证特性以及 Docker machine 自动化创建和运行一个远程 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 魔法,我们可以使用它们作为“公钥”来到达适当的平衡。这本身将是一个可以开专门帖子的好主题。

我们怎么处理第二种情况呢,换句话说,Nginx 丢失连接?

一旦“拦截”后面的机制被很好的识别,事情很快变得直接了当:一个通常的 HTTP 连接可以被看成是“半双工”网络。一端发起请求并且当它完成的时候,另外一端也可以发起请求等等,使用一个非常著名的协议。当做一个 docker attach,Docker 使用原始的“双工”模式的 TCP 连接,任何一端可以在它有任何东西想说的时候说。这就是反向代理为什么丢失:它们期望 - 并且回复 - 在 HTTP 协议方面会许多会被考虑到。

有趣的是,这里有另外一个主流的协议可以做到这个。事实证明,这个标准协议是如此流行以至于几年前已经被继承进了 Nginx。我称它为WebSocket。

因此,本质上,这个想法是告诉 Nginx 怎样像处理 websockets 一样处理 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)  {  1    The only remaining step is then to configure the reverse proxy, as usual. This should be easy <img src="https://blog.jtlebi.fr/wp-includes/images/smilies/icon_wink.gif" alt=";)" class="wp-smiley">     Just for the record, here is my test <code>nginx.conf</code>:    1  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 都需要为 websockets 协议切换到合适的 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(翻译:叶可强)
来自:http://dockerone.com/article/127