一、测试环境的CI/CD搭建
在将整个统计服务(Node应用)作为一个 Docker 容器发布到服务器上时,这带来一个问题,即这个 Node 应用该如何访问宿主机上的 Nginx 日志呢?
测试环境的 CI/CD 部署方案如下图所示,Docker 容器通过 docker-host
访问宿主机的数据库,并利用volumes 将宿主机的 Nginx 日志映射到 Docker 容器中。
1.1 基于 Git Actions 搭建 CI/CD 环境
测试环境的 CI/CD 环境是基于 Git Actions 搭建的,整个 workflows 的配置流程如下:
- 安装依赖,并在云端检测代码提交是否符合代码规范、是否满足单元测试(保证质量);
- 登录到远程服务器(利用远程服务器私钥),进入相应的项目目录下;
- 切换开发分支,并拉取代码;
- 构建镜像,然后运行该镜像;
测试环境的 deploy-dev.yml 配置
name: deploy for develop
on:
push:
branches:
- 'develop' # 只针对 develop 分支
paths:
- '.github/workflows/*'
# - '__test__/**' # 测试环境不需要立即测试
- 'app/**'
- 'Dockerfile'
- 'docker-compose.yml'
- 'bin/*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: 14
# 进行云端代码检测:是否符合代码、是否满足单元测试
- name: lint and test # 测试
run: |
yarn
yarn lint
yarn test
# 设置临时 ssh key,用于登录服务器拉取代码
- name: set ssh key
# 将远程服务器的 RSA 添加至 Git Actions 的 secrets 中
run: |
mkdir -p ~/.ssh/
echo "${{secrets.SSH_RSA}}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
# 查看远程服务器的公钥,并将公钥内容保存到known_hosts(ssh命令默认使用的文件是/.ssh/known_hosts)
# ssh-keyscan 端口号要设置正确,否则会构建失败!!!
ssh-keyscan -p 42xx2 xxxx.com >> ~/.ssh/known_hosts
# 部署(登录远程服务器,拉取代码,构建镜像)
- name: deploy
# 在进行部署操作前,要先登录服务器,完成下列操作:
# 操作一:将远程服务器的 RSA 存储至 GIT 全局设置中
# 操作二:创建 /home/work/ii-server/ii-server 目录
# 操作三:git clone git@github.com:username/cli-server.git -b develop
run: |
ssh -p 42xx2 root@xxxx.com "
cd /home/work/ii-server/cli-server;
git checkout develop;
# 重新下载最新代码
git pull origin develop;
# 启动 docker-compose, 与 docker-compose.yml 中的 service 名字保持一致
docker-compose build ii-server;
docker-compose up -d;
"
# 删除临时 ssh key
- name: delete ssh key
run: rm -rf ~/.ssh/id_rsa
1.2 Dockerfile 文件配置
Dockerfile
配置文件的主要作用是设置容器时区、创建 nginx 日志目录、安装依赖包、设置docker-host
,并将整个 Node 服务跑起来。其中 /spin/ip route
命令会列出当前系统的路由信息,并将 docker-host
域名解析至默认的静态路由上。
测试环境的Dockfile文件
# FROM 代表有一个基础镜像
FROM node:14
# 创建一个工作目录
WORKDIR /app
# 将当前目录下的所有文件或资源复制到镜像中(注:针对项目中的 node_modules 或
# dist 目录等不需要拷贝到镜像的文件,可以借助.dockerignore)
COPY . /app
# 设置时区(非常重要!!!!)
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
# 创建 nginx access.log 文件目录(文件目录如何映射到宿主机,可参照 docker-compose.yml 配置)
RUN mkdir -p nginx_logs/event_analytics
# 使用淘宝的镜像源进行加速
RUN npm set registry https://registry.npm.taobao.org
# 使用 --production 参数,或者当 NODE_ENV 环境变量被设置为 production,
# yarn 将不会安装 package.json 中的 devDependencies。
RUN yarn --production
# CMD 用于执行脚本。相当于镜像运行起来之后,它就会执行CMD后的命令。
# 1. docker-host 指向宿主机 ip,以方便 docker 内部访问宿主机;
# 2. 执行脚本 npm run start(通过 egg-scripts 来运行线上环境);
CMD /sbin/ip route|awk '/default/ { print $3,"\tdocker-host" }' >> /etc/hosts && npm run start
下面详细讲解下这条命令 /sbin/ip route|awk '/default/ { print $3,"\tdocker-host" }' >> /etc/hosts
的具体含义。
iproute 工具包
iproute 是 Linux 下一个网络管理工具包合集,用于取代先前的如 ifconfig、route、ifup、ifdown、netstat等历史网络管理工具。该工具包功能强大,它通过网络链路套接字接口与内核进行联系。
ip link
# 网络设备配置命令,如可以启用/禁用某个网络设备,改变 mtu 及 mac 地址等
ip addr
# 用于管理某个网络设备与协议(ip或ipv6)有关的地址。
# 与 ip link 类似,不过增加了协议有关的管理(ip地址管理)
ip route
# 管理路由,如添加,删除
awk 文本分析工具
awk 是一个强大的文本分析工具,在其对数据分析并生成报告时,显得尤为强大。简单来说 awk 就是把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行各种分析处理。awk 它允许您创建简短的程序,这些程序读取输入文件、为数据排序、处理数据、对输入执行计算以及生成报表,还有无数其他的功能。
使用方法
awk ‘{pattern + action}’ {filenames}
尽管操作可能会很复杂,但语法总是这样,其中 pattern
表示 AWK 在数据中查找的内容,而 action 是在找到匹配内容时所执行的一系列命令。花括号({})不需要在程序中始终出现,但它们用于根据特定的模式对一系列指令进行分组。 pattern就是要表示的正则表达式,用斜杠括起来。
通常,awk是以文件的一行为处理单位的。awk每接收文件的一行,然后执行相应的命令,来处理文本。
示例1
命令:
last -n 5 | awk '{print $3,"\tdocker-host"}'
结果输出:
其中:
$0
变量是指整条记录。$1
表示当前行的第一个域,$2
表示当前行的第二个域,……以此类推。print
是打印输出函数,其中print
函数的参数可以是变量、数值或者字符串。字符串必须用双引号引用,参数用逗号分隔。如果没有逗号,参数就串联在一起而无法区分。
示例2
命令:
/sbin/ip route | awk '/default/ {print $3,"\tdocker-host"
结果输出:
**/ect/hosts**
文件
Linux 的 /etc/hosts
这个文件告诉主机哪些域名或主机名分别对应哪些ip。比如文件中有这样的定义
192.168.102.136 dbfan aeolus
假设192.168.102.136
是一台网站服务器,在网页中输入 http://dbfan 或 http://aeolus 就会打开192.168.102.136
的网页,其中 dbfan 是域名,aeolus 是主机名别名。
1.3 Docker Compose 配置
docker-compose.yml
配置文件主要作用是构建一个 Docker 镜像,并将容器内的目录映射到宿主机上。
测试环境的**docker-compose.yml**
配置
version: '3'
# 服务
services:
# 服务 ii-server
ii-server:
# 服务除了可以基于指定的镜像,还可以基于一份 Dockerfile,在使用 up 启动之时执行构建任务,
# 这个构建标签就是 build,它可以指定 Dockerfile 所在文件夹的路径。Docker Compose 将会
# 利用它自动构建这个镜像,然后使用这个镜像启动服务容器。
build:
# context 选项是 Dockerfile 的文件路径
context: .
# 使用此 dockerfile 文件来构建,必须指定构建路径
dockerfile: Dockerfile
# 指定服务的镜像名称或镜像ID
image: event-analytics-server
# 为自定义的容器指定一个名词,而不是使用默认名称
container_name: ii-server
# 映射端口
ports:
# 将容器内部的服务端口 3002 映射到宿主机的 8080 端口上
- 8080:3002
# 挂载一个目录或者一个已存在的数据卷容器,可直接使用HOST:CONTAINER这样格式
volumes:
# 将宿主机目录/home/work/nginx_logs/event_analytics映射到容器目录/app/nginx_logs/event_analytics
- /home/work/nginx_logs/event_analytics:/app/nginx_logs/event_analytics
1.4 Nginx 配置
测试环境的 Nginx 配置
# 自定义事件统计服务 Nginx 配置文件
#
# 注:
# 1. 此处 nginx 配置文件跑在宿主机下,监听端口号与 Node 应用的端口号不是一致的
# 2. 需在 /ect/nginx/nginx_conf 把当前配置include进去
server {
# 记得在服务器上开启相应的防火墙!!!
listen 8080;
server_name event_analytics;
# 一定要在服务器上先创建目录以及相应的文件,否则会报错(日志格式为main格式)
access_log /home/work/nginx_logs/event_analytics/access.log main;
error_log /home/work/nginx_logs/event_analytics/error.log;
# 静态资源目录地址(获取event.gif图片等)
root /home/work/ii-server/cli-server/nginx_files;
}
二、线上环境的CI/CD搭建
测试环境下,我们通过 push develop 分支来触发 github actions,并实现自动发布到测试机。但如果要上线该什么时机触发呢,是 push master 分支的时候吗?
在测试环境下基于 push develop 分支触发部署,这样简单方便,因为测试环境通常不需要回滚。而线上环境,使用 tag 触发部署而不是 master 分支,这是因为:
- master 分支可能会被频繁合并,但不能频繁上线。push tag可以作为上线正式的“仪式”;
- 回滚时,使用 tag 更加方便。否则,就得在 master 分支去 revert commit 记录(操作很麻烦);
因此线上环境的 CI/CD 环境搭建主要基于:
- git tag + github actions 实现上线和回滚;
- 使用 Shell 脚本 release-it 进行改造,增加 changeLog
2.1 上线流程
部署线上服务
参考 bin/deploy.sh
#!/bin/bash
# 执行时需传入参数,即参数$1表示git tag,命令示例如`sh bin/deploy.sh v1.0.1`
# 在 shell 代码中使用$1、$2即可依次获取参数
serverPath="/home/prod/server"
repoPath="/home/prod/server/cli-server" # 项目目录,要和repo同名
repoGitUrl="git@github.com:xxxxx/cli-server.git"
if [ ! -d "$serverPath" ]; then
# 目录不存在,则创建
echo ========================== mkdir "$serverPath" =============================
mkdir "$serverPath"
fi
cd "$serverPath"
if [ ! -d "$repoPath" ]; then
# 如果repo目录不存在,则git clone (私有项目,需要 github 用户名和密码)
echo ========================== git clone start =============================
git clone "$repoGitUrl"
echo ========================== git clone end =============================
fi
cd "$repoPath"
# 撤销一切文件改动,否则会导致 pull 失败
git checkout .
# 下载最新master代码,tag 都是基于 master 分支提交的
git pull origin main
# 获取所有 tags,否则光执行 git pull origin master 获取不到新提交的代码
git fetch --tags
# 切换到当前tag,重要!!!
git checkout "$1"
echo ========================== git clone "$1" =============================
# 安装依赖
npm install --registry=https://registry.npm.taobao.org
# 运行服务(To do: 存在服务短暂不可用的情况)
npm run stop # 停止服务
npm run start # 运行服务
echo ========================== deploy success =============================
基于 push tag 触发
# 创建新的标签(-a指定标签名,-m指定说明文字)
git tag -a v1.0.1 -m "xxxx"
# 把所有 tags 提交到远端
git push --tags
参考 github actions 的 deploy-prod.yml
触发时机是在tags的时机下
2.2 回滚流程
一键生成 tag
手动操作,总有失误的时候。所以,要整合为一个命令,一键执行。
# 创建新的 tag
git tag -a v1.0.1 -m "xxxx"
# 把所有tags提交到远端
git push --tags
合成一条Shell脚本命令
参考 bin/create-version-tag.sh
由于window电脑不能执行shell脚本,可以使用release-it 来进行发布。
回滚
重新执行 github actions 任务。
可以基于Git Actions可以很清晰的进行回滚。
注意:
- 要上线,必须有版本、有回滚,否则无法闭环
- 上线是一件非常严肃的事情,从流程上就要严肃慎重对待