搭配使用 Launch Darkly 功能标记与 Bitbucket pipelines
Warren Marusiak
高级技术传播者
将新代码部署到生产环境是有风险的。即使在测试和暂存环境中对代码进行了单元测试、集成测试和系统测试之后,缺陷仍可能给生产带来风险。过去,一旦缺陷给生产带来风险,开发人员有两种选择,用户会受到影响。他们可以回滚缺陷的代码,或者向前推进修复;这两种解决方案都需要时间。现在,开发人员通过将相关的代码变更打包在功能标记中,只需点击一个按钮,即可在环境中开启或关闭功能。缺陷代码对用户的影响可以立即得到缓解,并且可以安全地开发和向前推进修复。本文使用 Bitbucket pipelines 和 ImageLabeller 演示应用中的 Launch Darkly 功能标记对此进行了演示。
先决条件
ImageLabeller 功能标记演示
ImageLabeller 是一款使用机器学习来标记图像的小应用。ImageLabeller 部署到五个环境:测试、暂存、Production-us-west-2、Production-us-east-1 和 Production-ca-central-1。本文演示了如何使用功能标记来管理对 ImageLabeller 的 SubmitImage 组件的变更。SubmitImage 是用 Go 编写的 AWS Lambda。您将使用 Launch Darkly 来管理功能标记,使用 Bitbucket 来管理源控制,使用 Bitbucket pipelines 来管理 CI/CD 功能。
如何搭配使用 Launch Darkly 功能标记与 Bitbucket pipelines
让您的本地 Launch Darkly 管理员创建项目和环境。在下面的屏幕截图中有一个项目 PMMImageLabellerDemo,有五个环境。测试和暂存是预生产环境。记下每个环境的 SDK 密钥。稍后,SDK 密钥将作为存储库变量添加到 Bitbucket 中。
在此示例中,当代码提交到功能分支时,Bitbucket pipelines 会部署到这些环境中。Production-us-west-2、Production-us-east-1 和 Production-ca-central-1 是与 AWS 环境对应的生产环境。当代码通过拉取请求从功能分支合并到主线中时,Bitbucket pipelines 就会部署到这些环境中。
在 Launch Darkly 中,为项目创建功能标记。选择测试环境,然后调整功能标记设置。在下面的屏幕截图中,在测试区域中,功能标记默认设置为返回“true”。如果特定用户 AtlassianTestUser@atlassian.com 提出请求,则功能标记将返回“false”。这样,特定的指定用户,例如系统测试套装中的测试用户,可以使代码以一种方式执行,而同一环境中的普通用户则可以使代码以不同的方式执行。
此行为可以根据每个环境进行调整。功能标记允许开发人员将新代码部署到所有区域,同时仅允许代码在特定环境中执行。在本演示情况下,该标记在暂存和所有三个生产环境中都设置为返回“false”。新代码只能在测试环境中执行。
从 Launch Darkly 中获取每个环境的 SDK 密钥。然后,转到 Bitbucket,将存储库变量添加到每个将使用此标记的存储库中。下面的屏幕截图显示添加了五个存储库变量。ld_test_env 包含用于测试环境的 Launch Darkly SDK 密钥。ld_staging_env 包含用于暂存环境的 Launch Darkly SDK 密钥。这些存储库变量稍后作为存储库的 bitbucket-pipelines.yml 文件中的引用。
将 SDK 密钥添加为存储库变量后,SDK 密钥值可以作为存储库的 bitbucket-pipeline.yml 文件中的引用。在下面的代码段中,STACK_PARAMETERS 已添加到 production-ca-central-1 的部署步骤中。STACK_PARAMETERS 将相应的 SDK 密钥的值作为参数发送给 AWS CloudFormation template.yml 文件。
- pipe: atlassian/aws-sam-deploy:1.2.0
variables:
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
AWS_DEFAULT_REGION: 'ca-central-1'
STACK_NAME: 'OpenDevOpsSubmitImage'
CAPABILITIES: [ 'CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND' ]
TEMPLATE: 'https://s3.amazonaws.com/open-devops-code-ca-central-1-${AWS_ACCOUNT_ID}/submit-image-packaged.yml'
WAIT: 'true'
DEBUG: 'true'
S3_BUCKET: 'open-devops-code-ca-central-1-${AWS_ACCOUNT_ID}'
SAM_TEMPLATE: 'build/template.yaml'
STACK_PARAMETERS: '[{
"ParameterKey": "LaunchDarklySDKKey",
"ParameterValue": "${ld_prod_cac1_env}"
}]'
将带字符串类型 LaunchDarklySDKKey 的“参数”部分添加到存储库的 template.yml 文件的“参数”部分中。此参数接收 bitbucket-pipelines.yml 文件中设置的 LaunchDarklySDKKey STACK_PARAMETER 的值。
Parameters:
LaunchDarklySDKKey:
Type: String
还要在 template.yml 文件中更新 SubmitImage 功能的 AWS Lambda 资源。将 LaunchDarklySDKKey 添加为环境变量。
Resources:
SubmitImageFunction:
Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
Properties:
CodeUri: submitImage/
Handler: submit-image
Runtime: go1.x
Tracing: Active # https://docs.aws.amazon.com/lambda/latest/dg/lambda-x-ray.html
Policies:
- AmazonDynamoDBFullAccess
- AmazonS3FullAccess
Events:
CatchAll:
Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
Properties:
Path: /submit-image
Method: GET
Environment:
Variables:
LaunchDarklySDKKey:
Ref: LaunchDarklySDKKey
在 Bitbucket pipelines 部署到环境中后,LaunchDarklySDKKey 环境变量将在 AWS Lambda 控制台中可见。此密钥的值对环境来说都是独有的。例如,测试中的 LaunchDarklySDKKey 环境变量将与 Production-us-west-2 中的环境变量不同。
SubmitImage 是用 Go 编写的 AWS Lambda。如需搭配使用 Launch Darkly 与 Go,请导入以下依赖关系。
"gopkg.in/launchdarkly/go-sdk-common.v2/lduser"
ld "gopkg.in/launchdarkly/go-server-sdk.v5"
添加一个函数,用于在运行时从 Launch Darkly 中检索功能标记值。
func getLaunchDarklyFlags(username string) (bool, error) {
client, _ := ld.MakeClient(os.Getenv("LaunchDarklySDKKey"), 5 * time.Second)
flagKey := "SubmitImageDemoFeature"
userUuid, uuidErr := uuid.NewRandom()
if uuidErr != nil {
return false, uuidErr
}
var user lduser.User
if(username == "") {
user = lduser.NewAnonymousUser(userUuid.String())
} else {
user = lduser.NewUser(username)
}
showFeature, _ := client.BoolVariation(flagKey, user, false)
if showFeature {
return true, nil
} else {
return false, nil
}
}
使用空字符串调用该函数以获取默认标记值,或使用用户电子邮件调用该函数以获取目标值。对于匿名用户,上面显示的设置应拉取 true,对于 AtlasianTestUser@atlassian.com 用户应拉取 false。
flagVal, flagErr := getLaunchDarklyFlags("")
if flagErr != nil {
return "", flagErr
}
fmt.Println("DEMO flagVal for anonymous user: ", flagVal)
flagVal, flagErr = getLaunchDarklyFlags("AtlassianTestUser@atlassian.com")
if flagErr != nil {
return "", flagErr
}
fmt.Println("DEMO flagVal for AtlassianTestUser@atlassian.com: ", flagVal)
运行代码后转到 AWS CloudWatch 日志,验证提取了正确的标记值。
转到“管理员设置”,然后转到“API 密钥”以获取每个环境的 API 密钥列表。在代码中的 API 调用期间,这些 api 密钥会被发送回 split,以获得正确的 split 版本。本指南使用每个环境的服务器端密钥。
转到您的 Bitbucket 存储库,再转到“存储库设置”,然后转到“存储库变量”,为每个 API 密钥添加变量。
编辑 bitbucket-pipelines.yml 文件,然后将 STACK_PARAMETERS 添加到 AWS SAM 部署步骤中。这是根据每个环境完成的。下面的 YAML 代码段显示了 AWS US-WEST-1 中“测试”区域的部署步骤。因此,该步骤引用了上面设置的 split_test_env 存储库变量。为每个环境使用相应的存储库变量。
- pipe: atlassian/aws-sam-deploy:1.2.0
variables:
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
AWS_DEFAULT_REGION: 'us-west-1'
STACK_NAME: 'OpenDevOpsSubmitImage'
CAPABILITIES: [ 'CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND' ]
TEMPLATE: 'https://s3.amazonaws.com/open-devops-code-us-west-1-${AWS_ACCOUNT_ID}/submit-image-packaged.yml'
WAIT: 'true'
DEBUG: 'true'
S3_BUCKET: 'open-devops-code-us-west-1-${AWS_ACCOUNT_ID}'
SAM_TEMPLATE: 'build/template.yaml'
STACK_PARAMETERS: '[{
"ParameterKey": "SplitIOSDKKey",
"ParameterValue": "${split_test_env}"
}]'
编辑 AWS CloudFormation template.yml 文件并添加引用 Split SDK 密钥的“参数”部分。
Parameters:
SplitIOSDKKey:
Type: String
在 template.yml 文件中,向需要访问 Split 的每个 AWS Lambda 资源添加一个“环境”部分。本指南演示
Environment:
Variables:
SplitIOSDKKey:
Ref: SplitIOSDKKey
将以下依赖关系导入将使用 Split SDK 的 Go 文件中。
"github.com/splitio/go-client/v6/splitio/client"
"github.com/splitio/go-client/v6/splitio/conf"
此函数创建客户端,并检索 Split UI 中创建的“SubmitImageDemoSplit”的功能标记值。它取一个参数“用户名”。
func getSplitIOFlag(username string) (string, error) {
splitIOSDKKey := os.Getenv("SplitIOSDKKey")
cfg := conf.Default()
factory, err := client.NewSplitFactory(splitIOSDKKey, cfg)
if err != nil {
fmt.Printf("SDK init error: %s\n", err)
return "", err
}
splitClient := factory.Client()
err = splitClient.BlockUntilReady(10)
if err != nil {
fmt.Printf("SDK timeout: %s\n", err)
return "", err
}
treatment := splitClient.Treatment(username, "SubmitImageDemoSplit", nil)
fmt.Printf("SPLIT_DEMO treatment is %s, username is %s\n", treatment, username)
return treatment, nil
}
使用电子邮件地址调用该函数。在这种情况下,someRandomUser@atlassian.com 将拉取功能标记的默认值,因为它不是与功能标记关联的允许列表的成员。AtlassianTestUser@atlassian.com 将拉取与它所在的允许列表关联的功能标记值。
在执行了代码后,查看 AWS CloudWatch 日志中的输出。请注意,当 someRandomUser@atlassian.com 访问功能标记时,它会返回“关闭”,当 AtlassianTestUser@atlassian.com 访问功能标记时,它会返回“打开”。
总之......
Launch Darkly 功能标记可轻松集成到通过 Bitbucket pipelines 部署的应用中。功能标记使开发人员能够控制已部署代码的执行。这可以更快响应缺陷部署,减少对用户的影响。用些时间启动 Bitbucket 和 Launch Darkly 的实例,然后测试您的团队的能力。
分享这篇文章
下一个主题
推荐阅读
将这些资源加入书签,以了解 DevOps 团队的类型,或获取 Atlassian 关于 DevOps 的持续更新。