
从个人项目到公司生产环境Docker和KubernetesK8s这套组合现在基本成了后端开发与运维绕不开的必修课。我第一次接触Docker是在一个多服务的个人项目里当时被各种版本依赖和部署环境折腾得够呛后来从Docker Desktop装起一路踩坑走到用K8s管理集群中间积累了不少经验教训。这篇总结会把核心概念、环境搭建、常用命令、真实业务部署以及K8s企业落地要点都梳理一遍适合刚接触容器化、想快速搭起一套可用环境的新手也适合已经用了一阵子Docker、打算往K8s方向深入的初级工程师。1. Docker的本质镜像、容器和仓库的关系1.1 容器和虚拟机的核心差异很多刚接触Docker的朋友会问容器跟虚拟机到底什么区别一句话概括虚拟机虚拟的是硬件容器虚拟的是操作系统。虚拟机里跑的每个Guest OS都有一套完整的内核所以启动慢、资源占用高而容器直接共享宿主机的内核只通过隔离机制把进程、文件系统、网络隔开所以几毫秒就能启动单台机器能跑几十上百个容器。这里有个很重要的点正因为容器共享宿主机内核所以Linux容器在Linux上跑是最自然的形态Windows上跑Linux容器本质是靠虚拟机套了一层WSL2来实现。这也是为什么Docker Desktop默认启用WSL2——没有WSL2Windows环境下Docker的体验会大打折扣。资源占用上我自己测过一个典型场景相同的服务虚拟机可能占用500MB内存容器往往只要几十兆。生产环境成本差异非常明显这也是容器能在云原生时代迅速铺开的核心原因。1.2 镜像分层与联合文件系统Docker镜像由一层一层只读文件系统叠加而成每一层对应Dockerfile里的一条指令。拉取镜像时可以只下载新增的层已有的层直接复用同一个基础镜像比如ubuntu不管被多少个其他镜像引用宿主机上只存一份底层数据。这种设计节省了大量磁盘空间和带宽。联合文件系统UnionFS是分层的底层支撑机制。在Docker中默认的存储驱动是overlay2它把多个目录合并挂载成一个统一视图。运行容器时Docker会在镜像的只读层之上再加一个可写层所有对文件系统的修改都写在这一层。容器删除后可写层随之销毁所以容器本身是“无状态”的。这个特性也带来一个经典坑不要把数据直接写在容器内部。容器重建后数据就丢了必须用数据卷volume把数据挂载到宿主机。后面实战部分我会再详细展开。1.3 容器从创建到运行的全过程执行docker run nginx:latest之后Docker引擎做的事大致如下先从本地仓库找nginx:latest镜像找不到就去配置的远端仓库拉取确认镜像完整后创建容器分配独立的文件系统、网络栈、进程命名空间最后启动进程并挂接容器的标准输入输出。整个过程中涉及docker image、docker container、docker network、docker volume等几个对象。我建议新手把这些对象分开理解不要混在一起。镜像只管怎么打包容器管运行实例网络管通信卷管持久化各司其职用起来逻辑就清晰了。还有一个容易忽略的细节docker run其实是“创建启动”两步的合并分开操作是docker create加docker start。调试阶段建议多用docker run -it进入交互模式跑通了再用-d后台运行。2. Docker环境搭建从Windows到Linux的完整实操2.1 Windows下安装Docker Desktop的三大坑Windows用户装Docker Desktop踩到最多的问题集中在三个地方。第一是“virtualization support not detected”或者“Docker Desktop failed to start because virtualization support is not detected”。这个报错大概率是BIOS里虚拟化没开。进BIOS找到Intel VT-x或AMD-V/SVM选项开启后重启再试。如果BIOS已经开了还要检查Windows功能里的“虚拟机平台”“Windows虚拟机监控程序”是否启用。第二是报“failed to connect to the docker api at npipe:////./pipe/dockerdesktoplinuxen”。这类npipe连接失败多半是Docker引擎没起来常见原因是WSL2内核版本太旧。在管理员PowerShell里执行wsl --update更新内核然后wsl --shutdown重启WSL基本能解决。第三是Docker Desktop安装成功后拉取镜像慢或者超时。解决办法在后文镜像加速部分会详细说。另外Windows 11上建议直接装最新版Docker Desktop它会自动配置WSL2。Windows 10用户如果开了Hyper-V也可以让Docker Desktop走Hyper-V后端但WSL2整体资源占用更小我实测下来体验更好。2.2 Linux环境下安装DockerLinux装Docker其实很简单Ubuntu和Debian系列推荐用官方脚本curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh装完执行sudo systemctl enable --now docker然后确认状态sudo systemctl status docker docker versionCentOS 7需要注意系统自带的Docker版本往往很老升级到新版Docker Engine 24会比较顺手。先用sudo yum remove docker*卸载旧包再安装yum-utils并配置官方仓库然后装docker-ce docker-ce-cli containerd.io。记住CentOS 7的内核是3.10如果要用较新的overlay2特性建议先把内核升级到长期支持版本不然某些功能会受限。Ubuntu系统如果是从旧版本升级过来的偶尔会遇到docker命令提示找不到daemon.json之类的路径问题。实际上Docker配置目录是/etc/docker/没有就手动创建权限设为0644。2.3 镜像加速源的配置docker pull慢是全世界新手都会撞上的问题。除了网络因素更常见的解法是配置镜像加速器。在/etc/docker/daemon.json里加registry-mirrors{ registry-mirrors: [ https://docker.m.daocloud.io, https://dockerproxy.com ] }改完后执行sudo systemctl daemon-reload和sudo systemctl restart docker才能生效。需要注意公共加速源有时会失效或变慢建议维护一个备选列表。另外如果公司内部有Harbor或Nexus仓库也可以把它作为mirror内网拉取速度会非常快。对于下载慢的单个镜像还有一个技巧先找一台网络好的机器拉下来导出为tar包再传到目标机器用docker load导入适合内网环境批量同步。2.4 常用命令速查与权限问题Docker命令数量不少但日常高频使用的其实就十来个。我把它们按使用场景整理成了一张速查表场景命令说明查看镜像docker images列出本地镜像拉取镜像docker pull 镜像名:tag不写tag默认latest运行容器docker run -d -p 8080:80 --name web nginx-d后台-p映射端口查看容器docker ps -a加-a看停止的容器进入容器docker exec -it 容器名 bash调试常用查看日志docker logs -f 容器名-f跟随输出停止/启动docker stop/start 容器名不删容器删除容器docker rm -f 容器名-f强制删除运行中的删除镜像docker rmi 镜像名先删依赖容器清理资源docker system prune清掉悬空镜像和停止容器查看资源占用docker stats类似top拷贝文件docker cp 容器:路径 本地路径容器内外互传文件权限问题也容易踩Linux下docker命令提示权限不足因为Docker守护进程监听在/var/run/docker.sock这个socket文件默认属于root组。把用户加进docker组sudo usermod -aG docker $USER然后重新登录。但这里提醒一下docker组的权限约等于root不要在普通开发机上随便把人加进docker组生产环境更要注意后面安全部分会展开。3. Docker实战用Compose部署MySQL、Redis和GitLab3.1 Dockerfile的常用写法Dockerfile是把应用固化成镜像的配方。以Python项目为例一个精简的Dockerfile长这样FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY . . EXPOSE 8000 CMD [python, app.py]几点经验尽量选官方基础镜像精简版-slim / alpine能明显减小体积WORKDIR一定要写避免各种路径问题RUN尽量合并减少镜像层数CMD用exec格式保证能正确接收信号。构建命令是docker build -t myapp:1.0 .。注意那个点不是可选项它指定构建上下文目录。构建上下文里的所有文件都会传给Docker守护进程如果目录里有大文件或者.git目录构建会很慢。建议写一个.dockerignore把无用文件排除掉。3.2 使用Docker Compose编排多容器单容器用docker run够用多容器联动就必须上Compose了。Compose用YAML文件描述整个应用栈一条docker compose up -d全部拉起。一个典型的Compose文件结构version: 3.8 services: web: build: . ports: - 8080:80 depends_on: - redis redis: image: redis:7 volumes: - redis_data:/data volumes: redis_data:depends_on控制启动顺序但如果Web应用依赖Redis就绪后才能工作光靠depends_on不够需要在应用里加重试逻辑或者用healthcheck。我在项目中就吃过这个亏容器启动了但应用连不上数据库因为数据库还在初始化。后来加了健康检查才稳定。3.3 MySQL 8.0主从部署实例生产环境数据库是最容易出问题的环节我建议即使是测试环境也养成用数据卷的习惯。部署单个MySQL 8.0docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourStrongPass \ -e MYSQL_ROOT_HOST% \ -v /data/mysql:/var/lib/mysql \ -v /data/mysql-conf:/etc/mysql/conf.d \ mysql:8.0MY_SQL_ROOT_HOST设成%允许外部连接但生产环境建议限定网段。数据卷务必挂出来我见过不少人容器一删数据库全没了的惨案。主从复制的话需要额外配置。先把主库的server-id和binlog打开再在从库容器里执行CHANGE MASTER TO。一个简化的做法是直接把自定义配置写在挂载的conf.d目录下两个容器用同一个自定义网络从库通过容器名连接主库。这里强调的是使用容器名而不是IP因为容器重建后IP会变容器名不会。3.4 Redis主从部署实例Redis的主从部署比MySQL简单。创建自定义网络然后启动主从两个容器docker network create redis-net docker run -d --name redis-master --network redis-net \ -p 6379:6379 \ -v redis-master-data:/data \ redis:7 --appendonly yes docker run -d --name redis-slave --network redis-net \ -p 6380:6379 \ -v redis-slave-data:/data \ redis:7 --appendonly yes --replicaof redis-master 6379验证主从状态进入从库容器执行redis-cli info replication看到role:slave并且master_link_status:up就说明主从通了。这里有个细节用--replicaof redis-master 6379而不是IPRedis会通过容器名解析到对应地址网络里跑得很稳。如果你把Redis当缓存用数据可以不用持久化但一旦当数据库或者消息队列用appendonly yes务必开。3.5 数据卷、网络与备份恢复Docker网络默认有三种模式bridge默认单机桥接、host共享宿主机网络、none。自定义bridge网络支持容器名间的DNS解析这是Compose和多容器部署最常用的网络形态。一次docker compose up创建的服务自动进入同一个网络服务名就是主机名互相访问非常方便。数据卷的备份恢复有个实用套路。备份docker run --rm -v mysql8_data:/data -v /backup:/backup \ busybox tar czf /backup/mysql_data.tar.gz -C /data .恢复就反向解压。这套方法对测试环境迁移数据很有用单机生产环境应急也能应付。真正的生产环境数据库备份请交给专门的备份工具并定期做恢复演练不要等出事了才后悔。4. Kubernetes核心架构与控制逻辑4.1 K8s到底解决了什么问题Docker解决了单机上的容器运行问题但当你有一堆服务器要跑几十个容器时一连串问题就来了容器挂在哪台机器扩容往哪扩服务怎么发现彼此升级时如何不停机挂掉的容器谁来拉起K8s就是来回答这些问题的。它的核心思想是声明式管理你告诉它“我期望有3个副本的nginx”K8s会不断把实际状态往期望状态上靠拢。副本掉了就拉起新的扩容就把副本数调大。这个“控制循环”的机制比任何脚本轮询都可靠。4.2 Master节点与工作节点的组件分工一个K8s集群在逻辑上分Master控制面和Node工作节点两部分。Master跑四类组件kube-apiserver是所有请求的入口etcd存储集群状态kube-controller-manager执行各类控制循环kube-scheduler负责把Pod调度到合适的节点。每个工作节点上则跑着kubelet与Master通信管理Pod生命周期、kube-proxy维护网络规则和容器运行时通常还是containerd。我在企业搭建时会特别关注etcd它是整个集群的“记忆”必须定期备份。etcd挂了而没备份就等于集群失忆所有服务和数据配置都找不到。企业项目中etcd至少三副本并分开部署这是底线。4.3 Pod、Deployment、Service三层抽象K8s有三层核心抽象理解了它们K8s的骨架就通了。Pod是K8s调度的最小单位一个Pod内含一个或多个容器共享网络和存储。真正的生产实践里我建议一个Pod尽量只放一个主容器边车容器sidecar才额外塞进去比如日志采集、流量代理这类辅助角色。Deployment管理一组无状态应用副本它负责创建ReplicaSet再通过ReplicaSet控制Pod数量。日常的滚动更新、回滚都是这个层面完成。举个例子apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deploy spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80Service提供的是稳定访问入口。Pod的IP随时会变Service用标签选择器找到一组Pod把请求负载均衡到它们上面。它解决的问题很实在访问方不需要知道具体是哪个Pod在响应。4.4 Device Plugin与资源调度机制K8s默认调度器主要看CPU和内存。但机器学习任务需要GPU有些场景要用FPGA或特殊网卡这时就要靠Device Plugin机制了。设备插件是运行在节点上的一个小服务它向kubelet上报设备列表kubelet把设备作为可扩展资源暴露给调度器。你在Pod的YAML里声明resources.limits比如nvidia.com/gpu: 2调度器就会只把Pod调度到确实还有两块GPU空闲的节点上。这块我在生产环境中感触很深。没有Device Plugin之前GPU资源全靠运维手工标注节点多了根本不敢乱调度。有了插件之后GPU资源变成了和CPU内存一样普通的调度对象数据科学团队申请资源就方便多了。5. Kubernetes企业项目实战与安全加固5.1 一个典型的企业项目部署流程实际企业项目很少有人直接裸写YAML基本都走GitOps流程。我的推荐路径是这样的代码提交到Git仓库CI阶段构建Docker镜像并推送到私有仓库CD阶段用Helm或Kustomize渲染K8s清单然后执行kubectl apply发布。举个例子一个典型的Web服务Helm Chart里会包含Deployment、Service、Ingress、ConfigMap、Secret这几类资源。ConfigMap存配置文件Secret存密码和密钥两者都可以挂载成环境变量或文件。有一个我反复强调的细节应用日志不要直接写进容器文件系统要通过stdout输出再靠采集器收集。这样日志跟着Pod生命周期走采集和查看都统一。直接在容器里写日志文件Pod一重启日志就断了。5.2 未授权访问漏洞与RBAC配置K8s是典型的“默认不够安全”的系统早期最出名的问题就是未授权访问。比如kubectl的--insecure-skip-tls-verify配合错误的RBAC配置可能导致任何人都能直接访问API Server。还有kubelet的10250端口如果不设认证外部可以读取Pod信息甚至执行命令。我自己在企业里踩过类似坑后来老老实实做了三件事一是开启RBAC授权用最小权限原则。比如给某个应用只授它所在Namespace的只读权限其他资源一概禁止。二是禁止匿名访问。API Server启动参数里不能有--anonymous-authtrue集群的ServiceAccount不能乱绑定cluster-admin权限。三是对外暴露面收口。API Server的6443端口不要直接暴露到公网kubelet的10250端口只在集群内部可达。Ingress和API Server是两码事别混在一起配置。5.3 滚动更新与回滚操作企业里发版最怕的是“一次发版挂掉整个服务”。Deployment天然支持滚动更新默认策略是逐个替换Pod每新增一个健康Pod后才杀掉一个旧Pod。命令很简单kubectl set image deployment/nginx-deploy nginxnginx:1.26如果新版本有问题看RollingUpdate的状态一目了然。回滚也只需要一行kubectl rollout undo deployment/nginx-deploy但更推荐的做法是“渐进式发布”先把新版本Deployment的副本数设为1用一个独立的Service引流少量测试流量确认稳定后再把副本数拉满。这个模式生产环境很实用只是不同团队的实施成本差别比较大小型团队可以先从RollingUpdate加健康检查开始逐步演进。5.4 网络不通、API连接失败等常见故障排查Docker和K8s的排障我把高频问题整理成一张速查表现象可能原因排查命令/动作Docker容器间网络不通没放同一个自定义网络docker network inspect容器能连外网但域名解析失败DNS配置问题检查daemon.json的dns配置K8s Pod一直Pending资源不够/污点限制kubectl describe pod看事件Pod频繁重启启动命令有问题或存活探测失败kubectl logs加--previousService访问不通标签选择器不匹配kubectl get endpointsDocker命令报权限错误用户不在docker组参考2.4节方案Docker Desktop npipe连接失败引擎未启动/WSL内核太旧wsl --update后重启这里说一个最关键的排查习惯先看事件再看日志最后看配置。很多人一上来就改配置结果越改越乱。kubectl describe pod xxx能直接告诉你调度失败的原因kubectl logs能告诉应用为什么起不来把这两步跑完大部分问题都已经定位了。网络方面K8s集群用Calico或Flannel这类CNI插件Pod和Service的流量走iptables或IPVS规则。如果出现“部分服务间歇性连不上”优先查CNI插件的健康状态其次查节点上的iptables规则是否被其他软件覆盖。我碰到过一次就是改防火墙规则时顺手刷掉了K8s的链折腾了半天才恢复。6. 学习路线与个人踩坑心得6.1 我建议的学习路径如果完全零基础我的建议顺序是先花一周把Linux基本命令练熟然后学Docker重点掌握docker run、docker build、docker compose这三件事。能用Compose把一套前后端加数据库的项目跑起来Docker这关就算过了。K8s的学习不要一上来就搭集群先在单机环境或云服务商托管集群上操作从kubectl get pods看起逐步理解Deployment、Service、Ingress、ConfigMap这些对象。等把工作负载玩明白了再学kubeadm搭建高可用集群了解etcd、证书、调度器这些控制面的东西。最后建议补上Helm和GitOps一个是打包和分发K8s应用的工业标准一个是把应用发布变成代码审查流程的工程实践。这两样在企业里几乎是标配早学早受益。6.2 几条比较实用的经验这几年下来有几点心得想分享给正在学习的朋友。第一不要过度包装镜像。我见过有人把所有工具都塞进一个镜像里结果镜像体积好几个GB拉取部署全是泪。尽量用官方基础镜像运行时依赖什么装什么体积小、漏洞面也小。第二环境隔离要分清边界。Docker解决的是应用级依赖隔离K8s解决的是集群级资源调度和故障恢复两者不是替代关系。别指望Docker能解决高可用也别嫌K8s太重搞清楚每种工具解决什么层次的问题架构设计才不会走偏。第三安全是学出来的也是配出来的。早期学习阶段把K8s的端口暴露公网练手是我做过最后悔的事之一。好在当时只是测试环境没酿成大祸。建议大家不论环境大小都养成配置RBAC、关闭匿名访问、不暴露管理端口的习惯。最后再提一个小技巧学习期间多用kubectl explain和docker inspect。这两个命令是官方“说明书”能让你不依赖搜索引擎也能搞清楚每个字段的含义。我自己遇到不确定的YAML字段第一反应永远是kubectl explain deployment.spec比翻文档快得多。容器化这条路越往后越有意思。Docker只是起点K8s的世界里还有Operator、Service Mesh、Serverless这些更广阔的主题等着探索。希望这篇总结能帮你少踩几个坑把时间和精力花在真正有价值的事情上。