Docker 中使用 Nginx 代理多個應用站點

chuoke發表於2019-12-21

 前言

 代理的作用是什麼?

- 多個域名解析到同一個伺服器

- 方便一臺伺服器多個應用只對外開放一個埠

- 訪問應用不需要帶著煩人的埠,直接域名訪問

- 應用隔離

- 降低耦合度

- ...

總的來說就是方便維護,並且在維護一個應用的時候,不影響其他應用。

 如何代理(容器間如何通訊)?

直接使用 nginx 的代理功能即可(相關能力另行查閱),這裡麻煩的就是 docker 容器間的通訊。

Docker 容器間通訊的主要方式有以下 4 種:

透過容器 IP 訪問:容器重啟後,IP 會發生變化。

透過宿主機的 ip:port 的方式訪問:如果宿主機 IP 改變,就得每個應用都得改一遍,並且還要繫結埠,麻煩。

透過 link 建立連結:相互依賴的太緊,不利於維護。

自定義 network: 在同一個橋接網路中的容器可以相互訪問。 

很明顯,會選擇 自定義 network 的方式,讓相關應用連結到同一個網路,這樣應用與應用、代理與被代理之間其實就沒什麼依賴,不僅維護方便,而且遷移也方便。配置也不麻煩,只需要將常規的 IP 或域名換成相應的容器名即可。

 


 一、統一網路

那麼,首先需要建立一個共用的橋接網路:

docker network create proxy-network

# 檢視
docker network ls

 二、代理服務容器

建立一個專門用來代理的 nginx 服務容器,取名:proxy-nginx,這裡使用 docker-compose 構建,其目錄結構最終如下:

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
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
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/.//g' /opt/startup.sh

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

EXPOSE 80 443

這裡將會建立執行使用者組和使用者 www-data,方便配置和控制,這個名字會用在 nginx 的配置中。

 nginx.conf
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
#!/bin/bash

# Start crond in background
crond -l 2 -b

# Start nginx in foreground
nginx

這個是用來啟動 nginx 程式用的,內容目前比較少,主要是為以後擴充內容方便。

 啟動代理服務容器
docker-compose up -d nginx

 檢視啟動是否正常 docker-compose ps ,如果不正常,檢查下配置是否有錯誤。

這個就這樣,先放著,去建立應用。


 三、新增應用

新增一個站點 https://baipiaoquan.com/。 

 配置應用容器

同樣使用 docker-compose 建立應用。

這是一個 php 專案,所以這個應用裡至少需要 nginx 和 php-fpm 兩個服務容器,專案目錄結構如下:

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 

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

# 宿主機中程式碼的位置
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 相關的就行了。

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/.//g' /opt/startup.sh

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

EXPOSE 80 443
 php-fpm 的 Dockerfile
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 的配置
server {

    listen 80 default_server;

    # For https
    listen 443 ssl default_server;
    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 的目錄下執行:

docker-compose up -d nginx

如果沒有意外,應用應該啟動並可以接收服務。亦可以測試下,進入容器訪問下 localhost,看看結果是不是想要的。我是這樣測試的:

docker-compose exec nginx wget localhost

然後產看返回的資料大小,根據情況判斷是不是成功了。
可以透過下面的命令檢視該應用是否成功連線到 proxy-network:

docker network inspect proxy-network

接下來要讓全世界的人都能訪問到這個應用。

 
 新增代理配置到 nginx-proxy

注意:要先啟動應用,然後再開始代理,不然會出現 nginx 找不到 upstream 報錯。

存放位置:proxy-nginx/sites/baipiaoquan.com.conf,只需要把上面的配置複製下來,改幾個地方就行,最終配置如下:

# 我這配的僅支援 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 目錄下執行:

# 先測試下配置檔案,這步一定要執行成功
docker-compose exec nginx nginx -t

# 如果提示成功,則重新載入,否則就按提示檢查修改配置檔案
docker-compose exec nginx nginx -s reload

稍等片刻,如果一切順利,那麼現在全世界的人應該能訪問到這個 https://baipiaoquan.com/ 網站了。  

如果還需要新增其他應用,是一樣的邏輯,流程照搬。例如我又加了一個應用:https://chaohuahui.com/ ,可以 ping 一下他們的 IP 是一樣的。

 大量參考 https://laradock.io/

本作品採用《CC 協議》,轉載必須註明作者和本文連結
? 我的導航網站已經可以公開使用啦:Cootab

相關文章