ci cd github actions

29 阅读7分钟

所有程序员最痛恨的就是:重复的无脑的动作。所以还是交付给自动化进行处理吧。

image.png

持续集成指的是代码更改持续集成到仓库中,

image.png

cd是指让仓库的代码持续地交付到测试环境或者生产环境中。

用github actions自动集成和交付。

示例项目放在github中。

需要在项目中新建一个.github的文件夹。再在.github文件夹下新建一个workflows,就在这个目录下创建一个yaml文件,比如说test.yml文件。

需要在这个test.yml文件下写workflow包含的信息,比如说叫什么、怎样触发、做什么。

它要在什么时间做什么事情,

使用name给这个workflow起个名字,

// test.yml

name: 'Test'

on: 

用on去表示这个workflow要在什么时候触发。触发的时候有很多类型。

比如提交push的时候,比如提交pr的pull_requests的时候,再比如说创建issues的时候,比如说定时触发schedule的时候:

// test.yml

name: "Test",

on: 
  push:
  pull_reqeusts:
  issues:
  schedule:

基本上在github做的所有事情,都可以做成workflow,可以在文档中找到更多类型。

触发 workflow 的事件:docs.github.com/en/actions/…

在on写了多条时机的时候,就会触发workflow,考虑到我们这个workflow是用于测试代码,那我们先用push和pullRequest来看看。

但我们不会在任何分支在push或者提交pr的时候都执行这个workflow,我们只希望main分支会执行。所以我们要增加一个过滤条件。

name: "Test"

on: 
  push:
    branches:
      - main
  pull_requests:
    branches:
      - main
      

如果我们只是希望main这个分支执行,我们就要增加一个过滤条件。

name: "Test"

on: 
  push:
    branches:
      - main
  pull_requests:
    branches:
      - main
      

但对于pr来说,pr有好多种行为,可能是开启一个pr,或者关掉一个pr,只是想限定开启的时候,所以就要限定它的类型。限定它的类型是开启。

name: "Test"

on: 
  push:
    branches:
      - main
  pull_requests:
    branches:
      - main
    types: [ opened ]

这样就完成了触发时机的设定。它可以是push到main分支的时候,也可以是对main分支开启一个pr的时候,只要满足其他一个条件,这个workflow就会被触发。

接下来就要定义这个workflow要做什么事,

需要一个关键字叫 jobs,一个job就是workflow要执行的一组任务,这个workflow要测试代码,所以我们的job就叫做test,

使用runs-on代表我们这个workflow需要在什么样的虚拟机上跑,在大部分情况下Ubuntu都是很好的选择,

name: "Test"

on: 
  push:
    branches:
      - main
  pull_requests:
    branches:
      - main
    types: [ opened ]
    
jobs:
  test:
    runs-on: ubuntu-latest

但如果你有特殊需求也可以用MacOS或者windows。如果需要一些基本的环境,比如nodejs python go等等,可以实施一个额容器,这样即不用再在虚拟机上安装了,因为我这个是个python项目,所以用一个uv的container

name: "Test"

on: 
  push:
    branches:
      - main
  pull_requests:
    branches:
      - main
    types: [ opened ]
    
jobs:
  test:
    runs-on: ubuntu-latest
    container: astral/uv:python3.12-bookworm-slim

接下来就要分步骤,说要怎样测代码

name: "Test"

on: 
  push:
    branches:
      - main
  pull_requests:
    branches:
      - main
    types: [ opened ]
    
jobs:
  test:
    runs-on: ubuntu-latest
    container: astral/uv:python3.12-bookworm-slim
    steps:

在我们本机测试代码,就是把代码拉下来,然后安装依赖跑测试。没问题的话,就合并了,所以第一个step就是获取代码,

name: "Test"

on: 
  push:
    branches:
      - main
  pull_requests:
    branches:
      - main
    types: [ opened ]
    
jobs:
  test:
    runs-on: ubuntu-latest
    container: astral/uv:python3.12-bookworm-slim
    steps:
      name: Checkout

steps可以是一个命令,或者别人封好的workflow。

image.png github.com/marketplace…

在github的市场可以看到很多,比如说这里的checkout,就是我们第一个step。

image.png

可以让我们获取代码。使用uses就可以用workflow了,

name: "Test"

on: 
  push:
    branches:
      - main
  pull_requests:
    branches:
      - main
    types: [ opened ]
    
jobs:
  test:
    runs-on: ubuntu-latest
    container: astral/uv:python3.12-bookworm-slim
    steps:
      name: Checkout
      uses: actions/checkout@v6

其实name这个属性可以不填,但是如果以后希望一看到就能读懂,最好还是写上去。这个地方的每一个step,都是一个列表的item,所以需要用列表形式:

name: "Test"

on: 
  push:
    branches:
      - main
  pull_requests:
    branches:
      - main
    types: [ opened ]
    
jobs:
  test:
    runs-on: ubuntu-latest
    container: astral/uv:python3.12-bookworm-slim
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Install Dependency

下一步是安装依赖,这次用的是run,而不是uses,表示我们要执行一个命令,而不是使用封装好的actions,所以我们用uv sync去安装依赖,如果是node的话,就用npmci,如果是go项目就用go mod download,总之随着你的项目来使用不同的命令。

下一步是测试代码,用pytest进行测试。

name: "Test"

on: 
  push:
    branches:
      - main
  pull_requests:
    branches:
      - main
    types: [ opened ]
    
jobs:
  test:
    runs-on: ubuntu-latest
    container: astral/uv:python3.12-bookworm-slim
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Install Dependency
        run: uv sync
      - name: Test
        run: uv run pytest tests/

同样如果你是node项目就用npm test,如果是go,就用go test,总之什么项目用什么测试方法。这样就完成了一个job的定义。

实际上还可以写更多的job。比如说test2什么的。

name: "Test"

on: 
  push:
    branches:
      - main
  pull_requests:
    branches:
      - main
    types: [ opened ]
    
jobs:
  test:
    runs-on: ubuntu-latest
    container: astral/uv:python3.12-bookworm-slim
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Install Dependency
        run: uv sync
      - name: Test
        run: uv run pytest tests/
        
   test2:
     

如果说我们定义了多个job,那么这两个job会big你小姑娘运行,而不是按照先后顺序运行,但我们的需求是一个job就够了,但我们的需求是一个job就够了,

将这个workflow push到github仓库上去。


在vscode当中,可以用git的插件,commit上去 add github actions workflow for testing

回到github页面看,

actions已经 有一个workflow。是刚刚运行有一个x表示run失败了。点进去看看,表示是pull reqeusts写错了。

name: "Test"

on: 
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
    types: [ opened ]
    
jobs:
  test:
    runs-on: ubuntu-latest
    container: astral/uv:python3.12-bookworm-slim
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Install Dependency
        run: uv sync
      - name: Test
        run: uv run pytest tests/

pull_request是没有s的。再次刷新。

再次失败,

image.png

没有搜到测试项目,因为在创建这个实例的时候,没有写测试项目,不重要,还是成功创建了一个workflow,

以上就是ci cd 中的ci。

cd

在workflows下创建deploy.yml,

// deploy.yml

name: "Deploy"

on:

on是触发时机,每当push或者pr的时候,不希望一有代码更改就马上部署,

// deploy.yml

name: "Deploy"

on:
  push:
    tag:
     - v*

可以将push的条件改成,帮我们push一个,以v开头的tag的时候,比如说v1.0,v2.0 这些push这样的一个tag就表示要发版了,

接着就是jobs。那么设置一个,

// deploy.yml

name: "Deploy"

on:
  push:
    tag:
     - v*

jobs:
  Deploy:
    runs-on: ubuntu-latest

如果是在本机的话,就先将代码拉下来放到服务器上,再到服务器上启动服务,先按流程来,这个时候我们就不需要使用容器了,因为我们并不需要运行什么东西,我们只是简单地把代码拉下来,然后放到服务器上,

// deploy.yml

name: "Deploy"

on:
  push:
    tag:
     - v*

jobs:
  Deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6

如果对shell命令很熟的话,可以直接写scp命令,如果部署,还得查各种参数的用法,

所以去选择直接去市场找了一个action,

// deploy.yml

name: "Deploy"

on:
  push:
    tag:
     - v*

jobs:
  Deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Upload
        uses: appleboy/scp-action@v1
        with:
          host:
          username:
          key:

appleboy/scp-action@v1 用的github上面的aciton,需要添加一些参数。

在仓库的settings,在下面的Secrets and variables里面的Actions,添加一个secret,定义名字和添加敏感信息:

image.png

// deploy.yml

name: "Deploy"

on:
  push:
    tag:
     - v*

jobs:
  Deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Upload
        uses: appleboy/scp-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username:
          key:

secret 就有了,username和key也是一样的道理:

// deploy.yml

name: "Deploy"

on:
  push:
    tag:
     - v*

jobs:
  Deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Upload
        uses: appleboy/scp-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USERNAME }}
          key: ${{ secrets.SERVER_KEY }}

接着还要写源位置和目标位置: // deploy.yml

name: "Deploy"

on:
  push:
    tag:
     - v*

jobs:
  Deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Upload
        uses: appleboy/scp-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USERNAME }}
          key: ${{ secrets.SERVER_KEY }}
          source: "./"
          target: "/home/usr/projects"

表示要从现在的位置,搬到服务器上的位置。

最后在服务器上启动服务: // deploy.yml

name: "Deploy"

on:
  push:
    tag:
     - v*

jobs:
  Deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Upload
        uses: appleboy/scp-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USERNAME }}
          key: ${{ secrets.SERVER_KEY }}
          source: "./"
          target: "/home/usr/projects"
       - name: Start service
         uses: appleboy/ssh-action@v1
         with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USERNAME }}
          key: ${{ secrets.SERVER_KEY }}
          script: |
            cd /home/usr/projects
            python main.py

如果很熟悉shell命令,可以直接用run来写命令,这里用action去完成。

这样就是写好了一个cd的workflow。