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

AJB*_*AJB 81 nginx docker

更新II

它现在是2015年7月16日,事情再次发生了变化.我从Jason Wilder那里发现了这个自动化容器: https://github.com/jwilder/nginx-proxy它解决了这个问题,只要它需要docker run容器.这是我用来解决这个问题的解决方案.

更新

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

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

此外,你可能还需要检查出多克的新生network,Hashicorp的consul,Weaveworks weave,杰夫·林赛的progrium/consul&gliderlabs/registrator,和谷歌的Kubernetes.

另外还有CoreOS利用的产品etcd,fleetflannel.

如果你真的想举办派对,你可以启动一个集群来运行Mesosphere,或者Deis,或者Flynn.

如果你是网络新手(比如我)那么你应该拿出你的老花镜,在Wi-Hi-Fi上弹出"涂上星星的天空 - 恩雅的最佳",然后破解啤酒 - 这将是在你真正理解你正在尝试做什么之前的一段时间.提示:你正试图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"]
Run Code Online (Sandbox Code Playgroud)



然后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;

    }

}
Run Code Online (Sandbox Code Playgroud)

然后另一个app.myapp.com也是.

然后我跑:

sudo docker run -p 80:80 -p 443:443 -d --name Nginx myusername/nginx
Run Code Online (Sandbox Code Playgroud)


这一切都很好,但请求没有传递到其他容器/端口.当我进入Nginx容器并检查日志时,我发现没有错误.

有帮助吗?

AJB*_*AJB 55

@ 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
Run Code Online (Sandbox Code Playgroud)

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

这样做的结果是对env变量和容器中的/etc/hosts文件进行了更改.结果如下: APIApp

/ etc/hosts文件

cat /etc/hostsNginx容器中运行将产生以下内容:

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
Run Code Online (Sandbox Code Playgroud)



ENV Vars

envNginx容器中运行将产生以下内容:

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
Run Code Online (Sandbox Code Playgroud)

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

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

sudo docker exec -i -t Nginx bash

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



配置Nginx

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



选项A:使用ENV Vars配置Nginx

这是我选择的选项,因为我无法使 /etc/hosts文件选项工作.我将尽快尝试选项B,并根据任何调查结果更新此帖子.

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

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

Dockerfile

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"]
Run Code Online (Sandbox Code Playgroud)

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;


}
Run Code Online (Sandbox Code Playgroud)

注意: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;
    }

}
Run Code Online (Sandbox Code Playgroud)

Nginx-Startup.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
Run Code Online (Sandbox Code Playgroud)

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

魔术发生在Nginx-Startup.sh这里我们用sed做的字符串替换APP_IP,我们已经写进占位upstream我们的块api.myapp.confapp.myapp.conf文件.

这个ask.ubuntu.com问题很好地解释了: 使用命令查找和替换文件中的文本

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

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

因此,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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

GOTCHA 一旦运行初始实例启动,就无法重新启动/重新运行任何容器.码头工人一经推出提供了一个新的IP每个容器似乎并没有重新使用任何其之前使用.因此api.myapp.com第一次获得172.0.0.2,但下次获得172.0.0.4.但是,Nginx将已经设置了第一个IP到其conf文件,或者在它的/etc/hosts文件,所以它不能够确定新的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;
}
Run Code Online (Sandbox Code Playgroud)

考虑到我的/etc/hosts文件中有一个条目是这样的:172.0.0.2 API我认为它只会拉入值,但它似乎不是.

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

  • 另一个使用链接的问题是,如果重新启动API容器,它很可能会获得一个新的ip.这不会反映在将继续使用旧ip的nginx容器/ etc/hosts文件中,因此也必须重新启动. (2认同)

gdb*_*dbj 11

我尝试使用流行的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-在丑/

定义网络

首先,我们需要一个网络,您的所有后端服务都可以在其上进行通信.我打电话给我,web但它可以是你想要的任何东西.

docker network create web
Run Code Online (Sandbox Code Playgroud)

构建应用程序

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

DockerFile:

FROM nginx
COPY default.conf /etc/nginx/conf.d/default.conf
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

泊坞窗,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
Run Code Online (Sandbox Code Playgroud)

请注意,我们不再需要端口映射.我们简单地公开端口80.这对于避免端口冲突很方便.

运行应用程序

点燃此网站

docker-compose up -d
Run Code Online (Sandbox Code Playgroud)

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

docker exec -it sample-site bash
ping sample-site
Run Code Online (Sandbox Code Playgroud)

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

构建代理

Nginx反向代理:

Dockerfile

FROM nginx

RUN rm /etc/nginx/conf.d/*
Run Code Online (Sandbox Code Playgroud)

我们重置所有虚拟主机配置,因为我们将自定义它.

泊坞窗,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
Run Code Online (Sandbox Code Playgroud)

运行代理

使用我们的信任启动代理

docker-compose up -d
Run Code Online (Sandbox Code Playgroud)

假设没有问题,那么你有两个运行的容器可以使用它们的名字相互通信.我们来试试吧.

docker exec -it nginx-proxy bash
ping sample-site
ping nginx-proxy
Run Code Online (Sandbox Code Playgroud)

设置虚拟主机

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

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

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

    server_name my.domain.com;

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

  }
Run Code Online (Sandbox Code Playgroud)

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

最后但同样重要的是,告诉nginx重新加载它的配置.

docker exec nginx-proxy service nginx reload
Run Code Online (Sandbox Code Playgroud)

当我遇到痛苦的502 Bad Gateway错误并且第一次学习nginx时,这些步骤的顺序是剧烈头痛的高潮,因为我的大多数经验都是在Apache上.

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

我希望这个答案可以帮助那些人节省数小时的痛苦,因为由于某种原因,让容器互相交谈真的很难弄清楚,尽管它是我期望的一个明显的用例.但话又说回来,我傻了.请告诉我如何改进这种方法.


T0x*_*ode 9

使用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;"]
Run Code Online (Sandbox Code Playgroud)

我的nginx配置(又名:conf/mysite.com):

server {
    listen 80 default;
    server_name mysite.com;

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

upstream website {
    server website:3000;
}
Run Code Online (Sandbox Code Playgroud)

最后,我如何启动我的容器:

$ docker run -dP --name website website
$ docker run -dP --name nginx --link website:website nginx
Run Code Online (Sandbox Code Playgroud)

这让我起步并运行所以我的nginx将上游指向第二个Docker容器,它暴露了端口3000.

  • 仅供参考我正在使用最新的官方nginx图像(1.9.2),它似乎对我有用.所以也许他们解决了这个问题. (2认同)

Dio*_*lor 6

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

因此,如果您想将来自nginx侦听的所有流量重定向80到另一个暴露的容器8080,则最小配置可以是:

nginx.conf:

server {
    listen 80;

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

}
Run Code Online (Sandbox Code Playgroud)

泊坞窗,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"
Run Code Online (Sandbox Code Playgroud)

Docker文档