使用 Docker 和 Traefik v2 搭建 Flarum 輕論壇應用

使用 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 啟動服務,然後就能看到久違的安裝界面了。

使用 Docker 和 Traefik v2 搭建 Flarum 輕論壇應用

又見面了,熟悉的Flarum安裝界面

參考上圖和上面的 .env 配置,就能夠完成 flarum 的安裝了。

使用 Docker 和 Traefik v2 搭建 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


分享到:


相關文章: