基于Docker Compose和Nginx容器部署复杂服务

概念扫盲

Nginx镜像

Nginx是轻量级且高性能的Web服务器及反向代理服务器,常用于Web项目部署及代理服务部署等场景。

但是如果在一个多服务协同的场景下直接在系统级别配置较为复杂的Nginx配置,可能会面临项目迁移困难的情况。因此考虑通过将复杂的Nginx配置打包到docker容器中,提升服务迁移的便捷性。

通过如下命令可以拉取官方最新版的nginx镜像:

docker pull nginx:latest

Docker Compose

Docker Compose 是一个用于定义和运行复杂的 Docker 应用程序的工具,我们可以通过简单的一个配置文件定义一个多服务应用程序的各个子服务的启动配置、资源占用、网络、依赖关系等,比如为其组装的容器创建专用网络,使得容器间可以相互通信,并通过简单的命令启动、停止、重启多服务程序。

Docker Compose极大地简化了复杂docker程序的管理和部署。

实战案例:基于Docker+Nginx容器部署图数据库服务及Web前后端服务

基本思路:设置N个容器:图数据库服务容器、前端容器、后端服务容器。其中前端容器可基于Nginx镜像运行,且该容器可配置反向代理到图数据库服务和后端服务容器。

镜像及应用容器分析

  1. 前端,基于Nginx镜像构建容器

    将前端文件打包或挂载到容器中,并通过nginx代理打包后的前端文件,其中nginx配置也可挂载到容器中。

  2. 后端,根据实际运行环境(镜像)构建容器。举例来说,Django项目应基于具有对应python环境的基础镜像构建。

    后端项目的接口可进一步通过第1步的前端容器中的nginx作代理/路由。

  3. 图数据库服务(Neo4j),基于官方镜像构建容器,同样可通过前端容器的nginx作代理。

为什么后端和图数据库服务也要由Nginx代理?希望整个应用只对外提供一个服务端口,而且便于统一配置鉴权或响应头等。

三个容器间的依赖关系如何?由于前端的正常访问和服务依赖于后端和数据库服务,所以容器的构建顺序是后端、图数据库、前端。

图数据库镜像及容器创建

参考Docker部署Neo4j

后端容器镜像构建

以Django项目为例,假设不需要将项目打包成可执行文件,而是直接基于源代码或预编译字节码部署,可以先打包python运行环境的镜像:

FROM python:<VERSION>
ENV PYTHONUNBUFFERED 1
EXPOSE 80
RUN groupadd -g 3002 backendgroup  \
    && useradd -c "backend user" -d /home/backenduser -g backendgroup -m -s /bin/sh -u 3002 backenduser  \
    && mkdir -p /opt/code/
USER backenduser
ADD ./requirements.txt /opt/code/
WORKDIR /opt/code
# download from network if failed offline
RUN pip install -r /opt/code/requirements.txt -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple|| \
    (pip install -r /opt/code/requirements.txt -i https://mirror.baidu.com/pypi/simple)

构建程序运行的镜像:

FROM <BASIC_IMAGE_NAME>:<TAG>
EXPOSE 80
WORKDIR /opt/code
# Django项目启动脚本(如wsgi服务运行shell脚本)
ADD ./start.sh /opt/code/
# Django项目源代码所在目录
ADD ./project /opt/code

前端Nginx容器配置

如果前端项目不复杂,可以直接基于Nginx官方镜像启动容器,只需要配置好前端文件的路径和Nginx配置即可。

nginx配置文件(default.conf)简单示例:

upstream backend{
    server docker_backend:80;
}

upstream graph_bolt_server{
    server docker_graph_database:7687;
}

upstream graph_browser_app{
    server docker_graph_database:7474;
}
server {

        listen  80;
        gzip            on;
        gzip_types      text/plain application/xml text/css application/javascript;
        gzip_min_length 1000;
    location = /oauth {
                internal;
                proxy_pass http://backend/auth/token;
                proxy_pass_request_body off;
                proxy_set_header Content-Length "";
                proxy_set_header X-Original-URI $request_uri;

    }
        # 后端API
        location /project/service/ {
                auth_request /oauth;
                error_page 401 = @error401_api;
                proxy_pass http://backend/;
                proxy_connect_timeout 120s;
                proxy_read_timeout 120s;
                proxy_send_timeout 120s;
                send_timeout 60;
        }
        # History模式的前端项目
        location /project/ {
             auth_request /oauth;
             error_page 401 = @error401;
             auth_request_set $auth_token $upstream_http_x_auth_token;
             auth_request_set $auth_status $upstream_status;
             proxy_set_header Access-Token $auth_token;
             if ($auth_token){
                add_header Authorization-Token $auth_token;
                add_header Set-Cookie "Auth-Token=$auth_token;Path=/;HttpOnly;SameSite=Lax";
             }
             alias /opt/code/project/;
             try_files $uri $uri/ /project/index.html; 

        }
        # 图数据库浏览器
        location /graph/ {
             auth_request /oauth;
             error_page 401 = @error401;
             auth_request_set $auth_token $upstream_http_x_auth_token;
             auth_request_set $auth_status $upstream_status;
             auth_request_set $new_cookie $sent_http_set_cookie; # use sent_http_*, not upstream_http_*
             proxy_set_header Access-Token $auth_token;
             if ($auth_token){
                add_header Authorization-Token $auth_token;
                add_header Set-Cookie "Auth-Token=$auth_token;Path=/";
             }
             if ($new_cookie){
                add_header Set-Cookie $new_cookie;
             }
             add_header Set-Cookie $sent_http_set_cookie;   
             proxy_pass http://graph_browser_app/;
             proxy_connect_timeout 300s;
             proxy_read_timeout 300s;
             proxy_send_timeout 300s;
             send_timeout 60;

    }
        # Neo4j数据库服务
        location / {
            auth_request /oauth;
            error_page 401 = @error401_api;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            proxy_pass http://graph_bolt_server/;
        }

        # 登录凭证无效处理——重定向
        location @error401{
                internal;
                return 302 http://www.spacetimelab.cn/?login=false;
    }

        location @error401_api{
                internal;
                default_type application/json;
                return 401 '{"msg":"Not Authorized. Please login first."}';

    }

}

编排容器示例

要点:

  • 服务名称可以作为主机名,使得容器可以被其它容器访问(详细参考default.conf)

  • Nginx配置好后挂载到容器中,提升后续项目迁移的便捷性

  • 通过depends_on设置容器间依赖关系,决定容器启动顺序

  • 图数据库设置对外服务的端口(可视情况关闭或在防火墙限制)

  • 在docker_frontend服务中设置对外暴露的端口

services:
  docker_backend:
    restart: always
    image: docker_backend-sso:latest
    container_name: "docker_backend_app"
    command: "/bin/bash start.sh"
  docker_graph_database:
    restart: always
    image: neo4j:5.23.0-community
    container_name: "docker_graph_database-sso"
    volumes:
      - "/project/programs/graph_database/server/data:/data:rw"
      - "/project/programs/graph_database/server/conf:/conf:rw"
      - "/project/programs/graph_database/server/import:/import:ro"

    # 对于示例的nginx配置而言,此处映射端口不是必须的
    ports:
      - "17473:7473"
      - "17474:7474"
      - "17687:7687"
    user: neo4j   
  docker_frontend:
      restart: always
      image: nginx:latest
      container_name: "docker_frontend_sso_app"
      depends_on:
        - docker_backend
        - docker_graph_database
      volumes:
        - "/project/programs/docker_frontend/default.conf:/etc/nginx/conf.d/default.conf:ro"
        - "/project/programs/docker_frontend/project/:/opt/code/project/:ro"
      ports:
        - "11080:80"

通过docker compose启动即可运行所有配置的容器服务:

docker compose -p sso-project up -d
CoolCats
CoolCats
理学学士

我的研究兴趣是时空数据分析、知识图谱、自然语言处理与服务端开发