Jenkins一站成魔【3】Docker平台CI/CD

  • 1 Jenkins针对Docker平台持续集成概述
  • 2 服务器准备
  • 3 前后端项目准备
  • 4 docker环境准备
  • 5 Harbor Prepare
  • 5.1 Install Harbor
  • 5.2 在Harbor创建用户和项目
  • 5.3 把镜像上传到Harbor
  • 5.4 从Harbor下载镜像
  • 6 项目代码上传到Gitlab/Gitee
  • 7 jenkins从Gitlab/Gitee拉取项目源码
  • 8 提交拉取的代码到SonarQube代码审查
  • 9 使用Dockerfile编译、生成镜像
  • 10 上传到Harbor镜像仓库
  • 11 拉取镜像和发布应用
  • 11.1 jenkins安装 Publish Over SSH 插件
  • 11.2 配置远程部署服务器
  • 11.3 部署前端静态web网站
  • 12 部署方案优化
  • 12.1 修改所有微服务配置
  • 12.2 设计Jenkins集群项目的构建参数
  • 12.3 完成微服务构建镜像,上传私服
  • 12.4 完成微服务多服务器远程发布
  • Nginx+Zuul集群实现高可用网关


1 Jenkins针对Docker平台持续集成概述


jenkins 容器slave_git

1)开发人员每天把代码提交到Gitlab代码仓库。
2)Jenkins从Gitlab中拉取项目源码,编译并打成jar包,然后构建成Docker镜像,将镜像上传到Harbor私有仓库。
3)Jenkins发送SSH远程命令,让生产部署服务器到Harbor私有仓库拉取镜像到本地,然后创建容器。
4)用户可以访问到容器项目。

2 服务器准备

服务器名称

IP地址

安装的软件

代码托管服务器

192.168.168.151

Gitlab

持续集成服务器

192.168.168.152

Docker18.06.1-ce,Jenkins,Maven

生产部署服务器

192.168.168.153

Docker18.06.1-ce

Docker仓库服务器

192.168.168.154

Docker18.06.1-ceHarbor1.9.2

3 前后端项目准备

后端项目:https://gitee.com/michael_linux/jenkins-docker 前端项目:https://gitee.com/michael_linux/jenkins-front-demo

4 docker环境准备

使用这个版本的docker:docker-ce-18.06.1.ce

docker安装请移步:
Dockerfile 请移步:

  • 制作镜像与测试docker环境
# Dockerfile编写
FROM java:8
MAINTAINER michael_linux@163.com
ARG JAR_FILE
ADD ${JAR_FILE} app.jar
EXPOSE 9001
ENTRYPOINT ["java","-jar","/app.jar"]
// 制作镜像
docker build --build-arg JAR_FILE=docker-eureka-server.jar -t docker-eureka:v1 .
// 运行镜像
docker run -d -p 10086:10086 docker-eureka:v1

5 Harbor Prepare

Harbor(港口,港湾)是一个用于存储和分发Docker镜像的企业级Registry服务器。
除了Harbor这个私有镜像仓库之外,还有Docker官方提供的Registry。相对Registry,Harbor具有很多优势:

  1. 提供分层传输机制,优化网络传输 Docker镜像是是分层的,而如果每次传输都使用全量文件(所以用FTP的方式并不适合),显然不经济。必须提供识别分层传输的机制,以层的UUID为标识,确定传输的对象。
  2. 提供WEB界面,优化用户体验 只用镜像的名字来进行上传下载显然很不方便,需要有一个用户界面可以支持登陆、搜索功能,包括区分公有、私有镜像。
  3. 支持水平扩展集群 当有用户对镜像的上传下载操作集中在某服务器,需要对相应的访问压力作分解。
  4. 良好的安全机制 企业中的开发团队有很多不同的职位,对于不同的职位人员,分配不同的权限,具有更好的安全性。

5.1 Install Harbor

Harbor需要安装在192.168.168.154主机上:
1)安装Docker并启动Docker(已完成)
参考之前的Docker安装过程。
2)安装docker-compose

curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

docker-compose -version

3)下载Harbor的压缩包(版本为:v1.9.2)
https://github.com/goharbor/harbor/releases

curl -L https://github.com/goharbor/harbor/releases/download/v1.9.2/harbor-offline-installer-v1.9.2.tgz -o harbor-offline-installer-v1.9.2.tgz

4)上传压缩包到linux,并解压

tar -xzvf harbor-offline-installer-v1.9.2.tgz
mkdir /opt/harbor
mv harbor/* /opt/harbor
cd /opt/harbor

5)修改Harbor的配置vi harbor.yml

修改 hostname 和 port
hostname: 192.168.168.154
port: 85

6)安装Harbor

./prepare
./install.sh

7)启动Harbor

cd /opt/harbor/
// 启动
docker-compose up -d
// 停止
docker-compose stop
// 重新启动
docker-compose restart

8)访问Harbor
http://192.168.168.154:85 默认账户密码:admin/Harbor12345


jenkins 容器slave_docker_02

5.2 在Harbor创建用户和项目

1)创建项目jenkins-docker

Harbor的项目分为公开和私有的:

公开项目:所有用户都可以访问,通常存放公共的镜像,默认有一个library公开项目。

私有项目:只有授权用户才可以访问,通常存放项目本身的镜像。

我们可以为微服务项目创建一个新的项目:

jenkins 容器slave_git_03


jenkins 容器slave_jenkins 容器slave_04

2)创建用户

jenkins 容器slave_ci/cd_05

3)给私有项目分配用户

进入jenkins-docker项目->成员

jenkins 容器slave_git_06

角色

权限说明

访客

对于指定项目拥有只读权限

开发人员

对于指定项目拥有读写权限

维护人员

对于指定项目拥有读写权限,创建 Webhooks

项目管理员

除了读写权限,同时拥有用户管理/镜像扫描等管理权限

4)以新用户登录Harbor

5.3 把镜像上传到Harbor

192.168.168.152
1)给镜像打上标签

docker tag docker-eureka:v1 192.168.168.154:85/jenkins-docker/docker-eureka:v1

2)推送镜像

docker push 192.168.168.154:85/jenkins-docker/docker-eureka:v1


jenkins 容器slave_docker_07

3)把Harbor地址加入到Docker信任列表

vim /etc/docker/daemon.json
{
	"registry-mirrors": ["https://0s2uk8va.mirror.aliyuncs.com"],
	"insecure-registries": ["192.168.168.154:85"]
}

需要重启Docker

4)再次执行推送命令,会提示权限不足

docker push 192.168.168.154:85/jenkins-docker/docker-eureka:v1


jenkins 容器slave_docker_08

需要先登录Harbor,再推送镜像

5)登录Harbor

docker login -u michael -p Csdn@123456 192.168.168.154:85


jenkins 容器slave_git_09

继续推送

docker push 192.168.168.154:85/jenkins-docker/docker-eureka:v1


jenkins 容器slave_git_10

5.4 从Harbor下载镜像

需求:在192.168.168.153服务器完成从Harbor下载镜像
1)安装Docker,并启动Docker(已经完成)
2)修改Docker配置

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
	"registry-mirrors": ["https://0s2uk8va.mirror.aliyuncs.com"],
	"insecure-registries": ["192.168.168.154:85"]
}
EOF

sudo systemctl daemon-reload
sudo systemctl restart docker

3)先登录,再从Harbor下载镜像

docker login -u michael -p Csdn@123456 192.168.168.154:85
docker pull 192.168.168.154:85/jenkins-docker/docker-eureka:v1
docker run -d -p 10086:10086 192.168.168.154:85/jenkins-docker/docker-eureka:v1

6 项目代码上传到Gitlab/Gitee

https://gitee.com/michael_linux/jenkins-docker.git https://gitee.com/michael_linux/jenkins-front-demo.git

7 jenkins从Gitlab/Gitee拉取项目源码

1)拉取项目源码

添加一个项目变量 String文本参数 branch=master

jenkins 容器slave_git_11

node {
    def gitee_url = "https://gitee.com/michael_linux/jenkins-docker.git"
    
	stage('拉取代码') {
        checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: 'GiteeAccount', url: "${gitee_url}"]]])
    }
}

2)拉取Jenkinsfile文件

在jenkins-docker项目下创建Jenkinsfile文件

jenkins 容器slave_git_12

8 提交拉取的代码到SonarQube代码审查

1)创建项目,并设置参数

创建jenkins-docker项目,添加一个Choice选项参数

jenkins 容器slave_jenkins 容器slave_13

2)每个项目的根目录下添加sonar-project.properties

# docker-admin-service docker-eureka-server docker-gathering docker-zuul
# must be unique in a given SonarQube instance
sonar.projectKey=docker-eureka-server
# this is the name and version displayed in the SonarQube UI. Was mandatory prior to SonarQube 6.1.
sonar.projectName=docker-eureka-server
sonar.projectVersion=1.0

# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
# This property is optional if sonar.modules is set.
sonar.sources=.
sonar.exclusions=**/test/**,**/target/**
sonar.java.binaries=.

sonar.java.source=1.8
sonar.java.target=1.8
sonar.java.libraries=**/target/classes/**

# Encoding of the source code. Default is default system encoding
sonar.sourceEncoding=UTF-8

注意:修改sonar.projectKey和sonar.projectName

3)修改Jenkinsfile构建脚本

node {
    def gitee_url = "https://gitee.com/michael_linux/jenkins-docker.git"

    stage('拉取代码') {
        checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: 'GiteeAccount', url: "${gitee_url}"]]])
    }

    stage('SonarQube代码审查') {
        echo '5555555555555555555555555555555'
        def scannerHome = tool 'SonarQube-Scanner'
        withSonarQubeEnv('sonarqube') {
            sh """
                cd ${jenkins_docker_project_choice}
                ${scannerHome}/bin/sonar-scanner
            """
        }
    }
}

9 使用Dockerfile编译、生成镜像

利用 dockerfile-maven-plugin 插件构建Docker镜像
1)在每个微服务项目的pom.xml加入dockerfile-maven-plugin插件

<plugin>
	<groupId>com.spotify</groupId>
	<artifactId>dockerfile-maven-plugin</artifactId>
	<version>1.3.6</version>
	<configuration>
		<repository>${project.artifactId}</repository>
		<buildArgs>
			<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
		</buildArgs>
	</configuration>
</plugin>

2)在每个微服务项目根目录下建立Dockerfile文件

FROM java:8
MAINTAINER michael_linux@163.com
ARG JAR_FILE
COPY ${JAR_FILE} app.jar
EXPOSE 10086
ENTRYPOINT ["java","-jar","/app.jar"]

注意:每个项目公开的端口不一样

3)修改Jenkinsfile构建脚本

node {
    def gitee_url = "https://gitee.com/michael_linux/jenkins-docker.git"
    def tag = "latest" // 构建版本的名称
//    //获取当前选择的项目名称
//    def selectedProjectNames = "${project_name}".split(",")
//    //获取当前选择的服务器名称
//    def selectedServers = "${publish_server}".split(",")

    stage('拉取代码') {
        checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: 'GiteeAccount', url: "${gitee_url}"]]])
    }

    stage('SonarQube代码审查') {
        echo '5555555555555555555555555555555'
        def scannerHome = tool 'SonarQube-Scanner'
        withSonarQubeEnv('sonarqube') {
            sh """
                cd ${jenkins_docker_project_choice}
                ${scannerHome}/bin/sonar-scanner
            """
        }
    }

    stage('编译,构建镜像') {
        //定义镜像名称
        def imageName = "${jenkins_docker_project_choice}:${tag}"
        //编译,安装公共工程
        sh "mvn -f docker-common clean install"
        //编译,构建本地镜像
        sh "mvn -f ${jenkins_docker_project_choice} clean package dockerfile:build"
    }
}

注意:如果出现找不到父工程依赖,需要手动把父工程的依赖上传到仓库中!!!

10 上传到Harbor镜像仓库

1)使用凭证管理Harbor私服账户和密码

先在凭证建立Harbor的凭证,在生成凭证脚本代码

jenkins 容器slave_jenkins 容器slave_14

2)修改Jenkinsfile构建脚本

node {
    def gitee_url = "https://gitee.com/michael_linux/jenkins-docker.git"
    def tag = "latest" // 构建版本的名称
    //Harbor私服地址
    def harbor_url = "192.168.168.154:85"
    //Harbor的项目名称
    def harbor_project_name = "jenkins-docker"
    def harbor_auth = "8ab36dc0-b526-4bd2-aff2-c833966403f3"

    stage('拉取代码') {
        checkout([$class: 'GitSCM', branches: [[name: "*/${branch}"]], extensions: [], userRemoteConfigs: [[credentialsId: 'GiteeAccount', url: "${gitee_url}"]]])
    }

    stage('SonarQube代码审查') {
        echo '5555555555555555555555555555555'
        def scannerHome = tool 'SonarQube-Scanner'
        withSonarQubeEnv('sonarqube') {
            sh """
                cd ${jenkins_docker_project_choice}
                ${scannerHome}/bin/sonar-scanner
            """
        }
    }

    stage('编译,构建镜像,上传harbor') {
        //定义镜像名称
        def imageName = "${jenkins_docker_project_choice}:${tag}"
        //编译,安装公共工程
        sh "mvn -f docker-common clean install"
        //编译,构建本地镜像
        sh "mvn -f ${jenkins_docker_project_choice} clean package dockerfile:build"

        //给镜像打标签
        sh "docker tag ${imageName} ${harbor_url}/${harbor_project_name}/${imageName}"
        //登录Harbor,并上传镜像
        withCredentials([usernamePassword(credentialsId: "${harbor_auth}", usernameVariable: 'username', passwordVariable: 'password')]) {
            //登录
            sh "docker login -u ${username} -p ${password} ${harbor_url}"
            //上传镜像
            sh "docker push ${harbor_url}/${harbor_project_name}/${imageName}"
        }
        //删除本地镜像
        sh "docker rmi -f ${imageName}"
        sh "docker rmi -f ${harbor_url}/${harbor_project_name}/${imageName}"
    }
}

11 拉取镜像和发布应用

前提条件:192.168.168.153部署服务器已经安装Docker并启动。

11.1 jenkins安装 Publish Over SSH 插件


jenkins 容器slave_jenkins_15

11.2 配置远程部署服务器

1)拷贝公钥到远程服务器
在jenkins所在主机执行,拷贝公钥到远程部署服务器

ssh-keygen -t rsa
ssh-copy-id 192.168.168.153

2)系统配置->添加远程服务器

jenkins 容器slave_jenkins_16


jenkins 容器slave_jenkins_17

3)添加String变量port参数

jenkins 容器slave_jenkins 容器slave_18

4)根据“流水线语法”修改Jenkinsfile构建脚本

/opt/jenkins_shell/deploy.sh ${harbor_url} ${harbor_project_name} ${jenkins_docker_project_choice} ${tag} ${port}

jenkins 容器slave_jenkins 容器slave_19

5)编写deploy.sh部署脚本
上传 deploy.sh 文件到部署服务器的 /opt/jenkins_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 michael -p Csdn@123456 $harbor_url
# 下载镜像
docker pull $imageName
# 启动容器
docker run -d -p $port:$port $imageName
echo "容器启动成功"

添加可执行权限:

chmod +x /opt/jenkins_shell/deploy.sh

6)修改服务端口,依次发布项目

jenkins 容器slave_docker_20

7)测试开始了…

jenkins 容器slave_docker_21

11.3 部署前端静态web网站

jenkins 容器slave_docker_22

1)安装启动Nginx服务器

yum install -y epel-release
yum install -y nginx

修改nginx的端口,默认80,改为9090

vim /etc/nginx/nginx.conf

还需要关闭selinux,将SELINUX=disabled
setenforce 0 先临时关闭
vi /etc/selinux/config 编辑文件,永久关闭 SELINUX=disabled

启动Nginx

systemctl enable nginx
systemctl start nginx
systemctl stop nginx
systemctl restart nginx

访问:http://192.168.168.153:9090/

jenkins 容器slave_jenkins_23

2)安装 NodeJS 插件

jenkins 容器slave_jenkins 容器slave_24

3)Jenkins配置Nginx服务器

Manage Jenkins->Global Tool Configuration

jenkins 容器slave_docker_25

4)创建前端流水线项目jenkins-front-demo

jenkins 容器slave_docker_26

jenkins 容器slave_jenkins 容器slave_27

jenkins 容器slave_jenkins_28

5)建立Jenkinsfile构建脚本

//gitlab的凭证
def git_auth = "6772687f-a034-4567-a9ff-1f776ff3dfa8"
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: 'publish_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)])
	}
}

测试:http://192.168.168.153:9090

12 部署方案优化

上面部署方案存在的问题:
1)一次只能选择一个微服务部署。
2)只有一台生产者部署服务器。
3)每个微服务只有一个实例,容错率低。

优化方案:

1)在一个Jenkins工程中可以选择多个微服务同时发布。

2)在一个Jenkins工程中可以选择多台生产服务器同时部署。

3)每个微服务都是以集群高可用形式部署。

jenkins 容器slave_docker_29

12.1 修改所有微服务配置

注册中心配置(*)

# 集群版
spring:
  application:
    name: EUREKA-HA

---
server:
  port: 10086
spring:
  # 指定profile=eureka-server1
  profiles: eureka-server1
eureka:
  instance:
    # 指定当profile=eureka-server1时,主机名是eureka-server1
    hostname: 192.168.168.153
  client:
    service-url:
     # 将自己注册到eureka-server1、eureka-server2这个Eureka上面去
      defaultZone: http://192.168.168.153:10086/eureka,http://192.168.168.155:10086/eureka

---
server:
  port: 10086
spring:
  profiles: eureka-server2
eureka:
  instance:
    hostname: 192.168.168.155
  client:
    service-url:
      defaultZone: http://192.168.168.153:10086/eureka/,http://192.168.168.155:10086/eureka/

在启动微服务的时候,加入参数: spring.profiles.active

其他微服务配置
除了Eureka注册中心以外,其他微服务配置都需要加入所有Eureka服务

#Eureka客户端配置
eureka:
  client:
    service-url:
      defaultZone: http://192.168.168.153:10086/eureka,http://192.168.168.155:10086/eureka
  instance:
    lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳
    lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期
    prefer-ip-address: true

把代码提交到Gitlab中。

12.2 设计Jenkins集群项目的构建参数

1)安装 Extended Choice Parameter 插件

支持多选框

jenkins 容器slave_jenkins_30

2)创建流水线项目

jenkins 容器slave_docker_31

3)添加参数

字符串参数:分支名称

jenkins 容器slave_jenkins 容器slave_32

多选框参数:项目名称

jenkins 容器slave_ci/cd_33

最后构建页面的效果:

jenkins 容器slave_ci/cd_34

12.3 完成微服务构建镜像,上传私服

12.4 完成微服务多服务器远程发布

1)配置远程部署服务器,拷贝公钥到远程服务器

ssh-copy-id 192.168.168.155

系统配置->添加远程服务器

2)修改Docker配置信任Harbor私服地址

重启Docker

3)添加参数
多选框参数:部署服务器

最终效果:

4)修改Jenkinsfile构建脚本

5)编写deployCluster.sh部署脚本

#!/bin/sh
#接收外部参数
harbor_url=$1
harbor_project_name=$2
project_name=$3
tag=$4
port=$5
profile=$6
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 michael -p Csdn@123456 $harbor_url
# 下载镜像
docker pull $imageName
# 启动容器
docker run -d -p $port:$port $imageName $profile
echo "容器启动成功"

6)集群效果

Nginx+Zuul集群实现高可用网关

jenkins 容器slave_docker_35


1)安装Nginx(已完成)

Nginx使用 192.168.168.154 主机验证测试。

2)修改Nginx配置

vim /etc/nginx/nginx.conf

upstream zuulServer {
	server 192.168.66.103:10020 weight=1;
	server 192.168.66.104:10020 weight=1;
}

server {
	listen 85 default_server;
	listen [::]:85 default_server;
	server_name _;
	root /usr/share/nginx/html;
	# Load configuration files for the default server block.
	include /etc/nginx/default.d/*.conf;
	location / {
		# 指定服务器负载均衡服务器
		proxy_pass http://zuulServer/;
	}
}

3)重启Nginx
systemctl restart nginx

4)验证通过Nginx的访问地址
http://192.168.168.154:8888/