news 2026/9/30 2:12:57

Uppy Companion 在 Kubernetes 上的容器化部署实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Uppy Companion 在 Kubernetes 上的容器化部署实战指南
  • 前端
  • UI组件
  • 后端

【免费下载链接】uppy

The next open source file uploader for web browsers :dog:

项目地址:https://gitcode.com/gh_mirrors/up/uppy
点击查看免费下载

Companion 是 Uppy 文件上传器的服务端配套组件,负责在浏览器与 Google Drive、Dropbox、Box 等云存储之间建立服务端到服务端的通信与 OAuth 授权中转。本文以仓库中的 KUBERNETES.md 为主线,完整讲解如何使用官方 Docker 镜像将 Companion 部署到 Kubernetes:从创建命名空间、用 Helm 安装 Redis,到以 Secret 注入全部环境变量、编写 Deployment 与 Service,并进一步结合仓库源码与基础设施目录,给出持久化存储、Ingress、HPA 等生产级增强方案。读完后你将获得一套可直接复制运行的 K8s 部署 YAML,以及每个配置项背后的源码级原理。

一、部署前准备:官方 Docker 镜像与默认端口

Companion 的 Kubernetes 部署建立在官方 Docker 镜像之上。仓库根目录的 Dockerfile 展示了镜像的构建过程:

  • 基于node:26.10.0-alpine分阶段构建,先用corepack yarn workspaces focus @uppy/companion安装依赖并执行build,再切到生产依赖,最终拷贝dist、package.json与node_modules到运行镜像;
  • 镜像入口命令为node /app/dist/bin/companion.js(见 Dockerfile 第 33 行),即启动 standalone 模式的 Companion 服务;
  • 镜像默认EXPOSE 3020(Dockerfile 第 35 行),这与源码中端口的默认值完全一致——start-server.ts 中监听端口取自process.env['COMPANION_PORT'] || process.env['PORT'] || 3020。也就是说,只要不显式设置COMPANION_PORT,容器内的服务必然监听 3020,这也是下文 Deployment 中containerPort: 3020、Service 中targetPort: 3020的依据。

在 Kubernetes 中使用的镜像是docker.io/transloadit/companion:latest。需要注意的是:latest标签适合快速验证,若要保证可复现性,建议像仓库的 gcloud-deploy.sh 一样按 commit 或 git tag 打镜像标签(该脚本同时推送transloadit/companion:$TRAVIS_COMMIT、transloadit/companion:$TRAVIS_TAG与latest三个标签),并在 Deployment 中固定引用版本化标签。

二、创建命名空间并安装 Redis

Companion 依赖 Redis 来共享会话与跨实例状态,因此部署的第一步是创建专属命名空间,并在其中安装 Redis。

kubectl create ns uppy

随后通过 Helm 在uppy命名空间中安装 Redis:

helm install --name redis \ --namespace uppy \ --set password=superSecretPassword \ stable/redis

说明:以上命令保留了 KUBERNETES.md 中的原始写法(Helm v2 时代的--name语法)。若使用 Helm v3,可等价写作helm install redis stable/redis -n uppy --set auth.password=superSecretPassword(具体参数以你所用的 Redis chart 版本为准)。核心思路不变:Redis 必须与 Companion 处于同一集群,Companion 通过集群内 DNS 地址访问它。

--set password=superSecretPassword设置了 Redis 密码,这个密码稍后会以redis://:superSecretPassword@...的形式出现在COMPANION_REDIS_URL中。

为什么必须用 Redis?从 standalone/index.ts 可以看到:Companion 用express-session管理会话,会话用于 OAuth2 授权跳转过程(避免把敏感 token 暴露在 URL 中)。当配置了redisUrl时,会话存储会切换为RedisStore(第 130-137 行),会话 cookie 的maxAge为 10 分钟(第 139-146 行),足够用户完成一次授权流程。在多副本部署下,任何副本都能通过共享的 Redis 读取会话,这正是部署两个及以上副本所必需的。

三、环境变量注入:companion-env.yml(Secret)

Companion 的全部运行配置都通过环境变量读取(standalone 模式的配置解析集中在 helper.ts 的getConfigFromEnv()中)。由于其中包含大量密钥(OAuth Key/Secret、AWS 凭证、服务端 Secret 等),Kubernetes 部署时统一放进Secret中,再通过 Deployment 的envFrom注入容器。

以下为原文档提供的companion-env.yml完整内容:

apiVersion: v1 data: COMPANION_CLIENT_ORIGINS: 'localhost:3452,uppy.io' COMPANION_DATADIR: 'PATH/TO/DOWNLOAD/DIRECTORY' COMPANION_DOMAIN: 'YOUR SERVER DOMAIN' COMPANION_DOMAINS: 'sub1.domain.com,sub2.domain.com,sub3.domain.com' COMPANION_PROTOCOL: 'YOUR SERVER PROTOCOL' COMPANION_STREAMING_UPLOAD: true COMPANION_TUS_DEFERRED_UPLOAD_LENGTH: true COMPANION_REDIS_URL: redis://:superSecretPassword@uppy-redis.uppy.svc.cluster.local:6379 COMPANION_SECRET: 'shh!Issa Secret!' COMPANION_PREAUTH_SECRET: 'another secret' COMPANION_DROPBOX_KEY: 'YOUR DROPBOX KEY' COMPANION_DROPBOX_SECRET: 'YOUR DROPBOX SECRET' COMPANION_BOX_KEY: 'YOUR BOX KEY' COMPANION_BOX_SECRET: 'YOUR BOX SECRET' COMPANION_GOOGLE_KEY: 'YOUR GOOGLE KEY' COMPANION_GOOGLE_SECRET: 'YOUR GOOGLE SECRET' COMPANION_AWS_KEY: 'YOUR AWS KEY' COMPANION_AWS_SECRET: 'YOUR AWS SECRET' COMPANION_AWS_BUCKET: 'YOUR AWS S3 BUCKET' COMPANION_AWS_REGION: 'AWS REGION' COMPANION_AWS_PREFIX: 'AWS PREFIX' COMPANION_OAUTH_DOMAIN: 'sub.domain.com' COMPANION_UPLOAD_URLS: 'http://tusd.tusdemo.net/files/,https://tusd.tusdemo.net/files/' kind: Secret metadata: name: companion-env namespace: uppy type: Opaque

注意:kind: Secret的data字段在真实 Kubernetes 中要求Base64 编码的值。上面的写法是文档中的示意形式,实际落地时应对每个值执行echo -n 'value' | base64。若希望直接写明文,可将data改为stringData。

各环境变量的作用与源码对应关系如下表:

环境变量对应配置项说明与源码依据
COMPANION_CLIENT_ORIGINScorsOrigins允许跨域访问的浏览器来源,逗号分隔。getCorsOrigins()(helper.ts)支持true/false/*三种特殊值;普通值若不带协议,会按COMPANION_PROTOCOL自动补全为http(s)://
COMPANION_DATADIRfilePath文件下载/暂存目录,必须可读写。validateConfig()(companion.ts)启动时会用fs.accessSync校验目录存在且具备读写权限,否则直接拒绝启动
COMPANION_DOMAINserver.hostCompanion 对外暴露的域名/主机名
COMPANION_DOMAINSserver.validHosts合法主机名白名单,逗号分隔;配置解析时会被parseAllowlist()转换为RegExp或字面量(helper.ts)。以^开头的条目按正则处理,未锚定的正则会同时匹配"仅包含"该模式的值,需谨慎
COMPANION_PROTOCOLserver.protocolhttp或https,默认http(helper.ts)
COMPANION_STREAMING_UPLOADstreamingUpload是否启用流式上传,true/false
COMPANION_TUS_DEFERRED_UPLOAD_LENGTHtusDeferredUploadLength是否启用 tus 延迟声明文件长度(服务端先不告知总长,边传边算),未设置时默认true(helper.ts)
COMPANION_REDIS_URLredisUrlRedis 连接串,如redis://:密码@服务名.命名空间.svc.cluster.local:6379
COMPANION_SECRETsecret服务端签名密钥。未设置时 Companion 会自动生成并打警告日志(helper.ts),但重启会失效,生产环境务必显式设置
COMPANION_PREAUTH_SECRETpreAuthSecret预授权(preflight)签名密钥,同理建议显式设置
COMPANION_DROPBOX_KEY/COMPANION_DROPBOX_SECRETproviderOptions.dropboxDropbox OAuth 凭证
COMPANION_BOX_KEY/COMPANION_BOX_SECRETproviderOptions.boxBox OAuth 凭证
COMPANION_GOOGLE_KEY/COMPANION_GOOGLE_SECRETproviderOptions.driveGoogle Drive OAuth 凭证(注意:源码中 Google Drive 的 provider 名是drive,见 helper.ts)
COMPANION_AWS_KEY/COMPANION_AWS_SECRET/COMPANION_AWS_BUCKET/COMPANION_AWS_REGION/COMPANION_AWS_PREFIXs3.*S3 上传配置:AccessKey、Secret、桶名、区域与对象键前缀。前缀会拼接到getKey生成的每个对象键之前(defaultStandaloneGetKey,见 helper.ts)
COMPANION_OAUTH_DOMAINserver.oauthDomainOAuth 回调使用的域名
COMPANION_UPLOAD_URLSuploadUrlstus 服务器上传地址白名单。这是安全关键项:validateConfig()在NODE_ENV=production下若未配置会直接抛错拒绝启动(companion.ts);同时会校验条目必须是带协议的绝对 URL(如https://example.com/files/),URL 中的查询串与 fragment 在匹配时会被忽略

仓库的 env_example 还列出了更多可选变量,按需补充到 Secret 中即可:

COMPANION_PORT=3020 # 监听端口,默认 3020 COMPANION_SELF_ENDPOINT=uppy.xxxx.com # 用于向远端上报的自身地址 COMPANION_HIDE_METRICS=false # false 时暴露 /metrics 统计端点 COMPANION_HIDE_WELCOME=false # false 时根路径返回欢迎/启动诊断信息 COMPANION_MAX_FILENAME_LENGTH=500 # 文件名最大长度,默认 500 COMPANION_AWS_ENDPOINT= # S3 兼容端点(非 AWS 时使用) COMPANION_AWS_FORCE_PATH_STYLE="false" # 是否强制 path-style 访问

另一个值得注意的机制是_FILE后缀变量:getSecret()(helper.ts)会优先读取COMPANION_SECRET_FILE、COMPANION_DROPBOX_SECRET_FILE等指向的文件内容,其次才读同名普通变量。在 Kubernetes 中,这正好可以与 Secret 卷挂载结合,让密钥以文件形式进入容器,避免出现在进程环境中。

四、Deployment:companion-deployment.yml

环境变量准备好后,编写 Deployment 拉起 Companion 容器。以下为原文档的companion-deployment.yml完整内容:

apiVersion: extensions/v1beta1 kind: Deployment metadata: name: companion namespace: uppy spec: replicas: 2 minReadySeconds: 5 strategy: type: RollingUpdate rollingUpdate: maxSurge: 2 maxUnavailable: 1 template: metadata: labels: app: companion spec: containers: - image: docker.io/transloadit/companion:latest imagePullPolicy: ifNotPresent name: companion resources: limits: memory: 150Mi requests: memory: 100Mi envFrom: - secretRef: name: companion-env ports: - containerPort: 3020 volumeMounts: - name: companion-data mountPath: /mnt/companion-data volumes: - name: companion-data emptyDir: {}
kubectl apply -f companion-deployment.yml

这份清单的关键点:

  • replicas: 2:多副本 + 共享 Redis 会话,是 Companion 横向扩展的基础;
  • 滚动更新策略:maxSurge: 2允许更新时最多多出 2 个新 Pod,maxUnavailable: 1允许最多 1 个旧 Pod 不可用,minReadySeconds: 5要求新 Pod 就绪后保持 5 秒才继续滚动,兼顾可用性与更新速度;
  • envFrom.secretRef: companion-env:把上一步的 Secret 中全部键值作为环境变量注入容器,是连接"配置"与"运行"的枢纽;
  • containerPort: 3020:与镜像EXPOSE 3020及源码默认端口一致;
  • /mnt/companion-data挂载emptyDir:承接COMPANION_DATADIR指向的下载/暂存目录。但emptyDir的生命周期与 Pod 相同,Pod 重建即清空。原文档在此采用emptyDir便于快速起步;生产环境若需保留中间数据,应改用持久卷(见下文第五节)。

兼容性提示:原文档使用的apiVersion: extensions/v1beta1属于旧版 Kubernetes API(1.16 之前),较新集群已移除。下文仓库自带的参考清单使用apps/v1,建议新部署直接采用。

五、Service:companion-service.yml

Deployment 只负责"跑起来",对外暴露还需要 Service。原文档的 Service 清单:

apiVersion: v1 kind: Service metadata: name: companion namespace: uppy spec: ports: - port: 80 targetPort: 3020 protocol: TCP selector: app: companion
kubectl apply -f companion-service.yml

要点:

  • port: 80是集群内访问端口,targetPort: 3020转发到 Pod 中 Companion 的实际监听端口;
  • selector: app: companion与 Deployment 模板中的 Pod 标签一一对应,Service 据此选择后端 Pod,Kubernetes 会自动为多个副本做负载均衡;
  • 在集群内部,其他组件可通过companion.uppy.svc.cluster.local:80访问它;对集群外部,则需额外配置 Ingress 或 NodePort/LoadBalancer。

六、生产级增强:仓库自带的全套参考清单

除了文档中的基础三件套,仓库在 infra/kube/companion/companion-kube.yaml 提供了一份更贴近生产环境的完整清单(Service + StatefulSet + Ingress + HPA,用---分隔的多个资源)。其关键差异正是对原文档方案的升级方向:

1. 用 StatefulSet 替代 Deployment,实现数据持久化

apiVersion: apps/v1 kind: StatefulSet metadata: name: companion namespace: companion spec: selector: matchLabels: app: companion replicas: 2 serviceName: 'companion' template: metadata: labels: app: companion spec: containers: - image: docker.io/transloadit/companion:latest imagePullPolicy: Always name: companion envFrom: - secretRef: name: companion-env ports: - containerPort: 3020 volumeMounts: - name: companion-data mountPath: /mnt/companion-data volumeClaimTemplates: - metadata: name: companion-data spec: accessModes: ['ReadWriteOnce'] resources: requests: storage: 10Gi

相比原文档的emptyDir,这里的volumeClaimTemplates会为每个副本自动创建独立的 PVC(每副本 10Gi,ReadWriteOnce),Pod 被调度/重建到任意节点时数据都能随卷保留。

2. Ingress + 自动 HTTPS

apiVersion: extensions/v1beta1 kind: Ingress metadata: name: companion namespace: companion annotations: kubernetes.io/tls-acme: 'true' kubernetes.io/ingress.class: 'nginx' certmanager.k8s.io/cluster-issuer: 'letsencrypt-prod' certmanager.k8s.io/acme-http01-edit-in-place: 'true' spec: tls: - secretName: server-tls hosts: - companion.uppy.io - secretName: uppy-tls hosts: - server.uppy.io rules: - host: companion.uppy.io http: paths: - path: / backend: serviceName: companion servicePort: 80 - host: server.uppy.io http: paths: - path: / backend: serviceName: companion servicePort: 80

通过 cert-manager 注解自动签发 Let's Encrypt 证书,companion.uppy.io与server.uppy.io两个域名都路由到companionService 的 80 端口。

3. HorizontalPodAutoscaler 自动扩缩容

apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: companion namespace: companion spec: scaleTargetRef: apiVersion: apps/v1 kind: Statefulset name: companion minReplicas: 1 maxReplicas: 5 targetCPUUtilizationPercentage: 80

当平均 CPU 使用率超过 80% 时,HPA 会在 1~5 个副本之间自动伸缩,与 StatefulSet 的滚动更新策略配合,形成弹性伸缩能力。

4. CI/CD 参考:gcloud-deploy.sh

仓库的 gcloud-deploy.sh 展示了镜像发布与集群更新的完整链路:构建并推送transloadit/companion镜像(commit/tag/latest 三标签)→ 解码KUBECONFIGVAR写入 kubeconfig → 用kubectl set image statefulset companion ...热更新镜像 → 依次kubectl get pods/service/deployment校验部署结果。这套脚本的思路可直接迁移到你自己的 CI 流水线中。

七、日志查看

部署完成后,查看生产 Pod 的日志:

kubectl logs my-pod-name

如果 Pod 名不易确定,也可以直接用 Deployment/StatefulSet 级联查看:

# 查看命名空间下所有 Pod kubectl get pods -n uppy # 跟随输出某个 Deployment 的最新日志 kubectl logs deployment/companion -n uppy --follow # 查看上一个(已崩溃的)容器实例的日志 kubectl logs my-pod-name --previous

Companion 的日志系统也内置了安全处理:在 standalone/index.ts 中,请求日志(morgan)会通过censorQuery()对access_token、uppyAuthToken等敏感查询参数做掩码处理(替换为********),避免 OAuth 过程中的令牌泄漏到日志里。排查问题时若发现日志中看不到完整参数,这属于预期行为,而非日志丢失。

八、部署自检清单

综合原文档与源码,完成部署后建议按以下顺序核对:

  1. 命名空间与资源就位:kubectl get ns uppy、kubectl get secret companion-env -n uppy、kubectl get pods -n uppy均正常;
  2. Redis 可达:COMPANION_REDIS_URL中的服务名、命名空间(uppy-redis.uppy.svc.cluster.local)、密码与 Helm 安装时--set password一致;
  3. 生产强制项:COMPANION_UPLOAD_URLS必须配置且为带协议的绝对 URL(含 tus 服务地址),否则NODE_ENV=production下 Companion 拒绝启动(companion.ts);
  4. 目录可写:COMPANION_DATADIR指向的挂载路径必须存在且可读写,否则validateConfig()在启动时抛错;
  5. Secret 显式设置:COMPANION_SECRET与COMPANION_PREAUTH_SECRET不要依赖自动生成,避免重启后会话/预授权签名失效;
  6. OAuth 回调:启动日志(欢迎页或kubectl logs)会列出每个 provider 的/redirect回调地址,需与云服务商开发者后台配置一致。

至此,从命名空间、Redis、Secret 配置到 Deployment、Service,再到 StatefulSet 持久化、Ingress、HPA 与日志排查,一套完整的 Companion on Kubernetes 部署链路已全部打通。若需要更完整的配置变量清单,可对照 env_example 与 schemas/companion.ts 按需补充。

  • 前端
  • UI组件
  • 后端

【免费下载链接】uppy

The next open source file uploader for web browsers :dog:

项目地址:https://gitcode.com/gh_mirrors/up/uppy
点击查看免费下载
上一篇:Go pflag 深度实战:POSIX/GNU 风格命令行 Flag 解析(KubeSphere 中的应用)
下一篇:3步部署SQLBot:5分钟搭好智能问数平台,从Docker启动到首次问数完整教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 2:11:51

旅行商问题:从数学模型到 Python 实战

旅行商问题(Traveling Salesman Problem, TSP)是组合优化领域中一颗璀璨的明珠,也是计算机科学中 NP-hard 问题的典型代表。其问题描述简洁而优雅:给定 nnn 个城市以及两两之间的距离 d(i,j)d(i, j)d(i,j),求解一条从某…

作者头像 李华
网站建设 2026/9/30 2:10:57

05-循环语句

循环语句 for循环 例 while循环 例 在写代码时,选择for循环还是while循环do…while循环无限循环 例 循环控制 break例题continue例题小结 猜数字(经典算法题) 生成随机数猜数字代码 循环嵌套 例题1例题2 循环语句 配套完整代码&#xff1…

作者头像 李华
网站建设 2026/9/30 2:10:09

嵌入式调试方法论:从柯南式排查到系统化调试思维

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华