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

时间:2015-01-13 00:03:58

标签: nginx docker

更新II

  

现在是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/consul & gliderlabs/registrator和Google的Kubernetes }。

     

还有CoreOS个产品使用etcdfleetflannel

     

如果您真的想举办派对,可以启动群集来运行MesosphereDeisFlynn

     

如果你是网络新手(比如我)那么你应该拿出你的老花镜,在Wi-Hi-Fi上弹出"Paint The Sky With Stars — The Best of 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容器并检查日志时,我发现没有错误。

任何帮助?

6 个答案:

答案 0 :(得分:55)

@T0xicCode's answer是正确的,但我想我会扩展细节,因为它实际上花了我大约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相关联,NginxAPIApp相关联。

这样做的结果是env变量和/etc/hostsAPI容器中的App文件的更改。结果如下:

<强>的/ etc /主机

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

我已截断了许多实际变量,但以上是将流量代理到容器所需的关键值。

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

sudo docker exec -i -t Nginx bash

您可以看到,您现在拥有/etc/hosts个文件条目和env个vars,其中包含已链接的任何容器的本地IP地址。据我所知,当您运行声明了链接选项的容器时,就会发生这种情况。但您现在可以使用此信息在nginx容器中配置Nginx



配置Nginx

这是一个有点棘手的地方,还有几个选择。您可以选择将站点配置为指向/etc/hosts创建的docker文件中的条目,或者您可以使用ENV变量并运行字符串替换(我使用{{1您sed以及nginx.conf文件夹中可能插入IP值的任何其他配置文件。



选项A:使用ENV Vars配置Nginx

  

这是我选择的选项,因为我无法获得   工作/etc/nginx/sites-enabled个文件选项。我很快就会尝试选项B.   并根据任何调查结果更新此帖子。

此选项与使用/etc/hosts文件选项之间的主要区别在于您如何编写/etc/hosts以使用shell脚本作为Dockerfile参数,后者又处理字符串替换将IP值从CMD复制到您的配置文件。

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

<强> Dockerfile

ENV

<强> nginx.conf

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"]
  

注意:在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;非常重要,以确保您的容器在启动后不会立即退出。

<强> api.myapp.conf

nginx.conf

<强> Nginx-Startup.sh

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;
    }

}

我会让您完成关于#!/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.conf的大部分内容的家庭作业。

魔术发生在api.myapp.conf,我们使用Nginx-Startup.sh在我们已写入sed APP_IP块的upstream占位符上进行字符串替换{1}}和api.myapp.conf个文件。

这个ask.ubuntu.com问题解释得非常好: Find and replace text within a file using commands

  

<强> GOTCHA   在OSX上,app.myapp.conf以不同方式处理选项,具体是sed标志。   在Ubuntu上,-i标志将处理替换&#39;到位&#39 ;;它   将打开文件,更改文本,然后“保存”。相同   文件。   在OSX上,-i标志需要您希望生成的文件的文件扩展名。如果您正在处理没有扩展名的文件,则必须输入“&#39;&#39;作为-i标志的值。

     

<强> GOTCHA   要在-i用于查找要替换的字符串的正则表达式中使用ENV变量,需要将var包装在双引号内。因此,正确的,尽管看起来很难看的语法如上所述。

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

upstream

您看到的IP地址可能有所不同,但我注意到它通常是upstream api_upstream{ server 172.0.0.2:3000; }

现在你应该适当地路由一切。

  

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



选项B:使用CoreOS文件条目

这个应该是更快,更简单的方法,但我无法让它工作。表面上,您只需将/etc/hosts条目的值输入到/etc/hostsapi.myapp.conf文件中,但我无法使用此方法。

  

更新:   有关如何使此方法有效的说明,请参阅@Wes Tod's answer

这是我在app.myapp.conf中所做的尝试:

api.myapp.conf

考虑到upstream api_upstream{ server API:3000; } 文件中的条目是这样的:/etc/hosts我认为它只会提取值,但它似乎不是。< / p>

我在所有AZ的172.0.0.2 API采购时也遇到了一些辅助问题,所以当我尝试这条路线时可能会出现这个问题。相反,我必须学习如何在Linux中处理替换字符串,所以这很有趣。我会在一段时间内尝试一下,看看它是怎么回事。

答案 1 :(得分:9)

使用docker links,您可以将上游容器链接到nginx容器。添加的功能是docker管理主机文件,这意味着您可以使用名称而不是潜在的随机ip来引用链接容器。

答案 2 :(得分:9)

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

想要加上我的2美分,因为上面关于linking个容器的讨论现在已经过时了,因为它是一个不推荐使用的功能。以下是使用networks解释如何执行此操作的说明。这个答案是使用Docker Compose和nginx配置将nginx设置为静态分页网站的反向代理的完整示例。

TL; DR;

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

定义网络

首先,我们需要一个网络,您的所有后端服务都可以在其上进行通信。我打电话给我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;
    }
}

搬运工-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.这对于避免端口冲突很方便。

运行App

使用

启动此网站
docker-compose up -d

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

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

这个ping应该在你的容器内工作。

构建代理

Nginx反向代理:

Dockerfile

FROM nginx

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

我们重置了所有虚拟主机配置,因为我们要自定义它。

搬运工-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错误。

我希望这个答案可以让那些人在那里度过几个小时的痛苦,因为由于某种原因,让容器互相交谈真的很难理解,尽管它是我期望的一个明显的用例。但话又说回来,我傻了。请告诉我如何改进这种方法。

答案 3 :(得分:7)

AJB's "Option B"可以通过使用基础Ubuntu映像并自行设置nginx来实现。 (当我使用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配置(又名: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.

答案 4 :(得分:6)

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

因此,如果您想将来自nginx收听resizeMode: stretch的所有流量重定向到另一个展示80的容器,则最低配置可以是:

nginx.conf:

8080

搬运工-compose.yml

server {
    listen 80;

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

}

Docker docs

答案 5 :(得分:2)

刚从Anand Mani Sankar找到一个article,它展示了一种使用nginx上游代理与docker composer的简单方法。

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