在本文中,我们将带你了解在Kubernetes上运行GitHubActionsSelf-hostedRunner在这篇文章中,我们将为您详细介绍在Kubernetes上运行GitHubActionsS
在本文中,我们将带你了解在 Kubernetes 上运行 GitHub Actions Self-hosted Runner在这篇文章中,我们将为您详细介绍在 Kubernetes 上运行 GitHub Actions Self-hosted Runner的方方面面,并解答kubernetes workload常见的疑惑,同时我们还将给您一些技巧,以帮助您实现更有效的Azure kubernetes-Istio和多个kubernetes应用程序?、centos7下kubernetes(10。kubernetes-daemonset)、centos7下kubernetes(11。kubernetes-运行一次性任务)、centos7下kubernetes(14。kubernetes-DNS访问service)。
本文目录一览:- 在 Kubernetes 上运行 GitHub Actions Self-hosted Runner(kubernetes workload)
- Azure kubernetes-Istio和多个kubernetes应用程序?
- centos7下kubernetes(10。kubernetes-daemonset)
- centos7下kubernetes(11。kubernetes-运行一次性任务)
- centos7下kubernetes(14。kubernetes-DNS访问service)
在 Kubernetes 上运行 GitHub Actions Self-hosted Runner(kubernetes workload)
GitHub Actions 很不错,相比较 Travis CI 而言排队不是很严重,除了用于 CI/CD 以外还可以通过提取内部的 DockerHub Credential 放到本地用于 docker pull 来避开 Docker Hub 的 429 Ratelimit 问题(参考:「 同步 docker hub library 镜像到本地 registry [1]」),对于一些小项目而言,GitHub Actions 提供的 Standard_DS2_v2 虚拟机确实性能还行,但是如果对于以下需求,使用 GitHub Actions 自带的机器可能就不是很合适了:
-
编译 TiKV(Standard_DS2_v2 的 2C7G 的机器用 build dist_release
可以编译到死(或者 OOM)) -
需要一些内部镜像协作,或使用到内网资源 -
私有仓库,且需要大量编译(官方的 Action 对于私有仓库只有 2000 分钟的使用时间) -
需要更大的存储空间(官方的 GitHub Actions 只有 15G 不到的可用空间)
这种时候,我们就需要使用 Self-hosted Runner,什么是 Self-hosted Runner?
Self-hosted runners offer more control of hardware, operating system, and software tools than GitHub-hosted runners provide. With self-hosted runners, you can choose to create a custom hardware configuration with more processing power or memory to run larger jobs, install software available on your local network, and choose an operating system not offered by GitHub-hosted runners. Self-hosted runners can be physical, virtual, in a container, on-premises, or in a cloud.
对于一个 Org 而言,要添加一个 Org Level (全 Org 共享的) Runner 比较简单,只需要:
$ mkdir actions-runner && cd actions-runner
$ curl -o actions-runner-linux-x64-2.278.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.278.0/actions-runner-linux-x64-2.278.0.tar.gz
$ ./config.sh --url https://github.com/some-github-org --token AF5TxxxxxxxxxxxA6PRRS
$ ./run.sh
你就可以获得一个 Self hosted Runner 了,但是这样做会有一些局限性,比如:
-
没法弹性扩容,只能一个个手动部署 -
直接部署在裸机上,会有环境不一致的问题
Runner in Containter
Simple Docker
为了解决这个问题,我们需要把 GitHub Runner 给容器化,这里提供一个 Dockerfile 的 Example (魔改自:https://github.com/SanderKnape/github-runner),由于需要使用到类似 dind 的环境(在 Actions 中直接使用到 Docker 相关的指令),所以我加入了 docker 的 binary 进去,由于默认 Runner 不允许以 root 权限运行,为了避开后续挂载宿主机 Docker 的 sock 导致的权限问题,使用的 GitHub Runner 是一个经过修改的版本,修改版本中让 Runner 可以以 root 权限运行,修改的脚本如下:

$ wget https://github.com/actions/runner/releases/download/v2.278.0/actions-runner-linux-x64-2.278.0.tar.gz
$ tar xzf ./actions-runner-linux-x64-2.278.0.tar.gz && rm -f actions-runner-linux-x64-2.278.0.tar.gz
# 这里删除了两个文件中判断是否 root 用户的部分
$ sed -i ''3,9d'' ./config.sh
$ sed -i ''3,8d'' ./run.sh
# End
# 重新打包
$ tar -czf actions-runner-linux-x64-2.278.0.tar.gz *
# 删除解压出来的不需要的文件
$ rm -rf bin config.sh env.sh externals run.sh
然后 Dockerfile 可以这么写
FROM ubuntu:18.04
ENV GITHUB_PAT ""
ENV GITHUB_ORG_NAME ""
ENV RUNNER_WORKDIR "_work"
ENV RUNNER_LABELS ""
RUN apt-get update \
&& apt-get install -y curl sudo git jq iputils-ping zip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& curl https://download.docker.com/linux/static/stable/x86_64/docker-20.10.7.tgz --output docker-20.10.7.tgz \
&& tar xvfz docker-20.10.7.tgz \
&& cp docker/* /usr/bin/
USER root
WORKDIR /root/
RUN GITHUB_RUNNER_VERSION="2.278.0" \
&& curl -Ls https://internal.knat.network/action-runner/actions-runner-linux-x64-${GITHUB_RUNNER_VERSION}.tar.gz | tar xz \
&& ./bin/installdependencies.sh
COPY entrypoint.sh runsvc.sh ./
RUN sudo chmod u+x ./entrypoint.sh ./runsvc.sh
ENTRYPOINT ["./entrypoint.sh"]
其中 entrypoint.sh
的内容如下:
#!/bin/sh
# 这里如果直接使用 ./config.sh --url https://github.com/some-github-org --token AF5TxxxxxxxxxxxA6PRRS 的方式注册的话,token 会动态变化,容易导致注册后无法 remove 的问题,所以参考 https://docs.github.com/en/rest/reference/actions#list-self-hosted-runners-for-an-organization 通过 Personal Access Token 动态获取 Runner 的 Token
registration_url="https://github.com/${GITHUB_ORG_NAME}"
token_url="https://api.github.com/orgs/${GITHUB_ORG_NAME}/actions/runners/registration-token"
payload=$(curl -sX POST -H "Authorization: token ${GITHUB_PAT}" ${token_url})
export RUNNER_TOKEN=$(echo $payload | jq .token --raw-output)
if [ -z "${RUNNER_NAME}" ]; then
RUNNER_NAME=$(hostname)
fi
./config.sh --unattended --url https://github.com/${GITHUB_ORG_NAME} --token ${RUNNER_TOKEN} --labels "${RUNNER_LABELS}"
# 在容器被干掉的时候自动向 GitHub 解除注册 Runner
remove() {
if [ -n "${GITHUB_RUNNER_TOKEN}" ]; then
export REMOVE_TOKEN=$GITHUB_RUNNER_TOKEN
else
payload=$(curl -sX POST -H "Authorization: token ${GITHUB_PAT}" ${token_url%/registration-token}/remove-token)
export REMOVE_TOKEN=$(echo $payload | jq .token --raw-output)
fi
./config.sh remove --unattended --token "${RUNNER_TOKEN}"
}
trap ''remove; exit 130'' INT
trap ''remove; exit 143'' TERM
./runsvc.sh "$*" &
wait $!
Build + 运行:
$ docker build . -t n0vad3v/github-runner
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -e GITHUB_PAT="ghp_bhxxxxxxxxxxxxx7xxxxxxxdONDT" -e GITHUB_ORG_NAME="some-github-org" -it n0vad3v/github-runner
此时你就可以看到你的 Org 下多了一个船新的 Runner 了,现在终于可以利用上自己的机器快速跑任务不排队,而且性能比 GitHub Actions 强了~
Scale with Kubernetes
但是这样并不 Scale,所有的 Runner 都需要手动管理,而且,GitHub Actions 如果同时写了多个 Job ,然后 Runner 数量小于 Job 数量的话,部分 Job 就会一直排队,对于排队时间的话:
Each job for self-hosted runners can be queued for a maximum of 24 hours. If a self-hosted runner does not start executing the job within this limit, the job is terminated and fails to complete.
那这个肯定是没法接受的,正好手边有个 k8s 集群,对于这类基本无状态的服务来说,让 k8s 来自动管理他们不是最好的嘛,于是可以想到写一个 Deployment,比如这样:
apiVersion: apps/v1
kind: Deployment
metadata:
name: github-runner-some-github-org
labels:
app: githubrunner
spec:
replicas: 10
selector:
matchLabels:
app: githubrunner
template:
metadata:
labels:
app: githubrunner
spec:
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: File
containers:
- name: github-runner-some-github-org
imagePullPolicy: Always
image: ''n0vad3v/github-runner''
env:
- name: GITHUB_PAT
value: "ghp_bhxxxxxxxxxxxxx7xxxxxxxdONDT"
- name: GITHUB_ORG_NAME
value: "some-github-org"
- name: RUNNER_LABELS
value: "docker,internal-k8s"
volumeMounts:
- mountPath: /var/run/docker.sock
name: docker-sock
readOnly: false
kubectl apply -f action.yml -n novakwok
,打上 Tag, 起飞!
[root@dev action]# kubectl get po -n novakwok
NAME READY STATUS RESTARTS AGE
github-runner-some-github-org-deployment-9cfb598d9-4shrk 1/1 Running 0 26m
github-runner-some-github-org-deployment-9cfb598d9-5rnj4 1/1 Running 0 26m
github-runner-some-github-org-deployment-9cfb598d9-cvkr9 1/1 Running 0 26m
github-runner-some-github-org-deployment-9cfb598d9-dmbnp 1/1 Running 0 26m
github-runner-some-github-org-deployment-9cfb598d9-ggl24 1/1 Running 0 26m
github-runner-some-github-org-deployment-9cfb598d9-gkgzx 1/1 Running 0 26m
github-runner-some-github-org-deployment-9cfb598d9-jcscq 1/1 Running 0 26m
github-runner-some-github-org-deployment-9cfb598d9-lrrxh 1/1 Running 0 26m
github-runner-some-github-org-deployment-9cfb598d9-pn9cn 1/1 Running 0 26m
github-runner-some-github-org-deployment-9cfb598d9-wj2tj 1/1 Running 0 26m

Demo on Docker
由于我的需求比较特殊,我需要在 Runner 内使用 Docker 相关的指令(比如需要在 Runner 上 docker build/push
),这里测试一下 Runner 是否可以正常工作,首先创建一个多 Job 的任务,像这样:
name: Test
on:
push:
branches: [ main ]
jobs:
test-1:
runs-on: [self-hosted,X64]
steps:
- uses: actions/checkout@v2
- name: Run a one-line script
run: |
curl ip.sb
df -h
lscpu
docker pull redis
test-2:
runs-on: [self-hosted,X64]
steps:
- uses: actions/checkout@v2
- name: Run a one-line script
run: |
curl ip.sb
df -h
lscpu
docker pull redis
test-3:
runs-on: [self-hosted,X64]
steps:
- uses: actions/checkout@v2
- name: Run a one-line script
run: |
curl ip.sb
df -h
lscpu
pwd
docker pull redis
然后跑一下看看是否可以 Work,首先确定是调度到了 Docker Runner 上:

然后看看 Docker 相关的操作是否可以 Work

好耶!
GC
有的时候会由于一些诡异的问题导致 Runner 掉线(比如 Remove 的时候网络断了之类的),这种之后 Org 下就会有一堆 Offline 的 Runner,为了解决这种情况,我们可以写一个简单的脚本来进行 GC,脚本如下:
import requests
import argparse
parser = argparse.ArgumentParser(description=''GC Dead Self-hosted runners'')
parser.add_argument(''--github_pat'', help=''GitHub Personal Access Token'')
parser.add_argument(''--org_name'', help=''GitHub Org Name'')
args = parser.parse_args()
def list_runners(org_name,github_pat):
list_runner_url = ''https://api.github.com/orgs/{}/actions/runners''.format(org_name)
headers = {"Authorization": "token {}".format(github_pat)}
r = requests.get(list_runner_url,headers=headers)
runner_list = r.json()[''runners'']
return runner_list
def delete_offline_runners(org_name,github_pat,runner_list):
headers = {"Authorization": "token {}".format(github_pat)}
for runner in runner_list:
if runner[''status''] == "offline":
runner_id = runner[''id'']
delete_runner_url = ''https://api.github.com/orgs/{}/actions/runners/{}''.format(org_name,runner_id)
print("Deleting runner " + str(runner_id) + ", with name of " + runner[''name''])
r = requests.delete(delete_runner_url,headers=headers)
if __name__ == ''__main__'':
runner_list = list_runners(args.org_name,args.github_pat)
delete_offline_runners(args.org_name,args.github_pat,runner_list)
用法是:python3 gc_runner.py --github_pat "ghp_bhxxxxxxxxxxxxx7xxxxxxxdONDT" --org_name "some-github-org"
Some limitations
除了我们自身硬件限制以外,GitHub Actions 本身还有一些限制,比如:
-
Workflow run time - Each workflow run is limited to 72 hours. If a workflow run reaches this limit, the workflow run is cancelled. -
Job queue time - Each job for self-hosted runners can be queued for a maximum of 24 hours. If a self-hosted runner does not start executing the job within this limit, the job is terminated and fails to complete. -
API requests - You can execute up to 1000 API requests in an hour across all actions within a repository. If exceeded, additional API calls will fail, which might cause jobs to fail. -
Job matrix - A job matrix can generate a maximum of 256 jobs per workflow run. This limit also applies to self-hosted runners. -
Workflow run queue - No more than 100 workflow runs can be queued in a 10 second interval per repository. If a workflow run reaches this limit, the workflow run is terminated and fails to complete.
其中 API requests 这个比较玄学,由于 GitHub Actions 的工作方法官方介绍如下:
The self-hosted runner polls GitHub to retrieve application updates and to check if any jobs are queued for processing. The self-hosted runner uses a HTTPS long poll that opens a connection to GitHub for 50 seconds, and if no response is received, it then times out and creates a new long poll.
所以不是很容易判断怎么样才算是一个 API request,这一点需要在大量使用的时候才可能暴露出问题。
Git Version
这里有个小坑,容器内的 Git 版本建议在 2.18 以上,Ubuntu 18.04 没问题(默认是 2.22.5),但是 arm64v8/ubuntu:18.04
官方源包管理工具的 Git 版本是 2.17,如果用这个版本的话,会遇到这种问题:

所以需要编译一个高版本的 Git,比如 Dockerfile 可以加上这么一行:
$ apt install -y gcc libssl-dev libcurl4-gnutls-dev zlib1g-dev make gettext wget
$ wget https://www.kernel.org/pub/software/scm/git/git-2.28.0.tar.gz && tar -xvzf git-2.28.0.tar.gz && cd git-2.28.0 && ./configure --prefix=/usr/ && make && make install
小结
如上,我们已经把 Runner 封进了 Docker 容器中,并且在需要 Scale 的情况下通过 k8s 进行水平扩展,此外,我们还有一个简单的 GC 程序对可能异常掉线的 Runner 进行 GC,看上去已经满足了一些初步的需求啦~
但是这样还是有一些问题,比如:
-
用 root 用户跑容器可能会有潜在的风险,尤其是还暴露了宿主机的 Docker sock,所以对于普通的任务来说,还是需要一个非 root 用户的容器来运行 -
还是没有实现自动化扩缩容,扩缩容依赖手动修改 replica,这里需要进行自动化(例如预留 20 个 Idle 的 Runner,如果 Idle Runner 小于 20 个就自动增加) -
Label 管理,由于 GitHub Actions 依赖的 Label 进行调度,所以这里打 Label 其实是一个需要长期考虑的事情
References
-
Running self-hosted GitHub Actions runners in your Kubernetes cluster [2] -
About GitHub-hosted runners [3] -
Actions [4]
脚注
同步 docker hub library 镜像到本地 registry : https://blog.k8s.li/sync-dockerhub-library-images.html
[2]Running self-hosted GitHub Actions runners in your Kubernetes cluster: https://sanderknape.com/2020/03/self-hosted-github-actions-runner-kubernetes/
[3]About GitHub-hosted runners: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
[4]Actions: https://docs.github.com/en/rest/reference/actions#list-self-hosted-runners-for-an-organization
你可能还喜欢
点击下方图片即可阅读
云原生是一种信仰
关注公众号
后台回复◉k8s◉获取史上最方便快捷的 Kubernetes 高可用部署工具,只需一条命令,连 ssh 都不需要!

点击 "阅读原文" 获取更好的阅读体验!
发现朋友圈变 “安静” 了吗?
本文分享自微信公众号 - 云原生实验室(cloud_native_yang)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与 “OSC 源创计划”,欢迎正在阅读的你也加入,一起分享。
Azure kubernetes-Istio和多个kubernetes应用程序?
通常,Istio控制平面将安装在其自身的名称空间中,该名称空间具有很高的可用性。这意味着具有其组件的多个实例。 Istio本身会与每个启用istio的应用程序一起部署特使代理,这些应用程序会将其相应的指标发送到已部署的控制平面。因此,我建议您一次将控制平面安装在一个单独的命名空间中,此外,这也与您对应用程序追求的隔离策略相对应。
有关如何在应用程序中启用istio的信息,请参见以下文档:https://istio.io/latest/docs/setup/additional-setup/sidecar-injection/
您有两个选择:
- 手动安装Sidecar容器。
- 让侧车自动注入。
centos7下kubernetes(10。kubernetes-daemonset)
deployment部署得副本pod会分布在各个node上,每个node上可以运行很多个pod。
daemonset的不同之处就在于,daemonset可以让每个node上只运行一个pod
daemonset的应用场景有:
1.在集群的每个节点上运行存储daemonset,比如glusterd或ceph
2.在那个节点上运行日志收集daemon,比如 flunentd 或 logstash
3.在每个节点上运行监控Daemon,比如 Prometheus Node Exporter 或 collectd
我们之前部署kubernetes时就发现master以及每个node上面运行就是K8s的系统组件,执行如下命令:
kubectl get daemonset --namespace=kube-system
daemonset kube-flannel-ds和kube-proxy分别负责在每个节点上运行flannel和kube-proxy组件
我们之前部署flannel的时候的时通过yml中部署的,我们现在查看一下flannel的yml文件
kind:属于daemonset
hostnetwork:指定pod使用的是node的网络,相当于docker run --network=host。考虑到flannel要为集群提供网络连接,这个要求是合理的
containers:定义了运行flannel服务的容器
kube-proxy
由于kube-proxyde 是我们在部署的时候直接使用命令起来的,可以使用
kubectl edit daemonset kube-proxy --namespace=kube-system
kind:daemonset类型
containers:定义了proxy的容器
status:是当前daemonset运行时的状态,这个部分是kubectl edit特有的
k8s集群中每个运行的资源都可以通过kubectl edit查看其配置和运行状态,比如kubectl edit deployment nginx-deployment
centos7下kubernetes(11。kubernetes-运行一次性任务)
容器按照持续运行的时间可以分为两类:服务类容器和工作类容器
服务类容器:持续提供服务
工作类容器:一次性任务,处理完后容器就退出
Deployment,replicaset和daemonset都用于管理服务类容器,
对于工作类的容器,我们用job
编辑一个简单的job类型的yml文件
1.apiversion:当前job的apiversion是batch/v1
2.kind:当前的资源类型是job
3.restartpolicy指定什么情况下需要重启容器。对于job只能设置为never或者onfailure
对于其他的controller(比如deployment,replicaset等)可以设置为always
创建job应用
通过kubectl get job进行查看
显示destire为1,成功1
说明是按照预期启动了一个pod,并且成功执行
查看pod的状态
由于myjob的pod处于completed的状态,所以需要加--show-all参数才能显示出来
通过kubectl logs 查看pod标准输出
如果job没有执行成功,怎么办?
修改job.yml文件,故意引起一个错误,然后重新启动myjob
先将原来的job删除
然后重新启动一个新的job
重新启动一个job,我们发现有一个未成功的job,查看pod的时候竟然有两个job相关的pod,目标job只有1个啊,为什么??
我们再次查看一下
目前达到了6个
原因是:当地一个pod启动时,容器失败退出,根据restartPolicy:Never,此失败容器不会被重启,但是job destired的pod是1,目前successful为1。由于我们的命令是错误的,successful永远不能到1,
job contorller会一直创建新的pod达到job得期望状态,最多重新创建6次,因为K8S为job提供了spec.bakcofflimits来限制重试次数,默认为6.
如果将restartpolicy设置为OnFailure会怎么样?我们来实验一下
修改job.yml文件
将restartpolicy修改为OnFailure
重新启动job.yml
pod数量只有1,job为失败得状态
但是pod得restart得次数在变化,说明onfailure生效,容器失败后会自动重启
并行执行job
之前我们得实验都是一次运行一个job里只有一个pod,当我们同时运行多个pod得时候,怎么进行设置呢?
可以通过:parallelism设置
修改job.yml文件
此次我们执行一个job同时运行3个pod
kubectl apply -f job.yml
job一共启动了3个pod,而且AGE相同,说明是并行运行得。
还可以通过completions设置job成功完成pod的总数;
配置含义:每次运行3个pod,知道运行了6个结束
重新执行一下
kubectl apply -f job.yml
也不是很准,但是确实不是同时并行启动的
定时执行job
kubernetes提供了类似crontab定时执行任务的功能
首先修改apiserver使api支持cronjob
vim /etc/kubernetes/manifests/kube-apiserver.yaml
- --runtime-config=batch/v1beta1=true 加入这一行
保存退出
kubectl apiversions 查看api版本(如果这里没有生效的话,需要重启kubelet这个服务)
systemctl restart kubelet.service
修改yml文件如下:
apiVersion: batch/v1beta1 batch/v1beta1当前cronjob的apiserver
kind: CronJob 当前资源类型为cronjob
metadata:
name: cronjob
spec:
schedule: "*/1 * * * *" 指定什么时候运行job,格式与linux中的计划任务一致
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
command: ["echo","hello k8s job!"]
restartPolicy: OnFailure
~
运行这个job
kubectl apply -f job.yml
如果出现一下错误请一定要检查yml文件的内容,进行修改
正常运行如下:
查看cronjob
kubectl get cronjob
查看job,通过时间间隔可以看到,每1分种创建一个pod
kubectl get job
查看pod日志
删除cronjob
kubectl delete cronjob cronjob
centos7下kubernetes(14。kubernetes-DNS访问service)
我们在部署kubernetes时,会自动部署dns组件,其作用是通过dns解析的方法访问service
coredns是一个DNS服务器,每当有新的service被创建,kube-dns会添加该service的dns记录。cluster中的pod可以通过{service_name.namespace_name:port}访问service
比如我们接着上一个实验,通过httpd-svc.default访问service httpd-svc
我们在一个临时的pod里面验证了DNS的有效性。
这个临时的pod与httpd-svc同属于一个default namespace,可以忽略default直接用httpd-svc访问service
要访问其他的namespace中的service,就必须带上namespace了
kubectl get namespace 查看已有的namespace
在 kube-public
中部署 Service httpd2-svc
,配置如下
kind: Deployment
metadata:
name: httpd2
namespace: kube-public
spec:
replicas: 3
template:
metadata:
labels:
run: httpd2
spec:
containers:
- name: httpd2
image: httpd
ports:
- containerPort: 80
apiVersion: v1
kind: Service
metadata:
name: httpd2-svc
namespace: kube-public
spec:
selector:
run: httpd2
ports:
- protocol: TCP
port: 8080
targetPort: 80
通过namespace:kube-public指定资源所属namespace。
多个资源可以在同一个yml文件中定义,用 --- 分割。
创建资源 kubectl apply -f httpd2.yml
一定要注意yml文件的格式,一个字母都不能错
查看kube-public的service
kubectl get service --namespace=kube-public
现在在临时的pod中访问httpd2-svc
因为同属于不同namespace,所以必须使用httpd2-svc.kube-public 才能访问到
关于在 Kubernetes 上运行 GitHub Actions Self-hosted Runner和kubernetes workload的问题我们已经讲解完毕,感谢您的阅读,如果还想了解更多关于Azure kubernetes-Istio和多个kubernetes应用程序?、centos7下kubernetes(10。kubernetes-daemonset)、centos7下kubernetes(11。kubernetes-运行一次性任务)、centos7下kubernetes(14。kubernetes-DNS访问service)等相关内容,可以在本站寻找。
本文标签: