使用 Docker 和 Traefik v2 搭建 Flarum 輕論壇應用
距離寫完已經過去了十個月。
在上一篇搭建教程中,我描述過這個應用的優劣勢,因為缺乏開發者,所以時隔近一年的時間裡,軟件除了能夠保持緩慢前行外,並沒有實質的變化。國內相關社區同樣因為缺少活力,依舊還在使用陳舊的迭代方案,短期來看,應該不會有太多驚喜出現,不過作為一款輕量社區來講,flarum 是合格的。
本文將介紹如何使用 Docker 來對 Flarum 最新版 v0.1.0-beta.12 進行容器封裝,以及如何搭配 traefik v2 一起使用。
寫在前面
在的末尾,我提過:
之前寫文章總是考慮沒有閱讀基礎的同學,而忽略了一直訂閱、關注著我的同學,未來重複的內容,我將會和本文一樣,給予簡短的指引,不贅述基礎建設,只聊主題相關的核心部分。
為了保證內容的簡潔,相關資料可以自行從網站歷史資料找翻閱,學習這件事只有探索折騰才有意思,不是麼?
使用環境
1. Docker: 19.03.8, build afacb8b
· docker-compose: version 1.25.4, build 8d51620a
· Flarum: v0.1.0-beta.12 (官方版本)
· PHP: 7.4 (官方鏡像)
· MySQL: 5.7 (官方鏡像)
· Redis: 5.0.8 (官方鏡像)
準備數據庫
單機論壇可以考慮使用下面的編排文件,啟動項目所需的數據庫和緩存服務。
version: '3.6'
services:
mysql:
container_name: ${DOCKER_MYSQL_HOST}
image: ${DOCKER_MYSQL_IMAGE}
restart: always
expose:
- 3306
networks:
- traefik
environment:
MYSQL_USER: ${DOCKER_MYSQL_USER}
MYSQL_PASSWORD: ${DOCKER_MYSQL_PASS}
MYSQL_DATABASE: ${DOCKER_MYSQL_NAME}
MYSQL_ROOT_PASSWORD: ${DOCKER_MYSQL_ROOT}
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_bin --default-storage-engine=INNODB --max_allowed_packet=256M --transaction-isolation=READ-COMMITTED --binlog_format=row --ngram_token_size=2
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ./db:/var/lib/mysql
healthcheck:
test: ["CMD-SHELL", "/etc/init.d/mysql status"]
interval: 30s
redis:
image: ${DOCKER_REDIS_IMAGE}
restart: always
container_name: ${DOCKER_REDIS_HOST}
expose:
- 6379
networks:
- traefik
healthcheck:
test: ["CMD", "redis-cli", "ping"]
environment:
TZ: Asia/Shanghai
networks:
traefik:
external: true
將上面的內容保存為 docker-compose.yml ,然後繼續配置 .env 環境變量文件。
DOCKER_REDIS_IMAGE=redis:5.0.8-alpine
DOCKER_REDIS_HOST=flarum-redis.lab.com
DOCKER_MYSQL_IMAGE=mysql:5.7
DOCKER_MYSQL_HOST=flarum-db.lab.com
DOCKER_MYSQL_USER=flarum
DOCKER_MYSQL_PASS=flarum
DOCKER_MYSQL_NAME=flarum
DOCKER_MYSQL_ROOT=flarum
兩個文件都準備好後,使用 docker-compose up -d,啟動數據庫和緩存服務即可。
為了讓 flarum 支持搜索中文內容,全文檢索以配置好,最小搜索長度為 2,如果有特殊需求可以修改 --ngram_token_size=2 為適合你的數值。
如果你不需要使用 Redis Session 功能,可以刪除掉 Redis 相關內容。
獲取當前版本最新代碼
為了項目的可維護性,我們一般需要將應用和其依賴組件進行版本鎖定。所以這裡建議使用 composer 將代碼下載下來後,作為"代碼基"使用代碼倉庫單獨管理保存,而非在容器中進行下載構建,這樣對於每次軟件變更都能做到心中有數。
和之前一樣,我們使用下面的命令可以將 flarum 當前最新的 beta 版本下載到本地。
composer create-project flarum/flarum ./codebase --stability=beta
如果長時間不能完成下載,可以在命令行後添加 -vvv,可以輔助判斷是因為什麼問題導致下載出現異常。
如果是因為網絡問題,可以考慮使用下面的方法,將 composer 源修改為阿里雲或其他國內 CDN 地址(以阿里云為例):
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
修改完畢之後,可以使用下面的命令驗證修改是否成功。
composer config -g -l repo.packagist | grep repositories.packagist.org.url
# 輸出如下則修改成功
[repositories.packagist.org.url] https://mirrors.aliyun.com/composer/
修改完畢之後,再次執行 create-project 命令即可。
如果是使用 flarum 做線上業務,此處可以考慮使用生產環境的私有 composer 搭配持續集成進行操作,安全性和可靠性會有極大的提升,細節可參考下面兩篇文章:、。
準備好的 codebase 目錄結構如下:
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── composer.lock
├── extend.php
├── flarum
├── public
├── site.php
├── storage
└── vendor
確認 codebase 目錄內已經保存了完整的 flarum 程序後,我們開始編寫容器鏡像配置文件。
封裝容器鏡像
之前文章中,我使用了當時最新的 PHP 7.3.2,如今 PHP 7.4 已經到來,所以這裡將使用最新版本的 PHP 封裝 Flarum 的運行環境,我當前選擇的版本是:php:7.4-fpm-alpine3.11。
Dockerfile 文件內容可參考下面:
FROM php:7.4-fpm-alpine3.11
LABEL maintainer="[email protected]"
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US.UTF-8
ENV LC_ALL=en_US.UTF-8
ARG USE_CHINA_MIRROR=0
RUN if [ "$USE_CHINA_MIRROR" = 1 ]; then \\
echo 'use china mirror' && \\
echo '' > /etc/apk/repositories && \\
echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.11/main" >> /etc/apk/repositories && \\
echo "https://mirror.tuna.tsinghua.edu.cn/alpine/v3.11/community" >> /etc/apk/repositories && \\
echo "Asia/Shanghai" > /etc/timezone; \\
fi
RUN apk --no-cache --no-progress update && \\
apk --no-cache --no-progress upgrade
RUN apk add libpng libpng-dev libjpeg-turbo libjpeg-turbo-dev libwebp libwebp-dev zlib-dev libxpm-dev freetype freetype-dev oniguruma-dev
RUN docker-php-ext-install pdo pdo_mysql mbstring
RUN docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ && \\
docker-php-ext-install -j$(getconf _NPROCESSORS_ONLN) gd
ENTRYPOINT ["docker-php-entrypoint"]
WORKDIR /var/www/html
RUN set -eux; \\
cd /usr/local/etc; \\
if [ -d php-fpm.d ]; then \\
# for some reason, upstream's php-fpm.conf.default has "include=NONE/etc/php-fpm.d/*.conf"
sed 's!=NONE/!=!g' php-fpm.conf.default | tee php-fpm.conf > /dev/null; \\
cp php-fpm.d/www.conf.default php-fpm.d/www.conf; \\
else \\
# PHP 5.x doesn't use "include=" by default, so we'll create our own simple config that mimics PHP 7+ for consistency
mkdir php-fpm.d; \\
cp php-fpm.conf.default php-fpm.d/www.conf; \\
{ \\
echo '[global]'; \\
echo 'include=etc/php-fpm.d/*.conf'; \\
} | tee php-fpm.conf; \\
fi; \\
{ \\
echo '[global]'; \\
echo 'error_log = /proc/self/fd/2'; \\
echo; echo '; https://github.com/docker-library/php/pull/725#issuecomment-443540114'; echo 'log_limit = 8192'; \\
echo; \\
echo '[www]'; \\
echo '; if we send this to /proc/self/fd/1, it never appears'; \\
echo 'access.log = /proc/self/fd/2'; \\
echo; \\
echo 'clear_env = no'; \\
echo; \\
echo '; Ensure worker stdout and stderr are sent to the main error log.'; \\
echo 'catch_workers_output = yes'; \\
echo 'decorate_workers_output = no'; \\
} | tee php-fpm.d/docker.conf; \\
{ \\
echo '[global]'; \\
echo 'daemonize = no'; \\
echo; \\
echo '[www]'; \\
echo 'listen = 9000'; \\
} | tee php-fpm.d/zz-docker.conf
# Override stop signal to stop process gracefully
# https://github.com/php/php-src/blob/17baa87faddc2550def3ae7314236826bc1b1398/sapi/fpm/php-fpm.8.in#L163
STOPSIGNAL SIGQUIT
EXPOSE 9000
CMD ["php-fpm"]
上面的配置文件主要做了幾件事:
1. 安裝必須的環境依賴;
2. 編譯 PHP 相關插件;
3. 設置 PHP FPM。
將內容保存為 Dockerfile 後,可以使用下面的命令構建我們所需要的鏡像:
docker build -t soulteary/flarum:v0.1.0-beta.12 -f Dockerfile .
如果是在國內網絡環境編譯,可以使用下面的命令,加速編譯構建過程。
docker build --build-arg USE_CHINA_MIRROR=1 -t soulteary/flarum:v0.1.0-beta.12 -f Dockerfile .
準備好運行鏡像後,我們就可以準備運行目錄和容器編排配置文件了。
準備目錄結構
我們提供給 flarum 使用的目錄結構如下:
├── Dockerfile
├── conf
├── docker-compose.yml
├── logs
├── start.sh
└── wwwroot
將之前準備好的 "CodeBase" 目錄中的以下內容複製到 wwwroot 目錄下:
1. extend.php
· site.php
· flarum
· public
考慮到後續會安裝/卸載 flarum 插件,這個工作可以交給啟動腳本來做,至於啟動腳本怎麼編寫,可以耐心繼續往下看。
準備編排文件及 Nginx 配置
因為使用 FPM 模式的 PHP 環境,我們需要藉助 Apache / Nginx 的幫助來提供 Web 服務,我這裡選擇 Nginx。
使用 Nginx,需要分別配置兩個文件,先配置 conf/docker-nginx.conf:
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;
# 交給 Traefik 或者 SLB 處理,不開啟 Gzip
#gzip on;
include /etc/nginx/conf.d/*.conf;
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;
}
接著來配置 conf/vhost.conf 文件:
server {
listen 80;
server_name lab.com lab.io;
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 = /get-health {
access_log off;
default_type text/html;
return 200 'alive';
}
# Pass requests that don't refer directly to files in the filesystem to index.php
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;
}
# Gzip 交給 Traefik , 不針對文件類型做壓縮
}
上面配置中的 server_name 需要改為你的目標站點名稱。將兩個配置文件保存好後,我們繼續處理容器編排文件,完整的內容如下:
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
- ./conf/vhost.conf:/etc/nginx/conf.d/vhost.conf
- ./wwwroot:/wwwroot
links:
- php:php
extra_hosts:
- "${DOCKER_DOMAIN_NAME}:127.0.0.1"
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
- "traefik.http.routers.www-flarum.middlewares=https-redirect@file"
- "traefik.http.routers.www-flarum.entrypoints=http"
- "traefik.http.routers.www-flarum.rule=Host(`$DOCKER_DOMAIN_NAME`)"
- "traefik.http.routers.ssl-flarum.middlewares=content-compress@file"
- "traefik.http.routers.ssl-flarum.entrypoints=https"
- "traefik.http.routers.ssl-flarum.tls=true"
- "traefik.http.routers.ssl-flarum.rule=Host(`$DOCKER_DOMAIN_NAME`)"
- "traefik.http.services.ngx-flarum-backend.loadbalancer.server.scheme=http"
- "traefik.http.services.ngx-flarum-backend.loadbalancer.server.port=80"
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: "10m"
php:
image: ${DOCKER_PHP_IMAGE}
restart: always
expose:
- 9000
env_file: .env
volumes:
- ./logs:/var/log
- ./wwwroot:/wwwroot
networks:
- traefik
healthcheck:
test: ["CMD-SHELL", "pidof php-fpm"]
interval: 5s
retries: 12
logging:
driver: "json-file"
options:
max-size: "10m"
networks:
traefik:
external: true
與之對應的 .env 配置內容:
DOCKER_PHP_IMAGE=soulteary/flarum:v0.1.0-beta.12
DOCKER_NGINX_IMAGE=nginx:1.17.1-alpine
DOCKER_DOMAIN_NAME=lab.com
FLARUM_DB_HOST=flarum-db.lab.com
FLARUM_DB_NAME=flarum
FLARUM_DB_USER=flarum
FLARUM_DB_PASS=flarum
FLARUM_REDIS_HOST=flarum-redis.lab.com
FLARUM_REDIS_PORT=6379
FLARUM_REDIS_PASS=
FLARUM_REDIS_DBNO=0
FLARUM_REDIS_CONNECTION_PARAMETERS=
FLARUM_REDIS_CLIENT_OPTIONS=
FLARUM_REDIS_PREFIX=session:
FLARUM_REDIS_LOCKING=1
FLARUM_REDIS_SPIN_LOCK_WAIT=150000
FLARUM_REDIS_HANDLER_OPTIONS=
FLARUM_REDIS_TTS=3600
FLARUM_APP_DEBUG=true
FLARUM_APP_URL=//lab.com
同樣使用 docker-compose up -d 啟動服務,然後就能看到久違的安裝界面了。
又見面了,熟悉的Flarum安裝界面
參考上圖和上面的 .env 配置,就能夠完成 flarum 的安裝了。
安裝完畢
安裝收尾
默認安裝完畢之後,會在 wwwroot/config.php 路徑生成配置文件:
'debug' => false,
'database' =>
array (
'driver' => 'mysql',
'host' => 'flarum-db.lab.com',
'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://lab.com',
'paths' =>
array (
'api' => 'api',
'admin' => 'admin',
),
);
為了更好的進行動態配置和代碼集中管理,這裡可以使用環境變量替換掉 hard code 的內容。
'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,
'engine' => 'InnoDB',
'prefix_indexes' => true,
),
'url' => $_SERVER['FLARUM_APP_URL'],
'paths' =>
array (
'api' => 'api',
'admin' => 'admin',
),
);
完成 config.php 文件的修改後,便可以將文件同樣提交至代碼倉庫,進行保存管理。
另外,因為程序最初設計未考慮到容器環境,所以對於運行目錄並沒有單獨進行設計,導致簡單封裝後,需要對目錄進行權限調整;以及初次安裝完畢後,從遠端下載的前端資源文件,如果清理緩存後會導致界面不正常;以及前文提到的反覆更新軟件相關插件...
所以我們需要準備一個啟動腳本:start.sh
#!/usr/bin/env bash
# 關閉服務
echo "嘗試停止之前啟動的服務"
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 "獲取基礎鏡像"
docker pull $image
fi
done
# 清理之前存在的歷史文件,並將新文件同步到執行目錄中
if [ -d "wwwroot/vendor" ]; then
echo "清理已經存在的 Vendor 文件"
rm -rf wwwroot/vendor
fi
if [ -d "../codebase" ]; then
echo "同步 Vendor 文件"
cp -r ../codebase/vendor/ wwwroot/
fi
echo '備份前端資源'
cp -r wwwroot/public/assets/* ./backup-assets
mkdir -p ./backup-assets
# 清理 & 重建目錄
echo '清理緩存目錄'
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 '還原前端資源'
rm -rf ./wwwroot/public/assets
mkdir -p ./wwwroot/public/assets
cp -r ./backup-assets/* ./wwwroot/public/assets/
# 重啟服務
echo '重啟服務'
docker-compose up -d
# 修正文件權限
docker ps -q -f status=running -f name=_php_1 | while read container ; do
echo "修正容器 $container 權限"
docker exec $container chown -R www-data:www-data /wwwroot
docker exec $container chmod -R 755 /wwwroot/storage
docker exec $container chmod -R 755 /wwwroot/public/assets
done
支持中日韓文字搜索
其實更好的方案是使用 ELK 插件去做全文檢索,但是其實使用 MySQL 開啟全文檢索,對於訪問量不大的站點影響不大。
尤其是在幾乎不需要付出額外的成本,使用的機器資源也相對較低的前提下。
隨手寫一個 PHP 腳本,執行下面兩條命令,稍等片刻,flarum 就能夠支持中文、日文的索引了。
ALTER TABLE flarum_posts DROP INDEX content;
CREATE FULLTEXT INDEX content ON `flarum_posts` (`content`) WITH PARSER ngram;
ALTER TABLE flarum_discussions DROP INDEX title;
CREATE FULLTEXT INDEX title ON `flarum_discussions` (`title`) WITH PARSER ngram;
卸載插件
為了提高可維護性,這裡建議對不使用的插件進行卸載,以 flarum/auth-twitter` 和 `flarum/auth-facebook 為例,在 codebase 目錄執行:
composer remove flarum/auth-twitter flarum/auth-facebook
成功卸載插件,將看到下面的提示。
Dependency "flarum/core" is also a root requirement, but is not explicitly whitelisted. Ignoring.
Dependency "flarum/core" is also a root requirement, but is not explicitly whitelisted. Ignoring.
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 0 installs, 0 updates, 4 removals
- Removing league/oauth2-facebook (2.0.1)
- Removing league/oauth1-client (1.7.0)
- Removing flarum/auth-twitter (v0.1.0-beta.12)
- Removing flarum/auth-facebook (v0.1.0-beta.12)
Writing lock file
Generating autoload files
完成之後,記得重新執行上面的啟動腳本,將程序源碼做一次新的同步。
支持中文語言包
有一位網友(@csineneo)做的中文語言包,質量相對比較不錯,和上面卸載無用插件類似,使用 composer 進行安裝:
composer require csineneo/lang-simplified-chinese
安裝完畢之後,將看到類似日誌。
Using version ^1.12 for csineneo/lang-simplified-chinese
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Installing csineneo/lang-simplified-chinese (1.12.4): Downloading (100%)
Writing lock file
Generating autoload files
同樣的,需要在再次執行啟動腳本,同步更新過的代碼到flarum運行目錄。
最後
除了搭建 Flarum 主體外,完成持續集成的環境也很重要,可以考慮使用中的方案一個 Git Server,配置自動部署。
或許未來我會聊聊在十個月前,我們是如何對 Flarum 進行調整,使它適合用於多機環境的,以及如何打通微信掃碼登錄、如何使用更靠譜的附件上傳...
至於什麼時候寫,或許得等到再有群友提問,能激發起我的興趣的時候吧,:)
--EOF
1
閱讀更多 soulteary 的文章