前提

近期我们在做版本迁移,由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但是总是各种异常,所以后面我就索性不用代理了。

  1. pipeline {
  2. agent {
  3. docker {
  4. image 'node:14.16.0-alpine'
  5. args '-p 3000:3000'
  6. }
  7. }
  8. stages{
  9. stage("test"){
  10. steps {
  11. sh "git --version"
  12. sh "node -v"
  13. sh "npm -v"
  14. sh "npm install --registry=https://registry.npm.taobao.org"
  15. sh "npm run build:stage"
  16. }
  17. }
  18. }
  19. }

jenkins安装nodejs(推荐)

所以我就直接再jenkins安装nodejs插件,选择对应版本即可。点击应用后我们再到流水线脚本使用agent tool+别名(与你填写的一致),我这里是成功的。
image.png
假设如果没成功就把它添加到宿主机的全局的环境变量中去

  1. vi /etc/profile
  2. #/xxx宿主机挂在目录/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/node14/bin
  3. PATH=/var/jenkins_home/tools/jenkins.plugins.nodejs.tools.NodeJSInstallation/node14/bin

image.png
image.png

  1. pipeline {
  2. agent any
  3. tools {nodejs "node14"}
  4. stages{
  5. stage('拉取代码') {
  6. steps{
  7. checkout([$class: 'GitSCM', branches: [[name: "*/${params.branchName}"]],
  8. doGenerateSubmoduleConfigurations: false,
  9. extensions: [],
  10. submoduleCfg: [],
  11. userRemoteConfigs: [[
  12. credentialsId: 'gitlab-humingming',
  13. url: "${params.gitUrl}"]]])
  14. }
  15. }
  16. stage("test"){
  17. steps {
  18. sh "git --version"
  19. sh "node -v"
  20. sh "npm -v"
  21. sh "npm install --registry=https://registry.npm.taobao.org"
  22. sh "npm run build:stage"
  23. }
  24. }
  25. }
  26. }

这样虽然可行,但是第一次构建的途中可能会出现访问github,可能会出现网页无果的情况,但是多试几次就行了,后面安装都会跳过这一步也就无需访问国外网站了,所以这里我暂时没做太多干预。
image.png

制作镜像

现在我们已经达成dist包了,需要把我们的dist文件放在nginx的html目录下即可,然后分配个admin-nginx.conf的配置文件监听某个端口即可,以此来实现nginx代理我们前端文件。
image.png

  1. FROM nginx:stable-alpine
  2. MAINTAINER heian<1158139789@qq.com>
  3. #系统编码
  4. ENV LANG=C.UTF-8 LC_ALL=C.UTF-8
  5. # 解决容器时间和宿主主机时间不一致问题
  6. RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
  7. #将项目根目录下 dist 文件夹下的所有文件复制到镜像中 /usr/share/nginx/html/ 目录下
  8. COPY dist/ /usr/share/nginx/html/
  9. # 目录下的admin-nginx.conf复制到 etc/nginx/conf.d/default.conf,用本地的配置来替换 Nginx 镜像里的默认配置
  10. COPY admin-nginx.conf /etc/nginx/conf.d/default.conf
  11. # COPY ./nginx.conf /etc/nginx/nginx.conf 我测试环境不会出现此问题,所以没配置
  12. EXPOSE 9527
  1. server {
  2. listen 9527;
  3. server_name localhost;
  4. #access_log logs/host_9527.access.log main;
  5. access_log off;
  6. error_page 500 502 503 504 /50x.html;
  7. location / {
  8. root /usr/share/nginx/html; #与Dockerfile路径对应上
  9. index index.html index.htm;
  10. }
  11. }

image.png

编写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)

注意点

  1. 我在部署我们官方网页的时候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登陆后即可实现
image.png