自2021年,我开始运维一个日均PV 10万+的 Discuz! X论坛站点。接手后陆续发现一些问题难以通过传统方法解决,遂决定对其进行容器化改造。

1. 为什么要对 Discuz! X 进行容器化改造

1.1. 原部署方案为单机部署

Discuz! X 论坛的默认部署方式是单机部署,自然会出现一些单机部署会发生的问题:

  1. 单点故障:如果这台机器出现任何问题,会导致整个论坛处于不可用状态。包括 Nginx、PHP 进程出现问题,机器关机、重启等。
  2. 扩容困难:遇到搞活动时期,扩容需重启(使用的虚拟机为腾讯云CVM,可动态修改虚拟机配置,但需要重启,重启时间为数分钟)。而且一旦容量预估失败,活动期间调整容量几乎不可能。

1.2. PHP环境部署复杂

  1. 扩展安装困难:开发以来的很多扩展安装比较复杂,新增一个扩展往往要倒腾半天。例如 grpc 扩展,开发人员本地安装成功但我按照开发人员的方法无法安装成功;更让人崩溃的是在测试环境安装调试OK后再生产环境安装依然出现了问题。pcel 执行也有失败的概率。
  2. 扩展安装时间久:新增 PHP 扩展往往需要停服更新,往往安装方法既受限于网速,又受限于源码编译构建的速度。万一出现意料之外的情况需要增加停机时间。

1.3. 支持滚动升级

实现版本更新用户无感知

2. 各项关键问题的解决方案

2.1. PHP 环境安装使用 Docker 镜像

这边使用的基础镜像为:php:7.2-fpm,为 PHP 官方发布的镜像。该镜像也能非常方便的安装 PHP 扩展。该项目也依赖了 composer,在 PHP 镜像中编译添加 composer 比较困难,比较简单的方法是,使用 COPY 命令从 composer 镜像中复制 composer 的二进制文件。示例的 Dockerfile 如下:

1
2
3
4
5
6
7
8
9
10
11
12
FROM php:7.2-fpm

COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

RUN apt update \
&& apt install -y libfreetype6-dev libjpeg62-turbo-dev libmagickwand-dev libpng-dev libwebp-dev libxpm-dev libz-dev libzstd-dev \
&& pecl install grpc \
&& pecl install protobuf \
&& pecl install redis \
&& docker-php-ext-enable grpc \
&& docker-php-ext-install pdo \
&& docker-php-ext-install pdo_mysql

2.2. NFS 解决 Discuz! X 负载均衡问题

Discuz! X 多机部署的一个问题就在于 Discuz! X 使用了本地缓存目录和本地存储目录。我这边的解决方案是:

  • 本地存储目录(用户上传的图片等资源):存储至腾讯云COS,使用此方案需要部署 cos-ftp-server ,其 Dockerfile 如下:

    1
    2
    3
    4
    5
    6
    7
    FROM python:2.7

    COPY ./cos-ftp-server-V5-master.zip /

    RUN mkdir /tmp/cos && pip install -i https://mirrors.tencent.com/pypi/simple/ cos-python-sdk-v5 pyftpdlib psutil && unzip cos-ftp-server-V5-master.zip && rm cos-ftp-server-V5-master.zip && cd cos-ftp-server-V5-master && python setup.py install

    CMD cd cos-ftp-server-V5-master && python ftp_server.py

    需要注意的是,示例的 Dockerfile 中没有修改 cos-ftp-server 的配置文件 vsftpd.conf,推荐在 k8s 中使用 configmap 替换此文件。

  • 缓存目录:使用腾讯云文件存储,其支持使用 NFS 协议挂载到容器。假设网站的根目录为/www/src,我们将在 yaml 中挂载 NFS 到 /nfs/src

    • pv

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      apiVersion: v1
      kind: PersistentVolume
      metadata:
      name: {{ .Release.Name }}
      labels:
      pv: {{ .Release.Name }}-pv
      spec:
      capacity:
      storage: 150Gi
      accessModes:
      - ReadWriteMany
      persistentVolumeReclaimPolicy: Retain
      nfs:
      path: /
      server: {{ .Values.nfs }}
      • pvc

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        kind: PersistentVolumeClaim
        apiVersion: v1
        metadata:
        name: {{ .Release.Name }}
        spec:
        accessModes:
        - ReadWriteMany
        storageClassName: ""
        resources:
        requests:
        storage: 150Gi
        selector:
        matchLabels:
        pv: {{ .Release.Name }}-pv
      • Deployment

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        ...
        volumeMounts:
        - mountPath: "/nfs"
        name: {{ .Release.Name }}
        ...
        volumes:
        - name: {{ .Release.Name }}
        persistentVolumeClaim:
        claimName: {{ .Release.Name }}
        readOnly: false
        ...

      在 Dockerfile 中,软链接缓存目录(缓存目录众多,如下仅为示例)。

      1
      2
      3
      4
      5
      ...
      ADD ./www/ /www/src

      RUN ln -s /nfs/src/data /www/src/
      ...

3. 整体架构参考

graph TB
  client("用户")
  cos(腾讯云COS)
     mysql(MySQL)
     nfs(NFS)
  clb(腾讯云CLB)
  subgraph k8s
        subgraph bbs_pod
            nginx(Nginx)
            php(PHP)
            cos-ftp(cos-ftp)
        end
        subgraph redis_pod
            redis(Redis)
        end
  end

    client --> clb --> nginx --> php --> cos-ftp --> cos
    php --> redis
    php ---> mysql
    nginx --> nfs
    php ---> nfs

4. 交付部署流程优化

使用了 Docker k8s 部署 Discuz! X 后,可以借助腾讯蓝鲸蓝盾优化整个部署流程,实现全流程零运维。

graph TB
    a1(Git 触发流水线构建) --> a2(构建 Docker 镜像) --> a3(通过 helm 滚动更新测试环境) --> a4(通过 helm 滚动更新生产环境)