安安 - 容器
type: channel
link: https://blog.90.vc/category/container/
description: 容器技术相关，包含docker、编排工具、k8s等
items: 基于容器应用，著名的设计原则、模式和反模式
items: Docker镜像中修改时区
items: kubernetes中的Ksniff抓包工具
items: docker启动报错the backing xfs filesystem is formatted without d_type support
items: Kubernetes 内部组件工作原理
items: 使用kubeadm部署kubernetes1.17.3
items: 国内无法访问gcr.io与quay.io镜像问题
items: Kubernetes CNI网络对比：Flannel、Calico、Canal和Weave
items: docker 修改镜像及数据存储目录
items: 避免Docker镜像中使用大型jar包

基于容器应用，著名的设计原则、模式和反模式
type: item
date: 2021-09-27T09:39:00+00:00
description: 容器和容器编排（Kubernetes）的广泛使用，让我们可以轻松的构建基于微服务的“云原生”（Cloud Native）的应用。容器成为了云时代的新的编程单元，类似面向对象概念下的对象 ，J2EE中的组件 或者函数式编程中的函数 。在面向对象时代，有许多著名的设计原则，模式和反模式等，例如：SOLID (单一功能、开闭原则、里氏替换、接口隔离 以及依赖反转 ）Design Patterns: Elements of Reusable Object-Oriented SoftwareAnti-Pattern在新的容器背景下，相应的原则和模式有助于帮助我们更好的构建“云原生”的应用。我们可以看到，这些原则和模式并非对之前模式的颠覆和推翻，更像是适应新环境的演进版本。原则单一职责原则 SINGLE CONCERN PRINCIPLE (SCP)与OO的单一功能相对应，每一个容器应该提供单一的职责，只关注于做好一件事。单一职责使得容器更容易重用。通常容器对应于一个进程，而该进程专注于做好一件事。高可观测性原则 HIGH OBSERVABILITY PRINCIPLE (HOP)容器像对象一样，应该是一个封装良好的黑盒子。但是在云的环境下，这个黑盒子应该提供良好的观测接口，使得其在云的环境下得到相应的监控和管理。这样，整个应用才能提供一致的生命周期的管理。可观测性包含：提供健康检查 Health Check，或者心跳提供状态把日志输出到标准输出(STDOUT)和标准出错(STDERR)等等生命周期确认原则 LIFE-CYCLE CONFORMANCE PRINCIPLE (LCP)生命周期确认原则指的是容器应该提供和平台交互来处理相应的生命周期的变化。捕获并响应Terminate (SIGTERM)信号，来尽快优雅的终止服务进程，以避免kill (SIGKILL)信号强行终止进程。例如一下的NodeJS代码。process.on('SIGTERM', function () { console.log("Received SIGTERM. Exiting.") server.close(function () { process.exit(0); }); });返回退出码process.exit(0);镜像不可变原则 IMAGE IMMUTABILITY PRINCIPLE (IIP)在运行时，配置可以不同，但是镜像应该是不可变的。我们可以理解为镜像是个类，是容器是对象实例，类是不变的，而容器是拥有不同配置参数的镜像实例。进程用完既丢原则 PROCESS DISPOSABILITY PRINCIPLE (PDP)在云环境下，我们应该假定所有的容器都是临时的，它随时有可能被其它的容器实例所替代。这也就意味着需要把容器的状态保存在容器之外。并且尽可能快速的启动和终止容器。通常越小的容器就越容易实现这一点。自包含原则 SELF-CONTAINMENT PRINCIPLE (S-CP)容器在构建的时候应该包含所有的依赖，也就是所说容器在运行时不应该有任何的外部依赖。限制运行资源原则 RUNTIME CONFINEMENT PRINCIPLE (RCP)容器的最佳实践应该是在运行时指定容器对资源配置的需求。例如需要多少的内存，CPU等等。这样做可以使得容器编排能都更有效的调度和管理资源。模式许多容器应用的模式和Pod的概念相关，Pod是Kubernetes为了有效的管理容器而提出的概念，它是容器的集合，我们可以理解为“超容器”（我随便发明的）。Pod包含的容器之间就好像运行在同一台机器上，这些容器共享Localhost主机地址，可以本机通信，共享卷等等。Kubernetes 类似云上OS，提供了用容器构建云原生应用的最佳实践。我们看看这些常见的模式都有什么。边车（侧斗）（Sidecar）Sidecar是最常见的模式，在同一个Pod中，我们需要把不同的责任分在不同的容器中，对外部提供一个完整的功能。这样的例子有很多，例如：上图中的Node后端和提供缓存的RedisWeb服务器和收集日志的服务Web服务器和负责监控服务器性能数据的服务这样做有点类似面向对象的组合模式，好处有很多：应用单一职责原则，每一个容器只负责专注做好一件事。隔离，容器之间不会出现互相竞争资源，当一个次要功能（例如日志收集或者缓存）失效或者崩溃的时候，对主要功能的影响降至最小。可以对每一个容器进行独立的生命周期管理可以对每一个容器进行独立的弹性扩张可以方便的替换其中一个容器代理（大使）容器类似于面向对象的Proxy模式，利用Pod中一个容器提供对外的访问连接。如下图中Node后端总是通过Service Discovery容器来和外部进行通信。这样做，负责Node模块开发的只需要假定…

Docker镜像中修改时区
type: item
date: 2020-10-29T07:41:00+00:00
description: 在Dockerfile中增加如下行：RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone

kubernetes中的Ksniff抓包工具
type: item
date: 2020-08-24T06:21:00+00:00
description: 对于相当一部分读者来说，在 Kubernetes 环境中，针对 Pod 进行抓包是个常规操作，在 Pod 中、在 Node 中都能够完成，抓出文件之后现场查看或者拷贝回来喂给 Wireshark 也都不难。Ksniff工具的作用是，把这些常规步骤组织起来，用一个简单的 kubectl 插件命令，就能完成这一系列的操作。Ksniff 有几个很有意思的特色：可以使用 krew 方便的进行安装。能够自动把 Pod 的 TCP Dump 数据输出给 Wireshark。能够方便的处理非特权 Pod 的抓包工作。无需触碰 Node。安装使用 Krew 能够很方便的安装 Ksniff：$ kubectl krew install sniff Updated the local copy of plugin index. Installing plugin: sniff CAVEATS: \ | This plugin needs the following programs: | * wireshark (optional, used for live capture) / Installed plugin: sniff抓包到 Wireshark部署一个简单的 httpbin 服务：apiVersion: v1 kind: Service metadata: name: httpbin labels: app: httpbin spec: ports: - name: http port: 8000 targetPort: 80 selector: app: httpbin --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: httpbin spec: replicas: 1 template: metadata: labels: app: httpbin version: v1 spec: containers: - image: docker.io/kennethreitz/httpbin imagePullPolicy: IfNotPresent name: httpbin ports: - containerPort: 80服务启动之后，再启动一个客户端：apiVersion: extensions/v1beta1 kind: Deployment metadata: name: sleep spec: replicas: 1 template: metadata: labels: app: sleep version: v1 spec: containers: - name: sleep image: dustise/sleep imagePullPolicy: IfNotPresent然后就可以启动 ksniff 插件来对 httpbin 的 Pod 进行监听了，例如：$ kubectl sniff httpbin-5fc7cf895d-lr89b ... INFO[0000] sniffing method: upload static tcpdump ... INFO[0000] using tcpdump path at: '/Users/dustise/.krew/store/sniff/ 。。。INFO[0002] executing command: '[/tmp/static-tcpdump -i any -U -w - ]' on container: 'httpbin', pod: 'httpbin-5fc7cf895d-lr89b', namespace: 'default'不难看出，ksniff 非常粗暴的将一个 tcpdump 上传到了被抓包的 Pod 上直接运行。并且命令执行后，直接启动了 Wireshark 进行监听。下面从 sleep Pod 上给被监听 Pod 制造一点流量。$ kubectl exec -it sleep-69bd44b5bb-tk6vn -- curl http://httpbin:8000/ip { "origin": "10.244.0.19" }在 Wireshark 中会看到相应的数据包：查看一下被监听 Pod 的进程：$ kubectl exec -it httpbin-5fc7cf895d-lr89b -- ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.6 85980 25100 ? Ss 15:42 0:01 /usr/bin/python root 8 0.0 0.8 130…

docker启动报错the backing xfs filesystem is formatted without d_type support
type: item
date: 2020-07-10T09:33:00+00:00
description: 问题现象docker启动报错，使用systemctl status docker 查看docker状态，显示如下[root@10-80-119-18 ~]# systemctl status docker.service ● docker.service - Docker Application Container Engine Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled) Active: failed (Result: start-limit) since Fri 2020-07-10 18:05:49 CST; 5min ago Docs: https://docs.docker.com Process: 57292 ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock (code=exited, status=1/FAILURE) Main PID: 57292 (code=exited, status=1/FAILURE) Jul 10 18:05:47 10-80-119-19.weinan.7x34.com systemd[1]: docker.service: main process exited, code=exited, status=1/FAILURE Jul 10 18:05:47 10-80-119-19.weinan.7x34.com systemd[1]: Failed to start Docker Application Container Engine. Jul 10 18:05:47 10-80-119-19.weinan.7x34.com systemd[1]: Unit docker.service entered failed state. Jul 10 18:05:47 10-80-119-19.weinan.7x34.com systemd[1]: docker.service failed. Jul 10 18:05:49 10-80-119-19.weinan.7x34.com systemd[1]: docker.service holdoff time over, scheduling restart. Jul 10 18:05:49 10-80-119-19.weinan.7x34.com systemd[1]: start request repeated too quickly for docker.service Jul 10 18:05:49 10-80-119-19.weinan.7x34.com systemd[1]: Failed to start Docker Application Container Engine. Jul 10 18:05:49 10-80-119-19.weinan.7x34.com systemd[1]: Unit docker.service entered failed state. Jul 10 18:05:49 10-80-119-19.weinan.7x34.com systemd[1]: docker.service failed.使用dockerd命令直接启动，输出如下[root@10-80-119-18 ~]# dockerd INFO[2020-07-10T17:12:39.818717601+08:00] Starting up INFO[2020-07-10T17:12:39.821536756+08:00] parsed scheme: "unix" module=grpc INFO[2020-07-10T17:12:39.821589117+08:00] scheme "unix" not registered, fallback to default scheme module=grpc INFO[2020-07-10T17:12:39.821628131+08:00] ccResolverWrapper: sending update to cc: {[{unix:///run/containerd/containerd.sock 0 <nil>}] <nil>} module=grpc INFO[2020-07-10T17:12:39.821647015+08:00] ClientConn switching balancer to "pick_first" …

Kubernetes 内部组件工作原理
type: item
date: 2020-03-31T14:32:00+00:00
description: 翻译自:https://blog.heptio.com/core-kubernetes-jazz-improv-over-orchestration-a7903ea92ca原作者：Joe Beda (Dad of two. CTO of Heptio. Started Google Compute Engine, Kubernetes and Google Container Engine.)本篇文章讲述了Kubernetes内部组件的工作原理，及创建POD的流程。如果你是运维人员或者是Kubernetes的使用者，你可以不需要知道Kubernetes的内部工作原理，但是如果你想理解Kubernetes内部的工作原理，这篇文章非常适合你。读这篇文章的前提是，你已经大致了解并会运用Kubernetes。这篇文章不会去描述什么是Kubernetes及其组件（如：Pod, Node, Kubelet）。 本篇将讨论Kubernetes核心组件及这些核心组件是如何让Kubernetes 对"爵士乐即兴演奏"。通常将像Kubernetes这样的通用系统称为容器的 "管弦乐编排"。但是管弦乐编排，必须有一个预先计划的指挥家。因此，这并不是Kubernetes的一个好的描述。相反，Kubernetes更像是爵士乐即兴演奏，有一系列演员互相配合协调完成演奏。接下来，开始介绍每个核心组件的功能。然后将看一个典型的调度和启动一个Pod的流程。1. Datastore: EtcdEtcd是Kubernetes的存储状态的数据库。虽然Kubernetes系统中有重要的内存缓存，但Etcd被认为是记录系统状态。Etcd的快速总结：它是一个集群分布式数据库，它可以提供分布式数据的一致性。这类的系统（如Zookeeper, Consul）是在 Google开发的chubby系统之后形成的，这些系统也称为"锁服务器"，因为他们可以实现分布式锁。Etcd和chubby的数据模型是一个简单的层次化的Key，并存储了简单的非结构化value，这看起来像是一个文件系统。有意思的是，在Google, chubby 被频繁用于为实现访问本地文件和对象存储的功能的抽象文件接口。然而，分布式数据库的高度一致性，提供了数据的严格写入顺序并允许client原子性的对数据做更新操作。可靠的系统的状态管理是任何系统中非常困难的一件事情。在分布式系统中，它是更加困难的，因为它引入了一致性算法，如raft或paxos。通过使用etcd，Kubernetes可以专注系统的其他部分。Etcd的watch机制是Kubernetes工作的关键。系统允许client去执行轻量级的对于Key值变化事件的订阅。当要watch的数据发生变化时, client会立即得到通知。这可以用作分布式系统组件之间的协调机制。 一个组件一旦写入etcd，其他组件可以立即对该变化作出反应。Etcd的消息机制正好和PubSub消息队列机制相反。在许多消息队列系统系统中，topic不存储真正的用户数据，但发布到这些topic的消息含有丰富的数据。对于像Etcd这样的系统，Key（类似于主题）存储了真实的数据而消息（数据变化通知）不含独特的丰富消息。换句话说，对于消息队列来说，topic很简单，而像Etcd则正好相反。（译者认为此处概括的非常准确）2. Policy Layer: API ServerKubernetes的核心组件是API Server，它是Kubernetes系统和Etcd直接对话的唯一组件。实际上，etcd是API server的实现细节，理论上也可以用其他分布式存储系统来支持Kubernetes.API server是一个策略组件，提供对Etcd的过滤访问。它的作用本质上是相对通用的，目前正在被分解处理。因此，API Server也可以用于其他系统的控制平面。API server的主要货物是资源，通过暴露简单的REST API 向外提供服务。这些资源有一个标准结构可以实现一些扩展功能。无论如何，API Server，允许各类组件创建，读取，写入，更新，和监视资源。API Server的具体的功能：认证和授权。Kubernetes有一个可插拔的认证系统。有一些内置的用户认证机制和授权这些用户访问资源。此外，还有一些方法可用于向外部服务提供这些服务。这种可扩展性是Kubernetes构建的核心功能。API Server运行一组可以拒绝或修改请求的准入控制器。 这些允许策略被应用并设置默认值。 这是确保在API Server客户端仍在等待请求确认时进入系统的数据有效性的关键。 虽然这些准入控制器目前正在编译到API Server中，但目前正在进行的工作是使其成为另一种可扩展性机制。API Server 有助…

使用kubeadm部署kubernetes1.17.3
type: item
date: 2020-02-29T13:28:00+00:00
description: 环境准备操作系统：centos 7 1908 mini设置主机名hostnamectl set-hostname k8s-sh-21.vm.90.vc hostnamectl set-hostname k8s-sh-22.vm.90.vc hostnamectl set-hostname k8s-sh-23.vm.90.vc配置hostsvim /etc/hosts10.0.2.21 k8s-sh-21.vm.90.vc 10.0.2.22 k8s-sh-22.vm.90.vc 10.0.2.23 k8s-sh-23.vm.90.vc防火墙与selinux主机防火墙需要开放相应端口，具体可参考kubernetes官方文档 Check required ports 一节。这里为了方便，直接关闭防火墙systemctl stop firewalld systemctl disable firewalld目前为止，kubernetes还不支持selinux，所以必须将selinux关闭。将 SELinux 设置为 permissive 模式（相当于将其禁用）setenforce 0 sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config关闭swap分区自kubernetes1.8版本开始，要求必须关闭swap，否则kubelet无法启动修改/etc/fstab配置文件，注释掉swap行。执行以下操作，关闭swap。swapoff -a sysctl -w vm.swappiness=0 echo "vm.swappiness=0">> /etc/sysctl.conf sysctl -p调整系统参数确保系统加载了 br_netfilter 模块。这可以通过运行 lsmod | grep br_netfilter 来检测。如没有加载则运行如下命令加载modprobe br_netfilter在 RHEL/CentOS 7 中：由于 iptables 可能被绕过而导致流量无法正确路由的问题。因此需要确保 在 sysctl 配置中的 net.bridge.bridge-nf-call-iptables 被设置为 1,在sysctl中添加如下配置cat <<EOF > /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 EOF运行 sysctl --system 生效安装docker从 v1.6.0 版本起，Kubernetes 开始默认允许使用 CRI（容器运行时接口）。从 v1.14.0 版本起，kubeadm 将通过观察已知的 UNIX 域套接字来自动检测 Linux 节点上的容器运行时。如果同时检测到 docker 和 containerd，则优先选择 docker。kubernetes 1.17官方推荐安装 docker 19.03.4 版本，但 1.13.1、17.03、17.06、17.09、18.06 和 18.09 版本也是可以的。安装docker依赖及yum配置工具yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo安装dockeryum -y install docker-ce-19.03.4-3.el7修改docker cgroup driver为systemd，以及一些docker的其他设置cat > /etc/docker/daemon.json <<EOF { "graph": "/data/docker", "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" }, "storage-driver": "overlay2", "storage-opts": [ "overlay2.override_kernel_check=true" ] } EOF重启docker并运行 docker info |grep cgroup 验证修改kubernetes 安装配置kubernetes的yum源cat <<EOF > /etc/…

国内无法访问gcr.io与quay.io镜像问题
type: item
date: 2020-02-29T11:48:25+00:00
description: 由于众所周知的原因，gcr.io在国内不可访问，quay.io也间歇性抽风。这里可以用azure中国的相关镜像来获取，如下表原始地址国内镜像gcr.iogcr.azk8s.cnquay.ioquay.azk8s.cn

Kubernetes CNI网络对比：Flannel、Calico、Canal和Weave
type: item
date: 2020-02-26T10:38:00+00:00
description: 介绍网络架构是Kubernetes中较为复杂、让很多用户头疼的方面之一。Kubernetes网络模型本身对某些特定的网络功能有一定要求，但在实现方面也具有一定的灵活性。因此，业界已有不少不同的网络方案，来满足特定的环境和要求。CNI意为容器网络接口，它是一种标准的设计，为了让用户在容器创建或销毁时都能够更容易地配置容器网络。在本文中，我们将集中探索与对比目前最流行的CNI插件：Flannel、Calico、Weave和Canal（技术上是多个插件的组合）。这些插件既可以确保满足Kubernetes的网络要求，又能为Kubernetes集群管理员提供他们所需的某些特定的网络功能。背景容器网络是容器选择连接到其他容器、主机和外部网络（如Internet）的机制。容器的runtime提供了各种网络模式，每种模式都会产生不同的体验。例如，Docker默认情况下可以为容器配置以下网络：none：将容器添加到一个容器专门的网络堆栈中，没有对外连接。host：将容器添加到主机的网络堆栈中，没有隔离。default bridge：默认网络模式。每个容器可以通过IP地址相互连接。自定义网桥：用户定义的网桥，具有更多的灵活性、隔离性和其他便利功能。Docker还可以让用户通过其他驱动程序和插件，来配置更高级的网络（包括多主机覆盖网络）。CNI的初衷是创建一个框架，用于在配置或销毁容器时动态配置适当的网络配置和资源。下面链接中的CNI规范概括了用于配制网络的插件接口，这个接口可以让容器运行时与插件进行协调：https://github.com/containernetworking/cni/blob/master/SPEC.md插件负责为接口配置和管理IP地址，并且通常提供与IP管理、每个容器的IP分配、以及多主机连接相关的功能。容器运行时会调用网络插件，从而在容器启动时分配IP地址并配置网络，并在删除容器时再次调用它以清理这些资源。运行时或协调器决定了容器应该加入哪个网络以及它需要调用哪个插件。然后，插件会将接口添加到容器网络命名空间中，作为一个veth对的一侧。接着，它会在主机上进行更改，包括将veth的其他部分连接到网桥。再之后，它会通过调用单独的IPAM（IP地址管理）插件来分配IP地址并设置路由。在Kubernetes中，kubelet可以在适当的时间调用它找到的插件，来为通过kubelet启动的pod进行自动的网络配置。术语在对CNI插件们进行比较之前，我们可以先对网络中会见到的相关术语做一个整体的了解。不论是阅读本文，还是今后接触到其他和CNI有关的内容，了解一些常见术语总是非常有用的。一些最常见的术语包括：第2层网络： OSI（Open Systems Interconnections，开放系统互连）网络模型的“数据链路”层。第2层网络会处理网络上两个相邻节点之间的帧传递。第2层网络的一个值得注意的示例是以太网，其中MAC表示为子层。第3层网络： OSI网络模型的“网络”层。第3层网络的主要关注点，是在第2层连接之上的主机之间路由数据包。IPv4、IPv6和ICMP是第3层网络协议的示例。VXLAN：代表“虚拟可扩展LAN”。首先，VXLAN用于通过在UDP数据报中封装第2层以太网帧来帮助实现大型云部署。VXLAN虚拟化与VLAN类似，但提供更大的灵活性和功能（VLAN仅限于4096个网络ID）。VXLAN是一种封装和覆盖协议，可在现有网络上运行。Overlay网络：Overlay网络是建立在现有网络之上的虚拟逻辑网络。Overlay网络通常用于在现有网络之上提供有用的抽象，并分离和保护不同的逻辑网络。封装：封装是指在附加层中封装网络数据包以提供其他上下文和信息的过程。在overlay网络中，封装被用于从虚拟网络转换到底层地址空间，从而能路由到不同的位置（数据包可以被解封装，并继续到其目的地）。网状网络：网状网络（Mesh network）是指每个节点连接到许多其他节点以协作路由、并实现更大连接的网络。网状网络允许通过多个路径进行路由，从而提供更可靠的网络。网状网格的缺点是每个附加节点都会增加大量开销。BGP：代表“边界网关协议”，用于管理边缘路由器之间数据包的路由方式。BGP通过考虑可用路径，路由规则和特定网络策略，帮助弄清楚如何将数据包从一个网络发送到另一个网络。BGP有时被用作CNI插件中的路由机制，而不是封装的覆盖网络。了解了技术术语和支持各类插件的各种技术之后，下面我们可以开始探索一些最流行的CNI插件了。CNI比较Flannel链接：https://github.com/coreos/flannel由CoreOS开发的项目Flannel，可能是最直接和最受欢迎的CNI插件。它是容器编排系统中最成熟的网络结构示例之一，旨在…

docker 修改镜像及数据存储目录
type: item
date: 2020-02-16T12:58:08+00:00
description: 修改配置文件编辑/etc/docker/daemon.json配置文件，如文件不存在则手动创建,文件中添加如下内容{ "graph": "/data/docker" }创建相应目录与上面配置文件的路径保持一致mkdir /data/docker修改selinux权限如selinux打开，还需要修改selinux相关权限，否则docker启动会报错chcon -R -u system_u /data/docker/ chcon -R -t container_var_lib_t /data/docker/重启docker服务systemctl daemon-reload systemctl restart docker

避免Docker镜像中使用大型jar包
type: item
date: 2020-02-13T13:50:00+00:00
description: 在 Docker 容器里放大型 JAR 包是对存储空间、网络带宽和时间的一种浪费。所幸的是，我们可以利用 Docker 镜像的分层机制和注册表缓存来创建增量式的小工件，甚至可以将一个工件的大小从 75MB 减到 1MB。值得一提的是，现在有一个 Maven 和 Gradle 插件可以处理这些事情。问题：大型 JAR 包里的依赖项Docker 镜像的分层机制非常强大。如果你所有的应用程序都使用了相同的基础镜像（比如 openjdk:11.0.4-jre-slim），Docker 重用了 OS 和 JRE 的一些层，这样就可以节省 Docker 注册表的存储空间，上传和下载镜像的速度也更快了，因为只需要传输更少的文件量（Docker 只会将新的层传输到注册表中）。可惜的是，很多应用程序并没有很好地利用这个强大的机制，因为他们把大型的 JAR 包塞进了 Docker 镜像里。我们假设将一个 Spring Boot 应用程序打成一个大型的 JAR 包。这个 JAR 包有 72MB，并在 Dockerfile 的最后一行将它加到镜像里。也就是说，每个新版本都需要 72MB 的存储空间，而且要上传到注册表中，或则从注册表中下载下来。现在来看一下这个 72MB 的镜像：一个大型的 JAR 包包含三个部分：依赖项：它们占了很大的一部分比例，但很少发生改动。大多数时候我们只会修改我们的代码，很少会去修改依赖项。但依赖项每次都会被拷贝到发布版本中。资源文件：这个问题跟依赖项差不多。虽然资源文件（HTML、CSS、图像、配置文件，等等）比依赖项更经常发生改动，但比起代码还是相对少一些。它们也会被拷贝到发布版本中。代码：代码只占 JAR 包很小的一部分（通常 300KB 到 2MB），但会经常发生改动。经常发生改动的代码只有几 MB，但每次都需要拷贝所有的依赖项和资源文件，这是对存储空间、带宽和时间的一种浪费。如果需要为每个 git 提交创建一个可部署的镜像，那浪费的空间就更多了。持续交付可能需要这么做，但这样做浪费了大量的空间，因为每一次提交都会占用额外的 72MB 空间。有哪些有用的工具可用来分析 Docker 镜像和可视化大型 JAR 对 Docker 镜像的影响？是 dive 和 docker history。docker history 命令也可以用来显示 JAR 包层：~ ❯❯❯ docker history registry.domain.com/neptune:latest IMAGE CREATED CREATED BY SIZE 44e77fa110e5 2 minutes ago /bin/sh -c #(nop) COPY dir:… 65.5MB ... <missing> 8 months ago /bin/sh -c set -ex; if [ … 217MB ... <missing> 8 months ago /bin/sh -c #(nop) ADD file:… 55.3MB解决方案：依赖项、资源文件和代码放在不同的层所幸的是，我们可以利用 Docker 镜像的分层机制，就像已经分好的 OS 和 JRE 层那样。我们更进一步，引入了依赖项层、资源文件层和代码层。我们还按照改动的频繁程度来安排这些层的次序。现在，如果新版本只包含代码层的改动，就只需要 2MB 的存储空间，因为我们可以重用依赖项层和资源层。它们在注册表中已经有了，不需要再次上传。谷歌的 Jib 插件实际上，我们不需要手动编写 Dockerfile，我们可以使用谷歌的 Jib 插件。Jib 是一个 Maven 或 Gradle 插件，用于简化 Java 应用程序镜像的打包过程。对于我们来说，Jib 最重要的一个特性是，它会扫描我们的 Java 项目，并为依赖项、资源文件和代码创建不同的层。使用步骤：1.在 pom.xml 中添加插件配置：<plugin> <groupId>com.google.cloud.tools</groupId> <artifactId>jib-maven-plugin</artifactId> <version>1.6.1</version> <configuration> <from> <image>openjdk:11.0.4-jre-slim</image> </from> <to> <image>domain.com/${project.artifactId}:latest</image> <tags> <tag>${git.commit.id}</tag> </tags> </to> <container> <jvmFlags> <jvmFlag>-server</jvmFlag> </jvmFlags> </con…