DevOps 是一种文化和哲学,它强调软件开发 (Dev) 和 IT 运维 (Ops) 之间的紧密协作与整合,目的是提高软件产品的交付速度和质量。DevOps 的核心理念在于打破传统软件开发过程中开发团队和运维团队之间的壁垒,通过自动化和持续改进的方法来缩短从开发到部署的时间,并确保整个流程的高效率和可靠性。
DevOps 的关键实践包括:
DevOps 实践通常依赖于一系列工具和技术,包括但不限于:
通过实施 DevOps,企业可以实现更快的上市时间,更高的软件质量和更有效的资源利用。
这里建立的虚拟机IP后面换成自己的就行了
centos安装docker - 问尤龙の时光 (wenyoulong.com)
centos安装docker compose - 问尤龙の时光 (wenyoulong.com)
docker compose安装gitlab - 问尤龙の时光 (wenyoulong.com)
docker安装jenkins - 问尤龙の时光 (wenyoulong.com)
容器内部自带了jdk,进入容器内获取安装路径配置即可
docker exec -it -u root jenkins bash
执行命令获取java安装位置
echo $JAVA_HOME
访问路径可以配置jdk
http://192.168.126.6:8080/manage/configureTools/
容器没有内置的maven
我们可以自己下载一个maven上传到jenkins,也可以使用jenkins下载一个maven
这里可以选择一个maven让jenkins自己去下载
我选择上传我自己的maven到容器内部
进入虚拟机maven所在目录,将maven复制到容器opt目录下
docker cp ./apache-maven-3.6.3/ jenkins:/opt/
再进入容器
docker exec -it -u root jenkins bash
切换到opt目录下就可以看到刚刚复制的maven了,然后更改maven配置中的以来下载位置,镜像地址等信息
然后回到jenkins页面配置maven安装目录
到这里还要对maven的文件和maven的仓库文件进行授权,修改maven的settings.xml中配置的仓库路径和下载镜像地址
# 进入容器
docker exec -it -u root jenkins bash
# 给仓库权限
mkdir -p /opt/mavenRepo/
chmod -R 777 /opt/mavenRepo/
# 给maven权限
chmod -R 777 /opt/apache-maven-3.6.3/
新建一个spring boot的demo项目上传到刚刚搭建的gitlab中
再jenkins中新建任务
然后可以看到git的操作日志,到这里就实现了将代码拉取到jenkins中
增加一个clean package的步骤
clean package -DskipTests
在此点击构建然后查看日志
显示编译成功
我们这里在建一台虚拟机192.168.126.7
在系统管理中配置这台虚拟机的链接信息
保存后回到构建后操作,将文件传输到服务器
再回去构建,日志就会显示文件已发送(这里截图是我后面加了docker文件补的,所以有三个文件传输,实际到这里只有一个jar过去)
如果要部署后启动,可以再构建后操作那里加上java -jar 来直接启动,但是要求目标服务器上安装了jdk
在项目中新增docker文件夹,下面新增DockerFile和docker-compose.yml文件
DockerFile文件内容
FROM adoptopenjdk/openjdk11
#把打好的jar包放到容器的/opt目录下
COPY demo.jar /opt/
WORKDIR /opt
CMD java -jar demo.jar
docker-compose.yml文件内容
version: '3.1'
services:
demo:
build:
#DockerFile文件名和所处位置
context: ./
dockerfile: DockerFile
image: 'demo:v1.0'
container_name: demo
ports:
- '8081:8080'
然后去修改jenkins配置
完成后点击构建,稍等片刻可以看到日志里操作成功,去目标服务器上执行docker images和docker ps可以看到对应的镜像和容器
访问页面也能看到对应的内容
jenkins配置标签,这里用到我们之前安装的git parameter插件,新增tag配置
再git中新建一个标签1.0
修改代码controller
@RestController
@RequestMapping("/hello")
public class HelloController {
@GetMapping("/sayHello")
public String helloWorld(){
return "Hello WenYL V2.0";
}
}
修改docker-compose.yml
version: '3.1'
services:
demo:
build:
#DockerFile文件名和所处位置
context: ./
dockerfile: DockerFile
image: 'demo:v2.0'
container_name: demo2
ports:
- '8082:8080'
提交后,再创建一个tag 2.0,此时两个tag中的端口和接口返回内容有区别,方便后面我们对比
构建时就可以看到我们在git创建的两个标签
选择对应的标签构建就可以得到不同版本的代码镜像容器了
这里容器只有一个是因为我们的构建后操作中,我们两次后传递到服务器的docker-compose.yml文件有覆盖,然后我们时先做了docker-compose down再执行的docker-compose up,第二次执行就会关掉第一次执行时的容器,然后创建了一个名为demo2的容器,学习过程中,可以先启动1.0查看了结果后,再启动2.0
docker compose安装SonarQube+SonarScanner - 问尤龙の时光 (wenyoulong.com)
进入jenkins插件安装,搜索SonarQube Scanner安装插件
安装完成后,访问http://192.168.126.6:8080/restart路径重启jenkins
重启后进入全局工具配置,设置SonarQube
这里我们再添加一个token,回到SonarQube先生成一个token
回到jenkins添加这个token
然后选择令牌
SonarScanner需要挂载到jenkins内部,将sonar-scanner文件夹复制到jenkins挂载的数据卷下,重启jenkins容器
前面我们已经安装了SonarScanner,这里要在配置一下
构建步骤要添加一个代码校验,顺序放在拉取代码和编译代码后面
properties内容如下
sonar.projectname=${JOB_NAME}
sonar.projectKey=${JOB_NAME}
sonar.source=./
sonar.java.binaries=target
在执行构建就能看到sonar-scanner执行的命令
执行完成还会显示代码检测结果的地址,点击即可查看
新建一个repo项目
切换到我们生成镜像的服务器
修改docker配置文件
vi /etc/docker/daemon.json
新增内容配置链接私有docker仓库,我的docker仓库使用的是http,所以配置insecure-registries参数
"insecure-registries":["192.168.126.6:80"]
完成后文件内容如下
重启docker
systemctl daemon-reload
systemctl restart docker
登录harbor仓库
docker login -u admin -p Harbor12345 192.168.126.6:80
切换到部署应用的服务器上,有如下几个仓库,现在尝试将demo v1.0推送到docker仓库
先给镜像打一个标签
docker tag 79a56aca69ae 192.168.126.6:80/repo/demo:v1.0
然后在执行docker images查看镜像多了一条标签信息
将这个镜像推送到私有docker仓库
docker push 192.168.126.6:80/repo/demo:v1.0
去harbor中查看镜像
要在jenkins容器内部实现将代码编辑打包后制作成一个docker镜像,并将镜像推送到harbor仓库
这里要注意的是,jenkins我们运行在容器中,所以首先要实现在容器内部使用宿主机的docker,实现镜像制作、推送
jenkins容器内部使用宿主机docker参考下述文章
docker容器内部使用docker - 问尤龙の时光 (wenyoulong.com)
这里执行完,重新构建了docker容器后,里面war包的版本可能又会出现问题,再参考下述文章解决
docker compose安装jenkins - 问尤龙の时光 (wenyoulong.com)
同时maven也需要重新放到容器,参考本文2.4.2章节
构建步骤修改,取消maven打包后推送jar包到部署服务器的操作
删除项目中的docker-compose.yml文件,只保留DockerFile,修改HelloController中版本为3.0
代码提交到git仓库,并在git仓库中新增标签3.0
构建步骤那里新增shell操作
内容如下
mv target/*.jar docker/
docker build -t 192.168.126.6:80/repo/demo:$tag -f docker/DockerFile docker/
docker login -u admin -p Harbor12345 192.168.126.6:80
docker push 192.168.126:80/repo/demo:$tag
保存后执行一次构建,选择3.0标签
看日志推送成功
去宿主机上查看镜像
去DockerHub上查看镜像
vi /usr/bin/deploy.sh
harbor_address=$1
harbor_repo=$2
project=$3
version=$4
host_port=$5
container_port=$6
imageName=$harbor_address/$harbor_repo/$project:$version
echo $imageName
containerId=`docker ps -a | grep ${project} | awk '{print $1}'`
if [ "$containerId" != "" ]; then
docker stop $containerId
docker rm $containerId
fi
imageTag=`docker images | grep ${project} | awk '{print $2}'`
if [[ "$imageTag" =~ "$version" ]]; then
docker rmi -f $imageName
fi
docker login -u admin -p Harbor12345 $harbor_address
docker pull $imageName
docker run -d -p $host_port:$container_port --name $project $imageName
保存后修改文件权限
chmod -R 777 /usr/bin/deploy.sh
新增两个port参数填写项目占用的端口和宿主机映射端口
新增调用目标服务器脚本
deploy.sh 192.168.126.6:80 repo ${JOB_NAME} $tag $host_port $container_port
保存后执行构建,选择3.0的tag执行成功
参考下述文章
jenkins流水线操作 - 问尤龙の时光 (wenyoulong.com)
Kubernetes的安装和基础操作 (wenyoulong.com)
在k8s的master和worker节点修改docker配置
vi /etc/docker/daemon.json
新增内容配置链接私有docker仓库,我的docker仓库使用的是http,所以配置insecure-registries参数
"insecure-registries":["192.168.126.6:80"]
配置dockerhub账号密码
在2.8中,我们设置了一个流水线操作,基于git项目中的Jenkinsfile文件,在原来的操作中我们处理好镜像后,需要通知应用服务器拉取应用,但是在这里,我们期望的是K8S的master节点,通知worker节点根据我们定义好的pipeline.yml文件,生成pod、service和ingress
在项目中新增pipeline.yml,内容如下,注意这里docker pull的时候任然时手动指定的镜像版本v6.0
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: test
name: pipeline
labels:
app: pipeline
spec:
replicas: 2
selector:
matchLabels:
app: pipeline
template:
metadata:
labels:
app: pipeline
spec:
containers:
- name: pipeline
image: 192.168.126.6:80/repo/pipeline:v6.0
imagePullPolicy: Always
ports:
- containerPort: 8080
imagePullSecrets:
- name: harbor
---
apiVersion: v1
kind: Service
metadata:
namespace: test
labels:
app: pipeline
name: pipeline
spec:
selector:
app: pipeline
ports:
- port: 8081
targetPort: 8080
type: NodePort
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: test
name: pipeline
spec:
ingressClassName: ingress
rules:
- host: long.pipeline.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: pipeline
port:
number: 8081
k8sMaster节点新增目录
mkdir /opt/k8s
jenkins中配置ssh server,测试配置成功后保存
Jenkinsfile中原来的操作时通知服务器部署应用
现在把这个命令删了,到jenkins的流水线语法中生成命令
原来部署的节点就改为了
stage('pipeline.yml传输到k8sMaster') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'k8sMaster', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: 'pipeline.yml')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
到这里我们就实现了想yml文件发送到k8sMaster,但是此时还需要在jenkins中调用k8s的kubectl命令,因此还需要实现jenkins所在服务器和k8s服务器的ssh免密登录,让jenkins调用k8s
这里我们只在jenkins中单向访问k8sMaster,而jenkins又是运行在容器中,所以直接以root身份进入容器执行操作即可,jenkins中的普通用户的ssh以root身份登录k8sMaster,要注意操作的角色,以此将对应角色的ssh公钥推送过去
# jenkins用户以root身份登录k8sMaster
ssh-copy-id -i /var/jenkins_home/.ssh/id_rsa.pub root@192.168.126.4
免密登录参考SSH免密登录 - 问尤龙の时光 (wenyoulong.com)
回到流水线语法,进行如下操作
Jenkinsfile此时就要新增一个节点
stage('k8sMaster部署应用') {
steps {
sh 'ssh root@192.168.126.4 kubectl apply -f /opt/k8s/pipeline.yml'
}
}
完整Jenkinsfile文件如下
pipeline {
agent any
environment {
harborUser="admin"
harborPwd="Harbor12345"
harborAddress="192.168.126.6:80"
harborRepo="repo"
}
// 存放所有任务的合集
stages {
stage('拉取Git代码') {
steps {
checkout scmGit(branches: [[name: '${tag}']], extensions: [], userRemoteConfigs: [[credentialsId: '8a03a40a-7f4d-43e9-b35e-5329093f59d1', url: 'http://192.168.126.6:8929/root/demo.git']])
}
}
stage('maven编译代码') {
steps {
sh '/opt/apache-maven-3.6.3/bin/mvn clean package -DskipTests'
}
}
stage('SonarQube检测代码质量') {
steps {
sh '/var/jenkins_home/sonar-scanner/bin/sonar-scanner -Dsonar.sources=./ -Dsonar.projectname=${JOB_NAME} -Dsonar.projectKey=${JOB_NAME} -Dsonar.java.binaries=./target/ -Dsonar.login=squ_aa85b8b5d377365e795102e555498d70a0e7f9e6'
}
}
stage('构建Docker镜像') {
steps {
sh '''mv ./target/*.jar ./docker/
docker build -t ${harborAddress}/${harborRepo}/${JOB_NAME}:$tag -f docker/DockerFile docker/'''
}
}
stage('推送镜像到Harbor') {
steps {
sh '''docker login -u ${harborUser} -p ${harborPwd} ${harborAddress}
docker push ${harborAddress}/${harborRepo}/${JOB_NAME}:$tag'''
}
}
stage('pipeline.yml传输到k8sMaster') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'k8sMaster', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: 'pipeline.yml')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
stage('k8sMaster部署应用') {
steps {
sh 'ssh root@192.168.126.4 kubectl apply -f /opt/k8s/pipeline.yml'
}
}
}
}
修改controller,提交代码,在git中新建tag为v6.0
jenkins执行构建成功
kuboard中有对应的pod\service\ingress
通过service端口访问正常
hosts文件配置域名后访问也正常
完成上述操作后,通过手动触发jenkins构建,就可以实现项目部署,在之前操作的基础上,我们期望将代码提交到gitlab指定节点后,就自动触发jenkins构建操作
安装gitlab插件
jenkins项目配置中配置触发器,注意这里的URL需要复制到gitlab中使用
jenkins取消请求认证
进入gitlab管理员页面修改网络配置后保存
配置gitlab中的项目
保存后可以测试
这里事件触发成功了,但是此时我们回到jenkins中,会发现构建失败,之前我们写的脚本都是基于tag标签来构建的,这里显然没有传递tag标签,因此报错
此时我们在jenkins中取消根据tag标签构建
然后修改jenkinsfile,拉取代码是从master分支拉取,文件中镜像版本都改为latest,修改后内容如下
pipeline {
agent any
environment {
harborUser="admin"
harborPwd="Harbor12345"
harborAddress="192.168.126.6:80"
harborRepo="repo"
}
// 存放所有任务的合集
stages {
stage('拉取Git代码') {
steps {
checkout scmGit(branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: '8a03a40a-7f4d-43e9-b35e-5329093f59d1', url: 'http://192.168.126.6:8929/root/demo.git']])
}
}
stage('maven编译代码') {
steps {
sh '/opt/apache-maven-3.6.3/bin/mvn clean package -DskipTests'
}
}
stage('SonarQube检测代码质量') {
steps {
sh '/var/jenkins_home/sonar-scanner/bin/sonar-scanner -Dsonar.sources=./ -Dsonar.projectname=${JOB_NAME} -Dsonar.projectKey=${JOB_NAME} -Dsonar.java.binaries=./target/ -Dsonar.login=squ_54c21bbdd981b32da1b3f4f83a9454f465135e02'
}
}
stage('构建Docker镜像') {
steps {
sh '''mv ./target/*.jar ./docker/
docker build -t ${harborAddress}/${harborRepo}/${JOB_NAME}:latest -f docker/DockerFile docker/'''
}
}
stage('推送镜像到Harbor') {
steps {
sh '''docker login -u ${harborUser} -p ${harborPwd} ${harborAddress}
docker push ${harborAddress}/${harborRepo}/${JOB_NAME}:latest'''
}
}
stage('pipeline.yml传输到k8sMaster') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'k8sMaster', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: 'pipeline.yml')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
stage('k8sMaster部署应用') {
steps {
sh 'ssh root@192.168.126.4 kubectl apply -f /opt/k8s/pipeline.yml'
}
}
}
}
同时修改pipeline.yml中拉取镜像的版本,修改后内容如下
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: test
name: pipeline
labels:
app: pipeline
spec:
replicas: 2
selector:
matchLabels:
app: pipeline
template:
metadata:
labels:
app: pipeline
spec:
containers:
- name: pipeline
image: 192.168.126.6:80/repo/pipeline:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
imagePullSecrets:
- name: harbor
---
apiVersion: v1
kind: Service
metadata:
namespace: test
labels:
app: pipeline
name: pipeline
spec:
selector:
app: pipeline
ports:
- port: 8081
targetPort: 8080
type: NodePort
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: test
name: pipeline
spec:
ingressClassName: ingress
rules:
- host: long.pipeline.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: pipeline
port:
number: 8081
改完后提交代码到git,触发构建,正常执行,kuboard中也看到了容器在运行
到这里其实还有一个问题,因为k8s是根据我们设置的yml文件来更新的,如果我们只改了代码,yml文件不变,k8s是不会帮我们重新发布容器的,可以在k8s执行yml文件后在加一条命令实现对应操作
kubectl rollout restart deployment pipeline -n test
此时jenkinsfile更新为
pipeline {
agent any
environment {
harborUser="admin"
harborPwd="Harbor12345"
harborAddress="192.168.126.6:80"
harborRepo="repo"
}
// 存放所有任务的合集
stages {
stage('拉取Git代码') {
steps {
checkout scmGit(branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: '8a03a40a-7f4d-43e9-b35e-5329093f59d1', url: 'http://192.168.126.6:8929/root/demo.git']])
}
}
stage('maven编译代码') {
steps {
sh '/opt/apache-maven-3.6.3/bin/mvn clean package -DskipTests'
}
}
stage('SonarQube检测代码质量') {
steps {
sh '/var/jenkins_home/sonar-scanner/bin/sonar-scanner -Dsonar.sources=./ -Dsonar.projectname=${JOB_NAME} -Dsonar.projectKey=${JOB_NAME} -Dsonar.java.binaries=./target/ -Dsonar.login=squ_54c21bbdd981b32da1b3f4f83a9454f465135e02'
}
}
stage('构建Docker镜像') {
steps {
sh '''mv ./target/*.jar ./docker/
docker build -t ${harborAddress}/${harborRepo}/${JOB_NAME}:latest -f docker/DockerFile docker/'''
}
}
stage('推送镜像到Harbor') {
steps {
sh '''docker login -u ${harborUser} -p ${harborPwd} ${harborAddress}
docker push ${harborAddress}/${harborRepo}/${JOB_NAME}:latest'''
}
}
stage('pipeline.yml传输到k8sMaster') {
steps {
sshPublisher(publishers: [sshPublisherDesc(configName: 'k8sMaster', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '', remoteDirectorySDF: false, removePrefix: '', sourceFiles: 'pipeline.yml')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)])
}
}
stage('k8sMaster部署应用') {
steps {
sh '''ssh root@192.168.126.4 kubectl apply -f /opt/k8s/pipeline.yml
ssh root@192.168.126.4 kubectl rollout restart deployment pipeline -n test'''
}
}
}
}
提交代码后,触发构建,此时我们再去修改代码提交,不修改yml也会重新部署了
到此就实现了一个简单的CD/CI操作,这里是基于最新提交的代码生成一个镜像,有些场景可能会需要基于提交的版本号来创建不同的镜像,然后k8s进行部署和回滚,在现有的基础上做一定调整也可以实现,这篇文章基于【DevOps教程】基于k8s+docker+jenkins的云原生DevOps实践 | DevOps运维 | DevOps开发 | DevOps测试_哔哩哔哩_bilibili写的,可以结合原视频参考本文实现自己的DevOps。