第 11 章:容器化部署
第 11 章:容器化部署
使用 Docker、Docker Compose 和 Kubernetes 部署和管理 rqlite 集群。
11.1 Docker 部署
11.1.1 镜像说明
rqlite 官方镜像发布在 Docker Hub:
| 镜像 | 说明 |
|---|---|
rqlite/rqlite | 官方镜像,包含 rqlited 和 rqlite CLI |
| 镜像大小 | ~30MB(基于 Alpine Linux) |
| 端口 | 4001(HTTP API)、4002(Raft 通信) |
| 数据目录 | /rqlite/file |
# 拉取最新镜像
docker pull rqlite/rqlite:latest
# 查看镜像信息
docker inspect rqlite/rqlite
11.1.2 单节点运行
# 最简启动
docker run -d --name rqlite \
-p 4001:4001 \
rqlite/rqlite
# 带持久化
docker run -d --name rqlite \
-p 4001:4001 \
-p 4002:4002 \
-v rqlite-data:/rqlite/file \
rqlite/rqlite
# 查看日志
docker logs -f rqlite
# 验证
curl http://localhost:4001/status?pretty
11.1.3 Docker 启动参数
docker run -d --name rqlite \
-p 4001:4001 \
-p 4002:4002 \
-v rqlite-data:/rqlite/file \
rqlite/rqlite \
rqlited \
-node-id=docker-node1 \
-http-addr=0.0.0.0:4001 \
-raft-addr=0.0.0.0:4002 \
-disco-mode=off \
-fk \
/rqlite/file
| Docker 参数 | rqlite 参数 | 说明 |
|---|---|---|
-p 4001:4001 | -http-addr | HTTP API 端口映射 |
-p 4002:4002 | -raft-addr | Raft 通信端口映射 |
-v rqlite-data:/rqlite/file | 数据目录 | 持久化数据卷 |
--restart=unless-stopped | — | 自动重启策略 |
11.2 Docker Compose 集群
11.2.1 三节点集群配置
# docker-compose.yml
version: "3.8"
services:
rqlite1:
image: rqlite/rqlite:latest
container_name: rqlite1
command:
- rqlited
- -node-id=node1
- -http-addr=0.0.0.0:4001
- -raft-addr=0.0.0.0:4002
- -disco-mode=off
- /rqlite/file
ports:
- "4001:4001"
- "4002:4002"
volumes:
- rqlite1-data:/rqlite/file
networks:
- rqlite-net
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:4001/status"]
interval: 10s
timeout: 5s
retries: 5
rqlite2:
image: rqlite/rqlite:latest
container_name: rqlite2
command:
- rqlited
- -node-id=node2
- -http-addr=0.0.0.0:4001
- -raft-addr=0.0.0.0:4002
- -disco-mode=off
- -join=http://rqlite1:4001
- /rqlite/file
ports:
- "4011:4001"
- "4012:4002"
volumes:
- rqlite2-data:/rqlite/file
networks:
- rqlite-net
depends_on:
rqlite1:
condition: service_healthy
restart: unless-stopped
rqlite3:
image: rqlite/rqlite:latest
container_name: rqlite3
command:
- rqlited
- -node-id=node3
- -http-addr=0.0.0.0:4001
- -raft-addr=0.0.0.0:4002
- -disco-mode=off
- -join=http://rqlite1:4001
- /rqlite/file
ports:
- "4021:4001"
- "4022:4002"
volumes:
- rqlite3-data:/rqlite/file
networks:
- rqlite-net
depends_on:
rqlite1:
condition: service_healthy
restart: unless-stopped
volumes:
rqlite1-data:
rqlite2-data:
rqlite3-data:
networks:
rqlite-net:
driver: bridge
11.2.2 启动和验证集群
# 启动集群
docker compose up -d
# 查看所有容器状态
docker compose ps
# 查看日志
docker compose logs -f
# 验证集群
curl -s http://localhost:4001/nodes?pretty | python3 -m json.tool
# 测试数据复制
curl -XPOST 'http://localhost:4001/db/execute' \
-H 'Content-Type: application/json' \
-d '[["CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, msg TEXT)"]]'
curl -XPOST 'http://localhost:4001/db/execute' \
-H 'Content-Type: application/json' \
-d '[["INSERT INTO test VALUES (1, \"hello docker\")"]]'
# 从 Follower 读取
curl -s -G 'http://localhost:4011/db/query' \
--data-urlencode 'q=SELECT * FROM test' | python3 -m json.tool
11.2.3 带 TLS 和认证的 Compose 配置
# docker-compose-secure.yml
version: "3.8"
services:
rqlite1:
image: rqlite/rqlite:latest
container_name: rqlite1
command:
- rqlited
- -node-id=node1
- -http-addr=0.0.0.0:4001
- -raft-addr=0.0.0.0:4002
- -http-cert=/certs/server.crt
- -http-key=/certs/server.key
- -node-cert=/certs/server.crt
- -node-key=/certs/server.key
- -auth=/config/auth.json
- -disco-mode=off
- /rqlite/file
ports:
- "4001:4001"
- "4002:4002"
volumes:
- rqlite1-data:/rqlite/file
- ./certs:/certs:ro
- ./config:/config:ro
networks:
- rqlite-net
restart: unless-stopped
# auth.json 内容
# [
# {"username": "admin", "password": "secure_password", "perm": "all"},
# {"username": "reader", "password": "read_pass", "perm": "ro"}
# ]
11.3 Kubernetes 部署
11.3.1 使用 StatefulSet
# k8s/rqlite-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: rqlite
labels:
app: rqlite
spec:
clusterIP: None
ports:
- name: http
port: 4001
targetPort: 4001
- name: raft
port: 4002
targetPort: 4002
selector:
app: rqlite
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: rqlite
labels:
app: rqlite
spec:
serviceName: rqlite
replicas: 3
selector:
matchLabels:
app: rqlite
template:
metadata:
labels:
app: rqlite
spec:
containers:
- name: rqlite
image: rqlite/rqlite:latest
command:
- rqlited
- -node-id=$(NODE_ID)
- -http-addr=0.0.0.0:4001
- -raft-addr=0.0.0.0:4002
- -disco-mode=off
- -join=http://rqlite-0.rqlite.default.svc.cluster.local:4001
- /rqlite/file
env:
- name: NODE_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
ports:
- name: http
containerPort: 4001
- name: raft
containerPort: 4002
volumeMounts:
- name: data
mountPath: /rqlite/file
readinessProbe:
httpGet:
path: /status/ready
port: http
initialDelaySeconds: 10
periodSeconds: 10
livenessProbe:
httpGet:
path: /status
port: http
initialDelaySeconds: 15
periodSeconds: 20
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
11.3.2 K8s 环境下的节点加入
由于 Kubernetes StatefulSet 的 Pod 启动顺序不确定,需要处理首次启动的特殊情况:
# 使用 Init Container 处理首次启动
initContainers:
- name: wait-for-leader
image: busybox:latest
command:
- sh
- -c
- |
# 如果是第一个 Pod (rqlite-0),直接启动,不等待
if [ "$(hostname)" = "rqlite-0" ]; then
echo "This is the first node, starting directly"
exit 0
fi
# 等待 rqlite-0 启动
echo "Waiting for rqlite-0 to be ready..."
until wget -qO- http://rqlite-0.rqlite.default.svc.cluster.local:4001/status/ready; do
sleep 2
done
echo "rqlite-0 is ready"
11.3.3 Headless Service 配置
Headless Service 允许 Pod 之间通过 DNS 名称互相发现:
rqlite-0.rqlite.default.svc.cluster.local → Node 1 (Leader)
rqlite-1.rqlite.default.svc.cluster.local → Node 2
rqlite-2.rqlite.default.svc.cluster.local → Node 3
11.3.4 Ingress 配置
# k8s/rqlite-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: rqlite
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
rules:
- host: rqlite.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: rqlite
port:
number: 4001
tls:
- hosts:
- rqlite.example.com
secretName: rqlite-tls
11.4 容器化运维技巧
11.4.1 容器内备份
# 从容器内备份
docker exec rqlite1 sh -c "wget -qO- 'http://localhost:4001/db/backup'" > backup.sql
# 使用 Docker volume 备份
docker run --rm -v rqlite1-data:/data -v $(pwd):/backup alpine \
tar czf /backup/rqlite-data-$(date +%Y%m%d).tar.gz -C /data .
11.4.2 容器日志管理
# docker-compose.yml 中添加日志限制
services:
rqlite1:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
11.4.3 资源限制
# Docker Compose 资源限制
services:
rqlite1:
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
reservations:
cpus: "0.25"
memory: 128M
11.5 业务场景:微服务配置中心
┌─────────────────────────────────────────┐
│ Kubernetes 集群 │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Web App │ │ API Svc │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ rqlite 集群 │ │
│ │ (3 Pod) │ │
│ └─────────────┘ │
│ │
│ 存储内容: │
│ - 服务配置 (feature flags) │
│ - 白名单/黑名单 │
│ - 系统参数 (限流阈值等) │
└─────────────────────────────────────────┘
配置表设计:
CREATE TABLE configs (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
type TEXT CHECK(type IN ('string', 'number', 'boolean', 'json')),
description TEXT,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE config_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL,
old_value TEXT,
new_value TEXT,
changed_by TEXT,
changed_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
11.6 容器化检查清单
| 检查项 | 说明 | 状态 |
|---|---|---|
| 持久化卷 | 使用 Docker Volume 或 PV | ✅ |
| 健康检查 | 配置 readiness/liveness probe | ✅ |
| 资源限制 | 设置 CPU 和内存限制 | ✅ |
| 日志管理 | 限制日志大小和文件数 | ✅ |
| 网络策略 | 限制 Pod 间通信 | ✅ |
| 安全上下文 | 使用非 root 用户运行 | ✅ |
| 镜像版本 | 固定版本号,避免 latest | ✅ |
11.7 本章小结
| 要点 | 内容 |
|---|---|
| Docker 单节点 | docker run rqlite/rqlite 快速启动 |
| Docker Compose | 推荐用于开发和小规模集群 |
| Kubernetes | 使用 StatefulSet + Headless Service |
| 持久化 | 必须使用 Volume 持久化数据 |
| 高可用 | 至少 3 个 Pod,使用 readiness probe |
| 备份 | 容器内执行或从 Volume 备份 |
上一章:第 10 章:客户端开发 下一章:第 12 章:监控与可观测性