前言

为什么要做

公司用到的中间件和用到的组件超级多,JDK、Nginx、Docker、Rabbitmq、Harbor、Mysql、pgsql、MinIO……大概有 20 多个,虽然写了部署文档,直接复制粘贴就能部署,但是每次都得折腾差不多 5-6 个小时左右,而且这期间得一直盯着,和组里另一个运维(已经奏了😭)聊天的时候,他是这么说的:“来公司的别的没学会啥,复制粘贴倒是变得很熟练了😂”

做了一段时间之后,总感觉每次都这么折腾很没有意义,纯属浪费时间,就决定要把这个过程自动化

为什么不选择 Ansible

在运维(网工也有)自动化部署里面,有着非常好用的工具——Ansible ,那么为什么还是要自己折腾一番,自己单独搞一套出来捏?考虑按照优先级有以下几点

  • 想做一些定制化的东西,用 Ansible 不大好弄

有一些定制化的需求,如果是自己开发就很好做,但是用 Ansible 就不大好整,随便举几个例子

​ 1、公司的软件仓库非常慢(共享 20Mbps 带宽,和镜像仓和代码仓还有一堆服务器共用,在开发提交代码、CI/CD 传镜像和客户服务器下载镜像的时候下载会非常慢,其实很多时候,折腾的几个小时里面,大部分时间都是在等下载(几十k/s)……)

​ 希望能够实现一套机制,有源仓库(公司仓库,云上自建、可靠)和镜像仓库(加速仓库,不一定可靠),优先从镜像仓库下载,如果所有的镜像仓库失效,再回退到源仓库下载

​ 2、部署完成后,根据部署是否成功,给指定的员工在企业微信发送通知,发送通知需要先请求一个接口获取 Token ,再传参,参数里面包括有工号,用 Ansible 的话不大好确定参数

  • 部署的脚本常常会调整,希望能够简化调整的流程

​ 部署的脚本有时候会有一些小的调整,因为经常上新业务,总是会引入各种中间件,或者是对里面的配置进行修改(举例来说,比如 MySQL 里面因为程序没有做自动建表,需要部署 MySQL 的时候建表),用 Ansible ,每次修改都要去改压缩包,然后 sftp 传到公司软件仓,很麻烦

  • 依赖 Python 环境,一些发行版对 SELinux 有要求,不够“一键”

​ 华为 OpenEuler 22.03 LTS SP3,部署的时候会提示需要去关一下 SELinux,也就是说还得手动去每台机器操作去,偷懒程度不够

​ 说到不够“一键”,经常需要根据客户提供服务器的情况,调整软件部署情况,一台机器有一台机器的部署方法、三台机器有三台机器的部署方法,给七台机器有七台机器的部署方法,如果用 Ansible ,按照安装软件写 yaml 文件里面的节点,比如下面这样,填写配置文件也很麻烦

 [nginx]
192.168.1.2
[mongodb]
192.168.1.2
[redis]
192.168.1.2
[postgres]
192.168.1.3
# 还会有很多...
  • 我愿意折腾 Golang,做这个非常好玩(自己来实现一个岂不是非常有趣,很有成就感,也能趁这个机会学到很多东西)

思路

分三端,运维端、服务器端、执行端,C/S 架构(前端现在还不大熟,所以暂时不用 B/S 😂)

运维端:主要是三部分(Component 组件、Solution 解决方案、Action 执行脚本)

  • Component 组件:描述如何安装某个组件,比如部署 Nginx、部署 ClickHouse 之类的,数据库的话一般也包含建表、改密码、调优这些环节,都自动化
  • Solution 解决方案:是多个组件的组合,比如标准化部署(组件固定)数据库服务器的流程
  • Action 执行脚本:通过组件(或/和)解决方案的组合生成出来的,最终在客户端的服务器上执行的脚本

脚本的编写格式参考 GitHub Action 来弄,之前写 Action ,感觉很好写,舒服,Component/Solution 使用 Git 管理

服务器端:校验 Action 有效性,执行端企业微信消息推送中转,鉴权(运维端、执行端。不能让谁都能拿到 Action 和操作不是)

执行端:从服务器端获取 Action 并在客户的服务器上运行,负责安装指定的软件

运维端

主要是使用 Golang + Fyne + SQLite

界面展示

组件界面

查看&加载组件&新增组件

点击展开/收合图片
Components
点击展开/收合图片
Components

组件示例。由上面看到的生成组件功能生成,也可以手动填写(有删改,Solution 和 Action 和这个的感觉相似,不再展示):

# API 版本
apiVersion: component/v1
# 适配系统架构
arch: amd64
# 组件的描述
description: Nginx部署
# 本节可用的变量,还支持全局变量以及在执行中添加变量到本节或全局
env:
- masterIPAddr
# 部署需要的文件
file:
# 文件信息
- fileinfo:
# 文件的描述
fileDescription: nginx-1.24.0
# 文件保存到本地的名字
filename: nginx-1.24.0.tar.gz
# 镜像仓,可以有多个
mirror:
- https://xxx/nginx-openeuler-latest.tar.gz
# 源仓库,只能有一个
source: http://xxx/nginx-openeuler-latest.tar.gz
- fileinfo:
fileDescription: nginx-systemd
filename: nginx.service
mirror:
- https://xxx/nginx.service
source: http://xxx/nginx.service
# 组件的保存位置
fileLocation: ./cocoaScript/components/deployNginx_openeuler22_03_amd64.yaml
# 表明这个文件的类型是 component
kind: component
# 组件的英文名称
name: deployNginx
# 组件适配的系统版本
os: openeuler22.03
# 描述如何安装这个组件
run:
# @开头的是编写的组件,在脚本运行前注入
# @checkPoint 是一系列模块化的组件之一,用来判断是否要进行后面的部署,也可以用来判断部署是否成功完成
- @checkPoint [skip] [Active] [systemctl status nginx]
- mkdir /opt/server/
- dnf install GeoIP-data GeoIP GeoIP-devel pcre -y
# 下载下来的文件会放在这里
- cd /root/cocoa/downloads
- tar xvf nginx-1.24.0.tar.gz -C /opt/server/
# @appendText 文本追加写组件,不想用 sed 可以写这个,因为很常用所以单独抽了个模块出来
- @appendText /etc/profile export PATH=$PATH:/opt/server/nginx/sbin
- source /etc/profile
# $COCOALocalAddr 是执行端内置的插件获取本机地址的全局变量,通过对外发 UDP 包,能够准确的获取外部访问机器的 IP 地址
- sed -i 's/config/$COCOALocalAddr/g' /opt/server/nginx/conf/config/upstream.conf
- sed -i 's/master/$masterIPAddr/g' /opt/server/nginx/conf/config/upstream.conf
- sed -i 's/yzconfig/$COCOALocalAddr/g' /opt/server/nginx/conf/config/main.conf
- cd /root/cocoa/downloads
- mv nginx.service /etc/systemd/system/
- systemctl daemon-reload
- systemctl enable nginx
- systemctl start nginx
- cat > /etc/logrotate.d/nginx <<EOF
- /opt/server/nginx/logs/*.log {
- " daily"
- " dateext"
- " rotate 7"
- " missingok"
- " compress"
- " delaycompress"
- " sharedscripts"
- " noolddir"
- " postrotate"
- " kill -USR1 `cat /opt/server/nginx/logs/nginx.pid`"
- " endscript"
- "}"
- EOF
- logrotate -vf /etc/logrotate.d/nginx
- @checkPoint [continue] [Active] [systemctl status nginx]
- cd /root

解决方案

解决方案是由组件构成的在某一台机器上安装所有需要的软件的组件集合,新增解决方案的话,就是选择由哪些组件去组成这个解决方案,就像搭积木一样,模块化

点击展开/收合图片
Solution

Action

选择操作系统和架构,组合要使用的组件和解决方案,填充里面的变量(如果)有,然后就可以去执行了

点击展开/收合图片
Action

挑战

之前没弄过 YAML ,小小的折腾了一下,不是很难,用的 github.com/gookit/config/v2/yamlv3 这个库,使用说明很到位,执行端也是用的这个库,也支持从远程读取 YAML ,挺好用的

服务器端

使用 Go + Gin + MySQL

挑战

基本上没啥挑战,做 YAML 的校验是在这里做的

执行端

使用 Go + tview + SQLite

界面展示

点击展开/收合图片
CocoaDeployAssistant
点击展开/收合图片
CocoaDeployAssistant

补充一张部署成功之后的通知截图

点击展开/收合图片
image-20240512001242864

挑战

这里的 UI 叫做 TUI (Terminal UI),和 Fyne 一样,UI 是运行在主线程的,会阻塞主线程,所有的执行逻辑,消息传递都需要自己开协程去做这个事情,Fyne 那边框架帮忙把这个事情做好了,而 tview 这边就需要自己手动处理这个事情,需要使用好 Goroutine 和 Channel ,比如上面的界面,界面是跑在主线程,左边的终端消息处理组件和右侧的执行总体进度消息传递组件单独跑在两个协程里面,命令执行器单独跑在一个协程里面,命令执行器调度下载、插件、shell 执行组件,向终端消息组件和总体进度消息组件传递运行状态,消息处理组件收集到消息之后会根据消息的紧要程度给消息上色,折叠过长的消息等等处理,写入日志并在 TUI 上渲染

后续想做的改进

运维端使用了 Fyne ,由于 Fyne 使用了比较高版本的 OpenGL,导致对低版本的 Windows 、没有显卡的服务器和远程桌面支持不大好(PS 启动不了,但是可以用 pal1000/mesa-dist-win 解决),目前考虑是准备用 Wails ,需要去学下前端

执行端的话,想做成 web 控制的,B/S 架构,点点鼠标来部署,从中心服务器端拉取配置,然后就不需要运维端,也是要学前端

所以是准备抽空搞搞前端咯