用 Jenkins 实现自动化 CI/CD_jenkins api自动执行
off999 2025-10-14 03:37 24 浏览 0 评论
场景设定(可替换为你的技术栈)
- 语言:Node.js(示例简单,任何语言思路一致)
- 制品:Docker 镜像(推送到 Docker Hub/Harbor)
- 运行环境:Kubernetes(staging + prod 两个命名空间)
- 流程:Push 触发 → 单元测试 → Lint → 构建镜像 → Trivy 安全扫描 → 推送镜像 → Helm 部署到 Staging → 人工审批 → 部署到 Prod → 通知
1. 环境准备
1.1 服务器要求
- 一台 Linux 主机(Ubuntu 22.04 例子)
- 已安装:docker、docker compose、kubectl、helm(Jenkins 也会用到)
- 有一个可访问的镜像仓库(Docker Hub/Harbor)
- 有可访问的 K8s 集群(含 staging 与 prod 两个 namespace)
# Docker / Compose(示例)
sudo apt-get update && sudo apt-get install -y ca-certificates curl gnupg
# ...(略:Docker 官方安装步骤)
docker --version
docker compose version
1.2 用 Docker Compose 启动 Jenkins(含 Docker 构建能力)
这种方式简单、隔离好、可快速恢复。
docker-compose.yml
services:
jenkins:
image: jenkins/jenkins:lts-jdk17
container_name: jenkins
user: root
ports:
- "8080:8080"
- "50000:50000" # JNLP agent 端口
volumes:
- jenkins_home:/var/jenkins_home
-
/var/run/docker.sock:/var/run/docker.sock # 让 Jenkins 内能使用宿主的 docker
- /usr/local/bin/kubectl:/usr/local/bin/kubectl
- /usr/local/bin/helm:/usr/local/bin/helm
environment:
- JAVA_OPTS=-Dorg.jenkinsci.plugins.durabletask.BourneShellScript.LAUNCH_DIAGNOSTICS=true
volumes:
jenkins_home:
docker compose up -d
docker logs -f jenkins # 取初始管理员密码
# 初始密码文件路径(容器内):
# /var/jenkins_home/secrets/initialAdminPassword
1.3 Jenkins 插件建议安装
- Pipeline, Git, Blue Ocean, Credentials Binding
- Docker Pipeline, Docker, Kubernetes, Kubernetes CLI
- Pipeline: Stage View, Timestamper
- Warnings NG(代码质量)
- Generic Webhook Trigger(或使用 GitHub 插件)
- Slack(或企业微信/钉钉通知插件)
1.4 Jenkins 凭据准备(Credentials)
在 Manage Jenkins → Credentials 新建:
- dockerhub-cred:类型 Username/Password,用于 docker login
- kubeconfig:Secret file(上传 kubeconfig)
- slack-webhook:Secret text(可选)
- git-ssh:SSH Username with private key(如果仓库用 SSH)
2. 示例项目源码
2.1 目录结构
demo-ci-cd/
├─ app/
│ ├─ app.js
│ ├─ package.json
│ └─ __tests__/app.test.js
├─ Dockerfile
├─ helm/
│ └─ demo/
│ ├─ Chart.yaml
│ ├─ values.yaml
│ └─ templates/
│ ├─ deployment.yaml
│ ├─ service.yaml
│ └─ ingress.yaml # 可选
└─ Jenkinsfile
2.2 Node.js 应用与测试
app/app.js
const express = require('express');
const app = express();
app.get('/', (_, res) => res.send('Hello CI/CD v1'));
app.get('/healthz', (_, res) => res.status(200).send('ok'));
const port = process.env.PORT || 3000;
if (require.main === module) {
app.listen(port, () => console.log(`Server on :${port}`));
}
module.exports = app;
app/package.json
{
"name": "demo-ci-cd",
"version": "1.0.0",
"main": "app.js",
"scripts": {
"start": "node app.js",
"test": "jest --runInBand",
"lint": "npx eslint . || true"
},
"dependencies": {
"express": "^4.19.2"
},
"devDependencies": {
"jest": "^29.7.0",
"supertest": "^6.3.3",
"eslint": "^9.8.0"
}
}
app/tests/app.test.js
const request = require('supertest');
const app = require('../app');
describe('GET /', () => {
it('returns hello', async () => {
const res = await request(app).get('/');
expect(res.statusCode).toBe(200);
expect(res.text).toMatch(/Hello CI\/CD/);
});
});
describe('GET /healthz', () => {
it('returns ok', async () => {
const res = await request(app).get('/healthz');
expect(res.statusCode).toBe(200);
});
});
2.3 Dockerfile
FROM node:20-alpine AS deps
WORKDIR /app
COPY app/package*.json ./
RUN npm ci --only=production
FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY app/ .
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:3000/healthz || exit 1
CMD ["node", "app.js"]
2.4 Helm Chart(K8s 部署)
helm/demo/Chart.yaml
apiVersion: v2
name: demo
version: 0.1.0
appVersion: "1.0.0"
helm/demo/values.yaml
image:
repository: your-dockerhub-username/demo-ci-cd
tag: "latest"
pullPolicy: IfNotPresent
replicaCount: 2
service:
type: ClusterIP
port: 80
targetPort: 3000
ingress:
enabled: false
className: ""
hosts:
- host: demo.example.com
paths:
- path: /
pathType: Prefix
resources: {}
helm/demo/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "demo.fullname" . }}
labels: { app: {{ include "demo.name" . }} }
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels: { app: {{ include "demo.name" . }} }
template:
metadata:
labels: { app: {{ include "demo.name" . }} }
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 3000
readinessProbe:
httpGet: { path: /healthz, port: 3000 }
initialDelaySeconds: 3
livenessProbe:
httpGet: { path: /healthz, port: 3000 }
initialDelaySeconds: 10
helm/demo/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "demo.fullname" . }}
spec:
type: {{ .Values.service.type }}
selector: { app: {{ include "demo.name" . }} }
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
helm/demo/templates/ingress.yaml(可选)
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "demo.fullname" . }}
spec:
ingressClassName: {{ .Values.ingress.className }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "demo.fullname" $ }}
port: { number: {{ $.Values.service.port }} }
{{- end }}
{{- end }}
{{- end }}
3. Jenkins Pipeline(Jenkinsfile)
Jenkinsfile(Declarative,含并行测试、Trivy 扫描、Helm 部署与人工审批)
pipeline {
agent any
options {
timestamps()
ansiColor('xterm')
buildDiscarder(logRotator(numToKeepStr: '30'))
}
environment {
REGISTRY = 'docker.io'
IMAGE = '
your-dockerhub-username/demo-ci-cd' // ← 改成你的仓库
COMMIT = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
DOCKER_CREDS = 'dockerhub-cred'
KUBECONFIG_CRED = 'kubeconfig'
CHART_DIR = 'helm/demo'
APP_NS_STG = 'staging'
APP_NS_PROD = 'prod'
TRIVY_CACHE_DIR = "${WORKSPACE}/.trivycache"
}
stages {
stage('Checkout') {
steps {
checkout scm
sh 'node -v || true'
}
}
stage('Install deps & Lint & Test') {
parallel {
stage('Install deps') {
steps { sh 'cd app && npm ci' }
}
stage('Lint') {
steps { sh 'cd app && npm run lint' }
}
stage('Unit Test') {
steps { sh 'cd app && npm test -- --ci --reporters=default' }
post { always { junit '**/junit*.xml' /* 如果用 jest-junit 可生成 */ } }
}
}
}
stage('Build Docker Image') {
steps {
script {
docker.withServer('unix:///var/run/docker.sock') {
withCredentials([usernamePassword(credentialsId: DOCKER_CREDS, usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh '''
echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin
docker build -t $IMAGE:$COMMIT -t $IMAGE:latest .
'''
}
}
}
}
}
stage('Security Scan (Trivy)') {
steps {
sh '''
mkdir -p "$TRIVY_CACHE_DIR"
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v "$TRIVY_CACHE_DIR":/root/.cache/ aquasec/trivy:0.54.1 image --exit-code 0 --severity HIGH,CRITICAL "$IMAGE:$COMMIT"
'''
}
}
stage('Push Image') {
steps {
withCredentials([usernamePassword(credentialsId: DOCKER_CREDS, usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh '''
echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin
docker push $IMAGE:$COMMIT
docker push $IMAGE:latest
'''
}
}
}
stage('Deploy to Staging') {
steps {
withCredentials([file(credentialsId: KUBECONFIG_CRED, variable: 'KUBECONFIG_FILE')]) {
sh '''
export KUBECONFIG="$KUBECONFIG_FILE"
helm upgrade --install demo-stg "$CHART_DIR" \
--namespace "$APP_NS_STG" --create-namespace \
--set image.repository=$IMAGE --set image.tag=$COMMIT
kubectl -n "$APP_NS_STG" rollout status deploy/demo
kubectl -n "$APP_NS_STG" get svc,po -o wide
'''
}
}
}
stage('Manual Approval for Prod') {
steps {
timeout(time: 30, unit: 'MINUTES') {
input message: "是否发布到生产?镜像: $IMAGE:$COMMIT", ok: '发布'
}
}
}
stage('Deploy to Prod') {
steps {
withCredentials([file(credentialsId: KUBECONFIG_CRED, variable: 'KUBECONFIG_FILE')]) {
sh '''
export KUBECONFIG="$KUBECONFIG_FILE"
helm upgrade --install demo-prod "$CHART_DIR" \
--namespace "$APP_NS_PROD" --create-namespace \
--set image.repository=$IMAGE --set image.tag=$COMMIT \
--set replicaCount=3
kubectl -n "$APP_NS_PROD" rollout status deploy/demo
'''
}
}
}
}
post {
success {
echo " 成功发布:$IMAGE:$COMMIT"
// 可接入 Slack/企业微信
// slackSend channel: '#devops', color: 'good', message: "Build ${env.BUILD_URL} success: $IMAGE:$COMMIT"
}
failure {
echo " 失败,触发回滚提示"
}
}
}
如果你的 Jenkins 控制节点无法使用 Docker,可在 agent 节点安装 Docker 并用 agent { label 'docker' } 指定标签。
4. 创建 Jenkins 任务并联动 Git
4.1 创建 Multibranch Pipeline(推荐)
- New Item → Multibranch Pipeline
- Branch Sources 选择你的 Git(GitHub/GitLab),配置凭据
- Build Configuration:按仓库根目录的 Jenkinsfile
- 开启 Webhook(GitHub/GitLab):Push/PR 自动触发
4.2 单分支 Pipeline(简化)
- New Item → Pipeline
- Pipeline script from SCM,指向仓库和分支
5. 运行与验证
- 首次运行将拉依赖、构建镜像、Trivy 扫描、推送、Helm 部署到 staging。
- 打开 kubectl get svc -n staging 查看服务 IP/Ingress;访问 / 应返回 “Hello CI/CD v1”。
- 在 Jenkins 审批步骤点“发布”,流水线继续将生产环境 prod 升级。
- 灰度/回滚:使用 Helm 历史与回滚命令:
# 查看历史
helm history demo-prod -n prod
# 回滚到上一个版本
helm rollback demo-prod 1 -n prod
6. 常见扩展与优化
- 并行化:更多语言的测试矩阵(Node 18/20、不同 OS)
- 缓存:Docker BuildKit + 多阶段 + npm ci 缓存
- 质量门禁:接入 SonarQube(withSonarQubeEnv)
- 镜像瘦身:使用 distroless/alpine,锁定 CVE
- 蓝绿/金丝雀:在 Helm 值里加 label/selector,或用 Istio/NGINX Ingress 分权重
- 自动回滚:结合 Prometheus 监控、错误率门限 + Jenkins 失败后脚本
- 密钥管理:K8s Secret/External Secrets/HashiCorp Vault
7. 一键初始化脚本(可选)
scripts/bootstrap.sh(在 Jenkins 容器里执行,装 Node/Trivy CLI 等)
#!/usr/bin/env bash
set -euo pipefail
# Node & npm 缓存目录(可选)
mkdir -p /var/jenkins_home/.npm
npm config set cache /var/jenkins_home/.npm --global || true
# 安装 trivy CLI 到宿主/容器(也可直接用 docker 运行 trivy)
which trivy || {
echo "Use docker run aquasec/trivy ... in Jenkinsfile"
}
echo "Bootstrap done."
8. 你可以直接用的命令清单(运维侧)
# 手动部署到 staging(本地验证)
helm upgrade --install demo-stg helm/demo -n staging --create-namespace \
--set image.repository=your-dockerhub-username/demo-ci-cd --set image.tag=latest
# 查看部署状态
kubectl -n staging get deploy,po,svc
# 查看 Pod 日志
kubectl -n staging logs -f deploy/demo
# 检查服务可达性
kubectl -n staging port-forward svc/demo 8080:80
curl http://localhost:8080/
结语
你现在拿到了一整套可运行的 Jenkins CI/CD 工程模板:
- Jenkins 容器化部署
- 完整 Jenkinsfile 流程(测试/扫描/打包/推送/Helm 部署/审批/发布)
- 示例应用与 Helm Chart
相关推荐
- winxp安装盘(winxp系统安装)
-
xp系统安装步骤如下1、将下载的xp系统iso压缩包文件下载到C盘之外的分区,比如下载到D盘,右键使用WinRAR等工具解压到当前文件夹或指定文件夹,不能解压到C盘和桌面,否则无法安装;?2、解压之后...
- 现在的win11稳定了吗(win11稳定嘛)
-
windows10更稳定,由于win11刚刚推出没多久,稳定差不够好,兼容性也有待提升,无论是应用还是游戏都会遇到不明程度的问题,因此,在日常的使用过程中,我们还是应当以稳定性为优先,选择win10是...
- xp安装包下载到手机(xp系统安装包)
-
手机是基于ARM架构的处理器,而WindowsXP是基于x86架构的操作系统,因此无法直接在手机上安装WindowsXP。除非您的手机是使用Intel处理器,但这种情况非常罕见。如果您需要在手机上...
- 如何查看硬盘序列号(windows如何查看硬盘序列号)
-
1.打开开始菜单栏,输入【cmd】点击【确定】;2.在命令窗口依次输入【diskpart】-【listdisk】-【selectdisk0】;3.选好要查看的硬盘后,接着输入【detaildi...
- 虚拟机安装win7教程(虚拟机安装win7教程图解)
-
1.首先,下载并安装虚拟机软件,如VMwareWorkstation、VirtualBox等。2.打开虚拟机软件,创建一个新的虚拟机。3.在创建虚拟机的过程中,选择安装Windows7专业版的IS...
- 系统脱敏法的操作程序如何
-
系统脱敏疗法(systematicdesensitization)又称交互抑制法,是由美国学者沃尔普创立和发展的。这种方法主要是诱导求治者缓慢地暴露出导致神经症焦虑、恐惧的情境,并通过心理的放松状态...
- 闪迪u盘低级格式化工具(闪迪u盘格式化分配单元大小)
-
闪迪U盘格式化后速度变慢的可能原因及解决方法如下:文件系统问题:格式化时选择的文件系统类型可能会影响U盘的性能。常见的文件系统类型包括FAT32、NTFS和exFAT等。如果文件系统类型不合适,可能会...
- psd文件下载(psd格式下载网站)
-
1、在photoshop中,不能通过置入的方法来加载PSD文件,因为,通过置入的方法加载PSD文件,它是以合并图层的方法把PSD文件加入,这样,就失去了PSD文件的所有图层信息。 2、在文档中想...
- 宏碁官网下载win7系统(宏碁官方系统)
-
宏基笔记本win8系统换成win7步骤:1、更改bios设置,关闭“SecureBoot”功能,启用传统的“LegacyBoot”。2、制作u启动U盘启动盘,下载win7系统安装包3、设置U盘启动...
- 如何重装系统win7旗舰版32位
-
首先下载制作一个带系统的启动u盘,然后按以下步骤安装:1、首先关闭电脑上面的杀毒软件,2、进入bios选择u盘启动。3、插入启动u盘重新启动电脑4、进入pe系统镜像环节,选择要安装的系统(32位),然...
- 应用程序发生异常0xe0000008
-
先查看一下对应的软件是不是出现了损坏,也可以重装此软件。我们还可以尝试通过修改注册表来解决。按Win+R(或者在开始菜单搜索框输入“运行”)打开运行,然后输入“regedit”回车,打开注册表恢复原来...
- 笔记本连接wifi显示无法连接网络
-
笔记本电脑连接wifi时提示无法连接到这个网络1、打开电脑“控制面板”,点击“网络连接”,选择本地连接,右键点击本地连接图标后选“属性”,在“常规”选项卡中双击“Internet协议(TCP/IP)...
- windowsc盘清理大师(c盘清理大师怎么样)
-
C盘清理大师是一款流氓软件,可不是windows10里自带。在你的电脑上出现这个软件一般情况下可以证明你使用的系统是盗版的,系统采用的是网上流传的系统镜像制作的。在网上流传这些系统镜像文件...
- realtek没声音如何设置(realtek怎么调出来)
-
你给无线连接配IP地址呗第一步:下载驱动精灵软件。第二步:安装驱动精灵软件。1、在打开的驱动软件安装窗口,确定程序安装路径后,点击:一键安装;2、正在安装。第三步:更新驱动程序。1、安装非常迅速,已经...
- 腾达路由器手机端登录入口(腾达路由器手机端登录入口在哪)
-
腾达路由器使用192.168.0.1或tendawifi.com作为登录地址。登录管理员页面的步骤:1、手机连接到腾达路由器的wifi信号;2、在手机上打开浏览器,在地址栏输入192.168.0.1后...
欢迎 你 发表评论:
- 一周热门
-
-
抖音上好看的小姐姐,Python给你都下载了
-
全网最简单易懂!495页Python漫画教程,高清PDF版免费下载
-
Python 3.14 的 UUIDv6/v7/v8 上新,别再用 uuid4 () 啦!
-
飞牛NAS部署TVGate Docker项目,实现内网一键转发、代理、jx
-
python入门到脱坑 输入与输出—str()函数
-
宝塔面板如何添加免费waf防火墙?(宝塔面板开启https)
-
Python三目运算基础与进阶_python三目运算符判断三个变量
-
(新版)Python 分布式爬虫与 JS 逆向进阶实战吾爱分享
-
失业程序员复习python笔记——条件与循环
-
系统u盘安装(win11系统u盘安装)
-
- 最近发表
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)
