GO面试要点笔记(持续更新)

第一周

image-20260620101531624

1.问题回答

1. 什么是闭包?闭包有什么缺陷?

  • 什么是闭包:闭包是指一个函数(在 Go 中通常指匿名函数)引用了其外部作用域中的变量。即使该外部函数已经返回,这个匿名函数依然可以访问并修改这些外部变量。
  • 缺陷(注意事项)
    1. 内存泄漏:如果闭包长期存活(例如作为全局变量或长期运行的协程),其捕获的外部变量将一直无法被 GC 回收,导致内存占用过高。
    2. 共享变量副作用:在循环中使用闭包非常容易出错。例如 for i:=0; i<10; i++ { go func() { println(i) }() } 会打印多个 10,因为所有闭包共享了同一个变量 i
    3. 性能开销:闭包本质上是通过指针访问外部变量,底层涉及逃逸分析,有一定的性能开销。

2. 什么情况下会出现栈溢出?

  • 无限递归:函数直接或间接地无限调用自身,导致调用栈无限堆积。
  • 分配过大的局部变量:在函数内部声明了非常大且不能逃逸到堆上的数组或结构体,导致单个栈帧过大。
  • Go 特定情况:Go 的协程(Goroutine)初始栈很小(约 2KB),但会自动扩容。虽然 Go 的栈是动态调整的,但如果递归极深或单帧极大,导致扩容申请失败时,依然会出现 stack overflow 导致程序崩溃。

3. 什么是不定参数?调用时能传 0 个值吗?方法内部怎么使用?

  • 什么是不定参数:函数最后一个参数前加上 ... 符号(如 func Sum(nums ...int)),表示该参数可接受 0 个或多个同类型的参数。
  • 能否传 0 个值可以。如果不传值,在函数内部得到的 nums 会是一个空切片(长度为 0,容量为 0)。
  • 内部使用方法:在方法内部,不定参数实际上是被作为一个切片[]T)来处理的。你可以使用 range 遍历它,也可以直接进行切片操作。如果你需要将其传给另一个不定参数函数,需要采用 nums... 的形式来解包。

4. 什么是 defer?你能解释一下 defer 的运作机制吗?

  • 什么是 deferdefer 语句用于延迟执行一个函数调用,被推迟的函数会在所在函数返回之前(无论是否发生 panic)执行。
  • 运作机制
    1. 后进先出(LIFO):如果函数内有多个 defer,它们按照声明的相反顺序执行(类似栈)。
    2. 参数立即求值defer 后面的函数参数(除了函数体内部的变量)会在声明 defer 的那一刻立即求值并拷贝保存,而不是在 defer 真正执行时才去求值。
    3. 数据结构:在 Go 的运行时中,defer 会被串联成一个链表挂在 Goroutine 结构体上。编译时会根据情况选择不同的实现(如堆分配、栈分配或开放编码)。

5. 一个方法内部 defer 能不能超过 8 个?

  • 。语法和运行机制上没有硬性代码限制,你可以写 8 个以上甚至 100 个。
  • 深入理解(性能优化):这个问题通常考察 Go 1.14 之后的编译优化。在 Go 1.14+ 中,如果 defer 数量不超过 8 个,且不是在循环中调用,编译器会采用**“开放编码” (Open-Coded Defer)**,这是一种非常高效的内联实现。一旦超过 8 个,defer 就会在运行时退化为栈或堆上的链表执行,性能会有所下降,但功能完全正常。

6. defer 内部能不能修改返回值?怎么改?

  • 能修改,但取决于返回值的声明方式
  • 情况 1(匿名返回值):如果函数返回值是匿名的(如 func f() int),defer 无法直接修改返回值(因为匿名返回值在函数栈中,defer 无法拿到它的地址)。除非你在 defer 里操作的是全局变量或指针类型的返回值。
  • 情况 2(具名返回值):如果函数返回值是具名的(如 func f() (result int)),defer 可以直接修改 result 的值,因为 defer 可以访问到该命名返回变量的内存地址。最终返回的是 defer 修改后的值。

7. 数组和切片有什么区别?

  • 长度
    • 数组:长度是固定的,且是数组类型定义的一部分(如 [5]int[10]int 是不同类型)。
    • 切片:长度是动态的,可以扩容。
  • 传参
    • 数组:作为参数传递时是值拷贝(整个数组都会复制一份)。
    • 切片:作为参数传递时是引用传递(传递的是指向底层数组的指针、长度和容量的结构体副本)。
  • 适用场景:底层存储为数组,日常开发中极少使用数组作为参数传递,绝大多数情况使用切片。

8. 切片怎么扩容的?

使用 append 函数向切片追加元素时,如果切片的容量(cap)不足以容纳新元素,就会触发扩容机制。

  • 扩容策略
    1. 需求容量:如果追加后的新长度 > 当前容量的 2 倍,则新容量直接设为新长度
    2. 2倍扩容:如果当前切片长度小于 1024,则新容量翻倍(cap * 2)。
    3. 1.25倍扩容:如果当前切片长度大于等于 1024,则新容量增长为原来的 1.25 倍
  • 补充细节:扩容并不是仅仅数学上乘以一个倍数,Go 在确定上述估算值后,还会进行内存对齐操作,对最终分配的内存块大小进行向上取整,以节省内存并适应计算机的缓存和内存分配策略。
  • 注意:扩容会重新分配底层数组。如果发生扩容,新切片和旧切片将不再共享底层数组

2.defer相关

image-20260620103744006

image-20260620104055822

image-20260620104156602

image-20260620104355535

3.切片扩容

image-20260620104413752

image-20260620104503538

回来看的时候记得看一下最新版本的go是怎么扩容的,就看看还是不是256,1.25之类的,这个经常改动

第二周

image-20260621105238593

image-20260621105252404

这张幻灯片里提到的三个问题,确实是 Gin 面试中最基础、最容易被考察的核心知识点。我帮你把这三个问题,连同底部那条“黄金面试建议”一并详细解答出来:


1. 什么是 Gin 的 middleware?能用来解决什么问题?

(核心:拦截器)

  • 定义:Middleware(中间件)本质上是一个函数,它在 HTTP 请求到达具体的业务处理函数(Handler)之前,以及处理完业务返回响应之后,插入执行的一段逻辑。它组成了一个“洋葱圈”的调用链。
  • 能解决的问题(应用场景)
    • 全局限流与防护:限制某个 IP 的请求频率,防止 DDOS 攻击。
    • 鉴权与认证:统一校验请求头里的 Token 或 Cookie,如果没登录,直接在中间件里拦截并返回 401,不让请求触达真实的业务接口。
    • 请求日志与追踪:记录每一个接口的耗时、请求参数、返回状态码,便于排查线上问题。
    • 统一异常处理:捕获业务代码中可能发生的 panic,防止服务直接崩溃,并返回统一的错误格式给前端。
    • 跨域处理(CORS):下面要讲到的跨域问题,通常就是通过中间件解决的。
  • 在 Gin 中怎么用:非常简单,直接调用 r.Use(MyMiddleware()) 就可以把中间件挂载到全局或特定的路由组上。

2. 什么是跨域问题,怎么解决?

(核心:浏览器同源策略)

  • 定义:为了安全,浏览器有一个同源策略。如果你的前端运行在 http://localhost:8080,但你请求的后端接口在 http://localhost:3000(只要协议、域名、端口有一个不同,就算跨域),浏览器就会拦下这个请求,直接报错。
  • 怎么解决(三种主要方式)
    1. 后端加 CORS 响应头(最常用):让后端告诉浏览器:“我是值得信任的,我允许前端跨域来访问我”。在 Gin 中,最推荐的做法是直接使用官方的 gin-contrib/cors 中间件包,配置极简。
    2. 前端代理:在开发阶段,使用 Webpack、Vite 等前端构建工具配置 proxy 代理,把对前端的请求转发给后端,因为“服务器与服务器之间”是没有跨域限制的。
    3. Nginx 反向代理:在生产环境,把前端和后端托管在同一个域名的不同路径下,比如 www.example.com/api 和后端,www.example.com 和前端,通过 Nginx 代理消除跨域。

3. 跨域问题需要设置哪些头部?

在 Gin 后端(或其他语言后端)解决跨域时,实质上就是在 HTTP 响应头里添加以下关键字段

  • Access-Control-Allow-Origin必须):明确告诉浏览器允许哪些域名来跨域访问。如果前端地址不固定,通常可以读取请求头里的 Origin 动态返回。注意:如果包含 Authorization 头部,这里不能写 *,必须写死具体的域名。
  • Access-Control-Allow-Methods:允许前端的请求方法,比如 GET, POST, PUT, DELETE, OPTIONS
  • Access-Control-Allow-Headers:允许前端在请求头里携带自定义的 Header,例如 Content-Type, Authorization, X-Requested-With 等。
  • Access-Control-Allow-Credentials(可选):如果前端请求携带了 Cookie,需要把这个值设为 true
  • Access-Control-Max-Age(预检缓存):浏览器在发出复杂请求前,会先发一个 OPTIONS 预检请求。设置这个头部(例如 86400 秒),可以让浏览器在一天内无需再次发送 OPTIONS 探测,直接发送真实请求,能显著提升性能。

额外补充:幻灯片底部的“黄金面试建议”

在 Gin 面试的时候,一定要提起自己研发了一个强大 Gin 插件库”,这句话非常有深意。它想考察的是你有没有读懂 Gin 的底层设计思想

你可以这么说:

“除了会使用中间件外,我对 Gin 的洋葱模型(Next() 机制)有深入的理解。基于这种机制,我在工作中提取并封装了一套公司内部通用的 Gin 插件库。比如:

  1. 基于 Redis + 令牌桶算法实现的全局限流中间件
  2. 统一处理 panic 和业务异常,将 error 转换为标准 JSON 结构体返回的中间件;
  3. 基于 pprof 做了个自定义的性能监控中间件,并且给公司内部的其他 Go 微服务框架复用。

熟练封装中间件的能力,能体现出我对 Gin 底层链式调用源码的理解。”

这样回答,展现了具备模块化封装、业务落地、和源码深度的能力,会是非常大的加分项!

image-20260625093738978

1. 什么是 Cookie,什么是 Session?

  • Cookie:是**客户端(浏览器)**保存的一小段数据(大小通常限制在 4KB)。服务器通过 HTTP 响应头 Set-Cookie 将其发送给浏览器,浏览器会在下次同源请求时自动携带该数据。常用于保存 Session ID 或用户偏好设置。
  • Session:是服务端保存的用户状态数据。当用户登录成功后,服务器会生成一个唯一的 Session ID,并将用户信息(如 UserID、过期时间)存放到服务端的存储中(如内存、Redis 或数据库)。服务器把 Session ID 发给客户端,客户端保存(通常通过 Cookie),后续请求时携带该 ID,服务器以此识别用户身份。
  • Go 实践:Go 标准库 net/http 提供了 Cookie 结构体。常用的第三方库是 gorilla/sessions,它封装了对 Cookie 和 Session 的操作。

注意这里问的是 Cookie 的缺点,相比服务端的 Session,Cookie 的短板主要体现在:

  1. 安全性低:数据存储在客户端(明文可见),极易被篡改或窃取(尤其是没有设置 HttpOnlySecure 属性时)。而 Session 数据存储在服务端,用户无法修改。
  2. 存储量小:单个 Cookie 最大约 4KB,无法存储复杂或大量的用户数据。
  3. 带宽消耗:每次 HTTP 请求都会携带所有未过期的 Cookie(包括不必要的),浪费网络带宽。
  4. 无法主动失效:服务端无法直接删除客户端的 Cookie。而 Session 存储在服务端,后端可以随时将 Session 销毁,从而实现强制用户下线。
  5. 跨域限制:Cookie 受同源策略(Same-origin Policy)的限制,跨域请求无法自动携带 Cookie,在前后端分离场景下不如 Authorization Token 灵活。

3. Session ID 可以放在哪里?(重点:Cookie 禁用的问题)

  • 默认方式: Cookie,浏览器自动处理携带。
  • 备选方案(应对 Cookie 禁用):
    1. URL 重写(URL Rewriting):将 Session ID 直接拼接到 URL 的参数中(如 http://example.com/home?session_id=abcdef),服务器解析 URL 参数获取。(面试官点名要提的方案)
    2. 隐藏表单字段(Hidden Form Field):在 HTML 表单中嵌入一个 <input type="hidden" name="session_id" value="..." />,提交后后端解析。
    3. 自定义请求头(Authorization):前端在 JS 中获取 Session ID,在 Ajax/API 请求时手动放入 Authorization: Bearer {session_id} 请求头中(特别适合移动端 App 或前后端分离架构)。

4. 用户密码加密算法选取有什么注意事项?你用的是什么?

  • 注意事项(避坑指南):
    1. 坚决不用:明文存储、MD5、SHA1、SHA256 等快速哈希算法(因为它们计算极快,很容易被现代的 GPU/ASIC 暴力破解或彩虹表攻击)。
    2. 必须加盐:为了防止彩虹表攻击,每个用户的密码在加密前必须加上随机生成的盐值(Salt),且盐值需要与哈希结果一起保存(或者盐值可以由固定的业务密钥+用户ID生成)。
    3. 使用慢速哈希算法:这是为了防止暴力破解,要让加密一秒钟只能算几次。
  • 具体用什么?
    • Argon2:2015 年密码哈希竞赛的获胜者,目前 OWASP 公认最安全的算法(但 Go 标准库未直接提供,需用 golang.org/x/crypto/argon2)。
    • bcrypt工业界最常用。Go 标准库提供 golang.org/x/crypto/bcrypt。使用简单,自带盐值管理,支持调节计算代价(Cost Factor)。(面试强烈推荐回答这个)
    • scrypt / PBKDF2:也可。

5. 怎么做登录校验?核心是利用 Gin 的 middleware(中间件)。

登录和鉴权在 Gin 项目里的标准实现流程:

  1. 登录接口(无需鉴权)
    • 接收用户名/密码。
    • 查库比对哈希密码。
    • 验证成功生成一个 Token(如 JWT 或 Session ID),并返回给前端。
  2. 中间件拦截(核心)
    • 定义一个 Gin 的 Middleware 函数。
    • 在每个受保护的 API 请求进来时,先执行这个中间件。
  3. 中间件内部逻辑
    • 从请求头(Authorization)或 Cookie 中获取 Token。
    • 验证 Token 是否有效(如 JWT 校验签名、检查过期时间;或者去 Redis 查 Session 是否存在)。
    • 验证失败:调用 c.AbortWithStatusJSON(401, ...) 中断请求,直接返回 401 未登录/未授权。
    • 验证成功:将解析出的 UserID 等用户信息通过 c.Set("userId", userID) 存入 gin.Context 中,然后调用 c.Next() 将请求放行给后续的业务 Handler 处理。

第三周

image-20260628172417776

一、 刷新 Session 过期时间的几种办法(深入分析优缺点)

面试官想考察对**“滑动窗口”与“固定时间”**的理解,以及在高并发下的权衡。

1. 方案一:滑动窗口策略(每访问一次就刷新过期时间)

  • 做法:用户每发起一次请求,就把 Session 在 Redis/内存中的过期时间重置为 30分钟
  • 优点:最符合用户直觉,只要用户一直操作,就不会掉线。
  • 缺点(一定要说出)高并发下写压力极大。在 Redis 或 Goroutine 中频繁执行 EXPIRE 命令会造成巨大的 I/O 负载。如果 Session 存储在 Go 内存中,频繁读写还要加锁,锁竞争会很严重。
  • Go优化手段:在 Redis 中使用 Lua 脚本(原子操作)将“判断是否存在”和“延长过期时间”合并在一次请求中,减少 RTT。

2. 方案二:固定时间刷新(主动续期)

  • 做法:比如初始有效期是 7 天,每次登录时颁发一个新的 Session ID。
  • 优点:极大减少了对存储系统的写操作,只有登录或踢下线才需要写数据库。
  • 缺点:容易出现**“正在操作但突然掉线”**的糟糕体验(比如用户正在填写一个长表单,7天时间刚好到,请求就失败了)。

3. 方案三:被动续期(惰性刷新)

  • 做法:设置一个长过期时间(如 24 小时)。在每次请求校验时,检查 Session 剩余时间是否小于阈值(比如小于 5 分钟)。如果小于,才去刷新过期时间。
  • 优点:平衡了方案一和方案二的优缺点,大部分请求不需要读写存储。
  • 缺点:需要多做一次“剩余时间”的判断逻辑。

👉 面试话术建议:

“如果让我来设计,我会优先考虑惰性刷新。因为它不仅能保持用户的体验,还能极大地减少高并发时对 Redis 的 I/O 压力,只有快过期的时候才去写一次。如果是项目初期并发量不大,为了简单,使用滑动窗口也没问题。”

二、 增强登录安全性

这里集中考察 Web 安全常识(XSS, CSRF, 中间人攻击)。

1. 如何保护 Session ID?

  • HTTPS 协议(TLS):最根本的防线,防止中间人抓包直接拿到 Cookie。

  • Cookie 的 Secure 属性:设置为 true。告诉浏览器这个 Cookie 只能通过 HTTPS 协议发送,绝对不能通过 HTTP 明文传递。

  • Cookie 的 HttpOnly 属性:设置为 true防止 XSS 攻击,恶意 JavaScript 脚本无法通过 document.cookie 读取到你的 Session ID。

  • Go 代码实现示意(Gin/原生 net/http)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    cookie := &http.Cookie{
    Name: "session_id",
    Value: sessionID,
    HttpOnly: true,
    Secure: true, // 生产环境必须为 true
    SameSite: http.SameSiteStrictMode, // 防止 CSRF 攻击
    Path: "/",
    MaxAge: 86400,
    }

2. Session ID 或 JWT 泄露后如何保护用户?

  • 核心思路:绑定“设备指纹”( User-Agent 就是最简单的指纹)。
  • 做法
    • 登录时记录:用户登录成功后,除了生成 Token,还在 Redis 或数据库记录本次登录的 User-AgentClient-IP、甚至加密后的 Device-ID
    • 校验时对比:每次请求验证 Token 是否合法时,额外对比当前请求的 User-Agent 和 IP 是否与签发 Token 时的记录一致。
  • 局限性:IP 会变(比如公司内网、移动端 4G/5G 切换),所以不能仅以 IP 不符就拒绝访问。正确的做法是增加安全等级:如果 IP 或 User-Agent 发生了剧烈变化,要求用户进行二次验证(短信/邮箱验证码)
  • JWT 泄露的痛点:JWT 自身无法被服务端“吊销”(除非结合黑名单)。因此针对 JWT,通常结合**“短时效 Access Token(如15分钟) + 长时效 Refresh Token(如7天)”**的双 Token 机制,强迫前端定期刷新 Token,从而缩短泄露危害的时间窗口。

三、 保护 Web 服务(限流与防护)

1. 限流分类与算法:

  • 针对 IP 限流:限制单个 IP 的请求频率(如每秒 100 次)。防止恶意爬虫或单个 IP 的 DDoS。
  • 针对整个集群限流:限制整个网关入口的总流量(如每秒 10000 次),防止大促或攻击打崩后端服务。
  • 限流算法(一定要讲区别)
    • 令牌桶算法:允许一定程度突发流量(Go 中常用 golang.org/x/time/rate 库)。可以应对流量洪峰,但后端压力会瞬间上升。
    • 漏桶算法:强行平滑流量,即使突发流量也恒定速率处理。对后端最友好,但容易拒绝突发合法请求。
  • 分布式限流实现
    • 在 Go 项目中,一般使用 Redis + Lua 脚本 或者 Redis 的 INCREXPIRE 命令 来实现滑动窗口/计数器限流,做到整个集群共享限流信息。

2. 其他的 Web 保护措施(可以拓展说明):

  • 参数校验与 XSS/SQL 注入:Go 框架(如 Gorm、Sqlx)一般都带有预编译防注入。但输入参数也要严格校验长度、类型,防止各种奇葩数据。
  • WAF (Web 应用防火墙):如果有条件,网关层部署 WAF 拦截恶意请求。
  • 熔断与降级:如果下游服务挂了,不能再把压力透传过去。

💡面试“话术”小抄

面试开场白(当被问到上述问题时):
“关于 Session 安全这一块,我在实际项目开发中有过几个维度的考量。首先肯定是要用 HTTPS 并且把 Cookie 的 HttpOnlySecure 设置为 true。这是最基本的。
其次,在防止 Session 泄露方面,我除了用这两个属性,还会在登录的时候额外记录下 User-Agent 等信息,在请求校验时进行一致性比对,如果发现差异,我会返回 403 并让用户强制重新登录,以此来提高安全性。
关于限流,目前的主流方案是令牌桶,但在 Go 中实现分布式限流,我会结合 Redis 做一个滑动窗口计数器,用 Lua 脚本保证原子性……”

建议:
图中最后一段话非常关键。不要只背概念,考前找一张纸,把自己平时写的 Go 代码(比如用 Gin 怎么设 Cookie,用 Redis 怎么设 EXPIRE)当作真实案例写下来。面试时**“顺着自己的代码逻辑”**去讲,会显得非常有实践感,比背诵教科书更得面试官喜欢

四、K8s 面试要点

业务研发(初中级)对 K8s 要求不高,会写简单配置、会基本排错命令即可。

面试常见问题 回答要点
Pod 和 Deployment 的关系? Deployment 管理 Pod,保证指定数量 Pod 始终运行
Service 的作用? 为 Pod 提供固定访问入口,做负载均衡
ClusterIP vs NodePort vs LoadBalancer? 集群内 / 节点端口 / 云厂商公网
Ingress 是什么? 七层路由,一个入口按域名/路径分发到多个 Service
Pod 出问题了怎么排查? kubectl describe pod 看状态 → kubectl logs 看日志
数据持久化怎么做? PersistentVolume / hostPath 挂载到宿主机

image-20260702133553226

Q1:Kubernetes 中的 apiVersion 是什么意思?

回答思路
apiVersion 是 Kubernetes API 的版本标识,用来告诉 K8s 这个 YAML 文件该由哪个 API 端点来解析和处理。

它反映了资源的成熟度

  • Alpha(如 v1alpha1):实验性,可能有 Bug,默认不开启。
  • Beta(如 v1beta1):已测试,但不保证兼容性(新版可能移除)。
  • Stable/GA(如 v1):正式发布,长期支持,可以放心在生产环境使用。

加分点:面试时可以提一句,不同资源属于不同 API Group(如 apps/v1 管理 Deployment,networking.k8s.io/v1 管理 Ingress),写配置时记得 kubectl api-versions 查一下当前集群支持哪些版本。


Q2:Kubernetes 中 Service、Deployment 和 Pod 的基本概念,能用来干什么?

回答思路(层层递进)

  • Pod:K8s 中最小的调度单元,里面跑着 1 个或多个容器(通常是业务容器 + 辅助容器)。它是“肉鸡”,但Pod 本身是“临时工”,随时可能被重建(IP 会变)。
  • Deployment管理 Pod 的“包工头”。负责声明式更新、滚动升级、回滚和副本数管理。你告诉它“我要 3 个副本”,它就帮你确保永远有 3 个 Pod 在跑。
  • Service给 Pod 做“负载均衡和固定入口”。因为 Pod IP 会变,Service 提供固定的 VIP(虚拟 IP)或 DNS 名称,把流量分发到背后的一组 Pod。

一句话总结:Deployment 管死活(数量/版本),Service 管接入(网络),Pod 管干活(运行容器)。


Q3:Service 的类型有哪几种?有什么区别?用在什么场景?

回答思路(4种类型)

类型 访问范围 场景
ClusterIP(默认) 集群内部访问 微服务之间互相调用(如 A 服务调 B 服务)。
NodePort 集群外部,通过节点 IP + 固定端口 开发测试环境临时暴露,或没有负载均衡器时应急使用(端口范围默认 30000-32767)。
LoadBalancer 集群外部,通过云厂商的 LB(如阿里云 SLB) 生产环境公网/内网接入,直接给云厂商 LB 分配一个公网 IP。
ExternalName 映射到集群外部的域名 把集群内的请求转发到外部中间件(如把 mysql.internal 指向云上的 RDS)。

面试话术:我们生产环境最常用 ClusterIP 做内部调用,LoadBalancer 暴露对外网关,NodePort 只用来调试。


Q4:什么是 Ingress?Ingress 和 Ingress-Controller 是什么关系?

回答思路

  • Ingress:是一个 “路由规则配置文件”(YAML)。它定义了“域名 api.xxx.com 访问路径 /v1 转发到 Service A”。
  • Ingress-Controller:是真正干活的人(实际运行的 Pod,如 Nginx、Traefik)。它会实时监控 Ingress 资源的变化,并动态更新自己的转发配置。

关系类比
Ingress 是交管局的**“交通法规”(写在纸上的规则),Ingress-Controller 是路口的“交警”**(负责按规则拦截和放行流量)。只有法规(Ingress)没有交警(Controller),没人执行,流量进不来;只有交警没有法规,他也不知道往哪拦。


Q5:PersistentVolume 和 PersistentVolumeClaim 是什么关系?为什么要有这两个?

回答思路

  • PV(PersistentVolume):集群里的**“存储资源池”**。由运维提前准备好的一块存储(如 NFS、云盘),独立于 Pod 存在。
  • PVC(PersistentVolumeClaim):用户(业务研发)提出的**“存储需求申请单”**。写明“我要 5G 空间,读写模式是 RWO”。

关系:PVC 去**“绑定”** 符合自己要求的 PV(类似于 Pod 去调度 Node)。当 PVC 绑定 PV 后,Pod 就能挂载这个 PVC 来使用存储。

为什么要有这两个(即本质价值)
核心目的是**“解耦”**。

  • 业务研发(使用者):不需要关心存储底层是 NFS、Ceph 还是云盘,只需在 YAML 里写 kind: PersistentVolumeClaim,声明我要多大、什么权限就行(按需申请)。
  • 运维(管理者):集中管理底层存储资源,防止业务随意占用(资源管控)。

这就像租房,PV 是房东手里的房源,PVC 是你提交的租房需求单,房东匹配房源给你,你不用管房子是哪个开发商的。


Q6:accessMode 有哪些取值?分别是什么含义?

回答思路(3种标准模式 + 1个新特性)

取值 缩写 含义
ReadWriteOnce RWO 该卷只能被单个节点读写方式挂载。(最常用)
ReadOnlyMany ROX 该卷可以被多个节点同时挂载,但只读
ReadWriteMany RWX 该卷可以被多个节点同时读写。(需要分布式存储如 NFS/CEPH 支持)
ReadWriteOncePod(新) RWOP 该卷只能被单个 Pod 以读写方式挂载(K8s 1.27+ 特性)。

面试补充:选型时注意,云厂商的块存储(如阿里云 SSD 云盘)通常只支持 RWO,而 NFS 通常支持 RWX。如果多个 Pod 要共享写日志,就得选支持 RWX 的存储类。