Jenkins是一款开源 CI&CD 工具,用于自动化各种任务,包括构建、测试和部署软件。从它面世不久,就有各种工具想要取代它,目前许多要取代它的工具已经销声匿迹,Jenkins依旧是 CI&CD 领域的常青树。
什么是CICD
CI/CD 是一套现代化的软件开发实践方法,旨在通过自动化的流程,频繁、可靠地交付软件变更。它包含了几个核心概念:
持续集成(Continuous Integration)
目前应用开发的目标是让多位开发人员同时处理同一个应用的不同功能,但是会在“发版日”将所有分支的源代码合并到一起,这可能会造成工作繁琐、耗时、手动完成。持续集成(CI)可以协助开发人员更加频繁(有时甚至是每天)将代码合并到特性分支或主分支。一旦开发人员对应用的更改被合并,系统就会自动构建应用、运行自动化测试来(一般是单元测试和集成测试)验收这些更改,确保这些更改不会对应用造成破坏。这意味着测试内容涵盖了从类和函数到构成整个应用的不同模块,如果自动化测试发现新代码和现有代码之间存在冲突,CI阶段可以更加容易的快速修复这些错误。
持续交付(Continuous Delivery)
完成CI阶段中构建及单元测试和集成测试的自动化流程之后,持续交付可自动将已验证的代码制品发布到制品库。为了实现高效的持续交付流程,务必要确保CI已内置于开发管道。持续交付的目标是拥有一个可随时部署到生产环境的制品,在流程结束时,运维团队可以快速、轻松的将应用部署到生产环境中。
持续部署(Continuous Deployment)
作为持续交付(自动将生产就绪型构建版本发布到制品库)的延伸,持续部署可以自动将应用发布到生产环境。如果在生产之前的管道没有手动处理阶段,持续部署在很大程度上都需要依赖精心设计的测试自动化,这可能需要前期较大的投资。
为什么用Jenkins
优点
1、开源且免费
这是 Jenkins 最吸引人的起点。无论是个人开发者、初创公司还是大型企业,都无需支付任何许可费用,这大大降低了入门门槛。
2、极其强劲的插件生态
这是 Jenkins 的核心竞争力。拥有超过 1800 个社区插件,几乎可以与任何技术栈和工具集成,无论你的需求多么特殊,几乎总能找到一个插件来满足。
3、高度可定制和灵活
Jenkins 不强制你使用某种特定的工作流程。你可以通过编写 Pipeline-as-Code(使用 Jenkinsfile) 来准确地定义复杂的构建、测试和部署流程。Groovy 脚本提供了强劲的逻辑控制能力,可以实现超级复杂的自动化场景。
4、庞大的社区和丰富的资源
作为一款历史悠久的软件,Jenkins 拥有庞大而活跃的社区。这意味着当你遇到问题时,很容易通过搜索引擎、Stack Overflow、官方文档或社区论坛找到解决方案和经验分享。
5、跨平台
由于基于 Java,Jenkins 可以运行在任何支持 Java 的主流操作系统上,包括 Windows, Linux 和 macOS。
6、分布式构建
Jenkins 支持主从(Master-Agent)架构,可以将构建任务分发到多台代理机器上执行。这极大地提高了扩展性,可以并行处理多个任务,并轻松地为不同的项目(如 Android, iOS, Python)配置特定的构建环境。
缺点
1、配置和维护开销大
Jenkins 的“自由”是有代价的,这个代价就是运维成本。你需要自己负责服务器的搭建、维护、升级、安全补丁和插件的兼容性管理。这需要专门的运维知识。
2、用户体验和界面相对落后
与许多现代化的 CI/CD 工具(如 GitLab CI, GitHub Actions, CircleCI)相比,Jenkins 的 Web 界面显得陈旧且不够直观。对于新手来说,学习曲线较陡峭。
3、插件管理的双刃剑
插件依赖和冲突: 插件之间可能存在依赖关系,更新一个插件可能会破坏另一个插件的功能。
质量参差不齐: 社区插件的质量和维护状态差异很大,有些插件可能已经无人维护,存在安全漏洞或 Bug。安全风险: 插件是 Jenkins 安全漏洞的主要来源,需要持续关注和更新。
4、基于状态
Jenkins 服务器本身是有状态的,所有的配置、构建历史、日志都存储在服务器上。这使得 Jenkins 服务器的备份、迁移和容器化(虽然可以实现)比无状态服务更复杂。
5、Pipeline 语法学习曲线
虽然 Pipeline-as-Code 是强劲且正确的方向,但编写复杂的 Jenkinsfile 需要学习 Groovy 语法和 Jenkins 特定的 DSL(领域特定语言),这对初学者有必定难度。
6、资源消耗相对较高
作为一个 Java 应用程序,Jenkins 主服务器本身对内存和 CPU 有必定的要求,尤其是在管理大量任务和代理节点时。
部署Jenkins
使用configuration as code进行jenkins配置,增加sidecar容器监听配置变更自动reload配置,并部署dockerd作为jenkins流水线构建镜像,做到部署完成即可使用,无需花费精力配置。
准备部署文件
ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
name: jenkins
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
spec:
ingressClassName: nginx
tls:
- hosts:
- jenkins.kubeop.com
secretName: kubeop.com-ssl
rules:
- host: jenkins.kubeop.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: jenkins
port:
number: 8080
service.yaml
---
apiVersion: v1
kind: Service
metadata:
name: jenkins
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
spec:
type: ClusterIP
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 8080
selector:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
---
apiVersion: v1
kind: Service
metadata:
name: jenkins-agent
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
spec:
type: ClusterIP
ports:
- name: agent-listener
port: 50000
protocol: TCP
targetPort: 50000
selector:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: jenkins-schedule-agents
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
rules:
- apiGroups:
- ""
resources:
- pods
- pods/exec
- pods/log
- persistentvolumeclaims
- events
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods
- pods/exec
- persistentvolumeclaims
verbs:
- create
- delete
- deletecollection
- patch
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: jenkins-casc-reload
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- watch
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: jenkins-schedule-agents
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: jenkins-schedule-agents
subjects:
- kind: ServiceAccount
name: jenkins
namespace: infra
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: jenkins-watch-configmaps
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: jenkins-casc-reload
subjects:
- kind: ServiceAccount
name: jenkins
namespace: infra
secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: jenkins
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
type: Opaque
data:
jenkins-admin-password: "YWRtaW4="
jenkins-admin-user: "YWRtaW4="
statefulset.yaml
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: jenkins
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
spec:
serviceName: "jenkins"
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
template:
metadata:
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: jenkins
operator: In
values:
- "true"
serviceAccountName: "jenkins"
enableServiceLinks: false
initContainers:
- name: hack
image: registry.cn-hangzhou.aliyuncs.com/kubeop/jenkins:2.528.1
imagePullPolicy: Always
securityContext:
runAsGroup: 0
runAsUser: 0
command:
- /bin/bash
- -c
- chown -R 1000:1000 ${JENKINS_HOME}
volumeMounts:
- name: jenkins-data
mountPath: /data/jenkins
- name: init
image: registry.cn-hangzhou.aliyuncs.com/kubeop/jenkins:2.528.1
imagePullPolicy: Always
securityContext:
allowPrivilegeEscalation: false
runAsGroup: 1000
runAsUser: 1000
command:
- /bin/bash
- -ec
- |
echo "disable Setup Wizard"
# Prevent Setup Wizard when JCasC is enabled
echo ${JENKINS_VERSION} > ${JENKINS_HOME}/jenkins.install.UpgradeWizard.state
echo ${JENKINS_VERSION} > ${JENKINS_HOME}/jenkins.install.InstallUtil.lastExecVersion
echo "download plugins"
jenkins-plugin-cli --war /usr/share/jenkins/jenkins.war -f /usr/share/jenkins/ref/plugins.txt --plugin-download-directory ${JENKINS_HOME}/plugins --verbose;
volumeMounts:
- name: jenkins-data
mountPath: /data/jenkins
containers:
- name: jenkins
image: registry.cn-hangzhou.aliyuncs.com/kubeop/jenkins:2.528.1
imagePullPolicy: Always
securityContext:
allowPrivilegeEscalation: false
runAsGroup: 1000
runAsUser: 1000
args: [ "--httpPort=8080"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: JAVA_OPTS
value: "-Xms8192m -Xmx8192m -Dcasc.reload.token=$(POD_NAME) -Dfile.encoding=utf-8 -Duser.timezone=GMT+08 -Duser.country=CN"
- name: JENKINS_OPTS
value: ""
- name: JENKINS_SLAVE_AGENT_PORT
value: "50000"
- name: CASC_JENKINS_CONFIG
value: /data/jenkins/casc_configs
ports:
- containerPort: 8080
name: http
protocol: TCP
- containerPort: 50000
name: agent-listener
protocol: TCP
resources:
limits:
cpu: 8
memory: 16Gi
requests:
cpu: 8
memory: 16Gi
startupProbe:
failureThreshold: 12
httpGet:
path: '/login'
port: http
initialDelaySeconds: 40
periodSeconds: 10
timeoutSeconds: 5
livenessProbe:
failureThreshold: 5
httpGet:
path: '/login'
port: http
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
failureThreshold: 3
httpGet:
path: '/login'
port: http
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 5
volumeMounts:
- name: jenkins-jcasc-config
mountPath: /data/jenkins/casc_configs
- name: admin-secret
mountPath: /run/secrets/admin-username
subPath: jenkins-admin-user
readOnly: true
- name: admin-secret
mountPath: /run/secrets/admin-password
subPath: jenkins-admin-password
readOnly: true
- name: tmp-volume
mountPath: /tmp
- name: jenkins-data
mountPath: /data/jenkins
- name: config-reload
image: registry.cn-hangzhou.aliyuncs.com/devops-system/k8s-sidecar:1.30.7
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: LABEL
value: "jenkins-config"
- name: FOLDER
value: "/data/jenkins/casc_configs"
- name: NAMESPACE
value: 'infra'
- name: REQ_URL
value: "http://localhost:8080/reload-configuration-as-code/?casc-reload-token=$(POD_NAME)"
- name: REQ_METHOD
value: "POST"
- name: REQ_RETRY_CONNECT
value: "10"
volumeMounts:
- name: jenkins-jcasc-config
mountPath: /data/jenkins/casc_configs
- name: jenkins-data
mountPath: /data/jenkins
volumes:
- name: jenkins-jcasc-config
emptyDir: {}
- name: admin-secret
secret:
secretName: jenkins
- name: tmp-volume
emptyDir: {}
- name: jenkins-data
persistentVolumeClaim:
claimName: jenkins
local-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins
namespace: infra
spec:
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- worker-001
capacity:
storage: 100Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local
local:
path: /data/jenkins
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: jenkins
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Gi
storageClassName: local
volumeMode: Filesystem
volumeName: jenkins
jcasc-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: jenkins-jcasc-config
namespace: infra
labels:
app.kubernetes.io/name: "jenkins"
app.kubernetes.io/component: "jenkins-controller"
jenkins-config: "true"
data:
jcasc-default-config.yaml: |-
credentials:
system:
domainCredentials:
- credentials:
- string:
description: "sonar"
id: "sonar"
scope: GLOBAL
secret: "sonar secret"
- usernamePassword:
description: "devops"
id: "devops"
password: "password"
scope: GLOBAL
username: "devops"
- file:
description: "jenkins"
fileName: "id_rsa"
id: "jenkins"
scope: GLOBAL
secretBytes: "<secretBytes_base64_encoded>"
- file:
description: "k8s-ops-prd"
fileName: "config"
id: "k8s-ops-prd"
scope: GLOBAL
secretBytes: "<secretBytes_base64_encoded>"
jenkins:
authorizationStrategy:
projectMatrix:
entries:
- group:
name: "authenticated"
permissions:
- "Job/Build"
- "Job/Cancel"
- "Job/Read"
- "Job/Workspace"
- "Overall/Read"
- "View/Read"
- user:
name: "admin"
permissions:
- "Overall/Administer"
clouds:
- kubernetes:
containerCap: 60
containerCapStr: "60"
connectTimeout: 5
readTimeout: 15
jenkinsUrl: "http://jenkins.infra.svc.cluster.local:8080"
jenkinsTunnel: "jenkins-agent.infra.svc.cluster.local:50000"
maxRequestsPerHostStr: "32"
name: "kubernetes"
namespace: "infra"
serverUrl: "https://kubernetes.default.svc.cluster.local"
podLabels:
- key: "jenkins/jenkins-agent"
value: "true"
templates:
- name: "sonar"
namespace: "infra"
podRetention: "never"
instanceCap: 20
instanceCapStr: "20"
label: "sonar"
nodeSelector: "jenkins=true,member=slave"
showRawYaml: false
yamlMergeStrategy: "override"
volumes:
- persistentVolumeClaim:
claimName: "jenkins"
mountPath: "/data/jenkins"
readOnly: false
containers:
- name: "sonar"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/sonar-scanner-cli:11.5
envVars:
- envVar:
key: "TZ"
value: "Asia/Shanghai"
- envVar:
key: "SONAR_SCANNER_OPTS"
value: "-Xmx2048m"
alwaysPullImage: false
privileged: "false"
command: "/bin/sh -c"
args: "cat"
runAsUser: 1000
runAsGroup: 1000
ttyEnabled: true
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "4096M"
workingDir: "/data/jenkins"
- name: "jnlp"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/inbound-agent:bookworm-jdk21
envVars:
- envVar:
key: "JAVA_OPTS"
value: "-Xms2048m -Xmx2048m"
runAsUser: 1000
runAsGroup: 1000
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "2"
resourceLimitMemory: "3072M"
workingDir: "/data/jenkins"
- name: "java"
namespace: "infra"
podRetention: "never"
instanceCap: 20
instanceCapStr: "20"
label: "java"
nodeSelector: "jenkins=true,member=slave"
showRawYaml: false
yamlMergeStrategy: "override"
volumes:
- persistentVolumeClaim:
claimName: "jenkins"
mountPath: "/data/jenkins"
readOnly: false
containers:
- name: "java"
image: "registry.cn-hangzhou.aliyuncs.com/kubeop/maven:3.9.11-java8"
alwaysPullImage: false
privileged: "false"
command: "/bin/sh -c"
args: "cat"
runAsUser: 1000
runAsGroup: 1000
ttyEnabled: true
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "4096M"
workingDir: "/data/jenkins"
- name: "jnlp"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/inbound-agent:bookworm-jdk21
runAsUser: 1000
runAsGroup: 1000
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "2"
resourceLimitMemory: "3072M"
envVars:
- envVar:
key: "JAVA_OPTS"
value: "-Xms2048m -Xmx2048m"
workingDir: "/data/jenkins"
- name: "java-11"
namespace: "infra"
podRetention: "never"
instanceCap: 20
instanceCapStr: "20"
label: "java-11"
nodeSelector: "jenkins=true,member=slave"
showRawYaml: false
yamlMergeStrategy: "override"
volumes:
- persistentVolumeClaim:
claimName: "jenkins"
mountPath: "/data/jenkins"
readOnly: false
containers:
- name: "java-11"
image: "registry.cn-hangzhou.aliyuncs.com/kubeop/maven:3.9.11-java11"
alwaysPullImage: false
privileged: "false"
command: "/bin/sh -c"
args: "cat"
runAsUser: 1000
runAsGroup: 1000
ttyEnabled: true
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "4096M"
workingDir: "/data/jenkins"
- name: "jnlp"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/inbound-agent:bookworm-jdk21
runAsUser: 1000
runAsGroup: 1000
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "2"
resourceLimitMemory: "3072M"
envVars:
- envVar:
key: "JAVA_OPTS"
value: "-Xms2048m -Xmx2048m"
workingDir: "/data/jenkins"
- name: "gradle"
namespace: "infra"
podRetention: "never"
instanceCap: 20
instanceCapStr: "20"
label: "gradle"
nodeSelector: "jenkins=true,member=slave"
showRawYaml: false
yamlMergeStrategy: "override"
volumes:
- persistentVolumeClaim:
claimName: "jenkins"
mountPath: "/data/jenkins"
readOnly: false
containers:
- name: "gradle"
image: "registry.cn-hangzhou.aliyuncs.com/kubeop/gradle:8.12.1-java8"
alwaysPullImage: false
privileged: "false"
command: "/bin/sh -c"
args: "cat"
runAsUser: 1000
runAsGroup: 1000
ttyEnabled: true
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "8192M"
workingDir: "/data/jenkins"
- name: "jnlp"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/inbound-agent:bookworm-jdk21
runAsUser: 1000
runAsGroup: 1000
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "8096M"
envVars:
- envVar:
key: "JAVA_OPTS"
value: "-Xms8096m -Xmx8096m"
workingDir: "/data/jenkins"
- name: "golang"
namespace: "infra"
podRetention: "never"
instanceCap: 20
instanceCapStr: "20"
label: "golang"
nodeSelector: "jenkins=true,member=slave"
showRawYaml: false
yamlMergeStrategy: "override"
volumes:
- persistentVolumeClaim:
claimName: "jenkins"
mountPath: "/data/jenkins"
readOnly: false
containers:
- name: "golang"
image: registry.cn-hangzhou.aliyuncs.com/kubeop/golang:1.25.3
alwaysPullImage: false
privileged: "false"
command: "/bin/sh -c"
args: "cat"
runAsUser: 1000
runAsGroup: 1000
ttyEnabled: true
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "4096M"
workingDir: "/data/jenkins"
- name: "jnlp"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/inbound-agent:bookworm-jdk21
envVars:
- envVar:
key: "JAVA_OPTS"
value: "-Xms2048m -Xmx2048m"
runAsUser: 1000
runAsGroup: 1000
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "2"
resourceLimitMemory: "3072M"
workingDir: "/data/jenkins"
- name: "nodejs"
namespace: "infra"
podRetention: "never"
instanceCap: 20
instanceCapStr: "20"
label: "nodejs"
nodeSelector: "jenkins=true,member=slave"
showRawYaml: false
yamlMergeStrategy: "override"
volumes:
- persistentVolumeClaim:
claimName: "jenkins"
mountPath: "/data/jenkins"
readOnly: false
containers:
- name: "nodejs"
image: registry.cn-hangzhou.aliyuncs.com/kubeop/nodejs:22.21.0
alwaysPullImage: false
privileged: "false"
command: "/bin/sh -c"
args: "cat"
runAsUser: 1000
runAsGroup: 1000
ttyEnabled: true
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "4096M"
workingDir: "/data/jenkins"
- name: "jnlp"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/inbound-agent:bookworm-jdk21
envVars:
- envVar:
key: "JAVA_OPTS"
value: "-Xms2048m -Xmx2048m"
runAsUser: 1000
runAsGroup: 1000
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "2"
resourceLimitMemory: "3072M"
workingDir: "/data/jenkins"
- name: "nodejs-22"
namespace: "infra"
podRetention: "never"
instanceCap: 20
instanceCapStr: "20"
label: "nodejs-22"
nodeSelector: "jenkins=true,member=slave"
showRawYaml: false
yamlMergeStrategy: "override"
volumes:
- persistentVolumeClaim:
claimName: "jenkins"
mountPath: "/data/jenkins"
readOnly: false
containers:
- name: "nodejs-24"
image: registry.cn-hangzhou.aliyuncs.com/kubeop/nodejs:24.10.0
alwaysPullImage: false
privileged: "false"
command: "/bin/sh -c"
args: "cat"
runAsUser: 1000
runAsGroup: 1000
ttyEnabled: true
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "4096M"
workingDir: "/data/jenkins"
- name: "jnlp"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/inbound-agent:bookworm-jdk21
envVars:
- envVar:
key: "JAVA_OPTS"
value: "-Xms2048m -Xmx2048m"
runAsUser: 1000
runAsGroup: 1000
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "2"
resourceLimitMemory: "3072M"
workingDir: "/data/jenkins"
- name: "docker"
namespace: "infra"
podRetention: "never"
instanceCap: 20
instanceCapStr: "20"
label: "docker"
nodeSelector: "jenkins=true,member=slave"
showRawYaml: false
yamlMergeStrategy: "override"
volumes:
- persistentVolumeClaim:
claimName: "jenkins"
mountPath: "/data/jenkins"
readOnly: false
- hostPathVolume:
hostPath: "/var/run/docker.sock"
mountPath: "/var/run/docker.sock"
readOnly: false
containers:
- name: "docker"
image: registry.cn-hangzhou.aliyuncs.com/kubeop/docker:28.5.1
alwaysPullImage: false
privileged: "true"
runAsUser: 1000
runAsGroup: 1000
ttyEnabled: true
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "4096M"
workingDir: "/data/jenkins"
- name: "jnlp"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/inbound-agent:bookworm-jdk21
envVars:
- envVar:
key: "JAVA_OPTS"
value: "-Xms2048m -Xmx2048m"
runAsUser: 1000
runAsGroup: 1000
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "2"
resourceLimitMemory: "3072M"
workingDir: "/data/jenkins"
- name: "kubectl"
namespace: "infra"
podRetention: "never"
instanceCap: 20
instanceCapStr: "20"
label: "kubectl"
nodeSelector: "jenkins=true,member=slave"
showRawYaml: false
yamlMergeStrategy: "override"
volumes:
- persistentVolumeClaim:
claimName: "jenkins"
mountPath: "/data/jenkins"
readOnly: false
containers:
- name: "kubectl"
image: registry.cn-hangzhou.aliyuncs.com/kubeop/kubectl:v1.34.1
alwaysPullImage: false
privileged: "false"
command: "/bin/sh -c"
args: "cat"
runAsUser: 1000
runAsGroup: 1000
ttyEnabled: true
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "4096M"
workingDir: "/data/jenkins"
- name: "jnlp"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/inbound-agent:bookworm-jdk21
envVars:
- envVar:
key: "JAVA_OPTS"
value: "-Xms2048m -Xmx2048m"
runAsUser: 1000
runAsGroup: 1000
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "2"
resourceLimitMemory: "3072M"
workingDir: "/data/jenkins"
- name: "kubedog"
namespace: "infra"
podRetention: "never"
instanceCap: 20
instanceCapStr: "20"
label: "kubedog"
nodeSelector: "jenkins=true,member=slave"
showRawYaml: false
yamlMergeStrategy: "override"
volumes:
- persistentVolumeClaim:
claimName: "jenkins"
mountPath: "/data/jenkins"
readOnly: false
containers:
- name: "kubedog"
image: registry.cn-hangzhou.aliyuncs.com/kubeop/kubedog:0.13.0
alwaysPullImage: false
privileged: "false"
command: "/bin/sh -c"
args: "cat"
runAsUser: 1000
runAsGroup: 1000
ttyEnabled: true
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "4096M"
workingDir: "/data/jenkins"
- name: "jnlp"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/inbound-agent:bookworm-jdk21
envVars:
- envVar:
key: "JAVA_OPTS"
value: "-Xms2048m -Xmx2048m"
runAsUser: 1000
runAsGroup: 1000
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "2"
resourceLimitMemory: "3072M"
workingDir: "/data/jenkins"
- name: "ansible"
namespace: "infra"
podRetention: "never"
instanceCap: 20
instanceCapStr: "20"
label: "ansible"
nodeSelector: "jenkins=true,member=slave"
showRawYaml: false
yamlMergeStrategy: "override"
volumes:
- persistentVolumeClaim:
claimName: "jenkins"
mountPath: "/data/jenkins"
readOnly: false
containers:
- name: "ansible"
image: registry.cn-hangzhou.aliyuncs.com/kubeop/ansible:2.16.14
alwaysPullImage: false
privileged: "false"
command: "/bin/sh -c"
args: "cat"
runAsUser: 1000
runAsGroup: 1000
ttyEnabled: true
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "4"
resourceLimitMemory: "4096M"
workingDir: "/data/jenkins"
- name: "jnlp"
image: registry.cn-hangzhou.aliyuncs.com/devops-system/inbound-agent:bookworm-jdk21
envVars:
- envVar:
key: "JAVA_OPTS"
value: "-Xms2048m -Xmx2048m"
runAsUser: 1000
runAsGroup: 1000
resourceRequestMemory: "512M"
resourceRequestCpu: "500m"
resourceLimitCpu: "2"
resourceLimitMemory: "3072M"
workingDir: "/data/jenkins"
crumbIssuer:
standard:
excludeClientIPFromCrumb: true
disableRememberMe: false
labelAtoms:
- name: "built-in"
markupFormatter:
rawHtml:
disableSyntaxHighlighting: false
mode: NORMAL
numExecutors: 20
primaryView:
all:
name: "all"
projectNamingStrategy:
pattern:
description: "任务命名规范"
forceExistingJobs: true
namePattern: ".*_.*"
quietPeriod: 5
scmCheckoutRetryCount: 0
securityRealm:
local:
allowsSignup: false
enableCaptcha: false
users:
- id: "${admin-username}"
name: "Admin"
password: "${admin-password}"
slaveAgentPort: 50000
updateCenter:
sites:
- id: "default"
url: "https://gh-proxy.com/raw.githubusercontent.com/kubeop/update-center/master/updates/ustc/update-center.json"
views:
- all:
name: "all"
- list:
columns:
- "status"
- "weather"
- "jobName"
- "lastSuccess"
- "lastFailure"
- "lastDuration"
- "buildButton"
description: "运维组"
includeRegex: "ops_.*"
name: "ops"
viewsTabBar: "standard"
security:
apiToken:
creationOfLegacyTokenEnabled: false
tokenGenerationOnCreationEnabled: false
usageStatisticsEnabled: true
sSHD:
port: -1
unclassified:
buildUserVars:
allBuilds: true
location:
url: "https://jenkins.kubeop.com/"
shell:
shell: "/data/jenkins/base-sile"
sonarGlobalConfiguration:
buildWrapperEnabled: true
installations:
- credentialsId: "sonar"
name: "sonarqube"
serverUrl: "https://sonar.kubeop.com"
triggers:
skipScmCause: false
skipUpstreamCause: false
dockerd.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: dockerd
namespace: infra
labels:
app.kubernetes.io/name: dockerd
spec:
selector:
matchLabels:
app.kubernetes.io/name: dockerd
template:
metadata:
labels:
app.kubernetes.io/name: dockerd
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: docker
operator: In
values:
- "true"
containers:
- name: dockerd
image: registry.cn-hangzhou.aliyuncs.com/kubeop/docker:28-dind
imagePullPolicy: Always
args:
- --insecure-registry=registry.kubeop.com
- --bip=192.168.100.1/24
- --data-root=/data/docker
securityContext:
privileged: true
env:
- name: TZ
value: Asia/Shanghai
volumeMounts:
- mountPath: /var/run
name: docker-sock
- mountPath: /data/docker
name: docker-data
volumes:
- name: docker-data
hostPath:
path: /data/docker
- name: docker-sock
hostPath:
path: /var/run
添加证书
kubectl create ns infra
kubectl create secret tls kubeop.com-ssl --cert kubeop.com.pem --key kubeop.com.key -n infra
添加镜像拉取secret
kubectl create secret docker-registry hubsecret
-n infra
--docker-server='<DOCKER_SERVER>'
--docker-username='<DOCKER_USER_NAME>'
--docker-password='<DOCKER_USER_PASSWORD>'
--docker-email='<DOCKER_USER_EMAIL>'
部署
kubectl apply -f . -n infra
配置流水线
简单流水线
以下示例是一个最简单的流水线,规模小、业务简单的提议使用这种方式
#!/usr/bin/env groovy
node {
APP_PROJECT = JOB_NAME.split('_')[0]
APP_NAME = JOB_NAME.split('_')[1]
APP_WORKSPACE = JENKINS_HOME + '/workspace/' + JOB_NAME.toLowerCase()
DOCKER_REGISTRY = "registry.cn-shanghai.aliyuncs.com"
GIT_NAME = "https://github.com/kubeop/java-demo.git"
}
pipeline {
agent any
options {
timestamps()
timeout(time: 30, unit: 'MINUTES')
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '10'))
}
parameters {
gitParameter(
branch: '',
branchFilter: 'origin/(.*)',
defaultValue: '',
listSize: '10',
name: 'SCM_REVISION',
quickFilterEnabled: true,
selectedValue: 'NONE',
sortMode: 'DESCENDING_SMART',
tagFilter: '*',
type: 'PT_BRANCH_TAG',
description: 'Please select a branch or tag to build')
choice(
name: 'ENVIRONMENT',
description: 'Please select Environment',
choices: 'dev
fat
uat
pro')
}
stages {
stage ("Initial Stages") {
steps {
script {
node ("built-in") {
dir(APP_WORKSPACE){
stage('stage 1: Git Clone') {
deleteDir()
checkout([$class: 'GitSCM',
branches: [[name: "$SCM_REVISION"]],
userRemoteConfigs: [[credentialsId: 'gitlab',url:"$GIT_NAME"]]])
if (!SCM_REVISION) { error "您没有选择Git分支或TAG!" }
wrap([$class: 'BuildUser']) { env.BUILD_USER = BUILD_USER }
currentBuild.displayName = BUILD_NUMBER + '-' + ENVIRONMENT
currentBuild.description = BUILD_USER + ' deploy by ' + SCM_REVISION
}
}
}
node ("sonar") {
container("sonar"){
dir(APP_WORKSPACE){
stage('stage 2: Scanner Code') {
withSonarQubeEnv('sonar') {
sh 'sonar-scanner -Dsonar.projectKey=$APP_NAME -Dsonar.projectName=$APP_NAME -Dsonar.projectVersion=$GIT_REVISION -Dsonar.projectBaseDir=. -Dsonar.language=java -Dsonar.sources=. -Dsonar.java.binaries=.'
}
}
}
}
}
node ("java") {
container("java"){
dir(APP_WORKSPACE){
stage('stage 3: Compile Code') {
sh 'mvn -U clean -Dmaven.test.skip:true package dependency:tree'
}
}
}
}
node ("java") {
container("java"){
dir(APP_WORKSPACE){
stage('stage 4: Junit Test') {
sh 'mvn test'
//junit 'reports/**/*.xml'
}
}
}
}
node ("docker") {
container("docker"){
dir(APP_WORKSPACE){
stage('stage 5: Build image') {
docker.withRegistry("https://" + DOCKER_REGISTRY, 'harbor') {
docker.build(DOCKER_REGISTRY + "/" + APP_PROJECT + "/" + APP_NAME + ":" + GIT_REVISION,"-f Dockerfile . --pull")
docker.image(DOCKER_REGISTRY + "/" + APP_PROJECT + "/" + APP_NAME + ":" + GIT_REVISION).push()
}
}
}
}
}
node ("kubectl") {
container("kubectl"){
dir(APP_WORKSPACE){
stage('stage 6: Deploy to Kubernetes') {
sh 'kubectl set image deployment/' + APP_NAME + ' ' + APP_NAME + '=' + DOCKER_REGISTRY + '/' + APP_PROJECT + '/' + APP_NAME + ':' + GIT_REVISION + ' -n ' + APP_PROJECT + '-' + ENVIRONMENT
}
}
}
}
}
}
}
}
}
进阶流水线
以下示例是基于Jenkins shard library,规模较大、流程复杂的提议使用这种方式
// Load library
library(
identifier: 'jenkins-shared-library@main',
retriever: modernSCM(
[
$class: 'GitSCMSource',
credentialsId: 'devops',
remote: 'https://github.com/kubeop/jenkins-shared-library.git',
traits: [
gitBranchDiscovery()
]
]
)
)
entry([
clean_workspace: true,
git_repo: "https://github.com/kubeop/java-demo.git",
build_command: "mvn",
build_options: "-U clean -Dmaven.test.skip:true package dependency:tree",
artifact_src: "target/demo-0.0.2.jar",
artifact_dest: "demo.jar",
docker_registry: "registry.cn-shanghai.aliyuncs.com",
docker_registry_credential: "harbor",
k8s_cluster: "ops-pro"
])
构建示例
构建参数

阶段视图-部署到kubernetes

阶段视图-部署到服务器

阶段视图-同时部署到kubernetes和服务器

















- 最新
- 最热
只看作者