通过jenkins交付微服务到kubernetes

2022-08-08 15:22:14 浏览数 (1)

随着Kubernetes的遍地开花,Kubernetes的优势可以说是深入人心,很多企业也是利用Kubernetes,来实现更高效的交付和更好地提高我们的资源使用率,推动标准化,适应云原生。

随着Kubernetes和云原生加速企业产品落地,现在总结以下几点

代码语言:javascript复制
1)更快的应用开发与交付
2)天然适合微服务,是微服务和Devops的桥梁
3)可移植性,支持公有云,私有云,裸机,虚拟机
4)标准化的应用开发与发布:声明式API和Operator
5)自动化运维:弹性伸缩(HPA),故障自愈,负载均衡,配置管理等

另外就是交付spring cloud到k8s之前说一下微服务的概念

什么是微服务?

早在2011年的5月份在威尼斯的一个架构研讨会上微服务的概念就被人提起了,当时的参会者对它的描述只是一种通用的软件并没给出明确的定义是什么,之后随着技术的不断发展,在2014年詹姆斯里维斯以及它的伙伴马丁福勒在它的微博中发表了一篇有关于微服务特点的文章,对微服务进行了全面的阐述,之后微服务就走进了我们的视野 https://martinfowler.com/articles/microservices.html 这是关于网站的描述 在这个文章中并给出微服务一个明确的定义,微服务其实是一种软件的架构风格,是一种将单体架构 拆分为小的服务进行去开发,每个服务都运行在自己的进程中,采用的是轻量级的restful或者http进行通信,并且都是独立开发独立部署和测试的,可以使用多种语言进行开发

对于微服务有一个关键点叫化整为零,把一个大的应用却成一个小的不同的应用 比如嘀嘀打车,早期在一个互联网应用上基本上都是单体架构,不是分布式的 单体情况下把很多程序都写一个程序中,然后一台服务器对所有服务进行运行,但是随着并发的提高,这种单体架构显然承受不了了,这样的话就需要我们对我们软件的指责进行慢慢的划分,将其剥离出来,形成一个一个的微服务,也就是多个微服务的模块形成一个完整的应用,这些都是独立部署独立运行的,一个微服务也会运行在一个虚拟机里面。

spring cloud微服务体系的组成

服务发现 (Eureka,Cousul,zookeeper) 也就是注册中心最为微服务必不可少的中央管理者,服务发现的主要职责就是将其它的微服务模块进行登记与管理,这就相当于生活中来了一家公司,去工商局进行登记一样的道理,在服务发现中它主包含了三个子模块,分别是eureka,cousul,zookeeper,这些spring cloud底层都支持的注册中心,一般常用的是eureka和consul,那么微服务构建好了之后,那么微服务与微服务直接怎么进行服务直接的通信,或者微服务遇到了故障无法达到请求的话(hystrix/ribbon/openfeign) 另外就是路由与过滤主要是针对对外接口的暴露的,这里主要涉及zuul,spring cloud gateway,这两个组件主要为外部的调用者比如其他的系统和我们的微服务进行通信的时候,由外到内是怎么彼此进行访问的,那么这些就是由这两个组件进行完成的 配置中心就是存放我们应用程序配置的地方,可能我们有上百个应用程序,那么每个应用程序都是一个微服务,那么就会产生一个很严重的问题,就是这些配置文件放在什么地方比如每个服务下都放一个xml,或者yml,维护起来是非常不方便的,因为改一个参数,就要对所有的应用进行调整,为了解决这个问题配置中心就出现了,相当于又提供了一个微服务把我们应用中所有的配置文件,都放在了配置中心中,那么其他应用都是通过配置中心来获取到这些配置文件的而不是我们要这个这个配置文件放到每个程序中,这样的好处就是可以将我们的配置文件进行集中的管理,只需要改一个地方所有地方都能生效

spring cloud微服务组成

消息总线,spring cloud stream或者spring cloud bus就跟我们的消息mq差不多就是我们发布一个信息,到我们队列里面由其他的微服务或者其他的应用进行获取提供了系统与系统之间或者微服务与微服务之间的消息传递过程,这个中间增加了一个额外的东西叫做消息总线,具体的消息总线可以是mq或者是Redis,不同的厂商实现了不同的实现 安全控制是针对我们安全的管理,在我们传统网站开发的时候,应用的访问控制有授权的可以使用这个功能,没有授权的就无法进行访问,安全控制在spring cloud中也是存在的提供了AUTH2.0方案的支持,链路监控就是对我们消息传递的过程要进行统筹和监控,比如系统中有10个微服务,而这10个微服务是彼此依赖的,第一个微服务它是底层最基础的用户管理而第二个微服务是基于用户管理开发一个权限管理,在往上是应用管理,应用系统的扩展,每一个微服务之间彼此之间进行依赖在顶层我们进行调用的时候会安装微服务的调用顺序一级一级消息往下传递,这样做有一个问题来了,如果中间有个环节出现了问题,没有响应服务我们在使用的角度当前我们的请求失败了,但是具体的环节不知道是在那一块出现问题,那么链路监控就是让我们快递定位消息传递过程哪个阶段进行出错,有助于我们问题的排查 spring cloud cli命令行工具,来实现我们开发来实现的一些功能,spring cloud cluster是对我们集群管理的一个辅助工具

现在去交付微服务到k8s中举个demo仅供参考

代码语言:javascript复制
一、发布流程设计
二、准备基础环境
三、在Kubernetes中部署jenkins
四、jenkins pipeline及参数化构建
五、jenkins在k8s中动态创建代理
六、自定义构建jenkins-slave镜像
七、基于kubernetes构建jenkins ci系统
八、pipeline集成helm发布spring cloud微服务

一、传统的发布流程是怎么样的? 现在的这个发布流程设计还是要和自己的项目中去考量,布置一个大体的拓扑图,那么比如没有这样的场景jenkins去发布微服务,它是一个怎么样的流程,作为运维来讲首先要去拉代码,开发已经将这个项目开发好了,并推送到git仓库中或者部署的私有的gitlab代码仓库中,第一步做的就是将这个代码拉下来,拉完代码,一般代码都是Java应用,会设计到一个编译,编译出来一个可部署的包,一般微服务是jar包,或者直接启动的应用程序,然后就开始去封装这个服务了,一般就是将这个jar包或者应用程序,通过dockerfile去达成一个可部署的镜像,这个镜像一般会自己制作jdk环境,或者就是jre环境,就是基础镜像能够运行这个镜像的底包,最后一步就是部署到k8s中,这里就会写一些yaml文件了去把这个镜像部署到k8s中,也就是容器的编排,另外还要考虑怎么将这个应用暴露出去,让用户访问到。

使用jenkins自动话发布的流程是这么样的? 显然这种方式发布多个微服务很不高效,所以就需要ci/cd,这么一说,那么有jenkins了,怎么将这种方式自动化起来,减少人工的干预。 上面那张图,首先是这样的,开发将代码推送到git仓库中,通过commit提交上去,然后再到jenkins了,它负责的任务就是checkout代码的拉取,code compile代码的编译,docker build &push ,镜像的构建与推送到harbor仓库中,然后deploy,将应用部署到k8s中,这里呢由于可能是很多的微服务,那么我们就需要模版的代替,去发布微服务,这里我们就会需要用到它原生的helm微服务发布工具来到k8s当中去deploy,发布到测试环境中去,然后通过slb提供一个统一的出口,发布出去,中间产生的镜像也都会存放到harbor仓库中,当QA测试没有问题,这个镜像也就可以去发布生产环境中。

为什么需要jenkins slave架构 另外这里还提到了一个jenkins,slave的一个架构,主要的是可以动态的可以完成这些任务,动态的去调度一个机器和一个pod来完成这几步的任务,因为当任务很多时,也就是都在jenkins master去做,显然任务多了负载就高了,所以就需要引入这个slave去解决这个问题。

二、准备基础环境,所需的组件来完成我们流程的发布

代码语言:javascript复制
1、k8s——(ingress controller、coredns、pv自动供给)
2、harbor,并启用chart存储功能,将我们的helm打成chart并存放到harbor中
3、helm-v3 工具,主要来实现模版化,动态的将应用渲染安装与卸载,更好的去管理微服务
4、gitlab代码仓库,docker-compose实现
5、MySQL,微服务数据库
6、在k8s中部署eureka(注册中心)

1、检查k8s基础组件的环境是否安装: 1、默认我的这个基础的组件都是安装好的,ingress 和coredns

代码语言:javascript复制
[root@k8s-master1 ~]# kubectl get pod -A
NAMESPACE              NAME                                         READY   STATUS    RESTARTS   AGE
ingress-nginx          nginx-ingress-controller-2vs56               1/1     Running   0          5h3m
ingress-nginx          nginx-ingress-controller-586gw               1/1     Running   0          167m
ingress-nginx          nginx-ingress-controller-pxztr               1/1     Running   0          5h3m
ingress-nginx          nginx-ingress-controller-qp266               1/1     Running   0          5h6m
kube-system            coredns-59fb8d54d6-vjn62                     1/1     Running   0          5h7m
kube-system            kube-flannel-ds-amd64-2hnkf                  1/1     Running   0          5h7m
kube-system            kube-flannel-ds-amd64-2smpl                  1/1     Running   0          5h7m
kube-system            kube-flannel-ds-amd64-jbrv4                  1/1     Running   0          167m
kube-system            kube-flannel-ds-amd64-jsxdf                  1/1     Running   0          5h7m
kubernetes-dashboard   dashboard-metrics-scraper-566cddb686-mddlb   1/1     Running   0          5h7m
kubernetes-dashboard   kubernetes-dashboard-c4bc5bd44-wpgc7         1/1     Running   0          5h7m

1.2、k8s pv的自动供给,这里当然也可以使用Ceph持久化存储,由于我的测试环境配置不够,先拿NFS对有状态的应用实现自动的PV供给。 先准备一台NFS服务器为K8S提供存储支持

代码语言:javascript复制
[root@k8s-node3 ~]# yum -y install nfs-utils
创建共享的目录
[root@k8s-node3 ~]# mkdir /ifi/kubernetes -p
[root@k8s-node3 ~]# cat /etc/exports
/ifi/kubernetes 10.4.7.0/24(rw,no_root_squash)
[root@k8s-node3 kubernetes]# systemctl start nfs
[root@k8s-node3 ~]# systemctl enable nfs

并且要在每个Node上安装nfs-utils包,用于mount挂载时用。
[root@k8s-master1 ~]# mount -t nfs 10.4.7.22:/ifi/kubernetes /mnt
由于K8S不支持NFS动态供给,还需要先安装nfs-client-provisioner插件
修改nfs的服务端地址和挂载的目录,这是我nfs-client的地址,如果借鉴的需要将id_rsa.pub给我
git clone git@gitee.com:zhaocheng172/nfs-client.git
[root@k8s-master1 nfs-client]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
busybox                                   1/1     Running   0          35m
nfs-client-provisioner-86dff449dd-68ngn   1/1     Running   0          106s

2、镜像仓库Harbor 2.1安装docker与docker-compose

代码语言:javascript复制
# wget http://mirrors.aliyun.com/docker-ce/linux/CentOS/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
# yum install docker-ce -y
# systemctl start docker
# systemctl enable docker
代码语言:javascript复制
docker-compose的下载地址:https://docs.docker.com/compose/install/
curl -L https://github.com/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod  x /usr/local/bin/docker-compose

2.2 解压离线包部署 下载地址:https://github.com/goharbor/harbor

代码语言:javascript复制
# tar zxvf harbor-offline-installer-v1.10.1.tgz
# cd harbor
# vi harbor.yml
hostname: harbor.zhaocheng.com
# ./prepare
# ./install.sh --with-chartmuseum
# docker-compose ps 

--with-chartmuseum 参数表示启用Charts存储功能。 进行访问: http://harbor.zhaocheng.com 这个已经启动chart功能

2.3 配置Docker可信任 由于habor未配置https,还需要在node节点上的docker配置可信任。

代码语言:javascript复制
# cat /etc/docker/daemon.json 
{
  "registry-mirrors": ["https://38vve9ja.mirror.aliyuncs.com"],
  "insecure-registries": ["harbor.zhaocheng.com"]
}
# systemctl restart docker

3、helm-v3 工具 3.1安装helm工具

代码语言:javascript复制
[root@k8s-master1 helm]# wget  https://get.helm.sh/helm-v3.0.0-linux-amd64.tar.gz
[root@k8s-master1 helm]# tar xf helm-v3.0.0-linux-amd64.tar.gz 
[root@k8s-master1 helm]# mv linux-amd64/helm /usr/bin/
[root@k8s-master1 helm]# helm  --help

3.2 安装push插件

代码语言:javascript复制
# git clone https://gitee.com/zhaocheng172/helm-push.git
# tar zxvf helm-push_0.7.1_linux_amd64.tar.gz
# mkdir -p /root/.local/share/helm/plugins/helm-push
# chmod  x bin/*
# mv bin plugin.yaml /root/.local/share/helm/plugins/helm-push

3.3 添加repo
# helm repo add  --username admin --password Harbor12345 myrepo http://192.168.30.27/chartrepo/library

3.4 推送与安装Chart helm安装好有默认的模版,那么我们先使用它的进行生成一个chart包,用于我们测试推送到我们的Harbor仓库中,这个Chart是当我们部署完之后,官方默认自带的模版,小的demo,install之后是一个Nginx的应用

代码语言:javascript复制
[root@k8s-master ~]# mkdir chart-test
[root@k8s-master ~]# cd chart-test/
[root@k8s-master chart-test]# helm create test
[root@k8s-master chart-test]# helm install test01 test
NAME: test01
LAST DEPLOYED: Sat Mar 14 17:10:20 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=test,app.kubernetes.io/instance=test01" -o jsonpath="{.items[0].metadata.name}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace default port-forward $POD_NAME 8080:80

这里是直接使用他们自己写的模版创建的pod 查看这个pod已经正常运行,而这个test01是我们自己起的名字,这个部署的时候一般是我们的微服务的名称 要是去加个--dry-run的话就是预先执行,一般看看这个模版有没有都执行成功,有没有问题

代码语言:javascript复制
[root@k8s-master chart-test]# helm install test02 --dry-run test/

[root@k8s-master chart-test]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-564dddddd6-dgv26   1/1     Running   8          26h
test01-f7f8c5759-zv8kf                    1/1     Running   0          31s

没什么问题的话我们就需要将这个chart达成一个包,以便下次的时候再用
[root@k8s-master chart-test]# helm package test/
Successfully packaged chart and saved it to: /root/chart-test/test-0.1.0.tgz
[root@k8s-master chart-test]# ls
test  test-0.1.0.tgz

推送到我们的镜像harbor仓库中
[root@k8s-master chart-test]# helm push test-0.1.0.tgz --username=admin --password=Harbor12345 http://192.168.30.27/chartrepo/library
Pushing test-0.1.0.tgz to http://192.168.30.27/chartrepo/library...
Done.

tgz包安装helm ,一般用于之前制作的包,比如我们之前制作的模版,如果想使用了,那么直接就可以再使用

代码语言:javascript复制
[root@k8s-master ~]# helm install test02 test-0.1.0.tgz 
NAME: test02
LAST DEPLOYED: Sat Mar 14 17:49:00 2020
NAMESPACE: default
STATUS: deployed
REVISION: 1

4、部署gitlab,这里分别我整理了两个方法来部署gitlab,第一个是英文版页面,第二个是中文版页面 4.1#安装gitlab,建议2c2g

代码语言:javascript复制
yum -y install policycoreutils openssh-server openssh-clients postfix
#修改postfix
sed -i 's/inet_interfaces = localhost/inet_interfaces = all/g' /etc/postfix/main.cf
sed -i 's/inet_protocols = all/inet_protocols = ipv4/g' /etc/postfix/main.cf
sudo systemctl enable postfix
sudo systemctl start postfix
curl -sS https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.rpm.sh | sudo bash
sudo EXTERNAL_URL="https://gitlab.example.com" yum install -y gitlab-ce

gitlab配置文件路径
/etc/gitlab/gitlab.rb
external_url 'http://10.4.7.200:9999'
# 端口可以变动
gitlab-ctl reconfigure  ,这个初始化配置比较久
# gitlab启动初始化操作
# gitlab-ctl restart
重启

4.2、使用docker-compose部署中文版Gitlab 1)首先在一台新的服务器上安装docker-ce,并使用docker加速器,使用中科大的加速器/阿里云加速器 2)部署安装docker-compose 3)安装gitlab,编辑docker-compose文件

代码语言:javascript复制
[root@localhost ~]# mkdir gitlab
[root@localhost ~]# cd gitlab/
[root@localhost gitlab]# vim docker-compose.yml
yml地址:git@gitee.com:zhaocheng172/gitlab-docker-compose.yml.git

4)创建相关目录,config存储gitlab配置信息,data 存储数据库,logs存储日志
[root@localhost gitlab]# mkdir -p /opt/gitlab/{config,data,logs}
[root@localhost gitlab]# ls -l /opt/gitlab/
total 0
drwxr-xr-x. 2 root root 6 Mar 14 19:44 config
drwxr-xr-x. 2 root root 6 Mar 14 19:44 data
drwxr-xr-x. 2 root root 6 Mar 14 19:44 logs

5)拉取gitlan中文版镜像,这个提前可以拉取下来,不然执行docker-compose需要等待
[root@localhost gitlab]# docker pull zhaocheng172/gitlab-ce-zh:latest

6)启动gitlab容器
在启动前,先要修改宿主机的22端口,因为宿主机占用了22端口,所以启动容器会失败,因为映射不过来,修改如下:
vim /etc/ssh/sshd_config
默认在第17行修改:Port 2222
修改后,重启sshd:systemctl restart sshd
[root@localhost gitlab]# netstat -anpt | grep 22
tcp 0 0 0.0.0.0:2222 0.0.0.0: LISTEN 31889/sshd
tcp6 0 0 :::2222 ::: LISTEN 31889/sshd

7)正式启动
[root@localhost gitlab]# docker-compose up -d
Creating network "gitlab_default" with the default driver
Creating gitlab ... done
[root@localhost gitlab]# docker-compose ps
 Name        Command               State                                      Ports                            
---------------------------------------------------------------------------------------------------------------
gitlab   /assets/wrapper   Up (health: starting)   0.0.0.0:22->22/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp

8)设置gitlab开机启动
[root@localhost gitlab]# chmod  x /etc/rc.local 
[root@localhost gitlab]# ls -l /etc/rc.local 
lrwxrwxrwx. 1 root root 13 Mar 14 12:57 /etc/rc.local -> rc.d/rc.local
[root@localhost gitlab]# echo "cd /root/gitlab && docker-compose up -d" >> /etc/rc.local

9)gitlab管理页面
输入配置的ip或者域名
这里我是使用的域名登陆
另外就是如果这台服务开启了selinux的话,要是重启机器的话会导致ssh连接不上,解决方法要不就是关闭selinux,要不就是开启支持的端口,默认docker-compose部署gitlab官方推荐的是修改22端口,所以要修改一些配置
或者要不就是直接
#sed -i 's/enforcing/disabled/' /etc/selinux/config  # 永久

# 安装修改工具
yum -y install policycoreutils-Python

# 查看selinux中的ssh的端口,输出为 22
semanage port -l | grep ssh

# 新增端口
semanage port -a -t ssh_port_t -p tcp 2222

由于我是拿测试机做的所以我需要将这个写入我windows的C:WindowsSystem32driversetchosts
192.168.30.27 harbor.zhaocheng.com
192.168.30.28 gitlab.zhaocheng.com
docker-compose重启命令,这个是需要在docker-compose.yml下才能重启
docker-compose restart

用户:root,首次登陆需要手动设置密码

将代码推送到gitlab中 创建ms的仓库名称

创建完需要我们将我们的本地的代码上传到gitlab中来

现在我们先克隆一下我们这个ms的仓库到本地来 设置git全局设置,这里我们需要我们的用户也就是我们gitlab去登录这个平台的账号,提交代码的时候我们需要达到master分支,其他的开发去使用这个gitlab的时候我们需要给他去创建一个用户,也就是在gitlab去创建的,以便它们提交代码到仓库中 [root@k8s-master1]# mkdir ms [root@k8s-master1]# cd ms [root@k8s-master1 ms]# git config --global user.name "zhaocheng" [root@k8s-master1 ms]# git config --global user.email "zhaocheng"

代码语言:javascript复制

看到只有一个仓库。里面是空的,这个地方需要输入我们gitlab的账号与密码,这样的话才能有访问控制,让你去拉这个代码

代码语言:javascript复制
[root@k8s-master ms]# git clone http://192.168.30.28/root/ms.git
Cloning into 'ms'...
Username for 'http://192.168.30.28': root
Password for 'http://root@192.168.30.28': 
warning: You appear to have cloned an empty repository.

当我们git clone之后这里有遗留下的空目录,因为这是刚才我们去测试去拉的代码,如有代码的话就拉取下来了

代码语言:javascript复制
[root@k8s-master ms]# ls
ms

现在将开发写的代码解压之后,将里面的服务都拷贝到刚才我们拉取代码遗留的空仓库中

代码语言:javascript复制
[root@k8s-master ms]# ls
ms  simple-microservice-master  simple-microservice-master.zip
[root@k8s-master ms]# cp simple-microservice-master/* -rf ms
[root@k8s-master ms]# cd ms
[root@k8s-master ms]# ls
basic-common  eureka-service   k8s      lombok.config  pom.xml         product-service  stock-service
db            gateway-service  LICENSE  order-service  portal-service  README.md

提交缓存区 [root@k8s-master1 ms]# git add .

使用commit打个tag 格式git commit -a -m "提交添加的注释信息"或者写成先添代码

代码语言:javascript复制
[root@k8s-master1 ms]# git commit -m "all"

推送到gitlab中

代码语言:javascript复制
[root@k8s-master ms]# git push origin master
Username for 'http://192.168.30.28': root
Password for 'http://root@192.168.30.28': 
Counting objects: 515, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (411/411), done.
Writing objects: 100% (515/515), 15.62 MiB | 17.78 MiB/s, done.
Total 515 (delta 60), reused 0 (delta 0)
remote: Resolving deltas: 100% (60/60), done.
To http://192.168.30.28/root/ms.git
 * [new branch]      master -> master

现在已经将代码推送到gitlab中

另外就是如果其他的开发人员需要将代码提交到gitlab上的话,那么我们就需要给他去创建一个用户,设置一定的权限,然后制定好密码,告诉他,它使用自己的账号去管理自己的项目代码

另外就是在使用jenkins做持续集成的时候,如果传统的一般会需要对gitlab做SSH秘钥对的认证,也就是在jenkins生成秘钥对,私钥自己留着将公钥放在右上角的gitlab账号处,会看到settings的下面,将公钥放到指定框内,也就是jenkins需要有权限去拉取gitlab上的代码,现在已经支持在Jenkins中以key的形式存储的slavepod中

另外就是分支说明 master主分支,有且只有一个 release线上分支,一般为线上版本,线上版本发布后,会将release分支合并到master develop 开发分支,通常给测试部署环境或者打包的分支,每个人在自己的分支上开发完成后,向develop分支合并 feature 通常为一个功能分支或者个人分支,一般有很多个,通常合并完成后会删除

5、mysql 微服务数据库 导入数据库到Mysql

代码语言:javascript复制
[root@k8s-master1 ~]# cd simple-microservice-dev3
[root@k8s-master1 simple-microservice-dev3]# ls
basic-common    gateway-service  lombok.config  portal-service   stock-service
db              k8s              order-service  product-service
eureka-service  LICENSE          pom.xml        README.md
[root@k8s-master1 simple-microservice-dev3]# cd db/
[root@k8s-master1 db]# ls
order.sql  product.sql  stock.sql
[root@k8s-master1 db]# scp * root@192.168.30.24:~

这里呢之前遇到一个问题当部署gitlab之后如果是使用的docker去部署的gitlab默认的22端口改成2222端口之后当用其他服务器进行scp或者ssh登录的时候就会显示拒接

代码语言:javascript复制
[root@k8s-master db]# scp order.sql root@192.168.30.28:/root/
Permission denied (publickey).
lost connection

这里我安装了一个mariadb的实例进行测试

代码语言:javascript复制
[root@harbor-mysql-gitlab ~]# yum install mariadb mariadb-server mariadb-devel
[root@harbor-mysql-gitlab ~]# systemctl start mariadb
[root@harbor-mysql-gitlab ~]# netstat -anpt |grep 3306
tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN      6226/mysqld    

修改mariadb的密码,默认没有密码

代码语言:javascript复制
[root@harbor-mysql ~]# mysql -u root -p
MariaDB [(none)]> set password for root@localhost = password('666666');
MariaDB [(none)]> flush privileges;

创建数据库

代码语言:javascript复制
MariaDB [(none)]> create database tb_order;
MariaDB [(none)]> create database tb_product;
MariaDB [(none)]> create database tb_stock;
进入order数据库,把我们根目录下的sql语句导入我们的数据库中
MariaDB [(none)]> use tb_order;
MariaDB [tb_order]> source /root/order.sql;
进入product数据库,把我们根目录下的sql语句导入我们的数据库中
MariaDB [tb_order]> use tb_product ;
MariaDB [tb_product]> source /root/product.sql
进入stock数据库,把我们根目录下的sql语句导入我们的数据库中
MariaDB [tb_product]> use tb_stock ;
Database changed
MariaDB [tb_stock]> source /root/stock.sql;

MariaDB [tb_stock]> show databases;
 -------------------- 
| Database           |
 -------------------- 
| information_schema |
| mysql              |
| performance_schema |
| tb_order           |
| tb_product         |
| tb_stock           |
| test               |
 -------------------- 
7 rows in set (0.00 sec)

6、将eureaka集群部署到k8s中

代码语言:javascript复制
[root@k8s-master ~]# yum install java-1.8.0-openjdk maven -y
软件包 1:java-1.8.0-openjdk-1.8.0.222.b10-0.el7_6.x86_64 已安装

将eureka-service进行maven编译可以执行的jar包
[root@k8s-master simple-microservice-dev1]# mvn clean package -D maven.test.skip=true

会在target下面生成jar包

代码语言:javascript复制
[root@k8s-master eureka-service]# ls
Dockerfile  pom.xml  src  target

将这个eureka制作成一个镜像,然后编排yaml文件好让这个容器运行起来

代码语言:javascript复制
[root@k8s-master1 eureka-service]# cat Dockerfile 
FROM java:8-jdk-alpine
RUN  apk add -U tzdata && 
     ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
COPY ./target/eureka-service.jar ./
EXPOSE 8888
CMD java -jar -Deureka.instance.hostname=${MY_POD_NAME}.eureka.ms /eureka-service.jar

构建并上传到我们的harbor仓库中, [root@k8s-master eureka-service]# docker build -t eureka .

这里需要我们去我们的Harbor创建一个项目仓库的名称,这里是创建的microservice,并达成推送我们镜像的tag名称,这样推送的时候会直接找我们镜像仓库的地址 [root@k8s-master eureka-service]# docker tag eureka 192.168.30.27/microservice/eureka:latest 上传到我们的harbor上

这里需要登录一下我们的harbor仓库,才能上传

代码语言:javascript复制
[root@k8s-master ~]# docker login 192.168.30.27
Username: admin
Password: 
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

[root@k8s-master eureka-service]# docker push 192.168.30.27/microservice/eureka:latest 

创建k8s登录harbor信息认证,这里并提前创建好命名空间ms,这样k8s才能到harbor仓库拉取镜像有这个secret 先创建一个ms的命名空间

代码语言:javascript复制
[root@k8s-master eureka-service]# kubectl create ns ms
namespace/ms created
[root@k8s-master eureka-service]# kubectl get ns
NAME              STATUS   AGE
default           Active   68m
ingress-nginx     Active   55m
kube-node-lease   Active   68m
kube-public       Active   68m
kube-system       Active   68m
ms                Active   4s
[root@k8s-master k8s]# kubectl create secret docker-registry registry-pull-secret --docker-server=192.168.30.27 --docker-username=admin --docker-password=Harbor12345 --docker-email=admin@zhaocheng.com -n ms

查看状态

代码语言:javascript复制
[root@k8s-master ~]# kubectl get pod,svc,ing  -n ms
NAME           READY   STATUS    RESTARTS   AGE![](https://s4.51cto.com/images/blog/202003/28/0c07c95b7ce22d59b778082427c7d2bd.png?x-oss-process=image/watermark,size_16,text_QDUxQ1RP5Y2a5a6i,color_FFFFFF,t_100,g_se,x_10,y_10,shadow_90,type_ZmFuZ3poZW5naGVpdGk=)
pod/eureka-0   1/1     Running   1          16h
pod/eureka-1   1/1     Running   0          16h
pod/eureka-2   1/1     Running   1          16h

访问eureka

三、在Kubernetes中部署jenkins

在k8s中去部署jenkins需要注意的是本身它需要一个存储,就是它需要存储这些插件和配置的job都是需要存储的,而我们部署到k8s中这个pod是不固定的,有可能在第一个节点也有可能在第二个节点,那就需要保证这个jenkins这个存储功能换个节点也能实时读取到,这就用到了k8s的pv和pvc了,就会使用到持久化存储,这样的话就会用到pv的自动供给,然后用jenkins持久化目录到pv上,持久到远程的存储上

创建一个service-account绑定到rbac里面,rbac主要就是设置一些权限让jenkins访问到k8s,service-connout主要是让pod访问到apiserver,它去apiserver调度创建pod 这是我jenkins的地址,如果借鉴的需要将id_rsa.pub给我

代码语言:javascript复制
git clone git@gitee.com:zhaocheng172/jenkins-k8s.git
[root@k8s-master1 jenkins]# ls
deployment.yml  ingress.yml  rbac.yml  service-account.yml  service.yml

deployment参数详解: 主要来部署jenkins的容器的配置

代码语言:javascript复制
serviceAccountName: jenkins  使用service-account创建的名字
image: jenkins/jenkins:lts 使用官方长期维护lts镜像
- containerPort: 8080       jenkins ui的访问的端口
- containerPort: 50000     slave访问master访问jenkins所使用的端口
volumeMounts:
            - name: jenkins-home
              mountPath: /var/jenkins_home   说到jenkins持久化数据,在jenkins中需要持久化的目录就是它的工作目录,也就是它所产生有状态的数据都放在这个/var/jenkins_home下
volumes:
        - name: jenkins-home
          persistentVolumeClaim:
            claimName: jenkins-home 而持久化的这个目录也都会落到这个pvc这个卷
name: jenkins-home
spec:
  storageClassName: "managed-nfs-storage"
  accessModes: ["ReadWriteOnce"]    而pvc这里就是我们创建使用的storageclass自动供给来提供的pv,申请5G,设置都可读写

查看创建的storage class

代码语言:javascript复制
[root@k8s-master1 jenkins]# kubectl get sc
NAME                  PROVISIONER      AGE
managed-nfs-storage   fuseim.pri/ifs   22h

service参数: 这里主要就是使用nodeport去开放一个端口,另外一般生产环境都使用ingress,ingress需要关联service,还要使用nginx做反向代理,将这个域名通过slb发布出去 [root@k8s-master1 jenkins]# kubectl apply -f .

四、jenkins pipeline及参数化构建 jenkins pipeline是一套插件,支持在jenkins中实现集成和持续交付管道; pipeline通过特定语法对简单到复杂的传输管道进行建模;

  • 声明式:遵循与Groovy相同语法。pipeline{}
  • 脚本式:支持Groovy大部分功能,也是非常表达和灵活的工具。node{} *jenkins pipeline的定义被写入一个文本文件,称为jenkinsfile。

去Manage jenkins去安装插件,找到manage plugins ,点击Available检索pipeline,进行安装,安装后选择install without restart 现在去创建一个流水线进行测试一下 部署完之后可以使用log查看登录的密码

代码语言:javascript复制
[root@k8s-master jenkins]# kubectl logs jenkins-7d5fbd857d-tmwbm         
6e22df63432f474f863dc07c9d291967

一般最好进去的时候不安装插件,第一默认使用的国外的源,下载插件的时候很慢,我们需要做做一些优化

先创建一个test的pipeline的语法流水线,熟悉一下怎么发布任务

在这里就可以看到语法的格式,这里分为两种,以pipeline开头的也叫声明式语法,主要遵循的Groovy的相同语法来实现的pipeline {} 这个也是比较主流的方式。

测试一个hello的语法,默认提供的pipeline,进行使用

直接build,这个就能构建

当我们构建完成后,这里会显示一个执行的步骤显示一个具体的内容,可以看到jenkins的工作目录,默认传统部署jenkins的目录为/root/.jenkins下,而作为在k8s部署jenkins需要考虑数据的持久化了,因为pod遇到不确定的因素进行重启之后,那么这个pod的数据就会丢失,所以针对这个问题,我们就需要将这个pod的工作目录挂载到持久卷上,这样的话,即使pod重启飘移到其他的节点也能读取到相应的数据了。

可以看到k8s的持久化目录是在这个jobs目录下,将我们构建的内容和数据都放在这里了。

代码语言:javascript复制
[root@k8s-node3 ~]# ll /ifi/kubernetes/default-jenkins-home-pvc-0d67f7f5-2b31-4dc8-aee2-5e7b9e0e7e19/jobs/test/
总用量 8
drwxr-xr-x 3 1000 1000   50 1月  13 11:36 builds
-rw-r--r-- 1 1000 1000 1021 1月  13 11:36 config.xml
-rw-r--r-- 1 1000 1000    2 1月  13 11:36 nextBuildNumber

测试一个hello的pipeline的语法格式

代码语言:javascript复制
pipeline {
   agent any

   stages {
      stage('Build') {
         steps {
            echo "hello" 
         }
      }
      stage('test') {
         steps {
            echo "hello"
         }
      }
      stage('deploy') {
         steps {
            echo "hello"
         }
      }
   }
}

控制台输出了三个任务,分别执行hello,大概就是这个样子

目录也持久化了,看到新增的jenkins的项目任务 [root@k8s-node3 ~]# ll /ifi/kubernetes/default-jenkins-home-pvc-0d67f7f5-2b31-4dc8-aee2-5e7b9e0e7e19/jobs/ 总用量 0 drwxr-xr-x 3 1000 1000 61 1月 13 11:36 test drwxr-xr-x 3 1000 1000 61 1月 13 14:04 test-demo

jenkins pipeline就像一个管道的建模一样,在这个脚本里完成了整个生命周期各个阶段,从development开发提交代码commit id,到build构建,再到test测试,再到stage步骤做一些处理,deploy部署到dev或者qa环境中,最后到线上,其实在这个流程中它是有一个目的的,刚开始是在开发环境,最终是把它带到线上环境,而中间一系列的流程都是通过管道的形式串起来,而这个管道这个模型是通过pipeline去书写的,这个语法就是这个模型,需要把这个生命周期的所需的都套进这个模型中来,然后由jenkins pipeline去管理

第一用这个pipepine它有很大的特点 1、可视化页面,每个步骤都可以可视化展示,方便我们去解决每个步骤的相关问题 2、每个步骤都写脚本里面了,只需要维护这个脚本就好了,而这个脚本可以写的具有通用性,如果想写多个项目时,比如发布3组微服务,那么第一个写的pipeline,那么也同样适用于第二个和第三个微服务的模版。那么这个需要考虑它们有哪些不同点? 不同点: 1)拉取git代码的地址不一样 2)分支名也不一样,因为是不同的git地址,所以打的分支名也不一样。 3)部署的机器也不一样,有可能这几个服务部署在node1,另外的服务部署在node2或者node3 4)打出的包名不一样 所以要把这些不同点,做成一种人工交互的形式去发布,这样的话这个脚本才具有通用性,发布服务才能使用这写好的pipeline发布更多的微服务,而且jenkins pipeline支持参数化构建。

这里也就是pipeline一些的语法,比如选择parameters参数化构建,选择框式参数choice parameter,比如给它几个值,然后让它动态的去输入,或者凭据参数credentials parameter,或者文件参数file parameter,或者密码参数password parameter,或者运行参数 run parameter,或者字符串参数 string paramether ,或者多行字符串参数multi-line string parameter,或者布尔型参数boolean parameter

比如选择choice parameter,选择型参数

比如发布的git地址不一样,那么就需要多个地址可以去选择,需要使用choice parameter选择框型参数,这个可以体现在构建的页面,也可以体现在configure配置的页面,这样配置也比较麻烦,所以直接在configure里面直接添加对应的参数就可以 先配置一个看一下效果

将生成的选择型参数添加到指定的pipeline的语法中,save一下

再回到项目中可以看到build名字从build now换成了build with parameters,也就是增加了几行的的配置,可以进行选择的去拉去哪个分支下的代码了

也可以在configure去配置,这两个地方都可以添加,要是再添加一个直接add parameter,可以选择多种类型的参数帮助我们去构建这个多样式的需求

再比如分支这一块,可能每次打的分支都不同,这个不是固定的,所以需要一个git的参数化构建,那么这个就需要动态的去从选择的git地址获取到当前的所有的分支 还有一个分布的机器不同,这个也可以使用刚才的choice parameter,将多个主机的ip也进去

将这个生产的语法,复制到pipeline语法中 choice choices: ['10.4.7.12', '10.4.7.21', '10.4.7.22'], description: '发布哪台node上', name: 'host'

构建一下,发现也可以使用选择型参数了,类似这个的人工交互就可以选择多个参数了,可以写一个通用的模版,就处理人工交互的逻辑

现在我们可以去人工选择了,这里面的值怎么获取到,我们处理不同的项目,必须在这里面去实现,比如选择这个git之后,拉取这个代码编译构建,这些可能都是一些相同点,不同点发布的机器不一样,所以要选择用户是拿到的哪个git地址,发布的哪个机器,在脚本里去拿到,其实默认这个name就是一个变量,jenkins已经将这个赋予变量,并且pipeline可以直接获取这个变量名,就是刚才定义的git,host这个名字,那么我们从刚才设置的parameters里去测试这个变量可不可以拿到,拿到的话说明这个就很好去处理

构建一下,现在构建成功后已经是构建成功了,也已经获取到刚才我们的git这个参数下的值了

有了这些方式,就可以将这些不同点通过里面的agent和shell脚本来处理了,写pipeline参数化构建就是满足更多的一个需求,能适配更多的项目,能让人工干预的做一些复杂的任务

五、jenkins在k8s中动态创建代理 如何在k8s中动态的创建slave代理? 当完成这些任务之后考虑的问题,这些任务都是在jenkins机器去完成的,那么这个也肯定是在pod中去运行的,因为我们的是将jenkins部署在pod中的,也就是这当前的这个节点去完成的拉取代码,编译,构建镜像,发布,那么可能会遇到一个问题,那么项目很多,每天做持续集成很高,十几次甚至上百次,面对这样的一个需求量,当前的这个pod是很难支撑的,就好比刚才的job,有十几个人去运行,来运行不同的服务,本来是可以几分钟完成的事情,最后导致10多分钟才执行完成,这样的话就很耽误项目进度了,所以就需要使用jenkins的master-slave架构了,而master只负责调度分配,slave来完成这些job任务,而slave是由物理机或者虚拟机存在的,和master保持通信,只要有任务就下发到slave节点,这样就解决了单jenkins的性能问题了

也就是提前创建好几个jenkins-slave,在其他节点让它们待定着,当master有人点job构建了,这个jenkins会帮你把这个job具体做的事,转发到slave去干活,master也就启到一个领导的角色,它本身就没什么压力了,只负责调度了,那么如果不用k8s的容器的这样架构,就好比在一台机器上装了一个jenkins,然后找台主机做slave,在manage node,添加new node,然后这个就会通过master下发任务让slave去完成了。

那么我们的jenkins是从k8s去部署的,所以我们的slave也是从k8s中去考虑,就可以将这个slave当成一个pod去处理,master就拿这个pod去处理

这个的话也就是可以预先起一些slave,也可以动态的去创建slave,要是预先启动slave的话,可能会消耗一些资源,这些资源也并不是必须要消耗的,所以就要考虑动态的去考虑slave,也就是即开即用,不用就销毁了,当这个量大的时候,这个就比较明显了,所以在k8s中去创建代理可以通过插件去做到的 直接去安装一个kubernetes的插件 安装插件的时候我们需要在我们的持久化目录中去修改我们的国外地址

代码语言:javascript复制
[root@k8s-node3 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

在jenkins页面上restart,重启jenkins生效

要想动态的去在k8s中创建slave pod,所以要连接k8s,定义连接k8s的地址是多少,创建slave pod连接jenkins的地址是多少 找到管理jenkins 这里,找到系统配置configure system 当安装好插件之后,会在最下面发现一个cloud

这里连接k8s直接使用service的地址就可以,因为我们部署了coredns直接可以就要解析到这个k8s,点击连接测试可以连接

如果jenkins是在外部去部署的,也就是单独哪台服务器去部署的,走的传统部署,也就是要连接k8s对外的地址也就是https://10.4.7.11:6443这个端口,并且将它的CA添加进来添加一个凭据,这样才能连接成功

并且还有将jenkins的地址也写上,http://jenkins.default,或者写成连接ip的地址也可以

现在jenkins已经知道要连接哪个k8s,那么再去创建这个slave容器的pod,要创建pod必须就的需要镜像,如果没有镜像,即使jenkins像k8s发起创建一个pod,k8s肯定不知道去创建哪个pod,必须有镜像,这个镜像也就是slave的镜像,所以下一步我们需要做的就是构建一个镜像。 让k8s使用这个镜像拉取一个pod,而这个镜像制作的话,看需要什么环境的包。

六、自定义构建jenkins-slave镜像 jenkins slave制作这个镜像看需要什么? 1、需要什么开发语言?因为slave去完成的需要代码的编译,如果是java的语言,那么就需要maven的环境,如果是go就需要去用go去编译,所以开发语言不一样,slave所做的事环境也不一样 2、额外环境,比如docker,需要打镜像对微服务,以及推送到镜像仓库都需要docker的环境,还会使用helm,helm是直接将服务部署到k8s中。

比如就拿java项目为例,一般微服务也都是使用java去写的 代码的编译,一般使用比较多的就是maven,这个maven还依赖jdk,打包镜像需要docker,我们需要使用helm去做持续部署,所以这也就是这个镜像里面需要封装的依赖

另外还有一点,这个镜像怎么去做为一个slave去存在呢,传统的在页面添加一个就可以了,当连接的时候,会连接到slave节点帮你启动一个agent,也就是下发一个jar包,这个agent就可以直接和master去交互了,那么将这个jar包kill掉,这个slave也就是不可用状态了,所以它们直接的一个交互就是由自己实现的一个程序去完成的,每个slave上面会有一个agent,master与agent去交互,agent去完成这个job任务,那么这个镜像里面也得需要包含这个agent这个jar包,传统的方式会自动的帮你去安装了,但是镜像的还得自己去加上,加上之后才能去连接jenkins,master才能去下发任务

代码语言:javascript复制
[root@k8s-master1 jenkins-slave]# ls
Dockerfile  helm  jenkins-slave  kubectl  settings.xml  slave.jar

这个jenkins-slave是一个脚本主要来启动这个slave.jar,那么这个还需 要一个docker环境,本身这个pod就是以docker container启动的,那么这个pod也就可以做数据卷挂载了,因为这个pod是在每个node上去启动的,每个node上都有docker,那么直接就可以把每个node上的docker挂载到容器中就可以了,所以这就用到了一个docker in docker,这个容器里面又有了一个docker的环境,所以我们需要写数据卷将它挂载进来,还有kubectl这个命名,一般主要来查看pod的一些状态,而且使用这个命令来完成k8s的各种各样的操作,但是需要它这个命令,只需要它连接k8s的认证信息,因为k8s有它的鉴权 现在将这个镜像打包成镜像推送到镜像仓库中

代码语言:javascript复制
[root@k8s-master1 jenkins-slave]# cat Dockerfile 
FROM centos:7

RUN yum install -y java-1.8.0-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  
COPY jenkins-slave /usr/bin/jenkins-slave
COPY settings.xml /etc/maven/settings.xml
RUN chmod  x /usr/bin/jenkins-slave
COPY helm kubectl /usr/bin/

ENTRYPOINT ["jenkins-slave"]

构建镜像的时候可以直接写harbor的地址加镜像的名称,这样直接可以push,不然构建完还得打个tag

代码语言:javascript复制
[root@k8s-master jenkins-slave]# docker build . -t 192.168.30.27/library/jenkins-slave:jdk-1.8
[root@k8s-master jenkins-slave]# docker push 192.168.30.27/library/jenkins-slave 

查看仓库已经将镜像推送成功

现在就可以去测试能不能动态的去创建jenkins-slave,需要将pipeline这个脚本能去调用刚才我们配置的k8s的插件 jenkins官方地址给出了相关的pipeline调用k8s的插件的用法 github地址:https://plugins.jenkins.io/kubernetes

七、基于kubernetes构建jenkins ci系统 现在去动态的在pipeline去引用并创建一个slave的镜像,使用pipeline在k8s去运行这个pod,然后将这个pod发布我们的任务,刚才我们是先安装的插件,以对jenkins可以实现slave的代理,可以正常去交互,又制作了我们的slave的镜像,将java所需的配置也都封装在这个slave里面这样的话,我们就可以采用这个slave来完成下发的任务了,而master只作为一个管理节点给他们下发任务到slave上 这就是我们的pipeline,现在目前是可以使用我们这个slave这个镜像来发布任务

安装pipeline插件

代码语言:javascript复制
pipeline {
   agent {
   kubernetes {
      label 'jenkins-slave'
      yaml """
apiVersion: v1
kind: Pod
metadata:
  name: jenkins-slave
spec:
  containers:
  - name: jnlp
    image: "192.168.30.27/library/jenkins-slave:jdk-1.8"
"""
    }
  }
   stages {
      stage('Build') {
         steps {
            echo "hello" 
         }
      }
      stage('test') {
         steps {
            echo "hello"
         }
      }
      stage('deploy') {
         steps {
            echo "hello"
         }
      }
   }
}

现在可以在jenkins中动态的去创建pod,只有有下发任务的时候才去创建pod,而jenkins-master只负责任务调度,slave来做任务处理来节省资源,当任务完成之后,这个slave-pod也会自动的销毁 [root@k8s-master1 ~]# kubectl get pod NAME READY STATUS RESTARTS AGE jenkins-7d5fbd857d-6tgdj 1/1 Running 0 23m jenkins-slave-gdtqz-p17b1 1/1 Running 0 40s

八、pipeline集成helm发布spring cloud微服务

现在编写这个pipeline脚本来实现自动化发布微服务

代码语言:javascript复制
#!/usr/bin/env groovy
// 所需插件: Git Parameter/Git/Pipeline/Config File Provider/kubernetes/Extended Choice Parameter
// 公共
def registry = "192.168.30.27"
// 项目
def project = "microservice"
def git_url = "http://192.168.30.28/root/ms.git"
def gateway_domain_name = "gateway.zhaocheng.com"
def portal_domain_name = "portal.zhaochengr.com"
// 认证
def image_pull_secret = "registry-pull-secret"
def harbor_registry_auth = "e5402e52-7dd0-4daf-8d21-c4aa6e47736b"
def git_auth = "a65680b4-0bf7-418f-a77e-f20778f9e737"
// ConfigFileProvider ID
def k8s_auth = "7ee65e53-a559-4c52-8b88-c968a637051e"

pipeline {
  agent {
    kubernetes {
        label "jenkins-slave"
        yaml """
kind: Pod
metadata:
  name: jenkins-slave
spec:
  containers:
  - name: jnlp
    image: "${registry}/library/jenkins-slave-jdk:1.8"
    imagePullPolicy: Always
    volumeMounts:
      - name: docker-cmd
        mountPath: /usr/bin/docker
      - name: docker-sock
        mountPath: /var/run/docker.sock
      - name: maven-cache
        mountPath: /root/.m2
  volumes:
    - name: docker-cmd
      hostPath:
        path: /usr/bin/docker
    - name: docker-sock
      hostPath:
        path: /var/run/docker.sock
    - name: maven-cache
      hostPath:
        path: /tmp/m2
"""
        }

      }
    parameters {
        gitParameter branch: '', branchFilter: '.*', defaultValue: '', 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 {
                sh """
                  mvn clean package -Dmaven.test.skip=true
                """
            }
        }
        stage('构建镜像') {
          steps {
              withCredentials([usernamePassword(credentialsId: "${harbor_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
                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
                """
                configFileProvider([configFile(fileId: "${k8s_auth}", targetLocation: "admin.kubeconfig")]){
                    sh """
                    # 添加镜像拉取认证
                    kubectl create secret docker-registry ${image_pull_secret} --docker-username=${username} --docker-password=${password} --docker-server=${registry} -n ${Namespace} --kubeconfig admin.kubeconfig |true

                    # 添加私有chart仓库
                    helm repo add  --username ${username} --password ${password} myrepo http://${registry}/chartrepo/${project}
                    """
                }
              }
          }
        }
        stage('Helm部署到K8S') {
          steps {
              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 helm history ${service_name} ${common_args} &>/dev/null;then
                  action=upgrade
                else
                  action=install
                fi

                # 针对服务启用ingress
                if [ ${service_name} == "gateway-service" ]; then
                  helm ${action} ${helm_args} 
                  --set ingress.enabled=true 
                  --set ingress.host=${gateway_domain_name} 
                   ${common_args}
                elif [ ${service_name} == "portal-service" ]; then
                  helm ${action} ${helm_args} 
                  --set ingress.enabled=true 
                  --set ingress.host=${portal_domain_name} 
                   ${common_args}
                else
                  helm ${action} ${helm_args} ${common_args}
                fi
              done
              # 查看Pod状态
              sleep 10
              kubectl get pods ${common_args}
              """
          }
        }
    }
}

pipeline解析 1、首先去安装这几个插件 Git Parameter 可以实现动态的从git中获取所有分支 Git 拉取代码 Pipeline 刚才安装的pipeline,来实现这个pipeline流水线的发布任务 Config File Provider 主要可以将kubeconfig配置文件存放在jenkins里,让这个pipeline引用这个配置文件,比如构建的slave镜像里面有kubectl,那么连接k8s肯定需要授权,直接拷贝这个命令肯定不好使,而且像helm -v3版本也是通过kubeconfig来连接k8s-api来部署的任务,这两个命令都是通过kubeconfig去读取的k8s,所以我们需要将这两个命令给他们权限来连接k8s,所以需要准备一个kubeconfig文件,能让这个两个工具可以读到,但是这两个命令都是在slave的pod中,所以我们需要使用这个插件将kubeconfig文件由jenkins来保存,然后再通过jenkins特定的语法让它拿到,让它保存到slave中,形成一个动态的文件,动态的加进去,然后helm,kubectl通过这个文件就能直接连接k8s集群了,这样的好处主要是安全,也可以直接将这个kubeconfig文件打到镜像中,也可以,但是这样也不是很安全,别人一旦拿到这个kubeconfig文件久相当于把kubectl的权限给别人了,可以访问k8s集群了,这样就不安全了,所以我们还是按插件的方式去安装这个,动态的放在jenkins中还是比较好的 kubernetes 动态的去创建代理,好让k8s连接到jenkins,可以动态的去伸缩slave节点 Extended Choice Parameter 进行对选择框插件进行扩展,可以多选,扩展参数构建,而且部署微服务还需要多选,比如同时发布两个微服务,三个微服务,不可能每发布一个要点一下,这样肯定是不现实的。 2、参数含义 // 公共 def registry = "192.168.30.27" 比如镜像仓库的地址 // 项目 def project = "microservice" 项目的名称 def git_url = "http://192.168.30.28/root/ms.git" 微服务的gitlab的项目的git地址 def gateway_domain_name = "gateway.zhaocheng.com" 微服务里面有几个对外提供服务,指定域名 def portal_domain_name = "portal.zhaocheng.com" 微服务里面有几个对外提供服务,指定域名,因为不是所有的微服务都是提供域名的,所以要为需要配置的提供一个域名 // 认证 def image_pull_secret = "registry-pull-secret" 当helm去部署应用的时候,我们需要拉取去在拉取仓库的镜像与k8s进行认证,那么这个就是在k8s中去创建好的,也就是创建k8s登录harbor信息的一个认证,一般会在yaml中定义imagePullSecrets,镜像拉取的认证 可以通过kubectl create secret docker-registry registry-pull-secret --docker-server=192.168.30.27 --docker-username=admin --docker-password=Harbor12345 --docker-email=admin@ooo.com -n ms 命令去创建 def harbor_registry_auth = "e5402e52-7dd0-4daf-8d21-c4aa6e47736b" 也就是拉取镜像时需要docker login登录一下才可以拉,一般私有的需要指定这个密钥 def git_auth = "a65680b4-0bf7-418f-a77e-f20778f9e737" 这个是拉取git的时候需要指定的认证需要保存一下 // ConfigFileProvider ID def k8s_auth = "7ee65e53-a559-4c52-8b88-c968a637051e" 这就是k8s的认证,这个也就是保存在kubeconfig中 这些都是定义的公共的变量,这些变量主要是让脚本适用于一个通用性,将一些变动的值传入进去这样主要可以让项目动态的去适配了

3、动态的在k8s中去创建slave-pod pipeline { agent { kubernetes { label "jenkins-slave" 指定标签 yaml """ kind: Pod metadata: name: jenkins-slave 指定pod的名字 spec: containers:

  • name: jnlp 默认使用jnlp image: "${registry}/library/jenkins-slave-jdk:1.8" 使用我们封装好的slave的镜像 imagePullPolicy: Always 镜像拉取策略,始终拉取镜像仓库的镜像 volumeMounts: 挂载的数据卷,我们在构建jenkins-slave镜像的时候,docker需要数据卷挂载
    • name: docker-cmd 也就是docker in docker mountPath: /usr/bin/docker
    • name: docker-sock mountPath: /var/run/docker.sock
    • name: maven-cache 主要是将容器中maven拉取依赖包的缓存挂载到宿主机的/tmp/m2下,一旦宿主机都具备这个缓存的话, mountPath: /root/.m2 那么以后构建都会先读取缓存,或者也就是可以将这些包放到共享存储里面pvc中去读取,可以在这个yaml volumes: 中去定义也都可以
      • name: docker-cmd hostPath: path: /usr/bin/docker 将宿主机上的/var/bin/docker和/var/run/docker.sock 挂载到容器中的目录mountpath:对应目录中
      • name: docker-sock 这样容器就可以使用docker命令了, hostPath: path: /var/run/docker.sock
      • name: maven-cache hostPath: path: /tmp/m2 """ }

      } 4、参数化构建 parameters { gitParameter branch: '', branchFilter: '.', defaultValue: '', description: '选择发布的分支', name: 'Branch', quickFilterEnabled: false, selectedValue: 'NONE', sortMode: 'NONE', tagFilter: '', type: 'PT_BRANCH' ###动态的去获取参数 extendedChoice defaultValue: 'none', description: '选择发布的微服务', ###加了扩展,可以多选发布多个微服务 微服务找出我们需要哪些需要人工交互的 就是使用的这套微服务都适用于这套chart模版 1、微服务名称,以及针对一些服务需要带上域名,另外比如去配置的微服务的名字都是不一样的,这个名字是保证是唯一的,需要使用include,,一般写在_helpers。tpl下,因为我们部署的时候已经拿到微服务的名称了,所以helm起的名字也是微服务的名字,然后再加上公用的标签就区分出来了,另外就是微服务的端口也是不一样的 2、端口,每个微服务的端口也都不一 3、命名空间 使用helm -n 就可以部署到指定的命名空间了 4、副本数 这个本来在helm中是3个副本,我们可以通过传参的形式变成5或者2都可以 5、资源的限制,本身这个k8s中的限制是无法满足一个java应用的限制的,一般1.8jdk版本是不兼容的,新的版本是兼容的,所以手动的去指定它的对内存的大小,这个一般在dockerfile启用jar包的时候带入 6、chart模版的选择 可能一个项目满足不了一个项目,那么可能就得需要两个模版来实现 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') }

然后需要将这个chart模版添加到repo里

代码语言:javascript复制
[root@k8s-master1 ~]# helm repo add  --username admin --password Harbor12345 myrepo http://192.168.30.27/chartrepo/library
"myrepo" has been added to your repositories
[root@k8s-master1 ~]# helm repo list
NAME    URL                                
myrepo  http://192.168.30.27/chartrepo/library

将helm制作完成后打包并push到仓库中,然后当我们部署的时候就去拉这个helm模版地址

代码语言:javascript复制
[root@k8s-master1 ~]# helm push ms-0.1.0.tgz --username=admin --password=Harbor12345 http://192.168.30.27/chartrepo/library
Pushing ms-0.1.0.tgz to http://192.168.30.27/chartrepo/library...
Done.

代码语言:javascript复制
5、jenkins-slave所执行的具体任务
 stages {
        stage('拉取代码'){
            steps {
                checkout([$class: 'GitSCM',                      
                branches: [[name: "${params.Branch}"]], 
                doGenerateSubmoduleConfigurations: false, 
                extensions: [], submoduleCfg: [], 
                userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]     它需要将这个参数传给上面的git parameters,让它能够动态的git地址中拉取所有的分支,
                ])
            }
        }
        stage('代码编译') {
            // 编译指定服务
            steps {
                sh """
                  mvn clean package -Dmaven.test.skip=true           
                """
            }
        }
        stage('构建镜像') {
          steps {
              withCredentials([usernamePassword(credentialsId: "${harbor_registry_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {                    这里使用了一个凭据的认证将连接harbor认证信息保存到凭据里面,为了安全性,使用了凭据的引用,动态的将它保存到变量中,然后通过调用变量的形式去登录这镜像仓库,这样的话就不用在pipeline中去体现密码了,
                sh """
                 docker login -u ${username} -p '${password}' ${registry}
                 for service in $(echo ${Service} |sed 's/,/ /g'); do
                    service_name=${service%:*}                因为我们是部署的微服务,所以我们需要很多的服务的构建,所以这里加了一个for循环
它调用的$service正是参数化构建中的选择的services,然后根据不同的服务推送到镜像仓库,
                    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
                """   之前说需要kubeconfig这个配置存到jenins中的slave的pod中,起个名字叫admin.kubeconfig
                configFileProvider([configFile(fileId: "${k8s_auth}", targetLocation: "admin.kubeconfig")]){
                    sh """
                    # 添加镜像拉取认证   当使用拉取镜像的认证信息的时候就可以直接指定admin.kubeconfig了,它就能连接到这个集群了
                    kubectl create secret docker-registry ${image_pull_secret} --docker-username=${username} --docker-password=${password} --docker-server=${registry} -n ${Namespace} --kubeconfig admin.kubeconfig |true
                    # 添加私有chart仓库到这个pod中
                    helm repo add  --username ${username} --password ${password} myrepo http://${registry}/chartrepo/${project}
                    """
                }
              }
          }
        }
------------------------------------------------------------------------------------------------------------------------------------------
6、deploy,使用helm部署到k8s中
stage('Helm部署到K8S') {
          steps {
              sh """定义公共的参数,使用helm,kubectl都要加namespace命名空间,连接k8s认证的kubeconfig文件
              common_args="-n ${Namespace} --kubeconfig admin.kubeconfig"

              for service in  $(echo ${Service} |sed 's/,/ /g'); do       for循环每个微服务的端口都不一样,所以在微服务这里添加微服务的名字和它对应的端口,把选择的服务和端口进行拆分
                service_name=${service%:*}
                service_port=${service#*:}
                image=${registry}/${project}/${service_name}
                tag=${BUILD_NUMBER} 。 jenkins构建的一个编号
                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 helm history ${service_name} ${common_args} &>/dev/null;then 那么加一个判断看看是不是部署了,为假就install,为真就upgrade
                  action=upgrade 旧部署的使用upgrade更新
                else
                  action=install 新部署的使用install
                fi

                # 针对服务启用ingress
                if [ ${service_name} == "gateway-service" ]; then
                  helm ${action} ${helm_args} 
                  --set ingress.enabled=true  为true就启用ingress,因为chart肯定默认的为force,就是不启用ingress
                  --set ingress.host=${gateway_domain_name} 
                   ${common_args}
                elif [ ${service_name} == "portal-service" ]; then 
                  helm ${action} ${helm_args} 
                  --set ingress.enabled=true 
                  --set ingress.host=${portal_domain_name} 
                   ${common_args}
                else
                  helm ${action} ${helm_args} ${common_args} 
                fi
              done
              # 查看Pod状态
              sleep 10
              kubectl get pods ${common_args}
              """
          }
        }
    }
}

修改一些认证信息,这个pipeline最好先从一个地方复制一下,修改完再放进pipeline
gitlab的项目地址def git_url = "http://192.168.30.28/root/ms.git"
修改harbor的凭据
选择credentials

点击jenkins

add 添加凭据

填写harbor的用户名和密码,密码Harbor12345 描述随便写,

再添加第二个

git的用户名和密码

然后更新一下,把密钥放到指定的pipeline中

将这个id放到pipeline中

将生成的密钥认证放到pipeline中 // 认证 def image_pull_secret = "registry-pull-secret" def harbor_registry_auth = "7177c1f3-9e6b-435c-b4cc-187c742c4516" def git_auth = "28484aa2-aeb4-479b-ad43-cf12c2a7d445" 然后将我们需要的插件都安装上 // 所需插件: Git Parameter/Git/Pipeline/Config File Provider/kubernetes/Extended Choice Parameter 现在去添加kubeconfig的文件

将这个ID放到我们k8s-auth的pipeline中,这个配置文件是k8s连接kubeconfig的ID,如果是kubeadm部署的需要到[root@k8s-master ~]# cat /root/.kube/config 这个文件下将文件拷贝到jenkins中 apiVersion: v1 clusters:

  • cluster: certificate-authority-data: 如果是二进制部署的需要到k8s的证书下面找到kubeconfig.sh下面手动进行生成,bash kubeconfig.sh,会生成一个admin客户端的连接k8s配置,将这个配置粘贴到jenkins的刚才生成config的content下面

最后进行测试发布在pipeline的配置指定发布的服务进行发布 查看pod的状态

0 人点赞