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

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

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

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

一、Docker 简介

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

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

docker1

以 Nuxt 应用为例,如有需要,我们可以将其运行需要依赖的 Nginx 、 NodeJs 、应用本身及其依赖等统统打包到一起。此时就算你拿到一个新的服务器,只要上面安装了 docker,只需要几个简单的指令就可以让应用运行起来,而不需要再进行繁琐地配置。

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

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

# 基于当前文件夹下的 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

复制代码

二、基于 docker 的解决方案

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

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

接下来,我们要有一个存储版本镜像的仓库。docker 自己推出了一个镜像组织和托管平台,docker hub,学习的时候可以简单地用它来熟悉整个操作流程。之后可以搭建自己的私有仓库来满足实际的业务需求。因为我们实际业务中各种服务器都基于阿里云产品,所以这里也选择阿里云容器镜像服务来管理镜像。

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

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

三、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

四、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"]

复制代码

五、配置阿里云镜像仓库

  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:[镜像版本号]
复制代码

六、为 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

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

七、设置GitLab webhook

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

webhook

  1. 配置 webhook

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

八、服务器端配置

1、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"

复制代码

2、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;
        }
    }

复制代码

九、为发布和回滚操作各自创建单独的 job

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

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

十、最后

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


分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改