本文供自己记录学习,我会尽量保证文章准确无误,但因本人水平有限,如果不幸产生任何错误,望读者不吝赐教。
浅析 gitlab-runner 相关配置
在 gitlab-runner 中每个 runner 都需要指定 executor,executor 有以下几种类型:
- SSH
- Shell
- Parallels
- VirtualBox
- Docker
- Docker Machine (auto-scaling)
- Kubernetes
- Custom
我们会将 gitlab-runner 部署在 kubernetes 集群中,所以此处选用 kubernetes 作为 executor。有关该 executor 的配置方式,在官方文档中有详细说明,此处就此次配置所需要的进行简单说明,其它的再视情况进行补充。
与其它类型的 executor 一样,kubernetes 也需要根据 gitlab 仓库的 url 和 token 对 runner 进行注册方可使用。注册后会有一个常驻的 pod (我姑且叫它 runner 服务端)对来自于 gitlab 的 pipeline 任务进行监听,当监听到有新的任务需要执行时,就会创建新的 pipeline 跑 .gitlab-ci.yml
上对应的job。所以 runner 服务端相关联的 ServieAccount 一定需要具备对应的 namespace 的 pod 的创建的权限。
配置一个 runner 有多种方式,包括启动参数、环境变量、配置文件等。这其中配置文件的功能最强,参数可配置项较环境变量稍多(或许它们具有相同的能力,只是我没有发现,如果你知道欢迎帮忙补充)。通过gitlab-runner help register
命令我们可以找到所有的启动参数和环境变量,参数与环境变量之间的关系请参照参数与环境变量对照表。
runner 的集群中权限
前面已经谈到 runner 服务端在注册成功监听到新的任务后,需要以 Pod 的形式创建 Worker 对新对任务进行处理。这里我们需要考虑几种权限:Pod 的操作权限、Worker 镜像的拉取权限以及在 Worker 中构建镜像时对私有镜像的拉取权限。
Pod 的操作权限通过 kubernetes 中的 ServiceAccount 来实现,我们需要赋予对应的 ServiceAccount 对指定命名空间的 Pod 的操作权限,相关的参数有--kubernetes-namespace
和--kubernetes-service-account
以及他们环境变量的开关参数--kubernetes-namespace_overwrite_allowed
和--kubernetes-service_account_overwrite_allowed
。这里指定的 ServiceAccount 一定要在指定的 Namespace 中有 Pod 的操作权限,否则 gitlab 在跑 pipeline 的时候会报权限错误。有关 ServiceAccount 的权限配置是通过 RBAC 实现的,详情参照kubernetes 中 RBAC 认证的文档。
对于 Worker 镜像的拉取权限,一般都是在集群创建时预先配置好的,此处无需另行配置。而 Worker 中构建镜像时对私有镜像的拉取和推送权限是在 .gitlab-ci.yml
中进行配置的,这里不要搞混。
runner 的持久化存储
runner 可以使用 hostPath 和 PVC 做持久化存储,跟 Kubernetes 中的概念完全一样。用法参照使用卷,参考配置如下:
concurrent = 4
[[runners]]
# usual configuration
executor = "kubernetes"
[runners.kubernetes]
[[runners.kubernetes.volumes.host_path]]
name = "hostpath-1"
mount_path = "/path/to/mount/point"
read_only = true
host_path = "/path/on/host"
[[runners.kubernetes.volumes.host_path]]
name = "hostpath-2"
mount_path = "/path/to/mount/point_2"
read_only = true
[[runners.kubernetes.volumes.pvc]]
name = "pvc-1"
mount_path = "/path/to/mount/point1"
[[runners.kubernetes.volumes.config_map]]
name = "config-map-1"
mount_path = "/path/to/directory"
[runners.kubernetes.volumes.config_map.items]
"key_1" = "relative/path/to/key_1_file"
"key_2" = "key_2"
[[runners.kubernetes.volumes.secret]]
name = "secrets"
mount_path = "/path/to/directory1"
read_only = true
[runners.kubernetes.volumes.secret.items]
"secret_1" = "relative/path/to/secret_1_file"
[[runners.kubernetes.volumes.empty_dir]]
name = "empty_dir"
mount_path = "/path/to/empty_dir"
medium = "Memory"
在 runner 中构建镜像
在 runner 中构建 Docker 镜像有两种方式,最常见的是使用docker build
命令,另一种是使用谷歌推出的kaniko
。相较于docker build
,kaniko
无需 Docker Daemon 即可工作,因此 Worker 无需在特权模式下工作,这种方式不会挑战 Kubernetes 集群的安全性。下面将具体谈谈两种方式:
docker build
使用这种方式构建 Docker 镜像我们不得不为执行该 Job 的 Worker 开启特权模式,这会带来安全性问题。如果你对此不以为然的话,就可以进行接下来的步骤了。
尽管这种方式本身就是不安全的,但是如果做好以下几点,依然可以有效降低安全风险:
- 将特权模式的 runner 打上专用标签,仅供使用对应标签的 job 调用;
- 创建这种类型的 runner 服务端时,使用 Taint 标记 Node,告诉其它应用这个结点的污点,在部署 runner 或其它可以容忍这个安全问题的应用时设置对应的 Toleration。
这可以确保特权模式不被滥用,亦可对应用的安全性负责。
以特权模式运行的docker: dind
容器会在2375/tcp
端口上进行监听,我们可以通过 TCP 协议对 Docker 进行操作。
image: docker:latest
variables:
DOCKER_HOST: tcp://localhost:2375 # 在 Kubernetes 中 service 会以 sidecar 的形式插到 Pod 中,同一 Pod 中的网络使用 localhost 访问即可
DOCKER_DRIVER: overlay2
services:
- docker:18-dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
build-master:
stage: build
script:
- docker build --pull -t "$CI_REGISTRY_IMAGE" .
- docker push "$CI_REGISTRY_IMAGE"
注:docker 宿主机的版本与
dind
的版本最好一致。比如我宿主机使用的18.03
的版本,那么镜像就应该使用docker:18-dind
,如果直接使用docker:dind
会使用最新版本的 docker。我当时遇到的一个问题是在 dind 容器中只监听了2376
端口,没有2375
。2376
端口是做了加密的,至少需要配置证书都能使用,2375
端口不能正常使用,不知道是因为版本不一致,还是本来就只会监听2376
,我看 Dockerfile 中,两个端口都有暴露,时间关系我就没有进一步细研了。当我换作docker:18-dind
后,问题都解决了。
kaniko
用法如下:
build:
stage: build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
使用 helm 安装 gitlab-runner
使用 gitlab 官方 chart 库进行 gitlab-runner 的安装。因为官方的 chart 使用参数和环境变量注册 gitlab-runner,在使用上会有一定的局限性,可能需要一些修改才能进行高级配置。以下是使用官方 chart 部署 gitlab-runner 的步骤:
前提
- 安装了 tiller 的 kubernetes 的集群
- 初始化完成了的 helm 命令行工具
1. 添加 gitlab 官方 chart 仓库
helm repo add gitlab https://charts.gitlab.io/
helm repo update
2. 在 gitlab 代码仓库中找到 url 和 runner 的 token
3. 安装 gitlab-runner
我们可以通过参数以及配置文件的形式为 helm 添加自定义配置。该 Chart 的可配置项可以查阅官方库对于 values.yaml
文件的注释,这里把关键配置提出来说明一下:
gitlabUrl
:gitlab 服务的地址,形如:https://gitlab.xxx.com;runnerRegistrationToken
:上一步中得到的 token;rbac.create
:我是没有遇到过没有rbac
的 kubernetes 集群,所以记得一定填true
,否则创建出的 gitlab-runner 将无权对集群进行操作;rbac.clusterWideAccess
:如果涉及到在不同的 namespace 跑 job,设置为true
,否则置为false
(这只能操作同一 namespace);rbac.serviceAccountName
:如果不设置默认使用 default,如果 sa 专用则建议另起一个名字,以避免 default 权限过大,其它用 default 的 pod 滥用;runners.image
:默认镜像,在 gitlab-ci.yml 中的 job 未配置image
时将自动选用;runners.privileged
:一般在该 runner 需要跑docker:dind
镜像时使用;runners.namespace
: Job 运行的 namespace;runners.imagePullSecrets
:在创建 job 时使用的镜像如果来自于私有仓库则需要配置该字段。注意使用前需要提前创建好对应的 secret;runners.cache
:如果要使用 runner 的 cache 功能,需要对此选项进行配置,不同的持久化方式配置不同,有些方式甚至需要对 chart 进行改造。
注:该 Chart 的多 runner 注册功能受限,对 kubernetes 的高级功能配置支持也有限,后期我将对这个 Chart 进行改造,使其支持。
浅析 .gitlab-ci.yml
相关配置
有关 .gitlab-ci.yml
更详细的配置参照官方文档,在此仅挑部分有特点的东西记录一下。
- image 将确定 job 运行的环境,script 将在该环境中运行;
- service 是环境的依赖,如
docker:dind
一般以这种方式注入,具体在 kubernetes 集群中会以 sidecar 的形式与主环境放在一个 pod 中,所以如果 service 提供的是 socket 服务,在主环境中使用 localhost 即可访问; - 注意 only/except 的高级用法,可以服务于多种场景;
- 使用 rules 对 job 进行合理控制;
- environment 跟持续部署相关,主要用于前端项目;
- cache 用于在多个 job 间缓存数据;
- artifacts 将对指定文件(一般是编译产物)进行打包,并上传至 gitlab,供下载使用,这也会在界面进行展示,配合 dependencies 使用,可以在不同 stage 的 job 间进行传递;
- 使用 coverage 抓取测试覆盖率
配置 .gitlab-ci.yml
stages:
- build
.build-common: &build-common
image: docker:latest
variables:
DOCKER_HOST: tcp://localhost:2375 # kubernetes 集群中使用 localhost 访问
DOCKER_DRIVER: overlay2
services:
- name: docker:18-dind # 匹配环境中 docker 的版本
command: ["--registry-mirror=https://mirror.ccs.tencentyun.com"] # 这是我找到的在编译镜像时,配置镜像加速器的最好办法
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
build-master:
<<: *build-common
stage: build
script:
- docker build -t "$CI_REGISTRY_IMAGE" .
- docker tag "$CI_REGISTRY_IMAGE" "$CI_REGISTRY/$CI_REGISTRY_IMAGE"
- docker tag "$CI_REGISTRY_IMAGE" "$CI_REGISTRY/$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}"
- docker push "$CI_REGISTRY/$CI_REGISTRY_IMAGE"
- docker push "$CI_REGISTRY/$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}"
only:
- master
build:
<<: *build-common
stage: build
script:
- docker build -t "$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}" .
- docker tag "$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}" "$CI_REGISTRY/$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}"
- docker push "$CI_REGISTRY/$CI_REGISTRY_IMAGE:${CI_COMMIT_SHA:0:9}"
except:
- master