近年来,容器以及 Kubernetes 成为开发者以及企业用户重点关注的技术趋势,本文总结了构建和管理容器的十个重要技巧来优化 IT 成本并提高效率。
容器是 Kubernetes 中应用程序的核心载体。当创建 Kubernetes 工作负载,例如创建用于调度、扩容或者升级应用程序的规则时,首先需要创建一个容器镜像,然后通过该镜像来运行服务或 Kubernetes 工作负载。在完成对镜像的测试并与应用程序其余代码整合后,用户通常会将镜像推送到容器注册中心。但在推送之前,仍然有很多实战技巧可以帮助构建和管理容器。
正文
通过 Kubernetes,用户可以自动扩展业务,而整个过程很少出现甚至零宕机,从而优化 IT 成本并提高系统可靠性。
1、使用Kubernetes 模式
随着 Kubernetes 不断推出新功能,其应用模式也在逐步改变。为了确保 Kubernetes 集群遵循当前 Kubernetes 的应用模式,用户需要定期查阅Kubernetes 官方文档以及每一个版本的发布说明。
2、复用基础镜像以节省时间
在 Kubernetes 集群中创建应用容器时,用户需要构建一个 Docker 基础镜像,然后在此镜像基础上构建部分或全部应用容器。有很多应用共享依赖项、库和配置,因此可以在基础镜像完成对共享部分进行配置,从而实现复用。
Docker Hub和Google Container 注册中心有数千个可供下载的基础镜像,这些镜像已经预先完成应用配置,随时可以投入使用,这可以节省大量时间。
3、不要轻易相信任何镜像
尽管使用预先构建的镜像很方便,但要格外小心并确保对其运行特定漏洞扫描。
一些开发人员会从 Docker Hub 中获取一个其他用户创建的基础镜像,然后将这个容器推送到生产环境,而这一切只是因为乍一看这个镜像包含了所需要的包。
这里有很多错误:镜像中的代码版本可能不正确;这些代码可能有漏洞;或者更糟糕的情形是该项目可能已经被故意绑定了恶意软件。
为了缓解上述问题,用户可以通过 Snyder 或 Twistlock 来运行静态分析,然后将其整合到 CI/CD(持续集成和持续交付)管道中,进而扫描所有容器漏洞。
一般来说,一旦在基础镜像中发现漏洞,用户就应该重新构建整个镜像,而不是仅仅修复漏洞。容器应该是不变的,因此,需要引入补丁重新构建和部署镜像。
4、优化基础镜像
从最精简、最可行的基础镜像开始,然后在此基础上构建软件包。通过这种方式,可以准确掌握容器中的全部内容。
较小的基础镜像也可以减少开销。例如,应用可能只有 5MB 大小,如果要添加一个现成的 Node.js 镜像,然后再安装所有的库,整个镜像很可能会变成 600MB 大小,但实际上并不需要这些额外的库。
因此,尽量保持最精简的镜像可以使:
- 构建更快
- 存储空间更小
- 镜像拉取更快
- 潜在威胁面更小
5、确保容器只运行一个进程
同保持基础镜像最小化类似的是,确保每个容器只有一个进程。容器的生命周期与它托管的应用程序相同,这意味着每个容器应该只包含一个父进程。
按照Google Cloud的说法,把容器当作虚拟机并同时运行多个进程是一个常见的错误。虽然容器可以实现这种方式,但这样就无法使用 Kubernetes 的自我修复属性。
通常,容器和应用应该同时启动;同样,当应用停止时,容器也应该停止。如果在一个容器中有多个进程,可能会出现应用程序状态混杂的情形,这将导致 Kubernetes 无法确定一个容器是否健康。
6、正确处理 Linux 信号
容器通过 Linux 信号来控制其内部进程的生命周期。为了将应用的生命周期与容器联系起来,需要确保应用能够正确处理 Linux 信号。
Linux 内核使用了诸如 SIGTERM、SIGKILL 和 SIGINIT 等信号来终止进程。但是,容器内的 Linux 会使用不同的方式来执行这些常见信号,如果执行结果同信号默认结果不符,将会导致错误和中断发生。
创建专门的 init 系统有助于解决此问题,比如专门针对容器的Linux Tini系统。这个工具正确注册了信号处理程序(比如 PID),容器化应用可以正确执行 Linux 信号,从而正常关闭孤立进程和僵尸进程,完成内存回收。
7、充分利用 Docker 的缓存构建机制
容器镜像由一系列镜像层组成,这些镜像层通过模板或 Dockerfile 中的指令生成。这些层以及构建顺序通常被容器平台缓存。例如,Docker 就有一个可以被不同层复用的构建缓存。这个缓存可以使构建更快,但是要确保当前层的所有父节点都保存了构建缓存,并且这些缓存没有被改变过。简单来讲,需要把不变的层放在前面,而把频繁改变的层放在后面。
例如,假设有一个包含步骤 X、Y 和 Z 的构建文件,对步骤 Z 进行了更改,构建文件可以在缓存中重用步骤 X 和 Y,因为这些层在更改 Z 之前就已经存在,这样可以加速构建过程。但是,如果改变了步骤 X,缓存中的层就不能再被复用。
虽然这是一种方便的行为,可以节省时间,但是必须确保所有镜像层都是当前的,而不是从旧的、过时的缓存构建而成。
8、使用类似 Helm 的包管理器
Helm作为 Kubernetes 的非官方软件包管理器,可以帮助安装和更新集群中运行的共同负载和容器。Helm 可以使用Chart声明自定义应用程序依赖项,并提供滚动升级和回滚工具。
用户可以通过现有基础镜像为 Kubernetes 集群提供通用服务,如数据库或 Web 服务;也可以为内部应用程序创建自定义基础镜像,创建自定义的 Charts 可以简化部署,减少开发团队的工作负担和重复性工作。
9、使用标签和语义化版本号
作为基本原则,用户不应该使用:latest标记。对大多数开发人员来说,这是显而易见的。如果不为容器添加自定义标签,它将尝试从镜像仓库中拉取当前版本,而容器可能并没有包括需要的更改。
在创建自定义镜像时,使用镜像标签和语义化版本号来追踪对 Docker 容器的更改。当它们在 Kubernetes 集群中运行时,Kubernetes 通过镜像标签确定应该运行哪个版本。在选择 Docker 镜像版本机制时,应该同时考虑生产负载和开发流程两种情况,这样才能在 Kubernetes 中获得更好的效果。
10、安全
在很多情况下,当构建 Docker 镜像时,需要让容器内的应用程序访问敏感数据,例如 API 令牌、私钥和数据库连接字符串等。
将这些秘密信息嵌入到容器中并不是一个安全的解决方案,即使只是保存到一个私有容器镜像中。将未加密的隐私数据作为 Docker 镜像的一部分进行处理会面临无数额外的安全风险,包括网络和镜像注册表的安全性,而 Docker 架构本身也决定了无法对容器中未加密的敏感数据进行优化。
相反,用户可以通过使用Kubernetes Secrets 对象将隐私信息存储在容器外面,这样更简单、安全。
Kubernetes 提供了一个 Secrets 抽象,允许在 Docker 镜像或 Pod 定义之外存储隐私数据。用户可以通过挂载卷或环境变量的方式把这些信息加载到容器中。更新时,只需更换相关服务的 Pod 并使用新的证书即可。用户也可以通过 Hashicorp Vault 以及Bitnami Sealed Secrets来保存隐私数据。