读 kubernetes for Developers

Kubernetes 起源于 Google

虚拟机(VM)出现前,通常将每个应用程序安装在共享主机上的不同目录中,每个应用程序在不同的端口上服务。这会带来一些问题,即各种应用程序在一定程度上需要相互协作,以共享依赖关系和机器的资源,如 CPU、内存和可用端口。这也很难扩展:如果您有一个应用程序突然接收到更多的流量,您如何只扩展那个应用程序,而让其他应用程序保持原样?

容器是一种仅打包您的应用程序及其所需依赖关系以在隔离环境中托管的方式,类似于 VM 但无需安装和管理操作系统中的应用程序

选择容器的一些主要原因包括语言灵活性(能够在容器平台上运行任何语言或环境)、轻量级隔离(保护您的工作负载不受彼此干扰而无需使用 VM)、开发效率(将生产环境更接近于开发并允许轻松设置)和可重现性(记录用于创建容器构建文件中环境的步骤)

用于创建容器的 Docker 工具在可重现性方面并不完美。像 apt-get 这样的命令用于安装依赖关系是在实时系统上操作的,因此您实际上无法为相同的输入得到相同的输出,因为那些依赖系统(如 APT 仓库)可能在构建之间已经改变。像 Bazel 这样的工具由 Google 开源,旨在解决这个问题以及更多问题,但它们自身具有复杂性,更推荐用于复杂的企业部署

Kubernetes 中,容器被组织成所谓的 Pods。Pod 仅仅是一组一起调度并作为单个单位处理的容器。通常情况下,一个 Pod 就是一个单独的容器,但如果您的应用由多个连接部分组成,则可能是多个容器。从概念上讲,Pod 是您的应用程序及其依赖项。服务用于为集群内部和外部的一组 Pods 提供连接

要警惕那些使简单事务更简单但使复杂事务更困难的工具

Kubernetes 在几件事情上确实很擅长,比如高密度运行无状态应用程序;混合多种工作负载,如现代无状态应用程序和遗留有状态单体;从过时的系统迁移服务到统一平台;处理高性能计算,如数据分析和机器学习的批处理作业;当然还有运行一堆微服务。在这些情况下,Kubernetes 通过实现高效率、统一您的托管平台、自动化系统和运行批处理作业,为您带来很多优势

运行了很多 Docker 容器后,您将最终拥有一个相当大的已停止容器列表(并且使用了大量硬盘空间)。要清理这些通常不需要保留的镜像,可以随时运行:

docker system prune -a

基础镜像 python,本身就是用 Dockerfile 构建的,并配置了运行 Python 程序所需的一切环境。对于 Docker Hub 上的容器镜像,它们的 Dockerfile 源代码链接可供查看,以便您了解它们是如何组成的。基础镜像通常以另一个基础镜像为起点,依此类推,直到从一个完全空的容器开始,称为 scratch

CMD 是独特的,因为它实际上并不改变容器的构建方式。它仅保存将在调用 docker run 时如果没有指定命令则执行的默认命令

本地预编译应用程序是一种选择,但为什么不使用 Docker 来编译您的应用程序呢!

使用多阶段构建编译代码: 使用 RUN 命令来编译代码或执行其他操作是可行的路径;然而,缺点是您最终会在容器镜像中配置执行 RUN 命令所需的工具。这些工具以及任何源代码都会结束在最终的容器镜像中。

$ docker run compiled_code ls
Dockerfile
Hello.class
Hello.java

您会看到源代码仍然存在。此外,Java 编译器(javac)仍然存在于镜像中,尽管它将不再被使用(我们运行应用程序时不需要编译器)。

容器镜像混合了构建和运行的职责,这不是理想的状态。这些额外的二进制文件不仅使容器镜像膨胀,而且还不必要地增加了容器的攻击面(因为容器中运行的任何进程现在都有一个编译器可以使用)。您可以使用一系列额外的 Docker 命令来清理容器(例如,删除源代码,卸载不再需要的工具),但这并不总是实际的,尤其是如果所有这些额外的工具都来自基础镜像。

通过多阶段构建,我们首先配置一个带有构建程序所需一切的临时容器,然后配置一个带有运行程序所需一切的最终容器。这样可以将关注点分离并整齐地隔离到它们自己的容器中。

FROM openjdk:11 AS buildstage                   # 构建容器命名为 buildstage,并负责构建代码
COPY . /app
WORKDIR /app
RUN javac Hello.java
 
FROM openjdk:11-jre-slim                        # 运行容器使用精简版基础镜像,不包括编译工具
COPY --from=buildstage /app/Hello.class /app/   # 从构建容器复制文件
WORKDIR /app
CMD java Hello

这个 Dockerfile 生成的最终产品是一个生产容器,仅包含编译后的 Java 类和运行它所需的依赖项。第一个容器构建应用程序的中间产物在构建完成后实际上被丢弃了(技术上,它保存在您的 Docker 缓存中,但在您用于生产的最终产品中不包括任何部分)。

-it 参数(实际上是两个参数,但通常一起使用)允许我们通过发送 SIGTERM(通常是 Ctrl/Command+C)来终止。这使得典型的开发循环(构建-运行-修复-重复)变得简单(运行,Ctrl+C,修复,重复)。或者,您可以使用 docker run -d -p 8080:80 timeserver 在后台运行 Docker。如果不使用 -it,您需要手动停止进程:docker ps 列出进程和 docker stop CONTAINERID停止它,或者dockerstopCONTAINER_ID 停止它,或者 docker stop (docker ps -q) 停止所有运行的容器。

docker cp 对于调试尤其有用。它允许您在宿主机和容器之间复制文件。这里是一个示例:

docker cp server.py $CONTAINER_ID:/app
docker cp $CONTAINER_ID:/app/server.py .

Compose 是一个微型容器编排器,可以启动和关闭逻辑组中的多个容器,并在运行之间保留运行时设置,这对本地测试很有用

services:
  web:
    build: ../timeserver
    command: python3 server.py
    ports:
      - "8080:80"
docker compose build; docker compose up

Compose 的一个非常有用的功能是,你可以将本地文件夹直接映射到容器中。换句话说,容器不是复制你的应用程序,而是直接链接到你硬盘上的应用程序文件夹。在开发过程中,这非常方便,因为它允许你直接从桌面上操作容器中的文件,无需来回复制文件或重新构建容器。