03-微服务发布平台

发布流程设计

03-微服务发布平台

之前的步骤:

  • 下载代码
  • 代码编译
  • 构建镜像并推送到镜像仓库
  • 将镜像部署到k8s平台

自动化发布:一键点击,完成上述多个阶段的工作

本次优化:jenkin自动获取代码,使用流水线方式自动编译和推送镜像,并简化yaml,结合helm chart部署

准备基础环境:Harbor、Gitlab、Jenkins

Harbor镜像仓库:

上已经部署

Gitlab代码仓库

选择在192.168.1.14上部署

在Gitlab创建一个项目,然后提交微服务项目代码。

Gitlab可以使用Docker启动一个(这里选择老板本):

mkdir /opt/gitlab
GITLAB_HOME=/opt/gitlab # 数据持久化目录
docker run --detach \
  --hostname gitlab.ctnrs.com \
  --publish 443:443 \
  --publish 88:80 \
  --publish 2222:22 \
  --name gitlab \
  --restart always \
  --volume $GITLAB_HOME/config:/etc/gitlab \
  --volume $GITLAB_HOME/logs:/var/log/gitlab \
  --volume $GITLAB_HOME/data:/var/opt/gitlab \
  gitlab/gitlab-ce:13.0.10-ce.0

访问地址:http://IP:88
如果安装gitlab最新版本的话初次登陆需要账户和密码,我们可以自己先创建一个:

# 1.进入容器
docker exec -it gitlab bash
# 2.进入gitlab目录
cd /opt/gitlab/bin
# 3.执行命令
gitlab-rails console

执行这步命令耗时可能较长,稍微等一会,待出现下面提示可继续进行操作

03-微服务发布平台
4.执行命令
u=User.where(id:1).first
5.执行命令–修改密码
u.password='Abcd1234!'
6.执行命令–确认密码
u.password_confirmation='Abcd1234!'
7.执行命令–保存修改
u.save
# 当提示true后说明修改成功,可进入gitlab登录页面使用root/Abcd1234!进行登录
03-微服务发布平台

新版本对于服务器配置要求很高,所以我们选择旧版本进行安装

旧的版本直接设置新密码即可:

03-微服务发布平台

登陆gitlab,新建一个项目:

03-微服务发布平台
03-微服务发布平台

添加一下推送服务器的公钥

03-微服务发布平台

推送现有文件夹:

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

cd existing_folder
git init
# git remote add origin http://gitlab.ctnrs.com/root/microservice.git
git add .

git commit -m "Initial commit"
git push -u origin master
# 之前的实验已经构建了项目,所以这次要把所有simple-microservice目录下target删除掉:
cd simple-microservice
find . -name "target" -exec rm -rf {} \;

git init
git remote add origin http://192.168.1.14:88/root/microservice.git
git remote set-url origin http://192.168.1.14:88/root/microservice.git

git config --global user.email "you@example.com"
git config --global user.name "Your Name"

git commit -m "Initial commit"
git push -u origin master

推送成功:

03-微服务发布平台

Jenkins CI系统

Jenkins是一款开源CI&CD 系统,用于自动化各种任务,包括构建、测试和部署。

Jenkins官方提供了镜像:https://hub.docker.com/r/jenkins/jenkins

使用Deployment来部署这个镜像,会暴露两个端口:8080 Web访问端口,50000 Slave通信端口,容器启动后Jenkins数据存储在/var/jenkins_home目录,所以需要将该目录使用PV持久化存储。

配置PV持久化存储:

# 1、部署NFS共享服务器
在所有k8s节点安装NFS软件包:yum install nfs-utils -y

# 2、找一个节点(192.168.1.12)作为NFS共享存储服务器
mkdir -p /ifs/kubernetes/jenkins-data
vi /etc/exports
# -------------------------------------------------
/ifs/kubernetes 192.168.1.0/24(rw,no_root_squash)
# -------------------------------------------------
systemctl start nfs
systemctl enable nfs

# 3、为Jenkins准备PV
# jenkins-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv0001
spec:
  capacity:
    storage: 5Gi
  accessModes: ["ReadWriteOnce"]
  nfs:
    path: /ifs/kubernetes/jenkins-data
    server: 192.168.1.12
# kubectl create -f jenkins-pv.yaml

部署jenkins:

最新版本之后不再兼容JDK8而是需要JDK11+版本

Jenkins Server和Agent的运行都需要依赖JDK;(Jenkins是docker安装的,其环境里自带了java,但是jenkins agent环境也是需要安装jdk的,因此这里的测试虚机就需要安装jdk11了。)

https://hub.docker.com/r/jenkins/jenkins/tags

jenkins.yaml(此时镜像最新版本为jenkins:2.440.1-lts-jdk11版本,建议选择新的版本,也可以选择自己指定版本)

apiVersion: apps/v1
kind: Deployment 
metadata:
  name: jenkins
  labels:
    name: jenkins
spec:
  replicas: 1
  selector:
    matchLabels:
      name: jenkins 
  template:
    metadata:
      name: jenkins
      labels:
        name: jenkins
    spec:
      terminationGracePeriodSeconds: 10
      serviceAccountName: jenkins
      containers:
        - name: jenkins
          image: jenkins/jenkins:2.440.1-lts-jdk11
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
            - containerPort: 50000
          resources:
            limits:
              cpu: 1
              memory: 1Gi
            requests:
              cpu: 0.2
              memory: 200Mi
          env:
            - name: JAVA_OPTS
              value: -Xmx1g 
          volumeMounts:
            - name: jenkins-home
              mountPath: /var/jenkins_home
      securityContext:
        fsGroup: 1000
        runAsUser: 0
      volumes:
        - name: jenkins-home
          persistentVolumeClaim:
            claimName: jenkins-home
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-home
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: Service
metadata:
  name: jenkins
spec:
  selector:
    name: jenkins
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 8080
      protocol: TCP
      nodePort: 30006
    - name: agent
      port: 50000
      protocol: TCP
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: jenkins
rules:
- apiGroups: [""]
  resources: ["pods","events"]
  verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
  resources: ["pods/exec"]
  verbs: ["create","delete","get","list","patch","update","watch"]
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get","list","watch"]
- apiGroups: [""]
  resources: ["secrets","events"]
  verbs: ["get"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: jenkins
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: jenkins
subjects:
- kind: ServiceAccount
  name: jenkins

安装成功后再页面输入密码,密码使用kubectl logs podname可以查看

03-微服务发布平台
03-微服务发布平台

选择插件安装:

03-微服务发布平台

先安装所需的插件:

可选:Jenkins下载插件默认服务器在国外,如果比较慢,建议修改国内源:

# 进入到nfs持久化服务器的共享目录
cd /ifs/kubernetes/jenkins-data/updates
sed -i 's/http:\/\/updates.jenkins-ci.org\/download/https:\/\/mirrors.tuna.tsinghua.edu.cn\/jenkins/g' default.json && 
sed -i 's/http:\/\/www.google.com/https:\/\/www.baidu.com/g' default.json
# 重建Pod生效(Pod名称改成你实际的)
kubectl delete pod jenkins-xxxxxx-xxxx -n ops

先不安装任何插件到达下一步:

03-微服务发布平台

管理Jenkins->系统配置-->管理插件-->分别搜索Git Parameter/Git/Pipeline/kubernetes/Config File Provider,选中点击安装以上插件。

•Git:拉取代码
•Git Parameter:Git参数化构建
•Pipeline:流水线
•kubernetes:连接Kubernetes动态创建Slave代理
•Config File Provider:存储配置文件
•Extended Choice Parameter:扩展选择框参数,支持多选

安装相关插件后展示:

03-微服务发布平台

Jenkins在K8s中动态创建代理

Jenkins主从架构介绍

Jenkins Master/Slave架构,Master(Jenkins本身)提供Web页面让用户来管理项目和从节点(Slave),项目任务可以运行在Master本机或者分配到从节点运行,一个Master可以关联多个Slave,这样好处是可以让Slave分担Master工作压力和隔离构建环境。

03-微服务发布平台

Master Slave架构图
03-微服务发布平台

当触发Jenkins任务时,Jenkins会调用Kubernetes API使用jnlp镜像创建Slave Pod,Pod启动后会连接Jenkins,接受任务并处理。

我们创建一个新的item:

03-微服务发布平台
03-微服务发布平台

再Build Steps选择Execute shell内容为hostname

03-微服务发布平台

构建并查看输出,可以看出,hostname为jenkins pod,在执行多个任务时会对此pod造成较大压力,所以需要再kubernetes中配置slave pod

03-微服务发布平台

Kubernetes插件配合

Kubernetes插件:用于Jenkins在Kubernetes集群中运行动态代理

插件介绍:https://github.com/jenkinsci/kubernetes-plugin

配置插件:管理Jenkins->管理Nodes和云->管理云->添加Kubernetes

新版本是在Manage Jenkins->Clouds->New cloud中创建

03-微服务发布平台

下面两个地址中,因为jenkins部署在k8s内部,所以可以选择地址为:https://kubernetes.default

03-微服务发布平台
03-微服务发布平台

这样就可以直接进行连接测试:

03-微服务发布平台

如果jenkins不在集群内部署,使用外部地址https://192.168.1.10:6443还需要添加ca.pem证书并配置pkcs凭证,然后进行连接测试,内部地址下述配置可以忽略:

==========================下述步骤在集群内部部署可忽略=========================

03-微服务发布平台

创建kubernetes凭据

03-微服务发布平台
03-微服务发布平台

在k8s-master01中使用openssl生成我们的pkcs文件

#1、查看kubernetes的config文件
cat ~/.kube/config

#2、根据配置文件生成证书.替换引号内部的信息为config内相关value
echo "certificate-authority-data" | base64 -d > ca.crt
echo "client-certificate-data" | base64 -d > client.crt
echo "client-key-data" | base64 -d > client.key

#3、生成jenkins使用的cert.pfx,此处需要设置一个4位数以上的密码
openssl pkcs12 -export -out default.pfx -inkey client.key -in client.crt -certfile ca.crt

生成下述文件
======================================
ca.crt cert.pfx client.key client.crt

将cert.pfx导入到我们的Credentials中

==========================上述步骤在集群内部部署可忽略=========================

jenkins地址选择:http://jenkins.default,保存配置,并使用凭证jenkins连接kubenetes测试

03-微服务发布平台

开启jenkins jnlp slave和master端口,一般都设置成50000

03-微服务发布平台
03-微服务发布平台

自定义Jenkins Slave镜像

项目发布流程:

1、下载代码
2、代码编译
3、构建景象,并推送镜像
4、将容器部署到k8s平台(编写yaml进行部署的,这次使用helm chart部署)
5、验证

用这个镜像创建的pod完成上述发布流程任务

slave pod的要求:
1、在slave pod中,需要可以拉取代码
2、jdk mvn
3、docker build/push(通过挂载宿主机docker命令和sock)
4、kubectl、helm
5、连接jenkins的工具

jenkins slave下的内容

以下为旧版本废弃方法,新版本可以Jenkins调度K8S API自动完成容器构建

===========================================================================

03-微服务发布平台

涉及六个文件:

•Dockerfile:构建镜像
•jenkins-slave:其实是个shell脚本,用来启动slave.jar,下载地址:https://github.com/jenkinsci/docker-jnlp-slave/blob/master/jenkins-slave(后改为jenkins-agent)
•settings.xml:修改maven官方源为阿里云源
•slave.jar:agent程序,接受master下发的任务,下载地址:http://$jenkinsip:$port/jnlpJars/slave.jar(项目里是http://192.168.1.10:30006/jnlpJars/slave.jar[后改为agent.jar])
•helm和kubectl客户端工具

Dockerfile

FROM centos:7
LABEL maintainer XXXXXX

RUN yum install -y java-11-openjdk maven curl git libtool-ltdl-devel && \
    yum clean all && \
    rm -rf /var/cache/yum/* && \
    mkdir -p /usr/share/jenkins

COPY slave.jar /usr/share/jenkins/slave.jar # 用于启动jenkin-slave
COPY jenkins-slave /usr/bin/jenkins-slave 
COPY settings.xml /etc/maven/settings.xml # mvn 配置阿里云仓库配置文件
RUN chmod +x /usr/bin/jenkins-slave 
COPY helm kubectl /usr/bin/ # helm命令添加到容器中

ENTRYPOINT ["jenkins-slave"] # 进入容器执行jenkin-slave

构建并推送到镜像仓库(旧版本为jdk1.8):

docker build -t hub.ljh.com/library/jenkins-slave-jdk:11 .
docker push hub.ljh.com/library/jenkins-slave-jdk:11
03-微服务发布平台

===========================================================================

以上通过Dockerfile创建容器方法已经废弃

测试主从架构是否正常

首先,我们创建一个多集群config文件添加到pod中

multi-kube-config

cp ~/.kube/config ./multi-cluster.yaml # 如果有其他集群,可以通过kubectl继续添加其他集群,这里我们不添了

# 生成secret,之后会挂载到k8s创建的容器中:
kubectl create secret generic multi-kube-config --from-file=/etc/kubernetes/pki/multi-cluster.yaml

新建项目(demo2)->流水线->Pipeline脚本(可生成示例)

03-微服务发布平台
03-微服务发布平台

内容如下:

pipeline {
  agent {
    kubernetes {
      cloud 'kubernetes'
      slaveConnectTimeout 1200
      yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
    - args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
      # 动态生成 Jenkins Slave pod,jnlp镜像为master创建的slave,负责临时产生相关任务命令,JNLP方式连接salve,不需要master必须能够ssh连接到slave,只需要两者能够ping通即可。这种连接方式的slave还可以作为服务运行在slave的机器上,开始pod会被创建,最后pod会被消除
      image: 'jenkins/inbound-agent:alpine-jdk11' # 镜像版本需要注意和jenkins版本适配
      name: jnlp
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "volume-2"
          readOnly: false
        - mountPath: "/etc/hosts"
          name: "volume-hosts"
          readOnly: false        
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      # build镜像承担项目构建任务
      image: "registry.cn-beijing.aliyuncs.com/citools/maven:3.5.3"
      imagePullPolicy: "IfNotPresent"
      name: "build"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "volume-2"
          readOnly: false
        - mountPath: "/root/.m2/" # 默认缓存目录
          name: "volume-maven-repo"
          readOnly: false
        - mountPath: "/etc/hosts"
          name: "volume-hosts"
          readOnly: false
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      # kubectl镜像承担更新镜像任务,因为要在docker中创建docker,所以将宿主机的docker.sock通过volume-docker方式挂载到容器中,并且将kubeconfig配置到容器中
      image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
      imagePullPolicy: "IfNotPresent"
      name: "kubectl"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "volume-2"
          readOnly: false
        - mountPath: "/var/run/docker.sock"
          name: "volume-docker"
          readOnly: false
        - mountPath: "/mnt/.kube/" #将kubernets config挂载到容器中,也可以通过config file provider插件配置config
          name: "volume-kubeconfig"
          readOnly: false
        - mountPath: "/etc/hosts"
          name: "volume-hosts"
          readOnly: false
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      # docker容器承担推送镜像任务,需要使用到docker命令,所以需要挂载docker.sock
      image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
      imagePullPolicy: "IfNotPresent"
      name: "docker"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "volume-2"
          readOnly: false
        - mountPath: "/var/run/docker.sock"
          name: "volume-docker"
          readOnly: false
        - mountPath: "/etc/hosts"
          name: "volume-hosts"
          readOnly: false
  restartPolicy: "Never"
  securityContext: {}
  volumes:
    - hostPath:
        path: "/var/run/docker.sock"
      name: "volume-docker"
    - hostPath:
        path: "/usr/share/zoneinfo/Asia/Shanghai"
      name: "volume-2"
    - hostPath:
        path: "/etc/hosts"
      name: "volume-hosts"
    - name: "volume-maven-repo"
      hostPath:
        path: "/opt/m2"
    - name: "volume-kubeconfig"
      secret:
        secretName: "multi-kube-config"
'''	
    }
  }
    
    stages {
        stage('Testing kubernetes......') {
            steps {
                sh 'hostname'
            }
        }
    }
}

这段代码定义了一个在Kubernetes环境中运行的Jenkins Pipeline。它包含了一系列容器,每个容器负责不同的任务,如构建、推送镜像、更新镜像等。容器之间通过挂载卷(volumeMounts)和环境变量进行通信。整个Pipeline定义了一套流程,用于在Kubernetes集群中执行持续集成和部署任务。

下面详细解释下上述代码:

1.Jenkins代理配置:

agent {
    kubernetes {
        cloud 'kubernetes'
        slaveConnectTimeout 1200
        yaml '''
        apiVersion: v1
        kind: Pod
        spec:
          ...
        '''
    }
}
  • agent: 指定Jenkins Pipeline的代理。
  • kubernetes: 指定代理将在Kubernetes环境中运行。
  • cloud 'kubernetes': 引用Jenkins中的Kubernetes云配置。
  • slaveConnectTimeout 1200: 设置连接到Kubernetes从节点的超时时间。

2.Jenkins从节点Pod配置:

apiVersion: v1
kind: Pod
spec:
  containers:
    - args: ['$(JENKINS_SECRET)', '$(JENKINS_NAME)']
      image: 'jenkins/inbound-agent:alpine-jdk11'
      name: jnlp
      ...
    - command:
        - "cat"
      image: "registry.cn-beijing.aliyuncs.com/citools/maven:3.5.3"
      name: "build"
      ...
    - command:
        - "sh"
        - "-c"
        - "wget https://get.helm.sh/helm-v3.2.1-linux-amd64.tar.gz && tar zxvf helm-v3.2.1-linux-amd64.tar.gz && mv linux-amd64/helm /usr/local/sbin/ && cat"
      image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
      name: "kubectl"
      ...
    - command:
        - "cat"
      image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
      name: "docker"
      ...
  restartPolicy: "Never"
  securityContext: {}
  volumes:
    ...
  • 这个YAML配置定义了一个Kubernetes Pod,包含多个容器执行不同的任务。
  • jnlp: Jenkins从节点容器,负责处理Jenkins JNLP连接。
  • build: Maven项目构建任务的容器。
  • kubectl: 用于更新镜像的容器,包括挂载Docker套接字和kubeconfig。
  • docker: 用于推送镜像的容器,需要挂载Docker套接字。
  • restartPolicy: "Never": 指定Pod不应自动重新启动。
  • volumes: 定义了Docker套接字、时区信息、主机文件、Maven仓库、kubeconfig、证书和共享Helm目录的卷挂载。

jenkins会动态创建slave pod执行任务最后删除pod

k describe pod demo2-xxxxxxxxxx

03-微服务发布平台

成功输出hostname测试成功:

03-微服务发布平台

Jenkins Pipeline流水线

Pipeline介绍

Jenkins Pipeline是一套运行工作流框架,将原本独立运行单个或者多个节点的任务链接起来,实现单个任务难以完成的复杂流程编排和可视化。

  • Jenkins Pipeline是一套插件,支持在Jenkins中实现持续集成和持续交付;
  • Pipeline通过特定语法对简单到复杂的传输管道进行建模;
  • Jenkins Pipeline的定义被写入一个文本文件,称为Jenkinsfile。
03-微服务发布平台

Pipeline语法

声明式

03-微服务发布平台

脚本式

03-微服务发布平台

声明式:支持大部分Groovy,具有丰富的语法特性,易于编写和设计。pipeline { }
脚本式:遵循与Groovy相同语法。node { }

Pipeline示例

Stages是Pipeline中最主要的组成部分,Jenkins将会按照Stages中描述的顺序从上往下的执行。

Stage:阶段,一个Pipeline 可以划分为若干个Stage,每个Stage 代表一组操作,比如:Build、Test、Deploy

Steps:步骤,Steps 是最基本的操作单元,可以是打印一句话,也可以是构建一个Docker 镜像,由各类Jenkins 插件提供,比如命令:sh ‘mvn',就相当于我们平时shell 终端中执行mvn命令一样。

pipeline {
  agent any
  stages {
    stage('Build') {
      steps {
        echo 'build...'
      }
    }
    stage('Test') {
      steps {
        echo 'test...'
      }
    }
    stage('Deploy') {
      steps {
        echo 'deploy...'
      }
    }
  }
}

流水线自动发布微服务项目

发布需求

在将微服务项目自动化部署到K8s平台会有这些需求:

  • 尽量完全自动化部署,无需过多人工干预
  • 可以选择升级某个、某些微服务
  • 在部署、升级微服务时,可对微服务某些特性做配置,例如命名空间、副本数量
03-微服务发布平台

实现思路

根据微服务不同点,我们用一个yaml部署多个微服务需要修改哪些值?

  • 服务名称
  • 仓库地址
  • 启动方式
  • 镜像
  • 端口
  • 是否需要service
  • 副本数
  • 名称空间

Pipeline编写思路:

在微服务架构中,会涉及几个、几十个微服务,如果每个服务都创建一个item,势必给运维维护成本增加很大,因此需要编写一个通用Pipeline脚本,将这些微服务部署差异化部分使用Jenkins参数化,人工交互确认发布的微服务、环境配置等。

但这只是解决用户交互层面,在K8s实际部署项目用YAML创建对应资源,现在问题是如何接收用户交互参数,自动化生成YAML文件,这就会用到Helm完成YAML文件高效复用和微服务部署。

03-微服务发布平台
03-微服务发布平台

编写流水线脚本

先来展示下完整的jenkins file:

#!/usr/bin/env groovy

//相关变量
def git_url = "http://192.168.1.14:88/root/microservice.git"
def git_auth = "52812e4d-45c9-40cd-ba03-c60d2c33a4ba"

def registry = "hub.ljh.com"
def project = "microservice"
def harbor_auth = "42c55700-a3d4-4d2e-8dbb-d1a2799674a7"

def image_pull_secret = "registry-pull-secret"
def k8s_auth = "48b53fb0-d168-4674-838b-44f65f8d2606"

def gateway_domain_name = "gateway.ctnrs.com"
def portal_domain_name = "portal.ctnrs.com"

pipeline {
  agent {
    kubernetes {
      cloud 'kubernetes'
      slaveConnectTimeout 1200
      yaml '''
apiVersion: v1
kind: Pod
spec:
  containers:
    - args: [\'$(JENKINS_SECRET)\', \'$(JENKINS_NAME)\']
      # 动态生成 Jenkins Slave pod,jnlp镜像为master创建的slave,负责临时产生相关任务命令,JNLP方式连接salve,不需要master必须能够ssh连接到slave,只需要两者能够ping通即可。这种连接方式的slave还可以作为服务运行在slave的机器上,开始pod会被创建,最后pod会被消除
      image: 'jenkins/inbound-agent:alpine-jdk11'
      name: jnlp
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "volume-2"
          readOnly: false
        - mountPath: "/etc/hosts"
          name: "volume-hosts"
          readOnly: false        
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      # build镜像承担项目构建任务
      image: "registry.cn-beijing.aliyuncs.com/citools/maven:3.5.3"
      imagePullPolicy: "IfNotPresent"
      name: "build"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "volume-2"
          readOnly: false
        - mountPath: "/root/.m2/"
          name: "volume-maven-repo"
          readOnly: false
        - mountPath: "/etc/hosts"
          name: "volume-hosts"
          readOnly: false
    - command:
        - "sh" 
        - "-c" 
        - "wget https://get.helm.sh/helm-v3.2.1-linux-amd64.tar.gz && tar zxvf helm-v3.2.1-linux-amd64.tar.gz && mv linux-amd64/helm /usr/local/sbin/ && cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      # kubectl镜像承担更新镜像任务,因为要在docker中创建docker,所以将宿主机的docker.sock通过volume-docker方式挂载到容器中,并且将kubeconfig配置到容器中
      image: "registry.cn-beijing.aliyuncs.com/citools/kubectl:self-1.17"
      imagePullPolicy: "IfNotPresent"
      name: "kubectl"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "volume-2"
          readOnly: false
        - mountPath: "/var/run/docker.sock"
          name: "volume-docker"
          readOnly: false
        - mountPath: "/mnt/.kube/"
          name: "volume-kubeconfig"
          readOnly: false
        - mountPath: "/etc/hosts"
          name: "volume-hosts"
          readOnly: false
        - name: "crt-volume"
          mountPath: /tmp
          readOnly: false
        - name: "helm-share"
          mountPath: /usr/local/sbin/
          readOnly: false
    - command:
        - "cat"
      env:
        - name: "LANGUAGE"
          value: "en_US:en"
        - name: "LC_ALL"
          value: "en_US.UTF-8"
        - name: "LANG"
          value: "en_US.UTF-8"
      # docker容器承担推送镜像任务,需要使用到docker命令,所以需要挂载docker.sock
      image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
      imagePullPolicy: "IfNotPresent"
      name: "docker"
      tty: true
      volumeMounts:
        - mountPath: "/etc/localtime"
          name: "volume-2"
          readOnly: false
        - mountPath: "/var/run/docker.sock"
          name: "volume-docker"
          readOnly: false
        - mountPath: "/etc/hosts"
          name: "volume-hosts"
          readOnly: false
        - name: "crt-volume"
          mountPath: /tmp
          readOnly: false
        - name: "helm-share"
          mountPath: /usr/local/sbin/
          readOnly: false

  restartPolicy: "Never"
  securityContext: {}
  volumes:
    - hostPath:
        path: "/var/run/docker.sock"
      name: "volume-docker"
    - hostPath:
        path: "/usr/share/zoneinfo/Asia/Shanghai"
      name: "volume-2"
    - hostPath:
        path: "/etc/hosts"
      name: "volume-hosts"
    - name: "volume-maven-repo"
      hostPath:
        path: "/opt/m2"
    - name: "volume-kubeconfig"
      secret:
        secretName: "multi-kube-config"
    - name: "crt-volume"
      projected:
        sources:
        - secret:
            name: server-crt
        - secret:
            name: server-key
    - name: helm-share
      emptyDir: {}

'''	
    }
  }

    parameters {
        gitParameter branch: '', branchFilter: '.*', defaultValue: 'origin/master', description: '选择发布的分支', name: 'Branch', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH'        
        extendedChoice defaultValue: 'none', description: '选择发布的微服务', \
          multiSelectDelimiter: ',', name: 'Service', type: 'PT_CHECKBOX', \
          value: 'gateway-service:9999,portal-service:8080,product-service:8010,order-service:8020,stock-service:8030'
        choice (choices: ['ms', 'demo'], description: '部署模板', name: 'Template')
        choice (choices: ['1', '3', '5', '7'], description: '副本数', name: 'ReplicaCount')
        choice (choices: ['ms'], description: '命名空间', name: 'Namespace')
    }
    
    stages {
        stage('拉取代码'){
            steps {
                checkout([$class: 'GitSCM', 
                branches: [[name: "${params.Branch}"]], 
                doGenerateSubmoduleConfigurations: false, 
                extensions: [], submoduleCfg: [], 
                userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]
                ])
            }
        }
        stage('代码编译') {
            // 编译指定服务
            steps {
              container(name: 'build') {
                sh """
                echo "building..."
                mvn clean package -Dmaven.test.skip=true
                """
              }
            }
        }
        stage('构建镜像') {
          steps {
              withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
                container(name: 'docker') {
                  sh """
                  docker login -u ${username} -p '${password}' ${registry}
                  for service in \$(echo ${Service} |sed 's/,/ /g'); do
                      service_name=\${service%:*}
                      image_name=${registry}/${project}/\${service_name}:${BUILD_NUMBER}
                      cd \${service_name}
                      if ls |grep biz &>/dev/null; then
                          cd \${service_name}-biz
                      fi
                      docker build -t \${image_name} .
                      docker push \${image_name}
                      cd ${WORKSPACE}
                  done
                  # 添加私有chart仓库
                  helm repo add --ca-file /tmp/server.crt --cert-file /tmp/server.crt --key-file /tmp/server.key  --username ${username} --password ${password} myrepo https://${registry}/chartrepo/${project}
                  """
                }
                configFileProvider([configFile(fileId: "${k8s_auth}", targetLocation: "admin.kubeconfig")]){
                  container(name: 'kubectl') {
                    sh """
                    # 添加镜像拉取认证
                    kubectl create secret docker-registry ${image_pull_secret} --docker-username=${username} --docker-password=${password} --docker-server=${registry} -n ${Namespace} --kubeconfig admin.kubeconfig |true
                    """
                  }
                }
              }

          }
        }
        stage('Helm部署到K8S') {
          steps {
            container(name: 'docker') {
              sh """
              common_args="-n ${Namespace} --kubeconfig admin.kubeconfig"
              for service in  \$(echo ${Service} |sed 's/,/ /g'); do
                service_name=\${service%:*}
                service_port=\${service#*:}
                image=${registry}/${project}/\${service_name}
                tag=${BUILD_NUMBER}
                helm_args="\${service_name} --set image.repository=\${image} --set image.tag=\${tag} --set replicaCount=${replicaCount} --set imagePullSecrets[0].name=${image_pull_secret} --set service.targetPort=\${service_port} myrepo/${Template}"

                # 判断是否为新部署
                if /usr/local/sbin/helm history \${service_name} \${common_args} &>/dev/null;then
                  action=upgrade
                else
                  action=install
                fi

                # 针对服务启用ingress
                if [ \${service_name} == "gateway-service" ]; then
                  /usr/local/sbin/helm \${action} \${helm_args} \
                  --set ingress.enabled=true \
                  --set ingress.host=${gateway_domain_name} \
                   \${common_args}
                elif [ \${service_name} == "portal-service" ]; then
                  /usr/local/sbin/helm \${action} \${helm_args} \
                  --set ingress.enabled=true \
                  --set ingress.host=${portal_domain_name} \
                   \${common_args}
                else
                  /usr/local/sbin/helm \${action} \${helm_args} \${common_args}
                fi
              done
              """
            }
            configFileProvider([configFile(fileId: "${k8s_auth}", targetLocation: "admin.kubeconfig")]){
              container(name: 'kubectl') {
                sh """
                common_args="-n ${Namespace} --kubeconfig admin.kubeconfig"
                # 查看Pod状态
                sleep 10
                kubectl get pods \${common_args}
                """
              }
            }
          }
        }
    }
}

将脚本添加到我们的pipeline script中

重点修改这几个变量:

def registry = "hub.ljh.com"
// 项目
def project = "microservice"
def git_url = "http://192.168.1.14:88/root/microservice.git"
def gateway_domain_name = "gateway.ctnrs.com"
def portal_domain_name = "portal.ctnrs.com"
// 认证
def image_pull_secret = "registry-pull-secret"
def harbor_auth = "42c55700-a3d4-4d2e-8dbb-d1a2799674a7"
def git_auth = "52812e4d-45c9-40cd-ba03-c60d2c33a4ba"
// ConfigFileProvider ID
def k8s_auth = "48b53fb0-d168-4674-838b-44f65f8d2606"

对上述脚本的相关解释:

先来关注这部分脚本内容的涵义:

    parameters {
        gitParameter branch: '', branchFilter: '.*', defaultValue: 'origin/master', description: '选择发布的分支', name: 'Branch', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'PT_BRANCH'        
        extendedChoice defaultValue: 'none', description: '选择发布的微服务', \
          multiSelectDelimiter: ',', name: 'Service', type: 'PT_CHECKBOX', \
          value: 'gateway-service:9999,portal-service:8080,product-service:8010,order-service:8020,stock-service:8030'
        choice (choices: ['ms', 'demo'], description: '部署模板', name: 'Template')
        choice (choices: ['1', '3', '5', '7'], description: '副本数', name: 'ReplicaCount')
        choice (choices: ['ms'], description: '命名空间', name: 'Namespace')
    }

这段代码定义了几个不同类型的参数,包括选择分支、微服务、部署模板、副本数和命名空间

parameters是jenkins内置参数,可以在项目中通过下面方式创建,这里使用之前创建的demo测试:

这里我们选择一个单选框Choice Parameter,多选是

03-微服务发布平台
03-微服务发布平台
03-微服务发布平台

保存后进行构建测试:

03-微服务发布平台

查看输出:

03-微服务发布平台

我们再添加一个多选框测试:

03-微服务发布平台
03-微服务发布平台

构建测试:

03-微服务发布平台
03-微服务发布平台

pipeline参数化编写脚本:

创建一个pipleline-demo,进行可视化编写脚本:

03-微服务发布平台

首先动态获取github上的Branch:

03-微服务发布平台
03-微服务发布平台

生成脚本:

parameters {
  gitParameter branch: '', branchFilter: '.*', defaultValue: 'origin/master', description: '选择发布的分支', name: 'Branch', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '*', type: 'GitParameterDefinition'
}

下面创建的是多选框生成过程:

03-微服务发布平台

生成脚本:

03-微服务发布平台
parameters {
  extendedChoice description: '多选', multiSelectDelimiter: ',', name: 'select2', quoteValue: false, saveJSONParameterToFile: false, type: 'PT_CHECKBOX', value: 'dev,test,prod', visibleItemCount: 5
}

下面是一些单选函数的生成过程:

03-微服务发布平台
        choice (choices: ['ms', 'demo'], description: '部署模板', name: 'Template')
        choice (choices: ['1', '3', '5', '7'], description: '副本数', name: 'ReplicaCount')
        choice (choices: ['ms'], description: '命名空间', name: 'Namespace')

上述相关参数函数最终生成的选择界面如下图:

03-微服务发布平台

流水线脚本与源代码一起版本管理

下面看代码拉取这部分:

    stages {
        stage('拉取代码'){
            steps {
                checkout([$class: 'GitSCM', 
                branches: [[name: "${params.Branch}"]], 
                doGenerateSubmoduleConfigurations: false, 
                extensions: [], submoduleCfg: [], 
                userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]
                ])
            }
        }

代码生成器使用方式:

03-微服务发布平台

这段代码的主要作用是在Jenkins Pipeline的一个阶段中执行代码拉取操作。在步骤中,使用GitSCM插件进行代码拉取,具体配置包括:

branches: 指定要拉取的分支,分支名称从参数${params.Branch}获取。
doGenerateSubmoduleConfigurations: 控制是否生成子模块配置,此处设置为false。
extensions, submoduleCfg: 针对子模块的配置,此处为空列表。
userRemoteConfigs: 指定远程仓库配置,包括凭证ID(${git_auth})和仓库URL(${git_url})。

git_auth和git_url参数定义:

# 定义了gitlab项目地址,jenkins从此项目获取代码
def git_url = "http://192.168.1.14:88/root/microservice.git"
# 配置gitlab安全凭证,通过安全凭证获得拉取代码权限
def git_auth = "52812e4d-45c9-40cd-ba03-c60d2c33a4ba"

git_auth配置位置如下:

03-微服务发布平台

接下来为编译代码步骤,没什么好讲的:

stage('代码编译') {
    // 编译指定服务
    steps {
        container(name: 'build') {
            sh """
            echo "开始编译..."
            mvn clean package -Dmaven.test.skip=true
            """
        }
    }
}

下面这段代码用于构建镜像。

stage('构建镜像') {
    steps {
        withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
            container(name: 'docker') {
                sh """
                docker login -u ${username} -p '${password}' ${registry}
                for service in \$(echo ${Service} | sed 's/,/ /g'); do
                    service_name=\${service%:*}
                    image_name=${registry}/${project}/\${service_name}:${BUILD_NUMBER}
                    cd \${service_name}
                    if ls | grep biz &>/dev/null; then
                        cd \${service_name}-biz
                    fi
                    docker build -t \${image_name} .
                    docker push \${image_name}
                    cd ${WORKSPACE}
                done
                # 添加私有chart仓库
                helm repo add --ca-file /tmp/server.crt --cert-file /tmp/server.crt --key-file /tmp/server.key --username ${username} --password ${password} myrepo https://${registry}/chartrepo/${project}
                """
            }
            configFileProvider([configFile(fileId: "${k8s_auth}", targetLocation: "admin.kubeconfig")]){
                container(name: 'kubectl') {
                    sh """
                    # 添加镜像拉取认证
                    kubectl create secret docker-registry ${image_pull_secret} --docker-username=${username} --docker-password=${password} --docker-server=${registry} -n ${Namespace} --kubeconfig admin.kubeconfig | true
                    """
                }
            }
        }
    }
}

凭据配置并代码生成:

harbor凭据配置可以使用用户名密码方式:

withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {

使用Jenkins的凭据插件,从Jenkins凭据存储中获取Harbor仓库的用户名和密码。

03-微服务发布平台

使用代码生成器如下:

03-微服务发布平台

Docker容器中的构建步骤:

container(name: 'docker') {
    sh """
    docker login -u ${username} -p '${password}' ${registry}
    for service in \$(echo ${Service} | sed 's/,/ /g'); do
        service_name=\${service%:*}
        image_name=${registry}/${project}/\${service_name}:${BUILD_NUMBER}
        cd \${service_name}
        if ls | grep biz &>/dev/null; then
            cd \${service_name}-biz
        fi
        docker build -t \${image_name} .
        docker push \${image_name}
        cd ${WORKSPACE}
    done
    # 添加私有chart仓库
    helm repo add --ca-file /tmp/server.crt --cert-file /tmp/server.crt --key-file /tmp/server.key --username ${username} --password ${password} myrepo https://${registry}/chartrepo/${project}
    """
}

在名为 'docker' 的Docker容器中执行以下操作:

  • 使用Docker登录到指定的Harbor仓库。
  • 遍历指定的微服务列表,构建并推送每个微服务的镜像。
  • 添加私有Chart仓库到Helm中,以便后续使用。

helm在docker harbor配置方式如下:

首先要求:版本按照https://www.ljh.cool/4978.html文档部署harbor,即v2.3.5版本,过高版本和过低版本都有可能不支持helm chat功能

# 支持helmchart需要添加安装参数:--with-chartmuseum
cd /usr/local/harbor
./install.sh --with-chartmuseum

首先,们需要制作chart包:

03-微服务发布平台

配置如下:

# Chart.yaml
apiVersion: v2
appVersion: 0.1.0
description: microservice
name: ms
type: application
version: 0.1.0
# ================================================================================
# values.yaml
env:
  JAVA_OPTS: -Xmx1g
image:
  pullPolicy: IfNotPresent
  repository: lizhenliang/java-demo
  tag: latest
imagePullSecrets: []
ingress:
  annotations:
    kubernetes.io/ingress.class: "nginx" #声名需要使用nginx方式解析ingress
    nginx.ingress.kubernetes.io/proxy-body-size: 100m
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
  enabled: false
  host: example.ctnrs.com
  tls: []
nodeSelector: {}
replicaCount: 3
resources:
  limits:
    cpu: 1000m
    memory: 1Gi
  requests:
    cpu: 100m
    memory: 128Mi
service:
  port: 80
  targetPort: 80
  type: ClusterIP
tolerations: []
# ================================================================================
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "demo.fullname" . }}
  labels:
    {{- include "demo.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "demo.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "demo.selectorLabels" . | nindent 8 }}
    spec:
    {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
    {{- end }}
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          env:
          {{- range $k, $v := .Values.env }}
            - name: {{ $k }}
              value: {{ $v | quote }}
          {{- end }}
          ports:
            - name: http
              containerPort: {{ .Values.service.targetPort }}
              protocol: TCP
          livenessProbe:
            tcpSocket:
              port: http
            initialDelaySeconds: 60
            periodSeconds: 10
          readinessProbe:
            tcpSocket:
              port: http
            initialDelaySeconds: 60
            periodSeconds: 10
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
# ================================================================================
# templates/_helpers.tpl
{{- define "demo.fullname" -}}
{{- .Chart.Name -}}-{{ .Release.Name }}
{{- end -}}

{{/*
公用标签
*/}}
{{- define "demo.labels" -}}
app: {{ template "demo.fullname" . }}
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
release: "{{ .Release.Name }}"
{{- end -}}

{{/*
标签选择器
*/}}
{{- define "demo.selectorLabels" -}}
app: {{ template "demo.fullname" . }}
release: "{{ .Release.Name }}"
{{- end -}}
# ================================================================================
# templates/ingress.yaml
{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "demo.fullname" . }}
  labels:
    {{- include "demo.labels" . | nindent 4 }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
{{- if .Values.ingress.tls }}
  tls:
    - hosts:
        - {{ .Values.ingress.host }}
      secretName: {{ .Values.ingress.tls.secretName }}
{{- end }}
  rules:
    - host: {{ .Values.ingress.host }}
      http:
        paths:
        - path: /
          pathType: Prefix
          backend:
            service:
              name: {{ include "demo.fullname" . }}
              port:
                number: {{ .Values.service.port }}
{{- end }}
# ================================================================================
# templates/NOTES.txt
URL:
{{- if .Values.ingress.enabled }}
  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .Values.ingress.host }}
{{- end }}
{{- if contains "NodePort" .Values.service.type }}
  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "demo.fullname" . }})
  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT
{{- end }}
# ================================================================================
# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "demo.fullname" . }}
  labels:
    {{- include "demo.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    {{- include "demo.selectorLabels" . | nindent 4 }}

打包chart:

tar czvf ms-0.1.0.tgz ms

将ms-0.1.0.tgz包传送至harbor:

03-微服务发布平台
03-微服务发布平台

harbor如果配置了https证书,需要添加--ca-file 、--cert-file 、--key-file 参数,其中 /tmp/server.crt和--cert-file指定的是harbor镜像仓库中/data/cert下的server.crt和server.key,需要通过secret以文件挂载方式挂到kubernets集群pod中/tmp目录下,上面挂载secret到tmp方式如下:

生成secret:

kubectl create secret generic server-crt --from-file=server.crt
kubectl create secret generic server-key --from-file=server.key

引用secret的步骤:

apiVersion: v1
kind: Pod
metadata:
  name: harbor-ssl
  labels:
    name: harbor-ssl
spec:
  containers:
  - name: docker
    image: "registry.cn-beijing.aliyuncs.com/citools/docker:19.03.9-git"
    volumeMounts:
      - name: "crt-volume"
        mountPath: /tmp
        readOnly: false
  volumes:
    - name: "crt-volume"
      projected:
        sources:
        - secret:
            name: server-crt
        - secret:
            name: server-key

Kubectl容器中的配置文件和认证步骤:

k8s_auth作为变量应用:

def k8s_auth = "48b53fb0-d168-4674-838b-44f65f8d2606"

k8s_auth配置过程:

03-微服务发布平台
03-微服务发布平台
03-微服务发布平台

代码生成:

03-微服务发布平台
configFileProvider([configFile(fileId: "${k8s_auth}", targetLocation: "admin.kubeconfig")]) {
    container(name: 'kubectl') {
        sh """
        # 添加镜像拉取认证
        kubectl create secret docker-registry ${image_pull_secret} --docker-username=${username} --docker-password=${password} --docker-server=${registry} -n ${Namespace} --kubeconfig admin.kubeconfig | true
        """
    }
}
  • 使用Jenkins的配置文件提供者插件,将指定target的Kubernetes配置文件(admin.kubeconfig)注入到名为 'kubectl' 的容器中。
  • 在 'kubectl' 容器中执行命令,创建用于拉取镜像的认证信息的Kubernetes Secret。

注意:还记得我们测试的时候用的是multi-kube-config使用名字为volume-kubeconfig,secret方式挂载到kubectl容器中吗?这里我们如果使用这种方式加载kubeconfig文件,后续可以通过cat ${KUBECONFIG_PATH} && > /tmp/1.yaml/usr/local/bin/kubectl config use-context ${CLUSTER} --kubeconfig=/tmp/1.yaml(因为权限问题需放在/tmp目录下)方式引用配置文件切换多集群,这里我们使用jenkins自带的配置插件配置config方式更好一些,所以这个方式没有引用,之后可以选择将挂载的配置删除掉即可,这里我就不删除这部分内容了

# 删除部分如下:
      volumeMounts:
        ...
        - mountPath: "/mnt/.kube/"
          name: "volume-kubeconfig"
  volumes:
    - name: "volume-kubeconfig"
      secret:
        secretName: "multi-kube-config"

下面看下Helm部署到K8S代码:

        stage('Helm部署到K8S') {
          steps {
            container(name: 'docker') {
              sh """
              common_args="-n ${Namespace} --kubeconfig admin.kubeconfig"
              for service in  \$(echo ${Service} |sed 's/,/ /g'); do
                service_name=\${service%:*}
                service_port=\${service#*:}
                image=${registry}/${project}/\${service_name}
                tag=${BUILD_NUMBER}
                helm_args="\${service_name} --set image.repository=\${image} --set image.tag=\${tag} --set replicaCount=${replicaCount} --set imagePullSecrets[0].name=${image_pull_secret} --set service.targetPort=\${service_port} myrepo/${Template}"

                # 判断是否为新部署
                if /usr/local/sbin/helm history \${service_name} \${common_args} &>/dev/null;then
                  action=upgrade
                else
                  action=install
                fi

                # 针对服务启用ingress
                if [ \${service_name} == "gateway-service" ]; then
                  /usr/local/sbin/helm \${action} \${helm_args} \
                  --set ingress.enabled=true \
                  --set ingress.host=${gateway_domain_name} \
                   \${common_args}
                elif [ \${service_name} == "portal-service" ]; then
                  /usr/local/sbin/helm \${action} \${helm_args} \
                  --set ingress.enabled=true \
                  --set ingress.host=${portal_domain_name} \
                   \${common_args}
                else
                  /usr/local/sbin/helm \${action} \${helm_args} \${common_args}
                fi
              done
              """
            }
            configFileProvider([configFile(fileId: "${k8s_auth}", targetLocation: "admin.kubeconfig")]){
              container(name: 'kubectl') {
                sh """
                common_args="-n ${Namespace} --kubeconfig admin.kubeconfig"
                # 查看Pod状态
                sleep 10
                kubectl get pods \${common_args}
                """
              }
            }
          }
        }
  1. common_args定义:
    • 定义了一些通用的参数,包括Kubernetes命名空间和kubeconfig文件路径。
  2. for循环:
    • 遍历指定的服务列表(通过${Service}参数),对每个服务执行以下操作。
  3. Helm参数设置:
    • 根据服务的不同设置Helm的参数,包括镜像信息、副本数量、镜像拉取凭证等。
  4. 判断部署类型(新部署或升级):
    • 使用/usr/local/sbin/helm history命令判断服务是否已经部署过,从而确定是执行升级(upgrade)还是新部署(install)。
  5. 启用Ingress:
    • 针对不同的服务,设置Ingress参数,以启用Ingress功能,并指定相应的域名。
  6. 查看Pod状态:
    • 使用kubectl get pods命令查看Kubernetes中Pod的状态。

测试结果:

首先在k8s上部署eureka和mysql服务,按照https://www.ljh.cool/40743.html中 “第五步:在K8s中部署Erureka集群和MySQL数据库”部署即可:

其他服务通过自动化构建实现:

注意:生产环境中一定要先构建后端微服务portal-service、order-service、stock-service,然后部署gateway-service,最后部署portal-service,严格按照调用顺序来部署,部署失败后,一定要删除对应服务的service和ingress,再进行构建,下面步骤也强烈建议按照部署顺序构建,否则会出现“接口异常”等错误

03-微服务发布平台

检查部署状态:

03-微服务发布平台

查看eureka注册状态:

03-微服务发布平台

登陆前端页面:

03-微服务发布平台

采购一个手机进行功能测试:

03-微服务发布平台
03-微服务发布平台

更新版本测试:

在如下路径中,修改版本

03-微服务发布平台

重新构建:

03-微服务发布平台
03-微服务发布平台

微服务回滚

添加一个ms-rollback的项目,使用过pipeline

03-微服务发布平台
03-微服务发布平台

因为不同微服务的镜像版本不都相同,所以下面这种版本选择设置并不能适配适合微服务情况,需要两个参数进行联动,需要使用Active Choices插件

03-微服务发布平台

安装插件:

03-微服务发布平台

所以我们选择联动参数:

03-微服务发布平台

注意:此脚本有未被审批的报错:查看审批日志: 在Jenkins中,可以查看审批日志,了解哪些脚本被拒绝了。导航到 "Manage Jenkins" > "In-process Script Approval"(或类似的选项),查看是否有相关的审批请求,以及为什么被拒绝。如果被拒绝,审批通过即可,审批通过如下图:

03-微服务发布平台

上图脚本内容:

需要通过shell获取的列表参数化成grovy脚本列表,/var/jenkins_home是我们jenkins的数据持久化路径

cmd = "/bin/bash /var/jenkins_home/get_tags.sh microservice ${Service}"
tags_list = cmd.execute().text.tokenize()
return tags_list

如何获取到docker harbor镜像仓库的tag?

  • 1、通过获取docker harbor api获取tag
  • 2、使用shell脚本方式获取tag

get_tags.sh是在jenkins容器里,内容如下:

#! /bin/bash
HARBOR_ADDR=hub.ljh.com
USERNAME=admin
PASSWORD=Harbor12345
PROJECT_NAME=$1
SERVICE_NAME=$2 #镜像名称要与选择框名称一样

curl -k -s -X GET -u "${USERNAME}:${PASSWORD}" "https://${HARBOR_ADDR}/v2/${PROJECT_NAME}/${SERVICE_NAME}/tags/list" | awk -F '[][]' '{split($2,array,",");for(tag in array)print array[tag]}' | sed 's/"//g' | sort -nr | head -5

首先测试curl命令获取api:

03-微服务发布平台

-F '[][]': 指定方括号 [] 为字段分隔符。

03-微服务发布平台

{split($2,array,",");for(tag in array)print array[tag]}: 在方括号中的内容(即第二个字段)使用逗号 , 进行分割,并将分割后的元素放入数组 array。然后,通过循环遍历数组,并逐行打印数组中的元素。

03-微服务发布平台

版本最好是倒序,并保留五个版本,脚本根据位置参数调用harbor api获取镜像列表,最后测试脚本

03-微服务发布平台

我们的jenkins通过nfs构建的pv持久化在192.168.1.12这台机器上,目录在/ifs/kubernetes/jenkins-data,

将脚本放在这个目录下即可(也可以使用kubectl cp命令放在jenkins pod的容器中),

配置基于参数Service进行联动

03-微服务发布平台

成功获取:

03-微服务发布平台

设置基于helm的回滚,先查看下历史版本:

helm list -n ms
helm history portal-service -n ms
03-微服务发布平台

发布者:LJH,转发请注明出处:https://www.ljh.cool/40863.html

(0)
上一篇 2024年2月20日 下午6:50
下一篇 2024年3月11日 下午6:53

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

评论列表(5条)

  • bought instagram followers
    bought instagram followers 2024年3月6日 上午8:16

    You are so cool! I do not think I have read a single thing like that before.
    So nice to discover someone with original thoughts on this
    topic. Really.. many thanks for starting this up. This web site is one thing that is needed on the internet, someone with some originality!

  • grow tiktok followers
    grow tiktok followers 2024年3月6日 下午2:56

    I was wondering if you ever thought of changing the structure of your website?
    Its very well written; I love what youve got to say.
    But maybe you could a little more in the way of content so people could
    connect with it better. Youve got an awful lot of text for only having 1 or two pictures.

    Maybe you could space it out better?