Buzeli
buzeliSoluções Digitais
Kubernetes

在Oracle Cloud (OKE) 上部署Kubernetes时遇到的10个陷阱

发布于 2026年3月10日

10 armadilhas ao subir Kubernetes no Oracle OKE — Buzeli Digital

背景

接手了一个项目:将一个大流量电商门户的全部基础设施从AWS迁移到Oracle Cloud Infrastructure(OCI)。动机纯粹是经济因素——预计每月云成本降低约78%。

在AWS上,技术栈运行着ECS Fargate承载Node.js/Next.js层、ARM EC2运行WordPress、托管数据库和应用负载均衡器。在OCI上,方案是用OKE(Oracle Kubernetes Engine)替换Fargate,节点使用VM.Standard.A1.Flex形态的ARM64实例——该实例属于Oracle的永久免费套餐。

我持有CKA认证,所以Kubernetes本身不是问题。问题出在OCI上。

以下是我在项目执行过程中实时记录的10个陷阱。

1. 12250端口——没人说清楚的文档

集群创建好了,节点池创建好了,等待。Worker节点在NotReady状态下停留了20分钟,控制台没有任何清晰的报错信息。

问题所在:NSG(网络安全组)必须为OKE端点开放12250端口。没有这个端口,节点Agent无法向控制平面注册。

Oracle的文档确实提到了这一点,但分散且不突出。没有明确的报错信息——你只是等待,什么都没有发生。

修复方案:在与OKE端点关联的NSG中添加12250端口的入站规则。修复后不到2分钟,节点完成了注册。

2. 云控制器管理器会创建自己的负载均衡器

我尝试通过`oci-load-balancer-id`注解复用已有的负载均衡器。没有成功。OCI CCM v1.34版本直接忽略该注解——它始终会创建一个自己管理的新LB。

尝试使用`oci-load-balancer-reserved-ip-id`来分配预留IP也没有效果。

最终的解决方案是接受CCM的行为:它自动创建和管理LB。优点是当节点发生变化时,它也会自动更新后端集——手动操作会很繁琐。

重要提示:CCM创建的LB的IP被归类为临时(ephemeral),但在实践中,只要对应的Kubernetes Service存在,IP就保持稳定,不会在你不销毁Service的情况下改变。

3. OCI WAF访问控制不会绕过保护规则

这个问题耗费了我好几个小时。

我在访问控制中创建了一条ALLOW规则,用于识别通过Session Cookie前缀认证的WordPress用户。我以为这样可以让这些请求不经过OWASP规则检查就直接通过。

OCI WAF并非这样工作的。访问控制和保护规则是完全独立的两个流水线。访问控制中的ALLOW规则对保护规则的执行没有任何影响。

正确的绕过方式是直接在需要对认证用户禁用的保护规则上添加条件:

复制
!contains(to_string(keys(http.request.cookies)), 'wordpress_logged_in_')

这条指令告诉规则:只有当认证Cookie不存在时才执行——从而有效地对已登录Session禁用该规则。

4. http.request.cookies与http.request.headers.cookie

还是在WAF配置上:我最初尝试在条件中使用`http.request.headers.cookie`——Cookie Header的原始字符串——来识别Cookie。在运行时表现不一致。

正确的版本是`http.request.cookies`——由WAF引擎已解析的Cookie对象。使用这个对象,保护规则表达式中的Cookie读取操作才能按预期工作。

5. OCI Cache要求TLS——而OpenResty不原生支持

OCI Cache(Redis兼容,基于Valkey)要求TLS连接,没有例外,没有禁用选项。

问题在于:OpenResty的srcache模块通过内部库连接Redis——该库不支持TLS。

解决方案:在VM上安装stunnel作为本地代理。nginx以明文方式连接到127.0.0.1:6379,stunnel封装TLS后转发到托管端点上的OCI Cache。

复制
OpenResty → 127.0.0.1:6379 → stunnel → OCI Cache FQDN:6379(TLS)

理解问题所在之后很简单,但如果你期望托管Redis能和普通nginx栈开箱即用,这并不直观。

6. REST API location中的try_files导致301循环

在为/wp-json/配置带有try_files的nginx location时,WordPress REST API的请求陷入了301重定向循环。

try_files会尝试提供物理文件或重定向到index——对于HTML页面这是正确行为,但对于需要直接到达PHP-FPM的REST端点来说是灾难性的。

修复方案:在location中用直接的fastcgi_pass替换try_files,不检查物理文件的存在。事后看来显而易见,但这个Bug在最初并不会以清晰的方式表现出来。

7. File.exists?在Ruby 3.2中被移除

为了将Pod日志集中到OCI Logging,我使用了fluent-plugin-oci-logging插件。问题是:这个gem使用了在Ruby 3.2中被删除的File.exists?方法。

结果是Fluentd在初始化时静默崩溃——没有清晰的报错信息,Pod就是起不来。

Dockerfile中的修复方案:

复制
RUN sed -i 's/File\.exists?/File.exist?/g' \
    $(gem contents fluent-plugin-oci-logging | grep '\.rb$')

只需一行patch,但找到根本原因花了不少时间,因为容器日志中的错误信息并不清晰。

8. OKE节点的动态组需要使用区间OCID

为了给运行在节点上的日志Agent赋予向OCI Logging发送数据的权限,我需要创建一个能识别集群实例的动态组。

初次尝试:使用节点池的OCID。失败——节点无法被识别为组的成员。

正确的规则使用区间(Compartment)的OCID:

复制
All {instance.compartment.id = 'ocid1.compartment.oc1..xxxxx'}

这会包含区间内的所有实例——范围比理想情况更广,但可用。如需更细粒度的范围,可以使用应用于节点池节点的OCI自定义标签。

9. Oracle Linux 8上的CRI-O要求完整的镜像名称

OKE节点在Oracle Linux 8上的容器运行时是以限制短名称模式配置的CRI-O。这意味着`prom/prometheus:latest`会失败——它不会自动推断docker.io仓库地址。

Kubernetes manifest中的所有`image:`字段都需要使用完整的仓库名称:

复制
image: docker.io/prom/prometheus:v2.53.0
image: registry.k8s.io/metrics-server/metrics-server:v0.7.2

看起来是细节问题,但根据来源环境的不同——在Docker Desktop中短名称会被自动解析——manifest可能没有显式的仓库地址,在OKE上会静默失败,没有清晰的报错信息。

10. required-server-files.json保存了构建时植入的所有变量

在迁移过程中,我需要从Next.js生产镜像中恢复嵌入的NEXT_PUBLIC_*环境变量——在无法访问原始代码库或CI/CD的情况下。

解决方案:从Docker镜像内部提取required-server-files.json文件:

复制
docker run --rm --entrypoint cat \
  <镜像>:<标签> \
  /app/.next/required-server-files.json

这个由next build生成的文件包含了构建时解析的所有环境变量——包括所有的NEXT_PUBLIC_*。在CI环境无法访问的应急情况下非常有用。

总结

OKE是托管Kubernetes的可靠选择,尤其是当ARM64节点(A1.Flex)的成本符合Oracle永久免费套餐的条件时。但OCI特有的学习曲线是真实存在的,不应被低估。

上述每个问题都耗费了诊断时间——有的几分钟,有的几个小时。Oracle的文档存在,但比较分散。大多数问题没有直接指向根本原因的报错信息。

如果你正在评估将OKE用于生产项目,希望这份清单能为你的下一个项目节省几个小时。