阿里云部署DSH与RAM完成运维

我只做了两件事:部署 DSH、开通 RAM,剩下的全交给它

这台阿里云 ECS 上现在跑着两个独立站点、两个域名、一张自动续期的证书、一套每日备份。

其中只有两件事是我亲手做的:把 DeepSeek Harness 装起来,以及在阿里云上给它开一个 RAM 身份。剩下的------建库、部署项目、开二级域名、配证书、排查故障------我都是"说了一句话"。

左边是 DSH 本体,右边是它部署出来的项目。同一台机器:2 vCPU / 1.8 GiB / 40 GiB。


零、先把分工说清楚

阶段 谁做 内容
准备 🧑 人 部署 DSH、开通 RAM
执行 🤖 DSH 建库、部署项目、开二级域名、扩证书、排查故障、加备份
决策 🧑 人 授权范围、破坏性操作确认、密钥轮换

为什么是这两件事必须人做?

  • 部署 DSH:鸡生蛋问题。Agent 还没跑起来,只能人装。
  • 开通 RAM:授权不能自己给自己。这是唯一一处"必须由更高权限的人点头"的地方。

这两件事做完,交棒。


阶段一 · 人只做两件事

一、第一件:把 DSH 装起来

1.1 装

DeepSeek Harness 是一个本地运行的 AI Agent Web UI,默认只监听 127.0.0.1。

bash 复制代码
npx --yes @deepseek-ai/dsh web --no-open

启动后会打印一个带 token 的地址:

ini 复制代码
dsh web: http://127.0.0.1:3080/?token=xxxxxxxx

这台机器只有 1.8 GiB 内存,装之前先加 swap,否则后面编译前端大概率 OOM:

bash 复制代码
fallocate -l 2G /swapfile && chmod 600 /swapfile
mkswap /swapfile && swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

1.2 用 systemd 托管

前台跑不是长久之计。写一个 unit:

ini 复制代码
# /etc/systemd/system/dsh-web.service
[Service]
Type=simple
WorkingDirectory=/root/dsh-workspace
ExecStart=/usr/bin/node .../.bin/dsh web \
  --no-open \
  --public-url https://chenjiaxin.me \
  --trusted-host chenjiaxin.me
Restart=always
RestartSec=5
Environment=NODE_ENV=production

两个参数不能省:

  • --public-url:告诉它对外地址,否则生成的链接是 127.0.0.1
  • --trusted-host:把域名加入 /api 的信任白名单,否则浏览器请求会被拒

1.3 nginx 反代 + HTTPS

服务只监听本机回环,对外统一走 nginx:

nginx 复制代码
server {
    listen 443 ssl;
    server_name chenjiaxin.me;

    ssl_certificate     /etc/letsencrypt/live/chenjiaxin.me/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/chenjiaxin.me/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3080;
        proxy_http_version 1.1;
        proxy_set_header Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

证书用 certbot 的 webroot 方式签发,不中断服务:

bash 复制代码
certbot certonly --webroot -w /var/www/certbot \
  -d chenjiaxin.me -d www.chenjiaxin.me \
  --non-interactive --agree-tos

1.4 换一把锁:从 token 到密码

DSH 默认用一次性 token 登录,每次重启都变。

对人来说这是麻烦;对 Agent 来说这是致命------你没法让它"告诉我新 token 是多少"再继续。

好在 DSH 的 cookie 签名密钥是持久的(在 ~/.dsh/.credentials.yaml),所以做法是:做一个密码网关,验签通过后用这个密钥签一个会话 cookie。

nginx 侧只需要两件事:/__login 转发给网关,把 DSH 返回的 401 转成跳登录页。

nginx 复制代码
proxy_intercept_errors on;      # 关键:默认是 off,不打开拦不到上游 401
error_page 401 = @dsh_login;

location @dsh_login {
    if ($http_accept ~* text/html) { return 302 /__login; }
    return 401;
}

这一步的意义不只是"好登录":它把门禁从"人记 token"变成了系统可管理的状态。后面让 Agent 演示站点,直接给地址就行。

二、第二件:在阿里云给 Agent 开身份

2.1 建 RAM 用户

RAM 控制台 → 用户 → 创建用户。名字里写清用途,半年后回来看不会懵。

一个用途一个身份:ai-dns-manager 是给 Agent 用的,chejx-oss 是另一个用途。这样出了问题能精确定位是谁干的。

2.2 只授一个域名

策略的关键是 Resource 收窄到一个具体域名,而不是 *:

json 复制代码
{
  "Version": "1",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "alidns:DescribeDomainRecords",
      "alidns:AddDomainRecord",
      "alidns:UpdateDomainRecord",
      "alidns:DeleteDomainRecord",
      "alidns:SetDomainRecordStatus"
    ],
    "Resource": "acs:alidns:*:<账号ID>:domain/chenjiaxin.me"
  }]
}

2.3 把 AccessKey 配进 CLI

Agent 不直接持有密钥字符串,而是通过 CLI 读配置:

bash 复制代码
aliyun configure
# 依次输入 AccessKey ID / Secret / 区域 cn-beijing / 输出 json

不要把 AccessKey 写进 prompt 或者代码里。 让工具去读配置,模型永远看不到明文。

2.4 验证边界(重要)

配好之后先做一件事:验证它能做什么,更要验证它不能做什么。

bash 复制代码
aliyun sts GetCallerIdentity
# → Arn: acs:ram::...:user/ai-dns-manager   ✅ 确认是子账号,不是主账号

aliyun alidns DescribeDomainRecords --DomainName chenjiaxin.me
# → 200   ✅ 能查

aliyun alidns DescribeDomains
# → Forbidden.RAM   ✅ 不能列全部域名

aliyun vpc DescribeVpcs
# → Forbidden.RAM   ✅ 碰不到网络资源

这就是交给 Agent 时的能力边界表:

能力 状态
查询指定域名解析 ✅
增删改该域名记录 ✅
列举账号全部域名 ❌
操作 VPC / SLB / ECS ❌

阶段二 · 交棒,剩下的全给它

到这里我停手了。下面是"我说了什么"和"它做了什么"。

三、开二级域名

创建一个二级域名 my.chenjiaxin.me,指向这台服务器

它执行的步骤:

bash 复制代码
# 1. 从元数据服务确认公网 IP,不靠猜
curl -s -H "Metadata-Version: 2.0" \
  http://100.100.200.200/latest/meta-data/eipv4

# 2. 先查重(避免重复记录)
aliyun alidns DescribeDomainRecords \
  --DomainName chenjiaxin.me --RRKeyWord my

# 3. 创建
aliyun alidns AddDomainRecord \
  --DomainName chenjiaxin.me --RR my --Type A \
  --Value 8.152.214.54 --TTL 600

能看出 Agent 和脚本的差别:它会先查重、再创建、最后验证解析。不需要你告诉它"要先查重"。

⚠️ 一个真实细节:改域名不是"重命名",是删除 + 新建 ------因为阿里云的 RecordId 不跟着域名走。它主动说明了这点,而不是假装改了个名字。

四、部署整个项目

把 my-agent 项目部署起来,部署后用 my.chenjiaxin.me 访问

它自己判断并完成了这些:

1. 发现环境不符合文档 项目自带 docker-compose.yml,但这台机器没装 Docker,80/443 也已被占用。它没有硬套文档,改成原生部署。

2. 装 MySQL、建库、创建专用账号(不用 root)

3. 建表 ------跑项目自带的 sync-schema:

4. systemd 托管后端 ,Restart=always

5. 构建前端,产物交给 nginx 托管

6. 配 nginx 新站点 :SPA 路由 + /api 反代 + SSE 关缓冲(不关的话回答会攒着一起吐)

7. 扩容证书,把新域名加进同一张:

bash 复制代码
certbot certonly --webroot -w /var/www/certbot \
  --cert-name chenjiaxin.me \
  -d chenjiaxin.me -d www.chenjiaxin.me -d my.chenjiaxin.me \
  --expand --non-interactive --agree-tos

到这一步,两个站点同机共存,共用一张证书、一套 nginx、一份备份策略。

五、它自己发现的问题

这是我认为最有价值的一段。

某个外部服务调用一直失败,报 Connection error,重试 5 次全挂。但同样的地址,curl 是通的。

指令只有一句:

查一下为什么不通,修好

它没有停在"配置写错了"的猜测上,而是对比了不同调用方式:

复制代码
curl 访问             → 200
Node 强制 IPv4        → 200
Node 默认(自动选择) → ETIMEDOUT

结论:这台 ECS 只有 link-local 地址,没有 IPv6 出口 ,而目标域名同时有 A 和 AAAA 记录。Node 的地址族自动选择会去试 IPv6 然后卡到超时,而不是快速失败回落。curl 有自己的回落逻辑,所以命令行一直正常------这就是"命令行能通、服务不行"的全部原因。

修复一行:

ini 复制代码
Environment=NODE_OPTIONS=--no-network-family-autoselection

再做一步收尾,从系统层关掉 IPv6,让所有运行时都受益:

bash 复制代码
# /etc/sysctl.d/99-zz-disable-ipv6.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

效果是:IPv6 连接的失败时间从"数秒超时"降到 22 毫秒------从"卡住"变成"立刻放弃、回落 IPv4"。

这个排查路径不在任何文档里,也不在我部署前能预想到的清单上。

六、顺手加的保险

这些也是它做的,但值得单独说,因为让 AI 操作生产环境,光有边界不够,还要有兜底。

1. 每日备份,保留 7 天

bash 复制代码
mysqldump --single-transaction --quick --routines --triggers \
  --default-character-set=utf8mb4 my_agent | gzip -9 > "$FILE"
find "$BACKUP_DIR" -name 'my_agent-*.sql.gz' -mtime +6 -delete

--single-transaction 保证备份期间不锁表 。systemd timer 每天跑,Persistent=true 让关机错过的任务开机补跑。

2. 并发写加版本号

项目里"保存"接口是整份覆盖,没有版本校验。它主动加了乐观锁:

ts 复制代码
if (currentRev && resume.rev !== currentRev) throw new ResumeConflictError()
resume.rev = randomUUID()

冲突时返回 409 让用户刷新,而不是静默覆盖。

这条原则值得推广 :任何"整份覆盖"的写接口都该有版本号------不是为了防恶意,是防并发和误操作,哪怕操作者是 AI。

3. 端口收口

后端默认监听 0.0.0.0:3001,等于绕过反代暴露明文 HTTP。加一条规则收死:

bash 复制代码
iptables -I INPUT 1 -p tcp --dport 3001 ! -i lo -j DROP

阶段三 · 复盘

七、这套模式真实的边界

用下来几天,说几句实话。

Agent 做得比人好的

场景 原因
"现象清楚、路径不明"的排查 一次读完日志、配置、进程状态,不遗漏
跨系统的操作 nginx + systemd + MySQL + DNS,切换成本为零
重复性配置调整 不会因为疲劳漏掉某个 reload
把过程写成记录 它本来就在记录每一步

人仍然必须管的

1. 授权范围。 RAM 策略给多大、密钥多久轮换------这个判断不能交出去。Agent 只会用你给的权限,不会替你决定该给多少。

2. 破坏性操作的确认。 删库、改安全组、动生产流量必须有人点头。这次的规则很简单:读操作随便做,写操作先说明再执行。

3. 密钥管理。 这次出过一个小事故------排查时我把一段配置打印到了终端,密钥落进了日志。后来立刻轮换了。Agent 不会主动帮你防范这种事,你得自己盯着。

4. 判断"什么该做"。 它能帮你把事做对,不能帮你决定做什么。

八、小结

这次真正有价值的不是"AI 会敲命令",而是把运维的上下文沉淀下来了:

  • 权限边界写在 RAM 策略里,不写在谁的记忆里
  • 部署步骤写在 systemd 和 nginx 配置里,不写在聊天记录里
  • 排查过程写在提交记录里,下次遇到同类问题能捞出来

而"部署"这件事------它恰恰是最容易被替代的部分。

真正稀缺的是"发现问题、解释清楚发生了什么、然后修好",这正好是 Agent 擅长的。


想试的话,建议这样起步:

  1. 部署 DSH(本文第一节)
  2. 开一个只读 的 RAM 用户(只给 Describe*)
  3. 让它帮你盘一遍现有资源,看它的判断靠不靠谱
  4. 觉得行,再逐步放开写权限

从只读到能写,这个过渡比一步到位安全得多。


环境:Alibaba Cloud Linux 3 / 2 vCPU 1.8 GiB / nginx 1.24 / MySQL 8.0 / Node 24 / DeepSeek Harness

相关推荐
aurora_zjj6 分钟前
一次持续 3 年的 OpenResty 死循环排查:最终定位到 LuaJIT ARM64 的一条汇编指令
后端
智驭未来掌门人8 分钟前
动态平台雷达目标引导系统设计与实现
后端
Bazingga11 分钟前
一次Agent超时熔断设计踩过的坑
后端
Lyy12 分钟前
DevOps平台 — 第十九篇:消息通知与预警的设计与实现
后端·devops·vibecoding
用户4993350250716 分钟前
JS 作用域劫持的死穴:动态 this 穿透,以及 AST 怎么救
前端
IT_陈寒1 小时前
React的setState真是个演员,你以为它同步其实在装睡
前端·人工智能·后端
ServBay2 小时前
Claude Fable 5.5 测试曝光,但它在悄悄给你打分
后端·ai编程·claude
孟健3 小时前
Claude Code 同需求写两版对比:装不装 frontend-design 差多远?
前端·ai编程·claude
IT规划师3 小时前
一文讲透五种发布机制:滚动、蓝绿、金丝雀、红黑与 AB
后端·面试