整合任务
可以将Build和Deploy整合成一个Pipeline任务;同时,若不需要外部调用来触发任务,可以将其中大部分参数固化,一个项目一个任务,以方便用户直接运行。
前端发布
前端采用Node技术开发,故需要在Jenkins上安装Node环境,以进行构建并生成最终的HTML文件;将其添加至 nginx:stable 作为基础镜像来生成项目镜像。
配置Node环境(220.140)
安装:
wget https://nodejs.org/dist/v14.15.4/node-v14.15.4-linux-x64.tar.xz tar Jxf node-v14.15.4-linux-x64.tar.xz -C /usr/local/ cd /usr/local/ mv node-v14.15.4-linux-x64 nodejs echo 'PATH=$PATH:/usr/local/nodejs/bin' > /etc/profile.d/nodejs.sh source /etc/profile.d/nodejs.sh node -v
安装插件:
npm --registry https://registry.npm.taobao.org install --ensure node-gyp -g npm --registry https://registry.npm.taobao.org install --unsafe-perm node-sass -g
基础镜像
这里就使用最新的nginx:stable作为源(内部Nginx版本为 18.0),来制作基础镜像:
docker pull nginx:stable docker history nginx:stable
mkdir ~/baseimg-nginx cd ~/baseimg-nginx/ vim nginx.conf
# Nginx Main Configure File. user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; 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; keepalive_timeout 65; gzip on; server { listen 8080; location /seid { add_header "Set-Cookie" "eid=$arg_eid"; return 200 "success"; } location / { root /data/www/; index index.html index.htm; try_files $uri $uri/ /index.html; } } }
vim Dockerfile
FROM nginx:stable as build RUN mkdir -p /data/www && \ sed -i 's/^# alias/alias/g' ~/.bashrc && \ sed -i 's/^# export/export/g' ~/.bashrc && \ sed -i "/^alias mv/a\alias ll='ls -lh --time-style=\"+%Y/%m/%d %H:%M\" --color'" ~/.bashrc COPY nginx.conf /etc/nginx/nginx.conf FROM scratch COPY --from=build / . LABEL maintainer="Chris" ENTRYPOINT ["/docker-entrypoint.sh"] EXPOSE 8080 CMD ["nginx","-g","daemon off;"] WORKDIR /data
docker build -t baseimg-nginx:1.18 . docker images
试运行:
docker run --name=nginx --rm -p 8080:8080 -d baseimg-nginx:1.18 dokcer ps
由于现在网站目录为空,访问会出现403;进入Container,生成个页面后刷新:
docker exec -it nginx /bin/bash echo "Hello, I'm run in docker." > /data/www/index.html
杀掉容器, 标签镜像:
docker kill nginx docker tag baseimg-nginx:1.18 nw-harbor.xxx.cc/library/baseimg-nginx:1.18 docker images
上传镜像:
docker push nw-harbor.xxx.cc/library/baseimg-nginx:1.18
Tips: Nginx官方的Docker容器中已将日志输出结果重定向到stdout和stderr,故不需再操作
创建任务
创建个Pipeline任务:
node{ // 用于前端项目(Nginx静态文件), 固化项目配置参数, 每项目一任务, 方便直接执行; 不建议外部调用 // 镜像仓库 String HarborUrl='nw-harbor.zongs365.cc' String HarborUser='admin' String HarborPasswd='Nw-Harbor123' // 构建用: 项目Gitlab地址 String GIT_URL='http://git.zongsheng.tech/all/demo-front.git' // Node NPM 构建用: 配置构建的环境 npm run build: String Build_Env='prod' // Docker镜像 推送,拉取用: 项目组名称,推送至此Harbor项目组 (e.g: ${HarborUrl}/${Group}) String Group='zongs-nw' // Docker镜像 构建用,部署配置用,Docker镜像 推送,拉取用: 填写项目名称 String Project_name='demo-front' // Docker镜像 构建用: 生成的前端包路径名称 String Target_name='dist.tar.gz' // Docker镜像 构建用: 生成项目Docker镜像的时区 String TIME_ZONE='Asia/Shanghai' // 项目tag(镜像构建的版本; 这里携带Jenkins任务构建版本) String Project_Tag="ng-${env.BUILD_NUMBER}" // 部署配置用: 部署至此命名空间 String NAMESPACE='default' // 部署配置用: 运行的 Pod 数目 String REPLICAS_NUM='1' // 部署配置用: 资源CPU需求 String REQUESTS_CPU='100m' // 部署配置用: 资源Memory需求 String REQUESTS_MEM='128Mi' // 部署配置用: 新创建的Pod状态为Ready持续了此时间后认为Available String MINREADSECONDS='5' // K8S YAML 模板文件所在服务器 (部署,服务模板文件) String TemplateUrl='http://172.16.220.105/k8s' // K8S Deployment YAML 模板文件名称 String Temp_Depoly='deployment-front.yaml' // K8S Service YAML 模板文件名称 String Temp_SVC='svc.yaml' // K8S 群集 操作主机 (用于SSH连接后执行 kubectl 命令) String KubernetHost='[email protected]' // 远端 操作主机 存放依模板生成的 YAML 文件 String Yaml_Path='/root/k8s_deploy' // 用于SSH连接到 操作主机 的密钥文件 (若Jenkins运行用户可直接连接,则可留空) String SSH_KeyFile='/data/jenkins/.ssh/id_rsa' properties([ parameters([ string(name: 'GIT_BRANCH', defaultValue: 'master', description: '构建用: 填写Git分支地址', trim: false), string(name: 'IS_SVC', defaultValue: 'No', description: '部署配置用: 是否创建服务(为 Yes 时部署svc.yaml)', trim: false), string(name: 'SVC_PORT', defaultValue: '80', description: '部署配置用: 服务本身监听端口', trim: false), string(name: 'TARGETPORT', defaultValue: '8080', description: '部署配置用: 服务后端项目端口(即项目本身所监听的端口)', trim: false) ]) ]) if(!Project_name){ error "项目名为空" } if(SSH_KeyFile){ if(!fileExists("${SSH_KeyFile}")){ error "连接密钥文件 ${SSH_KeyFile} 不存在" } SSH_Command="ssh -i ${SSH_KeyFile} -p 22 -o StrictHostKeyChecking=no" SCP_Command="scp -i ${SSH_KeyFile} -P 22 -o StrictHostKeyChecking=no" }else{ SSH_Command="ssh -p 22 -o StrictHostKeyChecking=no" SCP_Command="scp -P 22 -o StrictHostKeyChecking=no" } dir("${env.WORKSPACE}"){ stage('Git阶段'){ echo "1. 开始拉取代码 (${GIT_URL})" git branch: params.GIT_BRANCH, credentialsId: 'nw-gitlab', url: "${GIT_URL}" } stage('Nodejs阶段'){ echo "2. 开始 Node npm 编译" if(fileExists("${Target_name}")){ sh "rm -f ${Target_name}" } sh "npm --registry https://registry.npm.taobao.org install >/dev/null" sh "npm run build:${Build_Env}" sh "cd ./dist && tar -czf ../${Target_name} ./*" sh 'cd ../' } stage('Docker阶段'){ echo "3. 开始执行Docker编译、推送、删除" echo "--生成 Dockerfile" sh "echo 'FROM ${HarborUrl}/library/baseimg-nginx:1.18' > Dockerfile" sh "echo 'RUN ln -sf /usr/share/zoneinfo/${TIME_ZONE} /etc/localtime' >> Dockerfile" sh "echo 'ADD ${Target_name} /data/www/' >> Dockerfile" echo "--构建并推送 Docker 镜像 (${HarborUrl}/${Group}/${Project_name}:${Project_Tag})" step([$class: 'DockerBuilderPublisher', dockerFileDirectory: "${env.WORKSPACE}", cloud: 'docker_w1', tagsString: "${HarborUrl}/${Group}/${Project_name}:${Project_Tag}", cleanImages: true, cleanupWithJenkinsJobDelete: false, pushCredentialsId: 'nw-harbor', pushOnSuccess: true]) } stage('群集主机SSH验证'){ echo "验证k8s群集内主机 ${KubernetHost} 是否可连通,并确保YAML文件存放目录 ${Yaml_Path}" sh "${SSH_Command} ${KubernetHost} mkdir -p ${Yaml_Path}" } stage('K8S-Deployment 配置'){ echo "从 ${TemplateUrl} 拉取 K8S-Deployment 模板文件" sh "wget ${TemplateUrl}/${Temp_Depoly} -O ${Project_name}-deploy.yaml" echo "按参数配置修改模板文件" sh "sed -i 's#CI_PROJECT_NAME#${Project_name}#g' ${Project_name}-deploy.yaml" sh "sed -i 's#NAMESPACE#${NAMESPACE}#g' ${Project_name}-deploy.yaml" sh "sed -i 's#REPLICAS_NUM#${REPLICAS_NUM}#g' ${Project_name}-deploy.yaml" sh "sed -i 's#REPOSITORY_BASE#${HarborUrl}/${Group}#g' ${Project_name}-deploy.yaml" sh "sed -i 's#BUILD_IMAGE_VERSION#${Project_Tag}#g' ${Project_name}-deploy.yaml" sh "sed -i 's#REQUESTS_CPU#${REQUESTS_CPU}#g' ${Project_name}-deploy.yaml" sh "sed -i 's#REQUESTS_MEM#${REQUESTS_MEM}#g' ${Project_name}-deploy.yaml" sh "sed -i 's#MINREADSECONDS#${MINREADSECONDS}#g' ${Project_name}-deploy.yaml" echo "将修改的文件 ${Project_name}-deploy.yaml 传送至 k8s 群集内部机器 ${KubernetHost}" sh "${SCP_Command} ${Project_name}-deploy.yaml ${KubernetHost}:${Yaml_Path}" } if(IS_SVC == 'Yes'){ stage('K8S-SVC 配置(可选项)'){ echo "从 ${TemplateUrl} 拉取 K8S-SVC 模板文件" sh "wget ${TemplateUrl}/${Temp_SVC} -O ${Project_name}-svc.yaml" echo "按参数配置修改模板文件" sh "sed -i 's#CI_PROJECT_NAME#${Project_name}#g' ${Project_name}-svc.yaml" sh "sed -i 's#NAMESPACE#${NAMESPACE}#g' ${Project_name}-svc.yaml" sh "sed -i 's#SVC_PORT#${SVC_PORT}#g' ${Project_name}-svc.yaml" sh "sed -i 's#TARGETPORT#${TARGETPORT}#g' ${Project_name}-svc.yaml" echo "将修改的文件 ${Project_name}-svc.yaml 传送至 k8s 群集内部机器 ${KubernetHost}" sh "${SCP_Command} ${Project_name}-svc.yaml ${KubernetHost}:${Yaml_Path}" } } stage('部署至K8S'){ echo "部署应用 ${Project_name}-deploy.yaml" sh "${SSH_Command} ${KubernetHost} kubectl apply -f ${Yaml_Path}/${Project_name}-deploy.yaml" if(IS_SVC == 'Yes'){ echo "部署服务 ${Project_name}-svc.yaml" sh "${SSH_Command} ${KubernetHost} kubectl apply -f ${Yaml_Path}/${Project_name}-svc.yaml" } } } }
同样的,第一次执行会失败,主要是因为IS_SVC参数需要提供,前面构建均正常:
再次执行,初次部署,将IS_SVC设为Yes,以创建服务:
执行成功:
K8S上验证:
kubectl get deployments.apps kubectl get pods kubectl get svc