[Nuxt 系列 07] 不停机更新:基于 Docker & Jenkins

设想一下这些场景:Nuxt 版本出现重大更新须要升级;新功能的开发须要添加新的生产环境依赖;线上版本出现 bug 急需快速回滚至上一版本;开发环境和生产环境依赖版本一致性的保持……java

咱们须要解决的不只仅是不停机更新,还要使开发环境和生产环境的版本保持强一致性,而且能够轻松地追溯历史版本,以及更新过程使用户无感知等等。综合以上特质,能够选择 docker 一试。node

1、Docker 简介

Docker 是一个开源的应用容器引擎,基于 Go 语言并听从 Apache2.0 协议开源。linux

它号称本身是实如今任何地方安全构建、分享和运行现代应用的最快实现方式。它可让咱们把应用及其依赖所有打包到一个容器中,从而轻松地实如今任何系统下极速迁移。就像其 logo 展现的那样,一艘鲸鱼样子的大船载满集装箱飘荡在海面上。各个 OS 就是海洋,docker 承载着一个个容器飘荡在 OS 的海洋里。nginx

docker1

以 Nuxt 应用为例,若有须要,咱们能够将其运行须要依赖的 Nginx 、 NodeJs 、应用自己及其依赖等通通打包到一块儿。此时就算你拿到一个新的服务器,只要上面安装了 docker,只须要几个简单的指令就可让应用运行起来,而不须要再进行繁琐地配置。git

具体的使用细节再也不絮叨了,茫茫多的 docker 官方文档 正向你招手~菜鸟教程和 docker 中文社区也是系统学习的不错选择。web

惯例,抛出 docker cli 的经常使用指令:docker

# 基于当前文件夹下的 dockerfile 建立一个镜像
docker build -t helloworld .      
# 上面指令的全写
docker build --tag=helloworld .
# 运行这个镜像,并将本机 4000 端口映射到容器对外暴露的 80 端口,外部经过 4000端口访问 
docker run -p 4000:80 helloworld # 使 container 在后台运行
docker run -d -p 4000:80 helloworld # 全部正在运行的容器列表
docker container ls                        
# 全部容器列表
docker container ls -a  
# 停用一个容器
docker container stop <hash>
# 强制中止一个容器
docker container kill <hash>
# 移除一个容器
docker container rm <hash>  
# 移除全部容器
docker container rm $(docker container ls -a -q)
# 全部镜像列表
docker image ls -a 
# 移除一个镜像
docker image rm <image id> 
# 移除全部镜像
docker image rm $(docker image ls -a -q) 
# 登陆注册过的 docker hub
docker login 
# 为镜像打标签
docker tag <image> username/repository:tag
# 将镜像推送至远程仓库
docker push username/repository:tag 
# 运行这个镜像
docker run username/repository:tag 
复制代码

2、基于 docker 的解决方案

此时,容器 (container) 即当前承载应用的进程,它拥有一个独立的文件系统,里面包含了应用所需的全部代码、依赖和运行时等等。这被称之为镜像 (image) 的存在,正是咱们须要在以后生产并保存起来的东西。shell

假如每一次版本发布咱们都生成一个镜像 (image),并基于项目和版本号为其打上独一无二的标签,而后把它们保存到一块儿,须要的时候随时取用,依赖更新的问题天然而然获得解决。而当须要发布新版本的时候,咱们只须要提早拉新版本的镜像到生产环境,而后移除旧版本镜像正在运行的容器,同时执行新版本的容器,开启新的 container,这样就能够实现无缝升级,也实现了某种意义上的不停机。npm

接下来,咱们要有一个存储版本镜像的仓库。docker 本身推出了一个镜像组织和托管平台,docker hub,学习的时候能够简单地用它来熟悉整个操做流程。以后能够搭建本身的私有仓库来知足实际的业务需求。由于咱们实际业务中各类服务器都基于阿里云产品,因此这里也选择阿里云容器镜像服务来管理镜像。json

仓库的问题解决了,咱们又但愿在每次 push 一个 tag 的时候可以自动地生成一个镜像并上传至仓库。咱们的项目代码托管在私有 GitLab 上,它自己就集成了不错的 CI/CD 功能,能够做为备选的目标之一。咱们这里选择使用 Jenkins (一个基于 java 的持续集成工具)并结合 GitLab 的 webhooks 来实现这一需求。

最终方案定为 GitLab + Jenkins + Docker + 阿里云镜像服务,下面来看一下大致的实施步骤。

3、Centos 下的 Jenkins 安装的大体步骤

  • 安装 java -> yum install java

  • 添加 Jenkins 库至 yum -> sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo

  • 导入公钥 -> sudo rpm --import https://jenkins-ci.org/redhat/jenkins-ci.org.key

  • 安装 -> sudo yum -y install jenkins

  • 查看 Jenkins 根目录 -> cd /var/lib/jenkins

  • 配置 Jenkins 根权限:

    vim /etc/sysconfig/jenkins,修改或添加项 -> JENKINS_USER="root"JENKINS_GROUP="root",保存退出 执行 gpasswd -a root jenkins 这里jenkins服务的默认端口为 8080,可在配置文件中修改,保存后执行 service Jenkins restart 重启服务

  • 启动Jenkins -> service jenkins start。此时访问http://<服务器公网ip>:8080开启 Jenkins 界面

  • /var/lib/jenkins/secrets/initalAdminPassword 下获取管理员密码以开启 Jenkins

  • 设置管理员帐号密码后,便可开始使用 Jenkins

4、Centos下 Docker 安装的大体步骤

Docker 社区版和企业版,这里选择安装社区版本

  • 首先,Docker CE 官方要求 CentOs 版本为 CentOs7,以前的系统版本虽然可能能够经过升级系统内核来强行安装,但我的感受存在比较明显性能缺陷,未考证。
  • 删除可能存在的老版本 Docker,如无,忽略:
$ sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest- logrotate docker-logrotate docker-engine

复制代码
  • 若是首次安装,配置 Docker 库至 yum:
$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2
 $ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

复制代码
  • 安装 Docker CE -> $ sudo yum install docker-ce docker-ce-cli containerd.io
  • 开启docker -> $ sudo systemctl start docker
  • 经过运行 hello-world 镜像检测是否成功安装 Docker -> $ sudo docker run hello-world
  • nuxt-demo 为例的 Dockerfile 配置文件,因为 docker 自身拥有守护进程和健康检查的功能,因此能够考虑放弃 pm2:
FROM node:10.16.0 
  ENV HOST 0.0.0.0
  RUN mkdir -p /app   COPY . /app   WORKDIR /app   EXPOSE 3000
  RUN npm install   RUN npm run build   CMD ["npm", "start"] 
复制代码

5、配置阿里云镜像仓库

  1. 访问阿里云容器镜像服务

  2. 登陆后,建立命名空间,建立镜像仓库,这里以 psl_one 为例

    因为这里经过 jenkins 来构建 docker 镜像,因此再也不配置代码源,选择本地仓库并建立 (阿里云镜像服务自己也提供了镜像构建功能)

    aliregistry1
    aliregistry2

  3. 进入新建立的镜像仓库,能够在镜像版本中查看已上传的镜像列表,在基本信息中查看该仓库的基本信息,其第三条说明将镜像推送到Registry中有接下来要用到的指令:

 $ sudo docker login --username=[username] registry.cn-qingdao.aliyuncs.com
 $ sudo docker tag [ImageId] registry.cn-qingdao.aliyuncs.com/[命名空间]/hjxy_test:[镜像版本号]
 $ sudo docker push registry.cn-qingdao.aliyuncs.com/[命名空间]/hjxy_test:[镜像版本号]
复制代码

6、为 nuxt_demo 配置 Jenkins Job

  • 首先安装一些必要的插件。进入系统管理->插件管理->可选插件,在过滤中搜索须要的插件

    • 在高级选项卡下能够设置升级站点 http://mirror.xmission.com/jenkins/updates/update-center.json
    • Localization: Chinese (Simplified) 简体中文语言包
    • SSH Plugin 经过ssh链接远程服务器执行shell脚本
    • Git Parameter Plug-In参数化构建过程选项下得到git相关参数,如branch/tag等
    • Generic Webhook Trigger Plugin 构建触发器插件,接收一个http请求,与webhook配合,触发job执行构建
    • nvm-wrapper 提供一个nodejs的构建环境
    • Email Extension Plugin 设置邮件通知的插件
    • etc......
  • 进入系统管理 -> 系统设置,进行 ssh 配置

    ssh.png

  • 进入系统管理 -> 管理用户,查看|生成用户 id 和用户 token

  • 进入系统管理 -> 系统设置,配置邮件通知,这里以 qq 邮箱为例

    • 首先在 qq 邮箱中开启 SMTP,开启后会生成一个登陆受权码,记下备用。而后进行 jenkins location 和邮件通知设置
    • location
    • email
    • 测试可否成功发送邮件
  • 点击新建任务,选择建立一个自由风格的项目,为 nuxt_demo 建立一个镜像构建、上传并部署的 jenkins job。约定这个任务只用于当开发者向仓库提交 tag 时触发构建,不能进行主动构建(就算主动构建,由于缺乏 tag 信息也会失败)

    • newjob
    • 常规选项卡中,添加该任务的必要描述信息
    • 配置源码管理信息:
    • codesource

    • 配置构建触发器,选择 Generic Webhook Trigger
    • postparam

    • filter

    • 因为 GitLab 的 webhook 传递来的 ref 信息形如 refs/tags/v1.0.0,因此须要在执行脚本中进行字符串拆分,获取须要的部分: $ref|cut -c11-

    • 构建环境中选择 Execute shell script on remote host using ssh
    • ssh site 即刚才在系统设置中配置的ssh ,pre build script 能够置空或根据须要填写,这里主要配置 post build script,其中 ***-deploy.sh 为远程服务器中的部署脚本,将在构建任务结束后触发执行

    • executeshell

    • 构建中选择执行 shell 脚本,这里用到了在阿里云镜像仓库基本信息中的操做指令,构建好的镜像将被推送至镜像仓库
    • buildshell

    • 点击保存,构建任务配置完成

7、设置GitLab webhook

  1. 代码仓库右上角(视具体版本而定)

webhook

  1. 配置 webhook

gitwebhook2
这里只选择 tag push events,点击 add webhook 添加钩子,并点击 test按钮测试是否可用
gitwebhook3
若成功,将触发相应的 jenkins job 执行

8、服务器端配置

一、shell脚本

/var/myprojects/shell_script/***-deploy.sh

 #!/bin/bash
  echo "deploy start"

  preImageId=$(docker inspect -f {{.Image}} <Container Name>) # 获取当前运行容器的镜像Id,稍后用以删除镜像

  docker login -u <username> -p <password> <Docker Registry>    # 登陆远程镜像仓库
  docker <Image>:$1            # 拉取最新构建的镜像

  docker rm -f <Container Name> || true                                                     # 强制删除当前运行的容器
  docker run -d --name <Container Name> -p 3000:3001 --restart=always <Image>:$1   # 使用最新拉取的镜像开启新的容器
  docker rm -f <Container Name2> || true                                                     # 强制删除当前运行的容器
  docker run -d --name <Container Name2> -p 3001:3001 --restart=always <Image>:$1   # 使用最新拉取的镜像开启新的容器
  
  docker image rm -f ${preImageId}        # 移除上一个版本的镜像

  echo "deploy end"

复制代码

二、nginx配置文件(单机负载)

upstream nuxt_demo {
       server localhost:3000 max_fails=1 fail_timeout=15s weight=1;
       server localhost:3001 max_fails=1 fail_timeout=15s weight=1;
    }

    server {
        listen      80;
        server_name test.xyz.docker.com;

        location / {
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Nginx-Proxy true;
            proxy_cache_bypass $http_upgrade;
            proxy_pass http://nuxt_demo;
        }
    }

复制代码

9、为发布和回滚操做各自建立单独的 job

参考第六节中的部分,但再也不配置构建步骤。 在general选项卡中勾选参数化构建过程选项,并配置以下参数:

buildwithgitparams
此时,侧边栏的 当即构建将变成 build with parameters
buildwithparams2
点击 build with parameters 将看到以下界面。releasetag 默认值为配置时填写的 notag,此时可参照 taglist 填写正确的值执行构建操做:
startbuild
最终的任务列表大体以下:
jobls

10、最后

以上,提供一种可行思路的大体操做步骤,如要在生产环境实行还需细细考虑具体的场景和更多的细节问题~

相关文章
相关标签/搜索