戰(zhàn):Docker多租戶實(shí)例管理與部署)
簡介這是一份圍繞 Odoo SaaS Kit 的 PDF 技術(shù)文檔重點(diǎn)講解基于 Docker 的多租戶 Odoo 實(shí)例管理系統(tǒng)面向已有服務(wù)器管理和 Odoo 使用經(jīng)驗(yàn)、負(fù)責(zé)企業(yè)級應(yīng)用部署與運(yùn)維的 IT 專業(yè)人員。文檔覆蓋部署鏈路安裝 docker、erppeek、paramiko 等 Python 庫規(guī)劃 Odoo-SAAS-Data、docker_vhosts、common_addons 目錄構(gòu)建基礎(chǔ) Odoo Docker 鏡像配置 Nginx 虛擬主機(jī)與 PostgreSQL并為 Odoo 用戶授權(quán) Docker 與 Nginx 控制權(quán)限。隨后說明如何配置 SaaS 服務(wù)器、創(chuàng)建訂閱計(jì)劃使客戶自助生成實(shí)例同時(shí)設(shè)置計(jì)費(fèi)周期、試用期與模塊列表還特別提醒構(gòu)建鏡像時(shí)須保持主機(jī)與容器內(nèi) Odoo 用戶 ID 和組 ID 一致避免共享文件夾權(quán)限問題。針對實(shí)際運(yùn)維文檔整理模塊上傳、域名管理、日志查看、備份恢復(fù)和重啟客戶端進(jìn)程等常見問題解法便于快速排障。資源包共 1 個(gè) PDF 文件大小 3.99MB適合作為部署參考與運(yùn)維手冊。目前已有 140 人瀏覽學(xué)習(xí)適合有 Docker 與 Nginx 基礎(chǔ)的技術(shù)人員直接借鑒。1. Odoo SaaS Kit為什么多租戶實(shí)例管理是Odoo商業(yè)化繞不開的一道坎做Odoo實(shí)施的團(tuán)隊(duì)遲早會(huì)遇到同一個(gè)問題客戶從3家漲到30家不可能給每家單獨(dú)買一臺(tái)服務(wù)器也不可能在一套Odoo里塞三十個(gè)互不相干的業(yè)務(wù)庫。Odoo SaaS Kit這個(gè)方向解決的就是這件事——用Docker把每個(gè)租戶的Odoo實(shí)例、數(shù)據(jù)庫、資源配額管起來做到開新客戶像開賬號一樣快。它不是某個(gè)插件而是一整套“部署配置管理”的方案容器編排管實(shí)例生命周期PostgreSQL隔離租戶數(shù)據(jù)反向代理按域名分發(fā)請求再加一套運(yùn)維流程做備份、升級和擴(kuò)容。適合正在做Odoo實(shí)施交付的顧問、小團(tuán)隊(duì)SaaS創(chuàng)業(yè)者以及要給客戶提供Odoo訂閱服務(wù)的技術(shù)負(fù)責(zé)人。2. 先搭地基Odoo SaaS Kit 的架構(gòu)選型與Docker最小拓?fù)?.1 多租戶的三種隔離方式為什么最終選“一實(shí)例一庫一容器組”O(jiān)doo做多租戶隔離方式大致有三條路共享數(shù)據(jù)庫共享Schema、共享數(shù)據(jù)庫獨(dú)立Schema、獨(dú)立數(shù)據(jù)庫。第一條路在Odoo里基本走不通因?yàn)镺doo的模塊安裝在數(shù)據(jù)庫級別不同客戶裝了不同模塊之后公共表結(jié)構(gòu)根本沒法統(tǒng)一。第二條路要改造ORM層動(dòng)到Odoo框架底子風(fēng)險(xiǎn)大且升級時(shí)每次都要打補(bǔ)丁不適合長期維護(hù)。SaaS Kit走的是第三條路每個(gè)租戶一個(gè)獨(dú)立PostgreSQL數(shù)據(jù)庫前端可以共用一套Odoo容器也可以每個(gè)租戶獨(dú)立容器組。新手階段建議從“共用Odoo容器獨(dú)立租戶庫”起步成本和運(yùn)維量最低等某個(gè)租戶的負(fù)載明顯影響其他人時(shí)再把這個(gè)租戶拆成獨(dú)立容器組。這套方案依賴Odoo官方就支持的多數(shù)據(jù)庫機(jī)制PostgreSQL里建了庫Odoo通過db_filter參數(shù)自動(dòng)識別可用的租戶庫不需要改Odoo源碼。隔離性、升級靈活性、交付速度三個(gè)維度上它都是最穩(wěn)的折中。2.2 最小容器拓?fù)淅锩總€(gè)角色的職責(zé)與關(guān)鍵參數(shù)一套能跑起來的SaaS Kit最小拓?fù)涫撬膫€(gè)容器PostgreSQL、Odoo、Redis、Nginx。數(shù)據(jù)庫負(fù)責(zé)租戶數(shù)據(jù)隔離Odoo負(fù)責(zé)應(yīng)用邏輯Redis在Odoo里做session存儲(chǔ)和緩存Nginx負(fù)責(zé)按域名把請求路由到Odoo。四個(gè)容器放在同一個(gè)自定義bridge網(wǎng)絡(luò)里互相通過容器名通信。容器角色鏡像關(guān)鍵端口職責(zé)dbpostgres:165432每租戶一個(gè)獨(dú)立databaseodooodoo:208069處理HTTP請求與業(yè)務(wù)邏輯redisredis:7-alpine6379session存儲(chǔ)、緩存nginxnginx:1.27-alpine80/443域名路由、TLS終止、WebSocket轉(zhuǎn)發(fā)Odoo容器里有幾個(gè)參數(shù)直接影響多租戶行為必須在compose里顯式聲明。db_filter告訴Odoo哪些數(shù)據(jù)庫屬于租戶常見的做法是設(shè)為^odoo_只匹配以odoo_開頭的庫避免把PostgreSQL里其他管理庫暴露到前端。list_dbfalse關(guān)閉數(shù)據(jù)庫列表展示防止用戶在登錄頁看到全部租戶庫名。proxy_modetrue讓Odoo信任Nginx轉(zhuǎn)發(fā)過來的HTTP頭否則生成的回鏈URL會(huì)帶上內(nèi)網(wǎng)端口。workers數(shù)量一般按CPU核數(shù)*21估算max_cron_threads保留1個(gè)線程跑定時(shí)任務(wù)多租戶場景下cron線程太多會(huì)互相搶數(shù)據(jù)庫連接。2.3 版本鎖定為什么從Odoo 20和PostgreSQL 16起步鏡像tag選擇上我的建議是直接鎖定Odoo 20和PostgreSQL 16不要用latest。latest在Docker里是個(gè)黑匣子今天拉下來能用三個(gè)月后再拉可能就是新版本升級引發(fā)的兼容問題排查起來非常被動(dòng)。Odoo 20是目前社區(qū)活躍度最高的版本線Docker鏡像、第三方模塊適配都齊全Python版本和依賴在鏡像里已經(jīng)固定省去不少手工編譯的麻煩。PostgreSQL 16對Odoo 20的支持成熟還有一個(gè)實(shí)際原因備份恢復(fù)工具pg_dump/pg_restore在大版本之間不能保證向下兼容。如果線上是PostgreSQL 16恢復(fù)演練和災(zāi)備環(huán)境也必須是16鎖死版本能避免“生產(chǎn)好好的備份恢復(fù)不回去”的血淚事故。Redis用7-alpine即可Odoo對Redis版本不敏感但alpine鏡像體積小、內(nèi)存占用低適合容器密集部署。最后一個(gè)建議統(tǒng)一從私有鏡像倉庫拉取這三個(gè)鏡像把版本號寫進(jìn)compose文件不要在日常操作里手動(dòng)docker pull覆蓋tag。3. 部署落地用docker compose跑起第一套多租戶Odoo3.1 第一版docker-compose.yml先接受“一容器一庫”的簡單拓?fù)湎葘懙谝话鎐ompose文件不追求完美目標(biāo)是讓db、odoo、redis、nginx四個(gè)容器一次起來并且能通過db_filter識別租戶庫。后續(xù)要拆獨(dú)立租戶容器組時(shí)再擴(kuò)展但架構(gòu)骨架不變。version: 3.8 services: db: image: postgres:16 container_name: saas_db environment: POSTGRES_USER: odoo POSTGRES_PASSWORD: odoo_password POSTGRES_DB: postgres volumes: - db_data:/var/lib/postgresql/data healthcheck: test: [CMD-SHELL, pg_isready -U odoo] interval: 10s timeout: 5s retries: 5 networks: - saas_net odoo: image: odoo:20 container_name: saas_odoo depends_on: db: condition: service_healthy environment: HOST: db USER: odoo PASSWORD: odoo_password DB_FILTER: ^odoo_ LIST_DB: false PROXY_MODE: true WORKERS: 5 MAX_CRON_THREADS: 1 volumes: - odoo_data:/var/lib/odoo - ./addons:/mnt/extra-addons networks: - saas_net redis: image: redis:7-alpine container_name: saas_redis command: [redis-server, --appendonly, yes] networks: - saas_net nginx: image: nginx:1.27-alpine container_name: saas_nginx depends_on: - odoo ports: - 80:80 - 443:443 volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./certs:/etc/nginx/certs:ro networks: - saas_net volumes: db_data: odoo_data: networks: saas_net: driver: bridge這段compose的邏輯是db容器先啟動(dòng)healthcheck通過后odoo容器才會(huì)創(chuàng)建這套依賴機(jī)制在compose里用condition: service_healthy實(shí)現(xiàn)。DB_FILTER: ^odoo_是關(guān)鍵的正則Odoo啟動(dòng)時(shí)會(huì)掃描PostgreSQL里所有數(shù)據(jù)庫只把以odoo_開頭的庫當(dāng)作租戶實(shí)例。LIST_DB: false對應(yīng)Odoo配置里的list_db False登錄頁不再展示數(shù)據(jù)庫下拉框。PROXY_MODE: true對應(yīng)proxy_mode True后面Nginx轉(zhuǎn)發(fā)時(shí)才不會(huì)出URL端口錯(cuò)誤。workers數(shù)量這里寫了5適合2核4G的初始機(jī)器。workers不是越大越好每個(gè)worker都持有數(shù)據(jù)庫連接池PostgreSQL默認(rèn)max_connections只有100workers太多反而拖垮數(shù)據(jù)庫這是第一次部署時(shí)最容易忽略的容量關(guān)系。3.2 初始化租戶數(shù)據(jù)庫并讓Odoo識別多庫compose文件寫好后先啟動(dòng)數(shù)據(jù)庫容器確認(rèn)健康狀態(tài)再啟動(dòng)Odoo然后手動(dòng)創(chuàng)建第一個(gè)租戶庫。不要用docker compose up -d一把梭第一次跑先拆開做方便定位是數(shù)據(jù)庫沒起來還是Odoo連接失敗。docker compose up -d db docker compose exec db pg_isready -U odoo docker compose exec db createdb -U odoo -O odoo odoo_tenant_001 docker compose up -d第一行只啟動(dòng)db容器第二行用pg_isready探測PostgreSQL是否接受連接。返回accepting connections說明就緒再執(zhí)行第三行創(chuàng)建租戶庫。-U odoo指定用戶-O odoo把新庫的屬主設(shè)為odoo用戶這一步不能漏否則Odoo用odoo用戶登錄后對這個(gè)庫沒有完整權(quán)限運(yùn)行時(shí)會(huì)報(bào)permission denied。第四行把odoo、redis、nginx一起拉起來Odoo啟動(dòng)時(shí)讀到DB_FILTER自動(dòng)把odoo_tenant_001識別為可訪問的租戶庫。打開瀏覽器訪問http://服務(wù)器IP/web/database/selector如果配置生效頁面不會(huì)列出數(shù)據(jù)庫列表需要手動(dòng)輸入租戶庫名odoo_tenant_001才能進(jìn)入初始化安裝界面??吹竭@個(gè)行為說明多租戶入口已經(jīng)通了。首次進(jìn)入會(huì)在租戶庫里初始化Odoo基礎(chǔ)模塊耗時(shí)一兩分鐘耐心等頁面跳轉(zhuǎn)。3.3 Nginx反向代理與租戶域名路由Odoo本身不處理域名路由它只按數(shù)據(jù)庫名分發(fā)請求域名到租戶庫的映射要Nginx來做。最常見的方案是通配符解析每個(gè)租戶一個(gè)二級域名結(jié)構(gòu)是tenant001.yourdomain.comDNS里加一條*.yourdomain.com到服務(wù)器IP的A記錄Nginx用一個(gè)server塊接住所有租戶請求轉(zhuǎn)發(fā)給Odoo容器。upstream odoo_backend { server saas_odoo:8069; keepalive 64; } server { listen 80; server_name *.yourdomain.com; proxy_read_timeout 720s; proxy_connect_timeout 10s; proxy_send_timeout 720s; proxy_set_header Host $host; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; location / { proxy_pass http://odoo_backend; } }關(guān)鍵在兩處server_name *.yourdomain.com匹配所有租戶子域proxy_pass http://odoo_backend把請求轉(zhuǎn)給compose網(wǎng)絡(luò)里的odoo容器。Odoo拿到請求后根據(jù)Host頭里的子域前綴結(jié)合dbfilter正則匹配對應(yīng)租戶庫這就是為什么dbfilter必須用^odoo_開頭來匹配數(shù)據(jù)庫名的原因——tenant001.yourdomain.com需要和庫名odoo_tenant_001在命名上強(qiáng)對應(yīng)。WebSocket要單獨(dú)處理Odoo的實(shí)時(shí)通信比如消息通知走WebSocket協(xié)議Nginx默認(rèn)轉(zhuǎn)發(fā)不了。需要先定義map $http_upgrade $connection_upgrade { default upgrade; close; }這段映射放到http塊里再在server塊里帶上Upgrade和Connection頭。不帶這段Odoo后臺(tái)的在線用戶列表和聊天功能會(huì)偶發(fā)斷連報(bào)錯(cuò)日志里能看到websocket connection failed。4. 實(shí)例管理打通租戶生命周期、備份恢復(fù)與資源配額4.1 租戶生命周期管理的常規(guī)操作序列SaaS Kit的管理對象是租戶實(shí)例實(shí)例管理的第一步是把生命周期標(biāo)準(zhǔn)化創(chuàng)建、暫停、遷移、歸檔、銷毀每個(gè)動(dòng)作對應(yīng)一組確定的命令。不要手動(dòng)去容器里亂改要把命令沉淀成腳本否則租戶一多必然出錯(cuò)。# 創(chuàng)建租戶 docker compose exec db createdb -U odoo -O odoo odoo_tenant_002 # 暫停租戶停止Odoo側(cè)服務(wù)保留數(shù)據(jù) docker compose stop odoo # 恢復(fù)服務(wù) docker compose start odoo # 歸檔租戶導(dǎo)出數(shù)據(jù)并下線 docker compose exec db pg_dump -U odoo -Fc odoo_tenant_002 backups/odoo_tenant_002.dump docker compose exec db dropdb -U odoo odoo_tenant_002創(chuàng)建租戶其實(shí)只做一件事建一個(gè)空數(shù)據(jù)庫。Odoo會(huì)在首次訪問時(shí)自動(dòng)完成模塊初始化不需要事先安裝任何東西。暫停和恢復(fù)針對整個(gè)Odoo容器操作這在一容器多庫的架構(gòu)下是硬傷——停一個(gè)租戶所有人都訪問不了。所以暫停操作更適合用在“整體維護(hù)窗口”場景單獨(dú)的租戶停用要通過nginx層做路由攔截或者用數(shù)據(jù)庫級權(quán)限控制不能簡單stop Odoo容器。歸檔操作要特別注意順序先pg_dump導(dǎo)出確認(rèn)dump文件大小合理再dropdb刪除庫。順序反了就得從備份恢復(fù)多租戶系統(tǒng)里沒有后悔藥吃。dropdb之后dbfilter正則自動(dòng)不再匹配這個(gè)庫租戶的域名訪問會(huì)直接404屬于安全的離線狀態(tài)。4.2 備份與恢復(fù)多租戶下恢復(fù)比備份難十倍單機(jī)單庫的備份很簡單多租戶場景下難點(diǎn)在恢復(fù)不能把整個(gè)PostgreSQL目錄拷貝覆蓋那會(huì)把所有租戶一起回滾也不能在恢復(fù)時(shí)讓dbfilter誤匹配到半初始化狀態(tài)的庫。我的建議是采用租戶級備份策略每個(gè)租戶庫獨(dú)立導(dǎo)出獨(dú)立恢復(fù)互不影響。# 備份單個(gè)租戶壓縮格式支持選擇性恢復(fù) docker compose exec db pg_dump -U odoo -Fc odoo_tenant_001 backups/odoo_tenant_001_$(date %F).dump # 恢復(fù)單個(gè)租戶到新庫 docker compose exec db createdb -U odoo -O odoo odoo_tenant_001_restored docker compose exec db pg_restore -U odoo -d odoo_tenant_001_restored --no-owner --no-privileges backups/odoo_tenant_001_20250601.dump恢復(fù)時(shí)務(wù)必用--no-owner --no-privileges否則dump里的屬主信息會(huì)和當(dāng)前容器里的odoo用戶不一致Odoo連上去后經(jīng)常報(bào)權(quán)限錯(cuò)誤?;謴?fù)完成后新庫名如果不符合dbfilter正則Odoo不會(huì)識別它這時(shí)把容器里的DB_FILTER臨時(shí)改成正則匹配新庫名或者直接在Nginx層加一條測試域名指過去驗(yàn)證數(shù)據(jù)完整后再把舊庫切換走。cron定時(shí)備份的腳本建議分成兩層每晚全量備份所有租戶庫每小時(shí)只對變更最頻繁的兩三個(gè)租戶做增量。PostgreSQL沒有內(nèi)置增量常見的做法是結(jié)合WAL歸檔但對SaaS Kit這種規(guī)模來說太重。實(shí)際夠用的方案是夜間cron跑全套pg_dump白天每兩小時(shí)對活躍租戶單獨(dú)跑一次輕量dump保留最近7天備份?;謴?fù)演練每月至少做一次不要等真出事了才試。4.3 容器資源配額與SaaS套餐定價(jià)的映射多租戶系統(tǒng)管理到后期最大的問題不是功能是資源分配。一個(gè)租戶寫死一個(gè)Odoo容器不現(xiàn)實(shí)資源浪費(fèi)嚴(yán)重但共用容器又沒法控制單個(gè)租戶的CPU和內(nèi)存占用。SaaS Kit的常見做法是在容器編排層給租戶定義配額模板用compose里的mem_limit和cpus字段控制。# 示例獨(dú)立租戶容器組的資源配額 services: odoo_tenant_001: image: odoo:20 mem_limit: 2g cpus: 1.0 environment: WORKERS: 3先把配額模板和服務(wù)套餐綁定基礎(chǔ)版1G內(nèi)存1核專業(yè)版2G內(nèi)存2核旗艦版4G內(nèi)存4核。Odoo的workers數(shù)量跟隨內(nèi)存配額走1G內(nèi)存最多配3個(gè)workers再高數(shù)據(jù)庫連接池會(huì)先撐不住。這個(gè)映射關(guān)系要在交付文檔里寫明否則銷售簽了高并發(fā)客戶部署時(shí)內(nèi)存配額給不夠后續(xù)全是性能投訴。docker update可以在容器運(yùn)行中動(dòng)態(tài)調(diào)整配額比如某個(gè)租戶月底結(jié)賬時(shí)CPU飆高臨時(shí)docker update --cpus 2 --mem_limit 4g saas_odoo_tenant_001結(jié)完賬再調(diào)回來。調(diào)整后觀察容器重啟情況內(nèi)存配額調(diào)低到當(dāng)前占用以下容器會(huì)被內(nèi)核直接殺掉這個(gè)操作必須謹(jǐn)慎。quota和計(jì)費(fèi)系統(tǒng)聯(lián)動(dòng)時(shí)建議按“配額階梯計(jì)費(fèi)”而不是“實(shí)際消耗計(jì)費(fèi)”否則AWS賬單波動(dòng)會(huì)讓用戶投訴到懷疑人生SaaS套餐的費(fèi)用策略里這是最穩(wěn)的一種設(shè)計(jì)。5. 避坑清單Docker部署Odoo SaaS Kit的常見問題與排查路徑5.1 現(xiàn)象Docker Desktop啟動(dòng)失敗提示virtualization support not detectedWindows環(huán)境跑Docker Desktop最常撞上的錯(cuò)誤就是啟動(dòng)時(shí)彈出virtualization support not detected。原因是BIOS里的虛擬化技術(shù)沒開或者Windows的虛擬機(jī)平臺(tái)功能沒啟用。解決路徑重啟進(jìn)BIOS/UEFI找到Intel VT-x或AMD-V選項(xiàng)并開啟然后在Windows功能里勾選“適用于Linux的Windows子系統(tǒng)”和“虛擬機(jī)平臺(tái)”執(zhí)行bcdedit /set hypervisorlaunchtype auto后重啟。啟動(dòng)不了Docker Desktop后面所有compose操作都無法進(jìn)行。Linux服務(wù)器上如果遇到docker: failed to start daemon先查內(nèi)核模塊lsmod | grep overlay和cgroup掛載大多數(shù)是內(nèi)核太舊或系統(tǒng)盤空間不足導(dǎo)致dockerd啟動(dòng)失敗。5.2 現(xiàn)象連接不上Docker引擎報(bào)failed to connect to the docker apifailed to connect to the docker api at npipe:////./pipe/dockerDesktopLinuxEngine這個(gè)錯(cuò)誤在Windows和Mac上都很常見。表面意思是Docker客戶端連不上引擎實(shí)際是Docker Desktop啟動(dòng)到一半卡住了或者引擎在后臺(tái)崩潰。解決路徑先打開Docker Desktop界面看引擎狀態(tài)圖標(biāo)是紅色說明引擎沒起來點(diǎn)Restart如果Restart無效執(zhí)行wsl --shutdown關(guān)掉WSL虛擬機(jī)再重開Docker Desktop。Windows服務(wù)里重啟com.docker.service也行。最直接的辦法是查Docker Desktop日志路徑在%LOCALAPPDATA%\Docker\log\看到EOF或connection reset基本是WSL2內(nèi)核問題執(zhí)行wsl --update更新內(nèi)核后解決。5.3 現(xiàn)象容器間網(wǎng)絡(luò)不通Odoo連接PostgreSQL失敗compose文件部署時(shí)一切正常一旦有人手動(dòng)docker run補(bǔ)充容器就會(huì)出現(xiàn)Odoo容器連不上db容器的情況。原因很簡單docker run默認(rèn)加入的是bridge網(wǎng)絡(luò)而compose創(chuàng)建的是自定義網(wǎng)絡(luò)saas_net兩個(gè)網(wǎng)絡(luò)的容器無法通過容器名互相解析。解決路徑所有補(bǔ)充容器都加--network saas_net參數(shù)或者把已有容器連進(jìn)去docker network connect saas_net container_name。排查時(shí)用docker network inspect saas_net查看節(jié)點(diǎn)列表確認(rèn)目標(biāo)容器在不在網(wǎng)絡(luò)里。另一個(gè)常見故障是容器網(wǎng)絡(luò)模式設(shè)置了network_mode: host這會(huì)導(dǎo)致compose網(wǎng)絡(luò)無法管理和隔離流量SaaS Kit多租戶場景下不建議使用host網(wǎng)絡(luò)。5.4 現(xiàn)象docker pull odoo鏡像下載慢卡在等待層數(shù)據(jù)多租戶系統(tǒng)一旦跑起來擴(kuò)容時(shí)最不想遇到的就是拉鏡像卡死。odoo官方鏡像體積不小層數(shù)多國內(nèi)網(wǎng)絡(luò)環(huán)境下直接拉官方倉庫經(jīng)常幾KB每秒。解決路徑配置registry mirror是第一步。在/etc/docker/daemon.json里加registry-mirrors: [https://docker.mirrors.ustc.edu.cn]重啟docker daemon生效。如果公司網(wǎng)絡(luò)有緩存代理用registry-cache做內(nèi)網(wǎng)鏡像倉庫20個(gè)租戶的服務(wù)器都從內(nèi)網(wǎng)倉庫拉取速度質(zhì)變。切記不要在業(yè)務(wù)高峰期docker pull大鏡像這會(huì)擠占容器網(wǎng)絡(luò)帶寬出現(xiàn)整組服務(wù)響應(yīng)變慢的情況。實(shí)在拉不下來的鏡像換一臺(tái)網(wǎng)絡(luò)通暢的機(jī)器拉好再docker save成tar包scp過去docker load這是最土但最有效的后悔藥。5.5 現(xiàn)象所有租戶域名都進(jìn)入了同一個(gè)Odoo實(shí)例多個(gè)租戶域名配好后發(fā)現(xiàn)訪問tenant002.yourdomain.com卻打開了tenant001的登錄頁或者干脆提示數(shù)據(jù)庫不存在。這個(gè)坑十有八九是db_filter和域名路由失配。Odoo的多租戶識別鏈路是Nginx按Host轉(zhuǎn)發(fā)到OdooOdoo再用Host子域前綴去匹配數(shù)據(jù)庫名。任何一個(gè)環(huán)節(jié)的名字不對都會(huì)指向錯(cuò)誤的實(shí)例。解決路徑確認(rèn)數(shù)據(jù)庫名和子域名的映射關(guān)系。tenant002.yourdomain.com對應(yīng)的庫名必須是odoo_tenant_002dbfilter正則寫成^odoo_才能匹配上。再看Nginx配置里有沒有多個(gè)server塊互相搶流量server_name *.yourdomain.com和server_name tenant002.yourdomain.com同時(shí)存在時(shí)Nginx按精確優(yōu)先規(guī)則匹配但很多人把精確域名寫在通配前面導(dǎo)致所有請求都走精確匹配。最后檢查Odoo容器環(huán)境變量DB_FILTER有沒有被后面加載的配置文件覆蓋docker compose exec odoo env | grep DB_FILTER直接看運(yùn)行環(huán)境結(jié)果最可信。6. 進(jìn)階驗(yàn)證把SaaS Kit從“能跑”推到“敢上線”6.1 上線前要過的三關(guān)恢復(fù)演練、并發(fā)壓測、版本升級第一關(guān)是備份恢復(fù)演練選一個(gè)非生產(chǎn)租戶庫導(dǎo)出dump再恢復(fù)到全新庫全程記錄耗時(shí)。多租戶系統(tǒng)最容易翻車的就是災(zāi)備環(huán)節(jié)——備份天天跑恢復(fù)沒人試過。第二關(guān)是并發(fā)壓測用ab -n 1000 -c 20 http://localhost/觀察Odoo在20并發(fā)下的響應(yīng)時(shí)間和錯(cuò)誤率如果p95超過2秒先調(diào)workers數(shù)量再檢查PostgreSQL的shared_buffers。第三關(guān)是版本升級演練把odoo鏡像tag從20升到下一個(gè)版本前先在測試環(huán)境完整跑一遍odoo-bin -u all -d odoo_tenant_001確認(rèn)模塊遷移沒有破壞性變更。6.2 用兩條命令把多租戶狀態(tài)納入日常巡檢日常巡檢不一定要上監(jiān)控系統(tǒng)兩條命令足夠發(fā)現(xiàn)90%的問題。docker stats --no-stream看所有容器的CPU和內(nèi)存占用哪個(gè)租戶容器內(nèi)存持續(xù)飆到配額上限就該考慮拆分或升級套餐了。docker logs --since 30m saas_odoo | grep -E CRITICAL|ERROR看近半小時(shí)的應(yīng)用錯(cuò)誤日志Odoo的ERROR日志通常會(huì)帶租戶庫名能直接定位是哪個(gè)實(shí)例出了問題。我第一次上線SaaS Kit時(shí)最怕的不是某個(gè)容器掛了而是備份恢復(fù)沒人演練過結(jié)果第一個(gè)月就有一個(gè)租戶誤刪數(shù)據(jù)靠pg_dump恢復(fù)到十分鐘前才意識到這套方案的設(shè)計(jì)核心從來不是容器編排多花哨而是數(shù)據(jù)兜底夠不夠穩(wěn)。后來我把備份恢復(fù)腳本掛進(jìn)cron每月自動(dòng)郵件通知演練結(jié)果再也沒有因?yàn)槿藶檎`操作失眠過。做多租戶Odoo先敬畏數(shù)據(jù)再追求效率希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取