用 Jenkins 实现自动化 CI/CD_jenkins api自动执行
off999 2025-10-14 03:37 35 浏览 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
相关推荐
- 安全教育登录入口平台(安全教育登录入口平台官网)
-
122交通安全教育怎么登录:122交通网的注册方法是首先登录网址http://www.122.cn/,接着打开网页后,点击右上角的“个人登录”;其次进入邮箱注册,然后进入到注册页面,输入相关信息即可完...
- 大鱼吃小鱼经典版(大鱼吃小鱼经典版(经典版)官方版)
-
大鱼吃小鱼小鱼吃虾是于谦跟郭麒麟的《我的棒儿呢?》郭德纲说于思洋郭麒麟作诗的相声,最后郭麒麟做了一首,师傅躺在师母身上大鱼吃小鱼小鱼吃虾虾吃水水落石出师傅压师娘师娘压床床压地地动山摇。...
-
- 哪个软件可以免费pdf转ppt(免费的pdf转ppt软件哪个好)
-
要想将ppt免费转换为pdf的话,我们建议大家可以下一个那个wps,如果你是会员的话,可以注册为会员,这样的话,在wps里面的话,就可以免费将ppt呢转换为pdfpdf之后呢,我们就可以直接使用,不需要去直接不需要去另外保存,为什么格式转...
-
2026-02-04 09:03 off999
- 电信宽带测速官网入口(电信宽带测速官网入口app)
-
这个网站看看http://www.swok.cn/pcindex.jsp1.登录中国电信网上营业厅,宽带光纤,贴心服务,宽带测速2.下载第三方软件,如360等。进行在线测速进行宽带测速时,尽...
- 植物大战僵尸95版手机下载(植物大战僵尸95 版下载)
-
1可以在应用商店或者游戏平台上下载植物大战僵尸95版手机游戏。2下载教程:打开应用商店或者游戏平台,搜索“植物大战僵尸95版”,找到游戏后点击下载按钮,等待下载完成即可安装并开始游戏。3注意:确...
- 免费下载ppt成品的网站(ppt成品免费下载的网站有哪些)
-
1、Chuangkit(chuangkit.com)直达地址:chuangkit.com2、Woodo幻灯片(woodo.cn)直达链接:woodo.cn3、OfficePlus(officeplu...
- 2025世界杯赛程表(2025世界杯在哪个国家)
-
2022年卡塔尔世界杯赛程公布,全部比赛在卡塔尔境内8座球场举行,2022年,决赛阶段球队全部确定。揭幕战于当地时间11月20日19时进行,由东道主卡塔尔对阵厄瓜多尔,决赛于当地时间12月18日...
- 下载搜狐视频电视剧(搜狐电视剧下载安装)
-
搜狐视频APP下载好的视频想要导出到手机相册里方法如下1、打开手机搜狐视频软件,进入搜狐视频后我们点击右上角的“查找”,找到自已喜欢的视频。2、在“浏览器页面搜索”窗口中,输入要下载的视频的名称,然后...
- 永久免费听歌网站(丫丫音乐网)
-
可以到《我爱音乐网》《好听音乐网》《一听音乐网》《YYMP3音乐网》还可以到《九天音乐网》永久免费听歌软件有酷狗音乐和天猫精灵,以前要跳舞经常要下载舞曲,我从QQ上找不到舞曲下载就从酷狗音乐上找,大多...
- 音乐格式转换mp3软件(音乐格式转换器免费版)
-
有两种方法:方法一在手机上操作:1、进入手机中的文件管理。2、在其中选择“音乐”,将显示出手机中的全部音乐。3、点击“全选”,选中所有音乐文件。4、点击屏幕右下方的省略号图标,在弹出菜单中选择“...
- 电子书txt下载(免费的最全的小说阅读器)
-
1.Z-library里面收录了近千万本电子书籍,需求量大。2.苦瓜书盘没有广告,不需要账号注册,使用起来非常简单,直接搜索预览下载即可。3.鸠摩搜书整体风格简洁清晰,书籍资源丰富。4.亚马逊图书书籍...
- 最好免费观看高清电影(播放免费的最好看的电影)
-
在目前的网上选择中,IMDb(互联网电影数据库)被认为是最全的电影网站之一。这个网站提供了各种类型的电影和电视节目的海量信息,包括剧情介绍、演员表、评价、评论等。其还提供了有关电影制作背后的详细信息,...
- 孤单枪手2简体中文版(孤单枪手2简体中文版官方下载)
-
要将《孤胆枪手2》游戏的征兵秘籍切换为中文,您可以按照以下步骤进行操作:首先,打开游戏设置选项,通常可以在游戏主菜单或游戏内部找到。然后,寻找语言选项或界面选项,点击进入。在语言选项中,选择中文作为游...
欢迎 你 发表评论:
- 一周热门
- 最近发表
- 标签列表
-
- 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)
