使用亚马逊云科技AppConfig 部署和管理应用程序配置

作者: 阿迪亚·兰詹恩 |

跨多个环境和租户的配置管理对现代软件开发构成了重大挑战。组织必须平衡维护不同环境的不同设置,同时满足多租户架构中不同租户的独特需求。对一致性、版本控制、安全性和高效故障排除的要求加剧了这种复杂性。

亚马逊云科技AppConfig 为这些挑战提供了强大的解决方案。亚马逊云科技AppConfig 集中存储、管理和部署应用程序配置。它无需频繁部署代码即可简化推送更改。该服务还支持自动回滚,为配置更改提供安全网。

当与 CI/CD 管道(例如 GitLab)集成时,亚马逊云科技AppConfig 将成为简化的自动化配置管理系统的一部分。这种组合解决了多环境和多租户部署的复杂性,确保了整个应用程序生态系统中一致、版本控制和安全的配置管理。

解决方案和场景概述

本博客中的GitLab CI/CD管道重点介绍使用亚马逊云科技AppConfig管理和部署应用程序配置的方式。通过自动化从配置更新到多环境部署的整个过程,它提供了一种简化的配置管理方法。

在此配置管理设置中,我们处理的是利用亚马逊云科技AppConfig 进行配置部署的多环境、多租户应用程序结构。

它描述了多租户配置设置,其中每个租户都有专用环境(dev 和 qa)。这些可能代表什么的真实示例:

  • 开发(dev):开发人员测试新功能和更改的地方
  • 质量保证 (qa):质量保证团队在生产前验证变更的地方

该系统支持多个租户(tenant1、tenant2),每个租户都有自己的隔离环境。在现实世界的应用程序中,这些租户可以代表:

  • 不同的客户:
    • 零售公司(tenant1)
    • 医疗保健提供者(tenant2)
  • 不同的业务部门:
    • 北美分部(租户1)
    • 欧洲、中东和非洲分部(tenant2)

每个租户为其开发和质量保证环境维护不同的配置,并附有三个示例配置文件:

  1. allowlist.yml
  2. featureflags.yml
  3. ThrottlingLimits.yml

“模板” 目录提供基本配置文件,每个租户的环境特定配置可以继承和自定义这些文件。这种分层结构确保租户可以在遵守标准化模板格式的同时保持其独特的配置。

以下是模板 YAML 文件外观的示例:

  1. allowlist.yml
# AllowList.yml

# Network Access Controls
ip_allowlists:
  internal_networks:
    - "10.0.0.0/8"     # Internal corporate network
    - "172.16.0.0/12"  # VPC network range
    - "192.168.1.0/24" # Development network

# Domain Allowlist
domain_allowlist:
  api_consumers:
    - "api.partner1.com"
    - "services.partner2.com"
    - "*.trusted-client.com"
YAML
  1. featureflags.yml
# FeatureFlags.yml

features:
  new_search:
    enabled: true
    rollout_percentage: 76
    description: "Enhanced search functionality"
    
  ai_recommendations:
    enabled: true

  chat_support:
    enabled: false
    description: "In-app chat support"
YAML
  1. ThrottlingLimits.yml
#ThrottlingLimits.yml
api_limits:
  global:
    requests_per_second: 100
    concurrent_requests: 50
    max_retry_attempts: 3

service_specific:
  user_service:
      requests_per_second: 80
      burst_limit: 100
YAML

这些模板是所有环境和租户特定配置的起点。

文件夹结构反映了跨不同环境和租户组织配置的复杂方法。

├── template
│   ├── AllowList.yml
│   ├── FeatureFlags.yml
│   └── ThrottlingLimits.yml

└── tenants
├── tenant1
│   ├── dev
│   │   ├── AllowList.yml
│   │   ├── FeatureFlags.yml
│   │   └── ThrottlingLimits.yml
│   └── qa
│       ├── AllowList.yml
│       ├── FeatureFlags.yml
│       └── ThrottlingLimits.yml
└── tenant2
├── dev
│   ├── AllowList.yml
│   ├── FeatureFlags.yml
│   └── ThrottlingLimits.yml
└── qa
├── AllowList.yml
├── FeatureFlags.yml
└── ThrottlingLimits.yml
YAML

在根级别,我们有两个主目录:

  1. 模板:包含基本配置模板
  2. 租户:包含租户特定的配置

“租户” 目录遵循分层结构,其中每个租户(tenant1、tenant2)都有自己的目录。在每个租户的目录中,有 “dev” 和 “qa” 环境子目录。每个环境目录都包含三个配置文件:allowlist.yml、FeatureFlags.yml 和 ThrottlingLimits.yml。这些文件代表应用程序配置的不同方面,可以覆盖 “模板” 目录中的基本模板。这种结构允许特定环境的配置,同时保持租户与其各自环境之间的明确分离。

这种结构允许:

  1. 通过模板实现标准化:“模板” 目录中的基本模板可确保所有租户之间的一致性,提供可以根据租户特定需求有选择地覆盖的默认配置。
  2. 租户特定的自定义:每个租户都可以在其开发和质量保证环境中维护独特的配置,同时继承基本模板。这允许在不损失标准化优势的情况下进行自定义。
  3. 环境隔离:在每个租户的目录中明确区分开发环境和 qa 环境可确保一个环境中的配置更改不会影响其他环境
  4. 配置的版本控制:通过将配置存储在 Git 存储库中,可以在必要时跟踪、审查和回滚更改。
  5. 亚马逊云科技AppConfig 集成:
    1. 每个租户在亚马逊云科技AppConfig 中都有自己的应用程序
    2. 配置文件映射到不同的配置类型(AllowList、FeatureFlags、ThrottlingLimits)
    3. 每个租户的应用程序中都有单独的环境 (dev/qa)

我们正在设置的 GitLab CI/CD 管道将需要:

  1. 根据这些模板生成环境和租户特定的配置
  2. 在亚马逊云科技AppConfig 中更新相应的应用程序和配置文件
  3. 将适当的配置部署到每个租户和环境

先决条件

  1. 使用亚马逊云科技配置 GitLab CI/CD:请参阅从 GitLab CI/CD 部署到亚马逊云科技
  2. 设置 GitLab 运行器:如果你想在 EC2 上使用 Gitlab 运行器,请参阅在 Amazon EC2 上部署和管理 Gitlab 运行器,或者你可以参考 “安装 GitLab 运行器” “配置 GitLab 运行器” 指南
  3. 在 .gitlab-ci.yml 中配置 Runner:
    • 使用标签来指定哪个运行者应该执行你的作业:
job_name:
tags:
- aws-runner  # Tag of your specific runner
YAML

设置目录结构:

  1. 首先,使用以下命令创建基本目录结构:
# Create directory structure and files
mkdir -p template tenants/{tenant1,tenant2}/{dev,qa}
Bash
  1. 创建所有必需的 YAML 文件:
for file in AllowList.yml FeatureFlags.yml ThrottlingLimits.yml;do
  touch template/$file
  touch tenants/tenant{1,2}/{dev,qa}/$file
done
Bash
  1. 填充模板文件:
Copy the content of each YAML file (AllowList.yml, FeatureFlags.yml, ThrottlingLimits.yml) shown above into the corresponding files in the template directory.
Bash
  1. 对于租户特定的配置:
Start by copying the template files to each tenant's environment directory
Bash
  1. 验证文件夹结构。

设置 GitLab CI/CD 管道

GitLab 管道的代码在此存储库中。

这个阶段首先要清楚地了解管道的结构和流程,这构成了所有后续步骤的基础。

配置 .gitlab-ci.yml

    1. 在存储库根目录中创建 .gitlab-ci.yml 文件
    2. 为管道定义基础镜像(例如,alpine: latest)
    3. 设置流水线阶段:更新应用程序配置、部署应用程序配置
    4. 配置全局变量和默认设置
      • 在下面的 .gitlab-ci.yml 文件中找到这些部分,并将其替换为您的亚马逊云科技账户详细信息
variables:
  AWS_CREDS_TARGET_ROLE: arn:aws:iam::<aws_account_ID>:role/GitLab
  AWS_DEFAULT_REGION: <aws_region>
YAML
      • 确保在管道的两个阶段(更新应用程序配置和部署应用程序配置)中替换这些变量。亚马逊云科技角色应具有与亚马逊云科技AppConfig 服务交互的相应权限

以下是完整的 .gitlab-ci.yml 文件:

stages:
  - update-app-config
  - deploy-app-config

update-app-config:
  stage: update-app-config
  image:
    name: amazon/aws-cli:latest
    entrypoint:
      - '/usr/bin/env'
  script:
    - |
      # Get list of all tenant
      TENANTS=$(find tenants -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
      
      for TENANT in $TENANTS; do
        echo "Processing tenant: $TENANT"
        
        # Create/Get Application for tenant
        APP_ID=$(aws appconfig list-applications --query "Items[?Name=='$TENANT'].Id" --output text)
        if [ -z "$APP_ID" ]; then
          echo "Creating application for tenant '$TENANT'..."
          APP_ID=$(aws appconfig create-application --name $TENANT --query Id --output text)
        fi
        
        # Process each configuration type
        for CONFIG_TYPE in AllowList FeatureFlags ThrottlingLimits; do
          echo "Processing config type: $CONFIG_TYPE"
          
          # Create/Get Configuration Profile
          PROFILE_ID=$(aws appconfig list-configuration-profiles --application-id "$APP_ID" --query "Items[?Name=='$CONFIG_TYPE'].Id" --output text)
          if [ -z "$PROFILE_ID" ]; then
            echo "Creating configuration profile '$CONFIG_TYPE' for tenant '$TENANT'..."
            PROFILE_ID=$(aws appconfig create-configuration-profile --application-id "$APP_ID" --name "$CONFIG_TYPE" --description "Configuration profile for $CONFIG_TYPE" --location-uri hosted --query Id --output text)
          fi
          
          # Process each environment
          for ENV in dev qa; do
            echo "Processing environment: $ENV"
            
            # Priority: Use tenant-specific config if it exists, otherwise use template
            if [ -f "tenants/$TENANT/$ENV/$CONFIG_TYPE.yml" ]; then
              echo "Using tenant-specific configuration for $ENV"
              CONFIG_CONTENT=$(cat "tenants/$TENANT/$ENV/$CONFIG_TYPE.yml" | base64)
            else
              echo "Using template configuration for $ENV"
              CONFIG_CONTENT=$(cat "template/$CONFIG_TYPE.yml" | base64)
            fi
            
            echo "Creating new version for $CONFIG_TYPE configuration in $ENV..."
            aws appconfig create-hosted-configuration-version \
              --application-id "$APP_ID" \
              --configuration-profile-id "$PROFILE_ID" \
              --content "$CONFIG_CONTENT" \
              --content-type "application/json" \
              configuration_version_output
          done
        done
      done
  variables:
    AWS_CREDS_TARGET_ROLE: arn:aws:iam::<aws_account_ID>:role/GitLab 
    AWS_DEFAULT_REGION: <aws_region>

deploy-app-config:
  stage: deploy-app-config
  image: 
    name: amazon/aws-cli:latest
    entrypoint: 
      - '/usr/bin/env'
  script:
    - yum install -y jq
    - |
      TENANTS=$(find tenants -mindepth 1 -maxdepth 1 -type d -exec basename {} \;)
      
      for TENANT in $TENANTS; do
        echo "Processing tenant: $TENANT"
        APP_ID=$(aws appconfig list-applications --query "Items[?Name=='$TENANT'].Id" --output text)
        
        # Process each environment
        for ENV in dev qa; do
          echo "Processing environment: $ENV"
          
          # Create/Get Environment
          ENV_ID=$(aws appconfig list-environments --application-id "$APP_ID" --query "Items[?Name=='$ENV'].Id" --output text)
          if [ -z "$ENV_ID" ]; then
            echo "Creating environment '$ENV' for tenant '$TENANT'..."
            ENV_ID=$(aws appconfig create-environment --application-id "$APP_ID" --name "$ENV" --description "Environment for $ENV" --query Id --output text)
          fi
          
          # Process each configuration types
          for CONFIG_TYPE in AllowList FeatureFlags ThrottlingLimits; do
            echo "Processing $CONFIG_TYPE for $TENANT/$ENV"
            
            PROFILE_ID=$(aws appconfig list-configuration-profiles --application-id "$APP_ID" --query "Items[?Name=='$CONFIG_TYPE'].Id" --output text)

            echo " Profile ID $PROFILE_ID "
            # Get latest version for this specific profile
            LATEST_VERSION=$(aws appconfig list-hosted-configuration-versions \
              --application-id "$APP_ID" \
              --configuration-profile-id "$PROFILE_ID" \
              --query "Items[0].VersionNumber" \
              --output text)
            
            # Get current deployment for this specific profile
            CURRENT_DEPLOYMENT=$(aws appconfig list-deployments \
            --application-id "$APP_ID" \
            --environment-id "$ENV_ID" \
            --query "Items[?ConfigurationName=='$CONFIG_TYPE'].ConfigurationVersion | [0]" \
            --output text)


            echo "Current deployment $CURRENT_DEPLOYMENT"
              
            CURRENT_VERSION=$(aws appconfig list-deployments \
            --application-id "$APP_ID" \
            --environment-id "$ENV_ID" \
            --query "Items[?ConfigurationName=='$CONFIG_TYPE'].ConfigurationVersion | [0]" \
            --output text)
            
            echo "Latest Version: $LATEST_VERSION"
            echo "Current Version: $CURRENT_VERSION"
            
            if [[ "$CURRENT_DEPLOYMENT" == "None" ]] || [[ "$LATEST_VERSION" != "$CURRENT_VERSION" ]]; then
              echo "Starting deployment for $TENANT/$ENV/$CONFIG_TYPE..."
              DEPLOYMENT_RESPONSE=$(aws appconfig start-deployment \
                --application-id "$APP_ID" \
                --environment-id "$ENV_ID" \
                --deployment-strategy-id Linear50PercentEvery30Seconds \
                --configuration-profile-id "$PROFILE_ID" \
                --configuration-version "$LATEST_VERSION")
              
              DEPLOYMENT_ID=$(echo $DEPLOYMENT_RESPONSE | jq -r '.DeploymentNumber')
              
              # Monitor deployment
              max_attempts=10
              attempt=1
              while [ $attempt -le $max_attempts ]; do
                echo "Checking deployment status (attempt $attempt of $max_attempts)..."
                status=$(aws appconfig get-deployment \
                  --application-id "$APP_ID" \
                  --environment-id "$ENV_ID" \
                  --deployment-number "$DEPLOYMENT_ID" \
                  --query "State" \
                  --output text)
                
                if [ "$status" = "COMPLETE" ]; then
                  echo "Deployment completed successfully!"
                  break
                elif [ "$status" = "FAILED" ] || [ "$status" = "ROLLED_BACK" ]; then
                  echo "Deployment failed or was rolled back!"
                  exit 1
                fi
                
                if [ $attempt -eq $max_attempts ]; then
                  echo "Deployment timed out after $max_attempts attempts"
                  exit 1
                fi
                
                attempt=$((attempt + 1))
                sleep 30
              done
            else
              echo "No changes detected for $TENANT/$ENV/$CONFIG_TYPE (Current: $CURRENT_VERSION, Latest: $LATEST_VERSION). Skipping deployment..."
            fi
          done
        done
      done
  dependencies:
    - update-app-config
  variables:
    AWS_CREDS_TARGET_ROLE: arn:aws:iam::<aws_account_ID>:role/GitLab 
    AWS_DEFAULT_REGION: <aws_region>
YAML

实现流水线阶段

  1. 更新应用程序配置阶段:

  • 创建/更新亚马逊云科技AppConfig 应用程序:
    • 为每个租户(租户 1、租户 2)创建一个应用程序
    • 使用租户 ID 作为应用程序名称
    • 检索现有应用程序(如果已经存在)
  • 管理配置文件:
    • 为每个租户应用程序创建三个配置文件(AllowList、FeatureFlags、ThrottlingLimits)
    • 每个配置文件代表一种不同的配置类型
    • 如果尚不存在,则处理配置文件创建
  • 创建托管配置版本:
    • 处理来自模板和租户目录的更改
    • 优先考虑租户特定的配置,而不是模板
    • 仅为修改后的配置创建新版本
    • 将正确编码的配置上传到亚马逊云科技AppConfig
  1. 部署应用程序配置阶段:

    • 环境部署:
      • 管理每个租户的开发和质量保证环境
      • 如果不存在则创建环境
      • 使用分阶段部署策略
    • 租户配置流程:
      • 按租户和配置类型进行部署
      • 将当前部署的版本与最新版本进行对比
      • 仅在满足以下任一条件时才进行部署:
        • 未找到现有部署
        • 最新的托管配置版本与当前部署的版本不同
      • 维护租户特定的设置和版本历史记录
      • 提供清晰的部署状态消息,包括跳过部署的情况
    • 部署管理:
      • 执行亚马逊云科技AppConfig 部署
      • 监控部署状态
      • 处理故障和回滚
      • 重试 10 次后超时

执行管道

  1. 启动:
    • 管道由推送到存储库的更改触发
  1. 更新应用程序配置阶段:
    • 创建或更新应用程序和配置文件
    • 生成托管配置的新版本
  1. 部署应用程序配置阶段:
    • 迭代每个环境租户及其环境
    • 检查每个环境和租户的当前部署状态
    • 仅针对更改的配置启动新部署
    • 实施指定的亚马逊云科技AppConfig 部署策略

注意:本示例中使用的部署策略是用于测试的快速部署策略(每 30 秒线性 50%),但对于实际生产工作负载,读者应使用亚马逊云科技推荐的速度较慢的 Linear20PercentEver6Minutes 策略。更多细节在这里

这种结构化执行流程可确保在整个应用程序生态系统中高效、一致地部署配置变更,保持 GitLab 和亚马逊云科技AppConfig 之间的同步。

正在清理

要清理此解决方案创建的所有亚马逊云科技AppConfig 资源,您可以使用以下清理脚本。使用以下内容创建名为 delete_appconfig_resources.sh 的文件:

#!/bin/bash

# List all applications
APPS=$(aws appconfig list-applications --query 'Items[*].Id' --output text)

for APP_ID in $APPS
do
  echo "Processing application $APP_ID"
  
  # List and delete all environments for this application
  ENVS=$(aws appconfig list-environments --application-id $APP_ID --query 'Items[*].Id' --output text)
  for ENV_ID in $ENVS
  do
    echo "  Deleting environment $ENV_ID"
    aws appconfig delete-environment --application-id $APP_ID --environment-id $ENV_ID
  done

  # List and delete all configuration profiles for this application
  PROFILES=$(aws appconfig list-configuration-profiles --application-id $APP_ID --query 'Items[*].Id' --output text)
  for PROFILE_ID in $PROFILES
  do
    echo "  Deleting configuration profile $PROFILE_ID"
    
    # Delete all hosted configuration versions for this profile
    VERSIONS=$(aws appconfig list-hosted-configuration-versions --application-id $APP_ID --configuration-profile-id $PROFILE_ID --query 'Items[*].VersionNumber' --output text)
    for VERSION in $VERSIONS
    do
      echo "    Deleting hosted configuration version $VERSION"
      aws appconfig delete-hosted-configuration-version --application-id $APP_ID --configuration-profile-id $PROFILE_ID --version-number $VERSION
    done

    # Delete the configuration profile
    aws appconfig delete-configuration-profile --application-id $APP_ID --configuration-profile-id $PROFILE_ID
  done

  # Delete the application
  echo "  Deleting application $APP_ID"
  aws appconfig delete-application --application-id $APP_ID
done

echo "All AppConfig resources have been deleted."
Bash


该脚本是亚马逊云科技AppConfig 资源的综合清理工具。

要执行此脚本,您需要安装亚马逊云科技CLI,并使用有权删除 AppConfig 资源的相应证书进行配置。通过运行以下命令使 delete_appconfig_resources.sh 脚本可执行:

chmod +x cleanup_appconfig.sh.
Bash

在运行脚本之前,请确保您位于正确的亚马逊云科技账户和区域,因为此脚本将删除配置的账户和区域中的所有 AppConfig 资源。要执行脚本,只需从终端运行它即可:./ delete_appconfig_resources.sh

请务必注意,此脚本执行不可逆的删除。请格外谨慎地使用它,最好是在非生产环境中,或者当你绝对确定要移除所有 AppConfig 资源时。

结论

这篇博客文章探讨了GitLab CI/CD和亚马逊云科技AppConfig之间在管理多租户环境中的应用程序配置方面的强大协同作用。我们已经演示了这种集成如何自动化和简化更新、版本控制和部署配置变更的流程,从而提供可扩展性、版本控制以及一致性和灵活性之间的平衡等好处。通过采用这种方法,开发团队可以显著减少手动错误,节省时间,并将更多精力集中在构建功能上,从而在我们日益复杂的分布式计算环境中缩短开发周期并提高应用程序的可靠性。

进一步阅读的关键资源:

  • 亚马逊云科技AppConfig 概述视频
  • 使用亚马逊云科技AppConfig 博客解锁更快的版本
  • 亚马逊云科技AppConfig研讨会

作者简介

阿迪亚·兰詹恩

阿迪亚·兰詹是亚马逊网络服务的首席顾问。他帮助客户使用亚马逊云科技的最新技术(包括生成式 AI 服务)设计和实施架构完善的技术解决方案,使他们能够实现其业务目标和目的。


*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您发展海外业务和/或了解行业前沿技术选择推荐该服务。