本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

本文作者: 苏洋

创建时间: 2020年03月14日 统计字数: 8133字 阅读时间: 17分钟阅读 本文链接: https://soulteary.com/2020/03/14/ghost-running-in-docker-supports-alibaba-cloud-oss.html


让运行在 Docker 中的 Ghost 支持阿里云 OSS

最近在优化 Ghost 作为线上使用的内容管理后台,作为线上使用的系统,不同于内部 MIS ,可靠性和应用性能需要有一定保障。

解决性能问题,最简单的方案便是进行水平扩展,而我们知道,如果想要让一个服务做到水平可扩展,除了要将应用运行状态单独持久化外,也必须做到文件储存的持久化,云平台的对象储存就是一个很好的文件持久化方案。

Ghost 是一个典型的单体应用,v3.x 版本的容器化文档其实不多,而介绍如何使用 Aliyun OSS 的文档更是没有,折腾过程还是挺有趣的,记录下来,希望能够帮助到后面有需求的同学。

写在前面

官方文档在使用三方自定义储存部分其实写的不是很好:

  1. 文档有效性不敢恭维,虽然内容中提到支持阿里云,但是列表中的阿里云OSS插件仅针对于 1.x 版本,其中阿里云的 SDK 也比较旧,当时的 Node 环境也很陈旧。
  2. 自定义文档缺少技术细节、以及完整描述,需要通过实践和阅读源码去验证。
  3. 完全没提到如何在容器镜像,尤其是官方镜像中使用插件。

本文将通过相对流程化的容器方案,来解决以上问题。

之前的文章《从定制 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 来编写插件减少代码量了。

编写 OSS 储存插件

参考官方模版,以及阿里云 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 域名,请在这个字段里配置。

封装支持 OSS 插件的镜像

为了保证运行镜像性能足够高、尺寸相对较小,我们需要使用 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-storeFROM 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.0expose:- 2368environment:url: https://ghost.lab.iodatabase__client: mysqldatabase__connection__host: ghost-dbdatabase__connection__port: 3306database__connection__user: rootdatabase__connection__password: ghostdatabase__connection__database: ghostNODE_ENV: productionvolumes:# 这里参考前篇文章,或者本篇文章内容,选择性使用# 解决 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:- traefiklabels:- "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.7container_name: ghost-dbexpose:- 3306networks:- traefikrestart: alwaysenvironment:MYSQL_ROOT_PASSWORD: ghostvolumes:- ./localdb:/var/lib/mysqlnetworks:traefik:external: true

最后

本篇内容,以封装 Ghost 定制镜像简单说明了如何基于官方镜像进行扩展,并简单示范了 Docker Multistage Build,以及 Ghost 3.x 版本如何使用 Aliyun OSS。

或许下一篇内容会聊聊,Ghost 这类原本不支持 SSO 单点登录的应用如何快速接入 SSO。

--EOF


我现在有一个小小的折腾群,里面聚集了一些喜欢折腾的小伙伴。

在不发广告的情况下,我们在里面会一起聊聊软件、HomeLab、编程上的一些问题,也会在群里不定期的分享一些技术沙龙的资料。

喜欢折腾的小伙伴欢迎扫码添加好友。(请注明来源和目的,否则不会通过审核)

苏洋:关于折腾群入群的那些事​zhuanlan.zhihu.com

docker脚本安装 阿里云_让运行在 Docker 中的 Ghost 支持阿里云 OSS相关推荐

  1. 群晖NAS教程(二十一)、利用Docker安装DDNS动态域名解析,自动更新域名解析到公网IP(支持阿里云、腾讯云dnspod、Cloudflare、华为云)

    群晖NAS教程(二十一).利用Docker安装DDNS动态域名解析,自动更新域名解析到公网IP(支持阿里云.腾讯云dnspod.Cloudflare.华为云) 为了更好的浏览体验,欢迎光顾勤奋的凯尔森 ...

  2. 问题解决“Windows Installer 程序包有问题,此安装需要的程序不能运行。请与您的支持人员或程序包开发商联系”

    安装或者卸载软件时出现如下提示:Windows Installer 程序包有问题,此安装需要的程序不能运行.请与您的支持人员或程序包开发商联系 解决方法: 1.按Win+R打开运行,输入service ...

  3. Ubuntu 非常简单安装 docker 脚本安装

    内核版本检查 uname -a 检查 Device Mapper DeviceMapper自Linux 2.6被引入成为了Linux最重要的一个技术.它在内核中支持逻辑卷管理的通用设备映射机制,它为实 ...

  4. docker pull下载很慢_一文了解Docker容器技术的操作

    一文了解Docker容器技术的操作 前言 相信点进这篇文章的Coder,不管是在各大技术论坛上.技术交流群,亦或招聘网上,应该都有见到过Doker容器技术的面孔,随着社会节奏的加快以及迫于生活的压力, ...

  5. docker镜像内容如何查看_如何快速打通 Docker 镜像发布流程?

    作者 | 菜菜 责编 | 郭芮 YY妹:菜菜哥,我看了一下Docker相关的内容,但是还是有点迷糊. 菜菜:还有哪不明白呢? YY妹:如果我想用Docker实现所谓的云原生,我的项目该怎么发布呢? 菜 ...

  6. web项目上云_联想Filez—携手浙江中烟,发力“云”端,打造“烟草上云”新势能...

    中国烟草行业信息化建设始于上世纪九十年代,历经数十年的发展,已经迈入了更深层次的发展阶段.当前,烟草行业的信息化工作重点目标,是要建设一体化"数字烟草",适应数字经济发展新趋势,有 ...

  7. docker php安装gd扩展_给docker里的php安装gd扩展

    docker官方镜像为安装php扩展封装了函数,为开发者提供了很大的便利,以下以Dockerfile的形式演示安装gd扩展的方法,安装gd扩展需要安装几个依赖包,安装依赖包使用系统命令,安装命令根据基 ...

  8. 游戏中java未安装不了_游戏运行库和常见游戏安装问题的解决方法

    游戏运行库是玩游戏必不可少的东西,相信玩家们都遇到因为没装某某运行库而导致游戏无法安装或者玩不了的问题,今天小编就为大家列出了一些常见的运行库以及游戏安装和运行时的一些问题的解决方法. 首先小编要为大 ...

  9. linux docker漏洞,安装shadow或linux-pam的Alpine Linux Docker镜像有漏洞,附解决

    只要你在 Alpine Linux 系统 Docker 镜像中安装有 shadow 或 linux-pam 软件包,那么这个镜像是有安全漏洞的,需要尽快修补,要说明的是,其他 Alpine Linux ...

最新文章

  1. 160 - 13 badboy
  2. 干,认识Audio框架还因此发现一个雷
  3. Redis内存回收和持久化策略
  4. 华为鸿蒙为什么非要碰物联网?
  5. PAT (Basic Level) Practice1006 换个格式输出整数
  6. excel去重怎么操作_EXCEL根据进货、出货求库存怎么操作
  7. DAY1:尚学堂高琪JAVA(1~20)
  8. c语言编写while乘法表,用C语言的while循环,打印九九乘法表,
  9. VR分享会邀请函 | 如何利用VR影像创造商业应用新价值?
  10. 挑战52天背完小猪佩奇(第02天)
  11. UEFI+GPT引导实践篇 (UEFI引导安装64位Win7/Win8)
  12. PTA 7-192 浪漫的表白
  13. iphone SLO-MO, TIME-LAPSE, VIDEO,PANO
  14. 小游戏制作QQ宠物系列1 ---- 吹泡泡
  15. 一些linux常用操作(1)
  16. 成就亿万富翁的10条规则
  17. CLA182四位先行进位电路设计
  18. 如何学好模具设计,新手应该了解的UG塑胶模具设计知识
  19. warning MSB8028: The intermediate directory (Debug) contains files shared from an
  20. RFCN论文阅读笔记

热门文章

  1. js date 当前日志往后一个月_【应用实例】如何利用 Python 生成器 yield 监控日志?...
  2. 11旋转编码器原理图_雷恩PRECILEC I9H系列增量式编码器
  3. Hadoop环境搭建(二)CentOS7的下载与安装
  4. python教程:datetime与字符串互转
  5. python中wraps的详解
  6. Python正则表达式的7个使用典范
  7. php 多维数组按值排序,按子值对php多维数组排序
  8. php 浏览商品记录,php浏览历史记录
  9. 检测点是否在两条平行线段之间_解决最值问题的利器——垂线段最短
  10. source insight 注释乱码?(【File】 > 【Reload As Encoding…】 > 【Chinese Simplified (GB18030)】 > 选择后,点击load)