www8351/Docker-DevOps-Tooling

GitHub: www8351/Docker-DevOps-Tooling

生产级强化 Docker 运维工具包,通过声明式 compose 配置、类型化 Python CLI 和供应链安全门控 CI,实现可复现的容器化基础设施管理。

Stars: 0 | Forks: 0

# 🐳 dockerctl — 强化版 Docker 运维工具包 **类型化 Python CLI · 声明式强化 compose stack · 供应链安全门控 CI** *类型化 Python CLI · 声明式强化 compose stack · 供应链安全门控 CI* [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/www8351/Docker-DevOps-Tooling/actions/workflows/ci.yml) [![Release](https://static.pigsec.cn/wp-content/uploads/repos/cas/42/42ba98a60a0bb3b0ad908f024db145f9c5b831eb7df822f56ac578ee7d7215b3.svg)](https://github.com/www8351/Docker-DevOps-Tooling/actions/workflows/release.yml) [![GHCR](https://img.shields.io/badge/GHCR-images-2496ED?logo=docker&logoColor=white)](https://github.com/www8351?tab=packages&repo_name=Docker-DevOps-Tooling) ![Python](https://img.shields.io/badge/Python-3.10%2B-3776AB?logo=python&logoColor=white) ![Typer](https://img.shields.io/badge/CLI-Typer-009688) ![mypy](https://img.shields.io/badge/mypy-strict-blue) ![coverage](https://img.shields.io/badge/coverage-%E2%89%A590%25_gated-brightgreen) ![Task](https://img.shields.io/badge/Runner-Task-29BEB0?logo=task&logoColor=white) ![License](https://img.shields.io/badge/License-MIT-yellow)
## 🌍 这是什么? · 这是什么?
### 🇬🇧 中文 一个生产级的 Docker 工具包:**compose 负责管理状态**,**类型化 Python CLI** 负责 动态操作,**Taskfile** 负责将它们粘合在一起,而 **CI 负责执行所有检查**。每个 长时间运行的容器都是 **只读、非 root、已丢弃 capabilities 并且经过 healthcheck** 的。
### 🇮🇱 中文 一个生产级的 Docker 工具包:**compose 负责管理状态**,**类型化 Python CLI** 负责 动态操作,**Taskfile** 负责将它们粘合在一起,而 **CI 负责执行所有检查**。每个 长时间运行的容器都是 **只读、非 root、已丢弃 capabilities 并且经过 healthcheck** 的。
``` $ docker compose ps NAME IMAGE STATUS PORTS compose-counter-1 compose-counter Up 18 seconds (healthy) compose-httpd-1 httpd:2.4-alpine Up 18 seconds (healthy) 0.0.0.0:8081->80/tcp compose-nginx-1 nginx:1.27-alpine Up 18 seconds (healthy) 0.0.0.0:8080->80/tcp compose-portainer-1 portainer/portainer-ce:2.27.9 Up 18 seconds 0.0.0.0:9000->9000/tcp ``` ## ⚡ 核心亮点 - **78 MB Ubuntu → ~7 MB 强化版 Alpine** — counter 镜像的[前后对比](#-counter--from-78-mb-of-attack-surface-to-a-few-mb) - **只读容器,`cap drop: ALL`,`no-new-privileges`** — 参见 [`compose/docker-compose.yml`](compose/docker-compose.yml) - **SHA 固定 actions,Trivy 安全门控,CycloneDX SBOMs** — 参见 [`ci.yml`](.github/workflows/ci.yml) - **类型化 CLI:mypy strict,45 个测试,100% 覆盖率** — 参见 [`cli/`](cli/) - **已发布 [v0.2.0](https://github.com/www8351/Docker-DevOps-Tooling/releases/tag/v0.2.0)**,包含 GHCR 上的 semver 镜像 + [CHANGELOG](CHANGELOG.md) ## 💡 为什么会有这个项目 这个仓库最初是几年前为了学习 Docker 而编写的一套**交互式 bash 菜单**。 它们在敲击键盘时很好用,但非常脆弱且无法实现自动化。现在一切都 被重建为声明式配置、标志驱动的 CLI 以及每次 push 时的 CI 验证。 最终结果是**非交互式、可重现且 pipeline 安全的**。 ## 🏗 架构 ``` flowchart TD Dev([👩‍💻 Engineer / CI]) --> Task[Taskfile.yml
single entrypoint] Task --> Compose[docker compose
infrastructure + state] Task --> CLI[dockerctl
dynamic ops + logic] Compose --> Nginx[nginx
:8080] Compose --> Httpd[httpd
:8081] Compose --> Portainer[portainer
:9000] Compose --> Counter[counter
custom image] Nginx -. mounts .-> Web[web/index.html] Counter -. builds .-> Img[images/counter/] Compose -. profile: observability .-> Obs[prometheus :9090 · grafana :3000
cadvisor · nginx-exporter] Obs -. scrapes .-> Nginx CLI --> Docker[(Docker Engine)] ``` **关注点分离** — 每个工具只负责一项工作: | 层级 | 职责 | 工具 | |-------|------|------| | 🧱 基础设施 | 容器生命周期、网络、卷、状态 | `docker-compose` | | 🐍 逻辑 | 动态镜像/容器操作、错误处理 | Python + Typer | | ▶️ 编排 | 日常命令 | Taskfile | | 🤖 质量门禁 | 验证、无人工干预 | GitHub Actions | ## 📂 项目布局 ``` . ├── compose/ │ ├── docker-compose.yml # declarative stack (nginx, httpd, portainer, counter) │ └── .env.example # ports + image tags → copy to .env ├── images/ │ └── counter/ # custom image, built by compose │ ├── Dockerfile # multi-stage, hardened, Alpine, non-root │ └── counter.sh # POSIX sh (no bash needed) ├── web/ │ └── index.html # served by nginx from a read-only mount ├── cli/ # dockerctl — non-interactive Python CLI │ ├── Dockerfile # multi-stage build → slim Alpine, non-root │ ├── .dockerignore │ ├── pyproject.toml │ └── dockerctl/ │ ├── __main__.py # entrypoint + subcommand wiring │ ├── _docker.py # typed wrapper around the docker CLI │ ├── images.py # pull / rm │ └── containers.py # ls / rm / deploy ├── Taskfile.yml # engineering entrypoint └── .github/workflows/ci.yml # lint + validate on every push ``` ## ✅ 环境要求 | 工具 | 原因 | 安装 | |------|-----|---------| | Docker Engine + Compose v2 | 运行 stack | | | Python ≥ 3.10 | 运行 `dockerctl` | | | Task (可选) | 任务运行器 | | ## 🚀 快速开始 ``` # 配置 ports / image tags cp compose/.env.example compose/.env # 在后台启动所有内容 task up # or: docker compose -f compose/docker-compose.yml up -d # 打开服务 # nginx → http://localhost:8080 # httpd → http://localhost:8081 # portainer → http://localhost:9000 # 销毁 task down # or: docker compose -f compose/docker-compose.yml down ``` ## 🧱 Stack (docker-compose) 定义在 [`compose/docker-compose.yml`](compose/docker-compose.yml) 中。端口和标签 来自 `compose/.env`(绝不硬编码);标签默认**固定**。 | 服务 | 镜像 | 默认端口 | 健康检查 | 网络 | 备注 | |---------|-------|--------------|--------|---------|-------| | `nginx` | `nginx:1.27-alpine` | `8080:80` | `wget` 探针 | `web` | 只读服务 `web/` | | `httpd` | `httpd:2.4-alpine` | `8081:80` | `wget` 探针 | `web` | 替换旧的不存在的 `apache2` | | `portainer` | `portainer/portainer-ce:2.27.9` | `9000:9000` | — (scratch 镜像) | `mgmt` | Docker UI,替换已失效的 `dockerui` | | `counter` | 基于 `images/counter/` 构建 | — | `pgrep` 探针 | `web` | 递增计数器;强化版 Alpine,非 root(参见[套件](#-devops-tooling--containerization-suite)) | 每个服务都以**只读根文件系统**(已知的写入路径使用 tmpfs)、**丢弃所有 capabilities**(Web 服务器仅重新添加 80 端口所需权限)、`no-new-privileges`、CPU/内存限制以及有界的 json-file 日志方式运行。 `mgmt` 网络将 docker.sock 消费者与面向公网的层级隔离开来。 Alpine 标签是承重的:busybox `wget` 为健康检查提供支持。 ### 📈 可观测性概况 ``` docker compose -f compose/docker-compose.yml --profile observability up -d --wait # prometheus → http://localhost:9090 grafana → http://localhost:3000 ``` | 服务 | 镜像 | 端口 | 角色 | |---------|-------|------|------| | `prometheus` | `prom/prometheus:v3.13.0` | `9090` | 每 15s 抓取一次 cadvisor + nginx-exporter | | `grafana` | `grafana/grafana:12.3.8` | `3000` | **预配置**的数据源 + 已提交的[容器仪表板](compose/grafana/dashboards/containers.json) — 零点击操作 | | `cadvisor` | `gcr.io/cadvisor/cadvisor:v0.55.1` | — | 每个容器的 CPU/内存/网络指标 | | `nginx-exporter` | `nginx/nginx-prometheus-exporter:1.5.1` | — | nginx `stub_status` → Prometheus 指标 | 这四个组件都遵循相同的强化基准(只读,`cap_drop: ALL`, `no-new-privileges`,内存限制,固定标签)。CI 也会启动此配置,如果任何 Prometheus 目标宕机,则构建失败。故意排除在范围之外的: Alertmanager 和 Loki。 ``` task build # docker compose --profile tools build (counter + dockerctl) task pull # docker compose pull task logs # docker compose logs -f task clean # down + remove volumes ``` ## 🛡 DevOps 工具与容器化套件 两个专门构建的镜像,每个都作为在**最小化 Alpine 基础上的多阶段构建**,并由**非特权用户**运行。目标是体积小、可重现且攻击面低 — 构建工具链永远不会进入最终镜像,root 同样不会。 | 镜像 | 源码 | 基础镜像 | 多阶段布局 | 运行身份 | |-------|--------|------|--------------------|---------| | `counter` | [`images/counter/`](images/counter/) | `alpine:3.20` | `lint` (shellcheck 门控) → `runtime` | 非 root `app` (UID 10001) | | `dockerctl` | [`cli/`](cli/) | `python:3.12-alpine` | `builder` (通过 `python:3.12-slim` 构建 venv) → `runtime` | 非 root `app` (UID 10001) | ### 🔢 `counter` — 从 78 MB 的攻击面到几 MB 原始镜像在 `ubuntu:24.04` 上运行一个简单的 bash 循环,同时安装了 `openssh-server`、`sshpass`、`tcpdump`、`net-tools` 和 `python3` — **这些脚本根本没用到**。 仅 `sshpass` 和 `tcpdump` 就正是镜像扫描器首先标记的目标。 | | 之前 ❌ | 之后 ✅ | |--|----------|---------| | 基础镜像 | `ubuntu:24.04` (~78 MB) | `alpine:3.20` (~7 MB) | | 额外包 | ssh, sshpass, tcpdump, net-tools, python3 | 无 | | 用户 | root | 非 root `app` (UID/GID 10001) | | 阶段 | 1 | 2 (lint 门控 → runtime) | | 脚本 | bash + 失效的 `/var/log/my` 符号链接 | POSIX `sh`,无需 bash | **强化选择** - **构建时 lint 门控** — 阶段 1 对 `counter.sh` 运行 `shellcheck`;损坏的脚本 会导致 `docker build` 失败,因此糟糕的 shell 脚本永远不会进入镜像。 - **仅携带运行所需内容** — runtime 阶段仅复制经验证的脚本;旧镜像中的所有 包都不复存在。 - **构造上即非 root** — 固定 ID 的 `app` 用户不拥有任何它不需要的权限;该脚本 以仅读和执行权限 (`0555`) 安装。 - **POSIX `sh`** — 重写唯一的 bashism (`((counter++))` → `$((counter + 1))`) 让它可以在 Alpine 的 busybox `ash` 上运行,因此完全不安装 `bash` 包。 ### 🐍 `dockerctl` — 多阶段 Python,runtime 中无工具链 ``` docker compose --profile tools build dockerctl # or: docker build -t dockerctl cli docker run --rm dockerctl version # → 0.1.0 (no socket needed) docker run --rm dockerctl images --help # 通过挂载 host socket 来驱动真实引擎: docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \ dockerctl images pull nginx:latest ``` **强化选择** - **Builder/runtime 分离** — `builder` 阶段使用完整的 `python:3.12-slim` 将 项目安装到 `/opt/venv` 的独立 venv 中;runtime 阶段仅复制该 venv,因此 pip、 构建工具和缓存永远不会被打包进去。 - **最小化 runtime** — `python:3.12-alpine` 加上仅有的 `docker-cli`(客户端,非 daemon), 因为 `dockerctl` 需要 shell out 调用 `docker`(参见 [`cli/dockerctl/_docker.py`](cli/dockerctl/_docker.py))。 - **非 root `app` 用户**,并通过 [`cli/.dockerignore`](cli/.dockerignore) 精简构建上下文 (测试、缓存和 Dockerfile 本身已被排除)。 - **不内置 socket** — 引擎仅在运行时通过显式挂载的 `/var/run/docker.sock` 访问,因此镜像本身不赋予自身任何权限。 ### 🔎 在 CI 中扫描 两个镜像在每次 push 时都会由 **Trivy** 构建和扫描(参见 [CI](#-continuous-integration))。扫描**对 HIGH/CRITICAL 级别的问题进行门控** (`ignore-unfixed: true`,因此无法修补的上游基础镜像 CVE 不会中断构建)。 ## 🐍 CLI (dockerctl) 一个小巧的**非交互式** CLI,用于处理 compose 不应拥有的 动态操作(临时拉取、删除、一次性部署)。仅使用标志 — 没有提示 — 因此它在 CI 中是安全的。 每个命令都返回有意义的**退出码**并将错误写入 stderr。 ``` pip install -e cli # installs the `dockerctl` command ``` ``` dockerctl containers deploy nginx:latest -n web -p 8080:80 dockerctl containers stats --json dockerctl images push ghcr.io/acme/counter:1.0 dockerctl --version # or -V, or the `version` subcommand ```
完整命令参考 (2 个组,14 条命令) | 命令 | 替代项 | 示例 | |---------|----------|---------| | `images ls [--json]` | 菜单列表 | `dockerctl images ls --json` | | `images build -t ` | 构建菜单 | `dockerctl images build images/counter -t counter:dev` | | `images pull ` | `pull_images.sh` | `dockerctl images pull nginx:latest` | | `images rm [-f]` | `remove_images.sh` | `dockerctl images rm nginx:latest` | | `images tag ` | — | `dockerctl images tag counter: ghcr.io/acme/counter:1.0` | | `images push ` | — | `dockerctl images push ghcr.io/acme/counter:1.0` | | `containers ls [-a] [--json]` | 菜单列表 | `dockerctl containers ls --all` | | `containers rm [-f]` | `remove_Container.sh` | `dockerctl containers rm web --force` | | `containers deploy ` | 部署菜单 | `dockerctl containers deploy nginx:latest -n web -p 8080:80` | | `containers logs / stop / start / restart` | 菜单操作 | `dockerctl containers restart -t 5 web` | | `containers exec / inspect / stats` | 菜单操作 | `dockerctl containers stats --json` | `dockerctl --help` 会打印完整的命令树;每个组都有各自的 `--help`。
CLI **完全符合 mypy-strict**,并且其测试套件**覆盖门控为 90%**(目前为 100%);版本号单方面源自 `pyproject.toml`。 ## ▶️ 任务运行器 [`Taskfile.yml`](Taskfile.yml) 是所有人(以及 CI)使用的唯一入口。 ``` task # list all tasks task up # start the stack task down # stop + remove task build # build images task pull # pull images task logs # tail logs task clean # down + volumes task lint # run every linter locally task cli -- images pull nginx:latest # proxy to dockerctl ``` ## 🤖 持续集成 [`.github/workflows/ci.yml`](.github/workflows/ci.yml) 在每次 push 和 pull request 时运行 — **完全无人值守,没有任何提示** — 在最小权限下运行,并且 每个 action 都**固定到提交 SHA**,同时具有按分支的并发控制: **`lint` 任务** 1. ✅ **Compose** — `docker compose config` 验证 stack 2. 🐳 **Dockerfiles** — `hadolint` 检查 `images/counter/Dockerfile` 和 `cli/Dockerfile` 3. 🐚 **Shell** — 对 shell 脚本运行 `shellcheck` 4. 🐍 **Python** — `ruff` lint + 格式检查 5. 🔍 **Types** — strict 模式下的 `mypy` 6. 📦 **Tests** — 带有 90% 覆盖率门控的 `pytest` **`image` 任务** (矩阵: counter, dockerctl) - 通过 buildx 构建,利用 GitHub Actions **层缓存** - 使用 **Trivy** 扫描(门控 HIGH/CRITICAL,`ignore-unfixed`) - 为每个镜像生成 **CycloneDX SBOM**,并作为 artifact 上传 **`publish` 任务** (仅 push 到 `main` 时) - 将两个镜像推送到 **GHCR**,标记为 `latest` + 短提交 SHA [Dependabot](.github/dependabot.yml) 每周保持 GitHub Actions、pip 和 Dockerfiles 的更新。 ## 🚢 发布与已发布的镜像 打上 `vX.Y.Z` 标签(与 `cli/pyproject.toml` 匹配 — workflow 会对此进行断言) 会触发 [`release.yml`](.github/workflows/release.yml):在 GHCR 上发布带有 semver 标签的镜像, 并自动生成 GitHub Release。精选的更新记录在 [CHANGELOG.md](CHANGELOG.md) 中。 ``` docker pull ghcr.io/www8351/docker-devops-tooling/dockerctl:0.2.0 docker pull ghcr.io/www8351/docker-devops-tooling/counter:0.2.0 ``` ## 🎯 设计原则 - **声明式优于命令式** — compose 描述了期望的状态;我们不编写 `docker run` 脚本。 - **无交互** — 仅使用标志和环境变量,因此一切都能在 pipeline 中运行。 - **有意义的退出码** — 失败会明确报错并停止 CI。 - **单一入口点** — Taskfile,这样没人需要记住冗长的命令。 - **物尽其用** — compose 用于基础设施,Python 用于逻辑,Task 用于粘合,CI 用于强制执行。 ## 🧰 故障排除
常见问题与修复 | 症状 | 修复 | |---------|-----| | `task: command not found` | 安装 Task,或者使用上面提到的 `docker compose …` 等效命令。 | | 端口已被占用 | 更改 `compose/.env` 中的端口,然后重新运行 `task up`。 | | 页面看起来没有样式 | 强制刷新;`web/index.html` 是实时挂载的(无需重新构建)。 | | `dockerctl: command not found` | 首先运行 `pip install -e cli`。 | | docker socket 权限被拒绝 | 将您的用户添加到 `docker` 组,或使用 `sudo` 运行。 | | `dockerctl` 容器无法连接到 socket | 该镜像是非 root 的;为其赋予 socket 的组权限:`docker run --group-add "$(stat -c '%g' /var/run/docker.sock)" …` | | 服务不健康 / 不断崩溃重启 | 检查 `docker compose ps` + `task logs`;该 stack 以只读方式运行 — 请参阅 compose 文件中的 tmpfs 列表。 |
## 🤝 贡献 / 安全 / 更新日志 - [CONTRIBUTING.md](CONTRIBUTING.md) — 设置、质量门控、约定、发布步骤 - [SECURITY.md](SECURITY.md) — 报告漏洞、安全态势 - [CHANGELOG.md](CHANGELOG.md) — 精选内容,Keep-a-Changelog 格式
从交互式 bash 菜单到供应链门控的工具包 — CHANGELOG 讲述了这段故事。
标签:DevOps工具, Docker, LLM防护, Python, 安全防御评估, 无后门, 版权保护, 自定义请求头, 请求拦截, 软件供应链安全, 远程方法调用, 逆向工具