为什么

  1. 不想要开源博客的代码
  2. 主要是为了配置简单, 用AWS S3来储存Hexo生成的静态文件, CloudFront做CDN,还有Route53做域名管理。再加上用CodeBuild的webhook用来监听Github的源码更新。

Hexo初始设置

根据官网来初始化Hexo项目 (默认nodejs和yarn环境)

1
2
3
hexo init my-blog
cd my-blog
yarn add hexo-theme-butterfly

使用hexo-theme-butterfly来写博客。把_config.yml里把Hexo的主题改成butterfly

1
2
3
4
# Extensions
## Plugins: https://hexo.io/plugins/
## Themes: https://hexo.io/themes/
theme: butterfly

然后根据butterfly快速开始把本地node_modules/hexo-theme-butterfly下的_config.butterfly.yml复制到项目根目录下。之后就可以根据需求开启/关闭博客的一些功能。

运行 hexo serve -p 4000, 然后在浏览器打开localhost:4000预览结果.
localhost butterfly

AWS CodeBuild

登陆AWS console, 在顶部搜索CodeBuild服务。点击”Create build project”,

  • 填构建的工程名称
  • 在”Source”栏
    • 点击选择Github作为代码的源
      codebuild source
    • 勾选Rebuild every time a code change is pushed to this repository, 这样就注册了一个webhook, 可以让CodeBuild在Github代码更新的时候自动重新编译/发布博客
      codebuild source webhook
  • “Environment”栏依次选择
    • OS: Amazon Linux 2
    • Runtime(s): Standard
    • Image: aws/codebuild/amazonlinux2-x86_64-standard:5.0
    • 最后选择或者创建一个service role.
      codebuild environment
      • 备注一下: 在AWS中role主要包含了当前服务可以调用其他AWS服务的**权限(Permission)**。比如CodeBuild需要在打包阶段将结果写入S3, 那么被挂载到CodeBuild的Role应该包含有写入S3的权限.
  • “Buildspec”使用默认选项,具体描述在源码目录下的buildspec.yml中, 下面是当前博客使用的buildspec.yml, 具体配置参考AWS CodeBuild的官方文档
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    version: 0.2

    phases:
    # 安装所需要的包
    install:
    runtime-versions:
    nodejs: 18 # 使用nodejs 18
    commands:
    - npm install
    - npm install -g hexo-cli
    build:
    on-failure: ABORT # 错误发生将自己停止编译
    commands:
    - echo Start building website
    - hexo generate # 使用hexo generate生成网站(public目录)

    artifacts: #生成目录在CodeBuild中指定为S3
    # 下面两个选项可以理解为:
    # 拷贝public目录下的所有文件到S3.
    files:
    - '**/*'
    base-directory: public
  • “Artifact”栏选择
    • Amazon S3为输出地址, 选择目标Bucket
    • Name还有path分别填”/“和”./“, 否则的话CodeBuild最后会将结果打包成一个文件夹放到S3根目录下, 而不是直接在根目录下。
    • 在”Additional configuration”下填入从AWS KMS的加密密匙。具体方法在下面说明。
      codebuild artifact
      codebuild artifact encryption
    • 这里不使用AWS S3默认的密匙是因为, S3的密匙是用于当前账户下的所有S3 Bucket. 自己创建的密匙可以提供更精细(fine grained)的配置

AWS Key Management Service (KMS)

简单来说, KMS就是一个提供了集中管理/储存加密密匙的服务. 比如说:

  • S3需要从KMS获取密匙来加密上传的对象
  • CloudFront需要在用户发送网络请求的时候从KMS获取密匙来解密S3的对象,然后再将结果返回给用户.

这样的话, 就不需要硬编码密匙, 同时KMS也提供了每年进行密匙的更新。下面就创建一个KMS密匙 (默认选项).
kms create key

点击新创建的密匙, 查看并编辑Key Police. Key Police决定了那些AWS服务可以使用这个密匙

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{
"Version": "2012-10-17",
"Id": "key-consolepolicy-3",
"Statement": [
// AWS自带的, 说明当前账号可以对密匙进行修改
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<<account_id>>:root"
},
"Action": "kms:*",
"Resource": "*"
},
// 下面两个授权CloudFront和CodeBuild使用密匙进行加密和解密。
{
"Sid": "AllowCloudFrontServicePrincipalSSE-KMS",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": [
"kms:Decrypt",
"kms:Encrypt",
"kms:GenerateDataKey*"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<<account_id>>:distribution/<<cloudfront_id>>"
}
}
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<<account_id>>:role/service-role/<<service-role>>"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
}
]
}

CloudFront

AWS CloudFront可以用于缓存应用层面的数据和对象, 并加速对客户端的请求的响应. 登陆AWS创建一个新的CloudFront分布节点

  • “Origin”一栏选择S3为源. 这里选择推荐OAC (Origin Access Control), 即所有的网络请求只有通过CloudFront来获取资源, 并且S3将无法直接从网络访问.
    cloudfront source
    • 备注: 当前S3的website endpoint不支持 OAC, 所以不在这里使用.
  • “Default cache behavior”中, 为了数据传输安全,把Viewer protocol policy改成Redirect HTTP to HTTPS
  • “Web Application Firewall (WAF)”中, 可以勾选打开网络防火墙, 基础为每个月$8

创建为新的CloudFront节点以后, 可以得到一个CloudFront的URL如,d3lx8t3p3ypfbh.cloudfront.net, 在这之后需要更新S3的访问权限只让CloudFront访问

S3

如果CodeBuild配置成功的话, 目标S3下应该会出现以下文件,
s3 landing

  • 到“Property”下打开”Static website hosting”,
    • 将“Index document” 设置为”index.html”
  • 到”Permission”下修改S3访问权限, 可以根据官网文档配置, 或者直接在CloudFront的Origin中直接复制:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    {
    "Version": "2012-10-17",
    "Statement": {
    "Sid": "AllowCloudFrontServicePrincipalReadOnly",
    "Effect": "Allow",
    "Principal": {
    "Service": "cloudfront.amazonaws.com"
    },
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::<S3 bucket name>/*",
    "Condition": {
    "StringEquals": {
    "AWS:SourceArn": "arn:aws:cloudfront::<AWS account ID>:distribution/<CloudFront distribution ID>"
    }
    }
    }
    }

注意, 在这时S3的”Block public access”还是启用的(绿色on).

Route53 & ACM (AWS Certificate Manager)

这里将配置一个域名并将网络流量定向到CloudFront.

  • 首先先申请一个域名, 一般申请成功的话AWS会自动帮你创建一个Hosted Zone. 这里要注意的是要把申请完域名的名称服务器 (Name Server)改成Hosted Zone的.
    • 点击进入Hosted Zone, 可以看到有4个名称服务器的地址
      route53 hosted zone/name server
    • 打开左边菜单, 点击”Registered Domain”, 点击自己的域名, 然后在页面左上角”Action”下拉菜单中选择”Edit name servers”并更新名称服务器。
  • 接下来需要通过自己的域名将网络流量导给CloudFront.

首先要同过AWS Certificate Manager申请一个SSL/TLS证书, 这个证书可以让不同计算机系统在网络上安全地相互通信。

  • ***.ryon49.com**将映射在ryon49.com下的所有子域名, 如www.ryon49.com, blog.ryon49.com等等。
    acm create certificate
  • 创建完后点击进入, 选择”Create records in Route53”, 将自动把证书导入Route53. 返回Hosted Zone, 可以看到新增加了一条cname记录
    route53 hosted zone/cname

关联Route53和CloudFront:

  • 返回CloudFront的节点配置页面, 在setting一栏点击Edit
    • 在”Alternate domain name (CNAME)”下填写www.ryon49.com和blog.ryon49.com, 这样对这两个地址的网络访问都会被导向个CloudFront节点
    • 在”Custom SSL certificate”中下拉选择刚刚创建过的SSL证书.
    • 保存并等待CloudFront更新节点
      cloudfront setting

返回Route53自己域名的Hosted Zone

  • 点击”Create record”创建新的记录, 创建两条记录并同时指向CloudFront, 可以看到两条记录的名称都跟上面的对应.
    route53 hosted zone/cname

这样的话就可以通过在浏览器访问 www.ryon49.com 和 **blog.ryon49.com**看到最初显示的Hexo界面了.

Troubleshoot (错误Debug)

  1. CloudFront的Access Denied错误

正常情况下当我们访问博客的时候, 如wwww.ryon49.com, 实际上我们访问的是www.ryon49.com/**index.html**这个页面. 但是CloudFront只会根据路径名找资源, 并不会找到index.html. 这种情况下

  • 登陆AWS,点击进入自己的CloudFront. 打开CloudFront左边的菜单并进入Functions,
  • 创建一个新的函数, 名字就叫append-index-html
    aws cloudfront create function
  • 函数代码可以在官方文档找到
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function handler(event) {
    var request = event.request;
    var uri = request.uri;

    // // Check whether the URI is missing a file name.
    if (uri.endsWith('/')) {
    request.uri += 'index.html';
    }
    // Check whether the URI is missing a file extension.
    else if (!uri.includes('.')) {
    request.uri += '/index.html';
    }

    return request;
    }
  • 保存更改, 然后点击发布(Publish), 发布以后可以在”Associated distributions”标签下于CloudFront进行关联. 点击”Add association”, 选中当前CloudFront的节点
    • “Event type”为Viewer request, 代表URL将在进入CloudFront时, 到达S3之前进行处理, 跟具体的资料可以在官网文档进行查看
    • “Cache behavior勾选Default就行。
  • 查看改变, 回到CloudFront节点设置页面. 在Behaviors标签下编辑(Edit)优先级为0的行为(behavior), 下拉找到”Function associations”就可以看到Viewer request已经关联了刚刚创建的CloudFront函数了.
  1. KMS加密/解密错误

一般由两种情况

  1. CodeBuild在UPLOAD_ARTIFACTS的阶段发生错误,
    aws codebuild kms error
    图片说明CodeBuild没有获取加密密匙的权限, 无法生成密匙. 这种情况一般是上面KMS关于CodeBuild的Policy没有配置号导致,根据目录第4个AWS Key Management Service (KMS)检查一遍。

  2. CloudFront在解密S3的对象上发生错用, 可能有两个原因
    aws codebuild kms error
    1. 跟上面一样KMS的配置发生了错误
    2. CodeBuild在Artifact一栏没有配置正确KMS Arn,是CloudFront用于解密的密匙与CodeBuild用来加密的密匙不一样. 回到CodeBuild更改配置.