阅读 1034
Docker + Jenkins 实现持续集成持续部署

Docker + Jenkins 实现持续集成持续部署

笔者正在漫漫前端路上,文中如有不正确、不恰当的地方请评论告诉我,非常感谢! 😊 

现在我们有一个文档中心前端项目,我们要把它部署到服务器上发布,但每次更新项目里的文档或代码,都需要重新打包部署,这太麻烦了。如何让每次代码的提交、打包、部署更加自动化呢?本文将介绍利用Jenkins与Docker实现持续集成持续部署。主要内容如下:

Jenkins:jenkins介绍、安装、创建任务、pipeline部署流水线、pipeline两种语法、自动化触发执行。 Docker:docker介绍、安装、docker基础命令、使用dockerfile构建自定义镜像、docker-compose构建多个镜像容器。

Jenkins

认识Jenkins

Jenkins是一个开源的、提供友好操作界面的持续集成(CI)工具,主要用于持续、自动的构建/测试软件项目。

CI持续集成:频繁自动将代码集成到主干和生产环境,尽快地发现集成错误。流程:提交代码->拉取代码->编译->打包->测试->反馈问题->开发处理->提交代码。

CD持续交付:基于持续集成,频繁地将软件的新版本,交付给质量团队或者用户,以供评审。不管怎么更新,软件是随时随地可以交付的。

持续部署:持续交付的下一步,代码通过评审,自动化部署到生产环境。

所以我们可以在Jenkins创建任务,让Jenkins帮我们做一些持续重复的工作。

安装

在服务器上安装配置jenkins,参考jenkins安装与基本配置(Linux平台

也可以用docker安装jenkins,本节不介绍。

安装启动后,主界面如下图:

创建任务

点击“新建任务”,我们可以创建一个普通的任务,或是流水线任务。

自由风格

结合任何SCM和任何构建系统来构建项目,一般做简单的任务。

我们可以设置源码配置,在构建里描述任务,使用Node插件,执行npm run build脚本将项目打包然后部署到服务器上。每次点击构建任务时,jenkins会拉取gitlab的代码,放置在服务器上的jenkins的workspace文件夹下,构建脚本时将会以这个代码文件夹为工作目录。

pipeline

如果构建任务比较复杂时,可以创建pipleline流水线任务。

部署流水线Pipeline是Jenkins 2.x的精髓,将原本独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程,形成流水式发布,构建步骤视图化。Pipeline适用的场景更广泛,能胜任更复杂的发布流程。(甚至可以处理多个项目)

1、pipeline两种语法
  • 脚本式语法(使用用Groovy语言)

灵活、可扩展,更复杂,有学习成本。

node {
  stage('build') {
    try{ 
       def dockerName='documentcontainer'  //def关键字定义变量
       def imageUrl = "${dockerName}:${dockerTag}"
       def customImage = docker.build(imageUrl)
       sh "docker rm -f ${dockerName} | true"
       customImage.run("-it -p 8900:80 --name ${dockerName}")
        ...
       currentBuild.result="SUCCESS"
    }catch(e){
       currentBuild.result="FAILURE"         
       throw e
    }
  }
}
复制代码
  • 声明式语法
    更简单、更结构化
pipeline {  //代表整条流水线
    agent any  //指定流水线的执行位置
    stages { //包含一个或多个stage的容器
        stage('Docker build') { //阶段,一个stage里只有一个steps,这些阶段可被复用
            steps {  //包含一个或多个具体步骤的容器
              echo 'hello world' //echo就是一个步骤
              
            }
        }
        stage(‘test') { //阶段,一个stage里只有一个steps,这些阶段可被复用
            steps {  //包含一个或多个具体步骤的容器
              echo 'hello world' //echo就是一个步骤
              
            }
        }
    }
}
复制代码

这是一个声明式pipleline的基本结构,每一个部分都是必须的。

More:

1、post部分是在整个pipeline或阶段完成后的一些附加步骤

post{
  success{
     echo "========pipeline executed successfully ========"
  }
  failure{
     echo "========pipeline execution failed========"
  }
  always/changed/aborted...
}
复制代码

2、在声明式pipeline中无法使用if-else等,于是jenkins提供了script步骤,这样可以在steps里写groovy代码。

 stage('Docker build') { 
     steps {  
          script{
             def browsers=['chrome','firefox']
             for(int i=0;i<browsers.size();++i){
               echo "${browsers[i]}"
             }          
           } 
      }
 }
复制代码

3、支持一些指令,对基础结构的补充,指令有自己的作用域

如environment:设置环境变量,可定义在stage或pipeline部分

2、jenkinsfile :

可以把部署流水线的逻辑写在jenkinsfile文件中,放在项目里。

设置Pipeline Script from SCM从版本控制库里拉取pipeline

触发任务执行

现在我们每次提交代码,然后切到jenkins界面点击构建,这样不够自动化。

有几种触发任务的条件,可配置构建触发器。

  • 时间触发 定时触发、轮询代码仓库

  • 事件触发 如GitLab通知触发:当GitLab发现源代码有变化时,触发jenkins执行构建。(不错,很好用!)
    利用GitLab提供的webhook钩子

现在我们利用jenkins可以做到持续集成部署了,但我们需要在服务器上部署好nginx,再在jenkins里创造node环境安装依赖打包部署到nginx,这有两个问题:一是需要提前部署好nginx,或者如果这是后端项目需要部署数据库等等,部署环境过程复杂,如果我们要迁移服务器又要重新部署;二是安装依赖的时间太长了每次都要重新安装。

所以接下来我们运用Docker,把应用和运行环境放到一个docker容器里,方便快速地部署到任何一台服务器上,利用镜像缓存减少依赖安装的时间。

Docker

认识Docker

1、什么是docker

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,该容器包含了应用程序的代码、运行环境、依赖库、配置文件等必需的资源,通过容器就可以实现方便快速并且与平台解耦的自动化部署方式,无论你部署时的环境如何,容器中的应用程序都会运行在同一种环境下。

(总结:利用docker可以打包我们的应用以及依赖等包到一个容器中,通过容器就方便快速地部署到任何一台机器上,并且容器里的应用程序会运行在同一种环境下。)

2、docker解决了什么问题

  • 频繁搭建环境

    运维工程师搭建多台机器、服务器的迁移

  • 环境不一致

    window与linux操作系统不同、依赖版本不同

  • 二次虚拟化

      不是很懂。像云计算平台就是一种虚拟化,将计算机资源抽象。docker是操作系统级的虚拟化,每个容器都有自己的文件系统、进程管理(maybe),与外部隔离。

镜像、容器、仓库的区别

镜像(Image):相当于是一个 root 文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像本身是只读的,其内容在构建之后也不会被改变。

容器(Container):镜像的运行实例。容器基于镜像启动的时候,docker会在镜像的最上一层创建一个可写层,在容器里的操作不会修改镜像本身。

仓库(Repository):集中存放镜像的地方,分为公有仓库(DockerHub、dockerpool)和私有仓库。DockerHub:docker官方维护的一个公共仓库hub.docker.com,其中包括了15000多个的镜像,大部分都可以通过dockerhub直接下载镜像。也可通过docker search和docker pull命令来下载。

安装

1、Windows安装  暂时在本地电脑上安装,学习docker命令。

遇到的问题一:

net localgroup docker-users username /add
或者Add-LocalGroupMember -Group "docker-users" -Member "username "`
复制代码

然后重启

遇到的问题二:

docker pull failed

设置源镜像

{
  "experimental": false,
  "features": {
  "buildkit": true
  },
  "registry-mirrors": [
  "https://h3j9xv2v.mirror.aliyuncs.com"
  ]
}
复制代码

2、linux服务器安装

参考:linux 安装docker

使用docker

docker的基础命令,主要分为镜像和容器的命令

获取镜像 docker pull  node [镜像仓库地址]

查看所有镜像  docker image ls -a   (-a 包括中间层镜像,无标签,是其它镜像所依赖的镜像)

删除镜像 docker image rm <container_id 如08ec>

构建镜像 docker build -t documentcontainer .  (-t给镜像命名)基于dockerfile .代表dockerfile在当前目录

使用镜像创建容器 docker run -p 3000:80 -d --name documentApp documentcontainer

  • -p 3000:80 端口映射,将宿主的3000端口映射到容器的80端口

  • -d 后台方式运行

  • --name 容器名

  • -v 文件映射 /root/code:/data/code

查看所有容器 docker ps

开启/停止/重启容器 docker start/stop/restart <容器名>

进入容器 docker attach <container_id> 接管容器内的输出

docker exec -ti [container_id] /bin/bash

删除容器 docker rm  -f <container...>

基于Dockerfile来构建自定义镜像

参考:Docker——从入门到实践

Docker 部署 vue 项目

  • dockerfile指令

FROM :基础镜像,表示当前新镜像是基于哪个镜像进行更改的

RUN :执行shell命令,在docker build时运行

CMD :在docker run 时运行,为启动的容器指定默认要运行的程序,可被 docker run 命令行参数中指定要运行的程序所覆盖。

COPY 、ADD:把文件复制到容器中

EXPOSE:暴露容器端口

WORKDIR: 切换目录 类似于cd

以UCF3.0文档中心项目为例,部署这个前端应用的流程:

  1. npm install, 安装依赖

  2. npm run build,编译,打包,生成静态资源

  3. 服务化静态资源

FROM node:alpine as builder 

ENV NODE_ENV production   
WORKDIR /code
ADD . /code
RUN npm install --registry=https://registry.npm.taobao.org  --production && npm run build

//为了跨域,需要把打包后的文件夹部署到nginx下
//获取nginx镜像
FROM nginx
COPY --from=builder /code/nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /code/build/ /usr/share/nginx/html/

EXPOSE 82
CMD ["nginx", "-g", "daemon off;"]
复制代码

这样会获得一个以nginx为基础的镜像和一个无标签的中间镜像(需要删除)。

利用jenkins在jenkinsfile中使用docker命令构建docker镜像容器。

def dateFormat = new SimpleDateFormat("yyyyMMddHHmm")
def dockerTag = dateFormat.format(new Date())
pipeline {
    agent any
    environment {
        dockerName='documentcontainer'
    }
    stages { 
        stage('Docker build') {
            steps {
                sh 'pwd'
                sh 'ls'
                sh "docker stop ${dockerName} || true"
                sh "docker rm -f ${dockerName} || true"
                sh "docker build -t  ${dockerName}:${dockerTag} ."
                sh "docker rmi $(docker images -f dangling=true -q | sed -n  '3,$p' | awk '{print $0}') --force || true"
                sh "docker run -it -p 82:82 -d --name ${dockerName} ${dockerName}:${dockerTag}"
                //only retain last 3 images
                sh "docker rmi $(docker images | grep ${dockerName} | sed -n  '4,$p' | awk '{print $3}') --force || true"
            }
        }
    }
    post{
        success{
            echo "========pipeline executed successfully ========"
        }
        failure{
            echo "========pipeline execution failed========"
        }
    }
}
复制代码

这样我们已经实现持续集成持续部署了,但构建镜像的时间还太长,想想可以怎么优化。

如何优化,让镜像构建更高效

构建镜像时,经常会有镜像体积过大、构建镜像时间过长的问题,那如何优化呢?

参考:如何使用 docker 部署前端应用

  • dependencies 和 devDependencies

在生产环境中使用 npm install --production 装包

  • 利用镜像缓存
...
WORKDIR /code
ADD package.json /code //package.json 与源文件分隔开写入镜像
RUN npm install --registry=https://registry.npm.taobao.org  --production 
ADD . /code
RUN npm run build
...
复制代码
  • 多阶段构建

关于镜像体积的过大,很大一部分是因为node_modules 臭名昭著的体积。

可以利用 Docker 的多阶段构建,仅来提取编译后文件。

Docker Compose

如果我们的项目需要一个后端服务、mongo数据库,我们需要写很多docker命令构建各个镜像容器,我们可以用Docker Compose帮我们管理多个Docker容器,只需要简单的一条命令就可以构建启动各个镜像容器。

Docker Compose 通过一个配置文件docker-compose.yml来管理多个Docker容器,docker-compose.yml描述了多个容器的属性。

version: '3.2'
services:
  mongodb:
    image: mongo
    build:
      context: ./
      dockerfile: mongo-Dockerfile
    restart: always
    ports:
      - '27017:27017'
    volumes:
      - /usr/local/egg-app/mongo/backup/o45:/mongo/backup/o45
      - /usr/local/egg-app/mongo/data/db:/data/db
  server:
    image: egg-server
    volumes:
      - /usr/local/egg-app/run:/egg-app/run
      - /usr/local/egg-app/logs/server:/root/logs/server
    build: ./
    restart: always
    depends_on:
      - mongodb
    links:
      - mongodb
    ports:
      - '7001:7001'
复制代码
  • docker-compose 基础命令

关闭容器 docker-compose stop || true;

删除容器 docker-compose down || true;

构建镜像 docker-compose build;

启动并后台运行 docker-compose up -d;

遇到的问题

1、后端服务无法连接数据库

①把后端dockerfile里的RUN npm run start 改为CMD  npm run start

  • RUN 是在 docker build时运行

  • CMD 在docker run 时运行。所以后端服务应在数据库容器build构建后再启动

②mongoose连接url

 mongoose: {
      client: {
        url: 'mongodb://root:123456@172.27.24.217:27017/o45?authSource=admin&readPreference=primary&appname=MongoDB%20Compass&directConnection=true&ssl=false',//当有账号密码时
        options: {
          useNewUrlParser: true,
          useUnifiedTopology: true,//warning
        },
      },
    },
复制代码

2、后端服务容器一直启动失败

原因:egg-scripts start 改为前台运行

启动命令

$ egg-scripts start --port=7001 --daemon --title=egg-server-showcase
复制代码

如上示例,支持以下参数:

  • --port=7001 端口号,默认会读取环境变量 process.env.PORT,如未传递将使用框架内置端口 7001
  • --daemon 是否允许在后台模式,无需 nohup若使用 Docker 建议直接前台运行。
文章分类
前端