新坑:Hugo 全自动部署

前些日子的 Hexo 自动化构建折腾起来真心有趣,但是速度好慢,几十篇文章从构建到压缩处理需要等待至少好几秒的时间,构建时内存占用也很高,虽然只是一时但就是让人兴致大减。

所以肯定得换构建引擎,然后就是 Hugo 了,Go 语言编写,速度没得说,问题是扩展功能不多,所以今天可是折腾了一天,笑。

实现效果:

  • 本地保存即时全自动发布。

自动化流程:

  • 本地编辑器保存,webdav (或者其他你喜欢的方式)服务自动上传服务器。
  • 服务器监测到有文件上传,启动图片压缩程序,自动压缩图片。
  • 图片压缩之后,启动 Hugo 构建页面。
  • Hugo 构建结束,启动 Gulp 压缩网页资源文件。
  • 构建任务结束,推送到所有服务器。
  • 最后由各个节点的 Nginx 服务器向外展示网站。

为什么没有部署到 Github?也没有上传图片到 CDN?

  1. 因为 Github 不支持绑定域名的站点使用 HTTPS 访问;
  2. 我服务器多,自动同步,普通 CDN 太慢。

为了简化初次部署的繁琐,以上操作只需要一行命令即可启动。在此之前还是先解释一下整个流程的原理。

一共两个镜像:

REPOSITORY                 SIZE
zuolan/hugo                19.3 MB
zuolan/hugo:minify         79.73 MB

1. 自动上传服务器

这一步通过 Nextcloud 实现,我日常就是用这种私有云盘,所以这一步我跳过,如果你需要实现自动上传,可以考虑其他方法。比如 scp、rsync 之类的,Windows 下自动同步的工具更是数不尽啦。

2. 主镜像:zuolan/hugo

整合功能包括:

2.1. 服务器监测到有文件上传

这一步的工具就是 inotifywait 了,已经整合到容器中,当数据卷有变动就会触发一系列动作。

2.2. 图片压缩程序

就是一个脚本,自动压缩图片,使用的是 TinyPNG 的服务,虽然 Linux 有不少图片处理程序,但是压缩算法不好,这里使用的在线压缩服务非常不错,这个功能也已经整合到容器中。

因为是在 sh 中执行的脚本,平时在 bash 和 zsh 养成的习惯写法在 sh 中不一定有效,所以暂时先把这部分的脚本写死放进了容器中,等明天完善了再把这部分代码丢上来。

2.3. Hugo 构建页面

这个作为整个流程最关键的一步,近百篇文章耗时也只有几十毫秒。

构建一般不会出错,但是如何触发下一步压缩网页文件就是让我折腾了一天的关键点。

划重点时间

如何触发下一步压缩构建好的网页文件呢?

首先想到的就是使用前端打包工具执行,但是一个 Node.js 环境就会使我的主镜像体积翻了一番,这点不能忍,我要努力把主镜像压缩到仅有 6 MB 的存储体积。

所以把 Hugo 和 Gulp 放一块是不可能的,毕竟不是每个人需要压缩功能。

所以就想到了在容器中运行容器,但是需要在容器中装 Docker 环境,镜像体积必然破百 MB,而且每次容器重启还得重新拉压缩镜像,太二,所以不予考虑。

那,就使用 docker-compose 吧,通过挂载 docker.sock 与宿主机通信,避免了上面的问题,试了一下,只有 22 MB,还能接受。

但是额外加一个 docker-compose 还是让我不舒服,我要做到尽可能小的体积,所以,我选择 curl。

直接对着 unix socket 来操作。

2.3.1. 在容器中启动容器

API 文档地址:https://docs.docker.com/engine/api/v1.26/

API 返回的是 json 格式,本来用 jq 可以搞定的,迅速取到相应的值,但是不想装额外的软件包,所以强行使用 cut 把结果剪切出来了,好粗暴,笑。

ID=$(curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" -d '{"Image": "zuolan/hugo:minify", "Volumes": {"/hugo/public":"/work/html"}}' -X POST http:/v1.26/containers/create | cut -d: -f2 | cut -d, -f1 | cut -d\" -f2)
curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/start
curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/wait
curl --unix-socket /var/run/docker.sock "http:/v1.26/containers/$ID/logs?stdout=1"
curl --unix-socket /var/run/docker.sock -X DELETE http:/v1.26/containers/$ID

2.3.2. 主镜像源代码

run.sh

#!/bin/sh
SEPARATOR="================================================================"
echo "正在执行初次构建:"
echo $SEPARATOR
echo "正在构建页面:"
hugo
echo "正在压缩网页资源:"
ID=$(curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" -d '{"Image": "zuolan/hugo:minify", "Volumes": {"/hugo/public":"/work/html"}}' -X POST http:/v1.26/containers/create | cut -d: -f2 | cut -d, -f1 | cut -d\" -f2)
curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/start
curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/wait
curl --unix-socket /var/run/docker.sock "http:/v1.26/containers/$ID/logs?stdout=1"
curl --unix-socket /var/run/docker.sock -X DELETE http:/v1.26/containers/$ID
echo "页面已经发布,容器进入监视状态。"
VOLUMES="/hugo"
INOTIFY_EVENTS="create,delete,modify,move"
INOTIFY_OPTONS="--monitor --exclude=public"
inotifywait -rqe ${INOTIFY_EVENTS} ${INOTIFY_OPTONS} ${VOLUMES} | \
    while read -r notifies;
    do
        echo $SEPARATOR
        echo "文件有变动:"
        echo "$notifies"
        echo "正在重新构建页面:"
        hugo
        ID=$(curl --unix-socket /var/run/docker.sock -H "Content-Type: application/json" -d '{"Image": "zuolan/hugo:minify", "Volumes": {"/hugo/public":"/work/html"}}' -X POST http:/v1.26/containers/create | cut -d: -f2 | cut -d, -f1 | cut -d\" -f2)
        curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/start
        curl --unix-socket /var/run/docker.sock -X POST http:/v1.26/containers/$ID/wait
        curl --unix-socket /var/run/docker.sock "http:/v1.26/containers/$ID/logs?stdout=1"
        curl --unix-socket /var/run/docker.sock -X DELETE http:/v1.26/containers/$ID
        echo "新的页面构建完成。"
        echo $SEPARATOR
    done

Dockerfile

FROM alpine
WORKDIR /hugo
ENV GIT_USER=izuolan GIT_EMAIL=i@zuolan.me
COPY run.sh /run.sh
RUN apk add --no-cache inotify-tools hugo curl && \
    chmod a+x /run.sh
VOLUME ["/hugo"]
CMD ["/run.sh"]

3. 压缩网页文件

因为 Hugo 是使用 Go 语言写的,在网页处理上缺少相应的功能,所以为了压缩网页,这里使用 Gulp 来自动化压缩网页。

比较遗憾的是我不知道 Gulp 有哪些可以实现 replace (替换 link)效果的插件(类似的插件),所以没能实现把外联式资源、图片嵌入网页的功能。哪位大佬知道请告知~注意是不能修改 HTML 的情况下实现。如果修改了主题文件就不能适用于他人了。

3.1. Gulp 压缩网页资源文件

某一次的自动压缩过程:

$ docker-compose logs -f
Attaching to hugo, minify, blog
hugo      | 正在执行初次构建:
hugo      | ================================================================
hugo      | 正在构建并部署页面:
hugo      | Started building sites ...
hugo      | Built site for language en:
hugo      | 0 draft content
hugo      | 0 future content
hugo      | 0 expired content
hugo      | 6 regular pages created
hugo      | 16 other pages created
hugo      | 0 non-page files copied
hugo      | 14 paginator pages created
hugo      | 7 tags created
hugo      | 5 categories created
hugo      | total in 584 ms
hugo      | 页面已经发布,容器进入监视状态。
minify    | 正在执行资源文件压缩:
minify    | ================================================================
minify    | 
minify    | > @ build /work
minify    | > gulp build
minify    | 
minify    | [12:50:26] Requiring external module babel-register
minify    | [12:50:28] Using gulpfile /work/gulpfile.babel.js
minify    | [12:50:28] Starting 'build'...
minify    | [12:50:28] Starting 'minify-html'...
minify    | [12:50:29] Finished 'minify-html' after 1.43 s
minify    | [12:50:29] Starting 'minify-js'...
minify    | [12:50:32] Finished 'minify-js' after 2.33 s
minify    | [12:50:32] Finished 'build' after 3.77 s
minify    | 页面已经压缩,容器进入监视状态。

3.1.1. 压缩镜像源代码

Dockerfile

FROM mhart/alpine-node
WORKDIR /work
COPY . /work
RUN mkdir /work/html && \
    npm install
VOLUME ["/work/html"]
CMD ["npm", "run", "build"]

gulpfile.babel.js

import gulp from 'gulp'
import htmlmin from 'gulp-htmlmin'
import uglify from 'gulp-uglify'
import runSequence from 'run-sequence'

gulp.task('minify-html', () => {
  return gulp.src('html/**/*.html')
    .pipe(htmlmin({
      collapseWhitespace: true,
      minifyCSS: true,
      minifyJS: true,
      removeComments: true,
      useShortDoctype: true,
    }))
    .pipe(gulp.dest('./html'))
})

gulp.task('minify-js', () => {
    return gulp.src('./html/**/*.js')
        .pipe(uglify())
        .pipe(gulp.dest('./html'));
});

gulp.task('build', (callback) => {
  runSequence('minify-html','minify-js', callback)
})

package.json

{
  "private": true,
  "scripts": {
    "build": "gulp build"
  },
  "devDependencies": {
    "babel-preset-es2015": "^6.5.0",
    "babel-register": "^6.5.2",
    "gulp": "^3.9.1",
    "gulp-cli": "^1.2.1",
    "gulp-htmlmin": "^1.3.0",
    "gulp-uglify": "^2.0.0",
    "run-sequence": "^1.1.5"
  },
  "babel": {
    "presets": [
      "es2015"
    ]
  }
}

3.2. 推送到所有服务器

这个就各显神通了,文件都有,自己看着办。

不知不觉写到 23 点 49 分,怎么使用就留到明天吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Docker — 云时代的程序分发方式 要说最近一年云计算业界有什么大事件?Google Compute Engi...
    ahohoho阅读 15,505评论 15 147
  • 在现在的前端开发中,前后端分离、模块化开发、版本控制、文件合并与压缩、mock数据等等一些原本后端的思想开始...
    Charlot阅读 5,428评论 1 32
  • 0. 前言 docker是什么?docker是用GO语言开发的应用容器引擎,基于容器化,沙箱机制的应用部署技术。可...
    sessionboy阅读 3,836评论 2 49
  • 转载自 http://blog.opskumu.com/docker.html 一、Docker 简介 Docke...
    极客圈阅读 10,468评论 0 120
  • 二进制和八进制二进制以 0b(0B)开头八进制以0o(0O)开头 转换成十进制 检查数值是否为有限或者为空 解析整...
    _by_w_z阅读 646评论 0 1