在本章节开始之前,交给大家一个任务,自行部署一个可用的 K8s 集群(例如通过 Minikube、Kind 或云服务商提供的 K8s 服务或者本地计算机部署)以及一个配置好 kubectl
命令行的本地环境。
如果你尚未准备好,请参考相关文档完成 K8s 和 Docker 的安装与配置( Windows 或者 Macos 可以通过 Docker Desktop 来简易运行 K8s 服务)。
在前三章中,我们详细探讨了 K8s 的基本概念、架构以及工作原理。现在,是时候将这些理论知识付诸实践了!
本章将带你完成两个常见的实战部署任务:在 K8s 集群中部署一个 Redis 集群和一个 PostgreSQL 数据库。
在开始部署之前,我们需要明确有状态应用和持久化存储的概念。
通常在分布式架构环境的前提下,我们的程序都会尽可能设计为无状态应用(服务)。
为了支持有状态应用,K8s 也引入了持久化存储 (Persistent Storage) 的概念。它允许应用程序将数据存储在独立于 Pod 的存储卷上,即使 Pod 被删除或重新创建,数据也不会丢失。
在 K8s 中部署任何应用,都需要通过 YAML 文件来描述你期望的集群状态。我们将主要使用以下三种类型的 YAML 文件作为我们的“工具”:
ConfigMap (配置映射):配置的“百宝箱”
Service (服务):应用程序的“稳定入口”
StatefulSet (有状态集):管理有状态应用的“金牌管家”
Deployment
,StatefulSet 确保 Pod 具有有序的启动/停止/扩缩容(例如,redis-0
总是第一个启动),并且每个 Pod 都会绑定一个稳定的、唯一的名称和持久化存储卷。这对于数据库等需要数据持久性和身份一致性的应用至关重要。它就像一个“金牌管家”,精心地照顾每一个需要身份和私人空间(持久存储)的应用程序。感觉没听懂?没有关系,我们来实际的操作一下。
Redis 是一个高性能的键值存储数据库,常用于缓存、消息队列等场景。为了实现高可用和数据分片,我们通常会部署 Redis 集群。
在 K8s 中,我们主要会用到以下 K8s 对象来部署 Redis 集群:
部署步骤概览:
ConfigMap
来定义 Redis 的集群模式配置。StatefulSet
来部署 Redis 的主从 Pod,确保它们具有稳定的身份和持久化存储。Service
来为 Redis 节点提供集群内部的访问入口,并可能通过其他 Service 类型暴露给外部。让我们赶紧开始吧~。
创建 ConfigMap
我们的目标是部署一个简单的 Redis 集群,包含 3 个主节点和 3 个从节点。为了简化,我们将让每个主节点带一个从节点,共 3 对主从副本。
apiVersion: v1
kind: ConfigMap # 申明类型
metadata:
name: redis-cluster-config
data:
# redis.conf(配置信息)
redis.conf: |
cluster-enabled yes
cluster-config-file /data/nodes.conf
cluster-node-timeout 5000
appendonly yes
protected-mode no
bind 0.0.0.0
# 确保日志级别为 notice,方便调试
loglevel notice
创建 Headless Service
为了让 StatefulSet 中的每个 Pod 拥有稳定的网络名称(redis-0.redis-service
等),我们需要一个特殊的 Service,称为 Headless Service (无头服务)。它不会分配一个集群 IP,也不负责负载均衡,而是直接返回所有关联 Pod 的 IP 地址(暴露所有服务的地址)。
apiVersion: v1
kind:Service
metadata:
name:redis-service# 这个名称会被 StatefulSet 用作网络域
labels:
app:redis-cluster
spec:
ports:
-port:6379# Redis 端口
name:client
-port:16379# 集群总线端口
name:cluster
clusterIP:None# 关键:设置为 None 表示这是一个 Headless Service
selector:
app:redis-cluster
Headless Service 名称 redis-service
将会用于构成 Pod 的完整 FQDN:<pod-name>.<service-name>.<namespace>.svc.cluster.local
。
创建 StatefulSet
这是核心部分,定义了 Redis Pod 如何被管理、存储和网络标识。我们将创建 6 个副本(3 主 3 从)。
apiVersion: apps/v1
kind:StatefulSet
metadata:
name:redis # StatefulSet 的名称
labels: # StatefulSet 的标签,用于标识
app:redis-cluster
spec:
serviceName:"redis-service"# 必须指定一个 Headless Service 的名称,StatefulSet 会用它来为 Pod 提供稳定的网络标识
replicas:6 # 定义 Pod 数量,这里是 6 个 (3 主 + 3 从),它们将自动命名为 redis-0,...,redis-5
selector: # Pod 选择器,用于 StatefulSet 查找和管理其所属的 Pod
matchLabels:
app:redis-cluster
template: # Pod 模板,用于创建 Pod
metadata:
labels: # Pod 的标签,需要与 selector.matchLabels 匹配
app:redis-cluster
spec:
containers: # 定义 Pod 中运行的容器列表
-name:redis # 容器的名称
image:redis:6.2.7-alpine# 容器使用的镜像及其版本
command:["redis-server"]# 容器启动时执行的命令,这里是启动 Redis 服务器
args:["/etc/redis/redis.conf"]# 传递给 command 的参数,指定 Redis 配置文件路径
ports:
-containerPort:6379# 容器暴露的端口号,用于客户端连接
name:client # 端口名称
-containerPort:16379# 容器暴露的端口号,用于集群内部通信
name:cluster
volumeMounts: # 容器内卷的挂载点列表
-name:redis-config# 引用名为 'redis-config' 的卷
mountPath:/etc/redis# 将该卷挂载到容器内的 /etc/redis 路径
-name:redis-data # 引用名为 'redis-data' 的卷
mountPath:/data # 将该卷挂载到容器内的 /data 路径 (Redis 数据目录)
env: # 容器内的环境变量列表
-name:POD_NAME # 定义一个名为 POD_NAME 的环境变量
valueFrom: # 环境变量的值来源于 K8s 的某个字段
fieldRef:
fieldPath:metadata.name# 获取当前 Pod 的名称作为环境变量的值 (如 redis-0, redis-1)
volumes: # 定义 Pod 中可用的卷列表
-name:redis-config # 定义一个名为 'redis-config' 的卷
configMap: # 这个卷的数据来源是 ConfigMap
name:redis-cluster-config# 引用名为 'redis-cluster-config' 的 ConfigMap
volumeClaimTemplates:# 卷声明模板:StatefulSet 为每个 Pod 自动创建 PVC 的模板
-metadata:
name:redis-data # PVC 的名称,每个 Pod 会是 redis-data-<pod-name> (如 redis-data-redis-0)
spec:
storageClassName:standard# 明确示例存储类名称
accessModes:["ReadWriteOnce"]# 存储的访问模式:ReadWriteOnce 表示只能被单个节点以读写模式挂载
resources:
requests:
storage:1Gi # 请求的存储空间大小,每个 Pod 将获得 1GB
# storageClassName: your-storage-class # (可选) 指定存储类。如果 K8s 集群没有默认 StorageClass,或者需要特定的存储类型,请在此处指定。例如 'standard' 或 'gp2' (AWS)。
运行 Pod
拓展:
现在,我们配置都已经定义好了,只需要我们开始传达执行命令即可让整个服务运转起来。
kubectl 是 Kubernetes 的命令行工具 (Command-Line Interface)。它是你与 K8s 集群进行交互的主要方式,就像一个遥控器,它允许你发送指令给 K8s 集群的 API Server,从而实现对集群中各种资源的创建、管理、查看、删除等操作。
我们需要使用以下命令来应用我们的配置文件:
kubectl apply -f xxx.yaml
kubectl apply -f redis-config.yaml
kubectl apply -f redis-service.yaml
kubectl apply -f redis-statefulset.yaml
Pod
: K8s 中最小的部署单元。Service
: 用于暴露 Pod 的稳定网络服务。Deployment
: 用于管理无状态应用程序的 Pod 副本和更新。StatefulSet
: 用于管理有状态应用程序的 Pod 副本,提供稳定的身份和持久化存储。ConfigMap
: 用于存储非敏感配置数据。Secret
: 用于存储敏感数据(如密码、API 密钥)。PersistentVolumeClaim
(PVC): Pod 对持久化存储的请求。PersistentVolume
(PV): 集群提供的实际持久化存储资源。Namespace
: 用于集群内资源隔离的逻辑分组。Node
: 集群中的一台物理或虚拟机(由 K8s 自动发现并创建对应资源)。ReplicaSet
: 确保特定数量的 Pod 副本在运行(通常由 Deployment 间接创建和管理)。kubectl apply -f your-file.yaml
命令提交一个 YAML 文件时,K8s 的 API Server 会首先读取 kind
字段,以确定如何解析和处理这个 YAML 文件。如果 kind
写错了,K8s 就不知道你想要创建什么,从而报错。kind
帮助 K8s 内部系统对各种资源进行分类和管理。kubectl get <kind>
、kubectl describe <kind>
等命令时,也需要指定 kind
来筛选和查看特定类型的资源。好的,我们来详细解释一下 K8s YAML 文件中的 kind
字段。这是一个非常基础但核心的 K8s 概念。
kind
:K8s 对象的“类型标识”
在 K8s 的 YAML 定义文件中,kind
字段是有严格规定的,不能自定义名称。
1. kind
的定义
kind
(英文单词“种类”的意思)字段用于指定你正在创建或操作的 K8s 资源的类型。每一个 K8s 资源都有一个唯一的 kind
。
K8s 是一个高度模块化的系统,它通过各种不同的“对象”(也就是资源)来管理集群中的各种元素。kind
字段就是用来告诉 K8s API Server:“我正在定义一个什么类型的资源”。
2. 常见的 kind
值
以下是一些最常见的 kind
值及其对应的 K8s 对象:
3. kind
的重要性
4. kind
的来源
kind
值是 K8s API 定义的一部分,它们由 K8s 的开发者预先定义好,并在 K8s 的官方文档中列出。你可以在 K8s 官方文档的 API 参考中找到所有可用的 kind
类型。
验证 Pod 和 PVC 状态
让我们稍等一段时间,大约半分钟左右,K8s 会自动完成 Pod 的创建。
# 查看 Pod 状态,你应该会看到 `redis-0` 到 `redis-5` 陆续创建并变为 `Running` 状态。
kubectl get pods -l app=redis-cluster
# 查看 PVC 状态,你应该会看到为每个 Pod 自动创建的 PVC,例如 `redis-data-redis-0` 到 `redis-data-redis-5`,它们应该都处于 `Bound` 状态。
kubectl get pvc -l app=redis-cluster
初始化集群
嘿!别着急!还没有结束!
StatefulSet 启动 Redis 实例后,它们仅仅是独立的 Redis 服务器,还没有组成集群。我们需要手动执行 Redis 集群初始化命令。通常,我们会选择一个 Pod(例如 redis-0
)来执行这个命令。
通过上述步骤,你已经在 K8s 中成功部署了一个具备持久化存储的 Redis 集群。这展示了 K8s 在管理复杂有状态应用方面的强大能力。
kubectl exec -it redis-0 -- redis-cli cluster info
kubectl exec -it redis-0 -- redis-cli cluster nodes
----------
# 执行示例如下:
❯ kubectl exec -it redis-0 -- redis-cli cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:546
cluster_stats_messages_pong_sent:561
cluster_stats_messages_sent:1107
cluster_stats_messages_ping_received:556
cluster_stats_messages_pong_received:546
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:1107
❯ kubectl exec -it redis-0 -- redis-cli cluster nodes
5441d7b3cee660294a44436ce5e28f124a8a3013 10.1.0.19:6379@16379 master - 0 1749203764236 2 connected 5461-10922
6478f1d8216315e1442218966ef7653b0f73dce3 10.1.0.21:6379@16379 slave bae2c19ca08ff1836e7049ae2680f068f179ab46 0 1749203765000 3 connected
bae2c19ca08ff1836e7049ae2680f068f179ab46 10.1.0.20:6379@16379 master - 0 1749203764000 3 connected 10923-16383
d3969c843d26d8f7f69f7756f72cccf7ed250017 10.1.0.22:6379@16379 slave 1eb3dd6f7712c7661f934ec1c65d36ac4daa9ac3 0 1749203763514 1 connected
3e136252b41a52ec278911a086f674c21c83c270 10.1.0.23:6379@16379 slave 5441d7b3cee660294a44436ce5e28f124a8a3013 0 1749203765252 2 connected
1eb3dd6f7712c7661f934ec1c65d36ac4daa9ac3 10.1.0.18:6379@16379 myself,master - 0 1749203763000 1 connected 0-5460
你应该能看到集群的状态为 ok
,并且所有节点都已加入集群,并分配了主从角色。PostgreSQL 是一款功能强大、高度稳定、被广泛使用的开源关系型数据库。在 K8s 中部署 PostgreSQL,与 Redis 类似,核心在于处理持久化存储和敏感数据(密码)。我们将部署一个单实例的 PostgreSQL 数据库,并确保它的数据是持久化的。
在 K8s 中,我们将主要用到以下 K8s 对象来部署 PostgreSQL 数据库:
部署步骤概览:
让我们赶紧开始吧~。
创建 Secret
我们将创建一个 Secret
对象,用于安全地存储 PostgreSQL 的超级用户密码。
Secret 是一种包含少量敏感信息例如密码、令牌或密钥的对象。 这样的信息可能会被放在 Pod 规约中或者镜像中。 使用 Secret 意味着你不需要在应用程序代码中包含机密数据。
由于创建 Secret 可以独立于使用它们的 Pod, 因此在创建、查看和编辑 Pod 的工作流程中暴露 Secret(及其数据)的风险较小。 Kubernetes 和在集群中运行的应用程序也可以对 Secret 采取额外的预防措施, 例如避免将敏感数据写入非易失性存储。
Secret 类似于 ConfigMap 但专门用于保存机密数据。
apiVersion: v1
kind:Secret
metadata:
name:postgres-secret# Secret 的名称,你将在 Deployment 中引用它
type:Opaque# Opaque 是默认类型,表示通用的秘密数据
data:
# 将 'your_strong_password' 替换为你自己的密码,并进行 Base64 编码。
# 推荐使用在线工具或命令行(例如:echo -n 'mysecretpassword' | base64)生成(K8s 也可配置对信息进行自动编码)。
# 示例:如果你的密码是 'mysecretpassword',Base64 编码后可能是 'bXlzZWNyZXRwYXNzd29yZAo='
# 注意:这里的 'postgres-password' 是一个键名,在 Deployment 中引用时会用到
postgres-password:<在这里填入你的Base64编码密码>
注意,如果密码不是 Base64 编码格式,将会显示错误。
echo -n '123456' | base64 # 进行编码
MTIzNDU2 # 编码结果
创建 PersistentVolumeClaim (PVC)
我们需要一个持久化存储来保存 PostgreSQL 的数据库文件,防止 Pod 重启或删除时数据丢失。我们将创建一个 PVC
来声明这个存储需求。
apiVersion: v1
kind:PersistentVolumeClaim
metadata:
name:postgres-pvc# PVC 的名称,你将在 Deployment 中引用它
labels:
app:postgres# 标签,用于识别这个 PVC
spec:
accessModes:
-ReadWriteOnce# 访问模式:允许单个节点以读写模式挂载。对于数据库这类应用,这通常是安全的默认选择。
resources:
requests:
storage:5Gi# 请求 5GB 的存储空间
# storageClassName: your-storage-class # (可选) 生产环境请指定 StorageClass。
# 如果你的 K8s 集群有默认 StorageClass,可以不写。
# 例如:storageClassName: standard (云厂商的常见默认 StorageClass)。
创建 Deployment
现在,我们定义 PostgreSQL 的部署。我们将使用 Deployment
来创建一个 PostgreSQL Pod,并将其与之前创建的 Secret
和 PVC
关联起来。
apiVersion: apps/v1
kind:Deployment
metadata:
name:postgres-deployment# Deployment 的名称
labels:
app:postgres# Deployment 的标签
spec:
replicas:1# 部署一个 PostgreSQL 实例,因为是单实例数据库
selector:
matchLabels:
app:postgres# Pod 选择器,匹配带有 app: postgres 标签的 Pod
template:
metadata:
labels:
app:postgres# Pod 的标签,需要与 selector.matchLabels 匹配
spec:
containers:
-name:postgres# 容器的名称
image:postgres:13# 使用 PostgreSQL 13 官方镜像
imagePullPolicy:IfNotPresent# 如果本地有镜像就使用,否则拉取
ports:
-containerPort:5432# PostgreSQL 默认端口
env:# 环境变量,用于配置 PostgreSQL 镜像在启动时初始化数据库
-name:POSTGRES_DB# 数据库名称
value:mydatabase# 你希望创建的数据库名称
-name:POSTGRES_USER# 数据库用户
value:myuser# 你希望创建的数据库用户
-name:POSTGRES_PASSWORD# 数据库密码,从 Secret 中引用
valueFrom:
secretKeyRef:
name:postgres-secret# 引用名为 postgres-secret 的 Secret
key:postgres-password# 引用 Secret 中名为 postgres-password 的键
volumeMounts:# 容器内卷的挂载点列表
-name:postgres-storage# 引用名为 'postgres-storage' 的卷
mountPath:/var/lib/postgresql/data# PostgreSQL 数据文件存放的默认路径,**非常重要!**
volumes:# 定义 Pod 中可用的卷列表
-name:postgres-storage# 定义一个名为 'postgres-storage' 的卷
persistentVolumeClaim:
claimName:postgres-pvc# 引用上面定义的 PVC
创建 Service
为了让 K8s 集群内部的其他应用程序能够稳定地连接到 PostgreSQL,我们需要一个 Service
。
apiVersion: v1
kind:Service
metadata:
name:postgres-service# Service 的名称,其他 Pod 将通过这个名称访问
labels:
app:postgres
spec:
selector:
app:postgres# 选择带有 app: postgres 标签的 Pod 作为后端(即 postgres-deployment 创建的 Pod)
ports:
-protocol:TCP
port:5432 # Service 监听的端口 (集群内部访问 Service 使用这个端口)
targetPort:5432# 后端 Pod 内部容器监听的端口
type:ClusterIP # 默认类型,Service 只能在 K8s 集群内部访问
# 如果需要外部访问可按照以下形式修改。
# ports:
# - protocol: TCP
# port: 5432 # Service 监听的端口 (集群内部访问 Service 使用这个端口)
# targetPort: 5432 # 后端 Pod 内部容器监听的端口
# nodePort: 30001 # 在每个 Node 上暴露的端口,范围通常是 30000-32767
# type: NodePort # 将 Service 类型改为 NodePort
运行 Pod
现在,我们配置都已经定义好了,只需要我们开始传达执行命令即可让整个服务运转起来。
kubectl apply -f postgresql-secret.yaml
kubectl apply -f postgresql-pvc.yaml
kubectl apply -f postgresql-service.yaml
kubectl apply -f postgresql-deployment.yaml
验证运行状态
kubectl get pvc postgres-pvc # 确保 PVC 已绑定成功
kubectl get pods -l app=postgres # 确认 PostgreSQL Pod 正在运行
kubectl get svc postgres-service # 确认 Service 已创建并分配了 IP
最后,你可以尝试连接该数据库,如果可以正常连接,那么说明运行是成功的。
第五章节更新中,请前往账号主页搜索:闯进 Kubernetes 的世界。