文章目录
- 一、Kubernetes-plugin 使用
- 二、环境依赖
- 2.1、填写kubernetes插件配置
- 2.2、支持的凭据
- 三、Kubernetes插件参数
- 3.1、podTemplate - Pod和容器模板
- 3.2、container 容器模板的定义
- 3.3、Liveness Probe Usage
- 四、Pipeline 示例
- 4.1、Scripted Pipeline
- 4.2、Declarative Pipeline
- 4.3、Jenkins share library Groovy
一、Kubernetes-plugin 使用
- 说明: Kubernetes-plugin 它是在Kubernetes集群中运行动态代理的Jenkins插件, 该插件为每个启动的代理创建一个Kubernetes Pod,由Docker映像定义运行,并在每次构建后停止。
- 备注: 代理是使用JNLP启动的,因此预期图像会自动连接到Jenkins主机,以下是重要的环境变量(我们说过不建议使用自己jnlp覆盖默认的jnlp容器)。
# jenkins/inbound-agent
JENKINS_URL: Jenkins web interface url
JENKINS_SECRET: the secret key for authentication
JENKINS_AGENT_NAME: the name of the Jenkins agent
JENKINS_NAME: the name of the Jenkins agent (Deprecated. Only here for backwards compatibility)
- 插件地址: https://github.com/jenkinsci/kubernetes-plugin
- 该镜像是Jenkins自定义jnlp容器模板,主要用于Jenkins工作节点容器化使用,主要包含Gitlab_release发布、 docker 容器管理、kubectl 集群管理功能。
二、环境依赖
2.1、填写kubernetes插件配置
- 可在Jenkins UI并导航至Manage Jenkins-> Configure System-> Cloud-> Kubernetes并输入相应的Kubernetes URL和Jenkins URL;
2.2、支持的凭据
- Username/password - 用户名密码
- Secret File (kubeconfig file) - 秘密文件(kubeconfig文件)
- Secret text (Token-based authentication) (OpenShift) - 秘密文本(基于令牌的身份验证)(OpenShift)
- Google Service Account from private key (GKE authentication) - 来自私钥的Google服务帐户(GKE身份验证)
- X.509 Client Certificate - X.509客户端证书
三、Kubernetes插件参数
可以根据提供的选项生成Template Yaml
3.1、podTemplate - Pod和容器模板
...
podTemplate(label: label, cloud: 'kubernetes',
nodeSelector: "jenkins=salve",
containers: [
containerTemplate(
name: 'jnlp',
image: 'jenkins:jnlp-slave',
ttyEnabled: true,
privileged: false,
alwaysPullImage: false,
),
containerTemplate(name: 'node10', image: 'node:10', alwaysPullImage: false, ttyEnabled: true, command: 'cat',resourceLimitCpu: '1500m')
],
volumes: [
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]
)
...
- podTemplate 是用于创建代理的pod的模板可以通过用户界面或管道进行配置, 无论哪种方式它都可以访问以下字段
-
cloud
:在Jenkins设置中定义的云的名称。默认为kubernetes -
name
: Pod 名称 -
namespace
: 名称空间设置 -
label
: 标签值可以设置为唯一的值,以避免跨构建冲突,或省略,并在步骤中定义POD_LABEL。 -
yaml
: 资源清单声明 -
yamlFile
: 指定资源清单的文件 -
yamlMergeStrategy
: 控制yaml定义是覆盖还是与从用声明的pod模板继承的yaml定义合并inheritFrom。默认为override()。 -
showRawYaml
: 启用或禁用原始Yaml文件的输出默认为true (后续实践) -
serviceAccount
: k8s中创建的服务帐户 -
nodeSelector
:节点选择器 -
nodeUsageMode
: 节点调用模式(NORMAL它控制Jenkins是只调度标签表达式匹配的作业,EXCLUSIVE尽可能多地使用该节点。) -
volumes
:为pod定义各种类型的卷并在所有容器中挂载卷 -
envVars
:应用于所有容器的环境变量(envVar 环境变量其值是内联定义的, secretEnvVar 一个环境变量其值是从Kubernetes机密派生的。
) -
imagePullSecrets
: 从私有的镜像仓库中拉取镜像的凭据 -
annotations
: Pod 注解 -
InheritFrom
:继承的一个或多个Pod模板的列表 -
slaveConnectTimeout
: 代理程序联机超时时间(单位s) -
podRetention
:控制保留代理Pod的行为(Can be 'never()', 'onFailure()', 'always()', or 'default()'
),如果为空则按照下面的active Deadline Seconds参数进行。 -
activeDeadlineSeconds
: pod将在这个截止日期过后被删除。 -
idleMinutes
: 允许Pod保持活动状态以供重用,直到在执行最后一步以来经过了配置的分钟数为止。 -
runAsUser
: 用户ID,用于将pod中的所有容器运行为。 -
runAsGroup
: 组ID,用于将Pod中的所有容器运行为。 -
hostNetwork
: 使用主机网络。 -
container
: 用于创建pod容器的容器模板 (见下文) - (重点)。 -
defaultContainer
: 指定Pod中默认容器则在stages中不用加入container进行指定选择;
3.2、container 容器模板的定义
...
containers: [
containerTemplate(
name: 'jnlp',
image: 'jenkins:jnlp-slave',
ttyEnabled: true,
privileged: false,
alwaysPullImage: false,
),
...
- containerTemplate 是一个将被添加到pod中的容器模板它属于container中数组对象。同样它可以通过用户界面或管道进行配置,并允许你设置以下字段:
-
name
: 容器的名称。 -
image
: 容器的图像。 envVars
: 应用于容器的环境变量(补充和覆盖在pod级别设置的env var)。
-
envVar
: 环境变量,其值是内联定义的。 -
secretEnvVar
: 一个环境变量,其值是从Kubernetes机密派生的。
-
workingDir
: 工作空间目录 -
command
: 容器将执行的命令。 -
args
: 传递给命令的参数。 -
ttyEnabled
: 标记以指示应启用tty。 -
livenessProbe
: 要添加到容器中的exec -
liveness探针的参数
:(不支持httpGet liveness探针) -
ports
: 暴露容器上的端口。 -
alwaysPullImage
: 容器在启动时将拉出图像。 -
runAsUser
: 用于运行容器的用户ID。 -
runAsGroup
: 运行容器的组ID。
3.3、Liveness Probe Usage
...
containerTemplate(
name: 'node10',
image: 'node:10',
ttyEnabled: true,
command: 'cat',
livenessProbe: containerLivenessProbe(
execArgs: 'some --command',
initialDelaySeconds: 30,
timeoutSeconds: 1,
failureThreshold: 3,
periodSeconds: 10,
successThreshold: 1
)
)
...
- 默认情况下代理连接超时设置为100秒。在某些情况下您想要设置一个不同的值,如果可以可以将system属性设置org.csanchez.jenkins.plugins.kubernetes.PodTemplate.connectionTimeout为一个不同的值。
四、Pipeline 示例
4.1、Scripted Pipeline
podTemplate(
cloud: 'kubernetes',
name: 'jenkins-slave',
namespace: 'devops',
label: 'k8s-slave',
// 容器及容器模板定义
containers: [
containerTemplate(
name: 'maven',
image: 'maven:3.6-jdk-8-alpine',
ttyEnabled: true,
command: 'cat',
privileged: false,
alwaysPullImage: false,
workingDir: '/home/jenkins/agent',
resourceRequestCpu: '100m',
resourceLimitCpu: '500m',
resourceRequestMemory: '100Mi',
resourceLimitMemory: '500Mi',
envVars: [
envVar(key: 'MYSQL_ALLOW_EMPTY_PASSWORD', value: 'true')
//, secretEnvVar(key: 'MYSQL_PASSWORD', secretName: 'mysql-secret', secretKey: 'password')
]
//,ports: [portMapping(name: 'mysql', containerPort: 3306, hostPort: 3306)]
)
// ),
// containerTemplate(
// name: 'jnlp',
// image: 'registry.cn-hangzhou.aliyuncs.com/google-containers/jnlp-slave:alpine',
// args: '${computer.jnlpmac} ${computer.name}',
// command: ''
// )
]
,volumes: [
hostPathVolume(hostPath: '/nfsdisk-31/appstorage/mavenRepo', mountPath: '/home/jenkins'),
hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock'),
/*
emptyDirVolume(mountPath: '/etc/mount1', memory: false),
secretVolume(mountPath: '/etc/mount2', secretName: 'my-secret'),
configMapVolume(mountPath: '/etc/mount3', configMapName: 'my-config'),
hostPathVolume(mountPath: '/etc/mount4', hostPath: '/mnt/my-mount'),
nfsVolume(mountPath: '/etc/mount5', serverAddress: '127.0.0.1', serverPath: '/', readOnly: true),
persistentVolumeClaim(mountPath: '/home/jenkins', claimName: 'jenkins', readOnly: false),
*/
],
annotations: [
podAnnotation(key: "maven-pod", value: "Kubernetes-jenkins-Test")
],
showRawYaml: 'flase'
//,defaultContainer: 'maven' # scripted pipeline 不支持该参数
//,imagePullSecrets: [ 'pull-secret' ]
)
{
// 注意此次node中则为Pod模板名称
node ('k8s-slave') {
// container 中指定Pod中容器名称
container('maven') {
stage('maven') {
container('maven') {
sh "hostname && ip addr"
sh ("mvn -version")
sh "env"
}
}
}
}
}
4.2、Declarative Pipeline
pipeline {
agent {
kubernetes {
cloud 'kubernetes'
namespace 'devops'
workingDir '/home/jenkins/agent'
// 继承模板
inheritFrom 'jenkins-slave'
defaultContainer 'maven'
// yamlFile 'KubernetesPod.yaml'
yaml """\
apiVersion:
kind: Pod
metadata:
labels:
jenkins: "slave"
jenkins/label: 'k8s-slave'
spec:
containers:
- name: 'maven'
image: 'maven:3.6-jdk-8-alpine'
imagePullPolicy: 'IfNotPresent' # 镜像拉取策略
command:
- cat
tty: true
""".stripIndent()
}
}
stages {
stage ('declarative Pipeline - kubernetes') {
steps {
echo "declarative Pipeline - kubernetes"
container ('maven') {
sh "echo ${POD_CONTAINER}"
sh "mvn -version"
}
}
}
}
}
- 你也可以使用yamlFile将pod模板保存在单独的KubernetesPod.yaml文件中.
pipeline {
agent {
kubernetes {
yamlFile 'KubernetesPod.yaml'
}
}
stages {
...
}
}
4.3、Jenkins share library Groovy
#!groovy
@Library('jenkinslibrary@master') _
//func from shareibrary
def build = new org.devops.build()
def deploy = new org.devops.deploy()
def tools = new org.devops.tools()
def gitlab = new org.devops.gitlab()
def toemail = new org.devops.toemail()
def sonar = new org.devops.sonarqube()
def sonarapi = new org.devops.sonarapi()
def nexus = new org.devops.nexus()
def artifactory = new org.devops.artifactory()
def k8s = new org.devops.kubernetes()
def runOpts
//env
String buildType = "${env.buildType}"
String buildShell = "${env.buildShell}"
String deployHosts = "${env.deployHosts}"
String srcUrl = "${env.srcUrl}"
String branchName = "${env.branchName}"
String artifactUrl = "${env.artifactUrl}"
if ("${runOpts}" == "GitlabPush"){
branchName = branch - "refs/heads/"
currentBuild.description = "Trigger by ${userName} ${branch}"
gitlab.ChangeCommitStatus(projectId,commitSha,"running")
env.runOpts = "GitlabPush"
} else {
userEmail = "123@qq.com"
}
//pipeline
pipeline{
agent { node { label "build"}}
stages{
stage("GetCode"){
steps{
script{
println("${branchName}")
tools.PrintMes("获取代码","green")
checkout([$class: 'GitSCM', branches: [[name: "${branchName}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [],
submoduleCfg: [],
userRemoteConfigs: [[credentialsId: 'gitlab-admin-user', url: "${srcUrl}"]]])
}
}
}
stage("Build&Test"){
steps{
script{
tools.PrintMes("执行打包","green")
build.Build(buildType,buildShell)
}
}
}
//并行
stage('parallel01') {
parallel {
stage("QA"){
steps {
script{
tools.PrintMes("搜索项目","green")
result = sonarapi.SerarchProject("${JOB_NAME}")
println(result)
if (result == "false"){
println("${JOB_NAME}---项目不存在,准备创建项目---> ${JOB_NAME}!")
sonarapi.CreateProject("${JOB_NAME}")
} else {
println("${JOB_NAME}---项目已存在!")
}
tools.PrintMes("配置项目质量规则","green")
qpName="${JOB_NAME}".split("-")[0] //Sonar%20way
sonarapi.ConfigQualityProfiles("${JOB_NAME}","java",qpName)
tools.PrintMes("配置质量阈","green")
sonarapi.ConfigQualityGates("${JOB_NAME}",qpName)
tools.PrintMes("代码扫描","green")
sonar.SonarScan("test","${JOB_NAME}","${JOB_NAME}","src","${branchName}")
sleep 30
tools.PrintMes("获取扫描结果","green")
result = sonarapi.GetProjectStatus("${JOB_NAME}")
println(result)
if (result.toString() == "ERROR"){
toemail.Email("代码质量阈错误!请及时修复!",userEmail)
error " 代码质量阈错误!请及时修复!"
} else {
println(result)
}
}
}
}
//构建镜像
stage("BuildImages"){
steps{
script{
tools.PrintMes("构建上传镜像","green")
env.serviceName = "${JOB_NAME}".split("_")[0]
withCredentials([usernamePassword(credentialsId: 'aliyun-registry-admin', passwordVariable: 'password', usernameVariable: 'username')]) {
env.dockerImage = "registry.cn-beijing.aliyuncs.com/devopstest/${serviceName}:${branchName}"
sh """
docker login -u ${username} -p ${password} registry.cn-beijing.aliyuncs.com
docker build -t registry.cn-beijing.aliyuncs.com/devopstest/${serviceName}:${branchName} .
sleep 1
docker push registry.cn-beijing.aliyuncs.com/devopstest/${serviceName}:${branchName}
sleep 1
#docker rmi registry.cn-beijing.aliyuncs.com/devopstest/${serviceName}:${branchName}
"""
}
}
}
}
}
}
//发布
stage("Deploy"){
steps{
script{
tools.PrintMes("发布应用","green")
//获取旧镜像
yamlData = readYaml file: "k8stemplate.yaml"
println(yamlData[0])
println(yamlData[0]["spec"]["template"]["spec"]["containers"][0]["image"])
oldImage = yamlData[0]["spec"]["template"]["spec"]["containers"][0]["image"]
//替换镜像
sourceData = readFile file: 'k8stemplate.yaml'
println(sourceData)
println(sourceData.getClass())
sourceData = sourceData.replace(oldImage,dockerImage)
println(sourceData)
writeFile file: 'k8stemplate.yaml', text: """${sourceData}"""
sh """
#cat k8stemplate.yaml
kubectl apply -f k8stemplate.yaml
"""
}
}
}
//接口自动化测试
stage("InterfaceTest"){
steps{
script{
tools.PrintMes("接口测试","green")
}
}
}
}
post {
always{
script{
println("always")
}
}
success{
script{
println("success")
if ("${runOpts}" == "GitlabPush"){
gitlab.ChangeCommitStatus(projectId,commitSha,"success")
}
toemail.Email("流水线成功",userEmail)
}
}
failure{
script{
println("failure")
if ("${runOpts}" == "GitlabPush"){
gitlab.ChangeCommitStatus(projectId,commitSha,"failed")
}
toemail.Email("流水线失败了!",userEmail)
}
}
aborted{
script{
println("aborted")
if ("${runOpts}" == "GitlabPush"){
gitlab.ChangeCommitStatus(projectId,commitSha,"canceled")
}
toemail.Email("流水线被取消了!",userEmail)
}
}
}
}