跳到主要内容

K8S + PV + PVC + Logrotate

2024年12月11日
柏拉文
越努力,越幸运

一、认识


本文基于 Kubernetes 部署 Node.js 服务,具体方案如下:

  1. Secret: 安全管理敏感信息,如数据库密码。

  2. ConfigMap for Application: 存储后端服务器的非敏感配置数据。

  3. Service: 使用 NodePort 服务将后端服务器暴露在 3000 端口。

  4. Deployment: 管理后端服务器的部署,包括环境变量、资源限制和卷挂载。

  5. 使用 PersistentVolumePersistentVolumeClaim 持久化存储日志, 通过 hostPath 或者 NFS 的方式写入到本地。

  6. 基于 Logrotate 来管理 Node 开发、测试环境下的日志文件,定期轮转、压缩、删除,避免日志文件过大占用过多磁盘空间。在 Kubernetes 中,通过 ConfigMaplogrotate 配置文件挂载到容器中, 通过 CronJob 定期执行 logrotate 命令。

1.1 logrotate

logrotate 是一个用于管理日志文件的工具,它可以自动轮转、压缩、删除和邮件通知。它通常用于日志文件较大的系统或应用程序,可以帮助系统管理员减少磁盘空间的占用,并保持日志文件的整洁和可管理性。 Logrotate 的功能

  1. 轮转(Rotate):当日志文件达到指定大小时,会创建一个新的日志文件,并将当前日志文件重命名为备份文件。

  2. 压缩(Compress):轮转后的旧日志文件会进行压缩,减少磁盘空间的占用。

  3. 删除(Remove):删除历史日志文件,保留一定数量的日志文件,防止占满磁盘空间。

  4. 邮件通知(Mail):在日志文件轮转时发送通知邮件。

我们在 Kubernetes 中使用 Logrotate 来管理 Node 开发、测试环境下的日志文件,定期轮转、压缩、删除,避免日志文件过大占用过多磁盘空间。在 Kubernetes 中,通过 ConfigMaplogrotate 配置文件挂载到容器中, 通过 CronJob 定期执行 logrotate 命令 。具体过程如下:

  1. 创建 logrotate 配置文件,并将其存储在 ConfigMap

  2. ConfigMap 挂载到容器中,使容器能够读取该配置文件

  3. 使用 CronJob 定期执行 logrotate 命令,确保日志文件按预定规则进行轮转

二、构建镜像


构建 Node.js 服务镜像

2.1 目录结构

backend-server 

│── node_modules
│── .dockerignore
│── Dockerfile
│── index.js
└── package.json

2.2 index.js

const Koa = require("koa");
const pino = require("pino");
const KoaRouter = require("koa-router");

const port = 3000;
const app = new Koa();
const router = new KoaRouter();

const logFilePath =
process.env.APP_ENV === "development" || !process.env.APP_ENV
? "logs/backend-server.log"
: "/logs/backend-server.log";

const logFile = pino.destination(logFilePath);
const logger = pino(logFile);

const PORT = process.env.APP_PORT || 3000;
const DB_PASSWORD = process.env.DB_PASSWORD;

console.log("------PORT------", PORT);
console.log("-------DB_PASSWORD--------", DB_PASSWORD);

router.get("/", async (ctx) => {
logger.info("Hello again distributed logs");
ctx.body = {
code: 200,
message: "Hello Koa Server 001",
};
});

app.use(router.routes()).use(router.allowedMethods());

app.listen(port, () => {
console.log(`Koa HTTP Server is running on port ${port}`);
});

访问环境变量: 通过 process.env 可以访问 Dockerfile 以及 Deployment env 环境变量。在 Kubernetes 部署中,环境变量的优先级如下:

  • ConfigMapSecret 注入的环境变量(最高优先级)

  • 容器镜像(Dockerfile)中的 ENV 默认值

  • 应用程序代码中的默认值(如 || 3000

日志写入路径: 如果本地开发,可以将 logs 通过相对路径写入到 logs/xxx.log = ./logs/xxx.log。如果在 K8S 环境中,需要使用绝对路径 /logs/xx.log, 来确保它匹配 volumeMounts 中的 mountPath

2.3 Dockerfile

FROM registry.cn-hangzhou.aliyuncs.com/bolawen/node:22.7.0-linux-arm64
WORKDIR /server
COPY package*.json /server
RUN npm install --registry=https://registry.npmmirror.com
COPY . /server

ENV APP_ENV=test
ENV APP_PORT=3000
ENV DB_PASSWORD=defaultpassword

EXPOSE 3000
CMD ["node","index.js"]

ENV: ENV 指令设置镜像的默认环境变量值。在 Kubernetes 中运行时,Deployment env 的环境变量会覆盖 Dockerfile 中的默认值。

2.4 .dockerignore

dist
node_modules

2.5 构建并推送镜像

# 构建镜像 
docker build -t backend-server:1.0.0 .

# 推送镜像
docker tag backend-server:1.0.0 registry.cn-hangzhou.aliyuncs.com/bolawen/backend-server:1.0.0
docker push registry.cn-hangzhou.aliyuncs.com/bolawen/backend-server:1.0.0

三、部署服务


3.1 Service

创建 backend-server-service.yaml 文件

apiVersion: v1
kind: Service
metadata:
name: backend-server-service
spec:
type: NodePort
selector:
app: backend-server
ports:
- protocol: TCP
port: 3000
targetPort: 3000

3.2 Deployment

创建 backend-server-deployment.yaml 文件

apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-server-deployment
spec:
replicas: 2
selector:
matchLabels:
app: backend-server
template:
metadata:
labels:
app: backend-server
spec:
containers:
- name: backend-server
image: registry.cn-hangzhou.aliyuncs.com/bolawen/backend-server:1.0.3
ports:
- containerPort: 3000
env:
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: backend-server-config-map
key: APP_ENV
- name: APP_PORT
valueFrom:
configMapKeyRef:
name: backend-server-config-map
key: APP_PORT
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: backend-server-secret
key: DB_PASSWORD
volumeMounts:
- name: backend-server-logs-volume
mountPath: /logs
- name: backend-server-logrotate-config-volume
mountPath: /etc/logrotate.d
resources:
requests:
cpu: "250m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
volumes:
- name: backend-server-logs-volume
persistentVolumeClaim:
claimName: backend-server-logs-pvc
- name: backend-server-logrotate-config-volume
configMap:
name: backend-server-logrotate-config-map

3.3 Development Secret

创建 backend-server-secret.yaml 文件

apiVersion: v1
kind: Secret
metadata:
name: backend-server-secret
type: Opaque
data:
DB_PASSWORD: cm9vdA==

3.4 Development ConfigMap

创建 backend-server-config-map.yaml 文件

apiVersion: v1
kind: ConfigMap
metadata:
name: backend-server-config-map
data:
APP_PORT: "3000"
APP_ENV: "test"

3.5 logrotate ConfigMap 和 logrotate CronJob

backend-server-logrotate-config-map.yaml

apiVersion: v1
kind: ConfigMap
metadata:
name: backend-server-logrotate-config-map
data:
app-logrotate.conf: |
/logs/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0644 root root
postrotate
kill -HUP $(pidof node) || true
endscript
  • app-logrotate.conf: Logrotate 的配置文件通常分为两个部分:主配置文件 通常位于 /etc/logrotate.conf,控制全局设置, 单个日志文件配置:通常位于 /etc/logrotate.d/ 目录下,每个应用或服务的日志都有一个独立的配置文件。在 logrotate 中,app-logrotate.conf 文件(例如 /etc/logrotate.d/app-logrotate.conf)是你为应用程序或服务单独配置的日志轮转规则文件。这个文件通常位于 /etc/logrotate.d/ 目录下,用来为特定应用或日志文件定义轮转行为。这个文件并不是自动创建的,通过 ConfigMap 以及 CronJob volumeMounts 手动创建。

  • /logs/*.log: 日志文件的路径, 表示轮转 /logs 目录下所有 .log 后缀的日志文件。

  • daily:日志文件每天轮转一次。你也可以设置为 weeklymonthly,根据实际需求。

  • rotate 7:保留最近的 7 个日志文件。旧的日志文件会被删除。可以根据需求调整为其他数字。

  • compress:将轮转后的日志文件压缩。压缩格式通常是 .gz,可以节省磁盘空间。

  • delaycompress:延迟压缩。即不压缩最后一个轮转的日志文件,等下次轮转时才压缩。

  • missingok:如果日志文件丢失,logrotate 会忽略,不报错。

  • notifempty:如果日志文件为空,则不进行轮转。

  • create 0644 root root:为新创建的日志文件设置权限和属主,确保应用能正常写入。

  • postrotate:在日志轮转之后,kill -HUP $(pidof node) || true 会发送 SIGHUP 信号给 node 进程,从而重新打开日志文件。

backend-server-logrotate-cron-job.yaml

apiVersion: batch/v1
kind: CronJob
metadata:
name: backend-server-logrotate-cron-job
spec:
schedule: "0 0 * * *" # 每天午夜执行一次
jobTemplate:
spec:
template:
spec:
containers:
- name: logrotate
image: registry.cn-hangzhou.aliyuncs.com/bolawen/alpine:3.21.0-linux-arm64
command:
- "/bin/sh"
- "-c"
- "logrotate -f /etc/logrotate.d/app-logrotate.conf"
volumeMounts:
- name: logrotate-config
mountPath: /etc/logrotate.d
- name: logs-volume
mountPath: /logs
restartPolicy: OnFailure
volumes:
- name: logrotate-config
configMap:
name: backend-server-logrotate-config-map
- name: logs-volume
persistentVolumeClaim:
claimName: backend-server-logs-pvc
  • logrotate-config: 挂载了 ConfigMap backend-server-logrotate-config-map,并将其挂载到容器的 /etc/logrotate.d 目录。因此,容器会加载你在 ConfigMap 中指定的 app-logrotate.conf 配置文件。

  • logs-volume: 这个卷挂载了一个 PVC (backend-server-logs-pvc),并将其挂载到容器的 /logs 目录,这意味着容器访问的是通过 PVC 持久化存储的日志文件。logrotate 会执行日志轮转时,指定的路径是 /logs/*.log,所以它会轮转挂载到 /logs 目录下的日志文件。所以,logrotate 轮转的是 logs-volume 指向的通过 PVC 持久化存储的日志文件。

调试 logrotate: 在主容器中通过 kubectl exec 调试执行 logrotate 命令

kubectl exec -it <pod-name> -- logrotate -f /etc/logrotate.d/app-logrotate.conf

3.6 PersistentVolume 和 PersistentVolumeClaim

backend-server-logs-pv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
name: backend-server-logs-pv
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
hostPath:
path: /mnt/logs

backend-server-logs-pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: backend-server-logs-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi

3.7 应用以上配置文件

kubectl apply -f backend-server-config-map.yaml
kubectl apply -f backend-server-secret.yaml
kubectl apply -f backend-server-service.yaml
kubectl apply -f backend-server-logs-pv.yaml
kubectl apply -f backend-server-logs-pvc.yaml
kubectl apply -f backend-server-logrotate-config-map.yaml
kubectl apply -f backend-server-logrotate-cron-job.yaml
kubectl apply -f backend-server-deployment.yaml

四、访问服务


4.1 检测资源状态

# 查看 Pod 状态
kubectl get pods

# 查看 Deployment 状态
kubectl get deployments

# 查看 Service 状态
kubectl get services

4.2 NodePort 访问服务

1. 通过 kubectl describe pod [Pod Name] 获取 NodeIP

kubectl describe pod [Pod Name]

# 输出

Name: backend-server-deployment-64df744d7c-mcns7
Namespace: default
Priority: 0
Service Account: default
Node: k8s-node1/192.168.105.133
Start Time: Wed, 04 Dec 2024 20:16:41 +0800
Labels: app=backend-server

2. 通过 kubectl get services 获取 NodePort 端口

kubectl get services

# 输出
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
node-app-service NodePort 10.108.73.245 <none> 80:30007/TCP 2m

3. 使用集群节点的 IP 地址访问服务

http://<Node-IP>:<NodePort>

# 比如:
http://192.168.105.133:32490/

4.3 验证 Node 服务日志

方案一、直接在宿主机上查看日志目录 /mnt/logs 是否有写入内容

# 查看宿主机的日志目录
ls -la /mnt/logs
cat /mnt/logs/backend-server.log

方案二、进入 Pod 内部,直接写入 /logs 目录进行测试, 然后在宿主机 /mnt/logs 查看是否写入

kubectl exec -it <pod-name> -- /bin/sh
echo "Test log entry" >> /logs/test.log

4.4 验证 Logrotate CronJob

获取 Cronjob 列表

kubectl get cronjob 

或者

kubectl get cronjobs

获取 Cronjob 生成的 Pod: 找到具体的 Pod 名称后,你可以使用 kubectl logs 查看该 Pod 的日志

kubectl get pods --selector=job-name=<job-name>

// 例如
kubectl get pods --selector=job-name=backend-server-logrotate-cron-job

查看 Cronjob 生成的 Pod 日志: 找到具体的 Pod 名称后,你可以使用 kubectl logs 查看该 Pod 的日志

kubectl logs <Cronjob Pod Name>

获取 Cronjob 生成的 Job: CronJob 会定期执行任务,每次执行都会创建一个新的 Job。你可以查看 CronJob 创建的所有 Job 和它们对应的 Pod

kubectl get jobs

查看 Cronjob 执行历史和详细日志: 如果你需要检查 CronJob 执行的历史记录,可以使用 kubectl describe 命令来查看 CronJob 以及其 Job 运行的详细信息。会显示 CronJob 任务的调度信息、执行历史以及最近运行的 Job

kubectl describe cronjob <cronjob-name>

4.4 访问 PV+ PVC 持久化存储日志文件

tail -l /mnt/logs/backend-server.log