前提

研究了四天才把这个搞定,现在写一篇文章来总结下自己通过jenkins来实现自动部署然后发布到k8s集群上的一个过程。

发布流程分析

其实,发布流程都大同小异,只是看使用的是啥工具去实现发布流程,人工/shell/jenkins等,当然,基础流程是这样,但是有部分细节还是需要另外考虑,也很容易踩坑

  1. 拉取代码
  2. 编译打包
  3. 构建新的镜像
  4. 推送到私有仓库
  5. 使用模板生成新的发布yaml
  6. 调用发布命令

    实现过程

    项目结构目录

    我项目是一个多模块的maven项目,就以我们其中一个模块为代表来演示如和将这个simone-app项目打包—>镜像—>推送—>模板—>发布这个一系列流程
    image.png

    Dockerfile

    编写对应的dockerfile文件需要注意的就是容器内的时间与宿主机时间要保持一致,其次是你在通过jenkins部署的时候需要将对应的jar包添加到容器,由于jenkins所处工作目录是容器的/var/jenkins_home/workspace/pipeline-simone-app(这个是我的流水线名称),所以必须进入到对应指定目录执行否则会提示对应位置不是一个文件或者文件夹 ```dockerfile FROM openjdk:8u201-jdk-alpine3.9

MAINTAINER heian1158139789@qq.com

系统编码

ENV LANG=C.UTF-8 LC_ALL=C.UTF-8

声明一个挂载点,容器内此路径会对应宿主机的某个文件夹

VOLUME /tmp

解决容器时间和宿主主机时间不一致问题

RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

jenkins流水线部署只能在工作空间部署,所以加了一层路径

ADD simone-service/simone-app/target/simone-app-1.0-SNAPSHOT.jar app.jar

启动容器时的进程

ENTRYPOINT [“java”,”-Xms256m”,”-Xmx512m”,”-jar”,”/app.jar”]

EXPOSE 30021

  1. <a name="QWBv2"></a>
  2. ### k8s模板文件
  3. 因为我们在编写流水线文件时,是通过我们定义pipeline文件中的参数变量,然后通过sed命令进行一个替换,这样就可以做到以不变应万变,而我这里目前仅仅使用了两个yaml文件(service+deployment),目前暂时没有使用ingress。不过在发布之前,因为要往harbor下载镜像,所以你需要提前配置一些harbor的密钥在k8s上,因为拉取私有镜像需要此密钥配置才行。
  4. ```dockerfile
  5. apiVersion: v1
  6. kind: Service
  7. metadata:
  8. name: {{projectName}}
  9. namespace: {{namespace}}
  10. labels:
  11. app: {{projectName}}
  12. spec:
  13. selector:
  14. app: {{projectName}}
  15. type: NodePort
  16. ports:
  17. - port: 30021
  18. targetPort: 30021
  19. nodePort: 30021 #这里不指定就会随机,NodePort默认范围是30000-32767
  20. ---
  21. apiVersion: apps/v1
  22. kind: Deployment
  23. metadata:
  24. name: {{projectName}}
  25. namespace: {{namespace}}
  26. spec:
  27. replicas: 1 #Pod副本期待数量
  28. selector:
  29. matchLabels: #定义RS的标签
  30. app: {{projectName}} #符合目标的Pod拥有此标签
  31. template:
  32. metadata:
  33. labels:
  34. app: {{projectName}}
  35. spec:
  36. containers:
  37. - image: {{imgUrl}}
  38. imagePullPolicy: IfNotPresent
  39. name: {{projectName}}
  40. ports:
  41. - containerPort: 30021
  42. protocol: TCP
  43. volumeMounts:
  44. - name: logdir
  45. mountPath: /logs
  46. - name: localtime
  47. mountPath: /etc/localtime
  48. - name: timezone
  49. mountPath: /etc/timezone
  50. imagePullSecrets:
  51. - name: {{harborSecret}}
  52. volumes:
  53. - name: logdir
  54. emptyDir: {}
  55. - name: localtime
  56. hostPath:
  57. path: /etc/localtime
  58. - name: timezone
  59. hostPath:
  60. path: /etc/timezone
  61. ---
  62. apiVersion: extensions/v1beta1
  63. kind: Ingress
  64. metadata:
  65. name: ingress-simone-app
  66. namespace: default
  67. spec:
  68. rules:
  69. - host: k8s.simone.app #记得在主机hosts文件配置
  70. http:
  71. paths:
  72. - path: /
  73. backend:
  74. serviceName: {{projectName}}
  75. servicePort: 30021

k8s生成harbor密钥

有两种方式去生成对应的密钥,一个是通过官方给出的demo来产生密钥,另外一个是常规做法通过我们之前配置的私库配置,对应harbor相关可参考我另外一篇文章:harbor ,也可以参考官方文档:文档

  1. ### 拉取镜像所需秘钥
  2. ##### 通过命令方式创建secret
  3. ``` bash
  4. ➜ kubectl create secret docker-registry docker-harbor-registry \
  5. --docker-server=http://192.168.1.148:81 \
  6. --docker-username=admin \
  7. --docker-password=Harbor12345\
  8. --docker-email=1158139789@qq.com
  9. ➜ kubectl get secret docker-harbor-registry --output=yaml 输出ymml形式查看
通过声明式创建secret
  1. cat ~/.docker/config.json |base64 -w 0
  2. apiVersion: v1
  3. kind: Secret
  4. metadata:
  5. name: docker-harbor-registry
  6. namespace: default
  7. type: kubernetes.io/dockerconfigjson
  8. data:
  9. .dockerconfigjson: ewoJImF1dGhzIjogewoJCSIxOTIuMTY4LjEuMTQ4OjgxIjogewoJCQkiYXV0aCI6ICJZV1J0YVc0NlNHRnlZbTl5TVRJek5EVT0iCgkJfQoJfQp9
<a name="sIzOm"></a>
### Harbor镜像仓库
流水线执行完成后,推送的镜像会存储在这里,每次发布k8s的时候会从这里拉去,下载次数+1<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/771792/1629455152676-3be33ed3-016d-4fac-8f7e-c90ed946adea.png#clientId=u82574df4-0913-4&from=paste&height=436&id=u5280fdb8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=436&originWidth=1248&originalType=binary&ratio=1&size=40616&status=done&style=none&taskId=u58617199-1cd6-4dcc-a282-c88e5b21192&width=1248)
<a name="BMAjO"></a>
### 流水线脚本
我这里的镜像tag是以最后一次提交的git的提交号截取作为镜像号,另外需要提及的一点是我执行到发布这一个流程的时候,是通过切换从节点执行暂存的yaml文件
```groovy
pipeline {
    agent any
    // 环境变量
    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: 'projectName', defaultValue: 'simone-app', description: '项目名称')
        string(name: 'gitUrl', defaultValue: 'http://192.168.1.148:8888/jimudev/simone-server.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("编译打包"){
            steps{
                sh '''
                    #!/usr/bin/env bash
                    export MAVEN_HOME=/maven-setting/apache-maven-3.8.1
                    export PATH=$PATH:$MAVEN_HOME/bin
                    whoami
                    mvn clean package -pl simone-service/simone-app -am -amd -Dmaven.test.skip=true
                '''                
            }
        }
        stage('构建镜像及推送到Harbor') { 
            steps{
                //他只能在当前的工作空间,无法cd到某个文件夹
                sh "pwd"
                sh "ls"
                sh "docker build -t ${params.harborHost}/${params.harborNamespace}/${params.projectName}:${image_tag} -f simone-service/simone-app/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 "pwd"
                sh "ls"
                //修改进入到指定目录文件
                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;' ./simone-service/simone-app/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"
            }
        }

   }
}

权限问题

因为我是用的是maven挂载,在构建过程中遇到了问题,说我们命令没有权限执行
image.png
我本事是以root用户运行的,并且具备对应权限运行不知道为啥还会爆出权限不足,可能这个root是个假的root吧, 因为当映射本地数据卷时,/muvolume/jenkins目录的拥有者为root用户,而容器中root估计是个假人 所以需要对maven对应的安装目录进行赋权。

chmod 777 -R /root/project-server/jenkins/maven-setting/apache-maven-3.8.1

jenkins从节点配置问题

image.png
同时这里也隐藏了一个坑就是我这里选择了“只允许绑定这台机器的job”,如果你不选这里的话会出现我们流水线刚开始运行的时候会出现非master节点运行这个pipeline,也就是非jenkins容器执行下面脚本。
1629455065.jpg1629455065(1).jpg
配置这个从节点的前提无需安装jdk,如果你要安装可以使用下面方式安装:
centos可使用下面方式直接一键安装,在输入java -version检验下是否安装成功即可,参考安装jdk文档

yum search java 或者 yum list java*
yum install java-1.8.0-openjdk-devel.x86_64  版本可自行选择

配置完成后,还是无法连接,查看报错信息说我们对应know_hosts文件不存在,于是我们做出如下应对。
显示对应jenkins容器内对应/var/jenkins_home/.ssh/known_hosts 文件不存在,而且其中没有内容。这个需要我们手动配置

docker exec -it jenkins bash
cd /var/jenkins_home/.ssh/
#里面没有任何东西,需要我们自动生成。然后去生成对应的knows,自己登陆一遍即可
ssh root@192.168.1.148 -p 2222
#此时会在/root/.ssh/known_hosts 生成对应键值对,复制到此文件即可
cp /root/.ssh/known_hosts /var/jenkins_home/.ssh/

gitlab触发部署

当你配置jenkins的url出现问题时,需要你以超级管理员用户会把某个选项打钩即可,具体忘了在哪遇到百度下即可。配置完成后一旦你的git远程仓库有代码提交便会自动触发你对应的pipeline的job
image.png


参考:https://juejin.cn/post/6854573212563423240