如何configurationDocker端口映射来使用Nginx作为上游代理?

更新II

现在是2015年7月16日,事情又改变了。 我从Jason Wilder发现了这个自动化的容器: https://github.com/jwilder/nginx-proxyhttps://github.com/jwilder/nginx-proxy ,只要需要docker run容器,它就解决了这个问题。 这是现在我用来解决这个问题的解决scheme。

更新

现在是2015年7月,事情发生了巨大的变化,关于networkingDocker容器。 现在有许多不同的解决scheme可以解决这个问题(以各种方式)。

您应该使用这篇文章来获得对docker --link的基本了解 – 连接服务发现的方法,它基本上是一样的,工作得很好,实际上比大多数其他解决scheme所需的花式跳舞less。 这是有限的,很难在任何给定的集群单独的主机上的容器networking,容器不能重新启动一旦联网,但提供了一个快速和相对简单的方法,在同一个主机上的networking容器。 弄清楚你可能用来解决这个问题的软件实际上是在做什么的。

此外,你可能还想看看Docker的新生network ,Hashicorp的consul ,Weaveworks weave ,Jeff Lindsay的progrium/consulgliderlabs/registrator以及Google的Kubernetes

还有使用etcdfleetflannel etcd产品。

如果你真的想参加一个聚会,你可以build立一个集群来运行MesosphereDeis或者Flynn

如果你刚接触networking(比如我),那么你应该拿出你的老花镜,在Wi-Hi-Fi上popup“用星星涂写天空 – 恩雅的精华” ,然后开一杯啤酒 – 这将会是有一段时间你才真正明白你在做什么。 提示:您正试图在您的Cluster Control Plane实现Service Discovery Layer 。 这是一个非常好的方式来度过一个星期六晚上。

这很有趣,但是我希望自己能花点时间好好教育自己,以便更好地了解networking。我最终从仁慈的数字海洋教程(Digital Ocean Tutorials Gods)网站上find了几篇文章: Introduction to Networking TerminologyUnderstanding ... Networking Introduction to Networking Terminology Understanding ... Networking 。 我build议在潜水前先阅读几遍。

玩的开心!

原始post

我似乎无法掌握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.comconfiguration文件看起来像这样:

 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 

这一切都挺好,但请求没有传递到其他容器/端口。 当我SSH入Nginx容器,并检查日志,我没有看到任何错误。

任何帮助?

@ T0xicCode的答案是正确的,但我想我会扩大细节,因为它实际上花了我大约20个小时终于得到一个工作的解决scheme实施。

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

链接你的容器

docker run你的容器时,通常通过在User Datainput一个shell脚本,你可以声明指向任何其他正在运行的容器的链接。 这意味着你需要按顺序启动你的容器,只有后面的容器可以链接到前者。 像这样:

 #!/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容器链接到APINginx链接到APIApp

这样做的结果是改变了env vars和驻留在APIApp容器中的/etc/hosts文件。 结果如下所示:

/ etc / hosts文件

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

 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

在你的Nginx容器中运行env将产生以下内容:

 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 

我截断了许多实际的变数,但以上是将stream量代入您的容器所需的关键值。

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

sudo docker exec -i -t Nginx bash

您可以看到,现在已经有/etc/hosts文件条目和env vars包含任何已链接的容器的本地IP地址。 据我所知,这是当你运行声明链接选项的容器时发生的一切。 但是你现在可以使用这个信息来在你的Nginx容器中configurationnginx

configurationNginx

这是一个棘手的地方,有几个选项。 您可以select将站点configuration为指向docker创build的/etc/hosts文件中的条目,也可以使用ENVvariables并在nginx.conf和其他任何conf文件上运行stringreplace(我使用sed )可能在你的/etc/nginx/sites-enabled文件夹中插入IP值。

选项A:使用ENV VarsconfigurationNginx

这是我去的选项,因为我不能让/etc/hosts文件选项工作。 我会尽快尝试Option B,并更新这个post的任何发现。

此选项和使用/etc/hosts文件选项之间的主要区别在于,如何编写Dockerfile以使用shell脚本作为CMD参数, CMD参数又处理stringreplace以将IP值从ENV复制到您的conf文件S)。

下面是我最终的一组configuration文件:

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

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;非常重要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-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 

我将留给你做关于nginx.confapi.myapp.conf大部分内容的api.myapp.conf

神奇的事情发生在Nginx-Startup.sh ,我们使用sedAPP_IP占位符上进行stringreplace,我们已经写入了我们的api.myapp.confapp.myapp.conf文件的upstream模块。

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

GOTCHA在OSX上, sed处理选项的方式有所不同,特别是-i标志。 在Ubuntu上, -i标志将处理replace'in place'; 它会打开文件,更改文本,然后“保存”相同的文件。 在OSX上, -i标志需要文件扩展名,您希望生成的文件具有。 如果您使用的是没有扩展名的文件,则必须input“'作为-i标志的值。

GOTCHA要在正则expression式中使用ENVvariables来查找要replace的string,需要将variables包含在双引号内。 所以,正确的,虽然看起来很诡异,但语法如上。

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

 upstream api_upstream{ server 172.0.0.2:3000; } 

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

你现在应该有适当的路由。

GOTCHA运行初始实例启动后,无法重新启动/重新运行任何容器。 Docker在启动时为每个容器提供一个新的IP,似乎不会重复使用之前使用过的任何IP。 所以api.myapp.com第一次得到172.0.0.2,下次得到172.0.0.4。 但是Nginx已经将第一个IP设置到它的conf文件中,或者在/etc/hosts文件中,所以它将不能确定api.myapp.com的新IP。 解决这个问题的办法很可能是使用etcd及其etcd服务,在我有限的理解下, etcd服务对于注册在同一个CoreOS集群中的所有机器来说就像一个共享的ENV 。 这是我将要搭build的下一个玩具。

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

应该是这样做的更快,更简单的方法,但我不能得到它的工作。 表面上你只需api.myapp.conf /etc/hosts项的值input到你的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从所有AZ中采购都有一些辅助问题,所以当我尝试这个路由时可能会遇到问题。 相反,我不得不学习如何在Linux中处理replacestring,所以这很有趣。 我会稍微试一试,看看它是如何发展的。

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

AJB的“选项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;"] 

我的nginxconfiguration(又名: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。

我试着使用stream行的Jason Wilder反向代理,它代码神奇地适用于每个人,并且知道它不适用于每个人(即:我)。 而且我对NGINX是全新的,并不喜欢我不了解我正在尝试使用的技术。

想要增加我的2美分,因为上面关于linking容器的讨论现在是过时的,因为它是一个不推荐的function。 所以这里有一个解释如何使用networks来做到这一点。 这个答案是使用Docker Compose和nginxconfiguration将nginx设置为静态分页网站的反向代理的完整示例。

TL; DR;

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

定义networking

首先,我们需要一个所有后端服务可以与之通话的networking。 我打电话给我的web但它可以是任何你想要的。

 docker network create web 

build立应用程序

我们只是做一个简单的网站应用程序。 该网站是一个简单的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.这对于避免端口冲突是方便的。

运行应用程序

用这个网站启动

 docker-compose up -d 

一些有趣的检查你的容器的DNS映射:

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

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

build立代理

Nginx反向代理:

Dockerfile

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

我们重置所有的虚拟主机configuration,因为我们要定制它。

泊坞窗,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 

假设没有问题,那么你有两个运行的容器可以用他们的名字相互交谈。 我们来testing一下。

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

设置虚拟主机

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

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

  server { listen 80; listen [::]:80; server_name my.domain.com; location / { proxy_pass http://sample-site; } } 

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

最后但并非最不重要的,告诉nginx重新加载它的configuration。

 docker exec nginx-proxy service nginx reload 

这些顺序的步骤是我痛苦的502 Bad Gateway错误,并且第一次学习nginx,因为我的大部分经验都是使用Apache的,所以我要经历几个小时的冲击。

这个答案是演示如何杀死由于容器不能相互通信而导致的502错误网关错误。

我希望这个答案可以节省几个小时的痛苦,因为让容器相互交谈真的很难找出来,尽pipe这是我期望的一个明显的用例。 但是再一次,我愚蠢。 请让我知道如何改善这种方法。

刚刚find一篇来自Anand Mani Sankar的文章 ,展示了一个使用docker composer的nginx上游代理的简单方法。

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