本文是基于 App版本制作教程详解 的改编版本,起因是我在制作新版应用的时候忘记如何调用环境变量了,于是搜索官方文档,但是却发现官方文档的歧义很大,最终试了半天才发现官方文档写的有一些误导人,于是出现了我的评论:
App版本制作教程详解 - #5,来自 hhjmk
为了不让我码的字被浪费,于是我决定在这里打广告:
雨云优惠码:github 新用户注册可享首月5折
本文可能在官方文档修改后无法及时同步更新,因此,最终请以官方文档为准。不过截至文章编写日,官方文档反而有些字段是过时的。
文中补充的大量示例均为我云应用的截图,并且这些云应用至少用起来是正常的,但这并不代表我的示例就是最优解。
以下是正文
云应用的App版本设计参考了1Panel、docker-compose和Kubernetes里面的一些概念。并且进行了简化和抽象,提供了一些用户友好的接口供开发者创建使用。
App的架构
App是云应用的基础组成部份,一个App对应一个K8S中的Deployment。
App是由App模版创建而来的,App模版定义了App的基础信息以及选项参数,用户通过在商店中填入、选择对应App的信息和参数来实施具体部署,部署成功后的实例为App。
App模板
要制作一个新的App模板,请通过可视化工具进行创建:
但是,某些手段能够绕过官方可视化工具的前端限制实现一些高级功能,但由于云应用更新迭代快速,陈旧的结构化创建工具已经不保证可用,相关源代码可在此查看:
此源码部署的在线版本不保证可用性,因为新版的 windblog 也即将上线,届时可能替换此网站后端:
如果您希望自托管,可以尝试
顺带一提,此社区版控制台林雨也参观过,并且目前的云应用ui部分灵感就来自我的控制台
例如,设置项的类型不仅有官方的可视化工具那么多,在此开源项目编写时,还可以使用apps选项:
架构图
可以参考各个概念的总体架构图。
版本
App模板只定义了App的基础信息,只是介绍性质,要定义具体的内容,我们需要创建应用版本。
基本信息
- 版本号:软件的版本。
- 镜像:是Docker镜像名或完全限定镜像名,如:
nginx:latest或docker.io/library/nginx:latest - 最小CPU:部署此应用最低需求的CPU核心算力,将在用户部署的时候进行约束。
- 最小内存:部署此应用最低需求的内存数量,将在用户部署的时候进行约束。
Command &Env 命令与参数
-
Command:用于覆盖容器镜像中的默认
ENTRYPOINT。它定义了容器启动时执行的主要命令。 -
Args:用于覆盖镜像中的默认
CMD,作为容器主进程的参数列表。若同时设置Command,则这些参数会传递给Command指定的程序。 -
如果希望
/bin/sh或/bin/bash将后面的字符串作为 Shell 命令解析执行,需要使用-c,例如command: ["/bin/sh", "-c"]、args: ["echo hello && sleep 10"]。也就是下方的例子所标示的设置方式*
可以包含如${ENV_NAME}这样的格式来引用环境变量。
一个官方的配置示例是这样配置的:
同理,如果你启动一个jar程序,可以如此:
但是需要注意的是,如果这样写通常不正确:
因为此时Java收到的并非是两个参数而是一个,所以可能会遇到这个问题:
Unrecognized option: -jar xxx.jar
Error: Could not create the Java Virtual Machine.
Scripts 脚本设定
- 安装脚本:在容器运行之前,将通过一个单独的容器来执行的Shell脚本。例如某些Minecraft服务端需要在启动之前拉取服务端文件,可通过此功能实现。
- 安装镜像:要运行安装脚本的基础镜像。例如可以是
debian:bookworm - 启动脚本:在主容器内运行的、每一次容器启动都会运行的脚本。
- 停止脚本:在主容器内运行的、每一次容器停止前都会运行的脚本。
可以包含如${ENV_NAME}这样的格式来引用环境变量。
关于安装时容器的说明
安装时容器就是 initContainer,安装时容器可以在主容器运行之前执行一次性安装的任务。
它的环境变量和主容器共享一致。同时会在容器内的/app目录内挂载主容器所使用的全部持久化挂载点。已自动将/app目录设置为工作目录。
ConfigMap 配置文件
配置文件将在部署后给用户一个友好的GUI来编辑配置。如果你的App需要配置文件,使用此功能将很合适,而且还可以提前提供默认配置。
请注意,配置文件是只读挂载到容器内,因此只适用于程序不会修改的那种配置文件,如果容器程序会修改配置文件,则不适用,这种情况下,请使用持久化卷。
- 文件名:配置文件名,如nginx.conf。
- 容器内路径:要把配置文件挂载至容器内的路径。
- 配置文件内容:内容,建议提供预设内容。
一个典型的例子是这样的:
上述例子为 FrankenPHP 中挂载 Caddy 的配置文件 Caddyfile,注意,这里是大小写敏感的。
Env 环境变量
容器运行时使用的环境变量列表。
- 键(Key):环境变量的键。
- 值(Value):环境变量的值,可以包含如
${ENV_NAME}这样的格式,以引用其他环境变量来组合这个环境变量。
雨云云应用平台支持使用 ${ENV_NAME} 形式进行模板替换。注意,这属于平台提供的能力,不等同于 Kubernetes 原生 command/args 的环境变量展开规则。
但环境变量的名字是完全自由的,但是请不要尝试覆盖系统环境变量,例如 KUBERNETES_PORT_443_TCP_ADDR
一个典型的例子是这样的:
上述例子中的 环境变量 #2 引用了 环境变量 #7
此处还使用了一个技巧,环境变量的“值”不一定必须是干干净净的,可以混合写内容和环境变量引用;上述例子就是为了在用户视角只需要写一个端口号,例如 8899,而应用实际收到的是 :8899,因此, 环境变量 #2 的“值”中可以注意到,在环境变量引用前还有一个冒号。
VolumeMounts 持久化卷挂载
云应用每一个项目都有一个共享的磁盘,里面存放着项目下各个App的持久化数据,可通过此功能将容器产生的持久化数据进行保存。
- 名称:卷的名称。
- 容器内路径:挂载至容器内的路径。可以包含如
${ENV_NAME}这样的格式来引用环境变量。 - 持久卷内路径:挂载至应用在项目磁盘中的子路径。可以包含如
${ENV_NAME}这样的格式来引用环境变量。 - 内容类型:可选文件或目录两种类型。
- 预设内容:当内容类型为文件时,可通过
base64编码将二进制文件传入。
一个典型的目录持久化例子是:
一个典型的例子是:
上述例子是为了给 RabbitMQ 传递一个配置文件,但是内容区却是一段看起来乱码的东西,这个配置文件的原文是:
{
"vhosts": [{"name":"/"}],
"users": [{"name":"seeray","password_hash":"5SIcPnQgBjL17qTjUZjkQKuxXpia70I4hpTA0Es6eXByDi1C","hashing_algorithm":"rabbit_password_hashing_sha256","tags":"administrator"}],
"permissions": [{"user":"seeray","vhost":"/","configure":".*","write":".*","read":".*"}],
"exchanges": [{"name":"seeray.tracking","vhost":"/","type":"topic","durable":true,"auto_delete":false}],
"queues": [
{"name":"seeray.tracking.ingest.v1","vhost":"/","durable":true,"auto_delete":false,"arguments":{"x-queue-type":"quorum","x-delivery-limit":5}},
{"name":"seeray.tracking.retry.v1","vhost":"/","durable":true,"auto_delete":false,"arguments":{"x-queue-type":"quorum","x-message-ttl":5000}},
{"name":"seeray.tracking.dlq.v1","vhost":"/","durable":true,"auto_delete":false,"arguments":{"x-queue-type":"quorum"}},
{"name":"seeray.heatmap.ingest.v1","vhost":"/","durable":true,"auto_delete":false,"arguments":{"x-queue-type":"quorum","x-delivery-limit":5}},
{"name":"seeray.heatmap.dlq.v1","vhost":"/","durable":true,"auto_delete":false,"arguments":{"x-queue-type":"quorum"}}
],
"bindings": [
{"source":"seeray.tracking","vhost":"/","destination":"seeray.tracking.ingest.v1","destination_type":"queue","routing_key":"event.v1"},
{"source":"seeray.tracking","vhost":"/","destination":"seeray.heatmap.ingest.v1","destination_type":"queue","routing_key":"heatmap.v1"}
]
}
在这里需要对其进行一次 base64 编码,并且把编码结果填入“预设内容”
说实话,我大多数情况都使用configmap(配置文件),但是有一个值得解释的概念:持久卷内路径(子路径)
简单说,它的作用是:不要把整个 Volume 挂进来,只取 Volume 里面的这个子路径挂进来。
对于从docker来的人可能并不容易理解这个东西,但是可以这样理解(这在k8s中其实是半对半错的,如此比喻仅是方便新手):
容器内路径是“你希望这个文件在容器里的什么位置”
持久卷内路径是“你要挂载的文件名”
如果你有一些基础知识,这样理解才是正确的: 持久卷内路径是Volume 内的相对路径。
这里的 Volume 就是你在云应用里看到的这个:
其中每个 seeray-lens-6wu64k 就是一个 Volume,而“持久卷内路径”就是从这里开始起算的相对路径(不能是以 / 开头的绝对路径)
如果看到这里你还没有理解 Volume 这个东西,那么把 Volume 想成是一个文件夹即可
Volume(把它想成一个文件夹)
├── definitions.json
├── redis/
│ └── redis.conf
└── data/
上述是k8s视角,以上面的例子,这是雨云视角:
seeray-lens-6wu64k(你在文件管理中看到的其中一个文件夹)
├── definitions.json(这就是“持久卷内路径”填写为`definitions.json`的保存位置)
├── redis/ (这是一个文件夹)
│ └── redis.conf(这就是“持久卷内路径”填写为`redis/redis.conf`的保存位置)
└── data/ (这是你挂载的目录)
Services 服务配置
定义了容器要对外开放的端口。
- 服务名称:服务命名,以英文作标识。
- 显示名称:可以是中文介绍,用户友好。
- 服务类型:定义服务的默认可见度,分为
内部访问和外部访问两种。内部访问:仅供项目内其他应用访问,外部访问:开放至公网允许公网访问。 - 内部端口:容器内端口,支持嵌入环境变量,如
${ENV_NAME}。 - 外部端口:外部访问的端口,支持嵌入环境变量,如
${ENV_NAME}。 - 协议:服务要使用的协议,可以是tcp/udp或tcp_udp。
一个典型的例子是:
图中为 RabbitMQ 的服务配置,其中 5672 端口是给应用连接用的,故设置为内部访问,而 15672 端口是 RabbitMQ 的 WebUI 管理网页,所以需要设置为外部访问,这样我们才能在浏览器上访问它。
Options 选项
用户友好的选项设置,可以在部署的时候限定各种值、校验。
- 标签:用户界面上显示的名称。
- 环境变量键:对应到环境变量键,应用安装时,会根据用户输入的值,自动注入到对应的环境变量中。
- 类型:用户输入控件的类型。
- 默认值:用户未修改时的默认值。
- 验证规则:用户输入验证的规则。
- 是否必填:用户是否必须填写此选项。
- 是否禁用:启用后,用户将无法修改此参数的值。
- 是否随机生成:启用后,将以"默认值_随机字符串"(如
word_Nkd4uX)的格式自动生成。
一个典型的例子是:
要使用它,你至少需要一个环境变量:
对于这个例子,实际上不需要这些选项也能够正常启动,这里的选项只是充当了一个随机字符生成工具,因为不能给所有用户的密码等机密信息全部都设置为一样的,所以需要开启这里的随机生成来进行一次随机。
当设置了选项后,选项中的“默认值”或用户手动修改后的值或随机后的值会覆盖掉所选的“环境变量键”,你不必将环境变量的值与此处的默认值设置的一致,因为最终会被覆盖。
特殊环境变量
有一些特殊的环境变量可以使用,这些环境变量会在应用安装的时候被系统自动注入,会替换成实际的值。
- ${rca_svc_[容器名]_[服务名]_ext_ip}:如
${rca_svc_main_mysql_ext_ip},这个会自动获取对应容器的指定服务的公网IP地址。可用于部分程序需要提前配置公网访问IP的情况下使用。 - ${rca_svc_[容器名]_[服务名]}:如
${rca_svc_main_mysql},这个会自动获取对应容器的指定服务的内部集群连接地址。对于多容器互连的情况下使用,例如主容器需要连接到数据库容器,因此需要获取它的地址。
值得注意的是,这些环境变量最终被注入的只有目标服务的地址,不携带端口。
一个典型的例子是:
主应用:
数据库:
对于这个例子,我需要在应用运行的容器 main 中连接另一个容器 postgres 的PostgreSQL数据库,于是我通过将环境变量的值设置为 jdbc:postgresql://${rca_svc_postgres_pgsql}:5432/${POSTGRES_DB} 来解决这个问题。
但是更值得注意的是,如果你的service配置错了,那么你不能像连接另一台服务器那样连接你的数据库。
对于新手,请将你的 service 的内部和外部端口设置为一致的。
一个典型的应用内部的环境变量可能是(可以执行 env 命令获取当前容器的环境变量:
KUBERNETES_SERVICE_PORT_HTTPS=443
SEERAY_RABBITMQ_HOST=svc-seeray-lens-sdqmxq
KUBERNETES_SERVICE_PORT=443
SEERAY_DB_JDBC_URL=jdbc:postgresql://svc-seeray-lens-8jnrll:5432/seeray_WpNfYy
HOSTNAME=seeray-lens-pl2ajy-main-55f99d88b6-hzbwb
POSTGRES_PASSWORD=pwd_ikrcBM
SEERAY_RABBITMQ_PASSWORD=seeray
PWD=/work
SEERAY_GEO_TRUSTED_PROXY_CIDRS=127.0.0.1/32
SEERAY_SECRET_ENCRYPTION_KEY=YjkUdexQfnm8oScY2LBID+t5g3gvgm2FTdjBn7Q2sZk=
container=oci
HOME=/
KUBERNETES_PORT_443_TCP=tcp://10.16.0.1:443
SEERAY_DB_USERNAME=seeray_zBkDJd
SEERAY_RABBITMQ_PORT=5672
TERM=xterm
SEERAY_AUTH_JWT_ISSUER=seeray-lens
SHLVL=1
POSTGRES_USER=seeray_zBkDJd
SEERAY_CORS_ORIGINS=*
KUBERNETES_PORT_443_TCP_PROTO=tcp
SEERAY_AUTH_JWT_SECRET=local-development-only-change-before-production
MGMT_EXT_ADDR=svc-seeray-lens-41h5os
KUBERNETES_PORT_443_TCP_ADDR=10.16.0.1
SEERAY_GEO_LOCATION_ENABLED=false
KUBERNETES_SERVICE_HOST=10.16.0.1
KUBERNETES_PORT=tcp://10.16.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
SEERAY_RABBITMQ_USERNAME=seeray
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SEERAY_DB_PASSWORD=pwd_ikrcBM
POSTGRES_DB=seeray_WpNfYy
SEERAY_REDIS_HOSTS=redis://svc-seeray-lens-peo7qz
_=/usr/bin/env
可以注意到,无论是 MGMT_EXT_ADDR=svc-seeray-lens-41h5os 还是 SEERAY_REDIS_HOSTS=redis://svc-seeray-lens-peo7qz 都是不携带端口号的















