这篇文章简单介绍了如何通过 Travis CI 自动构建 Jekyll 博客、发布到 GAE,并配置使用自定义域名和 Cloud Flare 代理。

Google App Engine 基于 Google Cloud Platform,是谷歌提供的搭建可伸缩 Web 应用和移动后端的平台,属于 PaaS 。
目前支持 Python、Java、PHP、Go,当然,也支持静态文件,因而是 Github Pages 的替代方案之一。
(插一句题外话,当时的选项有 Surge、PubStorm、Netlify 这样的静态文件托管服务和 GAE、Heroku 这样的 PaaS、CaaS。然而 Surge 的 HTTPS 要收不菲的钱、PubStorm 即将关门、其他 PaaS、CaaS 有限的免费配额拿来做静态博客托管又有点浪费,最后就选定了 Netlify 和 GAE。)

开始之前

首先你要有一个 Jekyll Site 的 Git Repository,也是你最终要部署的站点。
这个站点的源码应该托管在 GitHub,以使用 Travis CI。为了使用 Travis CI 的命令行客户端,一个 ruby 环境也是必须的 —— 不过既然你都在用 Jekyll,这个应该没问题。
然后你需要一个 Google 账号,激活云平台。
如果不想使用 GAE 的子域名,就需要有一个你自己的域名,至少你需要有配置域名 CNAME 记录的权限。
如果你需要在墙内访问你的站点,就需要域名的整个所有权,因为 Cloud Flare 要求 Free Plan 将 NS 记录指向他们的服务器。

创建 GAE 项目

在 GAE 中创建一个项目;根据需要设置项目的区域,比如我选择的就是亚洲东北部。
Create project

创建 GAE 的私钥

你需要为对应的GAE项目创建服务账户,这样 Travis CI 才能通过服务账户的私钥获得部署的权限。(理论上你可以直接下载项目默认服务账户的私钥,但是推荐创建一个专用的服务账户。)
打开 Google Cloud Platform 的控制台,确定上方项目列表中选中的项目是你刚创建的项目,展开控制台左侧主菜单,点击 IAM和管理员,在打开的页面左侧面板点击 服务账户,在右侧的面板中点击 创建服务账户。
在弹出的窗口中给服务账户起个名字,如 Travis-CI,角色选择 项目 - 编辑者,勾选 提供新的私钥,密钥类型选择 JSON,然后点击 创建。
妥善保存下载到的私钥文件,可以先存放在 Git Repository 的根目录,但是加密之前不要提交。通过这个私钥可以获取对你 GAE 项目的写权限。建议在执行过加密之后删除这个未加密的私钥文件。
Create Service Account

配置 GAE 项目

首先配置 GAE,让 GAE 帮我们提供静态站点服务。
GAE 的配置通过 app.yaml 进行,这个文件需要放在 Git Repository 的根目录下,详细说明可以参考官方文档。大致地说明一下用到的一些配置:

  • runtime: python27 指定运行环境,使用 Python 2.7 。
  • threadsafe: false 关闭线程安全,允许并行服务。
  • skip_files: 指定不部署的文件。我跳过了除 _sites 外的所有文件夹和根目录下的大部分文件。
  • handlers 配置处理单元,可以配置多个,按照顺序匹配执行。 客户端的请求会按照url进行匹配,使用 static_dir 表示整个文件夹作为静态文件提供服务,使用 static_files 表示指定的一个(或一组)文件作为静态文件提供服务。使用static_files 匹配一组文件时,要注意同时指定 upload 的配置,告知 GAE 如何寻找对应的文件。
  • libraries: 指定要引用的库。这里我们需要引用 webapp2

完整的配置如下:

runtime: python27
api_version: 1
threadsafe: false

skip_files:
- ^(_drafts|_posts|assets|files|.sass-cache)/(.*)$
- ^[a-zA-z]+.(xml|txt|html|md|yml|ico)$
- Gemfile
- gae.json
- gae.json.enc

handlers:
- url: /
  static_files: _site/index.html
  upload: _site/index\.html
  secure: always

- url: /((archives|atom|categories|favicon|index|rss|sitemap|tags)\.(xml|txt|html|ico))$
  static_files: _site/\1
  upload: _site/.*\.(xml|txt|html|ico)$
  secure: always

- url: /assets
  static_dir: _site/assets
  secure: always

- url: /files
  static_dir: _site/files
  secure: always

- url: /(.+)/
  static_files: _site/\1/index.html
  upload: _site/.*/index\.html
  secure: always

- url: /(.*)
  static_files: _site/404.html
  upload: _site/404\.html
  secure: always

libraries:
- name: webapp2
  version: "latest"

配置 Travis CI

然后配置 Travis CI,让 Travis CI 在我们 push 博客文章之后自动 build 并发布到 GAE。
Travis CI 的配置通过 .travis.yml 进行,这个文件同样需要放在 Git Repository 的根目录下,详细说明见官方文档。几个需要注意的配置如下:

  • language: ruby 使用 ruby 环境。
  • rvm: - 2.2.5 使用 rvm,指定一个较新的 ruby 版本。
  • branches: only: - master 只处理 master 分支。
  • sudo: false 不需要 sudo 权限,Travis CI 会在 Docker 容器中执行构建,可以缩短构建的等待时间。
  • script: - bundle exec jekyll build 构建 Jekyll 站点,不需要指定 JEKYLL_ENV。
  • deploy: 指定部署方式。配置 provider: gae 指定部署到 GAE,配置 project: your-project-id 指定要部署的 GAE项目,配置 skip_cleanup: true 不清理生成文件, 配置 keyfile: your-service-account-key.json 指定 GAE 的私钥(注意是不加密的文件名,不带 .enc 后缀)。
  • before_install: - openssl ... 解密私钥文件,这条配置不需要手动写,会由下一步的加密过程自动加入。

完整配置如下:

language: ruby

rvm:
- 2.2.5

branches:
  only:
  - master

sudo: false

before_install:
- openssl aes-256-cbc -K $encrypted__key -iv $encrypted__iv -in gae.json.enc -out gae.json -d

script:
- bundle exec jekyll build

deploy:
  provider: gae
  skip_cleanup: true
  keyfile: gae.json
  project: blog-

加密 GAE 的私钥

安全起见,GAE 的私钥需要加密,才能添加到 Git Repository 中。
在站点根目录下执行如下命令,将私钥文件加密并将解密需要的信息加入 .travis.yml

travis encrypt-file your-service-account-key.json --add

加密会生成一个.enc结尾的加密文件,安全起见,请删除未加密的原文件,并将源文件名加入 .gitignore 在提交时进行排除。
参考 Travis CI 加密相关的文档

Jekyll 的排除设置

上述的这些配置文件,需要加入 _config.xmlexclude 节在构建时进行排除。

exclude:
  - vendor
  - Gemfile
  - Gemfile.lock
  - .gitignore
  - .travis.yml
  - app.yaml
  - gae.json
  - gae.json.enc

测试上述配置

至此,自动部署的基本配置已经完成了,现在可以提交并推送代码到 GitHub 了。稍候片刻,Travis CI 应该会自动触发 build,生成 Jekyll 站点并部署到 GAE。
Travis CI Auto Build
如果一切顺利,你应该可以通过https://your-project-id.appspot.com访问你的站点了。

配置 GAE 自定义域名和 SSL

GAE 默认提供一个子域名,并提供 SSL 支持。如果你不想使用 GAE 默认的子域名,可以添加自己的域名;为自己的域名上传证书之后,也可以开启 SSL 支持。官方文档中 使用自定义域名和SSL 一节详述了相关配置,这里我们简单介绍一下。
打开 Google Cloud Platform 的控制台,确定上方项目列表中选中的项目是你刚创建的项目,展开控制台左侧主菜单,点击 App引擎,在打开的页面左侧面板点击 设置,在右侧的面板中点击 自定义网域 标签,再点击下面的 添加 自定义网域 按钮。
在 选择要使用的网域 中,选择你需要绑定的自定义域名并点击 继续。如果以前没有添加过,则选择 验证新网域,输入自定义域名,点击 验证;网络管理员中心 的窗口会弹出,指引你进行下一步的验证域名所有权;在列表中选择你的域名提供商,根据提示操作,或者选择 其他 ,则有 TXT 记录和 CNAME 记录两种验证方式。完成验证之后,就可以将自定义域名添加到项目。
自定义域名添加之后,还需要在你的域名解析服务商处将域名指向谷歌,推荐直接配置 CNAME 记录到 ghs.google.com ;如果是根域名,推荐配置 A记录,可以在 自定义网域 标签下找到 IP地址列表。
GAE Custom Domain
刚添加的域名是没有 SSL 支持的,上传证书之后才能通过 HTTPS 访问。GAE支持下列几种证书(基本都支持):

  • 单域名证书
  • 自签名证书
  • 泛域名证书
  • 多域名证书

注意,Cloud Flare 的 Origin Certificate 是不被 GAE 支持的,你可以使用 Let’s Encrypt 的证书或者 自签名的证书。如何生成自签名证书可以参考这篇文章
点击 自定义网域 标签右边的 SSL 证书 标签,点击 上传新证书 按钮,填写 名称,选择公钥证书、私钥证书文件或者直接粘贴文件内容,点击上传。点击刚刚上传的证书,在 为以下自定义网域启用 SSL 的列表中勾选对应域名,点击 保存,新证书即可应用于当前 GAE 项目的指定自定义域名。
GAE Upload SSL Certificate
GAE Enable SSL

配置 Cloud Flare 代理

现在你的站点还无法在墙内访问;如果使用自签名的证书,也无法被浏览器信任。Cloud Flare 的代理可以解决这两个问题,只需要把你的域名添加到 Cloud Flare 并开启 Proxy 和 SSL。
登陆 Cloud Flare 的账户,点击右上角的 + Add Site,按照向导添加域名,并在域名服务商处修改 NS 记录。在新建站点的过程中,确保你之前的 DNS 记录都正确地导入了,或者在新建站点之后重新添加漏掉的记录。
添加站点之后,在 DNS 页面下找到你添加到 GAE 的域名的对应记录,确保右边的 Status 是橘色的云朵标志(DNS and HTTP proxy),即代理已经开启。等待新的 DNS 记录生效,就可以在墙内访问站点了。
CF Proxy
在 Crypto 页面,将 SSL 调整为 Full,即可启用 SSL 支持。如果你在 GAE 上传了正式的 SSL 证书(非自签名的证书,如 Let’s Encrypt 的证书),可以将 SSL 调整为 Full (strict),即严格模式,会验证源站点的SSL证书是否被信任(非严格模式只要求源站点支持SSL即可,因而自签名证书只能使用非严格模式),理论上更安全。
CF Enable SSL
在 Crypto 页面,还可以启用 HSTS(HTTP严格传输层安全)。
CF HSTS
至此,在 GAE 部署 Jekyll 博客的配置就完成了。目前本博客就在使用这个配置。每次 Push 新的文章到 GitHub,Travis CI就会自动进行构建,并发布到 GAE;通过 Cloud Flare,在墙内也可以访问,并且连接是 SSL 加密的。