01
背景
在云原生环境中,应用程序都需要以容器镜像的形式部署,相对于传统的应用程序部署方式,云原生应用部署存在以下挑战:
开发者不仅要进行业务逻辑的开发,还要维护自己项目的 Dockerfile
需要容器镜像具有一定的安全性
云端可能会有特定的部署策略,所以会要求容器镜像具有一定的规范性
针对以上挑战, 我们在函数计算平台中增加了构建系统 build-platform 。开发者上传代码到 build-platform 以后,只需经过简单的配置便可以生成安全且符合特定规范的容器镜像,开发者无需了解容器镜像的细节以及复杂的 Dockerfile 语法便可以简单高效地完成容器镜像的构建和应用程序升级。
02
概述
build-platform 在函数计算中负责用户项目构建:用户上传应用程序的代码 zip 包,build-platform 下载 zip 包后解压,根据用户选择的构建方式在云端完成项目的构建,生成应用程序镜像后由函数计算平台启动容器,在容器内运行用户的应用程序。
目前 build-platform 支持 Nodejs、Python、Go、Java、PHP 五种语言的项目构建,用于执行构建过程的镜像和构建生成的应用程序镜像均以 ubuntu:jammy 作为基础镜像。支持的平台为 x86_64。
本文主要以 Nodejs 项目构建为例说明 build-platform 的使用方法,其它语言的构建可参考控制台上的示例项目以及帮助文档。
03
目录结构约束
源代码目录结构约束
假设用户项目目录为 proj_dir,则用户上传的 zip 包需要保证解压后得到的目录为 proj_dir。build-platform 默认在 proj_dir 目录下进行构建。
Subdir配置
假设用户项目目录为 proj_dir,但是需要在 proj_dir 目录下的某个子目录下进行构建,则需设置页面上的 Subdir配置 。例如,如下所示的项目目录结构中需要在 proj_dir/subdir 目录下进行构建,则需要将 Subdir配置 设置为 subdir 。
工作目录
在函数构建和运行过程中,用户的项目目录会存放到 /workspace 目录下。/workspace 目录内容会保留到构建生成的镜像中。
本文剩余部分提到的工作目录含义为:默认为上传的项目的根目录,如果设置了subdir,则为对应的子目录。
在构建阶段,构建命令在工作目录下执行,在运行阶段,启动命令在工作目录下执行。
假设用户项目目录为 proj_dir 且此目录中包含 server.js 文件,则在构建和运行阶段,用户均可以通过相对目录 ./server.js 或者绝对目录 /workspace/proj_dir/server.js 访问原来的 proj_dir/server.js 文件。
假设用户项目目录为 proj_dir ,在控制台将 Subdir配置 设置为 subdir 且 proj_dir/subdir 中包含 server.js 文件,则在构建和运行阶段,用户均可以通过相对目录 ./server.js 或者绝对目录 /workspace/proj_dir/subdir/server.js 访问原来项目中的 proj_dir/subdir/server.js 文件。
04
构建变量
构建变量是指作用于构建过程中的环境变量,可以在控制台设置,也可以通过文件设置。
控制台设置构建变量
在自动构建方式下,在控制台选择了特定语言运行时以后,会自动显示出此运行时所支持的部分构建变量:
通过文件设置构建变量
project.toml
如果构建方式不是 Dockerfile 构建方式,且控制台没有提供构建变量设置入口,则可以在工作目录下创建 project.toml 文件,在 project.toml 文件中写入构建变量列表,格式如下所示:
[[build.env]]
name='BP_PHP_SERVER'
value='nginx'
[[build.env]]
name='KEY'
value='value'
如果某个构建变量既在 project.toml 中设置,又在控制台设置,则控制台设置的构建变量优先级更高。
argfile.conf
在 Dockerfile 构建方式下,不支持控制台设置构建变量,可以在工作目录下创建 argfile.conf 文件,在 argfile.conf 文件中写入构建变量列表,格式如下所示:
ENV1=value1
ENV2=value2
支持的构建变量列表
build-platform 支持的完整的构建变量列表见 build-platform 构建变量 ,除了此列表中的构建变量以外,构建过程中用到的用户自定义构建变量也可以通过文件设置。
build-platform构建变量请阅读(复制后浏览器打开):
https://apicloud.360.cn/user/apistore/detail?projectId=169830537878407000015&docId=169830999328821000001&sharedId=d/i895ABAAA_&config=menu_toc_search
05
使用内置运行时创建函数
内置运行时构建方式是指 build-platform 内部维护 Web 框架,用户根据平台提供的规范编写框架对应的请求处理程序,并将请求处理程序上传到云端,云端构建后生成镜像并部署。
请求处理程序是函数代码中处理请求的方法。对 Node.js 语言的函数应用而言,请求处理程序格式为 文件名.方法名。例如:处理请求的方法名为 handler,handler 方法所在的文件名为 index.js ,则请求处理程序为 index.handler 。
编写请求处理程序
创建项目目录 node-http-handler ,在此目录下添加 index.js 文件,代码如下:
/**
* Your HTTP handling function, invoked with each request. This is an example
*
* @param {Context} context a context object.
* @param {object} context.body the request body if any
* @param {object} context.query the query string deserialized as an object, if any
* @param {object} context.log logging object with methods for 'info', 'warn', 'error', etc.
* @param {object} context.headers the HTTP request headers
* @param {string} context.method the HTTP request method
* @param {string} context.httpVersion the HTTP protocol version
* See: https://github.com/knative/func/blob/main/docs/function-developers/nodejs.md#the-context-object
*/
const handler = async (context, body) => {
// YOUR CODE HERE
context.log.info("query", context.query);
context.log.info("body", body);
// If the request is an HTTP POST, the context will contain the request body
if (context.method === 'POST') {
return { body };
} else if (context.method === 'GET') {
return { statusCode: 200, statusMessage: 'Hello world' };
} else {
return { statusCode: 405, statusMessage: 'Method not allowed' };
}
}
module.exports = { handler };
部署函数
把 node-http-handler 目录压缩为 node-http-handler.zip ,如果是在 linux 环境下,使用以下打包命令:
zip -r node-http-handler.zip node-http-handler
zip 包目录结构为:
在函数计算页面点击 函数管理 -> 创建函数:
选择 使用内置运行时创建 ,输入函数名和描述,点击 本地zip包上传 ,上传 node-http-handler.zip :
在运行环境下拉菜单中选择项目对应的语言运行时,这里选择 Node.js 18.* ,填写请求处理程序为 index.handler ,这里 index 对应文件名,handler 对应方法名 :
配置运行环境,这里使用默认的配置。点击右下角完成创建 :
构建过程中可以修改构建和部署的配置(点击编辑即可),页面右上角会在构建过程中出现构建进度条,可以点击查看详情:
点击弹窗查看构建日志:
构建完成后点击触发器,复制地址即可访问函数:
06
使用自定义运行时创建函数
自定义运行时支持以下四种构建方式:自动构建、自定义构建、Dockerfile 构建、无需云端构建。
自动构建
在自动构建方式下,无需编写 Dockerfile ,build-platform 会根据工作目录中的文件,决定要下载和安装的运行时和依赖,例如:根据工作目录中的 go.mod 文件检测出此项目为 Go 项目,并从 go.mod 中获取 Go 版本以及项目依赖,然后下载安装 Go 运行时和依赖,用 go build 命令编译,编译完成后导出镜像。类似的,如果工作目录中有 package.json 文件,则会进行 nodejs 运行时和依赖的下载安装。
自动构建方式下,也可以在控制台运行环境下拉框中指定语言运行时和版本,默认使用自动检测:
自动构建方式的具体操作流程示例如下:
项目根目录为 func-nodejs ,将 func-nodejs 打包成 func-nodejs.zip,在 linux 环境下,可以使用以下命令打包:
zip -r func-nodejs.zip func-nodejs
zip 包目录结构为:
函数计算控制台依次点击 函数管理 -> 创建函数 -> 本地zip上传 ,将打包的 zip 包上传后选择 自动构建,运行环境下拉菜单中使用默认的 自动检测 ,由于是在项目根目录下构建,所以不用填 Subdir配置 。
自动构建方式会自动设置启动命令,所以通常无需手动设置启动命令。如果需要在启动命令中添加命令行参数,如指定配置文件,则需手动设置启动命令。
Go 函数项目的二进制可执行程序名称固定为工作目录下的 main ,可以设置类似 main -c conf/online.conf 这样的启动命令(如果构建之前工作目录中包含main 文件,则需要对此文件重命名后再上传项目进行构建,其它语言无此限制)。
Go 和 Java 等编译型语言构建完成后,基于安全性考虑,build-platform 会在生成的镜像中删除工作目录下的文件,所以如果启动命令中需要访问工作目录中的文件,需要通过设置构建变量来指定需要保留的文件列表。
执行以上操作以后点击 完成创建 ,跳转到函数详情页面:
页面右上角会在构建过程中出现构建进度条,可以点击查看详情:
构建完成后点击触发器, 复制链接即可访问web服务:
自定义构建
当自动构建不能满足需求时,可以使用自定义构建。自定义构建方式下,例如:在控制台 运行环境 下拉菜单中选择 Nodejs 18.* 后,build-platform 会安装Nodejs 18,用户在构建阶段和运行阶段都可以直接使用 node 命令,依赖下载和其它构建工作由用户自己控制,执行完用户自定义的构建工作以后,build-platform 负责导出应用程序镜像。
如果 运行环境 下拉菜单中没有需要的运行时版本,或者构建过程中涉及到不止一种语言运行时,则可以在 运行环境 下拉菜单中选择 ubuntu:jammy ,此时 build-platform 不会安装任何语言运行时,整个构建过程完全由用户控制,此时需要用户自行安装所需语言运行时。用户在构建阶段将需要导出到应用程序镜像的内容放到工作目录 ,在启动命令中访问工作目录中的内容。
使用预置的语言运行时
示例项目结构如下:
按照和自动构建相同的步骤上传zip包以后,选择 自定义构建 ,运行环境 下拉框中选择 Nodejs 18.* ,构建程序入口 设置为 npm ci --unsafe-perm ,启动命令 设置为:chmod +x ./run.sh; ./run.sh ,点击 完成创建 开始创建函数:
此示例函数对应的构建流程为:build-platform 提供 Nodejs 18 运行时 -> 执行 构建程序入口 指定的命令 -> 导出应用程序镜像。
由于 build-platform 已经预置了 nodejs 运行时,所以用户可以直接使用 npm 命令,构建过程在 /workspace/func-userdifined-nodejs 目录下进行,所以npm会安装依赖到 /workspace/func-userdifined-nodejs/node_modules 目录。
由于此示例的启动过程涉及到多条命令,所以编写 shell 脚本 run.sh 来包含这些命令,run.sh 会随着 /workspace 目录导出到应用程序镜像中。run.sh 内容如下,其中 start.sh 中包含命令 node server.js 。构建完成后访问函数的方法和自动构建一样。
#!/bin/sh
set -e
# pwd is /workspace/func-userdifined-nodejs
export NPM_CONFIG_LOGLEVEL="error"
export PATH="$PATH:/workspace/func-userdifined-nodejs/node_modules/.bin"
sh start.sh
不使用预置的语言运行时
示例项目结构如下:
按照和自动构建相同的步骤上传 zip 包以后,选择 自定义构建 , 运行环境 下拉框中选择 ubuntu:jammy ,构建程序入口 设置为 chmod +x ./build.sh; ./build.sh ,启动命令 设置为:chmod +x ./run.sh; ./run.sh ,点击 完成创建 开始创建函数:
相比于上节使用预置的语言运行时 Nodejs 18.* 的示例,这里的构建入口程序为 build.sh 脚本,脚本内安装了 nodejs 运行时,build.sh 内容如下:
#!/bin/sh
set -e
uri="https://nodejs.org/dist/v18.16.0/node-v18.16.0-linux-x64.tar.xz"
# 安装nodejs到当前目录(/workspace/func-userdefine/) ,/workspace中的内容会被添加到构建生成的image中
# 用户也可以使用自己上传到层目录中的运行时和依赖
mkdir -p node
wget -q -O - "$uri" | tar xJ -C "node" --strip-components=1
rm -rf node-v18.16.0-linux-x64.tar.xz
export NODE_HOME="${PWD}/node"
export PATH="${NODE_HOME}/bin:${PATH}"
npm ci --unsafe-perm
# 如果有不希望出现在镜像中的内容(比如go项目的源代码),请手动删除
# rm -rf [some files]
run.sh 的内容也有所不同,需要设置 nodejs 运行时相关的环境变量 NODE_HOME ,run.sh 内容如下:
#!/bin/sh
set -e
# 容器启动后在工作目录/workspace/func-userdefine/ 中运行
# 访问在build阶段安装在/workspace/func-userdefine/ 中的内容
export NODE_HOME="${PWD}/node"
export PATH="${PATH}:${NODE_HOME}/bin"
sh ./start.sh
其它的流程和操作方法与上节使用预置的语言运行时 Nodejs 18 的示例函数相同。
Dockerfile构建
如果项目中有 Dockerfile ,可直接使用 Dockerfile 构建方式:
控制台点击 函数管理 -> 创建函数 ,选择 自定义运行时构建,上传zip包后点击选择 Dockerfile ,如果项目根目录下有 Dockerfile 文件,则无需指定Dockerfile,否则,必须指定 Dockerfile,指定时以项目根目录为当前目录,比如 dockerfile、./dockerfile、 ./build/dockerfile 。
build-platform 会在 Dockerfile 文件所在的目录下进行构建。
无需云端构建
在不需要构建的情况下,可以使用无需云端构建方式,此方式仍可选择 build-platform 预置的语言运行时用于在运行阶段使用。用户在本地完成构建,然后将构建结果打包上传,build-platform 将用户上传的内容导出到应用程序镜像中,用户在启动命令中访问工作目录中的内容。
build-platform 导出的应用程序镜像以 ubuntu:jammy 为基础镜像,支持的平台为 x86_64 ,所以用户上传的项目需要兼容此环境。
07
在build-platform中使用层功能
您可以将函数依赖的公共库提炼到层,以减少部署或更新函数时的代码包体积。
build-platform 会将特定的目录添加到对应的语言的依赖包搜索路径中,如下表所示。如果在层 zip 包中定义了与其相同的文件夹结构,则函数代码无需指定路径即可访问层。
运行时 | 特定的目录 |
---|---|
Python | /opt/python |
Node.js | /opt/nodejs/node_modules |
Java | /opt/java/lib |
PHP | /opt/php |
公共 | /opt/bin (PATH) , /opt/lib (LD_LIBRARY_PATH) |
如果用户上传的层解压后不是以上目录,build-platform 不会将解压后的目录添加到依赖包搜索路径中,这时如果用户要使用这个层,则需要在代码中显式添加依赖库搜索路径。
使用方法
以 Nodejs 为例,示例项目目录结构如下:
层 zip 包结构如下,根目录名称为 nodejs ,依赖所在的目录为 nodejs/node_mudules ,zip 包会解压到 /opt 目录,解压后的目录结构为 /opt/nodejs/node_mudules :
在创建函数之前先创建层:首页点击 层管理 -> 创建层,弹出的窗口中输入层名称,选择运行环境为 Nodejs 的多个版本(创建之后只能被对应运行时版本的函数所使用),上传层的 zip 包 nodejs.zip ,点击确定后可看到创建的层:
创建函数使用层:上传项目代码 zip 包,选择运行环境为 Nodejs 18.* ,各种构建方式都支持层功能,此示例使用无需云端构建方式,点击层配置 -> 添加层 ,选择创建的层,点击确定,点击完成创建:
创建成功后点击触发器地址即可访问函数:
此示例把依赖作为层上传以后,构建过程中并没有任何安装依赖的地方,用户也无需配置依赖路径,函数启动后使用了层中的依赖。
特例
对于 PHP :如果要引用层中的依赖,需要在项目入口文件里添加如下语句:
<?php
$path = '/opt/php';
set_include_path(get_include_path() . PATH_SEPARATOR . $path);
对于 Dockerfile 构建方式::由于平台添加 ENV 指令到 Dockerfile 可能会覆盖用户设置的环境变量值,所以如果要引用层中的依赖,需要用户在需要时手动添加环境变量,添加方式如下:如果用户的 Dockerfile 中设置了 PATH 环境变量,则用户将 /opt/bin 添加到 PATH 环境变量,否则,在 Dockerfile 中添加一行:
ENV PATH=${PATH:+${PATH}:}"/opt/bin"
如果用户的 Dockerfile 中设置了 LD_LIBRARY_PATH 环境变量,则用户将 /opt/lib 添加到 LD_LIBRARY_PATH 环境变量,否则,在 Dockerfile 中添加一行:
ENV LD_LIBRARY_PATH=${LD_LIBRARY_PATH:+${LD_LIBRARY_PATH}:}"/opt/lib"
如果用户的 Dockerfile 中设置了 PYTHONPATH 环境变量,则用户将 /opt/python 添加到 PYTHONPATH 环境变量,否则,在 Dockerfile 中添加一行:
ENV PYTHONPATH=${PYTHONPATH:+${PYTHONPATH}:}"/opt/python"
如果用户的 Dockerfile 中设置了 NODE_PATH 环境变量,则用户将 /opt/nodejs/node_modules 添加到 NODE_PATH 环境变量,否则,在 Dockerfile 中添加一行:
ENV NODE_PATH=${NODE_PATH:+${NODE_PATH}:}"/opt/nodejs/node_modules"
如果用户的 Dockerfile 中设置了 CLASSPATH 环境变量,则用户将 /opt/java/lib 添加到 CLASSPATH 环境变量,否则,在 Dockerfile 中添加一行:
ENV CLASSPATH=${CLASSPATH:+${CLASSPATH}:}"/opt/java/lib"
08
总结
build-platform 将构建部分从软件开发流程中剥离出来,使开发者可以更专注于业务逻辑的开发,提升了开发效率,降低了开发门槛。
目前,build-platform 已提供了多种构建方式以实现从源码构建出安全且符合规范的容器镜像,可以满足大部分构建需求,且还在不断地进行功能完善和用户体验的提升。
build-platform 是函数计算平台的子系统,函数计算平台可以让您快速高效地完成代码的一站式构建和部署,欢迎使用函数计算平台!
更多产品和技术文章,敬请关注👆
360智汇云是以"汇聚数据价值,助力智能未来"为目标的企业应用开放服务平台,融合360丰富的产品、技术力量,为客户提供平台服务。
目前,智汇云提供数据库、中间件、存储、大数据、人工智能、计算、网络、视联物联与通信等多种产品服务以及一站式解决方案,助力客户降本增效,累计服务业务1000+。
智汇云致力于为各行各业的业务及应用提供强有力的产品、技术服务,帮助企业和业务实现更大的商业价值。
官网:https://zyun.360.cn
客服电话:4000052360