首页> 标签> Perl
"Perl"
共 15271 条结果
全部 问答 文章 公开课 课程 电子书 技术圈 体验
Kubernetes的Sidecar模式
实践apiVersion: v1 kind: Pod metadata: name: test-sidecar spec: volumes: - name: share-path emptyDir: {} containers: - name: sidecar image: busybox command: ["/bin/sh", "-c", "sleep 3600"] volumeMounts: - name: share-path mountPath: /var/share/sidecar lifecycle: postStart: exec: command: ["/bin/sh", "-c", "sleep 20"] - name: main image: busybox command: ["/bin/sh", "-c", "sleep 6666"] volumeMounts: - name: share-path mountPath: /var/share/main 创建日志在多个容器的Pod中,通常业务容器需要依赖sidecar。启动时sidecar需要先启动,退出时sidecar需要在业务容器退出后再退出。k8s目前对于sidecar的生命周期比较有争议,见issue、sidecarcontainers。Kubernetes Pod 内有两种容器: 初始化容器(init container)和应用容器(app container)。kubectl describe pod/test-sidecar -n defaultEvents: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 11m default-scheduler Successfully assigned default/test-sidecar to rds005-worker-1 Normal Pulled 11m kubelet Successfully pulled image "busybox" in 405.005566ms Normal Created 11m kubelet Created container main Normal Pulling 11m kubelet Pulling image "busybox" Normal Pulled 11m kubelet Successfully pulled image "busybox" in 140.242714ms Normal Started 10m kubelet Started container main Normal Created 74s (x2 over 11m) kubelet Created container sidecar Normal Pulling 74s (x2 over 11m) kubelet Pulling image "busybox" Normal Pulled 74s kubelet Successfully pulled image "busybox" in 385.214008ms Normal Started 73s (x2 over 11m) kubelet Started container sidecar 此方案可能存在的缺点:如果sidecar启动失败或者hook失败,其他容器会立即启动退出顺序容器启动顺序比较好解决,退出顺序则是按照相反的顺序,业务容器先退出,之后sidecar再退出。目前,在kubelet删除pod步骤如下;遍历容器,每个容器起一个goroutine删除删除时,先执行pre stop hook,得到gracePeriod=DeletionGracePeriodSeconds-period(stophook)再调用cri删除接口m.runtimeService.StopContainer(containerID.ID, gracePeriod)如果在sidecar的pre stop hook检测业务容器状态,那么可以延迟退出。apiVersion: v1 kind: Pod metadata: name: test-stop spec: containers: - name: sidecar image: busybox command: - "/bin/sh" - "-c" - | trap "touch /lifecycle/sidecar-terminated" 15 until [ -f "/lifecycle/sidecar-terminated" ];do date sleep 1 done sleep 5 cat /lifecycle/main-terminated t=$(date) echo "sidecar exit at $t" lifecycle: preStop: exec: command: - "/bin/sh" - "-c" - | until [ -f "/lifecycle/main-terminated" ];do sleep 1 done t=$(date) echo "main exit at $t" > /lifecycle/main-terminated volumeMounts: - name: lifecycle mountPath: /lifecycle - name: main image: busybox command: - "/bin/sh" - "-c" - | trap "touch /lifecycle/main-terminated" 15 until [ -f "/lifecycle/main-terminated" ];do date sleep 1 done volumeMounts: - name: lifecycle mountPath: /lifecycle volumes: - name: lifecycle emptyDir: {} 通过测试,使用postStopHook可以达到sidecar延迟退出的目的,但这种方式也有一些缺点配置复杂,多个sidecar都需要配置postStop监听业务容器状态业务容器需要有可观察性(提供特定形式的健康检测)poststop执行异常,会等到最大优雅退出时间(默认30s)后才终止总结相关命令# 进入指定的pod容器中 kubectl exec -it test-sidecar -c sidecar -- sh
文章
Kubernetes  ·  容器  ·  Perl
2022-08-08
微服务治理热门技术揭秘:无损上线
为什么有了无损下线,还需要无损上线?无损上线可以解决哪些问题?本篇文章将一一回答这些问题。无损上线功能不得不说是一个客户打磨出来的功能我们将从一次发布问题的排查与解决的过程说起。背景阿里云内部某应用中心服务在发布过程中出现了大量的5xx超时异常。初步怀疑是无损下线问题,于是很快便接入了 MSE 提供的无损下线功能。但是接入无损下线功能后继续发布,应用的发布过程依然存在大量的超时报错。根据业务方同学的反馈,大概应用在启动后 5秒左右,会有大量超时请求。无损下线功能未生效?于是拉了相关的同学开始排查。应用的调用情况如下: gateway - > A -> C 。发布的应用为 C 应用,发布过程中出现大量超时报错。我们通过相关日志与应用的启动情况,整理如下线索【服务端视角】:找了一台 C 应用的机器 xxx.xxx.xxx.60 观察 第一阶段 xxx.xxx.xxx.60 (C应用)下线阶段20:27:51 开始重启,执行重启脚本同时观察到执行了 sendReadOnlyEvent 动作,表明服务端发送只读事件,客户端不会再请求该服务端在 sendReadOnlyEvent 后,开始陆续执行注销服务动作20:27:54 注销所有 provider seivce 完成20:28:15  应用收到 kill -15 信号第二阶段 xxx.xxx.xxx.60 (C应用)上线阶段20:28:34 服务端重新启动20:29:19 在Dubbo注册中心控制台观察到 xxx.xxx.xxx.60 注册完毕20:29:19,257 日志中看到 start NettyServer 【客户端视角】:找了一台 A 应用的机器 XXX.XXX.XXX.142 观察20:27:51 received readOnly event,收到服务端发送的只读事件,此时该客户端不会请求至 XXX.XXX.XXX.60 机器20:27:56 close [xxx.xxx.xxx.142:0 -> /XXX.XXX.XXX.60:20880] ,关闭channel连接业务日志报错信息同时搜C应用的机器 XXX.XXX.XXX.60的报错相关的日志,共237条日志其中最早的time:  2020-07-30 20:29:26,524 其中最晚的 time:  2020-07-30 20:29:59,788结论观察这些迹象可以初步得出结论无损下线过程均符合预期,并且下线过程中并没有出现任何报错报错期间处于服务端应用成功启动后且注册成功后,与业务方观察的现象一致这时候怀疑是上线期间的问题,同时排查服务端相关日志,发在报错期间,服务端线程被打满问题定位为上线过程中的问题,与无损下线无关无损上线实践我们帮助用户解决问题的思路:帮助用户发现问题的本质、找到问题的通用性、解决问题、将解决通用问题的能力产品化。发现用户dubbo版本比较低,缺少自动打线程堆栈的能力通过MSE 增加Dubbo线程池满自动 JStack 能力这是每次发布必现的问题,通过观察线程池满时的 JStack 日志,有助于我们定位问题。阻塞在异步连接等资源准备上初步观察 JStack 日志,发现不少线程阻塞在 taril/druid 等异步连接资源准备上同时我们云上也有有客户遇到过,应用启动后一段时间内 Dubbo 线程池满的问题,后经过排查由于 Redis 连接池中的连接未提前建立,流量进来后大量线程阻塞在 Redis 连接建立上。连接池通过异步线程保持连接数量,默认在应用启动后 30 秒建立最小连接数的连接。解决思路提前建立连接使用服务延迟发布特性预建连接以 JedisPool 预建连接为例,提前建立Redis等连接池连接,而不是等流量进来后开始建立连接导致大量业务线程等待连接建立。org.apache.commons.pool2.impl.GenericObjectPool#startEvictor protected synchronized void startEvictor(long delay) { if(null != _evictor) { EvictionTimer.cancel(_evictor); _evictor = null; } if(delay > 0) { _evictor = new Evictor(); EvictionTimer.schedule(_evictor, delay, delay); } }JedisPool 通过定时任务去异步保证最小连接数的建立,但这会导致应用启动时,Redis连接并未建立完成。主动预建连接方式:在使用连接之前使用 GenericObjectPool#preparePool 方法去手动去准备连接。在微服务上线过程中,在初始化Redis的过程中提前去创建 min-idle 个 redis 连接,确保连接建立完成后再开始发布服务。JedisPool warm-internal-pool同样有类似问题,预建数据库连接等异步建连逻辑,保证在业务流量进来之前,异步连接资源一切就绪。延迟发布延迟发布为了一些需要异步加载的前置资源如提前准备缓存资源,异步下载资源等,需要控制服务注册时机,即控制流量进入的时机保证服务所需的前置资源准备完成该服务才可以进行发布,延迟发布有两种方式通过 delay 配置方式通过指定 delay 大小例如 300 s,Dubbo/Spring Cloud 服务将会在 Spring 容器初始化完成后进行后等待 5 分钟,再执行服务注册逻辑。online 命令上线通过打开默认不注册服务配置项,再配合发布脚本等方式执行 curl 127.0.0.1:54199/online 地址触发主动注册。我们可以在前置资源准备完成后,通过 online 命令去注册服务。也可以在 MSE 实例详情通过服务上线去注册服务。阻塞在 ASMClassLoader 类加载器上大量线程阻塞在 fastjson 的 ASMClassLoader 类加载器加载类的过程中,翻看 ClassLoader 加载类的代码其默认是同步类加载。在高并发场景下会导致大量线程阻塞在类加载上,从而影响服务端性能,造成线程池满等问题。private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; // 开启并行类加载 if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet<ProtectionDomain>()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; } } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { ...... return c; } } protected Object getClassLoadingLock(String className) { Object lock = this; //如果开启类加载器并行类加载,则锁在所加载的类上,而不是类加载器上 if (parallelLockMap != null) { Object newLock = new Object(); lock = parallelLockMap.putIfAbsent(className, newLock); if (lock == null) { lock = newLock; } } return lock; }解决思路开启类加载器并行加载类加载器开启并行类加载JDK7上,如果调用Classloader.registerAsParallelCapable方法,则会开启并行类加载功能,把锁的级别从ClassLoader对象本身,降低为要加载的类名这个级别。换句话说只要多线程加载的不是同一个类的话,loadClass方法都不会锁住。我们可以看 Classloader.registerAsParallelCapable 方法的介绍protected static boolean registerAsParallelCapable()Registers the caller as parallel capable.The registration succeeds if and only if all of the following conditions are met:1. no instance of the caller has been created2. all of the super classes (except class Object) of the caller are registered as parallel capable它要求注册该方法时,其注册的类加载器无实例并且该类加载器的继承链路上所有类加载器都调用过registerAsParallelCapable,对于低版本的Tomcat/Jetty webAppClassLoader 以及fastjson的ASMClassLoader都未开启类加载,如果应用里面有多个线程在同时调用loadClass方法进行类加载的话,那么锁的竞争将会非常激烈。MSE Agent 通过无侵入方式在类加载器被加载前开启其并行类加载的能力,无需用户升级Tomcat/Jetty,同时支持通过配置动态开启类加载并行类加载能力。http://140.205.61.252/2016/01/29/3721/其他一些问题JVM JIT编译问题引起cpu飙高日志同步打印导致线程阻塞Jetty 低版本类加载类同步加载K8s场景下,微服务与K8s Service 生命周期未对齐解决思路服务预热客户端负载均衡服务端服务分层发布业务日志异步化提供微服务 Readiness 接口业务日志异步化同步进行日志打印,由于日志打印使用的是业务线程,由于日志打印过程中存在序列化、类加载等逻辑,在高并发的场景下会导致业务线程hang住,导致服务框架线程池满等问题。MSE Agent支持动态使用异步日志打印能力,将日志打印任务与业务线程分开,提高业务线程吞吐量。小流量预热应用启动后,大量请求进入,导致应用存在许多问题,所以需要微服务的一些能力来解决服务预热问题JVM JIT编译线程占用CPU过高,CPU/load短期内飙高,Dubbo 处理请求性能下降瞬时请求量过大,导致线程阻塞在类加载、缓存等,从而导致 Dubbo 服务线程池满小流量预热,MSE 服务治理通过 OneAgent 无侵入提供了以下几种能力客户端负载均衡通过增强客户端负载均衡能力,对于刚上线的需要预热的节点进行流量权重的调整,做到刚上线的应用按照用户所配置的规则进行小流量预热,用户只需指定预热规则即可按照预期对刚上线的节点进行小流量预热业务方的一台服务端实例使用服务预热后的效果服务预热开启后,待预热的应用将在预热周期内通过小流量实现应用启动过程的预热初始化。下图预热时长为120秒,预热曲线为2次的预热效果图:说明 该测试Demo是定时伸缩模拟应用启动,因此除了预热过程,还包含应用下线的过程。下图预热时长为120秒,预热曲线为5次的预热效果图:如上图所示,相比于2次预热过程,5次预热过程刚启动的这段时间(即17:41:01~17:42:01),QPS一直保持在一个较低值,以满足需要较长时间进行预热的复杂应用的预热需求。服务端分层发布通过修改服务注册的逻辑,增加对应用load等指标的监控,对服务进行分批注册已经回滚注册等逻辑,保证服务注册过程中,流量分服务进入,系统load始终低于阈值,并且需要在指定时长内将服务注册上去。缺点:在应用的服务流量平均,不存在超热点接口的情况下,分层发布可以很好地解决服务预热问题。但是如果应用存在一些超热服务,可能这个服务几乎占所有流量90%以上,那服务端分层发布效果并不会很明显。注意:对于一些存在依赖的服务接口,服务分层发布可能需要业务梳理服务分批发布的顺序打通K8s与微服务生命周期K8S提供两种健康检查机制:livenessProbe,用于探测不健康的Pod,探测失败将会重启Pod。readinessProbe,用于探测一个Pod是否就绪接受流量,探测失败将会在Service路由上摘取该节点。如果不配置 readinessProbe ,默认只检查容器内进程是否启动运行,而对于进程的运行情况很难考量,Mse Agent 通过对外提供 readiness 接口,只有 Spring Bean 初始化完成以及异步资源准备就绪并且开始服务注册时, readiness 才返回 200。将微服务侧的服务暴露与 K8s Service 体系打通,使K8s管控能感知到进程内部的服务就绪时机,从而进行正确地服务上线。我们需要在MSE无损上线页面开启无损滚动发布的配置同时给应用配置K8s的就绪检查接口,如果您的应用在阿里云容器服务ACK上,可以在阿里云容器ACK服务对应应用配置的中健康检查区域,选中就绪检查右侧的开启,配置如下参数,然后单击更新。该应用在下次重启时,该配置即可生效。服务并行订阅与注册通过并行的服务注册与订阅,可以大幅提升应用启动的速度,解决服务启动慢的问题。以并行服务订阅为例:如上图所示,通过 Java Agent 将服务框架 refer 的流程从 SpringBean 的初始化流程中剥离出来并且通过异步线程来实现服务的并行订阅与注册。总结通过不断地观察业务情况,然后进行不断地问题分析思考与解决的尝试,直到开启了服务小流量预热能力后,彻底解决了业务团队应用在上线期间线程池满导致请求有损的问题。发布期间 Exception 总量与发布日期(包含无损上线功能陆续上线的节点)的情况如下图 9月15号发布了服务小流量预热能力后,发布期间相关 Exception 下降至 2。(经业务方确认不是因为发布引起的,可以忽略)上线了无损上线功能后,业务团队的应用中心持续多个月的发布报错问题总算告一段落,但是无损上线功能远不止于此。还解决许多云上客户上线有损的情况,功能的能力与场景也在不断地解决问题中逐渐完善与丰富。MSE 无损上线MSE 服务治理一个特点是通过 Agent 无侵入地支持市面上近五年来Dubbo、Spring Cloud所有版本,所以无损上线这个功能也会是如此,下面会以Dubbo为例子无损上线的功能,当然所有能力我们都是无缝支持 Dubbo、Spring Cloud 的。下面开始系统地介绍一下 MSE 服务治理的无损上线,我们可以先从开源的一个 Dubbo 应用上线的流程开始分析应用初始化,Spring Bean容器初始化收到 ContextRefreshedEvent后,Dubbo 会去拉取 Dubbo应用所需的配置、元数据等exportServices 注册服务开源 Dubbo 上线流程还是非常完善与严谨,但是依旧存在一些场景会导致服务上线存在问题当服务信息注册到注册中心后,在消费者看来该服务就是可以被调用的。然而,此时可能存在一些数据库、缓存资源等一些异步资源尚未加载完毕的场景,这取决于你的系统有没有对应的组件,它们何时加载完毕,也完全取决于你的业务。如果在大流量的场景下,服务在注册到注册中心后,马上有大流量进入,存在一系列问题,导致线程阻塞,对业务流量造成损失比如Redis的JedisPool连接池创建后并不会立即建立连接,会在流量进来后开始建立连接,如果一开始涌进的是大流量,则导致大量线程阻塞在连接池重的连接的建立上FastJson 以及 Jetty/tomcat等低版本中,并未开启类加载器并行类加载能力,导致大量线程阻塞在类加载器加载类上JVM JIT 编译问题引起cpu飙高线程阻塞在业务日志上云原生场景下,微服务与K8s的生命周期未对齐的情况滚动发布,重启的pod还未注册至注册中心,但是readiness检查以及通过。导致第一个pod还未注册至注册中心,最后一个pod以及下线,导致短时间内的客户端NoProvider异常针对如上问题,MSE 服务治理不仅提供了完整的解决方案,还提供了白屏化开箱即用的能力,动态配置实时生效。同时 MSE 服务治理针对无损上下线的场景还提供了完整的可观测能力。无损上线功能可以总结为以下这张图不只是无损上下线无损上下线能力是微服务流量治理中的重要的一环,当然除了无损下线,MSE还提供了全链路灰度、流控降级与容错、数据库治理等一系列的微服务治理能力。服务治理是微服务改造深入到一定阶段之后的必经之路,在这个过程中我们不断有新的问题出现。除了无损上下线,服务治理还有没其他能力?服务治理能力有没一个标准的定义,服务治理能力包含哪些?多语言场景下,有无全链路的最佳实践或者标准?异构微服务如何可以统一治理?当我们在探索服务治理的过程中,我们在对接其他微服务的时候,我们发现治理体系不同造成的困扰是巨大的,打通两套甚者是多套治理体系的成本也是巨大的。为此我们提出了 OpenSergo 项目。OpenSergo 要解决的是不同框架、不同语言在微服务治理上的概念碎片化、无法互通的问题。OpenSergo 社区也在联合各个社区进行进一步的合作,社区来一起讨论与定义统一的服务治理标准。当前社区也在联合 bilibili、字节跳动等企业一起共建标准,也欢迎感兴趣的开发者、社区与企业一起加入到 OpenSergo 服务治理标准共建中。欢迎大家加入 OpenSergo 社区交流群(钉钉群)进行讨论:34826335
文章
Kubernetes  ·  Dubbo  ·  NoSQL  ·  Java  ·  应用服务中间件  ·  Redis  ·  微服务  ·  容器  ·  Spring  ·  Perl
2022-08-08
虚函数和纯虚函数的区别,再不学就被卷没了
🎈虚函数(impure virtual)  C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现。  子类可以重写父类的虚函数实现子类的特殊化。  如下就是一个父类中的虚函数:class A { public: virtual void ss() { cout<<"我是基类的虚函数"<<endl; } };🎈纯虚函数(pure virtual)  C++中包含纯虚函数的类,被称为是“抽象类”。抽象类不能使用new出对象,只有实现了这个纯虚函数的子类才能new出对象。  C++中的纯虚函数更像是“只提供申明,没有实现”,是对子类的约束,是“接口继承”。  C++中的纯虚函数也是一种“运行时多态”。  如下面的类包含纯虚函数,就是“抽象类”:class A { public: virtual void out1(string s)=0; //我是基类的虚函数 };🎈举个栗子#include<iostream> #include<string> #include<cstring> #include<cstdlib> #include<algorithm> using namespace std; class a { private: public: a(){ //构造函数用内联函数的形式 } //虚函数 virtual void xhs(){ //这个虚函数必须得在基类中实现 cout<<"我是基类的虚函数"<<endl;//即使是空的虚函数也要在基类中实现 } //派生类中可以不写这个函数,但是派生类对象调用时会调用积累的虚函数 //纯虚函数 virtual void cxhs() =0; //这个纯虚函数不在基类中实现,必须在子类中实现 }; class b:public a { private: public: void xhs(){ //这个是可有可无的 cout<<"我是派生类覆盖基类虚函数的函数"<<endl; } //* //* void cxhs(){ //这个是必须有实现的 cout<<"我是派生类覆盖基类虚函数的函数"<<endl; } //* //* }; //* //* int main() //* //* { //* //* b c; //* //* c.xhs(); //* //调用派生类的 c.cxhs();//调用派生类的 }
文章
C++  ·  Perl
2022-08-08
K8S面试题
1、 k8s是什么?请说出你的了解?答:Kubenetes是一个针对容器应用,进行自动部署,弹性伸缩和管理的开源系统。主要功能是生产环境中的容器编排。K8S是Google公司推出的,它来源于由Google公司内部使用了15年的Borg系统,集结了Borg的精华。2、 K8s架构的组成是什么?答:和大多数分布式系统一样,K8S集群至少需要一个主节点(Master)和多个计算节点(Node)。主节点主要用于暴露API,调度部署和节点的管理;计算节点运行一个容器运行环境,一般是docker环境(类似docker环境的还有rkt),同时运行一个K8s的代理(kubelet)用于和master通信。计算节点也会运行一些额外的组件,像记录日志,节点监控,服务发现等等。计算节点是k8s集群中真正工作的节点。K8S架构细分:1、Master节点(默认不参加实际工作):Kubectl:客户端命令行工具,作为整个K8s集群的操作入口;Api Server:在K8s架构中承担的是“桥梁”的角色,作为资源操作的唯一入口,它提供了认证、授权、访问控制、API注册和发现等机制。客户端与k8s群集及K8s内部组件的通信,都要通过Api Server这个组件;Controller-manager:负责维护群集的状态,比如故障检测、自动扩展、滚动更新等;Scheduler:负责资源的调度,按照预定的调度策略将pod调度到相应的node节点上;Etcd:担任数据中心的角色,保存了整个群集的状态;2、Node节点:Kubelet:负责维护容器的生命周期,同时也负责Volume和网络的管理,一般运行在所有的节点,是Node节点的代理,当Scheduler确定某个node上运行pod之后,会将pod的具体信息(image,volume)等发送给该节点的kubelet,kubelet根据这些信息创建和运行容器,并向master返回运行状态。(自动修复功能:如果某个节点中的容器宕机,它会尝试重启该容器,若重启无效,则会将该pod杀死,然后重新创建一个容器);Kube-proxy:Service在逻辑上代表了后端的多个pod。负责为Service提供cluster内部的服务发现和负载均衡(外界通过Service访问pod提供的服务时,Service接收到的请求后就是通过kube-proxy来转发到pod上的);container-runtime:是负责管理运行容器的软件,比如dockerPod:是k8s集群里面最小的单位。每个pod里边可以运行一个或多个container(容器),如果一个pod中有两个container,那么container的USR(用户)、MNT(挂载点)、PID(进程号)是相互隔离的,UTS(主机名和域名)、IPC(消息队列)、NET(网络栈)是相互共享的。我比较喜欢把pod来当做豌豆夹,而豌豆就是pod中的container;3、 容器和主机部署应用的区别是什么?答:容器的中心思想就是秒级启动;一次封装、到处运行;这是主机部署应用无法达到的效果,但同时也更应该注重容器的数据持久化问题。另外,容器部署可以将各个服务进行隔离,互不影响,这也是容器的另一个核心概念。4、请你说一下kubenetes针对pod资源对象的健康监测机制?答:K8s中对于pod资源对象的健康状态检测,提供了三类probe(探针)来执行对pod的健康监测:1) livenessProbe探针可以根据用户自定义规则来判定pod是否健康,如果livenessProbe探针探测到容器不健康,则kubelet会根据其重启策略来决定是否重启,如果一个容器不包含livenessProbe探针,则kubelet会认为容器的livenessProbe探针的返回值永远成功。2) ReadinessProbe探针同样是可以根据用户自定义规则来判断pod是否健康,如果探测失败,控制器会将此pod从对应service的endpoint列表中移除,从此不再将任何请求调度到此Pod上,直到下次探测成功。3) startupProbe探针启动检查机制,应用一些启动缓慢的业务,避免业务长时间启动而被上面两类探针kill掉,这个问题也可以换另一种方式解决,就是定义上面两类探针机制时,初始化时间定义的长一些即可。每种探测方法能支持以下几个相同的检查参数,用于设置控制检查时间:initialDelaySeconds:初始第一次探测间隔,用于应用启动的时间,防止应用还没启动而健康检查失败periodSeconds:检查间隔,多久执行probe检查,默认为10s;timeoutSeconds:检查超时时长,探测应用timeout后为失败;successThreshold:成功探测阈值,表示探测多少次为健康正常,默认探测1次。上面两种探针都支持以下三种探测方法:1)Exec: 通过执行命令的方式来检查服务是否正常,比如使用cat命令查看pod中的某个重要配置文件是否存在,若存在,则表示pod健康。反之异常。Exec探测方式的yaml文件语法如下:spec: containers: - name: liveness image: k8s.gcr.io/busybox args: - /bin/sh - -c - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600 livenessProbe: #选择livenessProbe的探测机制 exec: #执行以下命令 command: - cat - /tmp/healthy initialDelaySeconds: 5 #在容器运行五秒后开始探测 periodSeconds: 5 #每次探测的时间间隔为5秒在上面的配置文件中,探测机制为在容器运行5秒后,每隔五秒探测一次,如果cat命令返回的值为“0”,则表示健康,如果为非0,则表示异常。2)Httpget: 通过发送http/htps请求检查服务是否正常,返回的状态码为200-399则表示容器健康(注http get类似于命令curl -I)。Httpget探测方式的yaml文件语法如下:spec: containers: - name: liveness image: k8s.gcr.io/liveness livenessProbe: #采用livenessProbe机制探测 httpGet: #采用httpget的方式 scheme:HTTP #指定协议,也支持https path: /healthz #检测是否可以访问到网页根目录下的healthz网页文件 port: 8080 #监听端口是8080 initialDelaySeconds: 3 #容器运行3秒后开始探测 periodSeconds: 3 #探测频率为3秒上述配置文件中,探测方式为项容器发送HTTP GET请求,请求的是8080端口下的healthz文件,返回任何大于或等于200且小于400的状态码表示成功。任何其他代码表示异常。3)tcpSocket: 通过容器的IP和Port执行TCP检查,如果能够建立TCP连接,则表明容器健康,这种方式与HTTPget的探测机制有些类似,tcpsocket健康检查适用于TCP业务。tcpSocket探测方式的yaml文件语法如下:spec: containers: - name: goproxy image: k8s.gcr.io/goproxy:0.1 ports: - containerPort: 8080 #这里两种探测机制都用上了,都是为了和容器的8080端口建立TCP连接 readinessProbe: tcpSocket: port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: tcpSocket: port: 8080 initialDelaySeconds: 15 periodSeconds: 20在上述的yaml配置文件中,两类探针都使用了,在容器启动5秒后,kubelet将发送第一个readinessProbe探针,这将连接容器的8080端口,如果探测成功,则该pod为健康,十秒后,kubelet将进行第二次连接。除了readinessProbe探针外,在容器启动15秒后,kubelet将发送第一个livenessProbe探针,仍然尝试连接容器的8080端口,如果连接失败,则重启容器。探针探测的结果无外乎以下三者之一:Success:Container通过了检查;Failure:Container没有通过检查;Unknown:没有执行检查,因此不采取任何措施(通常是我们没有定义探针检测,默认为成功)。若觉得上面还不够透彻,可以移步其官网文档:https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/5、 如何控制滚动更新过程?答:可以通过下面的命令查看到更新时可以控制的参数:[root@master yaml]# kubectl explain deploy.spec.strategy.rollingUpdatemaxSurge: 此参数控制滚动更新过程,副本总数超过预期pod数量的上限。可以是百分比,也可以是具体的值。默认为1。(上述参数的作用就是在更新过程中,值若为3,那么不管三七二一,先运行三个pod,用于替换旧的pod,以此类推)maxUnavailable: 此参数控制滚动更新过程中,不可用的Pod的数量。(这个值和上面的值没有任何关系,举个例子:我有十个pod,但是在更新的过程中,我允许这十个pod中最多有三个不可用,那么就将这个参数的值设置为3,在更新的过程中,只要不可用的pod数量小于或等于3,那么更新过程就不会停止)。推荐:250期面试题精选6、K8s中镜像的下载策略是什么?答:可通过命令“kubectl explain pod.spec.containers”来查看imagePullPolicy这行的解释。K8s的镜像下载策略有三种:Always、Never、IFNotPresent;Always:镜像标签为latest时,总是从指定的仓库中获取镜像;Never:禁止从仓库中下载镜像,也就是说只能使用本地镜像;IfNotPresent:仅当本地没有对应镜像时,才从目标仓库中下载。默认的镜像下载策略是:当镜像标签是latest时,默认策略是Always;当镜像标签是自定义时(也就是标签不是latest),那么默认策略是IfNotPresent。7、 image的状态有哪些?Running:Pod所需的容器已经被成功调度到某个节点,且已经成功运行,Pending:APIserver创建了pod资源对象,并且已经存入etcd中,但它尚未被调度完成或者仍然处于仓库中下载镜像的过程Unknown:APIserver无法正常获取到pod对象的状态,通常是其无法与所在工作节点的kubelet通信所致。8、 pod的重启策略是什么?答:可以通过命令“kubectl explain pod.spec”查看pod的重启策略。(restartPolicy字段)Always:但凡pod对象终止就重启,此为默认策略。OnFailure:仅在pod对象出现错误时才重启9、 Service这种资源对象的作用是什么?答:用来给相同的多个pod对象提供一个固定的统一访问接口,常用于服务发现和服务访问。10、版本回滚相关的命令?[root@master httpd-web]# kubectl apply -f httpd2-deploy1.yaml --record #运行yaml文件,并记录版本信息; [root@master httpd-web]# kubectl rollout history deployment httpd-devploy1 #查看该deployment的历史版本 [root@master httpd-web]# kubectl rollout undo deployment httpd-devploy1 --to-revision=1 #执行回滚操作,指定回滚到版本1 #在yaml文件的spec字段中,可以写以下选项(用于限制最多记录多少个历史版本): spec: revisionHistoryLimit: 5 #这个字段通过 kubectl explain deploy.spec 命令找到revisionHistoryLimit <integer>行获得11、 标签与标签选择器的作用是什么?标签:是当相同类型的资源对象越来越多的时候,为了更好的管理,可以按照标签将其分为一个组,为的是提升资源对象的管理效率。标签选择器:就是标签的查询过滤条件。目前API支持两种标签选择器:基于等值关系的,如:“=”、“”“==”、“!=”(注:“==”也是等于的意思,yaml文件中的matchLabels字段);基于集合的,如:in、notin、exists(yaml文件中的matchExpressions字段);注:in:在这个集合中;notin:不在这个集合中;exists:要么全在(exists)这个集合中,要么都不在(notexists);使用标签选择器的操作逻辑:在使用基于集合的标签选择器同时指定多个选择器之间的逻辑关系为“与”操作(比如:- {key: name,operator: In,values: [zhangsan,lisi]} ,那么只要拥有这两个值的资源,都会被选中);使用空值的标签选择器,意味着每个资源对象都被选中(如:标签选择器的键是“A”,两个资源对象同时拥有A这个键,但是值不一样,这种情况下,如果使用空值的标签选择器,那么将同时选中这两个资源对象)空的标签选择器(注意不是上面说的空值,而是空的,都没有定义键的名称),将无法选择出任何资源;在基于集合的选择器中,使用“In”或者“Notin”操作时,其values可以为空,但是如果为空,这个标签选择器,就没有任何意义了。两种标签选择器类型(基于等值、基于集合的书写方法):selector: matchLabels: #基于等值 app: nginx matchExpressions: #基于集合 - {key: name,operator: In,values: [zhangsan,lisi]} #key、operator、values这三个字段是固定的 - {key: age,operator: Exists,values:} #如果指定为exists,那么values的值一定要为空12、 常用的标签分类有哪些?标签分类是可以自定义的,但是为了能使他人可以达到一目了然的效果,一般会使用以下一些分类:版本类标签(release):stable(稳定版)、canary(金丝雀版本,可以将其称之为测试版中的测试版)、beta(测试版);环境类标签(environment):dev(开发)、qa(测试)、production(生产)、op(运维);应用类(app):ui、as、pc、sc;架构类(tier):frontend(前端)、backend(后端)、cache(缓存);分区标签(partition):customerA(客户A)、customerB(客户B);品控级别(Track):daily(每天)、weekly(每周)。13、 有几种查看标签的方式?答:常用的有以下三种查看方式:[root@master ~]# kubectl get pod --show-labels #查看pod,并且显示标签内容 [root@master ~]# kubectl get pod -L env,tier #显示资源对象标签的值 [root@master ~]# kubectl get pod -l env,tier #只显示符合键值资源对象的pod,而“-L”是显示所有的pod14、 添加、修改、删除标签的命令?#对pod标签的操作 [root@master ~]# kubectl label pod label-pod abc=123 #给名为label-pod的pod添加标签 [root@master ~]# kubectl label pod label-pod abc=456 --overwrite #修改名为label-pod的标签 [root@master ~]# kubectl label pod label-pod abc- #删除名为label-pod的标签 [root@master ~]# kubectl get pod --show-labels #对node节点的标签操作 [root@master ~]# kubectl label nodes node01 disk=ssd #给节点node01添加disk标签 [root@master ~]# kubectl label nodes node01 disk=sss –overwrite #修改节点node01的标签 [root@master ~]# kubectl label nodes node01 disk- #删除节点node01的disk标签15、 DaemonSet资源对象的特性?DaemonSet这种资源对象会在每个k8s集群中的节点上运行,并且每个节点只能运行一个pod,这是它和deployment资源对象的最大也是唯一的区别。所以,在其yaml文件中,不支持定义replicas,除此之外,与Deployment、RS等资源对象的写法相同。它的一般使用场景如下:在去做每个节点的日志收集工作;监控每个节点的的运行状态;16、 说说你对Job这种资源对象的了解?答:Job与其他服务类容器不同,Job是一种工作类容器(一般用于做一次性任务)。使用常见不多,可以忽略这个问题。#提高Job执行效率的方法: spec: parallelism: 2 #一次运行2个 completions: 8 #最多运行8个 template: metadata:17、描述一下pod的生命周期有哪些状态?Pending:表示pod已经被同意创建,正在等待kube-scheduler选择合适的节点创建,一般是在准备镜像;Running:表示pod中所有的容器已经被创建,并且至少有一个容器正在运行或者是正在启动或者是正在重启;Succeeded:表示所有容器已经成功终止,并且不会再启动;Failed:表示pod中所有容器都是非0(不正常)状态退出;Unknown:表示无法读取Pod状态,通常是kube-controller-manager无法与Pod通信。18、 创建一个pod的流程是什么?客户端提交Pod的配置信息(可以是yaml文件定义好的信息)到kube-apiserver;Apiserver收到指令后,通知给controller-manager创建一个资源对象;Controller-manager通过api-server将pod的配置信息存储到ETCD数据中心中;Kube-scheduler检测到pod信息会开始调度预选,会先过滤掉不符合Pod资源配置要求的节点,然后开始调度调优,主要是挑选出更适合运行pod的节点,然后将pod的资源配置单发送到node节点上的kubelet组件上。Kubelet根据scheduler发来的资源配置单运行pod,运行成功后,将pod的运行信息返回给scheduler,scheduler将返回的pod运行状况的信息存储到etcd数据中心。19、 删除一个Pod会发生什么事情?答:Kube-apiserver会接受到用户的删除指令,默认有30秒时间等待优雅退出,超过30秒会被标记为死亡状态,此时Pod的状态Terminating,kubelet看到pod标记为Terminating就开始了关闭Pod的工作;关闭流程如下:pod从service的endpoint列表中被移除;如果该pod定义了一个停止前的钩子,其会在pod内部被调用,停止钩子一般定义了如何优雅的结束进程;进程被发送TERM信号(kill -14)当超过优雅退出的时间后,Pod中的所有进程都会被发送SIGKILL信号(kill -9)。20、 K8s的Service是什么?答:Pod每次重启或者重新部署,其IP地址都会产生变化,这使得pod间通信和pod与外部通信变得困难,这时候,就需要Service为pod提供一个固定的入口。Service的Endpoint列表通常绑定了一组相同配置的pod,通过负载均衡的方式把外界请求分配到多个pod上21、 k8s是怎么进行服务注册的?答:Pod启动后会加载当前环境所有Service信息,以便不同Pod根据Service名进行通信。22、 k8s集群外流量怎么访问Pod?答:可以通过Service的NodePort方式访问,会在所有节点监听同一个端口,比如:30000,访问节点的流量会被重定向到对应的Service上面。23、 k8s数据持久化的方式有哪些?答:1)EmptyDir(空目录):没有指定要挂载宿主机上的某个目录,直接由Pod内保部映射到宿主机上。类似于docker中的manager volume。主要使用场景:只需要临时将数据保存在磁盘上,比如在合并/排序算法中;作为两个容器的共享存储,使得第一个内容管理的容器可以将生成的数据存入其中,同时由同一个webserver容器对外提供这些页面。emptyDir的特性:同个pod里面的不同容器,共享同一个持久化目录,当pod节点删除时,volume的数据也会被删除。如果仅仅是容器被销毁,pod还在,则不会影响volume中的数据。总结来说:emptyDir的数据持久化的生命周期和使用的pod一致。一般是作为临时存储使用。2)Hostpath:将宿主机上已存在的目录或文件挂载到容器内部。类似于docker中的bind mount挂载方式。这种数据持久化方式,运用场景不多,因为它增加了pod与节点之间的耦合。一般对于k8s集群本身的数据持久化和docker本身的数据持久化会使用这种方式,可以自行参考apiService的yaml文件,位于:/etc/kubernetes/main…目录下。3)PersistentVolume(简称PV):基于NFS服务的PV,也可以基于GFS的PV。它的作用是统一数据持久化目录,方便管理。在一个PV的yaml文件中,可以对其配置PV的大小,指定PV的访问模式:ReadWriteOnce:只能以读写的方式挂载到单个节点;ReadOnlyMany:能以只读的方式挂载到多个节点;ReadWriteMany:能以读写的方式挂载到多个节点。以及指定pv的回收策略:recycle:清除PV的数据,然后自动回收;Retain:需要手动回收;delete:删除云存储资源,云存储专用;PS:这里的回收策略指的是在PV被删除后,在这个PV下所存储的源文件是否删除)。若需使用PV,那么还有一个重要的概念:PVC,PVC是向PV申请应用所需的容量大小,K8s集群中可能会有多个PV,PVC和PV若要关联,其定义的访问模式必须一致。定义的storageClassName也必须一致,若群集中存在相同的(名字、访问模式都一致)两个PV,那么PVC会选择向它所需容量接近的PV去申请,或者随机申请。
文章
存储  ·  Kubernetes  ·  负载均衡  ·  网络协议  ·  API  ·  调度  ·  数据中心  ·  Docker  ·  容器  ·  Perl
2022-08-08
K8S面试题
1、 k8s是什么?请说出你的了解?答:Kubenetes是一个针对容器应用,进行自动部署,弹性伸缩和管理的开源系统。主要功能是生产环境中的容器编排。K8S是Google公司推出的,它来源于由Google公司内部使用了15年的Borg系统,集结了Borg的精华。2、 K8s架构的组成是什么?答:和大多数分布式系统一样,K8S集群至少需要一个主节点(Master)和多个计算节点(Node)。主节点主要用于暴露API,调度部署和节点的管理;计算节点运行一个容器运行环境,一般是docker环境(类似docker环境的还有rkt),同时运行一个K8s的代理(kubelet)用于和master通信。计算节点也会运行一些额外的组件,像记录日志,节点监控,服务发现等等。计算节点是k8s集群中真正工作的节点。K8S架构细分:1、Master节点(默认不参加实际工作):Kubectl:客户端命令行工具,作为整个K8s集群的操作入口;Api Server:在K8s架构中承担的是“桥梁”的角色,作为资源操作的唯一入口,它提供了认证、授权、访问控制、API注册和发现等机制。客户端与k8s群集及K8s内部组件的通信,都要通过Api Server这个组件;Controller-manager:负责维护群集的状态,比如故障检测、自动扩展、滚动更新等;Scheduler:负责资源的调度,按照预定的调度策略将pod调度到相应的node节点上;Etcd:担任数据中心的角色,保存了整个群集的状态;2、Node节点:Kubelet:负责维护容器的生命周期,同时也负责Volume和网络的管理,一般运行在所有的节点,是Node节点的代理,当Scheduler确定某个node上运行pod之后,会将pod的具体信息(image,volume)等发送给该节点的kubelet,kubelet根据这些信息创建和运行容器,并向master返回运行状态。(自动修复功能:如果某个节点中的容器宕机,它会尝试重启该容器,若重启无效,则会将该pod杀死,然后重新创建一个容器);Kube-proxy:Service在逻辑上代表了后端的多个pod。负责为Service提供cluster内部的服务发现和负载均衡(外界通过Service访问pod提供的服务时,Service接收到的请求后就是通过kube-proxy来转发到pod上的);container-runtime:是负责管理运行容器的软件,比如dockerPod:是k8s集群里面最小的单位。每个pod里边可以运行一个或多个container(容器),如果一个pod中有两个container,那么container的USR(用户)、MNT(挂载点)、PID(进程号)是相互隔离的,UTS(主机名和域名)、IPC(消息队列)、NET(网络栈)是相互共享的。我比较喜欢把pod来当做豌豆夹,而豌豆就是pod中的container;3、 容器和主机部署应用的区别是什么?答:容器的中心思想就是秒级启动;一次封装、到处运行;这是主机部署应用无法达到的效果,但同时也更应该注重容器的数据持久化问题。另外,容器部署可以将各个服务进行隔离,互不影响,这也是容器的另一个核心概念。4、请你说一下kubenetes针对pod资源对象的健康监测机制?答:K8s中对于pod资源对象的健康状态检测,提供了三类probe(探针)来执行对pod的健康监测:1) livenessProbe探针可以根据用户自定义规则来判定pod是否健康,如果livenessProbe探针探测到容器不健康,则kubelet会根据其重启策略来决定是否重启,如果一个容器不包含livenessProbe探针,则kubelet会认为容器的livenessProbe探针的返回值永远成功。2) ReadinessProbe探针同样是可以根据用户自定义规则来判断pod是否健康,如果探测失败,控制器会将此pod从对应service的endpoint列表中移除,从此不再将任何请求调度到此Pod上,直到下次探测成功。3) startupProbe探针启动检查机制,应用一些启动缓慢的业务,避免业务长时间启动而被上面两类探针kill掉,这个问题也可以换另一种方式解决,就是定义上面两类探针机制时,初始化时间定义的长一些即可。每种探测方法能支持以下几个相同的检查参数,用于设置控制检查时间:initialDelaySeconds:初始第一次探测间隔,用于应用启动的时间,防止应用还没启动而健康检查失败periodSeconds:检查间隔,多久执行probe检查,默认为10s;timeoutSeconds:检查超时时长,探测应用timeout后为失败;successThreshold:成功探测阈值,表示探测多少次为健康正常,默认探测1次。上面两种探针都支持以下三种探测方法:1)Exec: 通过执行命令的方式来检查服务是否正常,比如使用cat命令查看pod中的某个重要配置文件是否存在,若存在,则表示pod健康。反之异常。Exec探测方式的yaml文件语法如下:spec: containers: - name: liveness image: k8s.gcr.io/busybox args: - /bin/sh - -c - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600 livenessProbe: #选择livenessProbe的探测机制 exec: #执行以下命令 command: - cat - /tmp/healthy initialDelaySeconds: 5 #在容器运行五秒后开始探测 periodSeconds: 5 #每次探测的时间间隔为5秒在上面的配置文件中,探测机制为在容器运行5秒后,每隔五秒探测一次,如果cat命令返回的值为“0”,则表示健康,如果为非0,则表示异常。2)Httpget: 通过发送http/htps请求检查服务是否正常,返回的状态码为200-399则表示容器健康(注http get类似于命令curl -I)。Httpget探测方式的yaml文件语法如下:spec: containers: - name: liveness image: k8s.gcr.io/liveness livenessProbe: #采用livenessProbe机制探测 httpGet: #采用httpget的方式 scheme:HTTP #指定协议,也支持https path: /healthz #检测是否可以访问到网页根目录下的healthz网页文件 port: 8080 #监听端口是8080 initialDelaySeconds: 3 #容器运行3秒后开始探测 periodSeconds: 3 #探测频率为3秒上述配置文件中,探测方式为项容器发送HTTP GET请求,请求的是8080端口下的healthz文件,返回任何大于或等于200且小于400的状态码表示成功。任何其他代码表示异常。3)tcpSocket: 通过容器的IP和Port执行TCP检查,如果能够建立TCP连接,则表明容器健康,这种方式与HTTPget的探测机制有些类似,tcpsocket健康检查适用于TCP业务。tcpSocket探测方式的yaml文件语法如下:spec: containers: - name: goproxy image: k8s.gcr.io/goproxy:0.1 ports: - containerPort: 8080 #这里两种探测机制都用上了,都是为了和容器的8080端口建立TCP连接 readinessProbe: tcpSocket: port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: tcpSocket: port: 8080 initialDelaySeconds: 15 periodSeconds: 20在上述的yaml配置文件中,两类探针都使用了,在容器启动5秒后,kubelet将发送第一个readinessProbe探针,这将连接容器的8080端口,如果探测成功,则该pod为健康,十秒后,kubelet将进行第二次连接。除了readinessProbe探针外,在容器启动15秒后,kubelet将发送第一个livenessProbe探针,仍然尝试连接容器的8080端口,如果连接失败,则重启容器。探针探测的结果无外乎以下三者之一:Success:Container通过了检查;Failure:Container没有通过检查;Unknown:没有执行检查,因此不采取任何措施(通常是我们没有定义探针检测,默认为成功)。若觉得上面还不够透彻,可以移步其官网文档:https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/5、 如何控制滚动更新过程?答:可以通过下面的命令查看到更新时可以控制的参数:[root@master yaml]# kubectl explain deploy.spec.strategy.rollingUpdatemaxSurge: 此参数控制滚动更新过程,副本总数超过预期pod数量的上限。可以是百分比,也可以是具体的值。默认为1。(上述参数的作用就是在更新过程中,值若为3,那么不管三七二一,先运行三个pod,用于替换旧的pod,以此类推)maxUnavailable: 此参数控制滚动更新过程中,不可用的Pod的数量。(这个值和上面的值没有任何关系,举个例子:我有十个pod,但是在更新的过程中,我允许这十个pod中最多有三个不可用,那么就将这个参数的值设置为3,在更新的过程中,只要不可用的pod数量小于或等于3,那么更新过程就不会停止)。推荐:250期面试题精选6、K8s中镜像的下载策略是什么?答:可通过命令“kubectl explain pod.spec.containers”来查看imagePullPolicy这行的解释。K8s的镜像下载策略有三种:Always、Never、IFNotPresent;Always:镜像标签为latest时,总是从指定的仓库中获取镜像;Never:禁止从仓库中下载镜像,也就是说只能使用本地镜像;IfNotPresent:仅当本地没有对应镜像时,才从目标仓库中下载。默认的镜像下载策略是:当镜像标签是latest时,默认策略是Always;当镜像标签是自定义时(也就是标签不是latest),那么默认策略是IfNotPresent。7、 image的状态有哪些?Running:Pod所需的容器已经被成功调度到某个节点,且已经成功运行,Pending:APIserver创建了pod资源对象,并且已经存入etcd中,但它尚未被调度完成或者仍然处于仓库中下载镜像的过程Unknown:APIserver无法正常获取到pod对象的状态,通常是其无法与所在工作节点的kubelet通信所致。8、 pod的重启策略是什么?答:可以通过命令“kubectl explain pod.spec”查看pod的重启策略。(restartPolicy字段)Always:但凡pod对象终止就重启,此为默认策略。OnFailure:仅在pod对象出现错误时才重启9、 Service这种资源对象的作用是什么?答:用来给相同的多个pod对象提供一个固定的统一访问接口,常用于服务发现和服务访问。10、版本回滚相关的命令?[root@master httpd-web]# kubectl apply -f httpd2-deploy1.yaml --record #运行yaml文件,并记录版本信息; [root@master httpd-web]# kubectl rollout history deployment httpd-devploy1 #查看该deployment的历史版本 [root@master httpd-web]# kubectl rollout undo deployment httpd-devploy1 --to-revision=1 #执行回滚操作,指定回滚到版本1 #在yaml文件的spec字段中,可以写以下选项(用于限制最多记录多少个历史版本): spec: revisionHistoryLimit: 5 #这个字段通过 kubectl explain deploy.spec 命令找到revisionHistoryLimit <integer>行获得11、 标签与标签选择器的作用是什么?标签:是当相同类型的资源对象越来越多的时候,为了更好的管理,可以按照标签将其分为一个组,为的是提升资源对象的管理效率。标签选择器:就是标签的查询过滤条件。目前API支持两种标签选择器:基于等值关系的,如:“=”、“”“==”、“!=”(注:“==”也是等于的意思,yaml文件中的matchLabels字段);基于集合的,如:in、notin、exists(yaml文件中的matchExpressions字段);注:in:在这个集合中;notin:不在这个集合中;exists:要么全在(exists)这个集合中,要么都不在(notexists);使用标签选择器的操作逻辑:在使用基于集合的标签选择器同时指定多个选择器之间的逻辑关系为“与”操作(比如:- {key: name,operator: In,values: [zhangsan,lisi]} ,那么只要拥有这两个值的资源,都会被选中);使用空值的标签选择器,意味着每个资源对象都被选中(如:标签选择器的键是“A”,两个资源对象同时拥有A这个键,但是值不一样,这种情况下,如果使用空值的标签选择器,那么将同时选中这两个资源对象)空的标签选择器(注意不是上面说的空值,而是空的,都没有定义键的名称),将无法选择出任何资源;在基于集合的选择器中,使用“In”或者“Notin”操作时,其values可以为空,但是如果为空,这个标签选择器,就没有任何意义了。两种标签选择器类型(基于等值、基于集合的书写方法):selector: matchLabels: #基于等值 app: nginx matchExpressions: #基于集合 - {key: name,operator: In,values: [zhangsan,lisi]} #key、operator、values这三个字段是固定的 - {key: age,operator: Exists,values:} #如果指定为exists,那么values的值一定要为空12、 常用的标签分类有哪些?标签分类是可以自定义的,但是为了能使他人可以达到一目了然的效果,一般会使用以下一些分类:版本类标签(release):stable(稳定版)、canary(金丝雀版本,可以将其称之为测试版中的测试版)、beta(测试版);环境类标签(environment):dev(开发)、qa(测试)、production(生产)、op(运维);应用类(app):ui、as、pc、sc;架构类(tier):frontend(前端)、backend(后端)、cache(缓存);分区标签(partition):customerA(客户A)、customerB(客户B);品控级别(Track):daily(每天)、weekly(每周)。13、 有几种查看标签的方式?答:常用的有以下三种查看方式:[root@master ~]# kubectl get pod --show-labels #查看pod,并且显示标签内容 [root@master ~]# kubectl get pod -L env,tier #显示资源对象标签的值 [root@master ~]# kubectl get pod -l env,tier #只显示符合键值资源对象的pod,而“-L”是显示所有的pod14、 添加、修改、删除标签的命令?#对pod标签的操作 [root@master ~]# kubectl label pod label-pod abc=123 #给名为label-pod的pod添加标签 [root@master ~]# kubectl label pod label-pod abc=456 --overwrite #修改名为label-pod的标签 [root@master ~]# kubectl label pod label-pod abc- #删除名为label-pod的标签 [root@master ~]# kubectl get pod --show-labels #对node节点的标签操作 [root@master ~]# kubectl label nodes node01 disk=ssd #给节点node01添加disk标签 [root@master ~]# kubectl label nodes node01 disk=sss –overwrite #修改节点node01的标签 [root@master ~]# kubectl label nodes node01 disk- #删除节点node01的disk标签15、 DaemonSet资源对象的特性?DaemonSet这种资源对象会在每个k8s集群中的节点上运行,并且每个节点只能运行一个pod,这是它和deployment资源对象的最大也是唯一的区别。所以,在其yaml文件中,不支持定义replicas,除此之外,与Deployment、RS等资源对象的写法相同。它的一般使用场景如下:在去做每个节点的日志收集工作;监控每个节点的的运行状态;16、 说说你对Job这种资源对象的了解?答:Job与其他服务类容器不同,Job是一种工作类容器(一般用于做一次性任务)。使用常见不多,可以忽略这个问题。#提高Job执行效率的方法: spec: parallelism: 2 #一次运行2个 completions: 8 #最多运行8个 template: metadata:17、描述一下pod的生命周期有哪些状态?Pending:表示pod已经被同意创建,正在等待kube-scheduler选择合适的节点创建,一般是在准备镜像;Running:表示pod中所有的容器已经被创建,并且至少有一个容器正在运行或者是正在启动或者是正在重启;Succeeded:表示所有容器已经成功终止,并且不会再启动;Failed:表示pod中所有容器都是非0(不正常)状态退出;Unknown:表示无法读取Pod状态,通常是kube-controller-manager无法与Pod通信。18、 创建一个pod的流程是什么?客户端提交Pod的配置信息(可以是yaml文件定义好的信息)到kube-apiserver;Apiserver收到指令后,通知给controller-manager创建一个资源对象;Controller-manager通过api-server将pod的配置信息存储到ETCD数据中心中;Kube-scheduler检测到pod信息会开始调度预选,会先过滤掉不符合Pod资源配置要求的节点,然后开始调度调优,主要是挑选出更适合运行pod的节点,然后将pod的资源配置单发送到node节点上的kubelet组件上。Kubelet根据scheduler发来的资源配置单运行pod,运行成功后,将pod的运行信息返回给scheduler,scheduler将返回的pod运行状况的信息存储到etcd数据中心。19、 删除一个Pod会发生什么事情?答:Kube-apiserver会接受到用户的删除指令,默认有30秒时间等待优雅退出,超过30秒会被标记为死亡状态,此时Pod的状态Terminating,kubelet看到pod标记为Terminating就开始了关闭Pod的工作;关闭流程如下:pod从service的endpoint列表中被移除;如果该pod定义了一个停止前的钩子,其会在pod内部被调用,停止钩子一般定义了如何优雅的结束进程;进程被发送TERM信号(kill -14)当超过优雅退出的时间后,Pod中的所有进程都会被发送SIGKILL信号(kill -9)。20、 K8s的Service是什么?答:Pod每次重启或者重新部署,其IP地址都会产生变化,这使得pod间通信和pod与外部通信变得困难,这时候,就需要Service为pod提供一个固定的入口。Service的Endpoint列表通常绑定了一组相同配置的pod,通过负载均衡的方式把外界请求分配到多个pod上21、 k8s是怎么进行服务注册的?答:Pod启动后会加载当前环境所有Service信息,以便不同Pod根据Service名进行通信。22、 k8s集群外流量怎么访问Pod?答:可以通过Service的NodePort方式访问,会在所有节点监听同一个端口,比如:30000,访问节点的流量会被重定向到对应的Service上面。23、 k8s数据持久化的方式有哪些?答:1)EmptyDir(空目录):没有指定要挂载宿主机上的某个目录,直接由Pod内保部映射到宿主机上。类似于docker中的manager volume。主要使用场景:只需要临时将数据保存在磁盘上,比如在合并/排序算法中;作为两个容器的共享存储,使得第一个内容管理的容器可以将生成的数据存入其中,同时由同一个webserver容器对外提供这些页面。emptyDir的特性:同个pod里面的不同容器,共享同一个持久化目录,当pod节点删除时,volume的数据也会被删除。如果仅仅是容器被销毁,pod还在,则不会影响volume中的数据。总结来说:emptyDir的数据持久化的生命周期和使用的pod一致。一般是作为临时存储使用。2)Hostpath:将宿主机上已存在的目录或文件挂载到容器内部。类似于docker中的bind mount挂载方式。这种数据持久化方式,运用场景不多,因为它增加了pod与节点之间的耦合。一般对于k8s集群本身的数据持久化和docker本身的数据持久化会使用这种方式,可以自行参考apiService的yaml文件,位于:/etc/kubernetes/main…目录下。3)PersistentVolume(简称PV):基于NFS服务的PV,也可以基于GFS的PV。它的作用是统一数据持久化目录,方便管理。在一个PV的yaml文件中,可以对其配置PV的大小,指定PV的访问模式:ReadWriteOnce:只能以读写的方式挂载到单个节点;ReadOnlyMany:能以只读的方式挂载到多个节点;ReadWriteMany:能以读写的方式挂载到多个节点。以及指定pv的回收策略:recycle:清除PV的数据,然后自动回收;Retain:需要手动回收;delete:删除云存储资源,云存储专用;PS:这里的回收策略指的是在PV被删除后,在这个PV下所存储的源文件是否删除)。若需使用PV,那么还有一个重要的概念:PVC,PVC是向PV申请应用所需的容量大小,K8s集群中可能会有多个PV,PVC和PV若要关联,其定义的访问模式必须一致。定义的storageClassName也必须一致,若群集中存在相同的(名字、访问模式都一致)两个PV,那么PVC会选择向它所需容量接近的PV去申请,或者随机申请。
文章
存储  ·  Kubernetes  ·  负载均衡  ·  网络协议  ·  API  ·  调度  ·  数据中心  ·  Docker  ·  容器  ·  Perl
2022-08-08
K8S面试题
1、 k8s是什么?请说出你的了解?答:Kubenetes是一个针对容器应用,进行自动部署,弹性伸缩和管理的开源系统。主要功能是生产环境中的容器编排。K8S是Google公司推出的,它来源于由Google公司内部使用了15年的Borg系统,集结了Borg的精华。2、 K8s架构的组成是什么?答:和大多数分布式系统一样,K8S集群至少需要一个主节点(Master)和多个计算节点(Node)。主节点主要用于暴露API,调度部署和节点的管理;计算节点运行一个容器运行环境,一般是docker环境(类似docker环境的还有rkt),同时运行一个K8s的代理(kubelet)用于和master通信。计算节点也会运行一些额外的组件,像记录日志,节点监控,服务发现等等。计算节点是k8s集群中真正工作的节点。K8S架构细分:1、Master节点(默认不参加实际工作):Kubectl:客户端命令行工具,作为整个K8s集群的操作入口;Api Server:在K8s架构中承担的是“桥梁”的角色,作为资源操作的唯一入口,它提供了认证、授权、访问控制、API注册和发现等机制。客户端与k8s群集及K8s内部组件的通信,都要通过Api Server这个组件;Controller-manager:负责维护群集的状态,比如故障检测、自动扩展、滚动更新等;Scheduler:负责资源的调度,按照预定的调度策略将pod调度到相应的node节点上;Etcd:担任数据中心的角色,保存了整个群集的状态;2、Node节点:Kubelet:负责维护容器的生命周期,同时也负责Volume和网络的管理,一般运行在所有的节点,是Node节点的代理,当Scheduler确定某个node上运行pod之后,会将pod的具体信息(image,volume)等发送给该节点的kubelet,kubelet根据这些信息创建和运行容器,并向master返回运行状态。(自动修复功能:如果某个节点中的容器宕机,它会尝试重启该容器,若重启无效,则会将该pod杀死,然后重新创建一个容器);Kube-proxy:Service在逻辑上代表了后端的多个pod。负责为Service提供cluster内部的服务发现和负载均衡(外界通过Service访问pod提供的服务时,Service接收到的请求后就是通过kube-proxy来转发到pod上的);container-runtime:是负责管理运行容器的软件,比如dockerPod:是k8s集群里面最小的单位。每个pod里边可以运行一个或多个container(容器),如果一个pod中有两个container,那么container的USR(用户)、MNT(挂载点)、PID(进程号)是相互隔离的,UTS(主机名和域名)、IPC(消息队列)、NET(网络栈)是相互共享的。我比较喜欢把pod来当做豌豆夹,而豌豆就是pod中的container;3、 容器和主机部署应用的区别是什么?答:容器的中心思想就是秒级启动;一次封装、到处运行;这是主机部署应用无法达到的效果,但同时也更应该注重容器的数据持久化问题。另外,容器部署可以将各个服务进行隔离,互不影响,这也是容器的另一个核心概念。4、请你说一下kubenetes针对pod资源对象的健康监测机制?答:K8s中对于pod资源对象的健康状态检测,提供了三类probe(探针)来执行对pod的健康监测:1) livenessProbe探针可以根据用户自定义规则来判定pod是否健康,如果livenessProbe探针探测到容器不健康,则kubelet会根据其重启策略来决定是否重启,如果一个容器不包含livenessProbe探针,则kubelet会认为容器的livenessProbe探针的返回值永远成功。2) ReadinessProbe探针同样是可以根据用户自定义规则来判断pod是否健康,如果探测失败,控制器会将此pod从对应service的endpoint列表中移除,从此不再将任何请求调度到此Pod上,直到下次探测成功。3) startupProbe探针启动检查机制,应用一些启动缓慢的业务,避免业务长时间启动而被上面两类探针kill掉,这个问题也可以换另一种方式解决,就是定义上面两类探针机制时,初始化时间定义的长一些即可。每种探测方法能支持以下几个相同的检查参数,用于设置控制检查时间:initialDelaySeconds:初始第一次探测间隔,用于应用启动的时间,防止应用还没启动而健康检查失败periodSeconds:检查间隔,多久执行probe检查,默认为10s;timeoutSeconds:检查超时时长,探测应用timeout后为失败;successThreshold:成功探测阈值,表示探测多少次为健康正常,默认探测1次。上面两种探针都支持以下三种探测方法:1)Exec: 通过执行命令的方式来检查服务是否正常,比如使用cat命令查看pod中的某个重要配置文件是否存在,若存在,则表示pod健康。反之异常。Exec探测方式的yaml文件语法如下:spec: containers: - name: liveness image: k8s.gcr.io/busybox args: - /bin/sh - -c - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600 livenessProbe: #选择livenessProbe的探测机制 exec: #执行以下命令 command: - cat - /tmp/healthy initialDelaySeconds: 5 #在容器运行五秒后开始探测 periodSeconds: 5 #每次探测的时间间隔为5秒在上面的配置文件中,探测机制为在容器运行5秒后,每隔五秒探测一次,如果cat命令返回的值为“0”,则表示健康,如果为非0,则表示异常。2)Httpget: 通过发送http/htps请求检查服务是否正常,返回的状态码为200-399则表示容器健康(注http get类似于命令curl -I)。Httpget探测方式的yaml文件语法如下:spec: containers: - name: liveness image: k8s.gcr.io/liveness livenessProbe: #采用livenessProbe机制探测 httpGet: #采用httpget的方式 scheme:HTTP #指定协议,也支持https path: /healthz #检测是否可以访问到网页根目录下的healthz网页文件 port: 8080 #监听端口是8080 initialDelaySeconds: 3 #容器运行3秒后开始探测 periodSeconds: 3 #探测频率为3秒上述配置文件中,探测方式为项容器发送HTTP GET请求,请求的是8080端口下的healthz文件,返回任何大于或等于200且小于400的状态码表示成功。任何其他代码表示异常。3)tcpSocket: 通过容器的IP和Port执行TCP检查,如果能够建立TCP连接,则表明容器健康,这种方式与HTTPget的探测机制有些类似,tcpsocket健康检查适用于TCP业务。tcpSocket探测方式的yaml文件语法如下:spec: containers: - name: goproxy image: k8s.gcr.io/goproxy:0.1 ports: - containerPort: 8080 #这里两种探测机制都用上了,都是为了和容器的8080端口建立TCP连接 readinessProbe: tcpSocket: port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: tcpSocket: port: 8080 initialDelaySeconds: 15 periodSeconds: 20在上述的yaml配置文件中,两类探针都使用了,在容器启动5秒后,kubelet将发送第一个readinessProbe探针,这将连接容器的8080端口,如果探测成功,则该pod为健康,十秒后,kubelet将进行第二次连接。除了readinessProbe探针外,在容器启动15秒后,kubelet将发送第一个livenessProbe探针,仍然尝试连接容器的8080端口,如果连接失败,则重启容器。探针探测的结果无外乎以下三者之一:Success:Container通过了检查;Failure:Container没有通过检查;Unknown:没有执行检查,因此不采取任何措施(通常是我们没有定义探针检测,默认为成功)。若觉得上面还不够透彻,可以移步其官网文档:https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/5、 如何控制滚动更新过程?答:可以通过下面的命令查看到更新时可以控制的参数:[root@master yaml]# kubectl explain deploy.spec.strategy.rollingUpdatemaxSurge: 此参数控制滚动更新过程,副本总数超过预期pod数量的上限。可以是百分比,也可以是具体的值。默认为1。(上述参数的作用就是在更新过程中,值若为3,那么不管三七二一,先运行三个pod,用于替换旧的pod,以此类推)maxUnavailable: 此参数控制滚动更新过程中,不可用的Pod的数量。(这个值和上面的值没有任何关系,举个例子:我有十个pod,但是在更新的过程中,我允许这十个pod中最多有三个不可用,那么就将这个参数的值设置为3,在更新的过程中,只要不可用的pod数量小于或等于3,那么更新过程就不会停止)。推荐:250期面试题精选6、K8s中镜像的下载策略是什么?答:可通过命令“kubectl explain pod.spec.containers”来查看imagePullPolicy这行的解释。K8s的镜像下载策略有三种:Always、Never、IFNotPresent;Always:镜像标签为latest时,总是从指定的仓库中获取镜像;Never:禁止从仓库中下载镜像,也就是说只能使用本地镜像;IfNotPresent:仅当本地没有对应镜像时,才从目标仓库中下载。默认的镜像下载策略是:当镜像标签是latest时,默认策略是Always;当镜像标签是自定义时(也就是标签不是latest),那么默认策略是IfNotPresent。7、 image的状态有哪些?Running:Pod所需的容器已经被成功调度到某个节点,且已经成功运行,Pending:APIserver创建了pod资源对象,并且已经存入etcd中,但它尚未被调度完成或者仍然处于仓库中下载镜像的过程Unknown:APIserver无法正常获取到pod对象的状态,通常是其无法与所在工作节点的kubelet通信所致。8、 pod的重启策略是什么?答:可以通过命令“kubectl explain pod.spec”查看pod的重启策略。(restartPolicy字段)Always:但凡pod对象终止就重启,此为默认策略。OnFailure:仅在pod对象出现错误时才重启9、 Service这种资源对象的作用是什么?答:用来给相同的多个pod对象提供一个固定的统一访问接口,常用于服务发现和服务访问。10、版本回滚相关的命令?[root@master httpd-web]# kubectl apply -f httpd2-deploy1.yaml --record #运行yaml文件,并记录版本信息; [root@master httpd-web]# kubectl rollout history deployment httpd-devploy1 #查看该deployment的历史版本 [root@master httpd-web]# kubectl rollout undo deployment httpd-devploy1 --to-revision=1 #执行回滚操作,指定回滚到版本1 #在yaml文件的spec字段中,可以写以下选项(用于限制最多记录多少个历史版本): spec: revisionHistoryLimit: 5 #这个字段通过 kubectl explain deploy.spec 命令找到revisionHistoryLimit <integer>行获得11、 标签与标签选择器的作用是什么?标签:是当相同类型的资源对象越来越多的时候,为了更好的管理,可以按照标签将其分为一个组,为的是提升资源对象的管理效率。标签选择器:就是标签的查询过滤条件。目前API支持两种标签选择器:基于等值关系的,如:“=”、“”“==”、“!=”(注:“==”也是等于的意思,yaml文件中的matchLabels字段);基于集合的,如:in、notin、exists(yaml文件中的matchExpressions字段);注:in:在这个集合中;notin:不在这个集合中;exists:要么全在(exists)这个集合中,要么都不在(notexists);使用标签选择器的操作逻辑:在使用基于集合的标签选择器同时指定多个选择器之间的逻辑关系为“与”操作(比如:- {key: name,operator: In,values: [zhangsan,lisi]} ,那么只要拥有这两个值的资源,都会被选中);使用空值的标签选择器,意味着每个资源对象都被选中(如:标签选择器的键是“A”,两个资源对象同时拥有A这个键,但是值不一样,这种情况下,如果使用空值的标签选择器,那么将同时选中这两个资源对象)空的标签选择器(注意不是上面说的空值,而是空的,都没有定义键的名称),将无法选择出任何资源;在基于集合的选择器中,使用“In”或者“Notin”操作时,其values可以为空,但是如果为空,这个标签选择器,就没有任何意义了。两种标签选择器类型(基于等值、基于集合的书写方法):selector: matchLabels: #基于等值 app: nginx matchExpressions: #基于集合 - {key: name,operator: In,values: [zhangsan,lisi]} #key、operator、values这三个字段是固定的 - {key: age,operator: Exists,values:} #如果指定为exists,那么values的值一定要为空12、 常用的标签分类有哪些?标签分类是可以自定义的,但是为了能使他人可以达到一目了然的效果,一般会使用以下一些分类:版本类标签(release):stable(稳定版)、canary(金丝雀版本,可以将其称之为测试版中的测试版)、beta(测试版);环境类标签(environment):dev(开发)、qa(测试)、production(生产)、op(运维);应用类(app):ui、as、pc、sc;架构类(tier):frontend(前端)、backend(后端)、cache(缓存);分区标签(partition):customerA(客户A)、customerB(客户B);品控级别(Track):daily(每天)、weekly(每周)。13、 有几种查看标签的方式?答:常用的有以下三种查看方式:[root@master ~]# kubectl get pod --show-labels #查看pod,并且显示标签内容 [root@master ~]# kubectl get pod -L env,tier #显示资源对象标签的值 [root@master ~]# kubectl get pod -l env,tier #只显示符合键值资源对象的pod,而“-L”是显示所有的pod14、 添加、修改、删除标签的命令?#对pod标签的操作 [root@master ~]# kubectl label pod label-pod abc=123 #给名为label-pod的pod添加标签 [root@master ~]# kubectl label pod label-pod abc=456 --overwrite #修改名为label-pod的标签 [root@master ~]# kubectl label pod label-pod abc- #删除名为label-pod的标签 [root@master ~]# kubectl get pod --show-labels #对node节点的标签操作 [root@master ~]# kubectl label nodes node01 disk=ssd #给节点node01添加disk标签 [root@master ~]# kubectl label nodes node01 disk=sss –overwrite #修改节点node01的标签 [root@master ~]# kubectl label nodes node01 disk- #删除节点node01的disk标签15、 DaemonSet资源对象的特性?DaemonSet这种资源对象会在每个k8s集群中的节点上运行,并且每个节点只能运行一个pod,这是它和deployment资源对象的最大也是唯一的区别。所以,在其yaml文件中,不支持定义replicas,除此之外,与Deployment、RS等资源对象的写法相同。它的一般使用场景如下:在去做每个节点的日志收集工作;监控每个节点的的运行状态;16、 说说你对Job这种资源对象的了解?答:Job与其他服务类容器不同,Job是一种工作类容器(一般用于做一次性任务)。使用常见不多,可以忽略这个问题。#提高Job执行效率的方法: spec: parallelism: 2 #一次运行2个 completions: 8 #最多运行8个 template: metadata:17、描述一下pod的生命周期有哪些状态?Pending:表示pod已经被同意创建,正在等待kube-scheduler选择合适的节点创建,一般是在准备镜像;Running:表示pod中所有的容器已经被创建,并且至少有一个容器正在运行或者是正在启动或者是正在重启;Succeeded:表示所有容器已经成功终止,并且不会再启动;Failed:表示pod中所有容器都是非0(不正常)状态退出;Unknown:表示无法读取Pod状态,通常是kube-controller-manager无法与Pod通信。18、 创建一个pod的流程是什么?客户端提交Pod的配置信息(可以是yaml文件定义好的信息)到kube-apiserver;Apiserver收到指令后,通知给controller-manager创建一个资源对象;Controller-manager通过api-server将pod的配置信息存储到ETCD数据中心中;Kube-scheduler检测到pod信息会开始调度预选,会先过滤掉不符合Pod资源配置要求的节点,然后开始调度调优,主要是挑选出更适合运行pod的节点,然后将pod的资源配置单发送到node节点上的kubelet组件上。Kubelet根据scheduler发来的资源配置单运行pod,运行成功后,将pod的运行信息返回给scheduler,scheduler将返回的pod运行状况的信息存储到etcd数据中心。19、 删除一个Pod会发生什么事情?答:Kube-apiserver会接受到用户的删除指令,默认有30秒时间等待优雅退出,超过30秒会被标记为死亡状态,此时Pod的状态Terminating,kubelet看到pod标记为Terminating就开始了关闭Pod的工作;关闭流程如下:pod从service的endpoint列表中被移除;如果该pod定义了一个停止前的钩子,其会在pod内部被调用,停止钩子一般定义了如何优雅的结束进程;进程被发送TERM信号(kill -14)当超过优雅退出的时间后,Pod中的所有进程都会被发送SIGKILL信号(kill -9)。20、 K8s的Service是什么?答:Pod每次重启或者重新部署,其IP地址都会产生变化,这使得pod间通信和pod与外部通信变得困难,这时候,就需要Service为pod提供一个固定的入口。Service的Endpoint列表通常绑定了一组相同配置的pod,通过负载均衡的方式把外界请求分配到多个pod上21、 k8s是怎么进行服务注册的?答:Pod启动后会加载当前环境所有Service信息,以便不同Pod根据Service名进行通信。22、 k8s集群外流量怎么访问Pod?答:可以通过Service的NodePort方式访问,会在所有节点监听同一个端口,比如:30000,访问节点的流量会被重定向到对应的Service上面。23、 k8s数据持久化的方式有哪些?答:1)EmptyDir(空目录):没有指定要挂载宿主机上的某个目录,直接由Pod内保部映射到宿主机上。类似于docker中的manager volume。主要使用场景:只需要临时将数据保存在磁盘上,比如在合并/排序算法中;作为两个容器的共享存储,使得第一个内容管理的容器可以将生成的数据存入其中,同时由同一个webserver容器对外提供这些页面。emptyDir的特性:同个pod里面的不同容器,共享同一个持久化目录,当pod节点删除时,volume的数据也会被删除。如果仅仅是容器被销毁,pod还在,则不会影响volume中的数据。总结来说:emptyDir的数据持久化的生命周期和使用的pod一致。一般是作为临时存储使用。2)Hostpath:将宿主机上已存在的目录或文件挂载到容器内部。类似于docker中的bind mount挂载方式。这种数据持久化方式,运用场景不多,因为它增加了pod与节点之间的耦合。一般对于k8s集群本身的数据持久化和docker本身的数据持久化会使用这种方式,可以自行参考apiService的yaml文件,位于:/etc/kubernetes/main…目录下。3)PersistentVolume(简称PV):基于NFS服务的PV,也可以基于GFS的PV。它的作用是统一数据持久化目录,方便管理。在一个PV的yaml文件中,可以对其配置PV的大小,指定PV的访问模式:ReadWriteOnce:只能以读写的方式挂载到单个节点;ReadOnlyMany:能以只读的方式挂载到多个节点;ReadWriteMany:能以读写的方式挂载到多个节点。以及指定pv的回收策略:recycle:清除PV的数据,然后自动回收;Retain:需要手动回收;delete:删除云存储资源,云存储专用;PS:这里的回收策略指的是在PV被删除后,在这个PV下所存储的源文件是否删除)。若需使用PV,那么还有一个重要的概念:PVC,PVC是向PV申请应用所需的容量大小,K8s集群中可能会有多个PV,PVC和PV若要关联,其定义的访问模式必须一致。定义的storageClassName也必须一致,若群集中存在相同的(名字、访问模式都一致)两个PV,那么PVC会选择向它所需容量接近的PV去申请,或者随机申请。
文章
存储  ·  Kubernetes  ·  负载均衡  ·  网络协议  ·  API  ·  调度  ·  数据中心  ·  Docker  ·  容器  ·  Perl
2022-08-08
云原生系列四:Yelp 如何在 Kubernetes 上运行 Kafka
案例分享 | Yelp 如何在 Kubernetes 上运行 Kafka(第 1 部分 - 架构)编辑这几天小叶秋在网上冲浪的时候,发现一些与云原生相关的文章,特地拿来与大家分享~~本文译自 Kafka on PaaSTA: Running Kafka on Kubernetes at Yelp (Part 1 - Architecture)[1]。作者:Lennart Rudolph在 Yelp,Kafka 每天接收数百亿条消息来推进数据驱动并为关键业务管道和服务提供支持。我们最近通过在 PaaSTA (Yelp 自己的平台即服务)上运行集群,对 Kafka 部署架构进行一些改进。基于 K8s 的部署利用了 Kafka 的自定义 Kubernetes operator 以及用于生命周期管理的 Cruise Control 。编辑架构改进及动机过去,我们所有的 Kafka 集群都在 AWS 的专用 EC2 实例上运行。Kafka 直接部署在这些主机上,配置管理高度依赖 Puppet 仓库。这种部署模式有些繁琐,创建一个新集群平均需要两个多小时。因此我们着手开发一种新的部署模型,以下是改进目标: 减少对 Puppet 缓慢运行的依赖。 在内部推广 PaaSTA ,并利用其 CLI 工具来提高生产力。 提高生命周期管理系统的可维护性。 简化执行操作系统主机升级和 Kafka 版本升级的过程。 简化新 Kafka 集群的创建(与我们部署服务的方式一致)。 加快代理退役,简化主机故障的恢复过程。拥有重新连接 EBS 卷的能力,避免不必要地网络资源消耗,节省资金。 Yelp 之前开发了在 Kubernetes 上运行有状态应用程序的实践(例如,Cassandra on PaaSTA and Flink on PaaSTA),因此 PaaSTA 是这个用例的自然选择。新的部署架构利用 PaaSTA 池(或主机组)作为底层基础设施。Kafka 代理 pod 调度在 Kubernetes 节点上,并且代理 pod 具有可分离的 EBS 卷。新架构的两个关键组件是 Kafka operator 和 Cruise Control,后面会更详细地介绍这两者。我们在 PaaSTA 上部署了我们内部的 Kafka Kubernetes Operator 实例和各种 Sidecar 服务,并且每个 Kafka 集群的 PaaSTA 上也部署了一个 Cruise Control 实例。新旧架构的两个关键区别是 Kafka 现在运行在 Docker 容器中,我们的配置管理方法不再依赖 Puppet。配置管理现在与基于 PaaSTA 的配置管理解决方案一致,在该解决方案中,只要 YAML 文件更改提交到服务配置存储库, Jenkins 就会传播这些变化。由于这次架构大修,我们才能够利用现有的 PaaSTA CLI 工具来查看集群的状态、读取日志并重新启动集群。另一个好处是,能够通过提供必要的配置(见下文)来部署新的 Kafka 集群,这种方法使我们配置新 Kafka 集群的时间减半。编辑example-test-prod: deploy_group: prod.everything pool: kafka brokers: 15 cpus: 5.7 # CPU unit reservation breakdown: (5.7 (kafka) + 0.1 (hacheck) + 0.1 (sensu)) + 0.1 (kiam) = 6.0 (as an example, consider that our pool is comprised of m5.2xlarge instances) mem: 26Gi data: 910Gi storage_class: gp2 cluster_type: example cluster_name: test-prod use_cruise_control: true cruise_control_port: 12345 service_name: kafka-2-4-1 zookeeper: cluster_name: test-prod chroot: kafka-example-test-prod cluster_type: kafka_example_test config: unclean.leader.election.enable: "false" reserved.broker.max.id: "2113929216" request.timeout.ms: "300001" replica.fetch.max.bytes: "10485760" offsets.topic.segment.bytes: "104857600" offsets.retention.minutes: "10080" offsets.load.buffer.size: "15728640" num.replica.fetchers: "3" num.network.threads: "5" num.io.threads: "5" min.insync.replicas: "2" message.max.bytes: "1000000" log.segment.bytes: "268435456" log.roll.jitter.hours: "1" log.roll.hours: "22" log.retention.hours: "24" log.message.timestamp.type: "LogAppendTime" log.message.format.version: "2.4-IV1" log.cleaner.enable: "true" log.cleaner.threads: "3" log.cleaner.dedupe.buffer.size: "536870912" inter.broker.protocol.version: "2.4-IV1" group.max.session.timeout.ms: "300000" delete.topic.enable: "true" default.replication.factor: "3" connections.max.idle.ms: "3600000" confluent.support.metrics.enable: "false" auto.create.topics.enable: "false" transactional.id.expiration.ms: "86400000"运行 Kafka 2.4.1 版本的有 15 个代理的集群的示例配置文件新架构详解新架构的一个主要组件是 Kafka Kubernetes operator,它负责管理 Kafka 集群的状态。虽然我们仍然依赖外部 ZooKeeper 集群来维护集群元数据,但消息数据仍然保存在 Kafka 代理的磁盘中。由于 Kafka 用户依赖持久存储来检索数据,在 Kubernetes 中,Kafka 被认为是一个有状态的应用程序。Kubernetes 公开了用于管理有状态应用程序的工作负载 API 对象 。(例如 StatefulSets ),但 Kubernetes 默认没有 Kafka-specific 结构。因此,需要标准 Kubernetes API 之外的其他功能来维护我们的实例。用 Kubernetes 的说法,operator 是一个自定义控制器,它允许我们公开这种特定的应用功能。编辑Operator 负责确定 Kubernetes 需要对集群执行操作的时间。它有一个协调循环,观察自定义集群资源的状态,并通过与 Kubernetes API 交互以及调用另一个关键架构组件 Cruise Control 公开的 API 来协调差异。Cruise Control 是 LinkedIn 开发的开源 Kafka 集群管理系统。目标是减少与维护大型 Kafka 集群的开销。每个 Kafka 集群都有自己专用的 Cruise Control 实例,每个集群的 Operator 与其 Cruise Control 实例交互以执行生命周期管理操作,如检查集群的健康状况、重新平衡主题分区和添加/删除代理。Cruise Control 使用的范式在许多方面与 operator 使用的范式相似。Cruise Control 监控 Kafka 集群的状态,生成一个内部模型,扫描异常目标,并尝试解决异常问题。它公开了用于各种管理任务和上述生命周期管理操作的 API 。这些 API 可替代我们之前的临时生命周期管理实现,我们使用 EC2 支持的代理来执行条件性再平衡操作或与 SNS 和 SQS 等 AWS 资源进行互动,将这些整合到一项服务中帮助简化生命周期管理栈。编辑将这些组件放在一起就形成了一个集群架构,我们通过内部配置管理系统定义了一个 CRD,并将其与自定义 Kafka Docker 镜像结合起来。Kafka Kubernetes operator 在与 Kubernetes API 的交互中使用配置、CRD 和 Docker 镜像 ,在 Kubernetes 主服务器上生成 KafkaCluster 自定义资源,因此可以在 Kubernetes 节点上调度 Kafka pod,operator 通过 Kubernetes API 和 Cruise Control 服务公开的 API 来监督和维护集群的健康状况。我们可以通过 Cruise Control UI 或 PaaSTA CLI 工具观察集群并与之交互。最后,通过一个示例场景来说明整个操作流程。考虑通过删除代理来缩小集群规模的情况。一个开发者更新集群的配置并减少代理的数量,从而更新 Kafka 集群的 CRD。作为协调循环的一部分,operator 认识到期望的集群状态与 StatefulSet 中表示的实际状态不同,所以它要求 Cruise Control 删除代理,Cruise Control API 返回有关删除任务的信息,operator 使用这个任务的元数据注释退役 pod。当 Cruise Control 执行将分区从代理移开的过程,operator 会通过向 Cruise Control 发出请求来例行检查停用的状态。一旦任务被标记为已完成,operator 将移除 pod ,集群规范的内部状态就被调和了。编辑架构设计好后,我们会做什么?在设计了这个架构之后,我们构建了一个将 Kafka 集群从 EC2 无缝迁移到 PaaSTA 的流程。截止目前,我们已经将许多集群迁移到 PaaSTA,并使用新架构部署了新集群。我们还在继续调整硬件选择,以适应集群的不同属性。引用链接[1]原文链接: https://engineeringblog.yelp.com/2021/12/kafka-on-paasta-part-one.html本期分享到此结束,关注博主不迷路,叶秋学长带你上高速~~
文章
消息中间件  ·  存储  ·  Kubernetes  ·  Cloud Native  ·  Kafka  ·  API  ·  调度  ·  Docker  ·  容器  ·  Perl
2022-08-07
【笔记】最佳实践—如何优化数据导入导出
测试环境本文档的测试环境要求如下表:环境参数PolarDB-X版本polarx-kernel_5.4.11-16282307_xcluster-20210805节点规格16核64GB节点个数4个测试用表如下:CREATE TABLE `sbtest1` ( `id` int(11) NOT NULL, `k` int(11) NOT NULL DEFAULT '0', `c` char(120) NOT NULL DEFAULT '', `pad` char(60) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `k_1` (`k`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 dbpartition by hash(`id`);导入导出工具介绍PolarDB-X常见的数据导出方法有:mysql -e命令行导出数据musqldump工具导出数据select into outfile语句导出数据(默认关闭)Batch Tool工具导出数据(PolarDB-X配套的导入导出工具)PolarDB-X常见的数据导入方法有:source语句导入数据mysql命令导入数据程序导入数据load data语句导入数据Batch Tool工具导入数据(PolarDB-X配套的导入导出工具)MySQL原生命令使用示例mysql -e命令可以连接本地或远程服务器,通过执行sql语句,例如select方式获取数据,原始输出数据以制表符方式分隔,可通过字符串处理改成','分隔,以csv文件方式存储,方法示例:mysql -h ip -P port -u usr -pPassword db_name -N -e "SELECT id,k,c,pad FROM sbtest1;" >/home/data_1000w.txt## 原始数据以制表符分隔,数据格式:188092293 27267211 59775766593-64673028018-...-09474402685 01705051424-...-54211554755mysql -h ip -P port -u usr -pPassword db_name -N -e "SELECT id,k,c,pad FROM sbtest1;" | sed 's/\t/,/g' >/home/data_1000w.csv## csv文件以逗号分隔,数据格式:188092293,27267211,59775766593-64673028018-...-09474402685,01705051424-...-54211554755原始数据格式适合load data语句导入数据,使用方法可参考:LOAD DATA 语句,示例如下:LOAD DATA LOCAL INFILE '/home/data_1000w.txt' INTO TABLE sbtest1;## LOCAL代表从本地文件导入,local_infile参数必须开启csv文件数据适合程序导入,具体方式可查看使用程序进行数据导入。mysqldump工具使用示例mysqldump工具可以连接到本地或远程服务器,详细使用方法请参见使用mysqldump导入导出数据。导出数据示例:mysqldump -h ip -P port -u usr -pPassword --default-character-set=utf8mb4 --net_buffer_length=10240 --no-tablespaces --no-create-db --no-create-info --skip-add-locks --skip-lock-tables --skip-tz-utc --set-charset --hex-blob db_name [table_name] > /home/dump_1000w.sqlmysqldump导出数据可能会出现的问题及解决方法,这两个问题通常是mysql client和mysql server版本不一致导致的。问题:mysqldump: Couldn't execute 'SHOW VARIABLES LIKE 'gtid\_mode''解决方法:添加“--set-gtid-purged=OFF”参数关闭gtid_mode。问题:mysqldump: Couldn't execute 'SHOW VARIABLES LIKE 'ndbinfo\_version''解决方法:查看mysqldump --version和mysql版本是否一致,使用和mysql版本一致的mysql client。导出的数据格式是SQL语句方式,以Batch Insert语句为主体,包含多条SQL语句,INSERT INTO `sbtest1` VALUES (...),(...),“net_buffer_length”参数将影响batch size大小。SQL语句格式合适的导入数据方式:方法一:souce语句导入数据source /home/dump_1000w.sql方法二:mysql命令导入数据mysql -h ip -P port -u usr -pPassword --default-character-set=utf8mb4 db_name < /home/dump_1000w.sqlBatch Tool工具使用示例Batch Tool是阿里云内部开发的数据导入导出工具,支持多线程操作。导出数据:## 导出“默认值=分片数”个文件java -jar batch-tool.jar -h ip -P port -u usr -pPassword -D db_name -o export -t sbtest1 -s ,## 导出整合成一个文件java -jar batch-tool.jar -h ip -P port -u usr -pPassword -D db_name -o export -t sbtest1 -s , -F 1导入数据:## 导入32个文件java -jar batch-tool.jar -hpxc-spryb387va1ypn.polarx.singapore.rds.aliyuncs.com -P3306 -uroot -pPassw0rd -D sysbench_db -o import -t sbtest1 -s , -f "sbtest1_0;sbtest1_1;sbtest1_2;sbtest1_3;sbtest1_4;sbtest1_5;sbtest1_6;sbtest1_7;sbtest1_8;sbtest1_9;sbtest1_10;sbtest1_11;sbtest1_12;sbtest1_13;sbtest1_14;sbtest1_15;sbtest1_16;sbtest1_17;sbtest1_18;sbtest1_19;sbtest1_20;sbtest1_21;sbtest1_22;sbtest1_23;sbtest1_24;sbtest1_25;sbtest1_26;sbtest1_27;sbtest1_28;sbtest1_29;sbtest1_30;sbtest1_31" -np -pro 64 -con 32## 导入1个文件java -jar batch-tool.jar -h ip -P port -u usr -p password -D db_name -o import -t sbtest1 -s , -f "sbtest1_0" -np导出方法对比测试方法以PolarDB-X导出1000w行数据为例,数据量大概2GB左右。方式数据格式文件大小耗时性能(行/每秒)性能(MB/S)mysql -e命令 导出原始数据原始数据格式1998MB33.417s29924859.8mysql -e命令导出csv格式csv格式1998MB34.126s29303158.5mysqldump工具(net-buffer-length=10KB)sql语句格式2064MB30.223s33087368.3mysqldump工具(net-buffer-length=200KB)sql语句格式2059MB32.783s30503662.8batch tool工具文件数=32(分片数)csv格式1998MB4.715s2120890423.7batch tool工具文件数=1csv格式1998MB5.568s1795977358.8总结:mysql -e命令和mysqldump工具原理上主要是单线程操作,性能差别并不明显。Batch Tool工具采用多线程方式导出,并发度可设置,能够极大提高导出性能。导入方法对比测试方法以PolarDB-X导入1000w行数据为例,源数据是上一个测试中导出的数据,数据量大概2GB左右。方式数据格式耗时性能(行/每秒)性能(MB/S)source语句(net-buffer-length=10KB)sql语句格式10m24s160253.2source语句(net-buffer-length=200KB)sql语句格式5m37s296735.9mysql命令导入(net-buffer-length=10KB)sql语句格式10m27s159483.2mysql命令导入(net-buffer-length=200KB)sql语句格式5m38s295855.9load data语句导入原始数据格式4m0s416668.3程序导入batch-1000thread-1csv格式5m40s294115.9程序导入batch-1000thread-32csv格式19s526315105.3batch tool工具文件数=32(分片数)csv格式19.836s504133100.8batch tool工具文件数=1csv格式10.806s925411185.1总结:source语句和mysql命令导入方式,都是单线程执行SQL语句导入,实际是Batch Insert语句的运用,Batch size大小会影响导入性能。Batch size和mysqldump导出数据时的“net-buffer-length”参数有关。建议优化点如下:推荐将“net-buffer-length”参数设置大,不超过256K,以增大batch size大小,来提高插入性能。使用第三方工具,例如mysqldump,进行mydumper(备份)和myloader(导入)等,可多线程操作。load data语句是单线程操作,性能优于mysql命令和source语句。程序导入灵活性较好,可自行设置合适的batch size和并发度,可以达到较好性能。推荐batch大小为1000,并发度为16~32。Batch Tool工具支持多线程导入,且贴合分布式多分片的操作方式,性能优异。总结PolarDB-X兼容MySQL运维上常用的数据导入导出方法,但这些方法大多为MySQL单机模式设计,只支持单线程操作,性能上无法充分利用所有分布式资源。PolarDB-X提供Batch Tool工具,非常贴合分布式场景,在多线程操作下,能够达到极快的数据导入导出性能。
文章
SQL  ·  存储  ·  运维  ·  关系型数据库  ·  MySQL  ·  测试技术  ·  数据库  ·  数据格式  ·  Perl
2022-08-07
【K8S专栏】什么是Kubernetes
大家好,我是乔克。什么是Kubernetes?在《Docker容器技术》章节就有简单介绍Kuberntes,它是谷歌开源的容器容器集群管理系统,是谷歌内部容器管理系统Borg的开源版本。Borg系统是谷歌内部使用很多的容器管理系统,在早期是采用Chroot Jail实现安全隔离,后期采用Namespace,资源隔离是采用CGroup实现。为什么谷歌要推出Kubernetes开源版本呢?我个人的理解是:使用开源社区的力量来解决谷歌未解决的问题在云原生领域分一杯羹推动云原生的发展,毕竟谷歌在容器领域已经玩了许多年了Kubernetes具有以下特点:便携性: 无论公有云、私有云、混合云还是多云架构都全面支持可扩展: 它是模块化、可插拔、可挂载、可组合的,支持各种形式的扩展自修复: 它可以自保持应用状态、可自重启、自复制、自缩放的,通过声明式语法提供了强大的自修复能力使用 Kubernetes, 您可以快速高效地响应客户需求:快速、可预测地部署您的应用程序拥有即时扩展应用程序的能力不影响现有业务的情况下,无缝地发布新功能优化硬件资源,降低成本Kubernetes是一个声明式系统,声明式系统和命令式系统是有本质的区别。所谓声明式系统关注点是做什么,即告诉你将要达成什么样的期望,至于怎么达到是你系统的事情。而命令式系统则是必须按照相应的规定或者步骤达到某个目标或者完成某个任务,其关注点是在怎么做。命令式强调的是How,它需要你通过step-by-step的方式告诉计算机如何完成一个任务,在这种场景下,计算机是不具备“智能”,智能很机械的完成任务,至于完成的结果如何,需要看编程者的水平了。而声明式强调的是What,你只需要告诉计算机你想要什么,然后由计算机自己去执行,这时候的计算机是具备一定的“智能”。当然,声明式不一定会满足你所有的需要。在日常工作中,命令式编程比较普遍,这种编程实现比较方便,只需要按照一定的步骤开发即可,但是在一些特定的场合,声明式要比命令式方便,其实大多数声明式语言都是针对特定任务的领域专用语言,即DSL。最常见的声明式语言就是SQL,只需要告诉计算机你想要的结果集,数据库就会帮你设计获取这个结果集的执行路径,并返回结果。Kubernetes就是一个声明式系统,在使用Kubernetes的时候,用户不需要去定义A->B->C这种Workflow,而是直接去描述一个期望状态,然后Kubernetes就会帮助用户达到这个状态,至于如何达到这个状态,用户不需要关心。这种设计使得Kubernetes更加易用和健壮,也更具弹性和扩展性。Kubernetes的架构Kubernetes整体是Master-Slave架构,如下:其中:etcd 保存了整个集群的状态,就是一个数据库,只有API Server能与其通信;apiserver 提供了资源操作的唯一入口,并提供认证、授权、访问控制、API 注册和发现等机制;controller manager 负责维护集群的状态,比如故障检测、自动扩展、滚动更新等;scheduler 负责资源的调度,按照预定的调度策略将 Pod 调度到相应的机器上;kubelet 负责维护容器的生命周期,同时也负责 Volume(CSI)和网络(CNI)的管理;container runtime 负责镜像管理以及 Pod 和容器的真正运行(CRI);kube-proxy 负责为 Service 提供 cluster 内部的服务发现和负载均衡;registry是镜像仓库,负责存储容器镜像kubectl和dashboard都是客户端工具上面的架构是逻辑架构,在实际的生产运用中,为了达到高可用,会对架构做对应的调整,调整对象就是主节点,如下:主要做了以下改变:(1)将Master节点从单节点变成了多节点,在kube-apiserver前增加了load balancer用来负载,其他组件通信都是通过LB进行(2)将etcd和master节点独立开,避免由于某个master节点故障导致ectd受影响Kubernetes架构的设计原则是:只有APIServer可以直接访问Etcd存储,其他服务必须通过Kubernetes API来访问集群的状态单节点故障原则上不应该影响集群的状态在没有新请求的情况下,所有组件应该在故障恢复后继续执行上次最后收到的请求所有组件应该在内存中保持所需要的状态,APIServer将状态写入Etcd存储,而其他组件则通过APIServer更新并监听所有的变化,最终由Controller Manager去协调优先使用事件监听而不是轮询Kubernetes的重要组件上面介绍了Kubernetes的整体架构以及简单介绍了各个组件的作用,但是它们之间的关系具体如何并没有做过多的介绍,我们现在来看看各个组件以及它们之间是怎么协作的。(1)kubectl 客户端首先将CLI命令转化为RESTful的API调用,然后发送到kube-apiserver。(2)kube-apiserver 在认证、授权、准入验证过后,将任务元信息并存储到etcd,然后kube-scheduler会对任务进行调度,并将调度结果返回给kube-apiserver。(3)一旦 kube-scheduler 返回一个适合调度的目标节点后,kube-apiserver 就把任务的节点信息存入etcd,并创建任务。(4)此时目标节点中的 kubelet正监听apiserver,当监听到有新任务需要调度到本节点后,kubelet通过本地runtime创建任务容器,执行作业。(5)接着kubelet将任务状态等信息返回给apiserver存储到etcd。(6)kube-proxy也会监听apiserver,如果有网络策略相关的操作,就会在本机上创建对应的iptables或者ipvs规则。(7)这样我们的任务已经在运行了,此时control-manager发挥作用保证任务一直是我们期望的状态。其主要组件如下:EtcdAPI ServerController ManagerSchedulerKubeletKube-proxyEtcdEtcd 是兼具一致性和高可用性的键值存储,可用于服务发现、共享配置以及一致性保障,在Kubernetes中,Etcd是作为唯一的存储,保存Kubernetes的所有API对象。在生产级Kubernetes中etcd通常会以集群的方式存在,安全原因,它只能从 API 服务器访问。API ServerAPI Server是Kubernetes最重要的核心组件之一,主要提供以下功能:提供集群管理的REST API接口,包括:认证授权准入为其他模块提供数据交互和通信的枢纽API Server提供Etcd的数据缓存,减少集群对Etcd的访问Controller ManagerKubernetes在后台运行许多不同的控制器进程,当服务配置发生更改时(例如,替换运行 pod 的镜像,或更改配置 yaml 文件中的参数),控制器会发现更改并开始朝着新的期望状态工作。从逻辑上讲,每个控制器都是一个单独的进程, 但是为了降低复杂性,它们都被编译到同一个可执行文件,并在一个进程中运行。控制器包括:节点控制器(Node Controller): 负责在节点出现故障时进行通知和响应任务控制器(Job controller): 监测代表一次性任务的 Job 对象,然后创建 Pods 来运行这些任务直至完成端点控制器(Endpoints Controller): 填充端点(Endpoints)对象(即加入 Service 与 Pod)服务帐户和令牌控制器(Service Account & Token Controllers): 为新的命名空间创建默认帐户和 API 访问令牌下面就是Deployment Controller和ReplicaSet Controller两个控制器的工作流程。Schedulerkube-scheduler 负责监视新创建、未指定运行Node的 Pods,决策出一个让pod运行的节点。例如,如果应用程序需要 1GB 内存和 2 个 CPU 内核,那么该应用程序的 pod 将被安排在至少具有这些资源的节点上。每次需要调度 pod 时,调度程序都会运行。调度程序必须知道可用的总资源以及分配给每个节点上现有工作负载的资源。调度决策考虑的因素包括单个 Pod 和 Pod 集合的资源需求、硬件/软件/策略约束、亲和性和反亲和性规范、数据位置、工作负载间的干扰和最后时限。调度总共分为三个阶段:Predict:预选阶段,过滤不能满足业务需求的节点Priority:优选阶段,选择最优的节点Bind:绑定阶段,将最优节点和Pod进行绑定,完成调度KubeletKubelet是每个节点上的核心组件之一,负责管理节点的资源对象。从不同源获取Pod清单,并按需启停PodPod清单可以来自本地文件目录、给定的Http Server、API Server等Kubelet将Container Runtime、Network、Stroage抽象成CRI、CNI、CSI负责汇报节点的健康状态以及资源信息负责Pod的健康检查和状态汇报Kube-proxykube-proxy 是集群中每个节点上运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。用于处理单个主机子网划分并向外部世界公开服务。它跨集群中的各种隔离网络将请求转发到正确的 pod/容器。kube-proxy 维护节点上的网络规则。这些网络规则允许从集群内部或外部的网络会话与 Pod 进行网络通信。如果操作系统提供了数据包过滤层并可用的话,kube-proxy 会通过它来实现网络规则。否则, kube-proxy 仅转发流量本身。其他组件上面介绍的这些组件是集群的架子,光有架子还不够,还需要第三方的组件让其更强大:kube-dns:负责为整个集群提供DNS服务,常用的是CoreDNSIngress Controller:为集群提供外网访问入口Metrics-Server:为集群提供监控资源DashBoard:提供GUI,方便运维Prometheus:收集并监控集群资源Grafana:图形化展示监控数据ELK:收集、存储、查询集群日志最后,求关注。如果你还想看更多优质原创文章,欢迎关注我
文章
存储  ·  Kubernetes  ·  监控  ·  Cloud Native  ·  安全  ·  API  ·  调度  ·  数据库  ·  容器  ·  Perl
2022-08-06
【K8S专栏】Docker容器技术剖析(二)
MNT Namespace虽然我们已经通过 Linux 的命名空间解决了进程和网络隔离的问题,在 Docker 进程中我们已经没有办法访问宿主机器上的其他进程并且限制了网络的访问,但是 Docker 容器中的进程仍然能够访问或者修改宿主机器上的其他目录,这是我们不希望看到的。在新的进程中创建隔离的挂载点命名空间需要在 clone 函数中传入 CLONE_NEWNS,这样子进程就能得到父进程挂载点的拷贝,如果不传入这个参数子进程对文件系统的读写都会同步回父进程以及整个主机的文件系统。如果一个容器需要启动,那么它一定需要提供一个根文件系统(rootfs),容器需要使用这个文件系统来创建一个新的进程,所有二进制的执行都必须在这个根文件系统中。?想要正常启动一个容器就需要在 rootfs 中挂载以上的几个特定的目录,除了上述的几个目录需要挂载之外我们还需要建立一些符号链接保证系统 IO 不会出现问题。为了保证当前的容器进程没有办法访问宿主机器上其他目录,我们在这里还需要通过 libcotainer 提供的 pivor_root 或者 chroot 函数改变进程能够访问个文件目录的根节点。到这里我们就将容器需要的目录挂载到了容器中,同时也禁止当前的容器进程访问宿主机器上的其他目录,保证了不同文件系统的隔离。总之,mnt namespace允许不同的namespace看到的文件结构不同,这样每个namespace中的进程所看到的文件目录就被隔离开了。IPC NamespaceIPC Namespace主要实现进程间通信隔离。进程间通信涉及的IPC资源包括常见的信号量、消息队列和共享内存。申请IPC就是申请一个全局的32位ID,所以IPC Namespace中实际上包含了系统IPC标识符和实现消息队列的文件系统。容器的进程间交互依然是采用Linux常见的交互方法,所以每个容器就需要独立的IPC标识符,所以在容器创建的时候就要传入CLONE_NEWIPC 参数,实现IPC资源隔离。UTS NamespaceUTS(UNIX Time-sharing System)namespace提供了主机名与域名的隔离,这样每个docke容器就可以拥有独立的主机名和域名了,在网络上可以被视为一个独立的节点,而非宿主机上的一个进程。docker中,每个镜像基本都以自身所提供的服务名称来命名镜像的hostname,且不会对宿主机产生任何影响,其原理就是使用了UTS namespaceUSER Namespaceuser namespace主要隔离了安全相关的标识符(identifier)和属性(attribute),包括用户ID、用户组ID、root目录、key(指密钥)以及特殊权限。通俗地讲,一个普通用户的进程通过clone()创建的新进程在新user namespace中可以拥有不同的用户和用户组。这意味着一个进程在容器外属于一个没有特权的普通用户,但是它创建的容器进程却属于拥有所有权限的超级用户,这个技术为容器提供了极大的自由。user namespace时目前的6个namespace中最后一个支持的,并且直到linux内核3.8版本的时候还未完全实现(还有部分文件系统不支持)。user namespace实际上并不算完全成熟,很多发行版担心安全问题,在编译内核的时候并未开启USER_NS。Docker在1.10版本中对user namespace进行了支持。只要用户在启动Docker daemon的时候制定了–user-remap,那么当用户运行容器时,容器内部的root用户并不等于宿主机的root用户,而是映射到宿主机上的普通用户。Docker不仅使用了user namespace,还使用了在user namespace中涉及的Capability机制。从内核2.2版本开始,Linux把原来和超级用户相关的高级权限分为不同的单元,称为Capability。这样管理员就可以独立的对特定的Capability进行使用或禁止。Docker同时使用namespace和Capability,这很大程度上加强了容器的安全性。CGroupsDocker通过Linux Namespace实现了进程、文件系统、网络等隔离,但是Namespace并不能为其提供物理资源的隔离,比如CPU、Memory等。如果其中的某一个容器正在执行 CPU 密集型的任务,那么就会影响其他容器中任务的性能与执行效率,导致多个容器相互影响并且抢占资源。如何对多个容器的资源使用进行限制就成了解决进程虚拟资源隔离之后的主要问题,而 Control Groups(简称 CGroups)就是能够隔离宿主机器上的物理资源,例如 CPU、内存等。?每一个CGroup都是一组被相同的标准和参数限制的进程,不同的 CGroup 之间是有层级关系的,也就是说它们之间可以从父类继承一些用于限制资源使用的标准和参数。Linux 的 CGroup 能够为一组进程分配资源,也就是我们在上面提到的 CPU、内存、网络带宽等资源,通过对资源的分配。Linux 使用文件系统来实现 CGroup,我们可以直接使用下面的命令查看当前的 CGroup 中有哪些子系统:如果没有lssubsys命令,CentOS可以通过“yum install libcgroup-tools”命令安装。# lssubsys -m cpuset /sys/fs/cgroup/cpuset cpu,cpuacct /sys/fs/cgroup/cpu,cpuacct memory /sys/fs/cgroup/memory devices /sys/fs/cgroup/devices freezer /sys/fs/cgroup/freezer net_cls,net_prio /sys/fs/cgroup/net_cls,net_prio blkio /sys/fs/cgroup/blkio perf_event /sys/fs/cgroup/perf_event hugetlb /sys/fs/cgroup/hugetlb pids /sys/fs/cgroup/pidscpuset:如果是多核心CPU,这个子系统会为CGroup任务分配单独的CPUcpu:使用调度程序为CGroup提供CPU访问cpuacct:产生CGroup任务的CPU资源报告memory:设置每个CPU的内存限制以及产生内存资源报告devices:允许或拒绝CGroup任务对设备的访问freezer:暂停或恢复CGroup任务net_cls:标记每个网络包以供CGroup使用net_prio:针对每个网络接口设置cgroup的访问优先级blkio:设置限制每个块的输入输出,例如磁盘、光盘以及USB等perf_event:对CGroup进行性能监控hugetlb:限制cgroup的huge pages的使用量pids:限制一个cgroup及其子孙cgroup中的总进程数大多数 Linux 的发行版都有着非常相似的子系统,而之所以将上面的 cpuset、cpu 等东西称作子系统,是因为它们能够为对应的控制组分配资源并限制资源的使用。CPU子系统CPU子系统下主要的文件如下(可以通过ls /sys/fs/cgroup/cpu查看):文件功能cpu.shares顾名思义,shares=分享。它的工作原理非常类似于进程的nice值。shares就代表软限。cpu.cfs_period_us执行检测的周期,默认是100mscpu.cfs_quota_us在一个检测周期内,容器能使用cpu的最大时间,该值就是硬限,默认是-1,即不设置硬限cpu.state容器的状态:一共运行了多少个周期;一共被throttle了多少次;一共被throttle了多少时间cpu.rt_period_us执行检测的周期,默认是1scpu.rt_runtime_us在一个检测周期内,能使用的cpu最大时间,只作用于rt任务从上面的文件和功能可以看出,CPU的限制有软限制和硬限制。软限制软限制是通过设置cpu.shares来实现的。比如说现在有两个容器,但是只有1颗CPU。当给A容器的cpu.shares配置为512,给B容器的cpu.shares配置为1024,这表示A和B容器使用CPU的时间片比例为1:2,也就是A能用33%的时间片,B能用66%的时间片。如果把A的shares值改成1024,则A和B的时间片比例就变成了1:1。cpu.shares有两个特点:如果A不忙,没有使用到66%的CPU时间,那么剩余的CPU时间将会被系统分配给B,即B的CPU使用率可以超过33%如果添加了一个新的cgroup C,且它的shares值是1024,那么A的限额变成了1024/(1204+512+1024)=40%,B的变成了20%由此可以看出:在闲的时候,shares基本上不起作用,只有在CPU忙的时候起作用,这是一个优点。由于shares是一个绝对值,需要和其它cgroup的值进行比较才能得到自己的相对限额,而在一个部署很多容器的机器上,cgroup的数量是变化的,所以这个限额也是变化的,自己设置了一个高的值,但别人可能设置了一个更高的值,所以这个功能没法精确的控制CPU使用率。硬限制顾名思义,硬限制就是给你设置一个上限,你永远不能超过这个上限。硬限制由两个参数控制:cpu.cfs_period_us:用来配置时间周期长度,单位微秒,默认是100000微妙。cpu.cfs_quota_us:用来配置当前cgroup在设置的周期长度内所能使用的CPU时间数,单位微秒,取值大于1ms,-1代表不受限制。如果给cpu.cfs_quota_us设置为100000微秒,则表示限制只能用1个CPU,如果设置为50000微秒,则表示限制只能用0.5个CPU。CPU核数=cpu.cfs_quota_us / cpu.cfs_period_us。可以通过一个小实验来直观感受一下。# 创建工程 mkdir busyloop cd busyloop go mod init busyloop开发一个简单的代码package main func main(){ for{} }这个代码会启动一个线程,默认也就占用一个CPU。然后启动项目go build ./busyloop使用top命令可以看到使用了一个CPU。现在我们开始对其进行限制。# 进入cpu 子系统 cd /sys/fs/cgroup/cpu # 创建一个busyloop的目录 mkdir busyloop # 该目录下会自动生成如下文件 ll total 0 -rw-r--r-- 1 root root 0 Jun 9 17:19 cgroup.clone_children --w--w--w- 1 root root 0 Jun 9 17:19 cgroup.event_control -rw-r--r-- 1 root root 0 Jun 9 17:19 cgroup.procs -r--r--r-- 1 root root 0 Jun 9 17:19 cpuacct.stat -rw-r--r-- 1 root root 0 Jun 9 17:19 cpuacct.usage -r--r--r-- 1 root root 0 Jun 9 17:19 cpuacct.usage_percpu -rw-r--r-- 1 root root 0 Jun 9 17:19 cpu.cfs_period_us -rw-r--r-- 1 root root 0 Jun 9 17:19 cpu.cfs_quota_us -rw-r--r-- 1 root root 0 Jun 9 17:19 cpu.rt_period_us -rw-r--r-- 1 root root 0 Jun 9 17:19 cpu.rt_runtime_us -rw-r--r-- 1 root root 0 Jun 9 17:19 cpu.shares -r--r--r-- 1 root root 0 Jun 9 17:19 cpu.stat -rw-r--r-- 1 root root 0 Jun 9 17:19 notify_on_release -rw-r--r-- 1 root root 0 Jun 9 17:19 tasks # 首先在cgroup.procs中记录busyloop的进程 echo 13817 > cgroup.procs # 这时候还并为做任何限制,所以CPU使用还是1颗 # 给cpu.cfs_quota_us配置50000微妙,也就是使用0.5个CPU echo 50000 > cpu.cfs_quota_us这时候通过top命令可以看到busyloop进程使用0.5个CPU。Memory子系统Memory子系统用来限制内存的,常用的文件如下。文件功能memory.usage_in_bytescgroup下进程使用的内存memory.max_usage_in_bytescgroup下进程使用内存的最大值memory.limit_in_bytes设置cgroup下进程最多能使用的内存memory.soft_limit_in_bytes这个限制并不会阻止进程使用超过限额的内存,只是在系统内存足够时,会优先回收超过限额的内存,使之向限定值靠拢memory.oom_control设置是否在 Cgroup 中使用 OOM(Out of Memory)Killer,默认为使用。当属于该 cgroup 的进程使用的内存超过最大的限定值时, 会立刻被 OOM Killer 处理。Memory子系统和CPU子系统类似,在配置的时候也是先找到对应的进程,然后在对应的文件众配置额度,这里就不再赘述。补充:Linux进程调度Linux Kernel默认提供了5个调度器,Linux Kernel使用struct_sched_class来对调度器进行抽象。Stop调度器:stop_sched_class,优先级最高的调度类,可以抢占其他所有进程,不能被其他进程抢占。Deadline调度器:dl_sched_class,使用红黑树,把进程按绝对截止期限进行排序,选择最小进程进行调度允许。RT调度器:rt_sched_class,实时调度器,为每个优先级维护一个队列。CFS调度器:cfs_sched_class,完全公平调度器,采用完全公平调度算法,引入虚拟运行时间的概念。IDLE-TASK调度器:idle_sched_class,空闲调度器,每个CPU都有一个idle线程,当没有其他进程可以调度时,运行idle线程。CFS调度器CFS调度,是Completely Fair Scheduler的简称,即完全公平调度器。CFS实现的主要思想是维护为任务提供处理器时间方面的平衡,这意味着应给进程分配相当数量的处理器。当分给某个任务的时间失去平衡时,应该给失去平衡的任务分配时间,让其运行。CFS通过虚拟运行时间(vruntime)来维护平衡,维护提供给某个任务的时间量。vruntime = 实际运行时间 * 1024 / 进程权重进程按照各自不同的速率在物理时钟节拍里前进,优先级高则权重大,其虚拟时钟比真实时钟跑的慢,但获得比较多的运行时间。CFS调度器没有将进程维护在运行队列中,而是维护了一个以虚拟时间为顺序的红黑树。红黑树主要特点有两个:自平衡,树上没有一条路径会比其他路径长出两倍O(log n) 时间复杂度,能够在树上进行快速高效地插入或删除进程。UnionFSUnionFS是Union File System的简称,也就是联合文件系统。所谓UnionFS就是把不同物理位置的目录合并mount到同一个目录中,然后形成一个虚拟的文件系统。一个最典型的应用就是将一张CD/DVD和一个硬盘的目录联合mount在一起,然后用户就可以对这个只读的CD/DVD进行修改了。Docker就是充分利用UnionFS技术,将镜像设计成分层存储,现在使用的就是OverlayFS文件系统,它是众多UnionFS中的一种。OverlayFS只有lower和upper两层。顾名思义,upper层在上面,lower层在下面,upper层的优先级高于lower层。在使用mount挂载overlay文件系统的时候,遵守以下规则。lower和upper两个目录存在同名文件时,lower的文件将会被隐藏,用户只能看到upper的文件。lower低优先级的同目录同名文件将会被隐藏。如果存在同名目录,那么lower和upper目录中的内容将会合并。当用户修改merge中来自upper的数据时,数据将直接写入upper中原来目录中,删除文件也同理。当用户修改merge中来自lower的数据时,lower中内容均不会发生任何改变。因为lower是只读的,用户想修改来自lower数据时,overlayfs会首先拷贝一份lower中文件副本到upper中。后续修改或删除将会在upper下的副本中进行,lower中原文件将会被隐藏。如果某一个目录单纯来自lower或者lower和upper合并,默认无法进行rename系统调用。但是可以通过mv重命名。如果要支持rename,需要CONFIG_OVERLAY_FS_REDIRECT_DIR。下面以OverlayFS为例,直面感受一下这种文件系统的效果。系统:CentOS 7.9 Kernel:3.10.0(1)创建两个目录lower、upper、merge、work四个目录# # mkdir lower upper work merge其中:lower目录用于存放lower层文件upper目录用于存放upper层文件work目录用于存放临时或者间接文件merge目录就是挂载目录(2)在lower和upper两个目录中都放入一些文件,如下:# echo "From lower." > lower/common-file # echo "From upper." > upper/common-file # echo "From lower." > lower/lower-file # echo "From upper." > upper/upper-file # tree . ├── lower │ ├── common-file │ └── lower-file ├── merge ├── upper │ ├── common-file │ └── upper-file └── work可以看到lower和upper目录中有相同名字的文件common-file,但是他们的内容不一样。(3)将这两个目录进行挂载,命令如下:# mount -t overlay -o lowerdir=lower,upperdir=upper,workdir=work overlay merge挂载的结果如下:# tree . ├── lower │ ├── common-file │ └── lower-file ├── merge │ ├── common-file │ ├── lower-file │ └── upper-file ├── upper │ ├── common-file │ └── upper-file └── work └── work # cat merge/common-file From upper.可以看到两者共同目录common-dir内容进行了合并,重复文件common-file为uppderdir中的common-file。(4)在merge目录中创建一个文件,查看效果# echo "Add file from merge" > merge/merge-file # tree . ├── lower │ ├── common-file │ └── lower-file ├── merge │ ├── common-file │ ├── lower-file │ ├── merge-file │ └── upper-file ├── upper │ ├── common-file │ ├── merge-file │ └── upper-file └── work └── work可以看到lower层没有变化,新增的文件会新增到upper层。(5)修改merge层的lower-file,效果如下# echo "update lower file from merge" > merge/lower-file # tree . ├── lower │ ├── common-file │ └── lower-file ├── merge │ ├── common-file │ ├── lower-file │ ├── merge-file │ └── upper-file ├── upper │ ├── common-file │ ├── lower-file │ ├── merge-file │ └── upper-file └── work └── work # cat upper/lower-file update lower file from merge # cat lower/lower-file From lower.可以看到lower层同样没有变化,所有的修改都发生在upper层。从上面的实验就可以看到比较有意思的一点:不论上层怎么变,底层都不会变。Docker镜像怎么实现的Docker镜像就是存在联合文件系统的,在构建镜像的时候,会一层一层的向上叠加,每一层构建完就不会再改变了,后一层上的任何改变都只会发生在自己的这一层,不会影响前面的镜像层。我们通过一个例子来进行阐述,如下图。具体如下:基础L1层有file1和file2两个文件,这两个文件都有具体的内容。到L2层的时候需要修改file2的文件内容并且增加file3文件。在修改file2文件的时候,系统会先判定这个文件在L1层有没有,从上图可知L1层是有file2文件,这时候就会把file2复制一份到L2层,然后修改L2层的file2文件,这就是用到了联合文件系统写时复制机制,新增文件也是一样。到L3层修改file3的时候也会使用写时复制机制,从L2层拷贝file3到L3层 ,然后进行修改。然后我们在视图层看到的file1、file2、file3都是最新的文件。上面的镜像层是死的。当我们运行容器的时候,Docker Daemon还会动态生成一个读写层,用于修改容器里的文件,如下图。比如我们要修改file2,就会使用写时复制机制将file2复制到读写层,然后进行修改。同样,在容器运行的时候也会有一个视图,当我们把容器停掉以后,视图层就没了,但是读写层依然保留,当我们下次再启动容器的时候,还可以看到上次的修改。值得一提的是,当我们在删除某个文件的时候,其实并不是真的删除,只是将其标记为删除然后隐藏掉,虽然我们看不到这个文件,实际上这个文件会一直跟随镜像。Docker的常见操作Docker现在越来越下沉,甚至很多用户不再使用Docker,在以Kubernetes为中心的容器服务中,Docker不再是必要的选择。但是作为一款有时代意义的产品,Docker的基本操作对于技术人员来说还是有必要学习和了解的。Docker的常见指令Docker分为客户端和服务端,这些常见指令是针对客户端的。Docker客户端的指令有很多,可以通过docker -h来查看,这里只介绍一些比较常用的指令。docker builddocker psdocker pulldocker pushdocker imagedocker logindocker logsdocker execdocker versionDocker镜像的最佳实践认识DockerfileDocker的镜像是通过Dockerfile构建出来的,所以Dockerfile的操作是很重要的 。先通过一个例子来看看Dockerfile是什么样子。FROM docker.io/centos LABEL "auth"="joker" \ "mail"="unclejoker520@163.com" ENV TIME_ZOME Asia/Shanghai RUN yum install -y gcc gcc-c++ make openssl-devel prce-devel ADD nginx-1.14.2.tar.gz /opt/ RUN cd /opt/nginx-1.14.2 && \ ./configure --prefix=/usr/local/nginx && \ make -j 4 && \ make install RUN rm -rf /opt/nginx* && \ yum clean all && \ echo "${TIME_ZOME}" > /etc/timezone && \ ln -sf /usr/share/zoneinfo/${TIME_ZOME} /etc/localtime COPY nginx.conf /usr/local/nginx/conf/ WORKDIR /usr/local/nginx/ EXPOSE 80 CMD ["./sbin/nginx","-g","daemon off;"]其中FROM指令必须是开篇第一个非注释行,是必须存在的一个指令,后面所有的操作都是基于这个镜像的。后面的指令就是一些操作指令,指令的详情在后面介绍。最后是CMD指定,这个指令表示在容器运行是需要执行的命令。当定义好Dockerfile,然后使用docker build命令就可以将起构建成Docker镜像。Dockerfile常用指令指令说明FROM指定基础镜像LABEL指定标签COPY复制文件到镜像中ADD添加文件到镜像中,如果是压缩文件会自动解压WORKDIR指定工作目录,进入容器的时候默认就在工作目录中ENV指定环境变量RUN指定运行的命令ENTRYPOINT指定容器启动时运行的命令,可以接CMD命令CMD指定容器启动时运行的命令,可被覆盖ARG指定参数,一般在Build的时候使用EXPOSE暴露容器端口VOLUME在容器中创建挂载点USER指定运行用户最佳实践只会Dockerfile的命令是不够的,有时候你会发现为什么别人的镜像那么小,为什么别人构建镜像那么快。这其中是有一些技巧的。优化构建上下文什么是构建上下文?当执行docker build的时候,执行该命令所在的工作目录就是构建上下文。为什么要优化构建上下文呢?当执行docker build构建镜像的时候,会把当前工作目录下的所有东西都加载到docker daemon中,如果没有对上下文进行优化,可能导致构建时间长,构建所需资源多,构建镜像大等问题。应该如何优化呢?1、创建单独的目录存放Dockerfile,保持该目录整洁干净。2、如果没有办法把Dockerfile单独存放到某个目录,可以通过在Dockerfile所在目录中添加.dockeringnore文件,在该文件中把不需要的文件填写进去,这样在加载上下文的时候就会把这些文件排除出去。合理利用缓存docker在构建镜像的时候,会依次读取Dockerfile中的指令并按顺序依次执行。在读取指令的过程中,会去判断缓存中是否有已存在的镜像,如果存在就不会再执行构建,而是直接使用缓存,这样会加快构建速度。合理利用缓存,可以加快构建速度,所以在编写Dockerfile的时候把不会改变的指令放到前面,让起尽可能的使用到缓存。注意:如果某一层得缓存失效,后续的所有缓存都会失效。上图Dockerfile1和Dockerfile2分别为app1和app2制作镜像,通过这两个Dockerfile来介绍一下缓存是怎么用的。(1)FROM 指令代表的是基础镜像,app1和app2都是用的同一个,所以在打包的时候,如果本地存在该镜像,就不会到dockerhub上拉取了。(2)RUN指令执行的是定义的命令,docker会对比命令是否一样,如果一样就直接使用缓存。(3)ADD指令是拷贝用户文件到镜像中,docker会判断该镜像每一个文件的内容并生成一个checksum,与现存镜像进行比较,如果checksum一致则使用缓存,否则缓存就失效。合理的优化镜像体积Docker的镜像是会在服务器与服务器之间、服务器和镜像仓库之间来回传递,如果镜像太大不仅影响传输速度、还占用服务器主机资源,所以合理的优化镜像体积有助于提升效率。目前可以通过以下方法来优化镜像体积:(1)选用较小的基础镜像(2)删除不必要的软件包合理优化镜像层数镜像的层级越多,镜像的体积相对来说也会越大,而且项目的可维护性越低,目前Docker只有RUN、ADD、COPY这三个命令会创建层级,所以优化镜像层数主要是合理使用这三个命令。目前通用的解决办法是:(1)合并命令。如果一个Dokcerfile中有多个RUN命令,可以将它们合并成一个RUN。(2)使用多阶段构建,减少不必要的层级。合理选择初始化进程如果一个镜像无法避免使用多进程,那么就应该合理的选择初始化进程。初始化进程有有以下要求:能够捕获SIGTERM信号,并完成子进程的优雅终止能够完成子进程的清退,避免产生僵尸进程tini项目可以解决上面的问题,在选择初始化进程的时候可以考虑一下。ContainerdContainerd是从Docker中分离的一个项目,旨在为Kubernetes提供容器运行时,负责管理镜像和容器的生命周期。在kubernetes1.20后会逐步移除docker,不过现在docker和containerd都可以同时为Kubernetes提供运行时。如果是docker作为容器运行时,则调用关系是kubelet-->docker-shim-->dockerd-->containerd如果是containerd作为容器运行时,则调用关系是kubelet-->cri-plugin-->containerd可以看出containerd的调用链路比docker要短,但是相对的功能没有docker丰富。下面列举Containerd和Docker的命令差别,其他的其实用到的也不多,基本都是Kubernetes自己去调用。镜像相关镜像相关功能DockerContainerd显示本地镜像列表docker imagescrictl images下载镜像docker pullcrictl pull上传镜像docker push无删除本地镜像docker rmicrictl rmi查看镜像详情docker inspect IMAGE-IDcrictl inspecti IMAGE-ID容器相关容器相关功能DockerContainerd显示容器列表docker pscrictl ps创建容器docker createcrictl create启动容器docker startcrictl start停止容器docker stopcrictl stop删除容器docker rmcrictl rm查看容器详情docker inspectcrictl inspectattachdocker attachcrictl attachexecdocker execcrictl execlogsdocker logscrictl logsstatsdocker statscrictl statsPod相关POD 相关功能DockerContainerd显示 POD 列表无crictl pods查看 POD 详情无crictl inspectp运行 POD无crictl runp停止 POD无crictl stopp总结Docker公司虽然没有在最后的决战中胜出,但是Docker产品不失为一个好产品,它在整个云原生领域有着举足轻重的作用。(1)它做到了镜像一次编译,随时使用(2)可以一键启动依赖服务,搭建环境的成本降低(3)可以保持所有环境高度一致 (4)它可以达到秒级启动但是Docker也有其与生俱来的缺点,比如由于主机上的容器都是共用的底层操作系统,其隔离性不如真正的硬件隔离,而且随着并发的不断增大,也会因一些网络连接和数据交互等问题产生性能瓶颈。而且随着容器的不断发展,越来越多的容器运行时诞生,Docker在整个领域的地位会不断下降,它不再是大家必须的选择。
文章
消息中间件  ·  缓存  ·  Kubernetes  ·  Cloud Native  ·  Linux  ·  调度  ·  数据中心  ·  Docker  ·  容器  ·  Perl
2022-08-06
1 2 3 4 5 6 7 8 9
...
20
跳转至:
阿里云微服务引擎 MSE
7 人关注 | 0 讨论 | 49 内容
+ 订阅
  • 微服务治理热门技术揭秘:无损上线
  • 阿里云微服务引擎 MSE 2022年 7月份产品动态
  • 私有化输出的服务网格我们是这样做的
查看更多 >
开发与运维
5304 人关注 | 127112 讨论 | 211351 内容
+ 订阅
  • 关于自己初次使用云服务器的经验总结
  • 注解案例--简单的测试框架
  • ECS使用体验
查看更多 >
云原生
230908 人关注 | 10192 讨论 | 31515 内容
+ 订阅
  • Java架构
  • PAI Designer Python脚本V2组件使用异常临时解决方案
  • 飞天加速计划ESC使用体验
查看更多 >
数据库
249864 人关注 | 45643 讨论 | 66879 内容
+ 订阅
  • 关于自己初次使用云服务器的经验总结
  • MySQL基本查询和运算符
  • MySQL约束
查看更多 >
微服务
22889 人关注 | 10270 讨论 | 22852 内容
+ 订阅
  • 大一新生先学C语言编程还是先学C语言的数据结构和算法?
  • Linux系统编程-进程间通信(消息队列)
  • Monorepo与multirepo区别何在?
查看更多 >