Serverless 多环境配置方案探索

Stella981
• 阅读 725

相信读完前面几篇有关 Serverless Component 文章的小伙伴已经体验到,它给我们开发带来的遍历。但是实际我们的日常开发项目中,并不仅仅只是单纯地一个项目部署那么简单,我们的敏捷开发流程中,还有开发、联调、测试、预发布、正式环境等关键词。那么有小伙伴就有疑惑了,我的业务开发完了,如何管理不同环境的配置呢?比如测试环境的数据库配置和正式环境的如何切换?于是抛转引入,写了此篇文章,来跟大家一起学习和探讨。

读完本篇将你将了解到:

  1. Serverless Component 部署原理
  2. dotenv 模块的基本使用
  3. 如何基于 dotenv 来切换多环境配置
  4. 如何在 serverless.yml 提炼通用配置

Serverless Component

理论指导实践

在介绍方法之前,这里需要先对 Serverless Component 部署原理做个简单介绍。当我们在 serverless.yml 文件中配置好项目,执行 sls --debug 命令后,究竟发生了什么?

核心步骤如下:

1. 初始化 context:包括分析component依赖树,通过 dotenv 注入环境变量等。
2. 安装依赖组件模块:不同于 `npm install`, serverless component 会将 `component` 指定的 npm 模块下载并解压放到 `~/.serverless/components/registry/npm/@serverless/xxx@x.x.x` 中。
3. 执行组件模块的 `default` 函数:这个 default 函数就是开发者提供的部署逻辑代码,比如将打包压缩好的代码上传到cos,然后部署到scf。

本篇只需要关心第一步的环境变量注入就好。

可以发现,Serverless Framework 部署命令默认会帮我们注入 .env 文件中的环境变量到部署流程中,这也是为什么我们在使用腾讯云的组件时,需要创建一个内容如下的 .env 文件:

TENCENT_SECRET_ID=xxx
TENCENT_SECRET_KEY=xxx

注意:当然腾讯云的组件都是支持扫码一键登录的,如果你不想配置 .env 文件。是不是很炫酷 ~

基于此,我们就可以利用 .env 文件做很多事了。 比如在 serverless.yml 中可以通过 ${env.xxx} 方式来获取注入的环境变量。

dotenv 模块

Dotenv 是一个能够通过 .env 文件将环境变量注入到 process.env 的模块。

具体使用很简单,先安装 npm install dotenv --save,然后在你的项目入口文件中引入即可:

require("dotenv").config();

管理多环境配置

说了这么多,终于到了本篇的正题。这里以 tencent-koa 组件为例,我们先初始化我们的项目:

# 此命令会将指定包含 `serverless.yml` 的 github 目录作为项目模板,拷贝到本地
$ serverless create --template-url https://github.com/yugasun/tencent-serverless-demo/tree/master/serverless-env

# 安装依赖
$ cd severless-env && npm install

然后新建两个配置文件,分别为:

# .env.test 文件内容
USER_NAME=yugasun_test
USER_EMAIL=yugasun_test@163.com

# .env.release 文件内容
USER_NAME=yugasun_release
USER_EMAIL=yugasun_release@163.com

然后新建入口文件 app.js

const dotenv = require("dotenv");
const Koa = require("koa");
const KoaRouter = require("koa-router");

const { CODE_ENV } = process.env;
dotenv.config({
  path: `${__dirname}/.env.${CODE_ENV}`
});

const app = new Koa();
const router = new KoaRouter();

router.get("/", async ctx => {
  ctx.body = {
    name: process.env.USER_NAME,
    email: process.env.USER_EMAIL
  };
});

app.use(router.allowedMethods()).use(router.routes());

// don't forget to export!
module.exports = app;

这是一个简单的 demo,通过云函数注入的环境变量 CODE_ENV,来读取不同的 .env.xxx 配置,从而实现不同环境的配置切换,核心代码就是:

const { CODE_ENV } = process.env;
dotenv.config({
  path: `${__dirname}/.env.${CODE_ENV}`
});

备注:这里 dotenv 的 config 函数是可以指定 path 为目标 .env 文件路径。

所以只需要给云函数配置一个环境变量 CODE_ENV 就可以了,接下来我们来编写 serverless.yml 文件:

# 当前运行环境
CODE_ENV: test

MyExpress:
  component: "@serverless/tencent-koa"
  inputs:
    region: ap-guangzhou
    functionName: express-function
    code: ./
    functionConf:
      timeout: 10
      memorySize: 128
      environment:
        variables:
          CODE_ENV: ${CODE_ENV}
    apigatewayConf:
      protocols:
        - http
        - https
      environment: ${CODE_ENV}

相信大家都知道可以通过 functionConf.environment.variables 来配置环境变量。 这里不仅配置了云函数的环境变量,同时也配置了 apigatewayConf.environment,以此来区分 API 网关的测试和发布环境。

小技巧:可以在 yml 文件的顶端定义公共变量 CODE_ENV,然后通过 ${CODE_ENV} 的方式引用变量。

然后执行部署命令 sls --debug,部署成功后访问创建成功的 url 链接,就可以看到配置的环境变量结果了:

{
  "name": "yugasun_test",
  "email": "yugasun_test@163.com"
}

当我们开发完,需要部署到发布环境,只需要修改 serverless.yml 中的 CODE_ENV 值为 release,然后重新部署就行。

配置优化 1

细心的小伙伴会发现:部署成功的云函数,虽然可以成功地读取不同环境配置,但是每次部署都会将 .env.test.env.release 两份配置文件同时上传。有时我们并不想暴露生产环境的配置在测试环境,因此需要每次部署时,只上传对应配置文件。要做到这一点,只需要在 serverless.yml 配置文件中新增 excludeincldue 配置:

CODE_ENV: release

MyExpress:
  component: "@serverless/tencent-koa"
  inputs:
    region: ap-guangzhou
    functionName: express-function
    code: ./
    exclude:
      - .env.release
      - .env.test
    include:
      - .env.${CODE_ENV}
    functionConf:
      timeout: 10
      memorySize: 128
      environment:
        variables:
          CODE_ENV: ${CODE_ENV}
    apigatewayConf:
      protocols:
        - http
        - https
      environment: ${CODE_ENV}

这里先通过 exclude 配置忽略所有配置文件,然后通过 include 来包含指定的配置文件。之所以这么做,是因为我们指定了 code 字段为 ./ - 项目根目录 ,因此会默认上传项目根目录的所有文件。

配置优化 2

当然也可以将 serverless.yml 中的任何固定参数写到 .env 文件中,比如这里的 CODE_ENV 变量,然后通过 ${env.CODE_ENV} 引用即可。比如我们将 CODE_ENV 写入到 .env 中:

CODE_ENV=release

然后修改 serverless.yml 配置:

MyExpress:
  component: "@serverless/tencent-koa"
  inputs:
    region: ap-guangzhou
    functionName: express-function
    code: ./
    exclude:
      - .env.release
      - .env.test
    include:
      - .env.${env.CODE_ENV}
    functionConf:
      timeout: 10
      memorySize: 128
      environment:
        variables:
          CODE_ENV: ${env.CODE_ENV}
    apigatewayConf:
      protocols:
        - http
        - https
      environment: ${env.CODE_ENV}

需要说明的是 .env 文件有个缺点,就是无法定义对象和数组。但是对于私密的配置,还是放到 .env 中比较合适,这样就可以基于文件去忽略部署。

配置优化 3

当需要将同一份业务代码部署到不同的地区,但是函数参数配置和 API 网关配置都是一致时,如何配置呢?这里一样可以提炼出通用配置,如下:

# global config
CODE_ENV: release

# function config
FUNC_CONF:
  name: express-function
  memorySize: 128
  timeout: 10
  environment:
    variables:
      CODE_ENV: ${CODE_ENV}

# apigw config
API_CONF:
  protocols:
    - http
    - https
  environment: ${CODE_ENV}

# guangzhou service
MyExpressGZ:
  component: "@serverless/tencent-koa"
  inputs:
    region: ap-guangzhou
    functionName: ${FUNC_CONF.name}
    code: ./
    exclude:
      - .env.release
      - .env.test
    include:
      - .env.${CODE_ENV}
    functionConf: ${FUNC_CONF}
    apigatewayConf: ${API_CONF}

# beijing service
MyExpressBJ:
  component: "@serverless/tencent-koa"
  inputs:
    region: ap-beijing
    functionName: ${FUNC_CONF.name}
    code: ./
    exclude:
      - .env.release
      - .env.test
    include:
      - .env.${CODE_ENV}
    functionConf: ${FUNC_CONF}
    apigatewayConf: ${API_CONF}

如何选择配置方案

本文介绍了两种方案:

  1. 通过 .env 配置
  2. 通过在 serverless.yml 中定义变量

他们都可以定义全局变量,那么在实际开发中如何去抉择使用呢?

注意:serverless.yml 定义的变量,或者 .env 中自动注入的变量,只有在执行 sls --debug 命令后,才能够获取到。实际部署成功的代码,是需要通过 dotenv 模块来指定 .env 文件来手动加载注入的。当然如果你也可以通过解析 serverless.yml 文件来获取需要的变量也是可以的。

通常我会将跟 执行部署时的配置 放到 serverless.yml 中,将 业务相关的配置 放到 .env 文件中。当然,这里只是个人建议,具体如何去配置还是要看个人使用习惯。

其他语言

虽然本文只是讲述了如何在 Nodejs 项目中管理多环境配置,但是其他语言基本都实现了 dotenv 模块,所以此方法是通用的,比如 Pythonpython-dotenv 模块,使用起来基本差不多:

# settings.py
from dotenv import load_dotenv
from pathlib import Path  # python3 only
env_path = Path('.') / '.env.test'
load_dotenv(dotenv_path=env_path)

总结

本篇涉及到所有源码都维护在开源项目 tencent-serverless-demoserverless-env

全文到这里就结束了,当然这只是本人在日常开发中总结的经验而已,如果你有更好的实践方式,欢迎一起为 Serverless 开源社区做贡献。

Serverless Framework 30 天试用计划

我们诚邀您来体验最便捷的 Serverless 开发和部署方式。在试用期内,相关联的产品及服务均提供免费资源和专业的技术支持,帮助您的业务快速、便捷地实现 Serverless!

详情可查阅:Serverless Framework 试用计划

One More Thing

3 秒你能做什么?喝一口水,看一封邮件,还是 —— 部署一个完整的 Serverless 应用?

复制链接至 PC 浏览器访问:https://serverless.cloud.tencent.com/deploy/express

3 秒极速部署,立即体验史上最快的 Serverless HTTP 实战开发!

传送门:

欢迎访问:Serverless 中文网,您可以在 最佳实践 里体验更多关于 Serverless 应用的开发!


推荐阅读:《Serverless 架构:从原理、设计到项目实战》

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Java服务总在半夜挂,背后的真相竟然是... | 京东云技术团队
最近有用户反馈测试环境Java服务总在凌晨00:00左右挂掉,用户反馈Java服务没有定时任务,也没有流量突增的情况,Jvm配置也合理,莫名其妙就挂了
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这