docker和k8s的最佳实践
准备工作
- 安装docker desktop
- 通过docker desktop安装k8s集群
- 一个基础的nest项目
docker上手
容器常用命令
1 | |
镜像常用命令
1 | |
快速上手理解
1 | |
我们拆分来看
docker run:启动命令--name elasticsearch:给容器指定一个名字-d:表示 detached 模式,容器会在后台运行,而不是占用当前终端-p 9200:9200 -p 9300:9300:端口映射,将容器内部端口映射到宿主机端口。9200是REST API 调用和 Kibana 访问,9300是集群内部通信端口(Elasticsearch 节点间通讯)-v /data/elasticsearch:/usr/share/elasticsearch/data:宿主机目录/data/elasticsearch映射到容器内 Elasticsearch 数据目录/usr/share/elasticsearch/data,这样即使容器删除,数据仍然保留在宿主机上-e "discovery.type=single-node":告诉 Elasticsearch 这是 单节点模式docker.elastic.co/elasticsearch/elasticsearch:7.16.2:指定要运行的镜像和版本
把nest应用打包成镜像
这里我们需要一个nest new project-name,不需要任何的其他依赖,我们通过nest创建的基础模板应用可以让我们访问本机的127.0.0.1:3000端口并且成功返回Hello World!
现在我们需要把这个nest应用一步步打包成镜像,让它可以随处使用
首先,我们在项目的根目录下新建 Dockerfile 文件
1 | |
然后我们打包构建镜像,镜像名称是 nest-app,镜像的标签是 latest
1 | |
最终我们通过命令基于 nest-app 镜像启动一个容器,宿主机的端口是在左边,容器内部端口是在右边,容器名称为 nest-test,镜像标签为 nest-app:latest
1 | |
我们通过 docker images 可以查看我们的镜像信息
1 | |
可以看到一个 repository 名字叫 nest-app,它的tag叫 latest
通过 docker ps --filter="name=nest-test"命令来查看容器信息
1 | |
在浏览器上输入 http://127.0.0.1:3001/,即可以看到 Hello World!
Kubernetes上手
docker的优点
我们可以把项目直接打包成镜像,然后把镜像放到自己的镜像仓库。
docker从开发,运维,部署角度均有优点存在:
- 容器直接运行在操作系统内核上,不像虚拟机需要完整的 OS,因此启动快、占用资源少
- 同一台主机上可以运行 更多实例,节省硬件成本
- 容器把应用及依赖打包在一起,环境一致性保证,避免“依赖冲突”和“环境不一致”问题
- 镜像可移植:在不同服务器、云平台上都能运行
- 支持多阶段构建,能生成轻量化生产镜像,快速部署上线
- 与 Jenkins、GitHub Actions、GitLab CI 等 CI/CD 工具无缝集成
- 可以在构建阶段完成依赖安装、编译打包,再生成干净的生产镜像,实现标准化流水线
- 每个容器运行在独立的命名空间和文件系统中,不同容器之间互不干扰,减少了应用之间的依赖冲突
- 容器启动快、占用少,非常适合 微服务架构
- 可以从官方基础镜像出发,自定义镜像
为什么会有Kubernetes
我们发现启动一个容器,只能暴露一个宿主机端口,这就意味着,当我的nest应用需要水平扩展,这时候就需要通过nginx统一入口,然后启动容器的时候不映射宿主机端口,通过docker网络通信,用户访问nginx的固定端口,然后由nginx将请求分发到nest1和nest2应用,这显然比较麻烦。
所以docker有它的局限性:
- Docker 本身只管理单台主机上的容器,当应用需要跨多台服务器部署时,Docker 并没有原生的集群管理能力
- Docker 可以通过
docker-compose做简单的多容器编排,但在生产环境中,服务数量多、依赖复杂时,Compose 不够健壮 - 容器故障时,需要人工重启,负载均衡、弹性扩缩容、滚动更新等功能不够完善
所以k8s出现的意义,其实是与docker的互补
| 功能 | Docker 单机 | K8s(集群管理) |
|---|---|---|
| 集群管理 | ❌ 无 | ✅ 管理多台服务器上的容器 |
| 弹性伸缩 | ❌ 需要手动 | ✅ 自动扩缩容 |
| 健康检查 | ❌ 需要外部工具 | ✅ 自动重启或迁移故障容器 |
| 服务发现 & 负载均衡 | ❌ 内建不足 | ✅ 内建服务发现与负载均衡 |
| 滚动更新 / 回滚 | ❌ 手动 | ✅ 原生支持 |
| 存储与网络抽象 | ❌ 简单卷挂载 | ✅ 支持动态卷和多网络模式 |
k8s的核心概念
Pod
最小的运行单元,包含一个或多个容器(通常是一个)
1 | |
Depolyment
管理 Pod 的创建、更新、扩缩容,Deployment 是一个 控制器(Controller),用于声明“你希望有多少个 Pod、用什么镜像、如何更新”
1 | |
Service
暴露 Pod 的统一访问入口,提供负载均衡与服务发现
Service 是 Pod 的抽象访问层,为一组相同功能的 Pod 提供固定的虚拟 IP、内部负载均衡和服务发现
自动把请求分发到健康的 Pod
Pod IP 动态变化时,Service IP 不变
支持多种类型
ClusterIP(默认,集群内部访问)NodePort(通过节点端口暴露给外部)LoadBalancer(云环境自动接入外部 LB)
1 | |
Ingress
管理外部 HTTP/HTTPS 访问入口(反向代理 / API 网关)
基于域名或路径的路由(如 api.example.com → nest-service)
支持 SSL/TLS
可以配置重写路径、限流、鉴权等高级策略
1 | |
ConfigMap / Secret
管理应用配置(普通配置、敏感信息)
ConfigMap:存放普通配置(非敏感)
Secret:存放敏感信息(如密码、Token、证书),会 base64 加密存储
实现配置与镜像解耦,可注入为:环境变量、配置文件、命令行参数
1 | |
Replica Sets
ReplicaSet 是 负责维持 Pod 数量的控制器,由 Deployment 自动管理
维持一组 Pod 的副本数
Pod 挂了自动重启新 Pod
Deployment 滚动更新时,会创建新的 ReplicaSet 并逐步替换旧的
逻辑运行图
1 | |
k8s常用命令
1 | |
通过k8s部署nest应用
在项目根目录下新建 k8s-deploy.yaml 文件,文件内容如下:
1 | |
然后我们执行部署命令
1 | |
- apply:申明式部署
- -f:参数,表示从文件中读取定义
然后我们就成功部署了nest应用到k8s上
部署成功的表现如何
上面我们通过 kubectl proxy 启动了 k8s 的UI管理界面
我们通过 http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/workloads?namespace=default打开UI界面
我们可以看到一个deployments,两个pods,在services服务中看到了一个 nest-service,我这边暴露出来的是30080端口,直接在宿主机上访问 http://127.0.0.1:30080/,又成功看到了 Hello World!
我们用一张图来展示它们其中的关系~
flowchart TB
subgraph Cluster["Kubernetes Cluster"]
subgraph ConfigMapGroup["ConfigMap"]
C1["📄 ConfigMap<br>nest-app-config"]
end
subgraph DeployGroup["Deployment"]
D1["🧩 Deployment<br>nest-app"]
RS1["🧱 ReplicaSet<br>(由 Deployment 管理)"]
end
subgraph PodGroup["Pods (2 replicas)"]
P1["🟣 Pod #1<br>nest-app 容器"]
P2["🟣 Pod #2<br>nest-app 容器"]
end
subgraph ServiceGroup["Service"]
S1["🌐 Service<br>nest-service<br>NodePort:30080 → 3000"]
end
end
%% 关系
C1 -->|提供环境变量| P1
C1 -->|提供环境变量| P2
D1 -->|创建与管理| RS1
RS1 -->|维持副本 replicas=2| P1
RS1 --> P2
S1 -->|选择 app=nest-app| P1
S1 -->|选择 app=nest-app| P2
%% 样式
classDef config fill:#b3d4fc,stroke:#0366d6,stroke-width:1px,color:#000;
classDef deploy fill:#f9d29d,stroke:#b58900,stroke-width:1px,color:#000;
classDef service fill:#b7e4c7,stroke:#2d6a4f,stroke-width:1px,color:#000;
classDef pod fill:#d9c2f0,stroke:#6a1b9a,stroke-width:1px,color:#000;
class C1 config;
class D1,RS1 deploy;
class S1 service;
class P1,P2 pod;
docker + ci/cd + k8s的最佳实践
CI/CD 的本质是 “构建镜像(Build) → 推送仓库(Push) → 部署更新(Deploy)”
Kubernetes 负责运行与弹性伸缩,CI/CD 负责自动化管控
我们来拆解一下Docker + CI/CD + Kubernetes 的实际执行逻辑
- build阶段,CI/CD 工具(GitHub Actions、GitLab CI、Jenkins 等)主要拉取最新代码,然后安装依赖,构建产物,最后构建docker镜像,最后提供的是一个独立可运行的镜像(包含 Node 运行时 + 编译后的 Nest.js 应用)
- push阶段,推送镜像到自己的仓库,CI/CD 会自动使用仓库凭证登录推送
- Deploy 阶段,Kubernetes 根据 YAML 文件(Deployment / Service / ConfigMap 等)创建或更新资源
flowchart TD
A[开发提交代码<br>push to Git repo] --> B[CI 构建<br>npm ci + npm run build] --> C[构建 Docker 镜像<br>docker build] --> D[推送镜像仓库<br>docker push]
D --> E[部署阶段<br>kubectl apply 或<br>kubectl set image] --> F[Kubernetes Deployment<br>滚动更新 Pods] --> G[Service<br>统一访问入口] --> H[用户访问]
style B fill:#f9d29d,stroke:#b58900,color:#000
style C fill:#f4a261,stroke:#b56500,color:#fff
style D fill:#94d2bd,stroke:#2a9d8f,color:#000
style E fill:#bde0fe,stroke:#0077b6,color:#000
style F fill:#bde0fe,stroke:#0077b6,color:#000
style G fill:#caffbf,stroke:#588157,color:#000
由于我们的nest应用会不断更新迭代,所以我们遇到了一个问题,当我重复打镜像的时候,我需要先去 k8s-deployment.yaml 文件中修改镜像标签,然后再次执行 kubectl apply -f k8s-deployment.yaml,这显然很是麻烦,所以我们可以 kubectl set image deployment/nest-app nest-app=your-registry/nest-app:v2,它做了三件事:
- Kubernetes 直接修改 Deployment 的 Pod 模板镜像(spec.template.spec.containers[].image)
- Deployment 会检测到模板变化,自动触发 滚动更新
- 新的 Pod 会逐步替换旧 Pod,保证服务可用
如果你要增加副本数、修改资源限制、修改端口那可以再修改 k8s-deployment.yaml,后续只是更新镜像时,你 可以不用修改 YAML 文件,直接用 kubectl set image 就够了,YAML 文件仍然可以保留,用作版本控制或者重新部署集群时使用
1 | |
技术学习代办
- 后续有时间再记录K8S的基础配置和敏感配置的注入
- 记录多集群布署
- 记录nest项目连接MySQL、MONGO、ES、REDIS等数据存储介质的方式以及部署过程
- 使用真实服务器以及github的ci/cd部署项目