最近在做社区类型的项目, 出于后续从市场招人成本的考虑, 不得不优选市场招聘培养难度较低的 PHP, 再三挑选, 选择了这款还在 beta 状态的软件.
这是一款 Beta 了差不多 5 年的软件, 在 GitHub 上拥有接近一万 star 的耀眼成绩, 第一条提交记录 是 2014 年末的 v0.1.0-beta 发布.
本文将介绍使用容器如何简单快速的搭建 Flarum , 如果你还不熟悉 Traefik, 请翻阅之前的文章.
写在前面
关于选型的顾虑, 我想此刻看到文章的你, 也一定有所考虑.
前面提到这款软件还在 beta , 处于不是特别稳定的状况. 而且前一阵主要的贡献者在论坛里发布了一条消息, 宣布 " ", 看起来是情况不是特别乐观, 那么除了招聘维护成本的考虑之外, 为什么还要选择它呢:
交互体验和项目架子搭的还不错, 基本面做的都还行.
作为开源应用迭代了五年, 有提交记录和开放的代码, 程序不是特别复杂, 相对好追溯和解决问题.
项目文档虽然不全, 但是基础的部分还差不多是有的.
应用市场虽然东西不多, 但是插件机制已经建设好了, 功能扩展相对比较简单.
同语言实现的, 功能比较强大的某两款国产软件, 一个论坛停止运营, 一个计划重构 (原因你懂的), 显然当当下时间点都不值得托付.
WordPress 的 BuddyPress 生态可以考虑, 但是做同样需求改动量会更大一些, 因为冗余内容更多.
构建 PHP 容器
官方 安装文档 对于环境要求是这样的
支持 URL Rewrite 的服务器软件: Apache,Nginx...
PHP 7.1+, 以及 dom , gd , JSON , mbstring , openssl , , pdo_mysql , tokenizer 组件.
MySQL 5.6+ 或 MariaDB 10.0.5+
Composer
所以, Docker Hub 默认的提供的 PHP 镜像是使用不了的, 需要进行额外配置, 安装以上需要的软件. 我们默认不允许在已经运行起来的软件中执行插件卸载 (删除程序部分文件), 所以最后一点提到的 Composer 可以在本地安装, 或者干脆不进行安装.
这里以 PHP-FPM-ALPINE 7.3.2 为例:
- FROM PHP:7.3.2-fpm-alpine
- ENV LANG en_US.UTF-8
- ENV LANGUAGE en_US.UTF-8
- ENV LC_ALL=en_US.UTF-8
- RUN echo ''> /etc/apk/repositories && \
- echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.9/main" >> /etc/apk/repositories && \
- echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.9/community" >> /etc/apk/repositories && \
- echo "Asia/Shanghai"> /etc/timezone
- RUN apk --no-cache --no-progress update && \
- apk --no-cache --no-progress upgrade
- RUN apk add libpng libpng-dev libjpeg-turbo-dev libwebp-dev zlib-dev libxpm-dev
- RUN docker-PHP-ext-install pdo pdo_mysql mbstring gd
- ENTRYPOINT ["docker-php-entrypoint"]
- STOPSIGNAL SIGQUIT
- EXPOSE 9000
- CMD ["php-fpm"]
使用 build 命令将容器构建起来:
docker build -t PHP-fpm-flarum:7.3.2 -f Dockerfile .
使用 docker images 查看构建后的 PHP 镜像, 一百兆出头.
- REPOSITORY TAG IMAGE ID CREATED SIZE
- PHP-fpm-flarum 7.3.2 ef5ad124a35d 3 minutes ago 105MB
搭建数据库
上一小节有提到过, 官方对于数据库方面的要求是: MySQL 5.6+ 或 MariaDB 10.0.5+ , 所以你可以根据自己喜好来搞, 如果有性能要求, 建议使用云厂商的 RDS 产品.
下面给出一个数据库编排文件示例:
- version: '3.6'
- services:
- database:
- image: ${DB_IMAGE}
- restart: always
- container_name: ${DB_HOST}
- ports:
- - 3306:3306
- networks:
- - traefik
- environment:
- MYSQL_DATABASE: ${DB_NAME}
- MYSQL_USER: ${DB_USER}
- MYSQL_PASSWORD: ${DB_PASS}
- MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS}
- volumes:
- - ./data:/var/lib/MySQL
- networks:
- traefik:
- external: true
将上面的配置保存为 docker-compose.YAML , 继续编写配置需要的 .env 文件. 我这边为了测试方便, 就都用弱密码了, 实际使用应改成安全度较高的复杂密码.
- DB_IMAGE=MySQL:5.7.26
- DB_HOST=flarum
- DB_NAME=flarum
- DB_USER=flarum
- DB_PASS=flarum
- DB_ROOT_PASS=flarum
然后执行 docker-compose up -d 数据库就运行起来啦.
搭建应用运行框架
时至今日, 官方提供的安装方案也从传统的软件压缩包变成了一条简约的命令:
Composer create-project flarum/flarum . --stability=beta
但是这样做对于持续迭代的项目的开发部署体验是不友好的, 即使使用版本锁定功能, 项目构建还是时间会变长, 把代码都打到容器里由显得笨重, 并且造成了相同应用的代码割裂, 维护成本颇高.
而且后续需要在程序框架上做一些改动, 还要解决和未来的版本更新合并的问题, 并不只是简单的安装使用就完事了, 所以这里需要将应用代码储存下来.
安装 Composer PHP 包管理软件
因为软件发布模式变化, 所以我们下载软件包需要使用 Composer (PHP 环境安装不赘述).
- wget -O Composer-setup.PHP https://getcomposer.org/installer
- PHP Composer-setup.PHP --install-dir=bin --filename=Composer
Composer 在国内下载比较慢, 这里可以选择借助 阿里云的加速镜像 https://developer.aliyun.com/composer , 使用方法很简单, 就一条命令.
Composer config -g repo.packagist Composer https://mirrors.aliyun.com/composer/
下载 Flarum 程序代码
接着使用下面的命令将软件下载至 flarum 目录.
Composer create-project flarum/flarum ./flarum --stability=beta`
完成安装后的目录结构是下面的样子:
flarum
├── CHANGELOG.md
├── LICENSE
├── README.md
├── Composer.JSON
├── Composer.lock
├── extend.PHP
├── flarum
├── public
│ ├── assets
│ └── index.PHP
├── storage
│ ├── cache
│ ├── formatter
│ ├── Less
│ ├── locale
│ ├── logs
│ ├── sessions
│ ├── tmp
│ └── views
└── vendor
现在软件还运行不起来, 我们需要继续配置应用的运行环境.
配置软件运行环境
在上面的操作都就绪之后, 我们就可以进行程序运行环境搭建了. 这里使用 Nginx 作为 PHP 的前端, 整个环境搭建非常简单.
- version: "3.6"
- services:
- nginx:
- image: ${DOCKER_NGINX_IMAGE}
- restart: always
- expose:
- - 80
- volumes:
- - ./logs:/var/log/nginx
- - ./conf/docker-nginx.conf:/etc/nginx/nginx.conf
- - ./wwwroot:/wwwroot
- links:
- - PHP:PHP
- extra_hosts:
- - "${DOCKER_DOMAIN_NAME}:127.0.0.1"
- networks:
- - traefik
- labels:
- - "traefik.enable=true"
- - "traefik.port=80"
- - "traefik.frontend.rule=Host:${DOCKER_DOMAIN_NAME}"
- - "traefik.frontend.entryPoints=https,http"
- healthcheck:
- test: ["CMD-SHELL", "wget -q --spider --proxy off http://${DOCKER_DOMAIN_NAME}/get-health || exit 1"]
- interval: 5s
- retries: 12
- logging:
- driver: "json-file"
- options:
- max-size: "100m"
- PHP:
- image: ${DOCKER_PHP_IMAGE}
- restart: always
- expose:
- - 9000
- volumes:
- - ./logs:/var/log
- - ./wwwroot:/wwwroot
- extra_hosts:
- - "${DOCKER_DOMAIN_NAME}:127.0.0.1"
- networks:
- - traefik
- healthcheck:
- test: ["CMD-SHELL", "pidof php-fpm"]
- interval: 5s
- retries: 12
- logging:
- driver: "json-file"
- options:
- max-size: "100m"
- networks:
- traefik:
- external: true
搭配使用的 .env 可以这么写:
- DOCKER_DOMAIN_NAME=flarum.lab.com
- DOCKER_PHP_IMAGE=PHP-fpm-flarum:7.3.2
- DOCKER_NGINX_IMAGE=nginx:1.17.1-alpine
Nginx 的配置则可以参考下面的文件:
- user nginx;
- worker_processes 1;
- error_log /var/log/nginx/error.log warn;
- pid /var/run/nginx.pid;
- events { worker_connections 1024; }
- http {
- include /etc/nginx/mime.types;
- default_type application/octet-stream;
- log_format main '$http_x_forwarded_for - $remote_user [$time_local]"$request"$status $body_bytes_sent"$http_referer""$http_user_agent"';
- access_log off;
- sendfile on;
- keepalive_timeout 65;
- server {
- listen 80;
- server_name flarum.lab.com;
- server_tokens off;
- access_log /var/log/nginx/docker-access.log;
- error_log /var/log/nginx/docker-error.log;
- root /wwwroot/public;
- index index.PHP index.HTML;
- location ~ \.PHP$ {
- try_files $uri =404;
- fastcgi_split_path_info ^(.+\.PHP)(/.+)$;
- fastcgi_pass PHP:9000;
- fastcgi_index index.PHP;
- include fastcgi_params;
- fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
- fastcgi_param PATH_INFO $fastcgi_path_info;
- }
- location / {
- try_files $uri $uri/ /index.PHP?$query_string;
- }
- # The following directives are based on best practices from H5BP Nginx Server Configs
- # https://github.com/h5bp/server-configs-nginx
- # Expire rules for static content
- location ~* \.(?:manifest|appcache|HTML?|xml|JSON)$ {
- add_header Cache-Control "max-age=0";
- }
- location ~* \.(?:rss|atom)$ {
- add_header Cache-Control "max-age=3600";
- }
- location ~* \.(?:jpg|jpeg|gif|PNG|ico|cur|gz|svg|mp4|ogg|ogv|webm|htc)$ {
- add_header Cache-Control "max-age=2592000";
- access_log off;
- }
- location ~* \.(?:CSS|JS)$ {
- add_header Cache-Control "max-age=31536000";
- access_log off;
- }
- location ~* \.(?:ttf|ttc|otf|eot|woff|woff2)$ {
- add_header Cache-Control "max-age=2592000";
- access_log off;
- }
- location = /get-health {
- access_log off;
- default_type text/HTML;
- return 200 'alive';
- }
- # 可以考虑交给后面的程序去处理, 比如 traefik ...
- # Gzip compression
- gzip on;
- gzip_comp_level 5;
- gzip_min_length 256;
- gzip_proxied any;
- gzip_vary on;
- gzip_types application/atom+xml
- application/JavaScript
- application/JSON
- application/ld+JSON
- application/manifest+JSON
- application/rss+xml
- application/vnd.geo+JSON
- application/vnd.ms-fontobject
- application/x-font-ttf
- application/x-Web-App-manifest+JSON
- application/xhtml+xml
- application/xml
- font/opentype
- image/bmp
- image/svg+xml
- image/x-icon
- text/cache-manifest
- text/CSS
- text/plain
- text/vcard
- text/vnd.rim.location.xloc
- text/vtt
- text/x-component
- text/x-cross-domain-policy;
- }
- }
将之前下载完毕的 flarum 程序放置到 wwwroot 目录中, 便可以开始程序的安装了.
使用 docker-compose up -d 将程序运行起来, 访问 flarum.lab.com 对程序进行配置.
安装应用
简单填写安装界面需要的要素后, 点击安装按钮.
片刻之后, 程序安装就完毕了, 可以看到界面还是十分清爽的.
管理后台比较简单, 很难满足我们的日常使用需求, 不过好在现在可以编写插件进行功能增强.
对程序运行框架做优化
程序的安装部分已经结束了, 但是考虑到后续的维护, 我们还需要做一些额外的工作.
让应用自适应运行环境
应用安装完毕, 我们再次查看应用目录, 发现目录中多了一个 config.PHP 文件.
wwwroot
├── CHANGELOG.md
├── LICENSE
├── README.md
├── Composer.JSON
├── Composer.lock
├── config.PHP
├── extend.PHP
├── flarum
├── public
│ ├── assets
│ └── index.PHP
├── storage
│ ├── cache
│ ├── formatter
│ ├── Less
│ ├── locale
│ ├── logs
│ ├── sessions
│ ├── tmp
│ └── views
└── vendor
文件内容如下, 记录了程序运行配置:
- <?PHP return array (
- 'debug' => false,
- 'database' =>
- array (
- 'driver' => 'mysql',
- 'host' => 'flarum',
- 'port' => 3306,
- 'database' => 'flarum',
- 'username' => 'flarum',
- 'password' => 'flarum',
- 'charset' => 'utf8mb4',
- 'collation' => 'utf8mb4_unicode_ci',
- 'prefix' => 'flarum_',
- 'strict' => false,
- 'engine' => 'InnoDB',
- 'prefix_indexes' => true,
- ),
- 'url' => 'https://flarum.lab.com',
- 'paths' =>
- array (
- 'api' => 'api',
- 'admin' => 'admin',
- ),
- );
考虑到程序可能运行在不同的环境中 (生产, 测试, 开发), 数据库, 网站地址等信息存在变化的可能, 如果它能够自动读取环境变量可以免除维护多份配置的麻烦事, 并简化发布过程.
对它进行简单的修改:
- <?PHP return array (
- 'debug' => ($_SERVER['FLARUM_APP_DEBUG'] === 'true'),
- 'database' =>
- array (
- 'driver' => 'mysql',
- 'host' => $_SERVER['FLARUM_DB_HOST'],
- 'database' => $_SERVER['FLARUM_DB_NAME'],
- 'username' => $_SERVER['FLARUM_DB_USER'],
- 'password' => $_SERVER['FLARUM_DB_PASS'],
- 'charset' => 'utf8mb4',
- 'collation' => 'utf8mb4_unicode_ci',
- 'prefix' => 'flarum_',
- 'port' => '3306',
- 'strict' => false,
- ),
- 'url' => $_SERVER['FLARUM_APP_URL'],
- 'paths' =>
- array (
- 'api' => 'api',
- 'admin' => 'admin',
- ),
- );
同时也要对 docker-compose.YAML 进行简单的修改, 把 .env 当作环境变量 "注入" 到应用中.
PHP:
- image: ${DOCKER_PHP_IMAGE}
- restart: always
- expose:
- - 9000
- env_file: .env
最后, 在 .env 里声明上面配置文件需要的变量名称即可.
- DOCKER_DOMAIN_NAME=flarum.lab.com
- DOCKER_PHP_IMAGE=PHP-fpm-flarum:7.3.2
- DOCKER_NGINX_IMAGE=nginx:1.17.1-alpine
- FLARUM_DB_HOST=flarum
- FLARUM_DB_NAME=flarum
- FLARUM_DB_USER=flarum
- FLARUM_DB_PASS=flarum
- FLARUM_APP_DEBUG=true
- FLARUM_APP_URL=//flarum.lab.com
继续拆分项目结构
前文提过, 如果我们要持续修改完善这个处于 beta 状态下的软件, 需要对其代码进行保存维护, 但是如果将环境和代码放置一处, 修改调试的效率不免太低.
所以我们可以对上面的应用目录进行简化操作, 将 "应用代码" 和 "基础环境" 进行拆分, 未来调试发布仅需要更新文件即可, 而不必对环境进行重新部署, 重启等重操作.
如果你也有类似需求, 也可以参考下图进行拆分, 将应用软件的基础环境, Vendor 代码进行分拆.
简化后的目录如下:
.
├── LICENSE
├── README.md
├── .env
├── conf
│ └── docker-nginx.conf
├── logs
├── update.sh
└── wwwroot
├── config.PHP
├── extend.PHP
├── flarum
├── public
│ └── index.PHP
├── storage
└── vendor
可以看到, wwwroot 目录被简化到只留下四个 PHP 文件, 和几个空目录.
对程序进行修改发布, 则可以使用 CI 配合 update.sh 更新脚本使用, 将程序需要的 vendor 依赖文件进行同步更新, 更新脚本可以参考下面:
- #!/usr/bin/env bash
- # 关闭服务
- echo "stop services."
- docker-compose down --remove-orphans
- # 确保容器镜像存在
- cat .env | grep _IMAGE | cut -d '=' -f2 | while read image ; do
- if test -z "$(docker images -q $image)"; then
- echo "prepare docker images."
- docker pull $image
- fi
- done
- # 清理之前存在的历史文件, 并将新文件同步到执行目录中
- if [ -d "wwwroot/vendor" ]; then
- echo "cleanup wwwroot/vendor."
- rm -rf wwwroot/vendor
- fi
- if [ -d "/data/forum-test-vendor" ]; then
- echo "update vendor."
- cp -r /data/forum-test-vendor/vendor/ wwwroot/
- cp /data/forum-test-vendor/Composer.* wwwroot/
- fi
- # 清理 & 重建目录
- echo 'cleanup directory'
- rm -rf ./wwwroot/storage
- mkdir -p ./wwwroot/storage/cache
- mkdir -p ./wwwroot/storage/formatter
- mkdir -p ./wwwroot/storage/Less
- mkdir -p ./wwwroot/storage/locale
- mkdir -p ./wwwroot/storage/logs
- mkdir -p ./wwwroot/storage/sessions
- mkdir -p ./wwwroot/storage/tmp
- mkdir -p ./wwwroot/storage/views
- # 重启服务
- echo 'restart services.'
- docker-compose up -d
- # 修正文件权限
- docker ps -q -f status=running -f name=flarum_php | while read container ; do
- echo "fix container $container own && mod."
- docker exec $container chown -R www-data:www-data /wwwroot
- docker exec $container chmod -R 755 /wwwroot/storage
- done
在进行了目录拆分后, 项目的测试发布时间将可以从之前的分钟级缩短到 5~10s 秒.
最后
关于这个软件的折腾细节还有不少, 后续陆续慢慢写出来吧.
引用我前一段时间在朋友圈发的内容作为结束:
合作单位在关键时刻因不可抗力取消了合作, 周五机器删除代码, 数据库下线. 所有人五十天的忙碌付诸东流, 项目按计划上线压力骤大.
只好重定方案, 从 MVP 做起. 不得不再次变身救火队员: woman::fire_engine:.
从收拾遗留问题, 到做新架构设计, 搭建各种环境和基础设施, 调通后端基本流程, 整了差不多三天. 目测再搞一周应该就差不多啦!:tada:
感谢各种开源软件, 让问题变得快速可解, 让我这个月还能空出时间办个 "大事".
来源: http://www.tuicool.com/articles/UbuaQzM