本文使用「署名 4.0 國際 (CC BY 4.0)」許可協議,歡迎轉載、或從新修改使用,但須要註明來源。 署名 4.0 國際 (CC BY 4.0)html
本文做者: 蘇洋node
建立時間: 2020年03月14日 統計字數: 8133字 閱讀時間: 17分鐘閱讀 本文連接: soulteary.com/2020/03/14/…mysql
最近在優化 Ghost 做爲線上使用的內容管理後臺,做爲線上使用的系統,不一樣於內部 MIS ,可靠性和應用性能須要有必定保障。git
解決性能問題,最簡單的方案即是進行水平擴展,而咱們知道,若是想要讓一個服務作到水平可擴展,除了要將應用運行狀態單獨持久化外,也必須作到文件儲存的持久化,雲平臺的對象儲存就是一個很好的文件持久化方案。github
Ghost 是一個典型的單體應用,v3.x 版本的容器化文檔其實很少,而介紹如何使用 Aliyun OSS 的文檔更是沒有,折騰過程仍是挺有趣的,記錄下來,但願可以幫助到後面有需求的同窗。web
官方文檔在使用三方自定義儲存部分其實寫的不是很好:sql
本文將經過相對流程化的容器方案,來解決以上問題。docker
以前的文章《從定製 Ghost 鏡像聊聊優化 Dockerfile》、修理 Ghost 中文輸入法的 BUG 有提過,「如何對 Ghost 進行容器化封裝」,感興趣的同窗能夠了解下。數據庫
在「反覆橫跳」踩了一堆坑以後,相對穩妥的低成本維護方案即是爲 Ghost 編寫適合當前版本的儲存插件,並製做基於官方容器鏡像的補丁鏡像了。編程
在編寫插件以前,須要先確認官方環境中的 Node 版本,以肯定符號語法:
docker run --rm -it --entrypoint /usr/local/bin/node ghost:3.9.0-alpine -v
複製代碼
執行完上述命令,你將獲得 v12.16.1 的結果,看來能夠直接使用 async/await
來編寫插件減小代碼量了。
參考官方模版,以及Aliyun OSS SDK 完成儲存插件大概十幾分鍾就搞定了,相關代碼我已經上傳至 GitHub,若是須要二次封裝,能夠參考使用。
/**
* Ghost v3 Storage Adapter (Aliyun OSS)
* @author soulteary(soulteary@gmail.com)
*/
const AliOSS = require("ali-oss");
const GhostStorage = require("ghost-storage-base");
const { createReadStream } = require("fs");
const { resolve } = require("path");
class AliOSSAdapter extends GhostStorage {
constructor(config) {
super();
this.config = config || {};
this.oss = new AliOSS({
region: config.region,
accessKeyId: config.accessKeyId,
accessKeySecret: config.accessKeySecret,
bucket: config.bucket
});
this.ossURL = `${config.bucket}.${config.region}.aliyuncs.com`;
this.regexp = new RegExp(`^https?://${this.ossURL}`, "i");
this.domain = config.domain || null;
this.notfound = config.notfound || null;
}
async exists(filename, targetDir = this.getTargetDir("/")) {
try {
const { status } = await this.oss.head(resolve(targetDir, filename));
return status === 404;
} catch (err) {
return false;
}
}
delete() {
// it's unnecessary // Ghost missing UX } serve() { return function(req, res, next) { next(); }; } async read(options) { try { const { meta } = await this.oss.head(options.path); if (meta && meta.path) { return meta.path; } else { return this.notfound; } } catch (err) { console.error(`Read Image Error ${err}`); return this.notfound; } } async save(image, targetDir = this.getTargetDir("/")) { try { const filename = await this.getUniqueFileName(image, targetDir); const { url } = await this.oss.put(filename, createReadStream(image.path)); if (url && url.indexOf(`://${this.ossURL}`) > -1) { return this.domain ? url.replace(this.regexp, this.domain) : url; } else { return this.notfound; } } catch (err) { console.error(`Upload Image Error ${err}`); return this.notfound; } } } module.exports = AliOSSAdapter; 複製代碼
這裏支持的配置內容有:
{
"storage": {
"active": "ghost-aliyun-oss-store",
"ghost-aliyun-oss-store": {
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"accessKeySecret": "YOUR_ACCESS_SERCET",
"bucket": "YOUR_BUCKET_NAME",
"region": "oss-cn-beijing",
"domain": "https://your-public-domian",
"notfound": "https://s3-img.meituan.net/v1/mss_3d027b52ec5a4d589e68050845611e68/ff/n0/0k/4n/3s_73850.jpg"
}
}
}
複製代碼
其中 domian
、是可選項,若是你須要使用 CDN 域名,請在這個字段裏配置。
爲了保證運行鏡像性能足夠高、尺寸相對較小,咱們須要使用 docker multistage build方案。
先定義基礎鏡像,並安裝剛剛編寫的 Ghost Aliyun OSS 插件。
FROM ghost:3.9.0-alpine as oss
LABEL maintainer="soulteary@gmail.com"
WORKDIR $GHOST_INSTALL/current
RUN su-exec node yarn --verbose add ghost-aliyun-oss-store
複製代碼
接着定義運行使用的鏡像。
FROM ghost:3.9.0-alpine
LABEL maintainer="soulteary@gmail.com"
COPY --chown=node:node --from=oss $GHOST_INSTALL/current/node_modules $GHOST_INSTALL/current/node_modules
RUN mkdir -p $GHOST_INSTALL/current/content/adapters/storage/
RUN echo "module.exports = require('ghost-aliyun-oss-store');" > $GHOST_INSTALL/current/content/adapters/storage/ghost-aliyun-oss-store.js
複製代碼
參考以前的兩篇文章,若是想解決「不能正常進行中文輸入」的問題,而且提取出了構建後的內容,能夠在鏡像中添加下面的內容:
COPY ./docker-assets/admin-views $GHOST_INSTALL/current/core/server/web/admin/views
COPY ./docker-assets/built/assets $GHOST_INSTALL/current/core/built/assets
複製代碼
若是你不但願將配置單獨抽象爲文件,能夠添加下面的內容。
RUN set -ex; \
su-exec node ghost config storage.active ghost-aliyun-oss-store; \
su-exec node ghost config storage.ghost-aliyun-oss-store.accessKeyId YOUR_ACCESS_KEY_ID; \
su-exec node ghost config storage.ghost-aliyun-oss-store.accessKeySecret YOUR_ACCESS_SERCET; \
su-exec node ghost config storage.ghost-aliyun-oss-store.bucket YOUR_BUCKET_NAME; \
su-exec node ghost config storage.ghost-aliyun-oss-store.region oss-cn-beijing; \
su-exec node ghost config storage.ghost-aliyun-oss-store.domain https://your-public-domian; \
su-exec node ghost config storage.ghost-aliyun-oss-store.notfound https://s3-img.meituan.net/v1/mss_3d027b52ec5a4d589e68050845611e68/ff/n0/0k/4n/3s_73850.jpg; \
su-exec node ghost config privacy.useUpdateCheck false; \
su-exec node ghost config privacy.useGravatar false; \
su-exec node ghost config privacy.useRpcPing false; \
su-exec node ghost config privacy.useStructuredData false; \
複製代碼
固然,爲了更方便更新內容,抽象爲單獨的文件是更好的選擇,好比像下面這樣編寫 config.production.json
配置文件。
{
"server": {
"port": 2368,
"host": "0.0.0.0"
},
"privacy": {
"useUpdateCheck": false,
"useGravatar": false,
"useRpcPing": false,
"useStructuredData": false
},
"storage": {
"active": "ghost-aliyun-oss-store",
"ghost-aliyun-oss-store": {
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"accessKeySecret": "YOUR_ACCESS_SERCET",
"bucket": "baai-news-upload",
"region": "oss-cn-beijing",
"domain": "https://your-public-domian",
"notfound": "https://s3-img.meituan.net/v1/mss_3d027b52ec5a4d589e68050845611e68/ff/n0/0k/4n/3s_73850.jpg"
}
}
}
複製代碼
上面聊了許多定製化的選項,那麼一個最小可用的容器編排配置是什麼樣的呢?其實大概不到十行,就足以知足咱們的基礎需求。
FROM ghost:3.9.0-alpine as oss
WORKDIR $GHOST_INSTALL/current
RUN su-exec node yarn --verbose add ghost-aliyun-oss-store
FROM ghost:3.9.0-alpine
LABEL maintainer="soulteary@gmail.com"
COPY --chown=node:node --from=oss $GHOST_INSTALL/current/node_modules $GHOST_INSTALL/current/node_modules
RUN mkdir -p $GHOST_INSTALL/current/content/adapters/storage/
RUN echo "module.exports = require('ghost-aliyun-oss-store');" > $GHOST_INSTALL/current/content/adapters/storage/ghost-aliyun-oss-store.js
複製代碼
將上面的內容保存爲 Dockerfile
,若是須要其餘的功能,能夠參考上面的內容進行適當修改。
docker build -t soulteary/ghost-with-oss:3.9.0 -f Dockerfile .
複製代碼
執行上面的命令,稍等片刻,一個衍生自Ghost官方鏡像,支持 OSS 的容器鏡像就構建完畢了。
這裏給出一個完整編排文件供你們參考,若是不想使用 Traefik,只須要將端口單獨暴露出來便可。
至於 Traefik 如何使用,參考我以往的文章,熟悉以後,你將會發現一片新的天地。
version: "3.6"
services:
ghost-with-oss:
image: soulteary/ghost-with-oss:3.9.0
expose:
- 2368
environment:
url: https://ghost.lab.io
database__client: mysql
database__connection__host: ghost-db
database__connection__port: 3306
database__connection__user: root
database__connection__password: ghost
database__connection__database: ghost
NODE_ENV: production
volumes:
# 這裏參考前篇文章,或者本篇文章內容,選擇性使用
# 解決 Ghost 中文輸入的問題
# - ./docker-assets/built/assets:/var/lib/ghost/versions/current/core/built/assets:ro
# - ./docker-assets/admin-views:/var/lib/ghost/current/core/server/web/admin/views:ro
- ./config.production.json:/var/lib/ghost/config.production.json
extra_hosts:
- "ghost.lab.io:127.0.0.1"
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
- "traefik.http.routers.ghostweb.entrypoints=http"
- "traefik.http.routers.ghostweb.middlewares=https-redirect@file"
- "traefik.http.routers.ghostweb.rule=Host(`ghost.lab.io`)"
- "traefik.http.routers.ghostssl.middlewares=content-compress@file"
- "traefik.http.routers.ghostssl.entrypoints=https"
- "traefik.http.routers.ghostssl.tls=true"
- "traefik.http.routers.ghostssl.rule=Host(`ghost.lab.io`)"
- "traefik.http.services.ghostbackend.loadbalancer.server.scheme=http"
- "traefik.http.services.ghostbackend.loadbalancer.server.port=2368"
networks:
traefik:
external: true
複製代碼
將上面內容保存爲 docker-compose.yml
,使用 docker-compose up -d
啓動應用,最後訪問配置裏定義的域名便可開始使用這個支持 OSS 功能的 Ghost 。
固然,若是你沒有線上數據庫,也可使用 docker-compose
啓動一個數據庫:
version: '3'
services:
db:
image: mysql:5.7
container_name: ghost-db
expose:
- 3306
networks:
- traefik
restart: always
environment:
MYSQL_ROOT_PASSWORD: ghost
volumes:
- ./localdb:/var/lib/mysql
networks:
traefik:
external: true
複製代碼
本篇內容,以封裝 Ghost 定製鏡像簡單說明了如何基於官方鏡像進行擴展,並簡單示範了 Docker Multistage Build,以及 Ghost 3.x 版本如何使用 Aliyun OSS。
或許下一篇內容會聊聊,Ghost 這類本來不支持 SSO 單點登陸的應用如何快速接入 SSO。
--EOF
我如今有一個小小的折騰羣,裏面彙集了一些喜歡折騰的小夥伴。
在不發廣告的狀況下,咱們在裏面會一塊兒聊聊軟件、HomeLab、編程上的一些問題,也會在羣裏不按期的分享一些技術沙龍的資料。
喜歡折騰的小夥伴歡迎掃碼添加好友。(請註明來源和目的,不然不會經過審覈)