深入研究GitHub Actions的可重用工作流
关于如何在CI/CD中实现可重用工作流的详细说明
GitHub动作可重用工作流于2021年11月29日发布,并自那时起迅速流行起来。可重用工作流的好处非常直接:
- 遵循DRY (Don 't Repeat Yourself)原则
- 避免工作流程重复
- 更容易维护工作流
- 允许我们在其他人的工作基础上更快地创建新的工作流。
- 通过使用精心设计并已经过测试的工作流来促进最佳实践。
通过:observation from Thoughtworks’ Technology Radar,
“GitHub Actions中的可重用工作流为管道设计带来了模块化,甚至允许跨存储库进行参数化重用(只要工作流存储库是公共的[或私有的])。它们支持显式地将机密值作为秘密传递,并可以将输出传递给调用作业。
通过几行YAML, GitHub Actions现在为你提供了CircleCI orb或Azure Pipeline模板的灵活性,但不必离开GitHub作为一个平台。”
请注意,上述方括号内引用的修改源于GitHub在2022年12月发布的公告,该公告涉及来自私有存储库的共享操作和可重用工作流的GA发布。
在这个故事中,我们将说明如何在CI/CD项目中实现可重用工作流。我们将从概述开始,然后深入研究可重用工作流。在我们的例子中,我们将使用两个GitHub库:
- Spring Boot微服务,在其CI/CD中实现可重用工作流。这个微服务有两个工作流ci。Yml和cd.yml。这两个工作流的目的是不言自明的。
customer-service-reusable-workflow-example
这是一个典型的Spring Boot微服务,为客户提供CRUD端点。我们在它的.github/workflows目录ci下添加了两个工作流。Yml和cd.yml。请参阅下面的图表,了解这两个工作流中涉及的详细作业和步骤。
如果我们有许多具有类似工作流的微服务,那么在所有存储库中复制和粘贴这两个工作流就会变得乏味。如果我们需要对工作流中的一个或多个步骤进行更改,维护将成为一场噩梦。
让我们看看如何使这两个工作流可重用,这样就可以在集中式可重用工作流中进行更改,并反映在调用这些可重用工作流的所有微服务中。
reusable-workflows-modules
为了与上面提到的两个工作流相对应,我们从两个可重用工作流开始。你可以在下面看到它们。注意,我在可重用工作流的文件命名约定中指定了与特定工作流相关的编程语言(Java)、构建工具(Maven)和关键字(构建、测试、部署、ECS)。这允许在集中式存储库中轻松识别可重用工作流,特别是当您的组织可能使用多种编程语言时。每种语言都有自己的一组可重用工作流,由其命名约定标识。
- [java-maven-build-test.yml]:用于持续集成(CI),它负责代码构建、测试、映像构建以及向AWS弹性容器注册中心(ECR)推送映像。这个可重用的工作流将由ci.yml调用。假设您已经为微服务配置了ECR和ECR存储库。并在GitHub中配置了下面引用的秘密。
name: Build and Test workflow for Spring Boot microservices
on:
workflow_call:
inputs:
# pass in environment through manual trigger, if not passed in, default to 'dev'
env:
required: true
type: string
default: 'dev'
# working-directory is added to accommodate monorepo. For multi repo, defaults to '.', current directory
working-directory:
required: false
type: string
default: '.'
# pass in java version to allow different app requiring different java versions to reuse the same workflow, default to java 17
java-version:
required: false
type: string
default: '17'
# allowing calling workflows to pass in maven parameter(s) such as '-Dmaven.test.skip' for certain apps, default to blank, not to skip test
maven-params:
required: false
type: string
default: ''
jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
# accommodating monorepo, this sets the working directory at the job level, for multi repo, defaults to "."
defaults:
run:
working-directory: ${{ inputs.working-directory }}
environment: ${{ inputs.env || 'dev' }}
# only run this job for auto trigger by PR merge, if manual trigger for other environments than dev,
# no need to run this job as the image will be pulled and promoted to envs higher than dev
# also dependabot PRs do not need to run this flow as GitHub prohibits dependabot PRs to access workflows
# dealing with secrets due to security reason.
if: (inputs.env == null || inputs.env == 'dev') && github.actor != 'dependabot[bot]'
steps:
- name: Harden Runner
uses: step-security/harden-runner@248ae51c2e8cc9622ecf50685c8bf7150c6e8813
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout Code
uses: actions/checkout@d0651293c4a5a52e711f25b41b05b2212f385d28
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@05b148adc31e091bafbaf404f745055d4d3bc9d2
with:
role-to-assume: ${{ secrets.ROLE_TO_ASSUME }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Setup jdk
uses: actions/setup-java@2506d21b7426d2b544f65d027f277ead4c5f6a9f
with:
java-version: ${{ inputs.java-version }}
distribution: 'adopt'
cache: maven
- name: Build with Maven
run: mvn clean install ${{ inputs.maven-params }} --file pom.xml
- name: Set project version as environment variable
run: echo "PROJECT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
- name: Print debug info
run: |
echo environment is ${{ inputs.env }}
echo working_directory is ${{ inputs.working-directory }}
echo project version is ${{ env.PROJECT_VERSION }}
echo java-version is ${{ inputs.java-version }}
- name: Build, tag, and push image to AWS ECR
id: build-image
env:
ECR_REGISTRY: ${{ secrets.ECR_REGISTRY }}
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY_NAME }}
IMAGE_TAG: ${{ env.PROJECT_VERSION }}
run: |
# Build a docker container and push it to ECR so that it can be deployed to ECS.
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker login -u AWS -p $(aws ecr get-login-password --region eu-west-1) $ECR_REGISTRY
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
- name: Scan ECR image with Trivy vulnerability scanner
uses: aquasecurity/trivy-action@c666240787bede835456c7ceb9f75c9225c3c1b4
with:
image-ref: ${{ steps.build-image.outputs.image }}
format: 'table'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'
java-api-deploy-to-ecs.yml
:用于持续部署(CD),从ECR提取映像并将其部署到AWS弹性容器服务(ECS)。这个可重用的工作流将被cd.yml调用。假设你已经为你的微服务准备了ECS Fargate集群。并在GitHub中配置了下面引用的秘密。
name: Deploy Spring Boot microservice to ECS Fargate
on:
workflow_call:
inputs:
# pass in environment through manual trigger, if not passed in, default to 'dev'
env:
required: true
type: string
default: 'dev'
# working-directory is added to accommodate monorepo. For multi repo, defaults to '.', current directory
working-directory:
required: false
type: string
default: '.'
jobs:
deploy:
name: Deploy to AWS ECS Fargate
runs-on: ubuntu-latest
# accommodating monorepo, this sets the working directory at the job level, for multi repo, defaults to "."
defaults:
run:
working-directory: ${{ inputs.working-directory }}
# important to specify the environment here so workflow knows where to deploy your artifact to.
# default environment to "dev" if it is not passed in through workflow_dispatch manual trigger
environment: ${{ inputs.env || 'dev' }}
# only execute if PR is merged or manual trigger
if: github.event.pull_request.merged || inputs.env != null
steps:
- name: Harden Runner
uses: step-security/harden-runner@248ae51c2e8cc9622ecf50685c8bf7150c6e8813
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout Code
uses: actions/checkout@d0651293c4a5a52e711f25b41b05b2212f385d28
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@05b148adc31e091bafbaf404f745055d4d3bc9d2
with:
role-to-assume: ${{ secrets.ROLE_TO_ASSUME }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Set project version as environment variable
run: echo "PROJECT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
- name: Print debug info
run: |
echo environment is ${{ inputs.env }}
echo working_directory is ${{ inputs.working-directory }}
echo project version is ${{ env.PROJECT_VERSION }}
echo github.event.pull_request.merged is ${{ github.event.pull_request.merged }}
- name: Download task definition
run: |
aws ecs describe-task-definition --task-definition ${{ secrets.ECS_TASK_DEFINITION }} --query taskDefinition | jq -r 'del(
.taskDefinitionArn,
.requiresAttributes,
.compatibilities,
.revision,
.status,
.registeredAt,
.registeredBy
)' > task-definition.json
- name: Fill in the new image ID and pass in the environment variable in the ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@374ee96751fffe528c09b5f427848da60469bb55
with:
# important to specify working directory here to accommodate monorepo
task-definition: ${{ inputs.working-directory }}/task-definition.json
container-name: ${{ secrets.CONTAINER_NAME }}
image: ${{ secrets.ECR_REGISTRY }}/${{ secrets.ECR_REPOSITORY_NAME }}:${{ env.PROJECT_VERSION }}
# this ENVIRONMENT is passed into the active spring profile in start-service.sh in the startup command,
# so it knows which application yml to retrieve based on the active spring profile
environment-variables: |
ENVIRONMENT=${{ inputs.env || 'dev' }}
- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@9c18d81893224634ac107b91720119c91c1d600e
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ secrets.ECS_SERVICE }}
cluster: ${{ secrets.ECS_CLUSTER }}
wait-for-service-stability: true
现在让我们深入研究这两个可重用工作流:
可重用工作流的属性
可重用工作流的属性
与其他GitHub操作工作流文件一样,可重用工作流位于公共或私有存储库的. GitHub /workflows目录中。在我们的例子中,reusable-workflows-modules 公共存储库。
注意:不支持工作流目录下的子目录。
添加workflow_call触发器
Workflow_call触发器是可重用工作流与普通工作流之间的关键区别。对于可重用的工作流,on的值必须包括workflow_call:
on:
workflow_call:
添加可选的输入参数
可重用工作流在概念上是模板,这意味着它们很可能需要传递参数,以使它们特定于调用工作流。但是参数并不是使工作流可重用的必要条件。在大多数情况下,可重用工作流利用输入参数使其可用于各种环境、不同版本的编程语言等。
请参阅下面来自java-maven-build-test的代码片段。Yml,请注意每个输入参数上面的注释行,以了解更多关于这些输入参数的用途。
on:
workflow_call:
inputs:
# pass in environment through manual trigger, if not passed in, default to 'dev'
env:
required: true
type: string
default: 'dev'
# working-directory is added to accommodate monorepo. For multi repo, defaults to '.', current directory
working-directory:
required: false
type: string
default: '.'
# pass in java version to allow different app requiring different java versions to reuse the same workflow, default to java 17
java-version:
required: false
type: string
default: '17'
# allowing calling workflows to pass in maven parameter(s) such as '-Dmaven.test.skip' for certain apps, default to blank, not to skip test
maven-params:
required: false
type: string
default: ''
- 在可重用工作流中,我们使用输入关键字来定义将从调用方工作流传递的输入。
- 在可重用工作流中,我们以 on 方法中定义的输入被引用${{ inputs.###}},例如 ${{ inputs.env}}
如何调用可重用工作流?
由于我们将可重用工作流托管在可重用工作流模块公共存储库中,所以我们可以使用以下语法引用可重用工作流文件:
{owner}/{repo}/.github/workflows/{filename}@{ref}
{ref}可以是SHA、发布标记或分支名称。例如,我们的客户-服务-可重用工作流示例的ci。Yml将可重用工作流称为java-maven-build-test。yml来构建和测试应用程序。请参见下面的第21行:
# This CI workflow can be triggered by PR creation or code push in PR, or manually using workflow dispatch.
name: CI workflow for building, testing microservice, and publishing image to ECR
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to run the workflow against'
type: environment
required: true
pull_request:
branches: [ main ]
jobs:
build-and-test:
permissions:
id-token: write # need this for OIDC
contents: read
uses: wenqiglantz/reusable-workflows-modules/.github/workflows/java-maven-build-test.yml@main
with:
env: ${{ github.event.inputs.environment }}
secrets: inherit
- 在可重用工作流场景中,特定作业的权限是在调用工作流中定义的。参见上面的第18-20行。
- 为了从调用工作流传递输入或秘密,我们在调用工作流的作业中使用with关键字(第22行)。在同一个组织中调用可重用工作流的工作流可以使用inherit关键字(第24行)隐式传递在GitHub中定义的秘密。
通过在调用工作流中配置secrets: inherit,我们可以在可重用工作流中引用秘密,即使它们没有在on键中定义。请参见第4行和第5行。
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@05b148adc31e091bafbaf404f745055d4d3bc9d2
with:
role-to-assume: ${{ secrets.ROLE_TO_ASSUME }}
aws-region: ${{ secrets.AWS_REGION }}
类似地,cd.yml调用可重用工作流java-api-deploy-to-ec .yml。
# This CD workflow pulls the docker image from ECR and deploys it to the environment specified by the manual trigger
name: CD workflow for deploying microservice to ECS Fargate
on:
workflow_dispatch:
inputs:
environment:
description: 'Environment to run the workflow against'
type: environment
required: true
pull_request:
types: [closed] # when PR is merged, CD will be triggered
jobs:
deploy-to-ecs:
permissions:
id-token: write # need this for OIDC
contents: read
uses: wenqiglantz/reusable-workflows-modules/.github/workflows/java-api-deploy-to-ecs.yml@main
with:
env: ${{ github.event.inputs.environment }}
secrets: inherit
总结
在这篇文章中,我们探索了GitHub动作可重用工作流。我们首先概述了可重用工作流的好处。然后,我们深入研究了一个样本微服务的GitHub操作工作流,并将它们的作业/步骤提取到可重用的工作流中,这些工作流位于不同的公共存储库中。
我们探索了关于如何使工作流可重用、如何调用可重用工作流、如何向可重用工作流传递输入参数和秘密的详细步骤。一旦你开始在你的项目中使用可重用工作流,你就再也不会回头了,因为可重用工作流在推出应用程序的CI/CD工作流和消除维护头痛方面节省了大量的时间和精力。
- 点赞
- 收藏
- 关注作者
评论(0)