作为 Jenkins 构建过程的一部分,如何跨多个集群创建和修改 Kubernetes 资源?
How to create and modify Kubernetes resources across multiple clusters as part of a Jenkins build process?
具体来说,我想在现有 Kubernetes 集群中修改一些资源(目前用 YAML
定义)作为构建过程的一部分,还有一些我想从头开始创建。在每种情况下,我都想在多个区域执行此操作以保持所有区域同步。
有问题的资源是 Agones fleets
,看起来像这样(实际值已删除,但具有代表性):
apiVersion: agones.dev/v1
kind: Fleet
metadata:
annotations:
agones.dev/sdk-version: 1.11.0
name: test
namespace: game-servers
resourceVersion: "12324578"
selfLink: /apis/agones.dev/v1/namespaces/game-servers/fleets/test
spec:
replicas: 1
scheduling: Packed
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
branch: test
git_commit: 1b12371827fdea31231230901876ffe
spec:
health:
disabled: false
failureThreshold: 5
initialDelaySeconds: 10
periodSeconds: 5
ports:
- containerPort: 1234
name: default
portPolicy: Dynamic
protocol: UDP
sdkServer:
logLevel: Info
template:
metadata:
creationTimestamp: null
labels:
role: game-servers
spec:
containers:
- image: registry.example.com/gameserver:1b12371827fdea31231230901876ffe
name: agones
resources:
limits:
cpu: 500m
memory: 512m
requests:
cpu: 100m
memory: 256Mi
nodeSelector:
role: game-servers
如果有现有的 fleet
,我想将最新的 git commit
以及要使用的图像注入到标签中(假设它将在注册表中正确标记) .
如果不存在 fleet
,对于某些值,我想遍历并从头开始创建具有与上述类似特征的新队列。我尝试了几种不同的方法但都失败了——从集群权限问题到尝试在 Jenkins/Groovy.
中使用非常直接的 for
循环时出现的奇怪错误
与 Jenkins 相关的许多事情一样,要完成这项工作,需要几个插件,具体来说:
- Kubernetes CLI Plugin
- snakeyaml Plugin (this is per the readYaml Jenkins docs)
假设:
- 这些操作将 运行 在基于 Linux 的容器上正确配置以执行 Jenkins 构建
- 几个必需的“变量”将从本地文件系统中读取 - 这些可能是管道中早期的构建工件,或者
ENV
变量,具体取决于设置
- 集群上已存在 Kubernetes 服务帐户,具有足够的权限来执行任务
- 这些帐户的凭据已在 Jenkins 中配置
fleetconfig.yaml
在本地文件系统上可用,并且是一个完整的队列配置,类似于问题中提出的
为了区别对待不同的舰队,需要根据舰队本身的名称应用一些 selection 标准,然后需要一个循环遍历每个区域等。
为了简单起见,类型之间将有一个基本的 if
到 select 的语句(这可以很容易地扩展),然后是一个 2 元素循环来遍历多个元素区域(同样可以轻松扩展到更多区域)。
用 Jenkins 的术语来说,这是作为一个完整的 Stage
编写的,但显然它本身并不是完全可执行的。它尽可能接近现有的、经过测试的、工作的配置,该配置已经 运行 每天多次,已经有一段时间了。
虽然这只是示例资源,但没有理由不能使用它来修改,一般在 Kubernetes 中创建其他资源。
stage('DeployFleets') {
agent {
node {
label 'k8s-node-linux'
}
}
steps {
script {
// assume we can read the Fleet name in from a file
FLEET_NAME = readFile("/path/to/FLEET_NAME")
// let's assume that test is one of the fleets that is being modified, not created, deal with that first
container('jenkins-worker'){
if (FLEET_NAME != 'test') {
script {
// again, assume we can read the commit from a file
def GIT_COMMIT_TAG = readFile("/path/to/GIT_COMMIT")
// create a map of 2 fictional regions with an account to use and an cluster adddress for that region
def DEPLOY_REGIONS = [
"us-east-1": ["jenkins_service_acct_use1", 'https://useast1-cluster.example.com'],
"us-east-2": ["jenkins_service_acct_use2", 'https://useast2-cluster.example.com'],
]
// this each construction is needed in order to get around https://issues.jenkins-ci.org/browse/JENKINS-49732 which prevents using a for(element in DEPLOY_REGIONS)
DEPLOY_REGIONS.each { element ->
withKubeCredentials([[credentialsId: element.value[0], serverUrl: element.value[1]]]) {
sh """
kubectl patch fleet ${FLEET_NAME} -n game-servers --type=json -p='[{"op": "replace", "path": "/spec/template/spec/template/spec/containers/0/image", "value":"registry.example.com/gameserver/${FLEET_NAME}:${GIT_COMMIT_TAG}"}]'
kubectl patch fleet ${FLEET_NAME} -n game-servers --type=json -p='[{"op": "replace", "path": "/spec/template/metadata/labels/git_commit", "value":"${GIT_COMMIT_TAG}"}]'
"""
}
}
}
} else {
// rather than patching here, create a fleet from scratch using a source YAML file as a template
script {
def GIT_COMMIT_TAG = readFile("/path/to/GIT_COMMIT")
def NUM_REPLICAS = 1
def DEPLOY_REGIONS = [
"us-east-1": ["jenkins_service_acct_use1", 'https://useast1-cluster.example.com'],
"us-east-2": ["jenkins_service_acct_use2", 'https://useast2-cluster.example.com'],
]
// see note above about each construct
DEPLOY_REGIONS.each { element ->
// assume template available on file system
def FLEET_CONFIG = readYaml file: "/path/to/fleetconfig.yaml"
FLEET_CONFIG.metadata.name = env.SOME_SANE_NAME
FLEET_CONFIG.spec.template.metadata.labels.git_commit = GIT_COMMIT_TAG
FLEET_CONFIG.spec.replicas = NUM_REPLICAS
FLEET_CONFIG.spec.template.spec.template.spec.containers[0].image = "registry.example.com/gameserver/${FLEET_NAME}:${GIT_COMMIT_TAG}"
writeYaml file: "${env.SOME_SANE_NAME}_fleet.yaml", data: FLEET_CONFIG, overwrite: true
withKubeCredentials([[credentialsId: element.value[0], serverUrl: element.value[1]]]) {
sh """
kubectl -n game-servers apply -f "${env.SOME_SANE_NAME}_fleet.yaml"
"""
}
}
}
}
}
}
}
}
考虑到 Jenkins 通过插件为我们提供的 YAML
操作实用程序,这并不是特别困难,但找到一种端到端的方法可能是一个挑战。在 kubectl
中使用 patch 命令使得补丁对 Jenkins 来说不那么“原生”,但这种便利是非常值得的(例如,替代方法是使用 REST API)。 foreach
结构看起来很奇怪,但需要它来避免 Jenkins 中长期存在的错误。
具体来说,我想在现有 Kubernetes 集群中修改一些资源(目前用 YAML
定义)作为构建过程的一部分,还有一些我想从头开始创建。在每种情况下,我都想在多个区域执行此操作以保持所有区域同步。
有问题的资源是 Agones fleets
,看起来像这样(实际值已删除,但具有代表性):
apiVersion: agones.dev/v1
kind: Fleet
metadata:
annotations:
agones.dev/sdk-version: 1.11.0
name: test
namespace: game-servers
resourceVersion: "12324578"
selfLink: /apis/agones.dev/v1/namespaces/game-servers/fleets/test
spec:
replicas: 1
scheduling: Packed
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
branch: test
git_commit: 1b12371827fdea31231230901876ffe
spec:
health:
disabled: false
failureThreshold: 5
initialDelaySeconds: 10
periodSeconds: 5
ports:
- containerPort: 1234
name: default
portPolicy: Dynamic
protocol: UDP
sdkServer:
logLevel: Info
template:
metadata:
creationTimestamp: null
labels:
role: game-servers
spec:
containers:
- image: registry.example.com/gameserver:1b12371827fdea31231230901876ffe
name: agones
resources:
limits:
cpu: 500m
memory: 512m
requests:
cpu: 100m
memory: 256Mi
nodeSelector:
role: game-servers
如果有现有的 fleet
,我想将最新的 git commit
以及要使用的图像注入到标签中(假设它将在注册表中正确标记) .
如果不存在 fleet
,对于某些值,我想遍历并从头开始创建具有与上述类似特征的新队列。我尝试了几种不同的方法但都失败了——从集群权限问题到尝试在 Jenkins/Groovy.
for
循环时出现的奇怪错误
与 Jenkins 相关的许多事情一样,要完成这项工作,需要几个插件,具体来说:
- Kubernetes CLI Plugin
- snakeyaml Plugin (this is per the readYaml Jenkins docs)
假设:
- 这些操作将 运行 在基于 Linux 的容器上正确配置以执行 Jenkins 构建
- 几个必需的“变量”将从本地文件系统中读取 - 这些可能是管道中早期的构建工件,或者
ENV
变量,具体取决于设置 - 集群上已存在 Kubernetes 服务帐户,具有足够的权限来执行任务
- 这些帐户的凭据已在 Jenkins 中配置
fleetconfig.yaml
在本地文件系统上可用,并且是一个完整的队列配置,类似于问题中提出的
为了区别对待不同的舰队,需要根据舰队本身的名称应用一些 selection 标准,然后需要一个循环遍历每个区域等。
为了简单起见,类型之间将有一个基本的 if
到 select 的语句(这可以很容易地扩展),然后是一个 2 元素循环来遍历多个元素区域(同样可以轻松扩展到更多区域)。
用 Jenkins 的术语来说,这是作为一个完整的 Stage
编写的,但显然它本身并不是完全可执行的。它尽可能接近现有的、经过测试的、工作的配置,该配置已经 运行 每天多次,已经有一段时间了。
虽然这只是示例资源,但没有理由不能使用它来修改,一般在 Kubernetes 中创建其他资源。
stage('DeployFleets') {
agent {
node {
label 'k8s-node-linux'
}
}
steps {
script {
// assume we can read the Fleet name in from a file
FLEET_NAME = readFile("/path/to/FLEET_NAME")
// let's assume that test is one of the fleets that is being modified, not created, deal with that first
container('jenkins-worker'){
if (FLEET_NAME != 'test') {
script {
// again, assume we can read the commit from a file
def GIT_COMMIT_TAG = readFile("/path/to/GIT_COMMIT")
// create a map of 2 fictional regions with an account to use and an cluster adddress for that region
def DEPLOY_REGIONS = [
"us-east-1": ["jenkins_service_acct_use1", 'https://useast1-cluster.example.com'],
"us-east-2": ["jenkins_service_acct_use2", 'https://useast2-cluster.example.com'],
]
// this each construction is needed in order to get around https://issues.jenkins-ci.org/browse/JENKINS-49732 which prevents using a for(element in DEPLOY_REGIONS)
DEPLOY_REGIONS.each { element ->
withKubeCredentials([[credentialsId: element.value[0], serverUrl: element.value[1]]]) {
sh """
kubectl patch fleet ${FLEET_NAME} -n game-servers --type=json -p='[{"op": "replace", "path": "/spec/template/spec/template/spec/containers/0/image", "value":"registry.example.com/gameserver/${FLEET_NAME}:${GIT_COMMIT_TAG}"}]'
kubectl patch fleet ${FLEET_NAME} -n game-servers --type=json -p='[{"op": "replace", "path": "/spec/template/metadata/labels/git_commit", "value":"${GIT_COMMIT_TAG}"}]'
"""
}
}
}
} else {
// rather than patching here, create a fleet from scratch using a source YAML file as a template
script {
def GIT_COMMIT_TAG = readFile("/path/to/GIT_COMMIT")
def NUM_REPLICAS = 1
def DEPLOY_REGIONS = [
"us-east-1": ["jenkins_service_acct_use1", 'https://useast1-cluster.example.com'],
"us-east-2": ["jenkins_service_acct_use2", 'https://useast2-cluster.example.com'],
]
// see note above about each construct
DEPLOY_REGIONS.each { element ->
// assume template available on file system
def FLEET_CONFIG = readYaml file: "/path/to/fleetconfig.yaml"
FLEET_CONFIG.metadata.name = env.SOME_SANE_NAME
FLEET_CONFIG.spec.template.metadata.labels.git_commit = GIT_COMMIT_TAG
FLEET_CONFIG.spec.replicas = NUM_REPLICAS
FLEET_CONFIG.spec.template.spec.template.spec.containers[0].image = "registry.example.com/gameserver/${FLEET_NAME}:${GIT_COMMIT_TAG}"
writeYaml file: "${env.SOME_SANE_NAME}_fleet.yaml", data: FLEET_CONFIG, overwrite: true
withKubeCredentials([[credentialsId: element.value[0], serverUrl: element.value[1]]]) {
sh """
kubectl -n game-servers apply -f "${env.SOME_SANE_NAME}_fleet.yaml"
"""
}
}
}
}
}
}
}
}
考虑到 Jenkins 通过插件为我们提供的 YAML
操作实用程序,这并不是特别困难,但找到一种端到端的方法可能是一个挑战。在 kubectl
中使用 patch 命令使得补丁对 Jenkins 来说不那么“原生”,但这种便利是非常值得的(例如,替代方法是使用 REST API)。 foreach
结构看起来很奇怪,但需要它来避免 Jenkins 中长期存在的错误。