GitHub Actions 深入剖析:从原理到实践
·
simons
·
本文共 1621
个字 , 预计需要阅读 4
分钟
GitHub Actions
自动化
持续集成
工作流
YAML
Docker
GitHub
部署GitHub Actions 深入剖析:从原理到实践
最近收到一个问题:“为什么我的 CI/CD 流水线总是莫名其妙失败?“这让我想起了 GitHub Actions 的一些有趣特性。今天就来扒一扒它的底层原理。
GitHub Actions 的本质
本质上,GitHub Actions 就是一个事件驱动的工作流自动化引擎。它监听 GitHub 事件,然后执行预定义的动作。
# 这是最基础的工作流
name: CI
on: [push] # 事件触发器
jobs:
build: # 工作单元
runs-on: ubuntu-latest # 运行环境
steps: # 执行步骤
- uses: actions/checkout@v2
- run: npm test但这只是表象,让我们深入看看它的架构:
核心概念解析
- Runner(执行器)
# Runner 其实就是一个 Docker 容器
docker run --rm -v $PWD:/workspace \
-e GITHUB_TOKEN=$token \
myrunner:latest- Workflow(工作流)
# 这才是真正的工作流定义
name: Complex Flow
on:
push:
branches: [ main ]
paths-ignore: [ '**.md' ]
jobs:
build:
strategy:
matrix:
node: [12, 14, 16]
steps:
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}深入原理
GitHub Actions 的执行过程:
- 事件触发
- 工作流匹配
- Runner 分配
- 容器初始化
- 步骤执行
关键在于它的上下文传递:
# 上下文是如何传递的?
steps:
- id: step1
run: echo "::set-output name=foo::bar"
- run: echo ${{ steps.step1.outputs.foo }}这里涉及到环境变量、文件系统和网络隔离。
实战技巧
- 缓存优化
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}- 矩阵构建
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [12, 14, 16]
exclude:
- os: windows-latest
node: 12- 条件执行
if: github.event_name == 'push' && github.ref == 'refs/heads/main'常见坑点
- 权限问题
# 这样写才能访问其他仓库
jobs:
build:
permissions:
contents: read
packages: write- 环境变量作用域
env:
GLOBAL_VAR: value # 全局
jobs:
build:
env:
JOB_VAR: value # 作业级别- Runner 选择
# 自托管 runner 要考虑并发
runs-on: [self-hosted, linux]架构设计思考
为什么用 YAML?
- 人类可读
- 版本控制友好
- 但是逻辑复杂时很痛苦
事件驱动的优缺点
- 优点:解耦、灵活
- 缺点:调试困难
Runner 的设计
- 容器隔离
- 资源限制
- 安全考虑
记住:
- Actions 不是万能的,某些场景还是本地构建更合适
- 理解原理比记忆语法更重要
- 安全永远是第一位的
最后说一句:工具用对了是银弹,用错了是手雷。关键是理解它的设计思想和实现原理。
真受不了那些表面的介绍,今天我们扒开 GitHub Actions 的底裤,看看它到底是个啥玩意。
Event(事件) - 一切的起点
事件就是触发器,就这么简单。但关键是要理解事件的类型和它们的上下文数据。
# 最常见的事件触发
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
- cron: '0 0 * * *' # 每天零点触发妈的,光写这些配置有啥用?重点是要理解每个事件会带来什么上下文:
jobs:
print-context:
runs-on: ubuntu-latest
steps:
- name: 看看push事件带来了啥
if: github.event_name == 'push'
run: |
echo "提交人: ${{ github.actor }}"
echo "提交SHA: ${{ github.sha }}"
echo "修改的文件: ${{ join(github.event.commits.*.modified, ', ') }}"Workflow(工作流)- 编排你的自动化过程
工作流说白了就是一个YAML文件,放在 .github/workflows 目录下。但关键是要搞清楚这个文件的结构:
name: 构建测试部署一条龙
on: [push]
env:
GLOBAL_VAR: "全局变量老子想用就用"
jobs:
job1:
runs-on: ubuntu-latest
outputs:
output1: ${{ steps.step1.outputs.test }}
steps:
- id: step1
run: echo "::set-output name=test::value"
job2:
needs: job1 # 依赖关系,老子等job1完事才开始
runs-on: ubuntu-latest
steps:
- run: echo ${{ needs.job1.outputs.output1 }}Job(作业)- 实际干活的地方
Job 是最小的执行单元,但它里面的水太深了:
jobs:
build:
runs-on: ubuntu-latest # 这他妈才是关键,选错环境分分钟给你玩死
container:
image: node:14 # 想用容器?来!
services:
redis: # 需要依赖服务?安排!
image: redis
strategy:
matrix: # 多环境测试
node: [12, 14, 16]
os: [ubuntu-latest, windows-latest]
fail-fast: false # 一个失败其他继续跑
steps: ...Action(动作)- 组件化的精髓
Action 就是一个可重用的组件,但是它也分三种:
- JavaScript Action
const core = require('@actions/core');
const github = require('@actions/github');
try {
const nameToGreet = core.getInput('who-to-greet');
console.log(`Hello ${nameToGreet}!`);
} catch (error) {
core.setFailed(error.message);
}- Docker Container Action
FROM alpine:3.10
COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]- Composite Action
runs:
using: "composite"
steps:
- run: echo "这他妈就是个组合动作"
shell: bash实战技巧
- Secrets管理
steps:
- name: 配置密钥
env:
SSH_KEY: ${{ secrets.SSH_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa- 缓存依赖
- uses: actions/cache@v3
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-- 条件执行和错误处理
steps:
- name: 部署到生产环境
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
continue-on-error: true # 出错了也别停
run: |
curl ${{ secrets.DEPLOY_HOOK }}实战案例:完整CI/CD流程
name: 完整流程
on:
push:
branches: [ main ]
jobs:
ci-cd:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: 缓存依赖
uses: actions/cache@v3
with:
path: ~/.npm
key: npm-deps-${{ hashFiles('**/package-lock.json') }}
- name: 安装依赖
run: npm ci
- name: 代码检查
run: |
npm run lint
npm run type-check
- name: 运行测试
run: npm test
- name: 构建
run: npm run build
- name: 部署
if: success()
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
echo "$DEPLOY_KEY" > deploy_key
chmod 600 deploy_key
rsync -e "ssh -i deploy_key" ./dist/ user@server:/var/www/最后说点实在的:
- runner要选对,别tm用windows跑linux命令
- cache一定要用,不然慢死你
- secrets别写错,不然调试死你
- 报错信息好好看,别瞎改配置
要是你能把这些都整明白了,那就算入门了。想要进阶?自己写个Action练练手,看看GitHub官方的Action源码。记住:source code is your best teacher。

