在 Nginx 反向代理后面運行 Docker
想讓 Docker 擁有公共網關,能支持多租戶,以及我們期望的安全認證等等功能?請看作者通過重新編譯 Nginx 讓其像支持 websockets 一樣支持 Docker 自帶協議來實現上述需求。
幾周以前,我想做些試驗看看 Docker 怎樣運行在云共享宿主環境。同時,Docker 1.4 發行版帶來了額外的安全和身份認證特性以及 Docker machine 自動化創建和運行一個遠程 Docker 實例。
共享主機群組通常圍繞某種流入流出流量的公共網關建立,而且包括管理流量,包括 FTP 和 SSH。群組最大的部分 - 與冰山沒有什么不同 - 被隱藏在這些網關后面的私有網絡中。
因此,我的問題是,大家可以想象下,有沒有一種方式可以使得 Docker 擁有類似行為的網關,包含多租戶支持和你所期望的所有功能?
事實證明,有。
Docker 二進制文件扮演著 3 種角色:
- Docker 命令行 -> 使得 Docker 好用以及非常簡單
- Docker Daemon -> 在幕后默默做著苦差事
- Docker init -> 做幕后的早期容器設置 </ul>
- 現存的 Docker 客戶端無法與 HTTP 基礎身份認證通信
- 當 Docker 攔截連接的時候,現存的 Nginx 會完全丟失連接 </ul>
命令行和 Docker Daemon 主要使用基于HTTP協議來相互通信。我說“主要”是因為有幾個 API 會“攔截”連接,尤其是container/attach,又稱“forward my container’s console。”
要知道,常見的設置,已經被網絡上的博客文章很好的覆蓋了,推薦設置一個 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="
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; }
}</pre>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