GitHub Actions:在Kubernetes中運行自托管Runner的那些事兒
我们使用 GitHub Actions 的 Runners(运行器)进行部署,后来觉得把它们放到我们自己的 Kubernetes 集群上会更好,因为:
- 自托管的 GitHub 运行器更省钱——实际上,你只需支付运行作业的服务器费用
- 我们需要在 AWS RDS 上运行 SQL 迁移,该 RDS 位于 AWS VPC 的私有子网中
- 性能不受限制——我们可以使用任何类型的 AWS EC2 以及各种磁盘类型,不受 CPU/内存和 IOPS 的限制
关于 — 关于自托管运行器的介绍
GitHub 有一个单独的控制器用于这个 — Actions Runner Controller (ARC),我们将会使用它 (ARC)。
我们使用AWS Elastic Kubernetes Service v1.30,Karpenter v1.0进行EC2自动扩展,并用AWS Elastic Container Registry存储Docker镜像。
自托管运行器有一些Usage limits,但我们不太可能碰到这些问题。
今天我们要做什么呢:
- 使用 Helm 安装 Runner Controller Actions,并设置一个带有 Runners 的 Scale Set,以便在测试仓库中查看整体操作
- 将创建一个带有
taints
的 Karpenter NodePool,以确保 Runners 在专用的 Kubernetes 工作节点上运行 - 尝试实际构建和部署我们的后端 API,看看会发生什么错误
- 将创建我们自己的 Docker 镜像以用于 Runners
- 尝试 Docker in Docker 模式,看看效果如何
- 创建一个具有高 IOPS 的独立 Kubernetes 存储类,并观察它如何影响构建和部署的速度
首先,我们先手动做所有事情,然后尝试真正运行构建并部署 GitHub Actions 流程。
GitHub 授权文档 — 使用个人访问令牌的传统身份验证方法 (GitHub API 身份验证)
这里有两种选择——一是通过 GitHub 应用来更为正式地设置生产环境,二是使用个人访问令牌。
GitHub 应用程序似乎是个更好的选择,但我们是个小公司,使用个人令牌会更方便,所以我们现在先这么干,如果以后真的有必要的话,到时候我们再正规地做。
进入你的个人资料页面,点击设置,然后找到开发者设置,再点击个人访问令牌,点击生成新令牌。
目前暂时,我们只为一个仓库使用自托管运行程序,仅将权限设置为 repo
。
最好设置一个到期日期,但这只是一个 PoC(之后会像往常一样投入正式使用),所以暂时就这样——那就让它一直有效吧。
为 runner 创建一个 Kubernetes 命名空间:
$ kk create ns ops-github-runners-ns
命名空间/ops-github-runners-ns 创建完毕
创建一个带有 token 的 Kubernetes Secret:
$ kk -n ops-github-runners-ns create secret generic gh-runners-token --from-literal=github_token='ghp_FMT***5av'
secret/gh-runners-token 创建完成
看看这个:
运行命令来获取名为ops-github-runners-ns的命名空间中的secret信息,输出格式为yaml。
$ kk -n ops-github-runners-ns get secret -o yaml
items:
- data:
github_token: Z2h***hdg==
kind: Secret
...
《通过 Helm 来运行 Actions Runner 控制器》
动作运行控制器由两部分组成。
[gha-runner-scale-set-controller](https://github.com/actions/actions-runner-controller/tree/master/charts/gha-runner-scale-set-controller)
:控制器本身,它的 Helm 图表会创建所需的 Kubernetes 自定义资源定义(CRDs),并运行控制器 Pod,负责管理。[gha-runner-scale-set](https://github.com/actions/actions-runner-controller/tree/master/charts/gha-runner-scale-set)
:负责运行带有 GitHub Actions 运行器的 Kubernetes Pod,
还有一个老版本——[actions-runner-controller](https://github.com/actions/actions-runner-controller/tree/master/charts/actions-runner-controller)
,但不会使用它。
尽管 Scale Sets Controller 在文档中也被称为 Actions Runner Controller,并且还有一个旧版本的 Actions Runner Controller……这种情况有点令人困惑,因此请记得,谷歌上找到的一些例子或文档可能是关于旧版本的。
文档 — 快速入门:Actions Runner 控制器,或完整版本 — 使用 Actions Runner 控制器部署运行器规模集。
安装缩放集控制工具(如何进行)为控制器单独创建一个 Kubernetes 命名空间:
$ kk create ns ops-github-controller-ns
namespace/ops-github-controller-ns 创建成功
安装图表文件——它的配置文件很简单,非常简单,无需更新。它的配置文件无需更新。
$ helm -n ops-github-controller-ns upgrade --install github-runners-controller > oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller
检查Pods:
$ kk -n ops-github-controller-ns get pod
NAME 就绪状态 状态 重启次数 存活时间
github-runners-controller-gha-rs-controller-5d6c6b587d-fv8bz 1/1 运行 0 2m26s
查看新CRDs:
$ kk get crd | grep github # 获取与github相关的自定义资源定义 (Fetch the custom resource definitions related to github)
autoscalinglisteners.actions.github.com 2024-09-17T10:41:28Z # 标准ISO 8601格式时间戳 (Standard ISO 8601 formatted timestamp)
autoscalingrunnersets.actions.github.com 2024-09-17T10:41:29Z
ephemeralrunners.actions.github.com 2024-09-17T10:41:29Z
ephemeralrunnersets.actions.github.com 2024-09-17T10:41:30Z
安装 GitHub 托管机
注:如果需要解释“Scale Set for GitHub Runners”,可以将其翻译为“GitHub 托管机群组”或“GitHub 托管机集合”,以保持与原文概念的一致性。如果上下文中需要进一步说明,可以在正文或注释部分详细说明。
每个 Scale Set(AutoscalingRunnerSet
资源)负责特定的运行器,这些运行器会在工作流文件的 runs-on
中被使用。
设置两个环境变量,之后我们会在这自己的值文件中传递这些变量。
INSTALLATION_NAME
: 运行器的名字(可在values
中通过设置runnerScaleSetName
参数来指定)GITHUB_CONFIG_URL
: 格式为https://github.com/<ORG_NAME>/<REPO_NAME>
的 GitHub 组织或仓库
$ INSTALLATION_NAME="test-runners" # 安装名称 "test-runners"
$ GITHUB_CONFIG_URL="https://github.com/***/atlas-test" # GitHub 配置 URL "https://github.com/***/atlas-test"
安装图表资源并传递 githubConfigUrl
和 githubConfigSecret
- 在此我们已经创建了一个 Kubernetes Secret,因此使用该Secret。
$ helm --namespace ops-github-runners-ns 升级 --安装 test-runners \
> --set githubConfigUrl="${GITHUB_CONFIG_URL}" \
> --set githubConfigSecret=gh-runners-token \
> oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set
再次检查控制器命名空间中的Pod,应该发现新增了一个名为 test-runners-*-listener 的Pod——它将负责为 test-runners 池启动包含Runner的Pod。
$ kk -n ops-github-controller-ns 获取 pod
NAME READY STATUS RESTARTS 存活时间
github-runners-controller-gha-rs-controller-5d6c6b587d-fv8bz 1/1 Running 0 8m38s
test-runners-694c8c97-listener 1/1 Running 0 40秒
它是从 AutoscalingListeners
资源创建的。
$ kk -n ops-github-controller-ns get autoscalinglisteners
名称(NAME) GitHub 配置的网址 自动伸缩运行器集命名空间 自动伸缩运行器集名称
test-runners-694c8c97-listener https://github.com/***/atlas-test ops-github-runners-ns 测试运行器
检查运行者命名空间中的Pods,它现在还是空的。
$ kk -n ops-github-runners-ns get pod
没有找到任何资源在 ops-github-runners-ns 命名空间中。
实际上,这已经足够让你开始尝试运行作业。
使用 GitHub Actions 工作流进行测试人员的测试让我们试着运行一个最小化的构建过程,确认配置没问题。
在测试用的代码仓库中,新建一个 .github/workflows/test-gh-runners.yml
文件。
在 runs-on
中指定我们的 runner 池名为 test-runners:
name: "测试GitHub运行器实例"
concurrency:
group: github-test
cancel-in-progress: 不取消正在进行的任务
on:
workflow_dispatch:
permissions:
# 允许步骤读取仓库内容
contents: read
jobs:
aws-test:
name: 测试EKS运行器
runs-on: test-runners
steps:
- name: 测试运行器
run: echo $HOSTNAME # 输出主机名
推送更改到仓库,运行构建,等上一分钟,你就能看到Runner的名字:
在 Kubernetes 中查看 Pods:
$ kk -n ops-github-runners-ns get pod
名称 就绪 状态 重启 年龄
test-runners-p7j9h-runner-xhb94 1/1 运行中 0 6s
同一运行器及其相应的运行器规模设置将显示在设置 > 操作 > 运行器部分。
工作完成了
行了,搞定。接下来做什么?
- 我们需要为 GitHub Runners 创建一个专门用于 GitHub Runners 的 Karpenter NodePool
- 需要为 Runner Pods 配置
requests
- 看看如何在 Runners 中构建 Docker 镜像
让我们创建一个带有 taints
标签的专用 Karpenter 节点池,以便只有带有 GitHub 运行器的 Pod 可以在这些 EC2 上运行(参考 Kubernetes:Pods 和工作节点 – 控制 Pod 在节点上的调度):
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: github1abgt
spec:
重量: 20
模板:
metadata:
labels:
created-by: karpenter
component: devops
spec:
污点:
- key: GitHubOnly
operator: Exists
effect: NoSchedule
节点类引用:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: defaultv1a
要求:
- key: karpenter.k8s.aws/instance-family
operator: In
values: ["c5"]
- key: karpenter.k8s.aws/instance-size
operator: In
values: ["large", "xlarge"]
- key: topology.kubernetes.io/zone
operator: In
values: ["us-east-1a"]
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
# total cluster limits
限制:
cpu: 1000
memory: 1000Gi
中断:
整合政策: WhenEmptyOrUnderutilized
整合等待时间: 600s
预算:
- nodes: "1"
我没有在这里调整实例类型,只是复制了我们为后端API使用的NodePool配置,然后我们再看看runners在工作中使用了多少资源,再根据实际情况调整EC2实例类型。
调整 disruption
参数也很合理,例如,如果所有开发人员都在同一时区工作,您可以在夜间执行 WhenEmptyOrUnderutilized
,并且只在白天通过 WhenEmpty
删除节点。此外,您可以设置更高的 consolidateAfter
值,这样新的 GitHub Actions 作业就不会等待太久来创建 EC2 实例。参见Karpenter: 关于 Disruption Budgets 的介绍。
这里有几个选择:
- 可以使用 Terraform 的
[resource helm_release](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release)
- 可以创建自己的 helm chart,并通过Helm 依赖项来安装 GitHub Runner charts
或者我们可以更简单地做——创建一个包含配置和值的代码仓库,添加一个 Makefile
,暂时手动部署。
我可能会把这两样东西混起来。
- 控制器本身将从一个部署整个Kubernetes集群的Terraform脚本中安装,因为我们还有其他控制器如ExternalDNS、ALB Ingress Controller等已安装在那里
- 为了为每个仓库创建带有Runner池的Scale Sets,我将在一个单独的仓库中创建一个专门的Helm图表,并在其中添加每个Runner池的配置文件
- 但在还是PoC阶段时,将通过执行
helm install -f values.yaml
的Makefile
来安装Scale Sets
现在我们创建自己的 values.yaml
文件,将 runnerScaleSetName
、requests
和 tolerances
设置为 NodePool 的 tains
,
githubConfigUrl: "https://github.com/***/atlas-test"
githubConfigSecret: "github配置密钥"
runnerScaleSetName: "runner 规模设置名称"
template:
spec:
容器:
- 名称: runner
镜像: ghcr.io/actions/actions-runner:latest
命令: ["/home/runner/run.sh"]
资源:
请求:
cpu: 1
memory: 1Gi
容忍:
- key: "GitHubOnly"
效应: "NoSchedule"
操作符: "Exists"
添加一个简单的 Makefile
文件:
deploy-helm-runners-test:
helm -n ops-github-runners-ns upgrade --install test-eks-runners -f test-github-runners-values.yaml oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set (从 GHCR 获取的 Helm 图表)
部署图表如下:
$ make deploy-helm-runners-test
运行部署Helm Runner的测试命令。
看看这个跑者池的设置有没有变?
$ kk -n ops-github-runners-ns describe autoscalingrunnerset test-runners
名称: test-runners
命名空间: ops-github-runners-ns
...
API 版本: v1alpha1
类型: AutoscalingRunnerSet
...
配置:
GitHub 配置密钥: gh-runners-token
GitHub 配置 URL: https://github.com/***/atlas-test
RunnerSet 名称: test-runners
模板规格:
配置:
容器:
命令行:
/home/runner/run.sh
镜像: ghcr.io/actions/actions-runner:latest
名称: runner
资源需求:
请求:
CPU: 1
内存: 1Gi
重启策略: Never
服务账户: test-runners-gha-rs-no-permission
容忍列表:
效果: NoSchedule
键: GitHubOnly
操作符: Exists
运行一个测试构建,并检查节点声明,以确认新 Karpenter NodePool 是否会被采用。
$ kk get nodeclaim | grep git
github1abgt-dq8v5 c5.large spot us-east-1a 未知状态 0s
好的,实例已经创建好了,我们现在有了一个新的Runner:
$ kk -n ops-github-runners-ns get pod
名称: 就绪: 状态: 重启次数: 年龄:
test-runners-6s8nd-runner-2s47n 0/1 Container 正在创建 0 45s
这里一切正常。
# 构建一个真正的后端 API 工作
首先,让我们试一试用真实的代码和 GitHub Actions 工作流来运行构建并部署我们后端的流程。
为新的跑者群体创建一个新的值文件:
githubConfigUrl: "https://github.com/***/kraken"
githubConfigSecret: gh-runners-token
runnerScaleSetName: "kraken-eks-runners"
...
部署新的规模设置:
$ helm -n ops-github-runners-ns: 升级并安装 kraken-eks-runners \
-f kraken-github-runners-values.yaml \
oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set
编辑项目的流程 — 将 runs-on: ubuntu-latest
修改为 runs-on: kraken-eks-runners
:
...
jobs:
eks_build_deploy:
name: "构建或部署后端"
runs-on: kraken-eks-runners
...
一旦开始构建,一个新的 Pod 就会被创建出来:
$ kk -n ops-github-runners-ns get pod
NAME READY STATUS RESTARTS AGE
kraken-eks-runners-pg29x-runner-xwjxx 1/1 运行中 0 11s
(Pod正在运行,未重启,已经运行了11秒)
构建正在跑:
但随后马上就因为找不到一些必需的工具,如 make
和 git
而出错。
让我们手动验证一下,执行 ghcr.io/actions/actions-runner:latest
Docker 图像以进行本地运行。
$ docker run -ti ghcr.io/actions/actions-runner:latest bash
可以使用 "sudo <command>" 以管理员(用户 "root")身份运行命令。
有关详细信息,请参阅 "man sudo_root"。
runner@c8aa7e25c76c:~ $ make
bash: make: 命令未找到 (未安装该工具)
runner@c8aa7e25c76c:~ $ git
bash: git: 命令未找到 (未安装该工具)
没有 make
这是可以理解的,但为什么 git
在 GitHub 运行器上默认没有安装呢?
在這個GitHub Issue中,人们也对这个做法感到意外。
不过好吧……我们有了这些。我们可以基于 ghcr.io/actions/actions-runner
创建自己的镜像,并安装我们需要的所有工具来让我们开心。
请参阅在 ARC runner 图像中安装的软件。还有其他非 GitHub 的 Runners 镜像版,但本人未尝试过这些镜像。
我们使用的 GitHub Runners 镜像环境是 Ubuntu 22.04,因此我们可以使用 apt
安装所有必需的软件包。
创建一个 Dockerfile
文件,我已经添加了 AWS CLI 和几个 Python 相关的包。
FROM ghcr.io/actions/actions-runner:latest
RUN sudo curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | sudo bash
RUN sudo apt update && apt -y install git make python3-pip awscli python3-venv # 更新包列表并安装必要的工具
另外,还可能看到这种警告:
警告:脚本 gunicorn 已安装在 ‘/home/runner/.local/bin’,该目录未包含在 PATH 中。
考虑将该目录添加到 PATH,或者如果你希望忽略此警告,可以使用 — no-warn-script-location 选项.
因此,我也在Dockerfile中添加了PATH
变量定义。
FROM ghcr.io/actions/actions-runner:latest
ENV PATH="$PATH:/home/runner/.local/bin"
RUN sudo curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | sudo bash
RUN sudo apt update && \
sudo apt -y install git make python3-pip awscli python3-venv
在 AWS ECR 上创建一个镜像仓库:
制作镜像:
在终端里输入以下命令(构建并标记镜像):
$ docker build -t 492***148.dkr.ecr.us-east-1.amazonaws.com/github-runners/kraken -f Dockerfile.kraken .
登录到ECR:
$ aws --profile work ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 492***148.dkr.ecr.us-east-1.amazonaws.com
# 该命令使用 AWS 工作配置文件获取 ECR 的登录密码,并使用 Docker 使用该密码登录.
推送图片:
运行这条命令来推送镜像:$ docker push 492***148.dkr.ecr.us-east-1.amazonaws.com/github-runners/kraken
更改你的image
设置在“值”中。
...
runnerScaleSetName: "kraken-eks-runners"
template:
spec:
containers:
- name: runner
image: 492***148.dkr.ecr.us-east-1.amazonaws.com/github-runners/kraken:latest
command: ["/home/runner/run.sh"]
...
部署它,运行构建,我们就遇到了新状况。
无法连接到 Docker 守护程序的错误,以及 Scale Set 的containerMode
错误: "Docker in Docker"
现在Docker出了点问题:
Docker:无法连接到 Docker 守护进程 unix:///var/run/docker.sock。Docker 守护进程是不是没有运行?
在构建我们后端的过程中,会启动一个 Docker 容器来生成 OpenAPI 文档。
因此,在咱们的情况下,咱们得用Docker in Docker(尽管我真是不太喜欢这种方法,Docker in Docker即在一个Docker容器中运行另一个Docker容器,尽管我非常不喜欢这种方法)。
GitHub 文档中的 — 使用 Docker-in-Docker
在缩放集(Scale Sets)中,有一个专门的参数用于这种情况下的配置:[containerMode.type=dind](https://github.com/actions/actions-runner-controller/blob/master/charts/gha-runner-scale-set/values.yaml#L112C11-L112C29)
。
把这个加到数值里
...
runnerScaleSetName: "kraken-eks-runners" # 运行器缩放集名称
containerMode:
type: "dind" # 容器模式
template:
spec: # 规范
...
部署 Helm 图表后,我们现在有两个容器在 Runner Pod 中 — 一个是 runner
本身,另一个是 dind
:
==> 新容器实例 [kraken-eks-runners-trb9h-runner-klfxk:dind]
==> 新容器实例 [kraken-eks-runners-trb9h-runner-klfxk:runner]
再试一次构建,然后……我们又遇到了一个新的错误。
大家好,DinD 和 Docker 数据卷。
错误看起来是这样的:
Docker 中的 Docker,和 Docker 卷错误信息:ENOENT: 找不到文件或目录 '/app/openapi.yml'
问题出现是因为后端 API 的代码在 /tmp
目录中创建了一个新目录,并在此目录中生成了一个 openapi.yml
文件。接着,从这个文件生成了一个包含文档的 HTML 文件。
def 生成OpenAPI_HTML定义(yml_path: Path, html_path: Path):
print("运行Docker生成HTML")
app_volume_path = Path(tempfile.mkdtemp())
(app_volume_path / "openapi.yml").write_text(yml_path.read_text())
if subprocess.call(
[
"docker",
"run",
"-v",
f"{app_volume_path}:/app",
"--platform",
"linux/amd64",
"-e",
"yaml_path=/app/openapi.yml",
"-e",
"html_path=/app/openapi.html",
"492***148.dkr.ecr.us-east-1.amazonaws.com/openapi-generator:latest",
]
):
...
这里 Path(tempfile.mkdtemp())
会在 /tmp
目录中创建一个新目录,但这个操作在 kraken-eks-runners-trb9h-runner-klfxk:runner 容器中执行,而 docker run -v f"{app_volume_path}:/app"
这个操作则在 kraken-eks-runners-trb9h-runner-klfxk:dind 容器中执行。
我们先来看看 pod 的清单文件:
$ kk -n ops-github-runners-ns describe autoscalingrunnerset kraken-eks-runners
......
模板信息:
规格说明:
容器列表:
......
镜像: 492***148.dkr.ecr.us-east-1.amazonaws.com/github-runners/kraken:0.8
名称: runner
......
卷挂载信息:
挂载路径: /home/runner/_work
名称: work
......
镜像为 docker:dind,名称为 dind
......
卷挂载信息:
挂载路径: /home/runner/_work
名称: work
......
也就是说,这两个容器在 /home/runner/_work
这个共同目录上达成一致,该目录在主机(如 EC2 实例)上创建,然后挂载到它们所在的 Kubernetes Pod 中。
runner
容器中的 /tmp
目录对它来说是“本地”的,而不能被 dind
容器访问。
只需在 /home/runner/_work
目录下为 openapi.yml
文件创建一个新目录即可。
...
# 获取 $HOME,如果获取失败则默认使用 '/home/runner'
home = os.environ.get('HOME', '/home/runner')
# 将 app_volume_path 设置为 '/home/runner/_work/tmp/'
app_volume_path = Path(home) / "_work/tmp/"
# 递归创建目录,如果已存在则忽略
app_volume_path.mkdir(parents=True, exist_ok=True)
(app_volume_path / "openapi.yml").write_text(yml_path.read_text())
...
或者做得更好——如果构建过程将在 GitHub 主办的运行器上运行,请查看作业运行在哪一个运行器上,并根据情况选择创建目录的位置。
在我们的 Scale Set 中加入一个叫做 RUNNER_EKS
的变量。
...
模板:
规范:
containers:
- 名称: runner
image: 492***148.dkr.ecr.us-east-1.amazonaws.com/github-runners/kraken:0.8
command: ["/home/runner/run.sh"]
env:
- 名称: RUNNER_EKS
值: "true"
...
在代码里,检查一下这个变量,根据它的结果来设置 app_volume_path
目录。
# 我们的程序将具有 'RUNNER_EKS=true'
if os.environ.get('RUNNER_EKS', '').lower() == 'true':
# 获取 $HOME,回退到 '/home/runner'
home = os.environ.get('HOME', '/home/runner')
# 设置 app_volume_path 为 '/home/runner/_work/tmp/'
app_volume_path = Path(home) / "_work/tmp/"
# 递归创建文件夹,如果已存在则跳过
app_volume_path.mkdir(parents=True, exist_ok=True)
# 否则,如果是没有 'RUNNER_EKS' 的 GitHub 主机上的程序,使用旧的代码
else:
app_volume_path = Path(tempfile.mkdtemp())
# 将 yml_path 读取的文本写入到 (app_volume_path / "openapi.yml")
(app_volume_path / "openapi.yml").write_text(yml_path.read_text())
...
再运行一遍构建,现在一切都能正常运行了。
有时在构建部署的最后阶段,任务会以“错误:操作已取消”的提示结束。
原因在于,可以在运行日志中找到这个问题——它不能删除 _github_home/.kube/cache
目录,
...
kraken-eks-runners-wwn6k-runner-zlw7s:runner [WORKER 2024-09-20 10:55:23Z 信息] TempDirectoryManager: 正在清理运行器的临时文件夹:/home/runner/_work/_temp
kraken-eks-runners-wwn6k-runner-zlw7s:runner [WORKER 2024-09-20 10:55:23Z 错误] TempDirectoryManager: 出现一个或多个错误。 (访问路径 '/home/runner/_work/_temp/_github_home/.kube/cache' 被拒绝。)
kraken-eks-runners-wwn6k-runner-zlw7s:runner [WORKER 2024-09-20 10:55:23Z 错误] TempDirectoryManager: 系统未经授权访问异常,访问路径 '/home/runner/_work/_temp/_github_home/.kube/cache' 被拒绝。
kraken-eks-runners-wwn6k-runner-zlw7s:runner [WORKER 2024-09-20 10:55:23Z 错误] TempDirectoryManager: 系统IO异常,权限被拒绝
...
实际上,如果我们从 runner
容器中检查 /home/runner/_work/_temp/_github_home/
,它无法访问。
runner@kraken-eks-runners-7pd5d-runner-frbbb:~$ ls -l /home/runner/_work/_temp/_github_home/.kube/cache
ls: 无法打开目录 '/home/runner/_work/_temp/_github_home/.kube/cache': 没有权限
但是可以通过该目录创建的带有 dind
的容器进行访问。
/ # ls -l /home/runner/_work/_temp/_github_home/.kube/cache
0 个文件
drwxr-x--- 3 root root 78 Sep 24 08:36 discovery
drwxr-x--- 3 root root 313 Sep 24 08:36 http
在这种情形下,由root
创建,尽管其他目录是由用户1001创建。
/ # ls -l /home/runner/_work/_temp/
总计 40K
-rw-r--r-- 1 1001 1001 71 9月24 08:36 79b35fe7-ba51-47fc-b5a2-4e4cdf227076.sh
drwxr-xr-x 2 1001 1001 24 9月24 08:31 _github_workflow
...
1001 是 runner 容器中的 runner
用户:
runner@kraken-eks-runners-7pd5d-runner-frbbb:~$ id runner
运行命令`id runner`,显示用户runner的UID、GID和所属的用户组。UID为1001(runner),GID为1001(runner),用户组包括1001(runner),27(sudo),123(docker)。
有意思的是,错误时有时无,虽然工作流程没有改变过。
.kube/config
是由 bitovi/github-actions-deploy-eks-helm 动作生成的,该动作在其自己的 Docker 容器中运行 aws eks update-kubeconfig
,并且以 root 权限运行,因为它是在 Docker 中运行的另一个 Docker 容器。
为解决这个问题,想到有两个选项:
- 可以在部署的末尾简单地增加一个额外的命令
chown -r 1001:1001 /home/runner/_work/_temp/_github_home/.kube/cache
(虽然也可以使用相同的命令来简单地删除该目录) - 或者将
GITHUB_HOME
更改为另一个目录,然后aws eks update-kubeconfig
会将该目录创建在另一个位置,带有runner
标签的容器将能够执行 清理 runner 临时文件夹 操作
然而,我还是不明白为什么“清理运行者临时文件夹”每次都不起作用,因此它是一个“不稳定的bug”。让我们看看将来它会如何表现。
连接一个高IOPS的卷(IOPS是指每秒输入输出操作次数)我们想切换到自托管运行器,原因之一是为了加快构建和部署过程。
构建过程中耗时最长的部分通常是像 docker load
和 docker save
这样的命令。
所以,我想尝试连接一个具有高 IOPS 的 AWS EBS,因为默认的 gp2
每 GB 有 100 IOPS — 请参阅 Amazon EBS 卷类型。
创建一个新的 Kubernetes 存储类。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-iops
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
iopsPerGB: "16000"
throughput: "1000"
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
在我们 Runner 池的配置值里,添加 volumes
块,其中我们覆盖了默认由 [emptyDir: {}]
创建的 work
磁盘的参数。
在这里设置一个新的 storageClassName
,例如:
githubConfigUrl: "https://github.com/***/kraken"
githubConfigSecret: "GitHub 配置密钥" # gh-runners-token
runnerScaleSetName: "运行器规模集名称" # kraken-eks-runners
containerMode:
type: "容器模式类型" # dind # Docker in Docker 实现
template:
spec:
容器:
- name: runner
image: 492***148.dkr.ecr.us-east-1.amazonaws.com/github-runners/kraken:0.9
命令: ["/home/runner/run.sh"]
环境变量:
- name: RUNNER_EKS
value: "true"
资源请求:
请求:
CPU: 2
内存: 4Gi
卷:
- name: work
临时卷模板:
规格:
访问模式: [ "读写一次" ]
存储类名称: "gp3-iops"
存储资源请求:
请求:
存储容量: 10Gi
部署这些更改到AutoscalingRunnerSet,执行部署,带有Runners的Pod会被创建,但会立即被终止,任务本身也会宣告失败。
访问该路径“/home/runner/_work/_tool”被拒绝时的错误再次检查运行日志,看看:
_kraken-eks-runners-gz866-runner-nx89n:runner [RUNNER 2024–09–24 10:15:40Z JobDispatcher 错误] 未经授权的访问异常: 无法访问路径 "/home/runner/_work/tool"
我已经查看了关于错误“访问路径/home/runner/_work/_tool受到拒绝”的文档(错误:访问路径/home/runner/_work/_tool受到拒绝),当时我在寻找解决上述“访问路径‘/home/runner/_work/_temp/_github_home/.kube/cache’受到拒绝”错误的方法,这正是我所需要的。
要解决这个问题,添加另一个initContainer
、并运行chown
命令:
...
模板:
规范:
初始化容器:
- name: kube-init
image: ghcr.io/actions/actions-runner:latest
command: ["sudo", "chown", "-R", "1001:123", "/home/runner/_work"]
卷挂载:
- name: work
挂载路径: /home/runner/_work
容器:
- name: runner
image: 492***148.dkr.ecr.us-east-1.amazonaws.com/github-runners/kraken:0.9
command: ["/home/runner/run.sh"]
...
现在一切都好了。
咱们比一比看看结果。
“后端的构建和部署”环节大约花了9分钟:
就这样变成了6分钟了
基本上,就这样吧。
虽然一开始可能需要稍微调整一下,但总的来说它是可以工作的。
原发布于 RTFM:Linux、DevOps 和系统管理。
注:原文中的下划线表示斜体。
共同學習,寫下你的評論
評論加載中...
作者其他優質文章