Multi Environment Code Recipe (CN)#

示例代码的背景信息请参考 Multi Environment Deployment (CN)

Declare Environment Enumeration#

aws_ops_alpha 项目提供了一个抽象层, 用户可以使用任何自己喜欢的策略.

我推荐在你的项目代码中创建一个 multi_env.py 模块 (模块的名称不重要, 有这个模块就行), 用于定义你项目所使用的环境的枚举. 当然, 你需要遵循限制, 至少定义 devops, sbx, prd 三个环境. 在这个例子中, 我们除了必须的三个环境, 还定义了 tst, stg 两个环境. 示例代码如下.

[1]:
# content of multi_env.py
import aws_ops_alpha.api as aws_ops_alpha

class EnvNameEnum(aws_ops_alpha.BaseEnvNameEnum):
    """
    Environment enumeration for your project.
    """

    devops = aws_ops_alpha.CommonEnvNameEnum.devops.value # or you can just do: devops = "devops"
    sbx = aws_ops_alpha.CommonEnvNameEnum.sbx.value
    tst = aws_ops_alpha.CommonEnvNameEnum.tst.value
    stg = aws_ops_alpha.CommonEnvNameEnum.stg.value
    prd = aws_ops_alpha.CommonEnvNameEnum.prd.value

然后, 你就可以用这个 EnvNameEnum 类做一些 “判断”, “循环” 等操作了.

[3]:
# if test
current_env_name = "sbx"
print(current_env_name == EnvNameEnum.sbx)
print(current_env_name == EnvNameEnum.sbx.value)
True
True
[4]:
# for loop, deploy something to all environment
for env_name in EnvNameEnum:
    if env_name != EnvNameEnum.devops:
        print(f"deploy to {env_name} environment ...")
deploy to sbx environment ...
deploy to tst environment ...
deploy to stg environment ...
deploy to prd environment ...

Detect the Current Environment Name#

在 “Multi Environment Deployment (CN)” 一文中我们介绍了我们所推荐的获得当前 Environment Name 的方法. 诚然, 我们可以按照介绍的方法用 current_env_name = os.envrion["ENV_NAME"] 的方式获得 current_env_name, 但实际项目中的情况往往比这个复杂. 例如:

  1. 在本地的时候, 难道你要每次在运行代码之前, 手动在 Terminal 中输入 export ENV_NAME=sbx 来切换环境吗? 万一一个不小心切到了 prd 忘记切换回来怎么办?

  2. 在 CI 环境中, 你在运行一个 Job 的过程中可能要依次跟多个环境打交道, 但一个 Job 的 ENV_NAME 环境变量只能有一个值, 你如何能确保执行到某个代码片段的时候能确保 current_env_name 正确呢?

此外还有很多细微的地方可能出 bug. 为了避免在代码库中导出出现判断当前 env_name 的逻辑, 我们应该将这个判断过程封装成一个函数, 也可以将这个函数的返回值保存为一个可以被反复引用的变量作为缓存. 举例来说, 在 local 本地开发 runtime 下, 如果没有特殊指定, 我们默认使用 sandbox 环境进行开发, 所以这个函数永远返回 sbx. 而在 CI runtime 下, 我们可以用 USER_ENV_NAME + ENV_NAME 两个环境变量来指定该用哪个环境 (请参考 “Multi Environment Deployment (CN)” 一文中的介绍), 而在 App 的 runtime 下, 例如 EC2 可以用本地的配置文件来读取环境, Lambda Function 可以用 environment variables 来读. 总之在特定项目中是必然有一套约定的, 所以我们只要将这个约定用 detect_current_env 函数实现了即可. 这样避免了重复的条件判断逻辑在代码库中到处都是, 大大提高了代码的可读性和可维护性.

本项目的 detect_current_env() 函数实现了一套本项目认为最优的一套约定. 我推荐你直接使用这一函数获得 current_env_name, 并遵循这一套约定. 如果你不想遵循这一套约定, 你可以在 multi_env.py 模块中也实现一个自己的 detect_current_env 函数.

Note

本项目认为的最优的约定的详细说明如下

  1. 如果是 local runtime, 我们默认使用 sbx, 但是用户可以通过设定 USER_ENV_NAME 环境变量来覆盖它.

  2. 如果是 ci runtime 或是 app runtime, 我们先去找 USER_ENV_NAME, 如果没找到, 我们再去找 ENV_NAME. 这个设计是让我们将默认环境放在 ENV_NAME 中, 但同时又提供了 USER_ENV_NAME 可以让我们强行指定, 提高了灵活性.

最终你的 multi_env.py 代码看起来应该像这样.

[ ]:
# content of multi_env.py
import aws_ops_alpha.api as aws_ops_alpha
# import your runtime module
from .your_project_runtime_module import runtime


class EnvNameEnum(aws_ops_alpha.BaseEnvNameEnum):
    """
    Environment enumeration for your project.
    """

    devops = aws_ops_alpha.CommonEnvNameEnum.devops.value # or you can just do: devops = "devops"
    sbx = aws_ops_alpha.CommonEnvNameEnum.sbx.value
    tst = aws_ops_alpha.CommonEnvNameEnum.tst.value
    stg = aws_ops_alpha.CommonEnvNameEnum.stg.value
    prd = aws_ops_alpha.CommonEnvNameEnum.prd.value


def detect_current_env() -> str:
    # ----------------------------------------------------------------------
    # you can uncomment this line to force to use certain env
    # from your local laptop to run application code, tests, ...
    # ----------------------------------------------------------------------
    # return EnvNameEnum.prd.value

    # use the aws_ops_alpha recommended setup
    return aws_ops_alpha.detect_current_env(runtime, EnvNameEnum)

Environment Aware App Logic#

好了, 我们现在有了 multi_env.py 模块. 那么在其他的业务逻辑代码中要如何使用这个模块呢?

我们来看下面这个 “用于读取 config 数据” 的业务逻辑的例子. 我们的代码可能运行在 sbx, prd 任何一个环境下. 我们希望 sbx 只读 sbx 的 config, prd 只读 prd 的 config, 不能混淆了. 在下面的示例代码中, 我们先用 detect_current_env() 函数获得当前的环境名称, 然后根据环境名称找到对应的 AWS Parameter Store 并读取 Config. 在实际项目中, 很多业务逻辑都需要知道当前的 env_name. 例如需要根据 env_name 跟位于同一个环境下的其他系统通信. 我详细看了这个例子之后你可以举一反三, 写出例如 def call_api_in_another_project(env_name: str): 这样的函数了.

[ ]:
import typing as T
from path.to.multi_env import EnvNameEnum, detect_current_env

def load_config(
    env_name: T.Optional[str] = None,
):
    if env_name is None:
        env_name = detect_current_env()
    # then load config data of the given environment name
    param_name = env_name
    response = boto3.client("ssm").get_parameter(Parameter=param_name)
    ...
[ ]: