目录
前言
Kubernetes作为广泛使用的容器编排平台,具有很强的灵活性。但要发挥它的全部潜力,避免使用中的各种陷阱,还需要遵循一些最佳实践。本文将从Google云原生工程师发布的Kubernetes_Best_Practices PDF文件分析,结合自身经验从构建容器、部署应用、服务发现、集群管理等几个方面,总结kubernetes的最佳实践。
一、构建容器
不要信任任意的基础镜像,尽量使用官方镜像
在构建容器时,我们常常要基于一个base image。但是,如果使用一些来源不明的base image,很可能存在安全隐患。因为你不知道image作者是否在系统中留下了后门。因此,尽量选择官方镜像作为base image,如果没有官方镜像,也要尽量选择可信赖的来源。
使用工具对容器镜像进行静态安全扫描分析
即使我们选择了可信的base image,也不能完全放心。因为在往下构建的过程中,我们可能会引入一些有漏洞的依赖。因此,在构建完成后,要使用trivy等工具对容器镜像进行静态安全扫描分析,及时发现和修复漏洞。往往容器安全更容易让人忽略,可能使用的基础镜像本身就带有一些系统级别的安全漏洞
下图为使用trivy扫描centos某版本的基础镜像的输出结果
使用尽可能小的基础镜像,减少attack surface
一个原则是容器镜像要尽量小,其中包含的组件尽量少。这样可以减小容器的attack surface,降低被攻击的风险。比如对于node.js应用,使用node:alpine要优于使用node镜像。node:alpine是一个面向安全的最小化的node基础镜像,而node镜像中包含了很多调试工具,是不必要的。
Alpine与主流基础镜像Centos等区别:
Alpine Linux使用了musl作为其标准C库(libc)。musl是一个轻量级的C库实现,专为嵌入式系统和容器环境设计。musl与glibc相比,体积更小,启动速度更快,但在功能和兼容性方面有一些限制。
CentOS使用的是glibc(GNU C Library)作为标准C库。glibc是一个功能完善、广泛使用的C库实现,提供了丰富的系统调用和库函数。glibc与Linux生态系统高度兼容,支持广泛的软件包和应用程序。
采用builder模式,将编译环境和运行时环境分离
多阶段构建(Multi-stage Builds)是Docker 17.05版本引入的一个新特性。它允许在一个Dockerfile中使用多个FROM指令,每个FROM指令都可以使用不同的基础镜像,并且每个FROM指令都开始一个新的构建阶段。
多阶段构建的主要目的是将构建过程分为多个独立的阶段,每个阶段生成一个独立的镜像。后续阶段可以使用前面阶段生成的镜像作为基础镜像,从而实现构建环境和运行环境的分离,有效减小最终镜像的体积。
Multi-stage Builds Dockerfile示例
# 第一阶段:构建阶段
FROM golang:1.16-alpine AS builder
WORKDIR /app
# 复制Go模块文件并下载依赖
COPY go.mod go.sum ./
RUN go mod download
# 复制应用程序源码并构建
COPY . .
RUN go build -o main .
# 第二阶段:运行阶段
FROM alpine:latest
WORKDIR /app
# 从构建阶段复制构建好的二进制文件
COPY --from=builder /app/main .
# 设置容器的默认启动命令
CMD ["./main"]
在容器内使用非root用户运行进程
FROM node:14
RUN useradd --create-home appuser
USER appuser
将容器文件系统挂载为只读
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.19
volumeMounts:
- name: nginx-root
mountPath: /usr/share/nginx/html
readOnly: true #将文件系统设置为只读
volumes:
- name: nginx-root
emptyDir: {}
每个容器只运行一个进程。进程不要在失败时重启,应干净地退出
遵循一个容器只运行一个进程的最佳实践,可以使容器的行为更加一致可预测。当该进程异常退出时,容器也会完全终止,而不是留下孤儿进程。这样更容易进行监控和管理。同时,要让进程在发生错误时能够适当地退出而不是重启,促进容器的不可变性和幂等性。
不要通过systemd等方式将业务进程封装成容器,否则可能导致业务进程变为孤儿进程,容器无法准确监控其状态。这可能会出现业务进程卡死,而容器仍在运行的情况。
将日志打印到stdout/stderr,而不是文件。
容器中的进程应该将日志信息直接写到stdout和stderr,而不是写到文件系统中的日志文件。Kubernetes会自动收集和管理容器的stdout和stderr输出,以提供集中式的日志解决方案。
使用类似dumb-init工具防止僵尸进程
在容器中使用类似dumb-init这样的工具来避免出现僵尸进程是一种很好的实践。僵尸进程是指一个子进程在父进程退出后,仍然保留在进程表中的进程。如果不加以处理,僵尸进程会一直占用系统资源,并且在容器退出后也不会自动清理。
dumb-init是一个非常小巧的系统工具,它的作用是在容器启动时先启动一个监护进程(dumb-init),此后容器内的其他进程都将作为dumb-init的子进程运行。这样当子进程退出时,dumb-init就会正常接管它们的終止狀態信號,避免出现僵尸进程。
dumb-init Dockerfile示例
FROM ubuntu:18.04
# 安装dumb-init
RUN apt-get update && apt-get install -y dumb-init
# 应用程序代码...
# 使用dumb-init启动应用
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["executable", "param1", "param2"]
在这个例子中:
1. 我们从Ubuntu 18.04基础镜像开始构建。
2. 安装dumb-init工具。
3. 复制应用程序代码(步骤省略)。
4. 使用ENTRYPOINT将dumb-init设置为入口点。
5. CMD指令指定了实际要运行的应用程序及其参数。
当启动该容器时,dumb-init会先启动,然后调用CMD指定的命令启动应用程序进程。应用程序进程将作为dumb-init的子进程运行。如果应用进程异常退出,dumb-init会正确处理它的终止信号,避免出现僵尸进程。
使用dumb-init的另一个好处是,它还会正确处理信号,并将它们传递给应用程序进程。这对于基于信号的正常关停流程非常有帮助。
总之,dumb-init是一个非常轻量且有用的工具,能够解决容器中常见的僵尸进程问题,并提高容器进程的健壮性。许多公司和项目都在使用它来改善容器的运行时行为。
Google云原生工程师最佳实践指南下载
Kubernetes_Best_Practices.pdfhttps://c74p900o8m.feishu.cn/docx/S84ddjQg2oQRpMxxOykcdP8Snsc