首页
直播
统计
壁纸
留言
友链
关于
Search
1
IPTV直播源分享地址
3,063 阅读
2
PVE开启硬件显卡直通功能
2,655 阅读
3
在k8s(kubernetes) 上安装 ingress V1.1.0
2,149 阅读
4
二进制安装Kubernetes(k8s) v1.24.0 IPv4/IPv6双栈
1,980 阅读
5
Ubuntu 通过 Netplan 配置网络教程
1,935 阅读
默认分类
登录
/
注册
Search
chenby
累计撰写
217
篇文章
累计收到
124
条评论
首页
栏目
默认分类
页面
直播
统计
壁纸
留言
友链
关于
搜索到
217
篇与
默认分类
的结果
2026-10-09
GitHub Actions 工作流 CI/CD 的“发布”
GitHub Actions 工作流 CI/CD 的“发布”围绕你的 file-server-docker 项目,把五件事串起来:镜像标签(Tag)Registry 登录docker tagdocker push版本回滚第一步:理解发布的完整流程先看整体链路:flowchart TD A["GitHub 代码仓库\n源代码 + Dockerfile"] --> B["docker build\n构建本地 Docker 镜像"] --> C["docker tag\n为同一个镜像增加仓库地址和版本标签"] --> D["Registry 登录\n获得向目标镜像仓库推送的权限"] --> E["docker push\n将镜像上传到阿里云 ACR"] --> F["部署服务器\n拉取指定版本,启动容器,验证健康状态"] 注意:docker tag 不负责上传镜像,docker push 才负责上传;Registry 登录也不会自动帮你构建镜像。第二步:镜像标签究竟是什么?假设你构建了一个镜像:docker build -t file-server:latest .这里的:file-server:latest是镜像名称和标签。可以把镜像理解成一个应用的完整打包结果,而标签相当于给这个打包结果贴上的名字。常见的标签有三类:标签示例用途浮动标签latest通常表示当前主推版本,但含义由发布流程约定版本标签v1.2.0便于人工识别正式版本提交标签sha-a1b2c3d将镜像与某次 Git 提交关联为什么不建议只使用 latest?假设你今天发布了:file-server:latest → 版本 A明天又发布了:file-server:latest → 版本 B同一个标签现在指向版本 B。如果服务器之前运行版本 A,而你只记录了 latest,之后想回滚时,就很难仅凭这个标签确定之前到底运行的是哪个版本。更好的方式是同时发布:file-server:latest file-server:sha-a1b2c3d下一次发布时:file-server:latest file-server:sha-e4f5g6h这样,latest 可以指向当前发布版本,而旧的提交标签仍然可以用于定位历史版本,前提是仓库没有删除或覆盖这些标签。建议:生产部署使用明确的版本标签或镜像 digest,latest 只作为辅助标签。第三步:Registry 登录到底做了什么?Registry 就是 Docker 镜像仓库。你可以把它理解成存放镜像的远程服务器。你当前项目使用的目标仓库命名空间是:registry.cn-hangzhou.aliyuncs.com/chenby假设镜像仓库名为 file-server,完整镜像地址就可以写成:registry.cn-hangzhou.aliyuncs.com/chenby/file-server:sha-a1b2c3d它由四部分组成:字段值Registry 地址registry.cn-hangzhou.aliyuncs.com命名空间 / 用户或组织空间chenby镜像仓库名file-server镜像标签sha-a1b2c3d1. 登录命令一般形式:docker login registry.cn-hangzhou.aliyuncs.com执行后,Docker 会要求输入用户名和密码或访问凭证。对于阿里云 ACR,具体凭证应使用该实例或仓库对应的登录凭证。登录的作用是让 Docker 获得相应仓库的访问权限。它不会构建镜像,也不会上传镜像。2. GitHub Actions 中怎样登录?不要把仓库密码直接写进 YAML 文件,更不要提交到 GitHub 仓库。在 GitHub 仓库的 Settings → Secrets and variables → Actions 中配置必要的 Secrets,例如:ACR_USERNAMEACR_PASSWORD然后在 Workflow 中使用登录 Action:- name: Login to ACR uses: docker/login-action@v3 with: registry: registry.cn-hangzhou.aliyuncs.com username: ${{ secrets.ACR_USERNAME }} password: ${{ secrets.ACR_PASSWORD }}这里有两个重要区别:registry 是仓库服务的地址,不是完整的镜像地址。secrets.ACR_PASSWORD 是从 GitHub Secrets 读取凭证,不是 YAML 中的明文密码。实际使用时,应为自动化任务创建权限尽可能小的专用凭证,并根据阿里云 ACR 的权限模型限制其访问范围。第四步:docker tag 到底干了什么?假设你先构建了本地镜像:docker build -t file-server:latest .查看本地镜像:docker images你可能看到:REPOSITORY TAG IMAGE ID file-server latest a1b2c3d4e5f6现在要把它发布到 ACR,需要为它增加一个完整的仓库地址。执行:docker tag file-server:latest \ registry.cn-hangzhou.aliyuncs.com/chenby/file-server:sha-a1b2c3d此时,你再执行:docker images可能看到:REPOSITORY TAG IMAGE ID file-server latest a1b2c3d4e5f6 registry.cn-hangzhou.aliyuncs.com/chenby/file-server sha-a1b2c3d a1b2c3d4e5f6注意两行的 IMAGE ID 相同。这意味着 docker tag 通常只是给同一个本地镜像增加另一个名称,而不是重新构建一份镜像,也不是把镜像上传到网络。可以把它想成给同一个文件增加一个新的引用名称:内容没变,只是增加了一个指向它的名字。这里最容易出错的地方docker tag 的格式是:docker tag SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]SOURCE_IMAGE:本地已经存在的镜像。TARGET_IMAGE:准备使用的新名称。TAG:可选的标签;如果省略,通常默认使用 latest。如果源镜像不存在,就会报错。docker tag 不会替你从 Dockerfile 构建镜像。第五步:docker push 才是真正上传前面已经完成:构建镜像。登录 ACR。为镜像添加完整地址和版本标签。现在执行:docker push \ registry.cn-hangzhou.aliyuncs.com/chenby/file-server:sha-a1b2c3dDocker 会将目标镜像所需的数据层上传到对应仓库,并发布该标签的引用。流程是:flowchart TD A["本地镜像\nfile-server:latest"] --> B["docker tag\n增加 ACR 地址和版本标签"] --> C["docker push\n上传到远程镜像仓库"] --> D["ACR 远程仓库\n其他有权限的机器可以拉取该版本"] 推送成功后,你可以在 ACR 控制台中查看镜像仓库和对应标签。但是,推送成功只说明镜像已经发布到仓库,并不代表生产服务器已经运行这个版本。服务器还需要执行拉取和部署。把这几个命令放在一起看下面是一个从构建到推送的完整示例。请确保目标仓库 chenby/file-server 已经创建,并且你有相应权限。# 1. 构建镜像 docker build -t file-server:latest . # 2. 登录仓库 docker login registry.cn-hangzhou.aliyuncs.com # 3. 添加版本标签 docker tag file-server:latest \ registry.cn-hangzhou.aliyuncs.com/chenby/file-server:sha-a1b2c3d # 4. 推送镜像 docker push \ registry.cn-hangzhou.aliyuncs.com/chenby/file-server:sha-a1b2c3d这四步各司其职:命令作用docker build构建本地镜像docker login认证仓库访问权限docker tag为镜像增加目标名称和标签docker push上传镜像到远程仓库注意:上面的 sha-a1b2c3d 是示例标签。真实的 GitHub Actions 应当使用实际提交 SHA 或其他明确的版本标识,不能把示例字符串直接当作真实版本。第六步:服务器怎样拉取并运行指定版本?镜像推送到 ACR 后,服务器可以执行:docker pull \ registry.cn-hangzhou.aliyuncs.com/chenby/file-server:sha-a1b2c3d然后通过 Docker Compose 指定该版本。例如,Compose 文件可以使用环境变量:services: file-server: image: ${FILE_SERVER_IMAGE} restart: unless-stopped ports: - "8080:80" volumes: - ./uploads:/data/uploads这里的端口、容器路径和服务名称都是示例,必须与你项目的实际配置一致。部署时可以这样指定镜像:export FILE_SERVER_IMAGE="registry.cn-hangzhou.aliyuncs.com/chenby/file-server:sha-a1b2c3d" docker compose pull docker compose up -ddocker compose pull 会拉取 Compose 配置中指定的镜像,docker compose up -d 会按配置创建或更新容器。有个细节要注意:export 只在当前 Shell 会话及其子进程中生效。如果你在另一个终端或另一次 SSH 会话中执行命令,需要重新设置变量,或者使用专门的部署环境文件。第七步:如何实现版本回滚?假设你先后发布了两个版本:flowchart TD A["旧版本:sha-a1b2c3d\n已验证可用,应该保留作为回滚目标。"] --> B["新版本:sha-e4f5g6h\n已经推送并尝试部署,但健康检查失败。"] --> C["恢复旧版本\n重新指定旧标签,拉取并更新服务,然后再次验证健康状态。"] 如果 Compose 使用前面的 FILE_SERVER_IMAGE 变量,回滚可以是:export FILE_SERVER_IMAGE="registry.cn-hangzhou.aliyuncs.com/chenby/file-server:sha-a1b2c3d" docker compose pull docker compose up -d这会将 Compose 配置指向旧版本。前提是旧镜像仍然存在于仓库中,且服务器可以访问它。回滚并不等于删除新镜像。 新镜像可以留在仓库中供排查问题;真正重要的是让服务重新运行已知可用的版本。还要特别注意:用户上传的文件应存放在持久化卷或可靠的外部存储中。如果新版本修改了数据库结构或文件格式,单纯回滚镜像可能不够。docker compose up -d 返回成功后,还需要检查容器健康状态和业务接口。如果部署过程中旧容器已经被替换,恢复旧版本仍可能产生短暂中断。需要更高可用性时,要设计新旧实例并行运行、流量切换等策略。第八步:把整个发布流程记成一句话构建镜像 → 登录 Registry → 为镜像打版本标签 → 推送镜像 → 服务器拉取指定版本 → 部署并检查 → 失败则恢复旧版本。你可以用下面的交互小测验检验一下自己是否真正理解了。发布流程小测验你已经执行了 docker build 和 docker tag,并且成功登录了 ACR。接下来要把镜像真正上传到仓库,应该执行哪个命令?A. docker runB. docker pushC. docker tagD. docker compose up -d<details><summary>点击查看答案</summary>正确答案:B. docker pushdocker push 会将本地镜像推送到远程 Registry。前面的 docker tag 只是为镜像增加目标名称,并不会上传。docker run:用于创建并启动容器,不是上传镜像。docker tag:只修改镜像引用名称,不负责网络上传。docker compose up -d:用于根据 Compose 配置创建或更新服务,不负责推送镜像到 ACR。</details>
2026年10月09日
2 阅读
0 评论
0 点赞
2026-10-09
GitHub Actions 工作流 CI/CD 触发器与执行顺序
GitHub Actions 工作流 CI/CD 触发器与执行顺序一、先理解 GitHub Actions 是怎么工作的可以把 GitHub Actions 理解成 GitHub 提供的一套自动化执行系统。你平时可能会这样操作:修改项目代码。把代码提交到 Git。将代码推送到 GitHub。手动构建 Docker 镜像。把镜像推送到阿里云 ACR。登录服务器,更新容器。GitHub Actions 的作用,就是把这些步骤写进配置文件,让 GitHub 在指定的条件满足时,自动执行这些任务。整体过程如下:flowchart TD A["发生某个事件\n例如 push、Pull Request、定时任务"] --> B["匹配 Workflow 的触发条件\n检查事件类型、分支、路径等规则"] B --> C["创建一次 Workflow 运行\nGitHub 根据 Jobs 安排执行任务"] C --> D["启动 Runner 执行 Jobs\nRunner 是实际运行命令的执行环境"] D --> E["测试 → 构建 → 推送 → 部署\n具体执行哪些步骤,由你的配置文件决定"] 这里要先区分三个概念:名称含义举例Event(事件)什么事情发生了推送代码到 mainWorkflow(工作流)一份自动化任务配置docker-ci.ymlRunner(执行器)实际运行命令的机器或环境ubuntu-latest最重要的一点:GitHub Actions 不会因为你修改了本地代码就自动启动。 它需要 GitHub 收到符合条件的事件,或者有人手动触发等符合配置的触发方式。二、GitHub Actions 到底在什么情况下启动?控制触发条件的主要配置是 on。例如:name: File Server CI/CD on: push: branches: - main这段配置的意思是:当有代码推送到 main 分支时,触发这个 Workflow。注意,这里不是说只有你本人推送才触发。只要 GitHub 收到符合条件的 push 事件,就可能触发;推送者可以是你,也可以是其他有权限的人或自动化程序。1. push:推送代码时触发例如你本地执行:git add . git commit -m "Update upload feature" git push origin main过程是:本地修改代码。git commit 创建本地提交。git push 把提交推送到 GitHub。GitHub 收到 push 事件。检查 Workflow 的 on.push 条件。如果条件匹配,创建一次 Workflow 运行。但如果配置是:on: push: branches: - main你推送到 dev 分支,就不会因为这条规则触发。还有一个容易忽略的细节:路径过滤on: push: branches: - main paths: - "src/**" - "Dockerfile"这意味着要同时满足:推送目标分支是 main;本次推送涉及的文件路径符合 src/** 或 Dockerfile。比如,只修改 README.md,就不会匹配这条 push 规则。路径过滤适合大型项目,避免修改文档也重新构建所有镜像。不过,如果你给 Dockerfile、依赖文件或 Workflow 自身设置了不完整的路径规则,也可能漏掉本来应该执行的构建。2. pull_request:有人创建或更新 Pull Request 时触发例如:on: pull_request: branches: - main这里的 branches: main 指的是 Pull Request 的目标分支,不是你创建分支时所在的源分支。假设:feature/upload ──────┐ ├── Pull Request → main feature/login ──────┘有人创建一个从 feature/upload 合并到 main 的 Pull Request,或者向这个 PR 对应的分支继续推送提交,都可能触发检查。常见用途:检查代码能否编译。执行单元测试。构建 Docker 镜像,验证 Dockerfile 是否有效。在合并到 main 之前拦截错误。通常推荐把流程分成两类:PR 阶段: 测试和验证,不直接部署生产环境。合并或推送到 main 后: 发布正式镜像,再决定是否部署。这样可以避免一段尚未审核的代码直接影响正在运行的文件服务器。3. workflow_dispatch:手动启动配置:on: workflow_dispatch:它允许你在 GitHub 仓库的 Actions 页面手动启动工作流。常见用途:重新构建镜像。手动发布指定版本。重新执行部署。执行维护任务。还可以设置手动输入参数,例如选择发布版本。但要注意,输入参数只是数据,不会自动改变部署逻辑;你需要在 Workflow 中明确使用它们。还有一个小细节:手动启动的 Workflow 文件通常需要存在于仓库的默认分支中,才能正常从 Actions 页面触发。4. schedule:定时启动例如:on: schedule: - cron: "0 2 * * *"这是一个 Cron 表达式,表示每天在 UTC 时间 02:00 运行。如果你的服务器或团队按北京时间工作,记得转换时区。北京时间是 UTC+8,所以这个例子对应北京时间每天上午 10:00。定时任务适合:定期检查依赖。清理构建缓存。检查外部服务。定时同步镜像。不过,GitHub Actions 的定时任务可能延迟执行,并不适合要求秒级精度的调度。5. tags:发布版本时触发例如:on: push: tags: - "v*"当推送符合 v* 的标签时,例如:git tag v1.0.0 git push origin v1.0.0就会触发匹配的工作流。这种方式适合正式版本发布:v1.0.0 → 构建镜像 → 推送 ACR → 部署 v1.1.0 → 构建镜像 → 推送 ACR → 部署但请注意,Git 标签和 Docker 镜像标签是两个不同的东西。前者标记 Git 中的代码版本,后者标记镜像仓库中的镜像。你需要在工作流中把它们关联起来。6. 一个 Workflow 可以配置多个触发条件例如:on: push: branches: - main pull_request: branches: - main workflow_dispatch:这表示同一份 Workflow 可以由三种不同事件启动:向 main 推送代码。向 main 提交或更新 PR。人工点击运行。但这并不意味着三种情况都会执行完全相同的操作。你可以通过 if、事件类型判断、分支判断等方式决定是否发布或部署。例如,PR 适合执行测试,而生产部署通常应限制在可信的分支或受保护的发布环境中。三、启动之后,代码到底按照什么顺序执行?这是整个 GitHub Actions 最值得理解的部分。一个 Workflow 的结构通常是:Workflow ├── Job A │ ├── Step 1 │ ├── Step 2 │ └── Step 3 ├── Job B │ ├── Step 1 │ └── Step 2 └── Job C ├── Step 1 └── Step 2这里有两个不同层次的执行规则:Job 内部的 Step: 默认按配置顺序依次执行。Job 与 Job 之间: 默认可以并行执行;如果设置了 needs,则按依赖关系执行。这两个规则一定要分开理解。1. 同一个 Job 内部:Step 按顺序执行看这个例子:jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Show files run: ls -la - name: Build image run: docker build -t file-server:latest . - name: Show result run: docker images执行顺序是固定的:flowchart TD S1["1. Checkout\n把仓库代码检出到 Runner 工作目录。"] --> S2["2. Show files\n列出当前目录的文件,确认代码是否存在。"] --> S3["3. Build image\n读取 Dockerfile,尝试构建镜像。"] --> S4["4. Show result\n列出已构建的镜像。"] 为什么 Checkout 通常要放在前面?因为后面的步骤要读取仓库中的 Dockerfile、源代码和配置文件。如果没有检出代码,Runner 的工作目录通常没有你的项目文件,后续构建就可能失败。uses 和 run 也有区别:- uses: actions/checkout@v4表示使用一个现成的 Action。- run: docker build -t file-server:latest .表示让 Runner 的 Shell 执行一条命令。它们都是 Step,并且默认遵守同一套顺序规则。2. 如果某个 Step 失败,后面的 Step 怎么办?继续看:steps: - name: Step A run: echo "A" - name: Step B run: exit 1 - name: Step C run: echo "C"执行结果:Step结果Step A成功Step B失败,退出码为 1Step C默认跳过为什么?GitHub Actions 默认会跳过后续那些因为前面失败而不应继续执行的普通步骤。这里的 exit 1 是主动返回失败状态。一般来说,Shell 命令返回 0 表示成功,非零值表示失败。但不是所有失败都会让整个流程永久终止。你可以通过条件表达式明确规定后续动作。例如,失败后仍然收集日志:- name: Run tests run: ./run-tests.sh - name: Collect logs if: ${{ failure() }} run: ./collect-logs.sh这样,即使测试失败,日志收集步骤仍然可以执行。另外,continue-on-error: true 可以让某个步骤的失败不按普通失败方式阻断后续流程。不过,这会改变失败状态的处理方式,不能随便用在生产发布的关键步骤上。3. 不同 Job 之间:默认可以并行执行假设你写了:jobs: test: runs-on: ubuntu-latest steps: - run: echo "Run tests" build: runs-on: ubuntu-latest steps: - run: echo "Build image" lint: runs-on: ubuntu-latest steps: - run: echo "Check code style"这三个 Job 之间没有依赖关系。GitHub Actions 可以在有可用 Runner 和并发额度时,让它们并行执行,而不是保证 test 一定先于 build。可能的执行顺序是:test ─────────────── 完成 build ─────── 完成 lint ────────── 完成也可能是另一种顺序。注意:可以并行,不代表一定同时运行。 并发限制、Runner 可用性和排队情况都会影响实际开始时间。如果你要求先测试、再构建、最后部署,就不能只依靠 YAML 中的书写顺序。你需要用 needs 明确表达依赖关系。4. needs:决定 Job 之间的先后顺序这是 CI/CD 中最重要的配置之一。例如:jobs: test: runs-on: ubuntu-latest steps: - run: echo "1. Test" build: needs: test runs-on: ubuntu-latest steps: - run: echo "2. Build image" deploy: needs: build runs-on: ubuntu-latest steps: - run: echo "3. Deploy"这次执行顺序就被明确规定了:flowchart TD T["Job: test\n测试代码"] --> B["Job: build\n等待 test 成功后才执行"] B --> D["Job: deploy\n等待 build 成功后才执行"] 这里的关键是:needs: test它表示当前 Job 依赖 test。在没有覆盖默认条件的情况下,依赖 Job 必须成功,当前 Job 才会执行。如果 test 失败:test:失败。build:跳过。deploy:跳过。这就是为什么生产环境通常应该设计成:测试成功 → 构建成功 → 镜像推送成功 → 部署 → 健康检查。不应该出现测试失败了,却仍然把新版本发布到生产环境的情况。如果多个 Job 都依赖同一个 Job 呢?jobs: test: runs-on: ubuntu-latest steps: - run: echo "Test" build: needs: test runs-on: ubuntu-latest steps: - run: echo "Build" security: needs: test runs-on: ubuntu-latest steps: - run: echo "Security scan" deploy: needs: [build, security] runs-on: ubuntu-latest steps: - run: echo "Deploy"执行关系变成:flowchart TD T["test"] --> B["build\n构建镜像"] T --> S["security\n安全扫描"] B --> D["deploy\n等待 build 和 security 都成功"] S --> D build 和 security 之间没有依赖,因此可以并行执行。而 deploy 使用:needs: [build, security]意味着它需要等待两个 Job 都完成,而且在默认条件下,两个 Job 都必须成功。这就叫 依赖图(DAG,Directed Acyclic Graph,有向无环图)。GitHub Actions 根据这张依赖图调度 Job,而不是简单地从上到下逐行执行整个文件。四、一次完整的 GitHub Actions 运行,时间线是什么样的?假设你给 file-server-docker 配置了以下流程:你推送代码到 main │ ▼ GitHub 收到 push 事件 │ ▼ 匹配 Workflow 的触发条件 │ ▼ 创建 Workflow 运行 │ ▼ Runner 执行 test Job │ ▼ 测试成功? ┌────┴────┐ │ │ 失败 成功 │ │ ▼ ▼ 停止后续 build Job 发布流程 │ ▼ 构建 Docker 镜像 │ ▼ 推送镜像到 ACR │ ▼ deploy Job │ ▼ 服务器更新容器 │ ▼ 健康检查 ┌────┴────┐ │ │ 失败 成功 │ │ ▼ ▼ 回滚 发布完成这是一个概念性的完整流程。实际配置中,你可以把镜像构建和推送放在同一个 Job,也可以拆成多个 Job;回滚也需要你明确编写脚本或调用相应的部署机制。五、再补充几个容易搞混的细节1. 每个 Job 的 Runner 不一定是同一台机器如果你使用:runs-on: ubuntu-latestGitHub 会为 Job 分配符合要求的 Runner 环境。如果 test、build 和 deploy 是不同 Job,不能默认认为它们共享同一个本地文件系统。例如,build Job 生成了一个 file-server.tar 文件,后面的 deploy Job 不会自动获得这个文件。你需要使用构建产物(Artifacts)、镜像仓库,或者其他明确的数据传递方式。对于 Docker 项目,常见方式是:构建 Job 生成镜像。把镜像推送到 ACR。部署 Job 通过服务器拉取同一个镜像版本。这样,部署环境拿到的是仓库中的镜像,而不是依赖前一个 Runner 的本地状态。2. 同一个仓库的不同 Workflow 也不一定有顺序关系假设你有两个文件:.github/workflows/test.yml .github/workflows/deploy.yml两者都配置了:on: push: branches: - main一次向 main 推送的操作可能同时触发这两个 Workflow。不能认为 test.yml 一定先执行,deploy.yml 一定后执行。即使你在 GitHub 仓库里把测试 Workflow 文件放在前面,也不会因此获得执行顺序保证。如果部署必须依赖测试结果,就应该把测试和部署放在有明确依赖关系的同一工作流中,或者使用 workflow_run 等机制建立跨 Workflow 的触发关系,并谨慎检查触发事件和安全权限。3. 重新运行失败的 Workflow,不代表重新使用最新代码在 GitHub Actions 中,重新运行一次历史 Workflow,通常会针对原来那次运行关联的提交重新执行,而不是自动切换到仓库最新的提交。这对排错很有用:你可以确认同一份代码是否由于暂时的网络故障或仓库不可用而失败。不过,如果你希望部署最新版本,就应该明确选择要发布的提交或镜像版本,而不是把“重新运行旧工作流”当成“发布最新代码”。4. if 可以改变默认执行条件例如:- name: Notify failure if: ${{ failure() }} run: echo "Something failed"这个步骤在前面的步骤失败时执行,适合发通知或收集日志。而:- name: Always collect diagnostics if: ${{ always() }} run: echo "Collect diagnostics"会尝试无论之前成功还是失败都执行诊断步骤。但如果 Runner 被强制终止、任务被取消或环境不可用,它仍然不能保证一定运行。生产部署中不要随意使用 always() 覆盖依赖关系,否则可能让原本应该被阻止的后续操作在失败后继续执行。六、用一个小实验彻底理解执行顺序你可以直接在测试分支中创建一个简单的 Workflow,观察 GitHub Actions 的运行日志。name: Execution Order Demo on: workflow_dispatch: jobs: first: runs-on: ubuntu-latest steps: - name: Step A run: echo "A - first job" - name: Step B run: echo "B - second step" second: needs: first runs-on: ubuntu-latest steps: - name: Step C run: echo "C - dependent job" third: runs-on: ubuntu-latest steps: - name: Step D run: echo "D - independent job"这个实验里:first 内的 A 一定先于 B。second 必须等待 first 成功。third 与 first 没有依赖,可以独立调度,不保证它在 first 前还是后运行。只有手动触发时,这份 Workflow 才会因这里的 workflow_dispatch 配置而启动。你可以分别在 GitHub Actions 页面查看三个 Job 的运行时间和日志。如果想进一步观察失败传播,可以把 Step A 临时改成 run: exit 1,再观察后续 Job 的状态。实验结束后记得恢复。最后,把这四条记牢你想知道的问题应该看哪里什么时候启动?on一个 Job 内先做什么?steps 的排列顺序不同 Job 谁先执行?needs 依赖关系失败后还会不会继续?默认失败行为、if、continue-on-error如果只记住一句话,那就是:on 决定什么时候触发,needs 决定 Job 之间的依赖,steps 决定 Job 内部的执行顺序,而退出状态和条件表达式决定失败后怎么处理。理解这四点之后,你再看复杂的 Docker 构建、ACR 镜像推送、SSH 部署和自动回滚 Workflow,就不会再觉得它是一大堆看不懂的 YAML 了。
2026年10月09日
3 阅读
0 评论
0 点赞
2026-10-09
微WEB文件服务 Docker 部署指南
微WEB文件服务 Docker 部署指南本文介绍如何使用 Docker Compose 部署完整的文件上传/下载服务。完整部署包含 两个镜像:应用镜像(app):运行 Python / Gunicorn 后端,处理登录、上传、分片上传及文件下载等 API。网页入口镜像(nginx):提供网页静态文件、HTTPS 入口,并将 API 请求转发给应用容器。CI 会将这两个镜像分别发布到阿里云 ACR 和 GitHub Container Registry(GHCR)。每个镜像包含三个标签:latest、Git 提交短 SHA、北京时间构建时间戳(YYYYMMDD-HHMMSS)。这些是同一镜像的不同标签,不是额外的独立镜像。一、镜像地址阿里云 ACR(默认)用途镜像地址应用后端registry.cn-hangzhou.aliyuncs.com/chenby/file-server-docker:latestNginx 网页入口registry.cn-hangzhou.aliyuncs.com/chenby/file-server-docker-nginx:latestGitHub Container Registry(可选)用途镜像地址应用后端ghcr.io/cby-chen/file-server-docker:latestNginx 网页入口ghcr.io/cby-chen/file-server-docker-nginx:latest如果 GHCR 镜像是私有的,请先执行 docker login ghcr.io,并使用有权限读取该 Package 的 GitHub 令牌登录。二、服务器准备服务器需要安装 Docker Engine 和 Docker Compose v2(可使用 docker compose 命令)。以下命令以 Linux 服务器为例。克隆仓库并进入目录:git clone https://github.com/cby-chen/file-server-docker.git cd file-server-docker创建文件持久化目录:sudo mkdir -p /data/files sudo chown -R "$USER":"$USER" /data/files上传文件会保存在宿主机的 /data/files 中。容器重建或升级时,只要不删除这个目录,文件就会保留。三、配置环境变量复制示例配置:cp .env.example .env编辑 .env,至少修改登录密码和密钥:FILE_USER=admin FILE_PASSWORD=请替换为高强度且独立的密码 SECRET_KEY=请替换为足够长的随机字符串 MAX_UPLOAD_SIZE=50GB UPLOAD_CHUNK_SIZE=16MB UPLOAD_CONCURRENCY=4 SESSION_TIMEOUT_HOURS=12 # 默认使用阿里云 ACR;如需使用 GHCR,请改为下面两行 APP_IMAGE=registry.cn-hangzhou.aliyuncs.com/chenby/file-server-docker:latest NGINX_IMAGE=registry.cn-hangzhou.aliyuncs.com/chenby/file-server-docker-nginx:latest生成强随机密钥的示例:openssl rand -hex 32把生成的字符串填入 SECRET_KEY。请妥善保存 .env,不要提交到 Git 仓库,也不要把密码或密钥发给他人。如需改用 GHCR,将 .env 中的镜像地址改为:APP_IMAGE=ghcr.io/cby-chen/file-server-docker:latest NGINX_IMAGE=ghcr.io/cby-chen/file-server-docker-nginx:latestDocker Compose 会从项目目录中的 .env 文件读取用于镜像地址和环境变量替换的配置。更多说明可参考 Docker 官方环境变量文档。四、配置 HTTPS 证书Nginx 配置要求以下两个文件存在于服务器的 certs/ 目录:certs/server.crtcerts/server.key仓库提供了生成自签名证书的脚本。在仓库根目录运行:cd certs sh generate-cert.sh cd ..此脚本生成的自签名证书仅适合测试或内网临时使用,浏览器会显示证书安全警告。正式对公网提供服务时,请将其替换为域名对应的可信 TLS 证书,并确保文件名仍为 server.crt 和 server.key。五、启动完整服务在仓库根目录执行:docker compose pull docker compose up -d查看容器状态:docker compose ps docker compose logs --tail=100 app docker compose logs --tail=100 nginx检查后端健康状态:docker compose exec app python -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:5000/health').read().decode())"正常情况下,健康检查返回 ok。六、访问网站和防火墙当前 docker-compose.yml 默认映射端口:HTTP:宿主机 17001,访问后会重定向到 HTTPS 的 17002 端口。HTTPS:宿主机 17002。因此访问地址为:http://服务器IP:17001https://服务器IP:17002请在服务器防火墙和云厂商安全组中按需放行 TCP 17001、17002。生产环境建议使用域名和可信证书;如果希望使用标准的 80/443 端口,需要相应调整 Compose 端口映射及 Nginx 的重定向配置。不要将应用容器的 5000 端口直接暴露到公网。当前 Compose 仅在内部网络中向 Nginx 暴露该端口。七、升级镜像发布新版本后,在服务器仓库目录执行:docker compose pull docker compose up -d docker image prune -fdocker compose pull 会拉取 .env 指定的两个镜像,docker compose up -d 会按新镜像重建容器。升级前建议备份 /data/files 和 .env。如果希望固定到某次构建,而不是始终使用 latest,可以把 .env 中的两个镜像标签改成同一次构建对应的短 SHA 标签或北京时间标签,然后执行上述升级命令。八、GitHub Actions 自动构建与发布每次向 main 分支推送代码,或手动触发工作流,GitHub Actions 会执行代码检查、构建两个镜像、运行应用健康检查、检查 Nginx 配置,然后发布到配置的镜像仓库。工作流文件:.github/workflows/docker-ci.ymlACR 配置在 GitHub 仓库的 Settings → Secrets and variables → Actions 中配置以下 Repository variables:ACR_REGISTRY:例如 registry.cn-hangzhou.aliyuncs.com,必须与 ACR 控制台显示的实际仓库域名一致。ACR_IMAGE:应用镜像的完整仓库路径,不包含标签,例如 registry.cn-hangzhou.aliyuncs.com/chenby/cby。配置以下 Repository secrets:ACR_USERNAME:ACR 镜像仓库登录用户名。ACR_PASSWORD:ACR 镜像仓库访问密码或专用凭证,不一定等于阿里云控制台登录密码。工作流会将应用镜像推送到 ACR_IMAGE,将 Nginx 镜像推送到 ACR_IMAGE 后追加 -nginx 的仓库名。例如应用仓库为 registry.cn-hangzhou.aliyuncs.com/chenby/cby 时,Nginx 仓库为 registry.cn-hangzhou.aliyuncs.com/chenby/cby-nginx。GHCR 使用工作流的 GITHUB_TOKEN 推送,无需额外配置个人令牌;工作流已声明 packages: write 权限。查看构建状态:GitHub Actions九、常见问题1. 提示缺少 FILE_PASSWORD 或 SECRET_KEY检查仓库根目录下是否存在 .env,并确认这两个变量已设置为非空值。修改后重新执行:docker compose up -d2. 拉取镜像时提示 denied 或 unauthorized检查镜像仓库地址、标签和访问权限。私有 GHCR Package 需要先登录;ACR 私有仓库也需要使用有拉取权限的凭据登录。3. 网站无法访问或 HTTPS 报错检查容器状态和日志,确认 certs/server.crt、certs/server.key 存在,并检查服务器防火墙和云安全组是否放行 17001/17002 端口。自签名证书出现浏览器警告属于预期现象。4. 网页能打开,但上传或 API 报错检查两个容器是否都在运行,并查看 docker compose logs --tail=100 app nginx。不要把应用容器的 5000 端口暴露到公网。5. 上传文件在重建容器后不见了确认宿主机的 /data/files 挂载正确,并且没有删除宿主机文件目录。重要数据应另行备份。十、常用维护命令# 查看容器 docker compose ps # 查看实时日志 docker compose logs -f # 重启服务 docker compose restart # 停止并移除容器(不会删除绑定挂载中的 /data/files) docker compose down # 查看 Compose 最终解析出的配置(注意输出可能包含敏感环境变量) docker compose config注意:docker compose down 不会删除宿主机的 /data/files;请勿手动删除该目录,除非确定不再需要其中的数据。
2026年10月09日
4 阅读
0 评论
0 点赞
2026-10-09
GitHub Actions + Docker + ACR + GHCR 自动化构建与发布
GitHub Actions + Docker + ACR + GHCR 自动化构建与发布GitHub Actions + Docker + 阿里云 ACR + GitHub Container Registry(GHCR)自动化构建与发布工作流。一、先理解 CI/CD 到底是什么CI(持续集成):代码提交后,自动检查代码是否有问题、能否构建、基本功能能否运行。CD(持续交付 / 持续部署):把验证通过的程序交付出去,例如发布 Docker 镜像;如果进一步自动更新生产服务器,才是完整的自动部署流程。你的这个工作流已经包含了自动测试和镜像发布,但从这份 YAML 来看,它没有直接登录你的生产服务器,也没有执行服务器上的 docker compose pull 或 docker compose up -d。所以它完成的是“构建、测试、发布镜像”,不是自动更新运行中的服务。GitHub Actions 的核心机制是:事件触发工作流,工作流运行一个或多个 Job,每个 Job 再依次执行 Steps。二、你的工作流具体做了什么?查看这份文件。它只有一个 Job,但包含多个按顺序执行的步骤。阶段你的工作流做什么目的1. 触发监听 main 分支的 push,也支持手动运行自动启动流水线2. 获取代码Checkout 仓库把代码下载到临时机器3. 前置检查检查文件和 ACR 配置提前发现配置错误4. 代码检查设置 Python 3.12,检查 Python 语法避免低级错误5. 构建应用镜像构建 Flask/Python 应用镜像验证应用能否打包6. 冒烟测试启动应用容器,访问 /health验证应用基本可用7. 构建 Nginx 镜像构建 Web 入口镜像验证 Nginx 能否打包8. 配置检查执行 nginx -t检查 Nginx 配置9. 镜像发布登录两个仓库,推送镜像发布可供部署的镜像这里有一个特别值得学习的设计:测试通过之前,不会执行最后的镜像发布步骤。 如果前面的步骤失败,后续普通步骤默认会跳过。1. 为什么是两个镜像?应用镜像(app)包含 Python 应用及依赖,负责文件服务器的后端逻辑。示例:file-server-app-ci:版本号Web 镜像(nginx)包含 Nginx 和相关配置,负责 Web 入口、静态页面或反向代理。示例:file-server-nginx-ci:版本号这两个镜像会分别推送到两个仓库,因此按这份脚本的逻辑,最终有 4 个镜像仓库路径,每个路径 3 个标签,共 12 次镜像标签推送操作。这不代表构建了 12 个不同的镜像内容:同一个镜像的不同标签通常指向相同的镜像内容。三、理解最重要的几个概念1. on:什么情况下执行?on: push: branches: - main workflow_dispatch:push:有人向仓库推送代码时触发。branches: - main:只响应 main 分支的 push。workflow_dispatch:允许你在 GitHub 的 Actions 页面手动点击运行。注意:这并不意味着所有分支的代码都会自动发布。比如你在 dev 分支提交代码,默认不会触发这个工作流;合并到 main 后才会触发。2. jobs 和 steps:流水线与具体任务jobs: build-test-publish: runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v4可以这样理解:jobs:定义需要执行的任务。build-test-publish:Job 的内部标识符。runs-on:指定运行环境,这里是 GitHub 托管的 Ubuntu Linux 机器。steps:任务中的具体步骤。uses:调用别人已经编写好的 Action。run:直接执行 Shell 命令。例如,actions/checkout@v4 就是一个可复用的 Action,负责将仓库代码检出到工作目录。3. ${{ ... }} 和 $VARIABLE 有什么区别?这是阅读 GitHub Actions 时很容易混淆的地方。写法由谁处理示例${{ github.sha }}GitHub Actions 表达式引擎获取本次提交的完整 SHA${{ vars.ACR_IMAGE }}GitHub Actions 表达式引擎读取仓库变量${{ secrets.ACR_PASSWORD }}GitHub Actions 表达式引擎读取密码 Secret$ACR_IMAGEBash Shell读取已注入环境的变量${GITHUB_SHA}Bash Shell读取当前提交 SHA 对应的环境变量"$TAG"Bash Shell读取标签变量,并防止普通空格导致参数拆分比如:env: ACR_IMAGE: ${{ vars.ACR_IMAGE }} run: | docker push "${ACR_IMAGE}:latest"GitHub Actions 先把仓库变量注入环境,Bash 执行命令时再读取 $ACR_IMAGE。记住:${{ ... }} 是工作流表达式,$VARIABLE 是 Shell 变量。 两者处于不同的处理阶段。4. vars、secrets 和 GITHUB_TOKEN你的工作流使用了这几种配置:名称类型用途vars.ACR_REGISTRY仓库变量阿里云 ACR 的 Registry 地址vars.ACR_IMAGE仓库变量应用镜像的完整仓库路径secrets.ACR_USERNAMESecret阿里云仓库用户名secrets.ACR_PASSWORDSecret阿里云仓库密码或访问凭证secrets.GITHUB_TOKENGitHub 提供的令牌工作流登录 GHCR 时使用变量与 Secret 的关键区别是:普通配置通常放在 vars,敏感凭证放在 secrets。不要把真实密码直接写进 YAML。你的配置还包含:permissions: contents: read packages: writecontents: read 允许工作流读取仓库内容;packages: write 允许对应的 GITHUB_TOKEN 写入 GitHub Packages,包括 GHCR 镜像发布所需的权限。阿里云 ACR 的认证则另外使用 ACR_USERNAME 和 ACR_PASSWORD。四、 docker-ci.yml 添加详细中文注释下面是根据你当前文件整理的完整中文注释版。我保留了原有的触发条件、构建参数、测试逻辑、镜像名称和推送规则,主要增加注释,便于你逐行学习。有一点要注意:这是一份学习用的注释版,不是已经写回 GitHub 仓库的修改。# ============================================================ # 工作流名称 # 在 GitHub 仓库的 Actions 页面中显示这个名称 # ============================================================ name: File Server CI and Publish # ============================================================ # 触发条件:什么情况下启动 CI/CD # ============================================================ on: # 当代码被 push 到指定分支时触发 push: branches: - main # 只监听 main 分支 # 允许在 GitHub Actions 页面手动启动工作流 workflow_dispatch: # ============================================================ # 工作流权限 # 这里设置的是 GitHub 自动提供的 GITHUB_TOKEN 权限 # ============================================================ permissions: contents: read # 允许读取仓库代码 packages: write # 允许向 GitHub Packages / GHCR 发布镜像 # ============================================================ # 并发控制 # 防止同一组工作流同时执行 # ============================================================ concurrency: group: file-server-publish-main # 同组工作流使用相同的并发标识 cancel-in-progress: false # 新任务不会主动取消正在运行的旧任务 # ============================================================ # Jobs:定义工作流需要执行的任务 # 当前只有一个 Job,内部包含多个按顺序执行的 Step # ============================================================ jobs: build-test-publish: name: Build, test and publish # 在 Actions 页面显示的任务名称 # 使用 GitHub 托管的 Ubuntu Linux 环境 # 每次运行会分配干净的临时运行环境 runs-on: ubuntu-latest # 整个 Job 最多运行 30 分钟,超时会被终止 timeout-minutes: 30 # ======================================================== # Steps:具体执行步骤 # 同一个 Job 内的步骤按顺序运行 # 普通步骤失败后,后续步骤默认不会继续执行 # ======================================================== steps: # ------------------------------------------------------ # 第 1 步:下载仓库代码 # ------------------------------------------------------ - name: Checkout source # 使用 GitHub 官方维护的 checkout Action uses: actions/checkout@v4 # ------------------------------------------------------ # 第 2 步:检查项目文件和镜像仓库配置 # ------------------------------------------------------ - name: Validate repository and registry configuration shell: bash # 将 GitHub 仓库变量传入当前 Shell 步骤 env: ACR_REGISTRY: ${{ vars.ACR_REGISTRY }} ACR_IMAGE: ${{ vars.ACR_IMAGE }} # 多行 Shell 脚本 run: | # -e:命令失败时退出 # -u:使用未定义变量时退出 # -o pipefail:管道中任意命令失败,管道结果也视为失败 set -euo pipefail # 检查构建所需的文件是否存在 # 任意文件不存在,test 就会返回非零状态 test -f app/Dockerfile test -f app/app.py test -f app/requirements.txt test -f nginx/Dockerfile test -f nginx/nginx.conf test -f html/index.html test -f docker-compose.yml # 检查 ACR Registry 配置是否为空 # 为空时打印 GitHub Actions 错误信息并退出 test -n "$ACR_REGISTRY" || { echo "::error::Set Actions variable ACR_REGISTRY first" exit 1 } # 检查应用镜像完整路径是否为空 test -n "$ACR_IMAGE" || { echo "::error::Set Actions variable ACR_IMAGE first" exit 1 } # 检查应用镜像路径是否以 Registry 地址开头 # 例如: # ACR_REGISTRY=registry.example.com # ACR_IMAGE=registry.example.com/my-project/file-server case "$ACR_IMAGE" in "$ACR_REGISTRY"/*) ;; *) echo "::error::ACR_IMAGE must begin with ACR_REGISTRY/" exit 1 ;; esac # ------------------------------------------------------ # 第 3 步:安装并配置 Python # ------------------------------------------------------ - name: Set up Python uses: actions/setup-python@v5 with: # 指定 CI 检查使用的 Python 版本 python-version: "3.12" # ------------------------------------------------------ # 第 4 步:检查 Python 代码语法 # ------------------------------------------------------ - name: Check Python syntax # py_compile 会检查语法,并生成字节码缓存文件 # 注意:语法检查通过不等于应用业务逻辑没有问题 run: python -m py_compile app/app.py # ------------------------------------------------------ # 第 5 步:准备 Docker Buildx # Buildx 是 Docker 的构建工具,支持 BuildKit 和构建缓存 # ------------------------------------------------------ - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 with: # 使用 docker-container 驱动运行 BuildKit 构建器 driver: docker-container # ------------------------------------------------------ # 第 6 步:构建 Python 应用镜像 # 这里仅构建镜像,不推送到远程仓库 # ------------------------------------------------------ - name: Build application image uses: docker/build-push-action@v6 with: # 构建上下文目录 # Dockerfile 中 COPY 等指令可使用这个目录内的文件 context: ./app # 明确指定应用使用的 Dockerfile file: ./app/Dockerfile # 临时镜像标签使用完整提交 SHA # github.sha 是触发本次工作流的提交 ID tags: file-server-app-ci:${{ github.sha }} # 将构建结果加载到本机 Docker 镜像库 # 后面的 docker run 才能使用这个本地镜像 load: true # 这一阶段不推送镜像 push: false # 尝试读取之前保存的 GitHub Actions 构建缓存 cache-from: type=gha,scope=file-server-app # 保存本次构建缓存,供以后构建复用 # mode=max 尽可能保存中间构建层 cache-to: type=gha,mode=max,scope=file-server-app # ------------------------------------------------------ # 第 7 步:启动应用容器,执行冒烟测试 # 冒烟测试用于快速验证应用的基本功能是否可用 # ------------------------------------------------------ - name: Smoke test application container shell: bash run: | # 启用严格 Shell 检查 set -euo pipefail # 后台启动刚刚构建的应用镜像 docker run -d \ --name file-server-ci-test \ # 仅将容器的 5000 端口绑定到本机回环地址 -p 127.0.0.1:5000:5000 \ # 为测试容器注入临时环境变量 -e SECRET_KEY=ci-only-test-secret \ -e FILE_PASSWORD=ci-only-test-password \ # 使用本次提交对应的本地镜像 file-server-app-ci:${GITHUB_SHA} # 定义清理函数,删除测试容器 cleanup() { docker rm -f file-server-ci-test >/dev/null 2>&1 || true } # 当前 Shell 退出时执行清理 # 即使后面的健康检查失败,也会尝试清理容器 trap cleanup EXIT # success=0 表示尚未检测到成功 success=0 # 最多尝试 30 次,每次间隔 2 秒 # 用来等待应用启动完成 for i in $(seq 1 30); do # curl --fail:HTTP 错误状态返回失败 # --silent:不显示常规进度信息 if curl --fail --silent http://127.0.0.1:5000/health; then echo success=1 break fi sleep 2 done # 30 次尝试后仍然失败,则输出容器日志并终止任务 if [ "$success" -ne 1 ]; then docker logs file-server-ci-test echo "::error::Container health check failed" exit 1 fi # ------------------------------------------------------ # 第 8 步:构建 Nginx Web 镜像 # 同样只构建、不推送,先进行配置检查 # ------------------------------------------------------ - name: Build Nginx web image uses: docker/build-push-action@v6 with: # Nginx 构建上下文是项目根目录 # 这样 Dockerfile 可以访问根目录下允许使用的文件 context: . # Nginx 镜像使用自己的 Dockerfile file: ./nginx/Dockerfile # 使用提交 SHA 作为临时镜像标签 tags: file-server-nginx-ci:${{ github.sha }} # 加载到本地 Docker,供后续 nginx -t 使用 load: true # 测试阶段不发布镜像 push: false # Nginx 专属构建缓存,避免与应用镜像缓存混用 cache-from: type=gha,scope=file-server-nginx cache-to: type=gha,mode=max,scope=file-server-nginx # ------------------------------------------------------ # 第 9 步:检查 Nginx 配置是否有效 # ------------------------------------------------------ - name: Validate Nginx configuration shell: bash run: | set -euo pipefail # 在临时目录创建证书存放位置 mkdir -p "$RUNNER_TEMP/nginx-certs" # 生成仅用于 CI 检查的自签名证书 # -x509:生成自签名证书 # -nodes:不使用密码加密私钥 # -newkey rsa:2048:创建 2048 位 RSA 密钥 # -days 1:证书有效期为 1 天 # -subj:指定证书主题,避免交互式提问 openssl req -x509 -nodes -newkey rsa:2048 \ -keyout "$RUNNER_TEMP/nginx-certs/server.key" \ -out "$RUNNER_TEMP/nginx-certs/server.crt" \ -days 1 -subj "/CN=localhost" # 启动一个临时容器,只执行 Nginx 配置检查 docker run --rm \ # 让容器内的 app 主机名解析到 127.0.0.1 --add-host app:127.0.0.1 \ # 将临时证书目录以只读方式挂载到容器 -v "$RUNNER_TEMP/nginx-certs:/etc/nginx/certs:ro" \ # 使用刚刚构建的镜像执行 nginx -t file-server-nginx-ci:${GITHUB_SHA} nginx -t # ------------------------------------------------------ # 第 10 步:生成镜像标签 # 生成北京时间时间戳标签和短提交 SHA 标签 # ------------------------------------------------------ - name: Generate readable China-time tags id: image-tags shell: bash run: | set -euo pipefail # 使用上海时区生成时间戳 # 例如:20261009-184500 TAG="$(TZ=Asia/Shanghai date +'%Y%m%d-%H%M%S')" # 截取完整提交 SHA 的前 7 个字符 SHORT_SHA="${GITHUB_SHA:0:7}" # 写入 GitHub Actions 的步骤输出文件 # 后续步骤可以通过 steps.image-tags.outputs 读取 echo "timestamp=$TAG" >> "$GITHUB_OUTPUT" echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT" # 在日志中打印生成的时间标签 echo "Image timestamp tag: $TAG" # ------------------------------------------------------ # 第 11 步:登录阿里云 ACR # 使用 GitHub 仓库变量和 Secret 提供认证信息 # ------------------------------------------------------ - name: Login to Alibaba Cloud ACR uses: docker/login-action@v3 with: # ACR 的 Registry 地址 registry: ${{ vars.ACR_REGISTRY }} # 从 GitHub Secrets 读取用户名和密码 username: ${{ secrets.ACR_USERNAME }} password: ${{ secrets.ACR_PASSWORD }} # ------------------------------------------------------ # 第 12 步:登录 GitHub Container Registry # GHCR 的域名是 ghcr.io # ------------------------------------------------------ - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io # 当前触发工作流的 GitHub 用户或应用身份 username: ${{ github.actor }} # 使用 GitHub 自动提供的令牌认证 password: ${{ secrets.GITHUB_TOKEN }} # ------------------------------------------------------ # 第 13 步:给两个镜像打标签,并发布到两个仓库 # 到这里,前面的构建和测试都已成功完成 # ------------------------------------------------------ - name: Tag and publish both images to both registries shell: bash # 将仓库地址和之前生成的标签输出传给 Shell env: # ACR 应用镜像完整路径 ACR_IMAGE: ${{ vars.ACR_IMAGE }} # 使用当前 GitHub 仓库自动生成 GHCR 镜像路径 # 例如 ghcr.io/owner/repository GHCR_IMAGE: ghcr.io/${{ github.repository }} # 引用第 10 步生成的时间戳和短 SHA TIMESTAMP_TAG: ${{ steps.image-tags.outputs.timestamp }} SHORT_SHA: ${{ steps.image-tags.outputs.short_sha }} run: | set -euo pipefail # 定义可重复使用的镜像发布函数 # 参数 1:本地已有的源镜像 # 参数 2:需要推送到的目标镜像路径 publish_image() { local SOURCE="$1" local IMAGE="$2" # 同一份镜像生成三个标签 for TAG in "$TIMESTAMP_TAG" "$SHORT_SHA" latest; do # 将源镜像标记为目标仓库中的指定版本 docker tag "$SOURCE" "${IMAGE}:$TAG" # 将带标签的镜像推送到远程仓库 docker push "${IMAGE}:$TAG" done # 打印发布结果 echo "Published image: $IMAGE (tags: $TIMESTAMP_TAG, $SHORT_SHA, latest)" } # 发布应用镜像到阿里云 ACR publish_image "file-server-app-ci:${GITHUB_SHA}" "$ACR_IMAGE" # 发布 Nginx 镜像到 ACR # 通过 -nginx 区分应用镜像与 Web 镜像 publish_image "file-server-nginx-ci:${GITHUB_SHA}" "${ACR_IMAGE}-nginx" # 发布应用镜像到 GHCR publish_image "file-server-app-ci:${GITHUB_SHA}" "$GHCR_IMAGE" # 发布 Nginx 镜像到 GHCR publish_image "file-server-nginx-ci:${GITHUB_SHA}" "${GHCR_IMAGE}-nginx" # -------------------------------------------------- # 将发布摘要写入 GitHub Actions 的运行摘要 # GitHub 会在本次工作流的 Summary 页面显示这些内容 # -------------------------------------------------- { echo "### Published Docker images" echo echo "Tags: $TIMESTAMP_TAG, $SHORT_SHA, latest" echo echo "ACR app: $ACR_IMAGE" echo "ACR web: ${ACR_IMAGE}-nginx" echo "GHCR app: $GHCR_IMAGE" echo "GHCR web: ${GHCR_IMAGE}-nginx" } >> "$GITHUB_STEP_SUMMARY"五、这份工作流中最值得理解的 5 个细节1. 为什么先构建,再推送?你的两个构建步骤都设置了:load: true push: false这表示先将构建结果加载到本地 Docker 镜像库,不立即发布到远程仓库。接下来启动容器或检查 Nginx 配置,验证成功后才登录仓库并推送。这是一个很实用的原则:先验证,再发布。2. 为什么使用三个标签?你的发布函数会给每个镜像创建三个标签。标签示例用途时间戳20261009-184500方便按发布时间查找版本短 SHAa1b2c3d方便追溯对应的代码提交latestlatest表示当前发布的最新版本实际时间戳和 SHA 会根据每次运行动态生成。需要注意,latest 会被新版本覆盖,所以生产环境如果需要稳定回滚,最好使用时间戳、提交 SHA 或镜像摘要,而不是只依赖 latest。3. GITHUB_OUTPUT 是什么?你的代码中有:echo "timestamp=$TAG" >> "$GITHUB_OUTPUT" echo "short_sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"它将当前步骤生成的数据传递给后面的步骤。后面通过:TIMESTAMP_TAG: ${{ steps.image-tags.outputs.timestamp }} SHORT_SHA: ${{ steps.image-tags.outputs.short_sha }}读取这些值。其中 image-tags 是步骤的 id,timestamp 和 short_sha 是步骤输出的名称。这是一种很常见的工作流数据传递方式。4. set -euo pipefail 为什么常见?这条命令让 Shell 脚本更加严格:-e:大多数未被处理的命令失败会导致脚本退出。-u:使用未定义变量时退出。pipefail:管道中的命令失败不会轻易被后续成功命令掩盖。它可以避免脚本在某个关键操作失败后,仍然继续推送镜像。不过,Shell 的错误处理有一些例外情况,例如 if 条件中执行的命令、使用 || 处理的失败等,因此它不能替代明确的错误判断。5. 冒烟测试不等于完整测试你当前的 Python 检查:python -m py_compile app/app.py主要验证语法是否正确。而 /health 检查主要验证容器是否能够启动并返回成功响应。它不能证明登录、文件上传、分片合并、下载、权限控制等业务功能都正确。
2026年10月09日
2 阅读
0 评论
0 点赞
2026-09-29
使用kubeadm初始化IPV4/IPV6集群v1.37
使用kubeadm初始化IPV4/IPV6集群CentOS 配置YUM源cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.ustc.edu.cn/kubernetes/core:/stable:/v1.37/rpm/ enabled=1 gpgcheck=1 gpgkey=https://pkgs.k8s.io/core:/stable:/v1.37/rpm/repodata/repomd.xml.key EOF # 将 SELinux 设置为 permissive 模式(相当于将其禁用) sudo setenforce 0 sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config yum install -y kubelet kubeadm kubectl # 如安装老版本 # yum install kubelet-1.16.9-0 kubeadm-1.16.9-0 kubectl-1.16.9-0 systemctl enable kubelet && systemctl start kubelet sudo systemctl enable --now kubeletUbuntu 配置APT源echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.ustc.edu.cn/kubernetes/core:/stable:/v1.37/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list apt-get update apt-get install -y kubelet kubeadm kubectl # 如安装老版本 # apt install kubelet=1.23.6-00 kubeadm=1.23.6-00 kubectl=1.23.6-00配置containerd安装Containerd作为Runtime # https://github.com/opencontainers/runc/releases # 升级runc wget https://github.com/opencontainers/runc/releases/download/v1.5.2/runc.amd64 install -m 755 runc.amd64 /usr/local/sbin/runc cp -p /usr/local/sbin/runc /usr/local/bin/runc cp -p /usr/local/sbin/runc /usr/bin/runc # https://github.com/containerd/containerd/releases/ wget https://github.com/containerd/containerd/releases/download/v2.4.1/containerd-2.4.1-linux-amd64.tar.gz # https://github.com/containernetworking/plugins/releases/ wget https://github.com/containernetworking/plugins/releases/download/v1.9.1/cni-plugins-linux-amd64-v1.9.1.tgz #创建cni插件所需目录 mkdir -p /etc/cni/net.d /opt/cni/bin #解压cni二进制包 tar xf cni-plugins-linux-amd64-v*.tgz -C /opt/cni/bin/ # https://github.com/containerd/containerd/releases/ # wget https://github.com/containerd/containerd/releases/download/v2.0.5/containerd-2.0.5-linux-amd64.tar.gz #解压 tar -xzf containerd-*-linux-amd64.tar.gz -C /usr/local/ #创建服务启动文件 cat > /etc/systemd/system/containerd.service <<EOF [Unit] Description=containerd container runtime Documentation=https://containerd.io After=network.target local-fs.target [Service] ExecStartPre=-/sbin/modprobe overlay ExecStart=/usr/local/bin/containerd Type=notify Delegate=yes KillMode=process Restart=always RestartSec=5 LimitNPROC=infinity LimitCORE=infinity LimitNOFILE=infinity TasksMax=infinity OOMScoreAdjust=-999 [Install] WantedBy=multi-user.target EOF配置Containerd所需的模块cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf overlay br_netfilter EOF加载模块systemctl restart systemd-modules-load.service配置Containerd所需的内核cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-ip6tables = 1 EOF # 加载内核 sysctl --system创建Containerd的配置文件# 创建默认配置文件 mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml # 沙箱pause镜像 sed -i "s#registry.k8s.io#registry.aliyuncs.com/chenby#g" /etc/containerd/config.toml cat /etc/containerd/config.toml | grep sandbox # 配置加速器 [root@k8s-master01 ~]# vim /etc/containerd/config.toml [root@k8s-master01 ~]# cat /etc/containerd/config.toml | grep certs.d -C 5 [plugins.'io.containerd.cri.v1.images'.pinned_images] sandbox = 'registry.aliyuncs.com/chenby/pause:3.10' [plugins.'io.containerd.cri.v1.images'.registry] config_path = '/etc/containerd/certs.d' [plugins.'io.containerd.cri.v1.images'.image_decryption] key_model = 'node' [plugins.'io.containerd.cri.v1.runtime'] [root@k8s-master01 ~]# mkdir /etc/containerd/certs.d/docker.io -pv cat > /etc/containerd/certs.d/docker.io/hosts.toml << EOF server = "https://docker.io" [host."https://jockerhub.com"] capabilities = ["pull", "resolve"] EOF # 配置 GFW 代理 mkdir -p /etc/systemd/system/containerd.service.d cat > /etc/systemd/system/containerd.service.d/http-proxy.conf <<'EOF' [Service] Environment="HTTP_PROXY=http://192.168.1.100:7897" Environment="HTTPS_PROXY=http://192.168.1.100:7897" Environment="NO_PROXY=localhost,127.0.0.1,containerd,192.168.1.0/24,10.96.0.0/16,172.16.0.0/16,.svc,.svc.cluster.local,cluster.local,fd00:1111::/108,fd00:2222::/48" EOF systemctl daemon-reload systemctl restart containerd启动并设置为开机启动systemctl daemon-reload # 用于重新加载systemd管理的单位文件。当你新增或修改了某个单位文件(如.service文件、.socket文件等),需要运行该命令来刷新systemd对该文件的配置。 systemctl enable --now containerd.service # 启用并立即启动docker.service单元。docker.service是Docker守护进程的systemd服务单元。 systemctl stop containerd.service # 停止运行中的docker.service单元,即停止Docker守护进程。 systemctl start containerd.service # 启动docker.service单元,即启动Docker守护进程。 systemctl restart containerd.service # 重启docker.service单元,即重新启动Docker守护进程。 systemctl status containerd.service # 显示docker.service单元的当前状态,包括运行状态、是否启用等信息。配置crictl客户端连接的运行时位置# https://github.com/kubernetes-sigs/cri-tools/releases/ wget https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.37.0/crictl-v1.37.0-linux-amd64.tar.gz #解压 tar xf crictl-v*-linux-amd64.tar.gz -C /usr/bin/ #生成配置文件 cat > /etc/crictl.yaml <<EOF runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 debug: false EOF #测试 systemctl restart containerd crictl info 配置基础环境cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.ipv4.ip_forward = 1 net.bridge.bridge-nf-call-iptables = 1 fs.may_detach_mounts = 1 vm.overcommit_memory=1 vm.panic_on_oom=0 fs.inotify.max_user_watches=89100 fs.file-max=52706963 fs.nr_open=52706963 net.netfilter.nf_conntrack_max=2310720 net.ipv4.tcp_keepalive_time = 600 net.ipv4.tcp_keepalive_probes = 3 net.ipv4.tcp_keepalive_intvl =15 net.ipv4.tcp_max_tw_buckets = 36000 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_max_orphans = 327680 net.ipv4.tcp_orphan_retries = 3 net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_max_syn_backlog = 16384 net.ipv4.ip_conntrack_max = 65536 net.ipv4.tcp_max_syn_backlog = 16384 net.ipv4.tcp_timestamps = 0 net.core.somaxconn = 16384 net.ipv6.conf.all.disable_ipv6 = 0 net.ipv6.conf.default.disable_ipv6 = 0 net.ipv6.conf.lo.disable_ipv6 = 0 net.ipv6.conf.all.forwarding = 1 net.ipv6.conf.default.forwarding = 1 EOF sudo sysctl --system sed -ri 's/.*swap.*/#&/' /etc/fstab swapoff -a && sysctl -w vm.swappiness=0 cat /etc/fstab cat > /etc/hosts <<EOF 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 fc00::21 k8s-master01 fc00::22 k8s-node01 fc00::23 k8s-node02 192.168.1.21 k8s-master01 192.168.1.22 k8s-node01 192.168.1.23 k8s-node02 EOF hostnamectl set-hostname k8s-master01 hostnamectl set-hostname k8s-node01 hostnamectl set-hostname k8s-node02 systemctl stop firewalld systemctl disable firewalld 初始化安装[root@k8s-master01 ~]# kubeadm config images list registry.k8s.io/kube-apiserver:v1.37.1 registry.k8s.io/kube-controller-manager:v1.37.1 registry.k8s.io/kube-scheduler:v1.37.1 registry.k8s.io/kube-proxy:v1.37.1 registry.k8s.io/coredns/coredns:v1.14.6 registry.k8s.io/pause:3.10.2 registry.k8s.io/etcd:3.7.0-0 [root@k8s-master01 ~]# [root@k8s-master01 ~]# cat kubeadm.yaml apiVersion: kubeadm.k8s.io/v1beta4 kind: InitConfiguration localAPIEndpoint: advertiseAddress: "192.168.1.21" bindPort: 6443 nodeRegistration: taints: - effect: PreferNoSchedule key: node-role.kubernetes.io/master --- apiVersion: kubeadm.k8s.io/v1beta4 kind: ClusterConfiguration kubernetesVersion: v1.37.1 #imageRepository: registry.cn-hangzhou.aliyuncs.com/chenby networking: podSubnet: 172.16.0.0/16,fd00:2222::/48 serviceSubnet: 10.96.0.0/16,fd00:1111::/108 [root@k8s-master01 ~]# [root@k8s-master01 ~]# kubeadm init --config=kubeadm.yaml [root@k8s-node01 ~]# kubeadm join 192.168.1.21:6443 --token nw76f2.otg1p6gtt8wdxhwt \ --discovery-token-ca-cert-hash sha256:2633c00148c73d5f54db5633aeecbee7e819884d20078f80e90a2233e3752e59 # 重置集群 kubeadm reset -f rm -rf /etc/kubernetes/manifests rm -rf /var/lib/etcd rm -rf /var/lib/kubelet rm -rf ~/.kube systemctl restart containerd systemctl restart kubelet sleep 5 ss -tulnp | grep -E ':2379|:2380|:6443' 查看集群[root@k8s-master01 ~]# kubectl get node NAME STATUS ROLES AGE VERSION k8s-master01 NotReady control-plane 101s v1.37.1 k8s-node01 NotReady <none> 49s v1.37.1 k8s-node02 NotReady <none> 49s v1.37.1 [root@k8s-master01 ~]# [root@k8s-master01 ~]# kubectl get po -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-559f6c778d-8nzjj 0/1 Pending 0 97s kube-system coredns-559f6c778d-rpt7g 0/1 Pending 0 97s kube-system etcd-k8s-master01 1/1 Running 1 103s kube-system kube-apiserver-k8s-master01 1/1 Running 1 103s kube-system kube-controller-manager-k8s-master01 1/1 Running 1 103s kube-system kube-proxy-5l7bj 1/1 Running 0 54s kube-system kube-proxy-9n7rk 1/1 Running 0 54s kube-system kube-proxy-dct4b 1/1 Running 0 97s kube-system kube-scheduler-k8s-master01 1/1 Running 1 103s [root@k8s-master01 ~]# 更改calico网段# 查看版本 https://github.com/projectcalico/calico/tags # 安装operator kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/tigera-operator.yaml # 下载配置文件 curl https://raw.githubusercontent.com/projectcalico/calico/v3.32.2/manifests/custom-resources.yaml -O # 修改地址池 vim custom-resources.yaml apiVersion: operator.tigera.io/v1 kind: Installation metadata: name: default spec: calicoNetwork: ipPools: - name: default-ipv4-ippool blockSize: 26 cidr: 172.16.0.0/16 encapsulation: VXLANCrossSubnet natOutgoing: Enabled nodeSelector: all() # 修改地址池 vim custom-resources.yaml apiVersion: operator.tigera.io/v1 kind: Installation metadata: name: default spec: calicoNetwork: ipPools: - name: ipv4-ippool cidr: 172.16.0.0/16 blockSize: 26 # encapsulation: IPIP encapsulation: VXLANCrossSubnet natOutgoing: Enabled nodeSelector: all() - name: ipv6-ippool cidr: "fd00:2222::/48" blockSize: 122 encapsulation: VXLANCrossSubnet natOutgoing: Enabled nodeSelector: all() nodeAddressAutodetectionV4: interface: "eth.*|en.*" nodeAddressAutodetectionV6: interface: "eth.*|en.*" # 打开vxlan内核 适用于大多数使用 systemd 的发行版 echo "vxlan" | sudo tee /etc/modules-load.d/vxlan.conf systemctl restart systemd-modules-load.service lsmod | grep vxlan # 执行安装 kubectl create -f custom-resources.yaml # 安装客户端 curl -L https://github.com/projectcalico/calico/releases/download/v3.32.2/calicoctl-linux-amd64 -o calicoctl # 给客户端添加执行权限 chmod +x ./calicoctl # 查看集群节点 ./calicoctl get nodes --allow-version-mismatch # 查看集群节点状态 ./calicoctl node status --allow-version-mismatch #查看地址池 ./calicoctl get ipPool --allow-version-mismatch ./calicoctl get ipPool --allow-version-mismatch -o yaml 查看容器状态# calico 初始化会很慢 需要耐心等待一下,大约十分钟左右 [root@k8s-master01 kubernetes-v1.36.0]# kubectl get pod -A NAMESPACE NAME READY STATUS RESTARTS AGE calico-system calico-apiserver-7bb46cd974-2tb62 1/1 Running 0 7m46s calico-system calico-apiserver-7bb46cd974-92p96 1/1 Running 0 7m46s calico-system calico-kube-controllers-7c4f878bd8-xptks 1/1 Running 0 7m45s calico-system calico-node-6wbsv 1/1 Running 0 7m46s calico-system calico-node-djq59 1/1 Running 0 7m46s calico-system calico-node-dm97b 1/1 Running 0 7m46s calico-system calico-node-lvq6w 1/1 Running 0 7m46s calico-system calico-node-pmq6v 1/1 Running 0 7m46s calico-system calico-typha-758c8bf6f7-8tkss 1/1 Running 0 7m43s calico-system calico-typha-758c8bf6f7-dqsqq 1/1 Running 0 7m46s calico-system calico-typha-758c8bf6f7-h7569 1/1 Running 0 7m43s calico-system csi-node-driver-4rld6 2/2 Running 0 7m45s calico-system csi-node-driver-8krh7 2/2 Running 0 7m45s calico-system csi-node-driver-bvq9q 2/2 Running 0 7m45s calico-system csi-node-driver-qcb9d 2/2 Running 0 7m45s calico-system csi-node-driver-xkkcj 2/2 Running 0 7m45s calico-system goldmane-6885dcb7d-k26sd 1/1 Running 0 7m46s calico-system whisker-898cf7b47-75pdh 2/2 Running 0 6m53s tigera-operator tigera-operator-85dbff4478-sntj6 1/1 Running 0 9m13s [root@k8s-master01 kubernetes-v1.36.0]# # IPIP模式 仅支持IPv4,不支持IPv6,有tun网口 [root@k8s-master01 ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.1.1 0.0.0.0 UG 100 0 0 ens160 172.17.125.0 192.168.1.34 255.255.255.192 UG 0 0 0 tunl0 172.18.195.0 192.168.1.33 255.255.255.192 UG 0 0 0 tunl0 172.25.92.64 192.168.1.32 255.255.255.192 UG 0 0 0 tunl0 172.25.244.192 0.0.0.0 255.255.255.192 U 0 0 0 * 172.25.244.193 0.0.0.0 255.255.255.255 UH 0 0 0 calif3e9d7544a4 172.27.14.192 192.168.1.35 255.255.255.192 UG 0 0 0 tunl0 192.168.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens160 [root@k8s-master01 ~]# # VXLANCrossSubnet模式,无tun网口 [root@k8s-master01 ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.1.1 0.0.0.0 UG 100 0 0 ens160 172.16.32.128 0.0.0.0 255.255.255.192 U 0 0 0 * 172.16.32.129 0.0.0.0 255.255.255.255 UH 1024 0 0 calic9ff772e94d 172.16.58.192 192.168.1.35 255.255.255.192 UG 0 0 0 ens160 172.16.85.192 192.168.1.34 255.255.255.192 UG 0 0 0 ens160 172.16.122.128 192.168.1.32 255.255.255.192 UG 0 0 0 ens160 172.16.195.0 192.168.1.33 255.255.255.192 UG 0 0 0 ens160 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 192.168.1.0 0.0.0.0 255.255.255.0 U 100 0 0 ens160 [root@k8s-master01 ~]# # 路由表 [root@k8s-master01 ~]# route -n -4 -6 | grep vxlan fe80::/64 :: U 256 1 0 vxlan.calico fd00:100::88bd:e5c3:433c:2080/128 :: Un 0 2 0 vxlan-v6.calico fe80::/128 :: Un 0 3 0 vxlan.calico fe80::6454:d1ff:fe67:d36d/128 :: Un 0 2 0 vxlan.calico ff00::/8 :: U 256 1 0 vxlan.calico ff00::/8 :: U 256 1 0 vxlan-v6.calico [root@k8s-master01 ~]# 删除kubectl delete -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/tigera-operator.yaml --force --grace-period=0 kubectl delete -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/custom-resources.yaml --force --grace-period=0 # 在所有主机上执行 modprobe -r ipip # 删除 IPIP 模式虚拟网卡 modprobe -r vxlan # 删除 VXLAN 模式虚拟网卡 # 在所有主机上执行 sudo rm -rf /etc/cni/net.d/*calico* sudo rm -f /opt/cni/bin/calico* sudo rm -f /usr/local/bin/calico* sudo rm -rf /var/lib/cni/networks/calico/ # 在所有主机上执行 systemctl restart kubelet测试IPV6[root@k8s-master01 ~]# echo 'KUBELET_EXTRA_ARGS="--node-ip=192.168.1.21,fc00::21"' > /etc/sysconfig/kubelet [root@k8s-master01 ~]# systemctl daemon-reload && systemctl restart kubelet [root@k8s-node01 ~]# echo 'KUBELET_EXTRA_ARGS="--node-ip=192.168.1.22,fc00::22"' > /etc/sysconfig/kubelet [root@k8s-node01 ~]# systemctl daemon-reload && systemctl restart kubelet [root@k8s-node02 ~]# echo 'KUBELET_EXTRA_ARGS="--node-ip=192.168.1.23,fc00::23"' > /etc/sysconfig/kubelet [root@k8s-node02 ~]# systemctl daemon-reload && systemctl restart kubelet cat<<EOF | kubectl apply -f - apiVersion: apps/v1 kind: Deployment metadata: name: chenby spec: replicas: 1 selector: matchLabels: app: chenby template: metadata: labels: app: chenby spec: hostNetwork: true containers: - name: chenby image: docker.io/library/nginx resources: limits: memory: "128Mi" cpu: "500m" ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: chenby spec: ipFamilyPolicy: PreferDualStack ipFamilies: - IPv6 - IPv4 type: NodePort selector: app: chenby ports: - port: 80 targetPort: 80 EOF #查看端口 [root@k8s-master01 ~]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE chenby NodePort fd00:1111::a:2aaa <none> 80:30249/TCP 10s kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 9m56s [root@k8s-master01 ~]# [root@k8s-master01 ~]# curl -I http://[fd00:1111::a:2aaa] HTTP/1.1 200 OK Server: nginx/1.31.6 Date: Tue, 29 Sep 2026 09:05:19 GMT Content-Type: text/html Content-Length: 896 Last-Modified: Tue, 15 Sep 2026 12:54:15 GMT Connection: keep-alive ETag: "6aa93ff7-380" Accept-Ranges: bytes [root@k8s-master01 ~]# [root@k8s-master01 ~]# curl -I http://192.168.1.21:30249 HTTP/1.1 200 OK Server: nginx/1.31.6 Date: Tue, 29 Sep 2026 09:05:24 GMT Content-Type: text/html Content-Length: 896 Last-Modified: Tue, 15 Sep 2026 12:54:15 GMT Connection: keep-alive ETag: "6aa93ff7-380" Accept-Ranges: bytes [root@k8s-master01 ~]# [root@k8s-master01 ~]# curl -I http://[fc00::21]:30249 HTTP/1.1 200 OK Server: nginx/1.31.6 Date: Tue, 29 Sep 2026 09:05:31 GMT Content-Type: text/html Content-Length: 896 Last-Modified: Tue, 15 Sep 2026 12:54:15 GMT Connection: keep-alive ETag: "6aa93ff7-380" Accept-Ranges: bytes [root@k8s-master01 ~]# [root@k8s-master01 ~]# curl -I http://[2409:8a10:6d7:b511:c46e:8678:944d:3ddb]:30249 HTTP/1.1 200 OK Server: nginx/1.31.6 Date: Tue, 29 Sep 2026 09:05:39 GMT Content-Type: text/html Content-Length: 896 Last-Modified: Tue, 15 Sep 2026 12:54:15 GMT Connection: keep-alive ETag: "6aa93ff7-380" Accept-Ranges: bytes [root@k8s-master01 ~]# 关于https://www.oiox.cn/https://www.oiox.cn/index.php/start-page.htmlCSDN、GitHub、知乎、开源中国、思否、掘金、简书、华为云、阿里云、腾讯云、哔哩哔哩、今日头条、新浪微博、个人博客全网可搜《小陈运维》文章主要发布于微信公众号:《Linux运维交流社区》
2026年09月29日
10 阅读
0 评论
0 点赞
1
2
...
44