Docker 中使用 Nginx 代理多个应用站点

### 前言

##### 代理的作用是什么?

- 多个域名解析到同一个服务器

- 方便一台服务器多个应用只对外开放一个端口

- 访问应用不需要带着烦人的端口,直接域名访问

- 应用隔离

- 降低耦合度

- ...

总的来说就是方便维护,并且在维护一个应用的时候,不影响其他应用。

##### 如何代理(容器间如何通信)?

直接使用 nginx 的代理功能即可(相关能力另行查阅),这里麻烦的就是 docker 容器间的通信。

__Docker__ 容器间通信的主要方式有以下 4 种:

- __通过容器 IP 访问__:容器重启后,IP 会发生变化。

- __通过宿主机的 ip:port 的方式访问__:如果宿主机 IP 改变,就得每个应用都得改一遍,并且还要绑定端口,麻烦。

- __通过 link 建立链接__:相互依赖的太紧,不利于维护。

- __自定义 network__: 在同一个桥接网络中的容器可以相互访问。 

很明显,会选择 __自定义 network__ 的方式,让相关应用链接到同一个网络,这样应用与应用、代理与被代理之间其实就没什么依赖,不仅维护方便,而且迁移也方便。配置也不麻烦,只需要将常规的 IP 或域名换成相应的容器名即可。

#### 

-----

#### 一、统一网络

那么,首先需要创建一个共用的桥接网络:

```bash

docker network create proxy-network

# 查看

docker network ls

```

------

### 二、代理服务容器

创建一个专门用来代理的 `nginx` 服务容器,取名:__proxy-nginx__,这里使用 `docker-compose` 构建,其目录结构最终如下:

```bash

proxy-nginx

├── docker-compose.yml

├── logs # 日志

│   └── error.log

├── nginx

│   ├── Dockerfile

│   ├── nginx.conf

│   └── startup.sh

├── sites # 被代理站点配置

│   ├── baipiaoquan.com.conf

│   └── chaohuahui.com.conf

└── ssl # 证书文件

    └── baipiaoquan.com.pem

```

有些文件是在后续的运行过程产生的,配置时,只需要把必要的文件和目录创建好就行。

##### docker-compose.yml

```yml

version: "3"

networks:

  default:

    external:

      name: proxy-network

services:

  nginx:

    build:

      context: ./nginx

    volumes:

      - ./logs:/var/log/nginx

      - ./sites:/etc/nginx/sites-available

      - ./ssl:/etc/nginx/ssl

    ports:

      - "80:80"

      - "443:443"

```

把对外的 80、443 的端口绑定到代理服务器,所有的应用都可以从这里进来。

##### Dockerfile

```bash

FROM nginx:alpine

LABEL maintainer="chuoke"

COPY nginx.conf /etc/nginx/

RUN apk update \

    && apk upgrade \

    && apk add --no-cache openssl \

    && apk add --no-cache bash

RUN set -x ; \

    addgroup -g 82 -S www-data ; \

    adduser -u 82 -D -S -G www-data www-data && exit 0 ; exit 1

ADD ./startup.sh /opt/startup.sh

RUN sed -i 's/\r//g' /opt/startup.sh

CMD ["/bin/bash", "/opt/startup.sh"]

EXPOSE 80 443

```

这里将会创建运行用户组和用户 `www-data`,方便配置和控制,这个名字会用在 nginx 的配置中。

##### nginx.conf

```bash

user www-data;

worker_processes 4;

pid /run/nginx.pid;

daemon off;

events {

  worker_connections  2048;

  multi_accept on;

  use epoll;

}

http {

  server_tokens off;

  sendfile on;

  tcp_nopush on;

  tcp_nodelay on;

  keepalive_timeout 15;

  types_hash_max_size 2048;

  client_max_body_size 20M;

  include /etc/nginx/mime.types;

  default_type application/octet-stream;

  access_log /dev/stdout;

  error_log /dev/stderr;

  gzip on;

  gzip_disable "msie6";

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

  ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';

  include /etc/nginx/conf.d/*.conf;

  include /etc/nginx/sites-available/*.conf;

  open_file_cache off; # Disabled for issue 619

  charset UTF-8;

}

```

这个的内容拷贝 nginx 的默认就行,需要改的就是运行用户名,注意用户名要和前面的设置的保持一致。

##### startup.sh

```bash

#!/bin/bash

# Start crond in background

crond -l 2 -b

# Start nginx in foreground

nginx

```

这个是用来启动 nginx 程序用的,内容目前比较少,主要是为以后扩充内容方便。

##### 启动代理服务容器

```bash

docker-compose up -d nginx

```

 查看启动是否正常 `docker-compose ps` ,如果不正常,检查下配置是否有错误。

这个就这样,先放着,去创建应用。

-----

### 三、添加应用

添加一个站点 [https://baipiaoquan.com/](https://baipiaoquan.com/)。 

##### 配置应用容器

同样使用 docker-compose 创建应用。

这是一个 php 项目,所以这个应用里至少需要 `nginx` 和 `php-fpm` 两个服务容器,项目目录结构如下:

```bash

baipiaoquan/

├── docker-compose.yml

├── log

│   └── nginx

│       └── error.log

├── nginx

│   ├── Dockerfile

│   ├── log

│   ├── nginx.conf

│   ├── sites

│   │   └── baipiaoquan.com.conf

│   ├── ssl

│   │   ├── baipiaoquan.com.key

│   │   ├── baipiaoquan.com.pem

│   └── startup.sh

└── php-fpm

    ├── Dockerfile

    └── php.ini

```

__docker-compose.yml__ 

```bash

version: '3'

networks:

  proxy:

    external:

       name: ${PROXY_NETWORK_NAME}

  backend:

    driver: ${NETWORKS_DRIVER}

services:

 php-fpm:

      build:

        context: ./php-fpm

      volumes:

        - ./php-fpm/php.ini:/usr/local/etc/php/php.ini

        - ${APP_CODE_PATH_HOST}:${APP_CODE_PATH_CONTAINER}${APP_CODE_CONTAINER_FLAG}

      networks:

        - backend

 nginx:

      build:

        context: ./nginx

        args:

          - PHP_UPSTREAM_CONTAINER=${NGINX_PHP_UPSTREAM_CONTAINER}

          - PHP_UPSTREAM_PORT=${NGINX_PHP_UPSTREAM_PORT}

      volumes:

        - ${APP_CODE_PATH_HOST}:${APP_CODE_PATH_CONTAINER}${APP_CODE_CONTAINER_FLAG}

        - ./log:/var/log/nginx

        - ./sites:/etc/nginx/sites-available

        - ./ssl:/etc/nginx/ssl

      container_name: ${COMPOSE_PROJECT_NAME}_nginx

      depends_on:

        - php-fpm

      networks:

        - proxy

        - backend

```

为了方便调整,这里使用了环境变量。

注意 nginx 的容器名称 `container_name: ${COMPOSE_PROJECT_NAME}_nginx`,这个值很关键并且会在后续代理中用到。

__.env__

```bash

# 宿主机中代码的位置

APP_CODE_PATH_HOST=../

# 容器中代码的位置

APP_CODE_PATH_CONTAINER=/var/www

# 这个是抄的 laradock

APP_CODE_CONTAINER_FLAG=:cached

# 选择机器上的存储路径。适用于所有储存系统

DATA_PATH_HOST=~/.baipiaoquan/data

### Drivers ################################################

# All volumes driver  

VOLUMES_DRIVER=local  

# 网络驱动  

NETWORKS_DRIVER=bridge  

# 代理网络名称,这是前面创建的  

PROXY_NETWORK_NAME=proxy-network

### Docker compose files ##################################

COMPOSE_FILE=docker-compose.yml

# Change the separator from : to ; on Windows

COMPOSE_PATH_SEPARATOR=:

# 项目名称

COMPOSE_PROJECT_NAME=baipiaoquan

```

使用的代理网络名称是:proxy-network,这是在前面创建的;

nginx 的容器名称会是:baipiaoquan_nginx。

__nginx 的 Dockerfile__ 

这个文件可以把前面的那个直接拿来,然后加上关于 php 相关的就行了。

```bash

FROM nginx:alpine

COPY nginx.conf /etc/nginx/

RUN apk update \

    && apk upgrade \

    && apk --update add logrotate \

    && apk add --no-cache openssl \

    && apk add --no-cache bash

RUN set -x ; \

    addgroup -g 82 -S www-data ; \

    adduser -u 82 -D -S -G www-data www-data && exit 0 ; exit 1

ARG PHP_UPSTREAM_CONTAINER=php-fpm

ARG PHP_UPSTREAM_PORT=9000

# Set upstream conf and remove the default conf

RUN echo "upstream php-upstream { server ${PHP_UPSTREAM_CONTAINER}:${PHP_UPSTREAM_PORT}; }" > /etc/nginx/conf.d/upstream.conf \

    && rm /etc/nginx/conf.d/default.conf

ADD ./startup.sh /opt/startup.sh

RUN sed -i 's/\r//g' /opt/startup.sh

CMD ["/bin/bash", "/opt/startup.sh"]

EXPOSE 80 443

```

##### php-fpm 的 Dockerfile

```bash

FROM php:7.3-fpm

ARG PUID=1000

ENV PUID ${PUID}

ARG PGID=1000

ENV PGID ${PGID}

RUN groupmod -o -g ${PGID} www-data && \

    usermod -o -u ${PUID} -g www-data www-data

EXPOSE 9000

WORKDIR /var/www

CMD ["php-fpm"]

```

别忘了 `php.ini` 文件,也可以使用它默认的,那就要把这个相关的配置删掉。

##### 服务 baipiaoquan.com.conf 的配置

```bash

server {

    listen 80 default_server;

    listen [::]:80 default_server ipv6only=on;

    # For https

    listen 443 ssl default_server;

    listen [::]:443 ssl default_server ipv6only=on;

    ssl_certificate /etc/nginx/ssl/3243258_baipiaoquan.com.pem;

    ssl_certificate_key /etc/nginx/ssl/3243258_baipiaoquan.com.key;

    ssl_session_timeout 5m;

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; 

    ssl_prefer_server_ciphers on;

    add_header X-Frame-Options "SAMEORIGIN";

    add_header X-XSS-Protection "1; mode=block";

    add_header X-Content-Type-Options "nosniff";

    # localhost 一定要

    server_name localhost baipiaoquan.com www.baipiaoquan.com;

    root /var/www/;  # 这个和前面的配置保持一致

    index index.php index.html index.htm;

    location / {

         try_files $uri $uri/ /index.php$is_args$args;

    }

    location ~ \.php$ {

        try_files $uri /index.php =404;

        fastcgi_pass php-upstream; # 这个是 nginx Dockerfile 里配置的

        fastcgi_index index.php;

        fastcgi_buffers 16 16k;

        fastcgi_buffer_size 32k;

        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        #fixes timeouts

        fastcgi_read_timeout 600;

        include fastcgi_params;

    }

    location ~ /\.ht {

        deny all;

    }

    location /.well-known/acme-challenge/ {

        root /var/www/letsencrypt/;

        log_not_found off;

    }

}

```

我这里算是配全了,其实可以精简,只需要配置需要的即可。

##### 启动应用

此时,已经可以启动 baipiaoquan.com 的服务了,在 baipiaoquan 的目录下运行:

```bash

docker-compose up -d nginx

```

如果没有意外,应用应该启动并可以接收服务。亦可以测试下,进入容器访问下 localhost,看看结果是不是想要的。我是这样测试的:

```bash

docker-compose exec nginx wget localhost

```

然后产看返回的数据大小,根据情况判断是不是成功了。

可以通过下面的命令查看该应用是否成功连接到 proxy-network:

```bash

docker network inspect proxy-network

```

接下来要让全世界的人都能访问到这个应用。

##### 

##### 添加代理配置到 nginx-proxy

注意:要先启动应用,然后再开始代理,不然会出现 nginx 找不到 upstream 报错。

存放位置:`proxy-nginx/sites/baipiaoquan.com.conf`,只需要把上面的配置拷贝下来,改几个地方就行,最终配置如下:

```bash

# 我这配的仅支持 https,如果没要求,这个就不需要 

server {

    listen 80;

    server_name baipiaoquan.com www.baipiaoquan.com;

    return 301 https://$host$request_uri; 

}

server {

    # 如果是 http 就配置这个

    # listen 80 default_server;

    # 如果是 https 就配置这个

    listen 443 ssl;

    ssl_certificate           /etc/nginx/ssl/3243258_baipiaoquan.com.pem;

    ssl_certificate_key       /etc/nginx/ssl/3243258_baipiaoquan.com.key;

    ssl_session_timeout       5m;

    ssl_protocols             TLSv1 TLSv1.1 TLSv1.2;

    ssl_ciphers               ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;

    ssl_prefer_server_ciphers on;

    server_name baipiaoquan.com www.baipiaoquan.com;

    add_header X-Frame-Options "SAMEORIGIN";

    add_header X-XSS-Protection "1; mode=block";

    add_header X-Content-Type-Options "nosniff";

    location / {

        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_set_header  X-Forwarded-Host    $host;

        proxy_set_header  X-Forwarded-Port    $server_port;

        proxy_pass  http://baipiaoquan_nginx/;  # 这个值就是应用 nginx 的容器名称

    }

}

```

重新加载代理服务器的配置,在 nginx-proxy 目录下运行:

```bash

# 先测试下配置文件,这步一定要执行成功

docker-compose exec nginx nginx -t

# 如果提示成功,则重新加载,否则就按提示检查修改配置文件

docker-compose exec nginx nginx -s reload

```

稍等片刻,如果一切顺利,那么现在全世界的人应该能访问到这个 [https://baipiaoquan.com/](https://baipiaoquan.com/) 网站了。  

如果还需要添加其他应用,是一样的逻辑,流程照搬。例如我又加了一个应用:[https://chaohuahui.com/](https://chaohuahui.com/) ,可以 ping 一下他们的 IP 是一样的。

> 大量参考 [https://laradock.io/](https://laradock.io/) 

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容