Jenkins进阶
使用步骤
1.环境准备
需要一个springcloud项目
需要会一些基本的Dockerfile构建镜像
需要四台centos
搞一台,安装好docker,然后克隆三台就可以了(完整克隆)
太冗长,不是重点,就不上代码了
2.下载harbor
代码如下(示例):
#解压
tar -zxf harbor-offline-installer-v1.9.2.tgz
#移动
mv harbor /opt/
#进入harbor
cd /opt/harbor/
#修改yml文件
vim harbor.yml
#运行prepare
./prepare
#就会开始下载一些初始化镜像
#2.2.2版本prepare报错了,百度没找到答案,换成和视频相同版本1.9.2
#安装(安装的同时服务就已经开启了,可以用docker ps查看)
./install.sh
#访问harbor
192.168.59.132:85
#默认账号密码
admin--Harbor12345
harbor新建项目(示例):
harbor创建用户(示例):
harbor新建成员(即给项目分配权限):
3.将docker镜像push到harbor
代码如下(示例):
#打tag,tag名为 harbor地址+harbor项目名+镜像push后的名字
docker tag eureka-8001:latest 192.168.59.132:85/springcloud/eureka-8001
#修改docker配置文件,将harbor地址添加到docker的信任列表
vim /etc/docker/daemon.json
#修改内容
"insecure-registries": ["192.168.59.132:85"]
#重启docker
systemctl restart docker
#push发现访问被拒绝,因为harbor中的项目为私有,所以需要登入
#登入之前注册的账号,或者harbor默认的账号(Login Succeeded代表登入成功)
docker login -u chen -p Bmw123456 192.168.59.132:85
#push镜像
docker push 192.168.59.132:85/springcloud/eureka-8001
4.把镜像从harbor pull下来
代码如下(示例):
- 添加信任列表:不论上传或者下载都需要添加信任列表
- 登入,因为私有,需要权限
-
回到harbor界面
5.环境搭建小结
代码如下(示例):
不管push 还是 pull 都需要登入,登入就需要加入信任列表,加了信任列表需要restart docker
Jenkinsfile要引用变量 "${}" 需要用双引号,单引号不能解析
maven打包插件父项目和common是不需要的
common是依赖父项目的,如果没有父项目的pom,其他依赖common的项目是打不了包的(就算是install了common )
多看日志,大多数问题静下心看日志都能解决
6.Jenkins+sonarqube+docker+harbor
Jenkins(编译,打包)–>sonarqube(代码审查)–>docker(构建镜像)–>发布到harbor(示例):
编写Dockerfile
编写sonar-project.properties(如果报错说找不到class什么,末尾追加sonar.java.binaries=.)
pom文件添加maven插件(dockerfile 构建 image 插件)
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<repository>${project.artifactId}</repository>
<!--这个就是dockerfile里面需要传进去的arg参数,动态获取-->
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
创建Jenkins凭证
在Jenkins中使用凭证(流水线语法生成代码)
片段生成器–>withCredentials: Bind credentials to variables–>Username and password (separated)
编写Jenkinsfile
//git凭证
def git_auth="a5851cde-faab-47fb-92f0-7e0066dc110a"
//git url
def git_url="git@gitee.com:jx-chen/jenkins_springcloud.git"
//tag
def tag="latest"
//harbor-url
def harbor_url="192.168.59.132:85"
//harbor项目名
def harbor_project="springcloud"
//harbor凭证id
def harbor_auth="7a206b05-1a6b-46bb-a1ba-b632fd739db1"
node {
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: '*/${branch}']], extensions: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])
}
stage('代码审查') {
script {
//引用了之前全局工具配置里面配置的sonar-scanner
scannerHome = tool 'sonar-scanner'
}
//引用之前系统配置里面配置的sonarqube-server,sonarqube是当时起的名字
withSonarQubeEnv('sonarqube') {
sh """
cd ${project_name}
${scannerHome}/bin/sonar-scanner
"""
}
}
stage('编译,安装公共子工程') {
sh "mvn clean install"
}
stage('编译,打包微服务工程,上传镜像') {
sh "mvn -f ${project_name} clean package dockerfile:build"
//定义镜像名字
def imageName="${project_name}:${tag}"
//打tag
sh "docker tag ${imageName} ${harbor_url}/${harbor_project}/${imageName}"
//登入harbor账号 and push镜像
withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
// 登入
sh "docker login -u ${username} -p ${password} ${harbor_url}"
// push镜像到harbor
sh "docker push ${harbor_url}/${harbor_project}/${imageName}"
sh "echo 镜像push成功"
}
}
}
其他服务基本上都差不多
Dockerfile改下端口
sonar-project.properties改下名字
Dockerfile-maven插件复制粘贴当在harbor中看到上传的镜像,说明成功了
7.从harbor拉取镜像并部署
由Jenkins发送SSH远程调用,并执行被调用者的shell脚本,实现pull and deploy(示例):
搜索插件: Publish Over SSH --> install
Manage Jenkins --> Configure System
#在Jenkins服务器上使用ssh-copy-id将秘钥copy给pull and deploy服务器(需要输入密码)
ssh-copy-id 192.168.59.133
如果测试失败,报错(Failed to add SSH key. Message [invalid privatekey: [B@2a9cc18e])
可能是密匙版本太高,这种开头的(-----BEGIN OPENSSH PRIVATE KEY-----)
使用这个命令重新生成密匙并copy(ssh-keygen -m PEM -t rsa -b 4096
)
生成后是这种开头的(-----BEGIN RSA PRIVATE KEY-----)
然后重新copy应该就可以了
生成的密匙都在/root/.ssh
目录下
编写Jenkinsfile
片段生成器 --> sshPublisher: Send build artifacts over SSH(直接生成,不需要填写,其中最主要的参数为Exec command)
可以看到用到了前面创建的SSH Server
execCommand中的意思是去执行这个文件/opt/jenkins_shell/deploy.sh
并且携带了4个参数(最后一个参数port , 添加一个参数化构建)
//部署应用
sshPublisher(publishers: [sshPublisherDesc(configName: 'master_server', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "/opt/jenkins_shell/deploy.sh $harbor_url $harbor_project $project_name $tag $port", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
编写shell脚本
#! /bin/sh
#接收外部参数
harbor_url=$1
harbor_project_name=$2
project_name=$3
tag=$4
port=$5
imageName=$harbor_url/$harbor_project_name/$project_name:$tag
echo "$imageName"
#查询容器是否存在,存在则删除
containerId=`docker ps -a | grep -w ${project_name}:${tag} | awk '{print $1}'`
if [ "$containerId" != "" ] ; then
#停掉容器
docker stop $containerId
#删除容器
docker rm $containerId
echo "成功删除容器"
fi
#查询镜像是否存在,存在则删除
imageId=`docker images | grep -w $project_name | awk '{print $3}'`
if [ "$imageId" != "" ] ; then
#删除镜像
docker rmi -f $imageId
echo "成功删除镜像"
fi
# 登录Harbor私服
docker login -u chen -p Bmw123456 $harbor_url
# 下载镜像
docker pull $imageName
# 启动容器
docker run -di -p $port:$port $imageName
echo "容器启动成功"
当容器在master_server服务器启动成功,代表部署成功(不知道为什么shell脚本的echo没有在Jenkins日志中输出)
最后修改application.yaml文件里的地址(改成master_server的地址,全部服务都要改)
费劲千辛万苦,终于都启动了
当执行feign调用时发现异常(java.net.UnknownHostException: cf90055b05e9)
cf90055b05e9为容器id
幸好docker有些基础,默认的network容器之间是无法ping通的
所以要使用自己创建的网络
#创建mynet网络
docker network create mynet
#将所有容器都连接到自己创建mynet上
docker network connect mynet 4bf778c8575d
docker network connect mynet f39997bd7ab2
docker network connect mynet cf90055b05e9
docker network connect mynet 3d64bb63762a
#查看mynet
docker network inspect mynet
现在feign和gateway都能正常访问了
不知道视频上为什么直接就成功了,有知道的大佬告知下可以直接修改deploy.sh,启动容器的时候加上参数 --net mynet
8.部署前端网站
代码如下(示例):
前端我没写,我也没有视频源码(记录一下步骤)
搜索插件:NodeJS --> install
Manage Jenkins --> Global Tool Configuration(全局工具配置)创建一个流水线项目拉取前端代码
前端Jenkinsfile脚本
//gitlab的凭证
def git_auth = "a5851cde-faab-47fb-92f0-7e0066dc110a"
node {
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: '*/${branch}']],
doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [],
userRemoteConfigs: [[credentialsId: "${git_auth}", url:
'git@192.168.66.100:itheima_group/tensquare_front.git']]])
}
stage('打包,部署网站') {
//使用NodeJS的npm进行打包
nodejs('nodejs12'){
sh '''
npm install
npm run build
'''
}
//=====以下为远程调用进行项目部署========
sshPublisher(publishers: [sshPublisherDesc(configName: 'master_server',
transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '',
execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes:
false, patternSeparator: '[, ]+', remoteDirectory: '/usr/share/nginx/html',
remoteDirectorySDF: false, removePrefix: 'dist', sourceFiles: 'dist/**')],
usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
核心代码:
sourceFiles: 'dist/**' (此处为npm build后的代码)
remoteDirectory: '/usr/share/nginx/html'(此处为远程目录)
和之前execCommand(执行远程脚本)不同,此处执行的是一个copy操作,减npm build后的代码copy到远程的/usr/share/nginx/html中(Nginx)
9.持续部署方案优化
代码如下(示例):
上述部署方案存在的问题:
- 一次只能选择一个微服务部署
- 只有一台生产者部署服务器
- 每一个微服务只有一个实例,容错率低
优化方案:
- 在一个Jenkins工程中可以选择多个微服务同时发布
- 在一个Jenkins工程中可以选择多台生产服务器同时部署
- 每个微服务都是以集群高可用形式部署
10.cluster部署
1台Jenkins,一台harbor,两台deploy(一台master,一台slave):
修改eureka的application.yml
spring:
application:
name: eureka
---
server:
port: 8001
spring:
profiles: eureka-server1
eureka:
instance:
hostname: 192.168.59.133
client:
service-url:
defaultZone: http://192.168.59.133:8001/eureka/,http://192.168.59.129:8001/eureka/
---
server:
port: 8001
spring:
profiles: eureka-server2
eureka:
instance:
hostname: 192.168.59.129
client:
service-url:
defaultZone: http://192.168.59.133:8001/eureka/,http://192.168.59.129:8001/eureka/
创建一个Jenkins流水线项目(从Git上面拿Jenkinsfile)
参数化构建默认是没有多选框的,所以需要去下载一个查件
搜索插件: Extended Choice Parameter --> install点击构建,就可以看到下面的效果
添加第二台SSH Server
添加docker信任列表
在Jenkins服务器运行: ssh-copy-id 192.168.59.129 (将公匙copy给第二台ssh server)
添加参数化构建(选择服务器 master or slave)
修改Jenkinsfile
//git凭证
def git_auth="a5851cde-faab-47fb-92f0-7e0066dc110a"
//git url
def git_url="git@gitee.com:jx-chen/jenkins_springcloud.git"
//tag
def tag="latest"
//harbor-url
def harbor_url="192.168.59.132:85"
//harbor项目名
def harbor_project="springcloud"
//harbor凭证id
def harbor_auth="7a206b05-1a6b-46bb-a1ba-b632fd739db1"
node {
//获取当前选择项目的名称
def selectedProjectNames="${project_name}".split(",")
//获取当前选择服务器的名称
def selectedServers="${publish_server}".split(",")
stage('拉取代码') {
checkout([$class: 'GitSCM', branches: [[name: '*/${branch}']], extensions: [], userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_url}"]]])
}
stage('代码审查') {
//循环遍历出所有的项目名
for(int i=0;i<selectedProjectNames.length;i++){
//eureka-8001@8001
def projectInfo=selectedProjectNames[i];
//当前遍历的项目名
def currentProjectName=projectInfo.split("@")[0]
//当前遍历的端口
def currentProjectPort=projectInfo.split("@")[1]
script {
//引用了之前全局工具配置里面配置的sonar-scanner
scannerHome = tool 'sonar-scanner'
}
//引用之前系统配置里面配置的sonarqube-server,sonarqube是当时起的名字
withSonarQubeEnv('sonarqube') {
sh """
cd ${currentProjectName}
${scannerHome}/bin/sonar-scanner
"""
}
}
}
stage('编译,安装公共子工程') {
sh "mvn clean install"
}
stage('编译,打包微服务工程,上传镜像') {
//循环遍历出所有的项目名
for(int i=0;i<selectedProjectNames.length;i++){
//eureka-8001@8001
def projectInfo=selectedProjectNames[i];
//当前遍历的项目名
def currentProjectName=projectInfo.split("@")[0]
//当前遍历的端口
def currentProjectPort=projectInfo.split("@")[1]
sh "mvn -f ${currentProjectName} clean package dockerfile:build"
//定义镜像名字
def imageName="${currentProjectName}:${tag}"
//打tag
sh "docker tag ${imageName} ${harbor_url}/${harbor_project}/${imageName}"
//登入harbor账号 and push镜像
withCredentials([usernamePassword(credentialsId: "${harbor_auth}", passwordVariable: 'password', usernameVariable: 'username')]) {
// 登入
sh "docker login -u ${username} -p ${password} ${harbor_url}"
// push镜像到harbor
sh "docker push ${harbor_url}/${harbor_project}/${imageName}"
sh "echo 镜像push成功"
}
//遍历所有的服务器分别部署
for(int y=0;y<selectedServers.length;y++){
//获取当前服务器的名称
def currentServer=selectedServers[y]
//加上的参数格式,通过不同的参数,启动多文档模块下不同的eureka --spring.profiles.active=
def activeProfile="--spring.profiles.active="
if(currentServer=="master_server"){
activeProfile=activeProfile+"eureka-server1"
}else if(currentServer=="slave_server"){
activeProfile=activeProfile+"eureka-server2"
}
//部署应用
sshPublisher(publishers: [sshPublisherDesc(configName: "${currentServer}", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: "/opt/jenkins_shell/deployCluster.sh $harbor_url $harbor_project $currentProjectName $tag $currentProjectPort $activeProfile", execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
}
}
记得改名字,给执行权限
可以看到已经成功了
11.Jenkins主从架构
代码如下(示例):
Manage Jenkins --> Configure Global Security --> 代理
Manage Jenkins --> Manage Nodes and Clouds
将jar包放在root目录下,然后执行命令(当然需要java环境)
java -jar agent.jar -jnlpUrl http://192.168.59.131:8888/computer/slave1/jenkins-agent.jnlp -secret 65bd25c2158b08eeba6867864f924096496cea5169e71a00222a34ac3a4586ee -workDir "/root/jenkins"
在会到Jenkins刷新页面,可以看到已经连接了
创建一个*风格项目,并让其在salve1节点上运行
当然要确保salve1服务器上面有git环境,不然会报错,无法init
在slave1节点的工作目录(前面配置的),里面可以看到workspace,workspace内存放的就是刚刚拉取下来的项目了
创建流水线项目,并指定节点
一旦退出这个界面,连接就会断开
12.引入kubernates(即 k8s),集群配置失败,无奈转战尚硅谷k8s最新视频
传统Jenkins的master-slave方案的缺陷:
- master节点发生单点故障时,整个流程都不可用了
- 每个slave节点的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲
- 资源分配不均衡,有的slave节点要运行的job出现排队等待,而有的slave节点处于空闲状态
- 资源浪费,每台slave节点可能是实体机或者VM,当slave节点处于空闲状态时,也不会完全释放掉资源
kubernates简介:
kubernates(简称,k8s)是Google开源的容器集群管理系统,在docker技术的基础上,为容器化的应用提供部署运行,资源调度,服务发现和动态伸缩等一系列完整功能,提高了大规模容器集群管理的便捷性
其主要功能如下:
- 使用docker对应用程序包装(package),实例化(instantiate),运行(run)
- 以集群的方式运行,管理跨机器的容器
- 解决docker跨机器容器之间的通讯问题
- kubernates的自我修复机制使得容器集群总是运行在用户期望状态
kubernates+docker+Jenkins持续集成架构图:
kubernates+docker+Jenkins持续集成方案好处
服务高可用
: 当Jenkins master出现故障时,kubernates会自动创建一个新的Jenkins master容器,并且将volume分配给新创建的容器,保证数据不丢失,从而达到集群服务高可用动态伸缩,合理使用资源
: 每次运行job时,会自动创建一个Jenkins slave,job完成后,salve自动注销并删除容器,资源自动释放,而且kubernates会根据每个资源的使用情况,动态分配slave到空闲的节点上创建,降低出现因某节点资源利用率高,还排队等待在该节点的情况扩展性好
: 当kubernates集群的资源严重不足而导致job排队等待时,可以很容易的添加kubernates node到集群中,从而实现扩展
13.kubernates环境搭建
4台centos(1台harbor,1台k8s-master,两台k8s-slave):
设置主机名及修改hosts文件
#修改k8s-master主机名
hostnamectl set-hostname k8s-master
#修改k8s-node1主机名
hostnamectl set-hostname k8s-node1
#修改k8s-node2主机名
hostnamectl set-hostname k8s-node2
#查看主机名
hostname
前面主机名是点对点,这个是全局的
cat>>/etc/hosts<<EOF
192.168.59.135 k8s-master
192.168.59.136 k8s-node1
192.168.59.137 k8s-node2
EOF
#查看hosts文件
cat /etc/hosts
关闭防火墙(全局)
#关闭
systemctl stop firewalld
#关闭开启启动
systemctl disable firewalld
#临时关闭
setenforce 0
#永久关闭
#修改selinux
vim /etc/sysconfig/selinux
#改为
SELINUX=disabled
设置允许路由转发,不对bridge的数据进行处理(全部)
#创建文件
vi /etc/sysctl.d/k8s.conf
#文件内容
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
vm.swappiness = 0
#执行文件
sysctl -p /etc/sysctl.d/k8s.conf
kube-proxy开启ipvs的前置条件(全部)
#写入脚本
cat > /etc/sysconfig/modules/ipvs.modules <<EOF
#!/bin/bash
modprobe -- ip_vs
modprobe -- ip_vs_rr
modprobe -- ip_vs_wrr
modprobe -- ip_vs_sh
modprobe -- nf_conntrack
EOF
#给权限,并执行
chmod 755 /etc/sysconfig/modules/ipvs.modules && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep -E 'ip_vs|nf_conntrack'
报错: nf_conntrack_ipv4 not found
解决办法(百度来的,不知道行不行) :跳转链接
将 nf_conntrack_ipv4 改为 nf_conntrack
关闭swap(全部)
#又回来了,之前注释swap分区导致虚拟机打不开的问题解决了
#临时关闭
swapoff -a
第一步: vim /etc/fstab
第二部: vim /etc/default/grub
第三步: grub2-mkconfig -o /boot/grub2/grub.cfg
安装kubelet,kubeadm,kubectl(全部)
#清空yum缓存
yum clean all
#设置yum镜像源
cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes Repository
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=0
EOF
#安装
yum install -y kubelet kubeadm kubectl
#设置开机启动
systemctl enable kubelet
#查看版本
kubelet --version
master节点单独操作(全部)
#注意: version就是当前版本,addr就是当前master的ip
kubeadm init --kubernetes-version=1.21.1 \
--apiserver-advertise-address=192.168.59.135 \
--image-repository registry.aliyuncs.com/google_containers \
--service-cidr=10.1.0.0/16 \
--pod-network-cidr=10.244.0.0/16
总结几点
- CPU要大于2
- 内存最小2G(2g卡死了,3g吧)
- 关闭swap分区
照着前面的方法init还是报错swap的话,在执行一遍 swapoff -a- 警告:[WARNING IsDockerSystemdCheck]: detected “cgroupfs” as the Docker cgroup driver
vim /etc/docker/daemon.json
加入这一段"exec-opts":["native.cgroupdriver=systemd"]
然后重启docker- 查看日志
journalctl -xeu kubelet- 杂项
#查看版本(sort是排序)
yum --showduplicates list PACKAGE | sort -r
#安装指定版本
yum install PACKAGE-VERSION
#初始化(对于init失败情况)
kubeadm reset
#执行提示命令
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
#安装Calico(用于master和slave之间的网络通讯)
cd /root/ && mkdir k8s && cd /root/k8s
#下载yaml文件(下载不下来的话打开网页复制粘贴)
wget https://docs.projectcalico.org/v3.10/gettingstarted/kubernetes/installation/hosted/kubernetes-datastore/caliconetworking/1.7/calico.yaml
#修改通讯地址
sed -i 's/192.168.0.0/10.244.0.0/g' calico.yaml
#安装calico
kubectl apply -f calico.yaml
#查看所有命名空间的pod
kubectl get pod --all-namespaces
#等待所有pod处于READY状态(大概几分钟)
NAMESPACE NAME READY STATUS
总结
文章主要内容来自B站黑马
对于一个学java的我来说,k8s搭建还是太难了,比之前seata环境都难
几乎是每一步都有新的问题,百度也找不到的那种