博客迁移 + coding.net CI/CD功能体验

cover2

前言

最近几天对博客进行了一次迁移,简单说就是从一个服务器迁移到了另一个服务器。不过也没有这么简单,借着这次迁移,体验了一下coding.net提供的CI/CD功能,便写篇博客记录下。

先介绍一下迁移前的部署方式。首先这个博客是使用Hexo生成的静态博客,环境依赖非常简单,用Nginx提供静态资源的访问即可。其次,开始搭建博客的时候,为了实现“一行命令生成并部署博客”,git仓库是放到部署服务器上的,使用git hooks执行脚本更新博客内容(也就是git仓库和服务器软件运行在同一个环境)。

仓库迁移

首先要做的是仓库迁移,这一步其实很简单,在本地执行两条命令就可以搞定。

1
2
git clone --bare {原地址}
git push --mirror {新地址}

分别解释下:

git clone —-bare {原地址} 通常我们执行clone指令时会创建git的工作区,也就是我们进入clone下来的目录时看到的内容。这里加上 —bare 参数的目的是clone不带工作区的结构。以便于下一步将git仓库推送到新的仓库地址。

git push --mirror {新地址} push指令会将仓库推送到远端仓库 —mirror 的作用是让git将仓库的所有refs都推送到远端仓库。可以简单理解创建了一个镜像(mirror)。

CI/CD 服务结构

开始介绍CI/CD的配置之前,先谈谈我对CI/CD的理解。要理解CI/CD,可以先思考一下如果没有CI/CD,我们是怎么构建和交付服务的?

  • 如果我们要交付一个Android App,我们可以使用Android Studio构建项目并且对apk文件签名,然后就可以发布给用户了。
  • 如果我们要交付一个使用Java编写的网站服务,我们可能会使用IDE构建项目生成一个jar或者war文件,上传到Tomcat服务器中运行。
  • 如果我们要交付一个基于Hexo的博客网站,我们需要使用Hexo的命令生成静态文件,并打包上传到服务器上,解压到服务器软件能够正确读取的目录。

通过上面的描述,我们可以发现这个过程里面存在这些环境或者工具:

  • 开发环境:修改项目源代码的环境。
  • 构建工具:构建项目生成产物的环境。
  • 发布环境:也可以叫做部署环境,就是实际运行产品的环境。

上面的几个场景中,构建工具是运行在开发环境,也就是我们的电脑中的。那么如果使用CI/CD来进行“持续集成/持续部署”,这个过程会有什么变化吗?以这次的博客迁移为例,我花了一个结构图/流程图来体现这些环境,服务以及服务之间的关系。

cicd-service-structure

除了CI服务、CD服务,可以看到还包含了CI执行器、CD代理这两个角色。

CI执行器在实际工作时并不一定是独立于CI服务运行在两台服务器上的,但是在结构上,CI执行期和CI服务是分开的,并且独立运行的CI执行器有诸多好处:

  • 构建过程可能耗费较多的资源,将CI执行器独立运行可以降低CI服务的负载,提高CI服务的可用性。
  • 使用Docker等虚拟化技术的CI执行器可以方便的进行构建环境准备并且在构建任务完成后进行清理、缓存等操作,这些操作对宿主环境都没有影响。
  • CI执行器可以运行在不同的环境中进行特定的构建工作,比如在macOS上执行iOS app的构建任务。
  • 等等

CD服务用来配置和管理CD任务,监控任务执行状态。具体执行部署脚步的,就是CD代理。CD代理运行在发布环境中,执行部署脚本的过程就如同我们远程登录到发布环境中执行部署脚本一样。类似于CI执行器,CD服务只用来管理,具体任务交给CD代理执行,这样可以让任务的管理和执行解耦。

  • CD代理运行在运行环境中,需要有运行环境的权限,解耦后CD服务是不需要有权限的。
  • CD代理可以提供常用的通用功能,比如日志采集、资源下载等等。
  • 通过在不同的发布环境中添加CD代理并注册到CD服务,CD服务可以并行部署多台机器。一台部署失败也可以根据策略进行回滚或者重试。

以上就是我对CI/CD的一些理解,CI/CD通过将构建、集成、部署等流程自动化,极大的降低了人工执行这些任务的成本,使得这些任务可以“持续”进行,这就是 C 的来历吧。

CI配置

coding.net的CI配置和Jenkins是兼容的,默认也使用Jenkinsfile文件作为pipeline脚本。

首先新建CI任务,创建pipeline脚本。我们将脚本放到代码库中,这样如果需要更新脚本,只需要操作代码库即可。

Coding上手很容易,文档也比较全面,Jenkinsfile也支持图形化操作,所以就不详述了。下面附上现在使用的脚本,说明一些需要注意的点:

  1. Hexo对node版本的支持不同,这里使用的版本12,根据实际情况调整。
  2. 我这里上传了两个制品,将 deploy.sh 脚本和 public.zip 都上传到了制品仓库,这样的话更新任何一个都能触发重新部署的任务。(CD的触发配置见下文。)
  3. codingArtifactsGeneric 是预置的一个插件功能,上传制品到制品仓库,我这里需要上传的就是普通文件,所以使用这个插件。如果需要上传Docker镜像,有其他的插件可供使用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
pipeline {
agent {
docker {
reuseNode true
registryUrl 'https://coding-public-docker.pkg.coding.net'
image 'public/docker/nodejs:12'
}
}
stages {
stage('checkout code') {
steps {
checkout([
$class: 'GitSCM',
branches: [[name: env.GIT_BUILD_REF]],
userRemoteConfigs: [[
url: env.GIT_REPO_URL,
credentialsId: env.CREDENTIALS_ID
]]])
}
}
stage('build hexo') {
steps {
sh 'node -v'
sh 'npm install hexo-cli -g'
sh 'npm install hexo'
sh 'npm install'
sh 'hexo g'
}
}
stage('upload deploy.sh') {
steps {
codingArtifactsGeneric(files: 'deploy.sh', repoName: 'statics', credentialsId: '${env.CODING_ARTIFACTS_CREDENTIALS_ID}', withBuildProps: true)
}
}
stage('upload public.zip') {
steps {
sh 'zip -r public.zip public'
codingArtifactsGeneric(files: 'public.zip', repoName: 'statics', credentialsId: '${env.CODING_ARTIFACTS_CREDENTIALS_ID}', withBuildProps: true)
}
}
}
}

CD配置

coding.net的CD控制台在单独的服务。因为我要部署到自己的服务器,所以选择主机服务。先在coding的控制台添加主机配置。然后进入CD控制台配置任务。

配置主机时需要配置堡垒机和部署服务器。堡垒机的存在是为了防止CD服务直接访问业务服务器而存在的。CD服务只能访问堡垒机,然后通过堡垒机上配置的用户SSH到业务服务器执行部署操作。通过限制堡垒机账号的权限,即可限制CD服务对线上服务器的影响。

所以我在这里为CD-Agent配置独立的账号。另外这个账号不开放sodu权限。

CD任务分为两个大的步骤,当然根据实际情况可能更多。

  • 准备:配置部署任务的先决条件,触发器等。
  • 部署:下载制品,执行部署脚本。

关于触发器。因为部署任务直接影响到线上业务,所以除非CD任务可控,不建议直接自动部署,仍要手动确认。不过鉴于这个项目的影响是可控的,这里配置了制品触发器。上传制品后自动触发部署任务执行。

⚠️这里需要注意,触发器只能填写一个制品。如果任务依赖多个制品,其他的制品需要勾选使用默认制品或者上一次执行制品;同时保证作为触发器条件的制品在pipeline中最后上传。

关于部署脚本。因为本质上是在业务服务器上执行,所以可以提前在服务器上测试好。然后直接配置上即可。因为CD-Agent的账号没有sudo权限,所以脚本要访问的目录需要提前配置好读写权限。

部署脚本的流程就是将下载的 public.zip 解压到部署资源目录,然后软连接到服务器访问目录,这样就可以提供访问了。

使用软链接而不是直接解压到服务器目录的好处是如果需要回滚,可以方便的修改软连接即可;另外解压到独立目录可以避免对服务器目录文件修改过程中出现的中间状态。

下面贴一下部署脚本的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash

DATA_PATH="资源目录"
DEPLOY_PATH="部署目录"
ZIP_FILE=${DATA_PATH}/public.zip

echo "Preparing deploy path..."

mkdir -p $DATA_PATH
mkdir -p $DEPLOY_PATH

echo "Unzipping source..."

now=`date +%Y%m%d%H%M%S`
SRC_FILE=${DATA_PATH}/${now}
unzip -d $SRC_FILE -q $ZIP_FILE
rm -rf $ZIP_FILE

DEPLOY_TARGET=$DEPLOY_PATH/latest

echo "Create link..."

rm -rf $DEPLOY_TARGET
ln -s $SRC_FILE $DEPLOY_TARGET

echo "Finished."

HTTPS证书、Snap和Privoxy

迁移的最后一步,是配置HTTPS证书。我的证书是从 Let’s encrypt 申请的,使用 CertBot 自动化操作。

迁移之前的服务器还是很久之前部署的,certbot还可以独立下载安装。迁移之后发现certbot修改了分发方式,使用snap来安装,使用也更加简单,自动创建定时任务更新证书。

但实际安装的时候遇到了网络的问题,snap的服务器,以及github服务器在阿里云上访问都受到限制。github还好说,可以本地下载scp上去。但是snap的应用只能通过snap安装。

最终还是使用代理才完成了安装配置,实在感慨。

代理使用 SSR + Privoxy。简单解释下这两者的关系:SSR是 SOCKS5 代理服务,运行在会话层,SSR客户端启动之后,监听本地端口处理会话,但是HTTP请求在执行时需要被转发到这个代理端口。Privoxy的作用就是作为HTTP协议的代理,并且将请求转发到SOCKS代理上。

使用到的SSR工具:GitHub - shadowsocksrr/shadowsocksr: Python port of ShadowsocksR

安装和使用步骤可以参考:GitHub - OldFatBoy/shadowsocksR-client: SSR CentOS客户端自动搭建脚本。这个仓库提供了一键安装脚本,但是很遗憾因为网络问题,无法从github下载成功。

最终还是手动操作的。因为代理是临时使用,所以我也没有配置复杂的规则,直接全局转发,安装完停止代理即可。

总结

使用 CICD 服务可以将原本需要手动执行的操作自动化完成,同时可以将不同类型的任务分配到合适的运行环境中执行,甚至可以并行执行。CICD 服务的高效和便捷为敏捷开发提供了条件。

客户端项目通常不会“持续交付”,但是对项目进行持续集成和在测试环境发布,同样能提高团队的开发效率。灵活使用持续集成工具,可以将工作流的其他环节也自动执行。比如自动修改issue的状态,自动完成工作流节点,自动生成项目分析报告等等。

将持续集成服务看作一个工具,而不局限于某一个场景,就能发挥更大的作用。编程也是一样,程序也是在自动的执行一系列操作。所以不要局限于工作中的场景,打开视野也许能有更大的收获。