需要一个springcloud项目
需要会一些基本的Dockerfile构建镜像
需要四台centos
搞一台,安装好docker,然后克隆三台就可以了(完整克隆)
太冗长,不是重点,就不上代码了
代码如下(示例):
#解压
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新建成员(即给项目分配权限):
代码如下(示例):
#打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
代码如下(示例):
代码如下(示例):
不管push 还是 pull 都需要登入,登入就需要加入信任列表,加了信任列表需要restart docker
Jenkinsfile要引用变量 "${}" 需要用双引号,单引号不能解析
maven打包插件父项目和common是不需要的
common是依赖父项目的,如果没有父项目的pom,其他依赖common的项目是打不了包的(就算是install了common )
多看日志,大多数问题静下心看日志都能解决
Jenkins(编译,打包)–>sonarqube(代码审查)–>docker(构建镜像)–>发布到harbor(示例):
编写Dockerfile
编写sonar-project.properties(如果报错说找不到class什么,末尾追加sonar.java.binaries=.)
pom文件添加maven插件(dockerfile 构建 image 插件)
<plugin>
<groupId>com.spotifygroupId>
<artifactId>dockerfile-maven-pluginartifactId>
<version>1.3.6version>
<configuration>
<repository>${project.artifactId}repository>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jarJAR_FILE>
buildArgs>
configuration>
plugin>
在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="[email protected]: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中看到上传的镜像,说明成功了
由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
代码如下(示例):
前端我没写,我也没有视频源码(记录一下步骤)
搜索插件: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:
'[email protected]: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)
代码如下(示例):
上述部署方案存在的问题:
- 一次只能选择一个微服务部署
- 只有一台生产者部署服务器
- 每一个微服务只有一个实例,容错率低
优化方案:
- 在一个Jenkins工程中可以选择多个微服务同时发布
- 在一个Jenkins工程中可以选择多台生产服务器同时部署
- 每个微服务都是以集群高可用形式部署
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="[email protected]: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)])
}
}
}
}
代码如下(示例):
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内存放的就是刚刚拉取下来的项目了
传统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到集群中,从而实现扩展
kubernates博客链接跳转
文章主要内容来自B站黑马