基于容器应用，著名的设计原则、模式和反模式
容器和容器编排（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模块开发的只需要假定所有的通信都是来自于本机，而把通信的复杂性交给代理容器，去处理诸如负载均衡，安全，过滤请求，必要时中断通信等功能。适配器容器大家常常会把面向对象的Proxy模式，Bridge模式和Adapter模式搞混，因为单单从UML关系图上来看，它们都大同小异。似乎只是取了不同的名字。事实也确实如此，就像几乎所有的OO模式都是组合模式的衍生，所有容器模式都是边车模式的衍生。在下图的例子中，如果Logging Adapter的名字不提及Adapter，我们不会认为这是个适配器模式。其实适配器模式关注的是如果把Pod内部的不同容器的功能通过适配器统一的暴漏出来。在上图中，如果我们再多加一个容器，它同时会向卷中写入日志的化，这样就更清楚了。Logging Adapter适配不同容器用不同的接口提供的日志，并提供统一的访问接口。容器链类似于OO的责任链模式，把负责不同功能的容器按照依赖顺序链在一起，也是一种常见的模式。准备就绪的Pod通常作为服务的容器有一个启动的过程，在启动过程中，服务是不可用的。Kubernetes提供了Readiness探测功能。readinessProbe: httpGet: path: / port: 5000 timeoutSeconds: 1 periodSeconds: 5和其它模式相比，这个更像是一个使用Kubernetes的最佳实践。反模式构建环境和运行环境混杂在一起应该使得用于生产的运行环境的镜像尽可能的小，避免在运行环境的镜像中包含构建时的残留。例如下面的Dockerfile例子：FROM ubuntu:14.04 RUN apt-get update RUN apt-get install gcc RUN gcc hello.c -o /hello在这个构建的镜像中，有很多不需要也不应该出现在生产环境中的东西，例如gcc，源代码hello.c。这样的结果既不安全（直接暴漏源代码），也会有性能开销（过大的镜像体积导致加载变慢）。Docker17.05 以后提供的multi-stage builds也可以解决这个问题。直接使用Pod避免直接使用Pod，用Deployment来管理Pod。利用Deployment可以很方便的对Pod进行扩展和管理。使用latest标签Latest标签用于标记最近的稳定版本，然而在创建容器时，尽可能避免在生产环境使用Latest标签。即使使用imagePullPolicy选项为alway。快速失败的任务Job是Kubernetes提供的只运行一次的容器，和service正好相反。要避免快速失败apiVersion: batch/v1 kind: Job metadata: name: bad spec: template: metadata: name: bad spec: restartPolicy: Never containers: - name: box image: busybox command: ["/bin/sh", "-c", "exit 1"]如果你尝试在你的cluster里面创建以上的Job，你可能会碰到如下的状态。$ kubectl describe jobs Name: bad Namespace: default Image(s): busybox Selector: controller-uid=18a6678e-11d1-11e7-8169-525400c83acf Parallelism: 1 Completions: 1 Start Time: Sat, 25 Mar 2017 20:05:41 -0700 Labels: controller-uid=18a6678e-11d1-11e7-8169-525400c83acf job-name=bad Pods Statuses: 1 Running / 0 Succeeded / 24 Failed No volumes. Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: bad-fws8g 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: bad-321pk 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: bad-2pxq1 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: bad-kl2tj 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: bad-wfw8q 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: bad-lz0hq 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: bad-0dck0 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: bad-0lm8k 1m 1m 1 {job-controller } Normal SuccessfulCreate Created pod: bad-q6ctf 1m 1s 16 {job-controller } Normal SuccessfulCreate (events with common reason combined)因为任务快速失败。Kubernetes认为任务没能成功启动，尝试创建新的容器以恢复这个失败，导致的Cluster会在短时间创建大量的容器，这样的结果可能会消耗大量的计算资源。在Spec中使用.spec.activeDeadlineSeconds来避免这个问题。这个参数定了等待多长时间重试失败的Job。参考https://www.redhat.com/cms/managed-files/cl-cloud-native-container-design-whitepaper-f8808kc-201710-v3-en.pdfhttps://www.slideshare.net/luebken/container-patternshttps://github.com/luebken/container-patternshttps://kubernetes.io/blog/2015/06/the-distributed-system-toolkit-patterns/http://docs.projectatomic.io/container-best-practices/https://www.usenix.org/system/files/conference/hotcloud16/hotcloud16_burns.pdfhttps://github.com/gravitational/workshop/blob/master/k8sprod.md
在Dockerfile中增加如下行：RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ && echo "Asia/Shanghai" > /etc/timezone
对于相当一部分读者来说，在 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 130364 35164 ? S 15:42 0:01 /usr/bin/python root 35 0.0 0.0 6392 3568 ? Ss 15:50 0:00 /tmp/static-tcp root 47 0.0 0.0 6392 3564 ? Ss 15:58 0:00 /tmp/static-tcp root 70 0.0 0.0 6392 3564 ? Ss 16:17 0:00 /tmp/static-tcp root 90 0.0 0.0 6392 3568 ? Ss 17:01 0:00 /tmp/static-tcp root 102 0.0 0.0 6392 3568 ? Ss 17:05 0:00 /tmp/static-tcp不难看到，多出了几个 /tmp/static-tcp 的进程。无特权 Pod 怎么办Ksniff 还提供了 -p 参数，用于针对无特权 Pod 进行监听。带有这一参数之后，查询目标 Pod 所在节点，然后在该节点上利用节点亲和性创建共享节点网络的特权 Pod，然后在新 Pod 上对流量进行监控。$ kubectl sniff httpbin-5fc7cf895d-lr89b -p INFO[0000] sniffing method: privileged pod INFO[0000] using tcpdump path at: '/Users/dustise/.krew/store/sniff/71102253eded8900c8f7b0d0624c65b3c77ecd6bcd28fabc9a200da ac502282a/static-tcpdump' INFO[0000] no container specified, taking first container we found in pod. INFO[0000] selected container: 'httpbin' ... INFO[0000] creating privileged pod on node: 'vla' ... INFO[0008] pod: 'ksniff-qpznn' created successfully on node: 'vla' $ kubectl get pods flaskapp-v1-5f58cbc685-9v4z9 1/1 Running 0 92m httpbin-5fc7cf895d-lr89b 1/1 Running 0 93m ksniff-689sx 1/1 Running 0 66m sleep-69bd44b5bb-tk6vn 1/1 Running 0 93m可以看到，ksniff 创建了新的 Pod。并且也成功的启动了 Wireshark。再次执行：$ kubectl exec -it sleep-69bd44b5bb-tk6vn -- curl http://httpbin:8000/ip { "origin": "10.244.0.19" }可以看到，Wireshark 中出现了新的数据包。参考链接https://github.com/kubernetes-sigs/krewhttps://github.com/eldadru/ksniff
docker启动报错the backing xfs filesystem is formatted without d_type support
问题现象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" module=grpc INFO[2020-07-10T17:12:39.823666069+08:00] parsed scheme: "unix" module=grpc INFO[2020-07-10T17:12:39.823713672+08:00] scheme "unix" not registered, fallback to default scheme module=grpc INFO[2020-07-10T17:12:39.823743133+08:00] ccResolverWrapper: sending update to cc: {[{unix:///run/containerd/containerd.sock 0 <nil>}] <nil>} module=grpc INFO[2020-07-10T17:12:39.823758824+08:00] ClientConn switching balancer to "pick_first" module=grpc failed to start daemon: error initializing graphdriver: overlay2: the backing xfs filesystem is formatted without d_type support, which leads to incorrect behavior. Reformat the filesystem with ftype=1 to enable d_type support. Backing filesystems without d_type support are not supported.问题分析出现问题的docker配置存储驱动为overlay2，从报错上看，overlay2需要文件系统支持d_type，但出现问题的机器上配置的data-root目录采用的是xfs文件系统，默认并没有开启d_type，需要开启d_type的支持。什么是d_typed_type 是 Linux 内核的一个术语，表示 “目录条目类型”，而目录条目，其实是文件系统上目录信息的一个数据结构。d_type，就是这个数据结构的一个字段，这个字段用来表示文件的类型，是文件，还是管道，还是目录还是套接字等。d_type 从 Linux 2.6 内核开始就已经支持了，只不过虽然 Linux 内核虽然支持，但有些文件系统实现了 d_type，而有些，没有实现，有些是选择性的实现，也就是需要用户自己用额外的参数来决定是否开启d_type的支持。检查xfs文件系统是否支持d_type[root@10-80-119-18 ~]# xfs_info / meta-data=/dev/vda2 isize=256 agcount=9, agsize=2589248 blks = sectsz=512 attr=2, projid32bit=1 = crc=0 finobt=0 data = bsize=4096 blocks=20843184, imaxpct=25 = sunit=0 swidth=0 blks naming =version 2 bsize=4096 ascii-ci=0 ftype=0 log =internal bsize=4096 blocks=5057, version=2 = sectsz=512 sunit=0 blks, lazy-count=1 realtime =none extsz=4096 blocks=0, rtextents=0注意以上输出中的ftype字段，为0则表示不支持。解决方案指定docker的存储驱动类型为devicemapper，devicemapper不需要文件系统支持d_type，但docker官方目前推荐使用overlay2，因此不选择此方案。重新格式化docker data-root目录所在的文件系统，xfs格式化时添加参数ftype=1,如下mkfs.xfs -n ftype=1 /dev/mapper/vg_data-lv_data -f这里采用第二种解决方案重新启动docker，问题解决
翻译自: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 有助于API 版本控制。API 版本的一个关键问题是允许资源的字段的改变，字段添加，弃用，重新组织和以其他方式转换。 API Server在Etcd中存储资源的"true"表示，并根据满足的API版本转换/呈现该资源。 自项目早期开始，规划版本控制和API的发展一直是Kubernetes的一项重要工作。API Server 一个重要特性是支持watch机制。这意味着API Server的客户端可以使用与Etcd相同的协调模式。Kubernetes中的大多数协调包括写入另一个组件正在监视的API服务器资源的组件。 第二个组件将对几乎立即发生的变化做出反应。3. 业务逻辑：Controller Manager and Scheduler这些是通过API Server 进行协调的组件。这些称为Controller Manager和Scheduler的组件绑定到单独的服务器Master上Scheduler组件将做许多事情让系统工作：查找未分配给节点的Pod(未绑定的Pod)；检查集群的状态（缓存在内存中）；选择具有空闲空间并满足其他约束条件的节点;将pod绑定到该节点。Controller Manager 组件，实现ReplicaSet的行为。（ReplicaSet可以确保任何时候都可以运行一个Pod模板的副本数量）。控制器将根据资源中的选择器 监控ReplicaSet 资源和一组Pod。为了保持在ReplicaSet中稳定的一组Pod，控制器将创建、销毁Pod。4.Node Agent: Kubelet每一个Node上都有一个Agent。这也像其他组件一样对API Server进行身份验证。Agent负责监视绑定到其节点的一组Pod，并确保这些Pod正常运行，并且能实时返回这些Pod的运行状态。5.典型的流程为帮助理解，创建Pod的整个流程，时序图如下：这个时序图展示了创建pod的流程，基本的流程如下：用户提交创建Pod的请求，可以通过API Server的REST API ，也可用Kubectl命令行工具，支持Json和Yaml两种格式；API Server 处理用户请求，存储Pod数据到Etcd；Schedule通过和 API Server的watch机制，查看到新的pod，尝试为Pod绑定Node；过滤主机：调度器用一组规则过滤掉不符合要求的主机，比如Pod指定了所需要的资源，那么就要过滤掉资源不够的主机；主机打分：对第一步筛选出的符合要求的主机进行打分，在主机打分阶段，调度器会考虑一些整体优化策略，比如把一个Replication Controller的副本分布到不同的主机上，使用最低负载的主机等；选择主机：选择打分最高的主机，进行binding操作，结果存储到Etcd中；kubelet根据调度结果执行Pod创建操作： 绑定成功后，会启动container, docker run, scheduler会调用API Server的API在etcd中创建一个bound pod对象，描述在一个工作节点上绑定运行的所有pod信息。运行在每个工作节点上的kubelet也会定期与etcd同步bound pod信息，一旦发现应该在该工作节点上运行的bound pod对象没有更新，则调用Docker API创建并启动pod内的容器。总结通过使用API Server作为中心协调点，Kubernetes能够以松耦合的方式，实现组件相互交互。 希望读完此文，你可以对Kubernetes创建Pod的原理有更深入的认识。此外：k8s 也提供qos 服务，ref: https://cloud.90.vc/s/8WCqPHRH55AgMYY
环境准备操作系统：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/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg EOF安装kubeadm、kubelet、kubectlyum -y install kubeadm kubelet kubectlkubernetes初始化打印集群默认初始化配置kubeadm config print init-defaults > k8s-init.yaml编辑得到的k8s-init.yaml配置文件，修改如下项：advertiseAddress，将其修改为master的IP地址。imageRepository，因为k8s.gcr.io在国内不可用，因此，将的值修改为azure中国的镜像k8s.azure.cn/google_containers，否则，kubernetes需要的一些docker镜像拉不下来，kubernetes初始化会一直挂起。etcd相关设置，这里只修改一下etcd本地的存储目录。serviceSubnet子网掩码的范围，这里保持默认。新增配置项podSubnet，在network配置中增加pod的子网范围。修改后的初始化配置如下apiVersion: kubeadm.k8s.io/v1beta2 bootstrapTokens: - groups: - system:bootstrappers:kubeadm:default-node-token token: abcdef.0123456789abcdef ttl: 24h0m0s usages: - signing - authentication kind: InitConfiguration localAPIEndpoint: advertiseAddress: 10.14.230.21 bindPort: 6443 nodeRegistration: criSocket: /var/run/dockershim.sock name: k8s-sh-21.vm.90.vc taints: - effect: NoSchedule key: node-role.kubernetes.io/master --- apiServer: timeoutForControlPlane: 4m0s apiVersion: kubeadm.k8s.io/v1beta2 certificatesDir: /etc/kubernetes/pki clusterName: kubernetes controllerManager: {} dns: type: CoreDNS etcd: local: dataDir: /data/k8s/etcd imageRepository: k8s.azk8s.cn/google_containers kind: ClusterConfiguration kubernetesVersion: v1.17.0 networking: dnsDomain: k8s.90.vc serviceSubnet: 10.96.0.0/12 podSubnet: 10.244.0.0/16 scheduler: {}执行kubeadm init --config k8s-init.yaml 进行初始化返回结果如下W0229 21:02:26.431332 41307 validation.go:28] Cannot validate kube-proxy config - no validator is available W0229 21:02:26.431451 41307 validation.go:28] Cannot validate kubelet config - no validator is available [init] Using Kubernetes version: v1.17.0 [preflight] Running pre-flight checks [preflight] Pulling images required for setting up a Kubernetes cluster [preflight] This might take a minute or two, depending on the speed of your internet connection [preflight] You can also perform this action in beforehand using 'kubeadm config images pull' [kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env" [kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml" [kubelet-start] Starting the kubelet [certs] Using certificateDir folder "/etc/kubernetes/pki" [certs] Generating "ca" certificate and key [certs] Generating "apiserver" certificate and key [certs] apiserver serving cert is signed for DNS names [k8s-sh-21.vm.90.vc kubernetes kubernetes.default kubernetes.default.svc kubernetes.default.svc.k8s.90.vc] and IPs [10.96.0.1 10.14.230.21] [certs] Generating "apiserver-kubelet-client" certificate and key [certs] Generating "front-proxy-ca" certificate and key [certs] Generating "front-proxy-client" certificate and key [certs] Generating "etcd/ca" certificate and key [certs] Generating "etcd/server" certificate and key [certs] etcd/server serving cert is signed for DNS names [k8s-sh-21.vm.90.vc localhost] and IPs [10.14.230.21 127.0.0.1 ::1] [certs] Generating "etcd/peer" certificate and key [certs] etcd/peer serving cert is signed for DNS names [k8s-sh-21.vm.90.vc localhost] and IPs [10.14.230.21 127.0.0.1 ::1] [certs] Generating "etcd/healthcheck-client" certificate and key [certs] Generating "apiserver-etcd-client" certificate and key [certs] Generating "sa" key and public key [kubeconfig] Using kubeconfig folder "/etc/kubernetes" [kubeconfig] Writing "admin.conf" kubeconfig file [kubeconfig] Writing "kubelet.conf" kubeconfig file [kubeconfig] Writing "controller-manager.conf" kubeconfig file [kubeconfig] Writing "scheduler.conf" kubeconfig file [control-plane] Using manifest folder "/etc/kubernetes/manifests" [control-plane] Creating static Pod manifest for "kube-apiserver" [control-plane] Creating static Pod manifest for "kube-controller-manager" W0229 21:02:30.970566 41307 manifests.go:214] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC" [control-plane] Creating static Pod manifest for "kube-scheduler" W0229 21:02:30.971420 41307 manifests.go:214] the default kube-apiserver authorization-mode is "Node,RBAC"; using "Node,RBAC" [etcd] Creating static Pod manifest for local etcd in "/etc/kubernetes/manifests" [wait-control-plane] Waiting for the kubelet to boot up the control plane as static Pods from directory "/etc/kubernetes/manifests". This can take up to 4m0s [apiclient] All control plane components are healthy after 33.501703 seconds [upload-config] Storing the configuration used in ConfigMap "kubeadm-config" in the "kube-system" Namespace [kubelet] Creating a ConfigMap "kubelet-config-1.17" in namespace kube-system with the configuration for the kubelets in the cluster [upload-certs] Skipping phase. Please see --upload-certs [mark-control-plane] Marking the node k8s-sh-21.vm.90.vc as control-plane by adding the label "node-role.kubernetes.io/master=''" [mark-control-plane] Marking the node k8s-sh-21.vm.90.vc as control-plane by adding the taints [node-role.kubernetes.io/master:NoSchedule] [bootstrap-token] Using token: abcdef.0123456789abcdef [bootstrap-token] Configuring bootstrap tokens, cluster-info ConfigMap, RBAC Roles [bootstrap-token] configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials [bootstrap-token] configured RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token [bootstrap-token] configured RBAC rules to allow certificate rotation for all node client certificates in the cluster [bootstrap-token] Creating the "cluster-info" ConfigMap in the "kube-public" namespace [kubelet-finalize] Updating "/etc/kubernetes/kubelet.conf" to point to a rotatable kubelet client certificate and key [addons] Applied essential addon: CoreDNS [addons] Applied essential addon: kube-proxy Your Kubernetes control-plane has initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config You should now deploy a pod network to the cluster. Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at: https://kubernetes.io/docs/concepts/cluster-administration/addons/ Then you can join any number of worker nodes by running the following on each as root: kubeadm join 10.0.2.21:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:8248b6235e0ecf9cb8579dfefce45b6c1d2b3de928cebdf5582621e022f28963 如果执行结果返回Your Kubernetes control-plane has initialized successfully!的话，则说明kubernetes初始化成功。接下来，依据提示，分别进行添加kubectl的配置文件、网络插件、添加其他节点等操作。管理配置文件mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config部署flannel网络插件执行如下操作curl -O https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml kubectl apply -f kube-flannel.yml返回如下信息podsecuritypolicy.policy/psp.flannel.unprivileged created clusterrole.rbac.authorization.k8s.io/flannel created clusterrolebinding.rbac.authorization.k8s.io/flannel created serviceaccount/flannel created configmap/kube-flannel-cfg created daemonset.apps/kube-flannel-ds-amd64 created daemonset.apps/kube-flannel-ds-arm64 created daemonset.apps/kube-flannel-ds-arm created daemonset.apps/kube-flannel-ds-ppc64le created daemonset.apps/kube-flannel-ds-s390x created这里可能会遇到quay.io无法访问的情况，可以修改kube-flannel文件中的镜像地址，采用国内镜像源获取相关的docker镜像即可。在其余节点上执行如下操作，加入kubernetes集群kubeadm join 10.0.2.21:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:8248b6235e0ecf9cb8579dfefce45b6c1d2b3de928cebdf5582621e022f28963检查kubernetes集群运行状态[root@k8s-sh-21 ~]# kubectl get cs NAME STATUS MESSAGE ERROR scheduler Healthy ok controller-manager Healthy ok etcd-0 Healthy {"health":"true"} [root@k8s-sh-21 ~]# kubectl get node NAME STATUS ROLES AGE VERSION k8s-sh-21.vm.90.vc Ready master 101m v1.17.3 k8s-sh-22.vm.90.vc Ready <none> 99m v1.17.3 k8s-sh-23.vm.90.vc Ready <none> 99m v1.17.3 [root@k8s-sh-21 ~]# kubectl get pods -A -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES kube-system coredns-6cd559f5d5-8vkm8 1/1 Running 0 101m 10.244.0.4 k8s-sh-21.vm.90.vc <none> <none> kube-system coredns-6cd559f5d5-clhrt 1/1 Running 0 101m 10.244.0.3 k8s-sh-21.vm.90.vc <none> <none> kube-system etcd-k8s-sh-21.vm.90.vc 1/1 Running 0 101m 10.14.230.21 k8s-sh-21.vm.90.vc <none> <none> kube-system kube-apiserver-k8s-sh-21.vm.90.vc 1/1 Running 0 101m 10.14.230.21 k8s-sh-21.vm.90.vc <none> <none> kube-system kube-controller-manager-k8s-sh-21.vm.90.vc 1/1 Running 0 101m 10.14.230.21 k8s-sh-21.vm.90.vc <none> <none> kube-system kube-flannel-ds-amd64-6mvlf 1/1 Running 0 100m 10.14.230.23 k8s-sh-23.vm.90.vc <none> <none> kube-system kube-flannel-ds-amd64-dxpgk 1/1 Running 0 100m 10.14.230.21 k8s-sh-21.vm.90.vc <none> <none> kube-system kube-flannel-ds-amd64-mjmzz 1/1 Running 0 100m 10.14.230.22 k8s-sh-22.vm.90.vc <none> <none> kube-system kube-proxy-2xtwb 1/1 Running 0 100m 10.14.230.22 k8s-sh-22.vm.90.vc <none> <none> kube-system kube-proxy-fl9bs 1/1 Running 0 101m 10.14.230.21 k8s-sh-21.vm.90.vc <none> <none> kube-system kube-proxy-thhn8 1/1 Running 0 100m 10.14.230.23 k8s-sh-23.vm.90.vc <none> <none> kube-system kube-scheduler-k8s-sh-21.vm.90.vc 1/1 Running 0 101m 10.14.230.21 k8s-sh-21.vm.90.vc <none> <none>确保以上node、pod等均为ready和running即可。将kube-proxy改为ipvs模式cat > /etc/sysconfig/modules/ipvs.modules <<EOF #!/bin/bash modprobe -- ip_vs modprobe -- ip_vs_rr modprobe -- ip_vs_wrr modprobe -- ip_vs_sh modprobe -- nf_conntrack_ipv4 EOF chmod 755 /etc/sysconfig/modules/ipvs.modules && bash /etc/sysconfig/modules/ipvs.modules && lsmod | grep -e ip_vs -e nf_conntrack_ipv4 编辑configmap中kube-proxy的配置文件。将mode字段修改为ipvskubectl edit cm kube-proxy -n kube-system修改后重启kube-proxykubectl get pod -n kube-system | grep kube-proxy | awk '{system("kubectl delete pod "$1" -n kube-system")}'检查修改结果，kube-proxy日志中输出ipvs即表示已经启用ipvs模式，反之则是iptables kubectl get pod -n kube-system | grep kube-proxy |awk '{system("kubectl logs "$1" -n kube-system")}'|grep ProxierHelm的安装及使用部署dashboardingress
Kubernetes CNI网络对比：Flannel、Calico、Canal和Weave
介绍网络架构是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插件。它是容器编排系统中最成熟的网络结构示例之一，旨在实现更好的容器间和主机间网络。随着CNI概念的兴起，Flannel CNI插件算是早期的入门。与其他方案相比，Flannel相对容易安装和配置。它被打包为单个二进制文件flanneld，许多常见的Kubernetes集群部署工具和许多Kubernetes发行版都可以默认安装Flannel。Flannel可以使用Kubernetes集群的现有etcd集群来使用API存储其状态信息，因此不需要专用的数据存储。Flannel配置第3层IPv4 overlay网络。它会创建一个大型内部网络，跨越集群中每个节点。在此overlay网络中，每个节点都有一个子网，用于在内部分配IP地址。在配置pod时，每个节点上的Docker桥接口都会为每个新容器分配一个地址。同一主机中的Pod可以使用Docker桥接进行通信，而不同主机上的pod会使用flanneld将其流量封装在UDP数据包中，以便路由到适当的目标。Flannel有几种不同类型的后端可用于封装和路由。默认和推荐的方法是使用VXLAN，因为VXLAN性能更良好并且需要的手动干预更少。总的来说，Flannel是大多数用户的不错选择。从管理角度来看，它提供了一个简单的网络模型，用户只需要一些基础知识，就可以设置适合大多数用例的环境。一般来说，在初期使用Flannel是一个稳妥安全的选择，直到你开始需要一些它无法提供的东西。Calico链接：https://github.com/projectcalico/cni-pluginCalico是Kubernetes生态系统中另一种流行的网络选择。虽然Flannel被公认为是最简单的选择，但Calico以其性能、灵活性而闻名。Calico的功能更为全面，不仅提供主机和pod之间的网络连接，还涉及网络安全和管理。Calico CNI插件在CNI框架内封装了Calico的功能。在满足系统要求的新配置的Kubernetes集群上，用户可以通过应用单个manifest文件快速部署Calico。如果您对Calico的可选网络策略功能感兴趣，可以向集群应用其他manifest，来启用这些功能。尽管部署Calico所需的操作看起来相当简单，但它创建的网络环境同时具有简单和复杂的属性。与Flannel不同，Calico不使用overlay网络。相反，Calico配置第3层网络，该网络使用BGP路由协议在主机之间路由数据包。这意味着在主机之间移动时，不需要将数据包包装在额外的封装层中。BGP路由机制可以本地引导数据包，而无需额外在流量层中打包流量。除了性能优势之外，在出现网络问题时，用户还可以用更常规的方法进行故障排除。虽然使用VXLAN等技术进行封装也是一个不错的解决方案，但该过程处理数据包的方式同场难以追踪。使用Calico，标准调试工具可以访问与简单环境中相同的信息，从而使更多开发人员和管理员更容易理解行为。除了网络连接外，Calico还以其先进的网络功能而闻名。 网络策略是其最受追捧的功能之一。此外，Calico还可以与服务网格Istio集成，以便在服务网格层和网络基础架构层中解释和实施集群内工作负载的策略。这意味着用户可以配置强大的规则，描述pod应如何发送和接受流量，提高安全性并控制网络环境。如果对你的环境而言，支持网络策略是非常重要的一点，而且你对其他性能和功能也有需求，那么Calico会是一个理想的选择。此外，如果您现在或未来有可能希望得到技术支持，那么Calico是提供商业支持的。一般来说，当您希望能够长期控制网络，而不是仅仅配置一次并忘记它时，Calico是一个很好的选择。Canal链接：https://github.com/projectcalico/canalCanal也是一个有趣的选择，原因有很多。首先，Canal 是一个项目的名称，它试图将Flannel提供的网络层与Calico的网络策略功能集成在一起。然而，当贡献者完成细节工作时却发现，很明显，如果Flannel和Calico这两个项目的标准化和灵活性都已各自确保了话，那集成也就没那么大必要了。结果，这个官方项目变得有些“烂尾”了，不过却实现了将两种技术部署在一起的预期能力。出于这个原因，即使这个项目不复存在，业界还是会习惯性地将Flannel和Calico的组成称为“Canal”。由于Canal是Flannel和Calico的组合，因此它的优点也在于这两种技术的交叉。网络层用的是Flannel提供的简单overlay，可以在许多不同的部署环境中运行且无需额外的配置。在网络策略方面，Calico强大的网络规则评估，为基础网络提供了更多补充，从而提供了更多的安全性和控制。确保集群满足必要的系统要求（https://docs.projectcalico.org/v3.6/getting-started/kubernetes/requirements）后，用户需要应用两个manifest才能部署Canal，这使得其配置比单独的任何一个项目都困难。如果企业的IT团队计划改变他们的网络方案，且希望在实施改变之前能先对网络策略进行一些实验并获取一些经验，那么Canal是一个不错的选择。一般来说，如果你喜欢Flannel提供的网络模型，但发现Calico的一些功能很诱人，那么不妨尝试一下Canal。从安全角度来看，定义网络策略规则的能力是一个巨大的优势，并且在许多方面是Calico的杀手级功能。能够将该技术应用到熟悉的网络层，意味着您可以获得更强大的环境，且可以省掉大部分的过渡过程。Weave链接：https://www.weave.works/oss/net/Weave是由Weaveworks提供的一种Kubernetes CNI网络选项，它提供的模式和我们目前为止讨论的所有网络方案都不同。Weave在集群中的每个节点之间创建网状overlay网络，参与者之间可以灵活路由。这一特性再结合其他一些独特的功能，在某些可能导致问题的情况下，Weave可以智能地路由。为了创建网络，Weave依赖于网络中每台主机上安装的路由组件。然后，这些路由器交换拓扑信息，以维护可用网络环境的最新视图。当需要将流量发送到位于不同节点上的pod时，Weave路由组件会自动决定是通过“快速数据路径”发送，还是回退到“sleeve”分组转发的方法。快速数据路径依靠内核的本机Open vSwitch数据路径模块，将数据包转发到适当的pod，而无需多次移入和移出用户空间。Weave路由器会更新Open vSwitch配置，以确保内核层具有有关如何路由传入数据包的准确信息。相反，当网络拓扑不适合快速数据路径路由时，sleeve模式可用作备份。它是一种较慢的封装模式，在快速数据路径缺少必要的路由信息或连接的情况下，它可以来路由数据包。当流量通过路由器时，它们会了解哪些对等体与哪些MAC地址相关联，从而允许它们以更少的跳数、更智能地路由后续流量。当网络更改导致可用路由改变时，这一相同的机制可以帮助每个节点进行自行更正。与Calico一样，Weave也为Kubernetes集群提供网络策略功能。设置Weave时，网络策略会自动安装和配置，因此除了添加网络规则之外，用户无需进行其他配置。一个其他网络方案都没有、Weave独有的功能，是对整个网络的简单加密。虽然这会增加相当多的网络开销，但Weave可以使用NaCl加密(http://nacl.cr.yp.to)来为sleeve流量自动加密所有路由流量，而对于快速数据路径流量，因为它需要加密内核中的VXLAN流量，Weave会使用IPsec ESP来加密快速数据路径流量。对于那些寻求功能丰富的网络、同时希望不要增加大量复杂性或管理难度的人来说，Weave是一个很好的选择。它设置起来相对容易，提供了许多内置和自动配置的功能，并且可以在其他解决方案可能出现故障的场景下提供智能路由。网状拓扑结构确实会限制可以合理容纳的网络的大小，不过对于大多数用户来说，这也不是一个大问题。此外，Weave也提供收费的技术支持，可以为企业用户提供故障排除等等技术服务。结语Kubernetes采用的CNI标准，让Kubernetes生态系统中的网络解决方案百花齐放。更多样的选择，意味着大多数用户将能够找到适合其当前需求和部署环境的CNI插件，同时还可以在环境发生变化时也能找到新的解决方案。不同企业之间的运营要求差异很大，因此拥有一系列具有不同复杂程度和功能丰富性的成熟解决方案，大大有助于Kubernetes在满足不同用户独特需求的前提下，仍然能够提供一致的用户体验。[scode type="blue"]本文转载于公众号“RancherLabs”[/scode]
修改配置文件编辑/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 包是对存储空间、网络带宽和时间的一种浪费。所幸的是，我们可以利用 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> </container> </configuration> <executions> <execution> <id>build-and-push-docker-image</id> <phase>package</phase> <goals> <goal>build</goal> </goals> </execution> </executions> </plugin>2.使用mvn命令打包# execute the whole build lifecycle and push the image to the registry mvn package # only create and push the image. mvn jib:build # Note that `jib:build` is daemonless and won't create the image on your machine. # It talks directly to the registry. Use `docker pull` to fetch the created image. # only create and push the image via the Docker daemon. mvn jib:dockerBuild2.使用 dive 和 docker history 显示层结构。~ ❯❯❯ docker history registry.domain.com/neptune:latest IMAGE CREATED CREATED BY SIZE COMMENT a759771eb008 49 years ago jib-maven-plugin:1.6.1 1.22MB classes <missing> 49 years ago jib-maven-plugin:1.6.1 297kB resources <missing> 49 years ago jib-maven-plugin:1.6.1 64.6MB dependencies ... <missing> 8 months ago /bin/sh -c set -ex; ... 217MB ... <missing> 8 months ago /bin/sh -c #(nop) ADD... 55.3MB 清理（可选）禁用 maven-deploy-plugin、maven-install-plugin 和 maven-jar-plugin。这些步骤不需要了，即使程序员敲入 mvn deploy 也不会执行这些步骤。<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-deploy-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-install-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <executions> <execution> <id>default-jar</id> <phase>none</phase> </execution> </executions> </plugin>如果使用了 Spring Boot，可以移除 spring-boot-maven-plugin，现在不再需要创建大型 JAR 包了。使用部署配置我们可以在 pom.xml 中配置 Jib 的 JVM 和程序参数，但通常不会这么做。相反，我们会根据部署环境（比如本地环境、QA 环境、生产环境）来配置这些参数。我们通过这种方式来配置 Spring 参数和 JVM 堆大小。JVM 参数：我们使用环境变量 JAVA_TOOL_OPTIONS 来添加 JVM 参数，比如堆大小。Spring 配置：我们将外部配置文件挂载到 Docker 容器，并将它的路径作为一个程序参数传递给命令行。当然，你也可以使用环境变量。docker run -p 1309:1309 --net=host \ -e JAVA_TOOL_OPTIONS='-Xms1000M -Xmx1000M' \ -v /home/phauer/dev/app/app-config.yml:/app-config.yml \ registry.domain.com/app:latest \ --spring.config.additional-location=/app-config.yml参考资料Introducing Jib — build Java Docker images betterjib-maven-pluginHow do I set parameters for my image at runtime?原文地址：https://phauer.com/2019/no-fat-jar-in-docker-image/