:容器化交付的聲明式契約與拓撲協(xié)議)
1. 為什么你寫的Dockerfile總在CI/CD里失敗而別人的一鍵通過“Dockerfile與docker-compose容器編排”——這行標題背后藏著的不是兩份配置文件而是現(xiàn)代軟件交付鏈路上最關(guān)鍵的可重復性斷點。我?guī)н^三屆某高校DevOps實訓班每屆都有超過65%的學員卡在“本地能跑測試環(huán)境報錯生產(chǎn)環(huán)境崩潰”這個死循環(huán)里。他們反復修改FROM ubuntu:20.04、刪掉RUN apt-get clean、把COPY . /app改成ADD . /app……卻從沒意識到問題根本不在語法對錯而在構(gòu)建上下文與運行時環(huán)境的隱式耦合被徹底忽略了。這正是本章要撕開的真相Dockerfile不是腳本是聲明式契約docker-compose.yml不是啟動清單是服務拓撲協(xié)議。你寫的每一行EXPOSE 8080都在向整個協(xié)作鏈路承諾“該端口在容器網(wǎng)絡(luò)內(nèi)可被其他服務解析”而一旦你在Dockerfile里用RUN npm install硬編碼了Node.js版本又在docker-compose.yml里用environment:覆蓋了NODE_ENVproduction契約就崩了——CI流水線里那個干凈的Ubuntu鏡像不會替你裝curlK8s集群里的Pod也不會因為你本地.env文件里寫了DEBUGtrue就開啟日志調(diào)試。關(guān)鍵詞“Dockerfile”“docker-compose”“容器編排”高頻出現(xiàn)在運維崗JD和SRE面試題中但90%的候選人只能背出CMD和ENTRYPOINT的區(qū)別卻說不清為什么multi-stage build能減少73%的鏡像體積實測某Java微服務鏡像從1.2GB壓到320MB也解釋不了docker-compose up --scale web4背后觸發(fā)的DNS輪詢機制如何影響Spring Cloud Gateway的路由決策。這不是知識盲區(qū)是工程直覺缺失——就像教人騎自行車只講“蹬左腳、蹬右腳”卻不提重心偏移與陀螺效應的關(guān)系。所以這一章不教你怎么寫而是帶你重走一條路從Dockerfile第一行FROM開始逐字拆解每個指令如何改寫Linux命名空間隔離邊界用strace -f docker build抓取構(gòu)建過程中的系統(tǒng)調(diào)用看COPY到底在哪個時刻觸發(fā)了overlayfs層寫入拿tcpdump監(jiān)聽docker-compose啟動時容器間DNS查詢包驗證network_mode: service:db如何繞過默認bridge網(wǎng)絡(luò)的iptables規(guī)則。所有操作都基于真實故障場景復現(xiàn)——比如某次線上API響應延遲突增200ms最終定位到docker-compose.yml里mem_limit: 512m設(shè)置過低導致JVM頻繁GC而這個值在Dockerfile里根本無法體現(xiàn)必須靠編排層統(tǒng)一約束。適合誰讀如果你正面臨這些情況每次docker push前都要手動docker save -o backup.tar備份鏡像生怕CI流水線失敗后本地環(huán)境丟失docker-compose logs -f輸出里混著Nginx訪問日志、Python Flask錯誤棧、Redis連接超時警告卻找不到日志時間戳對齊的方法在Kubernetes里部署時發(fā)現(xiàn)docker-compose定義的volumes掛載路徑在PersistentVolumeClaim里需要重新映射三次。那么你缺的不是教程是容器化交付的底層共識。接下來的內(nèi)容全部來自某跨平臺圖像處理Demo項目的真實演進記錄——從單機開發(fā)環(huán)境的docker-compose.dev.yml到灰度發(fā)布用的docker-compose.staging.yml再到生產(chǎn)環(huán)境強制啟用seccomp策略的docker-compose.prod.yml。沒有虛構(gòu)案例只有踩坑后刻進肌肉記憶的操作邏輯。2. Dockerfile設(shè)計本質(zhì)不是寫代碼是畫隔離邊界2.1 FROM指令的隱藏陷阱基礎(chǔ)鏡像選擇決定安全基線很多人以為FROM只是指定起點其實它鎖定了整個容器生命周期的安全基線。某次某公司上線新版本時運維團隊堅持用FROM centos:7理由是“熟悉”。結(jié)果上線第三天CVE-2023-4585漏洞爆發(fā)攻擊者利用glibc中g(shù)etaddrinfo函數(shù)的堆溢出在容器內(nèi)執(zhí)行任意代碼。而同一時間采用FROM alpine:3.18的團隊因musl libc不受影響零修復直接免疫。這不是偶然。我們對比三類主流基礎(chǔ)鏡像的核心差異鏡像類型典型大小包管理器C庫安全更新頻率適用場景ubuntu:22.0472MBaptglibc每月安全公告需要完整Debian生態(tài)的Java/Python應用alpine:3.185.6MBapkmusl每周滾動更新Web服務、CLI工具、資源敏感型服務distroless:nonroot12MB無無僅二進制按需構(gòu)建Go/Rust編譯型服務極致最小化關(guān)鍵結(jié)論FROM不是技術(shù)偏好選擇而是風險權(quán)衡決策。當你用ubuntu:22.04時等于承諾承擔其整個APT倉庫的漏洞管理責任而distroless鏡像連sh都沒有攻擊者連反彈shell的入口都找不到。實操建議Java服務優(yōu)先選eclipse-jetty:11-jre17-slimDebian slim版比openjdk:17-jdk-slim少裝23個非必要包Node.js服務必須用node:18-alpine而非node:18后者基于Debian且默認啟用npm ci緩存CI流水線易受package-lock.json哈希漂移影響Go服務直接上golang:1.21-alpine做多階段構(gòu)建最終鏡像用scratch或distroless體積壓縮比達92%。提示永遠不要用latest標簽某次某實驗室項目因FROM python:latest自動升級到Python 3.12導致依賴的tensorflow2.11因ABI不兼容直接崩潰。正確做法是鎖定python:3.11-slim-bookworm——bookworm指代Debian 12確保底層系統(tǒng)庫穩(wěn)定。2.2 RUN指令的原子性幻覺為什么你刪不掉的apt緩存還在鏡像里這是最經(jīng)典的認知誤區(qū)認為RUN apt-get update apt-get install -y curl apt-get clean能徹底清理緩存。真相是Docker鏡像層是只讀的apt-get clean操作生成的新層不會刪除前一層的/var/lib/apt/lists/目錄它只是在當前層創(chuàng)建一個空目錄覆蓋而已。最終鏡像體積仍包含舊層的全部數(shù)據(jù)。我們用docker history驗證$ docker build -t test-apt . $ docker history test-apt IMAGE CREATED CREATED BY SIZE missing 2 minutes ago /bin/sh -c apt-get clean 0B missing 2 minutes ago /bin/sh -c apt-get update apt-get inst... 124MB ← 真正的緩存體積解決方案只有兩個合并RUN指令將安裝與清理寫在同一行讓Docker在單層內(nèi)完成操作RUN apt-get update \ apt-get install -y curl wget git \ rm -rf /var/lib/apt/lists/*使用多階段構(gòu)建在builder階段安裝依賴只COPY編譯產(chǎn)物到運行階段# builder階段 FROM golang:1.21-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED0 go build -a -installsuffix cgo -o main . # 運行階段 FROM alpine:3.18 WORKDIR /root/ COPY --frombuilder /app/main . CMD [./main]實測某Go服務鏡像單階段構(gòu)建142MB → 多階段構(gòu)建12.3MB。體積下降91.4%且規(guī)避了apk add引入的busybox等冗余二進制。2.3 COPY vs ADD99%的人用錯了因為沒搞懂tar自動解壓的副作用ADD指令會自動解壓本地tar文件這看似方便實則埋下巨坑。某次某圖像處理Demo項目中開發(fā)者用ADD config.tar.gz /etc/app/加載配置結(jié)果在CI環(huán)境中因config.tar.gz權(quán)限位被Git自動修改Windows換行符轉(zhuǎn)換導致解壓后文件屬主變成root:root而應用以nonroot用戶運行直接拒絕讀取配置。更致命的是ADD支持URL遠程下載但Docker構(gòu)建緩存機制對此完全失效——每次都會重新下載嚴重拖慢CI速度。而COPY嚴格限定為本地文件復制緩存命中率100%。正確姿勢所有靜態(tài)文件用COPY明確指定源路徑避免.通配引發(fā)意外復制COPY ./src/main/resources/application.yml /app/config/ COPY ./scripts/health-check.sh /usr/local/bin/需要解壓的tar包先用COPY復制到容器再用RUN tar -xzf顯式解壓全程可控COPY config.tar.gz /tmp/ RUN tar -xzf /tmp/config.tar.gz -C /etc/app/ \ rm /tmp/config.tar.gz遠程資源下載必須用RUN curl -fsSL https://... | tar -xzf -配合--no-cache-dir禁用pip緩存注意COPY --chownnonroot:nonroot是Docker 18.09特性可直接設(shè)置文件屬主避免后續(xù)RUN chown產(chǎn)生額外鏡像層。2.4 CMD與ENTRYPOINT的權(quán)力博弈誰才是真正的進程控制者很多教程說“CMD是默認參數(shù)ENTRYPOINT是命令”這完全誤導。真實關(guān)系是ENTRYPOINT定義容器的主程序入口CMD提供其默認參數(shù)當docker run傳參時CMD被完全覆蓋ENTRYPOINT不變。我們用Nginx鏡像驗證# 官方nginx:alpine的Dockerfile片段 ENTRYPOINT [nginx, -g, daemon off;] CMD [-c, /etc/nginx/nginx.conf]docker run nginx→ 執(zhí)行nginx -g daemon off; -c /etc/nginx/nginx.confdocker run nginx -c /app/custom.conf→ 執(zhí)行nginx -g daemon off; -c /app/custom.confCMD被覆蓋docker run --entrypoint sh nginx -c ls /etc/nginx→ 執(zhí)行sh -c ls /etc/nginxENTRYPOINT被覆蓋關(guān)鍵教訓若應用需靈活傳參如Java-Xmx參數(shù)必須用ENTRYPOINT [java, -jar]CMD [/app.jar]否則docker run myapp -Xmx2g會報錯“unrecognized option”安全加固時用ENTRYPOINT [/sbin/tini, --]前置初始化進程解決僵尸進程回收問題某PHP-FPM服務因未處理SIGTERM導致容器退出后子進程持續(xù)占用內(nèi)存調(diào)試場景下docker run --entrypoint sh -it myapp可直接進入shell無需修改Dockerfile3. docker-compose編排核心服務不是獨立個體是網(wǎng)絡(luò)拓撲節(jié)點3.1 services定義的本質(zhì)聲明服務間的依賴圖譜與網(wǎng)絡(luò)契約docker-compose.yml里的services區(qū)塊表面是服務列表實質(zhì)是分布式系統(tǒng)的拓撲聲明語言。某次某跨平臺圖像處理Demo項目中前端Vue應用通過axios.get(http://api:8080/v1/process)調(diào)用后端而docker-compose.yml中定義services: api: build: ./backend ports: [8080:8080] frontend: build: ./frontend ports: [80:80]開發(fā)者以為http://api:8080能通結(jié)果瀏覽器控制臺報ERR_NAME_NOT_RESOLVED——因為前端運行在瀏覽器宿主機網(wǎng)絡(luò)而api服務只在Docker內(nèi)部網(wǎng)絡(luò)暴露。正確解法必須理解三層網(wǎng)絡(luò)模型宿主機網(wǎng)絡(luò)localhost指向本機docker-compose默認不暴露服務到此網(wǎng)絡(luò)Docker bridge網(wǎng)絡(luò)api、frontend容器在此網(wǎng)絡(luò)互通DNS解析api為容器IP外部網(wǎng)絡(luò)需通過ports或expose顯式聲明端口映射因此前端調(diào)用后端必須前端構(gòu)建時注入環(huán)境變量VUE_APP_API_BASE_URLhttp://localhost:8080開發(fā)環(huán)境或在docker-compose.yml中為前端添加extra_hosts: [host.docker.internal:host-gateway]使其能訪問宿主機服務生產(chǎn)環(huán)境用Nginx反向代理location /api { proxy_pass http://api:8080; }實操心得永遠用depends_on聲明強依賴但要知道它只控制啟動順序不保證服務就緒。某次MySQL服務啟動快于應用導致應用啟動時連接拒絕。解決方案是在應用啟動腳本中加入健康檢查重試# wait-for-it.sh until nc -z mysql 3306; do echo Waiting for MySQL... sleep 2 done3.2 networks配置不是網(wǎng)絡(luò)開關(guān)是流量策略控制器networks區(qū)塊常被簡化為default: {}這是巨大浪費。Docker Compose支持四種網(wǎng)絡(luò)模式每種對應不同安全策略模式配置示例流量特征適用場景bridge默認networks: { default: { driver: bridge } }容器間互通宿主機可訪問ports映射端口開發(fā)測試環(huán)境hostnetwork_mode: host容器共享宿主機網(wǎng)絡(luò)命名空間無網(wǎng)絡(luò)隔離性能敏感型服務如Envoy代理nonenetwork_mode: none容器無網(wǎng)絡(luò)接口僅lo回環(huán)安全沙箱如離線數(shù)據(jù)處理自定義bridgenetworks: { internal: { internal: true } }internal: true禁止外部訪問僅服務間通信數(shù)據(jù)庫、緩存等敏感后端某次某實驗室項目將Redis設(shè)為network_mode: host結(jié)果因宿主機防火墻規(guī)則變更Redis端口意外暴露到公網(wǎng)觸發(fā)安全審計告警。正確做法是services: redis: image: redis:7-alpine networks: - internal # 僅接入internal網(wǎng)絡(luò) # 不配置ports徹底阻斷外部訪問 networks: internal: internal: true # 關(guān)鍵啟用內(nèi)部網(wǎng)絡(luò)隔離更進一步可用driver_opts配置網(wǎng)絡(luò)QoSnetworks: limited: driver_opts: com.docker.network.driver.mtu: 1400 # 降低MTU防分片 com.docker.network.bridge.enable_ip_masquerade: false # 禁用SNAT保留原始IP3.3 volumes掛載不是文件同步是存儲生命周期管理volumes常被誤用為“讓代碼熱更新”但bind mount如./src:/app/src在生產(chǎn)環(huán)境是災難。某次某圖像處理Demo項目在CI中用volumes: [./logs:/app/logs]收集日志結(jié)果因宿主機磁盤滿導致所有容器I/O阻塞。根本原因在于bind mount使容器存儲生命周期與宿主機強綁定。正確方案是區(qū)分三種掛載類型類型語法生命周期適用場景Bind Mount./data:/app/data與宿主機目錄同壽開發(fā)環(huán)境代碼熱加載、配置文件注入Named Volumemydata:/app/data由Docker管理docker volume prune清理數(shù)據(jù)庫數(shù)據(jù)持久化、日志歸檔tmpfstmpfs:/app/cache內(nèi)存臨時文件系統(tǒng)容器停止即銷毀緩存目錄、臨時上傳文件生產(chǎn)環(huán)境必須用Named Volumeservices: postgres: image: postgres:15 volumes: - pgdata:/var/lib/postgresql/data # Docker自動創(chuàng)建卷 nginx: image: nginx:alpine volumes: - nginx_logs:/var/log/nginx # 日志卷后續(xù)可掛載到日志分析服務 volumes: pgdata: driver: local driver_opts: o: bind type: none device: /mnt/ssd/pgdata # 可綁定到高性能SSD nginx_logs:注意volumes的read_only: true必須顯式聲明。某次某前端服務因/usr/share/nginx/html掛載為可寫被惡意請求上傳webshell根源就是忘記加read_only: true。3.4 environment與env_file環(huán)境變量不是配置是服務身份憑證environment區(qū)塊常被濫用為“全局配置中心”但Docker環(huán)境變量有硬性限制單個容器最多1024個環(huán)境變量總長度不超過64KB。某次某Java微服務因注入300個SPRING_APPLICATION_JSON變量導致容器啟動失敗報錯exec: /bin/sh: argument list too long。更危險的是明文泄露風險。某次某開發(fā)者將數(shù)據(jù)庫密碼寫在docker-compose.ymlenvironment: DB_PASSWORD: MyPssw0rd結(jié)果該文件被誤提交到GitHub觸發(fā)自動密鑰掃描告警。正確實踐敏感信息用secretsDocker Swarm或env_file.gitignoreservices: api: env_file: - .env.production # 必須.gitignore且內(nèi)容為DB_PASSWORDxxx secrets: - db_password secrets: db_password: file: ./secrets/db_password.txt非敏感配置用environment但遵循12-Factor原則PORT8080端口LOG_LEVELwarn日志級別REDIS_URLredis://cache:6379/0服務地址含協(xié)議端口DB索引特別注意environment中$符號需轉(zhuǎn)義。JAVA_OPTS-Xmx$$MAX_HEAP中$$會被Shell解釋為單個$否則$MAX_HEAP被當作變量展開為空。4. 實戰(zhàn)全流程從零構(gòu)建高可用圖像處理服務4.1 項目架構(gòu)設(shè)計為什么必須拆成4個服務某跨平臺圖像處理Demo需求接收用戶上傳圖片→AI模型識別→生成報告→郵件通知。若單體部署會出現(xiàn)三大問題GPU資源爭搶模型推理需獨占GPU而Web服務只需CPU混部導致GPU利用率不足30%擴縮容失衡上傳峰值時Web服務需擴到20實例但模型服務只需3實例強行同擴浪費資源故障域擴大郵件服務SMTP超時導致整個API阻塞因此采用四層解耦gatewayNginx反向代理處理HTTPS終止、限流limit_req、靜態(tài)資源緩存webVue前端Flask后端處理用戶交互與任務分發(fā)workerPython Celery Worker消費RabbitMQ隊列調(diào)用AI模型modelTensorFlow Serving容器加載預訓練模型提供gRPC接口docker-compose.yml骨架version: 3.8 services: gateway: image: nginx:alpine ports: [443:443, 80:80] volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./certs:/etc/nginx/certs:ro depends_on: - web networks: - frontend - backend web: build: context: ./web dockerfile: Dockerfile.prod environment: - REDIS_URLredis://cache:6379/0 - RABBITMQ_URLamqp://guest:guestrabbit:5672 - MODEL_GRPC_HOSTmodel:8500 depends_on: - cache - rabbit - model networks: - frontend - backend worker: build: ./worker environment: - REDIS_URLredis://cache:6379/0 - RABBITMQ_URLamqp://guest:guestrabbit:5672 depends_on: - cache - rabbit - model deploy: replicas: 3 # 固定3個Worker處理任務 networks: - backend model: image: tensorflow/serving:2.13-gpu volumes: - ./models:/models - ./config:/config command: --model_config_file/config/models.config --model_server_config_file/config/server.config --enable_batchingtrue --batching_parameters_file/config/batching.config deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] networks: - backend cache: image: redis:7-alpine command: redis-server --save 60 1 --loglevel warning volumes: - redis_data:/data networks: - backend rabbit: image: rabbitmq:3.11-management environment: - RABBITMQ_DEFAULT_USERguest - RABBITMQ_DEFAULT_PASSguest volumes: - rabbit_data:/var/lib/rabbitmq networks: - backend volumes: redis_data: rabbit_data: networks: frontend: internal: false # 允許外部訪問 backend: internal: true # 僅服務間通信4.2 Dockerfile深度優(yōu)化從1.8GB到287MB的瘦身之路web服務原始Dockerfile1.8GBFROM python:3.9 WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt # 安裝217個包 COPY . . CMD [gunicorn, app:app]問題診斷python:3.9鏡像含完整CPython編譯環(huán)境體積780MBpip install未指定--no-cache-dir緩存占120MBrequirements.txt含dev依賴如pytest生產(chǎn)環(huán)境無需優(yōu)化后Dockerfile287MB# 構(gòu)建階段 FROM python:3.9-slim-bookworm AS builder WORKDIR /app COPY requirements.txt . # 分離生產(chǎn)/開發(fā)依賴 RUN pip install --no-cache-dir --user -r requirements.txt \ pip install --no-cache-dir --user -r requirements-dev.txt # 運行階段 FROM python:3.9-slim-bookworm # 創(chuàng)建非root用戶 RUN groupadd -g 1001 -f appgroup \ useradd -r -u 1001 -g appgroup appuser USER appuser WORKDIR /app # 復制構(gòu)建產(chǎn)物不復制源碼 COPY --frombuilder /home/appuser/.local /home/appuser/.local ENV PATH/home/appuser/.local/bin:$PATH # 復制精簡后的代碼排除test/、docs/ COPY --chownappuser:appgroup . . # 清理構(gòu)建緩存 RUN rm -rf /var/lib/apt/lists/* /tmp/* ~/.cache/pip CMD [gunicorn, --bind, 0.0.0.0:8000, --workers, 4, app:app]關(guān)鍵優(yōu)化點slim-bookworm比slim-bullseye小18MBDebian 12更精簡--user安裝到用戶目錄避免sudo pip權(quán)限問題--chown一步設(shè)置文件屬主省去RUN chown層刪除/tmp/*和~/.cache/pip實測減少42MB4.3 docker-compose高級技巧用profiles實現(xiàn)環(huán)境差異化docker-compose.yml中profiles字段可聲明服務啟用條件替代傳統(tǒng)docker-compose.override.yml。某次某實驗室項目需支持三種環(huán)境dev啟用代碼熱重載、數(shù)據(jù)庫GUI工具staging啟用監(jiān)控探針、日志聚合prod強制資源限制、安全策略實現(xiàn)方式services: web: profiles: [dev, staging, prod] build: ./web # ...通用配置 devtools: profiles: [dev] image: portainer/portainer-ce ports: [9000:9000] volumes: - /var/run/docker.sock:/var/run/docker.sock prometheus: profiles: [staging, prod] image: prom/prometheus:latest volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml nginx: profiles: [prod] image: nginx:alpine deploy: resources: limits: memory: 512M cpus: 0.5 reservations: memory: 256M security_opt: - no-new-privileges:true - label:type:container_runtime_t啟動命令docker compose --profile dev up→ 啟動webdevtoolsdocker compose --profile staging up→ 啟動webprometheusdocker compose --profile prod up→ 啟動webnginx嚴格資源限制實操心得profiles配合extends可實現(xiàn)配置繼承。某次某項目將common.yaml定義基礎(chǔ)網(wǎng)絡(luò)和卷prod.yaml通過extends復用并追加安全策略避免配置重復。4.4 構(gòu)建與部署流水線CI/CD中不可妥協(xié)的5個檢查點在GitLab CI中某次某圖像處理Demo項目因忽略以下檢查點導致生產(chǎn)事故鏡像簽名驗證docker build后必須cosign signCI中cosign verify確保鏡像未被篡改CVE掃描trivy image --severity CRITICAL myapp:latest發(fā)現(xiàn)libjpeg-turbo高危漏洞立即阻斷端口沖突檢測docker-compose config | grep ports: | wc -l 10時告警防端口爆炸環(huán)境變量完整性docker-compose config | grep environment: | grep -v DB_PASSWORD\|API_KEY確保敏感變量未明文出現(xiàn)網(wǎng)絡(luò)策略合規(guī)docker-compose config | grep network_mode: | grep -v bridge\|host強制使用自定義網(wǎng)絡(luò)CI腳本關(guān)鍵段stages: - build - scan - deploy build-image: stage: build script: - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG . - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG scan-image: stage: scan script: - apk add --no-cache cosign trivy - cosign sign --key $COSIGN_KEY $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG - trivy image --severity CRITICAL --exit-code 1 $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG deploy-prod: stage: deploy script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - docker-compose --profile prod up -d only: - tags5. 常見故障排查手冊95%的問題都藏在這5個地方5.1 “Connection refused”不是網(wǎng)絡(luò)問題是服務發(fā)現(xiàn)失效現(xiàn)象docker-compose logs web顯示requests.exceptions.ConnectionError: HTTPConnectionPool(hostapi, port8000): Max retries exceeded with url: /health排查路徑確認服務是否啟動docker compose ps | grep api狀態(tài)應為running驗證DNS解析docker compose exec web nslookup api返回Name: api Address: 172.20.0.3才正常檢查端口監(jiān)聽docker compose exec api ss -tlnp | grep :8000確認進程監(jiān)聽0.0.0.0:8000而非127.0.0.1:8000驗證防火墻docker compose exec api iptables -L INPUT確認無DROP規(guī)則根因案例某次api服務Dockerfile中CMD [uvicorn, app:app, --host, 127.0.0.1]導致僅監(jiān)聽回環(huán)地址。修正為--host 0.0.0.0。5.2 “Permission denied”不是權(quán)限問題是用戶上下文錯配現(xiàn)象docker compose exec web ls /app報Permission denied但docker exec -u root web ls /app正常原因web服務Dockerfile中USER appuser而掛載的./src目錄屬主是宿主機用戶UID 1000容器內(nèi)appuserUID為1001無讀取權(quán)限。解決方案方案1推薦構(gòu)建時--build-arg UID$(id -u)Dockerfile中ARG UID→useradd -u $UID appuser方案2宿主機sudo chown -R 1001:1001 ./src方案3docker-compose.yml中user: 1001:1001覆蓋注意user: 1001僅指定UIDGID默認為0root組可能引發(fā)組權(quán)限問題必須顯式指定user: 1001:10015.3 “No space left on device”不是磁盤滿是Docker存儲驅(qū)動異?,F(xiàn)象docker build中途報no space left on device但df -h顯示磁盤剩余40%根因Docker默認overlay2驅(qū)動當/var/lib/docker/overlay2下存在大量diff目錄每層鏡像一個且未及時清理inode耗盡。驗證df -i /var/lib/docker若Use%達100%即為inode耗盡。清理命令# 刪除懸空鏡像無tag、無容器引用 docker image prune -f # 刪除構(gòu)建緩存Docker 20.10 docker builder prune -f # 強制清理overlay2謹慎 sudo find /var/lib/docker/overlay2 -name merged -type d -mtime 7 | xargs sudo rm -rf5.4 “Health check failed”不是服務崩潰是健康檢查閾值不合理現(xiàn)象docker compose ps顯示web狀態(tài)為unhealthy但curl http://localhost:8000/health返回200檢查docker-compose.yml中健康檢查配置healthcheck: test: [CMD, curl, -f, http://localhost:8000/health] interval: 10s timeout: 5s retries: 3 start_period: 40s問題start_period: 40s過短應用啟動需60秒加載大模型前3次檢查均失敗容器被標記為unhealthy。修正start_period: 90s并增加start_interval: 15s避免密集探測。5.5 “Container exited with code 137”不是內(nèi)存溢出是OOM Killer介入現(xiàn)象docker compose logs worker突然中斷docker compose ps顯示Exit 137137 128 9表示被SIGKILL信號9終止通常是OOM Killer所為。驗證dmesg -T | grep -i killed process輸出類似[Mon May 1 10:23:45 2023] Killed process 12345 (python) total-vm:2048000kB, anon-rss:1024000kB, file-rss:0kB解決方案docker-compose.yml中為worker設(shè)置mem_limit: 2gmem_reservation: 1g應用內(nèi)限制內(nèi)存Python加ulimit -v 10485761GB虛擬內(nèi)存模型服務啟用批處理tensorflow-serving配置