在kubernetes中更优雅的使用jenkins

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(time30, 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中更优雅的使用jenkins

阶段视图-部署到kubernetes

在kubernetes中更优雅的使用jenkins

阶段视图-部署到服务器

在kubernetes中更优雅的使用jenkins

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

在kubernetes中更优雅的使用jenkins

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 共1条

请登录后发表评论