本篇是系列中的第五篇内容,我们继续聊聊如何把一个简化过的私有云环境部署在笔记本里,以满足低成本、低功耗、低延时的实验环境。如果你有闲置的轻量云服务器,也可以动手试试。
写在前面
作为“持续集成”章节的第一篇内容,我们先来聊聊在单机服务器上的 CI 的使用。
关于基础的搭建,之前的文章中已经多次提到,所以我就不再赘述,本文将着重介绍过程中的一些细节,如果你对 Gitea 和 Drone 或者 GitLab 感兴趣,可以阅读之前的内容:
- 《容器方式下的轻量仓库与CI 使用方案:Gitea + Drone 基础篇》
- 《使用容器方式编译无功能限制的 Drone CI》
- 《轻量安全的部署方案》
- 《使用 Docker 和 Traefik v2 搭建轻量代码仓库(Gitea)》
- 一些 GitLab 相关的内容
为了更低的维护成本,以及后续多机扩展使用,本文所有程序的使用均在容器环境下。
单机 CI 设计
在展开实践细节之前,我们得先来聊聊“设计”。
架构设计
CI 过程中的参与者主要有下面这几类(本篇暂不聊软件仓库部分):用户、Git服务、CI 服务、CI 执行器。
简单针对上面的参与者进行定义:“用户”可以是有血有肉的人,也可以是自动化的脚本或者 BOT,各种数据的创造者;“Git 服务”,用于存储代码数据,提供基础的权限功能和界面管理的程序;“CI 服务”,提供持续集成的任务的调度和管理的程序;“CI 执行器”,用于执行具体的 CI 任务的程序。
考虑到单机服务器上除了 Git 服务和 CI 服务之外,还会运行我们需要更新和部署的程序,为了让资源使用效率更好、维护成本更低、避免我们为每一个 Web 程序配置 HTTPS 证书,我们可以添加一个支持服务发现的应用网关。
即使是单机服务器,我们依旧需要注意 SSH 的使用安全,在多机环境下,我们会使用跳板机和云服务器安全策略来进行集中的安全管理,在单机场景下,我使用 SSH 服务开关来完成简单的安全防护(不用的时候,直接关闭,也为互联网上的嗅探机器人省点电)。
如果将上面的“参与者”用图例来表示,一个最基础的单机 CI 使用模式会类似下面这样:
我将图中不同角色的数据交互进行的数字序号标注,简单解释一下这些序号代表的具体内容:
- “1” 表示了用户使用具体的域名来访问我们的 Git 服务和 CI 服务,来进行仓库管理或者配置 CI 任务。这类交互使用的是 HTTP 的方式,比如在浏览器中访问
https://gitea.lab.com
、https://gitlab.lab.com
、https://drone.lab.com
。 - “2” 表示了用户或者客户端使用 SSH 的方式访问 Git 仓库,需要搭配 RSA Key 使用。
- “3” 和 “4” 表示了 Traefik 使用服务发现的方式,聚合 Git 服务和 CI 服务,为用户提供域名形式的访问方式,这里使用的代理模式同样也是 HTTP。
- “5” 表示了 SSH 开关和 Git SSH 服务之间的数据交互,交互形式为 TCP。
- “6” 和 “7” 表示了 CI 服务 分别和Git 服务、CI 执行器之间的数据交互,从 Git 获取仓库变动,然后创建 CI 任务,接着将 CI 任务执行状态不断推送至 Git 服务中,交互形式不限,可以使用 HTTP API,也可以使用各种基于 TCP 的 RPC 的方式。
- “8” 则表示了 CI 执行器如何从 Git 服务器的代码仓库中获取代码,或者将一些数据更新回 Git 服务器中,一般情况下是使用 HTTP 的方式,我更推荐使用 Git Over SSH 进行交互。
部署模式
在单机全容器模式下,我们一般会用两种方式可以完成部署。
一类是基于文件挂载的方式,比如在 CI 过程中将 CI JOB 容器中的文件系统和宿主机打通,然后将构建产物同步到宿主机中、类似的变体还有使用各种网络文件协议进行文件系统挂载;另外一类,则是使用 SSH 或者 SCP 、Rsync 等方式,在容器中访问宿主机完成数据交换或者服务初始化或启停操作。
除此之外,如果我们借助软件仓库、容器仓库,还能够完成纯容器交付,让交互更纯粹和“干净”。这个话题,我们会在后续文章中展开。
单机 CI 配置实践
接下来,我们以上文中的 “SSH 开关”这个应用,在 Gitea 和 Drone 环境中进行持续集成和部署实践为例,来聊聊如何在单机模式下使用 CI。
因为这个项目类型是一个不支持热加载的、需要持续运行的网络程序,程序的更新需要重启服务。所以我们恰好可以使用“部署模式”中的挂载文件的方式更新文件,以及使用 SSH
的方式来进行服务的停止和重新启动。(如果是静态资源类的项目部署,则只需要完成资源替换更新即可)
定义 CI 配置文件
首先将需要集成 CI 的项目放置上传到 Gitea 中的某个仓库中,这里以上文中提到的 Git SSH 开关为例。在项目中创建一个名为 .drone.yml
的 CI 配置文件。
一个相对通用的 CI 配置可以用下面的形式来表达:
---
kind: pipeline
name: default
steps:
- name: clone
- name: stop-previous-services
depends_on: [ clone ]
- name: update-services
depends_on: [ stop-previous-services ]
- name: start-new-services
depends_on: [ update-services ]
上面的配置包含了:下载仓库代码、停止原先的服务、更新服务程序代码、重新启动服务四个过程。在实际生产中,根据业务类型,我们的执行顺序可能会有变化,甚至不再是上面的“串行”方式执行。
按照上面的配置将 CI 配置好之后,当我们推送代码到代码仓库触发 CI 任务后。在图形界面中,我们将看到类似上图的结果。
使用 SSH 协议下载代码
不论是使用哪一种 CI 工具,我都推荐你使用 Git Over SSH 的方式来获取代码,而非使用 Git Token 或者账号密码的方式来进行交互。这样可以让你的程序对于某一种 CI 或者 Git 仓库的依赖更低,更容易在合适的时间点、以低成本切换到更合适的工具。
在 Drone CI 中,如果想使用 SSH 方式来下载代码,可以使用下面的配置:(在 GitLab Runner 中同理)
---
kind: pipeline
name: default
clone:
disable: true
steps:
- name: clone
image: alpine/git
pull: if-not-exists
environment:
KEY:
from_secret: ssh_key
commands:
- GIT_HOST=$(echo $DRONE_GIT_SSH_URL | sed 's/git@/\1/' | sed 's/:.*/\1/') && mkdir "$HOME/.ssh" && echo "$KEY" > "$HOME/.ssh/id_rsa" && chmod 600 $HOME/.ssh/id_rsa && eval `ssh-agent -s` && ssh-add $HOME/.ssh/id_rsa && ssh-keyscan $GIT_HOST > ~/.ssh/known_hosts && chmod 400 "$HOME/.ssh/known_hosts";
- git clone $DRONE_GIT_SSH_URL .
- git -c advice.detachedHead=false checkout $DRONE_COMMIT
上面的代码中,为了使用 SSH 方式下载程序代码,CI 程序会做两件事:
- 从 CI 软件中读取我们预先配置好的
ssh_key
环境变量,然后将变量输出成程序可以直接使用的rsa_key
,并设置好权限,使用ssh-agent
加载程序。 - 将仓库使用默认的
HTTP
协议替换为Git
协议,以备程序使用。
当然,想要使用 SSH 方式下载代码,我们需要在 Git 软件的账号或者仓库中配置 SSH Key
。
使用 SSH 方式操作服务启停
这个应用中,我们在 docker-compose.yml
定义了容器的启动方式,所以服务的启动和关闭可以使用我们熟悉的命令 docker-compose up -d
和 docker-compose down
来完成。
因为 CI 在容器中执行,我们不能直接操作宿主机,所以需要借助 SSH 或者 dind 模式的 docker.sock
来完成服务状态的改变。
本文先聊聊如何使用 SSH 来解决基础的部署操作:
- name: stop-or-start-services
image: deploy-tool
depends_on: [ clone ]
pull: if-not-exists
environment:
KEY:
from_secret: ssh_key
# 环境变量,除了私密的定义在 CI 软件的环境变量中,也可显式声明在 CI 配置中
TARGET_HOST: user@host
TARGET_PORT: 22
commands:
- mkdir "$HOME/.ssh" && echo "$KEY" > "$HOME/.ssh/id_rsa" && chmod 600 $HOME/.ssh/id_rsa && eval `ssh-agent -s` && ssh-add $HOME/.ssh/id_rsa";
# 关闭服务
- ssh -i "$HOME/.ssh/id_rsa" -p $TARGET_PORT -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $TARGET_HOST "bash -c \"cd /app-path/ && docker-compose down\""
# 启动服务
- ssh -i "$HOME/.ssh/id_rsa" -p $TARGET_PORT -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $TARGET_HOST "bash -c \"cd /app-path/ && docker-compose up -d\""
和下载代码类似,我们从环境变量中初始化 rsa key
,然后在 ssh-agent
中加载私钥。然后使用 ssh
客户端连接宿主机,切换工作目录,执行命令操作服务的启动和关闭即可。
同样的,想要使用 SSH 操作服务器,我们需要在服务器对应用户的 ~/.ssh/authorized_keys
中配置对应的公钥。
使用文件挂载的方式更新代码
更新代码有两种方式,一种是使用上文中提到的 SSH 的方式,远程执行 scp
、rsync
等命令同步数据,另外一种则是使用文件挂载的方式。因为我们的部署在同一台机器上,所以文件挂载不失为一个高效的方式。
以 Drone CI 配置为例,演示如何挂载宿主机目录到容器内:
- name: update-services
image: deploy-tool
depends_on: [ stop-previous-services ]
pull: if-not-exists
commands:
- rm -rf /deploy/*
- cp -r /drone/src/* /deploy/
- cp -r /drone/src/.env /deploy/
volumes:
- name: host-dir
path: /deploy
volumes:
- name: host-dir
host:
path: /app-path
最后
在接下来的“持续集成”相关文章中,我将展开聊聊 CI 在多机和相对复杂场景下的使用,以及其他场景类型的部署实战细节。
–EOF