为 Azure 静态网站实现 GitHub Actions
在将Blazor 搜索应用部署到我的网站时,我意识到需要更新博客的部署管道。该流程与 DDD Sydney 网站的流程非常相似,但针对 Hugo 进行了调整。由于该流程是之前设置的,我使用的是Azure Pipelines中的 UI 设计器,而不是YAML 方法,因此这似乎是进行全面改进的绝佳机会。
但既然我要进行彻底改造并移植到 YAML,我决定是时候学习一些我一直想学的东西了,那就是GitHub Actions。毕竟,我已经广泛使用过 Azure Pipelines,所以为什么不学习一些新东西,并对这两个产品进行比较/对比呢?
活动部件
我的网站需要处理三个部分:使用 Hugo 生成静态网站、生成 Blazor WebAssembly 应用程序以及将其部署到 Azure 静态网站并更新 Azure CDN。我将尝试把这篇文章分成这三个部分来讲解,这样即使其中一部分与您无关,您也可以轻松地专注于您最需要的部分。
我的第一个行动
如果您之前没有使用过 GitHub Actions,它会出现在您仓库中名为“Actions”的新标签页下。GitHub Actions 允许您创建一个工作流,该工作流会在 GitHub 中发生各种触发事件时运行,例如创建 issue、发起 PR、推送 commit等等。GitHub 用户界面中有一个入门指南,您可以按照指南进行操作。如果您想在编辑器中开始,首先需要在您的仓库中创建一个新文件夹,.github/workflows并在其中添加一个 YAML 文件。该文件可以随意命名,只要它以 .yml.yml或 .yml 为.yaml扩展名即可,我的文件名为 .yml continuous-integration.yml。
在这里,我们可以定义工作流的元数据:
name: Build and Deploy Website
环境变量:
env:
OUTPUT_PATH: ${{ github.workspace }}/.output
以及触发工作流程的因素:
on:
push:
branches:
- master
这样我们就有了这样的开端:
name: Build and Deploy Website
env:
OUTPUT_PATH: ${{ github.workspace }}/.output
on:
push:
branches:
- master
然后我们可以创建jobs,也就是我们的工作流程所做的事情。
jobs:
job_name:
runs-on: <platform>
steps: <steps to run>
这runs-on类似于poolAzure Pipelines 中的配置,用于指定工作流运行的平台(Linux、macOS 或 Windows)。之后,我们steps通过指定要从应用市场使用的操作或要运行的命令来定义作业的具体功能。
步骤通常会包含一个uses指令,用于指定运行该步骤“任务”的操作。该操作可能来自操作市场,也可能来自你的 Git 仓库中的自定义操作。
我的同事Tierney Cyren写了一份非常棒的入门指南,你应该看看,以了解基本组成部分(我自己在创建此工作流程时也参考了它!)。
底漆处理完毕,让我们开始为我们需要处理的每个部分创建任务。
术语概要
简单总结一下我们最近接触到的一些新术语:
- GitHub Actions——我们正在使用的GitHub产品
- 行动——我们可以从市场中获得(或自行构建)的东西,它定义了我们能做什么。
- 工作流程——当某个事件发生时所执行的一系列环境变量、作业和步骤。
- 任务 - 我们的工作流程是做什么的
- 步骤 - 作业使用操作执行的任务
生成静态网站
我的博客使用Hugo,这是一个用 Golang 编写的简单静态网站生成器,它由一个单独的二进制文件组成。我博客所需的所有内容都放在我的GitHub 代码库中,甚至连 Hugo 二进制文件也放在里面,这样就可以轻松地克隆并运行。所以它非常简单,但我们来看看如何通过 GitHub Actions 来实现。
我们首先来为这个工作流定义一个作业:
jobs:
build_hugo:
runs-on: ubuntu-latest
职位名称是,我喜欢用角色(或)和它所扮演的角色build_hugo作为前缀来命名事物,但命名规则由你决定,只要确保它在两个月后你查看时仍然足够有意义即可!builddeploy
我已经明确表示我们将使用该ubuntu-latest镜像作为基础镜像,因为 Hugo 可以在 Linux 上运行,而且该镜像使用起来很简单。
这项作业是一个“持续集成”作业,这意味着它需要访问我们 Git 仓库中的“内容”,因此我们要执行的第一步是执行一个操作git checkout,为此我们可以使用以下actions/checkout@v1操作(注意:我已将版本锁定为v1):
build_hugo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
此操作仅需用于执行结账操作即可master,但如果您在 PR 中使用它,则可能需要对其进行一些调整。有关调整,请参阅操作文档。
在行动中建造雨果
鉴于我的 Git 仓库里有 Hugo 二进制文件,我可以把它当作 shell 脚本来运行,但我决定看看能不能用更“Action 式”的方式来实现,然后有人向我推荐了peaceiris/actions-hugo。这是一个预先构建好的 Action,专为与 Hugo 配合使用而设计。
从步骤输出信息
使用 Hugo Action 时,我们需要指定要使用的 Hugo 版本(它会自动下载)。既然我已经有了 Hugo 二进制文件,为什么不直接询问它的版本呢?让我们在作业中添加另一个步骤,在默认 shell 中运行一个脚本:
build_hugo:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Get Hugo Version
id: hugo-version
run: |
HUGO_VERSION=$(./hugo version | sed -r 's/^.*v([0-9]*\.[0-9]*\.[0-9]*).*/\1/')
echo "::set-output name=HUGO_VERSION::${HUGO_VERSION}"
这条./hugo version命令会生成一个相当冗长的字符串,该字符串会被传递给一个不太优雅的sed正则表达式,以生成一个可在本步骤中使用的环境变量。但由于我们需要在另一个步骤中使用它,因此必须将其转换为步骤输出,我们使用以下代码行来实现:
echo "::set-output name=HUGO_VERSION::${HUGO_VERSION}"
如果你用过 Azure Pipelines,那它和##vso[task.setvariable variable=MyVar]some-value你可能用过的那些奇怪的东西类似。
关于该步骤,您还需要了解的另一点信息是id,因为您可以通过它从其他步骤中引用它。
生成 Hugo 输出
有了 Hugo 版本,我们现在就可以生成 HTML 输出了:
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2.3.0
with:
hugo-version: "${{ steps.hugo-version.outputs.HUGO_VERSION }}"
- name: Build
run: hugo --minify --source ./src --destination ${{ env.OUTPUT_PATH }}
该Setup Hugo步骤使用我们的市场操作(peaceiris/actions-hugo@v2.3.0),并通过查看上一步的输出来设置版本。然后,我们使用hugo该操作生成的二进制文件运行构建步骤来生成输出文件。由于我的网站内容不在仓库根目录,而是在某个src文件夹中,因此我指定了--source标志并覆盖默认输出,以使用在工作流顶部创建的环境变量。
创建一件物品
一个作业由多个按顺序运行的步骤组成,因此您可以在一个作业中完成构建和发布,但我更喜欢将这些步骤拆分成定义清晰的作业,使我的工作流阶段一目了然。由于每个作业都在新的虚拟机上运行,我们需要某种方法将生成的工件导出以供后续作业使用。为此,我们将使用以下actions/upload-artifact@v1操作:
- name: Publish website output
uses: actions/upload-artifact@v1
with:
name: website
path: ${{ env.OUTPUT_PATH }}
- name: Publish blog json
uses: actions/upload-artifact@v1
with:
name: json
path: ${{ env.OUTPUT_PATH }}/index.json
再次以 Azure Pipelines 为例,这就像一个任务PublishPipelineArtifact,我们需要指定工件的名称及其在磁盘上的位置。无论工件是单个文件还是目录,系统都会将其打包成 zip 文件,因此除非您需要特殊处理,否则无需自行进行任何归档操作,但即使有特殊需求,最终也会得到一个压缩文件。
您可能还会注意到,我正在发布一个 JSON 文件,这是我的博客的 JSON 版本,它将用于生成我的搜索索引。
但是,就我们的静态网站而言,我们可以将其部署到 Azure。您可以在我的 GitHub 上找到完整的管道作业。
构建我们的搜索应用程序
我们需要构建的应用程序的另一部分是搜索应用程序和搜索索引,它们分别是Blazor WebAssembly应用程序和控制台应用程序。
为此,我将使用两个单独的任务,一个用于构建用户界面,另一个用于构建索引。
构建 Blazor UI
由于这是一个“持续集成”作业,build_hugo因此它将以如下方式开始git checkout,使用以下actions/checkout@v1操作:
build_search_ui:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
要使用 .NET 进行构建,actions/setup-dotnet我们可以使用一个便捷的操作,但这个操作需要知道要将哪个版本的 .NET 下载到作业的虚拟机中。我将在文件顶部添加一个新的环境变量(因为我们build_search_index稍后将在作业中使用相同的版本):
DOTNET_VERSION: "3.1.100-preview3-014645"
它看起来与 Azure Pipelines 非常相似:
build_search_ui:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Build search app
run: dotnet build --configuration Release
working-directory: ./Search
- name: Publish search UI
run: dotnet publish --no-build --configuration Release --output ${{ env.OUTPUT_PATH }}
working-directory: ./Search/Search.Site.UI
我们有设置 .NET 版本、运行程序dotnet build以及最终dotnet publish(用户界面)的步骤,然后我们可以打包输出(我们之前已经学过):
- name: Package search UI
uses: actions/upload-artifact@v1
with:
name: search
path: ${{ env.OUTPUT_PATH }}/Search.Site.UI/dist/_framework
Blazor 已完成(GitHub 链接),并已添加到我们的搜索索引中。
生成搜索索引
此作业将依赖于某个工件,build_hugo因此我们需要告诉 GitHub Actions 等待该工件完成。否则,我们的工作流将并行运行所有作业,为此,我们需要添加一个依赖项列表:
build_search_index:
runs-on: ubuntu-latest
needs: build_hugo
steps:
- uses: actions/checkout@v1
- uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
我们将使用相同的actions/checkout方法actions/setup-dotnet,因为我们最终会使用它dotnet run,但我们需要获取用于构建索引的 JSON 文件。为此,我们可以使用actions/download-artifact。
build_search_index:
runs-on: ubuntu-latest
needs: build_hugo
steps:
- uses: actions/checkout@v1
- uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Download index source
uses: actions/download-artifact@v1
with:
name: json
path: ${{ env.OUTPUT_PATH }}
它的优点在于actions/download-artifact它还会自动为你解压缩文件,所以你无需担心归档格式的问题!
现在我们可以构建索引并将其作为工件发布:
build_search_index:
runs-on: ubuntu-latest
needs: build_hugo
steps:
- uses: actions/checkout@v1
- uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Download index source
uses: actions/download-artifact@v1
with:
name: json
path: ${{ env.OUTPUT_PATH }}
- name: Build search index
run: dotnet run
working-directory: ./Search/Search.IndexBuilder
- name: Publish search index
uses: actions/upload-artifact@v1
with:
name: search-index
path: ./Search/Search.IndexBuilder/index.zip
你会注意到我上传了一个 zip 文件作为工件,所以它会被“双重压缩”,但这只是因为该压缩文件是 UI 应用程序将从我的网站下载的内容,所以这不是问题。
至此,最后一个构建任务已完成(GitHub 链接)。
部署到 Azure 静态网站
我的网站托管服务商是Azure Static Websites,价格便宜。结果发现已经有现成的 GitHub Action 可以实现这个功能,feeloor/azure-static-website-deploy部署起来超级简单。
这个预置操作会将文件部署到$web存储帐户中的容器中,这是静态网站的标准做法。但我并没有$web直接使用容器,而是将网站放在一个使用 Azure Pipelines 构建编号的子目录中。这样,如果需要,我可以回滚到之前的版本;或者,如果出现问题,我可以比较更改。因此,我将使用 Azure CLI 而不是这个操作来执行部署。
让我们开始创建任务:
deploy_website:
runs-on: ubuntu-latest
needs: [build_search_ui, build_search_index]
env:
STORAGE_NAME: aaronpowellstaticwebsite
CDN_NAME: aaronpowell
CDN_PROFILE_NAME: aaronpowell
RG_NAME: personal-website
这是一个“持续交付”作业,所以我给它加上了前缀deploy。它还依赖于其他作业的完成情况build_,这些作业在属性中定义needs。我选择不添加build_hugo依赖项needs,因为它是由完成作业的必要性强制要求的build_search_index,但我将来可能会更改这一点,以便更清楚地了解哪些作业是依赖的。
我还创建了一些 Azure 中需要的环境变量,主要是因为我不喜欢内联魔法字符串。这些环境变量是在此作业中创建的,而不是在工作流顶部创建的,因为它们仅在此作业中使用。
现在该上台阶了。
实际使用 Azure
微软提供了一些可供使用的操作azure/login。截至撰写本文时,还没有用于操作存储或 CDN 的操作,我们需要通过 CLI 来完成这些操作,但在此之前,我们需要使用以下命令登录 Azure :
steps:
- uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
要使用此功能,您需要创建一个 Azure 服务主体,然后将其存储为工作流的密钥变量(不要直接嵌入 Azure 凭据,那样做不好)。
接下来我们需要使用以下命令下载一些文件actions/download-artifact:
steps:
- uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Download website
uses: actions/download-artifact@v1
with:
name: website
path: ${{ env.OUTPUT_PATH }}
- name: Download search UI
uses: actions/download-artifact@v1
with:
name: search
path: ${{ env.OUTPUT_PATH }}/_framework
- name: Download search index
uses: actions/download-artifact@v1
with:
name: search-index
path: ${{ env.OUTPUT_PATH }}
我耍了个小聪明,把它们全部下载到同一个文件夹里,由于没有文件名冲突,我的作业虚拟机就能按照我想要的方式构建网站结构了!
现在是时候将文件上传到存储空间了:
- name: Deploy to Azure Storage
run: az storage blob upload-batch --source ${{ env.OUTPUT_PATH }} --destination \$web/${GITHUB_SHA} --account-name ${STORAGE_NAME}
我使用upload-batch命令行界面 (CLI) 中的命令进行批量上传,这比逐个文件上传要快得多。使用 Azure Pipelines 发布时,$web容器中的文件夹名称是构建编号,但使用 GitHub Actions 时没有构建编号,只有触发操作的提交的 SHA 值,我们可以使用 `git commit --version` 命令访问它${GITHUB_SHA}。这意味着我无法通过浏览存储帐户按顺序查找最新部署,但哪个提交对应哪个部署就更清晰了!
修复我们的 WASM 应用
当您将文件上传到 Azure 存储时,系统会尝试确定文件的 MIME 类型并进行相应设置。大多数情况下,此过程都能正常工作,但也有例外。目前看来,WebAssembly 文件似乎就是这类例外情况之一。WebAssembly.wasm文件的 MIME 类型会被错误地设置为 ` <object>`,application/octet-stream但实际上它应该是 `<object>` application/wasm,否则浏览器会拒绝该文件。
但使用 Azure CLI 可以轻松解决这个问题!
- name: Update wasm pieces
run: az storage blob update --container-name \$web/${GITHUB_SHA}/_framework/wasm --name "mono.wasm" --content-type "application/wasm" --account-name ${STORAGE_NAME}
我们可以运行一个命令blob update并更改content-type存储的内容,以便它能够正确地提供服务。
更新 Azure CDN
最新的网站更新已上传,文件类型也正确,所以现在只剩下一件事要做,那就是告诉 Azure CDN 开始使用新的更新。
同样,目前还没有现成的操作(截至撰写本文时),所以我们需要使用命令行界面 (CLI)。第一步是更新 CDN 端点以使用新文件夹:
- name: Update CDN endpoint
run: az cdn endpoint update --name ${CDN_NAME} --origin-path /${GITHUB_SHA} --profile-name ${CDN_PROFILE_NAME} --resource-group ${RG_NAME}
然后我们会清除 CDN 缓存,以便将新文件发送给我们的读者:
- name: Purge CDN
run: az cdn endpoint purge --profile-name ${CDN_PROFILE_NAME} --name ${CDN_NAME} --resource-group ${RG_NAME} --content-paths "/*"
这里我们执行的是彻底清除,直接删除缓存中的所有内容,但如果您在其他场景中使用它(或者对哪些文件发生了更改有一些了解),您可以设置不同的规则content-paths,清除速度会更快。
但这就是我们部署静态网站并从 GitHub Actions 更新 CDN 的方法(GitHub 链接)。
结论
在本文中,我们已经了解了 GitHub Actions 的多种用途。我们学习了如何使用 Actions 团队提供的一些 Actions 来检出源代码和处理工件。然后,我们使用了一些第三方 Actions 来与 Hugo 和 Azure 集成。
我们看到了如何通过将变量放在工作流的顶部来定义在整个工作流中可用的变量,如何为特定作业定义一些变量(例如我们的 Azure 信息),如何将它们从作业中的一个步骤输出到另一个步骤,甚至可以提供凭据。
您可以在我的 GitHub 上查看完整的 Workflow YAML 文件,并查看过去的运行情况,例如最近的一篇博客文章。
文章来源:https://dev.to/azure/implementing-github-actions-for-an-azure-static-website-488h