随着容器化技术的普及,越来越多的服务使用了 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_SERVER
和 BUILD_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 镜像的过程。