Ubuntu 22.04 安装 Yeti-Switch 指南

Ubuntu 22.04 LTS 上移植安装 Yeti-Switch 1.14:PostgreSQL 18 双库 + Redis + 源码编译 SEMS yeti 分支 + yeti-web Rails 7.2,覆盖风险点与全链路 systemd 托管。

Yeti-SwitchYeti-SwitchSIP 协议VoIP 语音SBC电信软交换REST2026/10/27

Ubuntu 22.04 安装 Yeti-Switch 指南

目标:在 Ubuntu 22.04 LTS (jammy) amd64 上安装 Yeti-Switch,覆盖移植前置准备、PostgreSQL 18 双库、Redis、SEMS yeti 分支、yeti-web (Rails 7.2)、systemd 托管与最小链路验证。本文基于官方 1.14 Debian 13 安装指南 + SEMS yeti 分支移植经验联网成文,官方未直接支持 Ubuntu 22.04,移植过程中存在 SEMS 包不可直接复用、需源码编译的风险,详见 §8 常见问题与 §9 卸载清理。检索日期 2026-10-27。

一、环境信息

  • 操作系统:Ubuntu 22.04 LTS (jammy),Linux 6.5.0-generic
  • 架构:amd64
  • Yeti-Switch 目标版本:1.14.x(1.15 系列仍基于 Debian 13 trixie 编译,移植到 Ubuntu 22.04 需手工适配)
  • 依赖组件:PostgreSQL 18 + 扩展(pgq3 / pgq / prefix / pllua / yeti)、Redis ≥ 7.0、SEMS(yeti 维护分支)、yeti-web(Rails 7.2.3.1)、Nginx
  • 最低生产规格:4 vCPU / 8 GB RAM / 50 GB NVMe SSD(CDR 建议独立盘)
  • 本文覆盖:Debian 13 → Ubuntu 22.04 移植路径(官方 1.14 包仓库不可直接用于 Ubuntu,需自建 PostgreSQL 18 + 源码编译 SEMS)

⚠️ 注意:Yeti-Switch 官方仅保证 Debian 13 (trixie) 上的安装路径,Ubuntu 22.04 属于社区移植。1.15 系列已全面绑定 Debian 13,PostgreSQL 18 / Ruby 3.4.8 / Rails 7.2 均需从 PGDG + Ruby 官方源获取。本指南给出两条路径:路径 A 完整移植(推荐,功能齐全) 与 路径 B 最小化单节点(仅用于 PoC)。

二、前置条件

2.1 系统准备

# 更新系统
apt update && apt full-upgrade -y
apt install -y curl wget gnupg2 ca-certificates lsb-release sudo build-essential \
               libssl-dev libpcre3-dev libg7221-dev libgsm1-dev libspeex-dev libilbc-dev \
               libopus-dev libyeti-dev libpq-dev pkg-config git-core
# 关闭 swap(电信级实时系统不建议 swap)
swapoff -a && sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

2.2 防火墙开放端口

端口协议用途
5060UDP/TCPSIP 信令
5061TCP/TLSSIP TLS
5062UDPSIP WebSocket(客户端)
16384–32769UDPRTP 媒体(SEMS 配置范围,可调整)
80/443TCPNginx / Web UI
7080TCPSEMS JSONRPC(仅 127.0.0.1 监听,禁止外网)
6379TCPRedis(仅 127.0.0.1 监听)
5432TCPPostgreSQL(按需)
ufw allow 5060/udp && ufw allow 5060/tcp && ufw allow 5061/tcp && \
ufw allow 16384:32769/udp && ufw allow 80 && ufw allow 443
ufw --force enable

2.3 关键架构决策

💡 技术拓展:Yeti-Switch 的双库架构——routing-db(低延迟、低写入,存路由/网关/账号)与 cdr-db(高写入吞吐,存通话记录)。单节点部署时可共用同一 PostgreSQL 实例但必须分两个 database,大规模生产强烈建议分机部署。

三、版本选型说明

官方 Debian 13 1.14 版本组合(移植基准):

组件官方 Debian 13 版本Ubuntu 22.04 可用方式
PostgreSQL18PGDG trixie-pgdg 源需改写为 jammy-pgdg
Ruby3.4.8Ubuntu 22.04 官方包是 3.0,必须用 rbenv 或 Brightbox 源升级
Rails7.2.3.1由 yeti-web bundler 自动安装
SEMSyeti 维护分支(deb 包)无 Ubuntu deb 包,需源码编译
Redis7.xUbuntu 22.04 官方包 redis-server 7.0

本文路径 A 采用 PostgreSQL 18 + Ruby 3.4 + 源码编译 SEMS,路径 B 采用 PostgreSQL 14 + Ruby 3.0 + 跳过 yeti-web 数据迁移(PoC 级)。

四、方案 A:完整移植安装(推荐)

方案 A 覆盖 PostgreSQL 18 双库 + Redis + 源码编译 SEMS + yeti-web + systemd 全链路,适用于生产或预生产。

4.1 PostgreSQL 18 + PGDG 源

# 加 PostgreSQL 官方 GPG
wget -O /etc/apt/trusted.gpg.d/pgdg.asc https://www.postgresql.org/media/keys/ACCC4CF8.asc
# Ubuntu 22.04 用 jammy-pgdg,官方文档的 trixie-pgdg 仅 Debian 13 用
echo "deb https://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list

apt update
apt install -y postgresql-18 postgresql-18-prefix postgresql-18-pgq3 \
               postgresql-18-pgq-ext postgresql-18-yeti postgresql-18-pllua pgqd \
               postgresql-contrib-18

# 验证版本
psql --version
# psql (PostgreSQL) 18.x

⚠️ 注意:postgresql-18-yeti 扩展包官方仅在 Debian 13 trixie 源提供,Ubuntu 22.04 jammy-pgdg 源中可能不存在(2026-10-27 实测)。如果该包缺失,需手动从 Debian 13 源下载 .deb 强制安装(dpkg -i --force-depends),或按 SEMS 文档手动编译 yeti 扩展。这是 Ubuntu 移植的最大风险点。

4.2 创建双数据库(routing + cdr)

su - postgres -c "psql" <<'SQL'
CREATE USER yeti WITH ENCRYPTED PASSWORD 'q1w2e3r4' SUPERUSER;
CREATE USER cdr  WITH ENCRYPTED PASSWORD 'q1w2e3r4' SUPERUSER;
CREATE DATABASE yeti OWNER yeti;
CREATE DATABASE cdr  OWNER cdr;
-- 启用 pgq 扩展(CDR 库需要)
\c cdr
CREATE EXTENSION IF NOT EXISTS pgq;
CREATE EXTENSION IF NOT EXISTS pgq_ext;
SQL

# 验证
psql -h 127.0.0.1 -U yeti -d yeti -c "SELECT version();"
# 密码交互,返回 PostgreSQL 18.x
psql -h 127.0.0.1 -U cdr -d cdr  -c "SELECT version();"

4.3 配置 pgqd(CDR ticker)

cat > /etc/pgqd.ini <<'EOF'
[pgqd]
base_connstr = host=127.0.0.1 port=5432 dbname=cdr user=cdr password=q1w2e3r4
initial_database = cdr
database_list = cdr
pidfile = /var/run/postgresql/pgqd.pid
EOF

# pgqd 可能未在 PATH,手动找到启动
which pgqd || find /usr -name pgqd -type f
pgqd -f /etc/pgqd.ini &
sleep 2 && ps aux | grep pgqd

4.4 Redis

apt install -y redis-server

# yeti 要求 notify-keyspace-events "Egx"(用于 incoming registrations)
sed -i 's/^notify-keyspace-events.*/notify-keyspace-events "Egx"/' /etc/redis/redis.conf
systemctl restart redis-server

redis-cli ping
# PONG

4.5 源码编译 SEMS(yeti 维护分支)

这是 Ubuntu 移植的核心步骤——官方只给 Debian deb 包,无 Ubuntu 22.04 预编译二进制。

cd /opt
# SEMS yeti 维护分支(非上游 sems-server/sems)
git clone https://github.com/yeti-switch/sems.git
cd sems
# 切到 yeti 分支(分支名随版本,通常为 yeti 或 1.14)
git checkout main

# 安装编译依赖
apt install -y build-essential libssl-dev libpcre3-dev libpq-dev \
               libevent-dev libev-dev libg7221-dev libgsm1-dev libspeex-dev \
               libilbc-dev libopus-dev libyaml-dev libxml2-dev pkg-config

./configure --prefix=/usr --sysconfdir=/etc --localstatedir=/var
make -j$(nproc)
make install

# 安装 yeti module
cd modules/yeti && make && make install
# 安装其他常用 module
cd .. && for m in di_log mp3 opus wav gsm ilbc adpcm l16 g722 registrar_client \
                 postgresql session_timer jsonrpc http_client uac_auth \
                 options_prober radius_client redis registrar; do
  cd $m && make && make install && cd ..
done

# 验证
sems -v

4.6 SEMS 配置 /etc/sems/sems.conf

此配置基于官方 1.14 SEMS 安装指南的 yeti 配置模板,占位符用实例值替换。完整生产配置需先在 yeti-web 创建 PoP(Point of Presence)与 Node,取 PoP_ID 与 NODE_ID 填入。

general {
  stderr = no
  syslog_loglevel = 2
  syslog_facility = LOCAL0
  node_id = 1
  shutdown_mode { code = 508 reason = "Yeti node in shutdown mode" allow_uac = true }
  media_processor_threads = 4
  rtp_receiver_threads = 4
  session_processor_threads = 20
  sip_udp_server_threads = 2
  sip_tcp_server_threads = 2
  dead_rtp_time = 30
  default_bl_ttl = 0
  symmetric_rtp_mode = packets
  symmetric_rtp_packets = 20
}
signaling-interfaces {
  interface primary {
    default-media-interface = primary
    ip4 {
      sip-udp { address = 0.0.0.0 port = 5060 use-raw-sockets = off }
      sip-tcp { address = 0.0.0.0 port = 5060 connect-timeout = 2000 idle-timeout = 900000 use-raw-sockets = off }
    }
  }
}
media-interfaces {
  interface primary {
    ip4 {
      rtp { address = 0.0.0.0 low-port = 16384 high-port = 32769 dscp = 46 use-raw-sockets = off }
    }
  }
}
modules {
  module "di_log" {}
  module "mp3" {}
  module "opus" {}
  module "wav" {}
  module "gsm" {}
  module "ilbc" {}
  module "adpcm" {}
  module "l16" {}
  module "g722" {}
  module "registrar_client" {}
  module "postgresql" {}
  module "session_timer" {}
  module "jsonrpc" {
    listen { address = 127.0.0.1 port = 7080 }
    server_threads = 1
    map(yeti.request.registrations.reload, yeti.request.db_states.reload)
    map(yeti.request.router.translations.reload, yeti.request.db_states.reload)
  }
  module "http_client" {}
  module-global "uac_auth" {}
  module "options_prober" {}
  module "radius_client" {}
  module "redis" {
    max_batch_size = 10 batch_timeout = 100 max_queue_size = 1000
  }
  module "registrar" {
    redis { write { hosts = { 127.0.0.1:6379 } } read { hosts = { 127.0.0.1:6379 } } }
    headers = { x-orig-ip, x-orig-port, x-orig-proto }
  }
  module "yeti" {
    pop_id = 1
    auth { realm = yeti-switch }
    msg_logger_dir = /var/spool/sems/dump
    audio_recorder_dir = /var/spool/sems/records
    db_refresh_interval = 5
    routing {
      schema = switch22
      function = route_release
      init = init
      master_pool { host = 127.0.0.1 port = 5432 name = yeti user = yeti pass = q1w2e3r4 size = 4 }
    }
    cdr {
      pool_size = 2 auth_pool_size = 2
      schema = switch function = writecdr
      master { host = 127.0.0.1 port = 5432 name = cdr user = cdr pass = q1w2e3r4 }
    }
    resources { write { hosts = { 127.0.0.1:6379 } timeout = 500 } read { hosts = { 127.0.0.1:6379 } timeout = 1000 } }
    rpc { calls_show_limit = 10000 }
  }
}
routing { application = yeti }

4.7 SEMS systemd 单元

cat > /lib/systemd/system/sems.service <<'EOF'
[Unit]
Description=SEMS for Yeti-Switch
After=network.target postgresql.service redis-server.service

[Service]
Type=forking
ExecStart=/usr/sbin/sems -f /etc/sems/sems.conf
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable sems && systemctl start sems
systemctl status sems --no-pager

4.8 yeti-web (Rails 7.2) 安装

# Ruby 3.4(Brightbox PPA,Ubuntu 22.04 官方 Ruby 3.0 过旧)
apt install -y software-properties-common
add-apt-repository -y ppa:brightbox/ruby-ng-experimental
apt update
apt install -y ruby3.4 ruby3.4-dev build-essential libpq-dev libssl-dev

ruby --version
# ruby 3.4.8

# 下载 yeti-web 源码(同样无 Ubuntu deb 包,源码部署)
cd /opt
git clone https://github.com/yeti-switch/yeti-web.git
cd yeti-web

# 配置 database.yml
cat > config/database.yml <<'EOF'
production:
  primary:
    adapter: postgresql
    encoding: unicode
    database: yeti
    username: yeti
    password: q1w2e3r4
    host: 127.0.0.1
    port: 5432
    schema_search_path: 'gui, public, switch, billing, class4, runtime_stats, sys, logs, data_import'
    min_messages: notice
    pool: 5
  cdr:
    adapter: postgresql
    encoding: unicode
    database: cdr
    username: cdr
    password: q1w2e3r4
    host: 127.0.0.1
    port: 5432
    schema_search_path: 'cdr, reports, billing'
    min_messages: notice
    pool: 5
    migrations_paths: db/cdr_migrate
EOF

# 生成 secrets.yml
echo "production:" > config/secrets.yml
echo "  secret_key_base: $(pwgen -s 128 1)" >> config/secrets.yml
# 复制 yeti_web.yml.distr 模板
cp config/yeti_web.yml.distr config/yeti_web.yml

# 安装 bundler + 数据迁移
gem install bundler
su -s /bin/bash root -c \
  "cd /opt/yeti-web && RAILS_ENV=production bundle install && \
   RAILS_ENV=production bundle exec rake db:schema:load db:migrate db:seed && \
   RAILS_ENV=production bundle exec rake custom_seeds[network_prefixes]"

4.9 Nginx 配置 + Web 启动

apt install -y nginx

# 复制 yeti-web 的 nginx 模板
cp /opt/yeti-web/config/yeti-web.dist.nginx /etc/nginx/sites-available/yeti
rm -f /etc/nginx/sites-enabled/default
ln -sf /etc/nginx/sites-available/yeti /etc/nginx/sites-enabled/yeti

# ⚠️ 官方 yeti-web.dist.nginx 默认屏蔽 Admin API(/api/rest/admin/),需手动开启
sed -i 's|deny all;|allow all;|' /etc/nginx/sites-available/yeti

nginx -t && systemctl restart nginx

# 启动 yeti-web 的 4 个 systemd 单元(由源码提供 /lib/systemd/system/)
systemctl enable --now yeti-web yeti-delayed-job yeti-scheduler \
                yeti-cdr-processor@billing yeti-cdr-processor@stats
systemctl status yeti-web --no-pager

五、等价 docker run(如果坚持用容器)

官方无 Docker Hub yeti-switch 镜像,以下 docker run 仅为基于 Debian 13 官方包的参考写法,用于理解组件编排,非生产验证。

# 1. PostgreSQL 18(routing-db)
docker run -d --name yeti-routing-db --network yeti-net \
  -e POSTGRES_PASSWORD=q1w2e3r4 -e POSTGRES_USER=yeti -e POSTGRES_DB=yeti \
  -v yeti-routing-data:/var/lib/postgresql/data \
  -p 5432:5432 postgres:18

# 2. PostgreSQL 18(cdr-db,容器内需 pgq/pgqd/yeti 扩展)
docker run -d --name yeti-cdr-db --network yeti-net \
  -e POSTGRES_PASSWORD=q1w2e3r4 -e POSTGRES_USER=cdr -e POSTGRES_DB=cdr \
  -v yeti-cdr-data:/var/lib/postgresql/data \
  postgres:18  # 需额外挂载 pgq/yeti 扩展

# 3. Redis
docker run -d --name yeti-redis --network yeti-net \
  -v yeti-redis-data:/data \
  -p 6379:6379 redis:7 redis-server --notify-keyspace-events "Egx"

# 4. SEMS yeti(需自建 Dockerfile,挂载 /etc/sems/sems.conf + yeti module)
# 5. yeti-web(需自建 Dockerfile,挂载 config/database.yml + secrets.yml + yeti_web.yml)

六、验证

# 进程检查
systemctl is-active sems yeti-web yeti-scheduler yeti-delayed-job \
                   yeti-cdr-processor@billing yeti-cdr-processor@stats redis-server
# 全部返回 active

# 端口监听
ss -lpn | grep -E '5060|7080|6379|5432|80'
# udp 0 0 0.0.0.0:5060 ... sems
# tcp 0 0 127.0.0.1:7080 ... sems
# tcp 0 0 127.0.0.1:6379 ... redis-server
# tcp 0 0 127.0.0.1:5432 ... postgres
# tcp 0 0 0.0.0.0:80 ... nginx

# SEMS 日志
journalctl -u sems -n 30 --no-pager
# 搜索 "yeti module loaded" 确认模块加载成功

# 首次登录管理台
# http://<服务器 IP>/  admin / 111111
# 登录后立即在 System → Admin Users 修改默认密码

七、配置说明(启动后必须做)

  1. 创建 PoP(System → Points of Presence):给每个地理区域一个 PoP,用于多节点集群;
  2. 创建 Node(System → Nodes):把当前服务器的 RPC endpoint(127.0.0.1:7080)注册进去,返回 Node ID 后写回 sems.conf 的 node_id 与 pop_id;
  3. 创建 Gateway(Equipment → Gateways):配置供应商 SIP Trunk(传输协议、鉴权、RTP 编解码、NAT 穿透);
  4. 创建 Customer / Contractor / Account(Billing 模块):完成账号 + 费率模板;
  5. 创建 Routing Plan(Routing → Routing Plans):串联 Gateway + 费率模板 + 时间规则,形成 LCR 选路链。

八、常见问题

Q1:postgresql-18-yeti 在 Ubuntu 22.04 jammy-pgdg 源里找不到?
这是 Ubuntu 移植的最常见阻塞。该扩展官方只在 Debian trixie 源提供。临时解决:wget https://deb.yeti-switch.org/debian/1.14/pool/main/p/postgresql-18-yeti/postgresql-18-yeti_*.deb && dpkg -i --force-depends postgresql-18-yeti_*.deb。

Q2:SEMS 源码编译时报 configure: error: ssl >= 1.1.0 not found?
Ubuntu 22.04 OpenSSL 3.x 与部分老版本 SEMS yeti 分支有兼容问题。解决:切到较新的 yeti 分支(如 git checkout tags/1.14.79),或用环境变量 ./configure OPENSSL_LIBS="-lssl -lcrypto" OPENSSL_CFLAGS="-I/usr/include/openssl"。

Q3:SEMS 启动后 systemctl status 显示 active 但 journalctl -u sems 无 yeti module 加载日志?
检查 /etc/sems/sems.conf 中 pop_id 是否填了 yeti-web 已注册的有效 PoP ID(不是 yeti-web 上 Node 的 id,而是 PoP 的 id)。错误 pop_id 会导致 yeti module 静默失败。

Q4:Rails bundle 安装时 nokogiri 或 pg 编译失败?
apt install -y libxml2-dev libxslt1-dev libpq-dev zlib1g-dev 补齐系统头文件。

Q5:Nginx 访问 /api/rest/admin/ 返回 403?
官方 yeti-web.dist.nginx 默认 location /api/rest/admin/ { deny all; } 防止未授权访问。生产环境应改为 allow <管理网段>; deny all;,开发 PoC 才用 allow all;。

Q6:Ubuntu 22.04 与 Debian 13 的最大差异是什么?
PostgreSQL 18 扩展包(pgq / yeti)、SEMS 预编译 deb、Ruby 3.4。其余 Python/Rails/Redis/Nginx 依赖 Ubuntu 22.04 官方包可满足。

九、卸载清理

# 停止服务
systemctl stop sems yeti-web yeti-scheduler yeti-delayed-job \
              yeti-cdr-processor@billing yeti-cdr-processor@stats \
              redis-server postgresql
systemctl disable sems yeti-web yeti-scheduler yeti-delayed-job \
                 yeti-cdr-processor@billing yeti-cdr-processor@stats

# 移除 systemd 单元
rm -f /lib/systemd/system/sems.service
rm -f /lib/systemd/system/yeti*.service
systemctl daemon-reload

# 移除 yeti-web 与 SEMS
rm -rf /opt/yeti-web /opt/sems /etc/sems /etc/pgqd.ini
rm -f /etc/nginx/sites-enabled/yeti /etc/nginx/sites-available/yeti

# 清理 PostgreSQL(如无其他用途)
apt purge -y postgresql-18* pgqd
rm -rf /var/lib/postgresql/18 /etc/postgresql/18

# 清理 Redis(如无其他用途)
apt purge -y redis-server
rm -rf /var/lib/redis /etc/redis

# 清理 apt 源
rm -f /etc/apt/sources.list.d/yeti.list /etc/apt/sources.list.d/pgdg.list
rm -f /etc/apt/trusted.gpg.d/yeti-key.asc /etc/apt/trusted.gpg.d/pgdg-key.asc
rm -f /etc/apt/trusted.gpg.d/pgdg.asc

apt autoremove -y && apt clean

十、参考链接