OpenEuler自建私有云表格服务:部署、配置与运维实践

发布时间:2026/9/23 3:58:55
OpenEuler自建私有云表格服务:部署、配置与运维实践
1. 为什么要在 OpenEuler 上自建云表格服务把在线表格跑在自己的服务器上这个念头最早来自一次团队内部的讨论。我们用过不少公有云的在线表格产品功能确实强但数据放在别人机房这件事总让负责合规的同事睡不踏实。尤其是涉及项目排期、成本核算、客户清单这类表格一旦外发就收不回来。于是就有了这个项目在 OpenEuler 上搭一套私有化的云表格服务数据完全落在自己手里局域网内随时访问外网按需开放。OpenEuler 是我选定的底座。它是国内主流的开源服务器操作系统长期支持版本稳定软件源里该有的组件基本都有社区文档也够用。更重要的是它在 ARM 和 x86 上都能跑公司里那批鲲鹏机器正好派上用场。云表格服务这块我最终选的是基于开源电子表格内核的方案前端用浏览器访问后端负责存储和协同整体架构不复杂但坑不少。这篇文章适合谁看如果你手上有 OpenEuler 的服务器想搭一套团队内部用的在线表格又不想依赖外部服务那这篇就是给你写的。我会从系统准备、依赖安装、服务部署、反向代理配置一路讲到权限和排错中间穿插我自己踩过的坑。整个过程不需要你精通 Linux但基本的命令行操作得会。读完你至少能拿到一套可运行的方案运气好的话一次就能跑通。需要提前说明的是云表格服务的选型有很多种我这里的方案偏向轻量、易维护适合几十人以内的小团队。如果你的场景是几百人协同、需要复杂的权限体系那可能需要考虑更重的方案但底层的 OpenEuler 配置思路是相通的。2. 环境准备与系统基础配置2.1 OpenEuler 版本选择与安装要点版本这块我建议直接用 OpenEuler 的 LTS 版本比如 22.03 LTS 或 24.03 LTS。LTS 意味着长期维护软件源稳定不会出现今天装完明天源就失效的情况。我这次用的是 22.03 LTS SP 系列实测下来很稳。安装的时候有几个点要注意分区方案上如果只是跑云表格服务根分区给 50G 足够但数据盘一定要单独挂载因为表格文件和数据库会持续增长混在根分区里后期扩容很麻烦。安装类型选“服务器”即可不需要图形界面。图形界面除了占资源对服务运行没有任何帮助。网络配置在安装阶段就可以设好静态 IP省得装完再改。这里有个细节OpenEuler 安装器里的网络配置和装完之后的配置方式不太一样安装阶段设的静态 IP 有时候在 NetworkManager 接管后会失效所以装完之后我习惯再检查一遍。提示安装时如果勾选了“自动分区”系统可能会用 LVM 把根分区撑满整个磁盘导致你后面想单独加数据盘时空间分配很别扭。建议手动分区或者至少留出一块未分配空间。装完系统第一件事更新软件源并升级现有包sudo dnf makecache sudo dnf update -yOpenEuler 默认的软件源速度还可以如果在内网环境可以换成公司内部的镜像源速度会快很多。升级完重启一次确保内核和关键组件都是最新的。2.2 网络配置的几种方式与选择OpenEuler 的网络管理有好几种方式这也是热词里经常被问到的点。常见的有 NetworkManager 的 nmcli、nmtsui 图形工具以及传统的 network-scripts。22.03 之后默认是 NetworkManager 接管network-scripts 虽然还能用但已经不推荐了。我习惯用 nmcli命令行操作清晰脚本化也方便。查看当前连接nmcli connection show假设你的网卡是 ens33要设静态 IP可以这样操作sudo nmcli connection modify ens33 ipv4.addresses 192.168.1.100/24 sudo nmcli connection modify ens33 ipv4.gateway 192.168.1.1 sudo nmcli connection modify ens33 ipv4.dns 223.5.5.5 114.114.114.114 sudo nmcli connection modify ens33 ipv4.method manual sudo nmcli connection up ens33这里有个坑如果你之前用 DHCP 拿过地址改成 manual 之后一定要把 connection 重新 up 一次否则 IP 不会立即生效。另外 DNS 建议配两个一个主用一个备用避免解析单点故障。如果你更习惯传统方式也可以编辑/etc/sysconfig/network-scripts/ifcfg-ens33把BOOTPROTO改成static然后手动填 IPADDR、GATEWAY、DNS1。改完用systemctl restart NetworkManager生效。两种方式选一种就行别混着用否则容易出现配置冲突。2.3 防火墙与 SELinux 的前期处理云表格服务要对外提供 Web 访问防火墙必须放行对应端口。OpenEuler 默认用的是 firewalld。我一般先看当前开了哪些端口sudo firewall-cmd --list-all假设云表格服务最终跑在 8080 端口反向代理走 80 和 443那就这样放行sudo firewall-cmd --permanent --add-port8080/tcp sudo firewall-cmd --permanent --add-servicehttp sudo firewall-cmd --permanent --add-servicehttps sudo firewall-cmd --reloadSELinux 这块我的建议是不要直接关掉而是用 permissive 模式先跑起来观察有没有拒绝日志确认没问题后再考虑保持 enforcing 或者针对性放行。直接setenforce 0虽然省事但等于放弃了系统的一道安全防线生产环境不推荐。sudo setenforce 0临时改成 permissive 之后用getenforce确认状态。如果后面服务跑起来有权限问题可以用ausearch -m avc -ts recent查看 SELinux 拒绝记录再决定是加策略还是调整文件上下文。3. 依赖组件安装与数据库准备3.1 安装 Docker 与容器运行时云表格服务的部署方式我选的是容器化。原因很简单依赖隔离、升级方便、迁移容易。OpenEuler 上装 Docker 有两条路一条是用系统源里的 docker另一条是加 Docker 官方源。系统源里的版本可能偏旧但对稳定性要求高的场景够用。我这次用的是系统源省得处理源信任问题。sudo dnf install -y docker sudo systemctl enable --now docker装完确认一下版本和运行状态docker version systemctl status docker如果你需要更新的 Docker 版本也可以配置官方源但要注意 OpenEuler 的版本代号和 CentOS 不完全一样直接套用 CentOS 的源地址可能会出问题。稳妥起见系统源自带的版本先用着除非你有明确的版本需求。注意Docker 装完后普通用户默认没有权限操作需要把用户加入 docker 组sudo usermod -aG docker $USER然后重新登录生效。这一步很多人会忘导致后面执行 docker 命令一直报权限错误。3.2 数据库选型与初始化云表格服务的后端需要存表格元数据、用户信息、协同记录数据库这块我选的是 PostgreSQL。相比 MySQLPostgreSQL 在 JSON 字段、并发控制上更符合这类应用的需求而且开源协议更宽松。安装直接用容器跑省得配系统服务docker run -d \ --name cloudsheet-db \ -e POSTGRES_PASSWORDYourStrongPassword \ -e POSTGRES_DBcloudsheet \ -v /data/pgdata:/var/lib/postgresql/data \ -p 5432:5432 \ postgres:15这里有几个关键点。第一POSTGRES_PASSWORD一定要设强密码别用默认的。第二数据目录挂载到宿主机/data/pgdata这样容器删了数据还在。第三端口映射我只映射到本机不对外暴露因为数据库只需要被同机的应用访问暴露到公网是自找麻烦。初始化完成后进容器建一个专用用户docker exec -it cloudsheet-db psql -U postgresCREATE USER sheetuser WITH PASSWORD AnotherStrongPassword; CREATE DATABASE cloudsheet OWNER sheetuser; GRANT ALL PRIVILEGES ON DATABASE cloudsheet TO sheetuser; \q数据库这块的坑主要在字符集和时区。PostgreSQL 容器默认可能是 UTC 时区如果你的表格里有时间字段显示出来会差几个小时。可以在启动容器时加-e TZAsia/Shanghai或者在数据库里设置时区。我一般直接在容器环境变量里解决省得后面改。3.3 反向代理组件 OpenResty 的引入热词里提到了 OpenResty这确实是个好东西。云表格服务本身跑在应用端口上但对外最好统一走 80 或 443这就需要反向代理。Nginx 也能做但 OpenResty 集成了 Lua后面要做限流、鉴权、动态路由会方便很多。OpenEuler 上装 OpenResty可以用官方源也可以用系统源里的 openresty 包。sudo dnf install -y openresty sudo systemctl enable --now openresty装完确认版本openresty -v如果系统源里没有或者版本太旧可以去 OpenResty 官网找对应 OpenEuler 的包。热词里提到的openresty1.11.2.5-1 for openeuler 24就是一个具体版本说明社区已经在做适配了。装的时候注意依赖pcre、openssl、zlib这几个开发库要提前装好否则编译或者运行时会报缺库。反向代理的配置我放到后面单独讲这里先把组件备齐。到这一步系统层面的准备基本完成OpenEuler 跑起来了网络通了防火墙放行了Docker 和数据库就绪OpenResty 也装好了。接下来就是云表格服务本体的部署。4. 云表格服务部署与核心配置4.1 服务镜像获取与容器启动云表格服务的镜像我用的是社区维护的开源方案。获取方式有两种一种是直接拉现成镜像另一种是自己构建。现成镜像省事但版本可能不是最新的自己构建可控但需要处理依赖。我建议先用现成镜像跑通再考虑定制。docker pull cloudsheet/cloudsheet:latest拉下来之后先别急着跑用docker inspect看看镜像暴露的端口和环境变量要求docker inspect cloudsheet/cloudsheet:latest | grep -A 20 Env这一步很多人跳过结果启动时报缺环境变量。看清楚需要哪些参数再写启动命令。我的启动命令大概长这样docker run -d \ --name cloudsheet-app \ -e DB_HOST172.17.0.1 \ -e DB_PORT5432 \ -e DB_NAMEcloudsheet \ -e DB_USERsheetuser \ -e DB_PASSWORDAnotherStrongPassword \ -e APP_URLhttp://sheet.example.com \ -v /data/cloudsheet/uploads:/app/uploads \ -p 8080:8080 \ cloudsheet/cloudsheet:latest这里DB_HOST用的是172.17.0.1这是 Docker 默认网桥的宿主机地址。因为数据库跑在另一个容器里应用容器要访问它走宿主机地址是最直接的方式。当然你也可以建自定义网络把两个容器放同一个网络里用容器名互访那样更优雅。我图省事先用宿主机地址实测也能跑。APP_URL这个变量很重要它决定了表格里生成的分享链接、回调地址长什么样。如果你后面配了域名这里一定要改成域名否则分享出去的链接还是 IP 加端口体验很差。4.2 数据库连接与初始化脚本执行应用容器起来之后第一次访问会触发数据库初始化。但有些方案需要手动执行迁移脚本。进应用容器看看docker exec -it cloudsheet-app sh如果里面有migrate或者init-db之类的命令就执行一下。执行前确认数据库连接参数没问题否则会报连接超时。我遇到过一种情况数据库容器起来了但应用容器启动太快连数据库时对方还没准备好导致初始化失败。解决办法是给应用容器加个重启策略或者手动重启一次。docker restart cloudsheet-app初始化完成后用docker logs看日志确认没有报错docker logs -f cloudsheet-app日志里如果出现Database connected、Migration completed这类字样基本就稳了。如果一直报连接拒绝检查数据库容器的端口映射和防火墙规则。注意容器之间的通信不走宿主机的 firewalld但走宿主机地址访问时如果 firewalld 拦了 5432也会连不上。我一般把数据库端口只绑定到127.0.0.1然后应用容器通过宿主机地址访问这样既安全又不会受防火墙影响。4.3 应用端口与访问验证应用跑在 8080 端口先在服务器本机验证curl -I http://127.0.0.1:8080如果返回 200 或者 302说明服务起来了。然后用浏览器访问http://服务器IP:8080应该能看到登录页或者初始化向导。第一次访问通常会让你创建管理员账号这个账号密码一定要记牢后面改起来麻烦。如果访问不了按这个顺序排查先看容器是否在运行docker ps再看端口是否监听ss -tlnp | grep 8080然后看防火墙是否放行最后看 SELinux 有没有拦截。这四步走完九成的问题都能定位。提示有些云表格方案默认只监听 127.0.0.1容器里如果也是这个配置那从外部访问就会失败。检查应用配置文件里的bind或host参数确保是0.0.0.0。到这一步云表格服务已经能通过 IP 加端口访问了。但这只是能用离好用还差一步反向代理和域名。接下来处理这块。5. 反向代理与访问入口优化5.1 OpenResty 反向代理配置详解用 OpenResty 做反向代理核心就是一段server配置。我把它放在/usr/local/openresty/nginx/conf/conf.d/cloudsheet.confserver { listen 80; server_name sheet.example.com; client_max_body_size 100m; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300s; proxy_send_timeout 300s; } location /uploads/ { alias /data/cloudsheet/uploads/; expires 7d; } }几个关键点解释一下。client_max_body_size默认是 1m表格里如果带图片或者附件很容易超我设成 100m。proxy_read_timeout和proxy_send_timeout默认 60s大表格导出或者协同操作时可能超时设成 300s 更稳妥。X-Forwarded-*这几个头必须带否则应用拿不到真实客户端 IP日志里全是 127.0.0.1排查问题很痛苦。/uploads/这个 location 是可选的如果应用本身能处理静态文件可以不加。但让 OpenResty 直接处理静态文件性能会好很多尤其是多人同时预览表格附件的时候。配置写完先测试语法sudo openresty -t没问题再 reloadsudo systemctl reload openresty5.2 域名解析与 HTTPS 配置域名这块内网用的话可以在内网 DNS 里加一条 A 记录指向服务器 IP。外网用的话正常做解析就行。HTTPS 我强烈建议配上哪怕是内网。原因有两个一是浏览器对非 HTTPS 站点越来越不友好二是表格数据在传输过程中加密心里踏实。证书可以用 Lets Encrypt 免费申请也可以用公司内部 CA 签。OpenResty 配 HTTPS 的配置大概这样server { listen 443 ssl http2; server_name sheet.example.com; ssl_certificate /etc/openresty/certs/sheet.crt; ssl_certificate_key /etc/openresty/certs/sheet.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }配完记得把 80 端口的访问重定向到 443server { listen 80; server_name sheet.example.com; return 301 https://$host$request_uri; }这样用户不管输 http 还是 https最终都走加密通道。证书续期如果用 Lets Encrypt可以配个 cron 任务自动续省得三个月后忘了导致站点打不开。5.3 访问入口的权限控制思路云表格服务虽然是自己人用但入口也不能完全裸奔。我的做法是在 OpenResty 层面加一层基础认证作为第一道门槛。配置如下location / { auth_basic Restricted; auth_basic_user_file /etc/openresty/htpasswd; proxy_pass http://127.0.0.1:8080; ... }htpasswd文件用htpasswd命令生成sudo htpasswd -c /etc/openresty/htpasswd admin这样访问时会先弹一个用户名密码框过了这关才到应用的登录页。对于内网服务来说这层防护足够挡住大部分扫描和误访问。当然如果你觉得多一层登录太麻烦也可以只在特定路径上加比如管理后台。注意基础认证的密码是明文传输的在 HTTPS 下是加密的所以一定要配合 HTTPS 使用。纯 HTTP 下加基础认证密码等于裸奔。到这一步用户可以通过https://sheet.example.com访问云表格服务了有域名、有证书、有基础防护。接下来处理一些运维层面的细节。6. 权限、存储与日常运维6.1 文件权限与目录规划OpenEuler 下跑容器文件权限是个绕不开的话题。容器里的进程通常以非 root 用户运行但挂载出来的目录如果属主不对就会报写入失败。我的做法是先在宿主机上把目录建好属主设成容器内运行的用户 UID。sudo mkdir -p /data/cloudsheet/uploads sudo chown -R 1000:1000 /data/cloudsheet/uploads sudo chmod -R 755 /data/cloudsheet/uploads这里的 1000 是容器内用户的 UID具体是多少要看镜像的 Dockerfile。可以用docker exec cloudsheet-app id查看。如果 UID 对不上容器写入时就会报 Permission denied。这个坑我踩过好几次后来养成习惯挂载目录之前先确认 UID。数据库目录/data/pgdata也一样属主必须是 PostgreSQL 容器内的用户通常是 999。设错了数据库直接起不来日志里会明确说权限问题。6.2 数据备份策略与实操自建服务最怕的就是数据丢。云表格里存的是团队的工作成果丢了没法交代。我的备份策略分两层数据库每天全量备份上传文件每周增量备份。数据库备份用pg_dumpdocker exec cloudsheet-db pg_dump -U sheetuser cloudsheet /data/backup/cloudsheet_$(date %Y%m%d).sql写成 cron 任务每天凌晨跑一次0 2 * * * docker exec cloudsheet-db pg_dump -U sheetuser cloudsheet /data/backup/cloudsheet_$(date \%Y\%m\%d).sql注意 cron 里的%要转义否则会被当成换行。备份文件保留最近 30 天用find清理旧的find /data/backup -name cloudsheet_*.sql -mtime 30 -delete上传文件目录用tar打包备份每周一次。备份文件最好再同步到另一台机器或者对象存储单机备份等于没备份磁盘一坏全完。6.3 日志查看与资源监控日常运维离不开看日志。应用日志、OpenResty 日志、数据库日志三个地方都要关注。应用日志用docker logs看OpenResty 日志在/usr/local/openresty/nginx/logs/下数据库日志在容器里。资源监控我用的比较简单htop看 CPU 和内存df -h看磁盘docker stats看容器资源占用。如果发现某个容器内存一直涨可能是内存泄漏需要重启或者升级版本。docker stats --no-stream这个命令能一次性列出所有容器的资源占用适合快速巡检。如果要做长期监控可以上 Prometheus 加 Grafana但对小团队来说有点重我一般先用脚本定期采集存到文件里出问题时翻记录。7. 常见问题排查与避坑经验7.1 服务启动失败排查速查表现象可能原因排查方法解决方式容器启动后立即退出环境变量缺失或数据库连不上docker logs 容器名补全环境变量确认数据库可达访问 8080 无响应应用未监听 0.0.0.0ss -tlnp看监听地址改配置为 0.0.0.0 或检查端口映射502 Bad Gateway反向代理后端不可达看 OpenResty error.log确认应用容器运行检查 proxy_pass 地址上传文件失败目录权限不对ls -l看属主调整宿主机目录属主为容器用户 UID数据库连接超时防火墙或端口未映射telnet 数据库IP 端口放行端口或改用容器网络互访页面样式错乱静态资源路径不对浏览器控制台看 404检查 APP_URL 和反向代理配置这张表是我自己遇到问题后整理的基本覆盖了八成以上的启动和访问故障。遇到问题先查表能省不少时间。7.2 数据库连接与字符集问题数据库这块最常见的两个问题连不上和乱码。连不上前面说了重点说乱码。PostgreSQL 默认字符集可能是 SQL_ASCII存中文会出问题。建库的时候要指定 UTF8CREATE DATABASE cloudsheet WITH ENCODING UTF8 LC_COLLATEzh_CN.UTF-8 LC_CTYPEzh_CN.UTF-8 OWNER sheetuser;如果库已经建好了改字符集比较麻烦建议删了重建。另外应用连接数据库时连接串里最好也带上字符集参数双保险。时区问题也常见。表格里填了“2024-01-01 10:00”存进去变成“2024-01-01 02:00”就是时区没对齐。解决办法是在数据库容器启动时设TZAsia/Shanghai应用容器也设同样的时区两边一致就不会差。7.3 反向代理超时与大文件上传反向代理超时和上传限制是云表格服务的高频问题。用户传一个 50M 的表格转半天最后报错体验极差。除了前面说的client_max_body_size和proxy_read_timeout还有一个参数容易忽略proxy_request_buffering。默认是 on意思是 OpenResty 先把整个请求体收完再转发给后端。大文件上传时这会占用大量磁盘和内存。设成 off 可以让请求体边收边转减轻代理压力。proxy_request_buffering off;但设成 off 之后后端要能处理流式请求否则可能出问题。我实测下来大部分云表格应用都能处理设了之后大文件上传明显顺畅。7.4 容器时间与宿主机不同步容器时间不同步这个问题很隐蔽。表现是日志时间对不上或者表格里的时间戳偏差。原因是容器默认用 UTC宿主机用 CST。解决办法很简单启动容器时挂载宿主机的时区文件-v /etc/localtime:/etc/localtime:ro或者设环境变量TZAsia/Shanghai。两种方式都行我一般两个都做确保万无一失。这个坑不解决后面排查问题时会被日志时间误导浪费大量精力。8. 性能调优与扩展思路8.1 数据库连接池与缓存配置云表格服务在多人同时编辑时数据库连接数会飙升。默认的连接池可能只有 10 个不够用。应用层面一般有连接池配置比如DB_POOL_SIZE之类的环境变量根据团队人数调整。几十人的团队设成 20 到 30 比较合适。数据库本身也要调。PostgreSQL 的max_connections默认 100如果应用连接池设大了可能把数据库连接占满。可以在数据库容器启动时加参数docker run -d ... postgres:15 -c max_connections200另外shared_buffers和work_mem也可以根据服务器内存调整。内存 8G 的机器shared_buffers设 2Gwork_mem设 16M基本够用。调这些参数前最好查一下官方文档别拍脑袋设。8.2 静态资源分离与 CDN 思路如果团队分布在不同地区访问速度可能是个问题。静态资源比如 JS、CSS、图片可以分离出来放到对象存储或者 CDN 上。OpenResty 配置里把/static/路径指向 CDN 地址应用只处理动态请求。这样能显著降低服务器压力提升加载速度。不过对小团队来说这一步不是必须的。内网访问的话静态资源走本机反而更快。等团队规模上来了或者有外网访问需求再考虑分离。8.3 多实例部署与负载均衡单实例跑久了总会遇到重启更新的需求。这时候服务会中断用户体验不好。解决办法是跑两个实例OpenResty 做负载均衡upstream cloudsheet { server 127.0.0.1:8080; server 127.0.0.1:8081; } location / { proxy_pass http://cloudsheet; ... }两个实例共享同一个数据库和上传目录更新时逐个重启用户几乎无感知。但要注意多实例部署对应用的会话管理有要求如果应用把 session 存在本地内存里多实例会出问题。需要确认应用支持共享 session或者用 sticky session。这块我还在摸索目前单实例够用等有需求再折腾。9. 我个人的实操体会这套云表格服务从第一次跑通到现在稳定运行前后折腾了大概两周。最大的感受是OpenEuler 作为底座确实省心软件源全社区活跃遇到问题搜一下基本都有答案。容器化部署让升级和迁移变得简单但也引入了权限、网络、时区这些新的坑每一个都得踩过才知道。如果让我给后来者一句建议那就是先把单机跑通再考虑优化。我一开始就想上多实例、上监控、上自动化备份结果基础环境没弄利索反而浪费了更多时间。后来退回来老老实实把系统、网络、数据库、应用一层层跑通再逐步加东西反而快了很多。还有一点备份一定要做而且一定要验证恢复。我见过太多人备份文件存了一堆真出事的时候发现恢复不了。定期拿备份文件在测试环境恢复一次确认流程走得通这比备份本身更重要。最后分享一个小技巧OpenEuler 的man命令很好用遇到不熟悉的命令man一下比搜网页快。比如man nmcli、man firewall-cmd官方文档永远是最准的。热词里提到的openeuler man命令确实值得花时间熟悉。