当前位置: 首页 > news >正文

CI/CD实践(五)Jenkins Docker 自动化构建部署Node服务

微服务CI/CD实践系列:
 
微服务CI/CD实践(一)环境准备及虚拟机创建
微服务CI/CD实践(二)服务器先决准备
微服务CI/CD实践(三)gitlab部署及nexus3部署
微服务CI/CD实践(四)Jenkins部署及环境配置
微服务CI/CD实践(五)Jenkins + Dokcer 部署微服务后端项目
微服务CI/CD实践(六)Jenkins + Dokcer 部署微服务前端VUE项目
微服务CI/CD实践(七)Minio服务器部署及应用

文章目录

  • 一、先决条件
    • 1.1 服务器先决条件
    • 1.2 项目配置
      • Dockerfile
      • Nginx配置文件
      • 部署脚本
  • 二、Jenkins构建部署
    • 2.1 创建项目
    • 2.2 配置项目基本信息
    • 2.3 定义 Pipeline script
    • 2.4 构建部署项目

前端项目是基于NodeJS(Vue)框架开发,我们通过打包成Docker镜像的方式进行部署,原理是先将项目打包成静态页面,然后再将静态页面直接copy到Nginx镜像中运行。构建部署流程如下:

  • 拉取代码
  • jenkins服务器进行nodejs编译
  • 使用dockerfile构建镜像并打包镜像
  • 上传镜像包
  • 执行sh

一、先决条件

1.1 服务器先决条件

Jenkins 和 server服务器先决条件参考微服务CI/CD实践(二)服务器先决准备 和 微服务CI/CD实践(四)Jenkins部署及环境配置

1.2 项目配置

Dockerfile

使用Jenkins本地编译项目在构建镜像

FROM nginx:latest
# 将生成的静态页面文件复制到nginx的/usr/share/nginx/html/目录
COPY dist/ /usr/share/nginx/html/
# 将mime文件复制到nginx的/etc/nginx/目录 后续配置ng会使用
COPY mime.types /etc/nginx/mime.types
# 容器启动时运行的命令
CMD ["nginx", "-g", "daemon off;"]

也可以直接使用docker 镜像编译-构建镜像,此模式jenkins服务器可以不需要node环境

# Install dependencies
FROM node:18.20.4 as builder
WORKDIR /app
# Install pnpm
RUN npm i -g pnpm
# copy file for next stage
COPY . /app
RUN pnpm install && pnpm run build
# copy dist from the first stage for Production
FROM nginx:latest AS runner
COPY --from=builder /app/dist/ /usr/share/nginx/html
COPY --from=builder /app/nginx.conf /etc/nginx/conf.d/default.conf

不过此模式在docker-hub停止国内服务后可能无法正常拉取镜像。

Nginx配置文件

根据项目要求编写ng配置

cd /data/container/nginx/etc
vi nginx.conf
# 编写配置并保存vi mime.types
# 编写配置并保存

以下为nginx.conf配置示例


events {worker_connections 1024;
}http {# 需要引入mime.types配置或者显示配置静态文件mimetype类型,否则运行后,浏览器会因为文件类型导致无法正常加载静态文件include       mime.types;log_format  main  '$remote_addr - $remote_user [$time_local] "$request" ''$status $body_bytes_sent "$http_referer" ''"$http_user_agent" "$http_x_forwarded_for"';access_log  /var/log/nginx/access.log  main;sendfile        on;tcp_nopush      on;tcp_nodelay     on;keepalive_timeout  65;types_hash_max_size 2048;client_max_body_size 100m;server {listen       80;listen  [::]:80;server_name  localhost;# 设置 CORS 相关的响应头add_header 'Access-Control-Allow-Origin' '*' always;add_header 'Access-Control-Allow-Methods' '*' always;add_header 'Access-Control-Max-Age' 1728000 always;add_header 'Access-Control-Allow-Headers' '*' always;add_header 'Access-Control-Allow-Credentials' 'true' always;gzip on;gzip_buffers 32 4k;gzip_comp_level 6;gzip_min_length 100;gzip_types application/javascript text/css text/xml text/plain application/x-javascript image/jpeg image/gif image/png;gzip_disable "MSIE [1-6]\.";gzip_vary on;charset utf8;location / {root   /usr/share/nginx/html;index  index.html index.htm;try_files $uri $uri/ /index.html;if (!-e $request_filename) {rewrite ^/(.*) /index.html last;break;}}location  ~ .*\.(jpg|png|js|css|woff2|ttf|woff|eot)$ {root   /usr/share/nginx/html;}#error_page  404              /404.html;# redirect server error pages to the static page /50x.html#error_page   500 502 503 504  /50x.html;location = /50x.html {root   /usr/share/nginx/html;}# 配置全局代理并统一处理CORS location /gateway-api/ {proxy_set_header Host $http_host;               proxy_set_header X-Real-Ip $remote_addr;proxy_set_header REMOTE-HOST $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_pass http://192.168.1.103:10000/;# 添加 CORS 相关的响应头add_header 'Access-Control-Allow-Origin' '*' always;add_header 'Access-Control-Allow-Methods' '*' always;add_header 'Access-Control-Max-Age' 1728000 always;add_header 'Access-Control-Allow-Headers' '*' always;add_header 'Access-Control-Allow-Credentials' 'true' always;# 处理 OPTIONS 请求if ($request_method = 'OPTIONS') {return 204;}}}
}

以下为mime.type示例

types {text/html                 html htm shtml;text/css                  css;image/gif                gif;image/jpeg               jpeg jpg;application/javascript    js;application/xml          xml;application/json         json;application/pdf          pdf;application/rss+xml      rss;application/atom+xml     atom;text/mathml              mml;text/plain               txt;text/vnd.sun.j2me.app-descriptor jad;text/vnd.wap.wml         wml;text/x-component         htc;image/png               png;image/svg+xml           svg;image/tiff              tif tiff;image/vnd.wap.wbmp      wbmp;image/x-icon            ico;image/x-jng             jng;image/x-ms-bmp          bmp;application/zip          zip;application/tar          tar;application/x-7z-compressed 7z;application/x-java-archive jar;application/x-rar-compressed rar;application/x-web-app-manifest+json webapp;application/xhtml+xml   xhtml;application/x-msdownload  exe dll;audio/midi              mid midi kar;audio/mpeg             mp3;video/mp4              mp4;video/mpeg             mpeg mpg;video/webm             webm;video/x-msvideo         avi;video/x-ms-wmv         wmv;video/x-ms-asf         asx asf;video/x-flv            flv;application/x-shockwave-flash swf;application/vnd.ms-excel  xls;application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet  xlsx;application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;application/vnd.ms-fontobject eot;application/vnd.apple.mpegurl m3u8;application/x-font-ttf  ttc ttf;application/x-httpd-php-source phps;
}

部署脚本

step1 定义入参
可以通过Jenkins任务将参数传入脚本中,我们定义了下面7个参数:
container_name : 容器名称
image_name : 镜像名称
version : 镜像版本
portal_port: 宿主主机端口映射
server_port: 容器内服务端口
portal_ssl_port: 宿主主机端口映射
serve_sslr_port: 容器内服务端口

step2 定义入参对参数进行检查
将必传参数放在最前面,这里根据自己的实际情况判断,检查是否传递参数。比如设置container_name、image_name、version 、portal_port、server_port5个参数必须传入,就设置参数的个数不能小于5。

echo "param validate"
if [ $# -lt 5 ]; then  echo "you must use like this : /usr/docker-sh/your_script.sh <container_name> <image_name> <version> [portal port] [server port] [portal ssl port] [server ssl port]"  exit  
fi

step3 入参赋值
如果有参数传入,则赋值参数

# 前五个参数是必传参数,无需判断直接赋值
container_name="$1"
image_name="$2"
version="$3"
portal_port="$4"
server_port="$5"
if [ "$6" != "" ]; thenportal_ssl_port="$6"
fi
echo "portal_ssl_port=" $portal_ssl_port
if [ "$7" != "" ]; thenserve_sslr_port="$7"
fi

step4 停止并删除容器

echo "执行docker ps"
docker ps 
if [[ "$(docker inspect $container_name 2> /dev/null | grep $container_name)" != "" ]]; 
then echo $container_name "容器存在,停止并删除"echo "docker stop" $container_namedocker stop $container_nameecho "docker rm" $container_namedocker rm $container_name
else echo $container_name "容器不存在"
fi

step5 停止并删除镜像

# 删除镜像
echo "执行docker images"
docker images
if [[ "$(docker images -q $image_name 2> /dev/null)" != "" ]]; 
then echo $image_name '镜像存在,删除镜像'docker rmi $(docker images -q $image_name 2> /dev/null) --force
else echo $image_name '镜像不存在'
fi

step6 备份和加载安装包

#bak image
echo "bak image" $image_name
BAK_DIR=/opt/bak/docker/$image_name/`date +%Y%m%d`
mkdir -p "$BAK_DIR"
cp "/opt/tmp/$container_name.tar" "$BAK_DIR"/"$image_name"_`date +%H%M%S`.tarecho "docker load" $image_name
docker load --input /opt/tmp/$container_name.tar

step7 执行运行镜像命令

echo "docker run" $image_name
docker run -d -p $portal_port:$server_port --name=$container_name --network=my-network -e TZ="Asia/Shanghai" --restart=always -v /data/container/nginx/www:/var/www -v /data/container/nginx/logs:/var/log/nginx -v /data/container/nginx/etc:/etc/nginx -v /data/container/nginx/etc/nginx.conf:/etc/nginx/nginx.conf -v /data/container/nginx/etc/mime.types:/etc/nginx/mime.types -v /etc/localtime:/etc/localtime -v /usr/share/zoneinfo/Asia/Shanghai:/etc/timezone $image_name

step8 执行删除安装包命令

echo "remove tmp " $image_name
rm -rf /opt/tmp/$container_name.tar

以下为完整的安装部署脚本

#!/usr/bin/env bashecho "param validate"
if [ $# -lt 5 ]; then  echo "you must use like this : /usr/docker-sh/your_script.sh <container_name> <image_name> <version> [portal port] [server port] [portal ssl port] [server ssl port]"  exit  
ficontainer_name="$1"
image_name="$2"
version="$3"
portal_port="$4"
server_port="$5"
if [ "$6" != "" ]; thenportal_ssl_port="$6"
fi
echo "portal_ssl_port=" $portal_ssl_port
if [ "$7" != "" ]; thenserve_sslr_port="$7"
fiecho "执行docker ps"
docker ps 
if [[ "$(docker inspect $container_name 2> /dev/null | grep $container_name)" != "" ]]; 
then echo $container_name "容器存在,停止并删除"echo "docker stop" $container_namedocker stop $container_nameecho "docker rm" $container_namedocker rm $container_name
else echo $container_name "容器不存在"
fi
# 删除镜像
echo "执行docker images"
docker images
if [[ "$(docker images -q $image_name 2> /dev/null)" != "" ]]; 
then echo $image_name '镜像存在,删除镜像'docker rmi $(docker images -q $image_name 2> /dev/null) --force
else echo $image_name '镜像不存在'
fi#bak image
echo "bak image" $image_name
BAK_DIR=/opt/bak/docker/$image_name/`date +%Y%m%d`
mkdir -p "$BAK_DIR"
cp "/opt/tmp/$container_name.tar" "$BAK_DIR"/"$image_name"_`date +%H%M%S`.tarecho "docker load" $image_name
docker load --input /opt/tmp/$container_name.tarecho "docker run" $image_name
docker run -d -p $portal_port:$server_port --name=$container_name --network=my-network -e TZ="Asia/Shanghai" --restart=always -v /data/container/nginx/www:/var/www -v /data/container/nginx/logs:/var/log/nginx -v /data/container/nginx/etc:/etc/nginx -v /data/container/nginx/etc/nginx.conf:/etc/nginx/nginx.conf -v /data/container/nginx/etc/mime.types:/etc/nginx/mime.types -v /etc/localtime:/etc/localtime -v /usr/share/zoneinfo/Asia/Shanghai:/etc/timezone $image_nameecho "remove tmp " $image_name
rm -rf /opt/tmp/$container_name.tarecho "Docker Portal is starting,please try to access $container_name conslone url"

二、Jenkins构建部署

2.1 创建项目

新建一个流水线任务
在这里插入图片描述

2.2 配置项目基本信息

创建完成项目,点击项目进入项目页面,点击左侧菜单》配置,进行项目基本配置
step1 项目构建历史存储策略配置
在这里插入图片描述
根据项目实际情况配置存储策略
step2 配置参数化构建过程
Jenkins List Git Branches插件 构建选择指定git分支,点击添加参数选择List Git branchers选项进行Jenkins List Git Branches插件配置
在这里插入图片描述
Jenkins List Git Branches插件配置流程如下:

  • 配置name
  • 配置仓库并选择凭证
  • 选择Parameter Type
  • 配置Branch Filter

在这里插入图片描述

2.3 定义 Pipeline script

step1 配置全局变量

environment {   REPOSITORY="http://192.168.1.101:8929/hka/hka-admin-wocwin.git"projectdir="hka-web-01"projectname="hka-admin-wocwin"
}

step2 获取代码
检出选择指定git分支的代码

stages {stage('获取代码') {steps {echo "start fetch code from git:${REPOSITORY} ${branch}"deleteDir()checkout([$class: 'GitSCM',branches: [[name: '${branch}']],doGenerateSubmoduleConfigurations: false,extensions: [],userRemoteConfigs: [[credentialsId: '2',url: 'http://192.168.1.101:8929/hka/hka-admin-wocwin.git']]])}}

step3 编译项目

这里需要显示指定node的环境变量,否则执行编译命令会抛异常

stage('Build NodeJS Vue') {steps {echo "build nodejs code"nodejs('node') {sh 'export PATH="/usr/local/nodejs/bin:$PATH"'sh 'node -v'sh 'npm -v'sh 'pnpm -v'sh 'pnpm install'sh 'pnpm run prod'}echo "build nodejs success"}}

step4 删除历史容器和镜像
如何没有在jenkins服务器运行容器可以忽略Delete Old Docker Container步骤

stage('Delete Old Docker Container') {steps {echo "delete docker container"sh '''if [[ "$(docker inspect ${projectname} 2> /dev/null | grep ${projectname})" != "" ]]; then echo ${projectname} "容器存在,停止并删除"echo "docker stop" ${projectname}docker stop ${projectname}echo "docker rm" ${projectname}docker rm ${projectname}else echo ${projectname} "容器不存在"fi'''}}stage('Delete Old Docker Image') {steps {echo "delete docker image"sh '''if [[ "$(docker images -q ${projectname} 2> /dev/null)" != "" ]]; then echo ${projectname} \'镜像存在,删除镜像\'docker rmi $(docker images -q ${projectname} 2> /dev/null) --forceelse echo ${projectname} \'镜像不存在,创建镜像\'fi'''}}

step5 构建镜像

stage('Build Docker Image') {steps {echo "start docker build ${projectname} code"sh 'docker build -t ${projectname} .'echo "save docker images tar"sh 'docker save -o ${projectname}.tar ${projectname}'}}
stage('Delete New Docker Image') {steps {echo "delete docker image"sh '''if [[ "$(docker images -q ${projectname} 2> /dev/null)" != "" ]]; then echo ${projectname} \'镜像存在,删除镜像\'docker rmi $(docker images -q ${projectname} 2> /dev/null) --forceelse echo ${projectname} \'镜像不存在,创建镜像\'fi'''}}

step6 上传镜像包
这里的configName: ‘103’, 就是微服务CI/CD实践(四)Jenkins部署及环境配置### 2.2.4 全局系统配置 SSH Server配置
该流水线步骤会通过ssh将 镜像tar包上传到SSH Server配置的Remote Directory目录下

stage('Upload img tar') {steps {sshPublisher(publishers: [sshPublisherDesc(configName: '103',transfers: [sshTransfer(cleanRemote: false,excludes: '',makeEmptyDirs: false,noDefaultExcludes: false,patternSeparator: '[, ]+',remoteDirectory: '',remoteDirectorySDF: false,removePrefix: '',sourceFiles: '${projectname}.tar')],usePromotionTimestamp: false,useWorkspaceInPromotion: false,verbose: false)])}}

step7 执行安装部署脚本
这里的configName: ‘103’, 就是微服务CI/CD实践(四)Jenkins部署及环境配置### 2.2.4 全局系统配置 SSH Server配置
该步骤通过ssh远程执行sh安装部署脚本

stage('Execute Command sh') {steps {sshPublisher(publishers: [sshPublisherDesc(configName: '103',transfers: [sshTransfer(execCommand: '/usr/docker-sh/publish_hka-admin-wocwin.sh hka-admin-wocwin hka-admin-wocwin latest 80 80',execTimeout: 300000)],usePromotionTimestamp: false,useWorkspaceInPromotion: false,verbose: false)])}}

以下为完整流水线定义

pipeline {agent anyenvironment {   REPOSITORY="http://192.168.1.101:8929/hka/hka-admin-wocwin.git"projectdir="hka-web-01"projectname="hka-admin-wocwin"}stages {stage('获取代码') {steps {echo "start fetch code from git:${REPOSITORY} ${branch}"deleteDir()checkout([$class: 'GitSCM',branches: [[name: '${branch}']],doGenerateSubmoduleConfigurations: false,extensions: [],userRemoteConfigs: [[credentialsId: '2',url: 'http://192.168.1.101:8929/hka/hka-admin-wocwin.git']]])}}stage('Build NodeJS Vue') {steps {echo "build nodejs code"nodejs('node') {sh 'export PATH="/usr/local/nodejs/bin:$PATH"'sh 'node -v'sh 'npm -v'sh 'pnpm -v'sh 'pnpm install'sh 'pnpm run prod'}echo "build nodejs success"}}stage('Delete Old Docker Container') {steps {echo "delete docker container"sh '''if [[ "$(docker inspect ${projectname} 2> /dev/null | grep ${projectname})" != "" ]]; then echo ${projectname} "容器存在,停止并删除"echo "docker stop" ${projectname}docker stop ${projectname}echo "docker rm" ${projectname}docker rm ${projectname}else echo ${projectname} "容器不存在"fi'''}}stage('Delete Old Docker Image') {steps {echo "delete docker image"sh '''if [[ "$(docker images -q ${projectname} 2> /dev/null)" != "" ]]; then echo ${projectname} \'镜像存在,删除镜像\'docker rmi $(docker images -q ${projectname} 2> /dev/null) --forceelse echo ${projectname} \'镜像不存在,创建镜像\'fi'''}}stage('Build Docker Image') {steps {echo "start docker build ${projectname} code"sh 'docker build -t ${projectname} .'echo "save docker images tar"sh 'docker save -o ${projectname}.tar ${projectname}'}}stage('Delete New Docker Image') {steps {echo "delete docker image"sh '''if [[ "$(docker images -q ${projectname} 2> /dev/null)" != "" ]]; then echo ${projectname} \'镜像存在,删除镜像\'docker rmi $(docker images -q ${projectname} 2> /dev/null) --forceelse echo ${projectname} \'镜像不存在,创建镜像\'fi'''}}stage('Upload img tar') {steps {sshPublisher(publishers: [sshPublisherDesc(configName: '103',transfers: [sshTransfer(cleanRemote: false,excludes: '',makeEmptyDirs: false,noDefaultExcludes: false,patternSeparator: '[, ]+',remoteDirectory: '',remoteDirectorySDF: false,removePrefix: '',sourceFiles: 'hka-admin-wocwin.tar')],usePromotionTimestamp: false,useWorkspaceInPromotion: false,verbose: false)])}}stage('Execute Command sh') {steps {sshPublisher(publishers: [sshPublisherDesc(configName: '103',transfers: [sshTransfer(execCommand: '/usr/docker-sh/publish_hka-admin-wocwin.sh hka-admin-wocwin hka-admin-wocwin latest 80 80 4413 4413',execTimeout: 300000)],usePromotionTimestamp: false,useWorkspaceInPromotion: false,verbose: false)])}}stage('Publish Results') {steps {echo "End Publish ${projectname}"  }}}
}

2.4 构建部署项目

回到项目页面,点击参数化构建,选择用于构建的分支点击Build执行构建任务。
在这里插入图片描述

在这里插入图片描述

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • kafka的12个重要概念
  • Spatial Structure Constraints for Weakly SupervisedSemantic Segmentation
  • Python实现分水岭图像分割算法
  • 数据类型 NVARCHAR2 与 VARCHAR2 的对比
  • 2024年6月 青少年等级考试机器人实操真题二级
  • 20240830 每日AI必读资讯
  • 公网信息泄露监测(网盘、暗网、搜索引擎、文档平台)思路分享
  • 【推推P1】第一期“小说详情模块”:JAVA开发文档官方版;快来在线实习吧
  • 服务器远程管理
  • R 2火灾温度预测
  • 442一场“吃干榨净”的富贵,是怎么拼出来的?
  • 【Java基础面试题】Java的优势
  • C++:共享指针(shared_ptr)详解
  • 【RK3588】yolov5的部署
  • 小白初次Vue启动遇到问题汇总
  • 【vuex入门系列02】mutation接收单个参数和多个参数
  • 【翻译】babel对TC39装饰器草案的实现
  • Babel配置的不完全指南
  • ES6之路之模块详解
  • Invalidate和postInvalidate的区别
  • JWT究竟是什么呢?
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • session共享问题解决方案
  • Vue全家桶实现一个Web App
  • 从0实现一个tiny react(三)生命周期
  • 动手做个聊天室,前端工程师百无聊赖的人生
  • 工程优化暨babel升级小记
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 官方新出的 Kotlin 扩展库 KTX,到底帮你干了什么?
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 如何设计一个比特币钱包服务
  • 十年未变!安全,谁之责?(下)
  • 微信支付JSAPI,实测!终极方案
  •  一套莫尔斯电报听写、翻译系统
  • 移动端唤起键盘时取消position:fixed定位
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • 阿里云服务器购买完整流程
  • 长三角G60科创走廊智能驾驶产业联盟揭牌成立,近80家企业助力智能驾驶行业发展 ...
  • ​Benvista PhotoZoom Pro 9.0.4新功能介绍
  • ​Python 3 新特性:类型注解
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • #传输# #传输数据判断#
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • (2)STM32单片机上位机
  • (leetcode学习)236. 二叉树的最近公共祖先
  • (Ruby)Ubuntu12.04安装Rails环境
  • (二)构建dubbo分布式平台-平台功能导图
  • (简单) HDU 2612 Find a way,BFS。
  • (六)软件测试分工
  • (七)Java对象在Hibernate持久化层的状态
  • (三) diretfbrc详解
  • (太强大了) - Linux 性能监控、测试、优化工具
  • (详细文档!)javaswing图书管理系统+mysql数据库
  • (一)、软硬件全开源智能手表,与手机互联,标配多表盘,功能丰富(ZSWatch-Zephyr)
  • (自适应手机端)响应式新闻博客知识类pbootcms网站模板 自媒体运营博客网站源码下载