如何配置Docker端口映射以将Nginx用作上游代理?


86

更新二

现在是2015年7月16日,情况又发生了变化。我从Jason Wilder找到了这个自动魔术容器: https://github.com/jwilder/nginx-proxy它可以解决这个问题,只要将它带到docker run容器中即可。现在这是我用来解决此问题的解决方案。

更新资料

现在是2015年7月,关于联网Docker容器的情况已经发生了巨大变化。现在有许多不同的产品(以多种方式)解决了这个问题。

您应该使用这篇文章对docker --link服务发现的方法有一个基本的了解,它几乎是最基本的,运作良好,并且实际上比大多数其他解决方案需要更少的花式跳舞。它的局限性在于,很难在任何给定群集中的单独主机上联网容器,并且一旦联网就无法重新启动容器,但是确实提供了一种快速且相对容易的方式来在同一主机上联网容器。这是了解您可能要使用的软件实际上是在做什么的一种好方法。

此外,您可能还需要查看Docker的新生network,Hashicorp的consul,Weaveworks weave,Jeff Lindsay的progrium/consulgliderlabs/registrator和Google的Kubernetes

另外还有CoreOS利用的产品etcdfleetflannel

如果您真的想开个聚会,可以启动一个集群来运行MesosphereDeisFlynn

如果您是网络新手(像我一样),那么您应该拿起老花镜,在Wi-Hi-Fi上弹出“用星星画天空-最好的Enya”,然后喝点啤酒-这将是过一会儿,您才真正了解要执行的操作。提示:你试图实现一个Service Discovery Layer在你的Cluster Control Plane。这是度过一个星期六晚上的好方法。

这很有趣,但是我希望我能花点时间在开始潜水之前花一些时间来更好地学习网络方面的知识。我最终从仁慈的《数字海洋教程》中找到了几篇文章:Introduction to Networking TerminologyUnderstanding ... Networking。我建议在潜水之前先阅读几次。

玩得开心!



原始帖子

我似乎无法掌握Docker容器的端口映射。具体来说,如何将请求从Nginx传递到另一个容器,侦听同一服务器上的另一个端口。

我有一个Nginx容器的Dockerfile像这样:

FROM ubuntu:14.04
MAINTAINER Me <me@myapp.com>

RUN apt-get update && apt-get install -y htop git nginx

ADD sites-enabled/api.myapp.com /etc/nginx/sites-enabled/api.myapp.com
ADD sites-enabled/app.myapp.com /etc/nginx/sites-enabled/app.myapp.com
ADD nginx.conf /etc/nginx/nginx.conf

RUN echo "daemon off;" >> /etc/nginx/nginx.conf

EXPOSE 80 443

CMD ["service", "nginx", "start"]



然后api.myapp.com配置文件如下所示:

upstream api_upstream{

    server 0.0.0.0:3333;

}


server {

    listen 80;
    server_name api.myapp.com;
    return 301 https://api.myapp.com/$request_uri;

}


server {

    listen 443;
    server_name api.mypp.com;

    location / {

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_pass http://api_upstream;

    }

}

然后还有一个app.myapp.com

然后我运行:

sudo docker run -p 80:80 -p 443:443 -d --name Nginx myusername/nginx


一切都很好,但是请求没有传递到其他容器/端口。当我将其放入Nginx容器并检查日志时,没有看到错误。

有什么帮助吗?


1
请在答案中添加答案材料,而不是问题的正文。
jscs '16

Answers:


56

@ T0xicCode的答案是正确的,但是我认为我会扩展细节,因为实际上花了大约20个小时才能最终实现可行的解决方案。

如果您希望在自己的容器中运行Nginx并将其用作反向代理以在同一服务器实例上负载均衡多个应用程序,那么您需要遵循的步骤如下:

链接您的容器

docker run您使用容器时,通常可以通过在中输入shell脚本User Data来声明指向任何其他正在运行的容器的链接。这意味着您需要按顺序启动容器,只有后者才能链接到前者。像这样:

#!/bin/bash
sudo docker run -p 3000:3000 --name API mydockerhub/api
sudo docker run -p 3001:3001 --link API:API --name App mydockerhub/app
sudo docker run -p 80:80 -p 443:443 --link API:API --link App:App --name Nginx mydockerhub/nginx

因此,在此示例中,该API容器未链接到任何其他App容器,但是该 容器已链接到API并且Nginx链接到APIApp

其结果是更改了envvars和/etc/hosts位于APIApp容器内的文件。结果看起来像这样:

/ etc / hosts

cat /etc/hosts在您的Nginx容器中运行将产生以下结果:

172.17.0.5  0fd9a40ab5ec
127.0.0.1   localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.3  App
172.17.0.2  API



ENV Vars

env在您的Nginx容器中运行将产生以下结果:

API_PORT=tcp://172.17.0.2:3000
API_PORT_3000_TCP_PROTO=tcp
API_PORT_3000_TCP_PORT=3000
API_PORT_3000_TCP_ADDR=172.17.0.2

APP_PORT=tcp://172.17.0.3:3001
APP_PORT_3001_TCP_PROTO=tcp
APP_PORT_3001_TCP_PORT=3001
APP_PORT_3001_TCP_ADDR=172.17.0.3

我已经截断了许多实际的var,但是以上是您需要将流量代理到容器的键值。

要获取一个shell在运行中的容器中运行上述命令,请使用以下命令:

sudo docker exec -i -t Nginx bash

您可以看到您现在同时具有/etc/hosts文件条目和envvar,它们包含所链接的任何容器的本地IP地址。据我所知,这是在运行带有声明的链接选项的容器时发生的所有情况。但是您现在可以使用此信息nginxNginx容器中进行配置。



配置Nginx

这是个棘手的问题,这里有两个选择。您可以选择将站点配置为指向/etc/hostsdocker创建文件中的条目,也可以使用ENVvarssednginx.conf文件/etc/nginx/sites-enabled夹中的conf文件和其他conf文件运行字符串替换(我用过)以插入IP。价值观。



选项A:使用ENV Vars配置Nginx

这是我选择的选项,因为我无法使用 /etc/hostsfile选项。我将尽快尝试选项B,并用任何发现更新本文。

此选项与使用/etc/hostsfile选项之间的主要区别在于,如何编写Dockerfile脚本以使用shell脚本作为CMD参数,而shell脚本依次处理字符串替换以将IP值复制ENV到conf文件中。

这是我最终得到的一组配置文件:

Docker文件

FROM ubuntu:14.04
MAINTAINER Your Name <you@myapp.com>

RUN apt-get update && apt-get install -y nano htop git nginx

ADD nginx.conf /etc/nginx/nginx.conf
ADD api.myapp.conf /etc/nginx/sites-enabled/api.myapp.conf
ADD app.myapp.conf /etc/nginx/sites-enabled/app.myapp.conf
ADD Nginx-Startup.sh /etc/nginx/Nginx-Startup.sh

EXPOSE 80 443

CMD ["/bin/bash","/etc/nginx/Nginx-Startup.sh"]

nginx.conf

daemon off;
user www-data;
pid /var/run/nginx.pid;
worker_processes 1;


events {
    worker_connections 1024;
}


http {

    # Basic Settings

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 33;
    types_hash_max_size 2048;

    server_tokens off;
    server_names_hash_bucket_size 64;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;


    # Logging Settings
    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;


    # Gzip Settings

gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 3;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/xml text/css application/x-javascript application/json;
    gzip_disable "MSIE [1-6]\.(?!.*SV1)";

    # Virtual Host Configs  
    include /etc/nginx/sites-enabled/*;

    # Error Page Config
    #error_page 403 404 500 502 /srv/Splash;


}

注意:重要的是要包含daemon off;nginx.conf文件中,以确保容器在启动后不会立即退出。

api.myapp.conf

upstream api_upstream{
    server APP_IP:3000;
}

server {
    listen 80;
    server_name api.myapp.com;
    return 301 https://api.myapp.com/$request_uri;
}

server {
    listen 443;
    server_name api.myapp.com;

    location / {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_pass http://api_upstream;
    }

}

Nginx-启动.sh

#!/bin/bash
sed -i 's/APP_IP/'"$API_PORT_3000_TCP_ADDR"'/g' /etc/nginx/sites-enabled/api.myapp.com
sed -i 's/APP_IP/'"$APP_PORT_3001_TCP_ADDR"'/g' /etc/nginx/sites-enabled/app.myapp.com

service nginx start

我把它留给你做你的最多的内容作业nginx.confapi.myapp.conf

魔术发生在Nginx-Startup.sh我们用来sedAPP_IP已写入到文件和文件upstream块中的占位符上进行字符串替换的地方。api.myapp.confapp.myapp.conf

这个ask.ubuntu.com问题很好地说明了这一点: 使用命令在文件中查找和替换文本

GOTCHA 在OSX上,以sed不同方式处理选项,特别是-i标志。在Ubuntu上,该-i标志将处理“就地”替换;它将打开文件,更改文本,然后“保存”相同的文件。在OSX上,该-i标志需要您想要结果文件具有的文件扩展名。如果使用的文件没有扩展名,则必须输入''作为-i标志值。

GOTCHA 要在sed用于查找要替换的字符串的正则表达式中使用ENV变量,需要将变量包装在双引号中。因此,正确的,尽管看上去有些古怪的语法如上所述。

因此docker启动了我们的容器并触发了Nginx-Startup.sh脚本运行,该脚本用于sed将值更改为我们在命令中提供APP_IP的相应ENV变量sed。现在我们的/etc/nginx/sites-enabled目录中有conf文件,这些文件具有ENVdocker在启动容器时设置的vars的IP地址。在api.myapp.conf文件中,您将看到upstream块已更改为:

upstream api_upstream{
    server 172.0.0.2:3000;
}

您看到的IP地址可能有所不同,但是我注意到它通常是172.0.0.x

现在,您应该正确路由所有路由。

GOTCHA 一旦运行了初始实例启动,就无法重新启动/重新运行任何容器。Docker在启动时为每个容器提供了一个新IP,并且似乎没有重复使用以前使用的任何容器。因此api.myapp.com,第一次将获得172.0.0.2,而下次将获得172.0.0.4。但是Nginx将已经在其conf文件或该/etc/hosts文件中设置了第一个IP ,因此它将无法确定的新IP api.myapp.com。据我有限的理解,此解决方案很可能会使用CoreOSetcd服务,并且它的服务就像ENV注册到同一CoreOS群集的所有计算机的共享一样。这是我要设置的下一个玩具。



选项B:使用/etc/hosts文件条目

应该是更快,更轻松的方法,但我无法使其正常工作。从表面上看,您只是将/etc/hosts条目的值输入到您的api.myapp.confapp.myapp.conf文件中,但是我无法使用此方法。

更新: 有关如何使此方法起作用的说明,请参见@Wes Tod的答案

这是我所做的尝试api.myapp.conf

upstream api_upstream{
    server API:3000;
}

考虑到我的/etc/hosts文件中有一个这样的条目:172.0.0.2 API我认为它只会提取值,但似乎没有。

Elastic Load Balancer从所有可用区的采购也遇到了一些辅助问题,因此当我尝试此路线时可能就是问题所在。相反,我不得不学习如何在Linux中处理替换字符串,所以这很有趣。我将在一段时间内尝试一下,看看效果如何。


2
使用链接的另一个陷阱是,如果重新启动API容器,则很可能会获得一个新的IP。这在nginx容器/ etc / hosts文件中没有反映出来,该文件将继续使用旧ip,因此也必须重新启动。
judoole

13

我尝试使用流行的Jason Wilder反向代理,该代理在代码上神奇地适用于每个人,并了解到它不适用于每个人(即:我)。而且我是NGINX的新手,不喜欢我不理解我尝试使用的技术。

想要加上2美分,因为上面关于linking容器在一起的讨论现在已过时,因为它已被弃用。因此,这里是有关如何使用的说明networks。此答案是使用Docker Composenginx配置将nginx设置为静态分页网站的反向代理的完整示例。

TL; DR;

将需要互相通信的服务添加到预定义的网络中。对于Docker网络的分步讨论,我在这里学到了一些东西:https : //technologyconversations.com/2016/04/25/docker-networking-and-dns-the-good-the-bad-and-丑陋的/

定义网络

首先,我们需要一个可以与您的所有后端服务进行通话的网络。我打电话给我,web但可以随便你。

docker network create web

编译应用

我们将只做一个简单的网站应用程序。该网站是一个简单的index.html页面,由nginx容器提供。内容是文件夹下主机的已装入卷content

DockerFile:

FROM nginx
COPY default.conf /etc/nginx/conf.d/default.conf

default.conf

server {
    listen       80;
    server_name  localhost;

    location / {
        root   /var/www/html;
        index  index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

docker-compose.yml

version: "2"

networks:
  mynetwork:
    external:
      name: web

services:
  nginx:
    container_name: sample-site
    build: .
    expose:
      - "80"
    volumes:
      - "./content/:/var/www/html/"
    networks:
      default: {}
      mynetwork:
        aliases:
          - sample-site

请注意,我们在这里不再需要端口映射。我们简单地暴露端口80。这对于避免端口冲突很方便。

运行应用

用这个启动网站

docker-compose up -d

有关容器的dns映射的一些有趣检查:

docker exec -it sample-site bash
ping sample-site

此ping应该在您的容器内起作用。

建立代理

Nginx反向代理:

Docker文件

FROM nginx

RUN rm /etc/nginx/conf.d/*

我们将自定义所有虚拟主机配置,因此将其重置。

docker-compose.yml

version: "2"

networks:
  mynetwork:
    external:
      name: web


services:
  nginx:
    container_name: nginx-proxy
    build: .
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./conf.d/:/etc/nginx/conf.d/:ro
      - ./sites/:/var/www/
    networks:
      default: {}
      mynetwork:
        aliases:
          - nginx-proxy

运行代理

使用我们的信任启动代理

docker-compose up -d

假设没有问题,那么您有两个正在运行的容器,可以使用它们的名称相互通信。让我们测试一下。

docker exec -it nginx-proxy bash
ping sample-site
ping nginx-proxy

设置虚拟主机

最后一个细节是设置虚拟主机文件,以便代理可以根据您要设置的匹配项来定向流量:

用于我们的虚拟主机配置的sample-site.conf:

  server {
    listen 80;
    listen [::]:80;

    server_name my.domain.com;

    location / {
      proxy_pass http://sample-site;
    }

  }

根据代理的设置方式,您需要将此文件存储在本地conf.d文件夹下,该文件夹是通过文件中的volumes声明装入的docker-compose

最后但并非最不重要的一点是,告诉nginx重新加载它的配置。

docker exec nginx-proxy service nginx reload

这些步骤序列是我经历着痛苦的502 Bad Gateway错误以及我第一次学习nginx时头痛不已的数小时的顶峰,因为我的大部分经验都来自于Apache。

此答案是为了演示如何消除由于容器之间无法相互通信而导致的502 Bad Gateway错误。

我希望这个答案可以使人们免于数小时的痛苦,因为尽管出于某种原因,使容器相互交谈真的很困难,尽管我希望这是一个显而易见的用例。但是话又说回来,我愚蠢。并且请让我知道如何改进这种方法。


啊! OLE 502 Gateway Error,这些天臭名昭著的经典。感谢@gdbj花费大量时间来推进对话并提供如此详细的解决方案。
AJB

只是想说谢谢您抽出宝贵的时间,它为我节省了很多麻烦。谢谢。
单一实体

10

使用docker链接,您可以将上游容器链接到nginx容器。Docker的一个附加功能是管理主机文件,这意味着您将能够使用名称而不是可能的随机IP来引用链接的容器。


7

通过使用基本的Ubuntu映像并自行设置nginx,可以使AJB的“选项B”起作用。(当我使用来自Docker Hub的Nginx映像时,它不起作用。)

这是我使用的Docker文件:

FROM ubuntu
RUN apt-get update && apt-get install -y nginx
RUN ln -sf /dev/stdout /var/log/nginx/access.log
RUN ln -sf /dev/stderr /var/log/nginx/error.log
RUN rm -rf /etc/nginx/sites-enabled/default
EXPOSE 80 443
COPY conf/mysite.com /etc/nginx/sites-enabled/mysite.com
CMD ["nginx", "-g", "daemon off;"]

我的Nginx配置(aka:conf / mysite.com):

server {
    listen 80 default;
    server_name mysite.com;

    location / {
        proxy_pass http://website;
    }
}

upstream website {
    server website:3000;
}

最后,我如何启动容器:

$ docker run -dP --name website website
$ docker run -dP --name nginx --link website:website nginx

这让我启动并运行,因此我的nginx将上游指向第二个docker容器,该容器暴露了端口3000。


感谢您对Wes的协助!假期回来时,我会尝试一下。
AJB

我在官方图片上遇到了类似的问题。我发现最好是从ubuntu框开始,然后直接从docker文件中插入行。不,这应该是必要的,但很可惜....
韦斯·托德

1
这很棒,但是我不明白的是nginx配置是如何知道'website'的值的。Nginx魔术或docker魔术,还是其他?
凯尔·查达

1
该行upstream website {定义了nginx的网站价值。那就是您随后在中使用的内容proxy_pass。泊坞窗部分仅使用相同的名称来保持一致性,但与nginx设置无关。为了更清楚一点,代理通行证应为:upstream website { server localhost:3000; }
Wes Todd 2015年

2
仅供参考,我使用的是最新的官方nginx图像(1.9.2),它似乎对我有用。因此,也许他们解决了这个问题。
Pejvan 2015年

6

@gdbj的答案是一个很好的解释,也是最新的答案。但是,这是一种更简单的方法。

因此,如果要将所有流量从nginx侦听重定向80到另一个暴露的容器8080,则最低配置可以低至:

nginx.conf:

server {
    listen 80;

    location / {
        proxy_pass http://client:8080; # this one here
        proxy_redirect off;
    }

}

docker-compose.yml

version: "2"
services:
  entrypoint:
    image: some-image-with-nginx
    ports:
      - "80:80"
    links:
      - client  # will use this one here

  client:
    image: some-image-with-api
    ports:
      - "8080:8080"

Docker文档


起初我以为您会遇到端口冲突的问题。
gdbj

@gdbj我的问题是容器之间的url / ip解析。猜猜我们也一样。在您的情况下,您使用的网络也可以正常工作,在我的情况下,我只是链接容器
Diolor

太好了,这就是我所需要的!感谢您的回答。
Floran Gmehlin

2

刚刚发现Anand Mani Sankar的一篇文章展示了一种将nginx上游代理与docker composer结合使用的简单方法。

基本上,必须在docker-compose文件中配置实例链接和端口,并在nginx.conf上进行上游更新。


1
本文使用links不推荐使用的内容。立即使用网络:docs.docker.com/engine/userguide/networking
gdbj

在这种情况下,神奇的nginx conf替换也可以使用吗?
睡鼠
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.