docker通过 nginx-proxy 实现自动反向代理

前言

当使用docker时,如果有多个docker容器作为网站提供服务,nginx不论单独安装还是作为docker容器,配置起来都有些麻烦。而利用nginx-proxy可以通过简单的配置完成docker容器的自动反向代理。

正文

我们使用docker-compose来配置docker服务。

为了方便区分开发和正式环境,我们创建三个配置文件:

  • docker-compose.yml
  • docker-compose.dev.yml
  • docker-compose.prod.yml

docker-compose.yml是所有服务软件的基本配置:

version: '3'
services:
  nginx-proxy:
    image: jwilder/nginx-proxy:0.8.0
    container_name: nginx-proxy
    restart: always
    ports:
      - 80:80
      - 443:443
    volumes:
      - /docker/volumes/nginx/conf:/etc/nginx/conf.d  #其他配置文件目录挂载
      - /docker/volumes/nginx/vhost:/etc/nginx/vhost.d  #虚拟主机配置文件目录挂载
      - /docker/volumes/nginx/html:/usr/share/nginx/html  #静态资源根目录挂载
      - /docker/volumes/nginx/dhparam:/etc/nginx/dhparam
      - /docker/volumes/nginx/certs:/etc/nginx/certs:ro
      - /var/run/docker.sock:/tmp/docker.sock:ro
      #- /docker/volumes/nginx/log:/var/log/nginx #日志文件挂载
  halo:
    image: ruibaby/halo
    container_name: halo
    restart: always
    volumes:
      - ~/.halo:/root/.halo #项目资源挂载
      - /etc/localtime:/etc/localtime:ro
  gogs:
    image: gogs/gogs:0.12
    container_name: gogs
    restart: always
    volumes:
      - /docker/volumes/gogs/data:/data
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro
  jenkins:
    image: jenkins/jenkins:2.263.1-lts-centos7
    container_name: jenkins
    restart: always
    volumes:
      - /docker/volumes/jenkins/jenkins_home:/var/jenkins_home
      - /etc/localtime:/etc/localtime:ro
      - /etc/timezone:/etc/timezone:ro

docker-compose.dev.yml是用来配置服务软件的开发测试用的(本例中不使用,可以忽略):

version: '3'
services:
  halo:
    ports:
      - 8090:8090
  gogs:
    ports:
      - 3000:3000
  jenkins:
    ports:
      - 8888:8080
      - 50000:50000

docker-compose.prod.yml是正式环境用的,里面有nginx-proxy相关的配置

version: '3'
services:
  halo:
    environment:
      - VIRTUAL_HOST=xxx.com,www.xxx.com
      - VIRTUAL_PORT=8090
  gogs:
    environment:
      - VIRTUAL_HOST=git.xxx.com
      - VIRTUAL_PORT=3000
  jenkins:
    environment:
      - VIRTUAL_HOST=jks.xxx.com
      - VIRTUAL_PORT=8080

里面的xxx.com替换为正式的域名。这里面最关键的就是VIRTUAL_HOSTVIRTUAL_PORT这两个环境变量,VIRTUAL_HOST配置想要反向代理的域名,VIRTUAL_PORT是网站服务容器的内部端口。

环境变量的正确写法

这里有一个需要注意的地方,环境变量的正确写法有两种如下:

environment:
      - VIRTUAL_HOST=xxx.com
      - VIRTUAL_PORT=80
environment:
      VIRTUAL_HOST: xxx.com
      VIRTUAL_PORT: 80

其他写法nginx-proxy是识别不了的。

自动反向代理原理

nginx-proxy容器对外暴露 80 端口,并且监听 80 端口,允许 80 端口的流量流入。
通过设置挂载点:/var/run/docker.sock:/tmp/docker.sock,允许nginx-proxy容器访问宿主机器的 Docker socket,这也就意味着有新容器加入,或者新容器关闭时都会通知到nginx-proxy容器。

这样每一次添加容器,nginx-proxy 就会通过 socket 来接收到事件,自动创建对应的配置文件,然后重启 nginx 来生效。nginx-proxy 会寻找带有 VIRTUAL_HOST 环境变量的容器,然后依照配置进行。

启动容器

我们想要查看自动反向代理的效果,直接运行如下命令:

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

输出如下:

[root@iZuf6gt0uel6sln10ai38rZ docker]# docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Creating network "docker_default" with the default driver
Creating jenkins      ... done
Creating gogs         ... done
Creating nginx-proxy  ... done
Creating halo         ... done

查看主机的 /docker/volumes/nginx/conf 文件夹下面的default.conf文件,在文件末尾可以看到nginx-proxy自动生成如下内容:

# git.xxx.com
upstream git.xxx.com {
				## Can be connected with "docker_default" network
			# gogs
			server 192.168.128.5:3000;
}
server {
	server_name git.xxx.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	location / {
		proxy_pass http://git.xxx.com;
	}
}
# xxx.com
upstream xxx.com {
				## Can be connected with "docker_default" network
			# halo
			server 192.168.128.8:8090;
}
server {
	server_name xxx.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	location / {
		proxy_pass http://xxx.com;
	}
}
# jks.xxx.com
upstream jks.xxx.com {
				## Can be connected with "docker_default" network
			# jenkins
			server 192.168.128.4:8080;
}
server {
	server_name jks.xxx.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	location / {
		proxy_pass http://jks.xxx.com;
	}
}
# www.xxx.com
upstream www.xxx.com {
				## Can be connected with "docker_default" network
			# halo
			server 192.168.128.8:8090;
}
server {
	server_name www.xxx.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	location / {
		proxy_pass http://www.xxx.com;
	}
}

如果你已经解析好上述服务的域名的话,你已经可以正常访问xxx.comwww.xxx.comgit.xxx.comjks.xxx.com这几个域名了。

HTTPS自动配置

nginx-proxy有一个对应的项目letsencrypt-nginx-proxy-companion ,他可以自动创建和续签 Let’s Encrypt 的证书。

不过这里使用的是阿里云的免费DV证书:
docker通过 nginx-proxy 实现自动反向代理_第1张图片
点击“购买证书”,选择免费证书:
docker通过 nginx-proxy 实现自动反向代理_第2张图片
nginx-proxy虽然是基于nginx,可是只能自动识别.crt和.key组合的证书,所以我们下载Apache证书:
docker通过 nginx-proxy 实现自动反向代理_第3张图片
解压下载下来的4927566_xxx.com_apache.zip文件,重命名里面的4927566_xxx.com_public.crt和4927566_xxx.com.key文件为xxx.com.crt和xxx.com.key,然后放入主机上面的 /docker/volumes/nginx/certs文件夹(证书文件夹 /etc/nginx/certs的挂载点)。

如果你的xxx.com.crt和xxx.com.key证书是适配所有三级域名的通配符证书的话,只放入这一对证书就行。这里用的是免费的单域名证书,所有的三级域名的证书都必须放入上面的证书文件夹下面。

重启nginx-proxy容器,运行如下命令:

docker-compose restart

查看主机的 /docker/volumes/nginx/conf 文件夹下面的default.conf文件,在文件末尾可以看到nginx-proxy自动生成如下内容:

# git.xxx.com
upstream git.xxx.com {
				## Can be connected with "docker_default" network
			# gogs
			server 192.168.128.3:3000;
}
server {
	server_name git.xxx.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	# Do not HTTPS redirect Let'sEncrypt ACME challenge
	location /.well-known/acme-challenge/ {
		auth_basic off;
		allow all;
		root /usr/share/nginx/html;
		try_files $uri =404;
		break;
	}
	location / {
		return 301 https://$host$request_uri;
	}
}
server {
	server_name git.xxx.com;
	listen 443 ssl http2 ;
	access_log /var/log/nginx/access.log vhost;
	ssl_session_timeout 5m;
	ssl_session_cache shared:SSL:50m;
	ssl_session_tickets off;
	ssl_certificate /etc/nginx/certs/git.xxx.com.crt;
	ssl_certificate_key /etc/nginx/certs/git.xxx.com.key;
	add_header Strict-Transport-Security "max-age=31536000" always;
	location / {
		proxy_pass http://git.xxx.com;
	}
}
# xxx.com
upstream xxx.com {
				## Can be connected with "docker_default" network
			# halo
			server 192.168.128.8:8090;
}
server {
	server_name xxx.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	# Do not HTTPS redirect Let'sEncrypt ACME challenge
	location /.well-known/acme-challenge/ {
		auth_basic off;
		allow all;
		root /usr/share/nginx/html;
		try_files $uri =404;
		break;
	}
	location / {
		return 301 https://$host$request_uri;
	}
}
server {
	server_name xxx.com;
	listen 443 ssl http2 ;
	access_log /var/log/nginx/access.log vhost;
	ssl_session_timeout 5m;
	ssl_session_cache shared:SSL:50m;
	ssl_session_tickets off;
	ssl_certificate /etc/nginx/certs/xxx.com.crt;
	ssl_certificate_key /etc/nginx/certs/xxx.com.key;
	add_header Strict-Transport-Security "max-age=31536000" always;
	location / {
		proxy_pass http://xxx.com;
	}
}
# jks.xxx.com
upstream jks.xxx.com {
				## Can be connected with "docker_default" network
			# jenkins
			server 192.168.128.4:8080;
}
server {
	server_name jks.xxx.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	# Do not HTTPS redirect Let'sEncrypt ACME challenge
	location /.well-known/acme-challenge/ {
		auth_basic off;
		allow all;
		root /usr/share/nginx/html;
		try_files $uri =404;
		break;
	}
	location / {
		return 301 https://$host$request_uri;
	}
}
server {
	server_name jks.xxx.com;
	listen 443 ssl http2 ;
	access_log /var/log/nginx/access.log vhost;
	ssl_session_timeout 5m;
	ssl_session_cache shared:SSL:50m;
	ssl_session_tickets off;
	ssl_certificate /etc/nginx/certs/jks.xxx.com.crt;
	ssl_certificate_key /etc/nginx/certs/jks.xxx.com.key;
	add_header Strict-Transport-Security "max-age=31536000" always;
	location / {
		proxy_pass http://jks.xxx.com;
	}
}
# www.xxx.com
upstream www.xxx.com {
				## Can be connected with "docker_default" network
			# halo
			server 192.168.128.8:8090;
}
server {
	server_name www.xxx.com;
	listen 80 ;
	access_log /var/log/nginx/access.log vhost;
	# Do not HTTPS redirect Let'sEncrypt ACME challenge
	location /.well-known/acme-challenge/ {
		auth_basic off;
		allow all;
		root /usr/share/nginx/html;
		try_files $uri =404;
		break;
	}
	location / {
		return 301 https://$host$request_uri;
	}
}
server {
	server_name www.xxx.com;
	listen 443 ssl http2 ;
	access_log /var/log/nginx/access.log vhost;
	ssl_session_timeout 5m;
	ssl_session_cache shared:SSL:50m;
	ssl_session_tickets off;
	ssl_certificate /etc/nginx/certs/xxx.com.crt;
	ssl_certificate_key /etc/nginx/certs/xxx.com.key;
	add_header Strict-Transport-Security "max-age=31536000" always;
	location / {
		proxy_pass http://www.xxx.com;
	}
}

可以看到 nginx-proxy 已经自动生成了SSL相关的配置。

域名解析正常的话,现在就可以通过https正常访问网站了。

HTTPS使用的注意点

我们从上面生成的SSL配置信息中可以看到,http的80端口默认跳转到https的443端口。

如果我们想要http和https都可以正常访问的话,可以添加环境变量:

      - HTTPS_METHOD=noredirect

如果我们某个域名不想通过https访问,可以添加环境变量:

      - HTTPS_METHOD=nohttps

添加上述环境变量之后,需要停止并删除容器,再启动容器,使环境变量生效:

docker-compose down
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

参考链接

https://hub.docker.com/r/jwilder/nginx-proxy

你可能感兴趣的