上篇文章已经初步搭建起来了DevOps环境了,并且创建了一个简单的项目进行演示了,这篇文章就真正的部署一个java的项目,来演示下一套完整的DevOps的流程是怎么样的,这里只演示一个简单的流程,项目也简单,就是一个spring boot的简单应用,流程如下:
1、创建一个spring boot项目,编写代码、jenkinsfile文件、Dockfile文件; 2、代码写好以后上传到gitee上;
3、在jenkins上创建流水线项目,通过pipeline来构建整个流程。
一个大体的流程入上图:
jenkins作为一个pod在k8s上运行,所有的任务都是由jenkins的master进行编排,流水线的编排工作也是在master上,当构建启动以后,会在对应的节点将创建一个slave的pod容器,这个pod容器作为jenkins的slave容器,可以使用宿主机的k8s客户端命令,也就是kubectl **命令,而整个构建过程是需要挂载的,比如说下载的代码放哪里?mavn的依赖包放哪里?所有需要有挂载,我这里一直使用的nfs,nfs在之前的文章中已经写了如何创建。
完整的流程如下:
- Git content(源码清单管理)
a) Source code(编写代码)
b) Jenkinsfile/Dockerfile/Deploy yaml(编写dockerfile jenkins pipeline等文件) 1 . Git repository(代码管理仓库,有三种)
a) GitHub
b) Gitee
c) GitLab- Docker registry-Images(镜像管理)
a) Docker Hub
b) Aliyun Hub c) Private- K8s – deploy enviroment(pod容器管理)
a) Testing
b) Productions
每一个都需要相应的用户名和密码进行连接,需要在 jenkins 进行配置
大体步骤: General(基础配置)–》源码管理–》构建触发器–》构建环境–》构建–》构建
开发人员把做好的 devops-java-sample 项目代码通过 git 推送到 gitee,然后 Jenkins 通过 gitee webhook下来,(前提是配置好),自动从拉取 gitee 上面拉取代码然后进行 build,编译、生成镜像、然后把镜像推送到 Harbor 仓库;然后在部署的时候通过 k8s 拉取 Harbor 上面的代码进行创建容器和服务,最终发布完成,然后可以用外网访问。(ps:看着我讲这么简单,但心里有许多小鹿在心里乱撞,没关系,下面将会好好的分享给大家)当然啦,上面只是粗略的,请看下图才更加形象。
我这里java项目已经提前创建好了,反正就是在idea中很简单的创建了一个java项目,项目gitee地址为:
https://gitee.com/scjava/devops-demo.git
项目如下:
deploy:是该项目的deployment文件,就是当镜像推送到仓库成功以后,创建部署的yaml文件目录
src:是源码文件,很简单,就一个启动类和一个controller
Dockerfile:docker 的build文件,当源码从gitee上拉取下来以后打包成jar过后,build镜像的时候的文件;
jenkinsfile:jenkins的流水线文件
比较重要的文件就这么几个,代码很简单,就写了一个Controller,内容如下:
这把重点说下jenkins的流水线文件,可能初学者不是很清楚这个jenkins文件编写语法,文件内容如下:
下面将会对其进行分段说明
pipeline {
agent {
label 'maven'
}
parameters {
string(name: 'TAG_NAME',defaultValue: '1.3',description: '')
choice choices: ['devops-java-sample', 'gateway', 'order', 'product'], name: 'APP_NAME'
}
environment {
DOCKER_CREDENTIAL_ID = 'aliyun-register'
GITEE_CREDENTIAL_ID = 'GITEE'
KUBECONFIG_CREDENTIAL_ID = 'demo-kubeconfig'
REGISTRY = 'registry.cn-hangzhou.aliyuncs.com'
DOCKERHUB_NAMESPACE = 'bml-services'
GITEE_ACCOUNT = 'scjava'
BRANCH_NAME = 'master'
}
stages {
stage ('checkout scm') {
steps {
// checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'gitee-id', url: 'https://gitee.com/liuyik8s/devops-java-sample.git']]])
sh 'pwd'
sh 'sleep 2'
}
}
stage('checking'){
steps {
sh '''
pwd
echo "webhook"
echo "webhook"
ls -l
sleep 2
echo "$[app]"
'''
}
}
stage('Unit Testing'){
steps {
echo "Unit Testing..."
}
}
stage ('unit test') {
steps {
container ('maven') {
// sh 'mvn clean -gs `pwd`/configuration/settings.xml test'
}
}
}
stage ('build & push') {
steps {
container ('maven') {
sh 'mvn -Dmaven.test.skip=true -gs `pwd`/configuration/settings.xml clean package'
sh 'sleep 1'
sh 'env'
sh 'ls -l target'
sh 'docker build -f Dockerfile-online -t $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BRANCH_NAME-$TAG_NAME .'
withCredentials([usernamePassword(passwordVariable : 'DOCKER_PASSWORD' ,usernameVariable : 'DOCKER_USERNAME' ,credentialsId : "$DOCKER_CREDENTIAL_ID" ,)]) {
sh 'echo "$DOCKER_PASSWORD" | docker login $REGISTRY -u "$DOCKER_USERNAME" --password-stdin'
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BRANCH_NAME-$TAG_NAME'
}
}
}
}
stage('push latest'){
when{
branch 'master'
}
steps{
container ('maven') {
sh 'docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BRANCH_NAME-$TAG_NAME $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest '
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest '
}
}
}
stage('deploy to dev') {
when{
branch 'master'
}
steps {
container ('maven'){
// input(id: 'deploy-to-dev', message: 'deploy to dev?')
sh "ls -l deploy/dev-ol"
sh "sleep 1"
sh "ls -l deploy/dev-ol"
sh "cat deploy/dev-ol/*.yaml"
sh "sleep 1"
sh '''
echo "changing parameter"
cp deploy/dev-ol/devops-sample.yaml k8s.yaml
sed -i "s#TAG_NAME#$BUILD_NUMBER#g" k8s.yaml
sed -i "s#REGISTRY#$REGISTRY#g" k8s.yaml
sed -i "s#DOCKERHUB_NAMESPACE#$DOCKERHUB_NAMESPACE#g" k8s.yaml
sed -i "s#APP_NAME#$APP_NAME#g" k8s.yaml
sed -i "s#BRANCH_NAME#$BRANCH_NAME#g" k8s.yaml
'''
// sh 'kubectl delete -f k8s.yaml -n testing'
sh "sleep 10"
sh "cat k8s.yaml"
sh 'kubectl apply -f k8s.yaml -n testing'
}
}
}
stage('push with tag'){
steps {
container ('maven') {
// input(id: 'release-image-with-tag', message: 'release image with tag?')
// withCredentials([usernamePassword(credentialsId: "$GITEE_CREDENTIAL_ID", passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
// sh 'git config --global user.email "[email protected]" '
// sh 'git config --global user.name "[email protected]" '
// sh 'git tag -a $TAG_NAME -m "$TAG_NAME" '
// sh 'git push http://$GIT_USERNAME:[email protected]/$GITEE_ACCOUNT/$APP_NAME.git --tags --ipv4'
// }
sh 'docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BRANCH_NAME-$TAG_NAME $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME '
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME '
}
}
}
stage('deploy to production') {
steps {
container ('maven') {
input(id: 'deploy-to-production', message: 'deploy to production?')
// sh 'kubectl delete -f k8s.yaml -n production'
sh "env"
sh "sleep 10"
sh "cat k8s.yaml"
sh 'kubectl apply -f k8s.yaml -n production'
}
}
}
}
}
agent {
label 'maven'
}
这个是在jenkins的云配置上配置的那个容器的标签,我们取名是maven,忘记的可以去看下上篇文章
parameters {
string(name: 'TAG_NAME',defaultValue: '1.3',description: '')
choice choices: ['devops-java-sample', 'gateway', 'order', 'product'], name: 'APP_NAME'
}
这里的意思是说进行参数化构建,比如说应用名称和应用版本,这个根据自身应用需要进行配置,上面配置了两个参数,一个是文本参数,一个范围参数,就是可以构造的时候可以自由选择,并且设置了默认值
environment {
DOCKER_CREDENTIAL_ID = 'aliyun-register'
GITEE_CREDENTIAL_ID = 'GITEE'
KUBECONFIG_CREDENTIAL_ID = 'demo-kubeconfig'
REGISTRY = 'registry.cn-hangzhou.aliyuncs.com'
DOCKERHUB_NAMESPACE = 'bml-services'
BRANCH_NAME = 'master'
}
DOCKER_CREDENTIAL_ID :配置的阿里云的id,这个id是jenkins上配置凭证里面自己设置的ID;
GITEE_CREDENTIAL_ID :类似,就是在jenkins上配置的gitee的ID;
KUBECONFIG_CREDENTIAL_ID: K8S配置ID REGISTRY :镜像仓库地址 DOCKERHUB_NAMESPACE
:阿里云仓库的命名空间 BRANCH_NAME:gitee的分支名称
stage ('checkout scm') {
steps {
// checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'gitee-id', url: 'https://gitee.com/liuyik8s/devops-java-sample.git']]])
sh 'pwd'
sh 'sleep 2'
}
}
这里我注释了,因为代码拉取的我让jenkins上配置的去做,待会儿就可以看到,如果说代码拉取这些要pipeline来做,也可以在脚本上写,反正都是可以的。
stage('checking'){
steps {
sh '''
pwd
echo "webhook"
echo "webhook"
ls -l
sleep 2
echo "$[app]"
'''
}
}
stage('Unit Testing'){
steps {
echo "Unit Testing..."
}
}
我这里只是模拟代码扫描,扫描是否有bug,根据需要进行,我这里模拟过程,这里可以将一些单元测试攻击和代码扫描攻击集成进来,然后编写脚本进行执行
stage ('build & push') {
steps {
container ('maven') {
sh 'mvn -Dmaven.test.skip=true -gs `pwd`/configuration/settings.xml clean package'
sh 'sleep 1'
sh 'env'
sh 'ls -l target'
sh 'docker build -f Dockerfile-online -t $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BRANCH_NAME-$TAG_NAME .'
withCredentials([usernamePassword(passwordVariable : 'DOCKER_PASSWORD' ,usernameVariable : 'DOCKER_USERNAME' ,credentialsId : "$DOCKER_CREDENTIAL_ID" ,)]) {
sh 'echo "$DOCKER_PASSWORD" | docker login $REGISTRY -u "$DOCKER_USERNAME" --password-stdin'
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BRANCH_NAME-$TAG_NAME'
}
}
}
}
1、mvn编译代码成jar
2、docker build成镜像;
3、docker login到镜像仓库;
4、docker本地打个标签,然后推送版本到镜像仓库;
stage('push latest'){
when{
branch 'master'
}
steps{
container ('maven') {
sh 'docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BRANCH_NAME-$TAG_NAME $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest '
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:latest '
}
}
}
这个意思就是说每次打版本以后作为最新的一个版本latest也推送到镜像仓库
stage('deploy to dev') {
when{
branch 'master'
}
steps {
container ('maven'){
// input(id: 'deploy-to-dev', message: 'deploy to dev?')
sh "ls -l deploy/dev-ol"
sh "sleep 1"
sh "ls -l deploy/dev-ol"
sh "cat deploy/dev-ol/*.yaml"
sh "sleep 1"
sh '''
echo "changing parameter"
cp deploy/dev-ol/devops-sample.yaml k8s.yaml
sed -i "s#TAG_NAME#$BUILD_NUMBER#g" k8s.yaml
sed -i "s#REGISTRY#$REGISTRY#g" k8s.yaml
sed -i "s#DOCKERHUB_NAMESPACE#$DOCKERHUB_NAMESPACE#g" k8s.yaml
sed -i "s#APP_NAME#$APP_NAME#g" k8s.yaml
sed -i "s#BRANCH_NAME#$BRANCH_NAME#g" k8s.yaml
'''
// sh 'kubectl delete -f k8s.yaml -n testing'
sh "sleep 10"
sh "cat k8s.yaml"
sh 'kubectl apply -f k8s.yaml -n testing'
}
}
}
将dev中的做好的yaml文件复制到根路径,然后通过 sed i “s//g”来替换里面的参数,比如镜像地址,应用版本这些
Sed 命令简要介绍
sed -i 就是直接对文本文件进行操作的
sed -i ‘s/原字符串/新字符串/’ /home/1.txt
sed -i ‘s/原字符串/新字符串/g’ /home/1.txt
需要使用双引号,如果替换值为环境变量
sed -i “s#BUILD_NUMBER#$BUILD_NUMBER#g” k8s.yaml
sed -i ‘sBUILD_NUMBER/TAG_NAME’ k8s.yaml
使用 sed 命令修改 YAML 文件中的变量值。
最后执行这个yaml文件到命名空间testing中
stage('push with tag'){
steps {
container ('maven') {
// input(id: 'release-image-with-tag', message: 'release image with tag?')
// withCredentials([usernamePassword(credentialsId: "$GITEE_CREDENTIAL_ID", passwordVariable: 'GIT_PASSWORD', usernameVariable: 'GIT_USERNAME')]) {
// sh 'git config --global user.email "[email protected]" '
// sh 'git config --global user.name "[email protected]" '
// sh 'git tag -a $TAG_NAME -m "$TAG_NAME" '
// sh 'git push http://$GIT_USERNAME:[email protected]/$GITEE_ACCOUNT/$APP_NAME.git --tags --ipv4'
// }
sh 'docker tag $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:SNAPSHOT-$BRANCH_NAME-$TAG_NAME $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME '
sh 'docker push $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME '
}
}
}
这里又推送了一个版本镜像到仓库,还在gitee上打了一个版本标签,只是这里我暂时注释掉了
stage('deploy to production') {
steps {
container ('maven') {
input(id: 'deploy-to-production', message: 'deploy to production?')
// sh 'kubectl delete -f k8s.yaml -n production'
sh "env"
sh "sleep 10"
sh "cat k8s.yaml"
sh 'kubectl apply -f k8s.yaml -n production'
}
}
}
让运维人员选择是否部署到生产,如果部署到生产则执行yaml
参数化也可以在这里配置,这里最好配置好,否则就是用jenkinsfile里面的默认值,这里配置了就可以根据情况需要输入不同的参数
对应到jenkinsfile里面的两个参数
gitee的webhook是需要安装插件的,安装 Generic Webhook Trigger 插件在“系统设置– 插件管理– 可选插件”界面搜索: Generic Webhook Trigger,可以看到,点击安装,然后重启。
安装完就重启了:
安装好了,再进行java这个项目的配置就可以看到有个选项:
在gitee上配置这个地址过后,当开发人员提交版本代码以后就会自动触发jenkins的自动化部署,这个在ci/cd上是很有用,完全不用人工干预就做到自动化构建和部署,除了这里还需要创建用户的token,比如我的jenkins用户是bml,那么在用户管理这里:
我的token生成了要马上复制自己保存,因为没有地方可以查询:
bml:1111a0a4944bf9add20578b2545080b56d
点击管理
因为我这里是本地的虚拟机,所以配置了了也没有用,回头自己搭建一个gitlab来测试。
这里定义的流水线是从gitee上下载,如果这里选了,在脚本里面就不用写拉取代码的步骤了,比如:
反正看自己选择,想在哪里写就在哪里写
最后是脚本路径,在项目里面这个脚本是放在根目录的,这样代码拉取下来过后,在根目录就可以找到这个文件了,就可以进行构建了,还记得上篇文章我们配置了pvc agent-workspace吗?这个就是用来存储这些构建项目的文件和jar包的,采用的nfs存储,因为我这里构建过了,所以可以看下:
在nfs里面:
都是有的,所以挂载的目录就是nfs的这个目录,所以在文章开头说的一个完整的构建过程就说完了,下面就开始构建:
我们是参数化构建,如果直接点击左边的build with Parameters,如下
这里就可以设置你想设置的参数,我只是演示了两个参数,真正构建如下:
这是我构建过的记录,在构建的最左下角有构建记录,可以点击进去看下:
还可以看下输出,这个构建是可以通过可视化的方式展示流水线的步骤和日志的,是非常好的一种方式
这上面的名称就是在上面我们每个讲解的阶段名称,鼠标移到上面还可以看日志,比如看下编译代码的日志:
点击可进入详细的日志:
构建成功以后,在容器里的命名空间testing如下:
在浏览器输入我们应用的地址:
镜像仓库:
可以看到有很多的版本;这就是一个算是比较复杂的Devops搭建过程