Github Action 探索
Posted on September 2, 2023 (Last modified on June 19, 2024) • 6 min read • 2,669 wordsGithub Action
以往在 build nginx-ingress 的补丁镜像的时候,都是直接在 vultr 上开一台 vps 做临时构建的。为的只是快和方便。虽然 docker 可以配置全局代理,但本地代理的 docker build 速度和 vultr 下真实的网络环境 build, 那速度差距还是蛮大的。构建加速的部分,buildkit 参数有使用条件的。除了 docker >= 18.09 版本要求,如果镜像偏向重度的编译构建非包安装构建,该参数对于镜像 build 的速度提升,受限于资源条件自己实际几乎感知不大。
linux 主机开全局代理后,对于机器上的测试环境也有很多的坑, 尤其是 k8s 环境。比如要注意设置 no_proxy 白名单等,趟过这些后,总觉得该换换别的办法了。如果不想用 vultr 开虚拟机,有没有啥办法,既可以 pull k8s.io 等特殊镜像或者快速 build 的办法?自己想到了 github action。
先给出自己私有仓库的代码结构:
root@xx /github-action (main) $ tree
.
├── build
│ ├── build1
│ │ └── Dockerfile
│ ├── build2
│ │ └── Dockerfile
│ └── build3
│ └── Dockerfile
├── dd.sh
├── README.md
└── toolkit
└── delete_all_workflow.py
root@agub20 /tmp/github-action (main) $ tree .github/workflows/
.github/workflows/
├── aliyun.yaml
├── ddns-build.yaml
└── tailscale-build.yaml
github action build 完成镜像,然后 save, 在配合 tailscale/github-action , scp 镜像给自己的 tailscale node。
首先需要在私有 tailscale 目标 node 及 acl 上添加 tag 信息。待 build 的镜像完成,tailscale action 的插件,无法自动认证 key,要手动点一下。针对这个部分,tailscale action 有两个版本 v1, v2。 两个版本内部针对 key 有一个过渡。导致专为临时节点设置的 ephemeral key 并不能很好的适配。同时官方的 tailscale action 已经不推荐你用 ephemeral key 的认证方式了。官方示例博文 Using GitHub Actions and Tailscale to build and deploy applications 也是失效的。
tailscale issues 上反馈了下,没有回复。最后实际上的问题是,github action 触发新增加的临时打包 node tailscale 认证成功了,但是他并不能获取到你的 tailscale node 网络。导致 github tailscale tmp node scp 自己的 tailscale node 超时。查阅了很多博文,针对这个问题,并没有比较有用的内容。只能换别的方法绕行了。
github action build 完成镜像,然后 save, 在配合 github action ssh/scp 直接传送给自己的公网机器,不走三方的镜像仓库。
build 镜像完成后,在 ssh/scp 阶段,对于github action 后台的秘密变量到底使用 linux 的 ssh 公钥还是私钥的问题上,折腾了一会。最终发现只有使用 ssh 的私钥才成功了。
在传输的过程中,观察到 scp 给国内公网机器的速度巨慢,7.8M的包,耗时了大约 5min, 换算网速在 27-100 KB/s,简直了。如果要传输一个普通的 200M 的镜像,那简直不敢想。是不是 github action 之前出现的安全问题,让其限制了往外直接 ssh/scp 的传输速度。查询了下国内外的论坛,发现 ssh/scp 慢的问题,不只是我在吐槽。这条路也不能走了。
github action pull/build 完成镜像后,直接推送到阿里云的镜像私有仓库,最后本地在使用脚本拉取就好了。
为了兼容更多的实际场景,在完成单 pull/build 任务后,也继续兼容了同时多 pull/build 镜像任务的配置和测试。
实现的功能举例:
比如想要下载几个特殊镜像(举例):
k8s.gcr.io/pause:3.2,k8s.gcr.io/pause:3.3,
因为国内网络原因,如果不用代理,或者不用别人的转存更改的 tag 镜像,你是不能直接下载的。现在可以直接通过 github action 下载;
截取自己 github action yaml 中的关键配置说明:
env:
IMAGE_NAMES: "k8s.gcr.io/pause:3.2 k8s.gcr.io/pause:3.3,"
images=(`echo $IMAGE_NAMES|tr ',' ' '`)
for image in ${images[*]}
do
## 镜像名格式转换 xx/xx:xx ---> xx-xx-xx
RELEASE=`echo $image |sed 's/\//-/g; s/:/-/g'`
docker pull $image
docker tag $image registry.cn-hangzhou.aliyuncs.com/xxxx/demp:$RELEASE
docker push registry.cn-hangzhou.aliyuncs.com/xxx/domo:$RELEASE
done
执行成功后,然后使用本地的 dd.sh 脚本,可以直接拉取你上传的镜像,并自动转换为原始的镜像 tag 信息 k8s.gcr.io/pause:3.2 k8s.gcr.io/pause:3.3。
pull 过程就是走 github action , aliyun 私有镜像仓库中转了一下,速度还是非常快的。
实现的功能举例:
比如像 build 如下的镜像, 如果只 build 一个,只需将需要 build 的文件及 Dockerfile 传入build1下,
IMAGE_NAMES: "dedded/tttttt:v0.11.0-amd64,"
如果是 build 多个, 只需将不同的 build 内容分别放在 build1, build2, build3 的目录下,简单更改 alyun.yaml , push 即可触发 build
IMAGE_NAMES: "dedded/tttttt:v0.11.0-amd64,dedded/sssssss:v0.11.0-amd64,bhjbjnbjn/tttttt:v0.11.0-amd64,"
最后直接执行 git 仓库下的 dd.sh 脚本即可下载你刚刚构建的 build 镜像。
关键的 aliyun.yaml ,dd.sh 脚本贴在下方,仅供参考:
aliyun.yaml
name: build/pull docker forward to aliyun
on:
push:
paths:
# - dnnl/**
- .github/workflows/aliyun.yaml
pull_request:
paths:
# - dnnl/**
- .github/workflows/aliyun.yaml
workflow_dispatch:
env:
# ENV_TYPE 字段选择为 pull or build
ENV_TYPE: build
# IMAGE_NAMES 镜像名无论是一个还是多个,末尾都要加逗号结尾。 IMAGES_NAMES镜像的个数 等于 NUM_IMAGES 的值
IMAGE_NAMES: "dedded/tttttt:v0.11.0-amd64,dedded/sssssss:v0.11.0-amd64,bhjbjnbjn/tttttt:v0.11.0-amd64,"
NUM_IMAGES: 3
jobs:
# build and push
Pull-Or-Build-1st:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: first steps
run: |
docker login --username="${{ secrets.DOCKER_ALIYUN_USERNAME }}" --password="${{ secrets.DOCKER_ALIYUN_PASSWD }}" registry.cn-hangzhou.aliyuncs.com
if [ $ENV_TYPE == 'pull' ]; then
echo "next to $ENV_TYPE"
elif [ $ENV_TYPE == 'build' ]; then
echo "next to $ENV_TYPE"
else
echo 'ENV_TYPE is null'
exit 1
fi
- name: pull images
run: |
if [ "$ENV_TYPE" == "pull" ]; then
echo "$GITHUB_JOB run $ENV_TYPE"
## echo "当前下载的镜像个数:${#images[@]}"
images=(`echo $IMAGE_NAMES|tr ',' ' '`)
for image in ${images[*]}
do
## 镜像名格式转换 xx/xx:xx ---> xx-xx-xx
RELEASE=`echo $image |sed 's/\//-/g; s/:/-/g'`
docker pull $image
docker tag $image registry.cn-hangzhou.aliyuncs.com/opsbase/demo:$RELEASE
docker push registry.cn-hangzhou.aliyuncs.com/opsbase/demo:$RELEASE
done
else
echo "$GITHUB_JOB: No need to pull"
fi
- name: build single images
run: |
if [ "$ENV_TYPE" == "build" -a $NUM_IMAGES == 1 ]; then
echo "$GITHUB_JOB run $ENV_TYPE"
images=(`echo $IMAGE_NAMES|tr ',' ' '`)
cd build/build1
build_name=${images[0]}
RELEASE=`echo $build_name |sed 's/\//-/g; s/:/-/g'`
docker build -t $build_name .
docker tag $build_name registry.cn-hangzhou.aliyuncs.com/opsbase/demo:$RELEASE
docker push registry.cn-hangzhou.aliyuncs.com/opsbase/demo:$RELEASE
elif [ "$ENV_TYPE" == "build" -a $NUM_IMAGES -gt 1 ]; then
echo "forward to multi build"
else
echo "$GITHUB_JOB: No need to build"
fi
multi-build1:
runs-on: ubuntu-latest
needs: Pull-Or-Build-1st
steps:
- uses: actions/checkout@v3
- run: |
echo "multi-build1"
images=(`echo $IMAGE_NAMES|tr ',' ' '`)
if [ "$ENV_TYPE" == "build" -a $NUM_IMAGES -gt 1 ]; then
cd build/build1
build_name=${images[0]}
RELEASE=`echo $build_name |sed 's/\//-/g; s/:/-/g'`
echo $build_name $RELEASE
docker build -t $build_name .
docker tag $build_name registry.cn-hangzhou.aliyuncs.com/opsbase/demo:$RELEASE
docker login --username="${{ secrets.DOCKER_ALIYUN_USERNAME }}" --password="${{ secrets.DOCKER_ALIYUN_PASSWD }}" registry.cn-hangzhou.aliyuncs.com
docker push registry.cn-hangzhou.aliyuncs.com/opsbase/demo:$RELEASE
elif [ "$ENV_TYPE" == "pull" -o $NUM_IMAGES == 1 ]; then
echo "no need to muti-build"
else
echo 'mult-build error'
exit 1
fi
multi-build2:
runs-on: ubuntu-latest
needs: Pull-Or-Build-1st
steps:
- uses: actions/checkout@v3
- run: |
echo "multi-build2"
images=(`echo $IMAGE_NAMES|tr ',' ' '`)
if [ "$ENV_TYPE" == "build" -a $NUM_IMAGES -gt 1 ]; then
cd build/build2
build_name=${images[1]}
RELEASE=`echo $build_name |sed 's/\//-/g; s/:/-/g'`
echo $build_name $RELEASE
docker build -t $build_name .
docker tag $build_name registry.cn-hangzhou.aliyuncs.com/opsbase/demo:$RELEASE
docker login --username="${{ secrets.DOCKER_ALIYUN_USERNAME }}" --password="${{ secrets.DOCKER_ALIYUN_PASSWD }}" registry.cn-hangzhou.aliyuncs.com
docker push registry.cn-hangzhou.aliyuncs.com/opsbase/demo:$RELEASE
elif [ "$ENV_TYPE" == "pull" -o $NUM_IMAGES == 1 ]; then
echo "no need to muti-build"
else
echo 'mult-build error'
exit 1
fi
multi-build3:
runs-on: ubuntu-latest
needs: Pull-Or-Build-1st
steps:
- uses: actions/checkout@v3
- run: |
echo "multi-build3"
images=(`echo $IMAGE_NAMES|tr ',' ' '`)
if [ "$ENV_TYPE" == "build" -a $NUM_IMAGES -gt 1 ]; then
cd build/build3
build_name=${images[2]}
RELEASE=`echo $build_name |sed 's/\//-/g; s/:/-/g'`
echo $build_name $RELEASE
docker build -t $build_name .
docker tag $build_name registry.cn-hangzhou.aliyuncs.com/opsbase/demo:$RELEASE
docker login --username="${{ secrets.DOCKER_ALIYUN_USERNAME }}" --password="${{ secrets.DOCKER_ALIYUN_PASSWD }}" registry.cn-hangzhou.aliyuncs.com
docker push registry.cn-hangzhou.aliyuncs.com/opsbase/demo:$RELEASE
elif [ "$ENV_TYPE" == "pull" -o $NUM_IMAGES == 1 ]; then
echo "no need to muti-build"
else
echo 'mult-build error'
exit 1
fi
dd.sh
#!/bin/bash
# function: 下载 build 或者 pull 转存在阿里云镜像私有仓库的特殊镜像;
# author: yh
# last update: 2023.8.31
function down_images() {
images_pre=`grep IMAGE_NAMES: .github/workflows/aliyun.yaml|awk '{print $NF}'`
num_images=`grep 'NUM_IMAGES:' .github/workflows/aliyun.yaml |awk '{print $NF}'`
for i in $(seq 1 $num_images)
do
image=`echo $images_pre|awk -F '"' '{print $2}'|cut -d ',' -f $i`
RELEASE=`echo $images_pre|awk -F '"' '{print $2}'|cut -d ',' -f $i|sed 's/\//-/g; s/:/-/g'`
docker pull registry.cn-hangzhou.aliyuncs.com/opsbase/demo:${RELEASE}
docker tag registry.cn-hangzhou.aliyuncs.com/opsbase/demo:${RELEASE} ${image}
done
}
down_images
当然把所有的shell脚本集中在 action yaml 中并不优雅,其实可以使用 shell/python 脚本封装,然后在 yaml 中直接调用就行。当 ci 功能比较多,流程也比较多的时候,脚本封装无疑会更加好一点。delete_all_workflow.py 为自用删除仓库 web 界面 action 记录的 py3 脚本,就不额外展示了。
高级的 if 判断条件 - Conditional Workflow
job的一些额外触发介绍-Step/Job/Workflow設計論
continuous integration - If condition in Github Actions for another Job - Stack Overflow
GitHub Action Unrecognized named-value: 'env' - Stack Overflow
在 GitHub Actions 上使用多 Job 并行构建,提升 Multi-Arch 镜像制作速度
使用环境变量 - GitHub Actions
Workflow syntax for GitHub Actions - GitHub Docs
GitHub - appleboy/scp-action: Copy files via SSH.