Background
Previously, when building patch images for nginx-ingress, I would directly spin up a VPS on Vultr for temporary builds, just for speed and convenience. Although Docker can be configured with a global proxy, the speed difference between building with a local proxy and building in a real network environment under Vultr is significant. For build acceleration, the buildkit parameters have usage conditions. Besides requiring Docker >= 18.09, if the image is heavily focused on compilation builds rather than package installation builds, the improvement in build speed from these parameters is barely noticeable due to resource constraints.
Setting up a global proxy on a Linux host also introduces many pitfalls for the test environment on the machine, especially for Kubernetes environments. For example, you need to set up no_proxy whitelists, etc. After going through these, I felt it was time to find another approach. If you don’t want to use Vultr to spin up a VM, is there a way to pull special images like k8s.io or build quickly? I thought of GitHub Actions.
First, here is the code structure of my private repository:
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
Phase One
Use GitHub Actions to build the image, then save it, and with tailscale/github-action, SCP the image to your Tailscale node.
First, you need to add tag information to the private Tailscale target node and ACL. After the image is built, the Tailscale action plugin cannot automatically authenticate the key; you need to manually click it. For this part, the Tailscale action has two versions, v1 and v2. There is a transition regarding keys between the two versions, causing the ephemeral key designed for temporary nodes to not work well. Additionally, the official Tailscale action no longer recommends using ephemeral key authentication. The official blog post Using GitHub Actions and Tailscale to build and deploy applications is also outdated.
I reported this on Tailscale issues but got no response. The actual problem is that when GitHub Actions triggers a new temporary packaging node, Tailscale authentication succeeds, but it cannot access your Tailscale node network. This causes the GitHub Tailscale temporary node to time out when SCPing to your Tailscale node. After reading many blog posts, I found no useful content addressing this issue. I had to find another workaround.
Phase Two
Use GitHub Actions to build the image, then save it, and use GitHub Actions SSH/SCP to directly transfer it to your public machine without going through a third-party image registry.
After building the image, during the SSH/SCP phase, I struggled with whether to use the Linux SSH public key or private key for the secret variables in GitHub Actions. Eventually, I found that only using the SSH private key worked.
During the transfer, I observed that SCP to a domestic public machine was extremely slow: a 7.8MB package took about 5 minutes, translating to a speed of 27-100 KB/s, which is terrible. Transferring a typical 200MB image would be unimaginable. Could it be that GitHub Actions, due to previous security issues, has limited the speed of direct SSH/SCP transfers? After checking forums both domestically and internationally, I found that many people are complaining about slow SSH/SCP speeds. This path is also not viable.
Phase Three
After pulling/building the image with GitHub Actions, directly push it to Alibaba Cloud’s private image registry, and then locally pull it using a script.
To accommodate more practical scenarios, after completing a single pull/build task, I also continued to support the configuration and testing of multiple simultaneous pull/build image tasks.
Steps to implement pull action
- Support single/multiple pull all serial downloads
- Loop to change tags and push to Alibaba Cloud
Example of implemented functionality:
For instance, if you want to download several special images (example):
k8s.gcr.io/pause:3.2,k8s.gcr.io/pause:3.3,
Due to network restrictions in China, you cannot directly download them without using a proxy or someone else’s retagged images. Now you can directly download them via GitHub Action.
Extract key configuration from your own GitHub Action YAML for explanation:
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
## Convert image name format 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
After successful execution, you can use the local dd.sh script to directly pull the uploaded images and automatically convert them back to the original image tag information: k8s.gcr.io/pause:3.2 k8s.gcr.io/pause:3.3.
The pull process goes through GitHub Action, with Alibaba Cloud private image repository as an intermediary, and the speed is quite fast.
Build Action Implementation Steps
- Distinguish between a single build and parallel builds of multiple images.
- For parallel multi-builds, based on the directory structure of build/build1, build/build2, build/build3, xxx, cd into target_dir/build* to build the image, then change the tag and push to the Alibaba Cloud private repository.
Example of implemented functionality:
For instance, to build the following images: if only building one, simply place the files and Dockerfile to be built under build1.
IMAGE_NAMES: "dedded/tttttt:v0.11.0-amd64,"
If building multiple, just place the different build contents in the build1, build2, build3 directories respectively, simply modify aliyun.yaml, and push to trigger the build.
IMAGE_NAMES: "dedded/tttttt:v0.11.0-amd64,dedded/sssssss:v0.11.0-amd64,bhjbjnbjn/tttttt:v0.11.0-amd64,"
Finally, directly execute the dd.sh script under the git repository to download the build image you just built.
The key aliyun.yaml and dd.sh scripts are attached below for reference:
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 field selects pull or build
ENV_TYPE: build
# IMAGE_NAMES: whether one or multiple image names, must end with a comma. The number of IMAGE_NAMES equals the value of 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 "Number of images to pull: ${#images[@]}"
images=(`echo $IMAGE_NAMES|tr ',' ' '`)
for image in ${images[*]}
do
## Image name format conversion 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`
```yaml
#!/bin/bash
# function: Download special images that are built or pulled and stored in Alibaba Cloud private registry;
# 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
Of course, putting all shell scripts in the action yaml is not elegant. Actually, you can use shell/python scripts to encapsulate, and then directly call them in yaml. When there are many CI functions and many workflows, script encapsulation is undoubtedly better. delete_all_workflow.py is a py3 script for personal use to delete action records in the repository web interface, which is not shown here.
Reference Documents
Advanced if Conditions - Conditional Workflow
Additional Trigger Introduction for Jobs - Step/Job/Workflow Design Theory
continuous integration - If condition in Github Actions for another Job - Stack Overflow
GitHub Action Unrecognized named-value: ‘env’ - Stack Overflow
Using Multi-Job Parallel Builds on GitHub Actions to Speed Up Multi-Arch Image Creation
Using Environment Variables - GitHub Actions
Workflow syntax for GitHub Actions - GitHub Docs
GitHub - appleboy/scp-action: Copy files via SSH.
Comments