将使用jenkins+k8s发布前端项目
1 环境准备
node环境
在部署jenkins的服务器上搭建node环境
bash
# 1.拉取
https://nodejs.org/download/release/v20.4.0/node-v20.4.0-linux-x64.tar.gz
# 2.解压到/usr/local目录下
sudo tar xf v20.4.0.tar.gz -C /usr/local
# 重命名为node20
mv v20.4.0 node20
# 3.配置环境变量
vim /etc/profile
export PATH=$PATH:/usr/local/node20/bin
# 文件生效
source /etc/profile
# 4.软连接
sudo ln -s /usr/local/node20/bin/npm /usr/local/bin/
sudo ln -s /usr/local/node20/bin/node /usr/local/bin/
# 5.验证
node -v
npm -v
验证环境没有问题后,将文件移动到jenkins的目录下
因为是docker部署的jenkins,挂载目录是/usr/local/docker/docker-jenkins/data,所以必须将node环境移动到该目录下,jenkins才能加载到
bash
mv node20 /usr/local/docker/docker-jenkins/data
jenkins中下载nodejs插件
插件中搜索 nodejs
安装完成后重启,再次进入
在全局工具配置中,找到nodejs
安装目录为docker中jenkins的node目录,可进入docker jinkens容器中查看
2 项目构建
前提:在gitlab中创建一个vue项目,并确保该项目在本地能正常运行
我的项目结构如下,需要添加Dockerfile和jenkinsfile以及deploy目录
创建完成后编写jenkinsfile
Groovy
pipeline{
agent any
environment {
//gitlab访问凭证
GIT_CREDENTIAL_ID = 'gitlab-root'
//gitlab地址
GIT_REPO_URL = '10.1.9.23:28080'
//gitlab分组
GIT_GROUP = 'devops'
//gitlab项目名称
GIT_NAME = 'fitmentfront'
//harbor凭证
HARBOR_ID = 'harbor-admin'
//harbor地址
HARBOR_URL = '39.10.18.1:8858'
//harbor项目
HARBOR_REPO = 'repo'
//发送delpoment.yml到k8s服务器上的地址
K8S_FILE_PATH = '/opt/k8s/deployfile'
//gitlab发送到服务器的目录
GITLAB_DEPLOYMENT_FILE = 'deploy'
}
parameters {
//git插件 分支参数
gitParameter(
branchFilter: '.*',
defaultValue: "${env.BRANCH_NAME ?: 'main'}",
name: 'BRANCH_NAME',
type: 'PT_BRANCH',
description: '请选择要发布的分支'
)
//git插件 标签参数
gitParameter(
branchFilter: '.*',
defaultValue: "${env.TAG_NAME ?: 'v:1.0.0'}",
name: 'TAG_NAME',
type: 'PT_TAG',
description: '请选择要发布的标签'
)
}
stages{
stage("基本信息输出"){
steps{
echo '选定待发布信息'
echo "项目地址 ${GIT_REPO_URL}"
echo "项目组 ${GIT_GROUP}"
echo "项目名 ${GIT_NAME}"
echo "分支 ${BRANCH_NAME}"
echo "TAG ${TAG_NAME}"
}
}
stage('拉取gitlab代码') {
steps {
//拉取gitlab代码,选择分支
checkout scmGit(
branches: [
[name: env.BRANCH_NAME]
],
extensions: [],
userRemoteConfigs: [
[
credentialsId: env.GIT_CREDENTIAL_ID,
url: "http://${env.GIT_REPO_URL}/${env.GIT_GROUP}/${env.GIT_NAME}.git"
]
]
)
echo '拉取gitlab代码 --SUCCESS'
}
}
stage("编译"){
steps{
nodejs('node') {
// some block
sh '''
npm install --registry=https://registry.npmmirror.com
npm run build
'''
}
}
}
stage("构建镜像"){
steps {
//docker制作镜像
//将maven打包的jar移动到docker目录下
//使用dockerfile进行构建镜像,镜像名称为 项目名:标签
sh """
echo $PWD
docker build -t ${env.GIT_NAME}:${env.TAG_NAME} .
"""
echo '通过docker制作镜像 --SUCCESS'
}
}
stage('推送镜像到harbor') {
steps {
//使用harbor凭证推送镜像
withCredentials([
usernamePassword(
credentialsId: env.HARBOR_ID,
passwordVariable: 'DOCKER_PASSWORD',
usernameVariable: 'DOCKER_USERNAME'
)
]) {
//打标签为远程仓库标签
//登陆到harbor
//推送镜像
sh """
docker tag ${env.GIT_NAME}:${env.TAG_NAME} ${env.HARBOR_URL}/${env.HARBOR_REPO}/${env.GIT_NAME}:${env.TAG_NAME}
echo "\$DOCKER_PASSWORD" | docker login -u "\$DOCKER_USERNAME" -p "\$DOCKER_PASSWORD" ${env.HARBOR_URL}
docker push ${env.HARBOR_URL}/${env.HARBOR_REPO}/${env.GIT_NAME}:${env.TAG_NAME}
"""
}
echo '推送镜像到harbor --SUCCESS'
}
}
stage('发送k8s部署yml文件至目标服务器') {
steps {
//请空文件夹下所有文件内容
sh """
ssh root@10.199.99.200 rm -rf $K8S_FILE_PATH/*
"""
//使用ssh插件 发送deploy目录下的部署yml文件到目标服务器
//须提前配置ssh免密登陆
sshPublisher(
publishers: [
sshPublisherDesc(
configName: 'k8s',
transfers: [
sshTransfer(
cleanRemote: false,
excludes: '',
execCommand: '',
execTimeout: 120000,
flatten: false,
makeEmptyDirs: false,
noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectory: '',
remoteDirectorySDF: false,
removePrefix: '',
sourceFiles: "${env.GITLAB_DEPLOYMENT_FILE}/*yaml"
)
],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: false
)
]
)
echo '发送yml文件至目标服务器 --SUCCESS'
}
}
stage('远程执行k8s部署yaml命令') {
steps {
//替换发送过来的部署文件
//部署
sh """
ssh root@10.19.99.200 sed -i'' "s#REGISTRY#${env.HARBOR_URL}#" ${env.K8S_FILE_PATH}/${env.GITLAB_DEPLOYMENT_FILE}/deployment.yaml
ssh root@10.19.99.200 sed -i'' "s#DOCKERHUB_NAMESPACE#${env.HARBOR_REPO}#" ${env.K8S_FILE_PATH}/${env.GITLAB_DEPLOYMENT_FILE}/deployment.yaml
ssh root@10.19.99.200 sed -i'' "s#APP_NAME#${env.GIT_NAME}#" ${env.K8S_FILE_PATH}/${env.GITLAB_DEPLOYMENT_FILE}/deployment.yaml
ssh root@10.19.99.200 sed -i'' "s#BUILD_NUMBER#${env.TAG_NAME}#" /${env.K8S_FILE_PATH}/${env.GITLAB_DEPLOYMENT_FILE}/deployment.yaml
ssh root@10.19.99.200 kubectl apply -f ${env.K8S_FILE_PATH}/${env.GITLAB_DEPLOYMENT_FILE}/
"""
echo '远程执行k8s部署yaml命令 --SUCCESS'
}
}
}
}
Dockerfile
Groovy
FROM node:14-alpine AS build
WORKDIR /build/fitment
COPY . .
RUN npm install --registry=https://registry.npmmirror.com && npm run build
FROM nginx:1.22
WORKDIR /app/fitment
COPY --from=build /build/fitment/dist .
EXPOSE 80
deploy中的deployment.yaml
Groovy
apiVersion: v1
kind: Namespace
metadata:
name: fitment
---
apiVersion: v1
kind: ConfigMap
metadata:
name: fitment-conf
namespace: fitment
labels:
app: nginx-conf
data:
nginx.conf: |
server {
listen 80;
server_name localhost;
location / {
root /app/fitment;
index index.html index.htm;
}
location /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://fitmentback.fitment/; #设置监控后端启动的端口
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: fitmet-ui
component: fitment-devops
tier: front
name: fitmet-ui
namespace: fitment
spec:
progressDeadlineSeconds: 600
replicas: 1
selector:
matchLabels:
app: fitmet-ui
component: fitment-devops
tier: front
strategy:
rollingUpdate:
maxSurge: 100%
maxUnavailable: 100%
type: RollingUpdate
template:
metadata:
labels:
app: fitmet-ui
component: fitment-devops
tier: front
spec:
imagePullSecrets:
- name: harbor-secret
containers:
- name: fitmet-ui
image: REGISTRY/DOCKERHUB_NAMESPACE/APP_NAME:BUILD_NUMBER
imagePullPolicy: Always
ports:
- containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 30
timeoutSeconds: 5
failureThreshold: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 20
timeoutSeconds: 5
failureThreshold: 3
periodSeconds: 10
resources:
limits:
cpu: 300m
memory: 600Mi
requests:
cpu: 100m
memory: 100Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/conf.d/
volumes:
- name: nginx-conf
configMap:
name: fitment-conf
items:
- key: nginx.conf
path: nginx.conf
dnsPolicy: ClusterFirst
restartPolicy: Always
terminationGracePeriodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
labels:
app: fitmet-ui
component: fitment-devops
name: fitmet-ui
namespace: fitment
spec:
ports:
- name: http
port: 80
protocol: TCP
targetPort: 80
selector:
app: fitmet-ui
component: fitment-devops
tier: front
sessionAffinity: None
type: NodePort
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: fitment-ui-ingress
namespace: fitment
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: fitment.fooleryang.cn # 域名配置,可以使用通配符 *
http:
paths: # 相当于 nginx 的 location 配置,可以配置多个
- pathType: Prefix # 路径类型,按照路径类型进行匹配 ImplementationSpecific 需要指定 IngressClass,具体匹配规则以 IngressClass 中的规则为准。Exact:精确匹配,URL需要与path完全匹配上,且区分大小写的。Prefix:以 / 作为分隔符来进行前缀匹配
backend:
service:
name: fitmet-ui # 代理到哪个 service
port:
number: 80 # service 的端口
path: /
3 jenkins创建项目
在vue项目的准备工作完成后【本地运行正常,各个文件准备完成、提交到gitlab中】,创建jenkins流水线项目
执行构建,然后停止【目的:拉取源码的jenkinsfile,得到参数化构建配置】
执行第一次构建后参数化配置即会出现
再次选择tag进行构建
构建完成后,去k8s中查看相应pod,发现处于运行状态
bash
[root@k8s-master k8s]# kubectl get all -n fitment
NAME READY STATUS RESTARTS AGE
pod/fitmet-ui-76c68f444-thm74 1/1 Running 0 23m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/fitmet-ui NodePort 10.106.206.48 <none> 80:30800/TCP 37m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/fitmet-ui 1/1 1 1 37m
NAME DESIRED CURRENT READY AGE
replicaset.apps/fitmet-ui-76c68f444 1 1 1 37m
访问
可以用配置的ingress进行访问,也可以使用nodeport进行访问
成功访问,说明发布成功