07 Github Action:打通IaC与GitOps的桥梁
你好,我是潘野。
前面的课程里,我们学习了通过持续集成与部署的方式管理IaC代码的思路。不过知识必须学以致用,才能真正掌握。接下来的三讲内容,我们就来实操演练,学会如何使用GitOps,从而更好地管理IaC代码。
今天,我们会使用GitHub Actions这个工具来管理Terraform代码。为什么要选择GitHub Actions呢?
之前我们已经了解到,GitOps方式离不开在GitHub里管理代码。GitHub是一个广泛使用的代码托管平台,提供了许多与代码管理和协作相关的功能。而GitHub Actions是Github中自带的CI/CD工具,上手门槛较低。掌握了这个工具,能让你的管理、部署工作事半功倍。
基于Github Action的管理架构
在进入实操演练之前,我们需要先了解一下基于Github Action的管理架构长什么样。具体来说,就是通过Github Action来管理Terrafrom代码,并自动执行变化的代码,从而更改基础设施的架构。
对照架构图,不难看出Github Action的Terraform代码的管理主要体现在以下方面:
- 用户提交Pull Request之后,Github Action 通过OIDC的方式与AWS账户进行认证。
- 通过认证之后,Github Action执行
terrafrom plan
和terrafrom apply
两个命令,Terrafrom会根据代码去更改基础设施配置。 - Terrafrom的状态文件会存在对象存储S3中,以便追踪Terrafrom的变化。
所以我们要做的是这么几步:
- 配置AWS IAM权限,使得GitHub Action可以通过OIDC的方式连接上AWS
- 创建S3存储桶,让Terraform通过Github Action的流水线将状态文件存储进S3中
- 编写Github Action配置,实现使用Github Action来执行Terraform的操作。
那么接下来,我们就进入实战环节。
GitHub Action如何与AWS做认证?
当我们手动执行 terrafrom plan
之前,需要先在AWS里的IAM中新建一个USER,并且生成一组access key,里面包含了Access Key ID,Secret Access Key。
但是这种方式存在一些弊端。首先是安全隐患。因为AWS Access Key 是以明文形式生成出来的,交给用户使用。所以,如果在使用的过程中没有规范存放,就有可能会泄露,带来安全风险。
其次,这种方式非常依赖人工检查,比较低效。AWS 建议定期更换 AWS Access Key,并且始终使用最低的必要权限来控制对 AWS Access Key 的访问。那么一旦Acess Key过期,我们又恰好忘记检查,就会导致自动化程序不可用。
所以,在GitHub上,我们可以通过使用 GitHub OIDC (OpenID Connect) 提供程序并仅允许在特定的 GitHub 操作中运行这些凭证。相比将AWS的Access Key的所有信息存储在 GitHub 密钥中,这样做更加安全且高效。
我们这就来看看GitHub OIDC是如何与AWS对接做认证的。你可以结合后面的认证流程图听我分析。
图里面序号1~5代表认证的操作步骤,我们挨个顺一遍。
- GitHub Action 向 Github OIDC 提供商请求 JWT(Java Web Token)。
- GitHub OIDC Provider 将签名的 JWT 发布到我们的 GitHub Action。
- 带有我们签名的 JWT 的 GitHub Action 将向 AWS 中的 IAM 身份提供商请求临时访问令牌。
- IAM 身份提供商将会使用 GitHub OIDC 提供商验证签名的 JWT,并验证身份提供商是否可以使用我们想要承担的角色。
- AWS IAM基于角色的权限,给GitHub 颁发临时访问令牌。
经过这些步骤后,我们的 GitHub 操作环境就可以访问AWS的相关资源了。
OIDC provider的整个配置过程也相当简单。如下图所示,我们只需要在IAM的身份提供商里填上GitHub Action的token URL以及受众,然后点击添加提供商即可。
最后,我们就会看到生成了一个专门用于GitHub Action的身份提供商。这样认证工作就完成了。
如何让Terrafrom将状态文件存到S3中?
我们回想一下第2讲学过的Terraform的状态管理,我曾提到状态文件管理非常重要。所以在GitHub Action中,我们需要将Terraform的状态文件存在AWS的S3中。这样既安全地存储我们的管理文件,又能让我们拥有版本追溯的能力。
创建S3
我们需要先创建一个 S3 存储桶。这里有两种创建方式,我们分别看一下。
第一种方式是登陆到Web Console上创建。我们在AWS的Web Console上进入S3界面,点击创建存储桶,然后输入存储桶名称 “cloudnative-tfstate-1”。
请注意,在对象所有权这里我们要选择 ACL已禁用,因为Terraform状态的文件也需要安全保护,不能被其他人随意访问到。
接着,点击启用存储桶版本,再点击默认加密启用即可。
另外一种创建方法是用Terrafrom代码来生成S3的存储桶。这里我的 GitHub代码库 中提供了一个创建S3存储桶样例代码,你可以自行尝试运行创建。
这里需要注意的是,创建了存储桶以后,我们还无法直接用上它。这是因为AWS采用了ABAC方式来管理资源,也就是基础属性的访问控制。怎么理解呢?其实ABAC这一种授权机制,作用就是根据资源和用户属性来控制用户访问资源的权限。
一般来说,创建好资源以后,还需要为其附加上相应的策略,这样才完成了资源的配置。这些策略一般描述的是,谁可以对这个资源作出哪些操作。对应到我们课程里的例子,就需要给cloudnative-tfstate-1这个存储桶做授权。
我们在IAM中创建一个角色叫 github-action-terraform
,这个角色是专门操作AWS资源的,具体配置,你可以参考后面的截图。
在步骤1的信任实体中,填入上一步创建的oidc-provider的arn号,以及你的github repo地址。这里请注意你的环境与我环境有所不同,不要直接复制粘贴,需要根据你的情况调整。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::{UID}:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:cloudnative-automation/github-action-terrafrom:*"
}
}
}
]
}
我们有了操作AWS资源的角色,那么我要为这个角色划定权限,也就是赋予一个权限策略,在AWS IAM 策略中,我们新建一个访问S3存储桶的策略,然后填入图上的权限配置,我将权限配置贴在后面,请对照文稿自行查阅。
策略的配置如下,也非常好理解,对于S3存储桶中的资源 arn:aws:s3:::cloudnative-tfstate-1
一般需要有List、Put等的权限,这里我在Action中加入了Put和List权限,具体请查看下面的代码。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::cloudnative-tfstate-1/*",
"arn:aws:s3:::cloudnative-tfstate-1"
]
}
]
}
最后我们将角色与策略绑定在一起,只要在角色-权限策略中,将你刚才新建的策略附加上就可以了。
除了自定义策略之外,你也可以复用AWS自带的策略,你可以在角色的权限中选择附加策略,这里我附加上AmazonEC2FullAccess的策略,在接下来的EC2创建中,我们会使用到这个策略。
密钥怎么处理?
现在,我们建好了S3存储桶,那么Github Action是怎么把状态文件存进去的呢?我们这就来看看S3要怎么使用。
因为Terrafrom里已经支持了将状态文件直接存储进S3,所以我们只要在terrafrom init的时候配置backend storage的存储桶以及相应权限即可。命令如下:
terraform init -backend-config="bucket=${AWS_BUCKET_NAME}" -backend-config="key=${AWS_BUCKET_KEY_NAME}" -backend-config="region=${AWS_REGION}"
我们需要注意的是,这条命令里有一些变量,比如 AWS_BUCKET_NAME,AWS_REGION 等。这些变量需要从GitHub上通过Secret注入到执行过程。
这一步的操作发生在GitHub上,过程也很简单。我们只需要点击代码仓库的setting,找到左下方的Secret and vaiables,在Actions中添加这几个Secret。
- AWS_BUCKET_NAME,存储桶名称,这里我们填入上一步中创建的存储桶的名称 cloudnative-tfstate-1
- AWS_BUCKET_KEY_NAME,这里指的是你在存储桶中存储文件的名称,这里我们填入的是eks.tfstate,稍后你可以在存储桶中看到这个文件。
- AWS_REGION :存储桶所在的区域,这里我们填入的是ap-east-1。
- AWS_ROLE :存储桶角色的 ARN,这里可以从创建好的存储桶的属性中找到,这里我们填入
arn:aws:s3:::cloudnative-tfstate-1
。
在Github的界面中,最终看起来像下图一样,就表示我们成功完成了配置。
Github Action的配置怎么写?
好,现在最关键的一步来了,也就是GitHub Action的配置要怎么写?完成这一步,我们才能真正用GitHub Action来管理Terrafrom代码。
GitHub Action中有这么几个重要的字段,on、env还有job。on表示在哪一个Git分支上执行。
env是环境变量,例如我们需要引入上一步中Region的这个变量,就可以这样写:
而在job这个字段里,就是我们定义真正执行操作步骤的地方。了解了重要字段,我们就可以动手写代码了。
首先,我们需要对环境做一些初始化,例如将代码checkout出来,配置Terraform的版本,环境等等,我将样例代码贴在了后面。
steps:
- name: Git checkout
uses: actions/checkout@v3
- name: Configure AWS credentials from AWS account
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ secrets.AWS_ROLE }}
aws-region: ${{ secrets.AWS_REGION }}
role-session-name: GitHub-OIDC-TERRAFORM
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.2.5
其次,GitHub Pull Request中有两个主要动作。第一是创建Pull Request,提交代码并审核;第二是在代码审核完成之后,合并代码。
相应地,我们在第一步创建Pull Request的时候,就需要运行terraform plan来检查这次提交的代码是否符合预期。然后在第二步合并代码的时候,通过GitHub Action运行terrafrom apply,真正执行基础设施上的变更。
GitHub Action的配置很长,为了让你聚焦重点,这里我将两步的核心代码贴在这里,帮助你理解核心用法。GitHub Action中定义Terraform plan这一步,它的触发条件是 github.event_name == ‘pull_request’。
- name: Terraform Plan
id: plan
run: terraform plan -no-color
if: github.event_name == 'pull_request'
continue-on-error: true
接着,我们来看GitHub Action中定义Terraform Apply这一步。它的触发条件是 github.ref == ‘refs/heads/main’ && github.event_name == ‘push’,含义是当main branch有push的这个动作时候,就执行terraform apply这个命令。
- name: Terraform Apply
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve -input=false
完整的GitHub Action的配置,请参考 GitHub仓库里 的代码。
万事俱备,运行管理
这一切都设置完成之后,就到了收割战果的时刻,我们提交一个PR来运行一下。
从图里我们可以看到,当Pull Request提交的时候,自动触发了Github Actions的流水线,此时正在运行的是terrafrom init以及terrafrom plan的步骤。
从这个图中,我们可以看到,当Pull Request被merge的时候,自动触发了Github Actions的流水线,此时正在运行的是terrafrom apply这一步。
最后,我们在AWS的页面上可以看到EC2实例创建成功了。
同时在S3存储桶中,我们可以看到Terraform的状态文件的不同版本,达到了安全地存储我们的管理文件,又能让我们拥有版本追溯的要求。
这样,我们就初步完成了用Github Action来管理Terraform的整个过程。这时,我们真正地将IaC完整地实现了。
总结
我们来做个总结。
通过今天的学习,我们应该理解了 GitOps 模式是如何工作的,GitOps是一种使用 Git 仓库作为 IaC 代码的中央存储库的模式,并使用 DevOps中的CI/CD的形式来管理它。
理解原理只是第一步,今天的重点是配置实操,这个过程掌握了,你才能熟练地使用GitHub Actions 来管理 Terraform 代码。
我们来回顾一下其中的关键步骤。首先,我们在AWS上配置了Github OIDC,让GitHub Action可以通过OIDC认证将AWS权限安全地转移给GitHub Action。
然后,我们创建了S3存储桶用于保存terrafrom状态文件,并且在创建S3存储桶的过程中,我们学习了AWS IAM是如何配置的,同时我们在GitHub上,利用了Github Secret的这个功能,存储了S3存储桶的一些相关信息,通过变量的形式传递给Github Action流水线。
最后,我们仓库中 .github/workflows
目录下创建一个新的 YAML 文件,来完成一个 GitHub Actions 工作流的设置。在这个YAML文件中,我们可以定义我们的工作流,包括触发的事件(例如 push 或 pull request)、运行的环境和执行的步骤。
其实整个配置的过程并不简单,尤其是Github Action的配置文件的编写,需要不断修改与调试才能达到满意的效果,希望你能够跟着课程实际操作一遍,加深对整个过程的理解。
思考题
课程里,我们将Terraform状态文件存放在S3这步中,S3存储桶是我们手动在页面上创建出来的,你能否自己写一段Terraform代码,通过GitHub Action来创建出S3存储桶呢?
欢迎在评论区里给我留言。如果这一讲对你有启发,别忘了分享给身边更多朋友。