这个系列更新到第6篇的时候,我已经把EX280的官方考试目标拆过一遍,并且用一套临时的OpenShift环境反复做了三个星期的破坏性实验。之所以强调“破坏性”,是因为考试里很多考点不是配置好一次就完事,而是要你在残缺环境里把它修复回正常状态。所以这篇文章不打算再复述一遍基础概念,而是把第6阶段里那些让我吃过亏、绕了远路、最后彻底搞清楚的环节整理出来。如果你也在备考RHCA的EX280,看这篇应该比翻一堆命令清单更值。
我当前的状态是:RBAC、配额、存储、网络策略、安全上下文约束(SCC)这几块核心内容已经基本能独立完成,正在做的是一轮又一轮的“模拟环境故障注入”,比如故意把某条访问权限删掉,把StorageClass改坏,或者在命名空间里乱加网络策略,然后逼自己在限时内修好。这个阶段的体验跟前面几篇很不一样,因为它不是在教你“怎么建”,而是在逼你回答“为什么坏了”。
1. 考场上的第一道坎:RBAC与身份认证,比想象中的更绕
1.1 先弄清“认证”和“授权”的分界线
我见过很多备考EX280的人,把认证和授权混在一个概念里,结果题目一换角度就懵。其实OpenShift里的规则分得非常清楚:
- 认证:确认“你是谁”。常见方式是OAuth、HTPasswd、LDAP等外部身份提供者。
- 授权:确认“你能做什么”。这是RBAC的事,通过Role、RoleBinding、ClusterRole、ClusterRoleBinding来管控。
考试里最容易出现的场景是:集群已经配好了OAuth,但某个用户登录后什么都看不到。这时候大多数人第一反应是去检查身份提供者配置,实际上问题往往出在授权上——用户根本没有任何角色绑定。
我第一次练手出的低级错误是:创建一个用户之后,忘了给他绑定角色,然后跑到另一台机器上拿这个用户的kubeconfig登录,结果连default项目都列不出来。这时候不要慌,oc get clusterrolebinding 和 oc auth can-i --list --as=用户名 才是排查的核心。oc auth can-i 这个命令在考试里非常实用,它可以直接验证“以某个用户身份到底能不能执行某个操作”,比翻半天RBAC YAML快得多。
1.2 一个最容易翻车的RBAC案例:ServiceAccount的权限
题目往往不是直接给你一个用户,而是要求某个部署在集群里的应用,通过ServiceAccount去调用Kubernetes API。比如我要让一个名叫ci-bot的ServiceAccount能够读取指定命名空间里的Pod列表,用来做CI/CD状态汇总。
我的做法是先创建ServiceAccount:
bash复制oc create sa ci-bot -n cicd
然后创建角色,并绑定:
bash复制oc create role pod-reader --verb=get,list,watch --resource=pods -n cicd
oc create rolebinding pod-reader-binding --role=pod-reader --serviceaccount=cicd:ci-bot -n cicd
这里有一个反复踩的坑:--resource 里填写的资源名必须是Kubernetes API里的复数形式,比如pods、services、deployments,而不是pod、service。一旦写错,角色能创建成功,但实际操作时API会返回404。对于不是特别熟悉API资源的考生,建议先在集群里跑一下 oc api-resources 确认资源名,再写进Role。
另外,Deployment YAML里要明确指定ServiceAccount:
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: ci-agent
namespace: cicd
spec:
replicas: 1
selector:
matchLabels:
app: ci-agent
template:
metadata:
labels:
app: ci-agent
spec:
serviceAccountName: ci-bot
containers:
- name: agent
image: registry.access.redhat.com/ubi8/ubi-minimal:latest
command: ["sleep", "infinity"]
注意是 serviceAccountName,不是 serviceAccount。后者在早期版本里出现过,OpenShift 4上已经不建议使用,我见过有人在旧笔记里抄了serviceAccount字段,结果Pod一直卡在ContainerCreating,报错说默认的default ServiceAccount没有权限。这就是低级但致命的细节。
1.3 身份提供者配置:HTPasswd与隐式陷阱
EX280考试范围内,身份提供者配置虽然不一定每次都会考,但考试环境里经常预置了一个没有用户的HTPasswd身份提供者,要求你添加用户并设置密码。这个场景一旦出现,顺序是很有讲究的。
第一步,用htpasswd工具生成密码:
bash复制htpasswd -c -B -b htpasswd-file devuser 'ReallyStrongP@ss'
第二步,创建Secret,注意命名空间必须是openshift-config:
bash复制oc create secret generic htpass-secret --from-file=htpasswd=htpasswd-file -n openshift-config
第三步,编辑OAuth配置:
bash复制oc edit oauth cluster
在spec.identityProviders里添加:
yaml复制spec:
identityProviders:
- name: my_htpasswd_provider
mappingMethod: claim
type: HTPasswd
htpasswd:
fileData:
name: htpass-secret
保存后,OpenShift会自动滚动更新OAuth相关Pod。这里最容易忽略的是:如果原有OAuth配置里已经有别的identityProviders,你不能直接把整个spec.identityProviders替换掉,而是要往数组里追加。很多人习惯用 oc apply -f oauth.yaml 覆盖,结果把原有的LDAP配置冲掉了,考场上一旦出现这种问题,两个用户都登录不了,直接浪费十几分钟。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 资源配额与LimitRange:把“多租户”压榨明白
2.1 配额到底扣的是哪个“资源”?CPU和内存的计量单位
配额考的就是细粒度。很多人眼里的“CPU”就是那种从监控图上看到的几核几核,但在OpenShift里,CPU配额的单位是毫核(m)。我见过有人在Quota里写:
yaml复制requests.cpu: "1"
这表示1个CPU核心,也就是1000m。但如果在LimitRange里用默认值,比如给每个容器默认请求500m,那么两个容器就会立刻超过这个配额。这其实是显而易见的数字问题,但实际操作时很多人压根没注意默认LimitRange的存在。
内存单位也有个坑:Gi不等于G。Gi是二进制单位,1Gi等于1024Mi,而G是十进制单位,1G等于1000M。在写配额的时候,如果你要求每个命名空间最多使用2Gi内存,考试环境里的题目表述可能直接用“2吉比字节”,你就必须写2Gi而不是2G,否则两者会有约24Mi的偏差,极端情况下会导致Pod无法创建。
2.2 LimitRange与Quota的叠加逻辑,两个一起才完整
配额控制的是命名空间总量,而LimitRange控制的是单个Pod和容器的边界。这两个东西看起来都会限制资源,但工作顺序不一样:
- 创建Pod时,LimitRange先检查单个容器是否满足最小和最大限制。
- 如果Pod里的容器没写
resources.requests或resources.limits,LimitRange会给它补默认值。 - 补完默认值之后,再把整个Pod的资源请求累加,和ResourceQuota比较。
这个顺序决定了:如果只配置配额,不配置LimitRange,那么一个没有显式声明资源请求的Pod可能直接通过配额检查,因为它的requests为0。很多人在考试里只创建了ResourceQuota,却发现一个完全不写资源的Pod也能正常启动,那就是没配合LimitRange。
模拟环境里,我通常这样建一对组合:
yaml复制apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
spec:
limits:
- default:
memory: 512Mi
cpu: 500m
defaultRequest:
memory: 256Mi
cpu: 100m
type: Container
yaml复制apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
spec:
hard:
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
2.3 用 describe 而不是只看数值
考试时如果发现创建Pod失败,最直接的信息在事件里,但很多人习惯直接看oc get quota,看输出的数字。其实oc describe quota更有价值,它会把每个资源的 Used、Hard、Percent 都列出来,一眼能看出瓶颈。
有一次我在模拟环境里为一个命名空间设置了requests.memory: 1Gi,然后创建一个申请1.5Gi内存的Pod,被Quota拒绝了。我没仔细看describe,以为是Quota没生效,反复重建Pod,浪费了不少时间。后来才发现事件里写着“exceeded quota”以及具体超了哪个资源。所以建议在所有和Quota有关的题目里,遇到Pod创建失败时,先执行oc describe quota -n 命名空间,同时看oc get events -n 命名空间,几乎能定位所有问题。
3. 存储配置:PV/PVC/StorageClass三者协作关系
3.1 PVC绑定不上PV的四种常见原因
在EX280考试里,存储的题目大概率会提供若干现成的PV,要求你创建一个PVC去绑定其中一个。我以前以为只要容量匹配就行,实际上PV和PVC的绑定条件多达四个,任何一个不满足都无法绑定。
第一,AccessMode必须匹配。PV是ReadWriteOnce,PVC就不能写ReadWriteMany,虽然OpenShift不会报语法错误,但绑定关系会一直空白。
第二,容量必须满足。PVC请求的存储量不能超过PV的capacity。如果PVC请求100Gi,而集群里只有50Gi和150Gi两个PV,它不会因为150Gi满足就自动绑定到150Gi,恰恰相反,如果集群里没有恰好满足100Gi的PV,这个PVC会一直Pending。
第三,StorageClass要一致。如果PV设置了storageClassName: fast,PVC也必须有相同字段;如果PV用的是空字符串storageClassName: "",表示它是静态供给且不绑定任何StorageClass,PVC也必须显式写空字符串,否则默认StorageClass会介入。
第四,如果PV上有label selector,PVC的selector必须匹配。这是最容易被忽视的一种,因为前面三个条件都满足了,但绑定状态还是空,最后排查下来就是selector没对上。
现场排查逻辑很简单:oc get pv 看PV的STATUS,oc describe pvc 看最底下Events。Events会直接告诉你是否有匹配的PV,以及为什么不匹配。
3.2 动态供应与静态供应的选择,别为了炫技选错
动态供应就是不需要手动建PV,只需要创建PVC,并由StorageClass的provisioner自动创建底层存储。EX280的考试环境里通常会有一个local类型的StorageClass,或者一个模拟NFS的StorageClass。
静态供应的PV是手动oc create -f pv.yaml创建的。
考试时首先要判断题目到底要哪种。如果题目说“创建一个持久卷声明,让它自动从存储类获取存储”,那就走动态;如果题目明确给了PV清单,让你“使用现有存储”,那就别去创建什么StorageClass。
我犯过的错误是:过于习惯动态供应,结果在一个给了PV的题目里额外创建PVC,系统虽然创建成功,但绑定的PV跟我预期的不一致,因为卷的容量一模一样,AccessMode也一样,最后造成题目结果不准确。
3.3 考试里最容易忽略的默认StorageClass
OpenShift集群安装完之后,可能某个StorageClass已经被标记为默认,比如local-storage,它的storageClassName在PVC里没写时会被自动补上。但EX280题目经常反着来:故意提供一个没被标记为默认的StorageClass,要求你在PVC里显式指定。
所以在做存储题之前,我强烈建议先跑一下:
bash复制oc get storageclass
oc get storageclass -o yaml | grep -A 5 annotations
确认有哪些StorageClass,以及哪个是默认。如果题目没有要求,就不要在PVC里留空,指定一个明确的StorageClass不一定错,但留空加上默认StorageClass存在,可能会让结果不可控。
另外ReclaimPolicy也值得留意。如果一个PV的persistentVolumeReclaimPolicy是Recycle,或者现在更常用的Delete,当PVC删除时PV会被自动清理。考试里如果要求“保证删除PVC后PV仍然保留”,你得选Retain策略,否则验证时才发现数据没了就晚了。
4. 应用部署与服务暴露:Route、Service、NetworkPolicy三件套
4.1 从Deployment到可访问的Pod,这条链路要一眼看穿
常见需求:部署一个应用,让外部能够通过域名访问。这涉及四个对象:
- Deployment:管理Pod副本。
- Service:在集群内部提供负载均衡入口。
- Endpoints/EndpointSlice:Service背后的Pod IP列表。
- Route:Ingress层的HTTP路由规则,用于外部访问。
考场时间很紧,不应该在每一步都翻文档。我的习惯是写一个完整YAML包含Deployment、Service、Route三个块,同时创建。但这里有个前提:Service的selector必须跟Pod的labels完全匹配。
有一次我在模拟环境里把Service的selector写成app: myapp,而Pod的label是app: my-app,结果Service创建了,Endpoints却是空的。路由配置再正确,访问还是502。最直接的验证命令是:
bash复制oc get endpoints -n 项目名
oc get route -o yaml
如果Endpoints有Pod IP,说明链路前半段正常;后半段再去看Route的status.ingress。
4.2 网络策略的default deny误区
谈到Route和Service,很多人忽略网络策略。实际上OpenShift集群里,如果某个命名空间已经存在NetworkPolicy,那么默认就是白名单机制:没有规则明确放行的流量都会被拒绝,除非使用oc adm修改默认值。
这个问题在考试里很阴:你创建了一个Deployment和Service,但Pod怎么都访问不通,而Service和Route都看着没问题。这时候要立刻想到NetworkPolicy。
一个标准默认拒绝全部入站的策略如下:
yaml复制apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
这里的关键是podSelector: {},它匹配命名空间所有Pod。策略数组里没有任何ingress规则,所以所有入站流量被拒绝。如果你只创建了Service,没有显式的NetworkPolicy允许流量进入Pod,那就等于把自己关在门外。
考试里如果要求“仅允许来自某个标签的Pod访问”,要记得from块里既可以用namespaceSelector,也可以用podSelector,甚至两者组合。最常见的错误是只写了podSelector,没写namespaceSelector,结果流量来自另一个命名空间,策略不生效。区分这两个:
namespaceSelector:匹配源Pod所在的命名空间。podSelector:匹配源Pod的标签。
想要限制“只有同一命名空间下,标签为app=frontend的Pod可以访问”,必须写:
yaml复制ingress:
- from:
- podSelector:
matchLabels:
app: frontend
而如果要求“只有另一个命名空间里的Pod可以访问”,就需要namespaceSelector。
4.3 Route配置的几个常见失分点
Route的YAML本身不复杂,但考场里失分点很多集中在TLS配置上。
edge终止:TLS在Route层终止,后面到Service是明文HTTP。如果应用本身不支持HTTPS,用edge最合适。passthrough:TLS透传,不终止,直接把加密数据发到后端Pod。如果应用强制HTTPS且自己持有证书,用这个。reencrypt:Route先终止TLS,再用新的TLS证书加密到后端,适合内部证书链复杂的情况。
考试题目如果提到“必须使用端到端加密”,那就要选passthrough或者reencrypt,不能用edge。如果只提到“外部通过HTTPS访问”,就可以用edge,并在insecureEdgeTerminationPolicy里设置Redirect,把HTTP请求重定向到HTTPS。
还有一个不起眼的配置:spec.port.targetPort。这里的targetPort必须对应Service里定义的port名称或数字,而不是容器实际监听的端口。如果Service端口叫8080-tcp,Route里写targetPort: 8080也能连上,但为了稳妥,我习惯在Service YAML里给端口起一个明确名字,Route里引用这个名字,避免数字写错。
5. 安全上下文约束(SCC):OpenShift区别于Kubernetes的关键点
5.1 SCC是什么,和PodSecurityPolicy有什么区别
如果你已经熟悉普通Kubernetes,可能知道Pod Security Policies或Pod Security Standards。OpenShift用的是SCC(Security Context Constraints),这是一套层级更重的安全机制。它会在Pod创建时检查容器运行的用户ID、文件系统组、特权模式、卷类型等,如果不符合SCC就拒绝创建。
EX280考试里,SCC题目常见于这种:部署一个镜像,但镜像要求以root用户运行,OpenShift默认的restricted-v2 SCC不允许容器以root运行,所以Pod一直创建失败。此时你需要调整SCC,或让Pod使用允许root的SCC。
5.2 给某个ServiceAccount授权anyuid,而不是直接给整个集群授特权
更符合考试逻辑的做法是:不是修改整个命名空间的默认值,而是给特定的ServiceAccount添加SCC权限。比如部署Pod时指定ServiceAccountmy-deployer,然后执行:
bash复制oc adm policy add-scc-to-user anyuid -z my-deployer -n myproject
这里的-z参数是ServiceAccount的简写,表示system:serviceaccount:myproject:my-deployer。这条命令比直接写oc adm policy add-scc-to-group anyuid system:authenticated安全得多,如果你给所有人放行anyuid,后续题目中要求限制其他普通Pod不能以root运行时就会出问题。
另外还要注意:SCC授权是基于User或ServiceAccount的,不是基于命名空间的。所以即使在同一个命名空间里,不同ServiceAccount可以有不同的SCC权限。这是OpenShift的一个强特性,考试里也可能考到。
5.3 如何快速定位SCC拒绝日志
看到Pod状态是CreateContainerConfigError或者ContainerCreating,不一定是镜像问题,很多时候是SCC拒绝。定位方法:
最直接的是查看事件:
bash复制oc get events --field-selector involvedObject.name=<pod-name> -n <namespace>
典型事件长这样:
code复制Error creating: pods "xxx" is forbidden: unable to validate against any security context constraint: [provider restricted: .spec.containers[0].securityContext.runAsUser: Invalid value: 0: must be in the ranges: [1000640000, 1000649999]]
看到runAsUser: 0就意味着Pod要跑root,但当前SCC限制只能用非root。解决办法就是在Deployment里显式声明securityContext.runAsUser: 0并给ServiceAccount授权anyuid。
还有一次我遇到的情况是Pod创建成功,但容器一直在CrashLoopBackOff。排查半天,才发现不是SCC问题,而是Pod里用了hostPath卷,而当前SCC不允许hostPath。这时候oc describe pod的Events会提示hostPath: Invalid value,要看仔细。
所以SCC排错顺序建议是:
- 先看事件,确认有没有
forbidden关键词; - 再确认ServiceAccount是否在正确的命名空间;
- 然后确认SCC授权的ServiceAccount和Pod YAML里写的
serviceAccountName是不是同一个。
6. 最后一周的实战模拟:如何高效“压真题”
6.1 用临时集群做破坏性实验
到了冲刺阶段,复习方式要换掉“照着文档敲一遍”的模式。我给自己定的规则是:每个考点至少做一次破坏性实验。比如:
- 调大Quota,让本来能创建的Pod被拒绝,然后再调小回来;
- 故意把StorageClass删掉,再创建一个同名的,观察PVC是否有影响;
- 给Service的selector写错,看Service的Endpoints变化;
- 在Route里写错targetPort,看返回502还是404;
- 给ServiceAccount删掉RoleBinding,再让Pod通过SDK调用API。
每做一个破坏,记录现象,再修复。这个过程会把“命令”变成“模式”,考场上一旦遇到异常,脑子里会立刻浮现出上一次修复时的现象对比。
如果手头没有完整集群,可以用单节点的OpenShift本地环境或者云上的临时集群。重点是不要怕搞坏,反正都是临时环境。EX280考的就是这些对象之间的联动关系,只在文档里学过、没亲手破坏过,遇到故障注入类题目大概率会慌。
6.2 考试计时与“不会就过”的决策策略
EX280考试时长一般是3小时左右,题目量大概在25题上下浮动,更准确的时间分配是:单题超过8到10分钟还没有任何推进,立刻标记,跳过,继续做后面的题。
为什么建议跳过?因为EX280的很多题目是独立的,每道题对应一个cluster或namespace,跳过一题不影响其他题目。死磕一道题的结果往往是后面简单题没有时间做,反而丢了该拿的分。
我的冲刺模拟节奏是:第一轮按顺序做,每道题不超过10分钟;第一轮做完后再回头处理跳过的题。这轮如果还剩30分钟,就只处理能在10分钟内确定能做对的题,剩下时间用来复查已经答完的题。
复查时重点看三件事:Pod是否Running,Service的Endpoints是否有IP,Route是否在oc get route里显示Accepted。很多看起来配置完的题,最后因为Pod没起来等于白做。
6.3 考试内置文档的高效查询技巧
考试环境里通常可以访问Red Hat官方文档,但这不等于可以乱翻。文档量巨大,查不到点子上会浪费大把时间。
我的经验是:先记住几个关键文档的大概位置。比如OAuth配置在“Authentication”章节,SCC在“Authentication”或者“Security”相关章节,存储类在“Storage”章节,Route在“Networking”章节。考试时一旦遇到不熟悉的YAML字段,直接搜“OpenShift 4.x 资源名”而不是打开文档首页从头找。
另外,平时练习时建议自己建立一张“一页纸速查表”,不需要背下来,但要记得每个考点大概有哪些命令。比如:
- 查看当前项目的配额:
oc describe quota - 查看当前项目的LimitRange:
oc get limitrange - 查看当前用户权限:
oc auth can-i --list - 查看SCC:
oc get scc、oc describe scc <name>
这张表不是为了抄,而是在考场紧张时快速帮你定位到对应章节,再用文档补细节。
从第1篇一路更新到第6篇,我的最大体会是:EX280并不是命令背诵考试,它更像一张“故障排查地图”,把OpenShift的认证、网络、存储、安全、资源控制全部串起来。到了这个阶段,我已经不再追求把所有命令一字不差背下来,而是更关注每个对象之间如果断开会怎样。这篇文章里的每一个坑,都是我实际在临时集群里踩过并用三十分钟到一个小时修复回来的。如果你正在做冲刺复习,建议你也给自己的模拟环境设置几个“故意破坏”任务,亲手跑一遍,比反复看别人的总结更能从容面对真正的考场。
