Skip to content

Docker, Redis & Kubernetes 新手完全指南:从容器到云原生部署

引言:一个“盖房子”的比喻

为了理解这三者的关系,我们先用一个盖房子的比喻:

  • Docker (集装箱/标准砖块):它负责把你的应用程序(家具、电器)和所有依赖(电线、水管)打包成一个标准的、可移动的“集装箱”。无论这个集装箱被运到哪里(开发、测试、生产环境),里面的东西都能正常工作。Docker 解决了“环境一致性”和“打包”的问题。

  • Redis (高性能家电):它就像你房子里的一个超高速智能冰箱。你把最常用的食材(热点数据)放进去,随用随取,速度飞快,而不用每次都去遥远的仓库(传统数据库)里翻找。Redis 解决了“数据访问性能”的问题。

  • Kubernetes (建筑公司/物业):当你需要盖一整个小区(大规模应用),而不是一栋房子时,你就需要一个专业的建筑公司。它负责调度所有砖块(Docker 容器)的摆放位置、搭建水电网络(服务发现)、监控房屋状况(健康检查),如果一栋房子塌了(程序崩溃)就自动重建,人流量大了(高并发)就自动加盖新楼(扩容)。Kubernetes 解决了“大规模应用编排和管理”的问题。

学习路径:我们将遵循 Docker -> Redis -> Kubernetes 的顺序,最后将它们完美地融合在一起。


第一部分:Docker - 应用的打包标准

1.1 Docker 是什么?(核心概念)

Docker 是一个开源的应用容器引擎,它允许开发者将应用及其依赖打包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 或 Windows 机器上,也可以实现虚拟化。

  • 与虚拟机的区别

    • 虚拟机 (VM):在物理机上虚拟化出一整套硬件,再安装一个完整的操作系统,最后部署应用。笨重、启动慢。
    • 容器 (Container):直接运行在宿主机的操作系统上,共享操作系统内核,只打包应用本身和必要的库。轻量、秒级启动。
  • 核心术语

    • 镜像 (Image):一个只读的模板,包含了运行应用所需的一切:代码、运行时、库、环境变量和配置文件。就像一张系统安装光盘。
    • 容器 (Container):镜像的运行实例。你可以从同一个镜像启动多个容器,它们之间相互隔离。就像用同一张光盘安装了多台电脑。
    • Dockerfile:一个文本文件,包含了构建镜像所需的所有指令。它是镜像的“食谱”或“设计蓝图”。
    • Docker Hub / Registry:存放和分发镜像的仓库,类似于代码界的 GitHub。

1.2 什么时候用 Docker?(应用场景)

  • 解决“在我电脑上明明是好的”问题:打包环境,确保开发、测试、生产环境完全一致。
  • 微服务架构:每个微服务都可以打包成一个独立的镜像,独立开发、部署和扩展。
  • CI/CD (持续集成/持续部署):构建一次镜像,可以在整个流水线中部署到任何地方。
  • 快速搭建复杂的开发环境:一行命令就能启动一个包含数据库、缓存、消息队列的完整环境。

1.3 简单案例:打包一个 Node.js Web 应用

第1步:准备应用代码 创建一个文件夹 my-node-app,里面包含两个文件:

  • app.js:
    javascript
    const http = require('http');
    const os = require('os');
    
    const server = http.createServer((req, res) => {
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end(`Hello from Container ID: ${os.hostname()}\n`);
    });
    
    server.listen(3000, () => {
        console.log('Server running on port 3000');
    });
  • package.json:
    json
    {
      "name": "my-node-app",
      "version": "1.0.0",
      "main": "app.js",
      "scripts": {
        "start": "node app.js"
      }
    }

第2步:编写 Dockerfilemy-node-app 文件夹中创建 Dockerfile 文件:

dockerfile
# 1. 选择一个基础镜像
FROM node:16-alpine

# 2. 设置工作目录
WORKDIR /app

# 3. 复制 package.json 文件并安装依赖
COPY package.json .
RUN npm install

# 4. 复制所有应用代码
COPY . .

# 5. 暴露容器的 3000 端口
EXPOSE 3000

# 6. 定义容器启动时执行的命令
CMD ["npm", "start"]

第3步:构建和运行容器my-node-app 目录下打开终端:

bash
# 构建镜像,并命名为 my-app:1.0
docker build -t my-app:1.0 .

# 运行容器
# -d: 后台运行
# -p 8080:3000: 将主机的 8080 端口映射到容器的 3000 端口
# --name web-server: 给容器起个名字
docker run -d -p 8080:3000 --name web-server my-app:1.0

# 测试
curl http://localhost:8080
# 你会看到类似 "Hello from Container ID: a1b2c3d4e5f6" 的输出

# 清理
docker stop web-server
docker rm web-server

1.4 Docker 使用理念

  • 不可变性:容器应该是不可变的。不要进入正在运行的容器去修改代码,而是修改代码后,重新构建镜像,再启动新容器。
  • 一个容器一个进程:最佳实践是每个容器只运行一个核心应用进程。这使得容器的职责更单一,更容易管理和监控。
  • 无状态化:尽量让你的应用容器无状态。需要持久化的数据(如数据库文件、日志)应该存储在数据卷 (Volume) 中,将数据与容器的生命周期解耦。

第二部分:Redis - 高性能数据引擎

(这部分概念与上一份回答类似,但案例将更侧重于与 Docker 结合)

2.1 Redis 是什么?(核心概念)

Redis 是一个开源的、基于内存的、键值对 (Key-Value) 数据库,以其极高的性能著称。它支持多种数据结构(字符串、哈希、列表等),并可通过持久化机制保证数据安全。

2.2 什么时候用 Redis?(应用场景)

  • 数据缓存:减轻数据库压力,加速应用响应。
  • 分布式会话存储:解决多实例应用下的用户登录状态共享问题。
  • 排行榜/计数器:利用其原子操作,高效实现实时计数功能。
  • 简单的消息队列:通过列表结构实现异步任务处理。

2.3 简单案例:使用 Docker Compose 搭建“Web应用 + Redis”

docker-compose 是一个用于定义和运行多容器 Docker 应用的工具。

第1步:改造 Node.js 应用以连接 Redis 安装 redis 客户端: npm install redis。修改 app.js

javascript
const http = require('http');
const os = require('os');
const { createClient } = require('redis');

// 连接 Redis
const client = createClient({
    // 'redis' 是 docker-compose 中定义的服务名
    url: 'redis://redis:6379'
});
client.on('error', (err) => console.log('Redis Client Error', err));
client.connect();

const server = http.createServer(async (req, res) => {
    // 每次访问,计数器加 1
    const visits = await client.incr('visits');

    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end(`Hello from Container ID: ${os.hostname()}\nVisits: ${visits}\n`);
});

server.listen(3000, () => {
    console.log('Server running on port 3000');
});

别忘了更新 package.json 并重新构建镜像 docker build -t my-app:2.0 .

第2步:编写 docker-compose.yml 文件 在项目根目录创建 docker-compose.yml 文件:

yaml
version: '3.8'

services:
  # Web 应用服务
  app:
    image: my-app:2.0 # 使用我们新构建的镜像
    ports:
      - "8080:3000"
    # 关键:定义了 app 服务依赖 redis 服务
    depends_on:
      - redis

  # Redis 服务
  redis:
    image: "redis:6-alpine"
    volumes:
      # 将 redis 数据持久化到本地的 ./redis-data 目录
      - ./redis-data:/data

volumes:
  redis-data:

第3步:一键启动

bash
# 启动所有服务
docker-compose up -d

# 测试
curl http://localhost:8080
# 第一次返回 Visits: 1
curl http://localhost:8080
# 第二次返回 Visits: 2

# 清理
docker-compose down

这个案例完美展示了 Docker 如何轻松地将多个服务(你的应用和Redis)连接在一起,并通过 volumes 实现数据持久化。


第三部分:Kubernetes (K8s) - 分布式应用的操作系统

3.1 K8s 是什么?(核心概念)

Kubernetes 是一个用于自动化部署、扩展和管理容器化应用程序的开源系统。它将多台机器组成一个集群,并为你提供统一的视图和API来管理它们。

  • 核心术语
    • Pod:K8s 中最小的部署单元,通常包含一个主应用容器。Pod 是短暂的,随时可能被销毁重建。
    • Deployment:管理无状态应用的控制器。你声明期望的副本数,它会确保这个数量的 Pod 始终在运行。负责应用的部署、更新和扩容
    • StatefulSet:管理有状态应用的控制器(如数据库)。它为 Pod 提供稳定的网络标识和持久化存储
    • Service:为一组 Pod 提供一个稳定、统一的访问入口。无论后端 Pod 如何变化,Service 的 IP 和 DNS 名称是固定的,解决了“如何找到 Pod”的问题。
    • PersistentVolume (PV) & PersistentVolumeClaim (PVC):K8s 的存储机制。PVC 是 Pod 对存储的“申请单”,PV 是实际的存储资源。这让存储和计算解耦。
    • kubectl:与 K8s 集群交互的命令行工具。

3.2 什么时候用 K8s?(应用场景)

  • 当你的应用需要高可用性(不能宕机)。
  • 当你的应用需要弹性伸缩(根据负载自动增减实例)。
  • 当你的微服务数量众多,手动管理变得不可能时。
  • 希望实现标准化的、与云厂商无关的声明式部署。

3.3 简单案例:将“Web应用 + Redis”部署到 K8s

现在,我们将 docker-compose 中的应用迁移到 K8s 上。(你需要一个 K8s 集群,如 Minikube, kind, or Docker Desktop 自带的 K8s)

第1步:确保你的镜像在 K8s 集群可访问的 Registry 中 如果是本地集群(如 Minikube),你可以直接使用本地 Docker daemon 的镜像。如果是云上的集群,你需要将镜像推送到 Docker Hub 或云厂商的镜像仓库(如 GCR, ECR)。 docker tag my-app:2.0 your-dockerhub-username/my-app:2.0docker push your-dockerhub-username/my-app:2.0

第2步:为 Redis 创建 StatefulSetService (redis.yaml)

yaml
# redis-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: redis-service
spec:
  selector:
    app: redis
  ports:
    - protocol: TCP
      port: 6379
      targetPort: 6379
---
# redis-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
spec:
  serviceName: "redis-service"
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:6-alpine
        ports:
        - containerPort: 6379
        volumeMounts:
        - name: redis-data
          mountPath: /data
  volumeClaimTemplates:
  - metadata:
      name: redis-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

为什么用 StatefulSet? 因为 Redis 需要稳定的存储,我们不希望 Pod 重启后数据丢失。

第3步:为 Node.js 应用创建 DeploymentService (app.yaml)

yaml
# app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app-deployment
spec:
  replicas: 3 # 部署3个副本,实现高可用
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-app
        # 替换成你自己的镜像地址
        image: your-dockerhub-username/my-app:2.0 
        ports:
        - containerPort: 3000
        env: # 注入环境变量
        - name: REDIS_HOST
          value: "redis-service" # K8s Service 的 DNS 名称
---
# app-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  # 使用 LoadBalancer 类型,让服务能从外部访问
  # 在 Minikube 中,可以使用 `minikube tunnel` 命令
  type: LoadBalancer 
  selector:
    app: my-app
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000

为什么用 Deployment? 因为我们的 Web 应用是无状态的,可以随意创建和销毁。

第4步:部署到 K8s

bash
# 部署 Redis
kubectl apply -f redis.yaml

# 部署 Web 应用
kubectl apply -f app.yaml

# 查看状态
kubectl get pods
# 等待所有 Pod 都变成 Running

kubectl get service
# 找到 my-app-service 的 EXTERNAL-IP

# 测试
# 多次访问 http://<EXTERNAL-IP>,你会看到计数器增加,
# 并且 Container ID 可能会变化,因为请求被负载均衡到了3个不同的 Pod

第四部分:全景图 - 它们如何协同工作

  1. 开发阶段:你在本地编写代码 (app.js) 和 Dockerfile
  2. 打包阶段:你使用 docker build 将应用打包成一个标准化的镜像
  3. 测试阶段:你可能使用 docker-compose 在本地快速拉起应用和 Redis,进行集成测试。
  4. 分发阶段:你将构建好的镜像 docker push 到一个镜像仓库 (Registry)。
  5. 部署阶段:你编写 K8s 的 YAML 文件(Deployment, StatefulSet, Service),声明式地描述你的应用最终应该是什么样子。
  6. 运行阶段kubectl apply 将这个“期望状态”告诉 K8s。K8s master 节点接收指令,开始调度:
    • 在某个工作节点上拉取 Redis 镜像,创建一个带持久化存储的 Pod。
    • 在其他工作节点上拉取你的应用镜像,创建3个 Pod 副本。
    • 创建内部的 redis-service,让应用 Pod 能通过固定的名字找到 Redis。
    • 创建外部的 my-app-service,为你提供一个公网 IP 来访问应用。
  7. 运维阶段:K8s 的控制器会持续监控集群状态。
    • 如果一个应用 Pod 崩溃了,Deployment 控制器会立刻创建一个新的来替代它。
    • 如果流量高峰来了,你可以通过 kubectl scale deployment my-app-deployment --replicas=10 轻松扩容。
    • 如果需要更新应用,你只需要修改 Deployment 中的镜像版本,K8s 会自动进行滚动更新。

常见问题汇总

  • Docker
    • 问题: docker: command not foundpermission denied
    • 解决: 确保 Docker 已安装并运行。在 Linux 上,将当前用户添加到 docker 用户组:sudo usermod -aG docker $USER (需重新登录生效)。
  • Redis on Docker
    • 问题: 应用容器连接不上 Redis 容器。
    • 解决: 确保它们在同一个 Docker 网络中(docker-compose 自动处理)。应用连接的地址应该是 Redis 服务的名称(如 redis),而不是 localhost
  • Kubernetes
    • 问题: Pod 状态为 ImagePullBackOff
    • 解决: K8s 集群无法拉取你的镜像。检查镜像名称是否正确,仓库是否是私有的(如果是,需要配置 ImagePullSecrets)。
    • 问题: Pod 状态为 CrashLoopBackOff
    • 解决: 容器启动后立即崩溃。使用 kubectl logs <pod-name> 查看容器日志,定位应用代码中的错误。
    • 问题: 应用连接不上 Redis redis-service
    • 解决: 检查 Serviceselector 是否正确匹配了 Redis Pod 的 label。使用 kubectl describe service redis-service 查看 Endpoints 是否有地址。