初体验:动手搭建"小清新式"局域网文档系统

您所在的团队是否拥有良好的文档分享氛围?文档是否方便查询和阅读?更新文档是否非常繁琐?

想象一下,如果您所在的团队拥有这样一套项目文档网站,是否 项目的长期维护会很轻松

就像Square公司的开源项目,通过聚合页引导至不同的项目 :

img.png

每个项目又拥有主题分明、聚合于目录导航下的网页文档,例如:

诚然,通过 Github.IO 及类似方式,可以较为简单的部署一套在线静态文档网站。

但公司项目文档并不适合对外公开,您是否想在公司局域网范围内,自己动手搭建一套用于 版本控制自动部署持续集成 的网页文档呢?

以往的繁琐 与 如今的简明

曾经,我像图中这样,在本地或EDM软件维护文档空间,经历繁琐地检索、排查找到文档,再进行分享、阅读。

img.png

而现在,我习惯像这样,访问网页,通过不超过3级的目录,直接定位到文档内容,进行阅读

实现思路

核心思路:对重点内容编写轻量级文档,并以某种便于查找的索引方式聚合文档内容,将其托管于方便访问的网站。

典型的方式:

  • 使用Markdown形式的轻量级文档,撰写重点内容,每一篇都主题鲜明
  • 按照主题与项目模块的之间的关系、主题之间的关联性,组织目录结构,形成 "索引"
  • 是用Gitbook、博客生成器等软件,将其制作成静态网站
  • 部署静态网站

在此基础上,再加上自动部署、版本控制

涉及到的技能树,其中蓝色高亮部分一般能让人眼前一亮

判断是否需要继续阅读

可能您已经 满怀期待 地准备开始跟随博客 展开实践,但出于责任心,我建议您结合下述 流程图参考经历 判断下 是否真的需要继续

如果您已经确定要了解笔者是如何实现这一系统的,可以直接进入下一个章节

img.png

供参考的例子

A公司

背景:

  • 研发团队百人规模,4-5条业务线,业务线之间存在一定数量的人员共享(例如移动端,web端后端救火队员)
  • 业务线之间有(业务、技术)交流,流动人员需要接入、交出
  • 除去服务端接口文档(YApi or Swagger),项目设计类文档一般托管于 Confluence

团队有强烈的 "基于文档交流、存档备份、查阅回顾、知识转移" 的需求有分享氛围

遇到的典型困难:

  • 当不熟悉文档空间时,查找的难度大
  • 维护文档空间内容索引难

B公司

背景:

  • 业务线独立,互不干涉,基本没有技术沟通(私下的交流除外)
  • 业务线研发团队30-40人规模
  • 技术资料无明确要求

仅在小组范围内存在 "碎片化" 的文档材料,可能托管于印象笔记、可能托管于项目代码库,可能在某电脑中

典型困难:

  • 聊胜于无,最终基本以查代码解决问题

C公司

背景:

  • 涉足医疗行业
  • 文档等资料的编写和管理发行遵照法规条款,受各种部门监管
  • 使用EDM系统

典型困难:

  • 各种文档的出发点契合法规需求,内容不一定满足研发人员的诉求(例如:往往我们需要详尽的类图、活动图、时序图、状态图、组件及对象生命周期关系,但文档一般只从系统角度给出程序流程图)
  • 针对"查询设计细节点"而言,存在大量的内容噪声信息
  • 文档空间大、内容多、检索难度高

从上述的三个案例来看,主要矛盾是文档内容不契合使用者诉求。

文档系统难用是次要矛盾。

在公司推行这一方案时,首先要确定主要矛盾能被解决,即团队愿意编写、维护一系列的文档(或内容资料)。 当团队拥有这些长期维护的文档、资料时,就可以着手解决系统不好用的问题。

否则不能有效推行,那此时只能退而求其次,尝试掌握额外的技能。

开始动手

明确环境

按照我们的目标,可以确定,我们需要以下必要环境:

  • 一个局域网,不能有AP隔离、网段不能访问隔离(即设备间可以互相访问)
  • 一台部署文档服务的机器
  • 多台访问设备

作者按:如果您已经开始在公司正式开始推行,大概率会申请到一台Linux虚机或者Docker;小范围尝试时,可能是使用自己的办公电脑,一般是Windows or macOS系统,Linux概率较低。

作者手边有一台 macOS10.15.7的机器,下文所有内容均以此版本为背景,注意:不同的操作系统可能会遇到不同的问题。

接下来我们需要搞定以下软件环境,注意目前处于macOS 10.15.7环境下

  • Gitlab 服务 及账号(至少具备创建、读取、提交项目的权限),版本无特定要求,用公司现成的即可
  • Docker 4.7.0 (77141),未调查版本特定要求,该版本已确定可用,较新
  • Docker-ubuntu 镜像,未调查版本特定要求,可使用最新版本
  • Gitbook 3.2.3, 版本无特定要求,用于生成Gitbook,结合需求选用版本

Docker

接下来我们开始安装Docker,并从基本镜像开始搭建静态网站

安装

首先下载 并安装Docker,这一步基本不会遇到问题

安装完成后,通过 docker version 命令,可以获得docker各个模块的版本信息

获取ubuntu镜像

docker image pull ubuntu:latest

以上命令可以获取官方镜像仓库中的最新的Ubuntu镜像

生成容器

docker run -p 80:80 --name doc -i -t ubuntu /bin/bash

通过以上命令可以 基于ubuntu镜像 得到一个 名为 doc 的容器 ,映射本机80端口和容器的80端口,可使用bash。

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

docker run 等价于 docker container run,在docker的迭代中,功能越来越多,增加了分组

注意:我在第一次体验docker时,没有处理好端口映射(随机端口分配),带来了很多问题,后面会给出专门的解决方案。

读者朋友可以结合以下内容做一些尝试,体会下各种功能

常用选项说明:

  • -d, --detach=false, 指定容器运行于前台还是后台,默认为false
  • -i, --interactive=false, 打开STDIN,用于控制台交互
  • -t, --tty=false, 分配tty设备,该可以支持终端登录,默认为false
  • -u, --user="", 指定容器的用户
  • -a, --attach=[], 登录容器(必须是以docker run -d启动的容器)
  • -w, --workdir="", 指定容器的工作目录
  • -c, --cpu-shares=0, 设置容器CPU权重,在CPU共享场景使用
  • -e, --env=[], 指定环境变量,容器中可以使用该环境变量
  • -m, --memory="", 指定容器的内存上限
  • -P, --publish-all=false, 指定容器暴露的端口
  • -p, --publish=[], 指定容器暴露的端口
  • -h, --hostname="", 指定容器的主机名
  • -v, --volume=[], 给容器挂载存储卷,挂载到容器的某个目录
  • --volumes-from=[], 给容器挂载其他容器上的卷,挂载到容器的某个目录
  • --cap-add=[], 添加权限,权限清单详见:http://linux.die.net/man/7/capabilities
  • --cap-drop=[], 删除权限,权限清单详见:http://linux.die.net/man/7/capabilities
  • --cidfile="", 运行容器后,在指定文件中写入容器PID值,一种典型的监控系统用法
  • --cpuset="", 设置容器可以使用哪些CPU,此参数可以用来容器独占CPU
  • --device=[], 添加主机设备给容器,相当于设备直通
  • --dns=[], 指定容器的dns服务器
  • --dns-search=[], 指定容器的dns搜索域名,写入到容器的/etc/resolv.conf文件
  • --entrypoint="", 覆盖image的入口点
  • --env-file=[], 指定环境变量文件,文件格式为每行一个环境变量
  • --expose=[], 指定容器暴露的端口,即修改镜像的暴露端口
  • --link=[], 指定容器间的关联,使用其他容器的IP、env等信息
  • --lxc-conf=[], 指定容器的配置文件,只有在指定--exec-driver=lxc时使用
  • --name="", 指定容器名字,后续可以通过名字进行容器管理,links特性需要使用名字
  • --net="bridge", 容器网络设置:
    • bridge 使用docker daemon指定的网桥
    • host //容器使用主机的网络
    • container:NAME_or_ID >//使用其他容器的网路,共享IP和PORT等网络资源
    • none 容器使用自己的网络(类似--net=bridge),但是不进行配置
  • --privileged=false, 指定容器是否为特权容器,特权容器拥有所有的capabilities
  • --restart="no", 指定容器停止后的重启策略:
    • no:容器退出时不重启
    • on-failure:容器故障退出(返回值非零)时重启
    • always:容器退出时总是重启
  • --rm=false, 指定容器停止后自动删除容器(不支持以docker run -d启动的容器)
  • --sig-proxy=true, 设置由代理接受并处理信号,但是SIGCHLD、SIGSTOP和SIGKILL不能被代理

为容器内的APP安装nginx

通过nginx可以很方便的代理静态站点,已经基于Ubuntu镜像,所以可以通过apt进行各类软件安装

先更新apt的资源,如果源出现问题,可以自行换源

apt-get update

安装nginx

apt-get install -y nginx

此处的过程一般不会出现问题

为容器内的APP安装Vim

因为是通过终端操作,Vim是必须的。

值得注意的是,docker中使用vim只能使用最基本的指令,需要适应

apt-get install -y vim

修改nginx配置

此时,您可以先确定好静态网站内容的存放文件目录,笔者将其托管于Gitlab,并clone到了docker 容器中.

apt-get install git 安装git

通过 whereis 命令找到nginx的安装位置 whereis nginx ,按照惯例:/etc 下存放配置文件,/sbin、/bin下是可执行文件

进入配置文件目录,使用VIM修改 default 文件内容

修改 root 配置,指定为静态网站根目录。

例如:

##
# You should look at the following URL's in order to grasp a solid understanding
# ... 注释内容移除
##

# Default server configuration
#
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    root /usr/local/work/doc/_book;


    server_name _;

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
    }
}

保存。

输入 nginx 启动nginx ,理论上环境变量已完善,若出现问题可自行配置环境变量

输入 ps -ef 可查看进程,确保nginx已经启动

确认端口映射

进入mac的Terminal,可以新建也可以退出docker的交互式容器

按序按ctrl+p,ctrl+q 可退出交互式容器

使用 docker ps 命令查看运行中的容器的端口映射情况。 docker ps -a 可以查看全部容器的情况。

使用 docker port "容器名" 命令可以查看 容器内部端口 到 docker容器在mac上分配的端口

例如,上文中我们将80端口分配给静态网站,称之容器内部端口。而此docker容器运行一个依托于mac真机的虚拟平台上,mac真机为其分配了一个端口。

如果创建容器时没有指定端口或者启动时获取端口失败,docker容器会被分配一个端口,举个例子,可能是67890端口。

此时,通过访问 localhost:67890 已经可以访问到静态站内容

通过端口映射解决

如上文所述,即便创建容器时指定了端口映射关系,也难以保障下次启动容器时还能获得一致的端口。

我们模拟一次docker重启

停止容器运行

docker stop "容器名"

注意,重新启动容器应用命令为:

docker start 容器名

而非 docker run

如果没有配置nginx自动启动,则需要重启nginx,不进入交互式容器可使用命令:

docker exec "容器名" nginx

顺带确认容器中运行的进程:

docker top "容器名"

再次确认端口关系,可能会很不幸,假定已经改变为65432端口。但没有关系,挑一个喜欢的端口,例如 66666

进行端口映射。

题外话,如果是Linux机器,我们可以使用iptables 命令处理转发

有些早期的博客指导修改 /var/lib/docker/containers/[hash_of_the_container]/hostconfig.json 配置转发,但该版本的docker并没有该文件。

通过macOS的端口映射

可参考此博客

但是步骤太复杂了,如果不是系统性的运用转发机制,我这么懒的人一定会采用临时规则方案:

echo "rdr pass proto tcp from any to any port {机器端口} -> 127.0.0.1 port {docker 镜像端口}" | sudo pfctl -Ef -

结合我们假设的设定:

echo "rdr pass proto tcp from any to any port 66666 -> 127.0.0.1 port 65432" | sudo pfctl -Ef -

通过:

sudo pfctl -F all -f /etc/pf.conf

可以删除临时规则

Gitbook or other

在上文内容中,我们已经默认使用了 Gitbook 生成文档静态网站, 使用教程 不再展开。

您也可以选用其他的软件工具生成网站

如果存在通过路由器构建的子网

简单回顾物理拓扑:存在必不可少局域网和一台文档服务器。

在早期的体验阶段,我们选用了个人办公电脑架设文档服务器。

而出于方便办公的目的,我们很有可能自行购买了路有设备,通过路由器接入公司的局域网,将办公电脑、个人电脑、手机等接入路由器构建的子网

此时,其他同事通过公司局域网访问该文档服务器就会 遇到麻烦 ,不过很好解决,基于公司局域网的管理,一般个人分配的IP固定,该场景下该IP对应路由器设备

分配一个路由器的端口作为访问文档服务的端口,通过路由器管理设置将该端口的访问定向转发到 部署文档服务器的机器 的文档服务端口,上文中使用了66666端口

当然,这样处理的前提是路由器按照静态IP分配,否则需要时常维护映射关系。

更新、CI、版本化

文档是需要长期维护的,必然会牵涉到更改、修订、新增。

在上文中,我们将生成的静态网站内容托管于Git,这是较差的做法,但可以在本地处理编译、生成静态网站资源时的 环境问题 而减少服务器的维护。

针对原始文档进行版本控制是必要的。引入健壮的环境维护机制后,在服务端进行静态网站资源编译、生成,是更佳的做法。

通过Git等VCS的帮助,已经拥有健全的版本管理方式,只需要采用相应的实践方式即可,例如参考Git最佳实践拆分发布、维护分支,通过tag维护发布版本

自动获取更新、部署

以上文中的做法为例,此时仅需要 周期性的 获取Git仓库中发布分支的最新内容即可。

如果已经在服务端实施编译、生成,可以编写定时任务脚本进行CI,也可以进一步引入jenkins,增加构建任务,通过web-hook机制可以实现实时部署。

同时获得多个版本

有时我们希望获得 文档、内容片段 在不同版本下的内容,在此方案基础上也容易实现。

针对不同的版本(tag),check-out 并编译、生成至 与版本一一映射 的文件目录,通过Nginx配置的方式即可实现。

此时,需要通过不同的URL访问不同的版本,这对于使用者并不友好,本着 以人为本 的原则,可以进行一次 优化

编写 "扫描" 程序或者脚本,扫出已经CheckOut、并在Nginx配置中有效的版本文件目录,(一般做法是将各个版本的目录规整至同一路径下,将该路径作为站点Root目录) 通过版本(tag)与文件目录名的映射关系、Git插件,取出版本Commit信息。聚合信息后按照模板生成入口页

优化永无止境 ,只要对文档、文档章节建立索引机制,将文档内容解析为AST,"比对文档章节在各个版本下的变化" 将可行,通过定制Gitbook插件的方式, 可将各章节在各个版本下的内容加以聚合、通过Spinner等方式切换阅读。

结语

至此,我们已经完成了基础目标,而文档中提到的技能树,要全部点亮想必也需要一定的时间,尤其是结合 GitMarkdown AST解析模板引擎等内容,自研一套软件用于网页生成。读者朋友们可以丰富自己的业余生活了。