统一开发团队的流水线哲学

统一开发团队的流水线哲学:从 .gitlab-ci.yml 到 IDP 平台化治理 问题:为什么每个项目的流水线都长得不一样? 团队规模还小的时候,CI/CD 流水线通常是怎么来的?某个开发者把上个项目的 .gitlab-ci.yml 拷过来,改两行,能跑就行。再过两个月新开一个服务,又从那个改过的版本拷过去再改两行。一年下来,十个微服务有十种写法,review 流水线配置的时间比 review 业务代码还长。 这不是某个团队的个例,而是缺少 统一流水线规范 的必然结果。 造成这种混乱的根源有三层: 第一层,认知门槛。GitLab CI 的配置语法看似简单——stages、jobs、script、only/except,但真正写好需要理解 runner 的执行模型、cache 和 artifact 的区别、image 与 service 的作用域。大部分人止步于"能跑就行",不会主动深究。 第二层,缺乏约束。GitLab CI 本身不做 schema 校验,before_script 里写什么都行,Dockerfile 里的 RUN 指令堆多少层也没人管。没有门禁、没有模板、没有 review 机制,流水线质量完全依赖开发者个人习惯。 第三层,业务压力。“先把功能上线"永远排在"把流水线写好"前面。流水线的技术债不像业务代码那样直接影响用户,于是越欠越多,直到有一天构建 40 分钟没人敢动。 📌 前置知识——GitLab CI 基础:建议先理解 .gitlab-ci.yml 的 stages、jobs、script、image、cache、artifacts 六个核心关键字(只需理解各自的职责和生效范围即可)。 结构:一条理想流水线的骨架 先从最核心的问题开始: 一条"完美"的 .gitlab-ci.yml 应该长什么样? 答案不是给你一个 500 行的 YAML 文件,而是说清楚 原则。原则对了,具体写法可以按项目微调。 四阶段流水线 flowchart TD S([📥 代码提交]) --> A1 subgraph Stage1["🔍 验证阶段"] A1[📌 编译检查]:::process A2[📌 代码风格]:::process A3[📌 单元测试]:::process end Stage1 --> Stage2 subgraph Stage2["📦 构建阶段"] B1[📌 构建镜像]:::process B2[📌 推送镜像仓库]:::process B3[📌 导出制品]:::process end Stage2 --> Stage3 subgraph Stage3["🚀 部署阶段"] C1[📌 部署开发环境]:::highlight C2[📌 集成测试]:::process C3{📌 验收通过?}:::condition end C3 -->|✅ 是| Stage4 C3 -->|❌ 否| Rollback[⛔ 回滚通知]:::reject subgraph Stage4["📡 生产发布"] D1[📌 灰度发布]:::highlight D2[📌 全量上线]:::startEnd end classDef startEnd fill:#701a4c,stroke:#e11d48,stroke-width:2px,color:#fce7f3,font-weight:bold; classDef condition fill:#2a1147,stroke:#a855f7,stroke-width:1.5px,color:#ede9fe,font-weight:bold; classDef process fill:#1e1e24,stroke:#6b7280,stroke-width:1.5px,color:#e5e7eb; classDef reject fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; classDef highlight fill:#450a0a,stroke:#dc2626,stroke-width:1.5px,color:#fecaca,font-weight:bold; 四个阶段各司其职: ...

一月 29, 2023 · 6 分钟 · 1241 字 · yaomingye

第4步:开发者 K8s 全景图 —— Ingress、Helm 和你的职责边界

开发者 K8s 全景图 一、目标说明 前四篇文章把 K8s 的概念地基、YAML 编写、探针配置、kubectl 命令全拆完了。这篇是收网篇——把剩下的重要但散落的知识点串起来,然后画一条清晰的线:什么归你管,什么扔给运维。 读完这篇文章,读者能: 写出完整的 Ingress YAML,理解域名路由规则 用 Helm 安装和管理应用( helm install / upgrade / rollback ) 选择适合自己场景的本地 K8s 环境 知道 StatefulSet、HPA、Job/CronJob、PVC 是干什么的、什么时候需要 认清 Dev vs Ops 的分界线,不再背不该背的锅 二、前置条件 前置条件 要求 理解 Service(ClusterIP/NodePort) 第 0 ~ 1 步已覆盖 会基本的 kubectl 操作 第 3 步已覆盖 了解域名和 HTTP 路径的基本概念 api.example.com/users 这种格式能看懂 三、分步实践 3.1 Ingress —— 域名路由,外部流量的大门 3.1.1 为什么需要 Ingress? Service 的三种类型: Service 类型 外部访问 问题 ClusterIP 不能 只能集群内用 NodePort 能( NodeIP:30000-32767 ) 端口丑、不能基于域名路由,一个端口只能绑一个 Service LoadBalancer 能(云 LB 分配公网 IP) 每个 Service 都要创建一个 LB,烧钱 Ingress 解决的问题:用一个入口(一个 LB / 一个公网 IP),根据域名和路径把流量分发到不同的 Service。 ...

一月 9, 2023 · 8 分钟 · 1621 字 · yaomingye

第3步:kubectl 生存手册 —— 开发者每天必敲的命令

kubectl 生存手册 一、目标说明 前三篇文章把概念、YAML、配置都讲完了。这一篇不讲"是什么",只讲 **“怎么查”**和 “怎么排” 。 这是整个系列最实用的一篇——开发者 90% 跟 K8s 打交道的时间,不是写 YAML,而是在这几个命令之间反复横跳: kubectl get → kubectl describe → kubectl logs → kubectl exec — 然后回到 get 读完这篇文章,读者能: 用 4 个核心查看命令快速定位问题 用 3 个交互命令深入容器内部或桥接流量 掌握 9 种 Pod 异常状态的完整诊断流程 用 -o wide/json/yaml 和 --sort-by 提取关键信息 建立"从现象到根因"的排查肌肉记忆 二、前置条件 前置条件 要求 本地 K8s 环境可用 kubectl cluster-info 正常 有几个 Pod 在跑 前几篇文章的 my-first-app 即可 理解 Pod / Deployment / Service 是什么 至少知道它们是干什么的 三、环境准备 沿用前面的 Namespace,确认有资源在跑: ...

一月 8, 2023 · 11 分钟 · 2166 字 · yaomingye

第2步:让 Pod 活得久一点 —— 探针、资源和配置注入实战

让 Pod 活得久一点 一、目标说明 上一篇文章成功部署了第一个 K8s 应用。但现实是——Pod 不会永远乖乖 Running。第二天打开监控一看:一个 Pod 被 OOMKilled,一个在 CrashLoopBackOff 无限重启,还有一个 Pending 了 3 小时没人管。 这篇文章要解决的就是:怎么让 Pod 活得久、死得明白、配置配得清楚。 读完这篇文章,读者能: 区分三种探针的适用场景,写出正确的探针配置 给容器设置合理的 resources 限制,避免 OOMKilled 和 CPU 被偷 掌握环境变量注入的 3 种方式及其选型标准 用 Volume Mount 把配置文件挂进 Pod 看懂 Pod 最常见的 6 种异常状态及其排查方向 二、前置条件 前置条件 要求 验证命令 已完成第 1 步 本地 K8s 能正常 deploy kubectl get deploy -n my-first-app 理解 Pod 基本概念 知道 Pod 里跑容器 看一眼第 0 步速查表即可 理解 Deployment 基本概念 知道 replicas、selector 看一眼第 1 步 Deployment YAML 即可 三、环境准备 沿用第 1 步的环境,先重新部署一遍做基准: ...

一月 7, 2023 · 8 分钟 · 1551 字 · yaomingye

第1步:写出你的第一个 K8s 应用

动手!部署你的第一个 K8s 应用 一、目标说明 上一篇文章把 Docker 和 K8s 的概念地图铺开了。这篇文章要做的是:真正动手,在本地 K8s 集群上部署一个完整的应用。 读完这篇文章,读者能: 验证本地 K8s 环境是否可用 写出一个完整的 Deployment YAML(并理解每一行在说什么) 写出 Service 让 Pod 可以稳定访问 用 ConfigMap 和 Secret 把配置从镜像里拆出来 用 kubectl apply 把整套东西一键部署 通过 kubectl port-forward 在浏览器里访问应用 二、前置条件 前置条件 要求 验证命令 Docker Desktop 已安装 4.x+ docker version Kubernetes 已开启 Docker Desktop Settings → Kubernetes → Enable Kubernetes kubectl cluster-info kubectl 已安装 Docker Desktop 自带 kubectl version --client 上一篇的概念理解 知道 Image / Container / Pod / Deployment / Service 是什么 脑子过一遍层级:Image → Container → Pod → Deployment 如果 kubectl cluster-info 输出类似以下内容,说明环境就绪: ...

一月 6, 2023 · 7 分钟 · 1422 字 · yaomingye

第0步:Docker 是什么,K8s 为什么要存在

Docker 与 K8s:从困惑到搞懂 一、目标说明 这篇文章要解决一个问题:一个从来没碰过容器的后端开发,怎么搞懂 Docker 和 K8s 那一堆名词? 读完这篇文章,读者能搞清楚以下事情: Docker 的 Image(镜像)和 Container(容器)到底是什么关系 为什么有了 Docker 还不够,还要搞一个 K8s 出来 K8s 的 Master / Worker Node 上各自跑了哪些组件,它们怎么配合 Pod、Deployment、Service、ConfigMap、Secret、Namespace 这些概念分别解决什么问题 一个 kubectl apply 命令背后,K8s 集群里发生了什么 ⚠️ 新手提示:这篇文章不会让你动手敲任何命令。目的是在脑子里建一张"K8s 全景地图"。有了这张地图,后面写 YAML、敲 kubectl 的时候才知道每一行是在操作什么东西。 二、前置条件 读者需要具备以下基础(都很基本): 前置知识 要求程度 验证方式 Linux 基本命令 会用 cd 、 ls 、 cat 、 ps 打开终端敲一下看看 进程概念 知道一个程序运行起来就是一个进程 打开任务管理器看一眼 IP + 端口 知道 127.0.0.1:8080 是什么意思 用过浏览器访问 localhost 即可 YAML 格式 见过 YAML,知道缩进表示层级 写过 Spring Boot 的 application.yml 就算 如果以上都 OK,往下看。 ...

一月 5, 2023 · 8 分钟 · 1581 字 · yaomingye

GitLab CI/CD 多环境部署与生产实践

从 dev 一路跑到 prod——点个按钮就上线 📖 前置阅读:本文假设读者已搭建 GitLab CI/CD 流水线(编译 → 测试 → 扫描 → 构建镜像 → 推送 Harbor),并已将微服务部署在 Kubernetes 上。如果还不熟悉,建议先阅读 搭建与 Pipeline 语法精讲 和 流水线实战。 一、⚡ 镜像推到 Harbor 了——但你还得手动 SSH 上去 kubectl apply——这叫啥 CI/CD? 前两篇搭好了 CI/CD Pipeline——代码 push → 编译 → 测试 → 扫描 → 构建镜像 → 推送到 Harbor。 但 Pipeline 到这里就停了——后面的部署还是人来操作: 当前状态(半自动): ✅ 代码 push → 自动编译、测试、扫描、构建镜像、推送 Harbor ❌ 然后——SSH 到跳板机 → kubectl set image → 看有没有报错 ❌ 然后——curl 验证——发现不对——kubectl rollout undo ❌ 然后——staging 和 prod 没有隔离——改了什么全凭记忆力 → CI 有了——CD 没做——半吊子自动化 真正的 CD——镜像推送到 Harbor 后——自动部署到 dev——验证通过——自动部署到 staging——人工审批——部署到 prod。人对生产的操作只剩下"点一个按钮"。 ...

十二月 26, 2022 · 9 分钟 · 1903 字 · yaomingye

GitLab CI/CD 流水线实战——编译、扫描、构建镜像、推送仓库

代码 push 之后——五道关卡自动跑完 📖 前置阅读:本文假设读者已搭建 GitLab + Runner 并理解 .gitlab-ci.yml 基础语法(stages/jobs/artifacts/cache/rules)。如果还不熟悉,建议先阅读 GitLab CI/CD 搭建与 Pipeline 语法精讲。 一、⚡ 编译过了——但你敢直接部署吗?代码质量谁保证? 上一篇文章的 Pipeline 只做了编译和测试——但真正的 CI/CD 不止这些: 真正的 CI/CD 流水线要回答 5 个问题: ① 编译成功了吗? → mvn compile ② 测试通过了吗? → mvn test + 覆盖率报告 ③ 代码质量合格吗? → SonarQube 扫描 + Quality Gate ④ 镜像构建成功了吗? → docker build ⑤ 镜像推送到仓库了吗? → docker push → Harbor 这 5 步全自动——缺一步都不能算 CI/CD 这篇的目标——搭一条完整的流水线:代码 push → 自动跑完上述 5 步——任何一个环节失败——Pipeline 变红——阻止部署。 二、🏗️ 完整的 Pipeline 架构 flowchart LR Push["git push"] --> Compile["① 编译\nmvn compile"] Compile --> Test["② 单元测试\nmvn test\n+ 覆盖率报告"] Test --> SonarQube["③ 代码扫描\nSonarQube\n+ Quality Gate"] SonarQube --> Package["④ 打包\nmvn package"] Package --> DockerBuild["⑤ 构建镜像\ndocker build"] DockerBuild --> HarborPush["⑥ 推送仓库\ndocker push\n→ Harbor"] HarborPush --> Notify["⑦ 通知\n企业微信/钉钉"] classDef style_SonarQube fill:#450a0a,stroke:#dc2626,stroke-width:2px,color:#fecaca; classDef style_HarborPush fill:#0f172a,stroke:#3b82f6,stroke-width:2px,color:#bfdbfe; class SonarQube style_SonarQube; class HarborPush style_HarborPush;``` ## 三、🔧 基础设施——SonarQube + Harbor 搭建 ### 3.1 Docker Compose——加 SonarQube 和 Harbor ```yaml # 在上一篇文章的 docker-compose.yml 基础上加两个服务 version: '3.8' services: # ===== GitLab + Runner(同上一篇——省略)===== # ... # ===== SonarQube——代码质量扫描 ===== sonarqube: image: sonarqube:10.3.0-community container_name: sonarqube environment: SONAR_JDBC_URL: jdbc:postgresql://sonarqube-db:5432/sonarqube SONAR_JDBC_USERNAME: sonar SONAR_JDBC_PASSWORD: sonar123 ports: - "9000:9000" volumes: - sonarqube-data:/opt/sonarqube/data - sonarqube-extensions:/opt/sonarqube/extensions depends_on: - sonarqube-db sonarqube-db: image: postgres:15-alpine container_name: sonarqube-db environment: POSTGRES_USER: sonar POSTGRES_PASSWORD: sonar123 POSTGRES_DB: sonarqube volumes: - sonarqube-db-data:/var/lib/postgresql/data # ===== Harbor——私有 Docker 镜像仓库 ===== # Harbor 官方推荐用 docker-compose 独立部署——这里简化 # 生产环境参考 https://goharbor.io/docs harbor: image: goharbor/registry-photon:v2.9.0 container_name: harbor-registry ports: - "5000:5000" volumes: - harbor-data:/var/lib/registry volumes: sonarqube-data: sonarqube-extensions: sonarqube-db-data: harbor-data: 3.2 SonarQube 初始化——创建项目 Token ① 浏览器打开 http://gitlab.local:9000 ② 默认登录:admin / admin——首次强制修改密码 ③ Administration → Projects → Create Project → Project key: order-service → Project name: order-service → 创建 ④ 创建 Token:My Account → Security → Generate Token → Token name: gitlab-ci → 复制 Token——后续要放在 GitLab CI/CD 变量中 ⑤ 在 GitLab 中配置 SonarQube 变量: GitLab → 项目 → Settings → CI/CD → Variables 添加: SONAR_HOST_URL = http://sonarqube:9000 SONAR_TOKEN = squ_xxxxxxxxxxxxxxxxxxxxxxxxxx ← 刚才复制的 Token 3.3 Maven 项目的 SonarQube 配置 <!-- pom.xml——加 JaCoCo 覆盖率插件 + SonarQube 插件 --> <build> <plugins> <!-- JaCoCo——代码覆盖率 --> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.11</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>test</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <properties> <!-- SonarQube 配置 --> <sonar.host.url>${env.SONAR_HOST_URL}</sonar.host.url> <sonar.login>${env.SONAR_TOKEN}</sonar.login> <sonar.projectKey>order-service</sonar.projectKey> <sonar.projectName>order-service</sonar.projectName> <sonar.java.binaries>target/classes</sonar.java.binaries> <sonar.coverage.jacoco.xmlReportPaths>target/site/jacoco/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths> </properties> 四、📝 完整的 .gitlab-ci.yml——从编译到推送镜像 4.1 完整 Pipeline 定义 # order-service/.gitlab-ci.yml # 完整的 CI/CD Pipeline——5 个阶段 stages: - compile # ① 编译 - test # ② 测试 + 覆盖率 - quality # ③ SonarQube 扫描 - package # ④ 打包 + 构建镜像 - push # ⑤ 推送镜像到 Harbor # ===== 全局变量 ===== variables: MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository" MAVEN_CLI_OPTS: "-B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN" # Harbor 地址——在 GitLab CI/CD Variables 中配置 HARBOR_URL: "harbor.local:5000" IMAGE_NAME: "$HARBOR_URL/order-service" # ===== 全局缓存——Maven 依赖 ===== cache: key: maven-${CI_COMMIT_REF_SLUG} paths: - .m2/repository/ policy: pull-push # ===== Stage 1: 编译 ===== compile: stage: compile image: maven:3.9-eclipse-temurin-17 script: - mvn $MAVEN_CLI_OPTS compile artifacts: paths: - target/classes/ expire_in: 1 hour tags: - docker # ===== Stage 2: 单元测试 + 覆盖率 ===== unit-test: stage: test image: maven:3.9-eclipse-temurin-17 script: - mvn $MAVEN_CLI_OPTS test jacoco:report artifacts: when: always paths: - target/surefire-reports/ - target/site/jacoco/ # ← JaCoCo 报告——给 SonarQube 用 expire_in: 7 days reports: junit: target/surefire-reports/TEST-*.xml # ← GitLab 自动展示测试结果 coverage: '/Total.*?([0-9]{1,3})%/' # ← GitLab 自动展示覆盖率百分比 tags: - docker # ===== Stage 3: SonarQube 代码扫描 ===== sonarqube-check: stage: quality image: maven:3.9-eclipse-temurin-17 script: - mvn $MAVEN_CLI_OPTS sonar:sonar -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_TOKEN # 只在 MR 或 main 分支扫描——feature 分支不扫(浪费 SonarQube 资源) rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - if: $CI_COMMIT_BRANCH == "main" tags: - docker # ===== Stage 4: 打包 ===== package: stage: package image: maven:3.9-eclipse-temurin-17 script: - mvn $MAVEN_CLI_OPTS package -DskipTests artifacts: paths: - target/*.jar expire_in: 1 hour tags: - docker # ===== Stage 5: 构建 Docker 镜像并推送到 Harbor ===== docker-build-push: stage: push image: docker:24-dind # ← Docker-in-Docker 镜像——在容器内跑 Docker services: - docker:24-dind # ← 启动 Docker daemon sidecar before_script: - apk add --no-cache bash # Alpine 需要 bash # 等待 Docker daemon 启动 - until docker info > /dev/null 2>&1; do sleep 1; done script: # ① 构建镜像——用 commit SHA 作为 tag - docker build -t $IMAGE_NAME:$CI_COMMIT_SHORT_SHA . # ② 打标签——如果 main 分支——打 latest;如果有 tag——打 release 版本 - | if [ "$CI_COMMIT_BRANCH" = "main" ]; then docker tag $IMAGE_NAME:$CI_COMMIT_SHORT_SHA $IMAGE_NAME:latest fi - | if [ -n "$CI_COMMIT_TAG" ]; then docker tag $IMAGE_NAME:$CI_COMMIT_SHORT_SHA $IMAGE_NAME:$CI_COMMIT_TAG fi # ③ 登录 Harbor——用户名密码配在 GitLab CI/CD Variables 中 - echo "$HARBOR_PASSWORD" | docker login $HARBOR_URL -u "$HARBOR_USERNAME" --password-stdin # ④ 推送所有标签 - docker push $IMAGE_NAME:$CI_COMMIT_SHORT_SHA - | if [ "$CI_COMMIT_BRANCH" = "main" ]; then docker push $IMAGE_NAME:latest fi - | if [ -n "$CI_COMMIT_TAG" ]; then docker push $IMAGE_NAME:$CI_COMMIT_TAG fi tags: - docker 4.2 Dockerfile——配合 CI/CD 的镜像构建 # order-service/Dockerfile # 多阶段构建——分离构建和运行——最终镜像只含 JRE # ===== Stage 1: 构建——用 Maven 编译 ===== FROM maven:3.9-eclipse-temurin-17 AS builder WORKDIR /build COPY pom.xml . # 先下载依赖——利用 Docker 缓存层——pom.xml 不变就不重新下载 RUN mvn dependency:go-offline -B COPY src ./src RUN mvn package -DskipTests -B # ===== Stage 2: 运行——只含 JRE——镜像小 ===== FROM eclipse-temurin:17-jre-alpine WORKDIR /app # 创建非 root 用户——安全最佳实践 RUN addgroup -S appgroup && adduser -S appuser -G appgroup # 从构建阶段复制 jar COPY --from=builder /build/target/*.jar app.jar # 切换到非 root 用户 USER appuser # Health check——K8s 会调用这个 HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ CMD wget -qO- http://localhost:8081/actuator/health || exit 1 EXPOSE 8081 ENTRYPOINT ["java", "-jar", "app.jar"] ⚠️ 新手提示:上面的 Dockerfile 是"CI 内编译"的方式——jar 包在 CI Pipeline 中由 Maven 打好——Dockerfile 只需要 COPY jar。还有一种方式是"Dockerfile 内编译"——CI 不编译——Dockerfile 用多阶段构建完成编译。两种方式的区别: ...

十二月 25, 2022 · 8 分钟 · 1615 字 · yaomingye

GitLab CI/CD 搭建与 Pipeline 语法精讲

把 15 步手动操作变成一次 git push 一、⚡ 周五下午 5 点上线——你手动执行了 15 步操作——到第 12 步出错了 回想一下你现在的发布流程: 发布一个微服务的流程: ① git pull latest ② mvn clean package -DskipTests("测试先跳过——着急") ③ 手动改 application-prod.yml 中的配置("这个值上次没改对") ④ docker build -t order-service:v1.2.3 . ⑤ docker tag order-service:v1.2.3 harbor.internal/order-service:v1.2.3 ⑥ docker push harbor.internal/order-service:v1.2.3 ⑦ ssh root@k8s-master ⑧ kubectl set image deployment/order-service order-service=harbor.internal/order-service:v1.2.3 ⑨ kubectl rollout status deployment/order-service ⑩ curl 验证——啊——404——服务没起来 ⑪ kubectl logs——发现是 application.yml 中的 Nacos 地址配错了 ⑫ kubectl rollout undo——回滚 ⑬ 改配置——重新来——docker build + push + deploy ⑭ 又发现 product-service 没同步上线——接口报错了 ⑮ 告警响了——用户已经在群里骂了 → 每次发布都像拆炸弹——不知道哪一步会出问题 CI/CD 要解决的就是:把人从 15 步中解放出来——每次 git push——自动编译、自动测试、自动构建镜像、自动部署——15 步变成 1 步。 ...

十二月 24, 2022 · 9 分钟 · 1883 字 · yaomingye

WSL2 Docker 数据持久化

☸️ WSL2 Docker 数据持久化:docker-desktop-data 缺失导致容器丢失的诊断与修复 📌 一、问题场景 在日常开发中,使用 Docker Desktop + WSL2 后端是一个常见组合。然而部分开发者在执行 wsl --shutdown 后,重新打开终端时发现一个严重问题: 之前创建的所有容器、镜像、数据卷全部消失 。 以下是一个典型的问题复现过程: # 1. 正常使用 Docker,创建测试容器 $ docker run -d --name my-app -p 8080:80 nginx Unable to find image 'nginx:latest' locally latest: Pulling from library/nginx ... cc3c2e0be814: Pull complete Status: Downloaded newer image for nginx:latest a1b2c3d4e5f6... # 容器启动成功 # 2. 确认容器正在运行 $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a1b2c3d4e5f6 nginx "/docker-entrypoint.…" 5 seconds ago Up 5 seconds 0.0.0.0:8080->80/tcp my-app # 3. 手动执行 WSL 关闭(或系统重启触发) $ wsl --shutdown # 4. 重新打开终端,检查容器 $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES # 输出为空 —— 所有容器消失! 这个场景的核心问题在于:Docker Desktop 在 WSL2 中的持久化数据没有被正确保存,导致 wsl --shutdown 后所有状态丢失。本文将深入分析根因并提供完整的修复方案。 ...

十月 10, 2022 · 7 分钟 · 1291 字 · yaomingye
Cat Radio