Docker Compose 实现nginx,trojan,vless 共用433端口

Posted by Eric Jin on 2022-08-22

为了速度和安全性,最近把梯子更新为Trojan 和Vless。

但同时也希望它们都共用433端口。

1. 准备

  • VPS
  • 一个域名,配置多个站点,多个站点都需要SSL 证书,直接使用letsencrypt 进行认证,可以参考升级https

2.实现

所有应用都docker 化,用docker-compose 来进行编排。轻量级,可复制,可迁移。

我们先参考:Trojan 共用 443 端口方案

nginx 需要配置需要ngx_stream_ssl_preread_module 模块,但是默认的nginx 的docker镜像没有配置这个模块的,所以我们需要build自己的nginx 镜像, 可以自己配置自己想要的模块。

Dockerfile:

FROM debian:stretch
ENV NGINX_VERSION=1.22.0

MAINTAINER Eric Jin <ericjin0819@gmail.com>

RUN apt-get update && apt-get -y upgrade && \
    apt-get install -y wget libpcre3-dev build-essential libssl-dev zlib1g-dev && \
    rm -rf /var/lib/apt/lists/*

RUN wget http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz && \
    tar -zxvf nginx-${NGINX_VERSION}.tar.gz && \
    cd nginx-1.* && \
    ./configure \
        --sbin-path=/usr/local/sbin/nginx \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --pid-path=/var/run/nginx/nginx.pid \
        --lock-path=/var/lock/nginx/nginx.lock \
        --http-log-path=/var/log/nginx/access.log \
        --http-client-body-temp-path=/tmp/nginx-client-body \
        --with-http_ssl_module \
        --with-threads \
        --with-ipv6 \
        --with-http_v2_module \
        --with-http_stub_status_module \
        --with-stream \
        --with-stream_ssl_module \
        --with-stream_ssl_preread_module && \
    make && make install && \
    cd .. && rm -rf nginx-1.*

# nginx user
RUN adduser --system --no-create-home --disabled-login --disabled-password --group nginx

# config dirs
RUN mkdir /etc/nginx/http.conf.d && mkdir /etc/nginx/stream.conf.d

# Forward logs to Docker
RUN ln -sf /dev/stdout /var/log/nginx/access.log && \
    ln -sf /dev/stderr /var/log/nginx/error.log

ADD nginx.conf /etc/nginx/nginx.conf

EXPOSE 80 443

CMD ["nginx", "-g", "daemon off;"]

默认配置的nginx.conf:

user  nginx;
worker_processes  auto;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    types_hash_bucket_size 64;
    types_hash_max_size 4096;
    gzip  on;

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

stream {
    include /etc/nginx/stream.conf.d/*.conf;    
}

用上面的Dockerfile直接build:

docker build -t='nginx_stream:1.0.0' .

接下来我们就可以直接使用上面的镜像了。

我们先看docker-compose 配置文件

docker-compose.yml

version: '3'
services:
  nginx:
   container_name: e_nginx
   image: nginx_stream:1.0.0
   restart: always
   ports:
     - 80:80
     - 433:443
   network_mode: host  
   volumes:
     - ./data/nginx/http.conf.d:/etc/nginx/http.conf.d
     - ./data/nginx/stream.conf.d:/etc/nginx/stream.conf.d
     - ./data/certbot/conf:/etc/letsencrypt
     - ./data/certbot/www:/var/www/certbot
     - /home/git/www/blog/:/usr/share/nginx/blog/
  
  trojan-go:
    restart: always
    image: p4gefau1t/trojan-go
    container_name: trojan-go
    network_mode: host
    volumes:
    - ./data/trojan-go/config.json:/etc/trojan-go/config.json
    - ./data/certbot/conf:/etc/letsencrypt
    depends_on:
    - nginx 
    command: /usr/bin/trojan-go -config /etc/trojan-go/config.json
 
  v2ray_vless: 
   restart: always
   depends_on:
   - nginx
   image: v2fly/v2fly-core
   volumes: 
   - ./data/v2fly/config.json:/etc/v2ray/config.json
   - ./data/certbot/conf/live/trojan.ericjin.com:/etc/v2ray/certs
   network_mode: host
   container_name: v2ray_vless

nginx stream.conf.d 里面的配置文件:

# 识别SNI, 将域名映射成配置名
map $ssl_preread_server_name $backend_name {
    trojan.ericjin.com trojan;
		vless.ericjin.com vless;
    ericjin.com web;
    default web;
}
    
upstream web {
    server 127.0.0.1:10240;
}

upstream trojan {
    server 127.0.0.1:10241;
}

upstream vless {
    server 127.0.0.1:10242;
}

# 这里就是监听443 端口,开启 ssl_preread
server {
    listen 443 reuseport;
    listen [::]:443 reuseport;
    proxy_pass $backend_name;
    #proxy_protocol on;
    ssl_preread on;
} 

上面的配置就会把不同域名进行流量转发。

http.conf.d 里面的配置文件:

# web 的配置,个人网站
server {              
    set $blog_root "/usr/share/nginx/";
    listen 10240 ssl http2;
    server_name ericjin.com;   

    ssl_certificate /etc/letsencrypt/live/ericjin.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/ericjin.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    include /etc/nginx/http.conf.d/webconf;  # 自己的网站的配置

}

# vless 的配置
server {
    # 开启 HTTP2 支持
    listen 10242 ssl http2;
    server_name  vless.erijin.com;

    ssl_certificate /etc/letsencrypt/live/vless.ericjin.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/vless.ericjin.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    # WS 协议转发
    location /nyj8Ji/ {
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:16888;
    }

    # 非标请求转发伪装
    location / {
       proxy_pass http://127.0.0.1:80;
    }
}

server {
    listen 80;
    server_name ericjin.com trojan.ericjin.com ;
    server_tokens off;
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    location / {
        return 301 https://$host$request_uri;
    }
}

vless 配置文件:

{
  "api": {
    "tag": "api"
  },
  "inbounds": [
    {
      "allocate": {
        "strategy": "always"
      },
      "port": 16888,
      "listen": "127.0.0.1",
      "protocol": "vless",
      "settings": {
        "clients": [
          {
            "alterId": 64,
            "id": "自己的唯一id",
            "level": 1
          }
        ],
        "decryption": "none"
      },
      "sniffing": {
        "enabled": false
      },
      "streamSettings": {
        "network": "ws",
        "security": "none",
        "wsSettings": {
           "path": "/nyj8Ji/"
        }
      }
    }
  ],
  "log": {
    "access": "/var/log/v2ray/access.log",
    "error": "/var/log/v2ray/error.log",
    "loglevel": "error"
  },
  "outbounds": [
    {
      "protocol": "freedom",
      "settings": {
        "domainStrategy": "AsIs"
      },
      "tag": "direct"
    },
    {
      "protocol": "blackhole",
      "settings": {
        "response": {
          "type": "none"
        }
      },
      "tag": "blocked"
    }
  ],
  "policy": {},
  "reverse": {},
  "routing": {
    "domainStrategy": "IPIfNonMatch",
    "rules": [
      {
        "ip": [
          "geoip:private"
        ],
        "outboundTag": "blocked",
        "type": "field"
      }
    ]
  },
  "stats": {},
  "transport": {}
}

trojan的配置文件:

{
    "run_type": "server",
    "local_addr": "127.0.0.1",
    "local_port": 10241,
    "remote_addr": "127.0.0.1",  // 这里直接转发给nginx的80即可
    "remote_port": 80,
    "password": [
        "password"
    ],
    "log_level": 1,
    "ssl": {
        "cert": "/etc/letsencrypt/live/trojan.ericjin.com/fullchain.pem",
        "key": "/etc/letsencrypt/live/trojan.ericjin.com/privkey.pem",
        "cipher": "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384",
        "cipher_tls13": "TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_256_GCM_SHA384",
        "prefer_server_cipher": true,
        "alpn": [
          "http/1.1",
          "h2"
        ],
        "alpn_port_override": {
          "h2": 81
        },
        "reuse_session": true,
        "session_ticket": false,
        "session_timeout": 600,
        "plain_http_response": "",
        "curves": "",
        "dhparam": "",
        "sni": "trojan.ericjin.com"
    }
}

这里的文件结构

.
.env
docker-compose.yml
data/
  certbot/
  nginx/
    http.conf.d/
      default.conf
    stream.conf.d/
			default.conf
  trojan-go/
			config.json
  v2fly/
			config.json

好了,现在只需要启动docker-compose 就好了

docker-compose up -d

参考:

Trojan 共用 443 端口方案