前提
近期我们在做版本迁移,由docker-compose构建的服务迁移到k8s上面去,现在要部署的是前端vue项目,之前我们的做法是前端人员打包成dist文件夹,然后手动提交给后台人员,后台人员编写nginx的compose启动一个nginx,然后将dist文件指向到nginx的映射代理的路径即可实现前端页面的展示和访问。
k8s其实类似,只不过我们是我们打包dist前提是需要配置下node环境,在编写dockerfile来编写一个nginx,再把打包好的dist文件放入对应映射目录下。有了镜像后, 我们再推送到harbor。然后通过我们编写好的k8s的yaml模板文件将变量内容填充,在通过agent代理模式实现apply -f creat k8_temp.yaml文件即可。
流水线
打包dist
agent代理方式安装nodejs
打包dist在jenkins我觉得还不简单,因为你会踩到很多坑。比如你打包的话需要依赖nodejs环境,好在jenkins给我们添加了工作环境的便利,比如你想要用nodejs,只需要在下面代码中加入agent对应的环境即可。但是前提是你jenkins必须安装2个插件:Docker plugin和Docker Pipeline。否则会提示你只能agent[node any none],你这样操作确实可以起到环境作用,但是git环境就不存在了。目前原因也不明。所以这种代理的方式还是有点不可靠,就像我之前部署springboot时候也想用代理maven但是总是各种异常,所以后面我就索性不用代理了。
pipeline {
agent {
docker {
image 'node:14.16.0-alpine'
args '-p 3000:3000'
}
}
stages{
stage("test"){
steps {
sh "git --version"
sh "node -v"
sh "npm -v"
sh "npm install --registry=https://registry.npm.taobao.org"
sh "npm run build:stage"
}
}
}
}
jenkins安装nodejs(推荐)
所以我就直接再jenkins安装nodejs插件,选择对应版本即可。点击应用后我们再到流水线脚本使用agent tool+别名(与你填写的一致),我这里是成功的。
假设如果没成功就把它添加到宿主机的全局的环境变量中去
vi /etc/profile
#/xxx宿主机挂在目录/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/node14/bin
PATH=/var/jenkins_home/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/node14/bin
pipeline {
agent any
tools {nodejs "node14"}
stages{
stage('拉取代码') {
steps{
checkout([$class: 'GitSCM', branches: [[name: "*/${params.branchName}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [],
submoduleCfg: [],
userRemoteConfigs: [[
credentialsId: 'gitlab-humingming',
url: "${params.gitUrl}"]]])
}
}
stage("test"){
steps {
sh "git --version"
sh "node -v"
sh "npm -v"
sh "npm install --registry=https://registry.npm.taobao.org"
sh "npm run build:stage"
}
}
}
}
这样虽然可行,但是第一次构建的途中可能会出现访问github,可能会出现网页无果的情况,但是多试几次就行了,后面安装都会跳过这一步也就无需访问国外网站了,所以这里我暂时没做太多干预。
制作镜像
现在我们已经达成dist包了,需要把我们的dist文件放在nginx的html目录下即可,然后分配个admin-nginx.conf的配置文件监听某个端口即可,以此来实现nginx代理我们前端文件。
FROM nginx:stable-alpine
MAINTAINER heian<1158139789@qq.com>
#系统编码
ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
# 解决容器时间和宿主主机时间不一致问题
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
#将项目根目录下 dist 文件夹下的所有文件复制到镜像中 /usr/share/nginx/html/ 目录下
COPY dist/ /usr/share/nginx/html/
# 目录下的admin-nginx.conf复制到 etc/nginx/conf.d/default.conf,用本地的配置来替换 Nginx 镜像里的默认配置
COPY admin-nginx.conf /etc/nginx/conf.d/default.conf
# COPY ./nginx.conf /etc/nginx/nginx.conf 我测试环境不会出现此问题,所以没配置
EXPOSE 9527
server {
listen 9527;
server_name localhost;
#access_log logs/host_9527.access.log main;
access_log off;
error_page 500 502 503 504 /50x.html;
location / {
root /usr/share/nginx/html; #与Dockerfile路径对应上
index index.html index.htm;
}
}
编写k8s yaml模板文件
apiVersion: v1
kind: Service
metadata:
name: {{projectName}}
namespace: {{namespace}}
labels:
app: {{projectName}}
spec:
selector:
app: {{projectName}}
type: NodePort
ports:
- port: 9527
targetPort: 9527
nodePort: 30027 #这里不指定就会随机,NodePort默认范围是30000-32767
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{projectName}}
namespace: {{namespace}}
spec:
replicas: 1 #Pod副本期待数量
selector:
matchLabels: #定义RS的标签
app: {{projectName}} #符合目标的Pod拥有此标签
template:
metadata:
labels:
app: {{projectName}}
spec:
containers:
- image: {{imgUrl}}
imagePullPolicy: IfNotPresent
name: {{projectName}}
ports:
- containerPort: 9527
protocol: TCP
volumeMounts:
- name: logdir
mountPath: /logs
- name: localtime
mountPath: /etc/localtime
- name: timezone
mountPath: /etc/timezone
imagePullSecrets:
- name: {{harborSecret}}
volumes:
- name: logdir
emptyDir: {}
- name: localtime
hostPath:
path: /etc/localtime
- name: timezone
hostPath:
path: /etc/timezone
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-simone-vue-admin
namespace: default
spec:
rules:
- host: k8s.simone.vue-admin
http:
paths:
- path: /
backend:
serviceName: {{projectName}}
servicePort: 9527
jenkins的pipeline流水线脚本
pipeline {
agent any
tools {nodejs "node14"}
// 环境变量
environment {
// 镜像版本
image_tag = sh(returnStdout: true,script: 'echo `date +"%Y%m%d%H%M"_``git describe --tags --always`').trim()
}
//测试环境参数
parameters {
string(name: 'namespace', defaultValue: 'default', description: '环境')
string(name: 'profile', defaultValue: 'stage', description: '打包环境')
string(name: 'projectName', defaultValue: 'vue-element-admin', description: '项目名称')
string(name: 'gitUrl', defaultValue: 'http://192.168.1.148:8888/jimudev/simone-admin.git', description: '仓库地址')
string(name: 'branchName', defaultValue: 'master', description: 'git分支')
string(name: 'harborHost', defaultValue: '192.168.1.148:81', description: '镜像仓库地址')
string(name: 'harborUser', defaultValue: 'admin', description: '镜像仓库用户')
string(name: 'harborPwd', defaultValue: 'Harbor12345', description: '镜像仓库密码')
string(name: 'harborNamespace', defaultValue: 'myshop', description: '镜像仓库命名空间')
string(name: 'harborSecret', defaultValue: 'docker-harbor-registry', description: 'harbor镜像仓库秘钥')
}
stages{
stage('拉取代码') {
steps{
checkout([$class: 'GitSCM', branches: [[name: "*/${params.branchName}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [],
submoduleCfg: [],
userRemoteConfigs: [[
credentialsId: 'gitlab-humingming',
url: "${params.gitUrl}"]]])
}
}
stage("test"){
steps {
sh "git --version"
sh "node -v"
sh "npm -v"
sh "npm install --registry=https://registry.npm.taobao.org"
sh "npm run build:${params.profile}"
}
}
stage('构建nginx镜像及推送到Harbor') {
steps{
//他只能在当前的工作空间,无法cd到某个文件夹
sh "docker build -t ${params.harborHost}/${params.harborNamespace}/${params.projectName}:${image_tag} -f ./Dockerfile ."
sh "docker login ${params.harborHost} -u ${params.harborUser} -p ${params.harborPwd}"
sh "docker push ${params.harborHost}/${params.harborNamespace}/${params.projectName}:${image_tag}"
sh "docker rmi -f ${params.harborHost}/${params.harborNamespace}/${params.projectName}:${image_tag}"
}
}
stage('生成k8s发布模板') {
steps {
//修改进入到指定目录文件
sh "sed -e 's#{{projectName}}#${params.projectName}#g;s#{{namespace}}#${params.namespace}#g;s#{{harborSecret}}#${params.harborSecret}#g;s#{{imgUrl}}#${params.harborHost}/${params.harborNamespace}/${params.projectName}:${image_tag}#g;' ./k8s_temp.yaml > k8s_${params.projectName}.yaml"
// 暂存文件,在node机器上可以使用此文件
stash name: "k8s_${params.projectName}.yaml", includes: "k8s_${params.projectName}.yaml"
sh "cat k8s_${params.projectName}.yaml"
}
}
stage('k8s发布') {
//选择使用哪一台代理节点运行 master或者slave-110
agent { node { label 'slave-110' } }
steps {
sh "pwd"
sh "ls"
// 取出文件
unstash("k8s_${params.projectName}.yaml")
sh "kubectl apply -f k8s_${params.projectName}.yaml"
}
}
}
}
需要注意的一点就是我这里是采用了github上的前端模板快速开发,打包使用使用的是nmp run build stage打包测试环境,所以需要对应下配置.env.staging的文件。完成后登陆访问即可<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/771792/1629883009690-573cdb74-4699-423f-8991-da80bd94c287.png#clientId=ue8e43bce-7a17-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=645&id=u440733cd&margin=%5Bobject%20Object%5D&name=image.png&originHeight=645&originWidth=790&originalType=binary&ratio=1&rotation=0&showTitle=false&size=54144&status=done&style=none&taskId=ud41f5eac-3a48-420a-a885-1598011d779&title=&width=790)
注意点
- 我在部署我们官方网页的时候nginx代理出现了403权限问题,后来搜索发现是因为nginx.conf的权限是nginx并非root,修改下对应nginx.conf配置即可。
```nginx
user root;
user nginx; 就就改了这一段
worker_processes auto;
error_log /var/log/nginx/error.log notice; 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;
include /etc/nginx/conf.d/*.conf;
}
2. 其次是dist的路径请配置正确,还有就是涉及端口映射请务必注意容器内nginx监听的端口与你dockerfile开启的端口保持一致。
2. 跨域问题的出现,很有可能就是接收请求的nginx已经设置了跨域,这时候需要服务端去除跨域(java代码或者网关yml的配置)以及前端去除跨域配置,只要出现了but only one is allowed就是多次设置了跨域。我这里就是服务端设置了,然后nginx也设置了导致
![image.png](https://cdn.nlark.com/yuque/0/2021/png/771792/1639043712951-3fe94752-b235-4561-8f7d-3a260ef568f7.png#clientId=ua78812b9-2ccb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=168&id=ub58c8451&margin=%5Bobject%20Object%5D&name=image.png&originHeight=187&originWidth=1111&originalType=binary&ratio=1&rotation=0&showTitle=false&size=37268&status=done&style=none&taskId=u1c99a04c-700f-4d78-b398-1f72fc2a409&title=&width=1000)![image.png](https://cdn.nlark.com/yuque/0/2021/png/771792/1639043784767-5e50bf1a-6e1f-4f43-ae83-e5059c240307.png#clientId=ua78812b9-2ccb-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=528&id=u7d614c11&margin=%5Bobject%20Object%5D&name=image.png&originHeight=384&originWidth=727&originalType=binary&ratio=1&rotation=0&showTitle=false&size=31120&status=done&style=none&taskId=u316f2134-7b56-424b-9d92-919f2751abd&title=&width=1000)
<a name="ZJSN3"></a>
## Node项目流水线
我的一个客服系统是在网易云信github上找到小程序编写的IM系统,前端采用vue后端采用nodejs,所以我这边得一块将他部署上去。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/771792/1629882374120-df3beca9-a3c4-4afa-a2fc-7bf7359c77f8.png#clientId=ue8e43bce-7a17-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=363&id=u9181253c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=363&originWidth=313&originalType=binary&ratio=1&rotation=0&showTitle=false&size=15675&status=done&style=none&taskId=uc638b5b3-d313-4099-895b-6e3d7de7822&title=&width=313)
<a name="W31ZQ"></a>
### 制作镜像+流水线
因为我这个打包需要node环境,所以流水线需要node环境,我这里也是通过jenkins插件tools实现。通过流水线得打包成dist后,接着执行流水线得dockerfile会把所有当前目录下得所有文件放到了容器得tmp目录下,然后使用node server来监听30028端口,以此来代替nginx
```dockerfile
pipeline {
agent any
tools {nodejs "node14"}
// 环境变量
environment {
// 镜像版本
image_tag = sh(returnStdout: true,script: 'echo `date +"%Y%m%d%H%M"_``git describe --tags --always`').trim()
}
//测试环境参数
parameters {
string(name: 'namespace', defaultValue: 'default', description: '环境')
string(name: 'profile', defaultValue: 'devbuild', description: '打包环境')
string(name: 'projectName', defaultValue: 'vue-web-im', description: '项目名称')
string(name: 'gitUrl', defaultValue: 'http://192.168.1.148:8888/jimudev/vue-web-im.git', description: '仓库地址')
string(name: 'branchName', defaultValue: 'master', description: 'git分支')
string(name: 'harborHost', defaultValue: '192.168.1.148:81', description: '镜像仓库地址')
string(name: 'harborUser', defaultValue: 'admin', description: '镜像仓库用户')
string(name: 'harborPwd', defaultValue: 'Harbor12345', description: '镜像仓库密码')
string(name: 'harborNamespace', defaultValue: 'myshop', description: '镜像仓库命名空间')
string(name: 'harborSecret', defaultValue: 'docker-harbor-registry', description: 'harbor镜像仓库秘钥')
}
stages{
stage('拉取代码') {
steps{
checkout([$class: 'GitSCM', branches: [[name: "*/${params.branchName}"]],
doGenerateSubmoduleConfigurations: false,
extensions: [],
submoduleCfg: [],
userRemoteConfigs: [[
credentialsId: 'gitlab-humingming',
url: "${params.gitUrl}"]]])
}
}
stage("nmp编译打包"){
steps {
sh "git --version"
sh "node -v"
sh "npm -v"
sh "npm install"
sh "npm run ${params.profile}"
}
}
stage('构建nginx镜像及推送到Harbor') {
steps{
//他只能在当前的工作空间,无法cd到某个文件夹
sh "docker build -t ${params.harborHost}/${params.harborNamespace}/${params.projectName}:${image_tag} -f ./Dockerfile ."
sh "docker login ${params.harborHost} -u ${params.harborUser} -p ${params.harborPwd}"
sh "docker push ${params.harborHost}/${params.harborNamespace}/${params.projectName}:${image_tag}"
sh "docker rmi -f ${params.harborHost}/${params.harborNamespace}/${params.projectName}:${image_tag}"
}
}
stage('生成k8s发布模板') {
steps {
//修改进入到指定目录文件
sh "sed -e 's#{{projectName}}#${params.projectName}#g;s#{{namespace}}#${params.namespace}#g;s#{{harborSecret}}#${params.harborSecret}#g;s#{{imgUrl}}#${params.harborHost}/${params.harborNamespace}/${params.projectName}:${image_tag}#g;' ./k8s_temp.yaml > k8s_${params.projectName}.yaml"
// 暂存文件,在node机器上可以使用此文件
stash name: "k8s_${params.projectName}.yaml", includes: "k8s_${params.projectName}.yaml"
sh "cat k8s_${params.projectName}.yaml"
}
}
stage('k8s发布') {
//选择使用哪一台代理节点运行 master或者slave-110
agent { node { label 'slave-110' } }
steps {
sh "pwd"
sh "ls"
// 取出文件
unstash("k8s_${params.projectName}.yaml")
sh "kubectl apply -f k8s_${params.projectName}.yaml"
}
}
}
}
FROM node:14.17.0-alpine
MAINTAINER heian<1158139789@qq.com>
# 进入到此目录并把当前所有文件放到此文件夹下
WORKDIR /tmp
ADD . .
CMD npm run dev
CMD node server
EXPOSE 30028
apiVersion: v1
kind: Service
metadata:
name: {{projectName}}
namespace: {{namespace}}
labels:
app: {{projectName}}
spec:
selector:
app: {{projectName}}
type: NodePort
ports:
- port: 30028
targetPort: 30028
nodePort: 30028 #这里不指定就会随机,NodePort默认范围是30000-32767
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{projectName}}
namespace: {{namespace}}
spec:
replicas: 1 #Pod副本期待数量
selector:
matchLabels: #定义RS的标签
app: {{projectName}} #符合目标的Pod拥有此标签
template:
metadata:
labels:
app: {{projectName}}
spec:
containers:
- image: {{imgUrl}}
imagePullPolicy: IfNotPresent
name: {{projectName}}
ports:
- containerPort: 30028
protocol: TCP
volumeMounts:
- name: logdir
mountPath: /logs
- name: localtime
mountPath: /etc/localtime
- name: timezone
mountPath: /etc/timezone
imagePullSecrets:
- name: {{harborSecret}}
volumes:
- name: logdir
emptyDir: {}
- name: localtime
hostPath:
path: /etc/localtime
- name: timezone
hostPath:
path: /etc/timezone
在浏览器输入:测试ip:30028登陆后即可实现