Skip to content

阿里云ACK K8S部署 ​

本文介绍在阿里云容器服务 Kubernetes 版(ACK)上部署织信 2.41 的完整流程,覆盖集群接入、镜像准备、编排文件说明、部署顺序、存储选型和常见问题。内容基于实际客户交付环境整理,既适用于 ACK 托管版集群,也适用于客户自建的专有版集群(控制面由阿里云托管、节点自持)。

阅读说明

文中 <...> 为占位符,需替换为实际环境值。示例里的 IP、域名、仓库账号和密钥仅作格式演示,不可直接照抄。

一、部署形态 ​

织信的数据面(PostgreSQL、Redis、对象存储)建议使用云产品,集群内只保留无状态应用和必须就近部署的中间件。这样数据不依赖容器本地磁盘,Pod 可以随时重建,备份与扩容沿用云产品能力。

组件建议位置说明
account(入口与调度)集群内 Deployment1 副本,端口 9881
biz(业务执行)集群内 Deployment通常 2 副本(biz-0、biz-1),端口 8881
nginx(反向代理)集群内 Deployment1 副本,80 与 19881,NodePort 30080 对外
RabbitMQ集群内 StatefulSet1 副本,5672 与 15672
Elasticsearch集群内 StatefulSet1 副本,9200 与 9300
PostgreSQL集群外(RDS PostgreSQL 13)需 ltree 等 contrib 扩展
Redis集群外(云数据库 Redis)注意 IP 白名单
对象存储集群外(OSS 或客户 S3 兼容服务)S3 协议,注意 path-style

两种常见形态:

  • 混合部署(推荐,也是本文的默认形态):PG、Redis、OSS 用云产品,RabbitMQ 与 ES 放在集群内。适合客户已有云数据库、只想把应用容器化的场景。
  • 全云产品:RabbitMQ 用云消息队列、ES 用阿里云 ES,集群内只有 account、biz、nginx。此时编排文件里对应中间件的 Service 改为 ExternalName 或直接填外网/内网地址,B-middleware.yaml 可以整段删掉。

1.1 组件与端口 ​

组件镜像 tag资源类型副本端口
accountinformat-account-2.41Deployment19881
bizinformat-biz-2.41Deployment2(biz-0 / biz-1)8881
nginxinformat-nginx-1.0Deployment180、19881、NodePort 30080
rabbitmqinformat-rabbitmq-1.0StatefulSet15672、15672
elasticsearchinformat-elasticsearch-7.15.2StatefulSet19200、9300

镜像 tag 不统一

应用镜像是 2.41,但 nginx 与 rabbitmq 是 1.0,Elasticsearch 用上游版本号 7.15.2。凭「所有组件都是 2.41」的直觉去写 YAML,会直接得到 ImagePullBackOff。

1.2 资源与容量估算 ​

单套 2 副本的最小资源配置参考:

组件requestslimits说明
account250m / 1Gi4GiJAVA_XMX=2g
biz(每副本)250m / 1Gi8GiJAVA_XMX=4g,内置 DinD
nginx100m / 64Mi256Mi
rabbitmq100m / 256Mi2Gi
elasticsearch250m / 1Gi2GiES_JAVA_OPTS=-Xms512m -Xmx512m

limits 合计约 24Gi,因此单节点建议 8 核 32Gi 起步;若节点只有 16Gi,务必先调低 JAVA_XMX 与 ES 堆大小,否则 Pod 会被 OOMKilled 或长期 Pending。

二、前置准备 ​

2.1 集群与 kubectl 版本 ​

项要求
集群版本1.20 及以上;本文实测环境为专有版 v1.20.11-aliyun.1
kubectl与集群小版本相差不超过 1,实测用 v1.20.11
容器运行时containerd 或 Docker 均可,不影响编排文件
节点架构镜像与节点架构必须一致(x86 集群不能用 arm64 镜像包)
bash
kubectl version --short
kubectl get nodes -o wide
kubectl get storageclass
kubectl auth can-i create pods -n <namespace>

kubectl 不要用太新的版本:1.23 起会出现大量弃用警告,1.26 之后与 1.20 集群的 API 协商可能直接失败。

2.2 操作机与 kubeconfig ​

客户的 ACK 集群常常不暴露公网 API Server,kubectl 只能在同 VPC 的一台 ECS 上执行。推荐直接把数据库服务器或一台跳板机当作操作机。

项说明
操作机同 VPC 内 ECS,能访问 API Server 内网地址与 kubeconfig
kubeconfigACK 控制台「集群信息 → 连接信息」导出,server 为内网地址
权限具备目标命名空间的 create / get / logs / exec 权限

离线方式把 config 与 kubectl 一起带上去:

bash
# 1) 本机上传离线包
scp k8s-client.tar.gz root@<操作机IP>:/root/

# 2) 操作机上解包
cd /root
tar -xzf k8s-client.tar.gz
mkdir -p /root/.kube
mv config /root/.kube/config && chmod 600 /root/.kube/config
chmod +x kubectl && mv kubectl /usr/local/bin/kubectl

# 3) 验证
kubectl version --short
kubectl get nodes -o wide

若操作机与集群不在同一 VPC,需要先打通网络(云企业网、对等连接或 VPN),否则 kubeconfig 里的内网 server 地址不可达。

2.3 镜像仓库凭据 ​

集群拉取私有镜像前,必须先建 imagePullSecrets,否则所有 Pod 都会停在 ImagePullBackOff。这一步要在 apply 任何 YAML 之前做。

bash
kubectl -n <namespace> create secret docker-registry registry-cred \
  --docker-server=<registry域名> \
  --docker-username='<仓库账号>' \
  --docker-password='<仓库密码>'

编排文件里每个 Pod 模板都引用它:

yaml
spec:
  imagePullSecrets:
    - name: registry-cred

使用 ACR 时也可以安装 aliyun-acr-credential-helper 组件实现免密拉取,但客户自建 Harbor 或第三方 registry 仍需手工建 Secret。

2.4 网络与白名单 ​

容器化部署失败,一大半原因在网络策略而不是 YAML 本身。上线前逐项确认:

方向目标说明
Pod → PG<PG地址>:5432云数据库需把节点网段或 Pod 网段加入白名单
Pod → Redis<Redis地址>:6379必须放行节点/Pod 网段,见 8.1
Pod → 对象存储<OSS Endpoint>:80/443OSS 走内网 Endpoint 可免流量费
外部 → 入口<节点IP>:30080安全组与主机防火墙都要放行,见 8.4

三、配置 ConfigMap 与 Secret ​

3.1 ConfigMap ​

地址类配置集中放在一个 ConfigMap 里,所有组件用 envFrom 注入,避免逐个 Deployment 改环境变量。

yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: informat-env
  namespace: <namespace>
data:
  PG_HOST: <PG地址>
  PG_PORT: '5432'
  PG_USER: postgres
  REDIS_HOST: <Redis地址>
  REDIS_PORT: '6379'
  RABBITMQ_HOST: informat-rabbitmq
  RABBITMQ_PORT: '5672'
  SEAWEEDFS_HOST: <对象存储Endpoint>
  S3_PORT: '80'
  ES_HOST: informat-elasticsearch
  ES_PORT: '9200'
  INTRANET_URL: http://informat-nginx:19881/
  PUBLIC_URL: http://<节点IP>:30080
变量说明
PUBLIC_URL浏览器真实访问地址,不能填 Service 域名,否则分享链接、附件预览全部错
INTRANET_URL集群内入口,供应用内部回调使用,指向 nginx 的 19881
SEAWEEDFS_HOST兼容 S3 的对象存储 Endpoint,OSS 用内网地址

3.2 Secret ​

密码与密钥统一放 Secret,用 base64 编码:

bash
echo -n '<PG密码>' | base64
yaml
apiVersion: v1
kind: Secret
metadata:
  name: informat-secret
  namespace: <namespace>
type: Opaque
data:
  PG_PASSWORD: <base64>
  REDIS_PASSWORD: <base64>
  RABBITMQ_USER: <base64>
  RABBITMQ_PASSWORD: <base64>
  S3_ACCESS_KEY: <base64>
  S3_SECRET_KEY: <base64>

不要把真实凭据提交到仓库

交付包里的 Secret 值应替换为现场生成的随机密码。数据库、Redis 的密码必须与云产品实例上的实际密码一致;修改 Secret 不会同步云产品的认证信息。

3.3 命名空间与 Pod 安全 ​

biz 内置 InformatClaw 的 Docker-in-Docker,需要特权容器。在开启了 Pod Security Admission 的集群上,必须给命名空间打特权标签,否则 Pod 会被直接拒绝创建(报 violates PodSecurity "baseline")。

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: <namespace>
  labels:
    pod-security.kubernetes.io/enforce: privileged
    pod-security.kubernetes.io/audit: privileged
    pod-security.kubernetes.io/warn: privileged

biz 本身:

yaml
securityContext:
  privileged: true

只给 biz 特权,account 与 nginx 不要开。biz 不挂载宿主机 Docker socket,Docker data-root 用独立的 emptyDir。

3.4 nginx 与多 biz 实例 ​

客户要求两个 biz 实例时,nginx 用 /web0/、/web1/ 分别转发到两个 Service,并按客户端 IP 做一致性哈希,保证同一客户端会话落在同一实例:

nginx
upstream backend_account {
  hash $proxy_add_x_forwarded_for;
  server informat-account:9881;
}

upstream backend_biz_s0 {
  hash $proxy_add_x_forwarded_for;
  server informat-biz-0:8881;
}

upstream backend_biz_s1 {
  hash $proxy_add_x_forwarded_for;
  server informat-biz-1:8881;
}

location ^~ /web0/ { proxy_pass http://backend_biz_s0/web/; }
location ^~ /web1/ { proxy_pass http://backend_biz_s1/web/; }

实例数必须与 upstream 对齐

biz 扩到 3 个实例时,nginx 配置里也要加 /web2/ 与对应 upstream,否则新实例永远接不到流量。反过来,删实例前先删 upstream 段并 reload nginx。

这份配置通过 ConfigMap 以 subPath 挂进 nginx 的 /etc/nginx/conf.d/,Pod 重建即生效,不需要重新打镜像。

四、部署 ​

交付包共 20 个资源,分成 5 个文件加 1 个合并版:

文件资源数内容
A-base.yaml3Namespace + ConfigMap + Secret
B-middleware.yaml4RabbitMQ、ES 的 Service 与 StatefulSet
D-db-init.yaml2account / biz 建库 Job
E-apps.yaml10account、biz-0、biz-1、nginx 的 Service 与 Deployment
F-config-update.yaml1配置写入 Job(实际改手工 psql,见步骤 5)
ALL-IN-ONE.yaml17~20合并版,可一次 apply

严格按「配置 → 地基 → 中间件 → 建库 → 写配置 → 应用」的顺序推进,每一步确认 Pod 状态后再执行下一步。

步骤 1:创建镜像拉取凭证 ​

bash
kubectl -n <namespace> create secret docker-registry registry-cred \
  --docker-server=<registry域名> \
  --docker-username='<仓库账号>' \
  --docker-password='<仓库密码>'

kubectl -n <namespace> get secret registry-cred

步骤 2:地基 A-base ​

bash
kubectl apply -f A-base.yaml
kubectl -n <namespace> get configmap,secret

此时先按实际环境改好 ConfigMap 与 Secret 的值,后面所有组件都从这里取配置。

步骤 3:中间件 B-middleware ​

bash
kubectl apply -f B-middleware.yaml
kubectl -n <namespace> get pod -w

StatefulSet 首启会初始化数据目录,RabbitMQ 与 ES 都可能需要 1~3 分钟。建议编排里配 startupProbe,把 failureThreshold 放大到 120,避免启动慢被反复重启:

yaml
startupProbe:
  tcpSocket:
    port: 9200
  periodSeconds: 5
  failureThreshold: 120

步骤 4:数据库初始化 ​

建库有两种做法,外置 PG 优先用第二种。

方式一:K8s Job(集群内 Pod 能直连 PG 时)

bash
kubectl apply -f D-db-init.yaml
kubectl -n <namespace> logs job/informat-account-db-init --tail=200

方式二:在 PG 服务器上直接跑安装脚本(推荐)

外置 PG 常常存在网络策略、账号权限、客户端版本等障碍,Job 容易卡在建库阶段。直接在数据库服务器上执行安装包里的 install.sh 更可控:

bash
cd /root/db
bash install.sh

脚本走 psql 驱动,会创建两个库:

  • db_informat2_account_prd
  • db_informat2_biz_prd_0

执行完确认:

bash
psql -h <PG地址> -p 5432 -U postgres -l | grep informat2

检查 contrib 扩展

PG 建库阶段报 could not open extension control file "ltree.control",说明缺少 contrib 包(ltree、pg_trgm 等都在里面)。先装扩展再重跑,否则建库会半途中断,需要 drop 库重来。

步骤 5:写入对象存储配置(启动应用之前) ​

这一步必须在启动 account/biz 之前完成。 配置项写在 t_config 表里,应用启动即读,缺配置会导致 biz 反复重启。

sql
-- 对象存储
UPDATE t_config SET value='<对象存储Endpoint>' WHERE id='fileservice.s3.endPoint';
UPDATE t_config SET value='<accessKey>'         WHERE id='fileservice.s3.accessKey';
UPDATE t_config SET value='<secretKey>'         WHERE id='fileservice.s3.secretKey';
UPDATE t_config SET value='true'                WHERE id='fileservice.s3.enablePathStyleAccess';
UPDATE t_config SET value='<bucketName>'        WHERE id='fileservice.s3.bucketName';
UPDATE t_config SET value='<region>'            WHERE id='fileservice.s3.region';

在数据库服务器上执行:

bash
psql -h <PG地址> -p 5432 -U postgres -d db_informat2_account_prd -c "UPDATE t_config SET value='<值>' WHERE id='fileservice.s3.endPoint';"

不要用 Job 改配置

用 K8s Job 把 SQL 塞进 YAML 里执行,会遇到引号转义、t_config 表存在性依赖、Job 重跑不可幂等等问题。改配置直接 psql 更快也更可控;若确实需要 Job,只让它执行 UPDATE,不要建表。

步骤 6:应用 E-apps ​

bash
kubectl apply -f E-apps.yaml
kubectl -n <namespace> get pod -w

预期结果:account 1/1、biz-0 1/1、biz-1 1/1、nginx 1/1。首次启动 account 约 1~2 分钟(含 flyway 校验),biz 启动更慢,需要等健康检查通过后才 Ready。

五、验证 ​

bash
# 1) 全量状态
kubectl -n <namespace> get pod,svc -o wide
kubectl -n <namespace> get events --sort-by=.metadata.creationTimestamp | tail -20

# 2) 应用健康
kubectl -n <namespace> exec -it deploy/informat-account -- \
  curl -s http://127.0.0.1:9881/actuator/health
kubectl -n <namespace> exec -it deploy/informat-biz-0 -- \
  curl -s http://127.0.0.1:8881/actuator/health

# 3) 集群内入口
kubectl -n <namespace> exec -it deploy/informat-nginx -- \
  curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/

# 4) 节点入口(在能访问节点 IP 的机器上)
curl -s -o /dev/null -w 'HTTP=%{http_code}\n' http://<节点IP>:30080/

# 5) 中间件连通性
kubectl -n <namespace> exec -it deploy/informat-account -- bash -c \
  'echo > /dev/tcp/informat-elasticsearch/9200 && echo es-ok'

验收要点:

检查项期望
account /actuator/healthstatus: UP,PG / Redis / RabbitMQ 均 UP
biz Pod1/1 Ready,日志无 ERROR、无反复重启
nginx 19881集群内返回 200
NodePort 30080外部返回 200,或按 8.4 处理防火墙
EScurl _cluster/health 为 green 或 yellow
附件上传后台传一个文件后能在对象存储里看到对象

六、存储方案选择 ​

这是 ACK 部署最容易卡住的地方。四种方案的实际差异:

方案适用场景局限
emptyDir应用运行目录(/data/informat-next)、DinD 的 /data/dockerPod 重建即清空,只能放可重建的缓存
hostPath + nodeSelector集群内中间件(RabbitMQ / ES)数据数据绑节点,节点故障即丢;多副本无法共享
PVC(云盘 CSI)生产推荐的单副本持久化云盘是 ReadWriteOnce,只能挂一个 Pod;需要 csi-plugin 与 ESSD 可用
PVC(NAS)需要多 Pod 共享目录(附件、OnlyOffice 缓存)性能低于云盘,小文件多时明显

云盘 PVC 一直 Pending

客户集群不给盘时,PVC 会永远停在 Pending,describe pvc 只报 provision 失败。此时有两条路:

  1. 换 NAS StorageClass(大多数 ACK 集群都有 alicloud-nas)。
  2. 彻底不用 PVC —— 织信的业务数据全在外置 PG 和 OSS 上,容器本地只是运行时缓存,改成 emptyDir 不影响持久性(中间件除外)。

关键结论:应用层可以零 PVC,中间件不行。 RabbitMQ 换 emptyDir 会在 Pod 重建后丢消息,ES 会丢索引,两者必须落 hostPath 或 PVC,并用 nodeSelector 固定节点。

七、镜像迁移到正式仓库 ​

测试仓库 → 正式仓库,tag 保持不变,只换仓库路径。

bash
# 拉(测试账号)
docker login --username=<测试账号> <registry域名>
docker pull <registry域名>/<测试项目>/informat:informat-account-2.41
# ... 其余 4 个镜像同理

# 推(正式账号)
docker login --username=<正式账号> <registry域名>
docker tag  <registry域名>/<测试项目>/informat:informat-account-2.41 \
            <registry域名>/<正式项目>/informat:informat-account-2.41
docker push <registry域名>/<正式项目>/informat:informat-account-2.41

两个实测踩过的坑:

  • 同一 registry 连续登录两次,后一次的凭据会覆盖前一次。 拉用测试账号、推用正式账号,中间必须重新登录,不要在一个 shell 里交替执行。
  • 同一个 IMAGE ID 被两个 tag 引用时,docker rmi <ID> 会报 must be forced。 按 仓库:tag 逐个删,或用 docker system prune -a -f。

迁移完成后,正式环境的 registry-cred 要用正式账号重建,编排文件里的镜像地址也要同步改。

八、常见问题 ​

现象根因处理
PVC 一直 Pending集群云盘 CSI 不给盘改 NAS,或应用层改 emptyDir(见第六节)
ImagePullBackOfftag 用错(把 nginx/rabbitmq 当成 2.41)对齐 1.1 节的 tag 表
StatefulSet 改镜像后 Pod 不变StatefulSet 改 image 不触发滚动更新手动 delete pod 强制重建
建库报 ltree.control 不存在缺 postgresql13-contrib装 contrib 后 drop 库重跑
account / biz 长期 0/1,日志停在 bannerRedis IP 白名单未放行放行节点/Pod 网段(见 8.1)
Pod 内 30080 通,外部超时防火墙按端口放行,只开了 6443放行 30080 或改 Ingress
Pod 创建被拒,报 PodSecurity命名空间未允许特权打 privileged 标签(见 3.3)

8.1 Redis 白名单(最隐蔽) ​

现象:account 与 biz 长期 0/1,日志打完 banner 就不动了;在 Pod 里用 /dev/tcp、nc 测端口都「通」。

根因不在网络层。从线程栈里能看到真实原因:

text
io.lettuce.core.RedisCommandExecutionException: ERR illegal address: 10.167.x.x:xxxxx

TCP 握手成功,但 Redis 在应用层拒绝了来源地址——云 Redis 开了 IP 白名单,没放行节点或 Pod 网段。

处理:把集群节点网段(或 Pod 网段)加入 Redis 白名单,account 立刻变 1/1。

排查方法

/dev/tcp、nc、telnet 只能证明 TCP 可达,不能证明应用层协议被放行。连不上时不要停留在端口层,直接看应用日志的具体异常,必要时 jstack 打线程栈定位。

8.2 StatefulSet 改镜像后不重建 ​

kubectl edit statefulset 改 image,Pod 仍旧是旧的、一直 ImagePullBackOff。StatefulSet 不像 Deployment 那样在镜像变更时自动滚动(尤其是只改 image、未改其他模板字段时)。

bash
kubectl -n <namespace> delete pod informat-rabbitmq-0
kubectl -n <namespace> get pod -w

删 Pod 不会动 hostPath/PVC 数据,是安全的。只有确认数据已备份时才考虑删 StatefulSet 本体。

8.3 建库缺 ltree 扩展 ​

text
could not open extension control file "/usr/pgsql-13/share/extension/ltree.control"
bash
# 装 contrib(跳过 libxslt 依赖,只影响 xml2 扩展,ltree 不受影响)
rpm -ivh --nodeps --force postgresql13-contrib-*.rpm

# drop 掉半成品库后重跑
psql -h <PG地址> -U postgres -c 'DROP DATABASE db_informat2_account_prd;'
cd /root/db && bash install.sh

8.4 NodePort 30080 外部不通 ​

Pod 内 curl http://<任一节点IP>:30080 返回 200,但从办公网或跳板机访问超时。

原因:6443 端口通是为了 kubectl,不代表 30080 被放行。安全组和主机防火墙都要单独开。

bash
# 操作机上验证端口
nc -zv <节点IP> 30080

三种处理:放行 30080(最简单)、改用 Ingress + 域名(推荐生产)、或临时用端口转发排障:

bash
kubectl -n <namespace> port-forward --address=0.0.0.0 service/informat-nginx 8080:80

九、上线检查清单 ​

  • [ ] 命名空间已打 privileged 标签,biz 能以特权容器启动
  • [ ] registry-cred 已创建,5 个镜像 tag 与 1.1 节一致
  • [ ] ConfigMap 中 PUBLIC_URL 是可访问的真实地址,INTRANET_URL 指向 19881
  • [ ] Secret 中密码与云产品实例一致,未提交到代码仓库
  • [ ] PG 已安装 contrib(ltree、pg_trgm),两个业务库创建成功
  • [ ] Redis 白名单已放行节点/Pod 网段
  • [ ] 对象存储 Endpoint、AK/SK、bucket、region 已写入 t_config,且 path-style 正确
  • [ ] RabbitMQ 与 ES 的数据落在 hostPath 或 PVC,并固定了节点
  • [ ] 30080(或 Ingress 域名)从外部可访问,附件上传下载正常
  • [ ] 备份方案覆盖 PG 逻辑备份、对象存储与中间件数据