基于 gitlab CI 的自动化镜像构建及部署

随着容器化技术的普及,越来越多的服务使用了 docker 来部署,更新的过程往往直接在生产环境服务器上执行 docker build 指令,而这又会带来影响生产环境性能的问题,如何简化这个过程称为课题。本文试着探讨如何利用 gitlab CI 和两台服务器,并结合镜像仓库来自动化构建和部署 docker 镜像,对两个过程一探究竟。

前置假设探讨

1. 为什么有两台服务器?

部署服务器用于生产环境,需要保持稳定。而构建镜像涉及到 build 阶段,需要消耗服务器的内存和 CPU 资源,这会降低服务器的性能,所以不能直接在生产环境的服务器上执行。而需要一台专门用于构建的构建服务器,这样即便构建过程特别消耗服务器资源,也不会把生产环境的服务器搞挂。

2. 为什么不直接用 runner 指定镜像构建,而需要用 ssh 指令在另外的构建服务器中构建,增加一台构建服务器不是需要更多的资源吗?

目前 runner 的运行内容是通过在 gitlabci 中指定运行的 docker 镜像,如果在该镜像的运行的容器中构建 docker 镜像,则需要用到 docker 客户端和服务端,这种在容器中使用 docker 再构建的实现方式较为麻烦,且不直观。关键的一点是,需要为容器配置 docker 命令困难,所谓 docker in docker 的方案不见得是最优解。

概述过程

基于 docker 镜像的服务的自动化部署过程具有三个基本的子过程(stages),即 build, deploy 和 notify。其中前两个过程是必需的。

大概需要用到哪些配置信息?以及大致的过程是怎么样的?

镜像自动化构建

构建服务器

  • 构建用户
  • 构建目录
  • 代码拉取
  • ssh 指令

镜像版本号

镜像推送

镜像自动化部署

部署服务器

  • 部署用户
  • 部署目录
  • 代码拉取
  • ssh 指令

镜像版本号

镜像拉取

问题细化

如何确保部署过程使用的镜像可以获取到?

需要确保使用镜像部署时,镜像已经被构建好并且推送到了部署过程使用的 registry。

如何确保部署过程使用正确的镜像?

一种是镜像构建过程将获得的镜像名和版本号传递/通知给部署过程,另一种则是部署过程使用和构建过程一致的版本号计算/配置方法。这里简便起见,使用第二种方法,即两者使用相同的版本号计算方法。且部署过程计算好版本号后,将版本号放入环境变量配置 .env 中配置,以便用于后续的过程和过程回顾。

CI 的环境变量配置

涉及到 ssh 指令在两台服务器中执行,那么分别是需要指定下面的环境变量

SSH_USER=
SSH_PRIVATE_KEY=
SSH_PORT=
SSH_SERVER=


BUILD_SERVER=

上述 SSH_SERVERBUILD_SERVER 分别用于生产环境和构建服务器的服务器地址。

为了简化构建过程和生产环境的部署过程的统一过程,使用相同的用户和相同的私钥、端口号,那么服务器ssh 用户信息可以共用。只需要在两台服务器中新建相同的用户名用于专用的过程,并且配置好目标私钥对应公钥到该用户的授权钥匙,在 ~/.ssh/authorized_keys 中配置。

配置好用户和公钥权限后,build 过程和 deploy 过程的 before_script 脚本可以使用几乎一致的内容,只有服务器地址的差别,都是配置容器到两台服务器的ssh 权限。

其中 build 过程的 before_script 如下:

build:
  stage: build
  before_script:
    - which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )
    - eval $(ssh-agent -s)
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan -p $SSH_PORT $BUILD_SERVER >> ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
    - ping -c 4 $BUILD_SERVER
    - ssh -p $SSH_PORT -T $SSH_USER@$BUILD_SERVER

deploy 的 before_script 内容与 build 的 before_script 内容一致,唯一的区别只不过是将 $BUILD_SERVER 替换为 $SSH_SERVER

计算版本号的方法

利用 shell 脚本,拼接当前的日期和该次构建过程所用 git 提交的 commitid 缩写作为版本号。如下脚本

ci-get-version.sh

#! /usr/bin/bash

export IMAGE_NAME='abc/imagename'
export DOCKER_REGISTRY='registry.cn-hangzhou.aliyuncs.com'

# 版本号设置

export DATE=$(date +%Y%m%d)

export COMMIT_ID=$(git rev-parse --short=7 HEAD)

export VERSION="${DATE}-${COMMIT_ID}"

echo "version:${VERSION}"

最后将版本号 VERSION=xxx-xxx 写入 .env 中

sed -i $"s/VERSION=[0-9]\{8\}-[0-9a-zA-Z]\{7\}/VERSION=$VERSION/g" .env

镜像构建过程脚本

#! /usr/bin/bash

export IMAGE_NAME='abc/imagename'
export DOCKER_REGISTRY='registry.cn-hangzhou.aliyuncs.com'

# 版本号设置

export DATE=$(date +%Y%m%d)

export COMMIT_ID=$(git rev-parse --short=7 HEAD)

export VERSION="${DATE}-${COMMIT_ID}"

echo "Docker image version: ${VERSION}"

docker build . -t "${IMAGE_NAME}:${VERSION}"
docker tag ${IMAGE_NAME}:${VERSION} ${DOCKER_REGISTRY}/${IMAGE_NAME}:${VERSION}
docker push ${DOCKER_REGISTRY}/${IMAGE_NAME}:${VERSION}

在 docker-compose.yml 中指定服务使用的 image,可以利用 docker-compose.yml 的变量语法,便于直接读取 .env 的 VERSION 变量配置,如下

services:
  servicename:
    image: registry.cn-hangzhou.aliyuncs.com/abc/imagename:${VERSION}
    container_name: conatinername

如上所述,实现了利用 gitlab CI pipeline 结合构建服务器和部署服务器自动化构建和部署 docker 镜像的过程。

关于本文如您有任何想法和意见,欢迎与我们联系,邮箱地址zhi@uqugu.com
您对本文有什么看法,喜欢或者不喜欢都可以发表意见。